[
  {
    "path": ".clang-format",
    "content": "---\nBasedOnStyle: Google\nSortIncludes: false\nUseTab: Never"
  },
  {
    "path": ".gitattributes",
    "content": "###############################################################################\n# Set default behavior to automatically normalize line endings.\n###############################################################################\n* text=auto\n\n###############################################################################\n# Set default behavior for command prompt diff.\n#\n# This is need for earlier builds of msysgit that does not have it on by\n# default for csharp files.\n# Note: This is only used by command line\n###############################################################################\n#*.cs     diff=csharp\n\n###############################################################################\n# Set the merge driver for project and solution files\n#\n# Merging from the command prompt will add diff markers to the files if there\n# are conflicts (Merging from VS is not affected by the settings below, in VS\n# the diff markers are never inserted). Diff markers may cause the following \n# file extensions to fail to load in VS. An alternative would be to treat\n# these files as binary and thus will always conflict and require user\n# intervention with every merge. To do so, just uncomment the entries below\n###############################################################################\n#*.sln       merge=binary\n#*.csproj    merge=binary\n#*.vbproj    merge=binary\n#*.vcxproj   merge=binary\n#*.vcproj    merge=binary\n#*.dbproj    merge=binary\n#*.fsproj    merge=binary\n#*.lsproj    merge=binary\n#*.wixproj   merge=binary\n#*.modelproj merge=binary\n#*.sqlproj   merge=binary\n#*.wwaproj   merge=binary\n\n###############################################################################\n# behavior for image files\n#\n# image files are treated as binary by default.\n###############################################################################\n#*.jpg   binary\n#*.png   binary\n#*.gif   binary\n\n###############################################################################\n# diff behavior for common document formats\n# \n# Convert binary document formats to text before diffing them. This feature\n# is only available from the command line. Turn it on by uncommenting the \n# entries below.\n###############################################################################\n#*.doc   diff=astextplain\n#*.DOC   diff=astextplain\n#*.docx  diff=astextplain\n#*.DOCX  diff=astextplain\n#*.dot   diff=astextplain\n#*.DOT   diff=astextplain\n#*.pdf   diff=astextplain\n#*.PDF   diff=astextplain\n#*.rtf   diff=astextplain\n#*.RTF   diff=astextplain\n"
  },
  {
    "path": ".github/workflows/docker-switch.yml",
    "content": "name: Docker Image CI for GHCR\non: \n  push:\n    paths:\n      - docker/impacto-switch/*\n      - .github/workflows/docker-switch.yml\n  workflow_dispatch:\n\njobs:\n  build_and_publish:\n    permissions:\n      contents: read\n      packages: write\n    runs-on: ubuntu-latest\n    steps: \n      - uses: actions/checkout@v6\n      - name: Log in to the Container registry\n        uses: docker/login-action@v2\n        with:\n          registry: ghcr.io\n          username: ${{ github.actor }}\n          password: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: Extract metadata (tags, labels) for Docker\n        id: meta\n        uses: docker/metadata-action@v4\n        with:\n          images: ghcr.io/committeeofzero/impacto-switch\n\n      - name: Build and push Docker image\n        uses: docker/build-push-action@v4\n        with:\n          push: true\n          context: docker/impacto-switch\n          file: docker/impacto-switch/Dockerfile\n          tags: ${{ steps.meta.outputs.tags }}\n          labels: ${{ steps.meta.outputs.labels }}"
  },
  {
    "path": ".github/workflows/impacto.yml",
    "content": "name: impacto\non:\n  push:\n    branches:\n    - \"master\"\n    tags:\n    - \"*\"\n  pull_request:\n    branches:\n    - \"master\"\n  workflow_dispatch:\n\nenv:\n  BRANCH_NAME: ${{ github.head_ref || github.ref_name }} \n\njobs:\n  get-version:\n    name: Get version\n    runs-on: ubuntu-24.04\n    permissions:\n      contents: read\n    outputs:\n      VERSION: ${{ steps.set-version.outputs.VERSION }}\n    steps:\n    - uses: actions/checkout@v6\n    - name: Set version\n      id: set-version\n      run: echo \"VERSION=$(cat VERSION)\" >> $GITHUB_OUTPUT\n    - name: Upload VERSION Artifact\n      uses: actions/upload-artifact@v7\n      with:\n        path: VERSION\n        name: VERSION\n        archive: false\n  lint:\n    name: Run linters\n    permissions:\n      checks: write\n      contents: write\n    runs-on: ubuntu-24.04\n    outputs:\n      commitid: ${{ steps.commit.outputs.commitid }}\n\n    steps:\n      - name: Check out Git repository\n        uses: actions/checkout@v6\n      - name: Update clang-format\n        run: |\n          sudo apt update\n          sudo pipx install clang-format\n          which clang-format\n          clang-format --version\n\n      - name: Run linters\n        uses: wearerequired/lint-action@v2\n        with:\n          auto_fix: ${{(github.event.pull_request.head.repo.full_name != 'CommitteeOfZero/impacto') && 'false' || 'true'}}\n          clang_format: true\n          clang_format_auto_fix: ${{(github.event.pull_request.head.repo.full_name != 'CommitteeOfZero/impacto') && 'false' || 'true'}}\n          continue_on_error: false\n      - name: Get latest commit\n        id: commit\n        run: |\n          SHA=$(git rev-parse HEAD)\n          echo \"commitid=$SHA\" >> $GITHUB_OUTPUT\n  job-matrix:\n    name: ${{ matrix.os_name }}\n    runs-on: ${{ matrix.os }}\n    timeout-minutes: 120\n    needs: \n      - lint\n      - get-version\n    permissions:\n      contents: read\n      packages: write\n    strategy:\n      fail-fast: false\n      matrix:\n        include:\n          - os: windows-2025\n            os_name: windows\n            triplet: x64-windows-release\n            host_triplet: x64-windows-release\n          - os: ubuntu-24.04\n            os_name: linux\n            triplet: x64-linux-ci\n            host_triplet: x64-linux-ci\n          - os: macos-15\n            os_name: macos_arm64\n            triplet: arm64-osx-ci\n            host_triplet: arm64-osx-ci\n          - os: macos-15-intel\n            os_name: macos_x64\n            triplet: x64-osx-ci\n            host_triplet: x64-osx-ci\n          - os: ubuntu-24.04\n            os_name: android\n            triplet: arm64-android-ci\n            host_triplet: x64-linux-ci\n    env:\n      VCPKG_DEFAULT_TRIPLET: ${{ matrix.triplet }}\n      VCPKG_BINARY_SOURCES: >-\n        ${{ github.event_name == 'pull_request_target' &&\n            (github.event.pull_request.head.repo.full_name != github.repository)\n            && 'clear;nuget,https://nuget.pkg.github.com/committeeofzero/index.json,read'\n            || 'clear;nuget,https://nuget.pkg.github.com/committeeofzero/index.json,readwrite' }}\n      vcpkgCommitId: '62159a45e18f3a9ac0548628dcaf74fcb60c6ff9'\n      PresetName: ${{ (matrix.os_name == 'android') && 'ci-release-android' || 'ci-release' }}\n      VERSION: ${{ needs.get-version.outputs.VERSION }}\n      VCPKG_FORCE_DOWNLOADED_BINARIES: 1\n\n    steps:\n    - uses: actions/checkout@v6\n      with:\n        ref: ${{ needs.lint.outputs.commitid }}\n\n    - name: Setup Android Environment\n      if: matrix.os_name == 'android'\n      run: |\n        export ABI=arm64-v8a\n        export MINSDKVERSION=28\n        echo \"MINSDKVERSION=$MINSDKVERSION\" >> $GITHUB_ENV\n        echo \"ABI=$ABI\" >> $GITHUB_ENV\n        echo \"CC=$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin/clang --target=aarch64-linux-android$MINSDKVERSION -fPIC\" >> $GITHUB_ENV\n        echo \"VULKAN_SDK=$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr\" >> $GITHUB_ENV\n      shell: bash\n\n    - name: Set up Java\n      uses: actions/setup-java@v4\n      if: matrix.os_name == 'android'\n      with:\n        java-version: 17\n        distribution: \"temurin\"\n        cache: 'gradle'\n\n    - uses: lukka/get-cmake@latest\n\n    - name: Setup vcpkg\n      uses: lukka/run-vcpkg@v11\n      id: runvcpkg\n      with:\n        vcpkgDirectory: '${{ runner.workspace }}/build/vcpkg'\n        vcpkgGitCommitId: '${{ env.vcpkgCommitId }}'\n        vcpkgJsonGlob: '**/vcpkg.json'\n\n    - name: Install Dependencies Linux\n      run: |\n        sudo apt-get update\n        sudo apt-get install nasm libx11-dev libxft-dev libxext-dev libwayland-dev libxkbcommon-dev libegl1-mesa-dev libibus-1.0-dev libxrandr-dev libltdl-dev mono-complete autoconf autoconf-archive automake libtool libdrm-dev\n      if: matrix.os_name == 'linux' || matrix.os_name == 'android'\n    - name: Install Dependencies Mac\n      run: brew install nasm mono\n      if: contains(matrix.os_name, 'macos')\n\n    - name: Configure NuGet feed via vcpkg\n      shell: bash\n      env:\n        FEED_URL: 'https://nuget.pkg.github.com/committeeofzero/index.json'\n      run: |\n        NUGET_PATH=$(vcpkg fetch nuget | tail -n 1)\n\n        if [[ \"$NUGET_PATH\" == *.exe && \"$RUNNER_OS\" != \"Windows\" ]]; then\n          EXEC=(mono \"$NUGET_PATH\")\n        else\n          EXEC=(\"$NUGET_PATH\")\n        fi\n\n        \"${EXEC[@]}\" sources add \\\n          -Source \"${{ env.FEED_URL }}\" \\\n          -StorePasswordInClearText \\\n          -Name GitHubPackages \\\n          -UserName \"GithubCI\" \\\n          -Password \"${{ github.token }}\"\n\n        \"${EXEC[@]}\" setapikey \"${{ github.token }}\" \\\n          -Source \"${{ env.FEED_URL }}\"\n\n    - name: Run CMake with vcpkg.json manifest\n      uses: lukka/run-cmake@v10\n      with:\n        cmakeListsTxtPath: '${{ github.workspace }}/CMakeLists.txt'\n        configurePreset: ${{ env.PresetName }}\n        configurePresetAdditionalArgs: >-\n          [\n            '-DVCPKG_TARGET_TRIPLET=${{ matrix.triplet }}',\n            '-DVCPKG_HOST_TRIPLET=${{ matrix.host_triplet }}',\n          ]\n        buildPreset: ${{ env.PresetName }}\n    - name: Copy docs\n      run: |\n        mkdir -p \"${{ github.workspace }}/release/${{ env.PresetName }}\"\n        cp THIRDPARTY.md README.md LICENSE \"${{ github.workspace }}/release/${{ env.PresetName }}\"\n      shell: bash\n    - name: Decode signing/prod.properties file\n      if: matrix.os_name == 'android' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository)\n      env:\n        PROD_PROPERTIES: ${{ secrets.ANDROID_PROD_PROPERTIES }}\n        PROD_JKS: ${{ secrets.ANDROID_PROD_JKS }}\n      run: |\n        echo \"$PROD_PROPERTIES\" > ${{ github.workspace }}/android/app/signing/prod.properties\n        echo \"$PROD_JKS\" | base64 -d > ${{ github.workspace }}/android/app/signing/prod.jks\n    - name: Build Android\n      if: matrix.os_name == 'android'\n      run: |\n        pushd \"${{ github.workspace }}/android/\"\n        ./gradlew assemble\n        cp \"${{ github.workspace }}/android/distribution/android/app/outputs/apk/release/app-release.apk\" \"${{ github.workspace }}/release/${{ env.PresetName }}/impacto-release.apk\"\n      shell: bash\n    - name: Archive Artifacts\n      run: |\n        mv ./release/${{ env.PresetName }} ./release/impacto\n        7z a -tzip -mm=Deflate -mx=9 impacto-${{ matrix.os_name }}-${{ env.VERSION }}${{ github.run_number }}-${{ github.sha }}.zip ./release/impacto\n    - name: Upload Artifact\n      uses: actions/upload-artifact@v7\n      with:\n        path: impacto-${{ matrix.os_name }}-${{ env.VERSION }}${{ github.run_number }}-${{ github.sha }}.zip\n        archive: false\n\n  switch:\n    runs-on: ubuntu-latest\n    timeout-minutes: 120\n    needs:\n      - lint\n      - get-version\n    permissions:\n      contents: read\n    container:\n      image: ghcr.io/committeeofzero/impacto-switch:latest\n    env:\n      VERSION: ${{ needs.get-version.outputs.VERSION }}\n    steps:\n      - uses: actions/checkout@v6\n        with:\n          ref: ${{ needs.lint.outputs.commitid }}\n      - name: Run build\n        run: |-\n          cmake . -DCMAKE_TOOLCHAIN_FILE=HorizonNX.toolchain\n          make -j2\n          elf2nro impacto impacto.nro\n          mkdir -p \"release/ci-release\"\n          cp impacto.nro \"release/ci-release\"\n          cp -r profiles \"release/ci-release\"\n          cp -r games \"release/ci-release\"\n      - name: Copy Shaders\n        run: cp -r src/shaders \"release/ci-release\"\n        shell: bash\n      - name: Copy docs\n        run: |\n          cp THIRDPARTY.md README.md LICENSE \"release/ci-release\"\n        shell: bash\n      - name: Archive Artifacts\n        run: |\n          mv ./release/ci-release ./release/impacto\n          7z a -tzip -mm=Deflate -mx=9 impacto-switch-${{ env.VERSION }}${{ github.run_number }}-${{ github.sha }}.zip ./release/impacto\n      - name: Upload Artifact\n        uses: actions/upload-artifact@v7\n        with:\n          path: impacto-switch-${{ env.VERSION }}${{ github.run_number }}-${{ github.sha }}.zip\n          archive: false"
  },
  {
    "path": ".github/workflows/notify.yml",
    "content": "name: notify\non:\n  workflow_run:\n    workflows: [impacto]\n    types:\n      - completed\njobs:\n  notify_fail:\n    name: Notify failure\n    runs-on: ubuntu-22.04\n    if: always() && (github.event.workflow_run.conclusion == 'cancelled' || github.event.workflow_run.conclusion == 'failure')\n    steps:\n    - uses: actions/checkout@v6\n    - name: 'Get Jobs Status'\n      id: get_jobs_status\n      run: |\n        echo \"Fetch workflow run jobs\"\n        json_data=$(curl -s -H \"Authorization: Bearer ${{ github.token }}\" \\\n                \"${{ github.event.workflow_run.jobs_url }}\")\n\n        name_conclusion_array=($(echo \"$json_data\" | jq -r '\n          .jobs[] | \n            select(.name | IN(\"macos_arm64\", \"linux\", \"windows\", \"switch\", \"macos_x64\", \"android\")) | \n          \"\\(.name).\\(.conclusion)\"'))\n\n        for item in \"${name_conclusion_array[@]}\"; do\n            IFS='.' read -r name conclusion <<< \"$item\"\n            echo \"$name=$conclusion\" >> $GITHUB_OUTPUT\n        done\n\n    - name: Get current date\n      id: date\n      run: echo \"NOW=$(date +'%Y-%m-%dT%H:%M:%S')\" >> $GITHUB_OUTPUT\n\n    - uses: tsickert/discord-webhook@v5.3.0\n      with:\n        webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }}\n        username: IMPACTO\n        avatar-url: https://cdn.discordapp.com/emojis/766988033127481344.png\n        embed-title : \"${{ github.event.commits[0].author.name }}\"\n        embed-color: 16711680\n        embed-timestamp: ${{ steps.date.outputs.NOW }}\n        embed-description: |\n          Commit\n          `${{ github.event.workflow_run.head_commit.message }} (${{ github.sha }})`\n          from branch `${{ github.event.workflow_run.head_branch }}` has failed build.\n\n          Job status:\n          windows: ${{steps.get_jobs_status.outputs.windows}} \n          linux: ${{steps.get_jobs_status.outputs.linux}} \n          macos_arm64: ${{steps.get_jobs_status.outputs.macos_arm64}} \n          macos_x64: ${{steps.get_jobs_status.outputs.macos_x64}} \n          switch: ${{steps.get_jobs_status.outputs.switch}} \n          android: ${{steps.get_jobs_status.outputs.android}} \n\n          Details:\n          ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.event.workflow_run.id }}"
  },
  {
    "path": ".github/workflows/publish.yml",
    "content": "name: publish\non:\n  workflow_run:\n    workflows: [impacto]\n    types: [completed]\n    branches: \n      - master\n      - v*\njobs:\n  publish_artifacts:\n    name: Publish Artifacts\n    runs-on: ubuntu-22.04\n    permissions:\n      contents: write\n      actions: read\n    if: github.event.workflow_run.conclusion == 'success'\n    steps:\n    - uses: actions/download-artifact@v8\n      with:\n        name: VERSION\n        github-token: ${{ github.token }}\n        run-id: ${{ github.event.workflow_run.id }}\n    - name: Set version\n      id: set-version\n      run: echo \"VERSION=$(cat VERSION)\" >> $GITHUB_OUTPUT\n    - uses: actions/download-artifact@v8\n      with:\n        pattern: impacto-*\n        path: \"${{ github.workspace }}/release\"\n        merge-multiple: true\n        skip-decompress: true\n        github-token: ${{ github.token }}\n        run-id: ${{ github.event.workflow_run.id }}\n    - name: Upload Release\n      uses: softprops/action-gh-release@v2\n      with:\n        Name: Latest ${{ env.BRANCH_NAME }} build\n        tag_name: ${{ steps.set-version.outputs.VERSION }}${{ github.event.workflow_run.run_number }}\n        files:\n          ${{ github.workspace }}/release/*.zip\n        prerelease: true\n        fail_on_unmatched_files: true\n        target_commitish: ${{ github.sha }}\n    - name: Get current date\n      id: date\n      run: echo \"NOW=$(date +'%Y-%m-%dT%H:%M:%S')\" >> $GITHUB_ENV\n\n    - uses: tsickert/discord-webhook@v5.3.0\n      with:\n        webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }}\n        username: IMPACTO\n        avatar-url: https://cdn.discordapp.com/emojis/766988033127481344.png\n        embed-title : \"${{ github.event.workflow_run.head_commit.author.name }}\"\n        embed-color: 3659647\n        embed-timestamp: ${{ env.NOW }}\n        embed-description: |\n          Commit\n          `${{ github.event.workflow_run.head_commit.message }} (${{ github.sha }})`\n          from branch `${{ github.event.workflow_run.head_branch }}` has been successfully built.\n\n          Release URL:\n          https://github.com/CommitteeOfZero/impacto/releases/tag/${{ steps.set-version.outputs.VERSION }}${{ github.event.workflow_run.run_number }}\n\n          Details:\n          - Build: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.event.workflow_run.id }}\n          - Publish: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}\n\n"
  },
  {
    "path": ".gitignore",
    "content": "## Ignore Visual Studio temporary files, build results, and\n## files generated by popular Visual Studio add-ons.\n\n# User-specific files\n*.suo\n*.user\n*.userosscache\n*.sln.docstates\n\n# User-specific files (MonoDevelop/Xamarin Studio)\n*.userprefs\n\n# Build results\n[Dd]ebug/\n[Dd]ebugPublic/\n[Rr]elease/\n[Rr]eleases/\nx64/\nx86/\nbld/\n[Bb]uild/\n[Oo]ut\n[Bb]in/\n[Oo]bj/\n[Ll]og/\n[Ii]nstall/\ncmake-build-*/\nimpacto-build/\nci-build/\n\n# Visual Studio 2015 cache/options directory\n.vs/\n# Uncomment if you have tasks that create the project's static files in wwwroot\n#wwwroot/\n\n# MSTest test Results\n[Tt]est[Rr]esult*/\n[Bb]uild[Ll]og.*\n\n# NUNIT\n*.VisualState.xml\nTestResult.xml\n\n# Build Results of an ATL Project\n[Dd]ebugPS/\n[Rr]eleasePS/\ndlldata.c\n\n# DNX\nproject.lock.json\nproject.fragment.lock.json\nartifacts/\n\n*_i.c\n*_p.c\n*_i.h\n*.ilk\n*.meta\n*.obj\n*.pch\n*.pdb\n*.pgc\n*.pgd\n*.rsp\n*.sbr\n*.tlb\n*.tli\n*.tlh\n*.tmp\n*.tmp_proj\n*.log\n*.vspscc\n*.vssscc\n.builds\n*.pidb\n*.svclog\n*.scc\n\n# Chutzpah Test files\n_Chutzpah*\n\n# Visual C++ cache files\nipch/\n*.aps\n*.ncb\n*.opendb\n*.opensdf\n*.sdf\n*.cachefile\n*.VC.db\n*.VC.VC.opendb\n\n# Visual Studio profiler\n*.psess\n*.vsp\n*.vspx\n*.sap\n\n# TFS 2012 Local Workspace\n$tf/\n\n# Guidance Automation Toolkit\n*.gpState\n\n# ReSharper is a .NET coding add-in\n_ReSharper*/\n*.[Rr]e[Ss]harper\n*.DotSettings.user\n\n# JustCode is a .NET coding add-in\n.JustCode\n\n# TeamCity is a build add-in\n_TeamCity*\n\n# DotCover is a Code Coverage Tool\n*.dotCover\n\n# NCrunch\n_NCrunch_*\n.*crunch*.local.xml\nnCrunchTemp_*\n\n# MightyMoose\n*.mm.*\nAutoTest.Net/\n\n# Web workbench (sass)\n.sass-cache/\n\n# Installshield output folder\n[Ee]xpress/\n\n# DocProject is a documentation generator add-in\nDocProject/buildhelp/\nDocProject/Help/*.HxT\nDocProject/Help/*.HxC\nDocProject/Help/*.hhc\nDocProject/Help/*.hhk\nDocProject/Help/*.hhp\nDocProject/Help/Html2\nDocProject/Help/html\n\n# Click-Once directory\npublish/\n\n# Publish Web Output\n*.[Pp]ublish.xml\n*.azurePubxml\n# TODO: Comment the next line if you want to checkin your web deploy settings\n# but database connection strings (with potential passwords) will be unencrypted\n#*.pubxml\n*.publishproj\n\n# Microsoft Azure Web App publish settings. Comment the next line if you want to\n# checkin your Azure Web App publish settings, but sensitive information contained\n# in these scripts will be unencrypted\nPublishScripts/\n\n# NuGet Packages\n*.nupkg\n# The packages folder can be ignored because of Package Restore\n**/packages/*\n# except build/, which is used as an MSBuild target.\n!**/packages/build/\n# Uncomment if necessary however generally it will be regenerated when needed\n#!**/packages/repositories.config\n# NuGet v3's project.json files produces more ignoreable files\n*.nuget.props\n*.nuget.targets\n\n# Microsoft Azure Build Output\ncsx/\n*.build.csdef\n\n# Microsoft Azure Emulator\necf/\nrcf/\n\n# Windows Store app package directories and files\nAppPackages/\nBundleArtifacts/\nPackage.StoreAssociation.xml\n_pkginfo.txt\n\n# Visual Studio cache files\n# files ending in .cache can be ignored\n*.[Cc]ache\n# but keep track of directories ending in .cache\n!*.[Cc]ache/\n\n# Others\nClientBin/\n~$*\n*~\n*.dbmdl\n*.dbproj.schemaview\n*.jfm\n*.pfx\n*.publishsettings\nnode_modules/\norleans.codegen.cs\n*.cbp\ncmake_install.cmake\ninclude/\nMakefile\n.DS_Store\n.cache\n\n# Since there are multiple workflows, uncomment next line to ignore bower_components\n# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)\n#bower_components/\n\n# RIA/Silverlight projects\nGenerated_Code/\n\n# Backup & report files from converting an old project file\n# to a newer Visual Studio version. Backup files are not needed,\n# because we have git ;-)\n_UpgradeReport_Files/\nBackup*/\nUpgradeLog*.XML\nUpgradeLog*.htm\n\n# SQL Server files\n*.mdf\n*.ldf\n\n# Business Intelligence projects\n*.rdl.data\n*.bim.layout\n*.bim_*.settings\n\n# Microsoft Fakes\nFakesAssemblies/\n\n# GhostDoc plugin setting file\n*.GhostDoc.xml\n\n# Node.js Tools for Visual Studio\n.ntvs_analysis.dat\n\n# Visual Studio 6 build log\n*.plg\n\n# Visual Studio 6 workspace options file\n*.opt\n\n# Visual Studio LightSwitch build output\n**/*.HTMLClient/GeneratedArtifacts\n**/*.DesktopClient/GeneratedArtifacts\n**/*.DesktopClient/ModelManifest.xml\n**/*.Server/GeneratedArtifacts\n**/*.Server/ModelManifest.xml\n_Pvt_Extensions\n\n# Paket dependency manager\n.paket/paket.exe\npaket-files/\n\n# FAKE - F# Make\n.fake/\n\n# JetBrains Rider\n.idea/\n*.sln.iml\n\n.luarc.json\n\n# CodeRush\n.cr/\n\n# Python Tools for Visual Studio (PTVS)\n__pycache__/\n*.pyc\n\n# VSCode\n.vscode\n\n###\n\ncompile_commands.json\n/CMakeSettings.json\nCMakeUserPresets.json\nvendor/avcpp\n\ngames/*/gamedata\ngames/*/savedata\ngames/*/trophydata"
  },
  {
    "path": "CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.28)\n\nif (POLICY CMP0141)\n  cmake_policy(SET CMP0141 NEW)\nendif()\n\nif (POLICY CMP0174)\n  cmake_policy(SET CMP0174 NEW)\nendif()\n\nproject(\"impacto\")\n\ninclude(FetchContent)\nfind_package(PkgConfig)\n\nif (EMSCRIPTEN)\n    list(APPEND CMAKE_MODULE_PATH ${EMSCRIPTEN_ROOT_PATH}/cmake/Modules)\nendif ()\n\nset(Impacto_Src\n    src/main.cpp\n    src/log.cpp\n    src/util.cpp\n    src/workqueue.cpp\n    src/game.cpp\n    src/mem.cpp\n    src/modelviewer.cpp\n    src/characterviewer.cpp\n    src/spriteanimation.cpp\n    src/sequencedanimation.cpp\n    src/background2d.cpp\n    src/mask2d.cpp\n    src/character2d.cpp\n    src/inputsystem.cpp\n    src/voicetable.cpp\n    src/animation.cpp\n\n    src/text/text.cpp\n    src/text/typewritereffect.cpp\n    src/text/dialoguepage.cpp\n\n    src/effects/wave.cpp\n    src/effects/blur.cpp\n    src/effects/mosaic.cpp\n    src/effects/chlcc/butterflyeffect.cpp\n    src/effects/chlcc/bubbleseffect.cpp\n    src/effects/chlcc/eyecatch.cpp\n\n    src/renderer/renderer.cpp\n    src/renderer/window.cpp\n\n    src/data/savesystem.cpp\n    src/data/tipssystem.cpp\n    src/data/achievementsystem.cpp\n    src/data/achievementsystemps3.cpp\n\n    src/profile/profile.cpp\n    src/profile/profile_internal.cpp\n    src/profile/game.cpp\n    src/profile/vfs.cpp\n    src/profile/scene3d.cpp\n    src/profile/sprites.cpp\n    src/profile/animations.cpp\n    src/profile/charset.cpp\n    src/profile/fonts.cpp\n    src/profile/dialogue.cpp\n    src/profile/vm.cpp\n    src/profile/scriptvars.cpp\n    src/profile/scriptinput.cpp\n    src/profile/configsystem.cpp\n    src/profile/subtitle.cpp\n\n    src/profile/data/bgeff.cpp\n    src/profile/data/savesystem.cpp\n    src/profile/data/tipssystem.cpp\n    src/profile/data/achievementsystem.cpp\n    src/profile/data/waveeffects.cpp\n\n    src/profile/hud/saveicon.cpp\n    src/profile/hud/loadingdisplay.cpp\n    src/profile/hud/datedisplay.cpp\n    src/profile/hud/tipsnotification.cpp\n\n    src/profile/ui/commonmenu.cpp\n    src/profile/ui/systemmenu.cpp\n    src/profile/ui/titlemenu.cpp\n    src/profile/ui/savemenu.cpp\n    src/profile/ui/sysmesbox.cpp\n    src/profile/ui/selectionmenu.cpp\n    src/profile/ui/backlogmenu.cpp\n    src/profile/ui/optionsmenu.cpp\n    src/profile/ui/tipsmenu.cpp\n    src/profile/ui/extramenus.cpp\n    src/profile/ui/trophymenu.cpp\n    src/profile/ui/helpmenu.cpp\n    src/profile/ui/gamespecific.cpp\n\n    src/profile/games/rne/tilebackground.cpp\n    src/profile/games/rne/systemmenu.cpp\n    src/profile/games/rne/titlemenu.cpp\n    src/profile/games/rne/sysmesbox.cpp\n\n    src/profile/games/dash/titlemenu.cpp\n\n    src/profile/games/chlcc/commonmenu.cpp\n    src/profile/games/chlcc/dialoguebox.cpp\n    src/profile/games/chlcc/titlemenu.cpp\n    src/profile/games/chlcc/savemenu.cpp\n    src/profile/games/chlcc/sysmesbox.cpp\n    src/profile/games/chlcc/clearlistmenu.cpp\n    src/profile/games/chlcc/moviemenu.cpp\n    src/profile/games/chlcc/albummenu.cpp\n    src/profile/games/chlcc/musicmenu.cpp\n    src/profile/games/chlcc/tipsmenu.cpp\n    src/profile/games/chlcc/optionsmenu.cpp\n    src/profile/games/chlcc/backlogmenu.cpp\n    src/profile/games/chlcc/systemmenu.cpp\n    src/profile/games/chlcc/trophymenu.cpp\n    src/profile/games/chlcc/delusiontrigger.cpp\n    src/profile/games/chlcc/tipsnotification.cpp\n\n    src/profile/games/cc/backlogmenu.cpp\n    src/profile/games/cc/dialoguebox.cpp\n    src/profile/games/cc/titlemenu.cpp\n    src/profile/games/cc/sysmesbox.cpp\n\n    src/profile/games/cclcc/systemmenu.cpp\n    src/profile/games/cclcc/titlemenu.cpp\n    src/profile/games/cclcc/savemenu.cpp\n    src/profile/games/cclcc/optionsmenu.cpp\n    src/profile/games/cclcc/tipsmenu.cpp\n    src/profile/games/cclcc/clearlistmenu.cpp\n    src/profile/games/cclcc/librarymenu.cpp\n    src/profile/games/cclcc/tipsnotification.cpp\n    src/profile/games/cclcc/mapsystem.cpp\n    src/profile/games/cclcc/delusiontrigger.cpp\n    src/profile/games/cclcc/yesnotrigger.cpp\n    src/profile/games/cclcc/helpmenu.cpp\n\n    src/profile/games/mo6tw/backlogmenu.cpp\n    src/profile/games/mo6tw/dialoguebox.cpp\n    src/profile/games/mo6tw/sysmesbox.cpp\n    src/profile/games/mo6tw/systemmenu.cpp\n    src/profile/games/mo6tw/titlemenu.cpp\n    src/profile/games/mo6tw/savemenu.cpp\n    src/profile/games/mo6tw/optionsmenu.cpp\n    src/profile/games/mo6tw/tipsmenu.cpp\n    src/profile/games/mo6tw/clearlistmenu.cpp\n    src/profile/games/mo6tw/moviemenu.cpp\n    src/profile/games/mo6tw/actorsvoicemenu.cpp\n    src/profile/games/mo6tw/musicmenu.cpp\n    src/profile/games/mo6tw/albummenu.cpp\n    src/profile/games/mo6tw/tipsnotification.cpp\n\n    src/profile/games/mo8/titlemenu.cpp\n    src/profile/games/mo8/systemmenu.cpp\n    src/profile/games/mo8/optionsmenu.cpp\n    src/profile/games/mo8/savemenu.cpp\n\n    src/profile/games/darling/sysmesbox.cpp\n\n    src/games/rne/tilebackground.cpp\n    src/games/rne/systemmenu.cpp\n    src/games/rne/titlemenu.cpp\n    src/games/rne/sysmesbox.cpp\n\n    src/games/dash/titlemenu.cpp\n\n    src/games/chlcc/commonmenu.cpp\n    src/games/chlcc/titlemenu.cpp\n    src/games/chlcc/savemenu.cpp\n    src/games/chlcc/sysmesbox.cpp\n    src/games/chlcc/animations/selectprompt.cpp\n    src/games/chlcc/animations/menutransition.cpp\n    src/games/chlcc/animations/saveicon.cpp\n    src/games/chlcc/savesystem.cpp\n    src/games/chlcc/tipssystem.cpp\n    src/games/chlcc/clearlistmenu.cpp\n    src/games/chlcc/moviemenu.cpp\n    src/games/chlcc/albummenu.cpp\n    src/games/chlcc/musicmenu.cpp\n    src/games/chlcc/tipsmenu.cpp\n    src/games/chlcc/optionsmenu.cpp\n    src/games/chlcc/backlogmenu.cpp\n    src/games/chlcc/systemmenu.cpp\n    src/games/chlcc/trophymenu.cpp\n    src/games/chlcc/delusiontrigger.cpp\n    src/games/chlcc/introsequence.cpp\n\n    src/games/cc/backlogmenu.cpp\n    src/games/cc/titlemenu.cpp\n    src/games/cc/sysmesbox.cpp\n\n    src/games/cclcc/titlemenu.cpp\n    src/games/cclcc/savemenu.cpp\n    src/games/cclcc/optionsmenu.cpp\n    src/games/cclcc/tipsmenu.cpp\n    src/games/cclcc/tipssystem.cpp\n    src/games/cclcc/clearlistmenu.cpp\n    src/games/cclcc/librarymenu.cpp\n    src/games/cclcc/librarysubmenus.cpp\n    src/games/cclcc/albummenu.cpp\n    src/games/cclcc/musicmenu.cpp\n    src/games/cclcc/moviemenu.cpp\n    src/games/cclcc/mapsystem.cpp\n    src/games/cclcc/delusiontrigger.cpp\n    src/games/cclcc/yesnotrigger.cpp\n    src/games/cclcc/savesystem.cpp\n    src/games/cclcc/systemmenu.cpp\n    src/games/cclcc/helpmenu.cpp\n\n    src/games/mo6tw/backlogmenu.cpp\n    src/games/mo6tw/sysmesbox.cpp\n    src/games/mo6tw/systemmenu.cpp\n    src/games/mo6tw/titlemenu.cpp\n    src/games/mo6tw/savemenu.cpp\n    src/games/mo6tw/optionsmenu.cpp\n    src/games/mo6tw/tipsmenu.cpp\n    src/games/mo6tw/savesystem.cpp\n    src/games/mo6tw/tipssystem.cpp\n    src/games/mo6tw/clearlistmenu.cpp\n    src/games/mo6tw/moviemenu.cpp\n    src/games/mo6tw/actorsvoicemenu.cpp\n    src/games/mo6tw/musicmenu.cpp\n    src/games/mo6tw/albummenu.cpp\n\n    src/games/mo8/titlemenu.cpp\n    src/games/mo8/systemmenu.cpp\n    src/games/mo8/optionsmenu.cpp\n    src/games/mo8/savemenu.cpp\n\n    src/games/darling/sysmesbox.cpp\n\n    src/hud/dialoguebox.cpp\n    src/hud/defaultsaveiconanimation.cpp\n    src/hud/datedisplay.cpp\n    src/hud/saveicondisplay.cpp\n    src/hud/loadingdisplay.cpp\n    src/hud/nametagdisplay.cpp\n    src/hud/autoicondisplay.cpp\n    src/hud/skipicondisplay.cpp\n    src/hud/waiticondisplay.cpp\n    src/hud/tipsnotification.cpp\n\n    src/hud/cc/dialoguebox.cpp\n    src/hud/cc/nametagdisplay.cpp\n\n    src/hud/cclcc/tipsnotification.cpp\n\n    src/hud/chlcc/dialoguebox.cpp\n    src/hud/chlcc/nametagdisplay.cpp\n    src/hud/chlcc/tipsnotification.cpp\n\n    src/hud/mo6tw/dialoguebox.cpp\n    src/hud/mo6tw/tipsnotification.cpp\n\n    src/hud/rne/datedisplay.cpp\n\n    src/ui/widget.cpp\n    src/ui/menu.cpp\n    src/ui/nullmenu.cpp\n    src/ui/selectionmenu.cpp\n    src/ui/sysmesbox.cpp\n    src/ui/backlogmenu.cpp\n    src/ui/tipsmenu.cpp\n    src/ui/optionsmenu.cpp\n    src/ui/widgets/label.cpp\n    src/ui/widgets/button.cpp\n    src/ui/widgets/backlogentry.cpp\n    src/ui/widgets/scrollbar.cpp\n    src/ui/widgets/toggle.cpp\n    src/ui/widgets/optiongroup.cpp\n    src/ui/widgets/group.cpp\n    src/ui/widgets/carousel.cpp\n    src/ui/widgets/cgviewer.cpp\n    src/ui/widgets/clickarea.cpp\n    src/ui/widgets/mo6tw/titlebutton.cpp\n    src/ui/widgets/mo6tw/saveentrybutton.cpp\n    src/ui/widgets/mo6tw/tipsentrybutton.cpp\n    src/ui/widgets/mo6tw/scenelistentry.cpp\n    src/ui/widgets/mo6tw/imagethumbnailbutton.cpp\n    src/ui/widgets/mo6tw/albumthumbnailbutton.cpp\n    src/ui/widgets/mo6tw/actorsvoicebutton.cpp\n    src/ui/widgets/mo6tw/albumcharacterbutton.cpp\n    src/ui/widgets/chlcc/titlebutton.cpp\n    src/ui/widgets/chlcc/saveentrybutton.cpp\n    src/ui/widgets/chlcc/systemmenuentrybutton.cpp\n    src/ui/widgets/chlcc/systemmessagebutton.cpp\n    src/ui/widgets/chlcc/moviemenuentrybutton.cpp\n    src/ui/widgets/chlcc/albumthumbnailbutton.cpp\n    src/ui/widgets/chlcc/trackselectbutton.cpp\n    src/ui/widgets/chlcc/backlogentry.cpp\n    src/ui/widgets/chlcc/trophymenuentry.cpp\n    src/ui/widgets/chlcc/optionsentry.cpp\n    src/ui/widgets/chlcc/optionsbutton.cpp\n    src/ui/widgets/chlcc/optionsslider.cpp\n    src/ui/widgets/chlcc/tipsentrybutton.cpp\n    src/ui/widgets/rne/sysmenubutton.cpp\n    src/ui/widgets/cc/backlogentry.cpp\n    src/ui/widgets/cc/titlebutton.cpp\n    src/ui/widgets/cclcc/titlebutton.cpp\n    src/ui/widgets/cclcc/optionsentry.cpp\n    src/ui/widgets/cclcc/optionsbinarybutton.cpp\n    src/ui/widgets/cclcc/optionsslider.cpp\n    src/ui/widgets/cclcc/optionsvoiceslider.cpp\n    src/ui/widgets/cclcc/saveentrybutton.cpp\n    src/ui/widgets/cclcc/sysmenubutton.cpp\n    src/ui/widgets/cclcc/tipsentrybutton.cpp\n    src/ui/widgets/cclcc/tipstabgroup.cpp\n    src/ui/gamespecific.cpp\n\n    src/stbi_impl.c\n\n    src/renderer/3d/camera.cpp\n    src/renderer/3d/model.cpp\n    src/renderer/3d/transform.cpp\n    src/renderer/3d/animation.cpp\n    src/renderer/3d/modelanimator.cpp\n\n    src/io/filemeta.cpp\n    src/io/vfs.cpp\n    src/io/assetpath.cpp\n    src/io/memorystream.cpp\n    src/io/physicalfilestream.cpp\n    src/io/uncompressedstream.cpp\n    src/io/zlibstream.cpp\n    src/io/vfsarchive.cpp\n    src/io/mpkarchive.cpp\n    src/io/cpkarchive.cpp\n    src/io/lnk4archive.cpp\n    src/io/textarchive.cpp\n    src/io/afsarchive.cpp\n\n    src/texture/texture.cpp\n    src/texture/s3tc.cpp\n    src/texture/bcdecode.cpp\n    src/texture/bntxloader.cpp\n    src/texture/gxtloader.cpp\n    src/texture/plainloader.cpp\n    src/texture/stbiloader.cpp\n    src/texture/ddsloader.cpp\n    src/texture/webpdecode.cpp\n\n    src/vm/vm.cpp\n    src/vm/expression.cpp\n    src/vm/thread.cpp\n    src/vm/inst_system.cpp\n    src/vm/inst_controlflow.cpp\n    src/vm/inst_dialogue.cpp\n    src/vm/inst_gamespecific.cpp\n    src/vm/inst_graphics2d.cpp\n    src/vm/inst_graphics3d.cpp\n    src/vm/inst_misc.cpp\n    src/vm/inst_movie.cpp\n    src/vm/inst_sound.cpp\n\n    src/vm/interface/scene3d.cpp\n    src/vm/interface/input.cpp\n\n    src/audio/audiosystem.cpp\n    src/audio/audiochannel.cpp\n    src/audio/audiostream.cpp\n    src/audio/vorbisaudiostream.cpp\n    src/audio/atrac9audiostream.cpp\n    src/audio/adxaudiostream.cpp\n    src/audio/hcaaudiostream.cpp\n\n    src/video/videosystem.cpp\n    src/video/videoplayer.cpp\n    src/video/clock.cpp\n\n    src/subtitle/subtitlesystem.cpp\n)\n\nset(Impacto_Header\n    src/impacto.h\n    src/log.h\n    src/util.h\n    src/workqueue.h\n    src/game.h\n    src/mem.h\n    src/modelviewer.h\n    src/characterviewer.h\n    src/spritesheet.h\n    src/spriteanimation.h\n    src/sequencedanimation.h\n    src/font.h\n    src/background2d.h\n    src/mask2d.h\n    src/character2d.h\n    src/loadable.h\n    src/inputsystem.h\n    src/rng.h\n    src/animation.h\n\n    src/text/text.h\n    src/text/typewritereffect.h\n    src/text/dialoguepage.h\n\n    src/effects/wave.h\n    src/effects/blur.h\n    src/effects/mosaic.h\n    src/effects/chlcc/butterflyeffect.h\n    src/effects/chlcc/bubbleseffect.h\n    src/effects/chlcc/eyecatch.h\n\n    src/renderer/renderer.h\n    src/renderer/window.h\n    src/renderer/yuvframe.h\n    src/renderer/nv12frame.h\n\n    src/data/savesystem.h\n    src/data/tipssystem.h\n    src/data/achievementsystem.h\n    src/data/achievementsystemps3.h\n\n    src/profile/profile.h\n    src/profile/profile_internal.h\n    src/profile/game.h\n    src/profile/vfs.h\n    src/profile/scene3d.h\n    src/profile/sprites.h\n    src/profile/animations.h\n    src/profile/charset.h\n    src/profile/fonts.h\n    src/profile/dialogue.h\n    src/profile/vm.h\n    src/profile/scriptvars.h\n    src/profile/scriptinput.h\n    src/profile/configsystem.h\n    src/profile/subtitle.h\n\n    src/profile/data/bgeff.h\n    src/profile/data/savesystem.h\n    src/profile/data/tipssystem.h\n    src/profile/data/achievementsystem.h\n    src/profile/data/waveeffects.h\n\n    src/profile/hud/saveicon.h\n    src/profile/hud/loadingdisplay.h\n    src/profile/hud/datedisplay.h\n    src/profile/hud/tipsnotification.h\n\n    src/profile/ui/commonmenu.h\n    src/profile/ui/systemmenu.h\n    src/profile/ui/titlemenu.h\n    src/profile/ui/savemenu.h\n    src/profile/ui/sysmesbox.h\n    src/profile/ui/selectionmenu.h\n    src/profile/ui/backlogmenu.h\n    src/profile/ui/optionsmenu.h\n    src/profile/ui/tipsmenu.h\n    src/profile/ui/extramenus.h\n    src/profile/ui/trophymenu.h\n    src/profile/ui/helpmenu.h\n    src/profile/ui/gamespecific.h\n\n    src/profile/games/rne/tilebackground.h\n    src/profile/games/rne/systemmenu.h\n    src/profile/games/rne/titlemenu.h\n    src/profile/games/rne/sysmesbox.h\n\n    src/profile/games/dash/titlemenu.h\n\n    src/profile/games/chlcc/commonmenu.h\n    src/profile/games/chlcc/dialoguebox.h\n    src/profile/games/chlcc/titlemenu.h\n    src/profile/games/chlcc/savemenu.h\n    src/profile/games/chlcc/sysmesbox.h\n    src/profile/games/chlcc/clearlistmenu.h\n    src/profile/games/chlcc/moviemenu.h\n    src/profile/games/chlcc/albummenu.h\n    src/profile/games/chlcc/musicmenu.h\n    src/profile/games/chlcc/tipsmenu.h\n    src/profile/games/chlcc/optionsmenu.h\n    src/profile/games/chlcc/backlogmenu.h\n    src/profile/games/chlcc/systemmenu.h\n    src/profile/games/chlcc/trophymenu.h\n    src/profile/games/chlcc/delusiontrigger.h\n    src/profile/games/chlcc/tipsnotification.h\n\n    src/profile/games/cc/backlogmenu.h\n    src/profile/games/cc/dialoguebox.h\n    src/profile/games/cc/titlemenu.h\n    src/profile/games/cc/sysmesbox.h\n\n    src/profile/games/cclcc/titlemenu.h\n    src/profile/games/cclcc/savemenu.h\n    src/profile/games/cclcc/optionsmenu.h\n    src/profile/games/cclcc/tipsmenu.h\n    src/profile/games/cclcc/clearlistmenu.h\n    src/profile/games/cclcc/librarymenu.h\n    src/profile/games/cclcc/tipsnotification.h\n    src/profile/games/cclcc/mapsystem.h\n    src/profile/games/cclcc/delusiontrigger.h\n    src/profile/games/cclcc/helpmenu.h\n\n    src/profile/games/mo6tw/backlogmenu.h\n    src/profile/games/mo6tw/dialoguebox.h\n    src/profile/games/mo6tw/sysmesbox.h\n    src/profile/games/mo6tw/systemmenu.h\n    src/profile/games/mo6tw/titlemenu.h\n    src/profile/games/mo6tw/savemenu.h\n    src/profile/games/mo6tw/optionsmenu.h\n    src/profile/games/mo6tw/tipsmenu.h\n    src/profile/games/mo6tw/clearlistmenu.h\n    src/profile/games/mo6tw/moviemenu.h\n    src/profile/games/mo6tw/actorsvoicemenu.h\n    src/profile/games/mo6tw/musicmenu.h\n    src/profile/games/mo6tw/albummenu.h\n    src/profile/games/mo6tw/tipsnotification.h\n\n    src/profile/games/mo8/titlemenu.h\n    src/profile/games/mo8/systemmenu.h\n    src/profile/games/mo8/optionsmenu.h\n    src/profile/games/mo8/savemenu.h\n\n    src/profile/games/darling/sysmesbox.h\n\n    src/games/rne/tilebackground.h\n    src/games/rne/systemmenu.h\n    src/games/rne/titlemenu.h\n    src/games/rne/sysmesbox.h\n\n    src/games/dash/titlemenu.h\n\n    src/games/chlcc/commonmenu.h\n    src/games/chlcc/titlemenu.h\n    src/games/chlcc/savemenu.h\n    src/games/chlcc/sysmesbox.h\n    src/games/chlcc/animations/selectprompt.h\n    src/games/chlcc/animations/menutransition.h\n    src/games/chlcc/animations/saveicon.h\n    src/games/chlcc/savesystem.h\n    src/games/chlcc/tipssystem.h\n    src/games/chlcc/clearlistmenu.h\n    src/games/chlcc/moviemenu.h\n    src/games/chlcc/albummenu.h\n    src/games/chlcc/musicmenu.h\n    src/games/chlcc/tipsmenu.h\n    src/games/chlcc/optionsmenu.h\n    src/games/chlcc/backlogmenu.h\n    src/games/chlcc/systemmenu.h\n    src/games/chlcc/trophymenu.h\n    src/games/chlcc/delusiontrigger.h\n    src/games/chlcc/introsequence.h\n\n    src/games/cc/titlemenu.h\n    src/games/cc/sysmesbox.h\n\n    src/games/cclcc/titlemenu.h\n    src/games/cclcc/savemenu.h\n    src/games/cclcc/optionsmenu.h\n    src/games/cclcc/tipsmenu.h\n    src/games/cclcc/clearlistmenu.h\n    src/games/cclcc/librarymenu.h\n    src/games/cclcc/librarysubmenus.h\n    src/games/cclcc/albummenu.h\n    src/games/cclcc/musicmenu.h\n    src/games/cclcc/moviemenu.h\n    src/games/cclcc/mapsystem.h\n    src/games/cclcc/delusiontrigger.h\n    src/games/cclcc/helpmenu.h\n\n    src/games/mo6tw/sysmesbox.h\n    src/games/mo6tw/systemmenu.h\n    src/games/mo6tw/titlemenu.h\n    src/games/mo6tw/savemenu.h\n    src/games/mo6tw/optionsmenu.h\n    src/games/mo6tw/tipsmenu.h\n    src/games/mo6tw/savesystem.h\n    src/games/mo6tw/tipssystem.h\n    src/games/mo6tw/clearlistmenu.h\n    src/games/mo6tw/moviemenu.h\n    src/games/mo6tw/actorsvoicemenu.h\n    src/games/mo6tw/musicmenu.h\n    src/games/mo6tw/albummenu.h\n\n    src/games/mo8/titlemenu.h\n    src/games/mo8/systemmenu.h\n    src/games/mo8/optionsmenu.h\n    src/games/mo8/savemenu.h\n\n    src/games/darling/sysmesbox.h\n\n    src/hud/dialoguebox.h\n    src/hud/defaultsaveiconanimation.h\n    src/hud/datedisplay.h\n    src/hud/saveicondisplay.h\n    src/hud/loadingdisplay.h\n    src/hud/nametagdisplay.h\n    src/hud/autoicondisplay.h\n    src/hud/skipicondisplay.h\n    src/hud/waiticondisplay.h\n    src/hud/tipsnotification.h\n\n    src/hud/cc/dialoguebox.h\n    src/hud/cc/nametagdisplay.h\n\n    src/hud/cclcc/tipsnotification.h\n\n    src/hud/chlcc/dialoguebox.h\n    src/hud/chlcc/nametagdisplay.h\n    src/hud/chlcc/tipsnotification.h\n\n    src/hud/mo6tw/dialoguebox.h\n    src/hud/mo6tw/tipsnotification.h\n\n    src/hud/rne/datedisplay.h\n\n    src/ui/ui.h\n    src/ui/menu.h\n    src/ui/nullmenu.h\n    src/ui/selectionmenu.h\n    src/ui/sysmesbox.h\n    src/ui/backlogmenu.h\n    src/ui/tipsmenu.h\n    src/ui/optionsmenu.h\n    src/ui/widget.h\n    src/ui/widgets/label.h\n    src/ui/widgets/button.h\n    src/ui/widgets/backlogentry.h\n    src/ui/widgets/scrollbar.h\n    src/ui/widgets/toggle.h\n    src/ui/widgets/optiongroup.h\n    src/ui/widgets/group.h\n    src/ui/widgets/carousel.h\n    src/ui/widgets/cgviewer.h\n    src/ui/widgets/clickarea.h\n    src/ui/widgets/mo6tw/titlebutton.h\n    src/ui/widgets/mo6tw/saveentrybutton.h\n    src/ui/widgets/mo6tw/tipsentrybutton.h\n    src/ui/widgets/mo6tw/scenelistentry.h\n    src/ui/widgets/mo6tw/imagethumbnailbutton.h\n    src/ui/widgets/mo6tw/albumthumbnailbutton.h\n    src/ui/widgets/mo6tw/actorsvoicebutton.h\n    src/ui/widgets/mo6tw/albumcharacterbutton.h\n    src/ui/widgets/chlcc/titlebutton.h\n    src/ui/widgets/chlcc/saveentrybutton.h\n    src/ui/widgets/chlcc/tipsentrybutton.h\n    src/ui/widgets/chlcc/systemmenuentrybutton.h\n    src/ui/widgets/chlcc/systemmessagebutton.h\n    src/ui/widgets/chlcc/moviemenuentrybutton.h\n    src/ui/widgets/chlcc/albumthumbnailbutton.h\n    src/ui/widgets/chlcc/trackselectbutton.h\n    src/ui/widgets/chlcc/backlogentry.h\n    src/ui/widgets/chlcc/trophymenuentry.h\n    src/ui/widgets/chlcc/optionsentry.h\n    src/ui/widgets/chlcc/optionsbutton.h\n    src/ui/widgets/chlcc/optionsslider.h\n    src/ui/widgets/rne/sysmenubutton.h\n    src/ui/widgets/cc/backlogentry.h\n    src/ui/widgets/cc/titlebutton.h\n    src/ui/widgets/cclcc/titlebutton.h\n    src/ui/widgets/cclcc/optionsentry.h\n    src/ui/widgets/cclcc/optionsbinarybutton.h\n    src/ui/widgets/cclcc/optionsslider.h\n    src/ui/widgets/cclcc/optionsvoiceslider.h\n    src/ui/gamespecific.h\n\n    src/renderer/3d/camera.h\n    src/renderer/3d/model.h\n    src/renderer/3d/renderable3d.h\n    src/renderer/3d/transform.h\n    src/renderer/3d/scene.h\n    src/renderer/3d/animation.h\n    src/renderer/3d/modelanimator.h\n\n    src/io/io.h\n    src/io/vfs.h\n    src/io/buffering.h\n    src/io/filemeta.h\n    src/io/assetpath.h\n    src/io/stream.h\n    src/io/vfsarchive.h\n    src/io/memorystream.h\n    src/io/physicalfilestream.h\n    src/io/uncompressedstream.h\n    src/io/zlibstream.h\n\n    src/texture/texture.h\n    src/texture/s3tc.h\n    src/texture/bcdecode.h\n    src/texture/gxtloader.h\n    src/texture/bntxloader.h\n    src/texture/plainloader.h\n\n    src/vm/vm.h\n    src/vm/expression.h\n    src/vm/thread.h\n    src/vm/inst_macros.inc\n    src/vm/inst_system.h\n    src/vm/inst_controlflow.h\n    src/vm/inst_dialogue.h\n    src/vm/inst_gamespecific.h\n    src/vm/inst_graphics2d.h\n    src/vm/inst_graphics3d.h\n    src/vm/inst_misc.h\n    src/vm/inst_movie.h\n    src/vm/inst_sound.h\n    src/vm/opcodetables_rne.h\n\n    src/vm/interface/scene3d.h\n    src/vm/interface/input.h\n\n    src/audio/audiobackend.h\n    src/audio/audiosystem.h\n    src/audio/audiocommon.h\n    src/audio/audiochannel.h\n    src/audio/audiostream.h\n    src/audio/buffering.h\n    src/audio/ffmpegaudioplayer.h\n    src/audio/vorbisaudiostream.h\n    src/audio/atrac9audiostream.h\n    src/audio/adxaudiostream.h\n    src/audio/hcaaudiostream.h\n\n    src/video/videosystem.h\n    src/video/videoplayer.h\n    src/video/clock.h\n\n    src/subtitle/subtitlesystem.h\n    src/subtitle/subtitlerenderer.h\n)\n\nif (WIN32)\n    list(APPEND Impacto_Src\n        src/manifest_windows.manifest\n    )\nendif ()\n\nadd_library(clHCA STATIC vendor/clHCA/clHCA.c)\ntarget_include_directories(clHCA PUBLIC vendor/clHCA)\n\nadd_library(minilua STATIC vendor/minilua/minilua_impl.c)\ntarget_include_directories(minilua PUBLIC vendor)\n\nadd_library(pcg STATIC vendor/pcg/src/pcg_basic.c)\ntarget_include_directories(pcg PUBLIC vendor/pcg/include)\n\nadd_library(squish STATIC\n    vendor/squish/alpha.cpp\n    vendor/squish/clusterfit.cpp\n    vendor/squish/colourblock.cpp\n    vendor/squish/colourfit.cpp\n    vendor/squish/colourset.cpp\n    vendor/squish/maths.cpp\n    vendor/squish/rangefit.cpp\n    vendor/squish/singlecolourfit.cpp\n    vendor/squish/squish.cpp\n)\ntarget_include_directories(squish PUBLIC vendor/squish)\n\nset(Impacto_Lib_Targets\n    clHCA\n    minilua\n    pcg\n    squish\n)\n\nif (NX OR EMSCRIPTEN)\n    set(IMPACTO_DISABLE_VULKAN ON)\n    set(IMPACTO_DISABLE_DX9 ON)\n    set(IMPACTO_DISABLE_MMAP ON)\n    set(BUILD_SHARED_LIBS OFF)\nendif ()\n\nif(ANDROID)\n    set(IMPACTO_DISABLE_IMGUI ON)\nendif ()\n\nif (UNIX AND NOT CYGWIN)\n    set(IMPACTO_DISABLE_DX9 ON)\nendif ()\n\nif (IMPACTO_ASAN)\n    if (CMAKE_CXX_COMPILER_ID STREQUAL \"Clang\" OR CMAKE_CXX_COMPILER_ID STREQUAL \"AppleClang\")\n        message(STATUS \"Enabling ASAN/UBSAN for clang\")\n        add_compile_options(-fsanitize=address,undefined)\n        add_link_options(-fsanitize=address,undefined)\n    elseif (CMAKE_CXX_COMPILER_ID STREQUAL \"GNU\")\n        message(STATUS \"Enabling ASAN/UBSAN for GCC\")\n        add_compile_options(-fsanitize=address,undefined)\n        add_link_options(-fsanitize=address,undefined)\n    elseif (CMAKE_CXX_COMPILER_ID STREQUAL \"MSVC\")\n        message(STATUS \"Enabling ASAN for MSVC\") # Only ASAN supported for MSVC on Windows\n        add_compile_options(/fsanitize=address)\n    else ()\n        message(WARNING \"Unknown compiler, not enabling ASAN and UBSAN\")\n    endif ()\nelse ()\n    message(STATUS \"NOT enabling ASAN and UBSAN, consider enabling them when developing\")\nendif ()\n\n\nif(ANDROID OR NX)\n    set(BUILD_SHARED_LIBS OFF)\n    set(LIBATRAC9_BUILD_SHARED OFF)\nendif()\n\nif(NX)\n    add_compile_definitions(__SWITCH__)\nendif()\n\n# dependencies\nFetchContent_Declare(\n    glm\n    GIT_REPOSITORY\thttps://github.com/g-truc/glm.git\n\tGIT_TAG \"1.0.1\"\n    SYSTEM\n    EXCLUDE_FROM_ALL\n)\n\nFetchContent_Declare(\n    libatrac9\n    GIT_TAG \"master\"\n    GIT_REPOSITORY \"https://github.com/Thealexbarney/LibAtrac9.git\"\n    PATCH_COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/vendor/patches/LibAtrac9/CMakeLists.txt <SOURCE_DIR>/CMakeLists.txt\n    SYSTEM\n    EXCLUDE_FROM_ALL\n)\nif (NOT VCPKG_TOOLCHAIN)\n    FetchContent_Declare(\n        fmt\n        GIT_REPOSITORY \"https://github.com/fmtlib/fmt.git\"\n        GIT_TAG \"11.1.4\"\n        SYSTEM\n        EXCLUDE_FROM_ALL\n    )\nendif ()\n\nFetchContent_Declare(\n    pugixml\n    GIT_REPOSITORY \"https://github.com/zeux/pugixml.git\"\n    GIT_TAG \"v1.14\"\n    SYSTEM\n    EXCLUDE_FROM_ALL\n)\n\nFetchContent_Declare(\n    utf8cpp\n    GIT_REPOSITORY \"https://github.com/nemtrif/utfcpp\"\n    GIT_TAG \"v4.0.5\"\n    SYSTEM\n    EXCLUDE_FROM_ALL\n)\n\nFetchContent_Declare(\n    concurrentqueue\n    GIT_REPOSITORY \"https://github.com/cameron314/concurrentqueue\"\n    GIT_TAG \"v1.0.4\"\n    SYSTEM\n    EXCLUDE_FROM_ALL\n)\n\nFetchContent_Declare(\n    readerwriterqueue\n    GIT_REPOSITORY \"https://github.com/cameron314/readerwriterqueue\"\n    GIT_TAG \"16b48ae1148284e7b40abf72167206a4390a4592\"\n    SYSTEM\n    EXCLUDE_FROM_ALL\n)\n\nFetchContent_Declare(\n    unordered_dense\n    GIT_REPOSITORY \"https://github.com/martinus/unordered_dense\"\n    GIT_TAG \"v4.5.0\"\n    SYSTEM\n    EXCLUDE_FROM_ALL\n)\n\nFetchContent_Declare(\n    magic_enum\n    GIT_TAG \"7d87efb4a3dddbbe8caa9ca14eff05ede1102ab8\"\n    GIT_REPOSITORY \"https://github.com/Neargye/magic_enum.git\"\n    SYSTEM\n    EXCLUDE_FROM_ALL\n)\n\nif (NOT VCPKG_TOOLCHAIN)\n    FetchContent_MakeAvailable(fmt)\nendif ()\nFetchContent_MakeAvailable(glm)\nFetchContent_MakeAvailable(pugixml)\nFetchContent_MakeAvailable(utf8cpp)\nFetchContent_MakeAvailable(libatrac9)\nFetchContent_MakeAvailable(concurrentqueue)\nFetchContent_MakeAvailable(readerwriterqueue)\nFetchContent_MakeAvailable(unordered_dense)\nFetchContent_MakeAvailable(magic_enum)\n\nif (NOT DEFINED IMPACTO_DISABLE_MMAP)\n    list(APPEND Impacto_Src\n        src/io/memorymappedfilestream.cpp\n    )\n    list(APPEND Impacto_Header\n        src/io/memorymappedfilestream.h\n    )\n    list(APPEND Impacto_Include_Dirs\n        vendor/mio\n    )\nendif ()\n\nif (NOT DEFINED IMPACTO_DISABLE_IMGUI)\n    FetchContent_Declare(\n            ImGui\n            GIT_REPOSITORY \"https://github.com/ocornut/imgui.git\"\n            GIT_TAG \"1db579d458da29fa43376af9d88d486910d9406a\"\n    )\n\n    FetchContent_MakeAvailable(ImGui)\nendif ()\n\nif (WIN32)\n    # Workaround for RelWithDebInfo builds not installing all the libraries\n    set(CMAKE_MAP_IMPORTED_CONFIG_MINSIZEREL Release)\n    set(CMAKE_MAP_IMPORTED_CONFIG_RELWITHDEBINFO Release)\n\n    set(SDL2_LIBRARIES SDL2::SDL2 SDL2::SDL2main)\nendif ()\n\nif (NOT NX) # avoid CMAKE_DL_LIBS for NX\n    list(APPEND Impacto_Libs\n        ${CMAKE_DL_LIBS}\n    )\nendif ()\n\nlist(APPEND Impacto_Include_Dirs ${CMAKE_CURRENT_SOURCE_DIR}/vendor)\nlist(APPEND Impacto_Include_Dirs ${CMAKE_CURRENT_SOURCE_DIR}/vendor/include)\n\nif (NOT DEFINED IMPACTO_DISABLE_OPENGL)\n    list(APPEND Impacto_Include_Dirs ${CMAKE_CURRENT_SOURCE_DIR}/vendor/glad/include)\nendif ()\n\nif(ANDROID)\n    set(SDL2_LIBRARIES SDL2::SDL2-static SDL2::SDL2main)\n\n    if (NOT DEFINED IMPACTO_DISABLE_OPENGL)\n        find_library(GLESv3 GLESv3)\n        list(APPEND Impacto_Libs\n            GLESv3\n        )\n        \n    endif ()\nendif ()\n\nif (NOT DEFINED IMPACTO_DISABLE_OPENAL)\n    find_package(OpenAL REQUIRED)\n\n    list(APPEND Impacto_Libs\n            ${OPENAL_LIBRARY}\n    )\n\n    list(APPEND Impacto_Src\n        src/audio/openal/audiobackend.cpp\n        src/audio/openal/openalaudiochannel.cpp\n        src/audio/openal/ffmpegaudioplayer.cpp\n    )\n    list(APPEND Impacto_Header\n        src/audio/openal/audiobackend.h\n        src/audio/openal/openalaudiochannel.h\n        src/audio/openal/audiocommon.h\n        src/audio/openal/ffmpegaudioplayer.h\n    )\n\n    list(APPEND Impacto_Include_Dirs ${OPENAL_INCLUDE_DIR})\nendif ()\n\nif (EMSCRIPTEN)\n    # BINARYEN_TRAP_MODE=clamp => https://groups.google.com/forum/#!topic/emscripten-discuss/IJr4ApiW_zU\n    # duk_heap_alloc() errors without this\n    # MAIN_MODULE=1 => SDL_GL_GetProcAddress falls back to dlsym() so we need dynamic linking support (...)\n\n    set(IMPACTO_EMSCRIPTEN_BUILD_FLAGS \"\\\n        -s USE_SDL=2 \\\n        -s USE_ZLIB=1 \\\n        -s USE_OGG=1 \\\n        -s USE_VORBIS=1 \\\n        -s WASM=1 \\\n        -s USE_WEBGL2=1 \\\n        -s MAIN_MODULE=2 \\\n        -s BINARYEN_TRAP_MODE=clamp \\\n        -s ALLOW_MEMORY_GROWTH=1 \\\n        -g4 \\\n        --preload-file ${CMAKE_CURRENT_SOURCE_DIR}/profiles@/profiles \\\n        --preload-file ${CMAKE_CURRENT_SOURCE_DIR}/games@/games \\\n        --preload-file ${CMAKE_CURRENT_SOURCE_DIR}/src/shaders@/shaders \\\n    \")\n    if (\"${CMAKE_BUILD_TYPE}\" STREQUAL \"Debug\")\n        set(IMPACTO_EMSCRIPTEN_BUILD_FLAGS \"${IMPACTO_EMSCRIPTEN_BUILD_FLAGS} \\\n            -s GL_ASSERTIONS=1 \\\n            -s GL_DEBUG=1 \\\n        \")\n    else ()\n        set(IMPACTO_EMSCRIPTEN_BUILD_FLAGS \"${IMPACTO_EMSCRIPTEN_BUILD_FLAGS} \\\n            -O3 \\\n        \")\n    endif ()\n    set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} ${IMPACTO_EMSCRIPTEN_BUILD_FLAGS}\")\n    set(CMAKE_C_FLAGS \"${CMAKE_C_FLAGS} ${IMPACTO_EMSCRIPTEN_BUILD_FLAGS}\")\n    set(CMAKE_EXE_LINKER_FLAGS \"${CMAKE_EXE_LINKER_FLAGS} ${IMPACTO_EMSCRIPTEN_BUILD_FLAGS}\")\n\n    string(REPLACE \"-O2\" \"\" CMAKE_CXX_FLAGS_RELEASE \"${CMAKE_CXX_FLAGS_RELEASE}\")\n    string(REPLACE \"-O2\" \"\" CMAKE_C_FLAGS_RELEASE \"${CMAKE_C_FLAGS_RELEASE}\")\n    string(REPLACE \"-O2\" \"\" CMAKE_EXE_LINKER_FLAGS_RELEASE \"${CMAKE_EXE_LINKER_FLAGS_RELEASE}\")\nelse ()\n    find_package(SDL2 CONFIG REQUIRED)\n    find_package(ZLIB REQUIRED)\n\n    set(SDL2_LIBRARIES \n        \"$<TARGET_NAME_IF_EXISTS:SDL2::SDL2main>\"\n        \"$<IF:$<TARGET_EXISTS:SDL2::SDL2>,SDL2::SDL2,SDL2::SDL2-static>\"\n    )\n    if (NX)\n        pkg_check_modules(ogg REQUIRED IMPORTED_TARGET ogg)\n        pkg_check_modules(vorbis REQUIRED IMPORTED_TARGET vorbis)\n        pkg_check_modules(vorbisfile REQUIRED IMPORTED_TARGET vorbisfile)\n        pkg_check_modules(libwebp REQUIRED IMPORTED_TARGET libwebp)\n        list(APPEND Impacto_Libs\n            ${SDL2_LIBRARIES}\n            ZLIB::ZLIB\n            PkgConfig::vorbisfile\n            PkgConfig::vorbis\n            PkgConfig::ogg\n            PkgConfig::libwebp\n            EGL\n            glapi\n            drm_nouveau\n            nx\n        )\n    else ()\n        find_package(Ogg CONFIG REQUIRED)\n        find_package(Vorbis CONFIG REQUIRED)\n        find_package(WebP CONFIG REQUIRED)\n\n        list(APPEND Impacto_Libs\n            ${SDL2_LIBRARIES}\n            ZLIB::ZLIB\n            WebP::webp WebP::webpdecoder WebP::webpdemux\n            Ogg::ogg\n            Vorbis::vorbis\n            Vorbis::vorbisfile\n        )\n\n        add_definitions(-DIMPACTO_OPENAL_HAVE_ALEXT)\n    endif ()\nendif ()\n\nlist(APPEND Impacto_Include_Dirs ${SDL2_INCLUDE_DIRS})\nlist(APPEND Impacto_Include_Dirs ${WebP_INCLUDE_DIRS})\n\nif (VCPKG_TOOLCHAIN)\n    find_package(fmt CONFIG REQUIRED)\nendif ()\nlist(APPEND Impacto_Libs fmt::fmt)\n\nlist(APPEND Impacto_Libs\n        pugixml::pugixml\n        atrac9\n        unordered_dense::unordered_dense\n        magic_enum\n        glm::glm\n        utf8cpp\n        concurrentqueue\n        readerwriterqueue\n)\n\nif (NOT DEFINED IMPACTO_DISABLE_LIBASS)\n    list(APPEND Impacto_Src src/subtitle/ass/subtitlerenderer.cpp)\n    list(APPEND Impacto_Header src/subtitle/ass/subtitlerenderer.h)\n    \n    pkg_check_modules(ass REQUIRED IMPORTED_TARGET GLOBAL libass)\n    # Libass links libc++ which overrides the static stllib linkage \n    if(ANDROID)\n        get_target_property(ASS_LINK_LIBS PkgConfig::ass INTERFACE_LINK_LIBRARIES)\n        set(CLEANED_ASS_LINK_LIBS \"\")\n        foreach(LIB_PATH IN LISTS ASS_LINK_LIBS)\n            get_filename_component(LIB_NAME \"${LIB_PATH}\" NAME)\n            \n            # Check if the filename starts with \"libc++.\" or \"libc++_\"\n            if(NOT (\"${LIB_NAME}\" MATCHES \"^libc\\\\+\\\\+\\\\.\" OR \"${LIB_NAME}\" MATCHES \"^libc\\\\+\\\\+_\"))\n                list(APPEND CLEANED_ASS_LINK_LIBS \"${LIB_PATH}\")\n            endif()\n        endforeach()\n\n        set_target_properties(PkgConfig::ass PROPERTIES INTERFACE_LINK_LIBRARIES \"${CLEANED_ASS_LINK_LIBS}\")\n    endif()\n    list(APPEND Impacto_Libs\n        PkgConfig::ass\n    )\n\nendif()\n\nif (NOT DEFINED IMPACTO_DISABLE_IMGUI)\n    set(imgui_src\n        ${imgui_SOURCE_DIR}/imgui.cpp\n        ${imgui_SOURCE_DIR}/imgui_demo.cpp\n        ${imgui_SOURCE_DIR}/imgui_draw.cpp\n        ${imgui_SOURCE_DIR}/imgui_tables.cpp\n        ${imgui_SOURCE_DIR}/imgui_widgets.cpp\n        ${imgui_SOURCE_DIR}/misc/cpp/imgui_stdlib.cpp\n        ${imgui_SOURCE_DIR}/backends/imgui_impl_sdl2.cpp\n    )\n    set(imgui_Include_Dirs\n        ${imgui_SOURCE_DIR}\n        ${imgui_SOURCE_DIR}/backends\n    )\n\n    list(APPEND Impacto_Src src/debugmenu.cpp)\n    list(APPEND Impacto_Header src/debugmenu.h)\n\n    if (NOT DEFINED IMPACTO_DISABLE_OPENGL)\n        list(APPEND imgui_src\n            vendor/imgui_custom/backends/imgui_impl_opengl3.cpp\n        )\n        list(APPEND imgui_Include_Dirs\n            vendor/imgui_custom/backends\n        )\n    endif ()\n\n    if (NOT DEFINED IMPACTO_DISABLE_VULKAN)\n        list(APPEND imgui_src\n            ${imgui_SOURCE_DIR}/backends/imgui_impl_vulkan.cpp\n        )\n    endif ()\n\n    if (NOT DEFINED IMPACTO_DISABLE_DX9)\n        list(APPEND imgui_src\n            ${imgui_SOURCE_DIR}/backends/imgui_impl_dx9.cpp\n        )\n    endif ()\n\n    add_library(imgui STATIC ${imgui_src})\n    target_include_directories(imgui\n        PUBLIC ${imgui_Include_Dirs}\n        PRIVATE ${SDL2_INCLUDE_DIRS}\n    )\n    target_link_libraries(imgui PRIVATE ${SDL2_LIBRARIES})\n    list(APPEND Impacto_Lib_Targets imgui)\nendif ()\n\nif (NOT DEFINED IMPACTO_DISABLE_OPENGL)\n    list(APPEND Impacto_Src\n        src/renderer/opengl/window.cpp\n        src/renderer/opengl/renderer.cpp\n        src/renderer/opengl/shader.cpp\n        src/renderer/opengl/glc.cpp\n        src/renderer/opengl/yuvframe.cpp\n        src/renderer/opengl/nv12frame.cpp\n        src/renderer/opengl/3d/renderable3d.cpp\n        src/renderer/opengl/3d/scene.cpp\n\n        vendor/glad/src/glad.c\n    )\n    list(APPEND Impacto_Header\n        src/renderer/opengl/window.h\n        src/renderer/opengl/renderer.h\n        src/renderer/opengl/shader.h\n        src/renderer/opengl/glc.h\n        src/renderer/opengl/yuvframe.h\n        src/renderer/opengl/nv12frame.h\n        src/renderer/opengl/3d/renderable3d.h\n        src/renderer/opengl/3d/scene.h\n    )\n\nendif ()\n\nif (NOT DEFINED IMPACTO_DISABLE_FFMPEG)\n    list(APPEND Impacto_Src\n        src/video/ffmpegplayer.cpp\n        src/video/ffmpegstream.cpp\n        src/subtitle/ffmpegsubtitlehelper.cpp\n    )\n    list(APPEND Impacto_Header\n        src/video/ffmpegplayer.h\n        src/video/ffmpegstream.h\n        src/subtitle/ffmpegsubtitlehelper.h\n    )\n\n    if (NOT VCPKG_TOOLCHAIN)\n        if(NOT BUILD_SHARED_LIBS)\n            set(AV_ENABLE_SHARED OFF CACHE BOOL \"Enable shared library build (Off)\" FORCE)            \n        endif()\n        set(AV_DISABLE_AVDEVICE ON CACHE INTERNAL \"Disable FFMPEG AVDevice\")\n        set(AV_DISABLE_AVFILTER ON CACHE INTERNAL \"Disable FFMPEG AVFilter\")\n        set(AV_BUILD_EXAMPLES OFF CACHE INTERNAL \"Build AVCPP Examples\")\n        FetchContent_Declare(\n            avcpp\n            GIT_REPOSITORY \"https://github.com/h4tr3d/avcpp\"\n            GIT_TAG \"6bf8c76b120cf2cc448d706ea22f112017803605\"\n            SYSTEM\n            GIT_SUBMODULES \"\"\n            PATCH_COMMAND ${AVCPP_PATCH}\n            UPDATE_DISCONNECTED 1\n        )\n\n        FetchContent_MakeAvailable(avcpp)\n        pkg_check_modules(FFmpeg REQUIRED IMPORTED_TARGET \n            libavcodec \n            libavutil \n            libswscale\n            libswresample\n            libavformat \n        )\n\n        list(APPEND Impacto_Libs\n            avcpp\n            PkgConfig::FFmpeg\n        )\n    else ()\n        find_package(FFMPEG REQUIRED)\n        list(APPEND Impacto_Include_Dirs ${FFMPEG_INCLUDE_DIRS})\n        list(APPEND Impacto_Libs ${FFMPEG_LIBRARIES})\n\n        find_package(avcpp CONFIG REQUIRED)\n        list(APPEND Impacto_Libs\n            avcpp::avcpp\n            avcpp::FFmpeg\n        )\n        \n    endif ()\n    list(APPEND Impacto_Include_Dirs ${FFMPEG_INCLUDE_DIRS})\n    list(APPEND Impacto_Libs ${FFMPEG_LIBRARIES})\n    pkg_check_modules(dav1d REQUIRED IMPORTED_TARGET dav1d)\n    list(APPEND Impacto_Libs PkgConfig::dav1d)\n    if(ANDROID)\n        find_library(mediandk-lib mediandk)\n        list(APPEND Impacto_Libs ${mediandk-lib})\n    endif()\nendif ()\n\nif (NOT DEFINED IMPACTO_DISABLE_MSPACK)\n    add_library(mspack STATIC\n        vendor/mspack/lzxd.c\n        vendor/mspack/system.c\n    )\n    target_include_directories(mspack PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/vendor/mspack)\n    list(APPEND Impacto_Lib_Targets mspack)\n\n    list(APPEND Impacto_Src src/io/lzxstream.cpp)\n    list(APPEND Impacto_Header src/io/lzxstream.h)\nendif ()\n\nif (NOT DEFINED IMPACTO_DISABLE_VULKAN)\n    list(APPEND Impacto_Src\n        src/renderer/vulkan/window.cpp\n        src/renderer/vulkan/renderer.cpp\n        src/renderer/vulkan/pipeline.cpp\n        src/renderer/vulkan/utils.cpp\n        src/renderer/vulkan/yuvframe.cpp\n        src/renderer/vulkan/nv12frame.cpp\n        src/renderer/vulkan/3d/renderable3d.cpp\n        src/renderer/vulkan/3d/scene.cpp\n    )\n    list(APPEND Impacto_Header\n        src/renderer/vulkan/window.h\n        src/renderer/vulkan/renderer.h\n        src/renderer/vulkan/pipeline.h\n        src/renderer/vulkan/utils.h\n        src/renderer/vulkan/yuvframe.h\n        src/renderer/vulkan/nv12frame.h\n        src/renderer/vulkan/3d/renderable3d.h\n        src/renderer/vulkan/3d/scene.h\n    )\n\n    find_package(Vulkan REQUIRED)\n\n    list(APPEND Impacto_Libs\n            ${Vulkan_LIBRARIES}\n    )\n    list(APPEND Impacto_Include_Dirs ${Vulkan_INCLUDE_DIRS})\nendif ()\n\nif (NOT DEFINED IMPACTO_DISABLE_DX9)\n    list(APPEND Impacto_Src\n        src/renderer/dx9/renderer.cpp\n        src/renderer/dx9/window.cpp\n        src/renderer/dx9/shader.cpp\n        src/renderer/dx9/yuvframe.cpp\n        src/renderer/dx9/nv12frame.cpp\n        src/renderer/dx9/3d/scene.cpp\n        src/renderer/dx9/3d/renderable3d.cpp\n    )\n    list(APPEND Impacto_Header\n        src/renderer/dx9/utils.h\n        src/renderer/dx9/renderer.h\n        src/renderer/dx9/window.h\n        src/renderer/dx9/shader.h\n        src/renderer/dx9/yuvframe.h\n        src/renderer/dx9/nv12frame.h\n        src/renderer/dx9/3d/scene.h\n        src/renderer/dx9/3d/renderable3d.h\n    )\n\n    list(APPEND Impacto_Libs\n            d3d9\n            d3dcompiler\n    )\nendif ()\n\nif(ANDROID)\n    add_library(impacto SHARED ${Impacto_Src} ${Impacto_Header})\n    target_link_options(impacto PRIVATE \"LINKER:--build-id=sha1\")\nelse()\n    add_executable(impacto ${Impacto_Src} ${Impacto_Header})\nendif()\n\nset_property(TARGET impacto PROPERTY COMPILE_WARNING_AS_ERROR ON)\n\nif(MSVC)\n    set_property(TARGET impacto PROPERTY WIN32_EXECUTABLE TRUE)\nendIf()\n\nlist(APPEND Impacto_Libs ${Impacto_Lib_Targets})\ntarget_link_libraries(impacto PRIVATE ${Impacto_Libs})\n\ntarget_compile_options(impacto PRIVATE \"$<$<C_COMPILER_ID:MSVC>:/utf-8>\")\ntarget_compile_options(impacto PRIVATE \"$<$<CXX_COMPILER_ID:MSVC>:/utf-8>\")\n\nif (IMPACTO_WARNINGS)\n    if (CMAKE_CXX_COMPILER_ID STREQUAL \"Clang\" OR CMAKE_CXX_COMPILER_ID STREQUAL \"AppleClang\")\n        message(STATUS \"Enabling warnings for clang\")\n        target_compile_options(impacto PRIVATE -Wall -Wextra -Wno-unused-parameter -Wno-nullability-completeness)\n    elseif (CMAKE_CXX_COMPILER_ID STREQUAL \"GNU\")\n        message(STATUS \"Enabling warnings for GCC\")\n        target_compile_options(impacto PRIVATE -Wall -Wextra -Wno-unused-parameter)\n    elseif (CMAKE_CXX_COMPILER_ID STREQUAL \"MSVC\")\n        message(STATUS \"Enabling warnings for MSVC\")\n        target_compile_options(impacto PRIVATE /W4 /external:anglebrackets /external:W0 /wd4100 /wd4324 /w15038 /w44388 /w44062)\n    else ()\n        message(WARNING \"Unknown compiler, not enabling warnings\")\n    endif ()\nelse ()\n    message(STATUS \"NOT enabling compiler warnings, consider enabling them when developing\")\nendif ()\n\n# compiler/dependency configuration\ntarget_compile_options(impacto PRIVATE ${Impacto_Compile_Options})\nset_property(TARGET impacto ${Impacto_Lib_Targets} PROPERTY CXX_STANDARD 20)\nset_property(TARGET impacto PROPERTY CXX_SCAN_FOR_MODULES OFF)\n\n# Hot reload for MSVC\nif(NOT IMPACTO_ASAN)\n    set_property(TARGET impacto PROPERTY MSVC_DEBUG_INFORMATION_FORMAT \"$<IF:$<AND:$<C_COMPILER_ID:MSVC>,$<CXX_COMPILER_ID:MSVC>>,$<$<CONFIG:Debug,RelWithDebInfo>:EditAndContinue>,$<$<CONFIG:Debug,RelWithDebInfo>:ProgramDatabase>>\")\nendif()\n\nif (EMSCRIPTEN)\n    set(CMAKE_EXECUTABLE_SUFFIX \".html\")\nendif ()\n\nif (NX)\n    add_definitions(-DENV64BIT)\nelse ()\n    if (CMAKE_SIZEOF_VOID_P EQUAL 8)\n        add_definitions(-DENV64BIT)\n    else ()\n        add_definitions(-DENV32BIT)\n        endif ()\nendif ()\n\nadd_definitions(-DGLM_FORCE_RADIANS) # lol\n\n# target configuration\n\nif (\"${CMAKE_BUILD_TYPE}\" STREQUAL \"Debug\")\n    set(IMPACTO_ENABLE_SLOW_LOG_DEFAULT ON)\n    set(IMPACTO_GL_DEBUG_DEFAULT ON)\nelse ()\n    set(IMPACTO_ENABLE_SLOW_LOG_DEFAULT OFF)\n    set(IMPACTO_GL_DEBUG_DEFAULT OFF)\nendif ()\n\noption(IMPACTO_ENABLE_SLOW_LOG\n\"Compile log statements that get hit very frequently or are in a hot path\"\n${IMPACTO_ENABLE_SLOW_LOG_DEFAULT})\noption(IMPACTO_GL_DEBUG\n\"Use an OpenGL debug context and log messages\"\n${IMPACTO_GL_DEBUG_DEFAULT})\n\nif (EMSCRIPTEN)\n    set(IMPACTO_HAVE_THREADS OFF)\n    set(IMPACTO_USE_SDL_HIGHDPI ON)\nelse ()\n    set(IMPACTO_HAVE_THREADS ON)\n    set(IMPACTO_USE_SDL_HIGHDPI OFF)\nendif ()\n\n# Enable Hot Reload for MSVC compilers if supported.\nif (POLICY CMP0141)\n  cmake_policy(SET CMP0141 NEW)\n  set(CMAKE_MSVC_DEBUG_INFORMATION_FORMAT \"$<IF:$<AND:$<C_COMPILER_ID:MSVC>,$<CXX_COMPILER_ID:MSVC>>,$<$<CONFIG:Debug,RelWithDebInfo>:EditAndContinue>,$<$<CONFIG:Debug,RelWithDebInfo>:ProgramDatabase>>\")\nendif()\n\nconfigure_file(src/config.h.in ${PROJECT_BINARY_DIR}/include/config.h)\ntarget_include_directories(impacto SYSTEM BEFORE PRIVATE ${Impacto_Include_Dirs})\ntarget_include_directories(impacto PRIVATE ${PROJECT_BINARY_DIR}/include)\n\ntarget_precompile_headers(impacto PRIVATE \n    \"$<$<COMPILE_LANGUAGE:CXX>:${CMAKE_SOURCE_DIR}/src/pch.h>\"\n)\n\n# binary install\n\nif (ANDROID)\n    install(TARGETS impacto LIBRARY DESTINATION ${CMAKE_SOURCE_DIR}/android/app/src/main/jniLibs/${CMAKE_ANDROID_ARCH_ABI})\nelseif (NX)\n    install(TARGETS impacto RUNTIME DESTINATION .)\nelseif (WIN32)\n    install(TARGETS impacto RUNTIME DESTINATION .)\n    install(FILES $<TARGET_RUNTIME_DLLS:impacto> DESTINATION .)\n    x_vcpkg_install_local_dependencies(TARGETS impacto DESTINATION .)\nelse()\n    if (APPLE)\n        set_property(\n            TARGET impacto\n            PROPERTY INSTALL_RPATH\n            \"@loader_path/\"\n            \"@loader_path/lib\"\n        )\n    elseif (LINUX)\n        set_property(\n            TARGET impacto\n            PROPERTY INSTALL_RPATH\n            \"$ORIGIN/\"\n            \"$ORIGIN/lib\"\n            )  \n    endif()\n\n    LIST(APPEND post_exclude_regexes \"^/lib\" \"^/usr\" \"^/bin\" \"libvulkan\\\\.so(\\\\..*)?\")\n    install(TARGETS impacto atrac9\n        RUNTIME_DEPENDENCIES\n        POST_EXCLUDE_REGEXES ${post_exclude_regexes}\n        RUNTIME DESTINATION .\n        LIBRARY DESTINATION ./lib\n        FRAMEWORK DESTINATION ./lib\n    )\nendif ()\n\n# asset install\n\nif (NOT ANDROID)\n    install(DIRECTORY src/shaders DESTINATION .)\n    install(DIRECTORY profiles DESTINATION .)\n    install(DIRECTORY games DESTINATION .)\nelse()\n    install(DIRECTORY src/shaders DESTINATION ${CMAKE_SOURCE_DIR}/android/app/src/main/assets)\n    install(DIRECTORY profiles DESTINATION ${CMAKE_SOURCE_DIR}/android/app/src/main/assets)\n    install(DIRECTORY games DESTINATION ${CMAKE_SOURCE_DIR}/android/app/src/main/assets PATTERN \"*/gamedata/*\" EXCLUDE)\nendif()\n\n\n"
  },
  {
    "path": "CMakePresets.json",
    "content": "{\n  \"version\": 3,\n  \"cmakeMinimumRequired\": {\n    \"major\": 3,\n    \"minor\": 25,\n    \"patch\": 0\n  },\n  \"configurePresets\": [\n    {\n      \"name\": \"Base\",\n      \"description\": \"Default Base Configuration\",\n      \"hidden\": true,\n      \"generator\": \"Ninja\",\n      \"binaryDir\": \"${sourceDir}/impacto-build/${presetName}\",\n      \"installDir\": \"${sourceDir}/install/${presetName}\",\n      \"toolchainFile\": \"$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake\",\n      \"cacheVariables\": {\n        \"CMAKE_BUILD_TYPE\": \"Debug\",\n        \"CMAKE_EXPORT_COMPILE_COMMANDS\": \"YES\",\n        \"VCPKG_OVERLAY_PORTS\": \"${sourceDir}/portfiles\",\n        \"IMPACTO_WARNINGS\": \"ON\"\n      }\n    },\n    {\n      \"name\": \"Release\",\n      \"description\": \"Default Release Build\",\n      \"hidden\": false,\n      \"inherits\": [\n        \"Base\"\n      ],\n      \"cacheVariables\": {\n        \"CMAKE_BUILD_TYPE\": \"RelWithDebInfo\"\n      }\n    },\n    {\n      \"name\": \"Debug\",\n      \"description\": \"Default Debug Build\",\n      \"hidden\": false,\n      \"inherits\": [\n        \"Base\"\n      ],\n      \"cacheVariables\": {\n        \"CMAKE_BUILD_TYPE\": \"Debug\"\n      }\n    },\n    {\n      \"name\": \"ci-release\",\n      \"description\": \"Release build for CI\",\n      \"hidden\": false,\n      \"generator\": \"Ninja\",\n      \"binaryDir\": \"ci-build/${presetName}\",\n      \"installDir\": \"release/${presetName}\",\n      \"toolchainFile\": \"$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake\",\n      \"cacheVariables\": {\n        \"CMAKE_BUILD_TYPE\": \"Release\",\n        \"VCPKG_OVERLAY_TRIPLETS\": \"${sourceDir}/triplets\",\n        \"VCPKG_OVERLAY_PORTS\": \"${sourceDir}/portfiles\",\n        \"IMPACTO_WARNINGS\": \"ON\"\n      }\n    },\n    {\n      \"name\": \"ci-release-android\",\n      \"description\": \"Android Release build for CI\",\n      \"hidden\": false,\n      \"inherits\": [\n        \"ci-release\"\n      ],\n      \"cacheVariables\": {\n        \"VCPKG_TARGET_TRIPLET\":\"arm64-android-ci\",\n        \"VCPKG_CHAINLOAD_TOOLCHAIN_FILE\":\"$env{VCPKG_ROOT}/scripts/toolchains/android.cmake\",\n        \"CMAKE_FIND_ROOT_PATH_MODE_LIBRARY\": \"BOTH\",\n        \"CMAKE_FIND_ROOT_PATH_MODE_INCLUDE\": \"BOTH\",\n        \"CMAKE_FIND_ROOT_PATH_MODE_PACKAGE\": \"BOTH\",\n        \"ANDROID_PLATFORM\":\"android-$env{MINSDKVERSION}\",\n        \"ANDROID_ABI\": \"arm64-v8a\",\n        \"ANDROID_STL\": \"c++_static\"\n      }\n    }\n  ],\n  \"buildPresets\": [\n    {\n      \"name\": \"ci-release\",\n      \"description\": \"x64 Release\",\n      \"configurePreset\": \"ci-release\",\n      \"targets\": [\n        \"install\"\n      ]\n    },\n    {\n      \"name\": \"ci-release-android\",\n      \"description\": \"x64 Release Android\",\n      \"configurePreset\": \"ci-release-android\",\n      \"targets\": [\n        \"install\"\n      ]\n    },\n    {\n      \"name\": \"x64-Release\",\n      \"description\": \"x64 Release with Debug Symbols\",\n      \"configurePreset\": \"Release\",\n      \"targets\": [\n        \"install\"\n      ]\n    },\n    {\n      \"name\": \"x64-Debug\",\n      \"description\": \"x64 Debug\",\n      \"configurePreset\": \"Debug\",\n      \"targets\": [\n        \"install\"\n      ]\n    }\n  ]\n}"
  },
  {
    "path": "HorizonNX.toolchain",
    "content": "\nset(CMAKE_SYSTEM_NAME \"Generic\")\nset(WITH_PORTLIBS ON CACHE BOOL \"use portlibs ?\")\n\nmacro(msys_to_cmake_path MsysPath ResultingPath)\n\tif(WIN32)\n\t\tstring(REGEX REPLACE \"^/([a-zA-Z])/\" \"\\\\1:/\" ${ResultingPath} \"${MsysPath}\")\n\telse()\n\t\tset(${ResultingPath} \"${MsysPath}\")\n\tendif()\nendmacro()\n\nmsys_to_cmake_path(\"$ENV{DEVKITPRO}\" DEVKITPRO)\n\nset(NX 1)\nlist(APPEND CMAKE_PREFIX_PATH \"${DEVKITPRO}/portlibs/switch/lib/cmake\")\n\nif(WIN32)\n set(CMAKE_C_COMPILER \"${DEVKITPRO}/devkitA64/bin/aarch64-none-elf-gcc.exe\")\n set(CMAKE_CXX_COMPILER \"${DEVKITPRO}/devkitA64/bin/aarch64-none-elf-g++.exe\")\n set(CMAKE_AR \"${DEVKITPRO}/devkitA64/bin/aarch64-none-elf-gcc-ar.exe\" CACHE STRING \"\")\n set(CMAKE_RANLIB \"${DEVKITPRO}/devkitA64/bin/aarch64-none-elf-gcc-ranlib.exe\" CACHE STRING \"\")\nelse()\n  set(CMAKE_C_COMPILER \"${DEVKITPRO}/devkitA64/bin/aarch64-none-elf-gcc\")\n  set(CMAKE_CXX_COMPILER \"${DEVKITPRO}/devkitA64/bin/aarch64-none-elf-g++\")\n  set(CMAKE_AR \"${DEVKITPRO}/devkitA64/bin/aarch64-none-elf-gcc-ar\" CACHE STRING \"\")\n  set(CMAKE_RANLIB \"${DEVKITPRO}/devkitA64/bin/aarch64-none-elf-gcc-ranlib\" CACHE STRING \"\")\nendif()\n\nset(PKG_CONFIG_EXECUTABLE \"${DEVKITPRO}/portlibs/switch/bin/aarch64-none-elf-pkg-config\" CACHE STRING \"\")\nset(CMAKE_C_FLAGS \"${CPPFLAGS} -march=armv8-a -mtune=cortex-a57 -mtp=soft -fPIC -ffunction-sections\" CACHE STRING \"C flags\")\nset(CMAKE_CXX_FLAGS \"${CPPFLAGS} ${CMAKE_C_FLAGS}\" CACHE STRING \"C++ flags\")\n\nif(WITH_PORTLIBS)\n    set(CMAKE_FIND_ROOT_PATH ${DEVKITPRO}/devkitA64 ${DEVKITPRO} ${DEVKITPRO}/libnx ${DEVKITPRO}/portlibs/switch)\nelse()\n    set(CMAKE_FIND_ROOT_PATH ${DEVKITPRO}/devkitA64 ${DEVKITPRO}/libnx ${DEVKITPRO})\nendif()\n\nset(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)\nset(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)\nset(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)\nset(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)\n#set(CMAKE_STATIC_LINKER_FLAGS_INIT \"-march=armv8-a -mtune=cortex-a57 -mtp=soft -L${DEVKITPRO}/libnx/lib -L${DEVKITPRO}/portlibs/switch/lib\")\nset(CMAKE_EXE_LINKER_FLAGS_INIT \"-specs=${DEVKITPRO}/libnx/switch.specs -march=armv8-a -mtune=cortex-a57 -mtp=soft -fPIE -L${DEVKITPRO}/libnx/lib -L${DEVKITPRO}/portlibs/switch/lib\")\n\nset(BUILD_SHARED_LIBS OFF CACHE INTERNAL \"Shared libs not available\")"
  },
  {
    "path": "LICENSE",
    "content": "Copyright (c) 2024 Committee of Zero and contributors\n\nPermission to use, copy, modify, and/or distribute this software for\nany purpose with or without fee is hereby granted, provided that the\nabove copyright notice and this permission notice appear in all copies.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\nWITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\nMERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\nANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\nWHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\nACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR\nIN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# impacto [![impacto](https://github.com/CommitteeOfZero/impacto/actions/workflows/impacto.yml/badge.svg)](https://github.com/CommitteeOfZero/impacto/actions/workflows/impacto.yml)\n\n**impacto** is an open-source **reimplementation** of the **\"MAGES.\" visual novel engine** in C++ and OpenGL. Using the original data files, impacto can run any supported game on any supported platform.\n\n## Status\n\n**impacto is in active development**. At current stage it should be possible to reach all endings in at least one supported game - \"Memories Off 6 \\~T-Wave\\~.\" However, some functionality and architectural design are still missing and **no games are fully complete yet**. Refer to [#1](https://github.com/CommitteeOfZero/impacto/issues/1) for supported games and remaining work.\n\nimpacto is currently being developed for **64-bit Windows 10 and desktop Linux PCs** but we've successfully experimented on macOS, Android (ARM), Switch (homebrew and Linux) and HTML5/WebAssembly (via Emscripten) and full official Android support is planned. OpenGL is used for the graphics renderer with experimental Vulkan and DirectX 9 options available.\n\n## Building\n\nFor building on Windows with Visual Studio 2019 or newer, please refer to the [building instructions](doc/vs_build.md).\n\nFor building on Linux, see the [instructions for Ubuntu Desktop](doc/ubuntu_build.md) and adapt to your distribution if necessary.\n\nMore platforms and toolchains are known to work.\n\n## Contributing\n\n**We are looking for contributors!** Check out the [Getting Started guide](doc/getting_started.md) for pointers on setting up games for testing, finding your way around the codebase and adding functionality. Also check out the [Contributor guide](doc/contributor_guide.md) for important information on the code style you should follow.\n\nThere is work to be done for C++ programmers of any skill level in a wide range of subjects, on game engine architecture (design and implementation), reverse-engineering the original (looking at just the game's outside behaviour as a *black box* or inspecting the internals through a *white box*), replicating it, improving on it with new functionality, documenting our efforts, fixing bugs and polish. \n\nIf you're interested, [come join our Discord](https://discord.gg/rq4GGCh) to discuss ideas and help you get into it.\n\n## Legal stuff\n\nimpacto source code as a whole is released under the liberal [ISC license](LICENSE). Some parts are based on or copied from third-party code under various licenses. Binary distributions or parts thereof may fall under more restrictive licensing terms. See [THIRDPARTY.md](THIRDPARTY.md) for details and attribution.\n\n"
  },
  {
    "path": "THIRDPARTY.md",
    "content": "# Third-party component index\n\n**impacto contains third-party code at the following locations in the source distribution:**\n\n* `src/audio/adxaudiostream.cpp`: based on [vgmstream](https://github.com/losnoco/vgmstream) ADX implementation\n* `src/io/cpkarchive.cpp`: CRILAYLA decompression based on work by [tpu](https://forum.xentax.com/viewtopic.php?f=21&t=5137&hilit=CRILAYLA) and [hcs64/vgm_ripping/utf_tab](https://github.com/hcs64/vgm_ripping/tree/master/multi/utf_tab)\n* `src/texture/bcdecode.cpp`: based on [bcndecode](https://github.com/ifeherva/bcndecode)\n* `src/texture/bntxloader.cpp`: Unswizzling code based on [Ryujinx](https://github.com/Ryujinx/Ryujinx)\n* `src/texture/gxtloader.cpp`: Unswizzling code based on [Scarlet](https://github.com/xdanieldzd/Scarlet/blob/d8aabf430307d35a81b131e40bb3c9a4828bdd7b/Scarlet/Drawing/ImageBinary.cs) and work by [FireyFly](http://xen.firefly.nu/up/rearrange.c.html) and [ryg](https://fgiesen.wordpress.com/2009/12/13/decoding-morton-codes/)\n* `src/texture/s3tc.cpp`: based on [s3tc-dxt-decompression](https://github.com/Benjamin-Dobell/s3tc-dxt-decompression)\n* `src/texture/ddsloader.cpp`: based on [OpenImageIO](https://github.com/OpenImageIO/oiio/tree/master/src/dds.imageio)\n* `src/video/ffmpegplayer.cpp`: syncing code based on [FFplay](https://github.com/FFmpeg/FFmpeg/blob/master/fftools/ffplay.c)\n* `vendor/clHCA`: part of [vgmstream](https://github.com/losnoco/vgmstream)\n* `vendor/minilua`: [minilua](https://github.com/edubart/minilua)\n* `vendor/glad`: output from [glad](https://github.com/Dav1dde/glad) generator, patched for Switch support\n* `vendor/include/stb_image.h`: [stb_image](https://github.com/nothings/stb)\n* `vendor/imgui`: [Dear ImGui](https://github.com/ocornut/imgui)\n* `vendor/pcg`: [PCG Random Number Generation, Minimal C Edition](https://github.com/imneme/pcg-c-basic)\n* `vendor/squish`: [Squish](http://sjbrown.co.uk/?code=squish)\n* `vendor/utf8-cpp`: [UTF8-CPP](https://github.com/nemtrif/utfcpp)\n* `vendor/mspack`: [libmspack](https://www.cabextract.org.uk/libmspack/), only includes LZX decompressor to reduce code size\n* `vendor/mio`: [mio](https://github.com/vimpunk/mio), except for platforms without mmap support\n\nAll third-party code mentioned above is mandatory, included in the build process and compiled into the output executable for impacto on every supported platform and build configuration.\n\n**Additionally, impacto depends on the following external libraries:**\n\n* Platform implementations of C runtime, C++ STL, OpenAL and OpenGL (ES)\n* [glm](https://github.com/g-truc/glm/)\n* [LibAtrac9](https://github.com/Thealexbarney/LibAtrac9)\n* [Ogg](https://github.com/xiph/ogg)\n* [SDL2](https://libsdl.org/download-2.0.php)\n* [Vorbis](https://github.com/xiph/vorbis)\n* [zlib](https://github.com/madler/zlib)\n* [libass](https://github.com/libass/libass)\n* [avcpp](https://github.com/h4tr3d/avcpp)\n* [concurrent_queue](https://github.com/cameron314/concurrentqueue)\n* [readerwriterqueue](https://github.com/cameron314/readerwriterqueue)\n* [fmtlib](https://github.com/fmtlib/fmt)\n* [pugixml](https://github.com/zeux/pugixml)\n* [UTF8-CPP](https://github.com/nemtrif/utfcpp)\n* [magic_enum](https://github.com/Neargye/magic_enum)\n\n**Sourcing and linkage:**\n\n* Refer to individual toolchain/platform documentation for standard library licenses and default external library sourcing behaviour.\n* Emscripten, NX, Android builds use static linking for all other external libraries, other platforms use dynamic linking.\n* Some external dependencies (due to being header only libraries) are always compiled into the output executable.\n* The example Emscripten build process explicitly uses emscripten-ports packages for [Ogg](https://github.com/emscripten-ports/Ogg), [SDL2](https://github.com/emscripten-ports/SDL2), [Vorbis](https://github.com/emscripten-ports/Vorbis) and [zlib](https://github.com/emscripten-ports/zlib).\n* Win32/MSVC, Linux, Android, Mac OS X build processes explicitly uses vcpkg packages listed in vcpkg.json, and the original authors' source distributions for other dependencies.\n* Win32 install targets copy all required DLLs except the standard C/C++ runtime libraries to the output folder.\n\n**Binary licensing:**\n\n* Unless specified otherwise, impacto source and binaries are covered by [the ISC license](LICENSE) but contain and use the dependencies listed above, requiring third-party copyright notices.\n* NX builds statically link OpenAL Soft (assuming this is used as the OpenAL implementation), placing NX binaries under LGPLv2 or later.\n* Android builds statically link FFmpeg, placing those binaries under LGPLv2. To disable FFmpeg, use define -DIMPACTO_DISABLE_FFMPEG.\n* All builds ship with libmspack in the impacto executable, placing it under LGPLv2 or later. If you wish to disable libmspack and get rid of LGPLv2 dependency, please use -DIMPACTO_DISABLE_MSPACK\n  define.\n\nLicense statements for third-party code in this repository (where given) and external libraries bundled by the build process in at least one supported configuration follow:\n\n# Statements\n\n## bcndecode\n\nhttps://github.com/ifeherva/bcndecode/blob/5bc7043002d7b2485c857624f5ef6f55576ba8b4/src/bcndecode.c\n\n>     decoder for DXTn-compressed data\n>\n>     Format documentation:\n>       http://oss.sgi.com/projects/ogl-sample/registry/EXT/texture_compression_s3tc.txt\n>\n>     The contents of this file are in the public domain (CC0)\n>     Full text of the CC0 license:\n>       https://creativecommons.org/publicdomain/zero/1.0/\n>\n>     To test:\n>       compile: gcc -Iinclude -DBCN_DECODER_TEST BcnDecode.c -o bcndecode\n>       run: dd bs=1 skip=128 if=bc3_test.dds | ./bcndecode 256 256 3 1 > bc3_test.png\n\nSee below for license text.\n\n## Magic Enum\n\nhttps://github.com/Neargye/magic_enum\n\nCopyright (c) 2019 - 2024 Daniil Goncharov\n\nSee below for license text(MIT)\n\n## MiniLua\n\nCopyright (c) 1994–2019 Lua.org, PUC-Rio.\nCopyright (c) 2020-2023 Eduardo Bart (https://github.com/edubart).\n\nSee below for license text(MIT)\n\n## Emscripten\n\nhttps://github.com/emscripten-core/emscripten/tree/1.38.21\n\nEmscripten is available under 2 licenses, the MIT license and the University of Illinois/NCSA Open Source License. See below for license texts.\n\nAuthors:\n\n>     The following authors have all licensed their contributions to Emscripten\n>     under the licensing terms detailed in LICENSE.\n>\n>     (Authors keep copyright of their contributions, of course; they just grant\n>     a license to everyone to use it as detailed in LICENSE.)\n>\n>     * Alon Zakai <alonzakai@gmail.com> (copyright owned by Mozilla Foundation)\n>     * Tim Dawborn <tim.dawborn@gmail.com>\n>     * Max Shawabkeh <max99x@gmail.com>\n>     * Sigmund Vik <sigmund_vik@yahoo.com>\n>     * Jeff Terrace <jterrace@gmail.com>\n>     * Benoit Tremblay <trembl.ben@gmail.com>\n>     * Andreas Bergmeier <abergmeier@gmx.net>\n>     * Ben Schwartz <bens@alum.mit.edu>\n>     * David Claughton <dave@eclecticdave.com>\n>     * David Yip <yipdw@member.fsf.org>\n>     * Julien Hamaide <julien.hamaide@gmail.com>\n>     * Ehsan Akhgari <ehsan.akhgari@gmail.com> (copyright owned by Mozilla Foundation)\n>     * Adrian Taylor <adrian@macrobug.com>\n>     * Richard Assar <richard.assar@gmail.com>\n>     * Nathan Hammond <emscripten@nathanhammond.com>\n>     * Behdad Esfahbod <behdad@behdad.org>\n>     * David Benjamin <davidben@mit.edu>\n>     * Pierre Renaux <pierre@talansoft.com>\n>     * Brian Anderson <banderson@mozilla.com>\n>     * Jon Bardin <diclophis@gmail.com>\n>     * Jukka Jylänki <jujjyl@gmail.com>\n>     * Aleksander Guryanov <caiiiycuk@gmail.com>\n>     * Chad Austin <chad@chadaustin.me> (copyright owned by IMVU)\n>     * nandhp <nandhp@gmail.com>\n>     * YeZhongWen <linghuye2.0@gmail.com>\n>     * Xingxing Pan <forandom@gmail.com>\n>     * Justin Kerk <dopefishjustin@gmail.com>\n>     * Andrea Bedini <andrea.bedini@gmail.com>\n>     * James Pike <totoro.friend@chilon.net>\n>     * Mokhtar Naamani <mokhtar.naamani@gmail.com>\n>     * Benjamin Stover <benjamin.stover@gmail.com>\n>     * Riccardo Magliocchetti <riccardo.magliocchetti@gmail.com>\n>     * Janus Troelsen <janus.troelsen@stud.tu-darmstadt.de>\n>     * Lars Schneider <lars.schneider@autodesk.com> (copyright owned by Autodesk, Inc.)\n>     * Joel Martin <github@martintribe.org>\n>     * Manuel Wellmann <manuel.wellmann@autodesk.com> (copyright owned by Autodesk, Inc.)\n>     * Xuejie Xiao <xxuejie@gmail.com>\n>     * Dominic Wong <dom@slowbunyip.org>\n>     * Alan Kligman <alan.kligman@gmail.com> (copyright owned by Mozilla Foundation)\n>     * Anthony Liot <wolfviking0@yahoo.com>\n>     * Michael Riss <Michael.Riss@gmx.de>\n>     * Jasper St. Pierre <jstpierre@mecheye.net>\n>     * Manuel Schölling <manuel.schoelling@gmx.de>\n>     * Bruce Mitchener, Jr. <bruce.mitchener@gmail.com>\n>     * Michael Bishop <mbtyke@gmail.com>\n>     * Roger Braun <roger@rogerbraun.net>\n>     * Vladimir Vukicevic <vladimir@pobox.com> (copyright owned by Mozilla Foundation)\n>     * Lorant Pinter <lorant.pinter@prezi.com>\n>     * Tobias Doerffel <tobias.doerffel@gmail.com>\n>     * Martin von Gagern <martin@von-gagern.net>\n>     * Ting-Yuan Huang <thuang@mozilla.com>\n>     * Joshua Granick <jgranick@blackberry.com>\n>     * Felix H. Dahlke <fhd@ubercode.de>\n>     * Éloi Rivard <azmeuk@gmail.com>\n>     * Alexander Gladysh <ag@logiceditor.com>\n>     * Arlo Breault <arlolra@gmail.com>\n>     * Jacob Lee <artdent@gmail.com> (copyright owned by Google, Inc.)\n>     * Joe Lee <jlee@imvu.com> (copyright owned by IMVU)\n>     * Andy Friesen <andy@imvu.com> (copyright owned by IMVU)\n>     * Bill Welden <bwelden@imvu.com> (copyright owned by IMVU)\n>     * Michael Ey <mey@imvu.com> (copyright owned by IMVU)\n>     * Llorens Marti Garcia <lgarcia@imvu.com> (copyright owned by IMVU)\n>     * Jinsuck Kim <jkim@imvu.com> (copyright owned by IMVU)\n>     * Todd Lee <tlee@imvu.com> (copyright owned by IMVU)\n>     * Anthony Pesch <inolen@gmail.com>\n>     * Robert Bragg <robert.bragg@intel.com> (copyright owned by Intel Corporation)\n>     * Sylvestre Ledru <sylvestre@debian.org>\n>     * Tom Fairfield <fairfield@cs.xu.edu>\n>     * Anthony J. Thibault <ajt@hyperlogic.org>\n>     * John Allwine <jallwine86@gmail.com>\n>     * Martin Gerhardy <martin.gerhardy@gmail.com>\n>     * James Gregory <jgregory@zynga.com> (copyright owned by Zynga, Inc.)\n>     * Dan Gohman <sunfish@google.com> (copyright owned by Google, Inc.)\n>     * Jeff Gilbert <jgilbert@mozilla.com> (copyright owned by Mozilla Foundation)\n>     * Frits Talbot <frits@metapathy.com>\n>     * Onno Jongbloed <hey@onnoj.net>\n>     * Jez Ng <me@jezng.com>\n>     * Marc Feeley <mfeeley@mozilla.com> (copyright owned by Mozilla Foundation)\n>     * Ludovic Perrine <jazzzz@gmail.com>\n>     * David Barksdale <david.barksdale@adcedosolutions.com>\n>     * Manfred Manik Nerurkar <nerurkar*at*made-apps.biz> (copyright owned by MADE, GmbH)\n>     * Joseph Gentle <me@josephg.com>\n>     * Douglas T. Crosher <dtc-moz@scieneer.com> (copyright owned by Mozilla Foundation)\n>     * Douglas T. Crosher <info@jsstats.com> (copyright owned by Scieneer Pty Ltd.)\n>     * Soeren Balko <soeren.balko@gmail.com>\n>     * Ryan Kelly (ryan@rfk.id.au)\n>     * Michael Lelli <toadking@toadking.com>\n>     * Yu Kobayashi <yukoba@accelart.jp>\n>     * Pin Zhang <zhangpin04@gmail.com>\n>     * Nick Bray <ncbray@chromium.org> (copyright owned by Google, Inc.)\n>     * Aidan Hobson Sayers <aidanhs@cantab.net>\n>     * Charlie Birks <admin@daftgames.net>\n>     * Ranger Harke <ranger.harke@autodesk.com> (copyright owned by Autodesk, Inc.)\n>     * Tobias Vrinssen <tobias@vrinssen.de>\n>     * Patrick R. Martin <patrick.martin.r@gmail.com>\n>     * Richard Quirk <richard.quirk@gmail.com>\n>     * Marcos Scriven <marcos@scriven.org>\n>     * Antoine Lambert <antoine.lambert33@gmail.com>\n>     * Daniel Aquino <mr.danielaquino@gmail.com>\n>     * Remi Papillie <remi.papillie@gmail.com>\n>     * Fraser Adams <fraser.adams@blueyonder.co.uk>\n>     * Michael Tirado <icetooth333@gmail.com>\n>     * Ben Noordhuis <info@bnoordhuis.nl>\n>     * Bob Roberts <bobroberts177@gmail.com>\n>     * John Vilk <jvilk@cs.umass.edu>\n>     * Daniel Baulig <dbaulig@fb.com> (copyright owned by Facebook, Inc.)\n>     * Lu Wang <coolwanglu@gmail.com>\n>     * Heidi Pan <heidi.pan@intel.com> (copyright owned by Intel)\n>     * Vasilis Kalintiris <ehostunreach@gmail.com>\n>     * Adam C. Clifton <adam@hulkamaniac.com>\n>     * Volo Zyko <volo.zyko@gmail.com>\n>     * Andre Weissflog <floooh@gmail.com>\n>     * Alexandre Perrot <alexandre.perrot@gmail.com>\n>     * Emerson José Silveira da Costa <emerson.costa@gmail.com>\n>     * Jari Vetoniemi <mailroxas@gmail.com>\n>     * Sindre Sorhus <sindresorhus@gmail.com>\n>     * James S Urquhart <jamesu@gmail.com>\n>     * Boris Gjenero <boris.gjenero@gmail.com>\n>     * jonas echterhoff <jonas@unity3d.com>\n>     * Sami Vaarala <sami.vaarala@iki.fi>\n>     * Jack A. Arrington <jack@epicpineapple.com>\n>     * Richard Janicek <r@janicek.co>\n>     * Joel Croteau <jcroteau@gmail.com>\n>     * Haneef Mubarak <haneef503@gmail.com>\n>     * Nicolas Peri <nicox@shivaengine.com> (copyright owned by ShiVa Technologies, SAS)\n>     * Bernhard Fey <e-male@web.de>\n>     * Dave Nicponski <dave.nicponski@gmail.com>\n>     * Jonathan Jarri <noxalus@gmail.com>\n>     * Daniele Di Proietto <daniele.di.proietto@gmail.com>\n>     * Dan Dascalescu <dNOSPAMdascalescu@gmail.com>\n>     * Thomas Borsos <thomasborsos@gmail.com>\n>     * Ori Avtalion <ori@avtalion.name>\n>     * Guillaume Blanc <guillaumeblanc.sc@gmail.com>\n>     * Usagi Ito <usagi@WonderRabbitProject.net>\n>     * Camilo Polymeris <cpolymeris@gmail.com>\n>     * Markus Henschel <markus.henschel@yager.de>\n>     * Ophir Lojkine <ophir.lojkine@eleves.ec-nantes.fr>\n>     * Ryan Sturgell <ryan.sturgell@gmail.com> (copyright owned by Google, Inc.)\n>     * Jason Green <jason@transgaming.com> (copyright owned by TransGaming, Inc.)\n>     * Ningxin Hu <ningxin.hu@intel.com> (copyright owned by Intel)\n>     * Nicolas Guillemot <nlguillemot@gmail.com>\n>     * Sathyanarayanan Gunasekaran <gsathya.ceg@gmail.com> (copyright owned by Mozilla Foundation)\n>     * Nikolay Vorobyov <nik.vorobyov@gmail.com>\n>     * Jonas Platte <mail@jonasplatte.de>\n>     * Sebastien Ronsse <sronsse@gmail.com>\n>     * Glenn R. Wichman <gwichman@zynga.com>\n>     * Hamish Willee <hamishwillee@gmail.com> (copyright owned by Mozilla Foundation)\n>     * Sylvain Chevalier <sylvain.chevalier@gmail.com>\n>     * Nathan Ross <nross.se@gmail.com>\n>     * Zachary Pomerantz <zmp@umich.edu>\n>     * Boris Tsarev <boristsarev@gmail.com>\n>     * Mark Logan <mark@artillery.com> (copyright owned by Artillery Games, Inc.)\n>     * Коренберг Марк <socketpair@gmail.com>\n>     * Gauthier Billot <gogoprog@gmail.com>\n>     * Árpád Goretity <h2co3@h2co3.org>\n>     * Nicholas Wilson <nicholas@nicholaswilson.me.uk>\n>     * Aaron Mandle <aaronmandle@gmail.com>\n>     * Bailey Hayes <Bailey.Hayes@sas.com> (copyright owned by SAS Institute Inc.)\n>     * Paul Holland <pholland@adobe.com>\n>     * James Long <longster@gmail.com>\n>     * David Anderson <danderson@mozilla.com> (copyright owned by Mozilla Foundation)\n>     * Eric Rannaud <e@nanocritical.com> (copyright owned by Nanocritical Corp.)\n>     * William Furr <wfurr@google.com> (copyright owned by Google, Inc.)\n>     * Dan Glastonbury <dglastonbury@mozilla.com> (copyright owned by Mozilla Foundation)\n>     * Warren Seine <warren.seine@aerys.in> (copyright owned by Aerys SAS)\n>     * Petr Babicka <babcca@gmail.com>\n>     * Akira Takahashi <faithandbrave@gmail.com>\n>     * Victor Costan <costan@gmail.com>\n>     * Pepijn Van Eeckhoudt <pepijn.vaneeckhoudt@luciad.com> (copyright owned by Luciad NV)\n>     * Stevie Trujillo <stevie.trujillo@gmail.com>\n>     * Edward Rudd <urkle@outoforder.cc>\n>     * Rene Eichhorn <rene.eichhorn1@gmail.com>\n>     * Nick Desaulniers <nick@mozilla.com> (copyright owned by Mozilla Foundation)\n>     * Luke Wagner <luke@mozilla.com> (copyright owned by Mozilla Foundation)\n>     * Matt McCormick <matt.mccormick@kitware.com>\n>     * Thaddée Tyl <thaddee.tyl@gmail.com>\n>     * Philipp Wiesemann <philipp.wiesemann@arcor.de>\n>     * Jan Jongboom <janjongboom@gmail.com> (copyright owned by Telenor Digital AS)\n>     * Tiago Quelhas <tiagoq@gmail.com>\n>     * Reinier de Blois <rddeblois@gmail.com>\n>     * Yuichi Nishiwaki <yuichi.nishiwaki@gmail.com>\n>     * Jérôme Bernard <jerome.bernard@ercom.fr> (copyright owned by Ercom)\n>     * Chanhwi Choi <ccwpc@hanmail.net>\n>     * Fábio Santos <fabiosantosart@gmail.com>\n>     * Thibaut Despoulain <thibaut@artillery.com> (copyright owned by Artillery Games, Inc.)\n>     * Wei Tjong Yao <weitjong@gmail.com>\n>     * Tim Guan-tin Chien <timdream@gmail.com>\n>     * Krzysztof Jakubowski <nadult@fastmail.fm>\n>     * Vladimír Vondruš <mosra@centrum.cz>\n>     * Brion Vibber <brion@pobox.com>\n>     * Philip Lafleur <sendsbeak@gmail.com>\n>     * Javier Meseguer de Paz <j.meseguer@gmail.com>\n>     * Michael A. Balazs <michael.balazs@gmail.com>\n>     * Andreas Blixt <me@blixt.nyc>\n>     * Haofeng Zhang <h.z@duke.edu>\n>     * Cody Welsh <codyw@protonmail.com>\n>     * Hoong Ern Ng <hoongern@gmail.com>\n>     * Kagami Hiiragi <kagami@genshiken.org>\n>     * Jan Bölsche <jan@lagomorph.de>\n>     * Sebastian Matthes <sebastianmatthes@outlook.com> (copyright owned by Volkswagen AG)\n>     * Robert Goulet <robert.goulet@autodesk.com> (copyright owned by Autodesk, Inc.)\n>     * Juha Järvi <befunge@gmail.com>\n>     * Louis Lagrange <lagrange.louis@gmail.com>\n>     * Ying-Ruei Liang <thumbd03803@gmail.com>\n>     * Stuart Geipel <lapimlu@gmail.com>\n>     * Yeonjun Lim <yjroot@gmail.com>\n>     * Andrew Karpushin <reven86@gmail.com>\n>     * Felix Zimmermann <fzimmermann89@gmail.com>\n>     * Sven-Hendrik Haase <svenstaro@gmail.com>\n>     * Simon Sandström <simon@nikanor.nu>\n>     * Khaled Sami <k.sami.mohammed@gmail.com>\n>     * Omar El-Mohandes <omar.elmohandes90@gmail.com>\n>     * Florian Rival <florian.rival@gmail.com>\n>     * Mark Achée <mark@achee.com>\n>     * Piotr Paczkowski <kontakt@trzeci.eu>\n>     * Braden MacDonald <braden@bradenmacdonald.com>\n>     * Kevin Cheung <kevin.cheung@autodesk.com> (copyright owned by Autodesk, Inc.)\n>     * Josh Peterson <petersonjm1@gmail.com>\n>     * eska <eska@eska.me>\n>     * Nate Burr <nate.oo@gmail.com>\n>     * Paul \"TBBle\" Hampson <Paul.Hampson@Pobox.com>\n>     * Andreas Plesch <andreasplesch@gmail.com>\n>     * Brian Armstrong <brian.armstrong.ece+github@gmail.com>\n>     * Vincenzo Chianese <vincenz.chianese@icloud.com>\n>     * Noam T.Cohen <noam@ecb.co.il>\n>     * Nick Shin <nick.shin@gmail.com>\n>     * Gregg Tavares <github@greggman.com>\n>     * Tanner Rogalsky <tanner@tannerrogalsky.com>\n>     * Richard Cook <rcook@tableau.com> (copyright owned by Tableau Software, Inc.)\n>     * Arnab Choudhury <achoudhury@tableau.com> (copyright owned by Tableau Software, Inc.)\n>     * Charles Vaughn <cvaughn@tableau.com> (copyright owned by Tableau Software, Inc.)\n>     * Pierre Krieger <pierre.krieger1708@gmail.com>\n>     * Jakob Stoklund Olesen <stoklund@2pi.dk>\n>     * Jérémy Anger <angerj.dev@gmail.com>\n>     * Derek Schuff <dschuff@chromium.org> (copyright owned by Google, Inc.)\n>     * Ashley Sommer <flubba86@gmail.com>\n>     * Dave Fletcher <graveyhead@gmail.com>\n>     * Lars-Magnus Skog <ralphtheninja@riseup.net>\n>     * Pieter Vantorre <pietervantorre@gmail.com>\n>     * Maher Sallam <maher@sallam.me>\n>     * Andrey Burov <burik666@gmail.com>\n>     * Holland Schutte <hgschutte1@gmail.com>\n>     * Kerby Geffrard <kerby.geffrard@gmail.com>\n>     * cynecx <me@cynecx.net>\n>     * Chris Gibson <cgibson@mrvoxel.com>\n>     * Harald Reingruber <code*at*h-reingruber.at>\n>     * Aiden Koss <madd0131@umn.edu>\n>     * Dustin VanLerberghe <good_ol_dv@hotmail.com>\n>     * Philip Bielby <pmb45-github@srcf.ucam.org> (copyright owned by Jagex Ltd.)\n>     * Régis Fénéon <regis.feneon@gmail.com>\n>     * Dominic Chen <d.c.ddcc@gmail.com> (copyright owned by Google, Inc.)\n>     * Junji Hashimoto <junji.hashimoto@gmail.com>\n>     * Heejin Ahn <aheejin@gmail.com> (copyright owned by Google, Inc.)\n>     * Andras Kucsma <andras.kucsma@gmail.com>\n>     * Mateusz Borycki <mateuszborycki@gmail.com>\n>     * Franklin Ta <fta2012@gmail.com>\n>     * Jacob Gravelle <jgravelle@google.com> (copyright owned by Google, Inc.)\n>     * Kagami Sascha Rosylight <saschanaz@outlook.com>\n>     * Benny Jacobs <benny@gmx.it>\n>     * Ray Brown <code@liquibits.com>\n>     * Christopher Serr <christopher.serr@gmail.com>\n>     * Aaron Ruß <aaron.russ@dfki.de> (copyright owned by DFKI GmbH)\n>     * Vilibald Wanča <vilibald@wvi.cz>\n>     * Alex Hixon <alex@alexhixon.com>\n>     * Vladimir Davidovich <thy.ringo@gmail.com>\n>     * Yuriy Levchenko <irov13@mail.ru>\n>     * Dmitry Tolmachov <dmitolm@gmail.com>\n>     * Dylan McKay <me@dylanmckay.io>\n>     * Christophe Gragnic <cgragnic@netc.fr>\n>     * Murphy McCauley <murphy.mccauley@gmail.com>\n>     * Anatoly Trosinenko <anatoly.trosinenko@gmail.com>\n>     * Brad Grantham <grantham@plunk.org>\n>     * Sam Clegg <sbc@chromium.org> (copyright owned by Google, Inc.)\n>     * Joshua Lind <joshualind007@hotmail.com>\n>     * Hiroaki GOTO as \"GORRY\" <gorry@hauN.org>\n>     * Mikhail Kremnyov <mkremnyov@gmail.com> (copyright owned by XCDS International)\n>     * Tasuku SUENAGA a.k.a. gunyarakun <tasuku-s-github@titech.ac>\n>     * Vitorio Miguel Prieto Cilia <vdrbandeiras@gmail.com>\n>     * Evan Wallace <evan.exe@gmail.com>\n>     * Henning Pohl <henning@still-hidden.de>\n>     * Tim Neumann <mail@timnn.me>\n>     * Ondrej Stava <ondrej.stava@gmail.com> (copyright owned by Google, Inc.)\n>     * Jakub Jirutka <jakub@jirutka.cz>\n>     * Loo Rong Jie <loorongjie@gmail.com>\n>     * Jean-François Geyelin <jfgeyelin@gmail.com>\n>     * Matthew Collins <thethinkofdeath@gmail.com>\n>     * Satoshi N. M <snmatsutake@yahoo.co.jp>\n>     * Ryan Speets <ryan@speets.ca>\n>     * Fumiya Chiba <fumiya.chiba@nifty.com>\n>     * Ryan C. Gordon <icculus@icculus.org>\n>     * Inseok Lee <dlunch@gmail.com>\n>     * Yair Levinson (copyright owned by Autodesk, Inc.)\n>     * Matjaž Drolc <mdrolc@gmail.com>\n>     * James Swift <james@3dengineer.com> (copyright owned by PSPDFKit GmbH)\n>     * Ryan Lester <ryan@cyph.com> (copyright owned by Cyph, Inc.)\n>     * Nikolay Zapolnov <zapolnov@gmail.com>\n>     * Nazar Mokrynskyi <nazar@mokrynskyi.com>\n>     * Yury Delendik <ydelendik@mozilla.com> (copyright owned by Mozilla Foundation)\n>     * Kenneth Perry <thothonegan@gmail.com>\n>     * Jim Mussared <jim.mussared@gmail.com>\n>     * Dirk Vanden Boer <dirk.vdb@gmail.com>\n>     * Mitchell Foley <mitchfoley@google.com> (copyright owned by Google, Inc.)\n>     * Oleksandr Chekhovskyi <oleksandr.chekhovskyi@gmail.com>\n>     * Michael Siebert <michael.siebert2k@gmail.com>\n>     * Jonathan Hale <squareys@googlemail.com>\n>     * Etienne Brateau <etienne.brateau@gmail.com>\n>     * Zhiming Wang <zmwangx@gmail.com>\n>     * Jameson Ernst <jameson@jpernst.com>\n>     * Yoan Lecoq <yoanlecoq.io@gmail.com>\n>     * Jiajie Hu <jiajie.hu@intel.com> (copyright owned by Intel Corporation)\n>     * Kamil Klimek <naresh@tlen.pl>\n>     * José Carlos Pujol <josecpujol(at)gmail.com>\n>     * Dannii Willis <curiousdannii@gmail.com>\n>     * Erik Dubbelboer <erik@dubbelboer.com>\n>     * Sergey Tsatsulin <tsatsulin@gmail.com>\n>     * varkor <github@varkor.com>\n>     * Stuart Knightley <website@stuartk.com>\n>     * Amadeus Guo<gliheng@foxmail.com>\n>     * Nathan Froyd <froydnj@gmail.com> (copyright owned by Mozilla Foundation)\n>     * Daniel Wirtz <dcode@dcode.io>\n>     * Kibeom Kim <kk1674@nyu.edu>\n>     * Marcel Klammer <m.klammer@tastenkunst.com>\n>     * Axel Forsman <axelsfor@gmail.com>\n>     * Ebrahim Byagowi <ebrahim@gnu.org>\n>     * Thorsten Möller <thorsten.moeller@sbi.ch>\n>     * Michael Droettboom <mdroettboom@mozilla.com>\n>     * Nicolas Bouquet <hgy01@hieroglyphe.net>\n>     * Miguel Saldivar <miguel.saldivar22@hotmail.com>\n>     * Gert Van Hoey <gert.vanhoey@gmail.com>\n>     * Valtteri Heikkilä <rnd@nic.fi>\n>     * Daniel McNab <daniel.mcnab6+emcc(at)gmail.com>\n>     * Tyler Limkemann <tslimkemann42 gmail.com>\n>     * Ben Smith <binji@google.com> (copyright owned by Google, Inc.)\n>     * Sylvain Beucler <beuc@beuc.net>\n>     * Patrik Weiskircher <patrik@weiskircher.name>\n>     * Tobias Widlund <widlundtobias(at)gmail.com>\n>     * Rob Fors <mail@robfors.com>\n>     * Mike Frysinger <vapier@chromium.org> (copyright owned by Google, Inc.)\n>     * Sébasiten Crozet <developer@crozet.re>\n>     * Andrey Nagikh <andrey@nagih.ru>\n>     * Dzmitry Malyshau <dmalyshau@mozilla.com> (copyright owned by Mozilla Foundation)\n>     * Bjorn Swenson <tie.372@gmail.com>\n>     * Ryhor Spivak <grisha@rusteddreams.net>\n>     * Jan Schär <jscissr@gmail.com>\n>     * Ryhor Spivak <grisha@rusteddreams.net>\n>     * Alexander Bich <quyse0@gmail.com>\n>     * Ashleigh Thomas <ashleighbcthomas@gmail.com>\n>     * Veniamin Petrenko <bjpbjpbjp10@gmail.com>\n>     * Ian Henderson <ian@ianhenderson.org>\n>     * Siim Kallas <siimkallas@gmail.com>\n>     * Carl Woffenden <cwoffenden@gmail.com> (copyright owned by Numfum GmbH)\n>     * Patrick Berger <patrick.berger@xmail.net> (copyright owned by Compusoft Group)\n>     * Alexander Frank Lehmann <alexander.frank.lehmann@compusoftgroup.com> (copyright owned by Compusoft Group)\n>     * Tommy Nguyen <tn0502@gmail.com>\n>     * Thomas Schander <info@thomasschander.com> (copyright owned by Enscape GmbH)\n>     * Benjamin S. Rodgers <acdimalev@gmail.com>\n>     * Paul Shapiro <paul@mymonero.com>\n>     * Elmo Todurov <elmo.todurov@eesti.ee>\n>     * Zoltán Žarkov <zeko@freecivweb.org>\n>     * Roman Yurchak <rth.yurchak@pm.me>\n>     * Hampton Maxwell <me@hamptonmaxwell.com>\n>     * Eric Fiselier <ericwf@google.com> (copyright owned by Google, Inc.)\n>     * Sirapop Wongstapornpat <sirapop.wongstapornpat@student.oulu.fi>\n>     * Matt Kane <m@mk.gg>\n>     * Altan Özlü <altanozlu7@gmail.com>\n>     * Mary S <ipadlover8322@gmail.com>\n>     * Martin Birks <mbirks@gmail.com>\n>     * Kirill Smelkov <kirr@nexedi.com> (copyright owned by Nexedi)\n>     * Lutz Hören <laitch383@gmail.com>\n>     * Pedro K Custodio <git@pedrokcustodio.com>\n\nEmscripten dependencies:\n\n>     This program uses portions of Node.js source code located in src/library_path.js,\n>     in accordance with the terms of the MIT license. Node's license follows:\n\n(See below for license text. node.js is \"Copyright Joyent, Inc. and other Node contributors. All rights reserved.\")\n\n>     The musl libc project is bundled in this repo, and it has the MIT license, see\n\nEmscripten 1.38.21 / musl 1.1.15 system/lib/libc/musl/COPYRIGHT:\n\n>     musl as a whole is licensed under the following standard MIT license:\n>\n>     ----------------------------------------------------------------------\n>     Copyright © 2005-2014 Rich Felker, et al.\n>\n>     Permission is hereby granted, free of charge, to any person obtaining\n>     a copy of this software and associated documentation files (the\n>     \"Software\"), to deal in the Software without restriction, including\n>     without limitation the rights to use, copy, modify, merge, publish,\n>     distribute, sublicense, and/or sell copies of the Software, and to\n>     permit persons to whom the Software is furnished to do so, subject to\n>     the following conditions:\n>\n>     The above copyright notice and this permission notice shall be\n>     included in all copies or substantial portions of the Software.\n>\n>     THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n>     EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n>     MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\n>     IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\n>     CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\n>     TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n>     SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n>     ----------------------------------------------------------------------\n>\n>     Authors/contributors include:\n>\n>     Alex Dowad\n>     Alexander Monakov\n>     Anthony G. Basile\n>     Arvid Picciani\n>     Bobby Bingham\n>     Boris Brezillon\n>     Brent Cook\n>     Chris Spiegel\n>     Clément Vasseur\n>     Daniel Micay\n>     Denys Vlasenko\n>     Emil Renner Berthing\n>     Felix Fietkau\n>     Felix Janda\n>     Gianluca Anzolin\n>     Hauke Mehrtens\n>     Hiltjo Posthuma\n>     Isaac Dunham\n>     Jaydeep Patil\n>     Jens Gustedt\n>     Jeremy Huntwork\n>     Jo-Philipp Wich\n>     Joakim Sindholt\n>     John Spencer\n>     Josiah Worcester\n>     Justin Cormack\n>     Khem Raj\n>     Kylie McClain\n>     Luca Barbato\n>     Luka Perkov\n>     M Farkas-Dyck (Strake)\n>     Mahesh Bodapati\n>     Michael Forney\n>     Natanael Copa\n>     Nicholas J. Kain\n>     orc\n>     Pascal Cuoq\n>     Petr Hosek\n>     Pierre Carrier\n>     Rich Felker\n>     Richard Pennington\n>     Shiz\n>     sin\n>     Solar Designer\n>     Stefan Kristiansson\n>     Szabolcs Nagy\n>     Timo Teräs\n>     Trutz Behn\n>     Valentin Ochs\n>     William Haddon\n>\n>     Portions of this software are derived from third-party works licensed\n>     under terms compatible with the above MIT license:\n>\n>     The TRE regular expression implementation (src/regex/reg* and\n>     src/regex/tre*) is Copyright © 2001-2008 Ville Laurikari and licensed\n>     under a 2-clause BSD license (license text in the source files). The\n>     included version has been heavily modified by Rich Felker in 2012, in\n>     the interests of size, simplicity, and namespace cleanliness.\n>\n>     Much of the math library code (src/math/* and src/complex/*) is\n>     Copyright © 1993,2004 Sun Microsystems or\n>     Copyright © 2003-2011 David Schultz or\n>     Copyright © 2003-2009 Steven G. Kargl or\n>     Copyright © 2003-2009 Bruce D. Evans or\n>     Copyright © 2008 Stephen L. Moshier\n>     and labelled as such in comments in the individual source files. All\n>     have been licensed under extremely permissive terms.\n>\n>     The ARM memcpy code (src/string/arm/memcpy_el.S) is Copyright © 2008\n>     The Android Open Source Project and is licensed under a two-clause BSD\n>     license. It was taken from Bionic libc, used on Android.\n>\n>     The implementation of DES for crypt (src/crypt/crypt_des.c) is\n>     Copyright © 1994 David Burren. It is licensed under a BSD license.\n>\n>     The implementation of blowfish crypt (src/crypt/crypt_blowfish.c) was\n>     originally written by Solar Designer and placed into the public\n>     domain. The code also comes with a fallback permissive license for use\n>     in jurisdictions that may not recognize the public domain.\n>\n>     The smoothsort implementation (src/stdlib/qsort.c) is Copyright © 2011\n>     Valentin Ochs and is licensed under an MIT-style license.\n>\n>     The BSD PRNG implementation (src/prng/random.c) and XSI search API\n>     (src/search/*.c) functions are Copyright © 2011 Szabolcs Nagy and\n>     licensed under following terms: \"Permission to use, copy, modify,\n>     and/or distribute this code for any purpose with or without fee is\n>     hereby granted. There is no warranty.\"\n>\n>     The x86_64 port was written by Nicholas J. Kain and is licensed under\n>     the standard MIT terms.\n>\n>     The mips and microblaze ports were originally written by Richard\n>     Pennington for use in the ellcc project. The original code was adapted\n>     by Rich Felker for build system and code conventions during upstream\n>     integration. It is licensed under the standard MIT terms.\n>\n>     The mips64 port was contributed by Imagination Technologies and is\n>     licensed under the standard MIT terms.\n>\n>     The powerpc port was also originally written by Richard Pennington,\n>     and later supplemented and integrated by John Spencer. It is licensed\n>     under the standard MIT terms.\n>\n>     All other files which have no copyright comments are original works\n>     produced specifically for use as part of this library, written either\n>     by Rich Felker, the main author of the library, or by one or more\n>     contibutors listed above. Details on authorship of individual files\n>     can be found in the git version control history of the project. The\n>     omission of copyright and license comments in each file is in the\n>     interest of source tree size.\n>\n>     In addition, permission is hereby granted for all public header files\n>     (include/* and arch/*/bits/*) and crt files intended to be linked into\n>     applications (crt/*, ldso/dlstart.c, and arch/*/crt_arch.h) to omit\n>     the copyright notice and permission notice otherwise required by the\n>     license, and to use these files without any requirement of\n>     attribution. These files include substantial contributions from:\n>\n>     Bobby Bingham\n>     John Spencer\n>     Nicholas J. Kain\n>     Rich Felker\n>     Richard Pennington\n>     Stefan Kristiansson\n>     Szabolcs Nagy\n>\n>     all of whom have explicitly granted such permission.\n>\n>     This file previously contained text expressing a belief that most of\n>     the files covered by the above exception were sufficiently trivial not\n>     to be subject to copyright, resulting in confusion over whether it\n>     negated the permissions granted in the license. In the spirit of\n>     permissive licensing, and of not having licensing issues being an\n>     obstacle to adoption, that text has been removed.\n\n## unordered_dense\n\nhttps://github.com/martinus/unordered_dense\n\nCopyright (c) 2022 Martin Leitner-Ankerl\nSee below for license text (MIT).\n\n## glad\n\nGenerated with https://github.com/Dav1dde/glad/tree/v0.1.27\n\n>     What's the license of glad generated code? #101\n>     Any of Public Domain, WTFPL or CC0.\n\nSee below for license text (CC0).\n\n## glm\n\nhttps://github.com/g-truc/glm/tree/0.9.9.3 or system version\n\nCopyright (c) 2005 - G-Truc Creation. GLM can be distributed and/or modified under the terms of either a) The Happy Bunny License, or b) the MIT License. See below for license text (MIT).\n\n## LibAtrac9\n\nhttps://github.com/Thealexbarney/LibAtrac9 or system version\n\nCopyright (c) 2018 Alex Barney. See below for license text (MIT).\n\n## Dear ImGui\n\nhttps://github.com/ocornut/imgui\n\nCopyright (c) 2014-2024 Omar Cornut. See below for license text (MIT).\n\n## Ogg\n\nsystem version, originally https://github.com/xiph/ogg\n\n>     Copyright (c) 2002, Xiph.org Foundation\n>\n>     Redistribution and use in source and binary forms, with or without\n>     modification, are permitted provided that the following conditions\n>     are met:\n>\n>     - Redistributions of source code must retain the above copyright\n>     notice, this list of conditions and the following disclaimer.\n>\n>     - Redistributions in binary form must reproduce the above copyright\n>     notice, this list of conditions and the following disclaimer in the\n>     documentation and/or other materials provided with the distribution.\n>\n>     - Neither the name of the Xiph.org Foundation nor the names of its\n>     contributors may be used to endorse or promote products derived from\n>     this software without specific prior written permission.\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 FOUNDATION\n>     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## OpenAL Soft\n\nsystem version, originally https://github.com/kcat/openal-soft\n\n>     OpenAL cross platform audio library\n>     Copyright (C) 1999-2007 by authors.\n>     This library is free software; you can redistribute it and/or\n>      modify it under the terms of the GNU Library 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>     This library 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 GNU\n>      Library General Public License for more details.\n>\n>     You should have received a copy of the GNU Library General Public\n>      License along with this library; if not, write to the\n>      Free Software Foundation, Inc.,\n>      51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n>     Or go to http://www.gnu.org/copyleft/lgpl.html\n\nSee below for license text (LGPLv2).\n\n## OpenImageIO\n\nhttps://github.com/OpenImageIO/oiio/blob/master/LICENSE.md\n\nCopyright (c) 2008-present by Contributors to the OpenImageIO project.\nAll Rights Reserved.\n\nSee below for license text (BSD 3-Clause License).\n\n## PCG Random Number Generation, Minimal C Edition\n\nhttps://github.com/imneme/pcg-c-basic/tree/bc39cd76ac3d541e618606bcc6e1e5ba5e5e6aa3\n\nCopyright 2014 Melissa O'Neill <oneill@pcg-random.org>\n\nLicensed under the Apache License, Version 2.0. See below for license text.\n\n# Ryujinx\n\nhttps://github.com/Ryujinx/Ryujinx/tree/eee639d6ba544fa5dd9352426d55e91bc54e157d/Ryujinx.Graphics/Graphics3d/Texture\n\nCopyright (c) Ryujinx Team and Contributors.\n\nSee below for license text (MIT).\n\n## s3tc-dxt-decompression\n\nhttps://github.com/Benjamin-Dobell/s3tc-dxt-decompression/blob/5f923ecc1553aa60058d7011360cefca73e8b0fd/s3tc.cpp\n\nCopyright (c) 2009 Benjamin Dobell, Glass Echidna.\n\nSee below for license text (MIT).\n\n## Scarlet\n\nhttps://github.com/xdanieldzd/Scarlet/blob/d8aabf430307d35a81b131e40bb3c9a4828bdd7b/Scarlet/Drawing/ImageBinary.cs\n\nCopyright (c) 2016 xdaniel (Daniel R.) / DigitalZero Domain\n\nSee below for license text (MIT).\n\n## SDL2\n\nsystem version, originally https://libsdl.org/download-2.0.php\n\nCopyright (C) 1997-2019 Sam Lantinga <slouken@libsdl.org>\n\nSee below for license text (zlib).\n\n## mio\n\nhttps://github.com/vimpunk/mio/blob/master/single_include/mio/mio.hpp\n\nCopyright (c) 2018 https://github.com/mandreyel/\n\nSee below for license text (MIT).\n\n## stb_image\n\nhttps://github.com/nothings/stb/blob/5d90a8375a1393abc0dc5f8cc9f1ac9fd9ae6530/stb_image.h\n\n>     ------------------------------------------------------------------------------\n>     This software is available under 2 licenses -- choose whichever you prefer.\n>     ------------------------------------------------------------------------------\n>     ALTERNATIVE A - MIT License\n>     Copyright (c) 2017 Sean Barrett\n\nSee below for license text.\n\n## Squish\n\nhttps://github.com/OpenImageIO/oiio/blob/master/src/dds.imageio/squish/README\n\n>     The squish library is distributed under the terms and conditions of the MIT\n>     license. This license is specified at the top of each source file and must be\n>     preserved in its entirety.\n\nSee below for license text.\n\n## vgm_ripping\n\nhttps://github.com/hcs64/vgm_ripping/tree/f460d548859d59dd03b2c3f29ed97e2dbf9bb5b3/multi/utf_tab\n\nCopyright (c) 2014 Adam Gashlin.\n\nSee below for license text (MIT).\n\n## vgmstream\n\nhttps://github.com/losnoco/vgmstream/tree/ec0043bf6b816c817ee786458f314f2808fd5846\n\n>     Copyright (c) 2008-2010 Adam Gashlin, Fastelbja, Ronny Elfert\n>\n>     Portions Copyright (c) 2004-2008, Marko Kreen\n>     Portions Copyright 2001-2007  jagarl / Kazunori Ueno <jagarl@creator.club.ne.jp>\n>     Portions Copyright (c) 1998, Justin Frankel/Nullsoft Inc.\n>     Portions Copyright (C) 2006 Nullsoft, Inc.\n>     Portions Copyright (c) 2005-2007 Paul Hsieh\n>     Portions Public Domain originating with Sun Microsystems\n\nSee below for license text (ISC).\n\n## Vorbis\n\nsystem version, originally https://github.com/xiph/vorbis\n\n>     Copyright (c) 2002-2018 Xiph.org Foundation\n>\n>     Redistribution and use in source and binary forms, with or without\n>     modification, are permitted provided that the following conditions\n>     are met:\n>\n>     - Redistributions of source code must retain the above copyright\n>     notice, this list of conditions and the following disclaimer.\n>\n>     - Redistributions in binary form must reproduce the above copyright\n>     notice, this list of conditions and the following disclaimer in the\n>     documentation and/or other materials provided with the distribution.\n>\n>     - Neither the name of the Xiph.org Foundation nor the names of its\n>     contributors may be used to endorse or promote products derived from\n>     this software without specific prior written permission.\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 FOUNDATION\n>     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## zlib\n\nsystem version, originally https://www.zlib.net/\n\nCopyright (C) 1995-2017 Jean-loup Gailly and Mark Adler\n\nSee below for license text (zlib).\n\n## FFmpeg\n\nhttps://www.ffmpeg.org/\n\nSee below for license text (LGPLv2).\n\n## UTF8-CPP\n\nhttps://github.com/nemtrif/utfcpp\n\nSee below for license text (BSL-1.0)\n\n## avcpp\n\nCopyright (c) 2013-2017, Alexander Drozdov <adrozdoff@gmail.com>\nAll rights reserved.\n\nSee below for license text (BSD 3-Clause License).\n\nCopyright 2006 Nemanja Trifunovic\n\n>     Permission is hereby granted, free of charge, to any person or organization\n>     obtaining a copy of the software and accompanying documentation covered by\n>     this license (the \"Software\") to use, reproduce, display, distribute,\n>     execute, and transmit the Software, and to prepare derivative works of the\n>     Software, and to permit third-parties to whom the Software is furnished to\n>     do so, all subject to the following:\n>     \n>     The copyright notices in the Software and this entire statement, including\n>     the above license grant, this restriction and the following disclaimer,\n>     must be included in all copies of the Software, in whole or in part, and\n>     all derivative works of the Software, unless such copies or derivative\n>     works are solely in the form of machine-executable object code generated by\n>     a source language processor.\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, TITLE AND NON-INFRINGEMENT. IN NO EVENT\n>     SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\n>     FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\n>     ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n>     DEALINGS IN THE SOFTWARE.\n\n## libmspack\n\nhttps://www.cabextract.org.uk/libmspack/\n\nSee below for license text (LGPLv2).\n\n## pugixml\n\nhttps://github.com/zeux/pugixml\n\nCopyright (c) 2006-2024 Arseny Kapoulkine\n\nSee below for license text (MIT).\n\n## fmtlib\n\nhttps://github.com/fmtlib/fmt\n\nCopyright (c) 2012 - present, Victor Zverovich and {fmt} contributors\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n\"Software\"), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\nLIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\nWITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n--- Optional exception to the license ---\n\nAs an exception, if, as a result of your compiling your source code, portions\nof this Software are embedded into a machine-executable object form of such\nsource code, you may redistribute such embedded portions in such object form\nwithout including the above copyright and permission notices.\n\n## concurrent_queue & readerwriterqueue\n\nhttps://github.com/cameron314/concurrentqueue\nhttps://github.com/cameron314/readerwriterqueue\n\nSimplified BSD License:\n\nCopyright (c) 2013-2016, Cameron Desrochers. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:\n\n    Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.\n    Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n## libass\nhttps://github.com/libass/libass\n\nSee below for license text (ISC)\n\n# Generic license texts\n\n## 2-clause BSD license\n\n>     Redistribution and use in source and binary forms, with or without modification,\n>     are permitted provided that the following conditions are met:\n>\n>     1. Redistributions of source code must retain the above copyright notice, this\n>     list of conditions and the following disclaimer.\n>\n>     2. Redistributions in binary form must reproduce the above copyright notice,\n>     this list of conditions and the following disclaimer in the documentation and/or\n>     other materials provided with the distribution.\n>\n>     THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\n>     ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n>     WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\n>     DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR\n>     ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n>     (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\n>     LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON\n>     ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n>     (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n>     SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n## 3-clause BSD license\n\n>     Redistribution and use in source and binary forms, with or without\n>     modification, are permitted provided that the following conditions are met:\n>\n>     1. Redistributions of source code must retain the above copyright notice, this\n>        list of conditions and the following disclaimer.\n>\n>     2. Redistributions in binary form must reproduce the above copyright notice,\n>        this list of conditions and the following disclaimer in the documentation\n>        and/or other materials provided with the distribution.\n>\n>     3. Neither the name of the copyright holder nor the names of its\n>        contributors may be used to endorse or promote products derived from\n>        this software without specific prior written permission.\n>\n>     THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n>     AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n>     IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\n>     DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\n>     FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n>     DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\n>     SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\n>     CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\n>     OR TORT (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## Apache License, Version 2.0\n\n>                                     Apache License\n>                               Version 2.0, January 2004\n>                            http://www.apache.org/licenses/\n>\n>       TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n>\n>       1. Definitions.\n>\n>          \"License\" shall mean the terms and conditions for use, reproduction,\n>          and distribution as defined by Sections 1 through 9 of this document.\n>\n>          \"Licensor\" shall mean the copyright owner or entity authorized by\n>          the copyright owner that is granting the License.\n>\n>          \"Legal Entity\" shall mean the union of the acting entity and all\n>          other entities that control, are controlled by, or are under common\n>          control with that entity. For the purposes of this definition,\n>          \"control\" means (i) the power, direct or indirect, to cause the\n>          direction or management of such entity, whether by contract or\n>          otherwise, or (ii) ownership of fifty percent (50%) or more of the\n>          outstanding shares, or (iii) beneficial ownership of such entity.\n>\n>          \"You\" (or \"Your\") shall mean an individual or Legal Entity\n>          exercising permissions granted by this License.\n>\n>          \"Source\" form shall mean the preferred form for making modifications,\n>          including but not limited to software source code, documentation\n>          source, and configuration files.\n>\n>          \"Object\" form shall mean any form resulting from mechanical\n>          transformation or translation of a Source form, including but\n>          not limited to compiled object code, generated documentation,\n>          and conversions to other media types.\n>\n>          \"Work\" shall mean the work of authorship, whether in Source or\n>          Object form, made available under the License, as indicated by a\n>          copyright notice that is included in or attached to the work\n>          (an example is provided in the Appendix below).\n>\n>          \"Derivative Works\" shall mean any work, whether in Source or Object\n>          form, that is based on (or derived from) the Work and for which the\n>          editorial revisions, annotations, elaborations, or other modifications\n>          represent, as a whole, an original work of authorship. For the purposes\n>          of this License, Derivative Works shall not include works that remain\n>          separable from, or merely link (or bind by name) to the interfaces of,\n>          the Work and Derivative Works thereof.\n>\n>          \"Contribution\" shall mean any work of authorship, including\n>          the original version of the Work and any modifications or additions\n>          to that Work or Derivative Works thereof, that is intentionally\n>          submitted to Licensor for inclusion in the Work by the copyright owner\n>          or by an individual or Legal Entity authorized to submit on behalf of\n>          the copyright owner. For the purposes of this definition, \"submitted\"\n>          means any form of electronic, verbal, or written communication sent\n>          to the Licensor or its representatives, including but not limited to\n>          communication on electronic mailing lists, source code control systems,\n>          and issue tracking systems that are managed by, or on behalf of, the\n>          Licensor for the purpose of discussing and improving the Work, but\n>          excluding communication that is conspicuously marked or otherwise\n>          designated in writing by the copyright owner as \"Not a Contribution.\"\n>\n>          \"Contributor\" shall mean Licensor and any individual or Legal Entity\n>          on behalf of whom a Contribution has been received by Licensor and\n>          subsequently incorporated within the Work.\n>\n>       2. Grant of Copyright License. Subject to the terms and conditions of\n>          this License, each Contributor hereby grants to You a perpetual,\n>          worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n>          copyright license to reproduce, prepare Derivative Works of,\n>          publicly display, publicly perform, sublicense, and distribute the\n>          Work and such Derivative Works in Source or Object form.\n>\n>       3. Grant of Patent License. Subject to the terms and conditions of\n>          this License, each Contributor hereby grants to You a perpetual,\n>          worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n>          (except as stated in this section) patent license to make, have made,\n>          use, offer to sell, sell, import, and otherwise transfer the Work,\n>          where such license applies only to those patent claims licensable\n>          by such Contributor that are necessarily infringed by their\n>          Contribution(s) alone or by combination of their Contribution(s)\n>          with the Work to which such Contribution(s) was submitted. If You\n>          institute patent litigation against any entity (including a\n>          cross-claim or counterclaim in a lawsuit) alleging that the Work\n>          or a Contribution incorporated within the Work constitutes direct\n>          or contributory patent infringement, then any patent licenses\n>          granted to You under this License for that Work shall terminate\n>          as of the date such litigation is filed.\n>\n>       4. Redistribution. You may reproduce and distribute copies of the\n>          Work or Derivative Works thereof in any medium, with or without\n>          modifications, and in Source or Object form, provided that You\n>          meet the following conditions:\n>\n>          (a) You must give any other recipients of the Work or\n>              Derivative Works a copy of this License; and\n>\n>          (b) You must cause any modified files to carry prominent notices\n>              stating that You changed the files; and\n>\n>          (c) You must retain, in the Source form of any Derivative Works\n>              that You distribute, all copyright, patent, trademark, and\n>              attribution notices from the Source form of the Work,\n>              excluding those notices that do not pertain to any part of\n>              the Derivative Works; and\n>\n>          (d) If the Work includes a \"NOTICE\" text file as part of its\n>              distribution, then any Derivative Works that You distribute must\n>              include a readable copy of the attribution notices contained\n>              within such NOTICE file, excluding those notices that do not\n>              pertain to any part of the Derivative Works, in at least one\n>              of the following places: within a NOTICE text file distributed\n>              as part of the Derivative Works; within the Source form or\n>              documentation, if provided along with the Derivative Works; or,\n>              within a display generated by the Derivative Works, if and\n>              wherever such third-party notices normally appear. The contents\n>              of the NOTICE file are for informational purposes only and\n>              do not modify the License. You may add Your own attribution\n>              notices within Derivative Works that You distribute, alongside\n>              or as an addendum to the NOTICE text from the Work, provided\n>              that such additional attribution notices cannot be construed\n>              as modifying the License.\n>\n>          You may add Your own copyright statement to Your modifications and\n>          may provide additional or different license terms and conditions\n>          for use, reproduction, or distribution of Your modifications, or\n>          for any such Derivative Works as a whole, provided Your use,\n>          reproduction, and distribution of the Work otherwise complies with\n>          the conditions stated in this License.\n>\n>       5. Submission of Contributions. Unless You explicitly state otherwise,\n>          any Contribution intentionally submitted for inclusion in the Work\n>          by You to the Licensor shall be under the terms and conditions of\n>          this License, without any additional terms or conditions.\n>          Notwithstanding the above, nothing herein shall supersede or modify\n>          the terms of any separate license agreement you may have executed\n>          with Licensor regarding such Contributions.\n>\n>       6. Trademarks. This License does not grant permission to use the trade\n>          names, trademarks, service marks, or product names of the Licensor,\n>          except as required for reasonable and customary use in describing the\n>          origin of the Work and reproducing the content of the NOTICE file.\n>\n>       7. Disclaimer of Warranty. Unless required by applicable law or\n>          agreed to in writing, Licensor provides the Work (and each\n>          Contributor provides its Contributions) on an \"AS IS\" BASIS,\n>          WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n>          implied, including, without limitation, any warranties or conditions\n>          of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n>          PARTICULAR PURPOSE. You are solely responsible for determining the\n>          appropriateness of using or redistributing the Work and assume any\n>          risks associated with Your exercise of permissions under this License.\n>\n>       8. Limitation of Liability. In no event and under no legal theory,\n>          whether in tort (including negligence), contract, or otherwise,\n>          unless required by applicable law (such as deliberate and grossly\n>          negligent acts) or agreed to in writing, shall any Contributor be\n>          liable to You for damages, including any direct, indirect, special,\n>          incidental, or consequential damages of any character arising as a\n>          result of this License or out of the use or inability to use the\n>          Work (including but not limited to damages for loss of goodwill,\n>          work stoppage, computer failure or malfunction, or any and all\n>          other commercial damages or losses), even if such Contributor\n>          has been advised of the possibility of such damages.\n>\n>       9. Accepting Warranty or Additional Liability. While redistributing\n>          the Work or Derivative Works thereof, You may choose to offer,\n>          and charge a fee for, acceptance of support, warranty, indemnity,\n>          or other liability obligations and/or rights consistent with this\n>          License. However, in accepting such obligations, You may act only\n>          on Your own behalf and on Your sole responsibility, not on behalf\n>          of any other Contributor, and only if You agree to indemnify,\n>          defend, and hold each Contributor harmless for any liability\n>          incurred by, or claims asserted against, such Contributor by reason\n>          of your accepting any such warranty or additional liability.\n>\n>       END OF TERMS AND CONDITIONS\n>\n>       APPENDIX: How to apply the Apache License to your work.\n>\n>          To apply the Apache License to your work, attach the following\n>          boilerplate notice, with the fields enclosed by brackets \"{}\"\n>          replaced with your own identifying information. (Don't include\n>          the brackets!)  The text should be enclosed in the appropriate\n>          comment syntax for the file format. We also recommend that a\n>          file or class name and description of purpose be included on the\n>          same \"printed page\" as the copyright notice for easier\n>          identification within third-party archives.\n>\n>       Copyright {yyyy} {name of copyright owner}\n>\n>       Licensed under the Apache License, Version 2.0 (the \"License\");\n>       you may not use this file except in compliance with the License.\n>       You may obtain a copy of the License at\n>\n>           http://www.apache.org/licenses/LICENSE-2.0\n>\n>       Unless required by applicable law or agreed to in writing, software\n>       distributed under the License is distributed on an \"AS IS\" BASIS,\n>       WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n>       See the License for the specific language governing permissions and\n>       limitations under the License.\n\n## Boost Software License, Version 1.0\n\n>     Boost Software License - Version 1.0 - August 17th, 2003\n>\n>     Permission is hereby granted, free of charge, to any person or organization\n>     obtaining a copy of the software and accompanying documentation covered by\n>     this license (the \"Software\") to use, reproduce, display, distribute,\n>     execute, and transmit the Software, and to prepare derivative works of the\n>     Software, and to permit third-parties to whom the Software is furnished to\n>     do so, all subject to the following:\n>\n>     The copyright notices in the Software and this entire statement, including\n>     the above license grant, this restriction and the following disclaimer,\n>     must be included in all copies of the Software, in whole or in part, and\n>     all derivative works of the Software, unless such copies or derivative\n>     works are solely in the form of machine-executable object code generated by\n>     a source language processor.\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, TITLE AND NON-INFRINGEMENT. IN NO EVENT\n>     SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\n>     FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\n>     ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n>     DEALINGS IN THE SOFTWARE.\n\n## CC0 1.0\n\n>     Creative Commons Legal Code\n>\n>     CC0 1.0 Universal\n>\n>         CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE\n>         LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN\n>         ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS\n>         INFORMATION ON AN \"AS-IS\" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES\n>         REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS\n>         PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM\n>         THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED\n>         HEREUNDER.\n>\n>     Statement of Purpose\n>\n>     The laws of most jurisdictions throughout the world automatically confer\n>     exclusive Copyright and Related Rights (defined below) upon the creator\n>     and subsequent owner(s) (each and all, an \"owner\") of an original work of\n>     authorship and/or a database (each, a \"Work\").\n>\n>     Certain owners wish to permanently relinquish those rights to a Work for\n>     the purpose of contributing to a commons of creative, cultural and\n>     scientific works (\"Commons\") that the public can reliably and without fear\n>     of later claims of infringement build upon, modify, incorporate in other\n>     works, reuse and redistribute as freely as possible in any form whatsoever\n>     and for any purposes, including without limitation commercial purposes.\n>     These owners may contribute to the Commons to promote the ideal of a free\n>     culture and the further production of creative, cultural and scientific\n>     works, or to gain reputation or greater distribution for their Work in\n>     part through the use and efforts of others.\n>\n>     For these and/or other purposes and motivations, and without any\n>     expectation of additional consideration or compensation, the person\n>     associating CC0 with a Work (the \"Affirmer\"), to the extent that he or she\n>     is an owner of Copyright and Related Rights in the Work, voluntarily\n>     elects to apply CC0 to the Work and publicly distribute the Work under its\n>     terms, with knowledge of his or her Copyright and Related Rights in the\n>     Work and the meaning and intended legal effect of CC0 on those rights.\n>\n>     1. Copyright and Related Rights. A Work made available under CC0 may be\n>     protected by copyright and related or neighboring rights (\"Copyright and\n>     Related Rights\"). Copyright and Related Rights include, but are not\n>     limited to, the following:\n>\n>       i. the right to reproduce, adapt, distribute, perform, display,\n>          communicate, and translate a Work;\n>      ii. moral rights retained by the original author(s) and/or performer(s);\n>     iii. publicity and privacy rights pertaining to a person's image or\n>          likeness depicted in a Work;\n>      iv. rights protecting against unfair competition in regards to a Work,\n>          subject to the limitations in paragraph 4(a), below;\n>       v. rights protecting the extraction, dissemination, use and reuse of data\n>          in a Work;\n>      vi. database rights (such as those arising under Directive 96/9/EC of the\n>          European Parliament and of the Council of 11 March 1996 on the legal\n>          protection of databases, and under any national implementation\n>          thereof, including any amended or successor version of such\n>          directive); and\n>     vii. other similar, equivalent or corresponding rights throughout the\n>          world based on applicable law or treaty, and any national\n>          implementations thereof.\n>\n>     2. Waiver. To the greatest extent permitted by, but not in contravention\n>     of, applicable law, Affirmer hereby overtly, fully, permanently,\n>     irrevocably and unconditionally waives, abandons, and surrenders all of\n>     Affirmer's Copyright and Related Rights and associated claims and causes\n>     of action, whether now known or unknown (including existing as well as\n>     future claims and causes of action), in the Work (i) in all territories\n>     worldwide, (ii) for the maximum duration provided by applicable law or\n>     treaty (including future time extensions), (iii) in any current or future\n>     medium and for any number of copies, and (iv) for any purpose whatsoever,\n>     including without limitation commercial, advertising or promotional\n>     purposes (the \"Waiver\"). Affirmer makes the Waiver for the benefit of each\n>     member of the public at large and to the detriment of Affirmer's heirs and\n>     successors, fully intending that such Waiver shall not be subject to\n>     revocation, rescission, cancellation, termination, or any other legal or\n>     equitable action to disrupt the quiet enjoyment of the Work by the public\n>     as contemplated by Affirmer's express Statement of Purpose.\n>\n>     3. Public License Fallback. Should any part of the Waiver for any reason\n>     be judged legally invalid or ineffective under applicable law, then the\n>     Waiver shall be preserved to the maximum extent permitted taking into\n>     account Affirmer's express Statement of Purpose. In addition, to the\n>     extent the Waiver is so judged Affirmer hereby grants to each affected\n>     person a royalty-free, non transferable, non sublicensable, non exclusive,\n>     irrevocable and unconditional license to exercise Affirmer's Copyright and\n>     Related Rights in the Work (i) in all territories worldwide, (ii) for the\n>     maximum duration provided by applicable law or treaty (including future\n>     time extensions), (iii) in any current or future medium and for any number\n>     of copies, and (iv) for any purpose whatsoever, including without\n>     limitation commercial, advertising or promotional purposes (the\n>     \"License\"). The License shall be deemed effective as of the date CC0 was\n>     applied by Affirmer to the Work. Should any part of the License for any\n>     reason be judged legally invalid or ineffective under applicable law, such\n>     partial invalidity or ineffectiveness shall not invalidate the remainder\n>     of the License, and in such case Affirmer hereby affirms that he or she\n>     will not (i) exercise any of his or her remaining Copyright and Related\n>     Rights in the Work or (ii) assert any associated claims and causes of\n>     action with respect to the Work, in either case contrary to Affirmer's\n>     express Statement of Purpose.\n>\n>     4. Limitations and Disclaimers.\n>\n>      a. No trademark or patent rights held by Affirmer are waived, abandoned,\n>         surrendered, licensed or otherwise affected by this document.\n>      b. Affirmer offers the Work as-is and makes no representations or\n>         warranties of any kind concerning the Work, express, implied,\n>         statutory or otherwise, including without limitation warranties of\n>         title, merchantability, fitness for a particular purpose, non\n>         infringement, or the absence of latent or other defects, accuracy, or\n>         the present or absence of errors, whether or not discoverable, all to\n>         the greatest extent permissible under applicable law.\n>      c. Affirmer disclaims responsibility for clearing rights of other persons\n>         that may apply to the Work or any use thereof, including without\n>         limitation any person's Copyright and Related Rights in the Work.\n>         Further, Affirmer disclaims responsibility for obtaining any necessary\n>         consents, permissions or other rights required for any use of the\n>         Work.\n>      d. Affirmer understands and acknowledges that Creative Commons is not a\n>         party to this document and has no duty or obligation with respect to\n>         this CC0 or use of the Work.\n\n## ISC license\n\n>     Permission to use, copy, modify, and distribute this software for any\n>     purpose with or without fee is hereby granted, provided that the above\n>     copyright notice and this permission notice appear in all copies.\n>\n>     THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n>     WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n>     MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n>     ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n>     WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n>     ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n>     OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n## LGPLv2 (GNU Library General Public License, Version 2)\n\n>                       GNU LIBRARY GENERAL PUBLIC LICENSE\n>                            Version 2, June 1991\n>\n>      Copyright (C) 1991 Free Software Foundation, Inc.\n>      51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA\n>      Everyone is permitted to copy and distribute verbatim copies\n>      of this license document, but changing it is not allowed.\n>\n>     [This is the first released version of the library GPL.  It is\n>      numbered 2 because it goes with version 2 of the ordinary GPL.]\n>\n>                                 Preamble\n>\n>       The licenses for most software are designed to take away your\n>     freedom to share and change it.  By contrast, the GNU General Public\n>     Licenses are intended to guarantee your freedom to share and change\n>     free software--to make sure the software is free for all its users.\n>\n>       This license, the Library General Public License, applies to some\n>     specially designated Free Software Foundation software, and to any\n>     other libraries whose authors decide to use it.  You can use it for\n>     your libraries, too.\n>\n>       When we speak of free software, we are referring to freedom, not\n>     price.  Our General Public Licenses are designed to make sure that you\n>     have the freedom to distribute copies of free software (and charge for\n>     this service if you wish), that you receive source code or can get it\n>     if you want it, that you can change the software or use pieces of it\n>     in new free programs; and that you know you can do these things.\n>\n>       To protect your rights, we need to make restrictions that forbid\n>     anyone to deny you these rights or to ask you to surrender the rights.\n>     These restrictions translate to certain responsibilities for you if\n>     you distribute copies of the library, or if you modify it.\n>\n>       For example, if you distribute copies of the library, whether gratis\n>     or for a fee, you must give the recipients all the rights that we gave\n>     you.  You must make sure that they, too, receive or can get the source\n>     code.  If you link a program with the library, you must provide\n>     complete object files to the recipients so that they can relink them\n>     with the library, after making changes to the library and recompiling\n>     it.  And you must show them these terms so they know their rights.\n>\n>       Our method of protecting your rights has two steps: (1) copyright\n>     the library, and (2) offer you this license which gives you legal\n>     permission to copy, distribute and/or modify the library.\n>\n>       Also, for each distributor's protection, we want to make certain\n>     that everyone understands that there is no warranty for this free\n>     library.  If the library is modified by someone else and passed on, we\n>     want its recipients to know that what they have is not the original\n>     version, so that any problems introduced by others will not reflect on\n>     the original authors' reputations.\n>\n>       Finally, any free program is threatened constantly by software\n>     patents.  We wish to avoid the danger that companies distributing free\n>     software will individually obtain patent licenses, thus in effect\n>     transforming the program into proprietary software.  To prevent this,\n>     we have made it clear that any patent must be licensed for everyone's\n>     free use or not licensed at all.\n>\n>       Most GNU software, including some libraries, is covered by the ordinary\n>     GNU General Public License, which was designed for utility programs.  This\n>     license, the GNU Library General Public License, applies to certain\n>     designated libraries.  This license is quite different from the ordinary\n>     one; be sure to read it in full, and don't assume that anything in it is\n>     the same as in the ordinary license.\n>\n>       The reason we have a separate public license for some libraries is that\n>     they blur the distinction we usually make between modifying or adding to a\n>     program and simply using it.  Linking a program with a library, without\n>     changing the library, is in some sense simply using the library, and is\n>     analogous to running a utility program or application program.  However, in\n>     a textual and legal sense, the linked executable is a combined work, a\n>     derivative of the original library, and the ordinary General Public License\n>     treats it as such.\n>\n>       Because of this blurred distinction, using the ordinary General\n>     Public License for libraries did not effectively promote software\n>     sharing, because most developers did not use the libraries.  We\n>     concluded that weaker conditions might promote sharing better.\n>\n>       However, unrestricted linking of non-free programs would deprive the\n>     users of those programs of all benefit from the free status of the\n>     libraries themselves.  This Library General Public License is intended to\n>     permit developers of non-free programs to use free libraries, while\n>     preserving your freedom as a user of such programs to change the free\n>     libraries that are incorporated in them.  (We have not seen how to achieve\n>     this as regards changes in header files, but we have achieved it as regards\n>     changes in the actual functions of the Library.)  The hope is that this\n>     will lead to faster development of free libraries.\n>\n>       The precise terms and conditions for copying, distribution and\n>     modification follow.  Pay close attention to the difference between a\n>     \"work based on the library\" and a \"work that uses the library\".  The\n>     former contains code derived from the library, while the latter only\n>     works together with the library.\n>\n>       Note that it is possible for a library to be covered by the ordinary\n>     General Public License rather than by this special one.\n>\n>                       GNU LIBRARY GENERAL PUBLIC LICENSE\n>        TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION\n>\n>       0. This License Agreement applies to any software library which\n>     contains a notice placed by the copyright holder or other authorized\n>     party saying it may be distributed under the terms of this Library\n>     General Public License (also called \"this License\").  Each licensee is\n>     addressed as \"you\".\n>\n>       A \"library\" means a collection of software functions and/or data\n>     prepared so as to be conveniently linked with application programs\n>     (which use some of those functions and data) to form executables.\n>\n>       The \"Library\", below, refers to any such software library or work\n>     which has been distributed under these terms.  A \"work based on the\n>     Library\" means either the Library or any derivative work under\n>     copyright law: that is to say, a work containing the Library or a\n>     portion of it, either verbatim or with modifications and/or translated\n>     straightforwardly into another language.  (Hereinafter, translation is\n>     included without limitation in the term \"modification\".)\n>\n>       \"Source code\" for a work means the preferred form of the work for\n>     making modifications to it.  For a library, complete source code means\n>     all the source code for all modules it contains, plus any associated\n>     interface definition files, plus the scripts used to control compilation\n>     and installation of the library.\n>\n>       Activities other than copying, distribution and modification are not\n>     covered by this License; they are outside its scope.  The act of\n>     running a program using the Library is not restricted, and output from\n>     such a program is covered only if its contents constitute a work based\n>     on the Library (independent of the use of the Library in a tool for\n>     writing it).  Whether that is true depends on what the Library does\n>     and what the program that uses the Library does.\n>\n>       1. You may copy and distribute verbatim copies of the Library's\n>     complete source code as you receive it, in any medium, provided that\n>     you conspicuously and appropriately publish on each copy an\n>     appropriate copyright notice and disclaimer of warranty; keep intact\n>     all the notices that refer to this License and to the absence of any\n>     warranty; and distribute a copy of this License along with the\n>     Library.\n>\n>       You may charge a fee for the physical act of transferring a copy,\n>     and you may at your option offer warranty protection in exchange for a\n>     fee.\n>\n>       2. You may modify your copy or copies of the Library or any portion\n>     of it, thus forming a work based on the Library, and copy and\n>     distribute such modifications or work under the terms of Section 1\n>     above, provided that you also meet all of these conditions:\n>\n>         a) The modified work must itself be a software library.\n>\n>         b) You must cause the files modified to carry prominent notices\n>         stating that you changed the files and the date of any change.\n>\n>         c) You must cause the whole of the work to be licensed at no\n>         charge to all third parties under the terms of this License.\n>\n>         d) If a facility in the modified Library refers to a function or a\n>         table of data to be supplied by an application program that uses\n>         the facility, other than as an argument passed when the facility\n>         is invoked, then you must make a good faith effort to ensure that,\n>         in the event an application does not supply such function or\n>         table, the facility still operates, and performs whatever part of\n>         its purpose remains meaningful.\n>\n>         (For example, a function in a library to compute square roots has\n>         a purpose that is entirely well-defined independent of the\n>         application.  Therefore, Subsection 2d requires that any\n>         application-supplied function or table used by this function must\n>         be optional: if the application does not supply it, the square\n>         root function must still compute square roots.)\n>\n>     These requirements apply to the modified work as a whole.  If\n>     identifiable sections of that work are not derived from the Library,\n>     and can be reasonably considered independent and separate works in\n>     themselves, then this License, and its terms, do not apply to those\n>     sections when you distribute them as separate works.  But when you\n>     distribute the same sections as part of a whole which is a work based\n>     on the Library, the distribution of the whole must be on the terms of\n>     this License, whose permissions for other licensees extend to the\n>     entire whole, and thus to each and every part regardless of who wrote\n>     it.\n>\n>     Thus, it is not the intent of this section to claim rights or contest\n>     your rights to work written entirely by you; rather, the intent is to\n>     exercise the right to control the distribution of derivative or\n>     collective works based on the Library.\n>\n>     In addition, mere aggregation of another work not based on the Library\n>     with the Library (or with a work based on the Library) on a volume of\n>     a storage or distribution medium does not bring the other work under\n>     the scope of this License.\n>\n>       3. You may opt to apply the terms of the ordinary GNU General Public\n>     License instead of this License to a given copy of the Library.  To do\n>     this, you must alter all the notices that refer to this License, so\n>     that they refer to the ordinary GNU General Public License, version 2,\n>     instead of to this License.  (If a newer version than version 2 of the\n>     ordinary GNU General Public License has appeared, then you can specify\n>     that version instead if you wish.)  Do not make any other change in\n>     these notices.\n>\n>       Once this change is made in a given copy, it is irreversible for\n>     that copy, so the ordinary GNU General Public License applies to all\n>     subsequent copies and derivative works made from that copy.\n>\n>       This option is useful when you wish to copy part of the code of\n>     the Library into a program that is not a library.\n>\n>       4. You may copy and distribute the Library (or a portion or\n>     derivative of it, under Section 2) in object code or executable form\n>     under the terms of Sections 1 and 2 above provided that you accompany\n>     it with the complete corresponding machine-readable source code, which\n>     must be distributed under the terms of Sections 1 and 2 above on a\n>     medium customarily used for software interchange.\n>\n>       If distribution of object code is made by offering access to copy\n>     from a designated place, then offering equivalent access to copy the\n>     source code from the same place satisfies the requirement to\n>     distribute the source code, even though third parties are not\n>     compelled to copy the source along with the object code.\n>\n>       5. A program that contains no derivative of any portion of the\n>     Library, but is designed to work with the Library by being compiled or\n>     linked with it, is called a \"work that uses the Library\".  Such a\n>     work, in isolation, is not a derivative work of the Library, and\n>     therefore falls outside the scope of this License.\n>\n>       However, linking a \"work that uses the Library\" with the Library\n>     creates an executable that is a derivative of the Library (because it\n>     contains portions of the Library), rather than a \"work that uses the\n>     library\".  The executable is therefore covered by this License.\n>     Section 6 states terms for distribution of such executables.\n>\n>       When a \"work that uses the Library\" uses material from a header file\n>     that is part of the Library, the object code for the work may be a\n>     derivative work of the Library even though the source code is not.\n>     Whether this is true is especially significant if the work can be\n>     linked without the Library, or if the work is itself a library.  The\n>     threshold for this to be true is not precisely defined by law.\n>\n>       If such an object file uses only numerical parameters, data\n>     structure layouts and accessors, and small macros and small inline\n>     functions (ten lines or less in length), then the use of the object\n>     file is unrestricted, regardless of whether it is legally a derivative\n>     work.  (Executables containing this object code plus portions of the\n>     Library will still fall under Section 6.)\n>\n>       Otherwise, if the work is a derivative of the Library, you may\n>     distribute the object code for the work under the terms of Section 6.\n>     Any executables containing that work also fall under Section 6,\n>     whether or not they are linked directly with the Library itself.\n>\n>       6. As an exception to the Sections above, you may also compile or\n>     link a \"work that uses the Library\" with the Library to produce a\n>     work containing portions of the Library, and distribute that work\n>     under terms of your choice, provided that the terms permit\n>     modification of the work for the customer's own use and reverse\n>     engineering for debugging such modifications.\n>\n>       You must give prominent notice with each copy of the work that the\n>     Library is used in it and that the Library and its use are covered by\n>     this License.  You must supply a copy of this License.  If the work\n>     during execution displays copyright notices, you must include the\n>     copyright notice for the Library among them, as well as a reference\n>     directing the user to the copy of this License.  Also, you must do one\n>     of these things:\n>\n>         a) Accompany the work with the complete corresponding\n>         machine-readable source code for the Library including whatever\n>         changes were used in the work (which must be distributed under\n>         Sections 1 and 2 above); and, if the work is an executable linked\n>         with the Library, with the complete machine-readable \"work that\n>         uses the Library\", as object code and/or source code, so that the\n>         user can modify the Library and then relink to produce a modified\n>         executable containing the modified Library.  (It is understood\n>         that the user who changes the contents of definitions files in the\n>         Library will not necessarily be able to recompile the application\n>         to use the modified definitions.)\n>\n>         b) Accompany the work with a written offer, valid for at\n>         least three years, to give the same user the materials\n>         specified in Subsection 6a, above, for a charge no more\n>         than the cost of performing this distribution.\n>\n>         c) If distribution of the work is made by offering access to copy\n>         from a designated place, offer equivalent access to copy the above\n>         specified materials from the same place.\n>\n>         d) Verify that the user has already received a copy of these\n>         materials or that you have already sent this user a copy.\n>\n>       For an executable, the required form of the \"work that uses the\n>     Library\" must include any data and utility programs needed for\n>     reproducing the executable from it.  However, as a special exception,\n>     the source code distributed need not include anything that is normally\n>     distributed (in either source or binary form) with the major\n>     components (compiler, kernel, and so on) of the operating system on\n>     which the executable runs, unless that component itself accompanies\n>     the executable.\n>\n>       It may happen that this requirement contradicts the license\n>     restrictions of other proprietary libraries that do not normally\n>     accompany the operating system.  Such a contradiction means you cannot\n>     use both them and the Library together in an executable that you\n>     distribute.\n>\n>       7. You may place library facilities that are a work based on the\n>     Library side-by-side in a single library together with other library\n>     facilities not covered by this License, and distribute such a combined\n>     library, provided that the separate distribution of the work based on\n>     the Library and of the other library facilities is otherwise\n>     permitted, and provided that you do these two things:\n>\n>         a) Accompany the combined library with a copy of the same work\n>         based on the Library, uncombined with any other library\n>         facilities.  This must be distributed under the terms of the\n>         Sections above.\n>\n>         b) Give prominent notice with the combined library of the fact\n>         that part of it is a work based on the Library, and explaining\n>         where to find the accompanying uncombined form of the same work.\n>\n>       8. You may not copy, modify, sublicense, link with, or distribute\n>     the Library except as expressly provided under this License.  Any\n>     attempt otherwise to copy, modify, sublicense, link with, or\n>     distribute the Library is void, and will automatically terminate your\n>     rights under this License.  However, parties who have received copies,\n>     or rights, from you under this License will not have their licenses\n>     terminated so long as such parties remain in full compliance.\n>\n>       9. You are not required to accept this License, since you have not\n>     signed it.  However, nothing else grants you permission to modify or\n>     distribute the Library or its derivative works.  These actions are\n>     prohibited by law if you do not accept this License.  Therefore, by\n>     modifying or distributing the Library (or any work based on the\n>     Library), you indicate your acceptance of this License to do so, and\n>     all its terms and conditions for copying, distributing or modifying\n>     the Library or works based on it.\n>\n>       10. Each time you redistribute the Library (or any work based on the\n>     Library), the recipient automatically receives a license from the\n>     original licensor to copy, distribute, link with or modify the Library\n>     subject to these terms and conditions.  You may not impose any further\n>     restrictions on the recipients' exercise of the rights granted herein.\n>     You are not responsible for enforcing compliance by third parties to\n>     this License.\n>\n>       11. If, as a consequence of a court judgment or allegation of patent\n>     infringement or for any other reason (not limited to patent issues),\n>     conditions are imposed on you (whether by court order, agreement or\n>     otherwise) that contradict the conditions of this License, they do not\n>     excuse you from the conditions of this License.  If you cannot\n>     distribute so as to satisfy simultaneously your obligations under this\n>     License and any other pertinent obligations, then as a consequence you\n>     may not distribute the Library at all.  For example, if a patent\n>     license would not permit royalty-free redistribution of the Library by\n>     all those who receive copies directly or indirectly through you, then\n>     the only way you could satisfy both it and this License would be to\n>     refrain entirely from distribution of the Library.\n>\n>     If any portion of this section is held invalid or unenforceable under any\n>     particular circumstance, the balance of the section is intended to apply,\n>     and the section as a whole is intended to apply in other circumstances.\n>\n>     It is not the purpose of this section to induce you to infringe any\n>     patents or other property right claims or to contest validity of any\n>     such claims; this section has the sole purpose of protecting the\n>     integrity of the free software distribution system which is\n>     implemented by public license practices.  Many people have made\n>     generous contributions to the wide range of software distributed\n>     through that system in reliance on consistent application of that\n>     system; it is up to the author/donor to decide if he or she is willing\n>     to distribute software through any other system and a licensee cannot\n>     impose that choice.\n>\n>     This section is intended to make thoroughly clear what is believed to\n>     be a consequence of the rest of this License.\n>\n>       12. If the distribution and/or use of the Library is restricted in\n>     certain countries either by patents or by copyrighted interfaces, the\n>     original copyright holder who places the Library under this License may add\n>     an explicit geographical distribution limitation excluding those countries,\n>     so that distribution is permitted only in or among countries not thus\n>     excluded.  In such case, this License incorporates the limitation as if\n>     written in the body of this License.\n>\n>       13. The Free Software Foundation may publish revised and/or new\n>     versions of the Library General Public License from time to time.\n>     Such new versions will be similar in spirit to the present version,\n>     but may differ in detail to address new problems or concerns.\n>\n>     Each version is given a distinguishing version number.  If the Library\n>     specifies a version number of this License which applies to it and\n>     \"any later version\", you have the option of following the terms and\n>     conditions either of that version or of any later version published by\n>     the Free Software Foundation.  If the Library does not specify a\n>     license version number, you may choose any version ever published by\n>     the Free Software Foundation.\n>\n>       14. If you wish to incorporate parts of the Library into other free\n>     programs whose distribution conditions are incompatible with these,\n>     write to the author to ask for permission.  For software which is\n>     copyrighted by the Free Software Foundation, write to the Free\n>     Software Foundation; we sometimes make exceptions for this.  Our\n>     decision will be guided by the two goals of preserving the free status\n>     of all derivatives of our free software and of promoting the sharing\n>     and reuse of software generally.\n>\n>                                 NO WARRANTY\n>\n>       15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO\n>     WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.\n>     EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR\n>     OTHER PARTIES PROVIDE THE LIBRARY \"AS IS\" WITHOUT WARRANTY OF ANY\n>     KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE\n>     IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n>     PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE\n>     LIBRARY IS WITH YOU.  SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME\n>     THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n>\n>       16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN\n>     WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY\n>     AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU\n>     FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR\n>     CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE\n>     LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING\n>     RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A\n>     FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF\n>     SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH\n>     DAMAGES.\n>\n>                          END OF TERMS AND CONDITIONS\n>\n>                How to Apply These Terms to Your New Libraries\n>\n>       If you develop a new library, and you want it to be of the greatest\n>     possible use to the public, we recommend making it free software that\n>     everyone can redistribute and change.  You can do so by permitting\n>     redistribution under these terms (or, alternatively, under the terms of the\n>     ordinary General Public License).\n>\n>       To apply these terms, attach the following notices to the library.  It is\n>     safest to attach them to the start of each source file to most effectively\n>     convey the exclusion of warranty; and each file should have at least the\n>     \"copyright\" line and a pointer to where the full notice is found.\n>\n>         <one line to give the library's name and a brief idea of what it does.>\n>         Copyright (C) <year>  <name of author>\n>\n>         This library is free software; you can redistribute it and/or\n>         modify it under the terms of the GNU Library 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>         This library 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 GNU\n>         Library General Public License for more details.\n>\n>         You should have received a copy of the GNU Library General Public\n>         License along with this library; if not, write to the Free Software\n>         Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA\n>\n>     Also add information on how to contact you by electronic and paper mail.\n>\n>     You should also get your employer (if you work as a programmer) or your\n>     school, if any, to sign a \"copyright disclaimer\" for the library, if\n>     necessary.  Here is a sample; alter the names:\n>\n>       Yoyodyne, Inc., hereby disclaims all copyright interest in the\n>       library `Frob' (a library for tweaking knobs) written by James Random Hacker.\n>\n>       <signature of Ty Coon>, 1 April 1990\n>       Ty Coon, President of Vice\n>\n>     That's all there is to it!\n\n## MIT License\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## zlib license\n\n>     This software is provided 'as-is', without any express or implied\n>     warranty.  In no event will the authors be held liable for any damages\n>     arising from the use of this software.\n>\n>     Permission is granted to anyone to use this software for any purpose,\n>     including commercial applications, and to alter it and redistribute it\n>     freely, subject to the following restrictions:\n>\n>     1. The origin of this software must not be misrepresented; you must not\n>        claim that you wrote the original software. If you use this software\n>        in a product, an acknowledgment in the product documentation would be\n>        appreciated but is not required.\n>     2. Altered source versions must be plainly marked as such, and must not be\n>        misrepresented as being the original software.\n>     3. This notice may not be removed or altered from any source distribution.\n\n"
  },
  {
    "path": "VERSION",
    "content": "0.9.8.\n"
  },
  {
    "path": "android/.gitignore",
    "content": "distribution\napp/src/main/assets\napp/src/main/jniLibs\napp/src/main/cpp/impacto/src/**\n\n# Gradle files\n.gradle/\nbuild/\n\n# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)\n!gradle-wrapper.jar\n\n# Avoid ignore Gradle wrappper properties\n!gradle-wrapper.properties\n\n# Local configuration file (sdk path, etc)\nlocal.properties\n\n# Log/OS Files\n*.log\n\n# Android Studio generated files and folders\ncaptures/\n.externalNativeBuild/\n.cxx/\n*.apk\noutput.json\n\n# IntelliJ\n*.iml\n.idea/\nmisc.xml\ndeploymentTargetDropDown.xml\nrender.experimental.xml\n\n# Keystore files\n*.jks\n*.keystore\n\n# Google Services (e.g. APIs or Firebase)\ngoogle-services.json\n\n# Android Profiling\n*.hprof"
  },
  {
    "path": "android/.run/app.run.xml",
    "content": "<component name=\"ProjectRunConfigurationManager\">\n  <configuration default=\"false\" name=\"app\" type=\"AndroidRunConfigurationType\" factoryName=\"Android App\" activateToolWindowBeforeRun=\"false\">\n    <module name=\"android.app\" />\n    <option name=\"ANDROID_RUN_CONFIGURATION_SCHEMA_VERSION\" value=\"1\" />\n    <option name=\"DEPLOY\" value=\"true\" />\n    <option name=\"DEPLOY_APK_FROM_BUNDLE\" value=\"false\" />\n    <option name=\"DEPLOY_AS_INSTANT\" value=\"false\" />\n    <option name=\"ARTIFACT_NAME\" value=\"\" />\n    <option name=\"PM_INSTALL_OPTIONS\" value=\"\" />\n    <option name=\"ALL_USERS\" value=\"false\" />\n    <option name=\"ALWAYS_INSTALL_WITH_PM\" value=\"false\" />\n    <option name=\"ALLOW_ASSUME_VERIFIED\" value=\"false\" />\n    <option name=\"CLEAR_APP_STORAGE\" value=\"false\" />\n    <option name=\"DYNAMIC_FEATURES_DISABLED_LIST\" value=\"\" />\n    <option name=\"ACTIVITY_EXTRA_FLAGS\" value=\"\" />\n    <option name=\"MODE\" value=\"default_activity\" />\n    <option name=\"RESTORE_ENABLED\" value=\"false\" />\n    <option name=\"RESTORE_FILE\" value=\"\" />\n    <option name=\"RESTORE_FRESH_INSTALL_ONLY\" value=\"false\" />\n    <option name=\"CLEAR_LOGCAT\" value=\"false\" />\n    <option name=\"SHOW_LOGCAT_AUTOMATICALLY\" value=\"false\" />\n    <option name=\"TARGET_SELECTION_MODE\" value=\"DEVICE_AND_SNAPSHOT_COMBO_BOX\" />\n    <option name=\"SELECTED_CLOUD_MATRIX_CONFIGURATION_ID\" value=\"-1\" />\n    <option name=\"SELECTED_CLOUD_MATRIX_PROJECT_ID\" value=\"\" />\n    <option name=\"DEBUGGER_TYPE\" value=\"Auto\" />\n    <Auto>\n      <option name=\"USE_JAVA_AWARE_DEBUGGER\" value=\"false\" />\n      <option name=\"SHOW_STATIC_VARS\" value=\"true\" />\n      <option name=\"WORKING_DIR\" value=\"\" />\n      <option name=\"TARGET_LOGGING_CHANNELS\" value=\"lldb process:gdb-remote packets\" />\n      <option name=\"SHOW_OPTIMIZED_WARNING\" value=\"true\" />\n      <option name=\"ATTACH_ON_WAIT_FOR_DEBUGGER\" value=\"false\" />\n      <option name=\"DEBUG_SANDBOX_SDK\" value=\"false\" />\n      <symbol_dirs symbol_path=\"$PROJECT_DIR$/app/src/main/cpp/impacto\" />\n      <startup_commands startup_commands_attr=\"settings append target.source-map $PROJECT_DIR$/.. $PROJECT_DIR$/app/src/main/cpp/impacto\" />\n      <post_attach_commands post_attach_commands_attr=\"settings append target.source-map $PROJECT_DIR$/.. $PROJECT_DIR$/app/src/main/cpp/impacto\" />\n    </Auto>\n    <Hybrid>\n      <option name=\"USE_JAVA_AWARE_DEBUGGER\" value=\"false\" />\n      <option name=\"SHOW_STATIC_VARS\" value=\"true\" />\n      <option name=\"WORKING_DIR\" value=\"\" />\n      <option name=\"TARGET_LOGGING_CHANNELS\" value=\"lldb process:gdb-remote packets\" />\n      <option name=\"SHOW_OPTIMIZED_WARNING\" value=\"true\" />\n      <option name=\"ATTACH_ON_WAIT_FOR_DEBUGGER\" value=\"false\" />\n      <option name=\"DEBUG_SANDBOX_SDK\" value=\"false\" />\n    </Hybrid>\n    <Java>\n      <option name=\"ATTACH_ON_WAIT_FOR_DEBUGGER\" value=\"false\" />\n      <option name=\"DEBUG_SANDBOX_SDK\" value=\"false\" />\n    </Java>\n    <Native>\n      <option name=\"USE_JAVA_AWARE_DEBUGGER\" value=\"false\" />\n      <option name=\"SHOW_STATIC_VARS\" value=\"true\" />\n      <option name=\"WORKING_DIR\" value=\"\" />\n      <option name=\"TARGET_LOGGING_CHANNELS\" value=\"lldb process:gdb-remote packets\" />\n      <option name=\"SHOW_OPTIMIZED_WARNING\" value=\"true\" />\n      <option name=\"ATTACH_ON_WAIT_FOR_DEBUGGER\" value=\"false\" />\n      <option name=\"DEBUG_SANDBOX_SDK\" value=\"false\" />\n    </Native>\n    <Profilers>\n      <option name=\"ADVANCED_PROFILING_ENABLED\" value=\"false\" />\n      <option name=\"STARTUP_PROFILING_ENABLED\" value=\"false\" />\n      <option name=\"STARTUP_CPU_PROFILING_ENABLED\" value=\"false\" />\n      <option name=\"STARTUP_CPU_PROFILING_CONFIGURATION_NAME\" value=\"Java/Kotlin Method Sample (legacy)\" />\n      <option name=\"STARTUP_NATIVE_MEMORY_PROFILING_ENABLED\" value=\"false\" />\n      <option name=\"NATIVE_MEMORY_SAMPLE_RATE_BYTES\" value=\"2048\" />\n    </Profilers>\n    <option name=\"DEEP_LINK\" value=\"\" />\n    <option name=\"ACTIVITY\" value=\"\" />\n    <option name=\"ACTIVITY_CLASS\" value=\"\" />\n    <option name=\"SEARCH_ACTIVITY_IN_GLOBAL_SCOPE\" value=\"false\" />\n    <option name=\"SKIP_ACTIVITY_VALIDATION\" value=\"false\" />\n    <method v=\"2\">\n      <option name=\"Gradle.BeforeRunTask\" enabled=\"false\" tasks=\"assemble\" externalProjectPath=\"$PROJECT_DIR$\" vmOptions=\"\" scriptParameters=\"\" />\n    </method>\n  </configuration>\n</component>"
  },
  {
    "path": "android/README.md",
    "content": "Android Development:\n\n# Set up Android NDK\nDownload the Android NDK\nSet $ANDROID_NDK_HOME to point to NDK path\nSet $MINSDKVERSION to point to desired minimum Android API level (currently 28 in pipeline)\n\n# Build impacto\nRun cmake with the ci-release-android preset or customize with your own in CMakeUserPresets.json\nVCPKG will automatically build dependencies for Android ARM64 target defined in the custom triplet.\n\n```shell\ncmake --preset ci-release-android\ncmake --build --preset ci-release-android\n```\nlibimpacto.so will automatically be copied to impacto/android/app/src/main/jniLibs/<ABI>/libimpacto.so\n\n# Packaging\nTo avoid issues with packaging the .apk, avoid bundling game assets as .apk files have a maximum file size limit.\nrun ./gradlew assemble in impacto/android\napks will be created in impacto/android/distribution/android/app/outputs/apk\n\n# Preparing impacto\nGame assets should be copied to the `/sdcard/Android/data/com.committeeofzero.impacto/files/games` folder, see [Getting Started](/doc/getting_started.md).\nUpon running the application for the first time, bundled files will be copied to the `/sdcard/Android/data/com.committeeofzero.impacto/files/` folder. This will also occur when impacto detects a .reset file in the same directory.\n\n# Debugging\nOverride the CMake preset and set CMAKE_BUILD_TYPE to DEBUG (-DCMAKE_BUILD_TYPE=DEBUG in command line or override with user preset) \nto build with debug symbols first.\nIn Android Studio, open the impacto/android folder\nMake sure the explorer is set to view Project or Project files and not Android so cpp files are actually visible.\nEdit the run configuratio\n- Go to debugger:\n- Add <absolute_path_to_impacto>/android/app/src/main/cpp/impacto to symbol directories\n- In LLDB Startup Commands, add the following so symbols get mapped to correct files\n`settings append target.source-map <absolute_path_to_impacto> <absolute_path_to_impacto>/android/app/src/main/cpp/impacto`\nBreakpoints should work now properly in C++ files when debugging"
  },
  {
    "path": "android/app/build.gradle",
    "content": "apply plugin: 'com.android.application'\napply from: 'signing.gradle'\nandroid {\n    defaultConfig {\n        compileSdk 33\n        applicationId \"com.committeeofzero.impacto\"\n        minSdkVersion 28\n        targetSdkVersion 34\n        versionCode 1\n        versionName \"1.0\"\n        testInstrumentationRunner \"android.support.test.runner.AndroidJUnitRunner\"\n        ndk {\n            abiFilters \"arm64-v8a\"\n        }\n    }\n    buildTypes {\n        release {\n            signingConfig signingConfigs.release\n            minifyEnabled true\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n            resValue \"string\", \"app_name\", \"Impacto\"\n        }\n        debug {\n            applicationIdSuffix \".debug\"\n            versionNameSuffix '-DEBUG'\n            resValue \"string\", \"app_name\", \"Impacto Debug\"\n            debuggable true\n            packaging {\n                jniLibs {\n                    keepDebugSymbols += \"**/*.so\"\n                }\n            }\n        }\n    }\n    namespace 'com.committeeofzero.impacto'\n    aaptOptions {\n       noCompress 'mp4', 'mpk'\n    }\n    applicationVariants.all { variant ->\n        def mergeTaskName = \"merge${variant.name.capitalize()}NativeLibs\"\n        tasks.named(mergeTaskName).configure {\n            outputs.upToDateWhen { false }\n        }\n    }\n\n}\n\ndependencies {\n    implementation fileTree(dir: 'libs', include: ['*.jar'])\n    implementation 'androidx.appcompat:appcompat:1.1.0'\n}\n"
  },
  {
    "path": "android/app/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in /home/luap/Logiciels/android-sdk/tools/proguard/proguard-android.txt\n# You can edit the include path and order by changing the proguardFiles\n# directive in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# Add any project specific keep options here:\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile\n\n-keepclassmembers class org.libsdl.app.** { \n   public static *** **(...); \n   public *** **(...); \n}\n"
  },
  {
    "path": "android/app/signing/.gitignore",
    "content": "prod.properties\nprod.jks\n!external.jks\n!external.properties"
  },
  {
    "path": "android/app/signing/external.properties",
    "content": "# Sign key alias for external.jks\nreleaseSignKeyAlias=key\n# Sign key password for external.jks\nreleaseSignKeyPassword=impacto\n# Path to external.jks\nreleaseStoreFilePath=./signing/external.jks\n# Keystore password for external.jks\nreleaseStorePassword=impacto"
  },
  {
    "path": "android/app/signing.gradle",
    "content": "// https://dev.to/ivanshafran/android-open-source-app-secure-build-config-38gi\ndef propertiesFilename = \"signing/prod.properties\"\nif (!project.file(propertiesFilename).exists()) {\n    propertiesFilename = \"signing/external.properties\"\n}\n\ndef signingProperties = new Properties()\nsigningProperties.load(new FileInputStream(file(propertiesFilename)))\n\nandroid {\n    signingConfigs {\n        release {\n            keyAlias signingProperties.releaseSignKeyAlias\n            keyPassword signingProperties.releaseSignKeyPassword\n            storeFile file(signingProperties.releaseStoreFilePath)\n            storePassword signingProperties.releaseStorePassword\n        }\n    }\n}"
  },
  {
    "path": "android/app/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <!-- OpenGL ES 3.0 -->\n    <uses-feature android:glEsVersion=\"0x00030000\" />\n\n    <!-- Touchscreen support -->\n    <uses-feature\n        android:name=\"android.hardware.touchscreen\"\n        android:required=\"false\" />\n\n    <!-- Game controller support -->\n    <uses-feature\n        android:name=\"android.hardware.bluetooth\"\n        android:required=\"false\" />\n    <uses-feature\n        android:name=\"android.hardware.gamepad\"\n        android:required=\"false\" />\n    <uses-feature\n        android:name=\"android.hardware.usb.host\"\n        android:required=\"false\" />\n\n    <!-- External mouse input events -->\n    <uses-feature\n        android:name=\"android.hardware.type.pc\"\n        android:required=\"false\" />\n\n    <!-- Allow access to Bluetooth devices -->\n    <!-- Currently this is just for Steam Controller support and requires setting SDL_HINT_JOYSTICK_HIDAPI_STEAM -->\n    <uses-permission android:name=\"android.permission.BLUETOOTH\" android:maxSdkVersion=\"30\" />\n    <uses-permission android:name=\"android.permission.BLUETOOTH_CONNECT\" />\n\n    <!-- Allow access to the vibrator -->\n    <uses-permission android:name=\"android.permission.VIBRATE\" />\n\n    <application\n        android:allowBackup=\"true\"\n        android:appCategory=\"game\"\n        android:label=\"@string/app_name\"\n        android:icon=\"@mipmap/ic_launcher\"\n        android:roundIcon=\"@mipmap/ic_launcher_round\"\n        android:supportsRtl=\"true\"\n        android:theme=\"@style/AppTheme\">\n        <activity\n            android:name=\".ImpactoActivity\"\n            android:configChanges=\"keyboardHidden|orientation|screenSize\"\n\t\t\tandroid:screenOrientation=\"landscape\"\n            android:label=\"@string/app_name\"\n            android:exported=\"true\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n            <!-- Let Android know that we can handle some USB devices and should receive this event -->\n            <intent-filter>\n                <action android:name=\"android.hardware.usb.action.USB_DEVICE_ATTACHED\" />\n            </intent-filter>\n\n        </activity>\n\n    </application>\n\n</manifest>\n"
  },
  {
    "path": "android/app/src/main/java/com/committeeofzero/impacto/ImpactoActivity.java",
    "content": "package com.committeeofzero.impacto;\n\nimport java.io.File;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\n\nimport android.content.Context;\nimport android.os.Bundle;\nimport android.util.Log;\nimport android.content.SharedPreferences;\n\nimport org.libsdl.app.SDLActivity;\n\npublic class ImpactoActivity extends SDLActivity {\n    SharedPreferences prefs = null;\n\n    /**\n     * This method is called by SDL before loading the native shared libraries.\n     * It can be overridden to provide names of shared libraries to be loaded.\n     * The default implementation returns the defaults. It never returns null.\n     * An array returned by a new implementation must at least contain \"SDL2\".\n     * Also keep in mind that the order the libraries are loaded may matter.\n     *\n     * @return names of shared libraries to be loaded (e.g. \"SDL2\", \"main\").\n     */\n    @Override\n    protected String[] getLibraries() {\n        return new String[]{\n            \"impacto\"\n        };\n    }\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        prefs = this.getPreferences(Context.MODE_PRIVATE);\n        File externalFilesDir = getExternalFilesDir(null);\n        File resetFile = new File(externalFilesDir, \".reset\");\n        boolean reset = prefs.getBoolean(\"firstRun\", true);\n        if (resetFile.exists()) {\n            resetFile.delete();\n            reset = true;\n        }\n\n        SharedPreferences.Editor editor = prefs.edit();\n        editor.putBoolean(\"firstRun\", false);\n        editor.apply();\n        copyAssetFolder(\"shaders\", getFilesDir().getAbsolutePath() + \"/\" + \"shaders\");\n        if (reset) {\n            copyAssetFolder(\"games\", externalFilesDir.getAbsolutePath() + \"/\" + \"games\");\n            copyAssetFolder(\"profiles\", externalFilesDir.getAbsolutePath() + \"/\" + \"profiles\");\n        }\n        super.onCreate(savedInstanceState);\n    }\n\n    public boolean copyAssetFolder(String srcName, String dstName) {\n        try {\n            boolean result = true;\n            String[] fileList = getAssets().list(srcName);\n            if (fileList == null) {\n                return false;\n            }\n\n            if (fileList.length == 0) {\n                result = copyAssetFile(srcName, dstName);\n            } else {\n                File file = new File(dstName);\n                result = file.mkdirs();\n                for (String filename : fileList) {\n                    result &= copyAssetFolder(srcName + File.separator + filename, dstName + File.separator + filename);\n                }\n            }\n            return result;\n        } catch (IOException e) {\n            Log.e(null, \"Failed to copy folder \\\"\" + srcName + \", error: \\\"\" + e.getMessage() + \"\\\"\\n.\");\n            return false;\n        }\n    }\n\n    public boolean copyAssetFile(String srcName, String dstName) {\n        try {\n            InputStream in = getAssets().open(srcName);\n            File outFile = new File(dstName);\n            OutputStream out = new FileOutputStream(outFile);\n            byte[] buffer = new byte[1024];\n            int read;\n            while ((read = in.read(buffer)) != -1) {\n                out.write(buffer, 0, read);\n            }\n            in.close();\n            out.close();\n            return true;\n        } catch (IOException e) {\n            Log.e(null, \"Failed to copy file \\\"\" + srcName + \", error: \\\"\" + e.getMessage() + \"\\\"\\n.\");\n            return false;\n        }\n    }\n\n}\n"
  },
  {
    "path": "android/app/src/main/java/org/libsdl/app/HIDDevice.java",
    "content": "package org.libsdl.app;\n\nimport android.hardware.usb.UsbDevice;\n\ninterface HIDDevice\n{\n    public int getId();\n    public int getVendorId();\n    public int getProductId();\n    public String getSerialNumber();\n    public int getVersion();\n    public String getManufacturerName();\n    public String getProductName();\n    public UsbDevice getDevice();\n    public boolean open();\n    public int sendFeatureReport(byte[] report);\n    public int sendOutputReport(byte[] report);\n    public boolean getFeatureReport(byte[] report);\n    public void setFrozen(boolean frozen);\n    public void close();\n    public void shutdown();\n}\n"
  },
  {
    "path": "android/app/src/main/java/org/libsdl/app/HIDDeviceBLESteamController.java",
    "content": "package org.libsdl.app;\n\nimport android.content.Context;\nimport android.bluetooth.BluetoothDevice;\nimport android.bluetooth.BluetoothGatt;\nimport android.bluetooth.BluetoothGattCallback;\nimport android.bluetooth.BluetoothGattCharacteristic;\nimport android.bluetooth.BluetoothGattDescriptor;\nimport android.bluetooth.BluetoothManager;\nimport android.bluetooth.BluetoothProfile;\nimport android.bluetooth.BluetoothGattService;\nimport android.hardware.usb.UsbDevice;\nimport android.os.Handler;\nimport android.os.Looper;\nimport android.util.Log;\nimport android.os.*;\n\n//import com.android.internal.util.HexDump;\n\nimport java.lang.Runnable;\nimport java.util.Arrays;\nimport java.util.LinkedList;\nimport java.util.UUID;\n\nclass HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDevice {\n\n    private static final String TAG = \"hidapi\";\n    private HIDDeviceManager mManager;\n    private BluetoothDevice mDevice;\n    private int mDeviceId;\n    private BluetoothGatt mGatt;\n    private boolean mIsRegistered = false;\n    private boolean mIsConnected = false;\n    private boolean mIsChromebook = false;\n    private boolean mIsReconnecting = false;\n    private boolean mFrozen = false;\n    private LinkedList<GattOperation> mOperations;\n    GattOperation mCurrentOperation = null;\n    private Handler mHandler;\n\n    private static final int TRANSPORT_AUTO = 0;\n    private static final int TRANSPORT_BREDR = 1;\n    private static final int TRANSPORT_LE = 2;\n\n    private static final int CHROMEBOOK_CONNECTION_CHECK_INTERVAL = 10000;\n\n    static public final UUID steamControllerService = UUID.fromString(\"100F6C32-1735-4313-B402-38567131E5F3\");\n    static public final UUID inputCharacteristic = UUID.fromString(\"100F6C33-1735-4313-B402-38567131E5F3\");\n    static public final UUID reportCharacteristic = UUID.fromString(\"100F6C34-1735-4313-B402-38567131E5F3\");\n    static private final byte[] enterValveMode = new byte[] { (byte)0xC0, (byte)0x87, 0x03, 0x08, 0x07, 0x00 };\n\n    static class GattOperation {\n        private enum Operation {\n            CHR_READ,\n            CHR_WRITE,\n            ENABLE_NOTIFICATION\n        }\n\n        Operation mOp;\n        UUID mUuid;\n        byte[] mValue;\n        BluetoothGatt mGatt;\n        boolean mResult = true;\n\n        private GattOperation(BluetoothGatt gatt, GattOperation.Operation operation, UUID uuid) {\n            mGatt = gatt;\n            mOp = operation;\n            mUuid = uuid;\n        }\n\n        private GattOperation(BluetoothGatt gatt, GattOperation.Operation operation, UUID uuid, byte[] value) {\n            mGatt = gatt;\n            mOp = operation;\n            mUuid = uuid;\n            mValue = value;\n        }\n\n        public void run() {\n            // This is executed in main thread\n            BluetoothGattCharacteristic chr;\n\n            switch (mOp) {\n                case CHR_READ:\n                    chr = getCharacteristic(mUuid);\n                    //Log.v(TAG, \"Reading characteristic \" + chr.getUuid());\n                    if (!mGatt.readCharacteristic(chr)) {\n                        Log.e(TAG, \"Unable to read characteristic \" + mUuid.toString());\n                        mResult = false;\n                        break;\n                    }\n                    mResult = true;\n                    break;\n                case CHR_WRITE:\n                    chr = getCharacteristic(mUuid);\n                    //Log.v(TAG, \"Writing characteristic \" + chr.getUuid() + \" value=\" + HexDump.toHexString(value));\n                    chr.setValue(mValue);\n                    if (!mGatt.writeCharacteristic(chr)) {\n                        Log.e(TAG, \"Unable to write characteristic \" + mUuid.toString());\n                        mResult = false;\n                        break;\n                    }\n                    mResult = true;\n                    break;\n                case ENABLE_NOTIFICATION:\n                    chr = getCharacteristic(mUuid);\n                    //Log.v(TAG, \"Writing descriptor of \" + chr.getUuid());\n                    if (chr != null) {\n                        BluetoothGattDescriptor cccd = chr.getDescriptor(UUID.fromString(\"00002902-0000-1000-8000-00805f9b34fb\"));\n                        if (cccd != null) {\n                            int properties = chr.getProperties();\n                            byte[] value;\n                            if ((properties & BluetoothGattCharacteristic.PROPERTY_NOTIFY) == BluetoothGattCharacteristic.PROPERTY_NOTIFY) {\n                                value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE;\n                            } else if ((properties & BluetoothGattCharacteristic.PROPERTY_INDICATE) == BluetoothGattCharacteristic.PROPERTY_INDICATE) {\n                                value = BluetoothGattDescriptor.ENABLE_INDICATION_VALUE;\n                            } else {\n                                Log.e(TAG, \"Unable to start notifications on input characteristic\");\n                                mResult = false;\n                                return;\n                            }\n\n                            mGatt.setCharacteristicNotification(chr, true);\n                            cccd.setValue(value);\n                            if (!mGatt.writeDescriptor(cccd)) {\n                                Log.e(TAG, \"Unable to write descriptor \" + mUuid.toString());\n                                mResult = false;\n                                return;\n                            }\n                            mResult = true;\n                        }\n                    }\n            }\n        }\n\n        public boolean finish() {\n            return mResult;\n        }\n\n        private BluetoothGattCharacteristic getCharacteristic(UUID uuid) {\n            BluetoothGattService valveService = mGatt.getService(steamControllerService);\n            if (valveService == null)\n                return null;\n            return valveService.getCharacteristic(uuid);\n        }\n\n        static public GattOperation readCharacteristic(BluetoothGatt gatt, UUID uuid) {\n            return new GattOperation(gatt, Operation.CHR_READ, uuid);\n        }\n\n        static public GattOperation writeCharacteristic(BluetoothGatt gatt, UUID uuid, byte[] value) {\n            return new GattOperation(gatt, Operation.CHR_WRITE, uuid, value);\n        }\n\n        static public GattOperation enableNotification(BluetoothGatt gatt, UUID uuid) {\n            return new GattOperation(gatt, Operation.ENABLE_NOTIFICATION, uuid);\n        }\n    }\n\n    public HIDDeviceBLESteamController(HIDDeviceManager manager, BluetoothDevice device) {\n        mManager = manager;\n        mDevice = device;\n        mDeviceId = mManager.getDeviceIDForIdentifier(getIdentifier());\n        mIsRegistered = false;\n        mIsChromebook = mManager.getContext().getPackageManager().hasSystemFeature(\"org.chromium.arc.device_management\");\n        mOperations = new LinkedList<GattOperation>();\n        mHandler = new Handler(Looper.getMainLooper());\n\n        mGatt = connectGatt();\n        // final HIDDeviceBLESteamController finalThis = this;\n        // mHandler.postDelayed(new Runnable() {\n        //     @Override\n        //     public void run() {\n        //         finalThis.checkConnectionForChromebookIssue();\n        //     }\n        // }, CHROMEBOOK_CONNECTION_CHECK_INTERVAL);\n    }\n\n    public String getIdentifier() {\n        return String.format(\"SteamController.%s\", mDevice.getAddress());\n    }\n\n    public BluetoothGatt getGatt() {\n        return mGatt;\n    }\n\n    // Because on Chromebooks we show up as a dual-mode device, it will attempt to connect TRANSPORT_AUTO, which will use TRANSPORT_BREDR instead\n    // of TRANSPORT_LE.  Let's force ourselves to connect low energy.\n    private BluetoothGatt connectGatt(boolean managed) {\n        if (Build.VERSION.SDK_INT >= 23 /* Android 6.0 (M) */) {\n            try {\n                return mDevice.connectGatt(mManager.getContext(), managed, this, TRANSPORT_LE);\n            } catch (Exception e) {\n                return mDevice.connectGatt(mManager.getContext(), managed, this);\n            }\n        } else {\n            return mDevice.connectGatt(mManager.getContext(), managed, this);\n        }\n    }\n\n    private BluetoothGatt connectGatt() {\n        return connectGatt(false);\n    }\n\n    protected int getConnectionState() {\n\n        Context context = mManager.getContext();\n        if (context == null) {\n            // We are lacking any context to get our Bluetooth information.  We'll just assume disconnected.\n            return BluetoothProfile.STATE_DISCONNECTED;\n        }\n\n        BluetoothManager btManager = (BluetoothManager)context.getSystemService(Context.BLUETOOTH_SERVICE);\n        if (btManager == null) {\n            // This device doesn't support Bluetooth.  We should never be here, because how did\n            // we instantiate a device to start with?\n            return BluetoothProfile.STATE_DISCONNECTED;\n        }\n\n        return btManager.getConnectionState(mDevice, BluetoothProfile.GATT);\n    }\n\n    public void reconnect() {\n\n        if (getConnectionState() != BluetoothProfile.STATE_CONNECTED) {\n            mGatt.disconnect();\n            mGatt = connectGatt();\n        }\n\n    }\n\n    protected void checkConnectionForChromebookIssue() {\n        if (!mIsChromebook) {\n            // We only do this on Chromebooks, because otherwise it's really annoying to just attempt\n            // over and over.\n            return;\n        }\n\n        int connectionState = getConnectionState();\n\n        switch (connectionState) {\n            case BluetoothProfile.STATE_CONNECTED:\n                if (!mIsConnected) {\n                    // We are in the Bad Chromebook Place.  We can force a disconnect\n                    // to try to recover.\n                    Log.v(TAG, \"Chromebook: We are in a very bad state; the controller shows as connected in the underlying Bluetooth layer, but we never received a callback.  Forcing a reconnect.\");\n                    mIsReconnecting = true;\n                    mGatt.disconnect();\n                    mGatt = connectGatt(false);\n                    break;\n                }\n                else if (!isRegistered()) {\n                    if (mGatt.getServices().size() > 0) {\n                        Log.v(TAG, \"Chromebook: We are connected to a controller, but never got our registration.  Trying to recover.\");\n                        probeService(this);\n                    }\n                    else {\n                        Log.v(TAG, \"Chromebook: We are connected to a controller, but never discovered services.  Trying to recover.\");\n                        mIsReconnecting = true;\n                        mGatt.disconnect();\n                        mGatt = connectGatt(false);\n                        break;\n                    }\n                }\n                else {\n                    Log.v(TAG, \"Chromebook: We are connected, and registered.  Everything's good!\");\n                    return;\n                }\n                break;\n\n            case BluetoothProfile.STATE_DISCONNECTED:\n                Log.v(TAG, \"Chromebook: We have either been disconnected, or the Chromebook BtGatt.ContextMap bug has bitten us.  Attempting a disconnect/reconnect, but we may not be able to recover.\");\n\n                mIsReconnecting = true;\n                mGatt.disconnect();\n                mGatt = connectGatt(false);\n                break;\n\n            case BluetoothProfile.STATE_CONNECTING:\n                Log.v(TAG, \"Chromebook: We're still trying to connect.  Waiting a bit longer.\");\n                break;\n        }\n\n        final HIDDeviceBLESteamController finalThis = this;\n        mHandler.postDelayed(new Runnable() {\n            @Override\n            public void run() {\n                finalThis.checkConnectionForChromebookIssue();\n            }\n        }, CHROMEBOOK_CONNECTION_CHECK_INTERVAL);\n    }\n\n    private boolean isRegistered() {\n        return mIsRegistered;\n    }\n\n    private void setRegistered() {\n        mIsRegistered = true;\n    }\n\n    private boolean probeService(HIDDeviceBLESteamController controller) {\n\n        if (isRegistered()) {\n            return true;\n        }\n\n        if (!mIsConnected) {\n            return false;\n        }\n\n        Log.v(TAG, \"probeService controller=\" + controller);\n\n        for (BluetoothGattService service : mGatt.getServices()) {\n            if (service.getUuid().equals(steamControllerService)) {\n                Log.v(TAG, \"Found Valve steam controller service \" + service.getUuid());\n\n                for (BluetoothGattCharacteristic chr : service.getCharacteristics()) {\n                    if (chr.getUuid().equals(inputCharacteristic)) {\n                        Log.v(TAG, \"Found input characteristic\");\n                        // Start notifications\n                        BluetoothGattDescriptor cccd = chr.getDescriptor(UUID.fromString(\"00002902-0000-1000-8000-00805f9b34fb\"));\n                        if (cccd != null) {\n                            enableNotification(chr.getUuid());\n                        }\n                    }\n                }\n                return true;\n            }\n        }\n\n        if ((mGatt.getServices().size() == 0) && mIsChromebook && !mIsReconnecting) {\n            Log.e(TAG, \"Chromebook: Discovered services were empty; this almost certainly means the BtGatt.ContextMap bug has bitten us.\");\n            mIsConnected = false;\n            mIsReconnecting = true;\n            mGatt.disconnect();\n            mGatt = connectGatt(false);\n        }\n\n        return false;\n    }\n\n    //////////////////////////////////////////////////////////////////////////////////////////////////////\n    //////////////////////////////////////////////////////////////////////////////////////////////////////\n    //////////////////////////////////////////////////////////////////////////////////////////////////////\n\n    private void finishCurrentGattOperation() {\n        GattOperation op = null;\n        synchronized (mOperations) {\n            if (mCurrentOperation != null) {\n                op = mCurrentOperation;\n                mCurrentOperation = null;\n            }\n        }\n        if (op != null) {\n            boolean result = op.finish(); // TODO: Maybe in main thread as well?\n\n            // Our operation failed, let's add it back to the beginning of our queue.\n            if (!result) {\n                mOperations.addFirst(op);\n            }\n        }\n        executeNextGattOperation();\n    }\n\n    private void executeNextGattOperation() {\n        synchronized (mOperations) {\n            if (mCurrentOperation != null)\n                return;\n\n            if (mOperations.isEmpty())\n                return;\n\n            mCurrentOperation = mOperations.removeFirst();\n        }\n\n        // Run in main thread\n        mHandler.post(new Runnable() {\n            @Override\n            public void run() {\n                synchronized (mOperations) {\n                    if (mCurrentOperation == null) {\n                        Log.e(TAG, \"Current operation null in executor?\");\n                        return;\n                    }\n\n                    mCurrentOperation.run();\n                    // now wait for the GATT callback and when it comes, finish this operation\n                }\n            }\n        });\n    }\n\n    private void queueGattOperation(GattOperation op) {\n        synchronized (mOperations) {\n            mOperations.add(op);\n        }\n        executeNextGattOperation();\n    }\n\n    private void enableNotification(UUID chrUuid) {\n        GattOperation op = HIDDeviceBLESteamController.GattOperation.enableNotification(mGatt, chrUuid);\n        queueGattOperation(op);\n    }\n\n    public void writeCharacteristic(UUID uuid, byte[] value) {\n        GattOperation op = HIDDeviceBLESteamController.GattOperation.writeCharacteristic(mGatt, uuid, value);\n        queueGattOperation(op);\n    }\n\n    public void readCharacteristic(UUID uuid) {\n        GattOperation op = HIDDeviceBLESteamController.GattOperation.readCharacteristic(mGatt, uuid);\n        queueGattOperation(op);\n    }\n\n    //////////////////////////////////////////////////////////////////////////////////////////////////////\n    //////////////  BluetoothGattCallback overridden methods\n    //////////////////////////////////////////////////////////////////////////////////////////////////////\n\n    public void onConnectionStateChange(BluetoothGatt g, int status, int newState) {\n        //Log.v(TAG, \"onConnectionStateChange status=\" + status + \" newState=\" + newState);\n        mIsReconnecting = false;\n        if (newState == 2) {\n            mIsConnected = true;\n            // Run directly, without GattOperation\n            if (!isRegistered()) {\n                mHandler.post(new Runnable() {\n                    @Override\n                    public void run() {\n                        mGatt.discoverServices();\n                    }\n                });\n            }\n        }\n        else if (newState == 0) {\n            mIsConnected = false;\n        }\n\n        // Disconnection is handled in SteamLink using the ACTION_ACL_DISCONNECTED Intent.\n    }\n\n    public void onServicesDiscovered(BluetoothGatt gatt, int status) {\n        //Log.v(TAG, \"onServicesDiscovered status=\" + status);\n        if (status == 0) {\n            if (gatt.getServices().size() == 0) {\n                Log.v(TAG, \"onServicesDiscovered returned zero services; something has gone horribly wrong down in Android's Bluetooth stack.\");\n                mIsReconnecting = true;\n                mIsConnected = false;\n                gatt.disconnect();\n                mGatt = connectGatt(false);\n            }\n            else {\n                probeService(this);\n            }\n        }\n    }\n\n    public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {\n        //Log.v(TAG, \"onCharacteristicRead status=\" + status + \" uuid=\" + characteristic.getUuid());\n\n        if (characteristic.getUuid().equals(reportCharacteristic) && !mFrozen) {\n            mManager.HIDDeviceFeatureReport(getId(), characteristic.getValue());\n        }\n\n        finishCurrentGattOperation();\n    }\n\n    public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {\n        //Log.v(TAG, \"onCharacteristicWrite status=\" + status + \" uuid=\" + characteristic.getUuid());\n\n        if (characteristic.getUuid().equals(reportCharacteristic)) {\n            // Only register controller with the native side once it has been fully configured\n            if (!isRegistered()) {\n                Log.v(TAG, \"Registering Steam Controller with ID: \" + getId());\n                mManager.HIDDeviceConnected(getId(), getIdentifier(), getVendorId(), getProductId(), getSerialNumber(), getVersion(), getManufacturerName(), getProductName(), 0, 0, 0, 0);\n                setRegistered();\n            }\n        }\n\n        finishCurrentGattOperation();\n    }\n\n    public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {\n    // Enable this for verbose logging of controller input reports\n        //Log.v(TAG, \"onCharacteristicChanged uuid=\" + characteristic.getUuid() + \" data=\" + HexDump.dumpHexString(characteristic.getValue()));\n\n        if (characteristic.getUuid().equals(inputCharacteristic) && !mFrozen) {\n            mManager.HIDDeviceInputReport(getId(), characteristic.getValue());\n        }\n    }\n\n    public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {\n        //Log.v(TAG, \"onDescriptorRead status=\" + status);\n    }\n\n    public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {\n        BluetoothGattCharacteristic chr = descriptor.getCharacteristic();\n        //Log.v(TAG, \"onDescriptorWrite status=\" + status + \" uuid=\" + chr.getUuid() + \" descriptor=\" + descriptor.getUuid());\n\n        if (chr.getUuid().equals(inputCharacteristic)) {\n            boolean hasWrittenInputDescriptor = true;\n            BluetoothGattCharacteristic reportChr = chr.getService().getCharacteristic(reportCharacteristic);\n            if (reportChr != null) {\n                Log.v(TAG, \"Writing report characteristic to enter valve mode\");\n                reportChr.setValue(enterValveMode);\n                gatt.writeCharacteristic(reportChr);\n            }\n        }\n\n        finishCurrentGattOperation();\n    }\n\n    public void onReliableWriteCompleted(BluetoothGatt gatt, int status) {\n        //Log.v(TAG, \"onReliableWriteCompleted status=\" + status);\n    }\n\n    public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {\n        //Log.v(TAG, \"onReadRemoteRssi status=\" + status);\n    }\n\n    public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {\n        //Log.v(TAG, \"onMtuChanged status=\" + status);\n    }\n\n    //////////////////////////////////////////////////////////////////////////////////////////////////////\n    //////// Public API\n    //////////////////////////////////////////////////////////////////////////////////////////////////////\n\n    @Override\n    public int getId() {\n        return mDeviceId;\n    }\n\n    @Override\n    public int getVendorId() {\n        // Valve Corporation\n        final int VALVE_USB_VID = 0x28DE;\n        return VALVE_USB_VID;\n    }\n\n    @Override\n    public int getProductId() {\n        // We don't have an easy way to query from the Bluetooth device, but we know what it is\n        final int D0G_BLE2_PID = 0x1106;\n        return D0G_BLE2_PID;\n    }\n\n    @Override\n    public String getSerialNumber() {\n        // This will be read later via feature report by Steam\n        return \"12345\";\n    }\n\n    @Override\n    public int getVersion() {\n        return 0;\n    }\n\n    @Override\n    public String getManufacturerName() {\n        return \"Valve Corporation\";\n    }\n\n    @Override\n    public String getProductName() {\n        return \"Steam Controller\";\n    }\n\n    @Override\n    public UsbDevice getDevice() {\n        return null;\n    }\n\n    @Override\n    public boolean open() {\n        return true;\n    }\n\n    @Override\n    public int sendFeatureReport(byte[] report) {\n        if (!isRegistered()) {\n            Log.e(TAG, \"Attempted sendFeatureReport before Steam Controller is registered!\");\n            if (mIsConnected) {\n                probeService(this);\n            }\n            return -1;\n        }\n\n        // We need to skip the first byte, as that doesn't go over the air\n        byte[] actual_report = Arrays.copyOfRange(report, 1, report.length - 1);\n        //Log.v(TAG, \"sendFeatureReport \" + HexDump.dumpHexString(actual_report));\n        writeCharacteristic(reportCharacteristic, actual_report);\n        return report.length;\n    }\n\n    @Override\n    public int sendOutputReport(byte[] report) {\n        if (!isRegistered()) {\n            Log.e(TAG, \"Attempted sendOutputReport before Steam Controller is registered!\");\n            if (mIsConnected) {\n                probeService(this);\n            }\n            return -1;\n        }\n\n        //Log.v(TAG, \"sendFeatureReport \" + HexDump.dumpHexString(report));\n        writeCharacteristic(reportCharacteristic, report);\n        return report.length;\n    }\n\n    @Override\n    public boolean getFeatureReport(byte[] report) {\n        if (!isRegistered()) {\n            Log.e(TAG, \"Attempted getFeatureReport before Steam Controller is registered!\");\n            if (mIsConnected) {\n                probeService(this);\n            }\n            return false;\n        }\n\n        //Log.v(TAG, \"getFeatureReport\");\n        readCharacteristic(reportCharacteristic);\n        return true;\n    }\n\n    @Override\n    public void close() {\n    }\n\n    @Override\n    public void setFrozen(boolean frozen) {\n        mFrozen = frozen;\n    }\n\n    @Override\n    public void shutdown() {\n        close();\n\n        BluetoothGatt g = mGatt;\n        if (g != null) {\n            g.disconnect();\n            g.close();\n            mGatt = null;\n        }\n        mManager = null;\n        mIsRegistered = false;\n        mIsConnected = false;\n        mOperations.clear();\n    }\n\n}\n\n"
  },
  {
    "path": "android/app/src/main/java/org/libsdl/app/HIDDeviceManager.java",
    "content": "package org.libsdl.app;\n\nimport android.app.Activity;\nimport android.app.AlertDialog;\nimport android.app.PendingIntent;\nimport android.bluetooth.BluetoothAdapter;\nimport android.bluetooth.BluetoothDevice;\nimport android.bluetooth.BluetoothManager;\nimport android.bluetooth.BluetoothProfile;\nimport android.os.Build;\nimport android.util.Log;\nimport android.content.BroadcastReceiver;\nimport android.content.Context;\nimport android.content.DialogInterface;\nimport android.content.Intent;\nimport android.content.IntentFilter;\nimport android.content.SharedPreferences;\nimport android.content.pm.PackageManager;\nimport android.hardware.usb.*;\nimport android.os.Handler;\nimport android.os.Looper;\n\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.Iterator;\nimport java.util.List;\n\npublic class HIDDeviceManager {\n    private static final String TAG = \"hidapi\";\n    private static final String ACTION_USB_PERMISSION = \"org.libsdl.app.USB_PERMISSION\";\n\n    private static HIDDeviceManager sManager;\n    private static int sManagerRefCount = 0;\n\n    public static HIDDeviceManager acquire(Context context) {\n        if (sManagerRefCount == 0) {\n            sManager = new HIDDeviceManager(context);\n        }\n        ++sManagerRefCount;\n        return sManager;\n    }\n\n    public static void release(HIDDeviceManager manager) {\n        if (manager == sManager) {\n            --sManagerRefCount;\n            if (sManagerRefCount == 0) {\n                sManager.close();\n                sManager = null;\n            }\n        }\n    }\n\n    private Context mContext;\n    private HashMap<Integer, HIDDevice> mDevicesById = new HashMap<Integer, HIDDevice>();\n    private HashMap<BluetoothDevice, HIDDeviceBLESteamController> mBluetoothDevices = new HashMap<BluetoothDevice, HIDDeviceBLESteamController>();\n    private int mNextDeviceId = 0;\n    private SharedPreferences mSharedPreferences = null;\n    private boolean mIsChromebook = false;\n    private UsbManager mUsbManager;\n    private Handler mHandler;\n    private BluetoothManager mBluetoothManager;\n    private List<BluetoothDevice> mLastBluetoothDevices;\n\n    private final BroadcastReceiver mUsbBroadcast = new BroadcastReceiver() {\n        @Override\n        public void onReceive(Context context, Intent intent) {\n            String action = intent.getAction();\n            if (action.equals(UsbManager.ACTION_USB_DEVICE_ATTACHED)) {\n                UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);\n                handleUsbDeviceAttached(usbDevice);\n            } else if (action.equals(UsbManager.ACTION_USB_DEVICE_DETACHED)) {\n                UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);\n                handleUsbDeviceDetached(usbDevice);\n            } else if (action.equals(HIDDeviceManager.ACTION_USB_PERMISSION)) {\n                UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);\n                handleUsbDevicePermission(usbDevice, intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false));\n            }\n        }\n    };\n\n    private final BroadcastReceiver mBluetoothBroadcast = new BroadcastReceiver() {\n        @Override\n        public void onReceive(Context context, Intent intent) {\n            String action = intent.getAction();\n            // Bluetooth device was connected. If it was a Steam Controller, handle it\n            if (action.equals(BluetoothDevice.ACTION_ACL_CONNECTED)) {\n                BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);\n                Log.d(TAG, \"Bluetooth device connected: \" + device);\n\n                if (isSteamController(device)) {\n                    connectBluetoothDevice(device);\n                }\n            }\n\n            // Bluetooth device was disconnected, remove from controller manager (if any)\n            if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) {\n                BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);\n                Log.d(TAG, \"Bluetooth device disconnected: \" + device);\n\n                disconnectBluetoothDevice(device);\n            }\n        }\n    };\n\n    private HIDDeviceManager(final Context context) {\n        mContext = context;\n\n        HIDDeviceRegisterCallback();\n\n        mSharedPreferences = mContext.getSharedPreferences(\"hidapi\", Context.MODE_PRIVATE);\n        mIsChromebook = mContext.getPackageManager().hasSystemFeature(\"org.chromium.arc.device_management\");\n\n//        if (shouldClear) {\n//            SharedPreferences.Editor spedit = mSharedPreferences.edit();\n//            spedit.clear();\n//            spedit.commit();\n//        }\n//        else\n        {\n            mNextDeviceId = mSharedPreferences.getInt(\"next_device_id\", 0);\n        }\n    }\n\n    public Context getContext() {\n        return mContext;\n    }\n\n    public int getDeviceIDForIdentifier(String identifier) {\n        SharedPreferences.Editor spedit = mSharedPreferences.edit();\n\n        int result = mSharedPreferences.getInt(identifier, 0);\n        if (result == 0) {\n            result = mNextDeviceId++;\n            spedit.putInt(\"next_device_id\", mNextDeviceId);\n        }\n\n        spedit.putInt(identifier, result);\n        spedit.commit();\n        return result;\n    }\n\n    private void initializeUSB() {\n        mUsbManager = (UsbManager)mContext.getSystemService(Context.USB_SERVICE);\n        if (mUsbManager == null) {\n            return;\n        }\n\n        /*\n        // Logging\n        for (UsbDevice device : mUsbManager.getDeviceList().values()) {\n            Log.i(TAG,\"Path: \" + device.getDeviceName());\n            Log.i(TAG,\"Manufacturer: \" + device.getManufacturerName());\n            Log.i(TAG,\"Product: \" + device.getProductName());\n            Log.i(TAG,\"ID: \" + device.getDeviceId());\n            Log.i(TAG,\"Class: \" + device.getDeviceClass());\n            Log.i(TAG,\"Protocol: \" + device.getDeviceProtocol());\n            Log.i(TAG,\"Vendor ID \" + device.getVendorId());\n            Log.i(TAG,\"Product ID: \" + device.getProductId());\n            Log.i(TAG,\"Interface count: \" + device.getInterfaceCount());\n            Log.i(TAG,\"---------------------------------------\");\n\n            // Get interface details\n            for (int index = 0; index < device.getInterfaceCount(); index++) {\n                UsbInterface mUsbInterface = device.getInterface(index);\n                Log.i(TAG,\"  *****     *****\");\n                Log.i(TAG,\"  Interface index: \" + index);\n                Log.i(TAG,\"  Interface ID: \" + mUsbInterface.getId());\n                Log.i(TAG,\"  Interface class: \" + mUsbInterface.getInterfaceClass());\n                Log.i(TAG,\"  Interface subclass: \" + mUsbInterface.getInterfaceSubclass());\n                Log.i(TAG,\"  Interface protocol: \" + mUsbInterface.getInterfaceProtocol());\n                Log.i(TAG,\"  Endpoint count: \" + mUsbInterface.getEndpointCount());\n\n                // Get endpoint details\n                for (int epi = 0; epi < mUsbInterface.getEndpointCount(); epi++)\n                {\n                    UsbEndpoint mEndpoint = mUsbInterface.getEndpoint(epi);\n                    Log.i(TAG,\"    ++++   ++++   ++++\");\n                    Log.i(TAG,\"    Endpoint index: \" + epi);\n                    Log.i(TAG,\"    Attributes: \" + mEndpoint.getAttributes());\n                    Log.i(TAG,\"    Direction: \" + mEndpoint.getDirection());\n                    Log.i(TAG,\"    Number: \" + mEndpoint.getEndpointNumber());\n                    Log.i(TAG,\"    Interval: \" + mEndpoint.getInterval());\n                    Log.i(TAG,\"    Packet size: \" + mEndpoint.getMaxPacketSize());\n                    Log.i(TAG,\"    Type: \" + mEndpoint.getType());\n                }\n            }\n        }\n        Log.i(TAG,\" No more devices connected.\");\n        */\n\n        // Register for USB broadcasts and permission completions\n        IntentFilter filter = new IntentFilter();\n        filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);\n        filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);\n        filter.addAction(HIDDeviceManager.ACTION_USB_PERMISSION);\n        mContext.registerReceiver(mUsbBroadcast, filter);\n\n        for (UsbDevice usbDevice : mUsbManager.getDeviceList().values()) {\n            handleUsbDeviceAttached(usbDevice);\n        }\n    }\n\n    UsbManager getUSBManager() {\n        return mUsbManager;\n    }\n\n    private void shutdownUSB() {\n        try {\n            mContext.unregisterReceiver(mUsbBroadcast);\n        } catch (Exception e) {\n            // We may not have registered, that's okay\n        }\n    }\n\n    private boolean isHIDDeviceInterface(UsbDevice usbDevice, UsbInterface usbInterface) {\n        if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_HID) {\n            return true;\n        }\n        if (isXbox360Controller(usbDevice, usbInterface) || isXboxOneController(usbDevice, usbInterface)) {\n            return true;\n        }\n        return false;\n    }\n\n    private boolean isXbox360Controller(UsbDevice usbDevice, UsbInterface usbInterface) {\n        final int XB360_IFACE_SUBCLASS = 93;\n        final int XB360_IFACE_PROTOCOL = 1; // Wired\n        final int XB360W_IFACE_PROTOCOL = 129; // Wireless\n        final int[] SUPPORTED_VENDORS = {\n            0x0079, // GPD Win 2\n            0x044f, // Thrustmaster\n            0x045e, // Microsoft\n            0x046d, // Logitech\n            0x056e, // Elecom\n            0x06a3, // Saitek\n            0x0738, // Mad Catz\n            0x07ff, // Mad Catz\n            0x0e6f, // PDP\n            0x0f0d, // Hori\n            0x1038, // SteelSeries\n            0x11c9, // Nacon\n            0x12ab, // Unknown\n            0x1430, // RedOctane\n            0x146b, // BigBen\n            0x1532, // Razer Sabertooth\n            0x15e4, // Numark\n            0x162e, // Joytech\n            0x1689, // Razer Onza\n            0x1949, // Lab126, Inc.\n            0x1bad, // Harmonix\n            0x20d6, // PowerA\n            0x24c6, // PowerA\n            0x2c22, // Qanba\n            0x2dc8, // 8BitDo\n            0x9886, // ASTRO Gaming\n        };\n\n        if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_VENDOR_SPEC &&\n            usbInterface.getInterfaceSubclass() == XB360_IFACE_SUBCLASS &&\n            (usbInterface.getInterfaceProtocol() == XB360_IFACE_PROTOCOL ||\n             usbInterface.getInterfaceProtocol() == XB360W_IFACE_PROTOCOL)) {\n            int vendor_id = usbDevice.getVendorId();\n            for (int supportedVid : SUPPORTED_VENDORS) {\n                if (vendor_id == supportedVid) {\n                    return true;\n                }\n            }\n        }\n        return false;\n    }\n\n    private boolean isXboxOneController(UsbDevice usbDevice, UsbInterface usbInterface) {\n        final int XB1_IFACE_SUBCLASS = 71;\n        final int XB1_IFACE_PROTOCOL = 208;\n        final int[] SUPPORTED_VENDORS = {\n            0x03f0, // HP\n            0x044f, // Thrustmaster\n            0x045e, // Microsoft\n            0x0738, // Mad Catz\n            0x0b05, // ASUS\n            0x0e6f, // PDP\n            0x0f0d, // Hori\n            0x10f5, // Turtle Beach\n            0x1532, // Razer Wildcat\n            0x20d6, // PowerA\n            0x24c6, // PowerA\n            0x2dc8, // 8BitDo\n            0x2e24, // Hyperkin\n            0x3537, // GameSir\n        };\n\n        if (usbInterface.getId() == 0 &&\n            usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_VENDOR_SPEC &&\n            usbInterface.getInterfaceSubclass() == XB1_IFACE_SUBCLASS &&\n            usbInterface.getInterfaceProtocol() == XB1_IFACE_PROTOCOL) {\n            int vendor_id = usbDevice.getVendorId();\n            for (int supportedVid : SUPPORTED_VENDORS) {\n                if (vendor_id == supportedVid) {\n                    return true;\n                }\n            }\n        }\n        return false;\n    }\n\n    private void handleUsbDeviceAttached(UsbDevice usbDevice) {\n        connectHIDDeviceUSB(usbDevice);\n    }\n\n    private void handleUsbDeviceDetached(UsbDevice usbDevice) {\n        List<Integer> devices = new ArrayList<Integer>();\n        for (HIDDevice device : mDevicesById.values()) {\n            if (usbDevice.equals(device.getDevice())) {\n                devices.add(device.getId());\n            }\n        }\n        for (int id : devices) {\n            HIDDevice device = mDevicesById.get(id);\n            mDevicesById.remove(id);\n            device.shutdown();\n            HIDDeviceDisconnected(id);\n        }\n    }\n\n    private void handleUsbDevicePermission(UsbDevice usbDevice, boolean permission_granted) {\n        for (HIDDevice device : mDevicesById.values()) {\n            if (usbDevice.equals(device.getDevice())) {\n                boolean opened = false;\n                if (permission_granted) {\n                    opened = device.open();\n                }\n                HIDDeviceOpenResult(device.getId(), opened);\n            }\n        }\n    }\n\n    private void connectHIDDeviceUSB(UsbDevice usbDevice) {\n        synchronized (this) {\n            int interface_mask = 0;\n            for (int interface_index = 0; interface_index < usbDevice.getInterfaceCount(); interface_index++) {\n                UsbInterface usbInterface = usbDevice.getInterface(interface_index);\n                if (isHIDDeviceInterface(usbDevice, usbInterface)) {\n                    // Check to see if we've already added this interface\n                    // This happens with the Xbox Series X controller which has a duplicate interface 0, which is inactive\n                    int interface_id = usbInterface.getId();\n                    if ((interface_mask & (1 << interface_id)) != 0) {\n                        continue;\n                    }\n                    interface_mask |= (1 << interface_id);\n\n                    HIDDeviceUSB device = new HIDDeviceUSB(this, usbDevice, interface_index);\n                    int id = device.getId();\n                    mDevicesById.put(id, device);\n                    HIDDeviceConnected(id, device.getIdentifier(), device.getVendorId(), device.getProductId(), device.getSerialNumber(), device.getVersion(), device.getManufacturerName(), device.getProductName(), usbInterface.getId(), usbInterface.getInterfaceClass(), usbInterface.getInterfaceSubclass(), usbInterface.getInterfaceProtocol());\n                }\n            }\n        }\n    }\n\n    private void initializeBluetooth() {\n        Log.d(TAG, \"Initializing Bluetooth\");\n\n        if (Build.VERSION.SDK_INT >= 31 /* Android 12  */ &&\n            mContext.getPackageManager().checkPermission(android.Manifest.permission.BLUETOOTH_CONNECT, mContext.getPackageName()) != PackageManager.PERMISSION_GRANTED) {\n            Log.d(TAG, \"Couldn't initialize Bluetooth, missing android.permission.BLUETOOTH_CONNECT\");\n            return;\n        }\n\n        if (Build.VERSION.SDK_INT <= 30 /* Android 11.0 (R) */ &&\n            mContext.getPackageManager().checkPermission(android.Manifest.permission.BLUETOOTH, mContext.getPackageName()) != PackageManager.PERMISSION_GRANTED) {\n            Log.d(TAG, \"Couldn't initialize Bluetooth, missing android.permission.BLUETOOTH\");\n            return;\n        }\n\n        if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE) || (Build.VERSION.SDK_INT < 18 /* Android 4.3 (JELLY_BEAN_MR2) */)) {\n            Log.d(TAG, \"Couldn't initialize Bluetooth, this version of Android does not support Bluetooth LE\");\n            return;\n        }\n\n        // Find bonded bluetooth controllers and create SteamControllers for them\n        mBluetoothManager = (BluetoothManager)mContext.getSystemService(Context.BLUETOOTH_SERVICE);\n        if (mBluetoothManager == null) {\n            // This device doesn't support Bluetooth.\n            return;\n        }\n\n        BluetoothAdapter btAdapter = mBluetoothManager.getAdapter();\n        if (btAdapter == null) {\n            // This device has Bluetooth support in the codebase, but has no available adapters.\n            return;\n        }\n\n        // Get our bonded devices.\n        for (BluetoothDevice device : btAdapter.getBondedDevices()) {\n\n            Log.d(TAG, \"Bluetooth device available: \" + device);\n            if (isSteamController(device)) {\n                connectBluetoothDevice(device);\n            }\n\n        }\n\n        // NOTE: These don't work on Chromebooks, to my undying dismay.\n        IntentFilter filter = new IntentFilter();\n        filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);\n        filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);\n        mContext.registerReceiver(mBluetoothBroadcast, filter);\n\n        if (mIsChromebook) {\n            mHandler = new Handler(Looper.getMainLooper());\n            mLastBluetoothDevices = new ArrayList<BluetoothDevice>();\n\n            // final HIDDeviceManager finalThis = this;\n            // mHandler.postDelayed(new Runnable() {\n            //     @Override\n            //     public void run() {\n            //         finalThis.chromebookConnectionHandler();\n            //     }\n            // }, 5000);\n        }\n    }\n\n    private void shutdownBluetooth() {\n        try {\n            mContext.unregisterReceiver(mBluetoothBroadcast);\n        } catch (Exception e) {\n            // We may not have registered, that's okay\n        }\n    }\n\n    // Chromebooks do not pass along ACTION_ACL_CONNECTED / ACTION_ACL_DISCONNECTED properly.\n    // This function provides a sort of dummy version of that, watching for changes in the\n    // connected devices and attempting to add controllers as things change.\n    public void chromebookConnectionHandler() {\n        if (!mIsChromebook) {\n            return;\n        }\n\n        ArrayList<BluetoothDevice> disconnected = new ArrayList<BluetoothDevice>();\n        ArrayList<BluetoothDevice> connected = new ArrayList<BluetoothDevice>();\n\n        List<BluetoothDevice> currentConnected = mBluetoothManager.getConnectedDevices(BluetoothProfile.GATT);\n\n        for (BluetoothDevice bluetoothDevice : currentConnected) {\n            if (!mLastBluetoothDevices.contains(bluetoothDevice)) {\n                connected.add(bluetoothDevice);\n            }\n        }\n        for (BluetoothDevice bluetoothDevice : mLastBluetoothDevices) {\n            if (!currentConnected.contains(bluetoothDevice)) {\n                disconnected.add(bluetoothDevice);\n            }\n        }\n\n        mLastBluetoothDevices = currentConnected;\n\n        for (BluetoothDevice bluetoothDevice : disconnected) {\n            disconnectBluetoothDevice(bluetoothDevice);\n        }\n        for (BluetoothDevice bluetoothDevice : connected) {\n            connectBluetoothDevice(bluetoothDevice);\n        }\n\n        final HIDDeviceManager finalThis = this;\n        mHandler.postDelayed(new Runnable() {\n            @Override\n            public void run() {\n                finalThis.chromebookConnectionHandler();\n            }\n        }, 10000);\n    }\n\n    public boolean connectBluetoothDevice(BluetoothDevice bluetoothDevice) {\n        Log.v(TAG, \"connectBluetoothDevice device=\" + bluetoothDevice);\n        synchronized (this) {\n            if (mBluetoothDevices.containsKey(bluetoothDevice)) {\n                Log.v(TAG, \"Steam controller with address \" + bluetoothDevice + \" already exists, attempting reconnect\");\n\n                HIDDeviceBLESteamController device = mBluetoothDevices.get(bluetoothDevice);\n                device.reconnect();\n\n                return false;\n            }\n            HIDDeviceBLESteamController device = new HIDDeviceBLESteamController(this, bluetoothDevice);\n            int id = device.getId();\n            mBluetoothDevices.put(bluetoothDevice, device);\n            mDevicesById.put(id, device);\n\n            // The Steam Controller will mark itself connected once initialization is complete\n        }\n        return true;\n    }\n\n    public void disconnectBluetoothDevice(BluetoothDevice bluetoothDevice) {\n        synchronized (this) {\n            HIDDeviceBLESteamController device = mBluetoothDevices.get(bluetoothDevice);\n            if (device == null)\n                return;\n\n            int id = device.getId();\n            mBluetoothDevices.remove(bluetoothDevice);\n            mDevicesById.remove(id);\n            device.shutdown();\n            HIDDeviceDisconnected(id);\n        }\n    }\n\n    public boolean isSteamController(BluetoothDevice bluetoothDevice) {\n        // Sanity check.  If you pass in a null device, by definition it is never a Steam Controller.\n        if (bluetoothDevice == null) {\n            return false;\n        }\n\n        // If the device has no local name, we really don't want to try an equality check against it.\n        if (bluetoothDevice.getName() == null) {\n            return false;\n        }\n\n        return bluetoothDevice.getName().equals(\"SteamController\") && ((bluetoothDevice.getType() & BluetoothDevice.DEVICE_TYPE_LE) != 0);\n    }\n\n    private void close() {\n        shutdownUSB();\n        shutdownBluetooth();\n        synchronized (this) {\n            for (HIDDevice device : mDevicesById.values()) {\n                device.shutdown();\n            }\n            mDevicesById.clear();\n            mBluetoothDevices.clear();\n            HIDDeviceReleaseCallback();\n        }\n    }\n\n    public void setFrozen(boolean frozen) {\n        synchronized (this) {\n            for (HIDDevice device : mDevicesById.values()) {\n                device.setFrozen(frozen);\n            }\n        }\n    }\n\n    //////////////////////////////////////////////////////////////////////////////////////////////////////\n    //////////////////////////////////////////////////////////////////////////////////////////////////////\n    //////////////////////////////////////////////////////////////////////////////////////////////////////\n\n    private HIDDevice getDevice(int id) {\n        synchronized (this) {\n            HIDDevice result = mDevicesById.get(id);\n            if (result == null) {\n                Log.v(TAG, \"No device for id: \" + id);\n                Log.v(TAG, \"Available devices: \" + mDevicesById.keySet());\n            }\n            return result;\n        }\n    }\n\n    //////////////////////////////////////////////////////////////////////////////////////////////////////\n    ////////// JNI interface functions\n    //////////////////////////////////////////////////////////////////////////////////////////////////////\n\n    public boolean initialize(boolean usb, boolean bluetooth) {\n        Log.v(TAG, \"initialize(\" + usb + \", \" + bluetooth + \")\");\n\n        if (usb) {\n            initializeUSB();\n        }\n        if (bluetooth) {\n            initializeBluetooth();\n        }\n        return true;\n    }\n\n    public boolean openDevice(int deviceID) {\n        Log.v(TAG, \"openDevice deviceID=\" + deviceID);\n        HIDDevice device = getDevice(deviceID);\n        if (device == null) {\n            HIDDeviceDisconnected(deviceID);\n            return false;\n        }\n\n        // Look to see if this is a USB device and we have permission to access it\n        UsbDevice usbDevice = device.getDevice();\n        if (usbDevice != null && !mUsbManager.hasPermission(usbDevice)) {\n            HIDDeviceOpenPending(deviceID);\n            try {\n                final int FLAG_MUTABLE = 0x02000000; // PendingIntent.FLAG_MUTABLE, but don't require SDK 31\n                int flags;\n                if (Build.VERSION.SDK_INT >= 31 /* Android 12.0 (S) */) {\n                    flags = FLAG_MUTABLE;\n                } else {\n                    flags = 0;\n                }\n                if (Build.VERSION.SDK_INT >= 33 /* Android 14.0 (U) */) {\n                   Intent intent = new Intent(HIDDeviceManager.ACTION_USB_PERMISSION);\n                   intent.setPackage(mContext.getPackageName());\n                   mUsbManager.requestPermission(usbDevice, PendingIntent.getBroadcast(mContext, 0, intent, flags));\n               } else {\n                   mUsbManager.requestPermission(usbDevice, PendingIntent.getBroadcast(mContext, 0, new Intent(HIDDeviceManager.ACTION_USB_PERMISSION), flags));\n               }\n            } catch (Exception e) {\n                Log.v(TAG, \"Couldn't request permission for USB device \" + usbDevice);\n                HIDDeviceOpenResult(deviceID, false);\n            }\n            return false;\n        }\n\n        try {\n            return device.open();\n        } catch (Exception e) {\n            Log.e(TAG, \"Got exception: \" + Log.getStackTraceString(e));\n        }\n        return false;\n    }\n\n    public int sendOutputReport(int deviceID, byte[] report) {\n        try {\n            //Log.v(TAG, \"sendOutputReport deviceID=\" + deviceID + \" length=\" + report.length);\n            HIDDevice device;\n            device = getDevice(deviceID);\n            if (device == null) {\n                HIDDeviceDisconnected(deviceID);\n                return -1;\n            }\n\n            return device.sendOutputReport(report);\n        } catch (Exception e) {\n            Log.e(TAG, \"Got exception: \" + Log.getStackTraceString(e));\n        }\n        return -1;\n    }\n\n    public int sendFeatureReport(int deviceID, byte[] report) {\n        try {\n            //Log.v(TAG, \"sendFeatureReport deviceID=\" + deviceID + \" length=\" + report.length);\n            HIDDevice device;\n            device = getDevice(deviceID);\n            if (device == null) {\n                HIDDeviceDisconnected(deviceID);\n                return -1;\n            }\n\n            return device.sendFeatureReport(report);\n        } catch (Exception e) {\n            Log.e(TAG, \"Got exception: \" + Log.getStackTraceString(e));\n        }\n        return -1;\n    }\n\n    public boolean getFeatureReport(int deviceID, byte[] report) {\n        try {\n            //Log.v(TAG, \"getFeatureReport deviceID=\" + deviceID);\n            HIDDevice device;\n            device = getDevice(deviceID);\n            if (device == null) {\n                HIDDeviceDisconnected(deviceID);\n                return false;\n            }\n\n            return device.getFeatureReport(report);\n        } catch (Exception e) {\n            Log.e(TAG, \"Got exception: \" + Log.getStackTraceString(e));\n        }\n        return false;\n    }\n\n    public void closeDevice(int deviceID) {\n        try {\n            Log.v(TAG, \"closeDevice deviceID=\" + deviceID);\n            HIDDevice device;\n            device = getDevice(deviceID);\n            if (device == null) {\n                HIDDeviceDisconnected(deviceID);\n                return;\n            }\n\n            device.close();\n        } catch (Exception e) {\n            Log.e(TAG, \"Got exception: \" + Log.getStackTraceString(e));\n        }\n    }\n\n\n    //////////////////////////////////////////////////////////////////////////////////////////////////////\n    /////////////// Native methods\n    //////////////////////////////////////////////////////////////////////////////////////////////////////\n\n    private native void HIDDeviceRegisterCallback();\n    private native void HIDDeviceReleaseCallback();\n\n    native void HIDDeviceConnected(int deviceID, String identifier, int vendorId, int productId, String serial_number, int release_number, String manufacturer_string, String product_string, int interface_number, int interface_class, int interface_subclass, int interface_protocol);\n    native void HIDDeviceOpenPending(int deviceID);\n    native void HIDDeviceOpenResult(int deviceID, boolean opened);\n    native void HIDDeviceDisconnected(int deviceID);\n\n    native void HIDDeviceInputReport(int deviceID, byte[] report);\n    native void HIDDeviceFeatureReport(int deviceID, byte[] report);\n}\n"
  },
  {
    "path": "android/app/src/main/java/org/libsdl/app/HIDDeviceUSB.java",
    "content": "package org.libsdl.app;\n\nimport android.hardware.usb.*;\nimport android.os.Build;\nimport android.util.Log;\nimport java.util.Arrays;\n\nclass HIDDeviceUSB implements HIDDevice {\n\n    private static final String TAG = \"hidapi\";\n\n    protected HIDDeviceManager mManager;\n    protected UsbDevice mDevice;\n    protected int mInterfaceIndex;\n    protected int mInterface;\n    protected int mDeviceId;\n    protected UsbDeviceConnection mConnection;\n    protected UsbEndpoint mInputEndpoint;\n    protected UsbEndpoint mOutputEndpoint;\n    protected InputThread mInputThread;\n    protected boolean mRunning;\n    protected boolean mFrozen;\n\n    public HIDDeviceUSB(HIDDeviceManager manager, UsbDevice usbDevice, int interface_index) {\n        mManager = manager;\n        mDevice = usbDevice;\n        mInterfaceIndex = interface_index;\n        mInterface = mDevice.getInterface(mInterfaceIndex).getId();\n        mDeviceId = manager.getDeviceIDForIdentifier(getIdentifier());\n        mRunning = false;\n    }\n\n    public String getIdentifier() {\n        return String.format(\"%s/%x/%x/%d\", mDevice.getDeviceName(), mDevice.getVendorId(), mDevice.getProductId(), mInterfaceIndex);\n    }\n\n    @Override\n    public int getId() {\n        return mDeviceId;\n    }\n\n    @Override\n    public int getVendorId() {\n        return mDevice.getVendorId();\n    }\n\n    @Override\n    public int getProductId() {\n        return mDevice.getProductId();\n    }\n\n    @Override\n    public String getSerialNumber() {\n        String result = null;\n        if (Build.VERSION.SDK_INT >= 21 /* Android 5.0 (LOLLIPOP) */) {\n            try {\n                result = mDevice.getSerialNumber();\n            }\n            catch (SecurityException exception) {\n                //Log.w(TAG, \"App permissions mean we cannot get serial number for device \" + getDeviceName() + \" message: \" + exception.getMessage());\n            }\n        }\n        if (result == null) {\n            result = \"\";\n        }\n        return result;\n    }\n\n    @Override\n    public int getVersion() {\n        return 0;\n    }\n\n    @Override\n    public String getManufacturerName() {\n        String result = null;\n        if (Build.VERSION.SDK_INT >= 21 /* Android 5.0 (LOLLIPOP) */) {\n            result = mDevice.getManufacturerName();\n        }\n        if (result == null) {\n            result = String.format(\"%x\", getVendorId());\n        }\n        return result;\n    }\n\n    @Override\n    public String getProductName() {\n        String result = null;\n        if (Build.VERSION.SDK_INT >= 21 /* Android 5.0 (LOLLIPOP) */) {\n            result = mDevice.getProductName();\n        }\n        if (result == null) {\n            result = String.format(\"%x\", getProductId());\n        }\n        return result;\n    }\n\n    @Override\n    public UsbDevice getDevice() {\n        return mDevice;\n    }\n\n    public String getDeviceName() {\n        return getManufacturerName() + \" \" + getProductName() + \"(0x\" + String.format(\"%x\", getVendorId()) + \"/0x\" + String.format(\"%x\", getProductId()) + \")\";\n    }\n\n    @Override\n    public boolean open() {\n        mConnection = mManager.getUSBManager().openDevice(mDevice);\n        if (mConnection == null) {\n            Log.w(TAG, \"Unable to open USB device \" + getDeviceName());\n            return false;\n        }\n\n        // Force claim our interface\n        UsbInterface iface = mDevice.getInterface(mInterfaceIndex);\n        if (!mConnection.claimInterface(iface, true)) {\n            Log.w(TAG, \"Failed to claim interfaces on USB device \" + getDeviceName());\n            close();\n            return false;\n        }\n\n        // Find the endpoints\n        for (int j = 0; j < iface.getEndpointCount(); j++) {\n            UsbEndpoint endpt = iface.getEndpoint(j);\n            switch (endpt.getDirection()) {\n            case UsbConstants.USB_DIR_IN:\n                if (mInputEndpoint == null) {\n                    mInputEndpoint = endpt;\n                }\n                break;\n            case UsbConstants.USB_DIR_OUT:\n                if (mOutputEndpoint == null) {\n                    mOutputEndpoint = endpt;\n                }\n                break;\n            }\n        }\n\n        // Make sure the required endpoints were present\n        if (mInputEndpoint == null || mOutputEndpoint == null) {\n            Log.w(TAG, \"Missing required endpoint on USB device \" + getDeviceName());\n            close();\n            return false;\n        }\n\n        // Start listening for input\n        mRunning = true;\n        mInputThread = new InputThread();\n        mInputThread.start();\n\n        return true;\n    }\n\n    @Override\n    public int sendFeatureReport(byte[] report) {\n        int res = -1;\n        int offset = 0;\n        int length = report.length;\n        boolean skipped_report_id = false;\n        byte report_number = report[0];\n\n        if (report_number == 0x0) {\n            ++offset;\n            --length;\n            skipped_report_id = true;\n        }\n\n        res = mConnection.controlTransfer(\n            UsbConstants.USB_TYPE_CLASS | 0x01 /*RECIPIENT_INTERFACE*/ | UsbConstants.USB_DIR_OUT,\n            0x09/*HID set_report*/,\n            (3/*HID feature*/ << 8) | report_number,\n            mInterface,\n            report, offset, length,\n            1000/*timeout millis*/);\n\n        if (res < 0) {\n            Log.w(TAG, \"sendFeatureReport() returned \" + res + \" on device \" + getDeviceName());\n            return -1;\n        }\n\n        if (skipped_report_id) {\n            ++length;\n        }\n        return length;\n    }\n\n    @Override\n    public int sendOutputReport(byte[] report) {\n        int r = mConnection.bulkTransfer(mOutputEndpoint, report, report.length, 1000);\n        if (r != report.length) {\n            Log.w(TAG, \"sendOutputReport() returned \" + r + \" on device \" + getDeviceName());\n        }\n        return r;\n    }\n\n    @Override\n    public boolean getFeatureReport(byte[] report) {\n        int res = -1;\n        int offset = 0;\n        int length = report.length;\n        boolean skipped_report_id = false;\n        byte report_number = report[0];\n\n        if (report_number == 0x0) {\n            /* Offset the return buffer by 1, so that the report ID\n               will remain in byte 0. */\n            ++offset;\n            --length;\n            skipped_report_id = true;\n        }\n\n        res = mConnection.controlTransfer(\n            UsbConstants.USB_TYPE_CLASS | 0x01 /*RECIPIENT_INTERFACE*/ | UsbConstants.USB_DIR_IN,\n            0x01/*HID get_report*/,\n            (3/*HID feature*/ << 8) | report_number,\n            mInterface,\n            report, offset, length,\n            1000/*timeout millis*/);\n\n        if (res < 0) {\n            Log.w(TAG, \"getFeatureReport() returned \" + res + \" on device \" + getDeviceName());\n            return false;\n        }\n\n        if (skipped_report_id) {\n            ++res;\n            ++length;\n        }\n\n        byte[] data;\n        if (res == length) {\n            data = report;\n        } else {\n            data = Arrays.copyOfRange(report, 0, res);\n        }\n        mManager.HIDDeviceFeatureReport(mDeviceId, data);\n\n        return true;\n    }\n\n    @Override\n    public void close() {\n        mRunning = false;\n        if (mInputThread != null) {\n            while (mInputThread.isAlive()) {\n                mInputThread.interrupt();\n                try {\n                    mInputThread.join();\n                } catch (InterruptedException e) {\n                    // Keep trying until we're done\n                }\n            }\n            mInputThread = null;\n        }\n        if (mConnection != null) {\n            UsbInterface iface = mDevice.getInterface(mInterfaceIndex);\n            mConnection.releaseInterface(iface);\n            mConnection.close();\n            mConnection = null;\n        }\n    }\n\n    @Override\n    public void shutdown() {\n        close();\n        mManager = null;\n    }\n\n    @Override\n    public void setFrozen(boolean frozen) {\n        mFrozen = frozen;\n    }\n\n    protected class InputThread extends Thread {\n        @Override\n        public void run() {\n            int packetSize = mInputEndpoint.getMaxPacketSize();\n            byte[] packet = new byte[packetSize];\n            while (mRunning) {\n                int r;\n                try\n                {\n                    r = mConnection.bulkTransfer(mInputEndpoint, packet, packetSize, 1000);\n                }\n                catch (Exception e)\n                {\n                    Log.v(TAG, \"Exception in UsbDeviceConnection bulktransfer: \" + e);\n                    break;\n                }\n                if (r < 0) {\n                    // Could be a timeout or an I/O error\n                }\n                if (r > 0) {\n                    byte[] data;\n                    if (r == packetSize) {\n                        data = packet;\n                    } else {\n                        data = Arrays.copyOfRange(packet, 0, r);\n                    }\n\n                    if (!mFrozen) {\n                        mManager.HIDDeviceInputReport(mDeviceId, data);\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "android/app/src/main/java/org/libsdl/app/SDL.java",
    "content": "package org.libsdl.app;\n\nimport android.content.Context;\n\nimport java.lang.Class;\nimport java.lang.reflect.Method;\n\n/**\n    SDL library initialization\n*/\npublic class SDL {\n\n    // This function should be called first and sets up the native code\n    // so it can call into the Java classes\n    public static void setupJNI() {\n        SDLActivity.nativeSetupJNI();\n        SDLAudioManager.nativeSetupJNI();\n        SDLControllerManager.nativeSetupJNI();\n    }\n\n    // This function should be called each time the activity is started\n    public static void initialize() {\n        setContext(null);\n\n        SDLActivity.initialize();\n        SDLAudioManager.initialize();\n        SDLControllerManager.initialize();\n    }\n\n    // This function stores the current activity (SDL or not)\n    public static void setContext(Context context) {\n        SDLAudioManager.setContext(context);\n        mContext = context;\n    }\n\n    public static Context getContext() {\n        return mContext;\n    }\n\n    public static void loadLibrary(String libraryName) throws UnsatisfiedLinkError, SecurityException, NullPointerException {\n        loadLibrary(libraryName, mContext);\n    }\n\n    public static void loadLibrary(String libraryName, Context context) throws UnsatisfiedLinkError, SecurityException, NullPointerException {\n\n        if (libraryName == null) {\n            throw new NullPointerException(\"No library name provided.\");\n        }\n\n        try {\n            // Let's see if we have ReLinker available in the project.  This is necessary for \n            // some projects that have huge numbers of local libraries bundled, and thus may \n            // trip a bug in Android's native library loader which ReLinker works around.  (If\n            // loadLibrary works properly, ReLinker will simply use the normal Android method\n            // internally.)\n            //\n            // To use ReLinker, just add it as a dependency.  For more information, see \n            // https://github.com/KeepSafe/ReLinker for ReLinker's repository.\n            //\n            Class<?> relinkClass = context.getClassLoader().loadClass(\"com.getkeepsafe.relinker.ReLinker\");\n            Class<?> relinkListenerClass = context.getClassLoader().loadClass(\"com.getkeepsafe.relinker.ReLinker$LoadListener\");\n            Class<?> contextClass = context.getClassLoader().loadClass(\"android.content.Context\");\n            Class<?> stringClass = context.getClassLoader().loadClass(\"java.lang.String\");\n\n            // Get a 'force' instance of the ReLinker, so we can ensure libraries are reinstalled if \n            // they've changed during updates.\n            Method forceMethod = relinkClass.getDeclaredMethod(\"force\");\n            Object relinkInstance = forceMethod.invoke(null);\n            Class<?> relinkInstanceClass = relinkInstance.getClass();\n\n            // Actually load the library!\n            Method loadMethod = relinkInstanceClass.getDeclaredMethod(\"loadLibrary\", contextClass, stringClass, stringClass, relinkListenerClass);\n            loadMethod.invoke(relinkInstance, context, libraryName, null, null);\n        }\n        catch (final Throwable e) {\n            // Fall back\n            try {\n                System.loadLibrary(libraryName);\n            }\n            catch (final UnsatisfiedLinkError ule) {\n                throw ule;\n            }\n            catch (final SecurityException se) {\n                throw se;\n            }\n        }\n    }\n\n    protected static Context mContext;\n}\n"
  },
  {
    "path": "android/app/src/main/java/org/libsdl/app/SDLActivity.java",
    "content": "package org.libsdl.app;\n\nimport android.app.Activity;\nimport android.app.AlertDialog;\nimport android.app.Dialog;\nimport android.app.UiModeManager;\nimport android.content.ClipboardManager;\nimport android.content.ClipData;\nimport android.content.Context;\nimport android.content.DialogInterface;\nimport android.content.Intent;\nimport android.content.pm.ActivityInfo;\nimport android.content.pm.ApplicationInfo;\nimport android.content.pm.PackageManager;\nimport android.content.res.Configuration;\nimport android.graphics.Bitmap;\nimport android.graphics.Color;\nimport android.graphics.PorterDuff;\nimport android.graphics.drawable.Drawable;\nimport android.hardware.Sensor;\nimport android.net.Uri;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.os.Handler;\nimport android.os.Message;\nimport android.text.Editable;\nimport android.text.InputType;\nimport android.text.Selection;\nimport android.util.DisplayMetrics;\nimport android.util.Log;\nimport android.util.SparseArray;\nimport android.view.Display;\nimport android.view.Gravity;\nimport android.view.InputDevice;\nimport android.view.KeyEvent;\nimport android.view.PointerIcon;\nimport android.view.Surface;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.view.Window;\nimport android.view.WindowManager;\nimport android.view.inputmethod.BaseInputConnection;\nimport android.view.inputmethod.EditorInfo;\nimport android.view.inputmethod.InputConnection;\nimport android.view.inputmethod.InputMethodManager;\nimport android.widget.Button;\nimport android.widget.EditText;\nimport android.widget.LinearLayout;\nimport android.widget.RelativeLayout;\nimport android.widget.TextView;\nimport android.widget.Toast;\n\nimport java.util.Hashtable;\nimport java.util.Locale;\n\n\n/**\n    SDL Activity\n*/\npublic class SDLActivity extends Activity implements View.OnSystemUiVisibilityChangeListener {\n    private static final String TAG = \"SDL\";\n    private static final int SDL_MAJOR_VERSION = 2;\n    private static final int SDL_MINOR_VERSION = 32;\n    private static final int SDL_MICRO_VERSION = 10;\n/*\n    // Display InputType.SOURCE/CLASS of events and devices\n    //\n    // SDLActivity.debugSource(device.getSources(), \"device[\" + device.getName() + \"]\");\n    // SDLActivity.debugSource(event.getSource(), \"event\");\n    public static void debugSource(int sources, String prefix) {\n        int s = sources;\n        int s_copy = sources;\n        String cls = \"\";\n        String src = \"\";\n        int tst = 0;\n        int FLAG_TAINTED = 0x80000000;\n\n        if ((s & InputDevice.SOURCE_CLASS_BUTTON) != 0)     cls += \" BUTTON\";\n        if ((s & InputDevice.SOURCE_CLASS_JOYSTICK) != 0)   cls += \" JOYSTICK\";\n        if ((s & InputDevice.SOURCE_CLASS_POINTER) != 0)    cls += \" POINTER\";\n        if ((s & InputDevice.SOURCE_CLASS_POSITION) != 0)   cls += \" POSITION\";\n        if ((s & InputDevice.SOURCE_CLASS_TRACKBALL) != 0)  cls += \" TRACKBALL\";\n\n\n        int s2 = s_copy & ~InputDevice.SOURCE_ANY; // keep class bits\n        s2 &= ~(  InputDevice.SOURCE_CLASS_BUTTON\n                | InputDevice.SOURCE_CLASS_JOYSTICK\n                | InputDevice.SOURCE_CLASS_POINTER\n                | InputDevice.SOURCE_CLASS_POSITION\n                | InputDevice.SOURCE_CLASS_TRACKBALL);\n\n        if (s2 != 0) cls += \"Some_Unknown\";\n\n        s2 = s_copy & InputDevice.SOURCE_ANY; // keep source only, no class;\n\n        if (Build.VERSION.SDK_INT >= 23) {\n            tst = InputDevice.SOURCE_BLUETOOTH_STYLUS;\n            if ((s & tst) == tst) src += \" BLUETOOTH_STYLUS\";\n            s2 &= ~tst;\n        }\n\n        tst = InputDevice.SOURCE_DPAD;\n        if ((s & tst) == tst) src += \" DPAD\";\n        s2 &= ~tst;\n\n        tst = InputDevice.SOURCE_GAMEPAD;\n        if ((s & tst) == tst) src += \" GAMEPAD\";\n        s2 &= ~tst;\n\n        if (Build.VERSION.SDK_INT >= 21) {\n            tst = InputDevice.SOURCE_HDMI;\n            if ((s & tst) == tst) src += \" HDMI\";\n            s2 &= ~tst;\n        }\n\n        tst = InputDevice.SOURCE_JOYSTICK;\n        if ((s & tst) == tst) src += \" JOYSTICK\";\n        s2 &= ~tst;\n\n        tst = InputDevice.SOURCE_KEYBOARD;\n        if ((s & tst) == tst) src += \" KEYBOARD\";\n        s2 &= ~tst;\n\n        tst = InputDevice.SOURCE_MOUSE;\n        if ((s & tst) == tst) src += \" MOUSE\";\n        s2 &= ~tst;\n\n        if (Build.VERSION.SDK_INT >= 26) {\n            tst = InputDevice.SOURCE_MOUSE_RELATIVE;\n            if ((s & tst) == tst) src += \" MOUSE_RELATIVE\";\n            s2 &= ~tst;\n\n            tst = InputDevice.SOURCE_ROTARY_ENCODER;\n            if ((s & tst) == tst) src += \" ROTARY_ENCODER\";\n            s2 &= ~tst;\n        }\n        tst = InputDevice.SOURCE_STYLUS;\n        if ((s & tst) == tst) src += \" STYLUS\";\n        s2 &= ~tst;\n\n        tst = InputDevice.SOURCE_TOUCHPAD;\n        if ((s & tst) == tst) src += \" TOUCHPAD\";\n        s2 &= ~tst;\n\n        tst = InputDevice.SOURCE_TOUCHSCREEN;\n        if ((s & tst) == tst) src += \" TOUCHSCREEN\";\n        s2 &= ~tst;\n\n        if (Build.VERSION.SDK_INT >= 18) {\n            tst = InputDevice.SOURCE_TOUCH_NAVIGATION;\n            if ((s & tst) == tst) src += \" TOUCH_NAVIGATION\";\n            s2 &= ~tst;\n        }\n\n        tst = InputDevice.SOURCE_TRACKBALL;\n        if ((s & tst) == tst) src += \" TRACKBALL\";\n        s2 &= ~tst;\n\n        tst = InputDevice.SOURCE_ANY;\n        if ((s & tst) == tst) src += \" ANY\";\n        s2 &= ~tst;\n\n        if (s == FLAG_TAINTED) src += \" FLAG_TAINTED\";\n        s2 &= ~FLAG_TAINTED;\n\n        if (s2 != 0) src += \" Some_Unknown\";\n\n        Log.v(TAG, prefix + \"int=\" + s_copy + \" CLASS={\" + cls + \" } source(s):\" + src);\n    }\n*/\n\n    public static boolean mIsResumedCalled, mHasFocus;\n    public static final boolean mHasMultiWindow = (Build.VERSION.SDK_INT >= 24  /* Android 7.0 (N) */);\n\n    // Cursor types\n    // private static final int SDL_SYSTEM_CURSOR_NONE = -1;\n    private static final int SDL_SYSTEM_CURSOR_ARROW = 0;\n    private static final int SDL_SYSTEM_CURSOR_IBEAM = 1;\n    private static final int SDL_SYSTEM_CURSOR_WAIT = 2;\n    private static final int SDL_SYSTEM_CURSOR_CROSSHAIR = 3;\n    private static final int SDL_SYSTEM_CURSOR_WAITARROW = 4;\n    private static final int SDL_SYSTEM_CURSOR_SIZENWSE = 5;\n    private static final int SDL_SYSTEM_CURSOR_SIZENESW = 6;\n    private static final int SDL_SYSTEM_CURSOR_SIZEWE = 7;\n    private static final int SDL_SYSTEM_CURSOR_SIZENS = 8;\n    private static final int SDL_SYSTEM_CURSOR_SIZEALL = 9;\n    private static final int SDL_SYSTEM_CURSOR_NO = 10;\n    private static final int SDL_SYSTEM_CURSOR_HAND = 11;\n\n    protected static final int SDL_ORIENTATION_UNKNOWN = 0;\n    protected static final int SDL_ORIENTATION_LANDSCAPE = 1;\n    protected static final int SDL_ORIENTATION_LANDSCAPE_FLIPPED = 2;\n    protected static final int SDL_ORIENTATION_PORTRAIT = 3;\n    protected static final int SDL_ORIENTATION_PORTRAIT_FLIPPED = 4;\n\n    protected static int mCurrentOrientation;\n    protected static Locale mCurrentLocale;\n\n    // Handle the state of the native layer\n    public enum NativeState {\n           INIT, RESUMED, PAUSED\n    }\n\n    public static NativeState mNextNativeState;\n    public static NativeState mCurrentNativeState;\n\n    /** If shared libraries (e.g. SDL or the native application) could not be loaded. */\n    public static boolean mBrokenLibraries = true;\n\n    // Main components\n    protected static SDLActivity mSingleton;\n    protected static SDLSurface mSurface;\n    protected static DummyEdit mTextEdit;\n    protected static boolean mScreenKeyboardShown;\n    protected static ViewGroup mLayout;\n    protected static SDLClipboardHandler mClipboardHandler;\n    protected static Hashtable<Integer, PointerIcon> mCursors;\n    protected static int mLastCursorID;\n    protected static SDLGenericMotionListener_API12 mMotionListener;\n    protected static HIDDeviceManager mHIDDeviceManager;\n\n    // This is what SDL runs in. It invokes SDL_main(), eventually\n    protected static Thread mSDLThread;\n\n    protected static SDLGenericMotionListener_API12 getMotionListener() {\n        if (mMotionListener == null) {\n            if (Build.VERSION.SDK_INT >= 26 /* Android 8.0 (O) */) {\n                mMotionListener = new SDLGenericMotionListener_API26();\n            } else if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {\n                mMotionListener = new SDLGenericMotionListener_API24();\n            } else {\n                mMotionListener = new SDLGenericMotionListener_API12();\n            }\n        }\n\n        return mMotionListener;\n    }\n\n    /**\n     * This method returns the name of the shared object with the application entry point\n     * It can be overridden by derived classes.\n     */\n    protected String getMainSharedObject() {\n        String library;\n        String[] libraries = SDLActivity.mSingleton.getLibraries();\n        if (libraries.length > 0) {\n            library = \"lib\" + libraries[libraries.length - 1] + \".so\";\n        } else {\n            library = \"libmain.so\";\n        }\n        return getContext().getApplicationInfo().nativeLibraryDir + \"/\" + library;\n    }\n\n    /**\n     * This method returns the name of the application entry point\n     * It can be overridden by derived classes.\n     */\n    protected String getMainFunction() {\n        return \"SDL_main\";\n    }\n\n    /**\n     * This method is called by SDL before loading the native shared libraries.\n     * It can be overridden to provide names of shared libraries to be loaded.\n     * The default implementation returns the defaults. It never returns null.\n     * An array returned by a new implementation must at least contain \"SDL2\".\n     * Also keep in mind that the order the libraries are loaded may matter.\n     * @return names of shared libraries to be loaded (e.g. \"SDL2\", \"main\").\n     */\n    protected String[] getLibraries() {\n        return new String[] {\n            \"SDL2\",\n            // \"SDL2_image\",\n            // \"SDL2_mixer\",\n            // \"SDL2_net\",\n            // \"SDL2_ttf\",\n            \"main\"\n        };\n    }\n\n    // Load the .so\n    public void loadLibraries() {\n       for (String lib : getLibraries()) {\n          SDL.loadLibrary(lib, this);\n       }\n    }\n\n    /**\n     * This method is called by SDL before starting the native application thread.\n     * It can be overridden to provide the arguments after the application name.\n     * The default implementation returns an empty array. It never returns null.\n     * @return arguments for the native application.\n     */\n    protected String[] getArguments() {\n        return new String[0];\n    }\n\n    public static void initialize() {\n        // The static nature of the singleton and Android quirkyness force us to initialize everything here\n        // Otherwise, when exiting the app and returning to it, these variables *keep* their pre exit values\n        mSingleton = null;\n        mSurface = null;\n        mTextEdit = null;\n        mLayout = null;\n        mClipboardHandler = null;\n        mCursors = new Hashtable<Integer, PointerIcon>();\n        mLastCursorID = 0;\n        mSDLThread = null;\n        mIsResumedCalled = false;\n        mHasFocus = true;\n        mNextNativeState = NativeState.INIT;\n        mCurrentNativeState = NativeState.INIT;\n    }\n    \n    protected SDLSurface createSDLSurface(Context context) {\n        return new SDLSurface(context);\n    }\n\n    // Setup\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        Log.v(TAG, \"Device: \" + Build.DEVICE);\n        Log.v(TAG, \"Model: \" + Build.MODEL);\n        Log.v(TAG, \"onCreate()\");\n        super.onCreate(savedInstanceState);\n\n        try {\n            Thread.currentThread().setName(\"SDLActivity\");\n        } catch (Exception e) {\n            Log.v(TAG, \"modify thread properties failed \" + e.toString());\n        }\n\n        // Load shared libraries\n        String errorMsgBrokenLib = \"\";\n        try {\n            loadLibraries();\n            mBrokenLibraries = false; /* success */\n        } catch(UnsatisfiedLinkError e) {\n            System.err.println(e.getMessage());\n            mBrokenLibraries = true;\n            errorMsgBrokenLib = e.getMessage();\n        } catch(Exception e) {\n            System.err.println(e.getMessage());\n            mBrokenLibraries = true;\n            errorMsgBrokenLib = e.getMessage();\n        }\n\n        if (!mBrokenLibraries) {\n            String expected_version = String.valueOf(SDL_MAJOR_VERSION) + \".\" +\n                                      String.valueOf(SDL_MINOR_VERSION) + \".\" +\n                                      String.valueOf(SDL_MICRO_VERSION);\n            String version = nativeGetVersion();\n            if (!version.equals(expected_version)) {\n                mBrokenLibraries = true;\n                errorMsgBrokenLib = \"SDL C/Java version mismatch (expected \" + expected_version + \", got \" + version + \")\";\n            }\n        }\n\n        if (mBrokenLibraries) {\n            mSingleton = this;\n            AlertDialog.Builder dlgAlert  = new AlertDialog.Builder(this);\n            dlgAlert.setMessage(\"An error occurred while trying to start the application. Please try again and/or reinstall.\"\n                  + System.getProperty(\"line.separator\")\n                  + System.getProperty(\"line.separator\")\n                  + \"Error: \" + errorMsgBrokenLib);\n            dlgAlert.setTitle(\"SDL Error\");\n            dlgAlert.setPositiveButton(\"Exit\",\n                new DialogInterface.OnClickListener() {\n                    @Override\n                    public void onClick(DialogInterface dialog,int id) {\n                        // if this button is clicked, close current activity\n                        SDLActivity.mSingleton.finish();\n                    }\n                });\n           dlgAlert.setCancelable(false);\n           dlgAlert.create().show();\n\n           return;\n        }\n\n        // Set up JNI\n        SDL.setupJNI();\n\n        // Initialize state\n        SDL.initialize();\n\n        // So we can call stuff from static callbacks\n        mSingleton = this;\n        SDL.setContext(this);\n\n        mClipboardHandler = new SDLClipboardHandler();\n\n        mHIDDeviceManager = HIDDeviceManager.acquire(this);\n\n        // Set up the surface\n        mSurface = createSDLSurface(this);\n\n        mLayout = new RelativeLayout(this);\n        mLayout.addView(mSurface);\n\n        // Get our current screen orientation and pass it down.\n        mCurrentOrientation = SDLActivity.getCurrentOrientation();\n        // Only record current orientation\n        SDLActivity.onNativeOrientationChanged(mCurrentOrientation);\n\n        try {\n            if (Build.VERSION.SDK_INT < 24 /* Android 7.0 (N) */) {\n                mCurrentLocale = getContext().getResources().getConfiguration().locale;\n            } else {\n                mCurrentLocale = getContext().getResources().getConfiguration().getLocales().get(0);\n            }\n        } catch(Exception ignored) {\n        }\n\n        setContentView(mLayout);\n\n        setWindowStyle(false);\n\n        getWindow().getDecorView().setOnSystemUiVisibilityChangeListener(this);\n\n        // Get filename from \"Open with\" of another application\n        Intent intent = getIntent();\n        if (intent != null && intent.getData() != null) {\n            String filename = intent.getData().getPath();\n            if (filename != null) {\n                Log.v(TAG, \"Got filename: \" + filename);\n                SDLActivity.onNativeDropFile(filename);\n            }\n        }\n    }\n\n    protected void pauseNativeThread() {\n        mNextNativeState = NativeState.PAUSED;\n        mIsResumedCalled = false;\n\n        if (SDLActivity.mBrokenLibraries) {\n            return;\n        }\n\n        SDLActivity.handleNativeState();\n    }\n\n    protected void resumeNativeThread() {\n        mNextNativeState = NativeState.RESUMED;\n        mIsResumedCalled = true;\n\n        if (SDLActivity.mBrokenLibraries) {\n           return;\n        }\n\n        SDLActivity.handleNativeState();\n    }\n\n    // Events\n    @Override\n    protected void onPause() {\n        Log.v(TAG, \"onPause()\");\n        super.onPause();\n\n        if (mHIDDeviceManager != null) {\n            mHIDDeviceManager.setFrozen(true);\n        }\n        if (!mHasMultiWindow) {\n            pauseNativeThread();\n        }\n    }\n\n    @Override\n    protected void onResume() {\n        Log.v(TAG, \"onResume()\");\n        super.onResume();\n\n        if (mHIDDeviceManager != null) {\n            mHIDDeviceManager.setFrozen(false);\n        }\n        if (!mHasMultiWindow) {\n            resumeNativeThread();\n        }\n    }\n\n    @Override\n    protected void onStop() {\n        Log.v(TAG, \"onStop()\");\n        super.onStop();\n        if (mHasMultiWindow) {\n            pauseNativeThread();\n        }\n    }\n\n    @Override\n    protected void onStart() {\n        Log.v(TAG, \"onStart()\");\n        super.onStart();\n        if (mHasMultiWindow) {\n            resumeNativeThread();\n        }\n    }\n\n    public static int getCurrentOrientation() {\n        int result = SDL_ORIENTATION_UNKNOWN;\n\n        Activity activity = (Activity)getContext();\n        if (activity == null) {\n            return result;\n        }\n        Display display = activity.getWindowManager().getDefaultDisplay();\n\n        switch (display.getRotation()) {\n            case Surface.ROTATION_0:\n                result = SDL_ORIENTATION_PORTRAIT;\n                break;\n\n            case Surface.ROTATION_90:\n                result = SDL_ORIENTATION_LANDSCAPE;\n                break;\n\n            case Surface.ROTATION_180:\n                result = SDL_ORIENTATION_PORTRAIT_FLIPPED;\n                break;\n\n            case Surface.ROTATION_270:\n                result = SDL_ORIENTATION_LANDSCAPE_FLIPPED;\n                break;\n        }\n\n        return result;\n    }\n\n    @Override\n    public void onWindowFocusChanged(boolean hasFocus) {\n        super.onWindowFocusChanged(hasFocus);\n        Log.v(TAG, \"onWindowFocusChanged(): \" + hasFocus);\n\n        if (SDLActivity.mBrokenLibraries) {\n           return;\n        }\n\n        mHasFocus = hasFocus;\n        if (hasFocus) {\n           mNextNativeState = NativeState.RESUMED;\n           SDLActivity.getMotionListener().reclaimRelativeMouseModeIfNeeded();\n\n           SDLActivity.handleNativeState();\n           nativeFocusChanged(true);\n\n        } else {\n           nativeFocusChanged(false);\n           if (!mHasMultiWindow) {\n               mNextNativeState = NativeState.PAUSED;\n               SDLActivity.handleNativeState();\n           }\n        }\n    }\n\n    @Override\n    public void onLowMemory() {\n        Log.v(TAG, \"onLowMemory()\");\n        super.onLowMemory();\n\n        if (SDLActivity.mBrokenLibraries) {\n           return;\n        }\n\n        SDLActivity.nativeLowMemory();\n    }\n\n    @Override\n    public void onConfigurationChanged(Configuration newConfig) {\n        Log.v(TAG, \"onConfigurationChanged()\");\n        super.onConfigurationChanged(newConfig);\n\n        if (SDLActivity.mBrokenLibraries) {\n           return;\n        }\n\n        if (mCurrentLocale == null || !mCurrentLocale.equals(newConfig.locale)) {\n            mCurrentLocale = newConfig.locale;\n            SDLActivity.onNativeLocaleChanged();\n        }\n    }\n\n    @Override\n    protected void onDestroy() {\n        Log.v(TAG, \"onDestroy()\");\n\n        if (mHIDDeviceManager != null) {\n            HIDDeviceManager.release(mHIDDeviceManager);\n            mHIDDeviceManager = null;\n        }\n\n        SDLAudioManager.release(this);\n\n        if (SDLActivity.mBrokenLibraries) {\n           super.onDestroy();\n           return;\n        }\n\n        if (SDLActivity.mSDLThread != null) {\n\n            // Send Quit event to \"SDLThread\" thread\n            SDLActivity.nativeSendQuit();\n\n            // Wait for \"SDLThread\" thread to end\n            try {\n                SDLActivity.mSDLThread.join();\n            } catch(Exception e) {\n                Log.v(TAG, \"Problem stopping SDLThread: \" + e);\n            }\n        }\n\n        SDLActivity.nativeQuit();\n\n        super.onDestroy();\n    }\n\n    @Override\n    public void onBackPressed() {\n        // Check if we want to block the back button in case of mouse right click.\n        //\n        // If we do, the normal hardware back button will no longer work and people have to use home,\n        // but the mouse right click will work.\n        //\n        boolean trapBack = SDLActivity.nativeGetHintBoolean(\"SDL_ANDROID_TRAP_BACK_BUTTON\", false);\n        if (trapBack) {\n            // Exit and let the mouse handler handle this button (if appropriate)\n            return;\n        }\n\n        // Default system back button behavior.\n        if (!isFinishing()) {\n            super.onBackPressed();\n        }\n    }\n\n    // Called by JNI from SDL.\n    public static void manualBackButton() {\n        mSingleton.pressBackButton();\n    }\n\n    // Used to get us onto the activity's main thread\n    public void pressBackButton() {\n        runOnUiThread(new Runnable() {\n            @Override\n            public void run() {\n                if (!SDLActivity.this.isFinishing()) {\n                    SDLActivity.this.superOnBackPressed();\n                }\n            }\n        });\n    }\n\n    // Used to access the system back behavior.\n    public void superOnBackPressed() {\n        super.onBackPressed();\n    }\n\n    @Override\n    public boolean dispatchKeyEvent(KeyEvent event) {\n\n        if (SDLActivity.mBrokenLibraries) {\n           return false;\n        }\n\n        int keyCode = event.getKeyCode();\n        // Ignore certain special keys so they're handled by Android\n        if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN ||\n            keyCode == KeyEvent.KEYCODE_VOLUME_UP ||\n            keyCode == KeyEvent.KEYCODE_CAMERA ||\n            keyCode == KeyEvent.KEYCODE_ZOOM_IN || /* API 11 */\n            keyCode == KeyEvent.KEYCODE_ZOOM_OUT /* API 11 */\n            ) {\n            return false;\n        }\n        return super.dispatchKeyEvent(event);\n    }\n\n    /* Transition to next state */\n    public static void handleNativeState() {\n\n        if (mNextNativeState == mCurrentNativeState) {\n            // Already in same state, discard.\n            return;\n        }\n\n        // Try a transition to init state\n        if (mNextNativeState == NativeState.INIT) {\n\n            mCurrentNativeState = mNextNativeState;\n            return;\n        }\n\n        // Try a transition to paused state\n        if (mNextNativeState == NativeState.PAUSED) {\n            if (mSDLThread != null) {\n                nativePause();\n            }\n            if (mSurface != null) {\n                mSurface.handlePause();\n            }\n            mCurrentNativeState = mNextNativeState;\n            return;\n        }\n\n        // Try a transition to resumed state\n        if (mNextNativeState == NativeState.RESUMED) {\n            if (mSurface.mIsSurfaceReady && mHasFocus && mIsResumedCalled) {\n                if (mSDLThread == null) {\n                    // This is the entry point to the C app.\n                    // Start up the C app thread and enable sensor input for the first time\n                    // FIXME: Why aren't we enabling sensor input at start?\n\n                    mSDLThread = new Thread(new SDLMain(), \"SDLThread\");\n                    mSurface.enableSensor(Sensor.TYPE_ACCELEROMETER, true);\n                    mSDLThread.start();\n\n                    // No nativeResume(), don't signal Android_ResumeSem\n                } else {\n                    nativeResume();\n                }\n                mSurface.handleResume();\n\n                mCurrentNativeState = mNextNativeState;\n            }\n        }\n    }\n\n    // Messages from the SDLMain thread\n    static final int COMMAND_CHANGE_TITLE = 1;\n    static final int COMMAND_CHANGE_WINDOW_STYLE = 2;\n    static final int COMMAND_TEXTEDIT_HIDE = 3;\n    static final int COMMAND_SET_KEEP_SCREEN_ON = 5;\n\n    protected static final int COMMAND_USER = 0x8000;\n\n    protected static boolean mFullscreenModeActive;\n\n    /**\n     * This method is called by SDL if SDL did not handle a message itself.\n     * This happens if a received message contains an unsupported command.\n     * Method can be overwritten to handle Messages in a different class.\n     * @param command the command of the message.\n     * @param param the parameter of the message. May be null.\n     * @return if the message was handled in overridden method.\n     */\n    protected boolean onUnhandledMessage(int command, Object param) {\n        return false;\n    }\n\n    /**\n     * A Handler class for Messages from native SDL applications.\n     * It uses current Activities as target (e.g. for the title).\n     * static to prevent implicit references to enclosing object.\n     */\n    protected static class SDLCommandHandler extends Handler {\n        @Override\n        public void handleMessage(Message msg) {\n            Context context = SDL.getContext();\n            if (context == null) {\n                Log.e(TAG, \"error handling message, getContext() returned null\");\n                return;\n            }\n            switch (msg.arg1) {\n            case COMMAND_CHANGE_TITLE:\n                if (context instanceof Activity) {\n                    ((Activity) context).setTitle((String)msg.obj);\n                } else {\n                    Log.e(TAG, \"error handling message, getContext() returned no Activity\");\n                }\n                break;\n            case COMMAND_CHANGE_WINDOW_STYLE:\n                if (Build.VERSION.SDK_INT >= 19 /* Android 4.4 (KITKAT) */) {\n                    if (context instanceof Activity) {\n                        Window window = ((Activity) context).getWindow();\n                        if (window != null) {\n                            if ((msg.obj instanceof Integer) && ((Integer) msg.obj != 0)) {\n                                int flags = View.SYSTEM_UI_FLAG_FULLSCREEN |\n                                        View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |\n                                        View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY |\n                                        View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |\n                                        View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |\n                                        View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.INVISIBLE;\n                                window.getDecorView().setSystemUiVisibility(flags);\n                                window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);\n                                window.clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);\n                                SDLActivity.mFullscreenModeActive = true;\n                            } else {\n                                int flags = View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_VISIBLE;\n                                window.getDecorView().setSystemUiVisibility(flags);\n                                window.addFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);\n                                window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);\n                                SDLActivity.mFullscreenModeActive = false;\n                            }\n                            if (Build.VERSION.SDK_INT >= 28 /* Android 9 (Pie) */) {\n                                window.getAttributes().layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;\n                            }\n                        }\n                    } else {\n                        Log.e(TAG, \"error handling message, getContext() returned no Activity\");\n                    }\n                }\n                break;\n            case COMMAND_TEXTEDIT_HIDE:\n                if (mTextEdit != null) {\n                    // Note: On some devices setting view to GONE creates a flicker in landscape.\n                    // Setting the View's sizes to 0 is similar to GONE but without the flicker.\n                    // The sizes will be set to useful values when the keyboard is shown again.\n                    mTextEdit.setLayoutParams(new RelativeLayout.LayoutParams(0, 0));\n\n                    InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);\n                    imm.hideSoftInputFromWindow(mTextEdit.getWindowToken(), 0);\n\n                    mScreenKeyboardShown = false;\n\n                    mSurface.requestFocus();\n                }\n                break;\n            case COMMAND_SET_KEEP_SCREEN_ON:\n            {\n                if (context instanceof Activity) {\n                    Window window = ((Activity) context).getWindow();\n                    if (window != null) {\n                        if ((msg.obj instanceof Integer) && ((Integer) msg.obj != 0)) {\n                            window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);\n                        } else {\n                            window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);\n                        }\n                    }\n                }\n                break;\n            }\n            default:\n                if ((context instanceof SDLActivity) && !((SDLActivity) context).onUnhandledMessage(msg.arg1, msg.obj)) {\n                    Log.e(TAG, \"error handling message, command is \" + msg.arg1);\n                }\n            }\n        }\n    }\n\n    // Handler for the messages\n    Handler commandHandler = new SDLCommandHandler();\n\n    // Send a message from the SDLMain thread\n    boolean sendCommand(int command, Object data) {\n        Message msg = commandHandler.obtainMessage();\n        msg.arg1 = command;\n        msg.obj = data;\n        boolean result = commandHandler.sendMessage(msg);\n\n        if (Build.VERSION.SDK_INT >= 19 /* Android 4.4 (KITKAT) */) {\n            if (command == COMMAND_CHANGE_WINDOW_STYLE) {\n                // Ensure we don't return until the resize has actually happened,\n                // or 500ms have passed.\n\n                boolean bShouldWait = false;\n\n                if (data instanceof Integer) {\n                    // Let's figure out if we're already laid out fullscreen or not.\n                    Display display = ((WindowManager) getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();\n                    DisplayMetrics realMetrics = new DisplayMetrics();\n                    display.getRealMetrics(realMetrics);\n\n                    boolean bFullscreenLayout = ((realMetrics.widthPixels == mSurface.getWidth()) &&\n                            (realMetrics.heightPixels == mSurface.getHeight()));\n\n                    if ((Integer) data == 1) {\n                        // If we aren't laid out fullscreen or actively in fullscreen mode already, we're going\n                        // to change size and should wait for surfaceChanged() before we return, so the size\n                        // is right back in native code.  If we're already laid out fullscreen, though, we're\n                        // not going to change size even if we change decor modes, so we shouldn't wait for\n                        // surfaceChanged() -- which may not even happen -- and should return immediately.\n                        bShouldWait = !bFullscreenLayout;\n                    } else {\n                        // If we're laid out fullscreen (even if the status bar and nav bar are present),\n                        // or are actively in fullscreen, we're going to change size and should wait for\n                        // surfaceChanged before we return, so the size is right back in native code.\n                        bShouldWait = bFullscreenLayout;\n                    }\n                }\n\n                if (bShouldWait && (SDLActivity.getContext() != null)) {\n                    // We'll wait for the surfaceChanged() method, which will notify us\n                    // when called.  That way, we know our current size is really the\n                    // size we need, instead of grabbing a size that's still got\n                    // the navigation and/or status bars before they're hidden.\n                    //\n                    // We'll wait for up to half a second, because some devices\n                    // take a surprisingly long time for the surface resize, but\n                    // then we'll just give up and return.\n                    //\n                    synchronized (SDLActivity.getContext()) {\n                        try {\n                            SDLActivity.getContext().wait(500);\n                        } catch (InterruptedException ie) {\n                            ie.printStackTrace();\n                        }\n                    }\n                }\n            }\n        }\n\n        return result;\n    }\n\n    // C functions we call\n    public static native String nativeGetVersion();\n    public static native int nativeSetupJNI();\n    public static native int nativeRunMain(String library, String function, Object arguments);\n    public static native void nativeLowMemory();\n    public static native void nativeSendQuit();\n    public static native void nativeQuit();\n    public static native void nativePause();\n    public static native void nativeResume();\n    public static native void nativeFocusChanged(boolean hasFocus);\n    public static native void onNativeDropFile(String filename);\n    public static native void nativeSetScreenResolution(int surfaceWidth, int surfaceHeight, int deviceWidth, int deviceHeight, float rate);\n    public static native void onNativeResize();\n    public static native void onNativeKeyDown(int keycode);\n    public static native void onNativeKeyUp(int keycode);\n    public static native boolean onNativeSoftReturnKey();\n    public static native void onNativeKeyboardFocusLost();\n    public static native void onNativeMouse(int button, int action, float x, float y, boolean relative);\n    public static native void onNativeTouch(int touchDevId, int pointerFingerId,\n                                            int action, float x,\n                                            float y, float p);\n    public static native void onNativeAccel(float x, float y, float z);\n    public static native void onNativeClipboardChanged();\n    public static native void onNativeSurfaceCreated();\n    public static native void onNativeSurfaceChanged();\n    public static native void onNativeSurfaceDestroyed();\n    public static native String nativeGetHint(String name);\n    public static native boolean nativeGetHintBoolean(String name, boolean default_value);\n    public static native void nativeSetenv(String name, String value);\n    public static native void onNativeOrientationChanged(int orientation);\n    public static native void nativeAddTouch(int touchId, String name);\n    public static native void nativePermissionResult(int requestCode, boolean result);\n    public static native void onNativeLocaleChanged();\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static boolean setActivityTitle(String title) {\n        // Called from SDLMain() thread and can't directly affect the view\n        return mSingleton.sendCommand(COMMAND_CHANGE_TITLE, title);\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static void setWindowStyle(boolean fullscreen) {\n        // Called from SDLMain() thread and can't directly affect the view\n        mSingleton.sendCommand(COMMAND_CHANGE_WINDOW_STYLE, fullscreen ? 1 : 0);\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     * This is a static method for JNI convenience, it calls a non-static method\n     * so that is can be overridden\n     */\n    public static void setOrientation(int w, int h, boolean resizable, String hint)\n    {\n        if (mSingleton != null) {\n            mSingleton.setOrientationBis(w, h, resizable, hint);\n        }\n    }\n\n    /**\n     * This can be overridden\n     */\n    public void setOrientationBis(int w, int h, boolean resizable, String hint)\n    {\n        int orientation_landscape = -1;\n        int orientation_portrait = -1;\n\n        /* If set, hint \"explicitly controls which UI orientations are allowed\". */\n        if (hint.contains(\"LandscapeRight\") && hint.contains(\"LandscapeLeft\")) {\n            orientation_landscape = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE;\n        } else if (hint.contains(\"LandscapeLeft\")) {\n            orientation_landscape = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;\n        } else if (hint.contains(\"LandscapeRight\")) {\n            orientation_landscape = ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;\n        }\n\n        /* exact match to 'Portrait' to distinguish with PortraitUpsideDown */\n        boolean contains_Portrait = hint.contains(\"Portrait \") || hint.endsWith(\"Portrait\");\n\n        if (contains_Portrait && hint.contains(\"PortraitUpsideDown\")) {\n            orientation_portrait = ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT;\n        } else if (contains_Portrait) {\n            orientation_portrait = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;\n        } else if (hint.contains(\"PortraitUpsideDown\")) {\n            orientation_portrait = ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT;\n        }\n\n        boolean is_landscape_allowed = (orientation_landscape != -1);\n        boolean is_portrait_allowed = (orientation_portrait != -1);\n        int req; /* Requested orientation */\n\n        /* No valid hint, nothing is explicitly allowed */\n        if (!is_portrait_allowed && !is_landscape_allowed) {\n            if (resizable) {\n                /* All orientations are allowed, respecting user orientation lock setting */\n                req = ActivityInfo.SCREEN_ORIENTATION_FULL_USER;\n            } else {\n                /* Fixed window and nothing specified. Get orientation from w/h of created window */\n                req = (w > h ? ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE : ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT);\n            }\n        } else {\n            /* At least one orientation is allowed */\n            if (resizable) {\n                if (is_portrait_allowed && is_landscape_allowed) {\n                    /* hint allows both landscape and portrait, promote to full user */\n                    req = ActivityInfo.SCREEN_ORIENTATION_FULL_USER;\n                } else {\n                    /* Use the only one allowed \"orientation\" */\n                    req = (is_landscape_allowed ? orientation_landscape : orientation_portrait);\n                }\n            } else {\n                /* Fixed window and both orientations are allowed. Choose one. */\n                if (is_portrait_allowed && is_landscape_allowed) {\n                    req = (w > h ? orientation_landscape : orientation_portrait);\n                } else {\n                    /* Use the only one allowed \"orientation\" */\n                    req = (is_landscape_allowed ? orientation_landscape : orientation_portrait);\n                }\n            }\n        }\n\n        Log.v(TAG, \"setOrientation() requestedOrientation=\" + req + \" width=\" + w +\" height=\"+ h +\" resizable=\" + resizable + \" hint=\" + hint);\n        mSingleton.setRequestedOrientation(req);\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static void minimizeWindow() {\n\n        if (mSingleton == null) {\n            return;\n        }\n\n        Intent startMain = new Intent(Intent.ACTION_MAIN);\n        startMain.addCategory(Intent.CATEGORY_HOME);\n        startMain.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n        mSingleton.startActivity(startMain);\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static boolean shouldMinimizeOnFocusLoss() {\n/*\n        if (Build.VERSION.SDK_INT >= 24) {\n            if (mSingleton == null) {\n                return true;\n            }\n\n            if (mSingleton.isInMultiWindowMode()) {\n                return false;\n            }\n\n            if (mSingleton.isInPictureInPictureMode()) {\n                return false;\n            }\n        }\n\n        return true;\n*/\n        return false;\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static boolean isScreenKeyboardShown()\n    {\n        if (mTextEdit == null) {\n            return false;\n        }\n\n        if (!mScreenKeyboardShown) {\n            return false;\n        }\n\n        InputMethodManager imm = (InputMethodManager) SDL.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);\n        return imm.isAcceptingText();\n\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static boolean supportsRelativeMouse()\n    {\n        // DeX mode in Samsung Experience 9.0 and earlier doesn't support relative mice properly under\n        // Android 7 APIs, and simply returns no data under Android 8 APIs.\n        //\n        // This is fixed in Samsung Experience 9.5, which corresponds to Android 8.1.0, and\n        // thus SDK version 27.  If we are in DeX mode and not API 27 or higher, as a result,\n        // we should stick to relative mode.\n        //\n        if (Build.VERSION.SDK_INT < 27 /* Android 8.1 (O_MR1) */ && isDeXMode()) {\n            return false;\n        }\n\n        return SDLActivity.getMotionListener().supportsRelativeMouse();\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static boolean setRelativeMouseEnabled(boolean enabled)\n    {\n        if (enabled && !supportsRelativeMouse()) {\n            return false;\n        }\n\n        return SDLActivity.getMotionListener().setRelativeMouseEnabled(enabled);\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static boolean sendMessage(int command, int param) {\n        if (mSingleton == null) {\n            return false;\n        }\n        return mSingleton.sendCommand(command, param);\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static Context getContext() {\n        return SDL.getContext();\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static boolean isAndroidTV() {\n        UiModeManager uiModeManager = (UiModeManager) getContext().getSystemService(UI_MODE_SERVICE);\n        if (uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION) {\n            return true;\n        }\n        if (Build.MANUFACTURER.equals(\"MINIX\") && Build.MODEL.equals(\"NEO-U1\")) {\n            return true;\n        }\n        if (Build.MANUFACTURER.equals(\"Amlogic\") && Build.MODEL.equals(\"X96-W\")) {\n            return true;\n        }\n        return Build.MANUFACTURER.equals(\"Amlogic\") && Build.MODEL.startsWith(\"TV\");\n    }\n\n    public static double getDiagonal()\n    {\n        DisplayMetrics metrics = new DisplayMetrics();\n        Activity activity = (Activity)getContext();\n        if (activity == null) {\n            return 0.0;\n        }\n        activity.getWindowManager().getDefaultDisplay().getMetrics(metrics);\n\n        double dWidthInches = metrics.widthPixels / (double)metrics.xdpi;\n        double dHeightInches = metrics.heightPixels / (double)metrics.ydpi;\n\n        return Math.sqrt((dWidthInches * dWidthInches) + (dHeightInches * dHeightInches));\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static boolean isTablet() {\n        // If our diagonal size is seven inches or greater, we consider ourselves a tablet.\n        return (getDiagonal() >= 7.0);\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static boolean isChromebook() {\n        if (getContext() == null) {\n            return false;\n        }\n        return getContext().getPackageManager().hasSystemFeature(\"org.chromium.arc.device_management\");\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static boolean isDeXMode() {\n        if (Build.VERSION.SDK_INT < 24 /* Android 7.0 (N) */) {\n            return false;\n        }\n        try {\n            final Configuration config = getContext().getResources().getConfiguration();\n            final Class<?> configClass = config.getClass();\n            return configClass.getField(\"SEM_DESKTOP_MODE_ENABLED\").getInt(configClass)\n                    == configClass.getField(\"semDesktopModeEnabled\").getInt(config);\n        } catch(Exception ignored) {\n            return false;\n        }\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static DisplayMetrics getDisplayDPI() {\n        return getContext().getResources().getDisplayMetrics();\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static boolean getManifestEnvironmentVariables() {\n        try {\n            if (getContext() == null) {\n                return false;\n            }\n\n            ApplicationInfo applicationInfo = getContext().getPackageManager().getApplicationInfo(getContext().getPackageName(), PackageManager.GET_META_DATA);\n            Bundle bundle = applicationInfo.metaData;\n            if (bundle == null) {\n                return false;\n            }\n            String prefix = \"SDL_ENV.\";\n            final int trimLength = prefix.length();\n            for (String key : bundle.keySet()) {\n                if (key.startsWith(prefix)) {\n                    String name = key.substring(trimLength);\n                    String value = bundle.get(key).toString();\n                    nativeSetenv(name, value);\n                }\n            }\n            /* environment variables set! */\n            return true;\n        } catch (Exception e) {\n           Log.v(TAG, \"exception \" + e.toString());\n        }\n        return false;\n    }\n\n    // This method is called by SDLControllerManager's API 26 Generic Motion Handler.\n    public static View getContentView() {\n        return mLayout;\n    }\n\n    static class ShowTextInputTask implements Runnable {\n        /*\n         * This is used to regulate the pan&scan method to have some offset from\n         * the bottom edge of the input region and the top edge of an input\n         * method (soft keyboard)\n         */\n        static final int HEIGHT_PADDING = 15;\n\n        public int x, y, w, h;\n\n        public ShowTextInputTask(int x, int y, int w, int h) {\n            this.x = x;\n            this.y = y;\n            this.w = w;\n            this.h = h;\n\n            /* Minimum size of 1 pixel, so it takes focus. */\n            if (this.w <= 0) {\n                this.w = 1;\n            }\n            if (this.h + HEIGHT_PADDING <= 0) {\n                this.h = 1 - HEIGHT_PADDING;\n            }\n        }\n\n        @Override\n        public void run() {\n            RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(w, h + HEIGHT_PADDING);\n            params.leftMargin = x;\n            params.topMargin = y;\n\n            if (mTextEdit == null) {\n                mTextEdit = new DummyEdit(SDL.getContext());\n\n                mLayout.addView(mTextEdit, params);\n            } else {\n                mTextEdit.setLayoutParams(params);\n            }\n\n            mTextEdit.setVisibility(View.VISIBLE);\n            mTextEdit.requestFocus();\n\n            InputMethodManager imm = (InputMethodManager) SDL.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);\n            imm.showSoftInput(mTextEdit, 0);\n\n            mScreenKeyboardShown = true;\n        }\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static boolean showTextInput(int x, int y, int w, int h) {\n        // Transfer the task to the main thread as a Runnable\n        return mSingleton.commandHandler.post(new ShowTextInputTask(x, y, w, h));\n    }\n\n    public static boolean isTextInputEvent(KeyEvent event) {\n\n        // Key pressed with Ctrl should be sent as SDL_KEYDOWN/SDL_KEYUP and not SDL_TEXTINPUT\n        if (event.isCtrlPressed()) {\n            return false;\n        }\n\n        return event.isPrintingKey() || event.getKeyCode() == KeyEvent.KEYCODE_SPACE;\n    }\n\n    public static boolean handleKeyEvent(View v, int keyCode, KeyEvent event, InputConnection ic) {\n        int deviceId = event.getDeviceId();\n        int source = event.getSource();\n\n        if (source == InputDevice.SOURCE_UNKNOWN) {\n            InputDevice device = InputDevice.getDevice(deviceId);\n            if (device != null) {\n                source = device.getSources();\n            }\n        }\n\n//        if (event.getAction() == KeyEvent.ACTION_DOWN) {\n//            Log.v(\"SDL\", \"key down: \" + keyCode + \", deviceId = \" + deviceId + \", source = \" + source);\n//        } else if (event.getAction() == KeyEvent.ACTION_UP) {\n//            Log.v(\"SDL\", \"key up: \" + keyCode + \", deviceId = \" + deviceId + \", source = \" + source);\n//        }\n\n        // Dispatch the different events depending on where they come from\n        // Some SOURCE_JOYSTICK, SOURCE_DPAD or SOURCE_GAMEPAD are also SOURCE_KEYBOARD\n        // So, we try to process them as JOYSTICK/DPAD/GAMEPAD events first, if that fails we try them as KEYBOARD\n        //\n        // Furthermore, it's possible a game controller has SOURCE_KEYBOARD and\n        // SOURCE_JOYSTICK, while its key events arrive from the keyboard source\n        // So, retrieve the device itself and check all of its sources\n        if (SDLControllerManager.isDeviceSDLJoystick(deviceId)) {\n            // Note that we process events with specific key codes here\n            if (event.getAction() == KeyEvent.ACTION_DOWN) {\n                if (SDLControllerManager.onNativePadDown(deviceId, keyCode) == 0) {\n                    return true;\n                }\n            } else if (event.getAction() == KeyEvent.ACTION_UP) {\n                if (SDLControllerManager.onNativePadUp(deviceId, keyCode) == 0) {\n                    return true;\n                }\n            }\n        }\n\n        if ((source & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE) {\n            // on some devices key events are sent for mouse BUTTON_BACK/FORWARD presses\n            // they are ignored here because sending them as mouse input to SDL is messy\n            if ((keyCode == KeyEvent.KEYCODE_BACK) || (keyCode == KeyEvent.KEYCODE_FORWARD)) {\n                switch (event.getAction()) {\n                case KeyEvent.ACTION_DOWN:\n                case KeyEvent.ACTION_UP:\n                    // mark the event as handled or it will be handled by system\n                    // handling KEYCODE_BACK by system will call onBackPressed()\n                    return true;\n                }\n            }\n        }\n\n        if (event.getAction() == KeyEvent.ACTION_DOWN) {\n            if (isTextInputEvent(event)) {\n                if (ic != null) {\n                    ic.commitText(String.valueOf((char) event.getUnicodeChar()), 1);\n                } else {\n                    SDLInputConnection.nativeCommitText(String.valueOf((char) event.getUnicodeChar()), 1);\n                }\n            }\n            onNativeKeyDown(keyCode);\n            return true;\n        } else if (event.getAction() == KeyEvent.ACTION_UP) {\n            onNativeKeyUp(keyCode);\n            return true;\n        }\n\n        return false;\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static Surface getNativeSurface() {\n        if (SDLActivity.mSurface == null) {\n            return null;\n        }\n        return SDLActivity.mSurface.getNativeSurface();\n    }\n\n    // Input\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static void initTouch() {\n        int[] ids = InputDevice.getDeviceIds();\n\n        for (int id : ids) {\n            InputDevice device = InputDevice.getDevice(id);\n            /* Allow SOURCE_TOUCHSCREEN and also Virtual InputDevices because they can send TOUCHSCREEN events */\n            if (device != null && ((device.getSources() & InputDevice.SOURCE_TOUCHSCREEN) == InputDevice.SOURCE_TOUCHSCREEN\n                    || device.isVirtual())) {\n\n                int touchDevId = device.getId();\n                /*\n                 * Prevent id to be -1, since it's used in SDL internal for synthetic events\n                 * Appears when using Android emulator, eg:\n                 *  adb shell input mouse tap 100 100\n                 *  adb shell input touchscreen tap 100 100\n                 */\n                if (touchDevId < 0) {\n                    touchDevId -= 1;\n                }\n                nativeAddTouch(touchDevId, device.getName());\n            }\n        }\n    }\n\n    // Messagebox\n\n    /** Result of current messagebox. Also used for blocking the calling thread. */\n    protected final int[] messageboxSelection = new int[1];\n\n    /**\n     * This method is called by SDL using JNI.\n     * Shows the messagebox from UI thread and block calling thread.\n     * buttonFlags, buttonIds and buttonTexts must have same length.\n     * @param buttonFlags array containing flags for every button.\n     * @param buttonIds array containing id for every button.\n     * @param buttonTexts array containing text for every button.\n     * @param colors null for default or array of length 5 containing colors.\n     * @return button id or -1.\n     */\n    public int messageboxShowMessageBox(\n            final int flags,\n            final String title,\n            final String message,\n            final int[] buttonFlags,\n            final int[] buttonIds,\n            final String[] buttonTexts,\n            final int[] colors) {\n\n        messageboxSelection[0] = -1;\n\n        // sanity checks\n\n        if ((buttonFlags.length != buttonIds.length) && (buttonIds.length != buttonTexts.length)) {\n            return -1; // implementation broken\n        }\n\n        // collect arguments for Dialog\n\n        final Bundle args = new Bundle();\n        args.putInt(\"flags\", flags);\n        args.putString(\"title\", title);\n        args.putString(\"message\", message);\n        args.putIntArray(\"buttonFlags\", buttonFlags);\n        args.putIntArray(\"buttonIds\", buttonIds);\n        args.putStringArray(\"buttonTexts\", buttonTexts);\n        args.putIntArray(\"colors\", colors);\n\n        // trigger Dialog creation on UI thread\n\n        runOnUiThread(new Runnable() {\n            @Override\n            public void run() {\n                messageboxCreateAndShow(args);\n            }\n        });\n\n        // block the calling thread\n\n        synchronized (messageboxSelection) {\n            try {\n                messageboxSelection.wait();\n            } catch (InterruptedException ex) {\n                ex.printStackTrace();\n                return -1;\n            }\n        }\n\n        // return selected value\n\n        return messageboxSelection[0];\n    }\n\n    protected void messageboxCreateAndShow(Bundle args) {\n\n        // TODO set values from \"flags\" to messagebox dialog\n\n        // get colors\n\n        int[] colors = args.getIntArray(\"colors\");\n        int backgroundColor;\n        int textColor;\n        int buttonBorderColor;\n        int buttonBackgroundColor;\n        int buttonSelectedColor;\n        if (colors != null) {\n            int i = -1;\n            backgroundColor = colors[++i];\n            textColor = colors[++i];\n            buttonBorderColor = colors[++i];\n            buttonBackgroundColor = colors[++i];\n            buttonSelectedColor = colors[++i];\n        } else {\n            backgroundColor = Color.TRANSPARENT;\n            textColor = Color.TRANSPARENT;\n            buttonBorderColor = Color.TRANSPARENT;\n            buttonBackgroundColor = Color.TRANSPARENT;\n            buttonSelectedColor = Color.TRANSPARENT;\n        }\n\n        // create dialog with title and a listener to wake up calling thread\n\n        final AlertDialog dialog = new AlertDialog.Builder(this).create();\n        dialog.setTitle(args.getString(\"title\"));\n        dialog.setCancelable(false);\n        dialog.setOnDismissListener(new DialogInterface.OnDismissListener() {\n            @Override\n            public void onDismiss(DialogInterface unused) {\n                synchronized (messageboxSelection) {\n                    messageboxSelection.notify();\n                }\n            }\n        });\n\n        // create text\n\n        TextView message = new TextView(this);\n        message.setGravity(Gravity.CENTER);\n        message.setText(args.getString(\"message\"));\n        if (textColor != Color.TRANSPARENT) {\n            message.setTextColor(textColor);\n        }\n\n        // create buttons\n\n        int[] buttonFlags = args.getIntArray(\"buttonFlags\");\n        int[] buttonIds = args.getIntArray(\"buttonIds\");\n        String[] buttonTexts = args.getStringArray(\"buttonTexts\");\n\n        final SparseArray<Button> mapping = new SparseArray<Button>();\n\n        LinearLayout buttons = new LinearLayout(this);\n        buttons.setOrientation(LinearLayout.HORIZONTAL);\n        buttons.setGravity(Gravity.CENTER);\n        for (int i = 0; i < buttonTexts.length; ++i) {\n            Button button = new Button(this);\n            final int id = buttonIds[i];\n            button.setOnClickListener(new View.OnClickListener() {\n                @Override\n                public void onClick(View v) {\n                    messageboxSelection[0] = id;\n                    dialog.dismiss();\n                }\n            });\n            if (buttonFlags[i] != 0) {\n                // see SDL_messagebox.h\n                if ((buttonFlags[i] & 0x00000001) != 0) {\n                    mapping.put(KeyEvent.KEYCODE_ENTER, button);\n                }\n                if ((buttonFlags[i] & 0x00000002) != 0) {\n                    mapping.put(KeyEvent.KEYCODE_ESCAPE, button); /* API 11 */\n                }\n            }\n            button.setText(buttonTexts[i]);\n            if (textColor != Color.TRANSPARENT) {\n                button.setTextColor(textColor);\n            }\n            if (buttonBorderColor != Color.TRANSPARENT) {\n                // TODO set color for border of messagebox button\n            }\n            if (buttonBackgroundColor != Color.TRANSPARENT) {\n                Drawable drawable = button.getBackground();\n                if (drawable == null) {\n                    // setting the color this way removes the style\n                    button.setBackgroundColor(buttonBackgroundColor);\n                } else {\n                    // setting the color this way keeps the style (gradient, padding, etc.)\n                    drawable.setColorFilter(buttonBackgroundColor, PorterDuff.Mode.MULTIPLY);\n                }\n            }\n            if (buttonSelectedColor != Color.TRANSPARENT) {\n                // TODO set color for selected messagebox button\n            }\n            buttons.addView(button);\n        }\n\n        // create content\n\n        LinearLayout content = new LinearLayout(this);\n        content.setOrientation(LinearLayout.VERTICAL);\n        content.addView(message);\n        content.addView(buttons);\n        if (backgroundColor != Color.TRANSPARENT) {\n            content.setBackgroundColor(backgroundColor);\n        }\n\n        // add content to dialog and return\n\n        dialog.setView(content);\n        dialog.setOnKeyListener(new Dialog.OnKeyListener() {\n            @Override\n            public boolean onKey(DialogInterface d, int keyCode, KeyEvent event) {\n                Button button = mapping.get(keyCode);\n                if (button != null) {\n                    if (event.getAction() == KeyEvent.ACTION_UP) {\n                        button.performClick();\n                    }\n                    return true; // also for ignored actions\n                }\n                return false;\n            }\n        });\n\n        dialog.show();\n    }\n\n    private final Runnable rehideSystemUi = new Runnable() {\n        @Override\n        public void run() {\n            if (Build.VERSION.SDK_INT >= 19 /* Android 4.4 (KITKAT) */) {\n                int flags = View.SYSTEM_UI_FLAG_FULLSCREEN |\n                        View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |\n                        View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY |\n                        View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |\n                        View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |\n                        View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.INVISIBLE;\n\n                SDLActivity.this.getWindow().getDecorView().setSystemUiVisibility(flags);\n            }\n        }\n    };\n\n    public void onSystemUiVisibilityChange(int visibility) {\n        if (SDLActivity.mFullscreenModeActive && ((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0 || (visibility & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0)) {\n\n            Handler handler = getWindow().getDecorView().getHandler();\n            if (handler != null) {\n                handler.removeCallbacks(rehideSystemUi); // Prevent a hide loop.\n                handler.postDelayed(rehideSystemUi, 2000);\n            }\n\n        }\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static boolean clipboardHasText() {\n        return mClipboardHandler.clipboardHasText();\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static String clipboardGetText() {\n        return mClipboardHandler.clipboardGetText();\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static void clipboardSetText(String string) {\n        mClipboardHandler.clipboardSetText(string);\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static int createCustomCursor(int[] colors, int width, int height, int hotSpotX, int hotSpotY) {\n        Bitmap bitmap = Bitmap.createBitmap(colors, width, height, Bitmap.Config.ARGB_8888);\n        ++mLastCursorID;\n\n        if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {\n            try {\n                mCursors.put(mLastCursorID, PointerIcon.create(bitmap, hotSpotX, hotSpotY));\n            } catch (Exception e) {\n                return 0;\n            }\n        } else {\n            return 0;\n        }\n        return mLastCursorID;\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static void destroyCustomCursor(int cursorID) {\n        if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {\n            try {\n                mCursors.remove(cursorID);\n            } catch (Exception e) {\n            }\n        }\n        return;\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static boolean setCustomCursor(int cursorID) {\n\n        if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {\n            try {\n                mSurface.setPointerIcon(mCursors.get(cursorID));\n            } catch (Exception e) {\n                return false;\n            }\n        } else {\n            return false;\n        }\n        return true;\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static boolean setSystemCursor(int cursorID) {\n        int cursor_type = 0; //PointerIcon.TYPE_NULL;\n        switch (cursorID) {\n        case SDL_SYSTEM_CURSOR_ARROW:\n            cursor_type = 1000; //PointerIcon.TYPE_ARROW;\n            break;\n        case SDL_SYSTEM_CURSOR_IBEAM:\n            cursor_type = 1008; //PointerIcon.TYPE_TEXT;\n            break;\n        case SDL_SYSTEM_CURSOR_WAIT:\n            cursor_type = 1004; //PointerIcon.TYPE_WAIT;\n            break;\n        case SDL_SYSTEM_CURSOR_CROSSHAIR:\n            cursor_type = 1007; //PointerIcon.TYPE_CROSSHAIR;\n            break;\n        case SDL_SYSTEM_CURSOR_WAITARROW:\n            cursor_type = 1004; //PointerIcon.TYPE_WAIT;\n            break;\n        case SDL_SYSTEM_CURSOR_SIZENWSE:\n            cursor_type = 1017; //PointerIcon.TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW;\n            break;\n        case SDL_SYSTEM_CURSOR_SIZENESW:\n            cursor_type = 1016; //PointerIcon.TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW;\n            break;\n        case SDL_SYSTEM_CURSOR_SIZEWE:\n            cursor_type = 1014; //PointerIcon.TYPE_HORIZONTAL_DOUBLE_ARROW;\n            break;\n        case SDL_SYSTEM_CURSOR_SIZENS:\n            cursor_type = 1015; //PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW;\n            break;\n        case SDL_SYSTEM_CURSOR_SIZEALL:\n            cursor_type = 1020; //PointerIcon.TYPE_GRAB;\n            break;\n        case SDL_SYSTEM_CURSOR_NO:\n            cursor_type = 1012; //PointerIcon.TYPE_NO_DROP;\n            break;\n        case SDL_SYSTEM_CURSOR_HAND:\n            cursor_type = 1002; //PointerIcon.TYPE_HAND;\n            break;\n        }\n        if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {\n            try {\n                mSurface.setPointerIcon(PointerIcon.getSystemIcon(SDL.getContext(), cursor_type));\n            } catch (Exception e) {\n                return false;\n            }\n        }\n        return true;\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static void requestPermission(String permission, int requestCode) {\n        if (Build.VERSION.SDK_INT < 23 /* Android 6.0 (M) */) {\n            nativePermissionResult(requestCode, true);\n            return;\n        }\n\n        Activity activity = (Activity)getContext();\n        if (activity.checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {\n            activity.requestPermissions(new String[]{permission}, requestCode);\n        } else {\n            nativePermissionResult(requestCode, true);\n        }\n    }\n\n    @Override\n    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {\n        boolean result = (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED);\n        nativePermissionResult(requestCode, result);\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static int openURL(String url)\n    {\n        try {\n            Intent i = new Intent(Intent.ACTION_VIEW);\n            i.setData(Uri.parse(url));\n\n            int flags = Intent.FLAG_ACTIVITY_NO_HISTORY | Intent.FLAG_ACTIVITY_MULTIPLE_TASK;\n            if (Build.VERSION.SDK_INT >= 21 /* Android 5.0 (LOLLIPOP) */) {\n                flags |= Intent.FLAG_ACTIVITY_NEW_DOCUMENT;\n            } else {\n                flags |= Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET;\n            }\n            i.addFlags(flags);\n\n            mSingleton.startActivity(i);\n        } catch (Exception ex) {\n            return -1;\n        }\n        return 0;\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static int showToast(String message, int duration, int gravity, int xOffset, int yOffset)\n    {\n        if(null == mSingleton) {\n            return - 1;\n        }\n\n        try\n        {\n            class OneShotTask implements Runnable {\n                String mMessage;\n                int mDuration;\n                int mGravity;\n                int mXOffset;\n                int mYOffset;\n\n                OneShotTask(String message, int duration, int gravity, int xOffset, int yOffset) {\n                    mMessage  = message;\n                    mDuration = duration;\n                    mGravity  = gravity;\n                    mXOffset  = xOffset;\n                    mYOffset  = yOffset;\n                }\n\n                public void run() {\n                    try\n                    {\n                        Toast toast = Toast.makeText(mSingleton, mMessage, mDuration);\n                        if (mGravity >= 0) {\n                            toast.setGravity(mGravity, mXOffset, mYOffset);\n                        }\n                        toast.show();\n                    } catch(Exception ex) {\n                        Log.e(TAG, ex.getMessage());\n                    }\n                }\n            }\n            mSingleton.runOnUiThread(new OneShotTask(message, duration, gravity, xOffset, yOffset));\n        } catch(Exception ex) {\n            return -1;\n        }\n        return 0;\n    }\n}\n\n/**\n    Simple runnable to start the SDL application\n*/\nclass SDLMain implements Runnable {\n    @Override\n    public void run() {\n        // Runs SDL_main()\n        String library = SDLActivity.mSingleton.getMainSharedObject();\n        String function = SDLActivity.mSingleton.getMainFunction();\n        String[] arguments = SDLActivity.mSingleton.getArguments();\n\n        try {\n            android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_DISPLAY);\n        } catch (Exception e) {\n            Log.v(\"SDL\", \"modify thread properties failed \" + e.toString());\n        }\n\n        Log.v(\"SDL\", \"Running main function \" + function + \" from library \" + library);\n\n        SDLActivity.nativeRunMain(library, function, arguments);\n\n        Log.v(\"SDL\", \"Finished main function\");\n\n        if (SDLActivity.mSingleton != null && !SDLActivity.mSingleton.isFinishing()) {\n            // Let's finish the Activity\n            SDLActivity.mSDLThread = null;\n            SDLActivity.mSingleton.finish();\n        }  // else: Activity is already being destroyed\n\n    }\n}\n\n/* This is a fake invisible editor view that receives the input and defines the\n * pan&scan region\n */\nclass DummyEdit extends View implements View.OnKeyListener {\n    InputConnection ic;\n\n    public DummyEdit(Context context) {\n        super(context);\n        setFocusableInTouchMode(true);\n        setFocusable(true);\n        setOnKeyListener(this);\n    }\n\n    @Override\n    public boolean onCheckIsTextEditor() {\n        return true;\n    }\n\n    @Override\n    public boolean onKey(View v, int keyCode, KeyEvent event) {\n        return SDLActivity.handleKeyEvent(v, keyCode, event, ic);\n    }\n\n    //\n    @Override\n    public boolean onKeyPreIme (int keyCode, KeyEvent event) {\n        // As seen on StackOverflow: http://stackoverflow.com/questions/7634346/keyboard-hide-event\n        // FIXME: Discussion at http://bugzilla.libsdl.org/show_bug.cgi?id=1639\n        // FIXME: This is not a 100% effective solution to the problem of detecting if the keyboard is showing or not\n        // FIXME: A more effective solution would be to assume our Layout to be RelativeLayout or LinearLayout\n        // FIXME: And determine the keyboard presence doing this: http://stackoverflow.com/questions/2150078/how-to-check-visibility-of-software-keyboard-in-android\n        // FIXME: An even more effective way would be if Android provided this out of the box, but where would the fun be in that :)\n        if (event.getAction()==KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) {\n            if (SDLActivity.mTextEdit != null && SDLActivity.mTextEdit.getVisibility() == View.VISIBLE) {\n                SDLActivity.onNativeKeyboardFocusLost();\n            }\n        }\n        return super.onKeyPreIme(keyCode, event);\n    }\n\n    @Override\n    public InputConnection onCreateInputConnection(EditorInfo outAttrs) {\n        ic = new SDLInputConnection(this, true);\n\n        outAttrs.inputType = InputType.TYPE_CLASS_TEXT |\n                             InputType.TYPE_TEXT_FLAG_MULTI_LINE;\n        outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI |\n                              EditorInfo.IME_FLAG_NO_FULLSCREEN /* API 11 */;\n\n        return ic;\n    }\n}\n\nclass SDLInputConnection extends BaseInputConnection {\n\n    protected EditText mEditText;\n    protected String mCommittedText = \"\";\n\n    public SDLInputConnection(View targetView, boolean fullEditor) {\n        super(targetView, fullEditor);\n        mEditText = new EditText(SDL.getContext());\n    }\n\n    @Override\n    public Editable getEditable() {\n        return mEditText.getEditableText();\n    }\n\n    @Override\n    public boolean sendKeyEvent(KeyEvent event) {\n        /*\n         * This used to handle the keycodes from soft keyboard (and IME-translated input from hardkeyboard)\n         * However, as of Ice Cream Sandwich and later, almost all soft keyboard doesn't generate key presses\n         * and so we need to generate them ourselves in commitText.  To avoid duplicates on the handful of keys\n         * that still do, we empty this out.\n         */\n\n        /*\n         * Return DOES still generate a key event, however.  So rather than using it as the 'click a button' key\n         * as we do with physical keyboards, let's just use it to hide the keyboard.\n         */\n\n        if (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) {\n            if (SDLActivity.onNativeSoftReturnKey()) {\n                return true;\n            }\n        }\n\n        return super.sendKeyEvent(event);\n    }\n\n    @Override\n    public boolean commitText(CharSequence text, int newCursorPosition) {\n        if (!super.commitText(text, newCursorPosition)) {\n            return false;\n        }\n        updateText();\n        return true;\n    }\n\n    @Override\n    public boolean setComposingText(CharSequence text, int newCursorPosition) {\n        if (!super.setComposingText(text, newCursorPosition)) {\n            return false;\n        }\n        updateText();\n        return true;\n    }\n\n    @Override\n    public boolean deleteSurroundingText(int beforeLength, int afterLength) {\n        if (Build.VERSION.SDK_INT <= 29 /* Android 10.0 (Q) */) {\n            // Workaround to capture backspace key. Ref: http://stackoverflow.com/questions>/14560344/android-backspace-in-webview-baseinputconnection\n            // and https://bugzilla.libsdl.org/show_bug.cgi?id=2265\n            if (beforeLength > 0 && afterLength == 0) {\n                // backspace(s)\n                while (beforeLength-- > 0) {\n                    nativeGenerateScancodeForUnichar('\\b');\n                }\n                return true;\n           }\n        }\n\n        if (!super.deleteSurroundingText(beforeLength, afterLength)) {\n            return false;\n        }\n        updateText();\n        return true;\n    }\n\n    protected void updateText() {\n        final Editable content = getEditable();\n        if (content == null) {\n            return;\n        }\n\n        String text = content.toString();\n        int compareLength = Math.min(text.length(), mCommittedText.length());\n        int matchLength, offset;\n\n        /* Backspace over characters that are no longer in the string */\n        for (matchLength = 0; matchLength < compareLength; ) {\n            int codePoint = mCommittedText.codePointAt(matchLength);\n            if (codePoint != text.codePointAt(matchLength)) {\n                break;\n            }\n            matchLength += Character.charCount(codePoint);\n        }\n        /* FIXME: This doesn't handle graphemes, like '🌬️' */\n        for (offset = matchLength; offset < mCommittedText.length(); ) {\n            int codePoint = mCommittedText.codePointAt(offset);\n            nativeGenerateScancodeForUnichar('\\b');\n            offset += Character.charCount(codePoint);\n        }\n\n        if (matchLength < text.length()) {\n            String pendingText = text.subSequence(matchLength, text.length()).toString();\n            for (offset = 0; offset < pendingText.length(); ) {\n                int codePoint = pendingText.codePointAt(offset);\n                if (codePoint == '\\n') {\n                    if (SDLActivity.onNativeSoftReturnKey()) {\n                        return;\n                    }\n                }\n                /* Higher code points don't generate simulated scancodes */\n                if (codePoint < 128) {\n                    nativeGenerateScancodeForUnichar((char)codePoint);\n                }\n                offset += Character.charCount(codePoint);\n            }\n            SDLInputConnection.nativeCommitText(pendingText, 0);\n        }\n        mCommittedText = text;\n    }\n\n    public static native void nativeCommitText(String text, int newCursorPosition);\n\n    public static native void nativeGenerateScancodeForUnichar(char c);\n}\n\nclass SDLClipboardHandler implements\n    ClipboardManager.OnPrimaryClipChangedListener {\n\n    protected ClipboardManager mClipMgr;\n\n    SDLClipboardHandler() {\n       mClipMgr = (ClipboardManager) SDL.getContext().getSystemService(Context.CLIPBOARD_SERVICE);\n       mClipMgr.addPrimaryClipChangedListener(this);\n    }\n\n    public boolean clipboardHasText() {\n       return mClipMgr.hasPrimaryClip();\n    }\n\n    public String clipboardGetText() {\n        ClipData clip = mClipMgr.getPrimaryClip();\n        if (clip != null) {\n            ClipData.Item item = clip.getItemAt(0);\n            if (item != null) {\n                CharSequence text = item.getText();\n                if (text != null) {\n                    return text.toString();\n                }\n            }\n        }\n        return null;\n    }\n\n    public void clipboardSetText(String string) {\n       mClipMgr.removePrimaryClipChangedListener(this);\n       ClipData clip = ClipData.newPlainText(null, string);\n       mClipMgr.setPrimaryClip(clip);\n       mClipMgr.addPrimaryClipChangedListener(this);\n    }\n\n    @Override\n    public void onPrimaryClipChanged() {\n        SDLActivity.onNativeClipboardChanged();\n    }\n}"
  },
  {
    "path": "android/app/src/main/java/org/libsdl/app/SDLAudioManager.java",
    "content": "package org.libsdl.app;\n\nimport android.content.Context;\nimport android.media.AudioDeviceCallback;\nimport android.media.AudioDeviceInfo;\nimport android.media.AudioFormat;\nimport android.media.AudioManager;\nimport android.media.AudioRecord;\nimport android.media.AudioTrack;\nimport android.media.MediaRecorder;\nimport android.os.Build;\nimport android.util.Log;\n\nimport java.util.Arrays;\n\npublic class SDLAudioManager {\n    protected static final String TAG = \"SDLAudio\";\n\n    protected static AudioTrack mAudioTrack;\n    protected static AudioRecord mAudioRecord;\n    protected static Context mContext;\n\n    private static final int[] NO_DEVICES = {};\n\n    private static AudioDeviceCallback mAudioDeviceCallback;\n\n    public static void initialize() {\n        mAudioTrack = null;\n        mAudioRecord = null;\n        mAudioDeviceCallback = null;\n\n        if(Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */)\n        {\n            mAudioDeviceCallback = new AudioDeviceCallback() {\n                @Override\n                public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) {\n                    Arrays.stream(addedDevices).forEach(deviceInfo -> addAudioDevice(deviceInfo.isSink(), deviceInfo.getId()));\n                }\n\n                @Override\n                public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) {\n                    Arrays.stream(removedDevices).forEach(deviceInfo -> removeAudioDevice(deviceInfo.isSink(), deviceInfo.getId()));\n                }\n            };\n        }\n    }\n\n    public static void setContext(Context context) {\n        mContext = context;\n        if (context != null) {\n            registerAudioDeviceCallback();\n        }\n    }\n\n    public static void release(Context context) {\n        unregisterAudioDeviceCallback(context);\n    }\n\n    // Audio\n\n    protected static String getAudioFormatString(int audioFormat) {\n        switch (audioFormat) {\n            case AudioFormat.ENCODING_PCM_8BIT:\n                return \"8-bit\";\n            case AudioFormat.ENCODING_PCM_16BIT:\n                return \"16-bit\";\n            case AudioFormat.ENCODING_PCM_FLOAT:\n                return \"float\";\n            default:\n                return Integer.toString(audioFormat);\n        }\n    }\n\n    protected static int[] open(boolean isCapture, int sampleRate, int audioFormat, int desiredChannels, int desiredFrames, int deviceId) {\n        int channelConfig;\n        int sampleSize;\n        int frameSize;\n\n        Log.v(TAG, \"Opening \" + (isCapture ? \"capture\" : \"playback\") + \", requested \" + desiredFrames + \" frames of \" + desiredChannels + \" channel \" + getAudioFormatString(audioFormat) + \" audio at \" + sampleRate + \" Hz\");\n\n        /* On older devices let's use known good settings */\n        if (Build.VERSION.SDK_INT < 21 /* Android 5.0 (LOLLIPOP) */) {\n            if (desiredChannels > 2) {\n                desiredChannels = 2;\n            }\n        }\n\n        /* AudioTrack has sample rate limitation of 48000 (fixed in 5.0.2) */\n        if (Build.VERSION.SDK_INT < 22 /* Android 5.1 (LOLLIPOP_MR1) */) {\n            if (sampleRate < 8000) {\n                sampleRate = 8000;\n            } else if (sampleRate > 48000) {\n                sampleRate = 48000;\n            }\n        }\n\n        if (audioFormat == AudioFormat.ENCODING_PCM_FLOAT) {\n            int minSDKVersion = (isCapture ? 23 /* Android 6.0 (M) */ : 21 /* Android 5.0 (LOLLIPOP) */);\n            if (Build.VERSION.SDK_INT < minSDKVersion) {\n                audioFormat = AudioFormat.ENCODING_PCM_16BIT;\n            }\n        }\n        switch (audioFormat)\n        {\n        case AudioFormat.ENCODING_PCM_8BIT:\n            sampleSize = 1;\n            break;\n        case AudioFormat.ENCODING_PCM_16BIT:\n            sampleSize = 2;\n            break;\n        case AudioFormat.ENCODING_PCM_FLOAT:\n            sampleSize = 4;\n            break;\n        default:\n            Log.v(TAG, \"Requested format \" + audioFormat + \", getting ENCODING_PCM_16BIT\");\n            audioFormat = AudioFormat.ENCODING_PCM_16BIT;\n            sampleSize = 2;\n            break;\n        }\n\n        if (isCapture) {\n            switch (desiredChannels) {\n            case 1:\n                channelConfig = AudioFormat.CHANNEL_IN_MONO;\n                break;\n            case 2:\n                channelConfig = AudioFormat.CHANNEL_IN_STEREO;\n                break;\n            default:\n                Log.v(TAG, \"Requested \" + desiredChannels + \" channels, getting stereo\");\n                desiredChannels = 2;\n                channelConfig = AudioFormat.CHANNEL_IN_STEREO;\n                break;\n            }\n        } else {\n            switch (desiredChannels) {\n            case 1:\n                channelConfig = AudioFormat.CHANNEL_OUT_MONO;\n                break;\n            case 2:\n                channelConfig = AudioFormat.CHANNEL_OUT_STEREO;\n                break;\n            case 3:\n                channelConfig = AudioFormat.CHANNEL_OUT_STEREO | AudioFormat.CHANNEL_OUT_FRONT_CENTER;\n                break;\n            case 4:\n                channelConfig = AudioFormat.CHANNEL_OUT_QUAD;\n                break;\n            case 5:\n                channelConfig = AudioFormat.CHANNEL_OUT_QUAD | AudioFormat.CHANNEL_OUT_FRONT_CENTER;\n                break;\n            case 6:\n                channelConfig = AudioFormat.CHANNEL_OUT_5POINT1;\n                break;\n            case 7:\n                channelConfig = AudioFormat.CHANNEL_OUT_5POINT1 | AudioFormat.CHANNEL_OUT_BACK_CENTER;\n                break;\n            case 8:\n                if (Build.VERSION.SDK_INT >= 23 /* Android 6.0 (M) */) {\n                    channelConfig = AudioFormat.CHANNEL_OUT_7POINT1_SURROUND;\n                } else {\n                    Log.v(TAG, \"Requested \" + desiredChannels + \" channels, getting 5.1 surround\");\n                    desiredChannels = 6;\n                    channelConfig = AudioFormat.CHANNEL_OUT_5POINT1;\n                }\n                break;\n            default:\n                Log.v(TAG, \"Requested \" + desiredChannels + \" channels, getting stereo\");\n                desiredChannels = 2;\n                channelConfig = AudioFormat.CHANNEL_OUT_STEREO;\n                break;\n            }\n\n/*\n            Log.v(TAG, \"Speaker configuration (and order of channels):\");\n\n            if ((channelConfig & 0x00000004) != 0) {\n                Log.v(TAG, \"   CHANNEL_OUT_FRONT_LEFT\");\n            }\n            if ((channelConfig & 0x00000008) != 0) {\n                Log.v(TAG, \"   CHANNEL_OUT_FRONT_RIGHT\");\n            }\n            if ((channelConfig & 0x00000010) != 0) {\n                Log.v(TAG, \"   CHANNEL_OUT_FRONT_CENTER\");\n            }\n            if ((channelConfig & 0x00000020) != 0) {\n                Log.v(TAG, \"   CHANNEL_OUT_LOW_FREQUENCY\");\n            }\n            if ((channelConfig & 0x00000040) != 0) {\n                Log.v(TAG, \"   CHANNEL_OUT_BACK_LEFT\");\n            }\n            if ((channelConfig & 0x00000080) != 0) {\n                Log.v(TAG, \"   CHANNEL_OUT_BACK_RIGHT\");\n            }\n            if ((channelConfig & 0x00000100) != 0) {\n                Log.v(TAG, \"   CHANNEL_OUT_FRONT_LEFT_OF_CENTER\");\n            }\n            if ((channelConfig & 0x00000200) != 0) {\n                Log.v(TAG, \"   CHANNEL_OUT_FRONT_RIGHT_OF_CENTER\");\n            }\n            if ((channelConfig & 0x00000400) != 0) {\n                Log.v(TAG, \"   CHANNEL_OUT_BACK_CENTER\");\n            }\n            if ((channelConfig & 0x00000800) != 0) {\n                Log.v(TAG, \"   CHANNEL_OUT_SIDE_LEFT\");\n            }\n            if ((channelConfig & 0x00001000) != 0) {\n                Log.v(TAG, \"   CHANNEL_OUT_SIDE_RIGHT\");\n            }\n*/\n        }\n        frameSize = (sampleSize * desiredChannels);\n\n        // Let the user pick a larger buffer if they really want -- but ye\n        // gods they probably shouldn't, the minimums are horrifyingly high\n        // latency already\n        int minBufferSize;\n        if (isCapture) {\n            minBufferSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat);\n        } else {\n            minBufferSize = AudioTrack.getMinBufferSize(sampleRate, channelConfig, audioFormat);\n        }\n        desiredFrames = Math.max(desiredFrames, (minBufferSize + frameSize - 1) / frameSize);\n\n        int[] results = new int[4];\n\n        if (isCapture) {\n            if (mAudioRecord == null) {\n                mAudioRecord = new AudioRecord(MediaRecorder.AudioSource.DEFAULT, sampleRate,\n                        channelConfig, audioFormat, desiredFrames * frameSize);\n\n                // see notes about AudioTrack state in audioOpen(), above. Probably also applies here.\n                if (mAudioRecord.getState() != AudioRecord.STATE_INITIALIZED) {\n                    Log.e(TAG, \"Failed during initialization of AudioRecord\");\n                    mAudioRecord.release();\n                    mAudioRecord = null;\n                    return null;\n                }\n\n                if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */ && deviceId != 0) {\n                    mAudioRecord.setPreferredDevice(getOutputAudioDeviceInfo(deviceId));\n                }\n\n                mAudioRecord.startRecording();\n            }\n\n            results[0] = mAudioRecord.getSampleRate();\n            results[1] = mAudioRecord.getAudioFormat();\n            results[2] = mAudioRecord.getChannelCount();\n\n        } else {\n            if (mAudioTrack == null) {\n                mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate, channelConfig, audioFormat, desiredFrames * frameSize, AudioTrack.MODE_STREAM);\n\n                // Instantiating AudioTrack can \"succeed\" without an exception and the track may still be invalid\n                // Ref: https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/media/java/android/media/AudioTrack.java\n                // Ref: http://developer.android.com/reference/android/media/AudioTrack.html#getState()\n                if (mAudioTrack.getState() != AudioTrack.STATE_INITIALIZED) {\n                    /* Try again, with safer values */\n\n                    Log.e(TAG, \"Failed during initialization of Audio Track\");\n                    mAudioTrack.release();\n                    mAudioTrack = null;\n                    return null;\n                }\n\n                if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */ && deviceId != 0) {\n                    mAudioTrack.setPreferredDevice(getInputAudioDeviceInfo(deviceId));\n                }\n\n                mAudioTrack.play();\n            }\n\n            results[0] = mAudioTrack.getSampleRate();\n            results[1] = mAudioTrack.getAudioFormat();\n            results[2] = mAudioTrack.getChannelCount();\n        }\n        results[3] = desiredFrames;\n\n        Log.v(TAG, \"Opening \" + (isCapture ? \"capture\" : \"playback\") + \", got \" + results[3] + \" frames of \" + results[2] + \" channel \" + getAudioFormatString(results[1]) + \" audio at \" + results[0] + \" Hz\");\n\n        return results;\n    }\n\n    private static AudioDeviceInfo getInputAudioDeviceInfo(int deviceId) {\n        if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {\n            AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);\n            return Arrays.stream(audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS))\n                    .filter(deviceInfo -> deviceInfo.getId() == deviceId)\n                    .findFirst()\n                    .orElse(null);\n        } else {\n            return null;\n        }\n    }\n\n    private static AudioDeviceInfo getOutputAudioDeviceInfo(int deviceId) {\n        if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {\n            AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);\n            return Arrays.stream(audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS))\n                    .filter(deviceInfo -> deviceInfo.getId() == deviceId)\n                    .findFirst()\n                    .orElse(null);\n        } else {\n            return null;\n        }\n    }\n\n    private static void registerAudioDeviceCallback() {\n        if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {\n            AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);\n            audioManager.registerAudioDeviceCallback(mAudioDeviceCallback, null);\n        }\n    }\n\n    private static void unregisterAudioDeviceCallback(Context context) {\n        if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {\n            AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);\n            audioManager.unregisterAudioDeviceCallback(mAudioDeviceCallback);\n        }\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static int[] getAudioOutputDevices() {\n        if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {\n            AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);\n            return Arrays.stream(audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS)).mapToInt(AudioDeviceInfo::getId).toArray();\n        } else {\n            return NO_DEVICES;\n        }\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static int[] getAudioInputDevices() {\n        if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {\n            AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);\n            return Arrays.stream(audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)).mapToInt(AudioDeviceInfo::getId).toArray();\n        } else {\n            return NO_DEVICES;\n        }\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static int[] audioOpen(int sampleRate, int audioFormat, int desiredChannels, int desiredFrames, int deviceId) {\n        return open(false, sampleRate, audioFormat, desiredChannels, desiredFrames, deviceId);\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static void audioWriteFloatBuffer(float[] buffer) {\n        if (mAudioTrack == null) {\n            Log.e(TAG, \"Attempted to make audio call with uninitialized audio!\");\n            return;\n        }\n\n        if (android.os.Build.VERSION.SDK_INT < 21 /* Android 5.0 (LOLLIPOP) */) {\n            Log.e(TAG, \"Attempted to make an incompatible audio call with uninitialized audio! (floating-point output is supported since Android 5.0 Lollipop)\");\n            return;\n        }\n\n        for (int i = 0; i < buffer.length;) {\n            int result = mAudioTrack.write(buffer, i, buffer.length - i, AudioTrack.WRITE_BLOCKING);\n            if (result > 0) {\n                i += result;\n            } else if (result == 0) {\n                try {\n                    Thread.sleep(1);\n                } catch(InterruptedException e) {\n                    // Nom nom\n                }\n            } else {\n                Log.w(TAG, \"SDL audio: error return from write(float)\");\n                return;\n            }\n        }\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static void audioWriteShortBuffer(short[] buffer) {\n        if (mAudioTrack == null) {\n            Log.e(TAG, \"Attempted to make audio call with uninitialized audio!\");\n            return;\n        }\n\n        for (int i = 0; i < buffer.length;) {\n            int result = mAudioTrack.write(buffer, i, buffer.length - i);\n            if (result > 0) {\n                i += result;\n            } else if (result == 0) {\n                try {\n                    Thread.sleep(1);\n                } catch(InterruptedException e) {\n                    // Nom nom\n                }\n            } else {\n                Log.w(TAG, \"SDL audio: error return from write(short)\");\n                return;\n            }\n        }\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static void audioWriteByteBuffer(byte[] buffer) {\n        if (mAudioTrack == null) {\n            Log.e(TAG, \"Attempted to make audio call with uninitialized audio!\");\n            return;\n        }\n\n        for (int i = 0; i < buffer.length; ) {\n            int result = mAudioTrack.write(buffer, i, buffer.length - i);\n            if (result > 0) {\n                i += result;\n            } else if (result == 0) {\n                try {\n                    Thread.sleep(1);\n                } catch(InterruptedException e) {\n                    // Nom nom\n                }\n            } else {\n                Log.w(TAG, \"SDL audio: error return from write(byte)\");\n                return;\n            }\n        }\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static int[] captureOpen(int sampleRate, int audioFormat, int desiredChannels, int desiredFrames, int deviceId) {\n        return open(true, sampleRate, audioFormat, desiredChannels, desiredFrames, deviceId);\n    }\n\n    /** This method is called by SDL using JNI. */\n    public static int captureReadFloatBuffer(float[] buffer, boolean blocking) {\n        if (Build.VERSION.SDK_INT < 23 /* Android 6.0 (M) */) {\n            return 0;\n        } else {\n            return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);\n        }\n    }\n\n    /** This method is called by SDL using JNI. */\n    public static int captureReadShortBuffer(short[] buffer, boolean blocking) {\n        if (Build.VERSION.SDK_INT < 23 /* Android 6.0 (M) */) {\n            return mAudioRecord.read(buffer, 0, buffer.length);\n        } else {\n            return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);\n        }\n    }\n\n    /** This method is called by SDL using JNI. */\n    public static int captureReadByteBuffer(byte[] buffer, boolean blocking) {\n        if (Build.VERSION.SDK_INT < 23 /* Android 6.0 (M) */) {\n            return mAudioRecord.read(buffer, 0, buffer.length);\n        } else {\n            return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);\n        }\n    }\n\n    /** This method is called by SDL using JNI. */\n    public static void audioClose() {\n        if (mAudioTrack != null) {\n            mAudioTrack.stop();\n            mAudioTrack.release();\n            mAudioTrack = null;\n        }\n    }\n\n    /** This method is called by SDL using JNI. */\n    public static void captureClose() {\n        if (mAudioRecord != null) {\n            mAudioRecord.stop();\n            mAudioRecord.release();\n            mAudioRecord = null;\n        }\n    }\n\n    /** This method is called by SDL using JNI. */\n    public static void audioSetThreadPriority(boolean iscapture, int device_id) {\n        try {\n\n            /* Set thread name */\n            if (iscapture) {\n                Thread.currentThread().setName(\"SDLAudioC\" + device_id);\n            } else {\n                Thread.currentThread().setName(\"SDLAudioP\" + device_id);\n            }\n\n            /* Set thread priority */\n            android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_AUDIO);\n\n        } catch (Exception e) {\n            Log.v(TAG, \"modify thread properties failed \" + e.toString());\n        }\n    }\n\n    public static native int nativeSetupJNI();\n\n    public static native void removeAudioDevice(boolean isCapture, int deviceId);\n\n    public static native void addAudioDevice(boolean isCapture, int deviceId);\n\n}\n"
  },
  {
    "path": "android/app/src/main/java/org/libsdl/app/SDLControllerManager.java",
    "content": "package org.libsdl.app;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.Comparator;\nimport java.util.List;\n\nimport android.content.Context;\nimport android.os.Build;\nimport android.os.VibrationEffect;\nimport android.os.Vibrator;\nimport android.util.Log;\nimport android.view.InputDevice;\nimport android.view.KeyEvent;\nimport android.view.MotionEvent;\nimport android.view.View;\n\n\npublic class SDLControllerManager\n{\n\n    public static native int nativeSetupJNI();\n\n    public static native int nativeAddJoystick(int device_id, String name, String desc,\n                                               int vendor_id, int product_id,\n                                               boolean is_accelerometer, int button_mask,\n                                               int naxes, int axis_mask, int nhats, int nballs);\n    public static native int nativeRemoveJoystick(int device_id);\n    public static native int nativeAddHaptic(int device_id, String name);\n    public static native int nativeRemoveHaptic(int device_id);\n    public static native int onNativePadDown(int device_id, int keycode);\n    public static native int onNativePadUp(int device_id, int keycode);\n    public static native void onNativeJoy(int device_id, int axis,\n                                          float value);\n    public static native void onNativeHat(int device_id, int hat_id,\n                                          int x, int y);\n\n    protected static SDLJoystickHandler mJoystickHandler;\n    protected static SDLHapticHandler mHapticHandler;\n\n    private static final String TAG = \"SDLControllerManager\";\n\n    public static void initialize() {\n        if (mJoystickHandler == null) {\n            if (Build.VERSION.SDK_INT >= 19 /* Android 4.4 (KITKAT) */) {\n                mJoystickHandler = new SDLJoystickHandler_API19();\n            } else {\n                mJoystickHandler = new SDLJoystickHandler_API16();\n            }\n        }\n\n        if (mHapticHandler == null) {\n            if (Build.VERSION.SDK_INT >= 26 /* Android 8.0 (O) */) {\n                mHapticHandler = new SDLHapticHandler_API26();\n            } else {\n                mHapticHandler = new SDLHapticHandler();\n            }\n        }\n    }\n\n    // Joystick glue code, just a series of stubs that redirect to the SDLJoystickHandler instance\n    public static boolean handleJoystickMotionEvent(MotionEvent event) {\n        return mJoystickHandler.handleMotionEvent(event);\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static void pollInputDevices() {\n        mJoystickHandler.pollInputDevices();\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static void pollHapticDevices() {\n        mHapticHandler.pollHapticDevices();\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static void hapticRun(int device_id, float intensity, int length) {\n        mHapticHandler.run(device_id, intensity, length);\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static void hapticStop(int device_id)\n    {\n        mHapticHandler.stop(device_id);\n    }\n\n    // Check if a given device is considered a possible SDL joystick\n    public static boolean isDeviceSDLJoystick(int deviceId) {\n        InputDevice device = InputDevice.getDevice(deviceId);\n        // We cannot use InputDevice.isVirtual before API 16, so let's accept\n        // only nonnegative device ids (VIRTUAL_KEYBOARD equals -1)\n        if ((device == null) || (deviceId < 0)) {\n            return false;\n        }\n        int sources = device.getSources();\n\n        /* This is called for every button press, so let's not spam the logs */\n        /*\n        if ((sources & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {\n            Log.v(TAG, \"Input device \" + device.getName() + \" has class joystick.\");\n        }\n        if ((sources & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD) {\n            Log.v(TAG, \"Input device \" + device.getName() + \" is a dpad.\");\n        }\n        if ((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) {\n            Log.v(TAG, \"Input device \" + device.getName() + \" is a gamepad.\");\n        }\n        */\n\n        return ((sources & InputDevice.SOURCE_CLASS_JOYSTICK) != 0 ||\n                ((sources & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD) ||\n                ((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD)\n        );\n    }\n\n}\n\nclass SDLJoystickHandler {\n\n    /**\n     * Handles given MotionEvent.\n     * @param event the event to be handled.\n     * @return if given event was processed.\n     */\n    public boolean handleMotionEvent(MotionEvent event) {\n        return false;\n    }\n\n    /**\n     * Handles adding and removing of input devices.\n     */\n    public void pollInputDevices() {\n    }\n}\n\n/* Actual joystick functionality available for API >= 12 devices */\nclass SDLJoystickHandler_API16 extends SDLJoystickHandler {\n\n    static class SDLJoystick {\n        public int device_id;\n        public String name;\n        public String desc;\n        public ArrayList<InputDevice.MotionRange> axes;\n        public ArrayList<InputDevice.MotionRange> hats;\n    }\n    static class RangeComparator implements Comparator<InputDevice.MotionRange> {\n        @Override\n        public int compare(InputDevice.MotionRange arg0, InputDevice.MotionRange arg1) {\n            // Some controllers, like the Moga Pro 2, return AXIS_GAS (22) for right trigger and AXIS_BRAKE (23) for left trigger - swap them so they're sorted in the right order for SDL\n            int arg0Axis = arg0.getAxis();\n            int arg1Axis = arg1.getAxis();\n            if (arg0Axis == MotionEvent.AXIS_GAS) {\n                arg0Axis = MotionEvent.AXIS_BRAKE;\n            } else if (arg0Axis == MotionEvent.AXIS_BRAKE) {\n                arg0Axis = MotionEvent.AXIS_GAS;\n            }\n            if (arg1Axis == MotionEvent.AXIS_GAS) {\n                arg1Axis = MotionEvent.AXIS_BRAKE;\n            } else if (arg1Axis == MotionEvent.AXIS_BRAKE) {\n                arg1Axis = MotionEvent.AXIS_GAS;\n            }\n\n            // Make sure the AXIS_Z is sorted between AXIS_RY and AXIS_RZ.\n            // This is because the usual pairing are:\n            // - AXIS_X + AXIS_Y (left stick).\n            // - AXIS_RX, AXIS_RY (sometimes the right stick, sometimes triggers).\n            // - AXIS_Z, AXIS_RZ (sometimes the right stick, sometimes triggers).\n            // This sorts the axes in the above order, which tends to be correct\n            // for Xbox-ish game pads that have the right stick on RX/RY and the\n            // triggers on Z/RZ.\n            //\n            // Gamepads that don't have AXIS_Z/AXIS_RZ but use\n            // AXIS_LTRIGGER/AXIS_RTRIGGER are unaffected by this.\n            //\n            // References:\n            // - https://developer.android.com/develop/ui/views/touch-and-input/game-controllers/controller-input\n            // - https://www.kernel.org/doc/html/latest/input/gamepad.html\n            if (arg0Axis == MotionEvent.AXIS_Z) {\n                arg0Axis = MotionEvent.AXIS_RZ - 1;\n            } else if (arg0Axis > MotionEvent.AXIS_Z && arg0Axis < MotionEvent.AXIS_RZ) {\n                --arg0Axis;\n            }\n            if (arg1Axis == MotionEvent.AXIS_Z) {\n                arg1Axis = MotionEvent.AXIS_RZ - 1;\n            } else if (arg1Axis > MotionEvent.AXIS_Z && arg1Axis < MotionEvent.AXIS_RZ) {\n                --arg1Axis;\n            }\n\n            return arg0Axis - arg1Axis;\n        }\n    }\n\n    private final ArrayList<SDLJoystick> mJoysticks;\n\n    public SDLJoystickHandler_API16() {\n\n        mJoysticks = new ArrayList<SDLJoystick>();\n    }\n\n    @Override\n    public void pollInputDevices() {\n        int[] deviceIds = InputDevice.getDeviceIds();\n\n        for (int device_id : deviceIds) {\n            if (SDLControllerManager.isDeviceSDLJoystick(device_id)) {\n                SDLJoystick joystick = getJoystick(device_id);\n                if (joystick == null) {\n                    InputDevice joystickDevice = InputDevice.getDevice(device_id);\n                    joystick = new SDLJoystick();\n                    joystick.device_id = device_id;\n                    joystick.name = joystickDevice.getName();\n                    joystick.desc = getJoystickDescriptor(joystickDevice);\n                    joystick.axes = new ArrayList<InputDevice.MotionRange>();\n                    joystick.hats = new ArrayList<InputDevice.MotionRange>();\n\n                    List<InputDevice.MotionRange> ranges = joystickDevice.getMotionRanges();\n                    Collections.sort(ranges, new RangeComparator());\n                    for (InputDevice.MotionRange range : ranges) {\n                        if ((range.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {\n                            if (range.getAxis() == MotionEvent.AXIS_HAT_X || range.getAxis() == MotionEvent.AXIS_HAT_Y) {\n                                joystick.hats.add(range);\n                            } else {\n                                joystick.axes.add(range);\n                            }\n                        }\n                    }\n\n                    mJoysticks.add(joystick);\n                    SDLControllerManager.nativeAddJoystick(joystick.device_id, joystick.name, joystick.desc,\n                            getVendorId(joystickDevice), getProductId(joystickDevice), false,\n                            getButtonMask(joystickDevice), joystick.axes.size(), getAxisMask(joystick.axes), joystick.hats.size()/2, 0);\n                }\n            }\n        }\n\n        /* Check removed devices */\n        ArrayList<Integer> removedDevices = null;\n        for (SDLJoystick joystick : mJoysticks) {\n            int device_id = joystick.device_id;\n            int i;\n            for (i = 0; i < deviceIds.length; i++) {\n                if (device_id == deviceIds[i]) break;\n            }\n            if (i == deviceIds.length) {\n                if (removedDevices == null) {\n                    removedDevices = new ArrayList<Integer>();\n                }\n                removedDevices.add(device_id);\n            }\n        }\n\n        if (removedDevices != null) {\n            for (int device_id : removedDevices) {\n                SDLControllerManager.nativeRemoveJoystick(device_id);\n                for (int i = 0; i < mJoysticks.size(); i++) {\n                    if (mJoysticks.get(i).device_id == device_id) {\n                        mJoysticks.remove(i);\n                        break;\n                    }\n                }\n            }\n        }\n    }\n\n    protected SDLJoystick getJoystick(int device_id) {\n        for (SDLJoystick joystick : mJoysticks) {\n            if (joystick.device_id == device_id) {\n                return joystick;\n            }\n        }\n        return null;\n    }\n\n    @Override\n    public boolean handleMotionEvent(MotionEvent event) {\n        int actionPointerIndex = event.getActionIndex();\n        int action = event.getActionMasked();\n        if (action == MotionEvent.ACTION_MOVE) {\n            SDLJoystick joystick = getJoystick(event.getDeviceId());\n            if (joystick != null) {\n                for (int i = 0; i < joystick.axes.size(); i++) {\n                    InputDevice.MotionRange range = joystick.axes.get(i);\n                    /* Normalize the value to -1...1 */\n                    float value = (event.getAxisValue(range.getAxis(), actionPointerIndex) - range.getMin()) / range.getRange() * 2.0f - 1.0f;\n                    SDLControllerManager.onNativeJoy(joystick.device_id, i, value);\n                }\n                for (int i = 0; i < joystick.hats.size() / 2; i++) {\n                    int hatX = Math.round(event.getAxisValue(joystick.hats.get(2 * i).getAxis(), actionPointerIndex));\n                    int hatY = Math.round(event.getAxisValue(joystick.hats.get(2 * i + 1).getAxis(), actionPointerIndex));\n                    SDLControllerManager.onNativeHat(joystick.device_id, i, hatX, hatY);\n                }\n            }\n        }\n        return true;\n    }\n\n    public String getJoystickDescriptor(InputDevice joystickDevice) {\n        String desc = joystickDevice.getDescriptor();\n\n        if (desc != null && !desc.isEmpty()) {\n            return desc;\n        }\n\n        return joystickDevice.getName();\n    }\n    public int getProductId(InputDevice joystickDevice) {\n        return 0;\n    }\n    public int getVendorId(InputDevice joystickDevice) {\n        return 0;\n    }\n    public int getAxisMask(List<InputDevice.MotionRange> ranges) {\n        return -1;\n    }\n    public int getButtonMask(InputDevice joystickDevice) {\n        return -1;\n    }\n}\n\nclass SDLJoystickHandler_API19 extends SDLJoystickHandler_API16 {\n\n    @Override\n    public int getProductId(InputDevice joystickDevice) {\n        return joystickDevice.getProductId();\n    }\n\n    @Override\n    public int getVendorId(InputDevice joystickDevice) {\n        return joystickDevice.getVendorId();\n    }\n\n    @Override\n    public int getAxisMask(List<InputDevice.MotionRange> ranges) {\n        // For compatibility, keep computing the axis mask like before,\n        // only really distinguishing 2, 4 and 6 axes.\n        int axis_mask = 0;\n        if (ranges.size() >= 2) {\n            // ((1 << SDL_GAMEPAD_AXIS_LEFTX) | (1 << SDL_GAMEPAD_AXIS_LEFTY))\n            axis_mask |= 0x0003;\n        }\n        if (ranges.size() >= 4) {\n            // ((1 << SDL_GAMEPAD_AXIS_RIGHTX) | (1 << SDL_GAMEPAD_AXIS_RIGHTY))\n            axis_mask |= 0x000c;\n        }\n        if (ranges.size() >= 6) {\n            // ((1 << SDL_GAMEPAD_AXIS_LEFT_TRIGGER) | (1 << SDL_GAMEPAD_AXIS_RIGHT_TRIGGER))\n            axis_mask |= 0x0030;\n        }\n        // Also add an indicator bit for whether the sorting order has changed.\n        // This serves to disable outdated gamecontrollerdb.txt mappings.\n        boolean have_z = false;\n        boolean have_past_z_before_rz = false;\n        for (InputDevice.MotionRange range : ranges) {\n            int axis = range.getAxis();\n            if (axis == MotionEvent.AXIS_Z) {\n                have_z = true;\n            } else if (axis > MotionEvent.AXIS_Z && axis < MotionEvent.AXIS_RZ) {\n                have_past_z_before_rz = true;\n            }\n        }\n        if (have_z && have_past_z_before_rz) {\n            // If both these exist, the compare() function changed sorting order.\n            // Set a bit to indicate this fact.\n            axis_mask |= 0x8000;\n        }\n        return axis_mask;\n    }\n\n    @Override\n    public int getButtonMask(InputDevice joystickDevice) {\n        int button_mask = 0;\n        int[] keys = new int[] {\n            KeyEvent.KEYCODE_BUTTON_A,\n            KeyEvent.KEYCODE_BUTTON_B,\n            KeyEvent.KEYCODE_BUTTON_X,\n            KeyEvent.KEYCODE_BUTTON_Y,\n            KeyEvent.KEYCODE_BACK,\n            KeyEvent.KEYCODE_MENU,\n            KeyEvent.KEYCODE_BUTTON_MODE,\n            KeyEvent.KEYCODE_BUTTON_START,\n            KeyEvent.KEYCODE_BUTTON_THUMBL,\n            KeyEvent.KEYCODE_BUTTON_THUMBR,\n            KeyEvent.KEYCODE_BUTTON_L1,\n            KeyEvent.KEYCODE_BUTTON_R1,\n            KeyEvent.KEYCODE_DPAD_UP,\n            KeyEvent.KEYCODE_DPAD_DOWN,\n            KeyEvent.KEYCODE_DPAD_LEFT,\n            KeyEvent.KEYCODE_DPAD_RIGHT,\n            KeyEvent.KEYCODE_BUTTON_SELECT,\n            KeyEvent.KEYCODE_DPAD_CENTER,\n\n            // These don't map into any SDL controller buttons directly\n            KeyEvent.KEYCODE_BUTTON_L2,\n            KeyEvent.KEYCODE_BUTTON_R2,\n            KeyEvent.KEYCODE_BUTTON_C,\n            KeyEvent.KEYCODE_BUTTON_Z,\n            KeyEvent.KEYCODE_BUTTON_1,\n            KeyEvent.KEYCODE_BUTTON_2,\n            KeyEvent.KEYCODE_BUTTON_3,\n            KeyEvent.KEYCODE_BUTTON_4,\n            KeyEvent.KEYCODE_BUTTON_5,\n            KeyEvent.KEYCODE_BUTTON_6,\n            KeyEvent.KEYCODE_BUTTON_7,\n            KeyEvent.KEYCODE_BUTTON_8,\n            KeyEvent.KEYCODE_BUTTON_9,\n            KeyEvent.KEYCODE_BUTTON_10,\n            KeyEvent.KEYCODE_BUTTON_11,\n            KeyEvent.KEYCODE_BUTTON_12,\n            KeyEvent.KEYCODE_BUTTON_13,\n            KeyEvent.KEYCODE_BUTTON_14,\n            KeyEvent.KEYCODE_BUTTON_15,\n            KeyEvent.KEYCODE_BUTTON_16,\n        };\n        int[] masks = new int[] {\n            (1 << 0),   // A -> A\n            (1 << 1),   // B -> B\n            (1 << 2),   // X -> X\n            (1 << 3),   // Y -> Y\n            (1 << 4),   // BACK -> BACK\n            (1 << 6),   // MENU -> START\n            (1 << 5),   // MODE -> GUIDE\n            (1 << 6),   // START -> START\n            (1 << 7),   // THUMBL -> LEFTSTICK\n            (1 << 8),   // THUMBR -> RIGHTSTICK\n            (1 << 9),   // L1 -> LEFTSHOULDER\n            (1 << 10),  // R1 -> RIGHTSHOULDER\n            (1 << 11),  // DPAD_UP -> DPAD_UP\n            (1 << 12),  // DPAD_DOWN -> DPAD_DOWN\n            (1 << 13),  // DPAD_LEFT -> DPAD_LEFT\n            (1 << 14),  // DPAD_RIGHT -> DPAD_RIGHT\n            (1 << 4),   // SELECT -> BACK\n            (1 << 0),   // DPAD_CENTER -> A\n            (1 << 15),  // L2 -> ??\n            (1 << 16),  // R2 -> ??\n            (1 << 17),  // C -> ??\n            (1 << 18),  // Z -> ??\n            (1 << 20),  // 1 -> ??\n            (1 << 21),  // 2 -> ??\n            (1 << 22),  // 3 -> ??\n            (1 << 23),  // 4 -> ??\n            (1 << 24),  // 5 -> ??\n            (1 << 25),  // 6 -> ??\n            (1 << 26),  // 7 -> ??\n            (1 << 27),  // 8 -> ??\n            (1 << 28),  // 9 -> ??\n            (1 << 29),  // 10 -> ??\n            (1 << 30),  // 11 -> ??\n            (1 << 31),  // 12 -> ??\n            // We're out of room...\n            0xFFFFFFFF,  // 13 -> ??\n            0xFFFFFFFF,  // 14 -> ??\n            0xFFFFFFFF,  // 15 -> ??\n            0xFFFFFFFF,  // 16 -> ??\n        };\n        boolean[] has_keys = joystickDevice.hasKeys(keys);\n        for (int i = 0; i < keys.length; ++i) {\n            if (has_keys[i]) {\n                button_mask |= masks[i];\n            }\n        }\n        return button_mask;\n    }\n}\n\nclass SDLHapticHandler_API26 extends SDLHapticHandler {\n    @Override\n    public void run(int device_id, float intensity, int length) {\n        SDLHaptic haptic = getHaptic(device_id);\n        if (haptic != null) {\n            Log.d(\"SDL\", \"Rtest: Vibe with intensity \" + intensity + \" for \" + length);\n            if (intensity == 0.0f) {\n                stop(device_id);\n                return;\n            }\n\n            int vibeValue = Math.round(intensity * 255);\n\n            if (vibeValue > 255) {\n                vibeValue = 255;\n            }\n            if (vibeValue < 1) {\n                stop(device_id);\n                return;\n            }\n            try {\n                haptic.vib.vibrate(VibrationEffect.createOneShot(length, vibeValue));\n            }\n            catch (Exception e) {\n                // Fall back to the generic method, which uses DEFAULT_AMPLITUDE, but works even if\n                // something went horribly wrong with the Android 8.0 APIs.\n                haptic.vib.vibrate(length);\n            }\n        }\n    }\n}\n\nclass SDLHapticHandler {\n\n    static class SDLHaptic {\n        public int device_id;\n        public String name;\n        public Vibrator vib;\n    }\n\n    private final ArrayList<SDLHaptic> mHaptics;\n\n    public SDLHapticHandler() {\n        mHaptics = new ArrayList<SDLHaptic>();\n    }\n\n    public void run(int device_id, float intensity, int length) {\n        SDLHaptic haptic = getHaptic(device_id);\n        if (haptic != null) {\n            haptic.vib.vibrate(length);\n        }\n    }\n\n    public void stop(int device_id) {\n        SDLHaptic haptic = getHaptic(device_id);\n        if (haptic != null) {\n            haptic.vib.cancel();\n        }\n    }\n\n    public void pollHapticDevices() {\n\n        final int deviceId_VIBRATOR_SERVICE = 999999;\n        boolean hasVibratorService = false;\n\n        int[] deviceIds = InputDevice.getDeviceIds();\n        // It helps processing the device ids in reverse order\n        // For example, in the case of the XBox 360 wireless dongle,\n        // so the first controller seen by SDL matches what the receiver\n        // considers to be the first controller\n\n        for (int i = deviceIds.length - 1; i > -1; i--) {\n            SDLHaptic haptic = getHaptic(deviceIds[i]);\n            if (haptic == null) {\n                InputDevice device = InputDevice.getDevice(deviceIds[i]);\n                Vibrator vib = device.getVibrator();\n                if (vib != null) {\n                    if (vib.hasVibrator()) {\n                        haptic = new SDLHaptic();\n                        haptic.device_id = deviceIds[i];\n                        haptic.name = device.getName();\n                        haptic.vib = vib;\n                        mHaptics.add(haptic);\n                        SDLControllerManager.nativeAddHaptic(haptic.device_id, haptic.name);\n                    }\n                }\n            }\n        }\n\n        /* Check VIBRATOR_SERVICE */\n        Vibrator vib = (Vibrator) SDL.getContext().getSystemService(Context.VIBRATOR_SERVICE);\n        if (vib != null) {\n            hasVibratorService = vib.hasVibrator();\n\n            if (hasVibratorService) {\n                SDLHaptic haptic = getHaptic(deviceId_VIBRATOR_SERVICE);\n                if (haptic == null) {\n                    haptic = new SDLHaptic();\n                    haptic.device_id = deviceId_VIBRATOR_SERVICE;\n                    haptic.name = \"VIBRATOR_SERVICE\";\n                    haptic.vib = vib;\n                    mHaptics.add(haptic);\n                    SDLControllerManager.nativeAddHaptic(haptic.device_id, haptic.name);\n                }\n            }\n        }\n\n        /* Check removed devices */\n        ArrayList<Integer> removedDevices = null;\n        for (SDLHaptic haptic : mHaptics) {\n            int device_id = haptic.device_id;\n            int i;\n            for (i = 0; i < deviceIds.length; i++) {\n                if (device_id == deviceIds[i]) break;\n            }\n\n            if (device_id != deviceId_VIBRATOR_SERVICE || !hasVibratorService) {\n                if (i == deviceIds.length) {\n                    if (removedDevices == null) {\n                        removedDevices = new ArrayList<Integer>();\n                    }\n                    removedDevices.add(device_id);\n                }\n            }  // else: don't remove the vibrator if it is still present\n        }\n\n        if (removedDevices != null) {\n            for (int device_id : removedDevices) {\n                SDLControllerManager.nativeRemoveHaptic(device_id);\n                for (int i = 0; i < mHaptics.size(); i++) {\n                    if (mHaptics.get(i).device_id == device_id) {\n                        mHaptics.remove(i);\n                        break;\n                    }\n                }\n            }\n        }\n    }\n\n    protected SDLHaptic getHaptic(int device_id) {\n        for (SDLHaptic haptic : mHaptics) {\n            if (haptic.device_id == device_id) {\n                return haptic;\n            }\n        }\n        return null;\n    }\n}\n\nclass SDLGenericMotionListener_API12 implements View.OnGenericMotionListener {\n    // Generic Motion (mouse hover, joystick...) events go here\n    @Override\n    public boolean onGenericMotion(View v, MotionEvent event) {\n        float x, y;\n        int action;\n\n        switch ( event.getSource() ) {\n            case InputDevice.SOURCE_JOYSTICK:\n                return SDLControllerManager.handleJoystickMotionEvent(event);\n\n            case InputDevice.SOURCE_MOUSE:\n                action = event.getActionMasked();\n                switch (action) {\n                    case MotionEvent.ACTION_SCROLL:\n                        x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);\n                        y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);\n                        SDLActivity.onNativeMouse(0, action, x, y, false);\n                        return true;\n\n                    case MotionEvent.ACTION_HOVER_MOVE:\n                        x = event.getX(0);\n                        y = event.getY(0);\n\n                        SDLActivity.onNativeMouse(0, action, x, y, false);\n                        return true;\n\n                    default:\n                        break;\n                }\n                break;\n\n            default:\n                break;\n        }\n\n        // Event was not managed\n        return false;\n    }\n\n    public boolean supportsRelativeMouse() {\n        return false;\n    }\n\n    public boolean inRelativeMode() {\n        return false;\n    }\n\n    public boolean setRelativeMouseEnabled(boolean enabled) {\n        return false;\n    }\n\n    public void reclaimRelativeMouseModeIfNeeded()\n    {\n\n    }\n\n    public float getEventX(MotionEvent event) {\n        return event.getX(0);\n    }\n\n    public float getEventY(MotionEvent event) {\n        return event.getY(0);\n    }\n\n}\n\nclass SDLGenericMotionListener_API24 extends SDLGenericMotionListener_API12 {\n    // Generic Motion (mouse hover, joystick...) events go here\n\n    private boolean mRelativeModeEnabled;\n\n    @Override\n    public boolean onGenericMotion(View v, MotionEvent event) {\n\n        // Handle relative mouse mode\n        if (mRelativeModeEnabled) {\n            if (event.getSource() == InputDevice.SOURCE_MOUSE) {\n                int action = event.getActionMasked();\n                if (action == MotionEvent.ACTION_HOVER_MOVE) {\n                    float x = event.getAxisValue(MotionEvent.AXIS_RELATIVE_X);\n                    float y = event.getAxisValue(MotionEvent.AXIS_RELATIVE_Y);\n                    SDLActivity.onNativeMouse(0, action, x, y, true);\n                    return true;\n                }\n            }\n        }\n\n        // Event was not managed, call SDLGenericMotionListener_API12 method\n        return super.onGenericMotion(v, event);\n    }\n\n    @Override\n    public boolean supportsRelativeMouse() {\n        return true;\n    }\n\n    @Override\n    public boolean inRelativeMode() {\n        return mRelativeModeEnabled;\n    }\n\n    @Override\n    public boolean setRelativeMouseEnabled(boolean enabled) {\n        mRelativeModeEnabled = enabled;\n        return true;\n    }\n\n    @Override\n    public float getEventX(MotionEvent event) {\n        if (mRelativeModeEnabled) {\n            return event.getAxisValue(MotionEvent.AXIS_RELATIVE_X);\n        } else {\n            return event.getX(0);\n        }\n    }\n\n    @Override\n    public float getEventY(MotionEvent event) {\n        if (mRelativeModeEnabled) {\n            return event.getAxisValue(MotionEvent.AXIS_RELATIVE_Y);\n        } else {\n            return event.getY(0);\n        }\n    }\n}\n\nclass SDLGenericMotionListener_API26 extends SDLGenericMotionListener_API24 {\n    // Generic Motion (mouse hover, joystick...) events go here\n    private boolean mRelativeModeEnabled;\n\n    @Override\n    public boolean onGenericMotion(View v, MotionEvent event) {\n        float x, y;\n        int action;\n\n        switch ( event.getSource() ) {\n            case InputDevice.SOURCE_JOYSTICK:\n                return SDLControllerManager.handleJoystickMotionEvent(event);\n\n            case InputDevice.SOURCE_MOUSE:\n            // DeX desktop mouse cursor is a separate non-standard input type.\n            case InputDevice.SOURCE_MOUSE | InputDevice.SOURCE_TOUCHSCREEN:\n                action = event.getActionMasked();\n                switch (action) {\n                    case MotionEvent.ACTION_SCROLL:\n                        x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);\n                        y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);\n                        SDLActivity.onNativeMouse(0, action, x, y, false);\n                        return true;\n\n                    case MotionEvent.ACTION_HOVER_MOVE:\n                        x = event.getX(0);\n                        y = event.getY(0);\n                        SDLActivity.onNativeMouse(0, action, x, y, false);\n                        return true;\n\n                    default:\n                        break;\n                }\n                break;\n\n            case InputDevice.SOURCE_MOUSE_RELATIVE:\n                action = event.getActionMasked();\n                switch (action) {\n                    case MotionEvent.ACTION_SCROLL:\n                        x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);\n                        y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);\n                        SDLActivity.onNativeMouse(0, action, x, y, false);\n                        return true;\n\n                    case MotionEvent.ACTION_HOVER_MOVE:\n                        x = event.getX(0);\n                        y = event.getY(0);\n                        SDLActivity.onNativeMouse(0, action, x, y, true);\n                        return true;\n\n                    default:\n                        break;\n                }\n                break;\n\n            default:\n                break;\n        }\n\n        // Event was not managed\n        return false;\n    }\n\n    @Override\n    public boolean supportsRelativeMouse() {\n        return (!SDLActivity.isDeXMode() || Build.VERSION.SDK_INT >= 27 /* Android 8.1 (O_MR1) */);\n    }\n\n    @Override\n    public boolean inRelativeMode() {\n        return mRelativeModeEnabled;\n    }\n\n    @Override\n    public boolean setRelativeMouseEnabled(boolean enabled) {\n        if (!SDLActivity.isDeXMode() || Build.VERSION.SDK_INT >= 27 /* Android 8.1 (O_MR1) */) {\n            if (enabled) {\n                SDLActivity.getContentView().requestPointerCapture();\n            } else {\n                SDLActivity.getContentView().releasePointerCapture();\n            }\n            mRelativeModeEnabled = enabled;\n            return true;\n        } else {\n            return false;\n        }\n    }\n\n    @Override\n    public void reclaimRelativeMouseModeIfNeeded()\n    {\n        if (mRelativeModeEnabled && !SDLActivity.isDeXMode()) {\n            SDLActivity.getContentView().requestPointerCapture();\n        }\n    }\n\n    @Override\n    public float getEventX(MotionEvent event) {\n        // Relative mouse in capture mode will only have relative for X/Y\n        return event.getX(0);\n    }\n\n    @Override\n    public float getEventY(MotionEvent event) {\n        // Relative mouse in capture mode will only have relative for X/Y\n        return event.getY(0);\n    }\n}\n"
  },
  {
    "path": "android/app/src/main/java/org/libsdl/app/SDLSurface.java",
    "content": "package org.libsdl.app;\n\n\nimport android.content.Context;\nimport android.content.pm.ActivityInfo;\nimport android.hardware.Sensor;\nimport android.hardware.SensorEvent;\nimport android.hardware.SensorEventListener;\nimport android.hardware.SensorManager;\nimport android.os.Build;\nimport android.util.DisplayMetrics;\nimport android.util.Log;\nimport android.view.Display;\nimport android.view.InputDevice;\nimport android.view.KeyEvent;\nimport android.view.MotionEvent;\nimport android.view.Surface;\nimport android.view.SurfaceHolder;\nimport android.view.SurfaceView;\nimport android.view.View;\nimport android.view.WindowManager;\n\n\n/**\n    SDLSurface. This is what we draw on, so we need to know when it's created\n    in order to do anything useful.\n\n    Because of this, that's where we set up the SDL thread\n*/\npublic class SDLSurface extends SurfaceView implements SurfaceHolder.Callback,\n    View.OnKeyListener, View.OnTouchListener, SensorEventListener  {\n\n    // Sensors\n    protected SensorManager mSensorManager;\n    protected Display mDisplay;\n\n    // Keep track of the surface size to normalize touch events\n    protected float mWidth, mHeight;\n\n    // Is SurfaceView ready for rendering\n    public boolean mIsSurfaceReady;\n\n    // Startup\n    public SDLSurface(Context context) {\n        super(context);\n        getHolder().addCallback(this);\n\n        setFocusable(true);\n        setFocusableInTouchMode(true);\n        requestFocus();\n        setOnKeyListener(this);\n        setOnTouchListener(this);\n\n        mDisplay = ((WindowManager)context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();\n        mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);\n\n        setOnGenericMotionListener(SDLActivity.getMotionListener());\n\n        // Some arbitrary defaults to avoid a potential division by zero\n        mWidth = 1.0f;\n        mHeight = 1.0f;\n\n        mIsSurfaceReady = false;\n    }\n\n    public void handlePause() {\n        enableSensor(Sensor.TYPE_ACCELEROMETER, false);\n    }\n\n    public void handleResume() {\n        setFocusable(true);\n        setFocusableInTouchMode(true);\n        requestFocus();\n        setOnKeyListener(this);\n        setOnTouchListener(this);\n        enableSensor(Sensor.TYPE_ACCELEROMETER, true);\n    }\n\n    public Surface getNativeSurface() {\n        return getHolder().getSurface();\n    }\n\n    // Called when we have a valid drawing surface\n    @Override\n    public void surfaceCreated(SurfaceHolder holder) {\n        Log.v(\"SDL\", \"surfaceCreated()\");\n        SDLActivity.onNativeSurfaceCreated();\n    }\n\n    // Called when we lose the surface\n    @Override\n    public void surfaceDestroyed(SurfaceHolder holder) {\n        Log.v(\"SDL\", \"surfaceDestroyed()\");\n\n        // Transition to pause, if needed\n        SDLActivity.mNextNativeState = SDLActivity.NativeState.PAUSED;\n        SDLActivity.handleNativeState();\n\n        mIsSurfaceReady = false;\n        SDLActivity.onNativeSurfaceDestroyed();\n    }\n\n    // Called when the surface is resized\n    @Override\n    public void surfaceChanged(SurfaceHolder holder,\n                               int format, int width, int height) {\n        Log.v(\"SDL\", \"surfaceChanged()\");\n\n        if (SDLActivity.mSingleton == null) {\n            return;\n        }\n\n        mWidth = width;\n        mHeight = height;\n        int nDeviceWidth = width;\n        int nDeviceHeight = height;\n        try\n        {\n            if (Build.VERSION.SDK_INT >= 17 /* Android 4.2 (JELLY_BEAN_MR1) */) {\n                DisplayMetrics realMetrics = new DisplayMetrics();\n                mDisplay.getRealMetrics( realMetrics );\n                nDeviceWidth = realMetrics.widthPixels;\n                nDeviceHeight = realMetrics.heightPixels;\n            }\n        } catch(Exception ignored) {\n        }\n\n        synchronized(SDLActivity.getContext()) {\n            // In case we're waiting on a size change after going fullscreen, send a notification.\n            SDLActivity.getContext().notifyAll();\n        }\n\n        Log.v(\"SDL\", \"Window size: \" + width + \"x\" + height);\n        Log.v(\"SDL\", \"Device size: \" + nDeviceWidth + \"x\" + nDeviceHeight);\n        SDLActivity.nativeSetScreenResolution(width, height, nDeviceWidth, nDeviceHeight, mDisplay.getRefreshRate());\n        SDLActivity.onNativeResize();\n\n        // Prevent a screen distortion glitch,\n        // for instance when the device is in Landscape and a Portrait App is resumed.\n        boolean skip = false;\n        int requestedOrientation = SDLActivity.mSingleton.getRequestedOrientation();\n\n        if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT) {\n            if (mWidth > mHeight) {\n               skip = true;\n            }\n        } else if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE) {\n            if (mWidth < mHeight) {\n               skip = true;\n            }\n        }\n\n        // Special Patch for Square Resolution: Black Berry Passport\n        if (skip) {\n           double min = Math.min(mWidth, mHeight);\n           double max = Math.max(mWidth, mHeight);\n\n           if (max / min < 1.20) {\n              Log.v(\"SDL\", \"Don't skip on such aspect-ratio. Could be a square resolution.\");\n              skip = false;\n           }\n        }\n\n        // Don't skip in MultiWindow.\n        if (skip) {\n            if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {\n                if (SDLActivity.mSingleton.isInMultiWindowMode()) {\n                    Log.v(\"SDL\", \"Don't skip in Multi-Window\");\n                    skip = false;\n                }\n            }\n        }\n\n        if (skip) {\n           Log.v(\"SDL\", \"Skip .. Surface is not ready.\");\n           mIsSurfaceReady = false;\n           return;\n        }\n\n        /* If the surface has been previously destroyed by onNativeSurfaceDestroyed, recreate it here */\n        SDLActivity.onNativeSurfaceChanged();\n\n        /* Surface is ready */\n        mIsSurfaceReady = true;\n\n        SDLActivity.mNextNativeState = SDLActivity.NativeState.RESUMED;\n        SDLActivity.handleNativeState();\n    }\n\n    // Key events\n    @Override\n    public boolean onKey(View v, int keyCode, KeyEvent event) {\n        return SDLActivity.handleKeyEvent(v, keyCode, event, null);\n    }\n\n    // Touch events\n    @Override\n    public boolean onTouch(View v, MotionEvent event) {\n        /* Ref: http://developer.android.com/training/gestures/multi.html */\n        int touchDevId = event.getDeviceId();\n        final int pointerCount = event.getPointerCount();\n        int action = event.getActionMasked();\n        int pointerFingerId;\n        int i = -1;\n        float x,y,p;\n\n        /*\n         * Prevent id to be -1, since it's used in SDL internal for synthetic events\n         * Appears when using Android emulator, eg:\n         *  adb shell input mouse tap 100 100\n         *  adb shell input touchscreen tap 100 100\n         */\n        if (touchDevId < 0) {\n            touchDevId -= 1;\n        }\n\n        // 12290 = Samsung DeX mode desktop mouse\n        // 12290 = 0x3002 = 0x2002 | 0x1002 = SOURCE_MOUSE | SOURCE_TOUCHSCREEN\n        // 0x2   = SOURCE_CLASS_POINTER\n        if (event.getSource() == InputDevice.SOURCE_MOUSE || event.getSource() == (InputDevice.SOURCE_MOUSE | InputDevice.SOURCE_TOUCHSCREEN)) {\n            int mouseButton = 1;\n            try {\n                Object object = event.getClass().getMethod(\"getButtonState\").invoke(event);\n                if (object != null) {\n                    mouseButton = (Integer) object;\n                }\n            } catch(Exception ignored) {\n            }\n\n            // We need to check if we're in relative mouse mode and get the axis offset rather than the x/y values\n            // if we are.  We'll leverage our existing mouse motion listener\n            SDLGenericMotionListener_API12 motionListener = SDLActivity.getMotionListener();\n            x = motionListener.getEventX(event);\n            y = motionListener.getEventY(event);\n\n            SDLActivity.onNativeMouse(mouseButton, action, x, y, motionListener.inRelativeMode());\n        } else {\n            switch(action) {\n                case MotionEvent.ACTION_MOVE:\n                    for (i = 0; i < pointerCount; i++) {\n                        pointerFingerId = event.getPointerId(i);\n                        x = event.getX(i) / mWidth;\n                        y = event.getY(i) / mHeight;\n                        p = event.getPressure(i);\n                        if (p > 1.0f) {\n                            // may be larger than 1.0f on some devices\n                            // see the documentation of getPressure(i)\n                            p = 1.0f;\n                        }\n                        SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p);\n                    }\n                    break;\n\n                case MotionEvent.ACTION_UP:\n                case MotionEvent.ACTION_DOWN:\n                    // Primary pointer up/down, the index is always zero\n                    i = 0;\n                    /* fallthrough */\n                case MotionEvent.ACTION_POINTER_UP:\n                case MotionEvent.ACTION_POINTER_DOWN:\n                    // Non primary pointer up/down\n                    if (i == -1) {\n                        i = event.getActionIndex();\n                    }\n\n                    pointerFingerId = event.getPointerId(i);\n                    x = event.getX(i) / mWidth;\n                    y = event.getY(i) / mHeight;\n                    p = event.getPressure(i);\n                    if (p > 1.0f) {\n                        // may be larger than 1.0f on some devices\n                        // see the documentation of getPressure(i)\n                        p = 1.0f;\n                    }\n                    SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p);\n                    break;\n\n                case MotionEvent.ACTION_CANCEL:\n                    for (i = 0; i < pointerCount; i++) {\n                        pointerFingerId = event.getPointerId(i);\n                        x = event.getX(i) / mWidth;\n                        y = event.getY(i) / mHeight;\n                        p = event.getPressure(i);\n                        if (p > 1.0f) {\n                            // may be larger than 1.0f on some devices\n                            // see the documentation of getPressure(i)\n                            p = 1.0f;\n                        }\n                        SDLActivity.onNativeTouch(touchDevId, pointerFingerId, MotionEvent.ACTION_UP, x, y, p);\n                    }\n                    break;\n\n                default:\n                    break;\n            }\n        }\n\n        return true;\n   }\n\n    // Sensor events\n    public void enableSensor(int sensortype, boolean enabled) {\n        // TODO: This uses getDefaultSensor - what if we have >1 accels?\n        if (enabled) {\n            mSensorManager.registerListener(this,\n                            mSensorManager.getDefaultSensor(sensortype),\n                            SensorManager.SENSOR_DELAY_GAME, null);\n        } else {\n            mSensorManager.unregisterListener(this,\n                            mSensorManager.getDefaultSensor(sensortype));\n        }\n    }\n\n    @Override\n    public void onAccuracyChanged(Sensor sensor, int accuracy) {\n        // TODO\n    }\n\n    @Override\n    public void onSensorChanged(SensorEvent event) {\n        if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {\n\n            // Since we may have an orientation set, we won't receive onConfigurationChanged events.\n            // We thus should check here.\n            int newOrientation;\n\n            float x, y;\n            switch (mDisplay.getRotation()) {\n                case Surface.ROTATION_90:\n                    x = -event.values[1];\n                    y = event.values[0];\n                    newOrientation = SDLActivity.SDL_ORIENTATION_LANDSCAPE;\n                    break;\n                case Surface.ROTATION_270:\n                    x = event.values[1];\n                    y = -event.values[0];\n                    newOrientation = SDLActivity.SDL_ORIENTATION_LANDSCAPE_FLIPPED;\n                    break;\n                case Surface.ROTATION_180:\n                    x = -event.values[0];\n                    y = -event.values[1];\n                    newOrientation = SDLActivity.SDL_ORIENTATION_PORTRAIT_FLIPPED;\n                    break;\n                case Surface.ROTATION_0:\n                default:\n                    x = event.values[0];\n                    y = event.values[1];\n                    newOrientation = SDLActivity.SDL_ORIENTATION_PORTRAIT;\n                    break;\n            }\n\n            if (newOrientation != SDLActivity.mCurrentOrientation) {\n                SDLActivity.mCurrentOrientation = newOrientation;\n                SDLActivity.onNativeOrientationChanged(newOrientation);\n            }\n\n            SDLActivity.onNativeAccel(-x / SensorManager.GRAVITY_EARTH,\n                                      y / SensorManager.GRAVITY_EARTH,\n                                      event.values[2] / SensorManager.GRAVITY_EARTH);\n\n\n        }\n    }\n\n    // Captured pointer events for API 26.\n    public boolean onCapturedPointerEvent(MotionEvent event)\n    {\n        int action = event.getActionMasked();\n\n        float x, y;\n        switch (action) {\n            case MotionEvent.ACTION_SCROLL:\n                x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);\n                y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);\n                SDLActivity.onNativeMouse(0, action, x, y, false);\n                return true;\n\n            case MotionEvent.ACTION_HOVER_MOVE:\n            case MotionEvent.ACTION_MOVE:\n                x = event.getX(0);\n                y = event.getY(0);\n                SDLActivity.onNativeMouse(0, action, x, y, true);\n                return true;\n\n            case MotionEvent.ACTION_BUTTON_PRESS:\n            case MotionEvent.ACTION_BUTTON_RELEASE:\n\n                // Change our action value to what SDL's code expects.\n                if (action == MotionEvent.ACTION_BUTTON_PRESS) {\n                    action = MotionEvent.ACTION_DOWN;\n                } else { /* MotionEvent.ACTION_BUTTON_RELEASE */\n                    action = MotionEvent.ACTION_UP;\n                }\n\n                x = event.getX(0);\n                y = event.getY(0);\n                int button = event.getButtonState();\n\n                SDLActivity.onNativeMouse(button, action, x, y, true);\n                return true;\n        }\n\n        return false;\n    }\n}\n"
  },
  {
    "path": "android/app/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"colorPrimary\">#3F51B5</color>\n    <color name=\"colorPrimaryDark\">#303F9F</color>\n    <color name=\"colorAccent\">#FF4081</color>\n</resources>\n"
  },
  {
    "path": "android/app/src/main/res/values/strings.xml",
    "content": "<resources>\n</resources>\n"
  },
  {
    "path": "android/app/src/main/res/values/styles.xml",
    "content": "<resources>\n\n    <!-- Base application theme. -->\n    <style name=\"AppTheme\" parent=\"Theme.AppCompat.Light.DarkActionBar\">\n        <!-- Customize your theme here. -->\n        <item name=\"colorPrimary\">@color/colorPrimary</item>\n        <item name=\"colorPrimaryDark\">@color/colorPrimaryDark</item>\n        <item name=\"colorAccent\">@color/colorAccent</item>\n    </style>\n\n</resources>\n"
  },
  {
    "path": "android/build.gradle",
    "content": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\n\nbuildscript {\n    repositories {\n        google()\n        jcenter()\n    }\n    dependencies {\n        classpath 'com.android.tools.build:gradle:8.4.0'\n\n        // NOTE: Do not place your application dependencies here; they belong\n        // in the individual module build.gradle files\n    }\n}\n\n// Instead of having a build dir inside android app dir use the same project top level build dir to reduce clutter\nbuildDir = file(\"distribution/${rootProject.name}/${project.name}\")\nsubprojects {\n\tbuildDir = file(\"../distribution/${rootProject.name}/${project.name}\")\n}\n\nallprojects {\n    repositories {\n        google()\n        jcenter()\n    }\n}\n\ntask clean(type: Delete) {\n    delete rootProject.buildDir\n}\n"
  },
  {
    "path": "android/buildDebug.bat",
    "content": ""
  },
  {
    "path": "android/gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-8.6-bin.zip\nnetworkTimeout=10000\nvalidateDistributionUrl=true\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "android/gradle.properties",
    "content": "# Project-wide Gradle settings.\n\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will override*\n# any settings specified in this file.\n\n# For more details on how to configure your build environment visit\n# http://www.gradle.org/docs/current/userguide/build_environment.html\n\n# Specifies the JVM arguments used for the daemon process.\n# The setting is particularly useful for tweaking memory settings.\norg.gradle.jvmargs=-Xmx12G\n\n# When configured, Gradle will run in incubating parallel mode.\n# This option should only be used with decoupled projects. More details, visit\n# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects\n# org.gradle.parallel=true\nandroid.useAndroidX=true\nandroid.nonTransitiveRClass=false\nandroid.nonFinalResIds=false\n"
  },
  {
    "path": "android/gradlew",
    "content": "#!/bin/sh\n\n#\n# Copyright © 2015-2021 the original authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\n##############################################################################\n#\n#   Gradle start up script for POSIX generated by Gradle.\n#\n#   Important for running:\n#\n#   (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is\n#       noncompliant, but you have some other compliant shell such as ksh or\n#       bash, then to run this script, type that shell name before the whole\n#       command line, like:\n#\n#           ksh Gradle\n#\n#       Busybox and similar reduced shells will NOT work, because this script\n#       requires all of these POSIX shell features:\n#         * functions;\n#         * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,\n#           «${var#prefix}», «${var%suffix}», and «$( cmd )»;\n#         * compound commands having a testable exit status, especially «case»;\n#         * various built-in commands including «command», «set», and «ulimit».\n#\n#   Important for patching:\n#\n#   (2) This script targets any POSIX shell, so it avoids extensions provided\n#       by Bash, Ksh, etc; in particular arrays are avoided.\n#\n#       The \"traditional\" practice of packing multiple parameters into a\n#       space-separated string is a well documented source of bugs and security\n#       problems, so this is (mostly) avoided, by progressively accumulating\n#       options in \"$@\", and eventually passing that to Java.\n#\n#       Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,\n#       and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;\n#       see the in-line comments for details.\n#\n#       There are tweaks for specific operating systems such as AIX, CygWin,\n#       Darwin, MinGW, and NonStop.\n#\n#   (3) This script is generated from the Groovy template\n#       https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt\n#       within the Gradle project.\n#\n#       You can find Gradle at https://github.com/gradle/gradle/.\n#\n##############################################################################\n\n# Attempt to set APP_HOME\n\n# Resolve links: $0 may be a link\napp_path=$0\n\n# Need this for daisy-chained symlinks.\nwhile\n    APP_HOME=${app_path%\"${app_path##*/}\"}  # leaves a trailing /; empty if no leading path\n    [ -h \"$app_path\" ]\ndo\n    ls=$( ls -ld \"$app_path\" )\n    link=${ls#*' -> '}\n    case $link in             #(\n      /*)   app_path=$link ;; #(\n      *)    app_path=$APP_HOME$link ;;\n    esac\ndone\n\n# This is normally unused\n# shellcheck disable=SC2034\nAPP_BASE_NAME=${0##*/}\n# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)\nAPP_HOME=$( cd \"${APP_HOME:-./}\" > /dev/null && pwd -P ) || exit\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=maximum\n\nwarn () {\n    echo \"$*\"\n} >&2\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n} >&2\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"$( uname )\" in                #(\n  CYGWIN* )         cygwin=true  ;; #(\n  Darwin* )         darwin=true  ;; #(\n  MSYS* | MINGW* )  msys=true    ;; #(\n  NONSTOP* )        nonstop=true ;;\nesac\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=$JAVA_HOME/jre/sh/java\n    else\n        JAVACMD=$JAVA_HOME/bin/java\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=java\n    if ! command -v java >/dev/null 2>&1\n    then\n        die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nfi\n\n# Increase the maximum file descriptors if we can.\nif ! \"$cygwin\" && ! \"$darwin\" && ! \"$nonstop\" ; then\n    case $MAX_FD in #(\n      max*)\n        # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC2039,SC3045\n        MAX_FD=$( ulimit -H -n ) ||\n            warn \"Could not query maximum file descriptor limit\"\n    esac\n    case $MAX_FD in  #(\n      '' | soft) :;; #(\n      *)\n        # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC2039,SC3045\n        ulimit -n \"$MAX_FD\" ||\n            warn \"Could not set maximum file descriptor limit to $MAX_FD\"\n    esac\nfi\n\n# Collect all arguments for the java command, stacking in reverse order:\n#   * args from the command line\n#   * the main class name\n#   * -classpath\n#   * -D...appname settings\n#   * --module-path (only if needed)\n#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.\n\n# For Cygwin or MSYS, switch paths to Windows format before running java\nif \"$cygwin\" || \"$msys\" ; then\n    APP_HOME=$( cygpath --path --mixed \"$APP_HOME\" )\n    CLASSPATH=$( cygpath --path --mixed \"$CLASSPATH\" )\n\n    JAVACMD=$( cygpath --unix \"$JAVACMD\" )\n\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    for arg do\n        if\n            case $arg in                                #(\n              -*)   false ;;                            # don't mess with options #(\n              /?*)  t=${arg#/} t=/${t%%/*}              # looks like a POSIX filepath\n                    [ -e \"$t\" ] ;;                      #(\n              *)    false ;;\n            esac\n        then\n            arg=$( cygpath --path --ignore --mixed \"$arg\" )\n        fi\n        # Roll the args list around exactly as many times as the number of\n        # args, so each arg winds up back in the position where it started, but\n        # possibly modified.\n        #\n        # NB: a `for` loop captures its iteration list before it begins, so\n        # changing the positional parameters here affects neither the number of\n        # iterations, nor the values presented in `arg`.\n        shift                   # remove old arg\n        set -- \"$@\" \"$arg\"      # push replacement arg\n    done\nfi\n\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS='\"-Xmx64m\" \"-Xms64m\"'\n\n# Collect all arguments for the java command:\n#   * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,\n#     and any embedded shellness will be escaped.\n#   * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be\n#     treated as '${Hostname}' itself on the command line.\n\nset -- \\\n        \"-Dorg.gradle.appname=$APP_BASE_NAME\" \\\n        -classpath \"$CLASSPATH\" \\\n        org.gradle.wrapper.GradleWrapperMain \\\n        \"$@\"\n\n# Stop when \"xargs\" is not available.\nif ! command -v xargs >/dev/null 2>&1\nthen\n    die \"xargs is not available\"\nfi\n\n# Use \"xargs\" to parse quoted args.\n#\n# With -n1 it outputs one arg per line, with the quotes and backslashes removed.\n#\n# In Bash we could simply go:\n#\n#   readarray ARGS < <( xargs -n1 <<<\"$var\" ) &&\n#   set -- \"${ARGS[@]}\" \"$@\"\n#\n# but POSIX shell has neither arrays nor command substitution, so instead we\n# post-process each arg (as a line of input to sed) to backslash-escape any\n# character that might be a shell metacharacter, then use eval to reverse\n# that process (while maintaining the separation between arguments), and wrap\n# the whole thing up as a single \"set\" statement.\n#\n# This will of course break if any of these variables contains a newline or\n# an unmatched quote.\n#\n\neval \"set -- $(\n        printf '%s\\n' \"$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\" |\n        xargs -n1 |\n        sed ' s~[^-[:alnum:]+,./:=@_]~\\\\&~g; ' |\n        tr '\\n' ' '\n    )\" '\"$@\"'\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "android/gradlew.bat",
    "content": "@rem\n@rem Copyright 2015 the original author or authors.\n@rem\n@rem Licensed under the Apache License, Version 2.0 (the \"License\");\n@rem you may not use this file except in compliance with the License.\n@rem You may obtain a copy of the License at\n@rem\n@rem      https://www.apache.org/licenses/LICENSE-2.0\n@rem\n@rem Unless required by applicable law or agreed to in writing, software\n@rem distributed under the License is distributed on an \"AS IS\" BASIS,\n@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n@rem See the License for the specific language governing permissions and\n@rem limitations under the License.\n@rem\n\n@if \"%DEBUG%\"==\"\" @echo off\n@rem ##########################################################################\n@rem\n@rem  Gradle startup script for Windows\n@rem\n@rem ##########################################################################\n\n@rem Set local scope for the variables with windows NT shell\nif \"%OS%\"==\"Windows_NT\" setlocal\n\nset DIRNAME=%~dp0\nif \"%DIRNAME%\"==\"\" set DIRNAME=.\n@rem This is normally unused\nset APP_BASE_NAME=%~n0\nset APP_HOME=%DIRNAME%\n\n@rem Resolve any \".\" and \"..\" in APP_HOME to make it shorter.\nfor %%i in (\"%APP_HOME%\") do set APP_HOME=%%~fi\n\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nset DEFAULT_JVM_OPTS=\"-Xmx64m\" \"-Xms64m\"\n\n@rem Find java.exe\nif defined JAVA_HOME goto findJavaFromJavaHome\n\nset JAVA_EXE=java.exe\n%JAVA_EXE% -version >NUL 2>&1\nif %ERRORLEVEL% equ 0 goto execute\n\necho. 1>&2\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2\necho. 1>&2\necho Please set the JAVA_HOME variable in your environment to match the 1>&2\necho location of your Java installation. 1>&2\n\ngoto fail\n\n:findJavaFromJavaHome\nset JAVA_HOME=%JAVA_HOME:\"=%\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\n\nif exist \"%JAVA_EXE%\" goto execute\n\necho. 1>&2\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2\necho. 1>&2\necho Please set the JAVA_HOME variable in your environment to match the 1>&2\necho location of your Java installation. 1>&2\n\ngoto fail\n\n:execute\n@rem Setup the command line\n\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\n\n\n@rem Execute Gradle\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %*\n\n:end\n@rem End local scope for the variables with windows NT shell\nif %ERRORLEVEL% equ 0 goto mainEnd\n\n:fail\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\nrem the _cmd.exe /c_ return code!\nset EXIT_CODE=%ERRORLEVEL%\nif %EXIT_CODE% equ 0 set EXIT_CODE=1\nif not \"\"==\"%GRADLE_EXIT_CONSOLE%\" exit %EXIT_CODE%\nexit /b %EXIT_CODE%\n\n:mainEnd\nif \"%OS%\"==\"Windows_NT\" endlocal\n\n:omega\n"
  },
  {
    "path": "android/installDebug.bat",
    "content": "gradlew.bat installDebug"
  },
  {
    "path": "android/settings.gradle",
    "content": "include ':app'\n\n"
  },
  {
    "path": "build-deps.ps1",
    "content": "param(\n    [ValidateSet(\"x64\", \"x86\")][string]$Arch = \"x64\"\n)\n\nfunction SetEnv() {\n    $vswhere = \"${env:ProgramFiles(x86)}/Microsoft Visual Studio/Installer/vswhere.exe\"\n    $ip = & $vswhere -prerelease -latest -property InstallationPath\n    if ($ip -and (test-path \"$ip\\Common7\\Tools\\vsdevcmd.bat\")) {\n        & \"${env:COMSPEC}\" /s /c \"`\"$ip\\Common7\\Tools\\vsdevcmd.bat`\" -arch=x64 -no_logo && set\" | foreach-object {\n            $name, $value = $_ -split '=', 2\n            set-content env:\\\"$name\" $value\n        }\n    }\n}\n\nfunction InstallPackages() {\n    $vcpkg = \"$env:VCPKG_ROOT/vcpkg.exe\"\n    if (!(Get-Command $vcpkg -ErrorAction SilentlyContinue)) {\n        $vcpkg = \"vcpkg\"\n    }\n\n    $local_vcpkg = $false\n    if(!(Get-Command $vcpkg -ErrorAction SilentlyContinue)) {\n        if (!(Test-Path build/vcpkg)) {\n            echo \"Cloning vcpkg...\"\n            mkdir build -Force | Out-Null\n            pushd build\n            & git clone https://github.com/Microsoft/vcpkg.git --depth 1\n            pushd vcpkg\n            ./bootstrap-vcpkg -disableMetrics\n            popd\n            popd\n            $vcpkg = \"build/vcpkg/vcpkg.exe\"\n        }\n        $local_vcpkg = $true\n    }\n\n    & $vcpkg integrate install\n    if ($local_vcpkg) {\n        Write-Output \"Cleaning up...\"\n        Remove-Item build/vcpkg/downloads -Recurse\n        Remove-Item build/vcpkg/buildtrees -Recurse\n    }\n}\n\nInstallPackages"
  },
  {
    "path": "doc/contributor_guide.md",
    "content": "# Code style\n\nThis project includes a clang-format file, which should be used for formatting all of the C++ code files. You can also integrate clang-format directly into your IDE of choice.\n\nCamelCase should be used for everything except local variables. lowerCamelCase should be used for local variables.\n\nAll of the code goes into the Impacto namespace. Additionally Systems, UI element bases and game specifc implementations go into their own namespaces.\n\nFor example:\n\n```cpp\n// Input handling system implementation\nnamespace Impacto {\nnamespace Input {\n   // ...\n}  // namespace Input\n}  // namespace Impacto\n```\n\n```cpp\n//Main menu UI element base implementation\nnamespace Impacto {\nnamespace MainMenu {\n   // ...\n}  // namespace MainMenu\n}  // namespace Impacto\n```\n\n```cpp\n//Robotics;Notes Elite specifc UI elements implementations\nnamespace Impacto {\nnamespace RNE {\n   // ...\n}  // namespace RNE\n}  // namespace Impacto\n```\n\n"
  },
  {
    "path": "doc/font-lb/CompoundCharacters.tbl",
    "content": "﻿[E000]=ｶﾞ\n[E001]=ﾀｯ\n[E002]=  \n[E003-EFFF]= "
  },
  {
    "path": "doc/font-lb/RobonoCharset.utf8",
    "content": " 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz /:-;!?'.@#%~*＿`()ﾟ^>+<ﾉｷﾘｯ$&\",[]=０１２３４５６７８９ＡＢＣＤＥＦＧＨＩＪＫＬＭＮＯＰＱＲＳＴＵＶＷＸＹＺａｂｃｄｅｆｇｈｉｊｋｌｍｎｏｐｑｒｓｔｕｖｗｘｙｚ、。，．：；？！゛゜‘’“”（）〔〕［］｛｝〈〉《》「」『』【】＜＞【】・…〜ー♪─ぁぃぅぇぉっゃゅょゎァィゥェォッャュョヮヵヶ①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮²♥©⑲⑳％―━＿／•①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①あいうえおかがきぎくぐけげこごさざしじすずせぜそぞただちぢつづてでとどなにぬねのはばぱひびぴふぶぷへべぺほぼぽまみむめもやゆよらりるれろわゐゑをんアイウエオカガキギクグケゲコゴサザシジスズセゼソゾタダチヂツヅテデトドナニヌネノハバパヒビピフブプヘベペホボポマミムメモヤユヨラリルレロワヰヱヲンヴ☆★◎○●△▲□■▽▼◇◆※→←＋－×÷＝≧≦＼〓＆〆追加新項目敗北勝利入力失成功分岐先指定海翔世界救戦誰言前全人類希望俺興味穂所詮自中衝動満最強丁寧不謹慎構事国位倒頂点一昴立完了求淳和電源積込英雄軍主公供給開始喋感度良好員押忍体風吹確実続誤差以下正常起済安充率連稼働限時間忘各種異有夫僕思悪後輩対応座標固待調整変葉遣減断解除回認居左右周辺範囲地形判用問題締上愛理本日年月天予報伝鹿児島県子屋久方東南晴波高温低降水貴重情部集合暇行初今終必戻格闘発進名八汐央校普科組出席号績活研究属詳得意野隠示画面内消小息薄暗室外夏爽駆抜狭熱違汗引心身浴景色期台多柄空碧隈半遺産横住火縄銃射場白真昔学舞話慢試遠呼声聞我長帰還娘現瀬乃宮捉同幼頃腐縁程仲遊付呑持口頑張状況隣具倉庫察来使想二物置端灯個当然春秋暑冬寒環境芝生雨答額滲的迎考恐教頭会議忙相手側費止粘交渉取原因去無茶要悲壮笑浮焦偉少覚称歴史遡女徒姉設果昇足盛近京優経験証拠数知洗脳作効古西矢逆派結何花落評舎技術準備陽陰十伊達金字塔登在駄向習怒削命令飽料特未練木腰放課決流万牙城崩悔材昨神品崇説奥深々素奇跡噛怪由切者様謎包才噂光栄揚並拝式基操撃繋簡単読潰裏仕掛局選択他機性能極到注退離油禁徴早精視反平均可処絞値導訳魅激狙皆絡論柔攻書法弊害礼苦労癖送疑傷返信嘘痕触別愚痴伸眺揺覆庭姿運歩距総園鳥鳴渡草静残寄独浸哲尊敬眼突途宣表聴衆造卒業絶僻存就職肢旅暮願漠夢浪萎観測走夕根唱諦叶将路錯馳態停投専保管緒従負舌打念着含腕適弱髪両容赦関義肩犬勇旧港閉鎖廃墟訪割雑広遮裾死駐滑幅道比車寝掲堂直鍵元防燃補踏埃窓涙晶鎮巨略超際乗縦算誇創計図描許町工譲提企段代受継社務妹巡凡胸吐首振頬叩弾幕拳隅棚缶携帯移替欠過例幽霊刻嫌這警告抵抗殺百虚＃難若憧黄昏識微妙擬似量背痛配膝隙潜勢奪制服汚短更衣皮肉勲章疲質虐輝握男士友納協街夜黒改装家唯営忌明週照臭腹遅呆盗買食晩儀休憩転故幸軽羽織宅軒脱挨拶影響角染血美緯太山勘慣慨繰像澄青越迫匂緑丸毎堅禮商店瑞榎怠醸級化粧飾随械助支障売介護厳密田辞践鼻蓄飲次蒸器冗談文袋土船便届育師顧彦扱豪語頼条件怖殊急門輪殿慌骨折損欄販破紹円速秒垂跳緊約承兼演躍訴騙猫泣震珍映宿嬉担三賛殴菓勉困泥川掃捨橋句箱煙詰敵勧誘辛辣淡履靴団彼価参壊修即噴醜肝健懲印象烈嘲永遇既権益努官僚縮疾涛復暴叫案奮収懸机絵枚巻宝紀及記録媒黙昭鉄型狂寸騒係溢析挑幻王邪楽鑑賞唇尖曲宇宙音吸換虫耳径泉冥沈漏暦片溶細朝鈴傘館扉童雰非徳凛漂捌蹴鋭遂拭濡恥弟尽快袖冷昼弁板委臼井至戸惑浜峰喜飯批撤咎省留顛末赴任壇善欲請余裕瞬筋博展秀般抱援剰再曜催競獲検討咳払姑束守伐厄晦甘廊蛙延午授罠粒砂仁嘆仰母玉借資貯毛誉焼董劇梅干酸荷探屈散豆把彰写溜募荒模索申逃貫孤役班貶胃増遭避憶抹君臓針刺涼第瞳炎尚稚系互坊魂魔推芙歳父農畑挟階玄尻舐釈七壁貼与吟斎親寂悟惚綿掴剥歪酷膚拗湿冊危純池床混懇私卓窺伏採迷己毒看圧建舗塞洋客藤治偏添沙汰促劣製賢貧乏香軸紙裂財貿易畳凶獄窒液嚥喉潤契愉脅傑肌層粉謝律翌孫族祖甲斐諸刃剣茨都妄蓋覗規驚招姓戚典乱怯罪拒否布札税漱石千侮辱盤鉢責掘四股擦渋節施概芻棒餅閃棄纏漁御順序紛老婆忠歯闇没懐則崖凍蒼据披露湯徹底揉熟編颯撼複璧摘威兆候脂徐藍稀弄症群医病療耗策傾貸預丘錆敷郷埋聖滅融踊芸馬星紅侵儚厚綴築溝訝揃尾仮潮誓膳贈籠眠票鬼湧昂泊憑清硬之市煌茫奢詞爆祈苛漬鬱陶恨架湾岸惜区駅朽森歌虎克耐睡抑雲剤寡列帳躊躇覧襲争志津也搭載繊歓匠酔餌煽氏挙嬢吠審胆俯瞰軌鍛旋鈍浅逸胴祭猛翼矯俄覇辿該凝褒革晒凄魚控彗祝福臨陣尋牽穴穏植捕険球杭遥如武氷五拡沸騰診肺緩洪唐炸讃澤斬囚撫透爪陸垢茂励偽賄賂罵往妥粗泳憎免詭等戒拘統陥呪愁筆犯共哀飼為Ⅱ暖閑恒拍塊詩杯頻郎淫靡筑粋浦齢伺罰屁贄棺桶穫宏柱醤呂沖韓鼓渇渦拾襖堵腋燥箸郡儲縛乙萌裸鏡慮訊桁億排蔵隔訂洞鎧却瞥嵐叔襟依刑箒斉査岩季窟亡骸政匹惨奄湖蛍秘謀雪汲嬌貢献婚松葬兄甥戯顎扇嗅蔑祥符仇羨航監狩撲悶峙双磁須曰寿仏偶林枝網錠執華詐欺被勃述累曖昧民閣府藪蛇括株享奴巧兵蔽圏域胡瓶房江頓羅牛些彩撮版倫莉栖院竹崎司棟龍雌汎敏薫寺綯睨糸雀衛舶´ゝ｀厨恋滴塵喧α屑哭劫黎曙封楼螺焉阿剛浩吉麻俊償榊＠β繁殖濃牧併ΜⅤ俗祉維九州桜薩摩浄暁伴癒娯亀玩燦枕銅康隷籍綻喩顕著遍誌亜℃蝕熊漫阪致災乾釣鐘薬砲銀誕岳肘隊雇奈箇衰村匿米稽稿賑迅雷鳳凰栗痩線赤接趣通倍気顔見番飛大丈占芳捗橙征羊捧盾卑麦滞謡六媚洩迂闊轄簿汁煮挫恰詛倣盟拷邁繕誠馴嫉妬怨潔陵鋼梳傍唾督捜睦庁粛措訟呈轟遙朗賭眩喝噤憫膨乳塗拉舟炭酪菌刊坂淵Ⅰ副脛曇腫漢旬穿釘寮孝慧芽幌梨富岡狼悠竦柵較擁芯奏悸恩署冒那垣拮刀縫裁芒谷鎌喫刷麗葛銭宛里漕沿串阻酒咲領嫁禿踪猿脆弛薦悩筒雫＄＊佐揮宴恵紺拙嗤隕吊枠皺核妨扁鉱枯盆宗莫樹竿淀乞茜託杞憂湊蚊岬貞惹豊妻尺叉瞑瓜墓逡憲糖皿軟喘鷹党彷彿吻膜弓奨沼賽河恍狗醒脚泡逢幹閲峯杖貨餓炉僅掌諜畜奉搾秩煎峡鮮堪飢泌裔脇塩翻遽紐朴欧殻冑錬丹患濁磨疎摯邂逅勤蛾冴朔某凹愕碑眈孕氾濫雅隆碗驕婿養爺噌鶏酢慶棘巾椅紋凸逞套誹謗嘔啖呵椀紫脊髄沢諭挿斜痺綜糾躁蹂躙砕饐瀉蝋槽甚慰蛮綺霞犠牲悦痙攣痍憤詫播婦訓揶揄贅虹溺旗菜仙又鵜凌墜憔悴煩駕旺爛蔦獣楚憐姫胞蘇羞偵搬擢填軋謙刹需窮叱抽秤抉俵腱鷲慟澹升弔栓棲慈卵賠召滝冠蜘蛛脈柳涯郭霧洲囁弧挺噪嚇蠢棋朱戮猶瓦礫胎ω｜憾狐乖蜃Ⅳ肯培炙臣忽駒荘闖肥咆哮鶴轢敢堕絆喰嗚咽騎毅炊撒喪埒蜂巣幾槍豚囮嚢餐坦膠凪渾煤榴臆輸拓榜皇邸郊箔厭邦租帝杉蜜肖鉛稲賀惰＾丼賃兎炒艦鍾ΣД濯緒錦桐祐彙沌鍋墨撰焚芋酎糞褐鱗砦ヽ゜刮肪徘徊暫逝弩涜條禄零券彫埼笛撥殲竜毀逮讐槌妊傭伎僧綱矛　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　"
  },
  {
    "path": "doc/font-lb/font-lb.md",
    "content": "Font is generated with mgsfontgen-dx (we should really publish that some time...) and the following patch:\n\n```\ndiff --git a/src/Program.cs b/src/Program.cs\nindex a69248b..016ad4f 100644\n--- a/src/Program.cs\n+++ b/src/Program.cs\n@@ -55,8 +55,8 @@ namespace MgsFontGenDX\n             var charset = File.ReadAllText(arguments.CharsetFileName);\n             var compoundCharTable = ReadCompoundCharacterTable(arguments.CompoundCharTableFileName);\n\n-            const int batchSize_outline = 4544;//5440;\n-            const int batchSize_font = 5440;\n+            const int batchSize_outline = 3200;//5440;\n+            const int batchSize_font = 3200;\n             using (var textRenderer = new TextRenderer())\n             using (var widthTableFile = File.Create(\"widths.bin\"))\n             using (var widthWriter = new BinaryWriter(widthTableFile))\ndiff --git a/src/TextRenderer.cs b/src/TextRenderer.cs\nindex 8b5f51c..05782fb 100644\n--- a/src/TextRenderer.cs\n+++ b/src/TextRenderer.cs\n@@ -17,7 +17,7 @@ namespace MgsFontGenDX\n         private const int OutlineCellWidth = 57;\n         private const int OutlineCellHeight = 57;\n         private const float Dpi = 96.0f;\n-        private const float GameWidthMultiplier = 1.5f;\n+        private const float GameWidthMultiplier = 1.0f;\n\n         private int _cellWidth;\n         private int _cellHeight;\n@@ -44,6 +44,7 @@ namespace MgsFontGenDX\n         {\n             _cellWidth = drawOutline ? OutlineCellWidth : NormalCellWidth;\n             _cellHeight = drawOutline ? OutlineCellHeight : NormalCellHeight;\n+            Console.WriteLine(characters.Length);\n             int rowCount = (int)Math.Ceiling((double)characters.Length / ColumnCount);\n             int bitmapWidth = _cellWidth * ColumnCount;\n             int bitmapHeight = 4 * (int)Math.Ceiling((double)_cellHeight * rowCount / 4);\n@@ -158,7 +159,7 @@ namespace MgsFontGenDX\n         private byte Measure(string character, TextLayout layout, bool stretched)\n         {\n             double multiplier = stretched ? GameWidthMultiplier * character.Length : GameWidthMultiplier;\n-            return  (byte)(Math.Ceiling(layout.Metrics.WidthIncludingTrailingWhitespace / multiplier) + 1);\n+            return  (byte)(Math.Ceiling(layout.Metrics.WidthIncludingTrailingWhitespace / multiplier));\n         }\n\n         private void DrawCompoundCharacter(string compoundCharacter, TextLayout layout)\n```\n\nThe charset uses U+2004 THREE-PER-EM SPACE because we don't have proper metrics and spaces happen to end up too small the way we're generating the widths..."
  },
  {
    "path": "doc/getting_started.md",
    "content": "# Getting started\n\n## Game profiles\n\nTo handle the complexity of the full range of MAGES. engine titles (originally developed on different engine branches with many per-game hardcoded customizations) within a single codebase and binary, impacto uses a data-driven, modular design.\n\nOn application load, a set of scripts we call *profile* is executed, generating a (declarative) configuration specifying the chosen game's featureset (such as implemented VM instructions or the 3D scene in Robotics;Notes), parameters (like dimensions, names etc. ), asset location/use and \"game objects\" (menus/UI, database of 3D models in Robotics;Notes, built from primitives). `/profiles/` contains these scripts. `/src/profile/` has the host code to execute them and manually deserialize the resulting configuration into global state.\n\n## Running games\nThe following directories must be in the same directory as the impacto executable:\n\n* games - directory that contains game resource files\n* profiles - directory that contains profile definition files\n* shaders - directory that contains shader files\n\nThese folders can be found in the repository root. When using the *install* build step they are automatically copied to your specified install location.\n\nIn order to launch a game using impacto, until UI is developed for this, you must specify the game that needs to be launched using either of these methods:\n- A file named `profile.txt` that contains the name of the profile to launch placed next to the impacto executable.\n- A command line argument with the name of the profile as the first argument after the executable name.\n\nThe following profiles are currently available:\n\n* **chlcc** - Chaos;Head Love Chu Chu\n* **cclcc** - Chaos;Child Love Chu Chu\n* **darling** - Steins;Gate My Darling's Embrace\n* **dash** - Robotics;Notes DaSH\n* **mo6tw** - Memories Off 6 ~T-Wave~\n* **rne** - Robotics;Notes Elite (PS Vita)\n* **characterviewer** - A viewer for 2D characters\n* **modelviewer** - A viewer for 3D models from Robotics;Notes Elite\n* **modelviewer-dash** - A viewer for 3D models from Robotics;Notes DaSH\n\nFor the list of required game resource files refer to the `vfs.lua` file located in desired game profile directory. The resource files should be placed in `/games/<profile_name>/gamedata/` directory.\n\n## General engine information\nTo get an overall understanding of how the original Mages. engine functions please refer to https://committeeofzero.gitbooks.io/mages-engine-compendium/content/\n\n## Making simple changes\nThe main game loop is located in `/src/game.cpp`. The main scripting execution loop is located in `/src/vm/vm.cpp`.\n\nAll of Mages. engine games use bytecode script files to define game behaviour.\nThe scripting engine instructions are split into groups and implemented in their respective files, for example the instructions that alter the control flow are located in `/src/vm/inst_controlflow.cpp`.\nIn order to make implementing new instructions easier, all instruction implementations use macros. A simple instruction implementation would be defined as:\n\n```cpp\nVmInstruction(InstJump) {\n  StartInstruction;\n  PopLocalLabel(labelAdr);\n  thread->Ip = labelAdr;\n}\n```\n\nAfter defining an instruction, it must be placed into the instruction table for the desired instruction set. Instruction set definitions are located in `/src/vm/opcodetables_*.cpp`\n\n## Making simple UI changes\nSince each game has its own custom interface, UI elements are implemented using class hierarchy. Base class implementations are located in `/src/hud/`, while specific class implementations are located in `/src/games/<game_name>/`. The UI element implementations used along with their display specifications (co-ordinates, animation parameters etc) for each game are defined by the profile configuration files, located in `/profiles/<profile_name>/hud/`.\n\nEach UI element class implements an Update functions, which updates dynamic values, and a Render function, which draws sprites on the screen. For example, you can draw a simple sprite inside the Render function by using the following function (defined in `/src/renderer2d.h`):\n\n```cpp\nRenderer->DrawSprite(Sprite, glm::vec2(X, Y));\n```\n\nSprites are defined in profile configuration files. For example, in order to define a simple sprite that uses a texture from the system archive, do the following:\n\n1. In the apporpriate UI definition file located in `/src/profile/games/<game_name>/` define a `Sprite` (defined in `/src/spritesheet.h`) variable, for example:\n\n```cpp\nSprite BackgroundSprite;\n```\n\n2. In profile sprites definition file located in `/profiles/<profile_name>/sprites.lua` in `root.SpriteSheets` list, define a spritesheet, for example:\n\n```lua\n[\"Data\"] = {\n    Path: { Mount = \"system\", Id = 5 },\n    DesignWidth = 2048,  --Spritesheet width\n    DesignHeight = 1024  --Spritesheet height\n},\n```\n\n3. In the appropriate profile UI definition file located in `/profiles/<profile_name>/hud/` add a sprite definition to the global `root.Sprites` dictionary, for example:\n\n```lua\nroot.Sprites[\"TitleMenuBackground\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 0, Y = 0, Width = 1920, Height = 1080 }\n};\n```\n\n4. In the appropriate profile UI definition file located in `/profiles/<profile_name>/hud/` in the UI element object define the sprite variable, for example:\n\n```lua\nroot.TitleMenu = {\n    BackgroundSpriteProfile = \"TitleMenuBackground\"\n};\n```\n\n5. In the appropriate UI definition file located in `/src/profile/games/<game_name>/` in the `Configure` function, get the sprite object defined in the profile UI definition file, for example:\n\n```cpp\nBackgroundSprite = EnsureGetMember<Sprite>(\"BackgroundSpriteProfile\");\n```\n\nYou can now use the defined sprite in the appropriate UI element.  \n"
  },
  {
    "path": "doc/ubuntu_build.md",
    "content": "# Build instructions based on Ubuntu 20.04 LTS\n\nYou can use the following shell script for getting dependencies and setting up first time build on Ubuntu.\n\nFirst time setup for installing dependencies, vcpkg, and cloning impacto.   \n\n```shell\n# Build instructions based on Ubuntu 20.04 LTS\n\n# Install required programs and dependencies\nsudo apt update\nsudo apt -y install git cmake ninja-build build-essential libstdc++6 nasm curl zip pkg-config autoconf autoconf-archive automake libtool libx11-dev libxft-dev libxext-dev libwayland-dev libxkbcommon-dev libegl1-mesa-dev libibus-1.0-dev xcb libxrandr-dev\n# Note: The CMake version available from your system package manager may be out of date, check the version with \"cmake --version\", \n# and if it's lower than the version in CMakeLists, update it to match.\n\n# Clone vcpkg\ngit clone https://github.com/microsoft/vcpkg.git\n./vcpkg/bootstrap-vcpkg.sh\n# Set VCPKG_ROOT env var on user startup, needed for cmake to find the vcpkg toolchain, change .bashrc to match whatever shell is your default\necho \"export VCPKG_ROOT=\\\"$(pwd)/vcpkg\\\"\" >> ~/.bashrc\nsource ~/.profile\n\n# Clone impacto \ngit clone https://github.com/CommitteeOfZero/impacto.git\n```\n\nOnce this is done, from now on you can just enter the impacto directory and build using CMake.\n```shell\ncd impacto\n# for building release /w symbols build\ncmake --preset Release && cmake --build --preset x64-Release\n# for building debug build\ncmake --preset Debug && cmake --build --preset x64-Debug\n```\nOnce built, files should be installed to the `install` directory. Follow instructions in [getting_started](doc/getting_started.md) on where to place game files and resources.\n\nYou can also customize additional CMake options by creating a file called `CMakeUserPresets.json` (it's gitignored)\nbased off of the existing `CMakePresets.json`. This will allow you to customize settings such as enabling warnings, install directory. \nMake sure preset names are different from the original presets. "
  },
  {
    "path": "doc/vs_build.md",
    "content": "# Building with Visual Studio\n\n## Dependencies\n\nVisual Studio must have the \"Desktop development with C++\" optional features installed (you can check if you have them installed by going into the \"Tools\" menu and selecting \"Get Tools and Features...\").\n\nGit command-line tool must be available from within PowerShell. If you don't have it installed, you can download it from https://git-scm.com/downloads\n\nEnable execution of PowerShell scripts by running the following command in an elevated PowerShell prompt:\n\n`Set-ExecutionPolicy RemoteSigned`\n\n- If running on a version of Visual Studio older than 2022 (or vcpkg isn't installed), make sure to set the environment variable `VCPKG_ROOT` to `${impactoDir}/build/vcpkg`, or wherever else your vcpkg root directory is located.\n\nRun `.\\build-deps.ps1` from a PowerShell prompt, addings args for architecture [`x64`, `x86`] if needed\n\n## Visual Studio CMake project setup\n- You may have to enable CMakePresets in VS Options -> Cmake\n\n- The included `CMakePresets.json` in the repository root should already have several default build configurations. If you need to make any changes, create a `CMakeUserPresets.json`, of the same format, in the repository root (don't worry, it's gitignored) and change paths if necessary (the directories will be created automatically upon building). Make sure to name the presets differently from the default presets, since user presets cannot override existing presets.\n\n- Open the project with *File->Open->Folder...* in Visual Studio\n- When picking a startup item, make sure to use `impacto.exe (Install)`\n\n## Cry\n\nabout the absolute state of C++ dependency management"
  },
  {
    "path": "docker/impacto-emscripten/Dockerfile",
    "content": "#   docker build -t emscripten:latest --build-arg EMSCRIPTEN_SDK=sdk-tag-1.38.21-64bit https://raw.githubusercontent.com/trzecieu/emscripten-docker/master/docker/trzeci/emscripten/Dockerfile\n#   docker build -t impacto-emscripten .\n# (after 1.38.22 we should switch to trzeci/emscripten:latest)\n#   docker run --rm -v /path/to/build/directory:/build -v /path/to/impacto/repository/root:/src -u emscripten impacto-emscripten\n\nFROM emscripten:latest\n\nUSER emscripten\n\nRUN echo 'Caching emscripten ports' \\\n    && touch /tmp/ports_cache.cpp \\\n    && emcc -s USE_SDL=2 -s USE_OGG=1 -s USE_VORBIS=1 -s USE_ZLIB=1 /tmp/ports_cache.cpp -o /tmp/ports_cache.js \\\n    && rm /tmp/ports_cache.*\n\nUSER root    \nRUN mkdir -p /impacto-deps/local/include \\\n    && mkdir -p /impacto-deps/local/lib \\\n    && chown -R emscripten /impacto-deps\nUSER emscripten\n    \nCOPY libatrac9-emscripten.mk /impacto-deps\n    \nRUN echo 'Building libatrac9' \\\n    && mkdir -p /tmp/libatrac9-build \\\n    && cd /tmp/libatrac9-build \\\n    && git clone https://github.com/Thealexbarney/LibAtrac9 \\\n    && cd LibAtrac9 \\\n    && git checkout 6a9e00f6c7abd74d037fd210b6670d3cdb313049 \\\n    && cd C \\\n    && emmake make -f /impacto-deps/libatrac9-emscripten.mk \\\n    && cp bin/libatrac9.a /impacto-deps/local/lib/ \\\n    && mkdir -p /impacto-deps/local/include/libatrac9 \\\n    && cp src/libatrac9.h /impacto-deps/local/include/libatrac9/ \\\n    && cd /src \\\n    && rm -rf /tmp/libatrac9-build\n    \nRUN echo 'Building glm' \\\n    && mkdir -p /tmp/glm-build \\\n    && cd /tmp/glm-build \\\n    && wget https://github.com/g-truc/glm/releases/download/0.9.9.3/glm-0.9.9.3.zip \\\n    && unzip -q glm-0.9.9.3.zip \\\n    && mkdir build \\\n    && cd build \\\n    && emcmake cmake -DCMAKE_INSTALL_PREFIX=/impacto-deps/local -DGLM_TEST_ENABLE=OFF ../glm \\\n    && emmake make \\\n    && emmake make install \\\n    && cd /src \\\n    && rm -rf /tmp/glm-build\n\nVOLUME /build\nWORKDIR /build\n\nCMD [\"/src/docker/impacto-emscripten/build_emscripten.sh\"]"
  },
  {
    "path": "docker/impacto-emscripten/build_emscripten.sh",
    "content": "#!/bin/sh\n\nemcmake cmake -DCMAKE_FIND_ROOT_PATH=/impacto-deps -DCMAKE_PREFIX_PATH=/local $@ /src\nemmake make\n# https://github.com/kripken/emscripten/issues/5436\nsed -i 's/$legalf32//g' impacto.js"
  },
  {
    "path": "docker/impacto-emscripten/libatrac9-emscripten.mk",
    "content": "NAME = libatrac9\n\nCC = emcc\nAR = emar\nSFLAGS = -O2\nCFLAGS = -Wall -Wextra -std=c99\n\nSRCDIR = src\nOBJDIR = obj\nBINDIR = bin\n\nSTATIC_OBJDIR = $(OBJDIR)_static\nSRCS = $(wildcard $(SRCDIR)/*.c)\nSTATIC_OBJS = $(SRCS:$(SRCDIR)/%.c=$(STATIC_OBJDIR)/%.bc)\n\nSTATIC_NAME = $(BINDIR)/$(NAME).a\n\nMKDIR = mkdir -p\nRM = rm -f\nRMDIR = rm -df\n\nall: static\nstatic: create_static_dir create_bin_dir $(STATIC_NAME)\n\ncreate_static_dir:\n\t@$(MKDIR) $(STATIC_OBJDIR)\n\t\ncreate_bin_dir:\n\t@$(MKDIR) $(BINDIR)\n\n$(STATIC_NAME): $(STATIC_OBJS)\n\t$(AR) rcs $@ $^\n\n$(STATIC_OBJS): $(STATIC_OBJDIR)/%.bc : $(SRCDIR)/%.c\n\t$(CC) $(SFLAGS) $(CFLAGS) -c $< -o $@\n\nclean:\n\t$(RM) $(STATIC_OBJS) $(STATIC_NAME)\n\t-@$(RMDIR) $(STATIC_OBJDIR) $(BINDIR) 2>/dev/null || true\n\n.PHONY: all static create_static_dir create_bin_dir clean"
  },
  {
    "path": "docker/impacto-switch/Dockerfile",
    "content": "FROM devkitpro/devkita64\n\nLABEL org.opencontainers.image.source https://github.com/committeeofzero/impacto\n\nRUN apt install p7zip-full -y\n\n# Install latest cmake\nRUN echo \"Removing older version of cmake\" && \\\n  apt remove --purge --auto-remove -y cmake\nADD https://github.com/Kitware/CMake/releases/download/v3.31.3/cmake-3.31.3-linux-x86_64.sh /cmake-3.31.3-linux-x86_64.sh\nRUN mkdir /opt/cmake\nRUN sh /cmake-3.31.3-linux-x86_64.sh --prefix=/opt/cmake --skip-license\nRUN ln -s /opt/cmake/bin/cmake /usr/local/bin/cmake\nRUN cmake --version"
  },
  {
    "path": "games/cc/.gitkeep",
    "content": ""
  },
  {
    "path": "games/chlcc/.gitkeep",
    "content": ""
  },
  {
    "path": "games/rne/.gitkeep",
    "content": ""
  },
  {
    "path": "games/sg0/.gitkeep",
    "content": ""
  },
  {
    "path": "portfiles/avcpp/0002-av_init_packet_deprecation.patch",
    "content": "diff --git a/CMakeLists.txt b/CMakeLists.txt\nindex a5fed05..7875b91 100644\n--- a/CMakeLists.txt\n+++ b/CMakeLists.txt\n@@ -24,6 +24,11 @@ set (AVCPP_WARNING_OPTIONS\n      $<$<CXX_COMPILER_ID:MSVC>:\n        /W4>)\n \n+# fixes vcpkg uwp failures due to /sdl\n+if(MSVC)\n+    add_compile_options(/wd4996)\n+endif()\n+\n # -pthread sets also some useful compile-time flags\n set(THREADS_PREFER_PTHREAD_FLAG ON)\n find_package(Threads)\n"
  },
  {
    "path": "portfiles/avcpp/portfile.cmake",
    "content": "if(VCPKG_TARGET_IS_WINDOWS)\n    # avcpp doesn't export any symbols\n    vcpkg_check_linkage(ONLY_STATIC_LIBRARY)\nendif()\n\nvcpkg_from_github(\n    OUT_SOURCE_PATH SOURCE_PATH\n    REPO h4tr3d/avcpp\n    REF \"6bf8c76b120cf2cc448d706ea22f112017803605\"\n    SHA512 037fc9f0f0307e94ef5c75ae8dd3b069e86e2918fa2a479e9a35a2f76842bf7201002eb91970514f2aa612d72a605e4a86b21703f764b8f1876b5b0b2c86a1ba\n    PATCHES\n        0002-av_init_packet_deprecation.patch\n)\n\nstring(COMPARE EQUAL \"${VCPKG_LIBRARY_LINKAGE}\" \"static\" AVCPP_ENABLE_STATIC)\nstring(COMPARE EQUAL \"${VCPKG_LIBRARY_LINKAGE}\" \"dynamic\" AVCPP_ENABLE_SHARED)\n\nvcpkg_find_acquire_program(PKGCONFIG)\n\nvcpkg_cmake_configure(\n    SOURCE_PATH \"${SOURCE_PATH}\"\n    OPTIONS\n        \"-DAV_ENABLE_STATIC=${AVCPP_ENABLE_STATIC}\"\n        \"-DAV_ENABLE_SHARED=${AVCPP_ENABLE_SHARED}\"\n        \"-DPKG_CONFIG_EXECUTABLE=${PKGCONFIG}\"\n        -DAV_BUILD_EXAMPLES=OFF\n        -DAV_DISABLE_AVDEVICE=ON\n        -DAV_DISABLE_AVFILTER=ON\n)\nvcpkg_cmake_install()\nvcpkg_cmake_config_fixup(CONFIG_PATH \"lib/cmake/${PORT}\")\n\nvcpkg_fixup_pkgconfig()\n\nfile(REMOVE_RECURSE \"${CURRENT_PACKAGES_DIR}/debug/include\")\nfile(REMOVE_RECURSE \"${CURRENT_PACKAGES_DIR}/debug/share\")\n\nfile(READ \"${SOURCE_PATH}/LICENSE.md\" LICENSE_MD)\nvcpkg_install_copyright(FILE_LIST \"${SOURCE_PATH}/LICENSE-bsd.txt\" \"${SOURCE_PATH}/LICENSE-lgpl2.txt\" COMMENT \"${LICENSE_MD}\")\n"
  },
  {
    "path": "portfiles/avcpp/vcpkg.json",
    "content": "{\n  \"name\": \"avcpp\",\n  \"version\": \"3.0.1.0\",\n  \"port-version\": 0,\n  \"description\": \"Wrapper for the FFmpeg that simplify usage it from C++ projects.\",\n  \"homepage\": \"https://github.com/h4tr3d/avcpp\",\n  \"license\": \"LGPL-2.1-only OR BSD-3-Clause\",\n  \"dependencies\": [\n    {\n      \"name\": \"ffmpeg\",\n      \"default-features\": false,\n      \"features\": [\n        \"avcodec\",\n        \"avformat\",\n        \"swresample\",\n        \"swscale\"\n      ]\n    },\n    {\n      \"name\": \"vcpkg-cmake\",\n      \"host\": true\n    },\n    {\n      \"name\": \"vcpkg-cmake-config\",\n      \"host\": true\n    }\n  ]\n}\n"
  },
  {
    "path": "profiles/cc/charset.lua",
    "content": "root.Charset = {\n    Flags = {}\n};\n\nfor i = 0, (64 * 117) - 1 do root.Charset.Flags[i] = 0; end\n\nlocal spaces = {[0]=0, 63};\nfor i = 0, #spaces do\n    root.Charset.Flags[spaces[i]] = root.Charset.Flags[spaces[i]] | CharacterTypeFlags.Space;\nend\n\n-- 、 。 ． ， ？ ！ 〜 ” ー ） 〕 ］ ｝ 〉 》 」 』 】☆ ★ ♪ 々 ぁ ぃ ぅ ぇ\n-- ぉ っ ゃ ゅ ょ ァ ィ ゥ ェ ォ ッ ャ ュ ョ –\nlocal wordEndingPuncts = {\n    [0]=0x00BE, 0x00BF, 0x00C1,\n    0x00C0, 0x00C4, 0x00C5,\n    0x00E4, 0x00CB, 0x00E5,\n    0x00CD, 0x00CF, 0x00D1,\n    0x00D3, 0x00D5, 0x00D7,\n    0x00D9, 0x00DB, 0x00DD,\n    0x01A5, 0x01A6, 0x00E6,\n    0x0187, 0x00E8, 0x00E9,\n    0x00EA, 0x00EB, 0x00EC,\n    0x00ED, 0x00EE, 0x00EF,\n    0x00F0, 0x00F2, 0x00F3,\n    0x00F4, 0x00F5, 0x00F6,\n    0x00F7, 0x00F8, 0x00F9,\n    0x00FA, 0x0113\n};\nfor i = 0, #wordEndingPuncts do\n    root.Charset.Flags[wordEndingPuncts[i]] = root.Charset.Flags[wordEndingPuncts[i]] | CharacterTypeFlags.WordEndingPunct;\nend\n\n-- space(63) space(0) “ （ 〔 ［ ｛ 〈 《 「\nlocal wordStartingPuncts = {\n    [0]=63, 0, 0x00CA,\n    0x00CC, 0x00CE, 0x00D0,\n    0x00D2, 0x00D4, 0x00D6,\n    0x00D8, 0x00DA, 0x00DC\n};\nfor i = 0, #wordStartingPuncts do\n    root.Charset.Flags[wordStartingPuncts[i]] = root.Charset.Flags[wordStartingPuncts[i]] | CharacterTypeFlags.WordStartingPunct;\nend"
  },
  {
    "path": "profiles/cc/config.lua",
    "content": "root.Language = \"Japanese\";\nroot.ResolutionWidth = 1920;\nroot.ResolutionHeight = 1080;\nroot.Fullscreen = false;\nroot.DateFormat = DateFormatType.YMD;"
  },
  {
    "path": "profiles/cc/dialogue.lua",
    "content": "root.Sprites[\"ADVBox\"] = {\n    Sheet = \"MesBox\",\n    Bounds = { X = 0, Y = 1, Width = 1920, Height = 298 }\n};\n\nroot.Sprites[\"ADVBoxMask\"] = {\n    Sheet = \"MesBox\",\n    Bounds = { X = 0, Y = 301, Width = 1920, Height = 298 }\n};\n\nroot.Sprites[\"WaitIconSprite\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 1, Y = 456, Width = 112, Height = 64 },\n};\n\nroot.Dialogue = {\n    REVBounds = { X = 547, Y = 0, Width = 1043, Height = 400 },\n    REVNameFontSize = 48,\n    REVColor = 0,\n    REVNameColor = 61, -- Unsure\n    REVNameOffset = 22,\n    REVNameLocation = REVNameLocationType.LeftTop,\n    REVOutlineMode = 1,\n    REVNameOutlineMode = 1,\n    NVLBounds = { X = 188, Y = 128, Width = 1536, Height = 600 },\n    ADVBounds = { X = 330, Y = 795, Width = 1240, Height = 270 },\n    ADVBoxSprite = \"ADVBox\",\n    ADVBoxMask = \"ADVBoxMask\",\n    ADVBoxEffectDuration = 10,\n    ADVBoxPos = { X = 0, Y = 760 },\n    FadeOutDuration = 0.33,\n    FadeInDuration = 0.33,\n    TextFadeInDuration = 0.33,\n    TextFadeOutDuration = 0.33,\n    DialogueBoxCurrentType = DialogueBoxType.CC,\n    NVLBoxMaxOpacity = 0.55,\n\n    ADVNameAlignment = TextAlignment.Left,\n    ADVNameFontSize = 33,\n    ADVNamePos = { X = 173, Y = 773 },\n\n    NametagCurrentType = NametagType.CC,\n\n    NametagMainSprites = {},\n    NametagLabelSprites = {},\n\n    NametagMainPos = { X = -1, Y = 764 },\n    NametagLabelPos = { X = 0, Y = 988 },\n\n    NametagShowDuration = 16 / 60;\n\n    WaitIconCurrentType = WaitIconType.None,\n    WaitIconSprite = \"WaitIconSprite\",\n    WaitIconOffset = { X = 1578, Y = 940 },\n    AutoIconCurrentType = AutoIconType.SpriteAnim,\n    AutoIconSpriteAnim = \"AutoIconSpriteAnim\",\n    AutoIconOffset = { X = 1570, Y = 696 },\n    SkipIconCurrentType = SkipIconType.SpriteAnim,\n    SkipIconSpriteAnim = \"SkipIconSpriteAnim\",\n    SkipIconOffset = { X = 1712, Y = 702 },\n    DialogueFont = \"Default\",\n    SetFontSizeRatio = 800.0,\n    DefaultFontSize = 48,\n    RubyFontSize = 21,\n    RubyYOffset = -21,\n    ColorTable = {\n        {0xFFFFFF, 0x000000}, {0x5080FF, 0x000000},\n        {0xFF7080, 0x000000}, {0xFFA0F8, 0x000000},\n        {0x46FF80, 0x000000}, {0x90FFFF, 0x000000},\n        {0xFFFF70, 0x000000}, {0x80FFC0, 0x000000},\n        {0xFFB080, 0x000000}, {0xB080FF, 0x000000},\n        {0x000000, 0x808080}, {0x000000, 0x5080FF},\n        {0x000000, 0xFF7080}, {0x000000, 0xFFA0F8},\n        {0x000000, 0x268840}, {0x000000, 0x409999},\n        {0x000000, 0x888830}, {0x000000, 0x80FFC0},\n        {0x000000, 0xFFB080}, {0x000000, 0xB080FF},\n        {0xD0D0D0, 0x000000}, {0xD0D0FF, 0x000000},\n        {0xFFD0D0, 0x000000}, {0xFFD0FF, 0x000000},\n        {0xD0FFD0, 0x000000}, {0xD0FFFF, 0x000000},\n        {0xFFFFD0, 0x000000}, {0xE8FFD0, 0x000000},\n        {0xFFE8D0, 0x000000}, {0xD0E8FF, 0x000000},\n        {0xFFFFFF, 0x808080}, {0xFFFFFF, 0x5080FF},\n        {0xFFFFFF, 0xFF7080}, {0xFFFFFF, 0xFFA0F8},\n        {0xFFFFFF, 0x46FF80}, {0xFFFFFF, 0x90FFFF},\n        {0xFFFFFF, 0xFFFF70}, {0xFFFFFF, 0x80FFC0},\n        {0xFFFFFF, 0xFFB080}, {0xFFFFFF, 0xB080FF},\n        {0xFFEEEE, 0x000000}, {0xFFCCCC, 0x000000},\n        {0xFFAAAA, 0x000000}, {0xFF9999, 0x000000},\n        {0xFF8888, 0x000000}, {0xFFFF00, 0x000000},\n        {0xFEF000, 0x000000}, {0xFF7777, 0x000000},\n        {0xFF6666, 0x000000}, {0xFF5555, 0x000000},\n        {0xFF4444, 0x000000}, {0xFF3333, 0x000000},\n        {0xFF2222, 0x000000}, {0xFF0000, 0x000000},\n        {0xDD0000, 0x000000}, {0xBB0000, 0x000000},\n        {0xB00000, 0x000000}, {0xAA0000, 0x000000},\n        {0x950000, 0x000000}, {0x808080, 0x000000},\n        {0xAAAAAA, 0x000000}, {0xAAC1C9, 0x000000},\n        {0x000000, 0x000000}, {0x000000, 0x000000},\n        {0x000000, 0x000000}, {0x000000, 0x000000},\n        {0x000000, 0x000000}, {0xF80B0B, 0x000000},\n        {0xF8910B, 0x000000}, {0x33F12A, 0x000000}\n    },\n    MaxPageSize = 2000,\n    PageCount = 3,\n    ColorTagIsUint8 = false,\n};\n\nMakeAnimation({\n    Name = \"SkipIconSpriteAnim\",\n    Sheet = \"Data\",\n    FirstFrameX = 0,\n    FirstFrameY = 812,\n    FrameWidth = 160,\n    ColWidth = 160,\n    FrameHeight = 154,\n    RowHeight = 154,\n    Frames = 9,\n    Duration = 0.7,\n    Rows = 1,\n    Columns = 9,\n    PrimaryDirection = AnimationDirections.Right\n});\n\nMakeAnimation({\n    Name = \"AutoIconSpriteAnim\",\n    Sheet = \"Data\",\n    FirstFrameX = 0,\n    FirstFrameY = 966,\n    FrameWidth = 160,\n    ColWidth = 160,\n    FrameHeight = 154,\n    RowHeight = 154,\n    Frames = 9,\n    Duration = 0.7,\n    Rows = 1,\n    Columns = 9,\n    PrimaryDirection = AnimationDirections.Right\n});\n\nlocal nametagMainX = 0;\nlocal nametagMainY = 0;\nlocal nametagLabelX = 3600;\nlocal nametagLabelY = 0;\nlocal nametagMainWidth = 448;\nlocal nametagMainHeight = 218;\nlocal nametagLabelWidth = 240;\nlocal nametagLabelHeight = 40;\n\nfor i = 1, 80 do\n    root.Sprites[\"NametagMainSprite\" .. i] = {\n        Sheet = \"NamePlate\",\n        Bounds = {\n            X = nametagMainX,\n            Y = nametagMainY,\n            Width = nametagMainWidth,\n            Height = nametagMainHeight\n        }\n    };\n    root.Dialogue.NametagMainSprites[#root.Dialogue.NametagMainSprites + 1] = \"NametagMainSprite\" .. i;\n\n    if i % 8 == 0 then\n        nametagMainY = nametagMainY + nametagMainHeight;\n        nametagMainX = 0;\n    else\n        nametagMainX = nametagMainX + nametagMainWidth;\n    end\n\n    root.Sprites[\"NametagLabelSprite\" .. i] = {\n        Sheet = \"NamePlate\",\n        Bounds = {\n            X = nametagLabelX,\n            Y = nametagLabelY,\n            Width = nametagLabelWidth,\n            Height = nametagLabelHeight\n        }\n    };\n    root.Dialogue.NametagLabelSprites[#root.Dialogue.NametagLabelSprites + 1] = \"NametagLabelSprite\" .. i;\n\n    if i % 2 == 0 then\n        nametagLabelX = 3600;\n        nametagLabelY = nametagLabelY + nametagLabelHeight;\n    else\n        nametagLabelX = nametagLabelX + nametagLabelWidth;\n    end\nend\n"
  },
  {
    "path": "profiles/cc/font-lb.lua",
    "content": "root.Fonts = {\n    [\"Default\"] = {\n        Type = FontType.LB,\n        ForegroundSheet = \"FontLBForeground\",\n        OutlineSheet = \"FontLBOutline\",\n        Columns = 64,\n        Rows = 125,\n        AdvanceWidths = \"games/cc/font-lb/widths.bin\",\n        AdvanceWidthsEmWidth = 48,\n        OutlineOffset = { X = -4, Y = -4 },\n        LineSpacing = 14,\n    }\n};\n\nroot.SpriteSheets[\"Font\"] = nil;\n\nroot.SpriteSheets[\"FontLBForeground\"] = {\n    Path = \"games/cc/font-lb/foreground.png\",\n    DesignWidth = 3072,\n    DesignHeight = 6000\n};\n\nroot.SpriteSheets[\"FontLBOutline\"] = {\n    Path = \"games/cc/font-lb/outline.png\",\n    DesignWidth = 3648,\n    DesignHeight = 7128\n};"
  },
  {
    "path": "profiles/cc/font.lua",
    "content": "root.Fonts = {\n    [\"Default\"] = {\n        Type = FontType.Basic,\n        Sheet = \"Font\",\n        Columns = 64,\n        Rows = 117,\n        AdvanceWidths = {},\n        AdvanceWidthsEmWidth = 32,\n        LineSpacing = 22,\n    }\n};\n\nlocal westernAdvanceWidths = {\n    [0]=0x20, 0x18, 0x0D, 0x18, 0x17, 0x1A, 0x16, 0x17, 0x17, 0x18, 0x17, 0x1C,\n        0x19, 0x1A, 0x1A, 0x16, 0x15, 0x1A, 0x1A, 0x08, 0x13, 0x1A, 0x17, 0x1E,\n        0x1A, 0x1C, 0x18, 0x1C, 0x1A, 0x18, 0x1C, 0x1A, 0x1C, 0x20, 0x1C, 0x1A,\n        0x1A, 0x14, 0x17, 0x16, 0x17, 0x16, 0x12, 0x16, 0x15, 0x09, 0x0F, 0x16,\n        0x08, 0x1E, 0x16, 0x16, 0x17, 0x17, 0x11, 0x14, 0x12, 0x16, 0x16, 0x1E,\n        0x16, 0x16, 0x14, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0C, 0x14, 0x08, 0x14,\n        0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,\n        0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x14, 0x11, 0x11, 0x11,\n        0x11, 0x11, 0x11, 0x11, 0x20, 0x15, 0x16, 0x16, 0x17, 0x17, 0x14, 0x11,\n        0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,\n        0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x18, 0x0D, 0x18, 0x17,\n        0x1A, 0x16, 0x17, 0x17, 0x18, 0x17, 0x1C, 0x19, 0x1A, 0x1A, 0x16, 0x15,\n        0x1A, 0x1A, 0x08, 0x13, 0x1A, 0x17, 0x1E, 0x1A, 0x1C, 0x18, 0x1C, 0x1A,\n        0x18, 0x1C, 0x1A, 0x1C, 0x20, 0x1C, 0x1A, 0x1A, 0x14, 0x17, 0x16, 0x17,\n        0x16, 0x12, 0x16, 0x15, 0x09, 0x0F, 0x16, 0x08, 0x1E, 0x16, 0x16, 0x17,\n        0x17, 0x11, 0x14, 0x12, 0x16, 0x16, 0x1E, 0x16, 0x16, 0x14, 0x14, 0x14,\n        0x14, 0x14, 0x09, 0x09, 0x14, 0x0C, 0x0B, 0x0A, 0x08, 0x08, 0x0D, 0x0C,\n        0x0C, 0x0E, 0x0D, 0x0C, 0x0C, 0x0D, 0x0C, 0x0C, 0x0F, 0x0F, 0x11, 0x11,\n        0x0D, 0x0D, 0x0E, 0x10, 0x0C, 0x0C, 0x1A, 0x1A, 0x0C, 0x0C, 0x10, 0x20,\n        0x20, 0x20, 0x1C, 0x20, 0x1A, 0x19, 0x16, 0x1A, 0x19, 0x18, 0x19, 0x19,\n        0x18, 0x19, 0x19, 0x18, 0x18, 0x1A, 0x1A, 0x19, 0x1A, 0x1A, 0x15, 0x16,\n        0x17, 0x1A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0C, 0x20, 0x20, 0x20, 0x20,\n        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20\n};\n\nfor i = 0, (64 * 117) - 1 do\n    if i <= #westernAdvanceWidths then\n        root.Fonts[\"Default\"].AdvanceWidths[i] = westernAdvanceWidths[i];\n    else\n        root.Fonts[\"Default\"].AdvanceWidths[i] = root.Fonts[\"Default\"].AdvanceWidthsEmWidth;\n    end\nend"
  },
  {
    "path": "profiles/cc/game.lua",
    "content": "root.ActiveRenderer = RendererType.OpenGL;\n\nroot.LayerCount = 100;\nroot.GameFeatures = GameFeature.Sc3VirtualMachine | GameFeature.Renderer2D | GameFeature.Input | GameFeature.Audio | GameFeature.Video;\nroot.DesignWidth = 1920;\nroot.DesignHeight = 1080;\n\nroot.WindowName = \"CHAOS;CHILD\";\nroot.WindowIconPath = \"games/cc/icondata/icon.png\";\nroot.CursorArrowPath = \"games/cc/icondata/cursor_arrow.png\";\nroot.CursorPointerPath = \"games/cc/icondata/cursor_pointer.png\";\nroot.UseMoviePriority = true;\nroot.UseWaveEffects = true;\n\nroot.CharaIsMvl = false;\nroot.LayFileBigEndian = false;\nroot.LayFileTexXMultiplier = 1;\nroot.LayFileTexYMultiplier = 1;\n\nroot.ScreenCaptureCount = 2;\n\nroot.Vm = {\n    StartScript = 1,\n    StartScriptBuffer = 0,\n    GameInstructionSet = InstructionSet.CC,\n    UseReturnIds = true,\n\n    ScrWorkChaStructSize = 40,\n    ScrWorkChaOffsetStructSize = 10,\n    ScrWorkBgStructSize = 40,\n    ScrWorkBgOffsetStructSize = 10,\n    ScrWorkCaptureStructSize = 20,\n    ScrWorkCaptureOffsetStructSize = 10,\n    ScrWorkCaptureEffectInfoStructSize = 3,\n    ScrWorkBgEffStructSize = 30,\n    ScrWorkBgEffOffsetStructSize = 20,\n\n    MaxLinkedBgBuffers = 2\n};\n\nroot.PlatformId = 131072;\n\ninclude('common/scriptinput.lua');\ninclude('common/scriptvars.lua');\ninclude('cc/config.lua');\ninclude('cc/scriptvars.lua');\ninclude('cc/savedata.lua');\ninclude('cc/tipssystem.lua');\ninclude('cc/vfs.lua');\ninclude('cc/sprites.lua');\ninclude('common/animation.lua');\ninclude('cc/charset.lua');\n--include('cc/font.lua');\ninclude('cc/font-lb.lua');\ninclude('cc/dialogue.lua');\ninclude('cc/waveeffects.lua');\ninclude('cc/hud/saveicon.lua');\ninclude('cc/hud/loadingdisplay.lua');\ninclude('cc/hud/datedisplay.lua');\ninclude('cc/hud/titlemenu.lua');\n--include('cc/hud/systemmenu.lua');\ninclude('cc/hud/backlogmenu.lua');\ninclude('cc/hud/sysmesboxdisplay.lua');\ninclude('cc/hud/selectiondisplay.lua');\ninclude('cc/hud/tipsmenu.lua');\ninclude('cc/hud/tipsnotification.lua');\n"
  },
  {
    "path": "profiles/cc/hud/backlogmenu.lua",
    "content": "root.BacklogMenu = {\n    Type = BacklogMenuType.CC,\n    DrawType = DrawComponentType.SystemMenu,\n\n    BacklogBackgroundSprite = \"BacklogBackground\",\n    BacklogBackgroundRepeatHeight = 1080,\n\n    BacklogHeaderSprite = \"BacklogHeader\",\n    BacklogHeaderPosition = { X = 0, Y = 0 },\n\n    BacklogControlsSprite = \"BacklogControls\",\n    BacklogControlsPosition = { X = 0, Y = 988 },\n\n    EntryHighlightSprite = \"EntryHighlight\",\n    EntryHighlightLocation = EntryHighlightLocationType.AllLinesLeftOfScreen,\n    EntryHighlightOffset = { X = 0, Y = 0 },\n    EntryHighlightPadding = 16.0,\n\n    VoiceIconSprite = \"VoiceIcon\",\n    VoiceIconOffset = { X = -7, Y = 3 },\n\n    ScrollbarTrackSprite = \"ScrollbarTrack\",\n    ScrollbarThumbSprite = \"ScrollbarThumb\",\n    ScrollbarPosition = { X = 1656, Y = 40 },\n    ScrollbarThumbLength = 87,\n\n    EntriesStart = { X = 547, Y = 149 },\n    RenderingBounds = { X = 0, Y = 121, Width = 1920, Height = 868 },\n    EntryYPadding = 26,\n\n    FadeInDuration = 0.5,\n    FadeOutDuration = 0.5,\n    FadeInDirectDuration = 0.25,\n    FadeOutDirectDuration = 0.25,\n\n    ScrollingSpeed = 900,\n    PageUpDownHeight = 765,\n\n    MenuMask = \"MenuMask\",\n    BacklogMask = \"BacklogMask\",\n    HoverBounds = { X = 380, Y = 145, Width = 1230, Height = 820 }\n};\n\nroot.Sprites[\"BacklogBackground\"] = {\n    Sheet = \"Backlog\",\n    Bounds = { X = 0, Y = 0, Width = 1920, Height = 1100 },\n};\n\nroot.Sprites[\"BacklogHeader\"] = {\n    Sheet = \"Backlog\",\n    Bounds = { X = 962, Y = 1210, Width = 542, Height = 542 },\n};\n\nroot.SpriteSheets[\"BacklogMask\"] = {\n    Path = { Mount = \"system\", Id = 2 },\n    DesignWidth = 1920,\n    DesignHeight = 1080\n};\n\nroot.Sprites[\"BacklogControls\"] = {\n    Sheet = \"Backlog\",\n    Bounds = { X = 0, Y = 1153, Width = 1920, Height = 57 },\n};\n\nroot.Sprites[\"MenuMask\"] = {\n    Sheet = \"MenuChip\",\n    Bounds = { X = 154, Y = 140, Width = 1900, Height = 1061 },\n};\n\nroot.Sprites[\"VoiceIcon\"] = {\n    Sheet = \"Backlog\",\n    Bounds = { X = 1519, Y = 1216, Width = 43, Height = 43 },\n};\n\nroot.Sprites[\"EntryHighlight\"] = {\n    Sheet = \"Backlog\",\n    Bounds = { X = 0, Y = 1101, Width = 1920, Height = 52 }\n};\n\nroot.Sprites[\"ScrollbarThumb\"] = {\n    Sheet = \"Backlog\",\n    Bounds = { X = 2025, Y = 1113, Width = 23, Height = 87 },\n};\n\nroot.Sprites[\"ScrollbarTrack\"] = {\n    Sheet = \"Backlog\",\n    Bounds = { X = 2030, Y = 41, Width = 11, Height = 915 },\n};"
  },
  {
    "path": "profiles/cc/hud/datedisplay.lua",
    "content": "local sheet = \"Data\";\nlocal name = \"Date\";\n\nlocal yearNumFirstX = 2287.5;\nlocal yearNumFirstY = 94.5;\nlocal yearNumWidth = 37.5;\nlocal yearNum1Width = 12;\nlocal yearNumHeight = 30;\n\nlocal numFirstX = 2286;\nlocal numFirstY = 49.5;\nlocal numWidth = 60;\nlocal num1Width = 18;\nlocal numHeight = 42;\n\nlocal weekFirstX = 2677.5;\nlocal weekFirstY = 94.5;\nlocal weekSecondX = 2287.5;\nlocal weekSecondY = 127.5;\nlocal weekWidth = 109.5;\nlocal weekFriWidth = 84;\nlocal weekHeight = 30;\n\nroot.DateDisplay = {\n    Type = DateDisplayType.RNE,\n    YearNumSprites = {},\n    MonthNumSprites = {},\n    DayNumSprites = {},\n    WeekSprites = {},\n    BackgroundStartPos = { X = 1632, Y = 109.5 },\n    BackgroundEndPos = { X = 1632 - 384, Y = 109.5 },\n    DateStartX = 1750.5,\n    YearWeekY = 90,\n    MonthDayY = 78,\n    Spacing = 1.5,\n    FadeInDuration = 0.5,\n    FadeOutDuration = 0.5\n};\n\nfor i = 0, 9 do\n    local yearW;\n    if i == 1 then\n        yearW = yearNum1Width;\n    else\n        yearW = yearNumWidth;\n    end\n\n    root.Sprites[name .. \"YearNum\" .. i] = {\n        Sheet = sheet,\n        Bounds = {\n            X = yearNumFirstX,\n            Y = yearNumFirstY,\n            Width = yearW,\n            Height = yearNumHeight\n        }\n    };\n    root.DateDisplay.YearNumSprites[#root.DateDisplay.YearNumSprites + 1] = name .. \"YearNum\" .. i;\n\n    local numW\n    if i == 1 then\n        numW = num1Width;\n    else\n        numW = numWidth;\n    end\n\n    root.Sprites[name .. \"Num\" .. i] = {\n        Sheet = sheet,\n        Bounds = {\n            X = numFirstX,\n            Y = numFirstY,\n            Width = numW,\n            Height = numHeight\n        }\n    };\n    root.DateDisplay.MonthNumSprites[#root.DateDisplay.MonthNumSprites + 1] = name .. \"Num\" .. i;\n    root.DateDisplay.DayNumSprites[#root.DateDisplay.DayNumSprites + 1] = name .. \"Num\" .. i;\n\n    if i == 1 then\n        yearNumFirstX = yearNumFirstX + yearNum1Width;\n        numFirstX = numFirstX + num1Width;\n    else\n        yearNumFirstX = yearNumFirstX + yearNumWidth;\n        numFirstX = numFirstX + numWidth;\n    end\nend\n\nfor i = 0, 6 do\n    if i > 1 then\n        local weekW;\n        if i == 5 then\n            weekW = weekFriWidth;\n        else\n            weekW = weekWidth;\n        end\n\n        root.Sprites[name .. \"Week\" .. i] = {\n            Sheet = sheet,\n            Bounds = {\n                X = weekSecondX,\n                Y = weekSecondY,\n                Width = weekW,\n                Height = weekHeight\n            }\n        };\n        if i == 5 then\n            weekSecondX = weekSecondX + weekFriWidth;\n        else\n            weekSecondX = weekSecondX + weekWidth;\n         end\n    else\n        root.Sprites[name .. \"Week\" .. i] = {\n            Sheet = sheet,\n            Bounds = {\n                X = weekFirstX,\n                Y = weekFirstY,\n                Width = weekWidth,\n                Height = weekHeight\n            }\n        };\n        weekFirstX = weekFirstX + weekWidth;\n    end\n    root.DateDisplay.WeekSprites[#root.DateDisplay.WeekSprites + 1] = name .. \"Week\" .. i;\nend\n\nroot.Sprites[name .. \"MonthDaySeparator\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 2845.5,\n        Y = 49.5,\n        Width = 15,\n        Height = 42\n    }\n};\nroot.DateDisplay.MDSeparatorSprite = name .. \"MonthDaySeparator\";\n\nroot.Sprites[name .. \"DayYearSeparator\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 2637,\n        Y = 94.5,\n        Width = 12,\n        Height = 30\n    }\n};\nroot.DateDisplay.DYSeparatorSprite = name .. \"DayYearSeparator\";\n\nroot.Sprites[name .. \"OpenBracket\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 2649,\n        Y = 94.5,\n        Width = 12,\n        Height = 30\n    }\n};\nroot.DateDisplay.OpenBracketSprite = name .. \"OpenBracket\";\n\nroot.Sprites[name .. \"CloseBracket\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 2664,\n        Y = 94.5,\n        Width = 12,\n        Height = 30\n    }\n};\nroot.DateDisplay.CloseBracketSprite = name .. \"CloseBracket\";\n\nroot.Sprites[name .. \"Background\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 2287.5,\n        Y = 2,\n        Width = 675,\n        Height = 42\n    }\n};\nroot.DateDisplay.BackgroundSprite = name .. \"Background\";"
  },
  {
    "path": "profiles/cc/hud/loadingdisplay.lua",
    "content": "root.LoadingDisplay = {\n    ResourceLoadBgAnim = \"ResourceLoadingBg\",\n    SaveLoadBgAnim = \"SaveLoadingBg\",\n    LoadingIconAnim = \"LoadingDisc\",\n    LoadingTextAnim = \"LoadingText\",\n    ResourceBgPos = { X = 1074, Y = 611 },\n    SaveBgPos = { X = 1019, Y = 598 },\n    IconPos = { X = 986, Y = 608 },\n    TextPos = { X = 1025, Y = 628 },\n    FadeInDuration = 0.66,\n    FadeOutDuration = 0.33\n};\n\nMakeAnimation({\n    Name = \"ResourceLoadingBg\",\n    Sheet = \"Data\",\n    FirstFrameX = 555,\n    FirstFrameY = 544,\n    FrameWidth = 206,\n    ColWidth = 206,\n    FrameHeight = 58,\n    RowHeight = 60,\n    Frames = 8,\n    Duration = 0.8,\n    Rows = 8,\n    Columns = 1,\n    PrimaryDirection = AnimationDirections.Down\n});\n\nMakeAnimation({\n    Name = \"SaveLoadingBg\",\n    Sheet = \"Data\",\n    FirstFrameX = 388,\n    FirstFrameY = 0,\n    FrameWidth = 277,\n    ColWidth = 279,\n    FrameHeight = 81,\n    RowHeight = 83,\n    Frames = 4,\n    Duration = 0.8,\n    Rows = 2,\n    Columns = 2,\n    PrimaryDirection = AnimationDirections.Down,\n    SecondaryDirection = AnimationDirections.Right\n});\n\nMakeAnimation({\n    Name = \"LoadingDisc\",\n    Sheet = \"Data\",\n    FirstFrameX = 172,\n    FirstFrameY = 1,\n    FrameWidth = 60,\n    ColWidth = 62,\n    FrameHeight = 60,\n    RowHeight = 62,\n    Frames = 3,\n    Duration = 0.8,\n    Rows = 1,\n    Columns = 3,\n    PrimaryDirection = AnimationDirections.Right\n});\nroot.Animations[\"LoadingDisc\"].Frames[#root.Animations[\"LoadingDisc\"].Frames + 1] = \"LoadingDisc1\";\n\nMakeAnimation({\n    Name = \"LoadingText\",\n    Sheet = \"Data\",\n    FirstFrameX = 173,\n    FirstFrameY = 91,\n    FrameWidth = 214,\n    ColWidth = 214,\n    FrameHeight = 21,\n    RowHeight = 23,\n    Frames = 5,\n    Duration = 0.8,\n    Rows = 5,\n    Columns = 1,\n    PrimaryDirection = AnimationDirections.Down\n});"
  },
  {
    "path": "profiles/cc/hud/saveicon.lua",
    "content": "root.SaveIcon = {\n    ForegroundAnimation = \"SaveIcon\",\n    DefaultPosition = { X = 92, Y = 713 },\n    BackgroundSprite = \"SaveIconBg\",\n    BackgroundOffset = { X = 0, Y = 0 },\n    BackgroundMaxAlpha = 0.0,\n    FadeInDuration = 0.5,\n    FadeOutDuration = 0.25\n};\n\nMakeAnimation({\n    Name = \"SaveIcon\",\n    Sheet = \"Data\",\n    FirstFrameX = 115,\n    FirstFrameY = 456,\n    FrameWidth = 258,\n    ColWidth = 258,\n    FrameHeight = 178,\n    RowHeight = 178,\n    Frames = 2,\n    Duration = 0.4,\n    Rows = 2,\n    Columns = 1,\n    PrimaryDirection = AnimationDirections.Down\n});\n\nroot.Sprites[\"SaveIconBg\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 630, Y = 479, Width = 179, Height = 62 }\n};"
  },
  {
    "path": "profiles/cc/hud/selectiondisplay.lua",
    "content": "root.SelectionDisplay = {\n    DrawType = DrawComponentType.Text,\n    SelectionBackgroundSprite = \"SelectionBackground\",\n    PlainSelectionFrameTopLeftSprite = \"PlainSelectionFrameTopLeft\",\n    PlainSelectionFrameTopSideSprite = \"PlainSelectionFrameTopSide\",\n    PlainSelectionFrameTopRightSprite = \"PlainSelectionFrameTopRight\",\n    PlainSelectionFrameLeftSideSprite = \"PlainSelectionFrameLeftSide\",\n    PlainSelectionFrameBottomLeftSprite = \"PlainSelectionFrameBottomLeft\",\n    PlainSelectionFrameRightSideSprite = \"PlainSelectionFrameRightSide\",\n    PlainSelectionFrameBottomRightSprite = \"PlainSelectionFrameBottomRight\",\n    PlainSelectionFrameBottomSideSprite = \"PlainSelectionFrameBottomSide\",\n    PlainSelectionFrameMiddleSprite = \"PlainSelectionFrameMiddle\",\n    SelectionHighlightSprite = \"SelectionHighlight\",\n    SelectionMaxCount = 5,\n    SelectionBackgroundX = 228,\n    SelectionBackgroundY = {277, 277, 196, 129, 95},\n    SelectionYSpacing = 81,\n    PlainSelectionYSpacing = 10,\n    FadeAnimationDurationInOut = 0.2\n};\n\nroot.Sprites[\"SelectionBackground\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 1, Y = 535, Width = 829, Height = 69 }\n};\n\nroot.Sprites[\"PlainSelectionFrameTopLeft\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 0, Y = 0, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameTopSide\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 16, Y = 0, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameTopRight\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 32, Y = 0, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameLeftSide\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 0, Y = 16, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameBottomLeft\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 0, Y = 32, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameRightSide\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 32, Y = 16, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameBottomRight\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 32, Y = 32, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameBottomSide\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 16, Y = 32, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameMiddle\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 16, Y = 16, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"SelectionHighlight\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 65, Y = 33, Width = 94, Height = 30 }\n};\n"
  },
  {
    "path": "profiles/cc/hud/sysmesboxdisplay.lua",
    "content": "local sheet = \"SysMesBox\";\nlocal name = \"SysMesBox\";\n\nroot.SysMesBoxDisplay = {\n    Type = SysMesBoxType.CC,\n    DrawType = DrawComponentType.SystemMessage,\n    SumoSealSprites = {},\n    SumoSealCenterPosX = {\n        1175, 1269, 542, 1386, 970, 813, 725, 997\n    },\n    SumoSealCenterPosY = {\n        612, 454, 520, 606, 625, 425, 584, 461\n    },\n    ButtonYesCenterPosX = 1098,\n    ButtonYesCenterPosY = 846,\n    ButtonNoCenterPosX = 1211,\n    ButtonNoCenterPosY = 736,\n    ButtonOKCenterPosX = 1170,\n    ButtonOKCenterPosY = 824,\n    TextFontSize = 32,\n    TextMiddleY = 1096,\n    TextX = 1920,\n    TextLineHeight = 32,\n    TextMarginY = 48,\n    SpriteMargin = 3,\n    AnimationSpeed = 25,\n    AnimationProgressWidgetsStartOffset = 9,\n    WidgetsAlphaMultiplier = 0.0625,\n    ButtonYesAnimationProgressEnd = 12,\n    ButtonNoDisplayStart = 4,\n    ButtonNoAnimationProgressOffset = 13,\n    ButtonYesNoScaleMultiplier = 0.0416,\n    ButtonYesNoAlphaDivider = 12,\n    ButtonOKScaleMultiplier = 0.03125,\n    ButtonScaleMax = 1.5,\n    FadeInDuration = 0.4,\n    FadeOutDuration = 0.4,\n\n    ButtonYesHoverBounds = {\n        X = 1128,\n        Y = 847,\n        Width = 141,\n        Height = 103\n    },\n    ButtonNoHoverBounds = {\n        X = 1224,\n        Y = 744,\n        Width = 156,\n        Height = 121\n    },\n    ButtonOkHoverBounds = {\n        X = 0,\n        Y = 0,\n        Width = 0,\n        Height = 0\n    }\n};\n\nroot.Sprites[name .. \"ButtonYes\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 640,\n        Y = 1229,\n        Width = 232,\n        Height = 298\n    }\n};\nroot.SysMesBoxDisplay.ButtonYes = name .. \"ButtonYes\";\n\nroot.Sprites[name .. \"ButtonNo\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 1109,\n        Y = 1124,\n        Width = 244,\n        Height = 200\n    }\n};\nroot.SysMesBoxDisplay.ButtonNo = name .. \"ButtonNo\";\n\nroot.Sprites[name .. \"ButtonOK\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 0,\n        Y = 1229,\n        Width = 318,\n        Height = 298\n    }\n};\nroot.SysMesBoxDisplay.ButtonOK = name .. \"ButtonOK\";\n\nroot.Sprites[name .. \"ButtonYesHighlighted\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 874,\n        Y = 1229,\n        Width = 232,\n        Height = 298\n    }\n};\nroot.SysMesBoxDisplay.ButtonYesHighlighted = name .. \"ButtonYesHighlighted\";\n\nroot.Sprites[name .. \"ButtonNoHighlighted\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 1109,\n        Y = 1326,\n        Width = 244,\n        Height = 200\n    }\n};\nroot.SysMesBoxDisplay.ButtonNoHighlighted = name .. \"ButtonNoHighlighted\";\n\nroot.Sprites[name .. \"ButtonOKHighlighted\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 320,\n        Y = 1229,\n        Width = 318,\n        Height = 298\n    }\n};\nroot.SysMesBoxDisplay.ButtonOKHighlighted = name .. \"ButtonOKHighlighted\";\n\nlocal sealX = 0;\nlocal sealY = 0;\n\nfor i = 0, 7 do\n    root.Sprites[name .. \"SumoSeal\" .. i] = {\n        Sheet = sheet,\n        Bounds = {\n            X = sealX,\n            Y = sealY,\n            Width = 478,\n            Height = 538\n        }\n    };\n\n    sealX = sealX + 480;\n    if sealX == 1920 then\n        sealX = 0;\n        sealY = sealY + 540;\n    end\n\n    root.SysMesBoxDisplay.SumoSealSprites[#root.SysMesBoxDisplay.SumoSealSprites + 1] = name .. \"SumoSeal\" .. i;\nend"
  },
  {
    "path": "profiles/cc/hud/systemmenu.lua",
    "content": "root.SystemMenu = {\n    Type = SystemMenuType.RNE,\n    DrawType = DrawComponentType.SystemMenu,\n    Background = {\n        DurationIn = 0.75,\n        DurationOut = 1.0,\n        Sprite = \"SystemMenuBackground\",\n        Seed = 0,\n        Rows = 7,\n        Columns = 12,\n        CenterY = 0.7,\n        VanishingPointX = 0.66,\n        Depth = 2,\n        MaxAngle = 3.141592653 / 2  -- pi / 2\n    },\n    ButtonBackgroundSprite = \"SystemMenuButtonBackground\",\n    SkyBackgroundSprite = \"SystemMenuSkyBackground\",\n    SkyArrowSprite = \"SystemMenuSkyArrow\",\n    SkyTextSprite = \"SystemMenuSkyText\",\n    ButtonPromptsSprite = \"SystemMenuButtonPrompts\",\n    SkyBackgroundBeginX = -80,\n    SkyBackgroundY = 0,\n    SkyTextBeginX = 287,\n    SkyTextY = 69,\n    ButtonBackgroundStartX = 1257,\n    ButtonBackgroundX = 0,\n    ButtonBackgroundY = 681,\n    ButtonBackgroundTargetWidth = 943,\n    ButtonBackgroundSprStartX = 1499,\n    MenuEntriesSprites = {},\n    MenuEntriesHighlightedSprites = {},\n    MenuEntriesX = 0,\n    MenuEntriesXSkew = 20,\n    MenuEntriesXOffset = 100,\n    MenuEntriesFirstY = 220,\n    MenuEntriesYPadding = 50,\n    MenuEntriesTargetWidth = 417,\n    SkyInStartProgress = 0.285,\n    SkyOutStartProgress = 0.715,\n    SkyMoveDurationIn = 0.415,\n    SkyMoveDurationOut = 0.415,\n    EntriesMoveDurationIn = 0.4,\n    EntriesMoveDurationOut = 0.4,\n    HighlightDurationIn = 0.15,\n    HighlightDurationOut = 0.15\n};\n\nfor i = 0, 7 do\n    root.Sprites[\"SystemMenuEntry\" .. i] = {\n        Sheet = \"Data\",\n        Bounds = {\n            X = 964,\n            Y = 296 + i * 38,\n            Width = root.SystemMenu.MenuEntriesTargetWidth,\n            Height = 26\n        },\n        BaseScale = { X = 1280 / 960, Y = 720 / 544 }\n    };\n    root.SystemMenu.MenuEntriesSprites[#root.SystemMenu.MenuEntriesSprites + 1] = \"SystemMenuEntry\" .. i;\nend\n\nfor i = 0, 7 do\n    root.Sprites[\"SystemMenuEntryHighlighted\" .. i] = {\n        Sheet = \"Data\",\n        Bounds = {\n            X = 964,\n            Y = 2 + i * 38,\n            Width = root.SystemMenu.MenuEntriesTargetWidth,\n            Height = 26\n        },\n        BaseScale = { X = 1280 / 960, Y = 720 / 544 }\n    };\n    root.SystemMenu.MenuEntriesHighlightedSprites[#root.SystemMenu.MenuEntriesHighlightedSprites + 1] = \"SystemMenuEntryHighlighted\" .. i;\nend\n\nroot.Sprites[\"SystemMenuBackground\"] = {\n    Sheet = \"Menu\",\n    Bounds = { X = 0, Y = 1088, Width = 1920, Height = 1080 },\n};\n\nroot.Sprites[\"SystemMenuButtonBackground\"] = {\n    Sheet = \"Menu\",\n    Bounds = { X = root.SystemMenu.ButtonBackgroundSprStartX, Y = 975, Width = 0, Height = 30 },\n    BaseScale = { X = 1280 / 960, Y = 720 / 544 }\n};\n\nroot.Sprites[\"SystemMenuSkyBackground\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 1383, Y = 534, Width = 418, Height = 90 },\n    BaseScale = { X = 1280 / 960, Y = 720 / 544 }\n};\n\nroot.Sprites[\"SystemMenuSkyArrow\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 1802, Y = 534, Width = 70, Height = 90 },\n    BaseScale = { X = 1280 / 960, Y = 720 / 544 }\n};\n\nroot.Sprites[\"SystemMenuSkyText\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 1875, Y = 587, Width = 119, Height = 30 },\n    BaseScale = { X = 1280 / 960, Y = 720 / 544 }\n};\n\nroot.Sprites[\"SystemMenuButtonPrompts\"] = {\n    Sheet = \"Menu\",\n    Bounds = { X = 1231, Y = 528, Width = 400, Height = 21 },\n    BaseScale = { X = 1280 / 960, Y = 720 / 544 }\n};"
  },
  {
    "path": "profiles/cc/hud/tipsmenu.lua",
    "content": "root.TipsMenu = {\n    Type = TipsMenuType.None\n};\n"
  },
  {
    "path": "profiles/cc/hud/tipsnotification.lua",
    "content": "root.TipsNotification = {\n    Type = TipsNotificationType.None,\n};\n"
  },
  {
    "path": "profiles/cc/hud/titlemenu.lua",
    "content": "root.TitleMenu = {\n    Type = TitleMenuType.CC,\n    DrawType = DrawComponentType.SystemMenu,\n    PressToStartX = 816,\n    PressToStartY = 738,\n    PressToStartAnimDurationIn = 0.7,\n    PressToStartAnimDurationOut = 0.7,\n    PressToStartAnimFastDurationIn = 0.1,\n    PressToStartAnimFastDurationOut = 0.1,\n    BackgroundX = 0,\n    BackgroundY = 0,\n    BackgroundBoundsX = 580,\n    BackgroundBoundsYNormal = 1,\n    BackgroundBoundsYTrue = 1535,\n    BackgroundBoundsWidth = 1920,\n    BackgroundBoundsHeight = 318,\n    FenceX = 0,\n    FenceY = 288,\n    FenceBoundsX = 1536,\n    FenceBoundsYNormal = 1,\n    FenceBoundsYTrue = 865,\n    FenceBoundsWidth = 1920,\n    FenceBoundsHeight = 862,\n    CopyrightX = 640,\n    CopyrightY = 947,\n    CopyrightXMoveOffset = 1536,\n    SmokeX = 0,\n    SmokeY = 580,\n    SmokeBoundsX = 20,\n    SmokeBoundsY = 1800,\n    SmokeBoundsWidth = 1920,\n    SmokeBoundsHeight = 500,\n    SmokeAnimationBoundsXOffset = 20,\n    SmokeAnimationBoundsXMax = 1919,\n    SmokeOpacityNormal = 0.25,\n    SmokeAnimationDurationIn = 32,\n    SmokeAnimationDurationOut = 32,\n    MoveLeftAnimationDurationIn = 0.7,\n    MoveLeftAnimationDurationOut = 0.7,\n    MenuSprite = \"TitleMenuMenu\",\n    MenuX = 27,\n    MenuY = 26,\n    PressToStartSprite = \"TitleMenuPressToStart\",\n    BackgroundSprite = \"TitleMenuBackground\",\n    FenceSprite = \"TitleMenuFence\",\n    CopyrightSprite = \"TitleMenuCopyright\",\n    OverlaySprite = \"TitleMenuOverlay\",\n    SmokeSprite = \"TitleMenuSmoke\",\n    ItemHighlightSprite = \"TitleMenuItemHighlight\",\n    ItemHighlightOffsetX = 174,\n    ItemHighlightOffsetY = 7,\n    ItemHighlightPointerSprite = \"TitleMenuPointerItemHighlight\",\n    ItemHighlightPointerY = 89,\n    ItemPadding = 56,\n    ItemYBase = 335,\n    SecondaryFirstItemHighlightOffsetX = 225,\n    SecondarySecondItemHighlightOffsetX = 370,\n    SecondaryThirdItemHighlightOffsetX = 590,\n    LoadSprite = \"TitleMenuLoad\",\n    LoadHighlightSprite = \"TitleMenuLoadHighlight\",\n    QuickLoadSprite = \"TitleMenuQLoad\",\n    QuickLoadHighlightSprite = \"TitleMenuQLoadHighlight\",\n    TipsSprite = \"TitleMenuTips\",\n    TipsHighlightSprite = \"TitleMenuTipsHighlight\",\n    LibrarySprite = \"TitleMenuLibrary\",\n    LibraryHighlightSprite = \"TitleMenuLibraryHighlight\",\n    EndingListSprite = \"TitleMenuEndingList\",\n    EndingListHighlightSprite = \"TitleMenuEndingListHighlight\",\n    MenuEntriesSprites = {},\n    MenuEntriesHighlightedSprites = {},\n    MenuEntriesNum = 5\n};\n\nfor i = 0, 4 do\n    root.Sprites[\"TitleMenuEntry\" .. i] = {\n        Sheet = \"TitleChip\",\n        Bounds = {\n            X = 4,\n            Y = 324 + i * root.TitleMenu.ItemPadding,\n            Width = 220,\n            Height = 30\n        }\n    };\n    root.TitleMenu.MenuEntriesSprites[#root.TitleMenu.MenuEntriesSprites + 1] = \"TitleMenuEntry\" .. i;\nend\n\nfor i = 0, 4 do\n    root.Sprites[\"TitleMenuEntry\" .. i] = {\n        Sheet = \"TitleChip\",\n        Bounds = {\n            X = 4,\n            Y = 324 + i * root.TitleMenu.ItemPadding,\n            Width = 220,\n            Height = 30\n        }\n    };\n    root.TitleMenu.MenuEntriesHighlightedSprites[#root.TitleMenu.MenuEntriesHighlightedSprites + 1] = \"TitleMenuEntry\" .. i;\nend\n\nroot.Sprites[\"TitleMenuItemHighlight\"] = {\n    Sheet = \"TitleChip\",\n    Bounds = { X = 896, Y = 64, Width = 638, Height = 50 },\n};\n\nroot.Sprites[\"TitleMenuLoad\"] = {\n    Sheet = \"TitleChip\",\n    Bounds = { X = 417, Y = 288, Width = 114, Height = 39 },\n};\n\nroot.Sprites[\"TitleMenuLoadHighlight\"] = {\n    Sheet = \"TitleChip\",\n    Bounds = { X = 414, Y = 342, Width = 116, Height = 39 },\n};\n\nroot.Sprites[\"TitleMenuQLoad\"] = {\n    Sheet = \"TitleChip\",\n    Bounds = { X = 553, Y = 288, Width = 245, Height = 39 },\n};\n\nroot.Sprites[\"TitleMenuQLoadHighlight\"] = {\n    Sheet = \"TitleChip\",\n    Bounds = { X = 550, Y = 342, Width = 249, Height = 39 },\n};\n\nroot.Sprites[\"TitleMenuTips\"] = {\n    Sheet = \"TitleChip\",\n    Bounds = { X = 416, Y = 402, Width = 95, Height = 34 },\n};\n\nroot.Sprites[\"TitleMenuTipsHighlight\"] = {\n    Sheet = \"TitleChip\",\n    Bounds = { X = 417, Y = 457, Width = 96, Height = 36 },\n};\n\nroot.Sprites[\"TitleMenuLibrary\"] = {\n    Sheet = \"TitleChip\",\n    Bounds = { X = 535, Y = 402, Width = 178, Height = 34 },\n};\n\nroot.Sprites[\"TitleMenuLibraryHighlight\"] = {\n    Sheet = \"TitleChip\",\n    Bounds = { X = 535, Y = 457, Width = 180, Height = 36 },\n};\n\nroot.Sprites[\"TitleMenuEndingList\"] = {\n    Sheet = \"TitleChip\",\n    Bounds = { X = 736, Y = 401, Width = 258, Height = 35 },\n};\n\nroot.Sprites[\"TitleMenuEndingListHighlight\"] = {\n    Sheet = \"TitleChip\",\n    Bounds = { X = 736, Y = 457, Width = 260, Height = 36 },\n};\n\nroot.Sprites[\"TitleMenuPointerItemHighlight\"] = {\n    Sheet = \"TitleChip\",\n    Bounds = { X = 805, Y = 78, Width = 56, Height = 36 },\n};\n\nroot.Sprites[\"TitleMenuPressToStart\"] = {\n    Sheet = \"TitleChip\",\n    Bounds = { X = 1508, Y = 689, Width = 304, Height = 94 },\n};\n\nroot.Sprites[\"TitleMenuFence\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 1536, Y = 1, Width = 1920, Height = 862 },\n};\n\nroot.Sprites[\"TitleMenuCopyright\"] = {\n    Sheet = \"TitleChip\",\n    Bounds = { X = 1, Y = 667, Width = 860, Height = 20 },\n};\n\nroot.Sprites[\"TitleMenuMenu\"] = {\n    Sheet = \"TitleChip\",\n    Bounds = { X = 971, Y = 705, Width = 521, Height = 236 },\n};\n\nroot.Sprites[\"TitleMenuBackground\"] = {\n    Sheet = \"TitleChip\",\n    Bounds = { X = 580, Y = 1, Width = 1920, Height = 318 },\n};\n\nroot.Sprites[\"TitleMenuOverlay\"] = {\n    Sheet = \"MenuChip\",\n    Bounds = { X = 155, Y = 142, Width = 1898, Height = 1058 },\n};\n\nroot.Sprites[\"TitleMenuSmoke\"] = {\n    Sheet = \"MenuChip\",\n    Bounds = { X = 0, Y = 1900, Width = 2000, Height = 410 },\n};"
  },
  {
    "path": "profiles/cc/savedata.lua",
    "content": "root.SaveData = {\n    Type = SaveDataType.None,\n    SaveFilePath = \"games/cc/savedata/SYSTEM.DAT\",\n};"
  },
  {
    "path": "profiles/cc/scriptvars.lua",
    "content": "local sv = root.ScriptVars;\n\nsv.SW_TITLECUR1 = 2139;\nsv.SW_TITLECUR2 = 2140; --Actual guess\nsv.SW_BG1POSX_OFS = 2400;\nsv.SW_BG1POSY_OFS = 2401;\nsv.SW_BG1SX_OFS = 2402;\nsv.SW_BG1SY_OFS = 2403;\nsv.SW_BG1SIZE_OFS = 2404;\nsv.SW_BG1LX_OFS = 2405;\nsv.SW_BG1LY_OFS = 2406;\nsv.SW_BG1ROTZ_OFS = 2407;\nsv.SW_BG1ALPHA_OFS = 2408;\nsv.SW_MASK1COLOR = 4400;\nsv.SW_MASK1ALPHA = 4401;\nsv.SW_MASK1PRI = 4402;\nsv.SW_MASK1POSX = 4403;\nsv.SW_MASK1POSY = 4404;\nsv.SW_MASK1SIZEX = 4405;\nsv.SW_MASK1SIZEY = 4406;\nsv.SW_MESMODE0 = 4363;\n\nsv.SF_SYSTEMMENUCAPTURE=1378;"
  },
  {
    "path": "profiles/cc/sprites.lua",
    "content": "root.SpriteSheets = {\n    [\"Data\"] = {\n        Path = { Mount = \"system\", Id = 5 },\n        DesignWidth = 3072,\n        DesignHeight = 1536\n    },\n  \t[\"MesBox\"] = {\n        Path = { Mount = \"system\", Id = 20 },\n        DesignWidth = 1920,\n        DesignHeight = 600\n    },\n    [\"Font\"] = {\n        Path = { Mount = \"system\", Id = 8 },\n        DesignWidth = 3072,\n        DesignHeight = 6000\n    },\n    [\"Menu\"] = {\n        Path = { Mount = \"system\", Id = 8 },\n        DesignWidth = 2048,\n        DesignHeight = 1024\n    },\n    [\"Title\"] = {\n        Path = { Mount = \"system\", Id = 29 },\n        DesignWidth = 3456,\n        DesignHeight = 1728\n    },\n    [\"TitleChip\"] = {\n        Path = { Mount = \"system\", Id = 30 },\n        DesignWidth = 2528,\n        DesignHeight = 2048\n    },\n    [\"MenuChip\"] = {\n        Path = { Mount = \"system\", Id = 19 },\n        DesignWidth = 4096,\n        DesignHeight = 3072\n    },\n    [\"SysMesBox\"] = {\n        Path = { Mount = \"system\", Id = 26 },\n        DesignWidth = 2048,\n        DesignHeight = 1528\n    },\n    [\"Backlog\"] = {\n        Path = { Mount = \"system\", Id = 1 },\n        DesignWidth = 2048,\n        DesignHeight = 2048\n    },\n    [\"NamePlate\"] = {\n        Path = { Mount = \"system\", Id = 23 },\n        DesignWidth = 4096,\n        DesignHeight = 2268\n    }\n};\n\nroot.Sprites = {};"
  },
  {
    "path": "profiles/cc/tipssystem.lua",
    "content": "root.TipsSystem = {\n    Type = TipsSystemType.None,\n};"
  },
  {
    "path": "profiles/cc/vfs.lua",
    "content": "root.Vfs = {\n    Mounts = {\n        [\"script\"] = {\"games/cc/gamedata/script.cls\"},\n        [\"system\"] = {\"games/cc/gamedata/system.mpk\"},\n        [\"bgm\"] = {\"games/cc/gamedata/bgm.mpk\"},\n        [\"se\"] = {\"games/cc/gamedata/se.mpk\"},\n        [\"sysse\"] = {\"games/cc/gamedata/sysse.mpk\"},\n        [\"voice\"] = {\"games/cc/gamedata/voice.mpk\"},\n        [\"bg\"] = {\"games/cc/gamedata/bg.mpk\"},\n        [\"chara\"] = {\"games/cc/gamedata/chara.mpk\"},\n        [\"mask\"] = {\"games/cc/gamedata/mask.mpk\"},\n        [\"movie\"] = {\"games/cc/gamedata/movie.cls\"}\n    }\n};"
  },
  {
    "path": "profiles/cc/waveeffects.lua",
    "content": "root.WaveEffects = {\n    WaveMaxCount = 20;\n    BGWaveGridSize = { X = 160, Y = 90 }\n};\n"
  },
  {
    "path": "profiles/cclcc/bgeff.lua",
    "content": "root.BgEffData = {\n    -- bgId, eff1Shader, eff2Shader, eff3Shader, effChaShader\n    BgEffShaderData = {\n        {0, ShaderProgramType.Sprite, ShaderProgramType.Sprite, ShaderProgramType.Sprite, ShaderProgramType.ColorDodgeMaskedSprite},\n        {1, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.Sprite, ShaderProgramType.OverlayMaskedSprite},\n        {2, ShaderProgramType.OverlayMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {3, ShaderProgramType.OverlayMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.Sprite, ShaderProgramType.HardLightMaskedSprite},\n        {4, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.OverlayMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.HardLightMaskedSprite},\n        {6, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.OverlayMaskedSprite},\n        {7, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.OverlayMaskedSprite},\n        {8, ShaderProgramType.HardLightMaskedSprite, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {9, ShaderProgramType.HardLightMaskedSprite, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {10, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {11, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {12, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.SoftLightMaskedSprite, ShaderProgramType.OverlayMaskedSprite, ShaderProgramType.OverlayMaskedSprite},\n        {13, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {14, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {15, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.OverlayMaskedSprite},\n        {16, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.OverlayMaskedSprite},\n        {17, ShaderProgramType.ColorBurnMaskedSprite, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {18, ShaderProgramType.ColorBurnMaskedSprite, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {19, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {20, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {21, ShaderProgramType.OverlayMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {22, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {23, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {24, ShaderProgramType.OverlayMaskedSprite, ShaderProgramType.SoftLightMaskedSprite, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.ColorMaskedSprite},\n        {25, ShaderProgramType.Sprite, ShaderProgramType.Sprite, ShaderProgramType.Sprite, ShaderProgramType.ColorDodgeMaskedSprite},\n        {26, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.Sprite, ShaderProgramType.SoftLightMaskedSprite},\n        {27, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.HardLightMaskedSprite, ShaderProgramType.ColorMaskedSprite},\n        {28, ShaderProgramType.Sprite, ShaderProgramType.Sprite, ShaderProgramType.Sprite, ShaderProgramType.AdditiveMaskedSprite},\n        {29, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.Sprite, ShaderProgramType.SoftLightMaskedSprite},\n        {30, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.ColorBurnMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {31, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.Sprite, ShaderProgramType.AdditiveMaskedSprite},\n        {32, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.Sprite, ShaderProgramType.AdditiveMaskedSprite},\n        {33, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.Sprite, ShaderProgramType.AdditiveMaskedSprite},\n        {34, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {35, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {36, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.SoftLightMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.LinearBurnMaskedSprite},\n        {37, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.OverlayMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {38, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.OverlayMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {39, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.SoftLightMaskedSprite, ShaderProgramType.OverlayMaskedSprite, ShaderProgramType.OverlayMaskedSprite},\n        {40, ShaderProgramType.MaskedSpriteNoAlpha, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {41, ShaderProgramType.MaskedSpriteNoAlpha, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {42, ShaderProgramType.MaskedSpriteNoAlpha, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {43, ShaderProgramType.MaskedSpriteNoAlpha, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {44, ShaderProgramType.HardLightMaskedSprite, ShaderProgramType.ColorBurnMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {45, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.LinearBurnMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.OverlayMaskedSprite},\n        {46, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.LinearBurnMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.OverlayMaskedSprite},\n        {47, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.LinearBurnMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.OverlayMaskedSprite},\n        {48, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {49, ShaderProgramType.ColorBurnMaskedSprite, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {50, ShaderProgramType.ColorBurnMaskedSprite, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {51, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.SoftLightMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {52, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.Sprite, ShaderProgramType.OverlayMaskedSprite},\n        {53, ShaderProgramType.ColorBurnMaskedSprite, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.ColorMaskedSprite},\n        {54, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.SoftLightMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {55, ShaderProgramType.OverlayMaskedSprite, ShaderProgramType.OverlayMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.LinearBurnMaskedSprite},\n        {56, ShaderProgramType.OverlayMaskedSprite, ShaderProgramType.OverlayMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.LinearBurnMaskedSprite},\n        {57, ShaderProgramType.OverlayMaskedSprite, ShaderProgramType.OverlayMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.LinearBurnMaskedSprite},\n        {58, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.OverlayMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.OverlayMaskedSprite},\n        {59, ShaderProgramType.ColorBurnMaskedSprite, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {60, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.SoftLightMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {61, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.Sprite, ShaderProgramType.OverlayMaskedSprite},\n        {62, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {63, ShaderProgramType.ColorBurnMaskedSprite, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {64, ShaderProgramType.MaskedSpriteNoAlpha, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.HardLightMaskedSprite},\n        {65, ShaderProgramType.OverlayMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {66, ShaderProgramType.LinearBurnMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.Sprite, ShaderProgramType.OverlayMaskedSprite},\n        {67, ShaderProgramType.LinearBurnMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.Sprite, ShaderProgramType.OverlayMaskedSprite},\n        {68, ShaderProgramType.LinearBurnMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.Sprite, ShaderProgramType.OverlayMaskedSprite},\n        {69, ShaderProgramType.LinearBurnMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.Sprite, ShaderProgramType.OverlayMaskedSprite},\n        {70, ShaderProgramType.LinearBurnMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.Sprite, ShaderProgramType.OverlayMaskedSprite},\n        {71, ShaderProgramType.LinearBurnMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.Sprite, ShaderProgramType.OverlayMaskedSprite},\n        {72, ShaderProgramType.LinearBurnMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.Sprite, ShaderProgramType.OverlayMaskedSprite},\n        {74, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {75, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {76, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {77, ShaderProgramType.ColorBurnMaskedSprite, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {78, ShaderProgramType.ColorBurnMaskedSprite, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {79, ShaderProgramType.ColorBurnMaskedSprite, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {80, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.SoftLightMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {81, ShaderProgramType.OverlayMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {82, ShaderProgramType.OverlayMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {83, ShaderProgramType.OverlayMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {87, ShaderProgramType.Sprite, ShaderProgramType.LinearBurnMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.OverlayMaskedSprite},\n        {88, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {89, ShaderProgramType.ColorBurnMaskedSprite, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {90, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.SoftLightMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {91, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {92, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.Sprite, ShaderProgramType.OverlayMaskedSprite},\n        {93, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {94, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.OverlayMaskedSprite, ShaderProgramType.OverlayMaskedSprite, ShaderProgramType.ColorMaskedSprite},\n        {95, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {96, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {97, ShaderProgramType.ColorBurnMaskedSprite, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {98, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.SoftLightMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {99, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.Sprite, ShaderProgramType.ScreenMaskedSprite},\n        {100, ShaderProgramType.ColorBurnMaskedSprite, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.SoftLightMaskedSprite},\n        {101, ShaderProgramType.ColorBurnMaskedSprite, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {102, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.SoftLightMaskedSprite, ShaderProgramType.OverlayMaskedSprite, ShaderProgramType.OverlayMaskedSprite},\n        {103, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ScreenMaskedSprite},\n        {104, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.OverlayMaskedSprite, ShaderProgramType.ScreenMaskedSprite},\n        {105, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.SoftLightMaskedSprite},\n        {106, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {107, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {108, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {109, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.SoftLightMaskedSprite, ShaderProgramType.OverlayMaskedSprite, ShaderProgramType.OverlayMaskedSprite},\n        {110, ShaderProgramType.HardLightMaskedSprite, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {111, ShaderProgramType.HardLightMaskedSprite, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {115, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {118, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {117, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {119, ShaderProgramType.SoftLightMaskedSprite, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.HardLightMaskedSprite},\n        {120, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {121, ShaderProgramType.MaskedSpriteNoAlpha, ShaderProgramType.MaskedSpriteNoAlpha, ShaderProgramType.MaskedSpriteNoAlpha, ShaderProgramType.OverlayMaskedSprite},\n        {122, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {123, ShaderProgramType.OverlayMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.Sprite, ShaderProgramType.OverlayMaskedSprite},\n        {124, ShaderProgramType.SoftLightMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.Sprite, ShaderProgramType.OverlayMaskedSprite},\n        {125, ShaderProgramType.SoftLightMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.Sprite, ShaderProgramType.OverlayMaskedSprite},\n        {127, ShaderProgramType.SoftLightMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.Sprite, ShaderProgramType.OverlayMaskedSprite},\n        {128, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.Sprite, ShaderProgramType.OverlayMaskedSprite},\n        {129, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {140, ShaderProgramType.SoftLightMaskedSprite, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ScreenMaskedSprite},\n        {141, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.SoftLightMaskedSprite},\n        {142, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {143, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ScreenMaskedSprite},\n        {145, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.SoftLightMaskedSprite},\n        {146, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {144, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ScreenMaskedSprite},\n        {147, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {148, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.Sprite, ShaderProgramType.ScreenMaskedSprite},\n        {150, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.Sprite, ShaderProgramType.SoftLightMaskedSprite},\n        {151, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {149, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ScreenMaskedSprite},\n        {152, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.HardLightMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.OverlayMaskedSprite},\n        {153, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.HardLightMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.OverlayMaskedSprite},\n        {154, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.HardLightMaskedSprite, ShaderProgramType.SoftLightMaskedSprite},\n        {155, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {156, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {157, ShaderProgramType.OverlayMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {159, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.Sprite, ShaderProgramType.SoftLightMaskedSprite},\n        {161, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.Sprite, ShaderProgramType.SoftLightMaskedSprite},\n        {162, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.Sprite, ShaderProgramType.SoftLightMaskedSprite},\n        {164, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.OverlayMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {165, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {166, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.Sprite, ShaderProgramType.SoftLightMaskedSprite},\n        {167, ShaderProgramType.MaskedSpriteNoAlpha, ShaderProgramType.MaskedSpriteNoAlpha, ShaderProgramType.Sprite, ShaderProgramType.OverlayMaskedSprite},\n        {168, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.HardLightMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.OverlayMaskedSprite},\n        {169, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {170, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.HardLightMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.OverlayMaskedSprite},\n        {172, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.HardLightMaskedSprite, ShaderProgramType.SoftLightMaskedSprite},\n        {173, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {171, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.HardLightMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.OverlayMaskedSprite},\n        {174, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.HardLightMaskedSprite, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.ColorMaskedSprite},\n        {175, ShaderProgramType.SoftLightMaskedSprite, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.ScreenMaskedSprite},\n        {176, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.HardLightMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.OverlayMaskedSprite},\n        {177, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.SoftLightMaskedSprite},\n        {178, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {179, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.SoftLightMaskedSprite, ShaderProgramType.OverlayMaskedSprite, ShaderProgramType.OverlayMaskedSprite},\n        {180, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.SoftLightMaskedSprite, ShaderProgramType.OverlayMaskedSprite, ShaderProgramType.OverlayMaskedSprite},\n        {181, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.HardLightMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.OverlayMaskedSprite},\n        {183, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.HardLightMaskedSprite, ShaderProgramType.SoftLightMaskedSprite},\n        {184, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {182, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.HardLightMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.OverlayMaskedSprite},\n        {185, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.HardLightMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.OverlayMaskedSprite},\n        {186, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.HardLightMaskedSprite, ShaderProgramType.SoftLightMaskedSprite},\n        {187, ShaderProgramType.HardLightMaskedSprite, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.ColorMaskedSprite},\n        {188, ShaderProgramType.SoftLightMaskedSprite, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.HardLightMaskedSprite, ShaderProgramType.OverlayMaskedSprite},\n        {189, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.HardLightMaskedSprite, ShaderProgramType.SoftLightMaskedSprite},\n        {190, ShaderProgramType.HardLightMaskedSprite, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.ColorMaskedSprite},\n        {191, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.HardLightMaskedSprite, ShaderProgramType.ColorMaskedSprite},\n        {192, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.OverlayMaskedSprite, ShaderProgramType.LinearBurnMaskedSprite},\n        {193, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {194, ShaderProgramType.OverlayMaskedSprite, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.SoftLightMaskedSprite, ShaderProgramType.ColorMaskedSprite},\n        {195, ShaderProgramType.ColorBurnMaskedSprite, ShaderProgramType.OverlayMaskedSprite, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.ColorMaskedSprite},\n        {197, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.HardLightMaskedSprite, ShaderProgramType.SoftLightMaskedSprite},\n        {198, ShaderProgramType.ColorBurnMaskedSprite, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.ColorMaskedSprite},\n        {199, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.SoftLightMaskedSprite, ShaderProgramType.OverlayMaskedSprite, ShaderProgramType.OverlayMaskedSprite},\n        {196, ShaderProgramType.ColorBurnMaskedSprite, ShaderProgramType.OverlayMaskedSprite, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.ColorMaskedSprite},\n        {200, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {201, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.HardLightMaskedSprite, ShaderProgramType.OverlayMaskedSprite},\n        {202, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.HardLightMaskedSprite, ShaderProgramType.SoftLightMaskedSprite},\n        {203, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.ColorBurnMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {204, ShaderProgramType.ColorBurnMaskedSprite, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.HardLightMaskedSprite, ShaderProgramType.SoftLightMaskedSprite},\n        {205, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.SoftLightMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {206, ShaderProgramType.ColorBurnMaskedSprite, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.HardLightMaskedSprite, ShaderProgramType.ColorMaskedSprite},\n        {207, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.ColorBurnMaskedSprite, ShaderProgramType.ColorMaskedSprite},\n        {208, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.ColorBurnMaskedSprite, ShaderProgramType.SoftLightMaskedSprite, ShaderProgramType.ColorMaskedSprite},\n        {209, ShaderProgramType.LinearBurnMaskedSprite, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.SoftLightMaskedSprite, ShaderProgramType.ColorMaskedSprite},\n        {210, ShaderProgramType.ColorBurnMaskedSprite, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {211, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {212, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.HardLightMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.SoftLightMaskedSprite},\n        {213, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.HardLightMaskedSprite, ShaderProgramType.SoftLightMaskedSprite},\n        {214, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.HardLightMaskedSprite, ShaderProgramType.ColorMaskedSprite},\n        {215, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.HardLightMaskedSprite, ShaderProgramType.SoftLightMaskedSprite},\n        {216, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.SoftLightMaskedSprite},\n        {217, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.SoftLightMaskedSprite, ShaderProgramType.HardLightMaskedSprite, ShaderProgramType.OverlayMaskedSprite},\n        {218, ShaderProgramType.ColorBurnMaskedSprite, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {219, ShaderProgramType.SoftLightMaskedSprite, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.ScreenMaskedSprite},\n        {221, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.HardLightMaskedSprite, ShaderProgramType.HardLightMaskedSprite},\n        {222, ShaderProgramType.HardLightMaskedSprite, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.ColorBurnMaskedSprite, ShaderProgramType.ColorMaskedSprite},\n        {223, ShaderProgramType.OverlayMaskedSprite, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.ColorDodgeMaskedSprite},\n        {224, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.HardLightMaskedSprite, ShaderProgramType.OverlayMaskedSprite},\n        {225, ShaderProgramType.OverlayMaskedSprite, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.ColorMaskedSprite},\n        {226, ShaderProgramType.OverlayMaskedSprite, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.ColorMaskedSprite},\n        {227, ShaderProgramType.OverlayMaskedSprite, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.ColorMaskedSprite},\n        {228, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.OverlayMaskedSprite},\n        {229, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.SoftLightMaskedSprite, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.ColorMaskedSprite},\n        {230, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.HardLightMaskedSprite, ShaderProgramType.OverlayMaskedSprite},\n        {231, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.HardLightMaskedSprite, ShaderProgramType.OverlayMaskedSprite},\n        {233, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.SoftLightMaskedSprite, ShaderProgramType.ColorMaskedSprite},\n        {234, ShaderProgramType.ColorBurnMaskedSprite, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.ColorMaskedSprite},\n        {235, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {236, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.SoftLightMaskedSprite, ShaderProgramType.ColorMaskedSprite},\n        {237, ShaderProgramType.OverlayMaskedSprite, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.ColorMaskedSprite},\n        {238, ShaderProgramType.OverlayMaskedSprite, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.ColorMaskedSprite},\n        {239, ShaderProgramType.OverlayMaskedSprite, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.ColorMaskedSprite},\n        {240, ShaderProgramType.ColorBurnMaskedSprite, ShaderProgramType.ColorBurnMaskedSprite, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.ColorMaskedSprite},\n        {241, ShaderProgramType.OverlayMaskedSprite, ShaderProgramType.ColorBurnMaskedSprite, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.ColorMaskedSprite},\n        {242, ShaderProgramType.ColorBurnMaskedSprite, ShaderProgramType.ColorBurnMaskedSprite, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.ColorMaskedSprite},\n        {243, ShaderProgramType.ColorBurnMaskedSprite, ShaderProgramType.ColorBurnMaskedSprite, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.ColorMaskedSprite},\n        {244, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.ColorBurnMaskedSprite, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.SoftLightMaskedSprite},\n        {248, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.OverlayMaskedSprite, ShaderProgramType.LinearBurnMaskedSprite},\n        {249, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.SoftLightMaskedSprite, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.ColorMaskedSprite},\n        {245, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.ColorBurnMaskedSprite, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.SoftLightMaskedSprite},\n        {250, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.SoftLightMaskedSprite, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.ColorMaskedSprite},\n        {251, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.SoftLightMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {252, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.SoftLightMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {253, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.ColorBurnMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {256, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.SoftLightMaskedSprite, ShaderProgramType.ColorMaskedSprite},\n        {259, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.HardLightMaskedSprite, ShaderProgramType.SoftLightMaskedSprite, ShaderProgramType.OverlayMaskedSprite},\n        {261, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.HardLightMaskedSprite, ShaderProgramType.LinearBurnMaskedSprite},\n        {262, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.ColorMaskedSprite},\n        {260, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {263, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.HardLightMaskedSprite, ShaderProgramType.ColorMaskedSprite},\n        {264, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.ColorMaskedSprite},\n        {265, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.ColorMaskedSprite},\n        {266, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.SoftLightMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {267, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.HardLightMaskedSprite, ShaderProgramType.ColorMaskedSprite},\n        {268, ShaderProgramType.OverlayMaskedSprite, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.LinearBurnMaskedSprite, ShaderProgramType.ColorMaskedSprite},\n        {269, ShaderProgramType.ColorBurnMaskedSprite, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.ColorMaskedSprite},\n        {270, ShaderProgramType.HardLightMaskedSprite, ShaderProgramType.ColorBurnMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {271, ShaderProgramType.HardLightMaskedSprite, ShaderProgramType.ColorBurnMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {272, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.OverlayMaskedSprite, ShaderProgramType.HardLightMaskedSprite, ShaderProgramType.ColorMaskedSprite},\n        {273, ShaderProgramType.ColorBurnMaskedSprite, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {274, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.ColorBurnMaskedSprite, ShaderProgramType.ColorMaskedSprite},\n        {275, ShaderProgramType.MaskedSpriteNoAlpha, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.ColorMaskedSprite},\n        {276, ShaderProgramType.ColorBurnMaskedSprite, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {277, ShaderProgramType.LinearBurnMaskedSprite, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {278, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.SoftLightMaskedSprite, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.ColorMaskedSprite},\n        {279, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.OverlayMaskedSprite, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.ColorMaskedSprite},\n        {281, ShaderProgramType.LinearBurnMaskedSprite, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {283, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.SoftLightMaskedSprite, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.ColorMaskedSprite},\n        {280, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.OverlayMaskedSprite, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.ColorMaskedSprite},\n        {282, ShaderProgramType.LinearBurnMaskedSprite, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {284, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.SoftLightMaskedSprite, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.ColorMaskedSprite},\n        {285, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.HardLightMaskedSprite, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.ColorMaskedSprite},\n        {286, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {287, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {290, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.LinearBurnMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {295, ShaderProgramType.LinearBurnMaskedSprite, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.ColorMaskedSprite},\n        {296, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.SoftLightMaskedSprite, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.ColorMaskedSprite},\n        {291, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.LinearBurnMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {292, ShaderProgramType.HardLightMaskedSprite, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.ColorMaskedSprite},\n        {297, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {298, ShaderProgramType.HardLightMaskedSprite, ShaderProgramType.SoftLightMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {301, ShaderProgramType.HardLightMaskedSprite, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {302, ShaderProgramType.ColorBurnMaskedSprite, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.ColorMaskedSprite},\n        {303, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.SoftLightMaskedSprite, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.ColorMaskedSprite},\n        {304, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.HardLightMaskedSprite, ShaderProgramType.SoftLightMaskedSprite},\n        {305, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.ColorBurnMaskedSprite, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.ColorMaskedSprite},\n        {306, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.OverlayMaskedSprite, ShaderProgramType.ColorMaskedSprite},\n        {307, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.HardLightMaskedSprite, ShaderProgramType.SoftLightMaskedSprite},\n        {308, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.ColorBurnMaskedSprite, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.ColorMaskedSprite},\n        {309, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.OverlayMaskedSprite, ShaderProgramType.ColorMaskedSprite},\n        {310, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.HardLightMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.OverlayMaskedSprite},\n        {311, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.HardLightMaskedSprite, ShaderProgramType.LinearBurnMaskedSprite},\n        {312, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.ColorMaskedSprite},\n        {313, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.ColorBurnMaskedSprite, ShaderProgramType.OverlayMaskedSprite, ShaderProgramType.SoftLightMaskedSprite},\n        {314, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.OverlayMaskedSprite, ShaderProgramType.ColorBurnMaskedSprite},\n        {315, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.OverlayMaskedSprite, ShaderProgramType.ColorMaskedSprite},\n        {316, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.ColorMaskedSprite},\n        {317, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.SoftLightMaskedSprite, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.ColorMaskedSprite},\n        {318, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.SoftLightMaskedSprite, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.ColorMaskedSprite},\n        {319, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.LinearBurnMaskedSprite, ShaderProgramType.ColorMaskedSprite},\n        {320, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.LinearBurnMaskedSprite, ShaderProgramType.ColorMaskedSprite},\n        {321, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.LinearBurnMaskedSprite, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.OverlayMaskedSprite},\n        {254, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.SoftLightMaskedSprite, ShaderProgramType.SoftLightMaskedSprite, ShaderProgramType.ColorMaskedSprite},\n        {255, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.SoftLightMaskedSprite, ShaderProgramType.SoftLightMaskedSprite, ShaderProgramType.ColorMaskedSprite},\n        {5, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.SoftLightMaskedSprite, ShaderProgramType.ColorMaskedSprite},\n        {112, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.SoftLightMaskedSprite, ShaderProgramType.ColorMaskedSprite},\n        {113, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.SoftLightMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {116, ShaderProgramType.SoftLightMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {130, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.SoftLightMaskedSprite, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.ColorMaskedSprite},\n        {247, ShaderProgramType.ColorBurnMaskedSprite, ShaderProgramType.SoftLightMaskedSprite, ShaderProgramType.ColorBurnMaskedSprite, ShaderProgramType.ColorMaskedSprite},\n        {257, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.SoftLightMaskedSprite, ShaderProgramType.ColorMaskedSprite},\n        {258, ShaderProgramType.ColorBurnMaskedSprite, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.SoftLightMaskedSprite, ShaderProgramType.ColorMaskedSprite},\n        {293, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.LinearBurnMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {294, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.LinearBurnMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {299, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {300, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.SoftLightMaskedSprite, ShaderProgramType.ColorMaskedSprite, ShaderProgramType.ColorMaskedSprite},\n        {323, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {324, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {84, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.SoftLightMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {85, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.SoftLightMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {73, ShaderProgramType.LinearBurnMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.Sprite, ShaderProgramType.OverlayMaskedSprite},\n        {86, ShaderProgramType.OverlayMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {158, ShaderProgramType.OverlayMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n        {160, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.Sprite, ShaderProgramType.SoftLightMaskedSprite},\n        {163, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.Sprite, ShaderProgramType.SoftLightMaskedSprite},\n        {126, ShaderProgramType.SoftLightMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.Sprite, ShaderProgramType.OverlayMaskedSprite},\n        {288, ShaderProgramType.MaskedSpriteNoAlpha, ShaderProgramType.ScreenMaskedSprite, ShaderProgramType.AdditiveMaskedSprite, ShaderProgramType.ColorMaskedSprite},\n        {289, ShaderProgramType.ColorBurnMaskedSprite, ShaderProgramType.ColorDodgeMaskedSprite, ShaderProgramType.Sprite, ShaderProgramType.ColorMaskedSprite},\n    },\n\n    -- bgId, bgEff1Texture, bgEff2Texture, bgEff3Texture, bgEffChaTexture\n    BgEffTextureData = {\n        {0, 0, 0, 0, 2},\n        {1, 3, 0, 0, 4},\n        {2, 5, 0, 0, 6},\n        {3, 5, 0, 0, 6},\n        {4, 7, 8, 0, 13},\n        {6, 14, 15, 0, 16},\n        {7, 14, 15, 0, 16},\n        {8, 17, 18, 0, 19},\n        {9, 17, 18, 0, 19},\n        {10, 20, 0, 0, 21},\n        {11, 20, 0, 0, 21},\n        {12, 282, 283, 284, 285},\n        {13, 22, 23, 0, 48},\n        {14, 22, 23, 0, 48},\n        {15, 24, 25, 0, 27},\n        {16, 24, 25, 0, 26},\n        {17, 28, 29, 0, 33},\n        {18, 30, 31, 0, 32},\n        {19, 34, 35, 0, 38},\n        {20, 34, 35, 0, 38},\n        {21, 36, 0, 0, 37},\n        {22, 39, 40, 0, 48},\n        {23, 41, 42, 0, 43},\n        {24, 44, 45, 46, 47},\n        {25, 0, 0, 0, 50},\n        {26, 51, 0, 0, 52},\n        {27, 53, 54, 55, 56},\n        {28, 0, 0, 0, 50},\n        {29, 51, 0, 0, 52},\n        {30, 57, 58, 0, 59},\n        {31, 49, 0, 0, 50},\n        {32, 49, 0, 0, 50},\n        {33, 49, 0, 0, 50},\n        {34, 60, 0, 0, 61},\n        {35, 60, 0, 0, 61},\n        {36, 62, 63, 0, 64},\n        {37, 65, 66, 0, 67},\n        {38, 65, 66, 0, 67},\n        {39, 282, 283, 284, 285},\n        {40, 68, 69, 0, 70},\n        {41, 68, 69, 0, 70},\n        {42, 68, 69, 0, 70},\n        {43, 68, 69, 0, 70},\n        {44, 71, 72, 0, 73},\n        {45, 74, 75, 0, 76},\n        {46, 74, 75, 0, 76},\n        {47, 74, 75, 0, 76},\n        {48, 78, 77, 0, 79},\n        {49, 80, 81, 0, 82},\n        {50, 80, 81, 0, 82},\n        {51, 83, 84, 0, 85},\n        {52, 86, 0, 0, 87},\n        {53, 88, 89, 90, 91},\n        {54, 92, 93, 0, 94},\n        {55, 95, 96, 0, 97},\n        {56, 95, 96, 0, 97},\n        {57, 95, 96, 0, 97},\n        {58, 98, 99, 0, 100},\n        {59, 101, 81, 0, 82},\n        {60, 102, 103, 0, 104},\n        {61, 105, 0, 0, 107},\n        {62, 108, 0, 0, 109},\n        {63, 80, 110, 0, 82},\n        {64, 111, 114, 0, 115},\n        {65, 112, 0, 0, 113},\n        {66, 75, 0, 0, 76},\n        {67, 75, 0, 0, 76},\n        {68, 75, 0, 0, 76},\n        {69, 75, 0, 0, 76},\n        {70, 75, 0, 0, 76},\n        {71, 75, 0, 0, 76},\n        {72, 75, 0, 0, 76},\n        {74, 116, 0, 0, 117},\n        {75, 116, 0, 0, 117},\n        {76, 116, 0, 0, 117},\n        {77, 118, 119, 0, 120},\n        {78, 118, 119, 0, 120},\n        {79, 118, 119, 0, 120},\n        {80, 121, 122, 0, 123},\n        {81, 112, 0, 0, 113},\n        {82, 112, 0, 0, 113},\n        {83, 112, 0, 0, 113},\n        {87, 0, 106, 0, 107},\n        {88, 108, 0, 0, 109},\n        {89, 80, 81, 0, 124},\n        {90, 125, 126, 0, 123},\n        {91, 127, 128, 0, 129},\n        {92, 105, 0, 0, 107},\n        {93, 130, 0, 0, 109},\n        {94, 131, 132, 133, 134},\n        {95, 135, 136, 0, 137},\n        {96, 138, 0, 0, 139},\n        {97, 140, 141, 0, 142},\n        {98, 143, 144, 0, 145},\n        {99, 146, 0, 0, 147},\n        {100, 148, 149, 0, 150},\n        {101, 151, 152, 0, 153},\n        {102, 282, 283, 284, 285},\n        {103, 154, 155, 0, 160},\n        {104, 156, 157, 158, 159},\n        {105, 161, 162, 0, 163},\n        {106, 164, 165, 0, 169},\n        {107, 166, 167, 0, 168},\n        {108, 164, 165, 0, 169},\n        {109, 282, 283, 284, 285},\n        {110, 170, 171, 0, 172},\n        {111, 173, 174, 0, 175},\n        {115, 183, 184, 0, 185},\n        {118, 189, 0, 0, 190},\n        {117, 186, 187, 0, 188},\n        {119, 191, 192, 193, 194},\n        {120, 195, 0, 0, 196},\n        {121, 197, 198, 199, 200},\n        {122, 201, 0, 0, 204},\n        {123, 202, 0, 0, 203},\n        {124, 205, 0, 0, 207},\n        {125, 205, 0, 0, 207},\n        {127, 205, 0, 0, 207},\n        {128, 206, 0, 0, 207},\n        {129, 208, 0, 0, 209},\n        {140, 214, 215, 0, 216},\n        {141, 217, 218, 0, 219},\n        {142, 220, 221, 0, 222},\n        {143, 223, 224, 0, 225},\n        {145, 226, 227, 228, 229},\n        {146, 230, 231, 0, 232},\n        {144, 223, 224, 0, 225},\n        {147, 230, 231, 0, 232},\n        {148, 233, 0, 0, 235},\n        {150, 236, 0, 0, 237},\n        {151, 220, 221, 0, 238},\n        {149, 233, 234, 0, 235},\n        {152, 239, 240, 0, 241},\n        {153, 239, 240, 0, 241},\n        {154, 242, 243, 244, 245},\n        {155, 246, 247, 0, 248},\n        {156, 246, 247, 0, 248},\n        {157, 249, 0, 0, 250},\n        {159, 251, 0, 0, 252},\n        {161, 253, 0, 0, 254},\n        {162, 253, 0, 0, 254},\n        {164, 255, 256, 0, 257},\n        {165, 258, 0, 0, 259},\n        {166, 253, 0, 0, 254},\n        {167, 260, 261, 0, 262},\n        {168, 239, 240, 0, 241},\n        {169, 263, 0, 0, 264},\n        {170, 239, 240, 0, 241},\n        {172, 242, 243, 265, 245},\n        {173, 266, 267, 0, 268},\n        {171, 239, 240, 0, 241},\n        {174, 269, 270, 271, 272},\n        {175, 411, 412, 413, 414},\n        {176, 273, 274, 0, 275},\n        {177, 276, 277, 278, 279},\n        {178, 280, 281, 0, 286},\n        {179, 282, 283, 284, 285},\n        {180, 282, 283, 284, 285},\n        {181, 287, 288, 0, 292},\n        {183, 242, 293, 294, 245},\n        {184, 220, 221, 0, 238},\n        {182, 289, 290, 0, 291},\n        {185, 295, 296, 0, 297},\n        {186, 298, 299, 300, 301},\n        {187, 302, 303, 304, 305},\n        {188, 306, 239, 240, 241},\n        {189, 307, 308, 309, 310},\n        {190, 311, 312, 313, 314},\n        {191, 315, 316, 317, 318},\n        {192, 319, 320, 321, 322},\n        {193, 323, 324, 0, 329},\n        {194, 325, 326, 327, 329},\n        {195, 330, 331, 332, 333},\n        {197, 334, 335, 336, 337},\n        {198, 338, 339, 341, 342},\n        {199, 340, 283, 284, 285},\n        {196, 330, 331, 332, 333},\n        {200, 343, 344, 0, 345},\n        {201, 346, 347, 348, 349},\n        {202, 350, 351, 352, 353},\n        {203, 354, 355, 0, 356},\n        {204, 357, 358, 359, 360},\n        {205, 361, 362, 0, 363},\n        {206, 364, 365, 366, 367},\n        {207, 368, 369, 370, 371},\n        {208, 372, 373, 378, 379},\n        {209, 374, 375, 376, 377},\n        {210, 380, 381, 0, 385},\n        {211, 382, 383, 0, 384},\n        {212, 386, 387, 0, 388},\n        {213, 389, 390, 391, 392},\n        {214, 393, 394, 395, 396},\n        {215, 397, 398, 399, 400},\n        {216, 401, 402, 0, 403},\n        {217, 404, 405, 406, 407},\n        {218, 408, 409, 0, 410},\n        {219, 411, 412, 413, 414},\n        {221, 415, 416, 417, 418},\n        {222, 419, 420, 421, 422},\n        {223, 423, 424, 425, 426},\n        {224, 427, 428, 429, 430},\n        {225, 431, 432, 437, 438},\n        {226, 433, 434, 435, 436},\n        {227, 433, 434, 435, 436},\n        {228, 439, 440, 0, 441},\n        {229, 442, 443, 444, 445},\n        {230, 446, 447, 448, 449},\n        {231, 446, 447, 448, 449},\n        {233, 450, 451, 456, 464},\n        {234, 452, 453, 454, 455},\n        {235, 457, 458, 0, 459},\n        {236, 460, 461, 462, 463},\n        {237, 465, 466, 467, 472},\n        {238, 465, 466, 467, 472},\n        {239, 468, 469, 470, 471},\n        {240, 473, 474, 475, 476},\n        {241, 477, 478, 479, 480},\n        {242, 473, 474, 475, 476},\n        {243, 473, 474, 475, 476},\n        {244, 481, 482, 487, 488},\n        {248, 493, 494, 495, 496},\n        {249, 497, 498, 499, 500},\n        {245, 483, 484, 485, 486},\n        {250, 497, 498, 499, 500},\n        {251, 501, 502, 0, 503},\n        {252, 501, 502, 0, 503},\n        {253, 504, 505, 0, 506},\n        {256, 513, 514, 515, 516},\n        {259, 521, 522, 523, 524},\n        {261, 528, 529, 530, 531},\n        {262, 532, 533, 534, 535},\n        {260, 525, 526, 0, 527},\n        {263, 536, 537, 538, 539},\n        {264, 540, 541, 542, 543},\n        {265, 544, 545, 549, 550},\n        {266, 546, 547, 0, 548},\n        {267, 551, 552, 553, 554},\n        {268, 555, 556, 557, 558},\n        {269, 559, 560, 561, 562},\n        {270, 71, 72, 0, 73},\n        {271, 71, 72, 0, 73},\n        {272, 563, 564, 565, 566},\n        {273, 140, 567, 0, 142},\n        {274, 568, 569, 570, 571},\n        {275, 572, 573, 574, 575},\n        {276, 80, 81, 0, 124},\n        {277, 576, 577, 0, 582},\n        {278, 578, 579, 580, 582},\n        {279, 583, 584, 585, 586},\n        {281, 587, 588, 0, 589},\n        {283, 578, 579, 590, 582},\n        {280, 583, 584, 585, 586},\n        {282, 587, 588, 0, 589},\n        {284, 578, 579, 590, 582},\n        {285, 591, 592, 593, 594},\n        {286, 595, 596, 0, 597},\n        {287, 598, 599, 0, 600},\n        {290, 601, 602, 0, 607},\n        {295, 608, 609, 610, 611},\n        {296, 612, 613, 617, 624},\n        {291, 601, 602, 0, 607},\n        {292, 603, 604, 605, 606},\n        {297, 39, 40, 0, 48},\n        {298, 618, 619, 0, 620},\n        {301, 625, 626, 0, 627},\n        {302, 628, 629, 630, 631},\n        {303, 632, 633, 634, 635},\n        {304, 636, 637, 638, 639},\n        {305, 640, 641, 642, 643},\n        {306, 644, 645, 646, 647},\n        {307, 636, 637, 638, 639},\n        {308, 640, 641, 642, 643},\n        {309, 644, 645, 646, 647},\n        {310, 239, 240, 0, 241},\n        {311, 528, 529, 530, 531},\n        {312, 532, 533, 534, 535},\n        {313, 648, 649, 650, 651},\n        {314, 493, 494, 495, 496},\n        {315, 644, 645, 646, 647},\n        {316, 652, 653, 654, 655},\n        {317, 656, 657, 658, 659},\n        {318, 656, 657, 658, 659},\n        {319, 660, 661, 662, 663},\n        {320, 664, 665, 666, 667},\n        {321, 668, 669, 670, 671},\n        {254, 507, 508, 511, 512},\n        {255, 509, 508, 511, 510},\n        {5, 9, 10, 11, 12},\n        {112, 176, 177, 178, 179},\n        {113, 180, 181, 0, 182},\n        {116, 176, 0, 0, 179},\n        {130, 210, 211, 212, 213},\n        {247, 489, 490, 491, 492},\n        {257, 176, 177, 178, 179},\n        {258, 517, 518, 519, 520},\n        {293, 601, 602, 0, 607},\n        {294, 601, 602, 0, 607},\n        {299, 621, 622, 0, 623},\n        {300, 612, 613, 617, 624},\n        {323, 672, 0, 0, 673},\n        {324, 674, 675, 0, 676},\n        {84, 121, 122, 0, 123},\n        {85, 121, 122, 0, 123},\n        {73, 75, 0, 0, 76},\n        {86, 112, 0, 0, 113},\n        {158, 249, 0, 0, 250},\n        {160, 251, 0, 0, 252},\n        {163, 253, 0, 0, 254},\n        {126, 205, 0, 0, 207},\n        {288, 572, 573, 574, 575},\n        {289, 80, 81, 0, 124},\n    },\n};"
  },
  {
    "path": "profiles/cclcc/charset.lua",
    "content": "root.CharsetInternal = {\n    CharsetStr = \" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz　/:-;!?'.@#%~*&`()°^>+<ﾉ･=″$′,[\\\\]_{|}—ｰéöáàś０１２３４５６７８９ＡＢＣＤＥＦＧＨＩＪＫＬＭＮＯＰＱＲＳＴＵＶＷＸＹＺａｂｃｄｅｆｇｈｉｊｋｌｍｎｏｐｑｒｓｔｕｖｗｘｙｚ、。，．：；？！゛゜‘’“”（）〔〕［］｛｝〈〉《》「」『』【】＜＞〖〗・〜ー♪―ぁぃぅぇぉっゃゅょゎァィゥェォッャュョヮヵヶ①②—④—⑥⑦⑧⑨⑩⑪⑫⑬ⁿ²♥₃％–—＿／•βγζημξρστυφχψωÅ√◯´`∣¯Д∥αδεθικλνοπヽヾゝゞ〃仝々〆〇＼＋－±×÷＝≠＜＞≦≧∞∴♂♀℃￥＄￠￡％＃＆＊＠§☆★○●◎◇◆□■△▲▽▼※〒→←↑↓〓∈∋⊆⊇⊂⊃∪∩∧∨￢⇒⇔∀∃∠⊥⌒∂∇≡≒≪≫∽∝∵∫∬‰♯♭♪†‡¶あいうえおかがきぎくぐけげこごさざしじすずせぜそぞただちぢつづてでとどなにぬねのはばぱひびぴふぶぷへべぺほぼぽまみむめもやゆよらりるれろわゐゑをんアイウエオカガキギクグケゲコゴサザシジスズセゼソゾタダチヂツヅテデトドナニヌネノハバパヒビピフブプヘベペホボポマミムメモヤユヨラリルレロヮワヰヱヲンヴΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩ∮∑∟⊿亜唖娃阿哀愛挨姶逢葵茜穐悪握渥旭葦芦鯵梓圧斡扱宛姐虻飴絢綾鮎或粟袷安庵按暗案闇鞍杏以伊位依偉囲霻夷委威尉惟意慰易椅為畏異移維緯胃萎衣謂違遺医井亥域育郁磯一壱溢逸稲茨芋鰯允印咽員因姻引飲淫胤蔭院陰隠韻吋右宇烏羽迂渦嘘唄欝蔚鰻姥厩浦瓜閏噂云運雲荏餌叡営嬰影映曳栄永泳洩瑛盈穎頴英衛詠鋭液疫益駅悦謁越閲榎厭円園堰奄宴延怨掩援沿演炎焔煙燕猿縁艶苑薗遠鉛鴛塩於汚甥凹央奥往応押旺横欧殴王翁襖鴬鴎黄岡沖荻億屋憶臆桶牡乙俺卸恩温穏音下化仮何伽価佳加可嘉夏嫁家寡科暇果架歌河火珂禍禾稼箇花苛茄荷華菓蝦課嘩貨迦過霞蚊俄峨我牙画臥芽蛾賀雅餓駕介会解回塊壊廻快怪悔恢懐戒拐改魁晦械海灰界皆絵芥蟹開階貝凱劾外咳害崖慨概涯碍蓋街該鎧骸浬馨蛙垣柿蛎鈎劃嚇各廓拡撹格核殻獲確穫覚角赫較郭閣隔革学岳楽額顎掛笠樫橿梶鰍潟割喝恰括活渇滑葛褐轄且鰹叶椛樺鞄株兜竃蒲釜鎌噛鴨栢茅萱粥刈苅瓦乾侃冠寒刊勘勧巻喚堪姦完官寛干幹患感慣憾換敢柑桓棺款歓汗漢澗潅環甘監看竿管簡緩缶翰肝艦莞観諌貫還鑑間閑関陥韓館舘丸含岸巌玩癌眼岩翫贋雁頑顔願企伎危喜器基奇嬉寄岐希幾忌揮机旗既期棋棄機帰毅気汽畿祈季稀紀徽規記貴起軌輝飢騎鬼亀偽儀妓宜戯技擬欺犠疑祇義蟻誼議掬菊鞠吉吃喫桔橘詰砧杵黍却客脚虐逆丘久仇休及吸宮弓急救朽求汲泣灸球究窮笈級糾給旧牛去居巨拒拠挙渠虚許距鋸漁禦魚亨享京供侠僑兇競共凶協匡卿叫喬境峡強彊怯恐恭挟教橋況狂狭矯胸脅興蕎郷鏡響饗驚仰凝尭暁業局曲極玉桐粁僅勤均巾錦斤欣欽琴禁禽筋緊芹菌衿襟謹近金吟銀九倶句区狗玖矩苦躯駆駈駒具愚虞喰空偶寓遇隅串櫛釧屑屈掘窟沓靴轡窪熊隈粂栗繰桑鍬勲君薫訓群軍郡卦袈祁係傾刑兄啓圭珪型契形径恵慶慧憩掲携敬景桂渓畦稽系経継繋罫茎荊蛍計詣警軽頚鶏芸迎鯨劇戟撃激隙桁傑欠決潔穴結血訣月件倹倦健兼券剣喧圏堅嫌建憲懸拳捲検権牽犬献研硯絹県肩見謙賢軒遣鍵険顕験鹸元原厳幻弦減源玄現絃舷言諺限乎個古呼固姑孤己庫弧戸故枯湖狐糊袴股胡菰虎誇跨鈷雇顧鼓五互伍午呉吾娯後御悟梧檎瑚碁語誤護醐乞鯉交佼侯候倖光公功効勾厚口向后喉坑垢好孔孝宏工巧巷幸広庚康弘恒慌抗拘控攻昂晃更杭校梗構江洪浩港溝甲皇硬稿糠紅紘絞綱耕考肯肱腔膏航荒行衡講貢購郊酵鉱砿鋼閤降項香高鴻剛劫号合壕拷濠豪轟麹克刻告国穀酷鵠黒獄漉腰甑忽惚骨狛込此頃今困坤墾婚恨懇昏昆根梱混痕紺艮魂些佐叉唆嵯左差査沙瑳砂詐鎖裟坐座挫債催再最哉塞妻宰彩才採栽歳済災采犀砕砦祭斎細菜裁載際剤在材罪財冴坂阪堺榊肴咲崎埼碕鷺作削咋搾昨朔柵窄策索錯桜鮭笹匙冊刷察拶撮擦札殺薩雑皐鯖捌錆鮫皿晒三傘参山惨撒散桟燦珊産算纂蚕讃賛酸餐斬暫残仕仔伺使刺司史嗣四士始姉姿子屍市師志思指支孜斯施旨枝止死氏獅祉私糸紙紫肢脂至視詞詩試誌諮資賜雌飼歯事似侍児字寺慈持時次滋治爾璽痔磁示而耳自蒔辞汐鹿式識鴫竺軸宍雫七叱執失嫉室悉湿漆疾質実蔀篠偲柴芝屡蕊縞舎写射捨赦斜煮社紗者謝車遮蛇邪借勺尺杓灼爵酌釈錫若寂弱惹主取守手朱殊狩珠種腫趣酒首儒受呪寿授樹綬需囚収周宗就州修愁拾洲秀秋終繍習臭舟蒐衆襲讐蹴輯週酋酬集醜什住充十従戎柔汁渋獣縦重銃叔夙宿淑祝縮粛塾熟出術述俊峻春瞬竣舜駿准循旬楯殉淳準潤盾純巡遵醇順処初所暑曙渚庶緒署書薯藷諸助叙女序徐恕鋤除傷償勝匠升召哨商唱嘗奨妾娼宵将小少尚庄床廠彰承抄招掌捷昇昌昭晶松梢樟樵沼消渉湘焼焦照症省硝礁祥称章笑粧紹肖菖蒋蕉衝裳訟証詔詳象賞醤鉦鍾鐘障鞘上丈丞乗冗剰城場壌嬢常情擾条杖浄状畳穣蒸譲醸錠嘱埴飾拭植殖燭織職色触食蝕辱尻伸信侵唇娠寝審心慎振新晋森榛浸深申疹真神秦紳臣芯薪親診身辛進針震人仁刃塵壬尋甚尽腎訊迅陣靭笥諏須酢図厨逗吹垂帥推水炊睡粋翠衰遂酔錐錘随瑞髄崇嵩数枢趨雛据杉椙菅頗雀裾澄摺寸世瀬畝是凄制勢姓征性成政整星晴棲栖正清牲生盛精聖声製西誠誓請逝醒青静斉税脆隻席惜戚斥昔析石積籍績脊責赤跡蹟碩切拙接摂折設窃節説雪絶舌蝉仙先千占宣専尖川戦扇撰栓栴泉浅洗染潜煎煽旋穿箭線繊羨腺舛船薦詮賎践選遷銭銑閃鮮前善漸然全禅繕膳糎噌塑岨措曾曽楚狙疏疎礎祖租粗素組蘇訴阻遡鼠僧創双叢倉喪壮奏爽宋層匝惣想捜掃挿掻操早曹巣槍槽漕燥争痩相窓糟総綜聡草荘葬蒼藻装走送遭鎗霜騒像増憎臓蔵贈造促側則即息捉束測足速俗属賊族続卒袖其揃存孫尊損村遜他多太汰詑唾堕妥惰打柁舵楕陀駄騨体堆対耐岱帯待怠態戴替泰滞胎腿苔袋貸退逮隊黛鯛代台大第醍題鷹滝瀧卓啄宅托択拓沢濯琢託鐸濁諾茸凧蛸只叩但達辰奪脱巽竪辿棚谷狸鱈樽誰丹単嘆坦担探旦歎淡湛炭短端箪綻耽胆蛋誕鍛団壇倭和話歪賄脇惑枠鷲亙亘鰐詫藁蕨椀弾断暖檀段男談値知地弛恥智池痴稚置致蜘遅馳築畜竹筑蓄逐秩窒茶嫡着中仲宙忠抽昼柱注虫衷註酎鋳駐樗瀦猪苧著貯丁兆凋喋寵帖帳庁弔張彫徴懲挑暢朝潮牒町眺聴脹腸蝶調諜超跳銚長頂鳥勅捗直朕沈珍賃鎮陳津墜椎槌追鎚痛通塚栂掴槻佃漬柘辻蔦綴鍔椿潰坪壷嬬紬爪吊釣鶴亭低停偵剃貞呈堤定帝底庭廷弟悌抵挺提梯汀碇禎程締艇訂諦蹄逓邸鄭釘鼎泥摘擢敵滴的笛適鏑溺哲徹撤轍迭鉄典填天展店添纏甜貼転顛点伝殿澱田電兎吐堵塗妬屠徒斗杜渡登菟賭途都鍍砥砺努度土奴怒倒党冬凍刀唐塔塘套宕島嶋悼投搭東桃梼棟盗淘湯涛灯燈当痘祷等答筒糖統到董蕩藤討謄豆踏逃透鐙陶頭騰闘働動同堂導憧撞洞瞳童胴萄道銅峠鴇匿得徳涜特督禿篤毒独読栃橡凸突椴届鳶苫寅酉瀞噸屯惇敦沌豚遁頓呑曇鈍奈那内乍凪薙謎灘捺鍋楢馴縄畷南楠軟難汝二尼弐迩匂賑肉虹廿日乳入如尿韮任妊忍認濡禰祢寧葱猫熱年念捻撚燃粘乃廼之埜嚢悩濃納能脳膿農覗蚤巴把播覇杷波派琶破婆罵芭馬俳廃拝排敗杯盃牌背肺輩配倍培媒梅楳煤狽買売賠陪這蝿秤矧萩伯剥博拍柏泊白箔粕舶薄迫曝漠爆縛莫駁麦函箱硲箸肇筈櫨幡肌畑畠八鉢溌発醗髪伐罰抜筏閥鳩噺塙蛤隼伴判半反叛帆搬斑板氾汎版犯班畔繁般藩販範釆煩頒飯挽晩番盤磐蕃蛮匪卑否妃庇彼悲扉批披斐比泌疲皮碑秘緋罷肥被誹費避非飛樋簸備尾微枇毘琵眉美鼻柊稗匹疋髭彦膝菱肘弼必畢筆逼桧姫媛紐百謬俵彪標氷漂瓢票表評豹廟描病秒苗錨鋲蒜蛭鰭品彬斌浜瀕貧賓頻敏瓶不付埠夫婦富冨布府怖扶敷斧普浮父符腐膚芙譜負賦赴阜附侮撫武舞葡蕪部封楓風葺蕗伏副復幅服福腹複覆淵弗払沸仏物鮒分吻噴墳憤扮焚奮粉糞紛雰文聞丙併兵塀幣平弊柄並蔽閉陛米頁僻壁癖碧別瞥蔑箆偏変片篇編辺返遍便勉娩弁鞭保舗鋪圃捕歩甫補輔穂募墓慕戊暮母簿菩倣俸包呆報奉宝峰峯崩庖抱捧放方朋法泡烹砲縫胞芳萌蓬蜂褒訪豊邦鋒飽鳳鵬乏亡傍剖坊妨帽忘忙房暴望某棒冒紡肪膨謀貌貿鉾防吠頬北僕卜墨撲朴牧睦穆釦勃没殆堀幌奔本翻凡盆摩磨魔麻埋妹昧枚毎哩槙幕膜枕鮪柾鱒桝亦俣又抹末沫迄侭繭麿万慢満漫蔓味未魅巳箕岬密蜜湊蓑稔脈妙粍民眠務夢無牟矛霧鵡椋婿娘冥名命明盟迷銘鳴姪牝滅免棉綿緬面麺摸模茂妄孟毛猛盲網耗蒙儲木黙目杢勿餅尤戻籾貰問悶紋門匁也冶夜爺耶野弥矢厄役約薬訳躍靖柳薮鑓愉愈油癒諭輸唯佑優勇友宥幽悠憂揖有柚湧涌猶猷由祐裕誘遊邑郵雄融夕予余与誉輿預傭幼妖容庸揚揺擁曜楊様洋溶熔用窯羊耀葉蓉要謡踊遥陽養慾抑欲沃浴翌翼淀羅螺裸来莱頼雷洛絡落酪乱卵嵐欄濫藍蘭覧利吏履李梨理璃痢裏裡里離陸律率立葎掠略劉流溜琉留硫粒隆竜龍侶慮旅虜了亮僚両凌寮料梁涼猟療瞭稜糧良諒遼量陵領力緑倫厘林淋燐琳臨輪隣鱗麟瑠塁涙累類令伶例冷励嶺怜玲礼苓鈴隷零霊麗齢暦歴列劣烈裂廉恋憐漣煉簾練聯蓮連錬呂魯櫓炉賂路露労婁廊弄朗楼榔浪漏牢狼篭老聾蝋郎六麓禄肋録論湾碗腕靕顗顥飯飼餧館馞驎髙髜魵魲鮏鮱鮻鰀鵰鵫鶴鸙黑靃靍靏靑弌丐丕个丱丶丼丿乂乖乘亂亅豫亊舒弍于亞亟亠亢亰亳亶从仍仄仆仂仗仞仭仟价伉佚估佛佝佗佇佶侈侏侘佻佩佰侑佯來侖儘俔俟俎俘俛俑俚俐俤俥倚倨倔倪倥倅伜俶倡倩倬俾俯們倆偃假會偕偐偈做偖偬偸傀傚傅傴傲僉僊傳僂僖僞僥僭僣僮價僵儉儁儂儖儕儔儚儡儺儷儼儻儿兀兒兌兔兢竸兩兪兮冀冂囘册冉冏冑冓冕冖冤冦冢冩冪冫决冱冲冰况冽凅凉凛几處凩凭凰凵凾刄刋刔刎刧刪刮刳刹剏剄剋剌剞剔剪剴剩剳剿剽劍劔劒剱劈劑辨辧劬劭劼劵勁勍勗勞勣勦飭勠勳勵勸勹匆匈甸匍匐匏匕匚匣匯匱匳匸區卆卅丗卉卍凖卞卩卮夘卻卷厂厖厠厦厥厮厰厶參簒雙叟曼燮叮叨叭叺吁吽呀听吭吼吮雨卯鵜窺丑碓臼吶吩吝呎咏呵咎呟呱呷呰咒呻咀呶咄咐咆哇咢咸咥咬哄哈咨咫哂咤咾咼哘哥哦唏唔哽哮哭哺哢唹啀啣啌售啜啅啖啗唸唳啝喙喀咯喊喃喩喇喨嗚嗅嗟嗄嗜嗤嗔嘔嗷嘖嗾嗽嘛嗹噎噐營嘴嘶嘲嘸噫噤嘯噬噪嚆嚀嚊嚠嚔嚏嚥嚮嚶嚴囂嚼囁囃囀囈囎囑囓囗囮囹圀囿圄圉圈國圍圓團圖嗇圜圦圷圸坎圻址坏坩埀垈坡坿垉垓垠垳垤垪垰埃埆埔埒埓堊埖埣堋堙堝塲堡塢塋塰毀塒堽塹墅墹墟墫墺壞墻墸墮壅壓壑壗壙壘壥壜壤壟壯壺壹壻壼壽夂夊夐夛梦夥夬夭夲夸夾竒奕奐奎奚奘奢奠奧奬奩奸妁妝佞侫妣妲姆姨姜妍姙姚娥娟娑娜娉娚婀婬婉娵娶婢婪媚媼媾嫋嫂媽嫣嫗嫦嫩嫖嫺嫻嬌嬋嬖嬲嫐嬪嬶嬾孃孅孀孑孕孚孛孥孩孰孳孵學斈孺宀它宦宸寃寇寉寔寐寤實寢寞寥寫寰寶寳尅將專對尓尠尢尨尸尹屁屆屎屓屐屏孱屬屮乢屶屹岌岑岔妛岫岻岶岼岷峅岾峇峙峩峽峺峭嶌峪崋崕崗嵜崟崛崑崔崢崚崙崘嵌嵒嵎嵋嵬嵳嵶嶇嶄嶂嶢嶝嶬嶮嶽嶐嶷嶼巉巍巓巒巖巛巫已巵帋帚帙帑帛帶帷幄幃幀幎幗幔幟幢幤幇幵并幺麼广庠廁廂廈廐廏廖廣廝廚廛廢廡廨廩廬廱廳廰廴廸廾弃弉彝彜弋弑弖弩弭弸彁彈彌彎弯彑彖彗彙彡彭彳彷徃徂彿徊很徑徇從徙徘徠徨徭徼忖忻忤忸忱忝悳忿怡恠怙怐怩怎怱怛怕怫怦怏怺恚恁恪恷恟恊恆恍恣恃恤恂恬恫恙悁悍惧悃悚悄悛悖悗悒悧悋惡悸惠惓悴忰悽惆悵惘慍愕愆惶惷愀惴惺愃愡惻惱愍愎慇愾愨愧慊愿愼愬愴愽慂慄慳慷慘慙慚慫慴慯慥慱慟慝慓慵憙憖憇憬憔憚憊憑憫憮懌懊應懷懈懃懆憺懋罹懍懦懣懶懺懴懿懽懼懾戀戈戉戍戌戔戛戞戡截戮戰戲戳扁扎扞扣扛扠扨扼抂抉找抒抓抖拔抃抔拗拑抻拏拿拆擔拈拜拌拊拂拇抛拉挌拮拱挧挂挈拯拵捐挾捍搜捏掖掎掀掫捶掣掏掉掟掵捫捩掾揩揀揆揣揉插揶揄搖搴搆搓搦搶攝搗搨搏摧摯摶摎攪撕撓撥撩撈撼據擒擅擇撻擘擂擱擧舉擠擡抬擣擯攬擶擴擲擺攀擽攘攜攅攤攣攫攴攵攷收攸畋效敖敕敍敘敞敝敲數斂斃變斛斟斫斷旃旆旁旄旌旒旛旙无旡旱杲昊昃旻杳昵昶昴昜晏晄晉晁晞晝晤晧晨晟晢晰暃暈暎暉暄暘暝曁暹曉暾暼曄暸曖曚曠昿曦曩曰曵曷朏朖朞朦朧霸朮朿朶杁朸朷杆杞杠杙杣杤枉杰枩杼杪枌枋枦枡枅枷柯枴柬枳柩枸柤柞柝柢柮枹柎柆柧檜栞框栩桀桍栲桎梳栫桙档桷桿梟梏梭梔條梛梃檮梹桴梵梠梺椏梍桾椁棊椈棘椢椦棡椌棍棔棧棕椶椒椄棗棣椥棹棠棯椨椪椚椣椡棆楹楷楜楸楫楔楾楮椹楴椽楙椰楡楞楝榁楪榲榮槐榿槁槓榾槎寨槊槝榻槃榧樮榑榠榜榕榴槞槨樂樛槿權槹槲槧樅榱樞槭樔槫樊樒櫁樣樓橄樌橲樶橸橇橢橙橦橈樸樢檐檍檠檄檢檣檗蘗檻櫃櫂檸檳檬櫞櫑櫟檪櫚櫪櫻欅蘖櫺欒欖鬱欟欸欷盜欹飮歇歃歉歐歙歔歛歟歡歸歹歿殀殄殃殍殘殕殞殤殪殫殯殲殱殳殷殼毆毋毓毟毬毫毳毯麾氈氓气氛氤氣汞汕汢汪沂沍沚沁沛汾汨汳沒沐泄泱泓沽泗泅泝沮沱沾沺泛泯泙泪洟衍洶洫洽洸洙洵洳洒洌浣涓浤浚浹浙涎涕濤涅淹渕渊涵淇淦涸淆淬淞淌淨淒淅淺淙淤淕淪淮渭湮渮渙湲湟渾渣湫渫湶湍渟湃渺湎渤滿渝游溂溪溘滉溷滓溽溯滄溲滔滕溏溥滂溟潁漑灌滬滸滾漿滲漱滯漲滌漾漓滷澆潺潸澁澀潯潛濳潭澂潼潘澎澑濂潦澳澣澡澤澹濆澪濟濕濬濔濘濱濮濛瀉瀋濺瀑瀁瀏濾瀛瀚潴瀝瀘瀟瀰瀾瀲灑灣炙炒炯烱炬炸炳炮烟烋烝烙焉烽焜焙煥煕熈煦煢煌煖煬熏燻熄熕熨熬燗熹熾燒燉燔燎燠燬燧燵燼燹燿爍爐爛爨爭爬爰爲爻爼爿牀牆牋牘牴牾犂犁犇犒犖犢犧犹犲狃狆狄狎狒狢狠狡狹狷倏猗猊猜猖猝猴猯猩猥猾獎獏默獗獪獨獰獸獵獻獺珈玳珎玻珀珥珮珞璢琅瑯琥珸琲琺瑕琿瑟瑙瑁瑜瑩瑰瑣瑪瑶瑾璋璞璧瓊瓏瓔珱瓠瓣瓧瓩瓮瓲瓰瓱瓸瓷甄甃甅甌甎甍甕甓甞甦甬甼畄畍畊畉畛畆畚畩畤畧畫畭畸當疆疇畴疊疉疂疔疚疝疥疣痂疳痃疵疽疸疼疱痍痊痒痙痣痞痾痿痼瘁痰痺痲痳瘋瘍瘉瘟瘧瘠瘡瘢瘤瘴瘰瘻癇癈癆癜癘癡癢癨癩癪癧癬癰癲癶癸發皀皃皈皋皎皖皓皙皚皰皴皸皹皺盂盍盖盒盞盡盥盧盪蘯盻眈眇眄眩眤眞眥眦眛眷眸睇睚睨睫睛睥睿睾睹瞎瞋瞑瞠瞞瞰瞶瞹瞿瞼瞽瞻矇矍矗矚矜矣矮矼砌砒礦砠礪硅碎硴碆硼碚碌碣碵碪碯磑磆磋磔碾碼磅磊磬磧磚磽磴礇礒礑礙礬礫祀祠祗祟祚祕祓祺祿禊禝禧齋禪禮禳禹禺秉秕秧秬秡秣稈稍稘稙稠稟禀稱稻稾稷穃穗穉穡穢穩龝穰穹穽窈窗窕窘窖窩竈窰窶竅竄窿邃竇竊竍竏竕竓站竚竝竡竢竦竭竰笂笏笊笆笳笘笙笞笵笨笶筐筺笄筍笋筌筅筵筥筴筧筰筱筬筮箝箘箟箍箜箚箋箒箏筝箙篋篁篌篏箴篆篝篩簑簔篦篥籠簀簇簓篳篷簗簍篶簣簧簪簟簷簫簽籌籃籔籏籀籐籘籟籤籖籥籬籵粃粐粤粭粢粫粡粨粳粲粱粮粹粽糀糅糂糘糒糜糢鬻糯糲糴糶糺紆紂紜紕紊絅絋紮紲紿紵絆絳絖絎絲絨絮絏絣經綉絛綏絽綛綺綮綣綵緇綽綫總綢綯緜綸綟綰緘緝緤緞緻緲緡縅縊縣縡縒縱縟縉縋縢繆繦縻縵縹繃縷縲縺繧繝繖繞繙繚繹繪繩繼繻纃緕繽辮繿纈纉續纒纐纓纔纖纎纛纜缸缺罅罌罍罎罐网罕罔罘罟罠罨罩罧罸羂羆羃羈羇羌羔羞羝羚羣羯羲羹羮羶羸譱翅翆翊翕翔翡翦翩翳翹飜耆耄耋耒耘耙耜耡耨耿耻聊聆聒聘聚聟聢聨聳聲聰聶聹聽聿肄肆肅肛肓肚肭冐肬胛胥胙胝胄胚胖脉胯胱脛脩脣脯腋隋腆脾腓腑胼腱腮腥腦腴膃膈膊膀膂膠膕膤膣腟膓膩膰膵膾膸膽臀臂膺臉臍臑臙臘臈臚臟臠臧臺臻臾舁舂舅與舊舍舐舖舩舫舸舳艀艙艘艝艚艟艤艢艨艪艫舮艱艷艸艾芍芒芫芟芻芬苡苣苟苒苴苳苺莓范苻苹苞茆苜茉苙茵茴茖茲茱荀茹荐荅茯茫茗茘莅莚莪莟莢莖茣莎莇莊荼莵荳荵莠莉莨菴萓菫菎菽萃菘萋菁菷萇菠菲萍萢萠莽萸蔆菻葭萪萼蕚蒄葷葫蒭葮蒂葩葆萬葯葹萵蓊葢蒹蒿蒟蓙蓍蒻蓚蓐蓁蓆蓖蒡蔡蓿蓴蔗蔘蔬蔟蔕蔔蓼蕀蕣蕘蕈蕁蘂蕋蕕薀薤薈薑薊薨蕭薔薛藪薇薜蕷蕾薐藉薺藏薹藐藕藝藥藜藹蘊蘓蘋藾藺蘆蘢蘚蘰蘿虍乕虔號虧虱蚓蚣蚩蚪蚋蚌蚶蚯蛄蛆蚰蛉蠣蚫蛔蛞蛩蛬蛟蛛蛯蜒蜆蜈蜀蜃蛻蜑蜉蜍蛹蜊蜴蜿蜷蜻蜥蜩蜚蝠蝟蝸蝌蝎蝴蝗蝨蝮蝙蝓蝣蝪蠅螢螟螂螯蟋螽蟀蟐雖螫蟄螳蟇蟆螻蟯蟲蟠蠏蠍蟾蟶蟷蠎蟒蠑蠖蠕蠢蠡蠱蠶蠹蠧蠻衄衂衒衙衞衢衫袁衾袞衵衽袵衲袂袗袒袮袙袢袍袤袰袿袱裃裄裔裘裙裝裹褂裼裴裨裲褄褌褊褓襃褞褥褪褫襁襄褻褶褸襌褝襠襞襦襤襭襪襯襴襷襾覃覈覊覓覘覡覩覦覬覯覲覺覽覿觀觚觜觝觧觴觸訃訖訐訌訛訝訥訶詁詛詒詆詈詼詭詬詢誅誂誄誨誡誑誥誦誚誣諄諍諂諚諫諳諧諤諱謔諠諢諷諞諛謌謇謚諡謖謐謗謠謳鞫謦謫謾謨譁譌譏譎證譖譛譚譫譟譬譯譴譽讀讌讎讒讓讖讙讚谺豁谿豈豌豎豐豕豢豬豸豺貂貉貅貊貍貎貔豼貘戝貭貪貽貲貳貮貶賈賁賤賣賚賽賺賻贄贅贊贇贏贍贐齎贓賍贔贖赧赭赱赳趁趙跂趾趺跏跚跖跌跛跋跪跫跟跣跼踈踉跿踝踞踐踟蹂踵踰踴蹊蹇蹉蹌蹐蹈蹙蹤蹠踪蹣蹕蹶蹲蹼躁躇躅躄躋躊躓躑躔躙躪躡躬躰軆躱躾軅軈軋軛軣軼軻軫軾輊輅輕輒輙輓輜輟輛輌輦輳輻輹轅轂輾轌轉轆轎轗轜轢轣轤辜辟辣辭辯辷迚迥迢迪迯邇迴逅迹迺逑逕逡逍逞逖逋逧逶逵逹迸遏遐遑遒逎遉逾遖遘遞遨遯遶隨遲邂遽邁邀邊邉邏邨邯邱邵郢郤扈郛鄂鄒鄙鄲鄰酊酖酘酣酥酩酳酲醋醉醂醢醫醯醪醵醴醺釀釁釉釋釐釖釟釡釛釼釵釶鈞釿鈔鈬鈕鈑鉞鉗鉅鉉鉤鉈銕鈿鉋鉐銜銖銓銛鉚鋏銹銷鋩錏鋺鍄錮錙錢錚錣錺錵錻鍜鍠鍼鍮鍖鎰鎬鎭鎔鎹鏖鏗鏨鏥鏘鏃鏝鏐鏈鏤鐚鐔鐓鐃鐇鐐鐶鐫鐵鐡鐺鑁鑒鑄鑛鑠鑢鑞鑪鈩鑰鑵鑷鑽鑚鑼鑾钁鑿閂閇閊閔閖閘閙閠閨閧閭閼閻閹閾闊濶闃闍闌闕闔闖關闡闥闢阡阨阮阯陂陌陏陋陷陜陞陝陟陦陲陬隍隘隕隗險隧隱隲隰隴隶隸隹雎雋雉雍襍雜霍雕雹霄霆霈霓霎霑霏霖霙霤霪霰霹霽霾靄靆靈靂靉靜靠靤靦靨勒靫靱靹鞅靼鞁靺鞆鞋鞏鞐鞜鞨鞦鞣鞳鞴韃韆韈韋韜韭齏韲竟韶韵頏頌頸頤頡頷頽顆顏顋顫顯顰顱顴顳颪颯颱颶飄飃飆飩飫餃餉餒餔餘餡餝餞餤餠餬餮餽餾饂饉饅饐饋饑饒饌饕馗馘馥馭馮馼駟駛駝駘駑駭駮駱駲駻駸騁騏騅駢騙騫騷驅驂驀驃騾驕驍驛驗驟驢驥驤驩驫驪骭骰骼髀髏髑髓體髞髟髢髣髦髯髫髮髴髱髷髻鬆鬘鬚鬟鬢鬣鬥鬧鬨鬩鬪鬮鬯鬲魄魃魏魍魎魑魘魴鮓鮃鮑鮖鮗鮟鮠鮨鮴鯀鯊鮹鯆鯏鯑鯒鯣鯢鯤鯔鯡鰺鯲鯱鯰鰕鰔鰉鰓鰌鰆鰈鰒鰊鰄鰮鰛鰥鰤鰡鰰鱇鰲鱆鰾鱚鱠鱧鱶鱸鳧鳬鳰鴉鴈鳫鴃鴆鴪鴦鶯鴣鴟鵄鴕鴒鵁鴿鴾鵆鵈鵝鵞鵤鵑鵐鵙鵲鶉鶇鶫鵯鵺鶚鶤鶩鶲鷄鷁鶻鶸鶺鷆鷏鷂鷙鷓鷸鷦鷭鷯鷽鸚鸛鸞鹵鹹鹽麁麈麋麌麒麕麑麝麥麩麸麪麭靡黌黎黏黐黔黜點黝黠黥黨黯黴黶黷黹黻黼黽鼇鼈皷鼕鼡鼬鼾齊齒齔齣齟齠齡齦齧齬齪齷齲齶龕龜龠堯槇遙瑤凜熙纊褜鍈銈蓜俉炻昱棈鋹曻彅丨仡仼伀伃伹佖侒侊侚侔俍偀倢俿倞偆偰偂傔僴僘兊兤冝冾凬刕劜劦勀勛匀匇匤卲厓厲叝﨎咜咊咩哿喆坙坥垬埈埇﨏塚增墲夋奓奛奝奣妤妺孖寀甯寘寬尞岦岺峵崧嵓﨑嵂嵭嶸嶹巐弡弴彧德忞恝悅悊惞惕愠惲愑愷愰憘戓抦揵摠撝擎敎昀昕昻昉昮昞昤晥晗晙晴晳暙暠暲暿曺朎朗杦枻桒柀栁桄棏﨓楨﨔榘槢樰橫橆橳橾櫢櫤毖氿汜沆汯泚洄涇浯涖涬淏淸淲淼渹湜渧渼溿澈澵濵瀅瀇瀨炅炫焏焄煜煆煇凞燁燾犱犾猤猪獷玽珉珖珣珒琇珵琦琪琩琮瑢璉璟甁畯皂皜皞皛皦益睆劯砡硎硤硺礰礼神祥禔福禛竑竧靖竫箞精絈絜綷綠緖繒罇羡羽茁荢荿菇菶葈蒴蕓蕙蕫﨟薰蘒﨡蠇裵訒訷詹誧誾諟諸諶譓譿賰賴贒赶﨣軏﨤逸遧郞都鄕鄧釚釗釞釭釮釤釥鈆鈐鈊鈺鉀鈼鉎鉙鉑鈹鉧銧鉷鉸鋧鋗鋙鋐﨧鋕鋠鋓錥錡鋻﨨錞鋿錝錂鍰鍗鎤鏆鏞鏸鐱鑅鑈閒隆﨩隝隯霳喟啻啾喘喞單啼Я‐ёд㍉☒ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ\";\n    Spaces = \" 　\";\n};\n"
  },
  {
    "path": "profiles/cclcc/config.lua",
    "content": "root.Language = \"Japanese\";\nroot.ResolutionWidth = 1920;\nroot.ResolutionHeight = 1080;\nroot.Fullscreen = false;\nroot.SubtitleConfig = SubtitleConfigType.Translation | SubtitleConfigType.Karaoke;\nroot.DateFormat = DateFormatType.YMD;"
  },
  {
    "path": "profiles/cclcc/configsystem.lua",
    "content": "root.ConfigSystem = {\n    ShowTipsNotification = true;\n    AdvanceTextOnDirectionalInput = false;\n    DirectionalInputForTrigger = false;\n    TriggerStopSkip = true;\n\n    TextSpeed = 768 / 60;\n    TextSpeedBounds = { X = 256 / 60, Y = 4096 / 60 };\n    AutoSpeed = 768 / 60;\n    AutoSpeedBounds = { X = 2048 / 60, Y = 256 / 60 };\n\n    SkipRead = true;\n    SyncVoice = true;\n    SkipVoice = false;\n\n    VoiceCount = 13;\n    VoiceMuted = {};\n    VoiceVolume = {};\n};\n\nfor i = 1, root.ConfigSystem.VoiceCount do\n    root.ConfigSystem.VoiceMuted[i] = false;\n    root.ConfigSystem.VoiceVolume[i] = 1;\nend"
  },
  {
    "path": "profiles/cclcc/dialogue.lua",
    "content": "root.Sprites[\"ADVBox\"] = {\n    Sheet = \"MesBox\",\n    Bounds = { X = 0, Y = 1, Width = 1920, Height = 298 }\n};\n\nroot.Sprites[\"ADVBoxMask\"] = {\n    Sheet = \"MesBox\",\n    Bounds = { X = 0, Y = 301, Width = 1920, Height = 298 }\n};\n\nroot.Dialogue = {\n    TipsBounds = { X = 1042, Y = 394, Width = 720, Height = 542 },\n    TipsColorIndex = 10,\n    REVBounds = { X = 547, Y = 0, Width = 1043, Height = 400 },\n    REVNameFontSize = 42,\n    REVColor = 10, -- Absolute guess\n    REVNameColor = 0,\n    REVNameOffset = 18,\n    REVNameLocation = REVNameLocationType.LeftTop,\n    REVOutlineMode = 0,\n    REVNameOutlineMode = 2,\n    NVLBounds = { X = 188, Y = 128, Width = 1536, Height = 600 },\n    ADVBounds = { X = 330, Y = 800, Width = 1250, Height = 270 },\n    ADVBoxSprite = \"ADVBox\",\n    ADVBoxMask = \"ADVBoxMask\",\n    ADVBoxEffectDuration = 10,\n    ADVBoxPos = { X = 0, Y = 760 },\n    FadeOutDuration = 0.33,\n    FadeInDuration = 0.33,\n    TextFadeInDuration = 0.33,\n    TextFadeOutDuration = 0.33,\n    DialogueBoxCurrentType = DialogueBoxType.CC,\n    NVLBoxMaxOpacity = 0.55,\n\n    ADVNameAlignment = TextAlignment.Left,\n    ADVNameFontSize = 33,\n    ADVNamePos = { X = 173, Y = 773 },\n\n    NametagCurrentType = NametagType.CC,\n\n    NametagMainSprites = {},\n    NametagLabelSprites = {},\n\n    NametagMainPos = { X = 0, Y = 780 },\n    NametagLabelPos = { X = 0, Y = 998 },\n\n    NametagShowDuration = 16 / 60;\n\n    WaitIconCurrentType = WaitIconType.SpriteAnimFixed,\n    WaitIconSpriteAnim = \"WaitIconSpriteAnim\",\n    WaitIconAnimationDuration = 0.7,\n    WaitIconOffset = { X = 1595, Y = 895 },\n    WaitIconFixedSpriteId = 6,\n    AutoIconCurrentType = AutoIconType.SpriteAnimFixed,\n    AutoIconSpriteAnim = \"AutoIconSpriteAnim\",\n    AutoIconOffset = { X = 1568, Y = 690 },\n    AutoIconFixedSpriteId = 6,\n    SkipIconCurrentType = SkipIconType.SpriteAnimFixed,\n    SkipIconSpriteAnim = \"SkipIconSpriteAnim\",\n    SkipIconOffset = { X = 1688, Y = 794 },\n    SkipIconFixedSpriteId = 6,\n    DialogueFont = \"Default\",\n    SetFontSizeRatio = 800.0,\n    DefaultFontSize = 42,\n    RubyFontSize = 21,\n    RubyYOffset = -21,\n    ColorTable = {\n        {0xFFFFFF, 0x000000}, {0x5080FF, 0x000000},\n        {0xFF7080, 0x000000}, {0xFFA0F8, 0x000000},\n        {0x46FF80, 0x000000}, {0x90FFFF, 0x000000},\n        {0xFFFF70, 0x000000}, {0x80FFC0, 0x000000},\n        {0xFFB080, 0x000000}, {0xB080FF, 0x000000},\n        {0x000000, 0x808080}, {0x000000, 0x5080FF},\n        {0x000000, 0xFF7080}, {0x000000, 0xFFA0F8},\n        {0x000000, 0x268840}, {0x000000, 0x409999},\n        {0x000000, 0x888830}, {0x000000, 0x80FFC0},\n        {0x000000, 0xFFB080}, {0x000000, 0xB080FF},\n        {0xD0D0D0, 0x000000}, {0xD0D0FF, 0x000000},\n        {0xFFD0D0, 0x000000}, {0xFFD0FF, 0x000000},\n        {0xD0FFD0, 0x000000}, {0xD0FFFF, 0x000000},\n        {0xFFFFD0, 0x000000}, {0xE8FFD0, 0x000000},\n        {0xFFE8D0, 0x000000}, {0xD0E8FF, 0x000000},\n        {0xFFFFFF, 0x808080}, {0xFFFFFF, 0x5080FF},\n        {0xFFFFFF, 0xFF7080}, {0xFFFFFF, 0xFFA0F8},\n        {0xFFFFFF, 0x46FF80}, {0xFFFFFF, 0x90FFFF},\n        {0xFFFFFF, 0xFFFF70}, {0xFFFFFF, 0x80FFC0},\n        {0xFFFFFF, 0xFFB080}, {0xFFFFFF, 0xB080FF},\n        {0xFFEEEE, 0x000000}, {0xFFCCCC, 0x000000},\n        {0xFFAAAA, 0x000000}, {0xFF9999, 0x000000},\n        {0xFF8888, 0x000000}, {0xFFFF00, 0x000000},\n        {0xFEF000, 0x000000}, {0xFF7777, 0x000000},\n        {0xFF6666, 0x000000}, {0xFF5555, 0x000000},\n        {0xFF4444, 0x000000}, {0xFF3333, 0x000000},\n        {0xFF2222, 0x000000}, {0xFF0000, 0x000000},\n        {0xDD0000, 0x000000}, {0xBB0000, 0x000000},\n        {0xB00000, 0x000000}, {0xAA0000, 0x000000},\n        {0x950000, 0x000000}, {0x808080, 0x000000},\n        {0xAAAAAA, 0x000000}, {0xAAC1C9, 0x000000},\n        {0x000000, 0x000000}, {0x000000, 0x000000},\n        {0x000000, 0x000000}, {0x000000, 0x000000},\n        {0x000000, 0x000000}, {0xF80B0B, 0x000000},\n        {0xF8910B, 0x000000}, {0x33F12A, 0x000000}\n    },\n    MaxPageSize = 2000,\n    PageCount = 3,\n    ColorTagIsUint8 = false,\n};\n\nMakeAnimation({\n    Name = \"WaitIconSpriteAnim\",\n    Sheet = \"Data\",\n    FirstFrameX = 864, --1114\n    FirstFrameY = 877,\n    FrameWidth = 190,\n    ColWidth = 216,\n    FrameHeight = 185,\n    RowHeight = 185,\n    Frames = 10,\n    Duration = 1,\n    Rows = 1,\n    Columns = 10,\n    PrimaryDirection = AnimationDirections.Right\n});\n\nMakeAnimation({\n    Name = \"AutoIconSpriteAnim\",\n    Sheet = \"Data\",\n    FirstFrameX = 854,\n    FirstFrameY = 456,\n    FrameWidth = 215,\n    ColWidth = 215,\n    FrameHeight = 220,\n    RowHeight = 220,\n    Frames = 10,\n    Duration = 0.7,\n    Rows = 1,\n    Columns = 10,\n    PrimaryDirection = AnimationDirections.Right\n});\n\nMakeAnimation({\n    Name = \"SkipIconSpriteAnim\",\n    Sheet = \"Data\",\n    FirstFrameX = 854,\n    FirstFrameY = 676,\n    FrameWidth = 215,\n    ColWidth = 215,\n    FrameHeight = 220,\n    RowHeight = 220,\n    Frames = 10,\n    Duration = 0.7,\n    Rows = 1,\n    Columns = 10,\n    PrimaryDirection = AnimationDirections.Right\n})\n\nlocal nametagMainX = 0;\nlocal nametagMainY = 0;\nlocal nametagLabelX = 3600;\nlocal nametagLabelY = 0;\nlocal nametagMainWidth = 448;\nlocal nametagMainHeight = 232;\nlocal nametagLabelWidth = 310;\nlocal nametagLabelHeight = 40;\n\nfor i = 1, 47 do\n    root.Sprites[\"NametagMainSprite\" .. i] = {\n        Sheet = \"NamePlate\",\n        Bounds = {\n            X = nametagMainX,\n            Y = nametagMainY,\n            Width = nametagMainWidth,\n            Height = nametagMainHeight\n        }\n    };\n    root.Dialogue.NametagMainSprites[#root.Dialogue.NametagMainSprites + 1] = \"NametagMainSprite\" .. i;\n\n    if i % 8 == 0 then\n        nametagMainY = nametagMainY + nametagMainHeight;\n        nametagMainX = 0;\n    else\n        nametagMainX = nametagMainX + nametagMainWidth;\n    end\n\n    root.Sprites[\"NametagLabelSprite\" .. i] = {\n        Sheet = \"NamePlate\",\n        Bounds = {\n            X = nametagLabelX,\n            Y = nametagLabelY,\n            Width = nametagLabelWidth,\n            Height = nametagLabelHeight\n        }\n    };\n    root.Dialogue.NametagLabelSprites[#root.Dialogue.NametagLabelSprites + 1] = \"NametagLabelSprite\" .. i;\n\n    nametagLabelY = nametagLabelY + nametagLabelHeight;\nend\n"
  },
  {
    "path": "profiles/cclcc/font-lb-italic.lua",
    "content": "root.Fonts = {\n    [\"Default\"] = {\n        Type = FontType.LB,\n        ForegroundSheet = \"FontLBForeground\",\n        OutlineSheet = \"FontLBOutline\",\n        Columns = 64,\n        Rows = 125,\n        AdvanceWidths = \"games/cclcc/font-lb-italic/widths.bin\",\n        AdvanceWidthsEmWidth = 48,\n        ForegroundOffset = { X = -13, Y = -13 },\n        OutlineOffset = { X = -13, Y = -13 },\n        BitmapEmWidth = 48,\n        BitmapEmHeight = 48,\n        LineSpacing = 5,\n    }\n};\n\nroot.SpriteSheets[\"Font\"] = nil;\n\nroot.SpriteSheets[\"FontLBForeground\"] = {\n    Path = \"games/cclcc/font-lb-italic/foreground.png\",\n    DesignWidth = 4096,\n    DesignHeight = 8000\n};\n\nroot.SpriteSheets[\"FontLBOutline\"] = {\n    Path = \"games/cclcc/font-lb-italic/outline.png\",\n    DesignWidth = 4096,\n    DesignHeight = 8000\n};\n"
  },
  {
    "path": "profiles/cclcc/font-lb.lua",
    "content": "root.Fonts = {\n    [\"Default\"] = {\n        Type = FontType.LB,\n        ForegroundSheet = \"FontLBForeground\",\n        OutlineSheet = \"FontLBOutline\",\n        Columns = 64,\n        Rows = 125,\n        AdvanceWidths = \"games/cc/font-lb/widths.bin\",\n        AdvanceWidthsEmWidth = 48,\n        OutlineOffset = { X = -4, Y = -4 },\n        LineSpacing = 14,\n    }\n};\n\nroot.SpriteSheets[\"Font\"] = nil;\n\nroot.SpriteSheets[\"FontLBForeground\"] = {\n    Path = \"games/cc/font-lb/foreground.png\",\n    DesignWidth = 3072,\n    DesignHeight = 6000\n};\n\nroot.SpriteSheets[\"FontLBOutline\"] = {\n    Path = \"games/cc/font-lb/outline.png\",\n    DesignWidth = 3648,\n    DesignHeight = 7128\n};"
  },
  {
    "path": "profiles/cclcc/font.lua",
    "content": "root.Fonts = {\n    [\"Default\"] = {\n        Type = FontType.Basic,\n        Sheet = \"Font\",\n        Columns = 64,\n        Rows = 117,\n        AdvanceWidths = {},\n        AdvanceWidthsEmWidth = 48,\n        LineSpacing = 22,\n    }\n};\n\nlocal westernAdvanceWidths = {\n    [0]=0x20, 0x18, 0x0D, 0x18, 0x17, 0x1A, 0x16, 0x17, 0x17, 0x18, 0x17, 0x1C,\n        0x19, 0x1A, 0x1A, 0x16, 0x15, 0x1A, 0x1A, 0x08, 0x13, 0x1A, 0x17, 0x1E,\n        0x1A, 0x1C, 0x18, 0x1C, 0x1A, 0x18, 0x1C, 0x1A, 0x1C, 0x20, 0x1C, 0x1A,\n        0x1A, 0x14, 0x17, 0x16, 0x17, 0x16, 0x12, 0x16, 0x15, 0x09, 0x0F, 0x16,\n        0x08, 0x1E, 0x16, 0x16, 0x17, 0x17, 0x11, 0x14, 0x12, 0x16, 0x16, 0x1E,\n        0x16, 0x16, 0x14, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0C, 0x14, 0x08, 0x14,\n        0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,\n        0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x14, 0x11, 0x11, 0x11,\n        0x11, 0x11, 0x11, 0x11, 0x20, 0x15, 0x16, 0x16, 0x17, 0x17, 0x14, 0x11,\n        0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,\n        0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x18, 0x0D, 0x18, 0x17,\n        0x1A, 0x16, 0x17, 0x17, 0x18, 0x17, 0x1C, 0x19, 0x1A, 0x1A, 0x16, 0x15,\n        0x1A, 0x1A, 0x08, 0x13, 0x1A, 0x17, 0x1E, 0x1A, 0x1C, 0x18, 0x1C, 0x1A,\n        0x18, 0x1C, 0x1A, 0x1C, 0x20, 0x1C, 0x1A, 0x1A, 0x14, 0x17, 0x16, 0x17,\n        0x16, 0x12, 0x16, 0x15, 0x09, 0x0F, 0x16, 0x08, 0x1E, 0x16, 0x16, 0x17,\n        0x17, 0x11, 0x14, 0x12, 0x16, 0x16, 0x1E, 0x16, 0x16, 0x14, 0x14, 0x14,\n        0x14, 0x14, 0x09, 0x09, 0x14, 0x0C, 0x0B, 0x0A, 0x08, 0x08, 0x0D, 0x0C,\n        0x0C, 0x0E, 0x0D, 0x0C, 0x0C, 0x0D, 0x0C, 0x0C, 0x0F, 0x0F, 0x11, 0x11,\n        0x0D, 0x0D, 0x0E, 0x10, 0x0C, 0x0C, 0x1A, 0x1A, 0x0C, 0x0C, 0x10, 0x20,\n        0x20, 0x20, 0x1C, 0x20, 0x1A, 0x19, 0x16, 0x1A, 0x19, 0x18, 0x19, 0x19,\n        0x18, 0x19, 0x19, 0x18, 0x18, 0x1A, 0x1A, 0x19, 0x1A, 0x1A, 0x15, 0x16,\n        0x17, 0x1A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0C, 0x20, 0x20, 0x20, 0x20,\n        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20\n};\n\nfor i = 0, (64 * 117) - 1 do\n    if i <= #westernAdvanceWidths then\n        root.Fonts[\"Default\"].AdvanceWidths[i] = westernAdvanceWidths[i] * 1.5;\n    else\n        root.Fonts[\"Default\"].AdvanceWidths[i] = root.Fonts[\"Default\"].AdvanceWidthsEmWidth;\n    end\nend"
  },
  {
    "path": "profiles/cclcc/game.lua",
    "content": "root.ActiveRenderer = RendererType.OpenGL;\n\nroot.LayerCount = 100;\nroot.GameFeatures = GameFeature.Sc3VirtualMachine | GameFeature.Renderer2D | GameFeature.Input | GameFeature.Audio |\n                        GameFeature.Video | GameFeature.Subtitles | GameFeature.DebugMenu;\nroot.DesignWidth = 1920;\nroot.DesignHeight = 1080;\n\nroot.WindowName = \"CHAOS;CHILD Love Chu☆Chu!!\";\nroot.WindowIconPath = \"games/cclcc/icondata/icon.png\";\nroot.CursorArrowPath = \"games/cclcc/icondata/cursor_arrow.png\";\nroot.CursorPointerPath = \"games/cclcc/icondata/cursor_pointer.png\";\n\nroot.CharaIsMvl = true;\nroot.UseMoviePriority = true;\nroot.UseBgChaEffects = true;\nroot.UseBgFrameEffects = false;\nroot.UseWaveEffects = true;\n\nroot.ScreenCaptureCount = 2;\n\nroot.Vm = {\n    StartScript = 1,\n    StartScriptBuffer = 0,\n    GameInstructionSet = InstructionSet.CC,\n    UseReturnIds = true,\n\n    ScrWorkChaStructSize = 40,\n    ScrWorkChaOffsetStructSize = 10,\n    ScrWorkBgStructSize = 40,\n    ScrWorkBgOffsetStructSize = 10,\n    ScrWorkCaptureStructSize = 20,\n    ScrWorkCaptureOffsetStructSize = 10,\n    ScrWorkCaptureEffectInfoStructSize = 3,\n    ScrWorkBgEffStructSize = 30,\n    ScrWorkBgEffOffsetStructSize = 20,\n\n    MaxLinkedBgBuffers = 2\n};\n\nroot.PlatformId = 131072;\n\ninclude('common/scriptinput.lua');\ninclude('common/scriptvars.lua');\n\ninclude('cclcc/scriptinput.lua');\ninclude('cclcc/scriptvars.lua');\ninclude('cclcc/config.lua');\ninclude('cclcc/sprites.lua');\ninclude('cclcc/savedata.lua');\ninclude('cclcc/tipssystem.lua');\ninclude('cclcc/vfs.lua');\ninclude('cclcc/charset.lua');\ninclude('cclcc/gamespecific.lua');\ninclude('common/animation.lua');\ninclude('common/charset.lua');\n--include('cclcc/font.lua');\n--include('cclcc/font-lb.lua');\ninclude('cclcc/font-lb-italic.lua');\ninclude('cclcc/dialogue.lua');\ninclude('cclcc/configsystem.lua');\ninclude('cclcc/bgeff.lua');\ninclude('cclcc/waveeffects.lua');\ninclude('cclcc/hud/saveicon.lua');\ninclude('cclcc/hud/loadingdisplay.lua');\ninclude('cclcc/hud/datedisplay.lua');\ninclude('cclcc/hud/titlemenu.lua');\ninclude('cclcc/hud/backlogmenu.lua');\ninclude('cclcc/hud/sysmesboxdisplay.lua');\ninclude('cclcc/hud/selectiondisplay.lua');\ninclude('cclcc/hud/optionsmenu.lua');\ninclude('cclcc/hud/tipsmenu.lua');\ninclude('cclcc/hud/extramenus.lua');\ninclude('cclcc/hud/tipsnotification.lua');\ninclude('cclcc/hud/systemmenu.lua');\ninclude('cclcc/hud/savemenu.lua');\ninclude('cclcc/hud/helpmenu.lua');"
  },
  {
    "path": "profiles/cclcc/gamespecific.lua",
    "content": "root.GameSpecific = {\n  Type = GameSpecificType.CCLCC,\n  UIButtonGuideSprites = {},\n  UIButtonGuideEndDisp = {\n    X = 0, Y = 988, Width = 60, Height = 54\n  }\n}\n\ninclude('cclcc/mapsystem.lua');\ninclude('cclcc/yesnotrigger.lua');\ninclude('cclcc/hud/delusiontrigger.lua');\n\nfor i = 0, 1 do\n    local index = i + 1;\n    root.Sprites[\"UIButtonGuide\" .. i] = {\n        Sheet = \"Data\",\n        Bounds = { X = 0, Y = 1121 + 56 * i, Width = 1920, Height = 54 },\n    };\n    root.GameSpecific.UIButtonGuideSprites[index] = \"UIButtonGuide\" .. i;\nend"
  },
  {
    "path": "profiles/cclcc/hud/backlogmenu.lua",
    "content": "root.BacklogMenu = {\n    Type = BacklogMenuType.CC,\n    DrawType = DrawComponentType.SystemMenu,\n\n    BacklogBackgroundSprite = \"BacklogBackground\",\n    BacklogBackgroundRepeatHeight = 1005,\n\n    BacklogHeaderSprite = \"BacklogHeader\",\n    BacklogHeaderPosition = { X = 0, Y = 0 },\n\n    BacklogControlsSprite = \"BacklogControls\",\n    BacklogControlsPosition = { X = 0, Y = 986 },\n\n    EntryHighlightSprite = \"EntryHighlight\",\n    EntryHighlightLocation = EntryHighlightLocationType.TopLineLeftOfScreen,\n    EntryHighlightOffset = { X = 0, Y = -3 },\n    EntryHighlightPadding = 4.0,\n\n    VoiceIconSprite = \"VoiceIcon\",\n    VoiceIconOffset = { X = -16, Y = 0 },\n\n    ScrollbarTrackSprite = \"ScrollbarTrack\",\n    ScrollbarThumbSprite = \"ScrollbarThumb\",\n    ScrollbarPosition = { X = 1656, Y = 40 },\n    ScrollbarThumbLength = 87,\n\n    EntriesStart = { X = 547, Y = 149 },\n    RenderingBounds = { X = 0, Y = 121, Width = 1920, Height = 868 },\n    EntryYPadding = 26,\n\n    FadeInDuration = 0.5,\n    FadeOutDuration = 0.5,\n    FadeInDirectDuration = 0.25,\n    FadeOutDirectDuration = 0.25,\n\n    ScrollingSpeed = 900,\n    PageUpDownHeight = 765,\n\n    MenuMask = \"MenuMask\",\n    BacklogMask = \"BacklogMask\",\n    HoverBounds = { X = 380, Y = 145, Width = 1230, Height = 820 }\n};\n\nroot.Sprites[\"BacklogBackground\"] = {\n    Sheet = \"Backlog\",\n    Bounds = { X = 0, Y = 0, Width = 1920, Height = 1080 },\n};\n\nroot.Sprites[\"BacklogHeader\"] = {\n    Sheet = \"Backlog\",\n    Bounds = { X = 962, Y = 1210, Width = 542, Height = 542 },\n};\n\nroot.SpriteSheets[\"BacklogMask\"] = {\n    Path = { Mount = \"system\", Id = 2 },\n    DesignWidth = 1920,\n    DesignHeight = 1080\n};\n\nroot.Sprites[\"BacklogControls\"] = {\n    Sheet = \"Backlog\",\n    Bounds = { X = 0, Y = 1151, Width = 1920, Height = 59 },\n}\n\nroot.Sprites[\"MenuMask\"] = {\n    Sheet = \"MenuChip\",\n    Bounds = { X = 154, Y = 140, Width = 1900, Height = 1061 },\n};\n\nroot.Sprites[\"VoiceIcon\"] = {\n    Sheet = \"Backlog\",\n    Bounds = { X = 1521, Y = 1218, Width = 38, Height = 38 },\n};\n\nroot.Sprites[\"EntryHighlight\"] = {\n    Sheet = \"Backlog\",\n    Bounds = { X = 0, Y = 1105, Width = 1920, Height = 44 }\n};\n\nroot.Sprites[\"ScrollbarThumb\"] = {\n    Sheet = \"Backlog\",\n    Bounds = { X = 2027, Y = 1115, Width = 18, Height = 83 },\n};\n\nroot.Sprites[\"ScrollbarTrack\"] = {\n    Sheet = \"Backlog\",\n    Bounds = { X = 2030, Y = 41, Width = 11, Height = 915 },\n};"
  },
  {
    "path": "profiles/cclcc/hud/datedisplay.lua",
    "content": "local sheet = \"Data\";\nlocal name = \"Date\";\n\nlocal yearNumFirstX = 2287.5;\nlocal yearNumFirstY = 94.5;\nlocal yearNumWidth = 37.5;\nlocal yearNum1Width = 12;\nlocal yearNumHeight = 30;\n\nlocal numFirstX = 2286;\nlocal numFirstY = 49.5;\nlocal numWidth = 60;\nlocal num1Width = 18;\nlocal numHeight = 42;\n\nlocal weekFirstX = 2677.5;\nlocal weekFirstY = 94.5;\nlocal weekSecondX = 2287.5;\nlocal weekSecondY = 127.5;\nlocal weekWidth = 109.5;\nlocal weekFriWidth = 84;\nlocal weekHeight = 30;\n\nroot.DateDisplay = {\n    Type = DateDisplayType.RNE,\n    YearNumSprites = {},\n    MonthNumSprites = {},\n    DayNumSprites = {},\n    WeekSprites = {},\n    BackgroundStartPos = { X = 1632, Y = 109.5 },\n    BackgroundEndPos = { X = 1632 - 384, Y = 109.5 },\n    DateStartX = 1750.5,\n    YearWeekY = 90,\n    MonthDayY = 78,\n    Spacing = 1.5,\n    FadeInDuration = 0.5,\n    FadeOutDuration = 0.5\n};\n\nfor i = 0, 9 do\n    local yearW;\n    if i == 1 then\n        yearW = yearNum1Width;\n    else\n        yearW = yearNumWidth;\n    end\n\n    root.Sprites[name .. \"YearNum\" .. i] = {\n        Sheet = sheet,\n        Bounds = {\n            X = yearNumFirstX,\n            Y = yearNumFirstY,\n            Width = yearW,\n            Height = yearNumHeight\n        }\n    };\n    root.DateDisplay.YearNumSprites[#root.DateDisplay.YearNumSprites + 1] = name .. \"YearNum\" .. i;\n\n    local numW\n    if i == 1 then\n        numW = num1Width;\n    else\n        numW = numWidth;\n    end\n\n    root.Sprites[name .. \"Num\" .. i] = {\n        Sheet = sheet,\n        Bounds = {\n            X = numFirstX,\n            Y = numFirstY,\n            Width = numW,\n            Height = numHeight\n        }\n    };\n    root.DateDisplay.MonthNumSprites[#root.DateDisplay.MonthNumSprites + 1] = name .. \"Num\" .. i;\n    root.DateDisplay.DayNumSprites[#root.DateDisplay.DayNumSprites + 1] = name .. \"Num\" .. i;\n\n    if i == 1 then\n        yearNumFirstX = yearNumFirstX + yearNum1Width;\n        numFirstX = numFirstX + num1Width;\n    else\n        yearNumFirstX = yearNumFirstX + yearNumWidth;\n        numFirstX = numFirstX + numWidth;\n    end\nend\n\nfor i = 0, 6 do\n    if i > 1 then\n        local weekW;\n        if i == 5 then\n            weekW = weekFriWidth;\n        else\n            weekW = weekWidth;\n        end\n\n        root.Sprites[name .. \"Week\" .. i] = {\n            Sheet = sheet,\n            Bounds = {\n                X = weekSecondX,\n                Y = weekSecondY,\n                Width = weekW,\n                Height = weekHeight\n            }\n        };\n        if i == 5 then\n            weekSecondX = weekSecondX + weekFriWidth;\n        else\n            weekSecondX = weekSecondX + weekWidth;\n         end\n    else\n        root.Sprites[name .. \"Week\" .. i] = {\n            Sheet = sheet,\n            Bounds = {\n                X = weekFirstX,\n                Y = weekFirstY,\n                Width = weekWidth,\n                Height = weekHeight\n            }\n        };\n        weekFirstX = weekFirstX + weekWidth;\n    end\n    root.DateDisplay.WeekSprites[#root.DateDisplay.WeekSprites + 1] = name .. \"Week\" .. i;\nend\n\nroot.Sprites[name .. \"MonthDaySeparator\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 2845.5,\n        Y = 49.5,\n        Width = 15,\n        Height = 42\n    }\n};\nroot.DateDisplay.MDSeparatorSprite = name .. \"MonthDaySeparator\";\n\nroot.Sprites[name .. \"DayYearSeparator\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 2637,\n        Y = 94.5,\n        Width = 12,\n        Height = 30\n    }\n};\nroot.DateDisplay.DYSeparatorSprite = name .. \"DayYearSeparator\";\n\nroot.Sprites[name .. \"OpenBracket\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 2649,\n        Y = 94.5,\n        Width = 12,\n        Height = 30\n    }\n};\nroot.DateDisplay.OpenBracketSprite = name .. \"OpenBracket\";\n\nroot.Sprites[name .. \"CloseBracket\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 2664,\n        Y = 94.5,\n        Width = 12,\n        Height = 30\n    }\n};\nroot.DateDisplay.CloseBracketSprite = name .. \"CloseBracket\";\n\nroot.Sprites[name .. \"Background\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 2287.5,\n        Y = 2,\n        Width = 675,\n        Height = 42\n    }\n};\nroot.DateDisplay.BackgroundSprite = name .. \"Background\";"
  },
  {
    "path": "profiles/cclcc/hud/delusiontrigger.lua",
    "content": "root.DelusionTrigger = {\n};\n"
  },
  {
    "path": "profiles/cclcc/hud/extramenus.lua",
    "content": "-- PgNumber, GridThumbnailPos, {X, Y, Width, Height} in album sheet for each variant\nlocal AlbumTbl = {\n    { 0, 0, {{0x0, 0x0, 0x154, 0xC5}}},\n    { 0, 1, {{0x1, 0x0, 0x154, 0xC5}, {0x2, 0x0, 0x154, 0xC5}}},\n    { 0, 2, {{0x6, 0x0, 0x154, 0xC5}}},\n    { 0, 3, {{0x7, 0x0, 0x154, 0xC5}}},\n    { 0, 4, {{0x3, 0x0, 0x154, 0x174}, {0x4, 0x0, 0x154, 0x174}, {0x5, 0x0, 0x154, 0x174}}},\n    { 0, 5, {{0x8, 0x0, 0x154, 0xC5}}},\n    { 0, 6, {{0x9, 0x0, 0x154, 0xC5}}},\n    { 0, 7, {{0xA, 0x0, 0x154, 0xC5}}},\n    { 0, 9, {{0xB, 0x0, 0x154, 0xC5}, {0x0, 0x1, 0x154, 0xC5}}},\n    { 0, 10, {{0x1, 0x1, 0x154, 0xC5}}},\n    { 0, 11, {{0x6, 0x1, 0x154, 0xC5}, {0x7, 0x1, 0x154, 0xC5}}},\n    { 1, 0, {{0x8, 0x1, 0x154, 0xC5}}},\n    { 1, 1, {{0x9, 0x1, 0x154, 0xC5}}},\n    { 1, 2, {{0xA, 0x1, 0x154, 0xC5}, {0xB, 0x1, 0x154, 0xC5}}},\n    { 1, 3, {{0x0, 0x2, 0x154, 0xC5}}},\n    { 1, 4, {{0x4, 0x2, 0x154, 0xC5}}},\n    { 1, 5, {{0x5, 0x2, 0x154, 0xC5}, {0x6, 0x2, 0x154, 0xC5}}},\n    { 1, 6, {{0x7, 0x2, 0x154, 0xC5}}},\n    { 1, 7, {{0x0, 0xA, 0x1B8, 0x1F0}, {0x4, 0xA, 0x1B8, 0x1F0}}},\n    { 1, 8, {{0x8, 0x2, 0x154, 0xC5}}},\n    { 1, 9, {{0x9, 0x2, 0x154, 0xC5}, {0x1, 0x2, 0x154, 0xC5}}},\n    { 1, 10, {{0x2, 0x3, 0x154, 0xC5}}},\n    { 2, 0, {{0x2, 0x1, 0x154, 0x174}}},\n    { 2, 1, {{0x5, 0x3, 0x154, 0xC5}}},\n    { 2, 2, {{0x4, 0x3, 0x154, 0xC5}}},\n    { 2, 3, {{0x8, 0x9, 0x1B8, 0xF8}}},\n    { 2, 5, {{0x6, 0x3, 0x154, 0xC5}, {0x7, 0x3, 0x154, 0xC5}, {0x8, 0x3, 0x154, 0xC5}}},\n    { 2, 6, {{0x9, 0x3, 0x154, 0xC5}}},\n    { 2, 7, {{0xB, 0x3, 0x154, 0x174}, {0x0, 0x4, 0x154, 0xC5}}},\n    { 2, 8, {{0x1, 0x4, 0x154, 0xC5}, {0x3, 0x4, 0x154, 0xC5}, {0x4, 0x4, 0x154, 0xC5}, {0x5, 0x4, 0x154, 0xC5}}},\n    { 2, 9, {{0xA, 0x3, 0x154, 0xC5}, {0x0, 0x3, 0x154, 0xC5}}},\n    { 2, 10, {{0x7, 0x4, 0x154, 0xC5}, {0x8, 0x4, 0x154, 0xC5}, {0x9, 0x4, 0x154, 0xC5}}},\n    { 3, 0, {{0xA, 0x2, 0x154, 0xC5}, {0xB, 0x2, 0x154, 0xC5}, {0x3, 0x2, 0x154, 0xC5}, {0x1, 0x3, 0x154, 0xC5}}},\n    { 3, 1, {{0xA, 0x4, 0x154, 0xC5}, {0x0, 0x5, 0x154, 0xC5}}},\n    { 3, 2, {{0x1, 0x5, 0x154, 0xC5}}},\n    { 3, 3, {{0x9, 0x5, 0x154, 0xC5}, {0xA, 0x5, 0x154, 0xC5}}},\n    { 3, 4, {{0x3, 0x5, 0x154, 0x174}}},\n    { 3, 5, {{0x4, 0x5, 0x154, 0xC5}, {0x5, 0x5, 0x154, 0xC5}, {0x6, 0x5, 0x154, 0xC5}, {0x7, 0x5, 0x154, 0xC5}}},\n    { 3, 6, {{0x8, 0x5, 0x154, 0xC5}}},\n    { 3, 7, {{0x2, 0x5, 0x154, 0x118}}},\n    { 3, 9, {{0x5, 0x6, 0x154, 0xC5}, {0x6, 0x6, 0x154, 0xC5}}},\n    { 3, 10, {{0x7, 0x6, 0x154, 0xC5}}},\n    { 3, 11, {{0x8, 0x6, 0x154, 0xC5}}},\n    { 4, 0, {{0xB, 0x5, 0x154, 0xC5}, {0x0, 0x6, 0x154, 0xC5}, {0x1, 0x6, 0x154, 0xC5}, {0x4, 0x6, 0x154, 0xC5}}},\n    { 4, 1, {{0x9, 0x6, 0x154, 0xC5}}},\n    { 4, 2, {{0xA, 0x6, 0x154, 0xC5}, {0xB, 0x6, 0x154, 0xC5}}},\n    { 4, 3, {{0x0, 0x7, 0x154, 0xC5}}},\n    { 4, 4, {{0x1, 0x7, 0x154, 0xC5}}},\n    { 4, 5, {{0x2, 0x7, 0x154, 0xC5}}},\n    { 4, 6, {{0x3, 0x7, 0x154, 0xC5}, {0x4, 0x7, 0x154, 0xC5}}},\n    { 4, 7, {{0x5, 0x7, 0x154, 0xC5}, {0x6, 0x7, 0x154, 0xC5}}},\n    { 4, 8, {{0x7, 0x7, 0x154, 0xC5}, {0x8, 0x7, 0x154, 0xC5}}},\n    { 4, 9, {{0x9, 0x7, 0x154, 0xC5}}},\n    { 4, 10, {{0xA, 0x7, 0x154, 0xC5}}},\n    { 4, 11, {{0xB, 0x7, 0x154, 0xC5}, {0x0, 0x8, 0x154, 0xC5}}},\n    { 5, 0, {{0x1, 0x8, 0x154, 0xC5}}},\n    { 5, 1, {{0x2, 0x8, 0x154, 0xC5}}},\n    { 5, 2, {{0x3, 0x8, 0x154, 0xC5}}},\n    { 5, 3, {{0x4, 0x8, 0x154, 0xC5}}},\n    { 5, 4, {{0x5, 0x8, 0x154, 0xC5}}},\n    { 5, 5, {{0x6, 0x8, 0x154, 0xC5}, {0x7, 0x8, 0x154, 0xC5}}},\n    { 5, 6, {{0x8, 0x8, 0x154, 0xC5}, {0x9, 0x8, 0x154, 0xC5}, {0xA, 0x8, 0x154, 0xC5}, {0xB, 0x8, 0x154, 0xC5}}},\n    { 5, 7, {{0x0, 0x9, 0x154, 0xC5}, {0x1, 0x9, 0x154, 0xC5}, {0x2, 0x9, 0x154, 0xC5}}},\n    { 5, 8, {{0x3, 0x9, 0x154, 0x174}}},\n    { 5, 10, {{0x4, 0x9, 0x154, 0xC5}}},\n};\n\nroot.ExtraMenus = {\n    ClearListMenu = {\n        DrawType = DrawComponentType.SystemMenu,\n        Type = ClearListMenuType.CCLCC,\n        FadeInDuration = 0.4,\n        FadeOutDuration = 0.4,\n        ClearListBookLayerSprite = \"ClearListBookLayer\",\n        ClearListGuideSprite = \"ClearListGuide\",\n        ClearListGuidePosition = {X = 0, Y = 989 },\n        ClearListMaskSprite = \"ClearListMask\",\n        EndingSprites = {\n            \"ItouBadEnd\", \"NonoNormalEnd\", \"NonoGoodEnd\", \"HinaeNormalEnd\", \"HinaeGoodEnd\", \"YuiYutoNormalEnd\",\n            \"YuiYutoGoodEnd\", \"HanaNormalEnd\", \"SerikaNormalEnd\", \"SerikaTrueEnd\"\n        },\n        EndingSpriteOffsetY = 1080.0,\n        MenuOffsetY = 26.0,\n    },\n    LibraryMenu = {\n        DrawType = DrawComponentType.SystemMenu,\n        Type = LibraryMenuType.CCLCC,\n        FadeInDuration = 0.4,\n        FadeOutDuration = 0.4,\n        ButtonBlinkDuration = 0.8,\n        ButtonBlinkTintMinimum = 0.2,\n        LibraryTransitionPositionOffset = 600.0,\n        LibraryBackgroundSprite = \"LibraryBackground\",\n        LibraryBackgroundPosition = { X = 291, Y = 0 },\n        LibraryIndexSprite = \"LibraryIndex\",\n        LibraryIndexPosition = { X = 0, Y = 31 },    \n        LibraryButtonGuidePosition = { X = 0, Y = 989 },\n        LibraryMaskSprite = \"LibraryMask\",\n        LibraryMaskAlpha = 0.8,\n\n        SnapPhotoSpriteHover = \"SnapPhotoHover\",\n        SnapPhotoSpriteSelect = \"SnapPhotoSelect\",\n        HitSongsSpriteHover = \"HitSongsHover\",\n        HitSongsSpriteSelect = \"HitSongsSelect\",\n        LoveMovieSpriteHover = \"LoveMovieHover\",\n        LoveMovieSpriteSelect = \"LoveMovieSelect\",\n\n        AlbumMenuGuideSprite = \"AlbumMenuGuide\",\n        AlbumMenuCGViewerGuideSprite = \"AlbumMenuCGViewerGuide\",\n        MusicMenuGuideSprite = \"MusicMenuGuide\",\n        MovieMenuGuideSprite = \"MovieMenuGuide\",\n        \n        SnapPhotoPos = {X=0, Y=80},\n        HitSongsPos = {X=0, Y=382},\n        LoveMoviePos = {X=0, Y=684},\n\n        SubMenuFadeInDuration = 0.2,\n        SubMenuFadeOutDuration = 0.2,\n\n        AlbumThumbnailThumbBlinkDuration = 0.5,\n        AlbumMaxThumbnailsPerPage = 12,\n        AlbumMaxPageCount = 6,\n        AlbumCameraPageIconSprite = \"AlbumCameraPageIcon\",\n        AlbumCameraPageIconPosition = { X = 1731.0, Y = 886.0 },\n        AlbumPageNumberSprites = {},\n        AlbumThumbnailPinSprites = {},\n        AlbumThumbnailThumbSprite = \"AlbumThumbnailThumb\", \n        AlbumThumbDispPos = {\n            {X = 656.0, Y = 167.0},\n            {X = 971.0, Y = 247.0},\n            {X = 1234.0, Y = 120.0},\n            {X = 1556.0, Y = 231.0},\n            {X = 682.0, Y = 390.0},\n            {X = 1009.0, Y = 485.0},\n            {X = 1328.0, Y = 366.0},\n            {X = 1496.0, Y = 553.0},\n            {X = 598.0, Y = 610.0},\n            {X = 763.0, Y = 795.0},\n            {X = 1089.0, Y = 694.0},\n            {X = 1356.0, Y = 794.0},\n        },\n        AlbumTbl = AlbumTbl,\n        AlbumItemFadeDuration = 16/60,\n        AlbumPgChangeDuration = 28/60,\n        AlbumModeChangeDuration = 32/60,\n        AlbumThumbnailPinRemoveOffset = { X = 20, Y = 20 },\n        AlbumPageNumberPositions = {\n            { X = 1787.0, Y = 887.0 },\n            { X = 1826.0, Y = 887.0 },\n        },\n        \n        MusicItemsBackgroundSprite = \"MusicItemsBackground\",\n        MusicItemsBackgroundRepeatHeight = 1200,\n        MusicItemsOverlaySprite = \"MusicItemsOverlay\",\n        MusicItemsCount = 61,\n        MusicItemPadding = 60,\n        MusicRenderingBounds = {\n            X = 293,\n            Y = 20,\n            Width = 1527,\n            Height = 1080\n        },\n        MusicHoverBounds = {\n            X = 293,\n            Y = 40,\n            Width = 1527,\n            Height = 900\n        },\n        MusicBGMFlagIds =  { \n            0x65, 0x67, 0x69, 0x6b, 0x6d, 0x6f, 0x71, 0x73, 0x75, 0x77, 0x79, 0x7b,\n            0x7d, 0x7f, 0x81, 0x01, 0x03, 0x05, 0x06, 0x09, 0x0b, 0x0c, 0x0f, 0x10,\n            0x13, 0x15, 0x16, 0x19, 0x1a, 0x1d, 0x1e, 0x21, 0x23, 0x24, 0x27, 0x29,\n            0x2b, 0x2c, 0x30, 0x33, 0x34, 0x37, 0x39, 0x3b, 0x3d, 0x3f, 0x41, 0x42,\n            0x45, 0x47, 0x49, 0x4b, 0x4d, 0x51, 0x55, 0x57, 0x5e, 0x53, 0x60, 0x83,\n            0x84 \n        },\n        MusicPlayIds =  { \n            0x66, 0x68, 0x6a, 0x6c, 0x6e, 0x70, 0x72, 0x74, 0x76, 0x78, 0x7a, 0x7c, \n            0x7e, 0x80, 0x82, 0x02, 0x04, 0x08, 0x07, 0x0a, 0x0e, 0x0d, 0x12, 0x11, \n            0x14, 0x18, 0x17, 0x1c, 0x1b, 0x20, 0x1f, 0x22, 0x26, 0x25, 0x28, 0x2a, \n            0x2e, 0x2d, 0x31, 0x36, 0x35, 0x38, 0x3a, 0x3c, 0x3e, 0x40, 0x44, 0x43, \n            0x46, 0x48, 0x4a, 0x4c, 0x4e, 0x52, 0x56, 0x58, 0x5f, 0x54, 0x61, 0x83,\n            0x84 \n        },\n        MusicStringTableId = 4,\n        MusicStringLockedIndex = 3,\n        MusicTrackNameSize = 30,\n        MusicTrackNameOffsetX = 160,\n        MusicTrackNumberOffsetX = 80,\n        MusicTrackArtistSize = 20,\n        MusicTrackArtistOffsetX = 800,\n        MusicButtonTextYOffset = 20,\n        MusicButtonPlayingDispOffset = {X = 55, Y = 10},\n        MusicButtonBounds = {\n            X = 320,\n            Y = 20,\n            Width = 1460,\n            Height = 60\n        },\n        MusicDirectionalHoldTime = 0.2,\n        MusicDirectionalFocusTimeInterval = 0.05,\n        MusicButtonHoverSprite = \"MusicButtonHover\",\n        MusicButtonSelectSprite = \"MusicButtonSelect\",\n        MusicButtonPlayingSprite = \"MusicButtonPlaying\",\n        MusicButtonTextColor = 0x4f4f4b,\n        MusicButtonTextOutlineColor = 0x0,\n\n        MusicNowPlayingNotificationSprite = \"MusicNowPlayingNotification\",\n        MusicNowPlayingNotificationPos = {X = 1096, Y = 30},\n        MusicNowPlayingNotificationFadeIn = 0.3,\n        MusicNowPlayingNotificationFadeOut = 0.3,\n        MusicNowPlayingNotificationTrackOffset = {X = 20, Y = 80},\n        MusicNowPlayingNotificationTrackFontSize = 36,\n        MusicNowPlayingTextColor = 0xffffff,\n        MusicNowPlayingTextOutlineColor = 0xffffff,\n        MusicPlayingModeSprites = {},\n        MusicPlayingModeDisplayBounds = {},\n\n        MovieDiskSprites = {},\n        MovieDiskHighlightSprites = {},\n        MovieDiskDisplayPositions = {\n            {X = 388, Y= 138},\n            {X = 956, Y= -64},\n            {X = 830, Y= 578},\n            {X = 1422, Y= 412},\n        },\n        MovieDiskPlayIds = {54, 53, 52, 51},\n\n        MovieExtraVideosEnabled = true,\n        MovieDiskExtraOp = \"MovieDiskExtraOp\",\n        MovieDiskExtraOpHighlight = \"MovieDiskExtraOpHighlight\",\n        MovieDiskExtraOpPlayId = 62\n    }\n};\n\n-- Common\nroot.Sprites[\"LibraryMask\"] = {\n    Sheet = \"MenuChip\",\n    Bounds = { X = 154, Y = 140, Width = 1900, Height = 1061 },\n};\n\n-- ClearList\nroot.Sprites[\"ClearListBookLayer\"] = {\n    Sheet = \"ClearList\",\n    Bounds = { X = 0, Y = 0, Width = 1920, Height = 1080 }\n};\n\nroot.Sprites[\"ClearListGuide\"] = {\n    Sheet = \"ClearList\",\n    Bounds = { X = 0, Y = 2265, Width = 1925, Height = 58 }\n};\n\nroot.Sprites[\"ClearListMask\"] = {\n    Sheet = \"ClearList\",\n    Bounds = { X = 2048, Y = 0, Width = 1920, Height = 1080 },\n};\n\nroot.Sprites[\"ItouBadEnd\"] = {\n    Sheet = \"ClearList\",\n    Bounds = { X = 1022, Y = 1660, Width = 260, Height = 287 }\n};\n\nroot.Sprites[\"NonoNormalEnd\"] = {\n    Sheet = \"ClearList\",\n    Bounds = { X = 129, Y = 1901, Width = 225, Height = 183 }\n};\n\nroot.Sprites[\"NonoGoodEnd\"] = {\n    Sheet = \"ClearList\",\n    Bounds = { X = 306, Y = 1182, Width = 184, Height = 458 }\n};\n\nroot.Sprites[\"HinaeNormalEnd\"] = {\n    Sheet = \"ClearList\",\n    Bounds = { X = 358, Y = 1713, Width = 225, Height = 183 }\n};\n\nroot.Sprites[\"HinaeGoodEnd\"] = {\n    Sheet = \"ClearList\",\n    Bounds = { X = 674, Y = 1182, Width = 184, Height = 465 }\n};\n\nroot.Sprites[\"YuiYutoNormalEnd\"] = {\n    Sheet = \"ClearList\",\n    Bounds = { X = 358, Y = 1901, Width = 225, Height = 183 }\n};\n\nroot.Sprites[\"YuiYutoGoodEnd\"] = {\n    Sheet = \"ClearList\",\n    Bounds = { X = 123, Y = 1182, Width = 184, Height = 458 }\n};\n\nroot.Sprites[\"HanaNormalEnd\"] = {\n    Sheet = \"ClearList\",\n    Bounds = { X = 490, Y = 1182, Width = 184, Height = 465 }\n};\n\nroot.Sprites[\"SerikaNormalEnd\"] = {\n    Sheet = \"ClearList\",\n    Bounds = { X = 129, Y = 1713, Width = 225, Height = 183 }\n};\n\nroot.Sprites[\"SerikaTrueEnd\"] = {\n    Sheet = \"ClearList\",\n    Bounds = { X = 1026, Y = 1182, Width = 723, Height = 425 }\n};\n\n-- LibraryMenu\nroot.Sprites[\"LibraryBackground\"] = {\n    Sheet = \"LibraryMenu\",\n    Bounds = { X = 0, Y = 700, Width = 1629, Height = 1080 },\n};\n\nroot.Sprites[\"LibraryIndex\"] = {\n    Sheet = \"LibraryMenu\",\n    Bounds = { X = 1658, Y = 0, Width = 381, Height = 1049 },\n};\n\nroot.Sprites[\"SnapPhotoHover\"] = {\n    Sheet = \"LibraryMenu\",\n    Bounds = { X = 0, Y = 0, Width = 256, Height = 300 },\n};\n\nroot.Sprites[\"HitSongsHover\"] = {\n    Sheet = \"LibraryMenu\",\n    Bounds = { X = 256, Y = 0, Width = 256, Height = 300 },\n};\n\nroot.Sprites[\"LoveMovieHover\"] = {\n    Sheet = \"LibraryMenu\",\n    Bounds = { X = 512, Y = 0, Width = 256, Height = 300 },\n};\n\nroot.Sprites[\"SnapPhotoSelect\"] = {\n    Sheet = \"LibraryMenu\",\n    Bounds = { X = 0, Y = 360, Width = 352, Height = 300 },\n};\n\nroot.Sprites[\"HitSongsSelect\"] = {\n    Sheet = \"LibraryMenu\",\n    Bounds = { X = 352, Y = 360, Width = 352, Height = 300 },\n};\n\nroot.Sprites[\"LoveMovieSelect\"] = {\n    Sheet = \"LibraryMenu\",\n    Bounds = { X = 704, Y = 360, Width = 352, Height = 300 },\n};\n\n\n-- Album Menu\nroot.Sprites[\"AlbumMenuCGViewerGuide\"] = {\n    Sheet = \"LibraryMenu\",\n    Bounds = { X = 0, Y = 1880, Width = 1920, Height = 59 },\n};\n\nroot.Sprites[\"AlbumMenuGuide\"] = {\n    Sheet = \"LibraryMenu\",\n    Bounds = { X = 0, Y = 1783, Width = 1926, Height = 57 },\n};\n\n-- Movie Menu\nfor i = 0, 3 do\n    root.Sprites[\"MovieDiskSprites\" .. i] = {\n        Sheet = \"MovMenu\",\n        Bounds = { X = i * 575, Y = 0, Width = 575, Height = 575 }\n    };\n    root.Sprites[\"MovieDiskHighlightSprites\" .. i] = {\n        Sheet = \"MovMenu\",\n        Bounds = { X = i * 575, Y = 575 + 9, Width = 575, Height = 575 }\n    };\n    root.ExtraMenus.LibraryMenu.MovieDiskSprites[#root.ExtraMenus.LibraryMenu.MovieDiskSprites + 1] = \"MovieDiskSprites\" .. i;\n    root.ExtraMenus.LibraryMenu.MovieDiskHighlightSprites[#root.ExtraMenus.LibraryMenu.MovieDiskHighlightSprites + 1] = \"MovieDiskHighlightSprites\" .. i;\nend\n\nroot.Sprites[\"MovieDiskExtraOp\"] = {\n    Sheet = \"MovMenu\",\n    Bounds = { X = 2300, Y = 0, Width = 575, Height = 575 },\n};\n\nroot.Sprites[\"MovieDiskExtraOpHighlight\"] = {\n    Sheet = \"MovMenu\",\n    Bounds = { X = 2300, Y = 575 + 9, Width = 575, Height = 575 },\n};\n\nroot.Sprites[\"MovieMenuGuide\"] = {\n    Sheet = \"MovMenu\",\n    Bounds = { X = 0, Y = 1169, Width = 1920, Height = 59 },\n};\n\n\n-- Music Menu\n\nroot.Sprites[\"MusicMenuGuide\"] = {\n    Sheet = \"MusicMenu\",\n    Bounds = { X = 0, Y = 1292, Width = 1920, Height = 59 },\n};\nroot.Sprites[\"MusicItemsBackground\"] = {\n    Sheet = \"MusicMenu\",\n    Bounds = { \n        X = 1, \n        Y = 1, \n        Width = 1527, \n        Height = 1200\n    },\n};\nroot.Sprites[\"MusicItemsOverlay\"] = {\n    Sheet = \"MusicMenu\",\n    Bounds = { \n        X = 1530, \n        Y = 1, \n        Width = 1527, \n        Height = 1200\n    },\n};\nroot.Sprites[\"MusicButtonHover\"] = {\n    Sheet = \"MusicMenu\",\n    Bounds = { \n        X = 141, \n        Y = 1231, \n        Width = 1387, \n        Height = 60\n    },\n};\nroot.Sprites[\"MusicButtonSelect\"] = {\n    Sheet = \"MusicMenu\",\n    Bounds = { \n        X = 1670, \n        Y = 1231, \n        Width = 1387, \n        Height = 60\n    },\n};\nroot.Sprites[\"MusicButtonPlaying\"] = {\n    Sheet = \"MusicMenu\",\n    Bounds = { \n        X = 865, \n        Y = 1393, \n        Width = 46, \n        Height = 46\n    },\n};\nroot.Sprites[\"MusicNowPlayingNotification\"] = {\n    Sheet = \"MusicMenu\",\n    Bounds = { \n        X = 1.0,\n        Y = 1353.0,\n        Width = 789.0,\n        Height = 138.0\n    },\n};\nroot.Sprites[\"MusicNowPlayingNotification\"] = {\n    Sheet = \"MusicMenu\",\n    Bounds = { \n        X = 1.0,\n        Y = 1353.0,\n        Width = 789.0,\n        Height = 138.0\n    },\n};\n\nlocal MusicNowPlayingModeBounds = {\n    { X = 2390, Y = 1351, Width = 349, Height = 58.0},  -- Mode 0\n    { X = 1940, Y = 1292, Width = 278, Height = 58.0},  -- Mode 1\n    { X = 2360, Y = 1292, Width = 368, Height = 58.0},  -- Mode 2\n    { X = 1940, Y = 1351, Width = 274, Height = 58.0}   -- Mode 3\n}\nfor i = 0, 3 do\n    local index = i + 1;\n    root.Sprites[\"MusicNowPlayingMode\" .. i] = {\n        Sheet = \"MusicMenu\",\n        Bounds = MusicNowPlayingModeBounds[index]\n    };\n    root.ExtraMenus.LibraryMenu.MusicPlayingModeSprites[index] = \"MusicNowPlayingMode\" .. i;\n    root.ExtraMenus.LibraryMenu.MusicPlayingModeDisplayBounds[index] = {\n        X = 1854 - MusicNowPlayingModeBounds[index].Width + 1;\n        Y = 42;\n        Width = MusicNowPlayingModeBounds[index].Width;\n        Height = MusicNowPlayingModeBounds[index].Height;\n    };\nend\n\n-- AlbumMenu\nroot.Sprites[\"AlbumCameraPageIcon\"] = {\n    Sheet = \"LibraryMenu\",\n    Bounds = { \n        X = 1403.0,\n        Y = 80.0,\n        Width = 65.0,\n        Height = 76.0\n    },\n};\nroot.Sprites[\"AlbumThumbnailThumb\"] = {\n    Sheet = \"LibraryMenu\",\n    Bounds = { \n        X = 1476.0,\n        Y = 1.0,\n        Width = 170.0,\n        Height = 198.0\n    },\n};\nfor i = 0, 9 do\n    root.Sprites[\"AlbumPageNumber\" .. i] = {\n        Sheet = \"LibraryMenu\",\n        Bounds = { \n            X = 873.0 + 53 * i,\n            Y = 80.0,\n            Width = 51.0,\n            Height = 76.0\n        },\n    };\n    root.ExtraMenus.LibraryMenu.AlbumPageNumberSprites[i + 1] = \"AlbumPageNumber\" .. i;\nend\nfor i = 0, 11 do\n    root.Sprites[\"AlbumThumbnailPin\" .. i] = {\n        Sheet = \"LibraryMenu\",\n        Bounds = { \n            X = (i % 6) * 64 + 868.0 + 1,\n            Y = (i // 6) * 78 + 194,\n            Width = 62.0,\n            Height = 76.0\n        },\n    };\n    root.ExtraMenus.LibraryMenu.AlbumThumbnailPinSprites[i + 1] = \"AlbumThumbnailPin\" .. i;\nend\n"
  },
  {
    "path": "profiles/cclcc/hud/helpmenu.lua",
    "content": "root.HelpMenu = {\n    DrawType = DrawComponentType.SystemMenu,\n    Type = HelpMenuType.CCLCC,\n    FadeInDuration = 32/60,\n    FadeOutDuration = 32/60,\n    NextPageInDuration = 0.4,\n    NextPageOutDuration = 0.4,\n    ManualPages = {},\n    HelpMaskSprite = \"HelpMask\",\n}\n\nlocal numberOfPages = root.Language == \"Japanese\" and 16 or 1\n\nfor i = 0, numberOfPages do\n    root.Sprites[\"ManualPage\" .. i] = {\n        Sheet = \"ManualSheet\" .. i,\n        Bounds = { X = 0, Y = 0, Width = 1920, Height = 1080 }\n    };\n    root.HelpMenu.ManualPages[#root.HelpMenu.ManualPages + 1] = \"ManualPage\" .. i;\nend\n\nroot.Sprites[\"HelpMask\"] = {\n    Sheet = \"MenuChip\",\n    Bounds = { X = 154, Y = 140, Width = 1900, Height = 1061 },\n};"
  },
  {
    "path": "profiles/cclcc/hud/loadingdisplay.lua",
    "content": "root.LoadingDisplay = {\n    ResourceLoadBgAnim = \"ResourceLoadingBg\",\n    SaveLoadBgAnim = \"SaveLoadingBg\",\n    LoadingIconAnim = \"LoadingDisc\",\n    LoadingTextAnim = \"LoadingText\",\n    ResourceBgPos = { X = 1074, Y = 611 },\n    SaveBgPos = { X = 1019, Y = 598 },\n    IconPos = { X = 986, Y = 608 },\n    TextPos = { X = 1025, Y = 628 },\n    FadeInDuration = 0.66,\n    FadeOutDuration = 0.33\n};\n\nMakeAnimation({\n    Name = \"ResourceLoadingBg\",\n    Sheet = \"Data\",\n    FirstFrameX = 555,\n    FirstFrameY = 544,\n    FrameWidth = 206,\n    ColWidth = 206,\n    FrameHeight = 58,\n    RowHeight = 60,\n    Frames = 8,\n    Duration = 0.8,\n    Rows = 8,\n    Columns = 1,\n    PrimaryDirection = AnimationDirections.Down\n});\n\nMakeAnimation({\n    Name = \"SaveLoadingBg\",\n    Sheet = \"Data\",\n    FirstFrameX = 388,\n    FirstFrameY = 0,\n    FrameWidth = 277,\n    ColWidth = 279,\n    FrameHeight = 81,\n    RowHeight = 83,\n    Frames = 4,\n    Duration = 0.8,\n    Rows = 2,\n    Columns = 2,\n    PrimaryDirection = AnimationDirections.Down,\n    SecondaryDirection = AnimationDirections.Right\n});\n\nMakeAnimation({\n    Name = \"LoadingDisc\",\n    Sheet = \"Data\",\n    FirstFrameX = 172,\n    FirstFrameY = 1,\n    FrameWidth = 60,\n    ColWidth = 62,\n    FrameHeight = 60,\n    RowHeight = 62,\n    Frames = 3,\n    Duration = 0.8,\n    Rows = 1,\n    Columns = 3,\n    PrimaryDirection = AnimationDirections.Right\n});\nroot.Animations[\"LoadingDisc\"].Frames[#root.Animations[\"LoadingDisc\"].Frames + 1] = \"LoadingDisc1\";\n\nMakeAnimation({\n    Name = \"LoadingText\",\n    Sheet = \"Data\",\n    FirstFrameX = 173,\n    FirstFrameY = 91,\n    FrameWidth = 214,\n    ColWidth = 214,\n    FrameHeight = 21,\n    RowHeight = 23,\n    Frames = 5,\n    Duration = 0.8,\n    Rows = 5,\n    Columns = 1,\n    PrimaryDirection = AnimationDirections.Down\n});"
  },
  {
    "path": "profiles/cclcc/hud/optionsmenu.lua",
    "content": "root.OptionsMenu = {\n    Type = OptionsMenuType.CCLCC,\n    DrawType = DrawComponentType.SystemMenu,\n\n    FadeInDuration = 0.5,\n    FadeOutDuration = 0.5,\n\n    HighlightColor = { X = 0.94, Y = 0.49, Z = 0.59 },\n\n    BackgroundSprite = \"OptionsBackground\",\n    BackgroundPosition = { X = 179, Y = 0 },\n    BackgroundFadeStartPosition = { X = 179, Y = -375 },\n\n    PointerSprite = \"OptionsPointer\",\n    PointerOffset = { X = 13, Y = 2 },\n\n    HeaderSprite = \"OptionsHeader\",\n    HeaderPosition = { X = 10, Y = 10 },\n    PageHeaderSprites = {},\n    PageHeaderPosition = { X = 696, Y = 182 },\n\n    PagePanelSprite = \"OptionsPagePanel\",\n    PagePanelPosition = { X = -114, Y = 0 },\n    PagePanelFadeStartPosition = { X = -464, Y = 0 },\n    PagePanelSprites = {},\n    PagePanelIconOffsets = {\n        { X = 259, Y = 73 },\n        { X = 235, Y = 333 },\n        { X = 261, Y = 585 },\n        { X = 238, Y = 831 }\n    },\n    PagePanelHoverBounds = {\n        { X = 168, Y = 79, Width = 150, Height = 183 },\n        { X = 147, Y = 343, Width = 154, Height = 177 },\n        { X = 169, Y = 591, Width = 146, Height = 181 },\n        { X = 149, Y = 837, Width = 150, Height = 179 },\n    },\n    PoleAnimation = \"OptionsPoleAnimation\",\n\n    SliderTrackSprite = \"OptionsSliderTrack\",\n    SliderTrackOffset = { X = 766, Y = 0 },\n    VoiceSliderTrackSprite = \"OptionsVoiceSliderTrack\",\n    VoiceSliderOffset = { X = 114, Y = 57 },\n    BinaryBoxSprite = \"OptionsBinaryBox\",\n    BinaryBoxOffset = { X = 914, Y = 0 },\n    SliderSpeed = 1.0,\n\n    SkipReadSprite = \"OptionsSkipRead\",\n    SkipAllSprite = \"OptionsSkipAll\",\n    OnSprite = \"OptionsOn\",\n    OffSprite = \"OptionsOff\",\n    YesSprite = \"OptionsYes\",\n    NoSprite = \"OptionsNo\",\n\n    GuideSprite = \"OptionsGuide\",\n    VoiceGuideSprite = \"OptionsVoiceGuide\",\n    GuidePosition = { X = 0, Y = 986 },\n    GuideFadeStartPosition = { X = 0, Y = 1375 },\n\n    EntriesStartPosition = { X = 419, Y = 313 },\n    EntriesVerticalOffset = 126,\n    SoundEntriesStartPosition = { X = 419, Y = 333 },\n    SoundEntriesVerticalOffset = 70,\n    VoiceEntriesOffset = { X = 408, Y = 160 },\n    EntryDimensions = { X = 1212, Y = 50 },\n    VoiceEntryDimensions = { X = 374, Y = 104 },\n\n    LabelSprites = {},\n    LabelOffset = { X = 102, Y = -2 },\n    NametagSprites = {},\n    NametagOffset = { X = 116, Y = 4 },\n    PortraitSprites = {},\n    PortraitOffset = { X = 3, Y = 2 },\n    VoicePosition = { X = 454, Y = 310 },\n\n    MenuMask = \"MenuMask\",\n};\n\nroot.Sprites[\"OptionsBackground\"] = {\n    Sheet = \"Config\",\n    Bounds = { X = 0, Y = 0, Width = 1571, Height = 1089 }\n};\n\nroot.Sprites[\"OptionsSliderTrack\"] = {\n    Sheet = \"Config\",\n    Bounds = { X = 684, Y = 1291, Width = 446, Height = 50 }\n};\n\nroot.Sprites[\"OptionsVoiceSliderTrack\"] = {\n    Sheet = \"Config\",\n    Bounds = { X = 951, Y = 1239, Width = 250, Height = 35 }\n};\n\nroot.Sprites[\"OptionsBinaryBox\"] = {\n    Sheet = \"Config\",\n    Bounds = { X = 684, Y = 1343, Width = 298, Height = 50 }\n};\n\nroot.Sprites[\"OptionsPointer\"] = {\n    Sheet = \"Config\",\n    Bounds = { X = 697, Y = 1241, Width = 71, Height = 45 }\n};\n\nroot.Sprites[\"OptionsHeader\"] = {\n    Sheet = \"Config\",\n    Bounds = { X = 5, Y = 1243, Width = 663, Height = 267 }\n};\n\nroot.Sprites[\"OptionsPagePanel\"] = {\n    Sheet = \"Config\",\n    Bounds = { X = 1571, Y = 0, Width = 477, Height = 1080 }\n};\n\nroot.Sprites[\"OptionsSkipRead\"] = {\n    Sheet = \"Config\",\n    Bounds = { X = 952, Y = 1394, Width = 148, Height = 50 }\n};\n\nroot.Sprites[\"OptionsSkipAll\"] = {\n    Sheet = \"Config\",\n    Bounds = { X = 952, Y = 1448, Width = 148, Height = 50 }\n};\n\nroot.Sprites[\"OptionsOn\"] = {\n    Sheet = \"Config\",\n    Bounds = { X = 952, Y = 1499, Width = 148, Height = 50 }\n};\n\nroot.Sprites[\"OptionsOff\"] = {\n    Sheet = \"Config\",\n    Bounds = { X = 952, Y = 1551, Width = 148, Height = 50 }\n};\n\nroot.Sprites[\"OptionsYes\"] = {\n    Sheet = \"Config\",\n    Bounds = { X = 952, Y = 1603, Width = 148, Height = 50 }\n};\n\nroot.Sprites[\"OptionsNo\"] = {\n    Sheet = \"Config\",\n    Bounds = { X = 952, Y = 1655, Width = 148, Height = 50 }\n};\n\nroot.Sprites[\"OptionsGuide\"] = {\n    Sheet = \"Config\",\n    Bounds = { X = 0, Y = 2476, Width = 1926, Height = 57 }\n};\n\nroot.Sprites[\"OptionsVoiceGuide\"] = {\n    Sheet = \"Config\",\n    Bounds = { X = 0, Y = 2416, Width = 1926, Height = 57 }\n};\n\nroot.Sprites[\"MenuMask\"] = {\n    Sheet = \"MenuChip\",\n    Bounds = { X = 154, Y = 140, Width = 1900, Height = 1061 },\n};\n\nfor i = 0, 3 do\n    height = 80;\n\n    root.Sprites[\"OptionsPageHeader\" .. i] = {\n        Sheet = \"Config\",\n        Bounds = {\n            X = 0,\n            Y = 1510 + height * i,\n            Width = 942,\n            Height = height\n        }\n    };\n\n    root.OptionsMenu.PageHeaderSprites[#root.OptionsMenu.PageHeaderSprites + 1] = \"OptionsPageHeader\" .. i;\nend\n\n-- Rearange the labels in the array so they're in order of appearance\nlabelIndices = {5, 6, 7, 1,\n                2, 3, 8,\n                9, 10, 11, 12, 13, 4};\nheight = 52;\nfor i = 0, 12 do\n    offset = ((i > 11) and {104} or {0})[1];\n\n    root.Sprites[\"OptionsLabel\" .. i] = {\n        Sheet = \"Config\",\n        Bounds = {\n            X = 1239,\n            Y = 1237 + height * i + offset,\n            Width = 809,\n            Height = height\n        }\n    };\n    root.OptionsMenu.LabelSprites[labelIndices[i + 1]] = \"OptionsLabel\" .. i;\nend\n\nfor i = 0, 12 do\n    width = 218;\n    height = 53;\n\n    root.Sprites[\"OptionsNametag\" .. i] = {\n        Sheet = \"Config\",\n        Bounds = {\n            X = width * (i % 6),\n            Y = 1863 + height * (i // 6),\n            Width = width,\n            Height = height\n        }\n    };\n    root.OptionsMenu.NametagSprites[#root.OptionsMenu.NametagSprites + 1] = \"OptionsNametag\" .. i;\nend\n\nfor i = 0, 7 do\n    -- Interweave highlighted and non-highlighted variants\n    width = 224;\n    offset = ((i % 2 == 1) and {width * 4} or {0})[1];\n\n    -- Non-highlighted Text and Sound panels are poorly placed in the spritesheet, for some reason\n    if (i == 3 or i == 5) then\n        offset = offset - 1;\n    end\n\n    root.Sprites[\"OptionsPagePanel\" .. i] = {\n        Sheet = \"Config\",\n        Bounds = {\n            X = offset + width * (i // 2),\n            Y = 2205,\n            Width = width,\n            Height = 195\n        }\n    };\n    root.OptionsMenu.PagePanelSprites[#root.OptionsMenu.PagePanelSprites + 1] = \"OptionsPagePanel\" .. i;\nend\n\nfor i = 0, 25 do\n    -- Interweave on and muted variants\n    width = 100;\n\n    root.Sprites[\"OptionsPortrait\" .. i] = {\n        Sheet = \"ConfigEx\",\n        Bounds = {\n            X = 768 + (width + 1) * (i // 2),\n            Y = ((i % 2 == 0) and {2256} or {2357})[1],\n            Width = width,\n            Height = width\n        }\n    };\n    root.OptionsMenu.PortraitSprites[#root.OptionsMenu.PortraitSprites + 1] = \"OptionsPortrait\" .. i;\nend\n\nMakeAnimation({\n    Name = \"OptionsPoleAnimation\",\n    Sheet = \"ConfigEx\",\n\n    FirstFrameX = 0,\n    FirstFrameY = 0,\n\n    FrameWidth = 539,\n    ColWidth = 539,\n    FrameHeight = 1080,\n    RowHeight = 1096,\n\n    Frames = 15,\n    Duration = 0.5,\n    Rows = 3,\n    Columns = 7,\n\n    PrimaryDirection = AnimationDirections.Right,\n    SecondaryDirection = AnimationDirections.Down\n});"
  },
  {
    "path": "profiles/cclcc/hud/saveicon.lua",
    "content": "root.SaveIcon = {\n    ForegroundAnimation = \"SaveIcon\",\n    DefaultPosition = { X = 104, Y = 736 },\n    BackgroundSprite = \"SaveIconBg\",\n    BackgroundOffset = { X = 0, Y = 0 },\n    BackgroundMaxAlpha = 0.0,\n    FadeInDuration = 16/60,\n    FadeOutDuration = 12/60,\n    FullyVisibleSpriteIndex = 1\n};\n\nMakeAnimation({\n    Name = \"SaveIcon\",\n    Sheet = \"Data\",\n    FirstFrameX = 127,\n    FirstFrameY = 541,\n    FrameWidth = 179,\n    ColWidth = 179,\n    FrameHeight = 62,\n    RowHeight = 62,\n    Frames = 2,\n    Duration = 2*18/60, -- 18 frames per sprite\n    Rows = 2,\n    Columns = 1,\n    PrimaryDirection = AnimationDirections.Up\n});\n\nroot.Sprites[\"SaveIconBg\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 127, Y = 479, Width = 179, Height = 62 }\n};"
  },
  {
    "path": "profiles/cclcc/hud/savemenu.lua",
    "content": "root.SaveMenu = {\n    Type = SaveMenuType.CCLCC,\n    DrawType = DrawComponentType.SystemMenu,\n    EmptyThumbnailSprite = \"EmptyThumbnail\",\n    EntryStartXL = 143,\n    EntryStartYL = 152,\n    EntryStartXR = 984,\n    EntryStartYR = 52,\n    EntryYPadding = 201,\n    FadeInDuration = 28 / 60,\n    FadeOutDuration = 28 / 60,\n    PageSwapDuration = 28 / 60,\n    SaveMenuMaskSprite = \"SaveMenuMask\",\n    SaveEntryPrimaryColor = 0xF07390,\n    LoadEntryPrimaryColor = 0x00A1E6,\n    SaveEntrySecondaryColor = 0x5E357C,\n    GuidePosition = {X = 1314, Y = 867},\n    MenuTextPosition = {X = 11, Y = 10}, \n    SlotsBackgroundPosition = {X = 135, Y = 0}, \n    PageNumberPosition = {X = 1314, Y = 867},\n    SlotLockedSpritePosition = {X = 656, Y = 9},\n    NoDataSpritePosition = {X = 232, Y = 9},\n};\n\nroot.Sprites[\"EmptyThumbnail\"] = {\n    Sheet = \"SaveMenu\",\n    Bounds = { X = 0, Y = 0, Width = 0, Height = 0 }\n};\n\nroot.Sprites[\"SaveMenuMask\"] = {\n    Sheet = \"MenuChip\",\n    Bounds = { X = 154, Y = 140, Width = 1900, Height = 1061 },\n};\n\nlocal menuTypes = { \"QuickLoad\", \"Save\", \"Load\" }\n\nfor i = 1,3 do\n    local menuType = menuTypes[i];\n    root.SaveMenu[menuType ..  \"TextSprite\"] = menuType .. \"Text\";\n    root.Sprites[menuType .. \"Text\"] = {\n        Sheet = menuType .. \"Menu\",\n        Bounds = { X = 1, Y = 1083, Width = 1066, Height = 298 }\n    };\n    root.SaveMenu[menuType .. \"EntrySlotsSprite\"] = menuType .. \"EntrySlots\";\n    root.Sprites[menuType .. \"EntrySlots\"] = {\n        Sheet = menuType .. \"Menu\",\n        Bounds = { X = 1, Y = 0, Width = 1680, Height = 1080 }\n    };\n    root.SaveMenu[menuType .. \"EntryHighlightedBoxSprite\"] = menuType .. \"EntryHighlightedBox\";\n    root.Sprites[menuType .. \"EntryHighlightedBox\"] = {\n        Sheet = menuType .. \"Menu\",\n        Bounds = { X = 1241, Y = 1083, Width = 806, Height = 184 }\n    };\n    root.SaveMenu[menuType .. \"EntryHighlightedTextSprite\"] = menuType .. \"EntryHighlightedText\";\n    root.Sprites[menuType .. \"EntryHighlightedText\"] = {\n        Sheet = menuType .. \"Menu\",\n        Bounds = { X = 1241, Y = 1269, Width = 254, Height = 87 }\n    };\n    root.SaveMenu[menuType .. \"ButtonGuideSprite\"] = menuType .. \"ButtonGuide\";\n    root.Sprites[menuType .. \"ButtonGuide\"] = {\n        Sheet = menuType .. \"Menu\",\n        Bounds = { X = 0, Y = 1457, Width = 1920, Height = 59 }\n    };\n    root.SaveMenu[menuType .. \"SeparationLineSprite\"] = menuType .. \"SeparationLine\";\n    root.Sprites[menuType .. \"SeparationLine\"] = {\n        Sheet = menuType .. \"Menu\",\n        Bounds = { X = 847, Y = 1409, Width = 478, Height = 27 }\n    };\n    \n\n    for i = 0, 9 do\n        root.SaveMenu[menuType .. \"NumberDigitSprite\" .. i] = menuType .. \"NumberDigit\" .. i;\n        root.Sprites[menuType .. \"NumberDigit\" .. i] = {\n            Sheet = menuType .. \"Menu\",\n            Bounds = {X = i * 40 + 1430 + 1, Y = 1383, Width = 38, Height = 56}\n        };\n    end\n\n    root.SaveMenu[menuType .. \"NoDataEntrySprite\"] = menuType .. \"NoDataEntry\";\n    root.Sprites[menuType .. \"NoDataEntry\"] = {\n        Sheet = menuType .. \"Menu\",\n        Bounds = {X = 1683, Y = 1, Width = 364, Height = 182}\n    };\n\n    root.SaveMenu[menuType .. \"BrokenDataEntrySprite\"] = menuType .. \"BrokenDataEntry\";\n    root.Sprites[menuType .. \"BrokenDataEntry\"] = {\n        Sheet = menuType .. \"Menu\",\n        Bounds = {X = 1683, Y = 185, Width = 364, Height = 182}\n    };\n\n    root.SaveMenu[menuType .. \"SlotLockedSprite\"] = menuType .. \"SlotLocked\";\n    root.Sprites[menuType .. \"SlotLocked\"] = {\n        Sheet = menuType .. \"Menu\",\n        Bounds = {X = 0, Y = 1402, Width = 132, Height = 48}\n    };\n\n\n    for i = 0, 5 do\n        local col = i//3;\n        local row = i % 3;\n        root.SaveMenu[menuType .. \"PageNumSprite\" .. i] = menuType .. \"PageNum\" .. i;\n        root.Sprites[menuType .. \"PageNum\" .. i] = {\n            Sheet = menuType .. \"Menu\",\n            Bounds = {\n                X = col * 240, \n                Y = row * 96 + 1534, \n                Width = 192, \n                Height = 64\n            }\n        };\n    end\n\n    root.SaveMenu[menuType .. \"SaveTimeSprite\"] = menuType .. \"SaveTime\";\n    root.Sprites[menuType .. \"SaveTime\"] = {\n        Sheet = menuType .. \"Menu\",\n        Bounds = {X = 839, Y = 1383, Width = 490, Height = 72}\n    };\n    \nend"
  },
  {
    "path": "profiles/cclcc/hud/selectiondisplay.lua",
    "content": "root.SelectionDisplay = {\n    DrawType = DrawComponentType.Text,\n    SelectionBackgroundSprite = \"SelectionBackground\",\n    PlainSelectionFrameTopLeftSprite = \"PlainSelectionFrameTopLeft\",\n    PlainSelectionFrameTopSideSprite = \"PlainSelectionFrameTopSide\",\n    PlainSelectionFrameTopRightSprite = \"PlainSelectionFrameTopRight\",\n    PlainSelectionFrameLeftSideSprite = \"PlainSelectionFrameLeftSide\",\n    PlainSelectionFrameBottomLeftSprite = \"PlainSelectionFrameBottomLeft\",\n    PlainSelectionFrameRightSideSprite = \"PlainSelectionFrameRightSide\",\n    PlainSelectionFrameBottomRightSprite = \"PlainSelectionFrameBottomRight\",\n    PlainSelectionFrameBottomSideSprite = \"PlainSelectionFrameBottomSide\",\n    PlainSelectionFrameMiddleSprite = \"PlainSelectionFrameMiddle\",\n    SelectionHighlightSprite = \"SelectionHighlight\",\n    SelectionMaxCount = 5,\n    SelectionBackgroundX = 228,\n    SelectionBackgroundY = {277, 277, 196, 129, 95},\n    SelectionYSpacing = 81,\n    PlainSelectionYSpacing = 10,\n    FadeAnimationDurationInOut = 0.2\n};\n\nroot.Sprites[\"SelectionBackground\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 1, Y = 535, Width = 829, Height = 69 }\n};\n\nroot.Sprites[\"PlainSelectionFrameTopLeft\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 0, Y = 0, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameTopSide\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 16, Y = 0, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameTopRight\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 32, Y = 0, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameLeftSide\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 0, Y = 16, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameBottomLeft\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 0, Y = 32, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameRightSide\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 32, Y = 16, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameBottomRight\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 32, Y = 32, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameBottomSide\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 16, Y = 32, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameMiddle\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 16, Y = 16, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"SelectionHighlight\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 65, Y = 33, Width = 94, Height = 30 }\n};\n"
  },
  {
    "path": "profiles/cclcc/hud/sysmesboxdisplay.lua",
    "content": "local sheet = \"SysMesBox\";\nlocal name = \"SysMesBox\";\n\nroot.SysMesBoxDisplay = {\n    Type = SysMesBoxType.CC,\n    DrawType = DrawComponentType.SystemMessage,\n    SumoSealSprites = {},\n    SumoSealCenterPosX = {\n        1175, 1269, 542, 1386, 970, 813, 725, 997\n    },\n    SumoSealCenterPosY = {\n        612, 454, 520, 606, 625, 425, 584, 461\n    },\n    ButtonYesCenterPosX = 1173,\n    ButtonYesCenterPosY = 882,\n    ButtonNoCenterPosX = 1288,\n    ButtonNoCenterPosY = 796,\n    ButtonOKCenterPosX = 1170,\n    ButtonOKCenterPosY = 824,\n    TextFontSize = 32,\n    TextMiddleY = 1096,\n    TextX = 1920,\n    TextLineHeight = 32,\n    TextMarginY = 48,\n    SpriteMargin = 3,\n    AnimationSpeed = 25,\n    AnimationProgressWidgetsStartOffset = 9,\n    WidgetsAlphaMultiplier = 0.0625,\n    ButtonYesAnimationProgressEnd = 12,\n    ButtonNoDisplayStart = 4,\n    ButtonNoAnimationProgressOffset = 13,\n    ButtonYesNoScaleMultiplier = 0.044,\n    ButtonYesNoAlphaDivider = 12,\n    ButtonOKScaleMultiplier = 0.03125,\n    ButtonScaleMax = 1.5,\n    FadeInDuration = 0.4,\n    FadeOutDuration = 0.4,\n\n    ButtonYesHoverBounds = {\n        X = 1109,\n        Y = 842,\n        Width = 141,\n        Height = 103\n    },\n    ButtonNoHoverBounds = {\n        X = 1224,\n        Y = 739,\n        Width = 156,\n        Height = 121\n    },\n    ButtonOkHoverBounds = {\n        X = 0,\n        Y = 0,\n        Width = 0,\n        Height = 0\n    }\n};\n\nroot.Sprites[name .. \"ButtonYes\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 694,\n        Y = 1150,\n        Width = 344,\n        Height = 378\n    }\n};\nroot.SysMesBoxDisplay.ButtonYes = name .. \"ButtonYes\";\n\nroot.Sprites[name .. \"ButtonNo\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 1473,\n        Y = 1273,\n        Width = 252,\n        Height = 255\n    }\n};\nroot.SysMesBoxDisplay.ButtonNo = name .. \"ButtonNo\";\n\nroot.Sprites[name .. \"ButtonOK\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 0,\n        Y = 1254,\n        Width = 301,\n        Height = 274\n    }\n};\nroot.SysMesBoxDisplay.ButtonOK = name .. \"ButtonOK\";\n\nroot.Sprites[name .. \"ButtonYesHighlighted\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 1086,\n        Y = 1150,\n        Width = 344,\n        Height = 378\n    }\n};\nroot.SysMesBoxDisplay.ButtonYesHighlighted = name .. \"ButtonYesHighlighted\";\n\nroot.Sprites[name .. \"ButtonNoHighlighted\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 1761,\n        Y = 1273,\n        Width = 252,\n        Height = 255\n    }\n};\nroot.SysMesBoxDisplay.ButtonNoHighlighted = name .. \"ButtonNoHighlighted\";\n\nroot.Sprites[name .. \"ButtonOKHighlighted\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 348,\n        Y = 1254,\n        Width = 301,\n        Height = 274\n    }\n};\nroot.SysMesBoxDisplay.ButtonOKHighlighted = name .. \"ButtonOKHighlighted\";\n\nlocal sealX = 0;\nlocal sealY = 0;\n\nfor i = 0, 7 do\n    root.Sprites[name .. \"SumoSeal\" .. i] = {\n        Sheet = sheet,\n        Bounds = {\n            X = sealX,\n            Y = sealY,\n            Width = 478,\n            Height = 538\n        }\n    };\n\n    sealX = sealX + 480;\n    if sealX == 1920 then\n        sealX = 0;\n        sealY = sealY + 540;\n    end\n\n    root.SysMesBoxDisplay.SumoSealSprites[#root.SysMesBoxDisplay.SumoSealSprites + 1] = name .. \"SumoSeal\" .. i;\nend"
  },
  {
    "path": "profiles/cclcc/hud/systemmenu.lua",
    "content": "root.SystemMenu = {\n    Type = SystemMenuType.CCLCC,\n    DrawType = DrawComponentType.SystemMenu,\n\n    FadeInDuration = 40 / 60,\n    FadeOutDuration = 40 / 60,\n    MoveInDuration = 40 / 60,\n    MoveOutDuration = 28 / 60,\n\n    ItemsFadeInDuration = 20/60,\n    ItemsFadeOutDuration = 20/60,\n\n    Seed = 0,\n\n    MenuEntriesNum = 9,\n    MenuEntriesHNum = 9,\n\n    FocusTint = 0xff9cb6,\n    MenuEntriesPositions = { \n        {X=41, Y=-4},\n        {X=41, Y=87},\n        {X=41, Y=119},\n        {X=41, Y=284},\n        {X=41, Y=365},\n        {X=41, Y=458},\n        {X=41, Y=574},\n        {X=41, Y=649},\n        {X=41, Y=725},\n    },\n\n    MenuEntriesButtonBounds = { \n        {X=120, Y= 74, Width=228, Height=60},\n        {X=120, Y=172, Width=260, Height=60},\n        {X=120, Y=268, Width=260, Height=60},\n        {X=120, Y=362, Width=128, Height=60},\n        {X=120, Y=454, Width=128, Height=60},\n        {X=120, Y=552, Width=228, Height=60},\n        {X=120, Y=650, Width=186, Height=60},\n        {X=120, Y=742, Width=128, Height=60},\n        {X=120, Y=838, Width=316, Height=60},\n    },\n    MenuEntriesSprites = {},\n    MenuEntriesHighlightedSprites = {},\n\n    MenuButtonGuide = \"SystemMenuButtonGuide\",\n    SystemMenuBG = \"SystemMenuBG\",\n    SystemMenuFrame = \"SystemMenuFrame\",\n    SystemMenuMask = \"SystemMenuMask\",\n\n    BGDispOffsetTopLeft = {X=-1200, Y= -330},\n    BGDispOffsetBottomLeft = {X=-1200, Y= 2080},\n    BGDispOffsetTopRight = {X=2520, Y= -330},\n    BGDispOffsetBottomRight = {X=2520, Y= 2080},\n\n    FrameOffsetTopLeft = {X=-144, Y= -131},\n    FrameOffsetBottomLeft = {X=-144, Y= 1252},\n    FrameOffsetTopRight = {X=2108, Y= -131},\n    FrameOffsetBottomRight = {X=2108, Y= 1252},\n\n    AngleMultiplier = {X=-196608 / 5760, Y=-131072 / 5760, Z=163840 / 5760},\n    BGRandPosRange = {X=4095,Y=4095},\n\n    BGTranslationOffset = {X=1452,Y=395},\n};\n\nroot.Sprites[\"SystemMenuBG\"] = {\n    Sheet = \"MenuBG\",\n    Bounds = {X = -600, Y = -165, Width = 1860, Height = 1205}\n};\n\nroot.Sprites[\"SystemMenuBacklog\"] = {\n    Sheet = \"MenuChip\",\n    Bounds = {X = 2752, Y = 0, Width = 420, Height = 202}\n};\nroot.SystemMenu.MenuEntriesSprites[#root.SystemMenu.MenuEntriesSprites + 1] = \"SystemMenuBacklog\";\n\nroot.Sprites[\"SystemMenuQuickSave\"] = {\n    Sheet = \"MenuChip\",\n    Bounds = {X = 2252, Y = 274, Width = 498, Height = 225}\n};\nroot.SystemMenu.MenuEntriesSprites[#root.SystemMenu.MenuEntriesSprites + 1] = \"SystemMenuQuickSave\";\n\nroot.Sprites[\"SystemMenuQuickLoad\"] = {\n    Sheet = \"MenuChip\",\n    Bounds = {X = 2252, Y = 1092, Width = 498, Height = 348}\n};\nroot.SystemMenu.MenuEntriesSprites[#root.SystemMenu.MenuEntriesSprites + 1] = \"SystemMenuQuickLoad\";\n\nroot.Sprites[\"SystemMenuSave\"] = {\n    Sheet = \"MenuChip\",\n    Bounds = {X = 2752, Y = 204, Width = 420, Height = 241}\n};\nroot.SystemMenu.MenuEntriesSprites[#root.SystemMenu.MenuEntriesSprites + 1] = \"SystemMenuSave\";\n\nroot.Sprites[\"SystemMenuLoad\"] = {\n    Sheet = \"MenuChip\",\n    Bounds = {X = 2252, Y = 0, Width = 498, Height = 305}\n};\nroot.SystemMenu.MenuEntriesSprites[#root.SystemMenu.MenuEntriesSprites + 1] = \"SystemMenuLoad\";\n\nroot.Sprites[\"SystemMenuTips\"] = {\n    Sheet = \"MenuChip\",\n    Bounds = {X = 2252, Y = 501, Width = 498, Height = 305}\n};\nroot.SystemMenu.MenuEntriesSprites[#root.SystemMenu.MenuEntriesSprites + 1] = \"SystemMenuTips\";\n\nroot.Sprites[\"SystemMenuConfig\"] = {\n    Sheet = \"MenuChip\",\n    Bounds = {X = 2752, Y = 447, Width = 420, Height = 253}\n};\nroot.SystemMenu.MenuEntriesSprites[#root.SystemMenu.MenuEntriesSprites + 1] = \"SystemMenuConfig\";\n\nroot.Sprites[\"SystemMenuHelp\"] = {\n    Sheet = \"MenuChip\",\n    Bounds = {X = 2752, Y = 702, Width = 420, Height = 259}\n};\nroot.SystemMenu.MenuEntriesSprites[#root.SystemMenu.MenuEntriesSprites + 1] = \"SystemMenuHelp\";\n\nroot.Sprites[\"SystemMenuReturnTitle\"] = {\n    Sheet = \"MenuChip\",\n    Bounds = {X = 2252, Y = 808, Width = 498, Height = 282}\n};\nroot.SystemMenu.MenuEntriesSprites[#root.SystemMenu.MenuEntriesSprites + 1] = \"SystemMenuReturnTitle\";\n\nfor i=1, #root.SystemMenu.MenuEntriesSprites do\n    local name = root.SystemMenu.MenuEntriesSprites[i] .. \"Highlighted\"\n    root.Sprites[name] = {\n        Sheet = \"MenuChip\",\n        Bounds = {\n            X = root.Sprites[root.SystemMenu.MenuEntriesSprites[i]].Bounds.X + 922,\n            Y = root.Sprites[root.SystemMenu.MenuEntriesSprites[i]].Bounds.Y,\n            Width = root.Sprites[root.SystemMenu.MenuEntriesSprites[i]].Bounds.Width,\n            Height = root.Sprites[root.SystemMenu.MenuEntriesSprites[i]].Bounds.Height\n        }\n    }\n    root.SystemMenu.MenuEntriesHighlightedSprites[#root.SystemMenu.MenuEntriesHighlightedSprites + 1] = name\nend\n\nroot.Sprites[\"SystemMenuButtonGuide\"] = {\n    Sheet = \"MenuChip\",\n    Bounds = {X = 0, Y = 1384, Width = 1920, Height = 59}\n};\n\nroot.Sprites[\"SystemMenuFrame\"] = {\n    Sheet = \"MenuChip\",\n    Bounds = {X = 0, Y = 0, Width = 2252, Height = 1383}\n};\n\nroot.Sprites[\"SystemMenuMask\"] = {\n    Sheet = \"MenuChip\",\n    Bounds = { X = 154, Y = 140, Width = 1900, Height = 1061 },\n};"
  },
  {
    "path": "profiles/cclcc/hud/tipsmenu.lua",
    "content": "root.TipsMenu = {\n    Type = TipsMenuType.CCLCC,\n    DrawType = DrawComponentType.SystemMenu,\n    FadeInDuration = 0.4,\n    FadeOutDuration = 0.4,\n    TransitionInDuration = 0.4,\n    TransitionOutDuration = 0.4,\n    BackgroundSprite = \"TipsBookLayer\",\n    TipsGuideSprite = \"TipsGuide\",\n    TipsGuideX = 0,\n    TipsGuideY = 990,\n    TipsMaskSprite = \"TipsMask\",\n    TipsNewSprite = \"TipsNewSprite\",\n    TipsHighlightedSprite = \"TipsHighlightedSprite\",\n\n    TipsEntryBounds = { X = 80, Y = 382, Width = 781, Height = 50},\n    TipEntryNewOffset = { X = 0, Y = 1 },\n    TipsEntryHighlightOffset = { X = 41, Y = 3 },\n    TipsEntryNumberOffset = { X = 41, Y = 12 },\n    TipsEntryNameOffset = {X = 94, Y = 10},\n    TipsEntryNumberFontSize = 26,\n    TipsEntryNameFontSize = 30,\n    TipsTabBounds = {X = 0, Y = 382, Width = 890, Height = 545 },\n    TipsHighlightedTabSprite = \"TipsHighlightedTabSprite\",\n    TipsTabNameDisplay = {X = 266, Y = 285},\n    TipsHighlightedTabAdder = 161,\n\n    TipsTextTableIndex = 2,\n    TipsTextEntryLockedIndex = 7,\n    TipsTextSortStringIndex = 10,\n\n    CategoryPos = { X = 1230, Y = 76},\n    CategoryFontSize = 40,\n    NamePos = { X = 1020, Y = 170},\n    NameFontSize = 54,\n    NumberPos = { X = 1875, Y = 80},\n    NumberFontSize = 62,\n    PronounciationPos = { X = 1020, Y = 246},\n    PronounciationFontSize = 34,\n\n    TipsEntryNameUnreadColor = 0x117733, \n    TipsMenuDarkTextColor = 0x3e3e3c,\n\n    TipsScrollThumbSprite = \"TipsScrollThumb\",\n    TipsScrollThumbLength = 40,\n    TipsScrollYStart = 385,\n    TipsScrollYEnd = 930,\n    TipsScrollEntriesX = 896,\n    TipsScrollDetailsX = 1783,\n\n    TipsMask = \"TipsMask\",\n};\n\nroot.Sprites[\"TipsBookLayer\"] = {\n    Sheet = \"Tips\",\n    Bounds = { X = 0, Y = 0, Width = 1920, Height = 1080 }\n};\n\nroot.Sprites[\"TipsGuide\"] = {\n    Sheet = \"Tips\",\n    Bounds = { X = 0, Y = 1152, Width = 1925, Height = 55 }\n};\n\nroot.Sprites[\"TipsMask\"] = {\n    Sheet = \"MenuChip\",\n    Bounds = { X = 154, Y = 140, Width = 1900, Height = 1061 },\n};\n\n-- Start of Tips Tab Name Sprites\nroot.Sprites[\"TipsHighlightedTabSprite\"] = {\n    Sheet = \"Tips\",\n    Bounds = { X = 1355, Y = 1212, Width = 160, Height = 44 }\n};\n\nroot.Sprites[\"TipsNewSprite\"] = {\n    Sheet = \"Tips\",\n    Bounds = { X = 1314, Y = 1212, Width = 40, Height = 40 }\n};\n\nroot.Sprites[\"TipsHighlightedSprite\"] = {\n    Sheet = \"Tips\",\n    Bounds = { X = 517, Y = 1208, Width = 740, Height = 40 }\n};\n\nroot.SpriteSheets[\"TipsMask\"] = {\n    Path = { Mount = \"system\", Id = 29 },\n    DesignWidth = 1920,\n    DesignHeight = 1080\n};\n\nroot.Sprites[\"TipsScrollThumb\"] = {\n    Sheet = \"Tips\",\n    Bounds = { X = 2016, Y = 994, Width = 11, Height = 40 }\n};\n"
  },
  {
    "path": "profiles/cclcc/hud/tipsnotification.lua",
    "content": "root.TipsNotification = {\n    Type = TipsNotificationType.CCLCC,\n    TextTableId = 0,\n    NotificationTextPart1MessageId = 100,\n    NotificationTextPart2MessageId = 101,\n    FadeInDuration = 0.5,\n    FadeOutDuration = 0.5,\n    NotificationBackground = \"TipNotificationBackground\",\n    BackgroundPositionX = 571,\n    BackgroundPositionYOffset = 55,\n    NotificationPositionX = 600,\n    NotificationPositionYOffset = 10,\n    TimerDuration = 2,\n    MoveAnimationDuration = 1,\n    FontSize = 26,\n    TipNameTextColor = 0x6a4ff,\n    TipNameOutlineColor = 0x35280,\n    NotificationTextTableColorIndex = 10,\n\n};\n\nroot.Sprites[\"TipNotificationBackground\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 1459, Y = 1, Width = 817, Height = 120 },\n};\n"
  },
  {
    "path": "profiles/cclcc/hud/titlemenu.lua",
    "content": "root.TitleMenu = {\n    Type = TitleMenuType.CCLCC,\n    DrawType = DrawComponentType.SystemMenu,\n    PressToStartX = 823,\n    PressToStartY = 749,\n    PressToStartAnimDurationIn = 0.6,\n    PressToStartAnimDurationOut = 0.6,\n    PrimaryFadeInDuration = 0.7,\n    PrimaryFadeOutDuration = 0.7,\n    SecondaryFadeInDuration = 0.4,\n    SecondaryFadeOutDuration = 0.4,\n    SlideItemsAnimationDurationIn = 0.5,\n    SlideItemsAnimationDurationOut = 0.5,\n    PressToStartSprite = \"TitleMenuPressToStart\",\n    BackgroundSprite = \"TitleMenuBackground\",\n    MainBackgroundSprite = \"MainMenuBackground\",\n    CopyrightTextSprite = \"CopyrightText\",\n    CopyrightTextX = 566,\n    CopyrightTextY = 955,\n    SmokeX = 0,\n    SmokeY = 580,\n    SmokeBoundsX = 20,\n    SmokeBoundsY = 1550,\n    SmokeBoundsWidth = 1920,\n    SmokeBoundsHeight = 500,\n    SmokeAnimationBoundsXOffset = 20,\n    SmokeAnimationBoundsXMax = 1919,\n    SmokeOpacityNormal = 0.15,\n    SmokeAnimationDurationIn = 32,\n    SmokeAnimationDurationOut = 32,\n    SmokeSprite = \"TitleMenuSmoke\",\n    OverlaySprite = \"TitleMenuOverlay\",\n    MenuSprite = \"TitleMenuMenu\",\n    MenuX = 27,\n    MenuY = 26,\n    ItemHighlightSprite = \"TitleMenuItemHighlight\",\n    ItemHighlightOffsetX = 174,\n    ItemHighlightOffsetY = 7,\n    ItemHighlightPointerSprite = \"TitleMenuPointerItemHighlight\",\n    ItemHighlightPointerY = 89,\n    ItemPadding = 56,\n    ItemYBase = 335,\n    SecondaryFirstItemHighlightOffsetX = 225,\n    SecondarySecondItemHighlightOffsetX = 370,\n    SecondaryThirdItemHighlightOffsetX = 590,\n    LoadSprite = \"TitleMenuLoad\",\n    LoadHighlightSprite = \"TitleMenuLoadHighlight\",\n    QuickLoadSprite = \"TitleMenuQLoad\",\n    QuickLoadHighlightSprite = \"TitleMenuQLoadHighlight\",\n    TipsSprite = \"TitleMenuTips\",\n    TipsHighlightSprite = \"TitleMenuTipsHighlight\",\n    LibrarySprite = \"TitleMenuLibrary\",\n    LibraryHighlightSprite = \"TitleMenuLibraryHighlight\",\n    EndingListSprite = \"TitleMenuEndingList\",\n    EndingListHighlightSprite = \"TitleMenuEndingListHighlight\",\n    TitleAnimationDurationIn = 32/60,\n    TitleAnimationDurationOut = 16/60,\n    TitleAnimationStartFrame = 15,\n    TitleAnimationFrameCount = 20,\n    TitleAnimationFileId = 32,\n    MenuEntriesSprites = {},\n    MenuEntriesHighlightedSprites = {},\n    ExitSprite = \"ExitSprite\",\n    MenuEntriesNum = 5,\n    ChoiceBlinkAnimationDurationIn = 1,\n    HighlightAnimationDurationIn = 15/60,\n    HighlightAnimationDurationOut = 15/60,\n    ExtraDisabledTint = 0x703030,\n};\n\nfor i = 0, 4 do\n    root.Sprites[\"TitleMenuEntry\" .. i] = {\n        Sheet = \"TitleChip\",\n        Bounds = {\n            X = 33,\n            Y = 288 + i * root.TitleMenu.ItemPadding,\n            Width = 223,\n            Height = 37\n        }\n    };\n    root.TitleMenu.MenuEntriesSprites[#root.TitleMenu.MenuEntriesSprites + 1] = \"TitleMenuEntry\" .. i;\nend\n\nfor i = 0, 4 do\n    root.Sprites[\"TitleMenuEntry\" .. i] = {\n        Sheet = \"TitleChip\",\n        Bounds = {\n            X = 33,\n            Y = 288 + i * root.TitleMenu.ItemPadding,\n            Width = 223,\n            Height = 37\n        }\n    };\n    root.TitleMenu.MenuEntriesHighlightedSprites[#root.TitleMenu.MenuEntriesHighlightedSprites + 1] = \"TitleMenuEntry\" .. i;\nend\n\nroot.Sprites[\"TitleMenuItemHighlight\"] = {\n    Sheet = \"TitleChip\",\n    Bounds = { X = 896, Y = 64, Width = 638, Height = 50 },\n};\n\nroot.Sprites[\"TitleMenuLoad\"] = {\n    Sheet = \"TitleChip\",\n    Bounds = { X = 417, Y = 288, Width = 114, Height = 39 },\n};\n\nroot.Sprites[\"TitleMenuLoadHighlight\"] = {\n    Sheet = \"TitleChip\",\n    Bounds = { X = 414, Y = 342, Width = 116, Height = 39 },\n};\n\nroot.Sprites[\"TitleMenuQLoad\"] = {\n    Sheet = \"TitleChip\",\n    Bounds = { X = 553, Y = 288, Width = 245, Height = 39 },\n};\n\nroot.Sprites[\"TitleMenuQLoadHighlight\"] = {\n    Sheet = \"TitleChip\",\n    Bounds = { X = 550, Y = 342, Width = 249, Height = 39 },\n};\n\nroot.Sprites[\"TitleMenuTips\"] = {\n    Sheet = \"TitleChip\",\n    Bounds = { X = 416, Y = 402, Width = 95, Height = 34 },\n};\n\nroot.Sprites[\"TitleMenuTipsHighlight\"] = {\n    Sheet = \"TitleChip\",\n    Bounds = { X = 417, Y = 457, Width = 96, Height = 36 },\n};\n\nroot.Sprites[\"TitleMenuLibrary\"] = {\n    Sheet = \"TitleChip\",\n    Bounds = { X = 535, Y = 402, Width = 178, Height = 34 },\n};\n\nroot.Sprites[\"TitleMenuLibraryHighlight\"] = {\n    Sheet = \"TitleChip\",\n    Bounds = { X = 535, Y = 457, Width = 180, Height = 36 },\n};\n\nroot.Sprites[\"TitleMenuEndingList\"] = {\n    Sheet = \"TitleChip\",\n    Bounds = { X = 736, Y = 401, Width = 258, Height = 35 },\n};\n\nroot.Sprites[\"TitleMenuEndingListHighlight\"] = {\n    Sheet = \"TitleChip\",\n    Bounds = { X = 736, Y = 457, Width = 260, Height = 36 },\n};\n\nroot.Sprites[\"TitleMenuPointerItemHighlight\"] = {\n    Sheet = \"TitleChip\",\n    Bounds = { X = 805, Y = 78, Width = 56, Height = 36 },\n};\n\nroot.Sprites[\"TitleMenuBackground\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 0, Y = 0, Width = 1920, Height = 1080 },\n};\n\nroot.Sprites[\"MainMenuBackground\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 0, Y = 1080, Width = 1920, Height = 1080 },\n};\n\nroot.Sprites[\"TitleMenuPressToStart\"] = {\n    Sheet = \"TitleChip\",\n    Bounds = { X = 583, Y = 75, Width = 198, Height = 169 },\n};\n\nroot.Sprites[\"CopyrightText\"] = {\n    Sheet = \"TitleChip\",\n    Bounds = { X = 582, Y = 9, Width = 785, Height = 21 },\n};\n\nroot.Sprites[\"TitleMenuMenu\"] = {\n    Sheet = \"TitleChip\",\n    Bounds = { X = 0, Y = 0, Width = 544, Height = 248 },\n};\n\nroot.Sprites[\"TitleMenuOverlay\"] = {\n    Sheet = \"MenuChip\",\n    Bounds = { X = 154, Y = 140, Width = 1900, Height = 1061 },\n};\n\nroot.Sprites[\"TitleMenuSmoke\"] = {\n    Sheet = \"MenuChip\",\n    Bounds = { X = 0, Y = 1638, Width = 2000, Height = 410 },\n};\n\nroot.Sprites[\"ExitSprite\"] = {\n    Sheet = \"TitleChip\",\n    Bounds = { X = 33, Y = 568, Width = 223, Height = 37 },\n};\n"
  },
  {
    "path": "profiles/cclcc/mapsystem.lua",
    "content": "root.MapSystem = {\n    MapBackground = \"MapBackground\",\n    MapPartsPhotoSprites = {},\n    MapPartsArticleSprites = {},\n    MapPartsPinSprites = {},\n    MapPartsTagSprites = {},\n    MapPartsPhotosNum = 18,\n    MapPartsArticlesNum = 16,\n    MapPartsPinsNum = 12,\n    MapPartsTagsNum = 16,\n    SelectedMapPoolTag = \"SelectedMapPoolTag\",\n    MapLineRed = \"MapLineRed\",\n    MapLine = \"MapLine\",\n    FadeAnimationDuration = 16 / 60\n};\n\nroot.Sprites[\"MapBackground\"] = {\n    Sheet = \"MapBG\",\n    Bounds = { X = 0, Y = 0, Width = 3378, Height = 1900 }\n};\n\nroot.Sprites[\"SelectedMapPoolTag\"] = {\n    Sheet = \"MapParts\",\n    Bounds = { X = 1770, Y = 2832, Width = 172, Height = 228 }\n};\n\nroot.Sprites[\"MapLineRed\"] = {\n    Sheet = \"MapParts\",\n    Bounds = { X = 1770, Y = 2832, Width = 2048, Height = 228 }\n};\n\nroot.Sprites[\"MapLine\"] = {\n    Sheet = \"MapParts\",\n    Bounds = { X = 0, Y = 13, Width = 2048, Height = 10 }\n};\n\nfor i = 0, root.MapSystem.MapPartsPhotosNum - 1 do\n    root.Sprites[\"MapPartsPhoto\" .. i] = {\n        Sheet = \"MapParts\",\n        Bounds = {\n            X = (i % 12) * 166 + 1,\n            Y = (i // 12) * 117 + 24 + 1,\n            Width = 164,\n            Height = 115\n        },\n    };\n    root.MapSystem.MapPartsPhotoSprites[#root.MapSystem.MapPartsPhotoSprites + 1] = \"MapPartsPhoto\" .. i;\nend\n\n\nfor i = 0, root.MapSystem.MapPartsArticlesNum - 1 do\n    root.Sprites[\"MapPartsArticle\" .. i] = {\n        Sheet = \"MapParts\",\n        Bounds = {\n            X = (i % 5) * 354 + 1,\n            Y = (i // 5) * 247 + 2014,\n            Width = 354,\n            Height = 247\n        },\n    };\n    root.MapSystem.MapPartsArticleSprites[#root.MapSystem.MapPartsArticleSprites + 1] = \"MapPartsArticle\" .. i;\nend\n\n\nfor i = 0, root.MapSystem.MapPartsPinsNum - 1 do\n    root.Sprites[\"MapPartsPin\" .. i] = {\n        Sheet = \"MapParts\",\n        Bounds = {\n            X = 2001,\n            Y = i * 60 + 24,\n            Width = 46,\n            Height = 58\n        },\n    };\n    root.MapSystem.MapPartsPinSprites[#root.MapSystem.MapPartsPinSprites + 1] = \"MapPartsPin\" .. i;\nend\n\nfor i = 0, root.MapSystem.MapPartsTagsNum - 1 do\n    root.Sprites[\"MapPartsTag\" .. i] = {\n        Sheet = \"MapParts\",\n        Bounds = {\n            X = (i % 2) * 97 + 1770,\n            Y = (i // 2) * 34 + 318,\n            Width = 95,\n            Height = 32\n        },\n    };\n  root.MapSystem.MapPartsTagSprites[#root.MapSystem.MapPartsTagSprites + 1] = \"MapPartsTag\" .. i;\nend\n"
  },
  {
    "path": "profiles/cclcc/savedata.lua",
    "content": "root.SaveData = {\n    Type = SaveDataType.CCLCC,\n    SaveFilePath = \"games/cclcc/savedata/SAVEDATA.DAT\",\n    ScriptMessageData={ -- Pairs of line count and offset into read flags array\n        {0x0, 0x0}, {0x6b, 0x0}, {0x8b, 0x6b}, {0x7e, 0xf6}, {0x144, 0x174}, {0x42, 0x2b8},\n        {0x0, 0x2fa}, {0xe3, 0x2fa}, {0x4b, 0x3dd}, {0x232, 0x428}, {0x78, 0x65a}, {0xb9, 0x6d2},\n        {0x46, 0x78b}, {0x194, 0x7d1}, {0x1f5, 0x965}, {0xda, 0xb5a}, {0x1f1, 0xc34}, {0x130, 0xe25},\n        {0x127, 0xf55}, {0x78, 0x107c}, {0xdc, 0x10f4}, {0x3d, 0x11d0}, {0x6a, 0x120d}, {0x109, 0x1277},\n        {0x79, 0x1380}, {0x46, 0x13f9}, {0x69, 0x143f}, {0x90, 0x14a8}, {0x62, 0x1538}, {0xf9, 0x159a},\n        {0x107, 0x1693}, {0xfc, 0x179a}, {0x5b, 0x1896}, {0x79, 0x18f1}, {0x15a, 0x196a}, {0x180, 0x1ac4},\n        {0x13f, 0x1c44}, {0xaa, 0x1d83}, {0x1f9, 0x1e2d}, {0xaa, 0x2026}, {0x4a, 0x20d0}, {0xf1, 0x211a},\n        {0x72, 0x220b}, {0x38, 0x227d}, {0xe5, 0x22b5}, {0x158, 0x239a}, {0xcb, 0x24f2}, {0x92, 0x25bd},\n        {0xc3, 0x264f}, {0x117, 0x2712}, {0x2c, 0x2829}, {0x98, 0x2855}, {0x4b, 0x28ed}, {0x177, 0x2938},\n        {0x98, 0x2aaf}, {0xe4, 0x2b47}, {0x18, 0x2c2b}, {0x199, 0x2c43}, {0xda, 0x2ddc}, {0x75, 0x2eb6},\n        {0xb7, 0x2f2b}, {0x1be, 0x2fe2}, {0x115, 0x31a0}, {0x0, 0x32b5}, {0x454, 0x32b5}, {0xee, 0x3709},\n        {0x78, 0x37f7}, {0x6f, 0x386f}, {0x83, 0x38de}, {0x118, 0x3961}, {0x97, 0x3a79}, {0x21, 0x3b10},\n        {0x70, 0x3b31}, {0xb, 0x3ba1}, {0xb, 0x3bac}, {0x27, 0x3bb7}, {0x2f, 0x3bde}, {0x8, 0x3c0d},\n        {0x78, 0x3c15}, {0x30, 0x3c8d}, {0x1a, 0x3cbd}, {0x147, 0x3cd7}, {0x29, 0x3e1e}, {0x72, 0x3e47},\n        {0x60, 0x3eb9}, {0x3d, 0x3f19}, {0x41, 0x3f56}, {0x2d, 0x3f97}, {0x43, 0x3fc4}, {0x28, 0x4007},\n        {0x17b, 0x402f}, {0x8b, 0x41aa}, {0x167, 0x4235}, {0x1, 0x439c}, {0x19, 0x439d}, {0x1, 0x43b6}\n    },\n\n    -- IDs in the save data EVflag array for all event CGs (+variants)\n    AlbumEvData = {\n        {0x1},\n        {0x2, 0x3},\n        {0x7},\n        {0x8},\n        {0x4, 0x5, 0x6},\n        {0x9},\n        {0xA},\n        {0xB},\n        {0xC, 0xD},\n        {0xE},\n        {0x10, 0x11},\n        {0x12},\n        {0x13},\n        {0x14, 0x15},\n        {0x16},\n        {0x19},\n        {0x1A, 0x1B},\n        {0x1C},\n        {0x17, 0x18},\n        {0x1D},\n        {0x1E, 0x1F},\n        {0x28},\n        {0xF},\n        {0x2B},\n        {0x2A},\n        {0x29},\n        {0x2C, 0x2D, 0x2E},\n        {0x2F},\n        {0x32, 0x33},\n        {0x34, 0x35, 0x36, 0x37},\n        {0x30, 0x31},\n        {0x38, 0x39, 0x3A},\n        {0x20, 0x21, 0x22, 0x23},\n        {0x3B, 0x3C},\n        {0x3D},\n        {0x45, 0x46},\n        {0x3F},\n        {0x40, 0x41, 0x42, 0x43},\n        {0x44},\n        {0x3E},\n        {0x4B, 0x4C},\n        {0x4D},\n        {0x4E},\n        {0x47, 0x48, 0x49, 0x4A},\n        {0x4F},\n        {0x50, 0x51},\n        {0x52},\n        {0x53},\n        {0x54},\n        {0x55, 0x56},\n        {0x57, 0x58},\n        {0x59, 0x5A},\n        {0x5B},\n        {0x5C},\n        {0x5D, 0x5E},\n        {0x5F},\n        {0x60},\n        {0x61},\n        {0x62},\n        {0x63},\n        {0x64, 0x65},\n        {0x66, 0x67, 0x68, 0x69},\n        {0x6A, 0x6B, 0x6C},\n        {0x6D},\n        {0x6E},\n    },\n    -- CG IDs for all the event CGs (+variants) that show up in the Album\n    AlbumData = {\n        {{0x19F}}, \n        {{0x1A0}, {0x1A1}}, \n        {{0x1A5}}, \n        {{0x1A6}}, \n        {{0x1A2}, {0x1A3}, {0x1A4}}, \n        {{0x1A7}}, \n        {{0x1A8}}, \n        {{0x1A9}}, \n        {{0x1AA}, {0x1AB}}, \n        {{0x1AC}}, \n        {{0x1AE}, {0x1AF}}, \n        {{0x1B0}}, \n        {{0x1B1}}, \n        {{0x1B2}, {0x1B3}}, \n        {{0x1B4}}, \n        {{0x1B7}}, \n        {{0x1B8}, {0x1B9}}, \n        {{0x1BA}}, \n        {{0x1B5}, {0x1B6}}, \n        {{0x1BB}}, \n        {{0x1BC}, {0x1BD}}, \n        {{0x1C6}}, \n        {{0x1AD}}, \n        {{0x1C9}}, \n        {{0x1C8}}, \n        {{0x1C7}}, \n        {{0x1CA}, {0x1CB}, {0x1CC}}, \n        {{0x1CD}}, \n        {{0x1D0}, {0x1D1}}, \n        {{0x1D2}, {0x1D3}, {0x1D4}, {0x1D5}}, \n        {{0x1CE}, {0x1CF}}, \n        {{0x1D6}, {0x1D7}, {0x1D8}}, \n        {{0x1BE}, {0x1BF}, {0x1C0}, {0x1C1}}, \n        {{0x1D9}, {0x1DA}}, \n        {{0x1DB}}, \n        {{0x1E3}, {0x1E4}}, \n        {{0x1DD}}, \n        {{0x1DE}, {0x1DF}, {0x1E0}, {0x1E1}}, \n        {{0x1E2}}, \n        {{0x1DC}}, \n        {{0x1E9}, {0x1EA}}, \n        {{0x1EB}}, \n        {{0x1EC}}, \n        {{0x1E5}, {0x1E6}, {0x1E7}, {0x1E8}}, \n        {{0x1ED}}, \n        {{0x1EE}, {0x1EF}}, \n        {{0x1F0}}, \n        {{0x1F1}}, \n        {{0x1F2}}, \n        {{0x1F3}, {0x1F4}}, \n        {{0x1F5}, {0x1F6}}, \n        {{0x1F7}, {0x1F8}}, \n        {{0x1F9}}, \n        {{0x1FA}}, \n        {{0x1FB}, {0x1FC}}, \n        {{0x1FD}}, \n        {{0x1FE}}, \n        {{0x1FF}}, \n        {{0x200}}, \n        {{0x201}}, \n        {{0x202}, {0x203}}, \n        {{0x204}, {0x205}, {0x206}, {0x207}}, \n        {{0x208}, {0x209}, {0x20A}}, \n        {{0x20B}}, \n        {{0x20C}},\n    }\n};"
  },
  {
    "path": "profiles/cclcc/scriptinput.lua",
    "content": "-- This is just common"
  },
  {
    "path": "profiles/cclcc/scriptvars.lua",
    "content": "local sv = root.ScriptVars;\n\nsv.SW_ATCHAN_SCROLL_MAX = 1504;\nsv.SW_TITLECUR1 = 2139;\nsv.SW_TITLECUR2 = 2140; --Actual guess\nsv.SW_CAP1POSX_OFS = 2380;\nsv.SW_CAP1POSY_OFS = 2381;\nsv.SW_CAP1SX_OFS = 2382;\nsv.SW_CAP1SY_OFS = 2383;\nsv.SW_CAP1SIZE_OFS = 2384;\nsv.SW_CAP1LX_OFS = 2385;\nsv.SW_CAP1LY_OFS = 2386;\nsv.SW_CAP1ROTZ_OFS = 2387;\nsv.SW_CAP1ALPHA_OFS = 2388;\nsv.SW_BG1POSX_OFS = 2400;\nsv.SW_BG1POSY_OFS = 2401;\nsv.SW_BG1SX_OFS = 2402;\nsv.SW_BG1SY_OFS = 2403;\nsv.SW_BG1SIZE_OFS = 2404;\nsv.SW_BG1LX_OFS = 2405;\nsv.SW_BG1LY_OFS = 2406;\nsv.SW_BG1ROTZ_OFS = 2407;\nsv.SW_BG1ALPHA_OFS = 2408;\nsv.SW_MASK1COLOR = 4400;\nsv.SW_MASK1ALPHA = 4401;\nsv.SW_MASK1PRI = 4402;\nsv.SW_MASK1POSX = 4403;\nsv.SW_MASK1POSY = 4404;\nsv.SW_MASK1SIZEX = 4405;\nsv.SW_MASK1SIZEY = 4406;\nsv.SW_MESWIN0TYPE = 4362;\nsv.SW_MESMODE0 = 4363;\nsv.SW_SVSCRNO1 = 3606;\nsv.SW_SVSCRNO2 = 3607;\nsv.SW_SVSCRNO3 = 3608;\nsv.SW_SVSCRNO4 = 3609;\nsv.SW_DELUSION_STATE = 6412;\nsv.SW_YESNO_PRI = 6437;\nsv.SW_UI_BTNGUIDE_REQ = 6500;\nsv.SW_UI_BTNGUIDE_PROG = 6501;\nsv.SW_UI_BTNGUIDE_TYPE = 6502;\n\nsv.SF_SYSTEMMENUCAPTURE=1378;\nsv.SF_MOVIECANCEL = 1822;\nsv.SF_MOVIEFL = 2485;\n\nsv.SF_CLR_TRUE_CCLCC=810;"
  },
  {
    "path": "profiles/cclcc/sprites.lua",
    "content": "root.SpriteSheets = {\n    [\"Album\"] = {\n        Path = { Mount = \"system\", Id = 0 },\n        DesignWidth = 4096,\n        DesignHeight = 4096\n    },\n    [\"Backlog\"] = {\n        Path = { Mount = \"system\", Id = 1 },\n        DesignWidth = 2048,\n        DesignHeight = 2048\n    },\n    [\"ClearList\"] = {\n        Path = { Mount = \"system\", Id = 3 },\n        DesignWidth = 4096,\n        DesignHeight = 2324\n    },\n    [\"Config\"] = {\n        Path = { Mount = \"system\", Id = 4 },\n        DesignWidth = 2048,\n        DesignHeight = 2538\n    },\n    [\"ConfigEx\"] = {\n        Path = { Mount = \"system\", Id = 5 },\n        DesignWidth = 4096,\n        DesignHeight = 3272\n    },\n    [\"Data\"] = {\n        Path = { Mount = \"system\", Id = 6 },\n        DesignWidth = 3072,\n        DesignHeight = 1536\n    },\n    [\"Menu\"] = {\n        Path = { Mount = \"system\", Id = 8 },\n        DesignWidth = 2048,\n        DesignHeight = 1024\n    },\n    [\"Font\"] = {\n        Path = { Mount = \"system\", Id = 9 },\n        DesignWidth = 3072,\n        DesignHeight = 5616\n    },\n    [\"LibraryMenu\"] = {\n        Path = { Mount = \"system\", Id = 14 },\n        DesignWidth = 2048,\n        DesignHeight = 2048\n    },\n    [\"LoadMenu\"] = {\n        Path = { Mount = \"system\", Id = 15 },\n        DesignWidth = 2048,\n        DesignHeight = 2048\n    },\n    [\"MapBG\"] = {\n        Path = { Mount = \"system\", Id = 16 },\n        DesignWidth = 3378,\n        DesignHeight = 1900\n    },\n    [\"MapParts\"] = {\n        Path = { Mount = \"system\", Id = 18 },\n        DesignWidth = 2048,\n        DesignHeight = 3072\n    },\n    [\"MenuBG\"] = {\n        Path = { Mount = \"system\", Id = 19 },\n        DesignWidth = 3000,\n        DesignHeight = 1500\n    },\n    [\"MenuChip\"] = {\n        Path = { Mount = \"system\", Id = 20 },\n        DesignWidth = 4096,\n        DesignHeight = 2048\n    },\n    [\"MesBox\"] = {\n        Path = { Mount = \"system\", Id = 21 },\n        DesignWidth = 1920,\n        DesignHeight = 600\n    },\n    [\"MovMenu\"] = {\n        Path = { Mount = \"system\", Id = 22 },\n        DesignWidth = 3072,\n        DesignHeight = 1228\n    },\n    [\"MusicMenu\"] = {\n        Path = { Mount = \"system\", Id = 23 },\n        DesignWidth = 3058,\n        DesignHeight = 1492\n    },\n    [\"NamePlate\"] = {\n        Path = { Mount = \"system\", Id = 24 },\n        DesignWidth = 4096,\n        DesignHeight = 2048\n    },\n    [\"QuickLoadMenu\"] = {\n        Path = { Mount = \"system\", Id = 25 },\n        DesignWidth = 2048,\n        DesignHeight = 2048\n    },\n    [\"SaveMenu\"] = {\n        Path = { Mount = \"system\", Id = 26 },\n        DesignWidth = 2048,\n        DesignHeight = 2048\n    },\n    [\"SysMesBox\"] = {\n        Path = { Mount = \"system\", Id = 27 },\n        DesignWidth = 2048,\n        DesignHeight = 1528\n    },\n    [\"Tips\"] = {\n        Path = { Mount = \"system\", Id = 28 },\n        DesignWidth = 2048,\n        DesignHeight = 2048\n    },\n    [\"Title\"] = {\n        Path = { Mount = \"system\", Id = 30 },\n        DesignWidth = 1920,\n        DesignHeight = 2160\n    },\n    [\"TitleChip\"] = {\n        Path = { Mount = \"system\", Id = 31 },\n        DesignWidth = 2048,\n        DesignHeight = 800\n    },\n    [\"YesNoBG0\"] = {\n        Path = { Mount = \"system\", Id = 35 },\n        DesignWidth = 3840,\n        DesignHeight = 2160\n    },\n    [\"YesNoBG1\"] = {\n        Path = { Mount = \"system\", Id = 36 },\n        DesignWidth = 3840,\n        DesignHeight = 2160\n    },\n    [\"YesNoBG2\"] = {\n        Path = { Mount = \"system\", Id = 37 },\n        DesignWidth = 3840,\n        DesignHeight = 2160\n    },\n    [\"YesNoBG3\"] = {\n        Path = { Mount = \"system\", Id = 38 },\n        DesignWidth = 3840,\n        DesignHeight = 2160\n    },\n    [\"YesNoChip\"] = {\n        Path = { Mount = \"system\", Id = 39 },\n        DesignWidth = 1536,\n        DesignHeight = 2400\n    },\n    [\"YesNoBlurMask\"] = {\n        Path = { Mount = \"mask\", Id = 12 },\n        DesignWidth = 512,\n        DesignHeight = 256\n    }\n};\n\nlocal numberOfPages = root.Language == \"Japanese\" and 16 or 1\n\nfor i = 0, numberOfPages do\n    root.SpriteSheets[\"ManualSheet\" .. i] = {\n        Path = { Mount = \"manual\", Id = i },\n        DesignWidth = 1920,\n        DesignHeight = 1080\n    };\nend\n\nroot.Sprites = {};\n"
  },
  {
    "path": "profiles/cclcc/tipssystem.lua",
    "content": "root.TipsSystem = {\n    Type = TipsSystemType.CCLCC,\n    MaxTipsCount = 300,\n    TipsTextTableId = 2,\n    TipsTextSortStrIndex = 10,\n};"
  },
  {
    "path": "profiles/cclcc/vfs.lua",
    "content": "root.Vfs = {\n    Mounts = {\n        [\"bg\"] = {\"games/cclcc/gamedata/bg.mpk\"},\n        [\"bgeffect\"] = {\"games/cclcc/gamedata/bgeffect.mpk\"},\n        [\"bgm\"] = {\"games/cclcc/gamedata/bgm.mpk\"},\n        [\"chara\"] = {\"games/cclcc/gamedata/chara.mpk\"},\n        [\"manual\"] = {\"games/cclcc/gamedata/manual.mpk\"},\n        [\"mask\"] = {\"games/cclcc/gamedata/mask.mpk\"},\n        [\"movie\"] = {\"games/cclcc/gamedata/movie.cls\"},        -- .cls generated; folder 'movie' needs manual addition\n        [\"script\"] = {\"games/cclcc/gamedata/script.cls\"},      -- .cls generated; folder 'script' needs manual addition\n        [\"se\"] = {\"games/cclcc/gamedata/se.mpk\"},\n        [\"sysse\"] = {\"games/cclcc/gamedata/sysse.mpk\"},\n        [\"system\"] = {\"games/cclcc/gamedata/system.mpk\"},\n        [\"voice\"] = {\"games/cclcc/gamedata/voice.mpk\"}\n    }\n};\n"
  },
  {
    "path": "profiles/cclcc/waveeffects.lua",
    "content": "root.WaveEffects = {\n    WaveMaxCount = 20;\n    BGWaveGridSize = { X = 160, Y = 90 }\n};\n"
  },
  {
    "path": "profiles/cclcc/yesnotrigger.lua",
    "content": "local function createYesNoData(value1, value2, value3, value4, value5, value6, value7, value8, value9, value10)\n    return {\n        bgPos = { X = value1, Y = value2 },\n        index = value3,\n        index2 = value4,\n        yesChipPos = { X = value5, Y = value6 },\n        noChipPos = { X = value7, Y = value8 },\n        bubblePos = { X = value9, Y = value10 }\n    }\nend\n\nlocal data1 = {\n    createYesNoData(698.0, -674.0, 4, 1, 2372.0, 278.0, 2626.0, 440.0, 2646.0, 330.0),\n    createYesNoData(698.0, -335.0, 5, 2, 2392.0, 588.0, 2626.0, 764.0, 2646.0, 645.0),\n    createYesNoData(698.0, 36.0, 6, 3, 2378.0, 897.0, 2713.0, 1069.0, 2646.0, 975.0),\n    createYesNoData(698.0, 234.0, 7, 14, 2410.0, 1224.0, 2702.0, 1347.0, 2661.0, 1287.0),\n    createYesNoData(238.0, -469.0, 8, 5, 2042.0, 344.0, 2048.0, 562.0, 2174.0, 504.0),\n    createYesNoData(238.0, -179.0, 9, 6, 1975.0, 784.0, 2126.0, 886.0, 2235.0, 765.0),\n    createYesNoData(238.0, 188.0, 10, 7, 1974.0, 1162.0, 2036.0, 1187.0, 2187.0, 1098.0),\n    createYesNoData(287.0, 412.0, 14, 15, 2330.0, 1440.0, 2040.0, 1460.0, 2229.0, 1356.0),\n    createYesNoData(-248.0, -636.0, 11, 9, 1459.0, 232.0, 1727.0, 439.0, 1689.0, 321.0),\n    createYesNoData(-283.0, -68.0, 12, 13, 1535.0, 694.0, 1589.0, 936.0, 1647.0, 852.0),\n    createYesNoData(-337.0, 396.0, 16, 15, 1424.0, 1370.0, 1540.0, 1448.0, 1632.0, 1341.0),\n    createYesNoData(-672.0, -487.0, 18, 12, 1052.0, 335.0, 1156.0, 504.0, 1269.0, 444.0),\n    createYesNoData(-708.0, -182.0, 18, 17, 1042.0, 638.0, 1049.0, 819.0, 1230.0, 750.0),\n    createYesNoData(-659.0, 131.0, 17, 16, 1064.0, 1029.0, 1184.0, 1181.0, 1302.0, 1068.0),\n    createYesNoData(381.0, 730.0, 0, 0, 2042.0, 344.0, 2048.0, 562.0, 2337.0, 1722.0),\n    createYesNoData(-427.0, 730.0, 0, 0, 2042.0, 344.0, 2048.0, 562.0, 1515.0, 1722.0),\n    createYesNoData(-1108.0, 499.0, 0, 0, 2042.0, 344.0, 2048.0, 562.0, 828.0, 1449.0),\n    createYesNoData(-1108.0, -31.0, 0, 0, 2042.0, 344.0, 2048.0, 562.0, 828.0, 957.0),\n    createYesNoData(-1108.0, -529.0, 0, 0, 2042.0, 344.0, 2048.0, 562.0, 828.0, 453.0),\n}\n\nlocal data2 = {\n    createYesNoData(1270.0, 198.0, 1, 2, 3304.0, 996.0, 3304.0, 1364.0, 3364.0, 1200.0),\n    createYesNoData(1189.0, -151.0, 3, 4, 2746.0, 767.0, 2883.0, 992.0, 3051.0, 846.0),\n    createYesNoData(1189.0, 635.0, 4, 5, 2881.0, 1393.0, 2741.0, 1628.0, 3051.0, 1584.0),\n    createYesNoData(592.0, -238.0, 6, 7, 2275.0, 564.0, 2159.0, 740.0, 2469.0, 762.0),\n    createYesNoData(592.0, 245.0, 7, 8, 2340.0, 1022.0, 2159.0, 1196.0, 2469.0, 1215.0),\n    createYesNoData(592.0, 716.0, 8, 9, 2340.0, 1482.0, 2154.0, 1668.0, 2469.0, 1674.0),\n    createYesNoData(-185.0, -598.0, 14, 10, 1396.0, 329.0, 1568.0, 489.0, 1719.0, 351.0),\n    createYesNoData(12.0, -214.0, 10, 11, 1588.0, 665.0, 1747.0, 891.0, 1908.0, 762.0),\n    createYesNoData(12.0, 278.0, 12, 13, 1588.0, 1124.0, 1747.0, 1349.0, 1908.0, 1215.0),\n    createYesNoData(-185.0, 701.0, 13, 18, 1680.0, 1482.0, 1396.0, 1668.0, 1719.0, 1674.0),\n    createYesNoData(-629.0, -340.0, 14, 15, 1111.0, 435.0, 998.0, 654.0, 1311.0, 618.0),\n    createYesNoData(-629.0, -71, 15, 16, 998.0, 807.0, 998.0, 940.0, 1311.0, 891.0),\n    createYesNoData(-629.0, 198.0, 16, 17, 998.0, 1059.0, 998.0, 1196.0, 1311.0, 1150.0),\n    createYesNoData(-629.0, 467.0, 17, 18, 998.0, 1364.0, 1111.0, 1578.0, 1311.0, 1422.0),\n    createYesNoData(-1245.0, -536.0, 0, 0, 2042.0, 344.0, 2048.0, 562.0, 615.0, 408.0),\n    createYesNoData(-1245.0, -253.0, 0, 0, 2042.0, 344.0, 2048.0, 562.0, 615.0, 723.0),\n    createYesNoData(-1245.0, 73.0, 0, 0, 2042.0, 344.0, 2048.0, 562.0, 615.0, 1032.0),\n    createYesNoData(-1245.0, 396.0, 0, 0, 2042.0, 344.0, 2048.0, 562.0, 615.0, 1344.0),\n    createYesNoData(-1245.0, 693.0, 0, 0, 2042.0, 344.0, 2048.0, 562.0, 615.0, 1668.0),\n}\n\nroot.YesNoTrigger = {\n    YesNoBackground0 = \"YesNoBackground0\",\n    YesNoBackground1 = \"YesNoBackground1\",\n    YesNoBackground2 = \"YesNoBackground2\",\n    YesNoBackground3 = \"YesNoBackground3\",\n    YNChipBlurBg0 = \"YNChipBlurBg0\",\n    YNChipBlurBg1 = \"YNChipBlurBg1\",\n    YNChipBlurBg2 = \"YNChipBlurBg2\",\n    YN1ChipYesL = \"YN1ChipYesL\",\n    YN1ChipNoL = \"YN1ChipNoL\",\n    YN1ChipYesS = \"YN1ChipYesS\",\n    YN1ChipNoS = \"YN1ChipNoS\",\n    YN2ChipYesL = \"YN2ChipYesL\",\n    YN2ChipNoL = \"YN2ChipNoL\",\n    YN2ChipYesS = \"YN2ChipYesS\",\n    YN2ChipNoS = \"YN2ChipNoS\",\n    ChipStar = \"YNChipStar\",\n    YNBlurMask = \"YNBlurMask\",\n    YNBgOverlay = \"YNBgOverlay\",\n    YesNoData1 = data1,\n    YesNoData2 = data2,\n    StarRotationPeriod = 164 / 60, \n};\n\nroot.Sprites[\"YesNoBackground0\"] = {\n    Sheet = \"YesNoBG0\",\n    Bounds = { X = 0, Y = 0, Width = 3840.0, Height = 2160 }\n};\nroot.Sprites[\"YesNoBackground1\"] = {\n    Sheet = \"YesNoBG1\",\n    Bounds = { X = 0, Y = 0, Width = 3840.0, Height = 2160 }\n};\nroot.Sprites[\"YesNoBackground2\"] = {\n    Sheet = \"YesNoBG2\",\n    Bounds = { X = 0, Y = 0, Width = 3840.0, Height = 2160 }\n};\nroot.Sprites[\"YesNoBackground3\"] = {\n    Sheet = \"YesNoBG3\",\n    Bounds = { X = 0, Y = 0, Width = 3840.0, Height = 2160 }\n};\nroot.Sprites[\"YNChipBlurBg0\"] = {\n    Sheet = \"YesNoChip\",\n    Bounds = { X = 0, Y = 0, Width = 960, Height = 540 }\n};\nroot.Sprites[\"YNChipBlurBg1\"] = {\n    Sheet = \"YesNoChip\",\n    Bounds = { X = 0, Y = 548, Width = 960, Height = 540 }\n};\nroot.Sprites[\"YNChipBlurBg2\"] = {\n    Sheet = \"YesNoChip\",\n    Bounds = { X = 0, Y = 1096, Width = 960, Height = 540 }\n};\nroot.Sprites[\"YN1ChipYesL\"] = {\n    Sheet = \"YesNoChip\",\n    Bounds = { X = 1056.0, Y = 0, Width = 96, Height = 55 }\n};\nroot.Sprites[\"YN1ChipNoL\"] = {\n    Sheet = \"YesNoChip\",\n    Bounds = { X = 1056.0, Y = 65, Width = 96, Height = 46 }\n};\nroot.Sprites[\"YN1ChipYesS\"] = {\n    Sheet = \"YesNoChip\",\n    Bounds = { X = 1056.0, Y = 128.0, Width = 96, Height = 55 }\n};\nroot.Sprites[\"YN1ChipNoS\"] = {\n    Sheet = \"YesNoChip\",\n    Bounds = { X = 1056.0, Y = 196.0, Width = 96, Height = 45 }\n};\nroot.Sprites[\"YN2ChipYesL\"] = {\n    Sheet = \"YesNoChip\",\n    Bounds = { X = 1248.0, Y = 0, Width = 148.0, Height = 70 }\n};\nroot.Sprites[\"YN2ChipNoL\"] = {\n    Sheet = \"YesNoChip\",\n    Bounds = { X = 1248.0, Y = 96, Width = 148.0, Height = 70 }\n};\nroot.Sprites[\"YN2ChipYesS\"] = {\n    Sheet = \"YesNoChip\",\n    Bounds = { X = 1248.0, Y = 192.0, Width = 106.0, Height = 46 }\n};\nroot.Sprites[\"YN2ChipNoS\"] = {\n    Sheet = \"YesNoChip\",\n    Bounds = { X = 1248.0, Y = 288.0, Width = 106.0, Height = 46 }\n};\nroot.Sprites[\"YNChipStar\"] = {\n    Sheet = \"YesNoChip\",\n    Bounds = { X = 1033.0, Y = 385.0, Width = 380, Height = 380 }\n};\nroot.Sprites[\"YNBgOverlay\"] = {\n    Sheet = \"MenuChip\",\n    Bounds = { X = 154.0, Y = 141.0, Width = 1900, Height = 1060 }\n};\nroot.Sprites[\"YNBlurMask\"] = {\n    Sheet = \"YesNoBlurMask\",\n    Bounds = { X = 0, Y = 0, Width = 512, Height = 256 }\n};\n"
  },
  {
    "path": "profiles/characterviewer/config.lua",
    "content": "root.Language = \"Japanese\";\nroot.ResolutionWidth = 1280;\nroot.ResolutionHeight = 720;\nroot.Fullscreen = false;"
  },
  {
    "path": "profiles/characterviewer/game.lua",
    "content": "include('chlcc/game.lua');\n\nroot.GameFeatures = GameFeature.Renderer2D | GameFeature.CharacterViewer | GameFeature.Audio | GameFeature.Input;\n\nroot.WindowName = \"Character Viewer\";"
  },
  {
    "path": "profiles/chlcc/achievementsystem.lua",
    "content": "root.AchievementData = {\n  Type = AchievementDataType.PS3,\n  AchievementDataPath = \"games/chlcc/trophydata/TROPHY.TRP\"\n}"
  },
  {
    "path": "profiles/chlcc/charset.lua",
    "content": "root.CharsetInternal = {\n    CharsetStr = \" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz　/:-;!?'.@#%~*&`()ﾟ^>+ﾀ０１２３４５６７８９ＡＢＣＤＥＦＧＨＩＪＫＬＭＮＯＰＱＲＳＴＵＶＷＸＹＺａｂｃｄｅｆｇｈｉｊｋｌｍｎｏｐｑｒｓｔｕｖｗｘｙｚ、。,.:;?!゛゜‘’“”（）〔〕［］｛｝〈〉《》「」『』【】＜＞【】・…～ー♪─ぁぃぅぇぉっゃゅょゎァィゥェォッャュョヮヵヶ①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮⑯⑰⑱⑲⑳％–━①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①①あいうえおかがきぎくぐけげこごさざしじすずせぜそぞただちぢつづてでとどなにぬねのはばぱひびぴふぶぷへべぺほぼぽまみむめもやゆよらりるれろわゐゑをんアイウエオカガキギクグケゲコゴサザシジスズセゼソゾタダチヂツヅテデトドナニヌネノハバパヒビピフブプヘベペホボポマミムメモヤユヨラリルレロワヰヱヲンヴ☆★＼／◎○△□－＋＝＆→←※＠〆〓αβ瞳僕見眼差部屋無機質天井突抜注呼誰身体重同時温感触暑刺分逃首持上星来独言今起話思超至近距離女子三次元豊満柔預例入寸前後顔細人指頬意識中必死記憶辿忘咲畑梨深名脳裏浮瞬間出事奔流噴渋谷連続猟奇殺件並行巻世界規模陰謀込知心彼会生秘密一度絶望助戦崩壊黒幕倒最廃墟化口交青泣好胸願制服緩外覗巨乳爆称号童貞卒業緊張場合脱待昨着衣常的考全濃厚付濡唇色舌蠢甘吐息回初自情充余裕態取卑屈点風疑問吹飛頭真白確日西條始第完先作期大襲慌押粉塵吸気管埃激破希残党嫉妬非野郎撃醜負組氏徐々晴影両手物騒足踏目傍腕肘当優実双姉妹惚修羅数年登萌品錯覚響不切震向嫁変魔法少以翠明学園勘違返二存在導答単妄想力雰囲悲悪消渡胆発呆然薄普段苦逸伝溶音操奪剣失別他仲良辛笑焦唯扉紙鮮断味配乙私仕聞喋本語半夏盛季節秋冬頃蒸特有困惑周景和光端駅平均乗客国街立高層放射熱陽炎揺信広歳校勉強方定受位継救男正直打砕英雄何反芻混乱内整理加速素得安翻弄Ⅱ験地建落君額滲汗拭雨月経過復興眠認駄逆刻引挙句酷現厄介動早胃痛弁穏訪耳塞臓任丈夫精神攻越比保証敵呂瀬象夢通将軍伸簡干渉幻格形祈戻涙途議状休週迎振舞遣肉由殻閉拒否犯能怖蒼番詳枷抱払予送趣捨未練裂守懐忍醒乃増殖羽悟了埋止血葉長社帰嫌題緒頑面握尻添冷火照赤馴謝慣喉隠背使遜種類壁散住妙室籠量更座具所腰掛膝海車椅凶公式蘇恐捕終我汝唱病念楽応声設表示幼様寄齢礼者下賤輩怒娘迫成妾説教選悔多杉駐推奨要求売根股画像巫山戯昇痴際映演万円黙決忠告遍故対難原歩垂納斬美員垣版権関係習活桐宮計限略結局朝鳴寝殴侵暴漢討勇絞開七親図暮敗驚急恥毎暗憧裸挑似危騙隙児型貧控奮供調囁接唾液舐床雑誌投害扱被勝欲飲溺嵐露狂兄追誘迸誓許喪廉姿厨褒喜嬉性新婚固癖奥恋春肩装借蔵主頼敬用買封価値擦草純穿属論威遠替尖久駆再文夜毛布寒弱家族解改尽挨拶小僧食窓況階丸偶探右責繋慢愛梢魂鬱珍済捜策徒造容適去繰猛烈衝善掌相鋭舎軽髪短竦案電豪系柄折転準備臨勢武闘派狩構倣悠健索協紛含割赦争隣祭筋岸歌惹魅紅潮撫邪痕跡典妖艶抗呪縛金楠走遅掲器約昔詰豹衆占陶群誇盗微集姦圧道険廊進従防書鍵利片貼秀尊効率報置噂検幽霊徴御把闇澄参机傾沈締静囚果拡個滅怯左父波博士除欠与慎眉穢水糸運命減叫冗談叩共皆棒院溢筒汚宣包網授退課没宿延焼匂路展怪縄移眺横角観察飾店涯孤費巡競泳嗅臭躍腋慮脅恨呑処申訳徳快代肝憤慨拘束可銃辱潤訴抵貴浴懲袖省禿銭歪十迷医診謹掴戸弾肌覆禁太到達符託裾潜滑聖帯絡読矢賛鼻算傷翼刃華麗軌描狭踊届狙輪郭霧輝轟謎瓦礫土砂為呟姫監陵吊働遺拠総冊飽源旧諦側技館募枕収遭障透律儀緑志坊騎毒則爽康支築程染厳席留疲克凝役奉提躊躇漁執仏頂即避釘母袋譲箱滴丁寧拍骸鱗穫渇都市級互環測徹百縮写撮耐荒亡八凍財玉枚釣若領域悶南京錠削研究羞抉蕩絵虹淀暇字脚羨溜科清浄王基幸蹴紹煩悩抑拗靴履委棚虐粘尋湯召喚逮罰複如窺拳歯噛末潔資昼晩肺居育師跳督試隔鏡肯鳥斜遙丘堀腹批判磁淡朱沸扇油塩拷蔑職悸酸遇憂援護ι゙極義千疾迅雷剥等漏堂奢底台換境雪看術爛漫泊抹寂鼓惨呈低劣掃翌停券洗丼汁浸偉因曇辞詞庫易緯便玄隅甲熟睡択肢湧顎眩俗伏峙己璧瀕窒友永些労遂努偵揶揄罵嘲辺施罠黄昏門松濤池閑憎疼紋木凡癒欺瞞茶革製犬四這銘陳列罪条誤庭貸彩損販薦擁灯花壇煌料仮拾泡鷲偽燥治陸橋轢煽欄紫据縁恩冒順壮章編揃午殊掻浅功潰遮贈幅担湿讐刹那撥餅脇獄豆異症朦朧窮骨醍醐硬板審慄償誉迂闊乾儲芸励詮歓及産忌宅析撤麦警補闖愚杯香堵鐘芯憑標紳顧藤融覧馬鹿蓋慕虫曖昧皮膚雲凹凸貌儚梯粒羊遊沌招恵寿鬼矛盾贄哀仰祝硫鉄曲訝聴妊婦丹田区斯瑕創植猜陥＃捏誠頻饒宇宙船靄鈴蝶塗斉塊貫杭閃竜渦擲咬纏沫捉彷徨抽陣歴史粋旋荘漂涼匹古譚詩銀囓衡懇隈奴隷棲積愕房秒夕顕兼昂鎖狼賢煮穴贖福伐概賭錐捧崇拝啓虜揚漆脊髄凛憬畏既紡咳奏洪郷愁唐工翔云煙統粧咆哮杞団猫般嘘柿晶貶鶏鳩球痙攣裁印東泉瞥斐依飯曜缶致炭頓耽俺専瞭官巧綻織絆膏虚老憐胴袈裟柱尾兆隊犠牲喫排挽惜曰魚苛懸喩爪材萎朗範乏促￥凄津揉濯米卵令北沢鍛慰腐札稚淵毅核凪刀架棘諸泰戟腺繕載齧営伽藍沙汰甚島赴旅査蓄醸倍僻摘樹槽荷筆膳嘆衛晒五晰衰牢臆挫敷叶阿槍猶胎墓石祷葬浜堪菓宴芽噌評免疫妥栄養劇菜炒朴漠喝箸皿碗磨帳録倉措桃腑紀票鵜餌鑑賞寡痺挿紐楕枝刊採隆焚港漬刷猥庁刑盲庇稼維鈍擬訂摯傘候畳燈氷桜岐績拓巳視線空降某商民稀吉六県沿軒川欧著各吻葡萄酒淫靡冥災＾×患脈宗侮偏匿嗜洋搭厭稽械献述孔兵棄療企妻踪町祥徘徊播獲鉱稿剛磯江俊榊契林愴ⅢⅣ粟累\";\n    Spaces = \" 　\";\n};\n"
  },
  {
    "path": "profiles/chlcc/config.lua",
    "content": "root.Language = \"Japanese\";\nroot.ResolutionWidth = 1280;\nroot.ResolutionHeight = 720;\nroot.Fullscreen = false;\nroot.DateFormat = DateFormatType.YMD;"
  },
  {
    "path": "profiles/chlcc/dialogue.lua",
    "content": "root.Sprites[\"ADVBox\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 768, Y = 815, Width = 1280, Height = 206 },\n};\nroot.Sprites[\"SecondaryADVBox\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 768, Y = 427, Width = 1280, Height = 206 },\n};\nroot.Sprites[\"ErinBox\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 36, Y = 143, Width = 541, Height = 168 },\n};\nroot.Sprites[\"AutoSkipArrowsSprite\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 174, Y = 2, Width = 62, Height = 62 },\n};\nroot.Sprites[\"AutoIconSprite\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 240, Y = 2, Width = 62, Height = 62 },\n};\nroot.Sprites[\"SkipIconSprite\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 306, Y = 2, Width = 62, Height = 62 },\n};\n\nroot.Dialogue = {\n    TipsBounds = { X = 404, Y = 150, Width = 800, Height = 420 },\n    TipsColorIndex = 0,\n    REVBounds = { X = 337, Y = 159, Width = 952, Height = 555 },\n    SecondaryREVBounds = {X = 338, Y = 159, Width = 480, Height = 106},\n    ErinBoxSprite = \"ErinBox\",\n    ErinBoxPos = { X = 314, Y = 156 },\n    REVNameFontSize = 32,\n    REVColor = 0,\n    REVNameColor = 24,\n    REVNameOffset = 34,\n    REVNameLocation = REVNameLocationType.TopLeft,\n    REVOutlineMode = 2,\n    REVNameOutlineMode = 2,\n    NVLBounds = { X = 125, Y = 85, Width = 1024, Height = 400 },\n    ADVBounds = { X = 161, Y = 546, Width = 928, Height = 180 },\n    ADVBoxSprite = \"ADVBox\",\n    SecondaryADVBoxSprite =  \"SecondaryADVBox\",\n    ADVBoxPos = { X = -1, Y = 512 },\n    FadeOutDuration = 0.33,\n    FadeInDuration = 0.33,\n    TextFadeInDuration = 0.33,\n    TextFadeOutDuration = 0.33,\n    DialogueBoxCurrentType = DialogueBoxType.CHLCC,\n    NVLBoxMaxOpacity = 0.55,\n    WaitIconSpriteAnim = \"WaitIconSpriteAnimDef\",\n    WaitIconCurrentType = WaitIconType.SpriteAnim,\n    AutoIconCurrentType = AutoIconType.CHLCC,\n    AutoIconSprite = \"AutoIconSprite\",\n    AutoIconOffset = { X = 1167, Y = 630 },\n    AutoIconRotationSpeed = 0.5 / 3,\n    SkipIconCurrentType = SkipIconType.CHLCC,\n    SkipIconSprite = \"SkipIconSprite\",\n    SkipIconOffset = { X = 1167, Y = 527 },\n    SkipIconRotationSpeed = 1.5 / 3,\n    AutoSkipArrowsSprite = \"AutoSkipArrowsSprite\",\n    REVWaitIconOffset = { X = 4, Y = -4 },\n    WaitIconOffset = { X = 4, Y = 4 },\n    DialogueFont = \"Default\",\n    REVLineHeight = 24,\n    REVLineSpacing = root.Language == \"Japanese\" and 16 or 0,\n    REVFontSize = 24,\n    SetFontSizeRatio = 1000.0,\n    DefaultFontSize = 32,\n    RubyFontSize = 14,\n    RubyYOffset = -18,\n    ColorTable = {\n        {0xFFFFFF, 0x000000}, {0x5080FF, 0x000000},\n        {0xFF7080, 0x000000}, {0xFFA0F8, 0x000000},\n        {0x46FF80, 0x000000}, {0x90FFFF, 0x000000},\n        {0xFFFF70, 0x000000}, {0x80FFC0, 0x000000},\n        {0xFFB080, 0x000000}, {0xB080FF, 0x000000},\n        {0x000000, 0x808080}, {0x000000, 0x5080FF},\n        {0x000000, 0xFF7080}, {0x000000, 0xFFA0F8},\n        {0x000000, 0x268840}, {0x000000, 0x409999},\n        {0x000000, 0x888830}, {0x000000, 0x80FFC0},\n        {0x000000, 0xFFB080}, {0x000000, 0xB080FF},\n        {0xD0D0D0, 0x000000}, {0xD0D0FF, 0x000000},\n        {0xFFD0D0, 0x000000}, {0xFFD0FF, 0x000000},\n        {0xD0FFD0, 0x000000}, {0xD0FFFF, 0x000000},\n        {0xFFFFD0, 0x000000}, {0xE8FFD0, 0x000000},\n        {0xFFE8D0, 0x000000}, {0xD0E8FF, 0x000000},\n        {0xFFFFFF, 0x808080}, {0xFFFFFF, 0x5080FF},\n        {0xFFFFFF, 0xFF7080}, {0xFFFFFF, 0xFFA0F8},\n        {0xFFFFFF, 0x46FF80}, {0xFFFFFF, 0x90FFFF},\n        {0xFFFFFF, 0xFFFF70}, {0xFFFFFF, 0x80FFC0},\n        {0xFFFFFF, 0xFFB080}, {0xFFFFFF, 0xB080FF},\n        {0xFFEEEE, 0x000000}, {0xFFCCCC, 0x000000},\n        {0xFFAAAA, 0x000000}, {0xFF9999, 0x000000},\n        {0xFF8888, 0x000000}, {0xFFFF00, 0x000000},\n        {0xFEF000, 0x000000}, {0xFF7777, 0x000000},\n        {0xFF6666, 0x000000}, {0xFF5555, 0x000000},\n        {0xFF4444, 0x000000}, {0xFF3333, 0x000000},\n        {0xFF2222, 0x000000}, {0xFF0000, 0x000000},\n        {0xDD0000, 0x000000}, {0xBB0000, 0x000000},\n        {0xB00000, 0x000000}, {0xAA0000, 0x000000},\n        {0x950000, 0x000000}, {0x808080, 0x000000},\n        {0xAAAAAA, 0x000000}, {0xAAC1C9, 0x000000},\n        {0x000000, 0x000000}, {0x000000, 0x000000},\n        {0x000000, 0x000000}, {0x000000, 0x000000},\n        {0x000000, 0x000000}, {0xF80B0B, 0x000000},\n        {0xF8910B, 0x000000}, {0x33F12A, 0x000000},\n        {0x00FF00, 0x000000}\n    },\n    MaxPageSize = 2000,\n    PageCount = 3,\n    ColorTagIsUint8 = false,\n\n    ADVNameAlignment = TextAlignment.Left,\n    ADVNameFontSize = 29,\n    ADVNamePos = { X = 132, Y = 470 },\n    NametagCurrentType = NametagType.CHLCC,\n    NametagPosition = { X = -1, Y = 465 },\n    NametagSprite = \"NametagSprite\",\n    SecondaryNametagSprite = \"SecondaryNametagSprite\"\n};\n\nMakeAnimation({\n    Name = \"WaitIconSpriteAnimDef\",\n    Sheet = \"Data\",\n    FirstFrameX = 0,\n    FirstFrameY = 97,\n    FrameWidth = 34,\n    ColWidth = 34,\n    FrameHeight = 34,\n    RowHeight = 34,\n    Frames = 8,\n    Duration = 1.5,\n    Rows = 8,\n    Columns = 1,\n    PrimaryDirection = AnimationDirections.Down,\n});\n\nroot.Sprites[\"NametagSprite\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 35, Y = 97, Width = 266, Height = 44 }\n}\n\nroot.Sprites[\"SecondaryNametagSprite\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 502, Y = 97, Width = 266, Height = 44 }\n}\n"
  },
  {
    "path": "profiles/chlcc/font-lb.lua",
    "content": "root.Fonts = {\n    [\"Default\"] = {\n        Type = FontType.LB,\n        ForegroundSheet = \"FontLBForeground\",\n        OutlineSheet = \"FontLBOutline\",\n        Columns = 64,\n        Rows = 39,\n        AdvanceWidths = \"games/chlcc/font-lb/widths.bin\",\n        AdvanceWidthsEmWidth = 60,\n        ForegroundOffset = { X = -7, Y = 0 },\n        OutlineOffset = { X = -6, Y = 1 },\n        LineSpacing = 0,\n    }\n};\n\nroot.SpriteSheets[\"Font\"] = nil;\n\nroot.SpriteSheets[\"FontLBForeground\"] = {\n    Path = \"games/chlcc/font-lb/foreground.png\",\n    DesignWidth = 4096,\n    DesignHeight = 2496\n};\n\nroot.SpriteSheets[\"FontLBOutline\"] = {\n    Path = \"games/chlcc/font-lb/outline.png\",\n    DesignWidth = 4096,\n    DesignHeight = 2496\n};"
  },
  {
    "path": "profiles/chlcc/font.lua",
    "content": "-- WARNING, 0-INDEXED ARRAYS AHEAD\n\nroot.Fonts = {\n    [\"Default\"] = {\n        Type = FontType.Basic,\n        Sheet = \"Font\",\n        Columns = 64,\n        Rows = 37,\n        AdvanceWidths = {},\n        AdvanceWidthsEmWidth = 32,\n        LineSpacing = 22,\n    }\n};\n\nlocal westernAdvanceWidths = {\n    [0]=0x20, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,\n        0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,\n        0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,\n        0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,\n        0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,\n        0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,\n        0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,\n        0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,\n        0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,\n        0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,\n        0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x1A, 0x08, 0x18, 0x18,\n        0x1A, 0x18, 0x1A, 0x18, 0x1A, 0x19, 0x1C, 0x19, 0x18, 0x1A, 0x16, 0x16,\n        0x18, 0x18, 0x08, 0x12, 0x17, 0x16, 0x1E, 0x18, 0x1A, 0x16, 0x1A, 0x18,\n        0x18, 0x1A, 0x18, 0x1C, 0x1E, 0x1A, 0x1A, 0x18, 0x14, 0x14, 0x14, 0x14,\n        0x14, 0x12, 0x14, 0x12, 0x08, 0x0E, 0x14, 0x08, 0x1C, 0x14, 0x16, 0x14,\n        0x14, 0x0F, 0x14, 0x12, 0x14, 0x16, 0x1C, 0x16, 0x12, 0x14, 0x11, 0x11,\n        0x08, 0x08, 0x08, 0x09, 0x10, 0x08, 0x0B, 0x09, 0x08, 0x08, 0x0E, 0x0E,\n        0x0A, 0x0A, 0x0C, 0x0C, 0x0B, 0x0B, 0x0A, 0x0A, 0x0D, 0x0D, 0x10, 0x10,\n        0x0D, 0x0D, 0x0E, 0x0E, 0x0D, 0x0D, 0x18, 0x18, 0x0D, 0x0D, 0x0A, 0x20,\n        0x1C, 0x1A, 0x16, 0x1E, 0x18, 0x17, 0x16, 0x18, 0x1A, 0x18, 0x18, 0x18, \n        0x18, 0x18, 0x18, 0x18, 0x16, 0x18, 0x18, 0x18, 0x18, 0x18, 0x14, 0x16, \n        0x16, 0x18,\n};\n\nfor i = 0, (64 * 37) - 1 do\n    if i <= #westernAdvanceWidths then\n        root.Fonts[\"Default\"].AdvanceWidths[i] = westernAdvanceWidths[i];\n    else\n        root.Fonts[\"Default\"].AdvanceWidths[i] = root.Fonts[\"Default\"].AdvanceWidthsEmWidth;\n    end\nend"
  },
  {
    "path": "profiles/chlcc/game.lua",
    "content": "root.ActiveRenderer = RendererType.OpenGL;\n\nroot.LayerCount = 100;\nroot.GameFeatures = GameFeature.Sc3VirtualMachine | GameFeature.Renderer2D | GameFeature.Input | \n    GameFeature.Audio | GameFeature.Video |  GameFeature.Subtitles | GameFeature.DebugMenu;\nroot.DesignWidth = 1280;\nroot.DesignHeight = 720;\n\nroot.WindowName = \"CHAOS;HEAD Love Chu☆Chu!\";\nroot.WindowIconPath = \"games/chlcc/icondata/icon.png\";\nroot.CursorArrowPath = \"games/chlcc/icondata/cursor_arrow.png\";\nroot.CursorPointerPath = \"games/chlcc/icondata/cursor_pointer.png\";\n\nroot.CharaIsMvl = false;\nroot.UseMoviePriority = true;\nroot.UseWaveEffects = true;\nroot.LayFileBigEndian = true;\nroot.LayFileTexXMultiplier = 2048;\nroot.LayFileTexYMultiplier = 1024;\n\nroot.ScreenCaptureCount = 2;\n\nroot.Vm = {\n    StartScript = 0,\n    StartScriptBuffer = 0,\n    GameInstructionSet = InstructionSet.CHLCC,\n    RestartMaskUsesThreadAlpha = true,\n    UseReturnIds = false,\n\n    ScrWorkChaStructSize = 20,\n    ScrWorkChaOffsetStructSize = 10,\n    ScrWorkBgStructSize = 20,\n    ScrWorkBgOffsetStructSize = 10,\n    ScrWorkCaptureEffectInfoStructSize = 2,\n};\n\ninclude('common/scriptinput.lua');\ninclude('common/scriptvars.lua');\ninclude('chlcc/config.lua');\ninclude('chlcc/scriptinput.lua');\ninclude('chlcc/scriptvars.lua');\ninclude('chlcc/savedata.lua');\ninclude('chlcc/achievementsystem.lua');\ninclude('chlcc/tipssystem.lua');\ninclude('chlcc/vfs.lua');\ninclude('chlcc/sprites.lua');\ninclude('common/animation.lua');\ninclude('chlcc/charset.lua');\ninclude('common/charset.lua');\nif root.Language == \"Japanese\" then\n    include('chlcc/font.lua');\nelse\n    include('chlcc/font-lb.lua');\nend\ninclude('chlcc/dialogue.lua');\ninclude('chlcc/hud/commonmenu.lua');\ninclude('chlcc/hud/saveicon.lua');\ninclude('chlcc/hud/loadingdisplay.lua');\ninclude('chlcc/hud/datedisplay.lua');\ninclude('chlcc/hud/titlemenu.lua');\ninclude('chlcc/hud/backlogmenu.lua');\ninclude('chlcc/hud/systemmenu.lua');\ninclude('chlcc/hud/savemenu.lua');\ninclude('chlcc/hud/sysmesboxdisplay.lua');\ninclude('chlcc/hud/selectiondisplay.lua');\ninclude('chlcc/hud/tipsmenu.lua');\ninclude('chlcc/hud/tipsnotification.lua');\ninclude('chlcc/hud/optionsmenu.lua');\ninclude('chlcc/hud/extramenus.lua');\ninclude('chlcc/hud/trophymenu.lua');\ninclude('chlcc/gamespecific.lua');\ninclude('chlcc/waveeffects.lua');"
  },
  {
    "path": "profiles/chlcc/gamespecific.lua",
    "content": "root.GameSpecific = {\n  Type = GameSpecificType.CHLCC,\n\n  MonitorScanline = \"MonitorScanline\",\n\n  ButterflySprites = {},\n  ButterflyFlapFrameDuration = 4/60,\n  ButterflyFrameCount = 8,\n  ButterflyFadeDuration = 64 / 60,\n\n  BubbleSpriteSmall = \"BubbleSpriteSmall\",\n  BubbleSpriteBig = \"BubbleSpriteBig\",\n  BubbleFadeDuration = 64/60,\n\n  EyecatchStar = \"EyecatchStar\",\n};\n\nroot.Sprites[\"MonitorScanline\"] = {\n  Sheet = \"Data\",\n  Bounds = { X = 1, Y = 369, Width = 640, Height = 360 },\n};\n\nroot.Sprites[\"EyecatchStar\"] = {\n  Sheet = \"Data\",\n  Bounds = { X = 1, Y = 917, Width = 106, Height = 106 },\n};\n\nfor i = 0, 5 do\n  local name = \"ButterflySprites\" .. i;\n  root.Sprites[name] = {\n      Sheet = \"Data\",\n      Bounds = {\n          X = i * 100 + 1,\n          Y = 817,\n          Width = 98,\n          Height = 98\n      },\n  };\n  root.GameSpecific.ButterflySprites[i + 1] = name\nend\n\nroot.Sprites[\"BubbleSpriteSmall\"] = {\n    Sheet = \"Data\",\n    Bounds = {\n        X = 129,\n        Y = 981,\n        Width = 22,\n        Height = 22\n    },\n};\n\nroot.Sprites[\"BubbleSpriteBig\"] = {\n    Sheet = \"Data\",\n    Bounds = {\n        X = 179,\n        Y = 967,\n        Width = 50,\n        Height = 50\n    },\n};\n\ninclude('chlcc/hud/delusiontrigger.lua');"
  },
  {
    "path": "profiles/chlcc/hud/backlogmenu.lua",
    "content": "root.BacklogMenu = {\n    Type = BacklogMenuType.CHLCC,\n    DrawType = DrawComponentType.SystemMenu,\n    BackgroundColor = 0x94b1ff,\n    CircleSprite = \"CircleBacklog\",\n    MenuTitleTextRightPosition = { X = 465, Y = 530 },\n    MenuTitleTextLeftPosition = { X = 77, Y = -5 },\n    MenuTitleTextAngle = 6.02,\n    MenuTitleText = \"BacklogMenuTitleText\",\n    ButtonPromptPosition = { X = 966, Y = 651 },\n    BacklogButtonPromptSprite = \"BacklogButtonPrompt\",\n    BacklogBackgroundSprite = \"BacklogBackground\",\n    EntryHighlightSprite = \"EntryHighlight\",\n    EntryHighlightLocation = EntryHighlightLocationType.AllLinesLeftOfScreen,\n    EntryHighlightOffset = { X = 75, Y = 0 },\n    EntryHighlightPadding = 2.0,\n    VoiceIconSprite = \"VoiceIcon\",\n    VoiceIconOffset = { X = 0, Y = 0 },\n    ScrollbarThumbSprite = \"ScrollbarThumb\",\n    ScrollbarTrackSprite = \"ScrollbarTrack\",\n    ScrollbarThumbLength = 59,\n    ScrollbarPosition = { X = 1183, Y = 92 },\n    EntriesStart = { X = 123, Y = 105 },\n    RenderingBounds = { X = 87, Y = 89, Width = 1086, Height = 539 },\n    EntryYPadding = 20,\n    HoverBounds = { X = 75, Y = 89, Width = 1086, Height = 535 },\n    FadeInDuration = 0.5,\n    FadeOutDuration = 0.5,\n    ScrollingSpeed = 600,\n    MinHoldTime = 0.5,\n    AdvanceFocusTimeInterval = 0.05,\n    PageUpDownHeight = 520\n};\n\nroot.Sprites[\"CircleBacklog\"] = {\n    Sheet = \"Backlog\",\n    Bounds = { X = 1, Y = 917, Width = 106, Height = 106 }\n}\n\nroot.Sprites[\"BacklogMenuTitleText\"] = {\n    Sheet = \"Backlog\",\n    Bounds = { X = 2, Y = 640, Width = 759, Height = 116 }\n}\n\nroot.Sprites[\"BacklogBackground\"] = {\n    Sheet = \"Backlog\",\n    Bounds = { X = 0, Y = 0, Width = 1208, Height = 634 },\n};\n\nroot.Sprites[\"VoiceIcon\"] = {\n    Sheet = \"Backlog\",\n    Bounds = { X = 1215, Y = 34, Width = 28, Height = 28 },\n};\n\nroot.Sprites[\"EntryHighlight\"] = {\n    Sheet = \"Backlog\",\n    Bounds = { X = 1, Y = 757, Width = 1099, Height = 30 }\n};\n\nroot.Sprites[\"BacklogButtonPrompt\"] = {\n    Sheet = \"Backlog\",\n    Bounds = { X = 1213, Y = 1, Width = 315, Height = 28 }\n}\n\nroot.Sprites[\"ScrollbarThumb\"] = {\n    Sheet = \"Backlog\",\n    Bounds = { X = 1222, Y = 68, Width = 14, Height = 59 },\n};\n\nroot.Sprites[\"ScrollbarTrack\"] = {\n    Sheet = \"Backlog\",\n    Bounds = { X = 0, Y = 0, Width = 14, Height = 535 },\n};"
  },
  {
    "path": "profiles/chlcc/hud/commonmenu.lua",
    "content": "root.CommonMenu = {\n  Type = CommonMenuType.CHLCC,\n  CircleStartPosition = { X = 20, Y = 20 },\n  CircleOffset = 200,\n\n  ErinSprite = \"Erin\",\n  ErinPosition = { X = 301, Y = 1 },\n  BackgroundFilter = \"BackgroundFilter\",\n\n  InitialRedBarPosition = { X = 0, Y = 538 },\n  RightRedBarPosition = { X = 1059, Y = 538 },\n  RedBarDivision = 1826,\n  RedBarBaseX = 1059,\n  RedBarSprite = \"RedBar\",\n  RedBarLabelPosition = { X = 1067, Y = 573 },\n  RedBarLabel = \"RedBarLabel\",\n\n  TitleFadeInDuration = 40 / 60,\n  TitleFadeOutDuration = 28 / 60,\n  MenuSelectPromptDuration =  110 / 60,\n  MenuSelectPromptInterval = 5 / 60,\n  MenuTransitionDuration = 64 / 60,\n  ShowPageAnimationStartTime = 16 / 60,\n  ShowPageAnimationDuration = (48 - 16) / 60,\n  ButtonPromptAnimationStartTime = 48 / 60,\n  ButtonPromptAnimationDuration = (64 - 48) / 60,\n  ButtonPromptStartPosition = { X = 603 + (64 - 48) * 40, Y = 651 },\n  DiagonalTitlesOffsetStart = {X = 572.0 * 3, Y= -460.0 },\n  DiagonalTitlesOffsetEnd = {X = -572.0 , Y= 460.0 / 3.0 }\n};\n\nroot.Sprites[\"Erin\"] = {\n    Sheet = \"Main\",\n    Bounds = { X = 641, Y = 1, Width = 978, Height = 798 }\n}\n\nroot.Sprites[\"BackgroundFilter\"] = {\n    Sheet = \"Main\",\n    Bounds = { X = 0, Y = 0, Width = 640, Height = 360 }\n}\n\nroot.Sprites[\"RedBar\"] = {\n    Sheet = \"Main\",\n    Bounds = { X = 767, Y = 913, Width = 1280, Height = 110 }\n}\n\nroot.Sprites[\"RedBarLabel\"] = {\n    Sheet = \"Main\",\n    Bounds = { X = 506, Y = 469, Width = 133, Height = 74 }\n}"
  },
  {
    "path": "profiles/chlcc/hud/datedisplay.lua",
    "content": "local sheet = \"Data\";\nlocal name = \"Date\";\n\nlocal yearNumFirstX = 1525;\nlocal yearNumFirstY = 63;\nlocal yearNumWidth = 25;\nlocal yearNum1Width = 8;\nlocal yearNumHeight = 20;\n\nlocal numFirstX = 1524;\nlocal numFirstY = 33;\nlocal numWidth = 40;\nlocal num1Width = 12;\nlocal numHeight = 28;\n\nlocal weekFirstX = 1785;\nlocal weekFirstY = 63;\nlocal weekSecondX = 1525;\nlocal weekSecondY = 85;\nlocal weekWidth = 73;\nlocal weekFriWidth = 56;\nlocal weekHeight = 20;\n\nroot.DateDisplay = {\n    Type = DateDisplayType.RNE,\n    YearNumSprites = {},\n    MonthNumSprites = {},\n    DayNumSprites = {},\n    WeekSprites = {},\n    BackgroundStartPos = { X = 1088, Y = 73 },\n    BackgroundEndPos = { X = 1088 - 256, Y = 73 },\n    DateStartX = 1167,\n    YearWeekY = 60,\n    MonthDayY = 52,\n    Spacing = 1,\n    FadeInDuration = 0.5,\n    FadeOutDuration = 0.5\n};\n\nfor i = 0, 9 do\n    local yearW;\n    if i == 1 then\n        yearW = yearNum1Width;\n    else\n        yearW = yearNumWidth;\n    end\n\n    root.Sprites[name .. \"YearNum\" .. i] = {\n        Sheet = sheet,\n        Bounds = {\n            X = yearNumFirstX,\n            Y = yearNumFirstY,\n            Width = yearW,\n            Height = yearNumHeight\n        }\n    };\n    root.DateDisplay.YearNumSprites[#root.DateDisplay.YearNumSprites + 1] = name .. \"YearNum\" .. i;\n\n    local numW\n    if i == 1 then\n        numW = num1Width;\n    else\n        numW = numWidth;\n    end\n\n    root.Sprites[name .. \"Num\" .. i] = {\n        Sheet = sheet,\n        Bounds = {\n            X = numFirstX,\n            Y = numFirstY,\n            Width = numW,\n            Height = numHeight\n        }\n    };\n    root.DateDisplay.MonthNumSprites[#root.DateDisplay.MonthNumSprites + 1] = name .. \"Num\" .. i;\n    root.DateDisplay.DayNumSprites[#root.DateDisplay.DayNumSprites + 1] = name .. \"Num\" .. i;\n\n    if i == 1 then\n        yearNumFirstX = yearNumFirstX + yearNum1Width;\n        numFirstX = numFirstX + num1Width;\n    else\n        yearNumFirstX = yearNumFirstX + yearNumWidth;\n        numFirstX = numFirstX + numWidth;\n    end\nend\n\nfor i = 0, 6 do\n    if i > 1 then\n        local weekW;\n        if i == 5 then\n            weekW = weekFriWidth;\n        else\n            weekW = weekWidth;\n        end\n\n        root.Sprites[name .. \"Week\" .. i] = {\n            Sheet = sheet,\n            Bounds = {\n                X = weekSecondX,\n                Y = weekSecondY,\n                Width = weekW,\n                Height = weekHeight\n            }\n        };\n        if i == 5 then\n            weekSecondX = weekSecondX + weekFriWidth;\n        else\n            weekSecondX = weekSecondX + weekWidth;\n         end\n    else\n        root.Sprites[name .. \"Week\" .. i] = {\n            Sheet = sheet,\n            Bounds = {\n                X = weekFirstX,\n                Y = weekFirstY,\n                Width = weekWidth,\n                Height = weekHeight\n            }\n        };\n        weekFirstX = weekFirstX + weekWidth;\n    end\n    root.DateDisplay.WeekSprites[#root.DateDisplay.WeekSprites + 1] = name .. \"Week\" .. i;\nend\n\nroot.Sprites[name .. \"MonthDaySeparator\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 1897,\n        Y = 33,\n        Width = 10,\n        Height = 28\n    }\n};\nroot.DateDisplay.MDSeparatorSprite = name .. \"MonthDaySeparator\";\n\nroot.Sprites[name .. \"DayYearSeparator\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 1758,\n        Y = 63,\n        Width = 8,\n        Height = 20\n    }\n};\nroot.DateDisplay.DYSeparatorSprite = name .. \"DayYearSeparator\";\n\nroot.Sprites[name .. \"OpenBracket\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 1766,\n        Y = 63,\n        Width = 8,\n        Height = 20\n    }\n};\nroot.DateDisplay.OpenBracketSprite = name .. \"OpenBracket\";\n\nroot.Sprites[name .. \"CloseBracket\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 1776,\n        Y = 63,\n        Width = 8,\n        Height = 20\n    }\n};\nroot.DateDisplay.CloseBracketSprite = name .. \"CloseBracket\";\n\nroot.Sprites[name .. \"Background\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 1525,\n        Y = 1,\n        Width = 450,\n        Height = 28\n    }\n};\nroot.DateDisplay.BackgroundSprite = name .. \"Background\";"
  },
  {
    "path": "profiles/chlcc/hud/delusiontrigger.lua",
    "content": "root.DelusionTrigger = {\n  BackgroundSprite = \"BackgroundSprite\",\n  BackgroundSpriteMask = \"BackgroundSpriteMask\",\n  ScreenMask = \"ScreenMask\",\n\n  DelusionTextGlyphs = {},\n  DelusionTextFadeDuration = 16/60,\n  DelusionTextXVelocity = 12,\n  DelusionScaledGlyphWidth = root.Language == \"Japanese\" and 330 or 195,\n  DelusionScaledGlyphHeight = 320,\n  DelusionTextLineSpacing = root.Language == \"Japanese\" and 10 or 5,\n};\n\nroot.Sprites[\"BackgroundSprite\"] = {\n  Sheet = \"Menu\",\n  Bounds = { X = 0, Y = 0, Width = 2048, Height = 1024 }\n}\nroot.Sprites[\"ScreenMask\"] = {\n  Sheet = \"DelusionUnderlayer\",\n  Bounds = { X = 0, Y = 0, Width = 1600, Height = 720 }\n}\nroot.Sprites[\"BackgroundSpriteMask\"] = {\n  Sheet = \"DelusionMask\",\n  Bounds = { X = 0, Y = 0, Width = 1024, Height = 1024 }\n}\n\nif root.Language == \"Japanese\" then\n  local textCharacterCount = {\n    13,\t13,\t12,\t17, 16,\t17,\t13,\t13,\n    14,\t19,\t16,\t16, 16,\t12,\t10,\t15,\n    12,\t20,\t15,\t14, 15,\n  }\n\n  for i = 0, 21 - 1 do\n    local currentLine = {}\n    for j = 0, textCharacterCount[i + 1] - 1 do\n        local name = \"DelusionTextGlyph\" .. i .. \"_\" .. j\n        root.Sprites[name] = {\n            Sheet = \"DelusionText\",\n            Bounds = {\n                X = j * 64,\n                Y = i * 64,\n                Width = 64,\n                Height = 64\n            },\n        };\n        currentLine[j + 1] = name;\n    end\n    root.DelusionTrigger.DelusionTextGlyphs[i + 1] = currentLine\n  end\nelse\n  local delusionTextGlyphs = {\n    {\n      { X = 0, Y = 0, Width = 39, Height = 64 },\n      { X = 39, Y = 0, Width = 39, Height = 64 },\n      { X = 78, Y = 0, Width = 39, Height = 64 },\n      { X = 117, Y = 0, Width = 39, Height = 64 },\n      { X = 156, Y = 0, Width = 39, Height = 64 },\n      { X = 195, Y = 0, Width = 39, Height = 64 },\n      { X = 234, Y = 0, Width = 39, Height = 64 },\n      { X = 273, Y = 0, Width = 39, Height = 64 },\n      { X = 312, Y = 0, Width = 39, Height = 64 },\n      { X = 351, Y = 0, Width = 39, Height = 64 },\n      { X = 390, Y = 0, Width = 39, Height = 64 },\n      { X = 429, Y = 0, Width = 39, Height = 64 },\n      { X = 468, Y = 0, Width = 39, Height = 64 },\n      { X = 507, Y = 0, Width = 39, Height = 64 },\n      { X = 546, Y = 0, Width = 39, Height = 64 },\n      { X = 585, Y = 0, Width = 39, Height = 64 },\n      { X = 624, Y = 0, Width = 39, Height = 64 },\n      { X = 663, Y = 0, Width = 39, Height = 64 },\n      { X = 702, Y = 0, Width = 39, Height = 64 },\n      { X = 741, Y = 0, Width = 39, Height = 64 },\n      { X = 780, Y = 0, Width = 39, Height = 64 },\n      { X = 819, Y = 0, Width = 39, Height = 64 },\n      { X = 858, Y = 0, Width = 39, Height = 64 },\n      { X = 897, Y = 0, Width = 39, Height = 64 },\n      { X = 936, Y = 0, Width = 39, Height = 64 },\n      { X = 975, Y = 0, Width = 39, Height = 64 },\n      { X = 1014, Y = 0, Width = 39, Height = 64 },\n      { X = 1053, Y = 0, Width = 39, Height = 64 },\n      { X = 1092, Y = 0, Width = 39, Height = 64 },\n      { X = 1131, Y = 0, Width = 39, Height = 64 },\n      { X = 1170, Y = 0, Width = 39, Height = 64 },\n      { X = 1209, Y = 0, Width = 39, Height = 64 },\n      { X = 1248, Y = 0, Width = 39, Height = 64 },\n      { X = 1287, Y = 0, Width = 39, Height = 64 },\n      { X = 1326, Y = 0, Width = 39, Height = 64 },\n      { X = 1365, Y = 0, Width = 39, Height = 64 },\n      { X = 1404, Y = 0, Width = 39, Height = 64 },\n      { X = 1443, Y = 0, Width = 39, Height = 64 },\n    },\n    {\n      { X = 0, Y = 64, Width = 39, Height = 64 },\n      { X = 39, Y = 64, Width = 39, Height = 64 },\n      { X = 78, Y = 64, Width = 39, Height = 64 },\n      { X = 117, Y = 64, Width = 39, Height = 64 },\n      { X = 156, Y = 64, Width = 39, Height = 64 },\n      { X = 195, Y = 64, Width = 39, Height = 64 },\n      { X = 234, Y = 64, Width = 39, Height = 64 },\n      { X = 273, Y = 64, Width = 39, Height = 64 },\n      { X = 312, Y = 64, Width = 39, Height = 64 },\n      { X = 351, Y = 64, Width = 39, Height = 64 },\n      { X = 390, Y = 64, Width = 39, Height = 64 },\n      { X = 429, Y = 64, Width = 39, Height = 64 },\n      { X = 468, Y = 64, Width = 39, Height = 64 },\n      { X = 507, Y = 64, Width = 39, Height = 64 },\n      { X = 546, Y = 64, Width = 39, Height = 64 },\n      { X = 585, Y = 64, Width = 39, Height = 64 },\n      { X = 624, Y = 64, Width = 39, Height = 64 },\n      { X = 663, Y = 64, Width = 39, Height = 64 },\n      { X = 702, Y = 64, Width = 39, Height = 64 },\n      { X = 741, Y = 64, Width = 39, Height = 64 },\n      { X = 780, Y = 64, Width = 39, Height = 64 },\n      { X = 819, Y = 64, Width = 39, Height = 64 },\n      { X = 858, Y = 64, Width = 39, Height = 64 },\n      { X = 897, Y = 64, Width = 39, Height = 64 },\n      { X = 936, Y = 64, Width = 39, Height = 64 },\n      { X = 975, Y = 64, Width = 39, Height = 64 },\n      { X = 1014, Y = 64, Width = 39, Height = 64 },\n      { X = 1053, Y = 64, Width = 39, Height = 64 },\n      { X = 1092, Y = 64, Width = 39, Height = 64 },\n      { X = 1131, Y = 64, Width = 39, Height = 64 },\n      { X = 1170, Y = 64, Width = 39, Height = 64 },\n      { X = 1209, Y = 64, Width = 39, Height = 64 },\n      { X = 1248, Y = 64, Width = 39, Height = 64 },\n      { X = 1287, Y = 64, Width = 39, Height = 64 },\n      { X = 1326, Y = 64, Width = 39, Height = 64 },\n      { X = 1365, Y = 64, Width = 39, Height = 64 },\n      { X = 1404, Y = 64, Width = 39, Height = 64 },\n      { X = 1443, Y = 64, Width = 39, Height = 64 },\n      { X = 1482, Y = 64, Width = 39, Height = 64 },\n      { X = 1521, Y = 64, Width = 39, Height = 64 },\n      { X = 1560, Y = 64, Width = 39, Height = 64 },\n      { X = 1599, Y = 64, Width = 39, Height = 64 },\n      { X = 1638, Y = 64, Width = 39, Height = 64 },\n      { X = 1677, Y = 64, Width = 39, Height = 64 },\n      { X = 1716, Y = 64, Width = 39, Height = 64 },\n      { X = 1755, Y = 64, Width = 39, Height = 64 },\n      { X = 1794, Y = 64, Width = 39, Height = 64 },\n      { X = 1833, Y = 64, Width = 39, Height = 64 },\n      { X = 1872, Y = 64, Width = 39, Height = 64 },\n      { X = 1911, Y = 64, Width = 39, Height = 64 },\n      { X = 1950, Y = 64, Width = 39, Height = 64 },\n      { X = 1989, Y = 64, Width = 39, Height = 64 },\n      { X = 2028, Y = 64, Width = 39, Height = 64 },\n      { X = 2067, Y = 64, Width = 39, Height = 64 },\n      { X = 2106, Y = 64, Width = 39, Height = 64 },\n      { X = 2145, Y = 64, Width = 39, Height = 64 },\n      { X = 2184, Y = 64, Width = 39, Height = 64 },\n      { X = 2223, Y = 64, Width = 39, Height = 64 },\n      { X = 2262, Y = 64, Width = 39, Height = 64 },\n      { X = 2301, Y = 64, Width = 39, Height = 64 },\n      { X = 2340, Y = 64, Width = 39, Height = 64 },\n      { X = 2379, Y = 64, Width = 39, Height = 64 },\n      { X = 2418, Y = 64, Width = 39, Height = 64 },\n      { X = 2457, Y = 64, Width = 39, Height = 64 },\n      { X = 2496, Y = 64, Width = 39, Height = 64 },\n      { X = 2535, Y = 64, Width = 39, Height = 64 },\n    },\n    {\n      { X = 0, Y = 128, Width = 39, Height = 64 },\n      { X = 39, Y = 128, Width = 39, Height = 64 },\n      { X = 78, Y = 128, Width = 39, Height = 64 },\n      { X = 117, Y = 128, Width = 39, Height = 64 },\n      { X = 156, Y = 128, Width = 39, Height = 64 },\n      { X = 195, Y = 128, Width = 39, Height = 64 },\n      { X = 234, Y = 128, Width = 39, Height = 64 },\n      { X = 273, Y = 128, Width = 39, Height = 64 },\n      { X = 312, Y = 128, Width = 39, Height = 64 },\n      { X = 351, Y = 128, Width = 39, Height = 64 },\n      { X = 390, Y = 128, Width = 39, Height = 64 },\n      { X = 429, Y = 128, Width = 39, Height = 64 },\n      { X = 468, Y = 128, Width = 39, Height = 64 },\n      { X = 507, Y = 128, Width = 39, Height = 64 },\n      { X = 546, Y = 128, Width = 39, Height = 64 },\n      { X = 585, Y = 128, Width = 39, Height = 64 },\n      { X = 624, Y = 128, Width = 39, Height = 64 },\n      { X = 663, Y = 128, Width = 39, Height = 64 },\n      { X = 702, Y = 128, Width = 39, Height = 64 },\n      { X = 741, Y = 128, Width = 39, Height = 64 },\n      { X = 780, Y = 128, Width = 39, Height = 64 },\n      { X = 819, Y = 128, Width = 39, Height = 64 },\n      { X = 858, Y = 128, Width = 39, Height = 64 },\n      { X = 897, Y = 128, Width = 39, Height = 64 },\n      { X = 936, Y = 128, Width = 39, Height = 64 },\n      { X = 975, Y = 128, Width = 39, Height = 64 },\n      { X = 1014, Y = 128, Width = 39, Height = 64 },\n      { X = 1053, Y = 128, Width = 39, Height = 64 },\n      { X = 1092, Y = 128, Width = 39, Height = 64 },\n      { X = 1131, Y = 128, Width = 39, Height = 64 },\n      { X = 1170, Y = 128, Width = 39, Height = 64 },\n      { X = 1209, Y = 128, Width = 39, Height = 64 },\n      { X = 1248, Y = 128, Width = 39, Height = 64 },\n      { X = 1287, Y = 128, Width = 39, Height = 64 },\n      { X = 1326, Y = 128, Width = 39, Height = 64 },\n      { X = 1365, Y = 128, Width = 39, Height = 64 },\n      { X = 1404, Y = 128, Width = 39, Height = 64 },\n      { X = 1443, Y = 128, Width = 39, Height = 64 },\n    },\n    {\n      { X = 0, Y = 192, Width = 39, Height = 64 },\n      { X = 39, Y = 192, Width = 39, Height = 64 },\n      { X = 78, Y = 192, Width = 39, Height = 64 },\n      { X = 117, Y = 192, Width = 39, Height = 64 },\n      { X = 156, Y = 192, Width = 39, Height = 64 },\n      { X = 195, Y = 192, Width = 39, Height = 64 },\n      { X = 234, Y = 192, Width = 39, Height = 64 },\n      { X = 273, Y = 192, Width = 39, Height = 64 },\n      { X = 312, Y = 192, Width = 39, Height = 64 },\n      { X = 351, Y = 192, Width = 39, Height = 64 },\n      { X = 390, Y = 192, Width = 39, Height = 64 },\n      { X = 429, Y = 192, Width = 39, Height = 64 },\n      { X = 468, Y = 192, Width = 39, Height = 64 },\n      { X = 507, Y = 192, Width = 39, Height = 64 },\n      { X = 546, Y = 192, Width = 39, Height = 64 },\n      { X = 585, Y = 192, Width = 39, Height = 64 },\n      { X = 624, Y = 192, Width = 39, Height = 64 },\n      { X = 663, Y = 192, Width = 39, Height = 64 },\n      { X = 702, Y = 192, Width = 39, Height = 64 },\n      { X = 741, Y = 192, Width = 39, Height = 64 },\n      { X = 780, Y = 192, Width = 39, Height = 64 },\n      { X = 819, Y = 192, Width = 39, Height = 64 },\n      { X = 858, Y = 192, Width = 39, Height = 64 },\n      { X = 897, Y = 192, Width = 39, Height = 64 },\n      { X = 936, Y = 192, Width = 39, Height = 64 },\n      { X = 975, Y = 192, Width = 39, Height = 64 },\n      { X = 1014, Y = 192, Width = 39, Height = 64 },\n      { X = 1053, Y = 192, Width = 39, Height = 64 },\n      { X = 1092, Y = 192, Width = 39, Height = 64 },\n      { X = 1131, Y = 192, Width = 39, Height = 64 },\n      { X = 1170, Y = 192, Width = 39, Height = 64 },\n      { X = 1209, Y = 192, Width = 39, Height = 64 },\n      { X = 1248, Y = 192, Width = 39, Height = 64 },\n      { X = 1287, Y = 192, Width = 39, Height = 64 },\n      { X = 1326, Y = 192, Width = 39, Height = 64 },\n      { X = 1365, Y = 192, Width = 39, Height = 64 },\n      { X = 1404, Y = 192, Width = 39, Height = 64 },\n      { X = 1443, Y = 192, Width = 39, Height = 64 },\n      { X = 1482, Y = 192, Width = 39, Height = 64 },\n      { X = 1521, Y = 192, Width = 39, Height = 64 },\n      { X = 1560, Y = 192, Width = 39, Height = 64 },\n    },\n    {\n      { X = 0, Y = 256, Width = 39, Height = 64 },\n      { X = 39, Y = 256, Width = 39, Height = 64 },\n      { X = 78, Y = 256, Width = 39, Height = 64 },\n      { X = 117, Y = 256, Width = 39, Height = 64 },\n      { X = 156, Y = 256, Width = 39, Height = 64 },\n      { X = 195, Y = 256, Width = 39, Height = 64 },\n      { X = 234, Y = 256, Width = 39, Height = 64 },\n      { X = 273, Y = 256, Width = 39, Height = 64 },\n      { X = 312, Y = 256, Width = 39, Height = 64 },\n      { X = 351, Y = 256, Width = 39, Height = 64 },\n      { X = 390, Y = 256, Width = 39, Height = 64 },\n      { X = 429, Y = 256, Width = 39, Height = 64 },\n      { X = 468, Y = 256, Width = 39, Height = 64 },\n      { X = 507, Y = 256, Width = 39, Height = 64 },\n      { X = 546, Y = 256, Width = 39, Height = 64 },\n      { X = 585, Y = 256, Width = 39, Height = 64 },\n      { X = 624, Y = 256, Width = 39, Height = 64 },\n      { X = 663, Y = 256, Width = 39, Height = 64 },\n      { X = 702, Y = 256, Width = 39, Height = 64 },\n      { X = 741, Y = 256, Width = 39, Height = 64 },\n      { X = 780, Y = 256, Width = 39, Height = 64 },\n      { X = 819, Y = 256, Width = 39, Height = 64 },\n      { X = 858, Y = 256, Width = 39, Height = 64 },\n      { X = 897, Y = 256, Width = 39, Height = 64 },\n      { X = 936, Y = 256, Width = 39, Height = 64 },\n      { X = 975, Y = 256, Width = 39, Height = 64 },\n      { X = 1014, Y = 256, Width = 39, Height = 64 },\n      { X = 1053, Y = 256, Width = 39, Height = 64 },\n      { X = 1092, Y = 256, Width = 39, Height = 64 },\n      { X = 1131, Y = 256, Width = 39, Height = 64 },\n      { X = 1170, Y = 256, Width = 39, Height = 64 },\n      { X = 1209, Y = 256, Width = 39, Height = 64 },\n      { X = 1248, Y = 256, Width = 39, Height = 64 },\n      { X = 1287, Y = 256, Width = 39, Height = 64 },\n      { X = 1326, Y = 256, Width = 39, Height = 64 },\n      { X = 1365, Y = 256, Width = 39, Height = 64 },\n    },\n    {\n      { X = 0, Y = 320, Width = 39, Height = 64 },\n      { X = 39, Y = 320, Width = 39, Height = 64 },\n      { X = 78, Y = 320, Width = 39, Height = 64 },\n      { X = 117, Y = 320, Width = 39, Height = 64 },\n      { X = 156, Y = 320, Width = 39, Height = 64 },\n      { X = 195, Y = 320, Width = 39, Height = 64 },\n      { X = 234, Y = 320, Width = 39, Height = 64 },\n      { X = 273, Y = 320, Width = 39, Height = 64 },\n      { X = 312, Y = 320, Width = 39, Height = 64 },\n      { X = 351, Y = 320, Width = 39, Height = 64 },\n      { X = 390, Y = 320, Width = 39, Height = 64 },\n      { X = 429, Y = 320, Width = 39, Height = 64 },\n      { X = 468, Y = 320, Width = 39, Height = 64 },\n      { X = 507, Y = 320, Width = 39, Height = 64 },\n      { X = 546, Y = 320, Width = 39, Height = 64 },\n      { X = 585, Y = 320, Width = 39, Height = 64 },\n      { X = 624, Y = 320, Width = 39, Height = 64 },\n      { X = 663, Y = 320, Width = 39, Height = 64 },\n      { X = 702, Y = 320, Width = 39, Height = 64 },\n      { X = 741, Y = 320, Width = 39, Height = 64 },\n      { X = 780, Y = 320, Width = 39, Height = 64 },\n      { X = 819, Y = 320, Width = 39, Height = 64 },\n      { X = 858, Y = 320, Width = 39, Height = 64 },\n      { X = 897, Y = 320, Width = 39, Height = 64 },\n      { X = 936, Y = 320, Width = 39, Height = 64 },\n      { X = 975, Y = 320, Width = 39, Height = 64 },\n      { X = 1014, Y = 320, Width = 39, Height = 64 },\n      { X = 1053, Y = 320, Width = 39, Height = 64 },\n      { X = 1092, Y = 320, Width = 39, Height = 64 },\n      { X = 1131, Y = 320, Width = 39, Height = 64 },\n      { X = 1170, Y = 320, Width = 39, Height = 64 },\n      { X = 1209, Y = 320, Width = 39, Height = 64 },\n      { X = 1248, Y = 320, Width = 39, Height = 64 },\n      { X = 1287, Y = 320, Width = 39, Height = 64 },\n      { X = 1326, Y = 320, Width = 39, Height = 64 },\n      { X = 1365, Y = 320, Width = 39, Height = 64 },\n      { X = 1404, Y = 320, Width = 39, Height = 64 },\n      { X = 1443, Y = 320, Width = 39, Height = 64 },\n      { X = 1482, Y = 320, Width = 39, Height = 64 },\n      { X = 1521, Y = 320, Width = 39, Height = 64 },\n      { X = 1560, Y = 320, Width = 39, Height = 64 },\n      { X = 1599, Y = 320, Width = 39, Height = 64 },\n      { X = 1638, Y = 320, Width = 39, Height = 64 },\n      { X = 1677, Y = 320, Width = 39, Height = 64 },\n      { X = 1716, Y = 320, Width = 39, Height = 64 },\n      { X = 1755, Y = 320, Width = 39, Height = 64 },\n      { X = 1794, Y = 320, Width = 39, Height = 64 },\n      { X = 1833, Y = 320, Width = 39, Height = 64 },\n      { X = 1872, Y = 320, Width = 39, Height = 64 },\n      { X = 1911, Y = 320, Width = 39, Height = 64 },\n      { X = 1950, Y = 320, Width = 39, Height = 64 },\n      { X = 1989, Y = 320, Width = 39, Height = 64 },\n      { X = 2028, Y = 320, Width = 39, Height = 64 },\n      { X = 2067, Y = 320, Width = 39, Height = 64 },\n      { X = 2106, Y = 320, Width = 39, Height = 64 },\n    },\n    {\n      { X = 0, Y = 384, Width = 39, Height = 64 },\n      { X = 39, Y = 384, Width = 39, Height = 64 },\n      { X = 78, Y = 384, Width = 39, Height = 64 },\n      { X = 117, Y = 384, Width = 39, Height = 64 },\n      { X = 156, Y = 384, Width = 39, Height = 64 },\n      { X = 195, Y = 384, Width = 39, Height = 64 },\n      { X = 234, Y = 384, Width = 39, Height = 64 },\n      { X = 273, Y = 384, Width = 39, Height = 64 },\n      { X = 312, Y = 384, Width = 39, Height = 64 },\n      { X = 351, Y = 384, Width = 39, Height = 64 },\n      { X = 390, Y = 384, Width = 39, Height = 64 },\n      { X = 429, Y = 384, Width = 39, Height = 64 },\n      { X = 468, Y = 384, Width = 39, Height = 64 },\n      { X = 507, Y = 384, Width = 39, Height = 64 },\n      { X = 546, Y = 384, Width = 39, Height = 64 },\n      { X = 585, Y = 384, Width = 39, Height = 64 },\n      { X = 624, Y = 384, Width = 39, Height = 64 },\n      { X = 663, Y = 384, Width = 39, Height = 64 },\n      { X = 702, Y = 384, Width = 39, Height = 64 },\n      { X = 741, Y = 384, Width = 39, Height = 64 },\n      { X = 780, Y = 384, Width = 39, Height = 64 },\n      { X = 819, Y = 384, Width = 39, Height = 64 },\n      { X = 858, Y = 384, Width = 39, Height = 64 },\n      { X = 897, Y = 384, Width = 39, Height = 64 },\n      { X = 936, Y = 384, Width = 39, Height = 64 },\n      { X = 975, Y = 384, Width = 39, Height = 64 },\n      { X = 1014, Y = 384, Width = 39, Height = 64 },\n      { X = 1053, Y = 384, Width = 39, Height = 64 },\n      { X = 1092, Y = 384, Width = 39, Height = 64 },\n      { X = 1131, Y = 384, Width = 39, Height = 64 },\n      { X = 1170, Y = 384, Width = 39, Height = 64 },\n      { X = 1209, Y = 384, Width = 39, Height = 64 },\n    },\n    {\n      { X = 0, Y = 448, Width = 39, Height = 64 },\n      { X = 39, Y = 448, Width = 39, Height = 64 },\n      { X = 78, Y = 448, Width = 39, Height = 64 },\n      { X = 117, Y = 448, Width = 39, Height = 64 },\n      { X = 156, Y = 448, Width = 39, Height = 64 },\n      { X = 195, Y = 448, Width = 39, Height = 64 },\n      { X = 234, Y = 448, Width = 39, Height = 64 },\n      { X = 273, Y = 448, Width = 39, Height = 64 },\n      { X = 312, Y = 448, Width = 39, Height = 64 },\n      { X = 351, Y = 448, Width = 39, Height = 64 },\n      { X = 390, Y = 448, Width = 39, Height = 64 },\n      { X = 429, Y = 448, Width = 39, Height = 64 },\n      { X = 468, Y = 448, Width = 39, Height = 64 },\n      { X = 507, Y = 448, Width = 39, Height = 64 },\n      { X = 546, Y = 448, Width = 39, Height = 64 },\n      { X = 585, Y = 448, Width = 39, Height = 64 },\n      { X = 624, Y = 448, Width = 39, Height = 64 },\n      { X = 663, Y = 448, Width = 39, Height = 64 },\n      { X = 702, Y = 448, Width = 39, Height = 64 },\n      { X = 741, Y = 448, Width = 39, Height = 64 },\n      { X = 780, Y = 448, Width = 39, Height = 64 },\n      { X = 819, Y = 448, Width = 39, Height = 64 },\n      { X = 858, Y = 448, Width = 39, Height = 64 },\n      { X = 897, Y = 448, Width = 39, Height = 64 },\n      { X = 936, Y = 448, Width = 39, Height = 64 },\n      { X = 975, Y = 448, Width = 39, Height = 64 },\n      { X = 1014, Y = 448, Width = 39, Height = 64 },\n      { X = 1053, Y = 448, Width = 39, Height = 64 },\n      { X = 1092, Y = 448, Width = 39, Height = 64 },\n      { X = 1131, Y = 448, Width = 39, Height = 64 },\n      { X = 1170, Y = 448, Width = 39, Height = 64 },\n      { X = 1209, Y = 448, Width = 39, Height = 64 },\n      { X = 1248, Y = 448, Width = 39, Height = 64 },\n      { X = 1287, Y = 448, Width = 39, Height = 64 },\n      { X = 1326, Y = 448, Width = 39, Height = 64 },\n      { X = 1365, Y = 448, Width = 39, Height = 64 },\n      { X = 1404, Y = 448, Width = 39, Height = 64 },\n      { X = 1443, Y = 448, Width = 39, Height = 64 },\n      { X = 1482, Y = 448, Width = 39, Height = 64 },\n      { X = 1521, Y = 448, Width = 39, Height = 64 },\n      { X = 1560, Y = 448, Width = 39, Height = 64 },\n      { X = 1599, Y = 448, Width = 39, Height = 64 },\n      { X = 1638, Y = 448, Width = 39, Height = 64 },\n    },\n    {\n      { X = 0, Y = 512, Width = 39, Height = 64 },\n      { X = 39, Y = 512, Width = 39, Height = 64 },\n      { X = 78, Y = 512, Width = 39, Height = 64 },\n      { X = 117, Y = 512, Width = 39, Height = 64 },\n      { X = 156, Y = 512, Width = 39, Height = 64 },\n      { X = 195, Y = 512, Width = 39, Height = 64 },\n      { X = 234, Y = 512, Width = 39, Height = 64 },\n      { X = 273, Y = 512, Width = 39, Height = 64 },\n      { X = 312, Y = 512, Width = 39, Height = 64 },\n      { X = 351, Y = 512, Width = 39, Height = 64 },\n      { X = 390, Y = 512, Width = 39, Height = 64 },\n      { X = 429, Y = 512, Width = 39, Height = 64 },\n      { X = 468, Y = 512, Width = 39, Height = 64 },\n      { X = 507, Y = 512, Width = 39, Height = 64 },\n      { X = 546, Y = 512, Width = 39, Height = 64 },\n      { X = 585, Y = 512, Width = 39, Height = 64 },\n      { X = 624, Y = 512, Width = 39, Height = 64 },\n      { X = 663, Y = 512, Width = 39, Height = 64 },\n      { X = 702, Y = 512, Width = 39, Height = 64 },\n      { X = 741, Y = 512, Width = 39, Height = 64 },\n      { X = 780, Y = 512, Width = 39, Height = 64 },\n      { X = 819, Y = 512, Width = 39, Height = 64 },\n      { X = 858, Y = 512, Width = 39, Height = 64 },\n      { X = 897, Y = 512, Width = 39, Height = 64 },\n      { X = 936, Y = 512, Width = 39, Height = 64 },\n      { X = 975, Y = 512, Width = 39, Height = 64 },\n      { X = 1014, Y = 512, Width = 39, Height = 64 },\n      { X = 1053, Y = 512, Width = 39, Height = 64 },\n      { X = 1092, Y = 512, Width = 39, Height = 64 },\n      { X = 1131, Y = 512, Width = 39, Height = 64 },\n      { X = 1170, Y = 512, Width = 39, Height = 64 },\n      { X = 1209, Y = 512, Width = 39, Height = 64 },\n      { X = 1248, Y = 512, Width = 39, Height = 64 },\n      { X = 1287, Y = 512, Width = 39, Height = 64 },\n      { X = 1326, Y = 512, Width = 39, Height = 64 },\n      { X = 1365, Y = 512, Width = 39, Height = 64 },\n      { X = 1404, Y = 512, Width = 39, Height = 64 },\n      { X = 1443, Y = 512, Width = 39, Height = 64 },\n      { X = 1482, Y = 512, Width = 39, Height = 64 },\n      { X = 1521, Y = 512, Width = 39, Height = 64 },\n      { X = 1560, Y = 512, Width = 39, Height = 64 },\n      { X = 1599, Y = 512, Width = 39, Height = 64 },\n      { X = 1638, Y = 512, Width = 39, Height = 64 },\n      { X = 1677, Y = 512, Width = 39, Height = 64 },\n      { X = 1716, Y = 512, Width = 39, Height = 64 },\n      { X = 1755, Y = 512, Width = 39, Height = 64 },\n      { X = 1794, Y = 512, Width = 39, Height = 64 },\n      { X = 1833, Y = 512, Width = 39, Height = 64 },\n      { X = 1872, Y = 512, Width = 39, Height = 64 },\n      { X = 1911, Y = 512, Width = 39, Height = 64 },\n      { X = 1950, Y = 512, Width = 39, Height = 64 },\n      { X = 1989, Y = 512, Width = 39, Height = 64 },\n      { X = 2028, Y = 512, Width = 39, Height = 64 },\n      { X = 2067, Y = 512, Width = 39, Height = 64 },\n      { X = 2106, Y = 512, Width = 39, Height = 64 },\n      { X = 2145, Y = 512, Width = 39, Height = 64 },\n      { X = 2184, Y = 512, Width = 39, Height = 64 },\n      { X = 2223, Y = 512, Width = 39, Height = 64 },\n      { X = 2262, Y = 512, Width = 39, Height = 64 },\n      { X = 2301, Y = 512, Width = 39, Height = 64 },\n      { X = 2340, Y = 512, Width = 39, Height = 64 },\n    },\n    {\n      { X = 0, Y = 576, Width = 39, Height = 64 },\n      { X = 39, Y = 576, Width = 39, Height = 64 },\n      { X = 78, Y = 576, Width = 39, Height = 64 },\n      { X = 117, Y = 576, Width = 39, Height = 64 },\n      { X = 156, Y = 576, Width = 39, Height = 64 },\n      { X = 195, Y = 576, Width = 39, Height = 64 },\n      { X = 234, Y = 576, Width = 39, Height = 64 },\n      { X = 273, Y = 576, Width = 39, Height = 64 },\n      { X = 312, Y = 576, Width = 39, Height = 64 },\n      { X = 351, Y = 576, Width = 39, Height = 64 },\n      { X = 390, Y = 576, Width = 39, Height = 64 },\n      { X = 429, Y = 576, Width = 39, Height = 64 },\n      { X = 468, Y = 576, Width = 39, Height = 64 },\n      { X = 507, Y = 576, Width = 39, Height = 64 },\n      { X = 546, Y = 576, Width = 39, Height = 64 },\n      { X = 585, Y = 576, Width = 39, Height = 64 },\n      { X = 624, Y = 576, Width = 39, Height = 64 },\n      { X = 663, Y = 576, Width = 39, Height = 64 },\n      { X = 702, Y = 576, Width = 39, Height = 64 },\n      { X = 741, Y = 576, Width = 39, Height = 64 },\n      { X = 780, Y = 576, Width = 39, Height = 64 },\n      { X = 819, Y = 576, Width = 39, Height = 64 },\n      { X = 858, Y = 576, Width = 39, Height = 64 },\n      { X = 897, Y = 576, Width = 39, Height = 64 },\n      { X = 936, Y = 576, Width = 39, Height = 64 },\n      { X = 975, Y = 576, Width = 39, Height = 64 },\n      { X = 1014, Y = 576, Width = 39, Height = 64 },\n      { X = 1053, Y = 576, Width = 39, Height = 64 },\n      { X = 1092, Y = 576, Width = 39, Height = 64 },\n      { X = 1131, Y = 576, Width = 39, Height = 64 },\n      { X = 1170, Y = 576, Width = 39, Height = 64 },\n      { X = 1209, Y = 576, Width = 39, Height = 64 },\n      { X = 1248, Y = 576, Width = 39, Height = 64 },\n      { X = 1287, Y = 576, Width = 39, Height = 64 },\n      { X = 1326, Y = 576, Width = 39, Height = 64 },\n      { X = 1365, Y = 576, Width = 39, Height = 64 },\n      { X = 1404, Y = 576, Width = 39, Height = 64 },\n      { X = 1443, Y = 576, Width = 39, Height = 64 },\n      { X = 1482, Y = 576, Width = 39, Height = 64 },\n      { X = 1521, Y = 576, Width = 39, Height = 64 },\n      { X = 1560, Y = 576, Width = 39, Height = 64 },\n      { X = 1599, Y = 576, Width = 39, Height = 64 },\n      { X = 1638, Y = 576, Width = 39, Height = 64 },\n      { X = 1677, Y = 576, Width = 39, Height = 64 },\n      { X = 1716, Y = 576, Width = 39, Height = 64 },\n      { X = 1755, Y = 576, Width = 39, Height = 64 },\n      { X = 1794, Y = 576, Width = 39, Height = 64 },\n      { X = 1833, Y = 576, Width = 39, Height = 64 },\n      { X = 1872, Y = 576, Width = 39, Height = 64 },\n      { X = 1911, Y = 576, Width = 39, Height = 64 },\n      { X = 1950, Y = 576, Width = 39, Height = 64 },\n      { X = 1989, Y = 576, Width = 39, Height = 64 },\n      { X = 2028, Y = 576, Width = 39, Height = 64 },\n      { X = 2067, Y = 576, Width = 39, Height = 64 },\n      { X = 2106, Y = 576, Width = 39, Height = 64 },\n      { X = 2145, Y = 576, Width = 39, Height = 64 },\n      { X = 2184, Y = 576, Width = 39, Height = 64 },\n      { X = 2223, Y = 576, Width = 39, Height = 64 },\n      { X = 2262, Y = 576, Width = 39, Height = 64 },\n      { X = 2301, Y = 576, Width = 39, Height = 64 },\n      { X = 2340, Y = 576, Width = 39, Height = 64 },\n      { X = 2379, Y = 576, Width = 39, Height = 64 },\n      { X = 2418, Y = 576, Width = 39, Height = 64 },\n      { X = 2457, Y = 576, Width = 39, Height = 64 },\n      { X = 2496, Y = 576, Width = 39, Height = 64 },\n      { X = 2535, Y = 576, Width = 39, Height = 64 },\n      { X = 2574, Y = 576, Width = 39, Height = 64 },\n      { X = 2613, Y = 576, Width = 39, Height = 64 },\n      { X = 2652, Y = 576, Width = 39, Height = 64 },\n      { X = 2691, Y = 576, Width = 39, Height = 64 },\n    },\n    {\n      { X = 0, Y = 640, Width = 39, Height = 64 },\n      { X = 39, Y = 640, Width = 39, Height = 64 },\n      { X = 78, Y = 640, Width = 39, Height = 64 },\n      { X = 117, Y = 640, Width = 39, Height = 64 },\n      { X = 156, Y = 640, Width = 39, Height = 64 },\n      { X = 195, Y = 640, Width = 39, Height = 64 },\n      { X = 234, Y = 640, Width = 39, Height = 64 },\n      { X = 273, Y = 640, Width = 39, Height = 64 },\n      { X = 312, Y = 640, Width = 39, Height = 64 },\n      { X = 351, Y = 640, Width = 39, Height = 64 },\n      { X = 390, Y = 640, Width = 39, Height = 64 },\n      { X = 429, Y = 640, Width = 39, Height = 64 },\n      { X = 468, Y = 640, Width = 39, Height = 64 },\n      { X = 507, Y = 640, Width = 39, Height = 64 },\n      { X = 546, Y = 640, Width = 39, Height = 64 },\n      { X = 585, Y = 640, Width = 39, Height = 64 },\n      { X = 624, Y = 640, Width = 39, Height = 64 },\n      { X = 663, Y = 640, Width = 39, Height = 64 },\n      { X = 702, Y = 640, Width = 39, Height = 64 },\n      { X = 741, Y = 640, Width = 39, Height = 64 },\n      { X = 780, Y = 640, Width = 39, Height = 64 },\n      { X = 819, Y = 640, Width = 39, Height = 64 },\n      { X = 858, Y = 640, Width = 39, Height = 64 },\n      { X = 897, Y = 640, Width = 39, Height = 64 },\n      { X = 936, Y = 640, Width = 39, Height = 64 },\n      { X = 975, Y = 640, Width = 39, Height = 64 },\n      { X = 1014, Y = 640, Width = 39, Height = 64 },\n      { X = 1053, Y = 640, Width = 39, Height = 64 },\n      { X = 1092, Y = 640, Width = 39, Height = 64 },\n      { X = 1131, Y = 640, Width = 39, Height = 64 },\n      { X = 1170, Y = 640, Width = 39, Height = 64 },\n      { X = 1209, Y = 640, Width = 39, Height = 64 },\n      { X = 1248, Y = 640, Width = 39, Height = 64 },\n      { X = 1287, Y = 640, Width = 39, Height = 64 },\n      { X = 1326, Y = 640, Width = 39, Height = 64 },\n      { X = 1365, Y = 640, Width = 39, Height = 64 },\n      { X = 1404, Y = 640, Width = 39, Height = 64 },\n      { X = 1443, Y = 640, Width = 39, Height = 64 },\n      { X = 1482, Y = 640, Width = 39, Height = 64 },\n      { X = 1521, Y = 640, Width = 39, Height = 64 },\n      { X = 1560, Y = 640, Width = 39, Height = 64 },\n      { X = 1599, Y = 640, Width = 39, Height = 64 },\n      { X = 1638, Y = 640, Width = 39, Height = 64 },\n      { X = 1677, Y = 640, Width = 39, Height = 64 },\n      { X = 1716, Y = 640, Width = 39, Height = 64 },\n      { X = 1755, Y = 640, Width = 39, Height = 64 },\n    },\n    {\n      { X = 0, Y = 704, Width = 39, Height = 64 },\n      { X = 39, Y = 704, Width = 39, Height = 64 },\n      { X = 78, Y = 704, Width = 39, Height = 64 },\n      { X = 117, Y = 704, Width = 39, Height = 64 },\n      { X = 156, Y = 704, Width = 39, Height = 64 },\n      { X = 195, Y = 704, Width = 39, Height = 64 },\n      { X = 234, Y = 704, Width = 39, Height = 64 },\n      { X = 273, Y = 704, Width = 39, Height = 64 },\n      { X = 312, Y = 704, Width = 39, Height = 64 },\n      { X = 351, Y = 704, Width = 39, Height = 64 },\n      { X = 390, Y = 704, Width = 39, Height = 64 },\n      { X = 429, Y = 704, Width = 39, Height = 64 },\n      { X = 468, Y = 704, Width = 39, Height = 64 },\n      { X = 507, Y = 704, Width = 39, Height = 64 },\n      { X = 546, Y = 704, Width = 39, Height = 64 },\n      { X = 585, Y = 704, Width = 39, Height = 64 },\n      { X = 624, Y = 704, Width = 39, Height = 64 },\n      { X = 663, Y = 704, Width = 39, Height = 64 },\n      { X = 702, Y = 704, Width = 39, Height = 64 },\n      { X = 741, Y = 704, Width = 39, Height = 64 },\n      { X = 780, Y = 704, Width = 39, Height = 64 },\n      { X = 819, Y = 704, Width = 39, Height = 64 },\n      { X = 858, Y = 704, Width = 39, Height = 64 },\n      { X = 897, Y = 704, Width = 39, Height = 64 },\n      { X = 936, Y = 704, Width = 39, Height = 64 },\n      { X = 975, Y = 704, Width = 39, Height = 64 },\n      { X = 1014, Y = 704, Width = 39, Height = 64 },\n      { X = 1053, Y = 704, Width = 39, Height = 64 },\n      { X = 1092, Y = 704, Width = 39, Height = 64 },\n      { X = 1131, Y = 704, Width = 39, Height = 64 },\n      { X = 1170, Y = 704, Width = 39, Height = 64 },\n      { X = 1209, Y = 704, Width = 39, Height = 64 },\n      { X = 1248, Y = 704, Width = 39, Height = 64 },\n      { X = 1287, Y = 704, Width = 39, Height = 64 },\n      { X = 1326, Y = 704, Width = 39, Height = 64 },\n      { X = 1365, Y = 704, Width = 39, Height = 64 },\n      { X = 1404, Y = 704, Width = 39, Height = 64 },\n      { X = 1443, Y = 704, Width = 39, Height = 64 },\n      { X = 1482, Y = 704, Width = 39, Height = 64 },\n      { X = 1521, Y = 704, Width = 39, Height = 64 },\n    },\n    {\n      { X = 0, Y = 768, Width = 39, Height = 64 },\n      { X = 39, Y = 768, Width = 39, Height = 64 },\n      { X = 78, Y = 768, Width = 39, Height = 64 },\n      { X = 117, Y = 768, Width = 39, Height = 64 },\n      { X = 156, Y = 768, Width = 39, Height = 64 },\n      { X = 195, Y = 768, Width = 39, Height = 64 },\n      { X = 234, Y = 768, Width = 39, Height = 64 },\n      { X = 273, Y = 768, Width = 39, Height = 64 },\n      { X = 312, Y = 768, Width = 39, Height = 64 },\n      { X = 351, Y = 768, Width = 39, Height = 64 },\n      { X = 390, Y = 768, Width = 39, Height = 64 },\n      { X = 429, Y = 768, Width = 39, Height = 64 },\n      { X = 468, Y = 768, Width = 39, Height = 64 },\n      { X = 507, Y = 768, Width = 39, Height = 64 },\n      { X = 546, Y = 768, Width = 39, Height = 64 },\n      { X = 585, Y = 768, Width = 39, Height = 64 },\n      { X = 624, Y = 768, Width = 39, Height = 64 },\n      { X = 663, Y = 768, Width = 39, Height = 64 },\n      { X = 702, Y = 768, Width = 39, Height = 64 },\n      { X = 741, Y = 768, Width = 39, Height = 64 },\n      { X = 780, Y = 768, Width = 39, Height = 64 },\n      { X = 819, Y = 768, Width = 39, Height = 64 },\n      { X = 858, Y = 768, Width = 39, Height = 64 },\n      { X = 897, Y = 768, Width = 39, Height = 64 },\n      { X = 936, Y = 768, Width = 39, Height = 64 },\n      { X = 975, Y = 768, Width = 39, Height = 64 },\n      { X = 1014, Y = 768, Width = 39, Height = 64 },\n      { X = 1053, Y = 768, Width = 39, Height = 64 },\n      { X = 1092, Y = 768, Width = 39, Height = 64 },\n      { X = 1131, Y = 768, Width = 39, Height = 64 },\n      { X = 1170, Y = 768, Width = 39, Height = 64 },\n      { X = 1209, Y = 768, Width = 39, Height = 64 },\n      { X = 1248, Y = 768, Width = 39, Height = 64 },\n      { X = 1287, Y = 768, Width = 39, Height = 64 },\n      { X = 1326, Y = 768, Width = 39, Height = 64 },\n      { X = 1365, Y = 768, Width = 39, Height = 64 },\n      { X = 1404, Y = 768, Width = 39, Height = 64 },\n      { X = 1443, Y = 768, Width = 39, Height = 64 },\n      { X = 1482, Y = 768, Width = 39, Height = 64 },\n      { X = 1521, Y = 768, Width = 39, Height = 64 },\n      { X = 1560, Y = 768, Width = 39, Height = 64 },\n      { X = 1599, Y = 768, Width = 39, Height = 64 },\n      { X = 1638, Y = 768, Width = 39, Height = 64 },\n      { X = 1677, Y = 768, Width = 39, Height = 64 },\n      { X = 1716, Y = 768, Width = 39, Height = 64 },\n      { X = 1755, Y = 768, Width = 39, Height = 64 },\n    },\n    {\n      { X = 0, Y = 832, Width = 39, Height = 64 },\n      { X = 39, Y = 832, Width = 39, Height = 64 },\n      { X = 78, Y = 832, Width = 39, Height = 64 },\n      { X = 117, Y = 832, Width = 39, Height = 64 },\n      { X = 156, Y = 832, Width = 39, Height = 64 },\n      { X = 195, Y = 832, Width = 39, Height = 64 },\n      { X = 234, Y = 832, Width = 39, Height = 64 },\n      { X = 273, Y = 832, Width = 39, Height = 64 },\n      { X = 312, Y = 832, Width = 39, Height = 64 },\n      { X = 351, Y = 832, Width = 39, Height = 64 },\n      { X = 390, Y = 832, Width = 39, Height = 64 },\n      { X = 429, Y = 832, Width = 39, Height = 64 },\n      { X = 468, Y = 832, Width = 39, Height = 64 },\n      { X = 507, Y = 832, Width = 39, Height = 64 },\n      { X = 546, Y = 832, Width = 39, Height = 64 },\n      { X = 585, Y = 832, Width = 39, Height = 64 },\n      { X = 624, Y = 832, Width = 39, Height = 64 },\n      { X = 663, Y = 832, Width = 39, Height = 64 },\n      { X = 702, Y = 832, Width = 39, Height = 64 },\n      { X = 741, Y = 832, Width = 39, Height = 64 },\n      { X = 780, Y = 832, Width = 39, Height = 64 },\n      { X = 819, Y = 832, Width = 39, Height = 64 },\n      { X = 858, Y = 832, Width = 39, Height = 64 },\n      { X = 897, Y = 832, Width = 39, Height = 64 },\n      { X = 936, Y = 832, Width = 39, Height = 64 },\n      { X = 975, Y = 832, Width = 39, Height = 64 },\n      { X = 1014, Y = 832, Width = 39, Height = 64 },\n      { X = 1053, Y = 832, Width = 39, Height = 64 },\n      { X = 1092, Y = 832, Width = 39, Height = 64 },\n    },\n    {\n      { X = 0, Y = 896, Width = 39, Height = 64 },\n      { X = 39, Y = 896, Width = 39, Height = 64 },\n      { X = 78, Y = 896, Width = 39, Height = 64 },\n      { X = 117, Y = 896, Width = 39, Height = 64 },\n      { X = 156, Y = 896, Width = 39, Height = 64 },\n      { X = 195, Y = 896, Width = 39, Height = 64 },\n      { X = 234, Y = 896, Width = 39, Height = 64 },\n      { X = 273, Y = 896, Width = 39, Height = 64 },\n      { X = 312, Y = 896, Width = 39, Height = 64 },\n      { X = 351, Y = 896, Width = 39, Height = 64 },\n      { X = 390, Y = 896, Width = 39, Height = 64 },\n      { X = 429, Y = 896, Width = 39, Height = 64 },\n      { X = 468, Y = 896, Width = 39, Height = 64 },\n      { X = 507, Y = 896, Width = 39, Height = 64 },\n      { X = 546, Y = 896, Width = 39, Height = 64 },\n      { X = 585, Y = 896, Width = 39, Height = 64 },\n      { X = 624, Y = 896, Width = 39, Height = 64 },\n      { X = 663, Y = 896, Width = 39, Height = 64 },\n      { X = 702, Y = 896, Width = 39, Height = 64 },\n      { X = 741, Y = 896, Width = 39, Height = 64 },\n      { X = 780, Y = 896, Width = 39, Height = 64 },\n      { X = 819, Y = 896, Width = 39, Height = 64 },\n      { X = 858, Y = 896, Width = 39, Height = 64 },\n      { X = 897, Y = 896, Width = 39, Height = 64 },\n      { X = 936, Y = 896, Width = 39, Height = 64 },\n      { X = 975, Y = 896, Width = 39, Height = 64 },\n      { X = 1014, Y = 896, Width = 39, Height = 64 },\n      { X = 1053, Y = 896, Width = 39, Height = 64 },\n      { X = 1092, Y = 896, Width = 39, Height = 64 },\n      { X = 1131, Y = 896, Width = 39, Height = 64 },\n      { X = 1170, Y = 896, Width = 39, Height = 64 },\n      { X = 1209, Y = 896, Width = 39, Height = 64 },\n      { X = 1248, Y = 896, Width = 39, Height = 64 },\n    },\n    {\n      { X = 0, Y = 960, Width = 39, Height = 64 },\n      { X = 39, Y = 960, Width = 39, Height = 64 },\n      { X = 78, Y = 960, Width = 39, Height = 64 },\n      { X = 117, Y = 960, Width = 39, Height = 64 },\n      { X = 156, Y = 960, Width = 39, Height = 64 },\n      { X = 195, Y = 960, Width = 39, Height = 64 },\n      { X = 234, Y = 960, Width = 39, Height = 64 },\n      { X = 273, Y = 960, Width = 39, Height = 64 },\n      { X = 312, Y = 960, Width = 39, Height = 64 },\n      { X = 351, Y = 960, Width = 39, Height = 64 },\n      { X = 390, Y = 960, Width = 39, Height = 64 },\n      { X = 429, Y = 960, Width = 39, Height = 64 },\n      { X = 468, Y = 960, Width = 39, Height = 64 },\n      { X = 507, Y = 960, Width = 39, Height = 64 },\n      { X = 546, Y = 960, Width = 39, Height = 64 },\n      { X = 585, Y = 960, Width = 39, Height = 64 },\n      { X = 624, Y = 960, Width = 39, Height = 64 },\n      { X = 663, Y = 960, Width = 39, Height = 64 },\n      { X = 702, Y = 960, Width = 39, Height = 64 },\n      { X = 741, Y = 960, Width = 39, Height = 64 },\n      { X = 780, Y = 960, Width = 39, Height = 64 },\n      { X = 819, Y = 960, Width = 39, Height = 64 },\n      { X = 858, Y = 960, Width = 39, Height = 64 },\n      { X = 897, Y = 960, Width = 39, Height = 64 },\n      { X = 936, Y = 960, Width = 39, Height = 64 },\n      { X = 975, Y = 960, Width = 39, Height = 64 },\n      { X = 1014, Y = 960, Width = 39, Height = 64 },\n      { X = 1053, Y = 960, Width = 39, Height = 64 },\n      { X = 1092, Y = 960, Width = 39, Height = 64 },\n      { X = 1131, Y = 960, Width = 39, Height = 64 },\n      { X = 1170, Y = 960, Width = 39, Height = 64 },\n      { X = 1209, Y = 960, Width = 39, Height = 64 },\n      { X = 1248, Y = 960, Width = 39, Height = 64 },\n      { X = 1287, Y = 960, Width = 39, Height = 64 },\n      { X = 1326, Y = 960, Width = 39, Height = 64 },\n      { X = 1365, Y = 960, Width = 39, Height = 64 },\n      { X = 1404, Y = 960, Width = 39, Height = 64 },\n      { X = 1443, Y = 960, Width = 39, Height = 64 },\n      { X = 1482, Y = 960, Width = 39, Height = 64 },\n      { X = 1521, Y = 960, Width = 39, Height = 64 },\n      { X = 1560, Y = 960, Width = 39, Height = 64 },\n      { X = 1599, Y = 960, Width = 39, Height = 64 },\n      { X = 1638, Y = 960, Width = 39, Height = 64 },\n      { X = 1677, Y = 960, Width = 39, Height = 64 },\n      { X = 1716, Y = 960, Width = 39, Height = 64 },\n      { X = 1755, Y = 960, Width = 39, Height = 64 },\n      { X = 1794, Y = 960, Width = 39, Height = 64 },\n      { X = 1833, Y = 960, Width = 39, Height = 64 },\n      { X = 1872, Y = 960, Width = 39, Height = 64 },\n      { X = 1911, Y = 960, Width = 39, Height = 64 },\n    },\n    {\n      { X = 0, Y = 1024, Width = 39, Height = 64 },\n      { X = 39, Y = 1024, Width = 39, Height = 64 },\n      { X = 78, Y = 1024, Width = 39, Height = 64 },\n      { X = 117, Y = 1024, Width = 39, Height = 64 },\n      { X = 156, Y = 1024, Width = 39, Height = 64 },\n      { X = 195, Y = 1024, Width = 39, Height = 64 },\n      { X = 234, Y = 1024, Width = 39, Height = 64 },\n      { X = 273, Y = 1024, Width = 39, Height = 64 },\n      { X = 312, Y = 1024, Width = 39, Height = 64 },\n      { X = 351, Y = 1024, Width = 39, Height = 64 },\n      { X = 390, Y = 1024, Width = 39, Height = 64 },\n      { X = 429, Y = 1024, Width = 39, Height = 64 },\n      { X = 468, Y = 1024, Width = 39, Height = 64 },\n      { X = 507, Y = 1024, Width = 39, Height = 64 },\n      { X = 546, Y = 1024, Width = 39, Height = 64 },\n      { X = 585, Y = 1024, Width = 39, Height = 64 },\n      { X = 624, Y = 1024, Width = 39, Height = 64 },\n      { X = 663, Y = 1024, Width = 39, Height = 64 },\n      { X = 702, Y = 1024, Width = 39, Height = 64 },\n      { X = 741, Y = 1024, Width = 39, Height = 64 },\n      { X = 780, Y = 1024, Width = 39, Height = 64 },\n      { X = 819, Y = 1024, Width = 39, Height = 64 },\n      { X = 858, Y = 1024, Width = 39, Height = 64 },\n      { X = 897, Y = 1024, Width = 39, Height = 64 },\n      { X = 936, Y = 1024, Width = 39, Height = 64 },\n      { X = 975, Y = 1024, Width = 39, Height = 64 },\n      { X = 1014, Y = 1024, Width = 39, Height = 64 },\n      { X = 1053, Y = 1024, Width = 39, Height = 64 },\n      { X = 1092, Y = 1024, Width = 39, Height = 64 },\n    },\n    {\n      { X = 0, Y = 1088, Width = 39, Height = 64 },\n      { X = 39, Y = 1088, Width = 39, Height = 64 },\n      { X = 78, Y = 1088, Width = 39, Height = 64 },\n      { X = 117, Y = 1088, Width = 39, Height = 64 },\n      { X = 156, Y = 1088, Width = 39, Height = 64 },\n      { X = 195, Y = 1088, Width = 39, Height = 64 },\n      { X = 234, Y = 1088, Width = 39, Height = 64 },\n      { X = 273, Y = 1088, Width = 39, Height = 64 },\n      { X = 312, Y = 1088, Width = 39, Height = 64 },\n      { X = 351, Y = 1088, Width = 39, Height = 64 },\n      { X = 390, Y = 1088, Width = 39, Height = 64 },\n      { X = 429, Y = 1088, Width = 39, Height = 64 },\n      { X = 468, Y = 1088, Width = 39, Height = 64 },\n      { X = 507, Y = 1088, Width = 39, Height = 64 },\n      { X = 546, Y = 1088, Width = 39, Height = 64 },\n      { X = 585, Y = 1088, Width = 39, Height = 64 },\n      { X = 624, Y = 1088, Width = 39, Height = 64 },\n      { X = 663, Y = 1088, Width = 39, Height = 64 },\n      { X = 702, Y = 1088, Width = 39, Height = 64 },\n      { X = 741, Y = 1088, Width = 39, Height = 64 },\n      { X = 780, Y = 1088, Width = 39, Height = 64 },\n      { X = 819, Y = 1088, Width = 39, Height = 64 },\n      { X = 858, Y = 1088, Width = 39, Height = 64 },\n      { X = 897, Y = 1088, Width = 39, Height = 64 },\n      { X = 936, Y = 1088, Width = 39, Height = 64 },\n      { X = 975, Y = 1088, Width = 39, Height = 64 },\n      { X = 1014, Y = 1088, Width = 39, Height = 64 },\n      { X = 1053, Y = 1088, Width = 39, Height = 64 },\n      { X = 1092, Y = 1088, Width = 39, Height = 64 },\n      { X = 1131, Y = 1088, Width = 39, Height = 64 },\n      { X = 1170, Y = 1088, Width = 39, Height = 64 },\n      { X = 1209, Y = 1088, Width = 39, Height = 64 },\n      { X = 1248, Y = 1088, Width = 39, Height = 64 },\n      { X = 1287, Y = 1088, Width = 39, Height = 64 },\n      { X = 1326, Y = 1088, Width = 39, Height = 64 },\n      { X = 1365, Y = 1088, Width = 39, Height = 64 },\n      { X = 1404, Y = 1088, Width = 39, Height = 64 },\n      { X = 1443, Y = 1088, Width = 39, Height = 64 },\n      { X = 1482, Y = 1088, Width = 39, Height = 64 },\n      { X = 1521, Y = 1088, Width = 39, Height = 64 },\n      { X = 1560, Y = 1088, Width = 39, Height = 64 },\n      { X = 1599, Y = 1088, Width = 39, Height = 64 },\n      { X = 1638, Y = 1088, Width = 39, Height = 64 },\n      { X = 1677, Y = 1088, Width = 39, Height = 64 },\n      { X = 1716, Y = 1088, Width = 39, Height = 64 },\n      { X = 1755, Y = 1088, Width = 39, Height = 64 },\n      { X = 1794, Y = 1088, Width = 39, Height = 64 },\n      { X = 1833, Y = 1088, Width = 39, Height = 64 },\n      { X = 1872, Y = 1088, Width = 39, Height = 64 },\n      { X = 1911, Y = 1088, Width = 39, Height = 64 },\n      { X = 1950, Y = 1088, Width = 39, Height = 64 },\n      { X = 1989, Y = 1088, Width = 39, Height = 64 },\n      { X = 2028, Y = 1088, Width = 39, Height = 64 },\n      { X = 2067, Y = 1088, Width = 39, Height = 64 },\n      { X = 2106, Y = 1088, Width = 39, Height = 64 },\n      { X = 2145, Y = 1088, Width = 39, Height = 64 },\n      { X = 2184, Y = 1088, Width = 39, Height = 64 },\n      { X = 2223, Y = 1088, Width = 39, Height = 64 },\n      { X = 2262, Y = 1088, Width = 39, Height = 64 },\n      { X = 2301, Y = 1088, Width = 39, Height = 64 },\n      { X = 2340, Y = 1088, Width = 39, Height = 64 },\n      { X = 2379, Y = 1088, Width = 39, Height = 64 },\n      { X = 2418, Y = 1088, Width = 39, Height = 64 },\n      { X = 2457, Y = 1088, Width = 39, Height = 64 },\n    },\n    {\n      { X = 0, Y = 1152, Width = 39, Height = 64 },\n      { X = 39, Y = 1152, Width = 39, Height = 64 },\n      { X = 78, Y = 1152, Width = 39, Height = 64 },\n      { X = 117, Y = 1152, Width = 39, Height = 64 },\n      { X = 156, Y = 1152, Width = 39, Height = 64 },\n      { X = 195, Y = 1152, Width = 39, Height = 64 },\n      { X = 234, Y = 1152, Width = 39, Height = 64 },\n      { X = 273, Y = 1152, Width = 39, Height = 64 },\n      { X = 312, Y = 1152, Width = 39, Height = 64 },\n      { X = 351, Y = 1152, Width = 39, Height = 64 },\n      { X = 390, Y = 1152, Width = 39, Height = 64 },\n      { X = 429, Y = 1152, Width = 39, Height = 64 },\n      { X = 468, Y = 1152, Width = 39, Height = 64 },\n      { X = 507, Y = 1152, Width = 39, Height = 64 },\n      { X = 546, Y = 1152, Width = 39, Height = 64 },\n      { X = 585, Y = 1152, Width = 39, Height = 64 },\n      { X = 624, Y = 1152, Width = 39, Height = 64 },\n      { X = 663, Y = 1152, Width = 39, Height = 64 },\n      { X = 702, Y = 1152, Width = 39, Height = 64 },\n      { X = 741, Y = 1152, Width = 39, Height = 64 },\n      { X = 780, Y = 1152, Width = 39, Height = 64 },\n      { X = 819, Y = 1152, Width = 39, Height = 64 },\n      { X = 858, Y = 1152, Width = 39, Height = 64 },\n      { X = 897, Y = 1152, Width = 39, Height = 64 },\n      { X = 936, Y = 1152, Width = 39, Height = 64 },\n      { X = 975, Y = 1152, Width = 39, Height = 64 },\n      { X = 1014, Y = 1152, Width = 39, Height = 64 },\n      { X = 1053, Y = 1152, Width = 39, Height = 64 },\n      { X = 1092, Y = 1152, Width = 39, Height = 64 },\n      { X = 1131, Y = 1152, Width = 39, Height = 64 },\n      { X = 1170, Y = 1152, Width = 39, Height = 64 },\n      { X = 1209, Y = 1152, Width = 39, Height = 64 },\n      { X = 1248, Y = 1152, Width = 39, Height = 64 },\n      { X = 1287, Y = 1152, Width = 39, Height = 64 },\n      { X = 1326, Y = 1152, Width = 39, Height = 64 },\n      { X = 1365, Y = 1152, Width = 39, Height = 64 },\n      { X = 1404, Y = 1152, Width = 39, Height = 64 },\n      { X = 1443, Y = 1152, Width = 39, Height = 64 },\n      { X = 1482, Y = 1152, Width = 39, Height = 64 },\n      { X = 1521, Y = 1152, Width = 39, Height = 64 },\n      { X = 1560, Y = 1152, Width = 39, Height = 64 },\n      { X = 1599, Y = 1152, Width = 39, Height = 64 },\n      { X = 1638, Y = 1152, Width = 39, Height = 64 },\n      { X = 1677, Y = 1152, Width = 39, Height = 64 },\n      { X = 1716, Y = 1152, Width = 39, Height = 64 },\n      { X = 1755, Y = 1152, Width = 39, Height = 64 },\n      { X = 1794, Y = 1152, Width = 39, Height = 64 },\n      { X = 1833, Y = 1152, Width = 39, Height = 64 },\n      { X = 1872, Y = 1152, Width = 39, Height = 64 },\n      { X = 1911, Y = 1152, Width = 39, Height = 64 },\n      { X = 1950, Y = 1152, Width = 39, Height = 64 },\n      { X = 1989, Y = 1152, Width = 39, Height = 64 },\n      { X = 2028, Y = 1152, Width = 39, Height = 64 },\n      { X = 2067, Y = 1152, Width = 39, Height = 64 },\n      { X = 2106, Y = 1152, Width = 39, Height = 64 },\n      { X = 2145, Y = 1152, Width = 39, Height = 64 },\n      { X = 2184, Y = 1152, Width = 39, Height = 64 },\n      { X = 2223, Y = 1152, Width = 39, Height = 64 },\n      { X = 2262, Y = 1152, Width = 39, Height = 64 },\n      { X = 2301, Y = 1152, Width = 39, Height = 64 },\n      { X = 2340, Y = 1152, Width = 39, Height = 64 },\n      { X = 2379, Y = 1152, Width = 39, Height = 64 },\n      { X = 2418, Y = 1152, Width = 39, Height = 64 },\n      { X = 2457, Y = 1152, Width = 39, Height = 64 },\n      { X = 2496, Y = 1152, Width = 39, Height = 64 },\n      { X = 2535, Y = 1152, Width = 39, Height = 64 },\n    },\n    {\n      { X = 0, Y = 1216, Width = 39, Height = 64 },\n      { X = 39, Y = 1216, Width = 39, Height = 64 },\n      { X = 78, Y = 1216, Width = 39, Height = 64 },\n      { X = 117, Y = 1216, Width = 39, Height = 64 },\n      { X = 156, Y = 1216, Width = 39, Height = 64 },\n      { X = 195, Y = 1216, Width = 39, Height = 64 },\n      { X = 234, Y = 1216, Width = 39, Height = 64 },\n      { X = 273, Y = 1216, Width = 39, Height = 64 },\n      { X = 312, Y = 1216, Width = 39, Height = 64 },\n      { X = 351, Y = 1216, Width = 39, Height = 64 },\n      { X = 390, Y = 1216, Width = 39, Height = 64 },\n      { X = 429, Y = 1216, Width = 39, Height = 64 },\n      { X = 468, Y = 1216, Width = 39, Height = 64 },\n      { X = 507, Y = 1216, Width = 39, Height = 64 },\n      { X = 546, Y = 1216, Width = 39, Height = 64 },\n      { X = 585, Y = 1216, Width = 39, Height = 64 },\n      { X = 624, Y = 1216, Width = 39, Height = 64 },\n      { X = 663, Y = 1216, Width = 39, Height = 64 },\n      { X = 702, Y = 1216, Width = 39, Height = 64 },\n      { X = 741, Y = 1216, Width = 39, Height = 64 },\n      { X = 780, Y = 1216, Width = 39, Height = 64 },\n      { X = 819, Y = 1216, Width = 39, Height = 64 },\n      { X = 858, Y = 1216, Width = 39, Height = 64 },\n      { X = 897, Y = 1216, Width = 39, Height = 64 },\n      { X = 936, Y = 1216, Width = 39, Height = 64 },\n      { X = 975, Y = 1216, Width = 39, Height = 64 },\n      { X = 1014, Y = 1216, Width = 39, Height = 64 },\n      { X = 1053, Y = 1216, Width = 39, Height = 64 },\n      { X = 1092, Y = 1216, Width = 39, Height = 64 },\n      { X = 1131, Y = 1216, Width = 39, Height = 64 },\n      { X = 1170, Y = 1216, Width = 39, Height = 64 },\n      { X = 1209, Y = 1216, Width = 39, Height = 64 },\n      { X = 1248, Y = 1216, Width = 39, Height = 64 },\n      { X = 1287, Y = 1216, Width = 39, Height = 64 },\n      { X = 1326, Y = 1216, Width = 39, Height = 64 },\n      { X = 1365, Y = 1216, Width = 39, Height = 64 },\n      { X = 1404, Y = 1216, Width = 39, Height = 64 },\n      { X = 1443, Y = 1216, Width = 39, Height = 64 },\n      { X = 1482, Y = 1216, Width = 39, Height = 64 },\n      { X = 1521, Y = 1216, Width = 39, Height = 64 },\n      { X = 1560, Y = 1216, Width = 39, Height = 64 },\n      { X = 1599, Y = 1216, Width = 39, Height = 64 },\n      { X = 1638, Y = 1216, Width = 39, Height = 64 },\n      { X = 1677, Y = 1216, Width = 39, Height = 64 },\n      { X = 1716, Y = 1216, Width = 39, Height = 64 },\n      { X = 1755, Y = 1216, Width = 39, Height = 64 },\n      { X = 1794, Y = 1216, Width = 39, Height = 64 },\n      { X = 1833, Y = 1216, Width = 39, Height = 64 },\n      { X = 1872, Y = 1216, Width = 39, Height = 64 },\n      { X = 1911, Y = 1216, Width = 39, Height = 64 },\n      { X = 1950, Y = 1216, Width = 39, Height = 64 },\n      { X = 1989, Y = 1216, Width = 39, Height = 64 },\n    },\n    {\n      { X = 0, Y = 1280, Width = 39, Height = 64 },\n      { X = 39, Y = 1280, Width = 39, Height = 64 },\n      { X = 78, Y = 1280, Width = 39, Height = 64 },\n      { X = 117, Y = 1280, Width = 39, Height = 64 },\n      { X = 156, Y = 1280, Width = 39, Height = 64 },\n      { X = 195, Y = 1280, Width = 39, Height = 64 },\n      { X = 234, Y = 1280, Width = 39, Height = 64 },\n      { X = 273, Y = 1280, Width = 39, Height = 64 },\n      { X = 312, Y = 1280, Width = 39, Height = 64 },\n      { X = 351, Y = 1280, Width = 39, Height = 64 },\n      { X = 390, Y = 1280, Width = 39, Height = 64 },\n      { X = 429, Y = 1280, Width = 39, Height = 64 },\n      { X = 468, Y = 1280, Width = 39, Height = 64 },\n      { X = 507, Y = 1280, Width = 39, Height = 64 },\n      { X = 546, Y = 1280, Width = 39, Height = 64 },\n      { X = 585, Y = 1280, Width = 39, Height = 64 },\n      { X = 624, Y = 1280, Width = 39, Height = 64 },\n      { X = 663, Y = 1280, Width = 39, Height = 64 },\n      { X = 702, Y = 1280, Width = 39, Height = 64 },\n      { X = 741, Y = 1280, Width = 39, Height = 64 },\n      { X = 780, Y = 1280, Width = 39, Height = 64 },\n      { X = 819, Y = 1280, Width = 39, Height = 64 },\n      { X = 858, Y = 1280, Width = 39, Height = 64 },\n      { X = 897, Y = 1280, Width = 39, Height = 64 },\n      { X = 936, Y = 1280, Width = 39, Height = 64 },\n      { X = 975, Y = 1280, Width = 39, Height = 64 },\n      { X = 1014, Y = 1280, Width = 39, Height = 64 },\n      { X = 1053, Y = 1280, Width = 39, Height = 64 },\n      { X = 1092, Y = 1280, Width = 39, Height = 64 },\n      { X = 1131, Y = 1280, Width = 39, Height = 64 },\n      { X = 1170, Y = 1280, Width = 39, Height = 64 },\n      { X = 1209, Y = 1280, Width = 39, Height = 64 },\n      { X = 1248, Y = 1280, Width = 39, Height = 64 },\n      { X = 1287, Y = 1280, Width = 39, Height = 64 },\n      { X = 1326, Y = 1280, Width = 39, Height = 64 },\n      { X = 1365, Y = 1280, Width = 39, Height = 64 },\n      { X = 1404, Y = 1280, Width = 39, Height = 64 },\n      { X = 1443, Y = 1280, Width = 39, Height = 64 },\n      { X = 1482, Y = 1280, Width = 39, Height = 64 },\n      { X = 1521, Y = 1280, Width = 39, Height = 64 },\n      { X = 1560, Y = 1280, Width = 39, Height = 64 },\n      { X = 1599, Y = 1280, Width = 39, Height = 64 },\n      { X = 1638, Y = 1280, Width = 39, Height = 64 },\n      { X = 1677, Y = 1280, Width = 39, Height = 64 },\n      { X = 1716, Y = 1280, Width = 39, Height = 64 },\n      { X = 1755, Y = 1280, Width = 39, Height = 64 },\n      { X = 1794, Y = 1280, Width = 39, Height = 64 },\n      { X = 1833, Y = 1280, Width = 39, Height = 64 },\n      { X = 1872, Y = 1280, Width = 39, Height = 64 },\n      { X = 1911, Y = 1280, Width = 39, Height = 64 },\n      { X = 1950, Y = 1280, Width = 39, Height = 64 },\n      { X = 1989, Y = 1280, Width = 39, Height = 64 },\n      { X = 2028, Y = 1280, Width = 39, Height = 64 },\n    },\n  };\n\n  for i = 0, #delusionTextGlyphs - 1 do\n    local currentLine = {}\n    for j = 0, #delusionTextGlyphs[i + 1] - 1 do\n        local name = \"DelusionTextGlyph\" .. i .. \"_\" .. j\n        root.Sprites[name] = {\n            Sheet = \"DelusionText\",\n            Bounds = delusionTextGlyphs[i + 1][j + 1],\n        };\n        currentLine[j + 1] = name;\n    end\n    root.DelusionTrigger.DelusionTextGlyphs[i + 1] = currentLine\n  end\nend\n"
  },
  {
    "path": "profiles/chlcc/hud/extramenus.lua",
    "content": "root.ExtraMenus = {\n    ClearListMenu = {\n        DrawType = DrawComponentType.SystemMenu,\n        Type = ClearListMenuType.CHLCC,\n        BackgroundColor = 0x405f86,\n        CircleSprite = \"Circle\",\n        MenuTitleTextRightPos = { X = 788, Y = 127 },\n        MenuTitleTextLeftPos = { X = 1, Y = 1 },\n        MenuTitleTextAngle = 4.45,\n        MenuTitleText = \"MenuTitleText\",\n        ClearListLabel = \"ClearListLabel\",\n        LabelPosition = { X = 800, Y = 44 },\n        Digits = {},\n        TimePositions = root.Language == \"Japanese\" and {\n            { X = 988, Y = 69 }, { X = 1008, Y = 69 },\n            { X = 1079, Y = 69 }, { X = 1099, Y = 69 },\n            { X = 1148, Y = 69 }, { X = 1168, Y = 69 },\n        } or {\n            { X = 1026, Y = 69 }, { X = 1046, Y = 69 },\n            { X = 1087, Y = 69 }, { X = 1107, Y = 69 },\n            { X = 1157, Y = 69 }, { X = 1177, Y = 69 },\n        },\n        AlbumPositions = {\n            { X = 1125, Y = 152 },\n            { X = 1145, Y = 152 },\n            { X = 1165, Y = 152 }\n        },\n        EndingCountPosition = { X = 1159, Y = 96 },\n        TIPSCountPositions = root.Language == \"Japanese\" and {\n            { X = 1105, Y = 124 }, { X = 1125, Y = 124 }, { X = 1145, Y = 124 },\n        } or {\n            { X = 1102, Y = 124 }, { X = 1122, Y = 124 }, { X = 1142, Y = 124 },\n        },\n        EndingList = \"EndingList\",\n        ListPosition = { X = 0, Y = 0 },\n        EndingBox = \"EndingBox\",\n        BoxPositions = {\n            { X = 341, Y = 218 }, { X = 572, Y = 146 },\n            { X = 341, Y = 345 }, { X = 341, Y = 472 },\n            { X = 572, Y = 400 }, { X = 572, Y = 273 },\n            { X = 572, Y = 527 }, { X = 110, Y = 146 }\n        },\n        EndingThumbnails = {},\n        ThumbnailPositions = {},\n        LockedThumbnail = \"LockedThumbnail\",\n        ButtonPromptPosition = { X = 1112, Y = 651 },\n        ButtonPromptSprite = \"ButtonPrompt\"\n    },\n    MovieMenu = {\n        DrawType = DrawComponentType.SystemMenu,\n        Type = MovieMenuType.CHLCC,\n        BackgroundColor = 0x8e4f9f,\n        CircleSprite = \"CircleMovie\",\n        MenuTitleTextRightPos = { X = 788, Y = 80 },\n        MenuTitleTextLeftPos = { X = 3, Y = 3 },\n        MenuTitleTextAngle = 4.45,\n        MenuTitleText = \"MovieMenuTitleText\",\n        MovieLabel = \"MovieLabel\",\n        LabelPosition = { X = 800, Y = 44 },\n        MovieList = \"MovieList\",\n        ListPosition = { X = 0, Y = 0 },\n        MovieBox = \"MovieBox\",\n        BoxPositions = {\n            { X = 93, Y = 127 }, { X = 93, Y = 255 },\n            { X = 93, Y = 383 }, { X = 93, Y = 511 },\n            { X = 341, Y = 199 }, { X = 341, Y = 327 },\n            { X = 341, Y = 455 }, { X = 589, Y = 127 },\n            { X = 589, Y = 255 }, { X = 589, Y = 383 },\n        },\n        MoviesThumbnails = {},\n        ThumbnailPositions = {},\n        LockedThumbnail = \"MovieLockedThumbnail\",\n        ButtonPromptPosition = { X = 1022, Y = 651 },\n        ButtonPromptSprite = \"MovieButtonPrompt\",\n        SelectedMovieAnimation = \"SelectedMovieAnimDef\",\n        SelectedMovieYellowDot = \"SelectedMovieYellowDot\",\n        SelectMovie = {},\n        SelectMoviePos = {\n            { X = 94, Y = 51 },\n            { X = 107, Y = 51 },\n            { X = 120, Y = 51 },\n            { X = 132, Y = 51 },\n            { X = 146, Y = 51 },\n            { X = 158, Y = 51 },\n            { X = 178, Y = 51 },\n            { X = 197, Y = 51 },\n            { X = 211, Y = 51 },\n            { X = 228, Y = 51 },\n            { X = 233, Y = 51 },\n        },\n        MovieExtraVideosEnabled = true,\n        MovieBoxExtra = \"MovieBoxExtra\",\n        MovieThumbnailExtraOp = \"MovieThumbnailExtraOp\",\n        MovieThumbnailExtraOp2 = \"MovieThumbnailExtraOp2\",\n        MovieButtonExtraPromptPosition = { X = 877, Y = 651 },\n        MovieButtonExtraPrompt = \"MovieButtonExtraPrompt\",\n        SelectedMovieExtraAnimation = \"SelectedMovieExtraAnimDef\",\n    },\n    AlbumMenu = {\n        DrawType = DrawComponentType.SystemMenu,\n        Type = AlbumMenuType.CHLCC,\n        BackgroundColor = 0xf36989,\n        CircleSprite = \"CircleCG\",\n        CGList = \"CGList\",\n        CGListPosition = { X = 0, Y = 0 },\n        PageCountLabel = \"PageCountLabel\",\n        PageLabelPosition = { X = 1090, Y = 61 },\n        CGBox = \"CGBox\",\n        CGBoxTemplatePosition = { X = 86, Y = 142 },\n        AlbumThumbnails = {},\n        ThumbnailTemplatePosition = { X = 95, Y = 156 },\n        VariationUnlocked = \"VariationUnlocked\",\n        VariationLocked = \"VariationLocked\",\n        VariationTemplateOffset = { X = 208, Y = 106 },\n        LockedCG = \"LockedCG\",\n        ThumbnailOffset = { X = 264, Y = 152 },\n        ThumbnailHighlight = \"ThumbnailHighlight\",\n        AlbumPages = 6,\n        EntriesPerPage = 9,\n        PageNums = {},\n        CurrentPageNumPos = { X = 1133, Y = 65 },\n        PageNumSeparatorSlash = \"PageNumSeparatorSlash\",\n        PageNumSeparatorSlashPos = { X = 1161, Y = 90 },\n        MaxPageNumPos = { X = 1179, Y = 90 },\n        ReachablePageNums = {},\n        ButtonGuide = \"ButtonGuide\",\n        ButtonGuidePos = { X = 1019, Y = 651 },\n        SelectDataSprites = {},\n        SelectDataPos = {\n            { X = 94, Y = 51 },\n            { X = 109, Y = 51 },\n            { X = 122, Y = 51 },\n            { X = 134, Y = 51 },\n            { X = 147, Y = 51 },\n            { X = 161, Y = 51 },\n            { X = 180, Y = 51 },\n            { X = 199, Y = 51 },\n            { X = 213, Y = 51 },\n            { X = 228, Y = 51 }\n        },\n        AlbumMenuTitle = \"AlbumMenuTitle\",\n        AlbumMenuTitleRightPos = { X = 787, Y = 106 },\n        AlbumMenuTitleLeftPos = { X = 1, Y = 1 },\n        AlbumMenuTitleAngle = 4.45,\n        CgViewerButtonGuideVariation = \"CgViewerButtonGuideVariation\",\n        CgViewerButtonGuideNoVariation = \"CgViewerButtonGuideNoVariation\",\n        CgViewerButtonGuidePos = { X = 208, Y = 648 },\n        SelectionMarkerSprite = \"SelectionMarkerSprite\",\n        SelectionMarkerRelativePos = { X = -16, Y = 50 },\n        CgFadeDuration = 16 / 60,\n    },\n    MusicMenu = {\n        DrawType = DrawComponentType.SystemMenu,\n        Type = MusicMenuType.CHLCC,\n        BackgroundColor = 0x339455,\n        CircleSprite = \"CircleSound\",\n        TrackTreeSprite = \"TrackTreeSprite\",\n        TrackTreePos = { X = 0, Y = 0 },\n        TrackButtonPosTemplate = { X = 94, Y = 170 },\n        TrackNameOffset = { X = 95, Y = 5 },\n        ArtistOffset = { X = 411, Y = 5 },\n        TrackOffset = { X = 0, Y = 28 },\n        TrackHighlight = \"TrackHighlight\",\n        TrackNumRelativePos = { X = 25, Y = 5 },\n        ButtonPromptPosition = { X = 738, Y = 651 },\n        ButtonPromptSprite = \"MusicButtonPrompt\",\n        PlaymodeRepeatPos = { X = 718, Y = 146 },\n        PlaymodeAllPos = { X = 771, Y = 146 },\n        PlaymodeRepeat = \"PlaymodeRepeat\",\n        PlaymodeAll = \"PlaymodeAll\",\n        PlaymodeRepeatHighlight = \"PlaymodeRepeatHighlight\",\n        PlaymodeAllHighlight = \"PlaymodeAllHighlight\",\n        NowPlaying = \"NowPlaying\",\n        NowPlayingPos = { X = 745, Y = 44 },\n        NowPlayingAnimationDuration = 16 / 60,\n        PlayingTrackOffset = { X = 1, Y = 25 },\n        PlayingTrackArtistOffset = { X = 1, Y = 59 },\n        SoundLibraryTitle = \"SoundLibraryTitle\",\n        SoundLibraryTitleLeftPos = { X = 1, Y = 1 },\n        SoundLibraryTitleRightPos = { X = 787, Y = 103 },\n        SoundLibraryTitleAngle = 4.45,\n        HighlightStar = \"HighlightStar\",\n        HighlightStarRelativePos = { X = 1, Y = 1 },\n        TrackListBounds = { X = 94, Y = 170, Width = 608, Height = 448},\n        ScrollTrackBounds = { X = 14, Y = 446},\n        ScrollThumb = \"ScrollThumb\",\n        ScrollbarPosition = { X = 687, Y = 169},\n\n        Playlist = {\n            8, 14, 26, 10, 28, 12, 20, 16, 18, 24, 22, 80, 76, 32, 34,\n            36, 38, 77, 41, 43, 78, 46, 49, 79, 51, 53, 56, 81, 59, 61,\n            63, 65, 82, 83, 84, 0, 1, 2, 4, 6, 85, 72, 74, 75, 71\n        },\n        SelectSoundSprites = {},\n        SelectSoundPos = {\n            { X = 85, Y = 49 },\n            { X = 98, Y = 49 },\n            { X = 111, Y = 49 },\n            { X = 123, Y = 49 },\n            { X = 137, Y = 49 },\n            { X = 149, Y = 49 },\n            { X = 170, Y = 49 },\n            { X = 185, Y = 49 },\n            { X = 200, Y = 49 },\n            { X = 215, Y = 49 },\n            { X = 231, Y = 49 },\n        },\n        AyaseEndingBgmId = 74,\n        NormalEndingBgmId = 75,\n        PresetBgmFlags = {0, 1, 2, 4, 6, 32, 51, 58, 67, 68, 69, 72, 85},\n        DstBgmPairedFlag = {2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 32, \n                            34, 36, 38, 41, 43, 48, 46, 51, 53, 56, 59, 61, 63, 65, \n                            71, 74, 76, 77, 78, 49, 79, 80, 81, 82, 83, 84},\n        SrcBgmPairedFlag = {3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 33, \n                            35, 37, 39, 42, 44, 49, 47, 52, 54, 57, 60, 62, 64, 66, \n                            70, 73, 31, 40, 45, 48, 50, 55, 58, 67, 68, 69}\n    }\n}\n\n--ClearList\n\nroot.Sprites[\"Circle\"] = {\n    Sheet = \"ClearList\",\n    Bounds = { X = 1, Y = 917, Width = 106, Height = 106 }\n}\n\nroot.Sprites[\"MenuTitleText\"] = {\n    Sheet = \"ClearList\",\n    Bounds = { X = 607, Y = 1, Width = 120, Height = 912 }\n}\n\nroot.Sprites[\"ClearListLabel\"] = {\n    Sheet = \"ClearList\",\n    Bounds = { X = 1, Y = 591, Width = 490, Height = 136 }\n}\n\nfor i = 0, 9 do\n    root.Sprites[\"Digit\" .. i] = {\n        Sheet = \"ClearList\",\n        Bounds = {\n            X = 211 + 22 * i,\n            Y = 729,\n            Width = 20,\n            Height = 30\n        }\n    }\n    root.ExtraMenus.ClearListMenu.Digits[#root.ExtraMenus.ClearListMenu.Digits + 1] = \"Digit\" .. i;\nend\n\nroot.Sprites[\"EndingList\"] = {\n    Sheet = \"ClearList\",\n    Bounds = { X = 1, Y = 1, Width = 604, Height = 588 }\n}\n\nroot.Sprites[\"EndingBox\"] = {\n    Sheet = \"ClearList\",\n    Bounds = { X = 1, Y = 761, Width = 198, Height = 96 }\n}\n\nlocal thumbnailXOffset = 30;\nlocal thumbnailYOffset = 3;\nlocal bp = root.ExtraMenus.ClearListMenu.BoxPositions;\n\nfor i = 1, #bp do\n    root.ExtraMenus.ClearListMenu.ThumbnailPositions[#root.ExtraMenus.ClearListMenu.ThumbnailPositions + 1] = {\n        X = bp[i].X + thumbnailXOffset,\n        Y = bp[i].Y + thumbnailYOffset\n    };\nend\n\nroot.Sprites[\"LockedThumbnail\"] = {\n    Sheet = \"ClearList\",\n    Bounds = { X = 729, Y = 1, Width = 160, Height = 90 }\n}\n\nlocal firstX = 729;\nlocal firstY = 93;\nlocal yDelta = 92;\n\nfor i = 1, 8 do\n    root.Sprites[\"EndingThumbnail\" .. i] = {\n        Sheet = \"ClearList\",\n        Bounds = {\n            X = firstX,\n            Y = firstY,\n            Width = 160,\n            Height = 90\n        }\n    };\n    root.ExtraMenus.ClearListMenu.EndingThumbnails[#root.ExtraMenus.ClearListMenu.EndingThumbnails + 1] = \"EndingThumbnail\" .. i;\n    firstY = firstY + yDelta;\nend\n\n--Reorganize to be in the flag order (801 -> 808)\n--Yes I know it's bad\net = root.ExtraMenus.ClearListMenu.EndingThumbnails;\ntemp = et[1];\net[1] = et[2];\net[2] = temp;\net[2] = et[5];\net[5] = temp;\net[5] = et[7];\net[7] = temp;\net[7] = et[8];\net[8] = temp;\n\nroot.Sprites[\"ButtonPrompt\"] = {\n    Sheet = \"ClearList\",\n    Bounds = { X = 1, Y = 729, Width = 208, Height = 30 }\n}\n\n--MovieList\nroot.Sprites[\"CircleMovie\"] = {\n    Sheet = \"Movie\",\n    Bounds = { X = 1, Y = 917, Width = 106, Height = 106 }\n}\n\nroot.Sprites[\"MovieBox\"] = {\n    Sheet = \"Movie\",\n    Bounds = { X = 728, Y = 1, Width = 198, Height = 122 }\n}\n\nroot.Sprites[\"MovieBoxExtra\"] = {\n    Sheet = \"Movie\",\n    Bounds = { X = 1091, Y = 1, Width = 198, Height = 122 }\n}\n\nroot.Sprites[\"MovieThumbnailExtraOp\"] = {\n    Sheet = \"Movie\",\n    Bounds = { X = 1291, Y = 93, Width = 160, Height = 90 }\n}\n\nroot.Sprites[\"MovieThumbnailExtraOp2\"] = {\n    Sheet = \"Movie\",\n    Bounds = { X = 1291, Y = 185, Width = 160, Height = 90 }\n}\n\nroot.Sprites[\"MovieButtonExtraPrompt\"] = {\n    Sheet = \"Movie\",\n    Bounds = { X = 1, Y = 732, Width = 427, Height = 28 }\n}\n\nlocal thumbnailXOffset = 20;\nlocal thumbnailYOffset = 16;\nlocal bp = root.ExtraMenus.MovieMenu.BoxPositions;\n\nfor i = 1, #bp do\n    root.ExtraMenus.MovieMenu.ThumbnailPositions[#root.ExtraMenus.MovieMenu.ThumbnailPositions + 1] = {\n        X = bp[i].X + thumbnailXOffset,\n        Y = bp[i].Y + thumbnailYOffset\n    };\nend\n\nroot.Sprites[\"MovieLabel\"] = {\n    Sheet = \"Movie\",\n    Bounds = { X = 1, Y = 591, Width = 490, Height = 136 }\n}\n\nroot.Sprites[\"MovieList\"] = {\n    Sheet = \"Movie\",\n    Bounds = { X = 1, Y = 1, Width = 605, Height = 585 }\n}\n\nroot.Sprites[\"MovieMenuTitleText\"] = {\n    Sheet = \"Movie\",\n    Bounds = { X = 609, Y = 3, Width = 116, Height = 1005 }\n}\n\nlocal firstX = 928;\nlocal firstY = 93;\nlocal yDelta = 92;\n\nfor i = 1, 10 do\n    root.Sprites[\"MovieThumbnail\" .. i] = {\n        Sheet = \"Movie\",\n        Bounds = {\n            X = firstX,\n            Y = firstY,\n            Width = 160,\n            Height = 90\n        }\n    };\n    root.ExtraMenus.MovieMenu.MoviesThumbnails[#root.ExtraMenus.MovieMenu.MoviesThumbnails + 1] = \"MovieThumbnail\" .. i;\n    firstY = firstY + yDelta;\nend\n\nroot.Sprites[\"MovieLockedThumbnail\"] = {\n    Sheet = \"Movie\",\n    Bounds = { X = 928, Y = 1, Width = 160, Height = 90 }\n}\n\nroot.Sprites[\"MovieButtonPrompt\"] = {\n    Sheet = \"Movie\",\n    Bounds = { X = 1, Y = 686, Width = 282, Height = 28 }\n}\n\nlocal selectMovieBounds = {\n    { X = 1, Y = 597, Width = 15, Height = 57 },\n    { X = 19, Y = 597, Width = 16, Height = 57 },\n    { X = 37, Y = 597, Width = 15, Height = 57 },\n    { X = 53, Y = 597, Width = 16, Height = 57 },\n    { X = 72, Y = 597, Width = 17, Height = 57 },\n    { X = 89, Y = 597, Width = 13, Height = 57 },\n    { X = 114, Y = 597, Width = 21, Height = 57 },\n    { X = 138, Y = 597, Width = 16, Height = 57 },\n    { X = 157, Y = 597, Width = 15, Height = 57 },\n    { X = 179, Y = 597, Width = 7, Height = 57 },\n    { X = 189, Y = 597, Width = 16, Height = 57 }\n}\n\nfor i, bounds in ipairs(selectMovieBounds) do\n    root.Sprites[\"SelectMovie\" .. i - 1] = {\n        Sheet = \"Movie\",\n        Bounds = bounds\n    }\n    root.ExtraMenus.MovieMenu.SelectMovie[i] = \"SelectMovie\" .. i - 1;\nend\n\nroot.Sprites[\"SelectedMovieYellowDot\"] = {\n    Sheet = \"Movie\",\n    Bounds = { X = 294, Y = 694, Width = 14, Height = 14 }\n}\n\nMakeAnimation({\n    Name = \"SelectedMovieAnimDef\",\n    Sheet = \"Movie\",\n    FirstFrameX = 728,\n    FirstFrameY = 125,\n    FrameWidth = 198,\n    ColWidth = 198,\n    FrameHeight = 124,\n    RowHeight = 124,\n    Frames = 7,\n    Duration = 0.2,\n    Rows = 7,\n    Columns = 1,\n    PrimaryDirection = AnimationDirections.Down,\n});\n\nMakeAnimation({\n    Name = \"SelectedMovieExtraAnimDef\",\n    Sheet = \"Movie\",\n    FirstFrameX = 1091,\n    FirstFrameY = 125,\n    FrameWidth = 198,\n    ColWidth = 198,\n    FrameHeight = 124,\n    RowHeight = 124,\n    Frames = 7,\n    Duration = 0.2,\n    Rows = 7,\n    Columns = 1,\n    PrimaryDirection = AnimationDirections.Down,\n});\n\n--SoundList\nroot.Sprites[\"CircleSound\"] = {\n    Sheet = \"Sound\",\n    Bounds = { X = 1, Y = 917, Width = 106, Height = 106 }\n}\n\nroot.Sprites[\"TrackTreeSprite\"] = {\n    Sheet = \"Sound\",\n    Bounds = { X = 1, Y = 1, Width = 802, Height = 608 }\n}\n\nroot.Sprites[\"TrackHighlight\"] = {\n    Sheet = \"Sound\",\n    Bounds = { X = 1, Y = 693, Width = 676, Height = 29 }\n}\n\nroot.Sprites[\"PlaymodeRepeat\"] = {\n    Sheet = \"Sound\",\n    Bounds = { X = 257, Y = 644, Width = 51, Height = 10 }\n}\n\nroot.Sprites[\"PlaymodeAll\"] = {\n    Sheet = \"Sound\",\n    Bounds = { X = 310, Y = 644, Width = 28, Height = 10 }\n}\n\nroot.Sprites[\"PlaymodeRepeatHighlight\"] = {\n    Sheet = \"Sound\",\n    Bounds = { X = 257, Y = 632, Width = 51, Height = 10 }\n}\n\nroot.Sprites[\"PlaymodeAllHighlight\"] = {\n    Sheet = \"Sound\",\n    Bounds = { X = 310, Y = 632, Width = 28, Height = 10 }\n}\n\nroot.Sprites[\"NowPlaying\"] = {\n    Sheet = \"Sound\",\n    Bounds = { X = 1, Y = 611, Width = 656, Height = 19 }\n}\n\nroot.Sprites[\"MusicButtonPrompt\"] = {\n    Sheet = \"Sound\",\n    Bounds = { X = 1, Y = 724, Width = 552, Height = 28}\n}\n\n\nroot.Sprites[\"SoundLibraryTitle\"] = {\n    Sheet = \"Sound\",\n    Bounds = { X = 805, Y = 1, Width = 118, Height = 961 }\n}\n\nroot.Sprites[\"HighlightStar\"] = {\n    Sheet = \"Sound\",\n    Bounds = { X = 216, Y = 632, Width = 23, Height = 23 }\n}\n\nlocal selectSoundBounds = {\n    { X = 2, Y = 633, Width = 15, Height = 58 },\n    { X = 20, Y = 633, Width = 16, Height = 58 },\n    { X = 38, Y = 633, Width = 15, Height = 58 },\n    { X = 54, Y = 633, Width = 16, Height = 58 },\n    { X = 73, Y = 633, Width = 15, Height = 58 },\n    { X = 90, Y = 633, Width = 13, Height = 58 },\n    { X = 116, Y = 633, Width = 16, Height = 58 },\n    { X = 136, Y = 633, Width = 16, Height = 58 },\n    { X = 156, Y = 633, Width = 17, Height = 58 },\n    { X = 176, Y = 633, Width = 18, Height = 58 },\n    { X = 197, Y = 633, Width = 17, Height = 58 }\n}\n\nfor i, bounds in ipairs(selectSoundBounds) do\n    root.Sprites[\"SelectSound\" .. i - 1] = {\n        Sheet = \"Sound\",\n        Bounds = bounds\n    }\n    root.ExtraMenus.MusicMenu.SelectSoundSprites[i] = \"SelectSound\" .. i - 1;\nend\n\nroot.Sprites[\"ScrollThumb\"] = {\n    Sheet = \"Sound\",\n    Bounds = { X = 241, Y = 632, Width = 14, Height = 59 }\n}\n\n--CGList\nroot.Sprites[\"CircleCG\"] = {\n    Sheet = \"CG\",\n    Bounds = { X = 1, Y = 917, Width = 106, Height = 106 }\n}\n\nroot.Sprites[\"CGList\"] = {\n    Sheet = \"CG\",\n    Bounds = { X = 1, Y = 1, Width = 638, Height = 538 }\n}\n\nroot.Sprites[\"PageCountLabel\"] = {\n    Sheet = \"CG\",\n    Bounds = { X = 1, Y = 611, Width = 204, Height = 57 }\n}\n\nroot.Sprites[\"CGBox\"] = {\n    Sheet = \"CG\",\n    Bounds = { X = 235, Y = 611, Width = 252, Height = 158 }\n}\n\nfor i = 0, 6 do\n    for j = 0, 7 do\n        root.Sprites[\"AlbumThumbnail\" .. (8 * i + j)] = {\n            Sheet = \"AlbumThumbnailSheet\",\n            Bounds = { X = 234 * j + 1, Y = 132 * i + 1, Width = 232, Height = 130 }\n        }\n        root.ExtraMenus.AlbumMenu.AlbumThumbnails[#root.ExtraMenus.AlbumMenu.AlbumThumbnails + 1] = \"AlbumThumbnail\" .. (8 * i + j);\n    end\nend\n\nfor i = 0, 6 do\n    root.Sprites[\"AlbumThumbnail\" .. (56 + i)] = {\n        Sheet = \"AlbumThumbnailSheet2\",\n        Bounds = { X =  234 * i + 1, Y = 1, Width = 232, Height = 130 }\n    }\n    root.ExtraMenus.AlbumMenu.AlbumThumbnails[#root.ExtraMenus.AlbumMenu.AlbumThumbnails + 1] = \"AlbumThumbnail\" .. (56 + i);\nend\n\nroot.Sprites[\"VariationUnlocked\"] = {\n    Sheet = \"CG\",\n    Bounds = { X = 489, Y = 611, Width = 22, Height = 22 }\n}\n\nroot.Sprites[\"VariationLocked\"] = {\n    Sheet = \"CG\",\n    Bounds = { X = 513, Y = 611, Width = 22, Height = 22 }\n}\n\nroot.Sprites[\"LockedCG\"] = {\n    Sheet = \"CG\",\n    Bounds = { X = 235, Y = 785, Width = 232, Height = 130 }\n}\n\nroot.Sprites[\"ThumbnailHighlight\"] = {\n    Sheet = \"CG\",\n    Bounds = { X = 1, Y = 785, Width = 232, Height = 130 }\n}\n\nfor i = 0, 9 do\n    root.Sprites[\"PageNum\" .. i] = {\n        Sheet = \"Data\",\n        Bounds = { X = i * 36 + 371, Y = 1, Width = 34, Height = 48 }\n    };\n    root.ExtraMenus.AlbumMenu.PageNums[#root.ExtraMenus.AlbumMenu.PageNums + 1] = \"PageNum\" .. i;\nend\n\nroot.Sprites[\"PageNumSeparatorSlash\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 173, Y = 67, Width = 16, Height = 22 }\n};\n\nfor i = 0, 9 do\n    root.Sprites[\"ReachablePageNum\" .. i] = {\n        Sheet = \"Data\",\n        Bounds = { X = 191 + 18 * i, Y = 67, Width = 16, Height = 22 }\n    };\n    root.ExtraMenus.AlbumMenu.ReachablePageNums[#root.ExtraMenus.AlbumMenu.ReachablePageNums + 1] = \"ReachablePageNum\" .. i;\nend\n\nroot.Sprites[\"ButtonGuide\"] = {\n    Sheet = \"CG\",\n    Bounds = { X = 1, Y = 581, Width = 380, Height = 28 }\n}\n\nlocal selectDataBounds = {\n    { X = 1, Y = 670, Width = 18, Height = 55 },\n    { X = 21, Y = 670, Width = 16, Height = 55 },\n    { X = 39, Y = 670, Width = 15, Height = 55 },\n    { X = 56, Y = 670, Width = 16, Height = 55 },\n    { X = 74, Y = 670, Width = 17, Height = 55 },\n    { X = 93, Y = 670, Width = 22, Height = 55 },\n    { X = 117, Y = 670, Width = 22, Height = 55 },\n    { X = 141, Y = 670, Width = 17, Height = 55 },\n    { X = 159, Y = 670, Width = 18, Height = 55 },\n    { X = 179, Y = 670, Width = 20, Height = 55 }\n}\n\nfor i, bounds in ipairs(selectDataBounds) do\n    root.Sprites[\"SelectData\" .. i - 1] = {\n        Sheet = \"CG\",\n        Bounds = bounds\n    }\n    root.ExtraMenus.AlbumMenu.SelectDataSprites[i] = \"SelectData\" .. i - 1;\nend\n\nroot.Sprites[\"AlbumMenuTitle\"] = {\n    Sheet = \"CG\",\n    Bounds = { X = 641, Y = 1, Width = 120, Height = 954 }\n}\n\nroot.Sprites[\"CgViewerButtonGuideVariation\"] = {\n    Sheet = \"CG\",\n    Bounds = { X = 757, Y = 578, Width = 1190, Height = 38 }\n}\n\nroot.Sprites[\"CgViewerButtonGuideNoVariation\"] = {\n    Sheet = \"CG\",\n    Bounds = { X = 757, Y = 536, Width = 1190, Height = 38 }\n}\n\nroot.Sprites[\"SelectionMarkerSprite\"] = {\n    Sheet = \"CG\",\n    Bounds = { X = 433, Y = 581, Width = 28, Height = 28 }\n}"
  },
  {
    "path": "profiles/chlcc/hud/loadingdisplay.lua",
    "content": "root.LoadingDisplay = {\n    ResourceLoadBgAnim = \"ResourceLoadingBg\",\n    SaveLoadBgAnim = \"SaveLoadingBg\",\n    LoadingIconAnim = \"LoadingDisc\",\n    LoadingTextAnim = \"LoadingText\",\n    ResourceBgPos = { X = 1074, Y = 611 },\n    SaveBgPos = { X = 1019, Y = 598 },\n    IconPos = { X = 986, Y = 608 },\n    TextPos = { X = 1025, Y = 628 },\n    FadeInDuration = 0.66,\n    FadeOutDuration = 0.33\n};\n\nMakeAnimation({\n    Name = \"ResourceLoadingBg\",\n    Sheet = \"Data\",\n    FirstFrameX = 555,\n    FirstFrameY = 544,\n    FrameWidth = 206,\n    ColWidth = 206,\n    FrameHeight = 58,\n    RowHeight = 60,\n    Frames = 8,\n    Duration = 0.8,\n    Rows = 8,\n    Columns = 1,\n    PrimaryDirection = AnimationDirections.Down\n});\n\nMakeAnimation({\n    Name = \"SaveLoadingBg\",\n    Sheet = \"Data\",\n    FirstFrameX = 388,\n    FirstFrameY = 0,\n    FrameWidth = 277,\n    ColWidth = 279,\n    FrameHeight = 81,\n    RowHeight = 83,\n    Frames = 4,\n    Duration = 0.8,\n    Rows = 2,\n    Columns = 2,\n    PrimaryDirection = AnimationDirections.Down,\n    SecondaryDirection = AnimationDirections.Right\n});\n\nMakeAnimation({\n    Name = \"LoadingDisc\",\n    Sheet = \"Data\",\n    FirstFrameX = 172,\n    FirstFrameY = 1,\n    FrameWidth = 60,\n    ColWidth = 62,\n    FrameHeight = 60,\n    RowHeight = 62,\n    Frames = 3,\n    Duration = 0.8,\n    Rows = 1,\n    Columns = 3,\n    PrimaryDirection = AnimationDirections.Right\n});\nroot.Animations[\"LoadingDisc\"].Frames[#root.Animations[\"LoadingDisc\"].Frames + 1] = \"LoadingDisc1\";\n\nMakeAnimation({\n    Name = \"LoadingText\",\n    Sheet = \"Data\",\n    FirstFrameX = 173,\n    FirstFrameY = 91,\n    FrameWidth = 214,\n    ColWidth = 214,\n    FrameHeight = 21,\n    RowHeight = 23,\n    Frames = 5,\n    Duration = 0.8,\n    Rows = 5,\n    Columns = 1,\n    PrimaryDirection = AnimationDirections.Down\n});"
  },
  {
    "path": "profiles/chlcc/hud/optionsmenu.lua",
    "content": "root.OptionsMenu = {\n    DrawType = DrawComponentType.SystemMenu,\n    Type = OptionsMenuType.CHLCC,\n\n    FadeInDuration = 64 / 60,\n    FadeOutDuration = 64 / 60,\n\n    BackgroundColor = 0xa988e5,\n\n    CircleSprite = \"CircleConfig\",\n\n    ShowPageAnimationStartTime = 16 / 60,\n    ShowPageAnimationDuration = (48 - 16) / 60,\n\n    MenuTitleTextRightPos = { X = 551, Y = 525 },\n    MenuTitleTextAngle = 6.02,\n    MenuTitleText = \"MenuTitleTextConfig\",\n\n    -- (π / 2) * 0.56 in the binary, but the voice page blips out too early lol\n    -- This does mean the rotation is slightly faster than in the original,\n    -- but I'd rather preserve the rotation's duration over its angular velocity\n    PageRotationAngle = (3.14159 / 2) * 0.56 * 1.25,\n    PageTransitionDuration = 32 / 60;\n\n    ButtonPromptSprite = \"ButtonPromptConfig\",\n    ButtonPromptPosition = { X = 603, Y = 651 },\n\n    SelectedSprite = \"SelectedSprite\",\n    SelectedSlideDuration = 32 / 60;\n    SelectedLabelSprite = \"SelectedLabelSprite\",\n    SelectedLabelOffset = { X = 104, Y = 1 },\n    SelectedDotSprite = \"SelectedDotSprite\",\n    SelectedDotOffset = { X = -9, Y = 10 },\n    SelectedDotVoicesOffset = { X = -10, Y = 10 },\n    VoiceMutedSprite = \"VoiceMutedSprite\",\n    VoiceMutedOffset = { X = 23, Y = 0 },\n\n    SelectedLabelBaseSpeed = 49 / (4 / 60),\n    SelectedLabelModalDistancePerEntry = 49,\n\n    BasicSettingsSprite = \"BasicSettingsSprite\",\n    BasicSettingsPos = { X = 0, Y = 46 },\n    TextSettingsSprite = \"TextSettingsSprite\",\n    TextSettingsPos = { X = 0, Y = 360 },\n    SoundSettingsSprite = \"SoundSettingsSprite\",\n    SoundSettingsPos = { X = 0, Y = 45 },\n    VoiceSettingsSprite = \"VoiceSettingsSprite\",\n    VoiceSettingsPos = { X = 0, Y = 47 },\n\n    SliderBarBaseSprite = \"SliderBarBaseSprite\",\n    SliderBarFillSprite = \"SliderBarFillSprite\",\n    SliderBarFadeDuration = 32 / 60 / 2;\n    SliderBarTopRightOffset = { X = -14, Y = 6 },\n    SliderBarFillOffset = { X = 7, Y = 0 },\n\n    SettingInstantSprite = \"SettingInstantSprite\",\n    SettingFastSprite = \"SettingFastSprite\",\n    SettingNormalSprite = \"SettingNormalSprite\",\n    SettingSlowSprite = \"SettingSlowSprite\",\n    SettingShortSprite = \"SettingShortSprite\",\n    SettingLongSprite = \"SettingLongSprite\",\n    SettingDoSprite = \"SettingDoSprite\",\n    SettingDontSprite = \"SettingDontSprite\",\n    SettingYesSprite = \"SettingYesSprite\",\n    SettingNoSprite = \"SettingNoSprite\",\n    SettingReadSprite = \"SettingReadSprite\",\n    SettingAllSprite = \"SettingAllSprite\",\n    SettingOnTriggerSprite = \"SettingOnTriggerSprite\",\n    SettingOnSceneSprite = \"SettingOnSceneSprite\",\n    SettingOnTriggerAndSceneSprite = \"SettingOnTriggerAndSceneSprite\",\n    SettingTypeASprite = \"SettingTypeASprite\",\n    SettingTypeBSprite = \"SettingTypeBSprite\",\n    SettingButtonTopRightOffset = { X = -13, Y = 6 },\n\n    TextPageEntryPositions = {\n        -- Basic settings\n        { X = 104, Y = 123 },\n        { X = 104, Y = 172 },\n        { X = 104, Y = 221 },\n        { X = 104, Y = 270 },\n        { X = 104, Y = 319 },\n        -- Text settings\n        { X = 104, Y = 444 },\n        { X = 104, Y = 493 },\n        { X = 104, Y = 542 },\n    },\n    SoundPageEntryPositions = {\n        { X = 104, Y = 123 },\n        { X = 104, Y = 172 },\n        { X = 104, Y = 221 },\n        { X = 104, Y = 270 },\n        { X = 104, Y = 319 },\n        { X = 104, Y = 368 },\n    },\n    VoicePageEntryPositions = {\n        { X = 104, Y = 123 },\n        { X = 104, Y = 169 },\n        { X = 104, Y = 215 },\n        { X = 104, Y = 261 },\n        { X = 104, Y = 307 },\n        { X = 104, Y = 353 },\n        { X = 104, Y = 399 },\n        { X = 104, Y = 445 },\n        { X = 104, Y = 491 },\n        { X = 104, Y = 537 },\n        { X = 104, Y = 583 },\n    },\n\n    TriggerStopSkipValues = { true, false },\n    ShowTipsNotificationValues = { true, false },\n    AutoQuickSaveValues = {\n        AutoQuickSaveType.OnTrigger | AutoQuickSaveType.OnScene,\n        AutoQuickSaveType.Never,\n        AutoQuickSaveType.OnTrigger,\n        AutoQuickSaveType.OnScene\n    },\n    ControllerTypeValues = { 0, 1 },\n    TextSpeedValues = { 0x300 / 60, 0x600 / 60, 0xfff0000 / 60, 0x100 / 60 },\n    AutoSpeedValues = { 0x300 / 60, 0x600 / 60, 0x100 / 60 },\n    SkipReadValues = { true, false },\n    SyncVoiceValues = { true, false },\n    SkipVoiceValues = { false, true },\n};\n\nroot.Sprites[\"CircleConfig\"] = {\n    Sheet = \"Options\",\n    Bounds = { X = 1, Y = 917, Width = 106, Height = 106 }\n}\n\nroot.Sprites[\"MenuTitleTextConfig\"] = {\n    Sheet = \"Options\",\n    Bounds = { X = 482, Y = 604, Width = 592, Height = 117 }\n}\n\nroot.Sprites[\"ButtonPromptConfig\"] = {\n    Sheet = \"Options\",\n    Bounds = { X = 480, Y = 824, Width = 677, Height = 28 }\n}\n\nroot.Sprites[\"SelectedSprite\"] = {\n    Sheet = \"Options\",\n    -- Yes, the single-pixel-wide overhang is in the binary\n    Bounds = { X = 719, Y = 766, Width = 232, Height = 40 }\n}\n\nroot.Sprites[\"SelectedLabelSprite\"] = {\n    Sheet = \"Options\",\n    Bounds = { X = 480, Y = 724, Width = 630, Height = 40 }\n}\n\nroot.Sprites[\"SelectedDotSprite\"] = {\n    Sheet = \"Options\",\n    Bounds = { X = 42, Y = 602, Width = 18, Height = 18 }\n}\n\nroot.Sprites[\"VoiceMutedSprite\"] = {\n    Sheet = \"Options\",\n    Bounds = { X = 1, Y = 602, Width = 39, Height = 39 },\n    BaseScale = { X = 40 / 39, Y = 40 / 39 }\n}\n\nroot.Sprites[\"BasicSettingsSprite\"] = {\n    Sheet = \"Options\",\n    Bounds = { X = 453, Y = 1, Width = 574, Height = 314 }\n}\n\nroot.Sprites[\"TextSettingsSprite\"] = {\n    Sheet = \"Options\",\n    Bounds = { X = 453, Y = 315, Width = 504, Height = 284 }\n}\n\nroot.Sprites[\"SoundSettingsSprite\"] = {\n    Sheet = \"Options\",\n    Bounds = { X = 109, Y = 602, Width = 369, Height = 369 }\n}\n\nroot.Sprites[\"VoiceSettingsSprite\"] = {\n    Sheet = \"Options\",\n    Bounds = { X = 1, Y = 1, Width = 450, Height = 599 }\n}\n\nroot.Sprites[\"SliderBarBaseSprite\"] = {\n    Sheet = \"Options\",\n    Bounds = { X = 480, Y = 766, Width = 237, Height = 27 }\n}\n\nroot.Sprites[\"SliderBarFillSprite\"] = {\n    Sheet = \"Options\",\n    Bounds = { X = 487, Y = 795, Width = 225, Height = 27 }\n}\n\n-- Setting sprites\nroot.Sprites[\"SettingInstantSprite\"] = {\n    Sheet = \"Options\",\n    Bounds = { X = 959, Y = 1, Width = 218, Height = 28 }\n}\n\nroot.Sprites[\"SettingFastSprite\"] = {\n    Sheet = \"Options\",\n    Bounds = { X = 959, Y = 31, Width = 218, Height = 28 }\n}\n\nroot.Sprites[\"SettingNormalSprite\"] = {\n    Sheet = \"Options\",\n    Bounds = { X = 959, Y = 61, Width = 218, Height = 28 }\n}\n\nroot.Sprites[\"SettingSlowSprite\"] = {\n    Sheet = \"Options\",\n    Bounds = { X = 959, Y = 91, Width = 218, Height = 28 }\n}\n\nroot.Sprites[\"SettingShortSprite\"] = {\n    Sheet = \"Options\",\n    Bounds = { X = 959, Y = 121, Width = 218, Height = 28 }\n}\n\nroot.Sprites[\"SettingLongSprite\"] = {\n    Sheet = \"Options\",\n    Bounds = { X = 959, Y = 151, Width = 218, Height = 28 }\n}\n\nroot.Sprites[\"SettingDoSprite\"] = {\n    Sheet = \"Options\",\n    Bounds = { X = 959, Y = 181, Width = 218, Height = 28 }\n}\n\nroot.Sprites[\"SettingDontSprite\"] = {\n    Sheet = \"Options\",\n    Bounds = { X = 959, Y = 211, Width = 218, Height = 28 }\n}\n\nroot.Sprites[\"SettingYesSprite\"] = {\n    Sheet = \"Options\",\n    Bounds = { X = 959, Y = 241, Width = 218, Height = 28 }\n}\n\nroot.Sprites[\"SettingNoSprite\"] = {\n    Sheet = \"Options\",\n    Bounds = { X = 959, Y = 271, Width = 218, Height = 28 }\n}\n\nroot.Sprites[\"SettingReadSprite\"] = {\n    Sheet = \"Options\",\n    Bounds = { X = 959, Y = 301, Width = 218, Height = 28 }\n}\n\nroot.Sprites[\"SettingAllSprite\"] = {\n    Sheet = \"Options\",\n    Bounds = { X = 959, Y = 331, Width = 218, Height = 28 }\n}\n\nroot.Sprites[\"SettingOnTriggerSprite\"] = {\n    Sheet = \"Options\",\n    Bounds = { X = 959, Y = 361, Width = 218, Height = 28 }\n}\n\nroot.Sprites[\"SettingOnSceneSprite\"] = {\n    Sheet = \"Options\",\n    Bounds = { X = 959, Y = 391, Width = 218, Height = 28 }\n}\n\nroot.Sprites[\"SettingOnTriggerAndSceneSprite\"] = {\n    Sheet = \"Options\",\n    Bounds = { X = 959, Y = 421, Width = 218, Height = 28 }\n}\n\nroot.Sprites[\"SettingTypeASprite\"] = {\n    Sheet = \"Options\",\n    Bounds = { X = 959, Y = 451, Width = 218, Height = 28 }\n}\n\nroot.Sprites[\"SettingTypeBSprite\"] = {\n    Sheet = \"Options\",\n    Bounds = { X = 959, Y = 481, Width = 218, Height = 28 }\n}\n\nif root.Language ~= \"Japanese\" then\n    local uiSprites = {\n        \"MenuTitleTextConfig\",\n        \"ButtonPromptConfig\",\n        \"SelectedSprite\",\n        \"SelectedLabelSprite\",\n        \"SliderBarBaseSprite\",\n        \"SliderBarFillSprite\"\n    }\n    for i,spriteName in ipairs(uiSprites) do\n        root.Sprites[spriteName].Bounds.X = root.Sprites[spriteName].Bounds.X + 343\n    end\n\n    local valueSprites = {\n        \"SettingInstantSprite\",\n        \"SettingFastSprite\",\n        \"SettingNormalSprite\",\n        \"SettingSlowSprite\",\n        \"SettingShortSprite\",\n        \"SettingLongSprite\",\n        \"SettingDoSprite\",\n        \"SettingDontSprite\",\n        \"SettingYesSprite\",\n        \"SettingNoSprite\",\n        \"SettingReadSprite\",\n        \"SettingAllSprite\",\n        \"SettingOnTriggerSprite\",\n        \"SettingOnSceneSprite\",\n        \"SettingOnTriggerAndSceneSprite\",\n        \"SettingTypeASprite\",\n        \"SettingTypeBSprite\"\n    }\n    for i,spriteName in ipairs(valueSprites) do\n        root.Sprites[spriteName].Bounds.X = 1282\n    end\n\n    root.Sprites[\"BasicSettingsSprite\"].Bounds.Width = 659\n    root.Sprites[\"TextSettingsSprite\"].Bounds.Width = 542\n    root.Sprites[\"SoundSettingsSprite\"].Bounds.Width = 627\nend\n"
  },
  {
    "path": "profiles/chlcc/hud/saveicon.lua",
    "content": "root.SaveIcon = {\n    DefaultPosition = { X = 1120, Y = 495 },\n    SaveIconMenuOverlay = false,\n    SaveIconCurrentType = SaveIconType.CHLCC,\n    SaveIconSprites = {},\n    FadeInDuration = 15/60,\n    FadeOutDuration = 15/60,\n    ActiveAnimationDuration = 48/60\n};\n\nfor i = 1, 3 do\n    root.Sprites[\"SaveIcon\" .. i - 1] = {\n        Sheet = \"Data\",\n        Bounds = {\n            X = 372,\n            Y = 51 + (i - 1) * 21,\n            Width = 127,\n            Height = 19\n        }\n    }\n    root.SaveIcon.SaveIconSprites[i] = \"SaveIcon\" .. i - 1;\nend"
  },
  {
    "path": "profiles/chlcc/hud/savemenu.lua",
    "content": "root.SaveMenu = {\n    Type = SaveMenuType.CHLCC,\n    DrawType = DrawComponentType.SystemMenu,\n    SaveBackgroundColor = 0xff8e99,\n    LoadBackgroundColor = 0x50b5ff,\n    QuickLoadBackgroundColor = 0x01b06c,\n    SaveCircle = \"PinkCircle\",\n    LoadCircle = \"LightBlueCircle\",\n    QuickLoadCircle = \"GreenCircle\",\n    QuickLoadTextSprite = \"QuickLoadText\",\n    SaveTextSprite = \"SaveText\",\n    LoadTextSprite = \"LoadText\",\n    MenuTitleTextAngle = 4.45,\n    MenuTitleTextLeftPos = {X = 1, Y = 1},\n    MenuTitleTextRightPos = {X = 782, Y = 584},\n    SaveListPosition = {X = 0, Y = 0},\n    SaveListSprite = \"SaveList\",\n    EntryPositions = {\n        {X = 91, Y = 127}, {X = 91, Y = 254},\n        {X = 91, Y = 381}, {X = 91, Y = 508},\n        {X = 661, Y = 127}, {X = 661, Y = 508}\n    },\n    EmptyThumbnailSprite = \"EmptyThumbnail\",\n    EntryStartX = 153,\n    EntryXPadding = 512,\n    EntryStartY = 102,\n    EntryYPadding = 141,\n    QuickLoadEntrySprite = \"QuickLoadEntry\",\n    SaveEntrySprite = \"SaveEntry\",\n    LoadEntrySprite = \"LoadEntry\",\n    SelectionMarkerSprite = \"SelectionMarker\",\n    SelectionMarkerOffset = {X = 5, Y = 49},\n    EntryHighlightedSprite = \"EntryHighlighted\",\n    LockedSymbolSprite = \"LockedSymbol\",\n    LockedSymbolRelativePos = {X = 205, Y = 79},\n    ThumbnailRelativePos = {X = 21, Y = 12},\n    FadeInDuration = 64 / 60,\n    FadeOutDuration = 64 / 60,\n    PageNumBackgroundPos = {X = 1090, Y = 61},\n    PageNumBackground = \"PageNumBackground\",\n    CurrentPageNumPos = {X = 1133, Y = 65},\n    BigDigits = {},\n    PageNumSeparatorSlashPos = {X = 1161, Y = 90},\n    PageNumSeparatorSlash = \"PageNumSeparatorSlash\",\n    MaxPageNumPos = {X = 1179, Y = 90},\n    MaxPageNum = \"MaxPageNum\",\n    ButtonPromptPosition = {X = 853, Y = 651},\n    ButtonPrompt = \"SaveButtonPrompt\",\n    SelectDataTextPositions = {\n        {X = 94, Y = 51}, {X = 109, Y = 51},\n        {X = 122, Y = 51}, {X = 134, Y = 51},\n        {X = 147, Y = 51}, {X = 161, Y = 51},\n        {X = 180, Y = 51}, {X = 199, Y = 51},\n        {X = 213, Y = 51}, {X = 228, Y = 51}\n    },\n    SelectDataText = {},\n    EntryNumberHintTextRelativePos = {X = 209, Y = 10},\n    EntryNumberTextRelativePos = {X = 249, Y = 10},\n    SceneTitleTextRelativePos = {X = 209, Y = 35},\n    NoDataTextRelativePos = {X = 299, Y = 45},\n    PlayTimeHintTextRelativePos = {X = 259, Y = 67},\n    PlayTimeTextRelativePos = {X = 400, Y = 67},\n    SaveDateHintTextRelativePos = {X = 259, Y = 84},\n    SaveDateTextRelativePos = {X = 325, Y = 84},\n    SaveHourTextRelativePos = {X = 402, Y = 84},\n    MaxTitleWidth = 306,\n};\n\nroot.Sprites[\"PinkCircle\"] = {\n    Sheet = \"Save\",\n    Bounds = {X = 1, Y = 917, Width = 106, Height = 106}\n};\n\nroot.Sprites[\"LightBlueCircle\"] = {\n    Sheet = \"Save\",\n    Bounds = {X = 109, Y = 917, Width = 106, Height = 106}\n};\n\nroot.Sprites[\"GreenCircle\"] = {\n    Sheet = \"Save\",\n    Bounds = {X = 217, Y = 917, Width = 106, Height = 106}\n};\n\nroot.Sprites[\"SaveMenuBackground\"] = {\n    Sheet = \"Save\",\n    Bounds = {X = 0, Y = 0, Width = 1280, Height = 720}\n};\n\nroot.Sprites[\"EmptyThumbnail\"] = {\n    Sheet = \"Save\",\n    Bounds = {X = 1, Y = 727, Width = 160, Height = 90}\n};\n\nroot.Sprites[\"QuickLoadText\"] = {\n    Sheet = \"Save\",\n    Bounds = {X = 945, Y = 1, Width = 130, Height = 974}\n};\n\nroot.Sprites[\"SaveText\"] = {\n    Sheet = \"Save\",\n    Bounds = {X = 681, Y = 1, Width = 130, Height = 420}\n};\n\nroot.Sprites[\"LoadText\"] = {\n    Sheet = \"Save\",\n    Bounds = {X = 813, Y = 1, Width = 130, Height = 420}\n};\n\nroot.Sprites[\"SaveList\"] = {\n    Sheet = \"Save\",\n    Bounds = {X = 1, Y = 1, Width = 678, Height = 578}\n}\n\nroot.Sprites[\"QuickLoadEntry\"] = {\n    Sheet = \"Save\",\n    Bounds = {X = 1077, Y = 233, Width = 532, Height = 116}\n};\nroot.Sprites[\"SaveEntry\"] = {\n    Sheet = \"Save\",\n    Bounds = {X = 1077, Y = 1, Width = 532, Height = 116}\n};\nroot.Sprites[\"LoadEntry\"] = {\n    Sheet = \"Save\",\n    Bounds = {X = 1077, Y = 117, Width = 532, Height = 116}\n};\n\nroot.Sprites[\"SelectionMarker\"] = {\n    Sheet = \"Save\",\n    Bounds = {X = 440, Y = 589, Width = 14, Height = 14}\n};\n\nroot.Sprites[\"EntryHighlighted\"] = {\n    Sheet = \"Save\",\n    Bounds = {X = 1077, Y = 349, Width = 532, Height = 116}\n};\n\nroot.Sprites[\"LockedSymbol\"] = {\n    Sheet = \"Save\",\n    Bounds = {X = 463, Y = 581, Width = 28, Height = 28}\n};\n\nroot.Sprites[\"PageNumBackground\"] = {\n    Sheet = \"Save\",\n    Bounds = {X = 1, Y = 611, Width = 204, Height = 57}\n};\n\nfor i = 0, 9 do\n    root.Sprites[\"BigDigit\" .. i] = {\n        Sheet = \"Data\",\n        Bounds = {X = i * 36 + 371, Y = 1, Width = 34, Height = 48}\n    };\n    root.SaveMenu.BigDigits[#root.SaveMenu.BigDigits + 1] = \"BigDigit\" .. i;\nend\n\nroot.Sprites[\"PageNumSeparatorSlash\"] = {\n    Sheet = \"Data\",\n    Bounds = {X = 173, Y = 67, Width = 16, Height = 22}\n};\n\nroot.Sprites[\"MaxPageNum\"] = {\n    Sheet = \"Data\",\n    Bounds = {X = 335, Y = 67, Width = 16, Height = 22}\n};\n\nroot.Sprites[\"SaveButtonPrompt\"] = {\n    Sheet = \"Save\",\n    Bounds = {X = 1, Y = 581, Width = 430, Height = 28}\n};\n\nlocal saveMenuFadeTextBounds = {\n    { X = 1, Y = 670, Width = 18, Height = 55 },\n    { X = 21, Y = 670, Width = 16, Height = 55 },\n    { X = 39, Y = 670, Width = 15, Height = 55 },\n    { X = 56, Y = 670, Width = 16, Height = 55 },\n    { X = 74, Y = 670, Width = 17, Height = 55 },\n    { X = 93, Y = 670, Width = 22, Height = 55 },\n    { X = 117, Y = 670, Width = 22, Height = 55 },\n    { X = 141, Y = 670, Width = 17, Height = 55 },\n    { X = 159, Y = 670, Width = 18, Height = 55 },\n    { X = 179, Y = 670, Width = 20, Height = 55 }\n}\n\nfor i, bounds in ipairs(saveMenuFadeTextBounds) do\n    root.Sprites[\"SaveMenuFadeText\" .. i - 1] = {\n        Sheet = \"Save\",\n        Bounds = bounds\n    }\n    root.SaveMenu.SelectDataText[i] = \"SaveMenuFadeText\" .. i - 1;\nend"
  },
  {
    "path": "profiles/chlcc/hud/selectiondisplay.lua",
    "content": "root.SelectionDisplay = {\n    DrawType = DrawComponentType.Text,\n    SelectionBackgroundSprite = \"SelectionBackground\",\n    PlainSelectionFrameTopLeftSprite = \"PlainSelectionFrameTopLeft\",\n    PlainSelectionFrameTopSideSprite = \"PlainSelectionFrameTopSide\",\n    PlainSelectionFrameTopRightSprite = \"PlainSelectionFrameTopRight\",\n    PlainSelectionFrameLeftSideSprite = \"PlainSelectionFrameLeftSide\",\n    PlainSelectionFrameBottomLeftSprite = \"PlainSelectionFrameBottomLeft\",\n    PlainSelectionFrameRightSideSprite = \"PlainSelectionFrameRightSide\",\n    PlainSelectionFrameBottomRightSprite = \"PlainSelectionFrameBottomRight\",\n    PlainSelectionFrameBottomSideSprite = \"PlainSelectionFrameBottomSide\",\n    PlainSelectionFrameMiddleSprite = \"PlainSelectionFrameMiddle\",\n    SelectionHighlightSprite = \"SelectionHighlight\",\n    SelectionMaxCount = 5,\n    SelectionBackgroundX = 228,\n    SelectionBackgroundY = {277, 277, 196, 129, 95},\n    SelectionYSpacing = 81,\n    PlainSelectionYSpacing = 10,\n    FadeAnimationDurationInOut = 0.2\n};\n\nroot.Sprites[\"SelectionBackground\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 1, Y = 535, Width = 829, Height = 69 }\n};\n\nroot.Sprites[\"PlainSelectionFrameTopLeft\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 0, Y = 0, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameTopSide\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 16, Y = 0, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameTopRight\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 32, Y = 0, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameLeftSide\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 0, Y = 16, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameBottomLeft\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 0, Y = 32, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameRightSide\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 32, Y = 16, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameBottomRight\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 32, Y = 32, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameBottomSide\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 16, Y = 32, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameMiddle\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 16, Y = 16, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"SelectionHighlight\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 65, Y = 33, Width = 94, Height = 30 }\n};\n"
  },
  {
    "path": "profiles/chlcc/hud/sysmesboxdisplay.lua",
    "content": "local sheet = \"Data\";\nlocal name = \"SysMesBox\";\n\nroot.SysMesBoxDisplay = {\n    Type = SysMesBoxType.CHLCC,\n    DrawType = DrawComponentType.SystemMessage,\n    BoxX = 0,\n    BoxY = 230,\n    TextFontSize = 32,\n    TextMiddleY = 236,\n    TextX = 640,\n    TextLineHeight = 34,\n    TextMarginY = 14,\n    ChoicePadding = 40,\n    ChoiceY = 365,\n    ChoiceXBase = 680,\n    MinMaxMesWidth = 294,\n    MinHighlightWidth = 48,\n    HighlightBaseWidth = 144,\n    HighlightYOffset = 0,\n    HighlightXOffset = 0,\n    HighlightXBase = 658,\n    HighlightXStep = 132,\n    HighlightRightPartSpriteWidth = 24,\n    AnimationSpeed = 55,\n    FadeInDuration = 0.33,\n    FadeOutDuration = 0.25,\n    LoadingStarsPosition = { X = 580, Y = 357 },\n    LoadingStarsFadeDuration = 0.533\n};\n\nroot.Sprites[name .. \"Box\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 767,\n        Y = 637,\n        Width = 1280,\n        Height = 172\n    }\n};\nroot.SysMesBoxDisplay.Box = name .. \"Box\";\n\nroot.Sprites[name .. \"BoxDecoration\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 1383,\n        Y = 117,\n        Width = 592,\n        Height = 8\n    }\n};\nroot.SysMesBoxDisplay.BoxDecoration = name .. \"BoxDecoration\";\n\nroot.Sprites[name .. \"SelectionLeftPart\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 504,\n        Y = 52,\n        Width = 10,\n        Height = 34\n    }\n};\nroot.SysMesBoxDisplay.SelectionLeftPart = name .. \"SelectionLeftPart\";\n\nroot.Sprites[name .. \"SelectionRightPart\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 644,\n        Y = 52,\n        Width = 13,\n        Height = 34\n    }\n};\nroot.SysMesBoxDisplay.SelectionRightPart = name .. \"SelectionRightPart\";\n\nroot.Sprites[name .. \"SelectionMiddlePart\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 518,\n        Y = 52,\n        Width = 124,\n        Height = 34\n    }\n};\nroot.SysMesBoxDisplay.SelectionMiddlePart = name .. \"SelectionMiddlePart\";\n\nroot.Sprites[name .. \"LoadingStar\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 1,\n        Y = 97,\n        Width = 32,\n        Height = 32\n    }\n}\nroot.SysMesBoxDisplay.LoadingStar = name .. \"LoadingStar\""
  },
  {
    "path": "profiles/chlcc/hud/systemmenu.lua",
    "content": "root.SystemMenu = {\n    Type = SystemMenuType.CHLCC,\n    DrawType = DrawComponentType.SystemMenu,\n    BackgroundColor = 0xff9cb6,\n    CircleSprite = \"SystemMenuCircle\",\n\n    MenuLoopDuration = 1.815,\n    MenuHoverLerpSpeed = 20,\n    SystemMenuBackground = \"SystemMenuBackground\",\n    SystemMenuBackgroundPosition = {X = 93,Y = 0},\n    SystemMenuItemsLine = \"SystemMenuItemsLine\",\n    SystemMenuItemsLinePosition = {X = 82,Y = 0},\n    MainMenuTitleText = \"MainMenuTitleText\",\n    MenuTitleTextPosition = {X = 260, Y = 0},\n    MainMenuLabelRightPosition = {X = 793, Y = 135},\n    MenuTitleTextAngle = -1.83,\n    SystemMenuSelectionDot = \"SystemMenuSelectionDot\",\n    SystemMenuSelectionDotPosition = {X = 87, Y = 162},\n    SystemMenuSelectionDotMultiplier = 52,\n    SystemMenuSelection = \"SystemMenuSelection\",\n    SystemMenuSelectionPosition = {X = 93, Y = 73},\n    SystemMenuRunningSelectedLabel = \"SystemMenuRunningSelectedLabel\",\n    SystemMenuRunningSelectedLabelPosition = {X = 0,Y=112},\n    SystemMenuRunningSelectedLabelAngle = -0.268,\n    SystemMenuButtonPrompt= \"SystemMenuButtonPrompt\",\n    SystemMenuButtonPromptPosition = {X = 1022, Y = 651},\n    MenuSelectedLabelSpeed = -400,\n    MenuEntriesNum = 9,\n    MenuEntriesHNum = 0,\n    ------\n    --Not used since it isn't laid out in a way that would make it easier to use this\n    MenuEntriesX = 173,\n    MenuEntriesXOffset = 0,\n    MenuEntriesFirstY = 445,\n    MenuEntriesYPadding = 0,\n    ------\n    FocusTint = 0xff9cb6,\n    MenuEntriesPositions = {\n        {X = 110, Y = 107}, {X = 110, Y = 180},\n        {X = 110, Y = 234}, {X = 110, Y = 254},\n        {X = 110, Y = 309}, {X = 110, Y = 379},\n        {X = 110, Y = 445}, {X = 110, Y = 479},\n        {X = 110, Y = 506}\n    },\n        SelectMenuTextPositions = {\n        {X = 95, Y = 50}, {X = 110, Y = 50},\n        {X = 123, Y = 50}, {X = 135, Y = 50},\n        {X = 148, Y = 50}, {X = 162, Y = 50},\n        {X = 178, Y = 50}, {X = 196, Y = 50},\n        {X = 209, Y = 50}, {X = 225, Y = 50}\n    },\n    MenuEntriesSprites = {},\n    SelectMenuSprites = {},\n    StarSprite = \"SysMenuStar\",\n    StarAnimationDuration = 18 / 60,\n    StarRotationSpeed = math.rad(325), -- rads/sec\n    LeftAngle = math.rad(-15.0),\n    StarsOffsetsStart = {\n        { X = -15, Y = -25 },\n        { X = 11,  Y = -47 },\n        { X = 39,  Y = -65 },\n        { X = 72,  Y = -73 },\n        { X = 106, Y = -74 },\n        { X = 139, Y = -69 },\n    },\n    StarsOffsetsEnd = {\n        { X = -74,  Y = -23 },\n        { X = -58,  Y = -72 },\n        { X = -6,   Y = -105 },\n        { X = 61,   Y = -127 },\n        { X = 137,  Y = -125 },\n        { X = 188,  Y = -79 },\n    }\n};\n\nroot.Sprites[\"SystemMenuBackground\"] = {\n    Sheet = \"Main\",\n    Bounds = {X = 1770, Y = 1, Width = 278, Height = 720}\n};\n\nroot.Sprites[\"SystemMenuItemsLine\"] = {\n    Sheet = \"Main\",\n    Bounds = {X = 0, Y = 361, Width = 171, Height = 600}\n};\n\nroot.Sprites[\"MainMenuTitleText\"] = {\n    Sheet = \"Main\",\n    Bounds = {X = 1634, Y = 5, Width = 115, Height = 900}\n};\n\nroot.Sprites[\"SystemMenuRunningSelectedLabel\"] = {\n    Sheet = \"Main\",\n    Bounds = {X = 1396, Y = 801, Width = 226, Height = 102}\n};\n\nroot.Sprites[\"SystemSelectMenuHeader\"] = {\n    Sheet = \"Main\",\n    Bounds = {X = 449, Y = 587, Width = 191, Height = 55}\n};\n\nroot.Sprites[\"SystemMenuButtonPrompt\"] = {\n    Sheet = \"Main\",\n    Bounds = {X = 1789, Y = 723, Width = 258, Height = 28}\n};\n\nroot.Sprites[\"SystemMenuSelection\"] = {\n    Sheet = \"Main\",\n    Bounds = {X = 1771, Y = 756, Width = 277, Height = 116}\n};\n\nroot.Sprites[\"SystemMenuSelectionDot\"] = {\n    Sheet = \"Main\",\n    Bounds = {X = 511, Y = 550, Width = 14, Height = 14}\n};\n\nroot.Sprites[\"SystemMenuCircle\"] = {\n    Sheet = \"Main\",\n    Bounds = {X = 533, Y = 361, Width = 106, Height = 106}\n}\n\nroot.Sprites[\"SystemMenuBacklog\"] = {\n    Sheet = \"Main\",\n    Bounds = {X = 173, Y = 445, Width = 243, Height = 67}\n};\nroot.SystemMenu.MenuEntriesSprites[#root.SystemMenu.MenuEntriesSprites + 1] = \"SystemMenuBacklog\";\n\nroot.Sprites[\"SystemMenuSave\"] = {\n    Sheet = \"Main\",\n    Bounds = {X = 173, Y = 514, Width = 243, Height = 44}\n};\nroot.SystemMenu.MenuEntriesSprites[#root.SystemMenu.MenuEntriesSprites + 1] = \"SystemMenuSave\";\n\nroot.Sprites[\"SystemMenuLoad\"] = {\n    Sheet = \"Main\",\n    Bounds = {X = 173, Y = 560, Width = 243, Height = 45}\n};\nroot.SystemMenu.MenuEntriesSprites[#root.SystemMenu.MenuEntriesSprites + 1] = \"SystemMenuLoad\";\n\nroot.Sprites[\"SystemMenuQuickSave\"] = {\n    Sheet = \"Main\",\n    Bounds = {X = 173, Y = 607, Width = 243, Height = 75}\n};\nroot.SystemMenu.MenuEntriesSprites[#root.SystemMenu.MenuEntriesSprites + 1] = \"SystemMenuQuickSave\";\n\nroot.Sprites[\"SystemMenuQuickLoad\"] = {\n    Sheet = \"Main\",\n    Bounds = {X = 173, Y = 684, Width = 243, Height = 73}\n};\nroot.SystemMenu.MenuEntriesSprites[#root.SystemMenu.MenuEntriesSprites + 1] = \"SystemMenuQuickLoad\";\n\nroot.Sprites[\"SystemMenuConfig\"] = {\n    Sheet = \"Main\",\n    Bounds = {X = 173, Y = 800, Width = 243, Height = 53}\n};\nroot.SystemMenu.MenuEntriesSprites[#root.SystemMenu.MenuEntriesSprites + 1] = \"SystemMenuConfig\";\n\n\nroot.Sprites[\"SystemMenuTips\"] = {\n    Sheet = \"Main\",\n    Bounds = {X = 173, Y = 759, Width = 243, Height = 39}\n};\nroot.SystemMenu.MenuEntriesSprites[#root.SystemMenu.MenuEntriesSprites + 1] = \"SystemMenuTips\";\n\nroot.Sprites[\"SystemMenuTrophy\"] = {\n    Sheet = \"Main\",\n    Bounds = {X = 173, Y = 945, Width = 243, Height = 58}\n};\nroot.SystemMenu.MenuEntriesSprites[#root.SystemMenu.MenuEntriesSprites + 1] = \"SystemMenuTrophy\";\n\nroot.Sprites[\"SystemMenuReturnTitle\"] = {\n    Sheet = \"Main\",\n    Bounds = {X = 173, Y = 855, Width = 243, Height = 86}\n};\nroot.SystemMenu.MenuEntriesSprites[#root.SystemMenu.MenuEntriesSprites + 1] = \"SystemMenuReturnTitle\";\n\nlocal fadeTextSprites = {\n    {name = \"S\",  x = 447, w = 18},\n    {name = \"E\",  x = 467, w = 16},\n    {name = \"L\",  x = 485, w = 15},\n    {name = \"E2\", x = 502, w = 16},\n    {name = \"C\",  x = 520, w = 17},\n    {name = \"T\",  x = 539, w = 22},\n    {name = \"M\",  x = 560, w = 22},\n    {name = \"E3\", x = 583, w = 17},\n    {name = \"N\",  x = 601, w = 18},\n    {name = \"U\",  x = 622, w = 17},\n}\n\nfor _, sprite in ipairs(fadeTextSprites) do\n    local id =  \"SelectMenu_\" .. sprite.name\n    root.Sprites[id] = {\n        Sheet = \"Main\",\n        Bounds = {X = sprite.x, Y = 587, Width = sprite.w, Height = 55}\n    }\n    root.SystemMenu.SelectMenuSprites[#root.SystemMenu.SelectMenuSprites + 1] = id;\nend\n\nroot.Sprites[\"SysMenuStar\"] = {\n    Sheet = \"Main\",\n    Bounds = { X = 599, Y = 546, Width = 40, Height = 39 }\n}"
  },
  {
    "path": "profiles/chlcc/hud/tipsmenu.lua",
    "content": "root.TipsMenu = {\n    DrawType = DrawComponentType.SystemMenu,\n    Type = TipsMenuType.CHLCC,\n    TransitionDuration = 64 / 60,\n    BackgroundColor = 0x007efe,\n    CircleSprite = \"CircleTips\",\n    MenuTitleTextRightPos = { X = 787, Y = 209 },\n    MenuTitleTextLeftPos = { X = 257, Y = 1 },\n    MenuTitleTextAngle = 4.45,\n    MenuTitleText = \"MenuTitleTextTips\",\n    TipsTree = \"TipsTree\",\n    TreePosition = { X = 53, Y = 0 },\n    TipsGradient = \"TipsGradient\",\n    GradientPosition = { X = 75, Y = 0 },\n    EndOfGradientColor = 0x2691ff,\n    ButtonPromptSprite = \"ButtonPromptTips\",\n    ButtonPromptPosition = { X = 685, Y = 651 },\n    CurrentTipBackgroundSprite = \"CurrentTipBackgroundSprite\",\n    CurrentTipBackgroundPosition = { X = 381, Y = 42 },\n    SelectWordSprites = {},\n    SelectWordPos = {\n        { X = 78, Y = 49 },\n        { X = 91, Y = 49 },\n        { X = 105, Y = 49 },\n        { X = 117, Y = 49 },\n        { X = 131, Y = 49 },\n        { X = 144, Y = 49 },\n        { X = 156, Y = 49 },\n        { X = 183, Y = 49 },\n        { X = 199, Y = 49 },\n        { X = 215, Y = 49 }\n    },\n    SelectWordDuration = 110/60,\n    SelectWordInterval = 5/60,\n    CategoryString = \"【 】\",\n    TipsStringTable = 2,\n    CategoryStringIndex = 5, \n    SortStringIndex = 10, \n    LockedTipsIndex = 7, \n    NumberLabelStrIndex = 8, \n    PageSeparatorIndex = 9, \n    NewLabelStrIndex = 11,\n    UnreadLabelStrIndex = 12,\n    TipListEntryBounds = { X = 75, Y = 130, Width = 286, Height = 24 },\n    TipListEntryTextOffsetX = 6;\n    TipListEntryFontSize = 20,\n    TipListYPadding = 24,\n    TipsListBounds = { X = 75, Y = 130, Width = 286, Height = 504 },\n    TipsListRenderBounds = { X = 30, Y = 130, Width = 380, Height = 504 },\n    NumberLabelPosition = { X = 396, Y = 52 },\n    NumberLabelFontSize = 22,\n    NumberBounds = { X = 409, Y = 52, Width = 0, Height = 0 },\n    NumberFontSize = 22,\n    CurrentPageSprites = {},\n    TotalPageSprites = {},\n    PageSeparatorSprite = \"PageSeparatorSprite\",\n    CurrentPagePosition = { X = 1155, Y = 586 },\n    TotalPagesPosition = { X = 1195, Y = 611 },\n    PageSeparatorPosition = { X = 1181, Y = 611 },\n    DefaultColorIndex = 0,\n    UnreadColorIndex = 70,\n    PronounciationFontSize = 18,\n    NameFontSize = 30,\n    TipListEntryNameXOffset = 55,\n    NameInitialBounds = { X = 1210, Y = 80, Width = 0, Height = 0 },\n    PronounciationInitialBounds = { X = 1210, Y = 110, Width = 0, Height = 0 },\n    TipsListEntryDotOffset = { X = -17, Y = -4},\n    TipsListNewDotOffset= { X = -40, Y = -4},\n    TipScrollbarPos = { X = 359, Y = 127},\n\n    TipsEntryHighlightBarSprite = \"TipsEntryHighlightBar\",\n    TipsEntryHighlightDotSprite = \"TipsEntryHighlightDot\",\n    TipsEntryNewDotSprite = \"TipsEntryNewDot\",\n    TipsLeftLineSprite = \"TipsLeftLine\",\n    TipsLeftLineEndSprite = \"TipsLeftLineEnd\",\n    TipsLeftLineHoleSprite = \"TipsLeftLineHole\",\n    TipsLeftLineHoleEndSprite = \"TipsLeftLineHoleEnd\",\n    TipsListBgBarSprite = \"TipsListBgBar\",\n    TipsListBgBarHoleSprite = \"TipsListBgBarHole\",\n    TipsScrollThumbSprite = \"TipsScrollThumb\",\n    TipsScrollTrackSprite = \"TipsScrollTrack\",\n\n}\n\nroot.Sprites[\"CircleTips\"] = {\n    Sheet = \"Tips\",\n    Bounds = { X = 1, Y = 917, Width = 106, Height = 106 }\n}\n\nroot.Sprites[\"MenuTitleTextTips\"] = {\n    Sheet = \"Tips\",\n    Bounds = { X = 1151, Y = 1, Width = 120, Height = 742 }\n}\n\nroot.Sprites[\"TipsTree\"] = {\n    Sheet = \"Tips\",\n    Bounds = { X = 851, Y = 273, Width = 182, Height = 131 }\n}\n\n--Purposefully cut the texture to create smaller Rect below\nroot.Sprites[\"TipsGradient\"] = {\n    Sheet = \"Tips\",\n    Bounds = { X = 852, Y = 1, Width = 295, Height = 132 }\n}\n\nroot.Sprites[\"ButtonPromptTips\"] = {\n    Sheet = \"Tips\",\n    Bounds = { X = 3, Y = 611, Width = 595, Height = 28 }\n}\n\nroot.Sprites[\"CurrentTipBackgroundSprite\"] = {\n    Sheet = \"Tips\",\n    Bounds = { X = 1, Y = 1, Width = 846, Height = 605 }\n}\n\nlocal selectWordBounds = {\n    { X = 1, Y = 643, Width = 16, Height = 57 },\n    { X = 20, Y = 643, Width = 17, Height = 57 },\n    { X = 38, Y = 643, Width = 15, Height = 57 },\n    { X = 55, Y = 643, Width = 17, Height = 57 },\n    { X = 74, Y = 643, Width = 16, Height = 57 },\n    { X = 92, Y = 643, Width = 15, Height = 57 },\n    { X = 109, Y = 643, Width = 30, Height = 57 },\n    { X = 141, Y = 643, Width = 19, Height = 57 },\n    { X = 162, Y = 643, Width = 19, Height = 57 },\n    { X = 181, Y = 643, Width = 18, Height = 57 }\n}\n\nfor i, bounds in ipairs(selectWordBounds) do\n    root.Sprites[\"SelectWord\" .. i - 1] = {\n        Sheet = \"Tips\",\n        Bounds = bounds\n    }\n    root.TipsMenu.SelectWordSprites[i] = \"SelectWord\" .. i - 1;\nend\n\nroot.Sprites[\"TipsEntryHighlightBar\"] = {\n    Sheet = \"Tips\",\n    Bounds = { X = 851, Y = 510, Width = 286, Height = 24 }\n}\nroot.Sprites[\"TipsEntryHighlightDot\"] = {\n    Sheet = \"Tips\",\n    Bounds = { X = 636, Y = 609, Width = 32, Height = 32 }\n}\nroot.Sprites[\"TipsEntryNewDot\"] = {\n    Sheet = \"Tips\",\n    Bounds = { X = 602, Y = 609, Width = 32, Height = 32 }\n}\nroot.Sprites[\"TipsLeftLine\"] = {\n    Sheet = \"Tips\",\n    Bounds = { X = 851, Y = 406, Width = 32, Height = 24 }\n}\nroot.Sprites[\"TipsLeftLineHole\"] = {\n    Sheet = \"Tips\",\n    Bounds = { X = 851, Y = 432, Width = 32, Height = 24 }\n}\nroot.Sprites[\"TipsLeftLineHoleEnd\"] = {\n    Sheet = \"Tips\",\n    Bounds = { X = 851, Y = 458, Width = 32, Height = 24 }\n}\nroot.Sprites[\"TipsLeftLineEnd\"] = {\n    Sheet = \"Tips\",\n    Bounds = { X = 851, Y = 484, Width = 286, Height = 8 }\n}\nroot.Sprites[\"TipsListBgBar\"] = {\n    Sheet = \"Tips\",\n    Bounds = { X = 851, Y = 134, Width = 286, Height = 24 }\n}\nroot.Sprites[\"TipsListBgBarHole\"] = {\n    Sheet = \"Tips\",\n    Bounds = { X = 851, Y = 160, Width = 286, Height = 24 }\n}\nroot.Sprites[\"TipsScrollThumb\"] = {\n    Sheet = \"Tips\",\n    Bounds = { X = 1275, Y = 512, Width = 14, Height = 60 }\n}\nroot.Sprites[\"TipsScrollTrack\"] = {\n    Sheet = \"Tips\",\n    Bounds = { X = 1281, Y = 0, Width = 2, Height = 499 }\n}\n\nfor i = 0, 9 do\n    root.Sprites[\"CurrentPageSprites\" .. i] = {\n        Sheet = \"Data\",\n        Bounds = { X = 371 + (i * 36), Y = 1, Width = 34, Height = 48 }\n    }\n    root.TipsMenu.CurrentPageSprites[i + 1] = \"CurrentPageSprites\" .. i ;\nend\n\nfor i = 0, 9 do\n    root.Sprites[\"TotalPageSprites\" .. i] = {\n        Sheet = \"Data\",\n        Bounds = { X = 190 + (i * 18), Y = 67, Width = 16, Height = 22 }\n    }\n    root.TipsMenu.TotalPageSprites[i + 1] = \"TotalPageSprites\" .. i ;\nend\n\nroot.Sprites[\"PageSeparatorSprite\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 173, Y = 67, Width = 16, Height = 22 }\n}"
  },
  {
    "path": "profiles/chlcc/hud/tipsnotification.lua",
    "content": "root.TipsNotification = {\n    Type = TipsNotificationType.CHLCC,\n\n    TextTableId = 2,\n    NotificationTextPart1MessageId = 1,\n    NotificationTextPart2MessageId = 2,\n\n    FadeInDuration = 16 / 60,\n    FadeOutDuration = 16 / 60,\n    \n    HeaderMessageId = 0,\n    HeaderPosition = { X = 312, Y = 40 },\n    HeaderFontSize = 20,\n    HeaderColor = { TextColor = 0xAAFFAA, OutlineColor = 0x000000 },\n\n    TextStartPosition = { X = 986, Y = 60 },\n    TextTargetPosition = { X = 326, Y = 60 },\n    TextFontSize = 20,\n    TipNameColorIndex = 2,\n\n    SlideTime = (660 / 8) / 60,\n    HoldTime = ((1286 - 660) / 8) / 60,\n\n    RenderBounds = { X = 0, Y = 0, Width = 1010, Height = 720 },\n};\n"
  },
  {
    "path": "profiles/chlcc/hud/titlemenu.lua",
    "content": "local languageSuffix = root.Language\nif root.Language == \"Japanese\" then languageSuffix = \"\" end\n\nroot.TitleMenu = {\n    Type = TitleMenuType.CHLCC,\n    PressToStartX = 72,\n    PressToStartY = 595,\n    PressToStartAnimDurationIn = 0.5,\n    PressToStartAnimDurationOut = 0.5,\n    PressToStartSprite = \"TitleMenuPressToStart\",\n    BackgroundSprite = \"TitleMenuBackground\",\n    IntroBackgroundSprite = \"TitleMenuIntroBackground\",\n    IntroHighlightSprites = {\n        \"IntroBrightGreenHighlight\",\n        \"IntroSunHighlight\",\n        \"IntroGrayHighlight\",\n        \"IntroCrescentRainbowHighlight\",\n        \"IntroBlueHighlight\",\n        \"IntroWhiteHighlight\",\n        \"IntroBrownHighlight\",\n        \"IntroDiamondHighlight\",\n        \"IntroDarkGreenHighlight\",\n        \"IntroCircularRainbowHighlight\"\n    },\n\n    -- Positions along the diagonal normalized between -1 and 1\n    IntroHighlightPositions = {\n        -1.13, -1.00, -0.49, 0.00, 0.17,\n        0.30, 0.58, 0.69, 0.91, 1.12\n    },\n    IntroPanningAnimationDuration = 2.1,\n    IntroAfterPanningWaitDuration = 0.8,\n    IntroBouncingStarSprite = \"StarLogo\",\n    IntroBouncingStarAnimationDuration = 3.73;\n    IntroExplodingStarSprite = \"IntroSmallStar\",\n    IntroExplodingStarAnimationDuration = 1.067,\n    IntroExplodingStarAnimationRotationDuration = 0.5,\n    IntroExplodingStarAnimationDistance = 294,\n    IntroFallingStarSprite = \"IntroBigStar\",\n    IntroFallingStarsAnimationDuration = 3,\n    IntroFallingStarsAnimationDistance = 2546,\n    IntroFallingStarsAnimationDirection = { X = -1, Y = 1 },\n    IntroFallingStarsAnimationRotationDuration = 0.7,\n    IntroCHLogoFadeAnimationDuration = 2,\n    IntroCHLogoFadeAnimationStartY = 406,\n    IntroLCCLogoAnimationDuration = 1.067,\n    IntroLogoStarHighlightSprite = \"LogoStarHighlight\";\n    IntroLogoStarHighlightPosition = { X = 471, Y = 342 };\n    IntroLogoStarHighlightAnimationDuration = 0.5334,\n    IntroDelusionADVAnimationDuration = 1.4;\n    IntroDelusionADVSprites = {},\n    IntroDelusionADVPositions = root.Language == \"Japanese\" and {\n        { X = 80,  Y = 402 },\n        { X = 104, Y = 402 },\n        { X = 127, Y = 402 },\n        { X = 150, Y = 402 },\n        { X = 172, Y = 402 },\n        { X = 195, Y = 402 },\n        { X = 215, Y = 402 }\n    } or {\n        { X = 78,  Y = 400 },\n        { X = 78,  Y = 418 },\n        { X = 174, Y = 400 },\n        { X = 194, Y = 400 },\n        { X = 211, Y = 400 }\n    },\n    IntroDelusionADVHighlightAnimationDuration = 0.5334,\n    IntroSeiraAnimationDuration = 0.8,\n    IntroLogoPopOutAnimationDuration = 0.2,\n    IntroLogoPopOutAnimationDelay = 0.5334,\n    IntroLogoPopOutOffset = { X = -6, Y = -6 },\n    IntroCopyrightAnimationDuration = 0.25,\n    LCCLogoSprites = {\n        \"LoveLogo\",\n        \"ChuLeftLogo\",\n        \"ChuRightLogo\",\n        \"ExclMarkLogo\"\n    };\n    LCCLogoPositions = {\n        root.Language == \"Japanese\"\n            and { X = 235, Y = 336 }\n            or { X = 231, Y = 333 },\n        { X = 353, Y = 336 },\n        { X = 500, Y = 316 },\n        { X = 614, Y = 316 }\n    },\n    DelusionADVSpriteCount = 7,\n    DelusionADVSprites = {},\n    DelusionADVUnderSprite = \"DelusionADVUnder\" .. languageSuffix,\n    DelusionADVSprite = \"DelusionADV\" .. languageSuffix,\n    DelusionADVPosition = root.Language == \"Japanese\"\n        and { X = 76, Y = 394 }\n        or { X = 76, Y = 397 },\n    DelusionADVPopoutOffset = { X = -2, Y = -3 },\n    SeiraUnderSprite = \"SeiraUnder\",\n    SeiraUnderPosition = { X = 733, Y = 0 },\n    SeiraPopoutOffset = { X = -48, Y = -48 };\n    SeiraSprite = \"Seira\",\n    SeiraPosition = { X = 728, Y = -47 },\n    CHLogoSprite = \"CHLogo\",\n    CHLogoPosition = { X = 61, Y = 279 },\n    LCCLogoUnderSprite = \"LCCLogoUnder\",\n    LCCLogoUnderPosition = root.Language == \"Japanese\"\n        and { X = 241, Y = 327 }\n        or { X = 242, Y = 328 },\n    StarLogoSprite = \"StarLogo\",\n    StarLogoPosition = { X = 465, Y = 316 },\n    CopyrightTextSprite = \"CopyrightText\",\n    CopyrightTextPosition = { X = 72, Y = 675 },\n    SpinningCircleSprite = \"SpinningCircle\",\n    SpinningCirclePosition = { X = 610.5, Y = -285.5 },\n    SpinningCircleAnimationDuration = 15,\n    SpinningCircleFlashingAnimationDuration = 0.833;\n    ItemHighlightSprite = \"TitleMenuItemHighlight\",\n    ItemHighlightOffset = { X = 73, Y = 7 },\n    ItemPadding = 40,\n    ItemYBase = 69,\n    ItemFadeInDuration = 0.3,\n    ItemFadeOutDuration = 0.6,\n    SecondaryItemFadeInDuration = 0.2,\n    SecondaryItemFadeOutDuration = 0.2,\n    PrimaryFadeInDuration = 16/60,\n    PrimaryFadeOutDuration = 32/60,\n    SecondaryFadeInDuration = 8/60,\n    SecondaryFadeOutDuration = 8/60,\n    ItemHyperUpLine = \"TitleMenuItemHyperUpLine\",\n    ItemSuperUpLine = \"TitleMenuItemSuperUpLine\",\n    ItemUpLine = \"TitleMenuItemUpLine\",\n    ItemStraightLine = \"TitleMenuItemStraightLine\",\n    ItemDownLine = \"TitleMenuItemDownLine\",\n    ItemSuperDownLine = \"TitleMenuItemSuperDownLine\",\n    ItemLoadQuickSprite = \"TitleMenuItemLoadQuick\",\n    SecondaryItemX = 320,\n    ItemLoadY = 109,\n    ItemLoadQuickY = 83,\n    ItemLoadSprite = \"TitleMenuItemLoad\",\n    ItemLoadQuickHighlightedSprite = \"TitleMenuItemLoadQuickHighlighted\",\n    ItemLoadHighlightedSprite = \"TitleMenuItemLoadHighlighted\",\n    SecondaryItemHighlightSprite = \"TitleMenuSecondaryItemHighlight\",\n    ItemClearListY = 71,\n    ItemCGLibraryY = 97,\n    ItemSoundLibraryY = 123,\n    ItemMovieLibraryY = 149,\n    ItemTipsY = 175,\n    ItemTrophyY = 201,\n    ItemConfigY = 163,\n    ItemSystemSaveY = 189,\n    SecondaryItemHighlightX = 286,\n    SecondaryMenuPaddingY = 26,\n    SecondaryMenuLoadOffsetY = 76,\n    SecondaryMenuLineX = 241,\n    SecondaryMenuLoadLineY = 93,\n    SecondaryMenuLoadQuickLineY = 119,\n    SecondaryMenuExtraClearY = 81,\n    SecondaryMenuExtraCGY = 107,\n    SecondaryMenuExtraSoundY = 133,\n    SecondaryMenuExtraMovieY = 159,\n    SecondaryMenuExtraTipsY = 159,\n    SecondaryMenuExtraTrophyY = 159,\n    SecondaryMenuSystemConfigY = 173,\n    SecondaryMenuSystemSaveY = 199,\n    MenuEntriesNum = 14,\n    MenuEntriesSprites = {},\n    MenuEntriesHighlightedSprites = {},\n    ExitSprite = \"ExitSprite\",\n    ExitHighlightSprite = \"ExitHighlightSprite\",\n    LineNum = 6,\n    LineEntriesSprites = {}\n};\n\nroot.TitleMenu.IntroDelusionADVSpriteCount = #root.TitleMenu.IntroDelusionADVPositions;\nfor i = 1, root.TitleMenu.IntroDelusionADVSpriteCount do\n    root.TitleMenu.IntroDelusionADVSprites[i] = \"IntroDelusionADV\" .. i .. languageSuffix\nend\n\nfor i = 0, 3 do\n    root.Sprites[\"TitleMenuEntry\" .. i] = {\n        Sheet = \"Title\",\n        Bounds = {\n            X = 1151,\n            Y = 101 + i * 25,\n            Width = 188,\n            Height = 23\n        }\n    };\n    root.TitleMenu.MenuEntriesSprites[#root.TitleMenu.MenuEntriesSprites + 1] = \"TitleMenuEntry\" .. i;\nend\n\nfor i = 0, 9 do\n    root.Sprites[\"TitleMenuEntry\" .. (i + 4)] = {\n        Sheet = \"Title\",\n        Bounds = {\n            X = 1369,\n            Y = 684 + i * 22,\n            Width = 216,\n            Height = 20\n        }\n    };\n    root.TitleMenu.MenuEntriesSprites[#root.TitleMenu.MenuEntriesSprites + 1] = \"TitleMenuEntry\" .. (i + 4);\nend\n\nfor i = 0, 3 do\n    root.Sprites[\"TitleMenuEntryHighlighted\" .. i] = {\n        Sheet = \"Title\",\n        Bounds = {\n            X = 1151,\n            Y = 1 + i * 25,\n            Width = 188,\n            Height = 23\n        }\n    };\n    root.TitleMenu.MenuEntriesHighlightedSprites[#root.TitleMenu.MenuEntriesHighlightedSprites + 1] = \"TitleMenuEntryHighlighted\" .. i;\nend\n\nfor i = 0, 9 do\n    root.Sprites[\"TitleMenuEntryHighlighted\" .. (i + 4)] = {\n        Sheet = \"Title\",\n        Bounds = {\n            X = 1151,\n            Y = 684 + i * 22,\n            Width = 216,\n            Height = 20\n        }\n    };\n    root.TitleMenu.MenuEntriesHighlightedSprites[#root.TitleMenu.MenuEntriesHighlightedSprites + 1] = \"TitleMenuEntryHighlighted\" .. (i + 4);\nend\n\nroot.Sprites[\"TitleMenuPressToStart\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 1, Y = 921, Width = 313, Height = 28 },\n};\n\nroot.Sprites[\"DelusionADVUnder\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 1863, Y = 772, Width = 163, Height = 27 },\n};\n\nroot.Sprites[\"DelusionADVUnderEnglish\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 1862, Y = 785, Width = 154, Height = 33 },\n};\n\nroot.Sprites[\"DelusionADV\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 1863, Y = 728, Width = 163, Height = 27 },\n};\n\nroot.Sprites[\"DelusionADVEnglish\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 1862, Y = 734, Width = 154, Height = 33 },\n};\n\nroot.Sprites[\"IntroDelusionADV1\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 1863, Y = 684, Width = 23, Height = 20 },\n};\n\nroot.Sprites[\"IntroDelusionADV2\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 1888, Y = 684, Width = 22, Height = 20 },\n};\n\nroot.Sprites[\"IntroDelusionADV3\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 1912, Y = 684, Width = 22, Height = 20 },\n};\n\nroot.Sprites[\"IntroDelusionADV4\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 1936, Y = 684, Width = 21, Height = 20 },\n};\n\nroot.Sprites[\"IntroDelusionADV5\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 1959, Y = 684, Width = 22, Height = 20 },\n};\n\nroot.Sprites[\"IntroDelusionADV6\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 1983, Y = 684, Width = 20, Height = 20 },\n};\n\nroot.Sprites[\"IntroDelusionADV7\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 2005, Y = 684, Width = 21, Height = 20 },\n};\n\nroot.Sprites[\"IntroDelusionADV1English\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 1862, Y = 683, Width = 90, Height = 18 },\n};\n\nroot.Sprites[\"IntroDelusionADV2English\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 1862, Y = 701, Width = 90, Height = 15 },\n};\n\nroot.Sprites[\"IntroDelusionADV3English\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 1958, Y = 683, Width = 19, Height = 33 },\n};\n\nroot.Sprites[\"IntroDelusionADV4English\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 1978, Y = 683, Width = 17, Height = 33 },\n};\n\nroot.Sprites[\"IntroDelusionADV5English\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 1996, Y = 683, Width = 19, Height = 33 },\n};\n\nroot.Sprites[\"SeiraUnder\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 555, Y = 1, Width = 594, Height = 768 },\n};\n\nroot.Sprites[\"Seira\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 1, Y = 1, Width = 552, Height = 768 },\n};\n\nroot.Sprites[\"CHLogo\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 1, Y = 771, Width = 592, Height = 115 },\n};\n\nroot.Sprites[\"LCCLogoUnder\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 597, Y = 771, Width = 462, Height = 122 },\n};\n\nroot.Sprites[\"LCCLogoUnderEnglish\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 591, Y = 771, Width = 468, Height = 122 },\n};\n\nroot.Sprites[\"ChuLeftLogo\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 483, Y = 915, Width = 136, Height = 108 },\n};\n\nroot.Sprites[\"ChuRightLogo\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 693, Y = 895, Width = 136, Height = 128 },\n};\n\nroot.Sprites[\"LoveLogo\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 341, Y = 915, Width = 140, Height = 108 },\n};\n\nroot.Sprites[\"StarLogo\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 621, Y = 895, Width = 70, Height = 128 },\n};\n\nroot.Sprites[\"ExclMarkLogo\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 831, Y = 895, Width = 82, Height = 128 },\n};\n\nroot.Sprites[\"CopyrightText\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 193, Y = 891, Width = 380, Height = 24 },\n};\n\nroot.Sprites[\"SpinningCircle\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 1366, Y = 1, Width = 681, Height = 681 },\n};\n\nroot.Sprites[\"TitleMenuIntroBackground\"] = {\n    Sheet = \"TitleBg1\",\n    Bounds = { X = 0, Y = 0, Width = 1280, Height = 720 },\n};\n\nroot.Sprites[\"IntroSmallStar\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 1153, Y = 534, Width = 45, Height = 44 },\n};\n\nroot.Sprites[\"IntroBigStar\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 1156, Y = 345, Width = 178, Height = 170 },\n};\n\nroot.Sprites[\"LogoStarHighlight\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 1202, Y = 915, Width = 70, Height = 108 }\n}\n\nroot.Sprites[\"IntroBrightGreenHighlight\"] = {\n    Sheet = \"Highlights\",\n    Bounds = { X = 1536, Y = 0, Width = 256, Height = 256 },\n};\n\nroot.Sprites[\"IntroSunHighlight\"] = {\n    Sheet = \"Highlights\",\n    Bounds = { X = 0, Y = 0, Width = 512, Height = 512 },\n};\n\nroot.Sprites[\"IntroGrayHighlight\"] = {\n    Sheet = \"Highlights\",\n    Bounds = { X = 1536, Y = 256, Width = 256, Height = 256 },\n};\n\nroot.Sprites[\"IntroCrescentRainbowHighlight\"] = {\n    Sheet = \"Highlights\",\n    Bounds = { X = 0, Y = 512, Width = 512, Height = 512 },\n};\n\nroot.Sprites[\"IntroBlueHighlight\"] = {\n    Sheet = \"Highlights\",\n    Bounds = { X = 1280, Y = 256, Width = 256, Height = 256 },\n};\n\nroot.Sprites[\"IntroWhiteHighlight\"] = {\n    Sheet = \"Highlights\",\n    Bounds = { X = 1280, Y = 0, Width = 256, Height = 256 },\n};\n\nroot.Sprites[\"IntroBrownHighlight\"] = {\n    Sheet = \"Highlights\",\n    Bounds = { X = 1024, Y = 256, Width = 256, Height = 256 },\n};\n\nroot.Sprites[\"IntroDiamondHighlight\"] = {\n    Sheet = \"Highlights\",\n    Bounds = { X = 512, Y = 512, Width = 512, Height = 512 },\n};\n\nroot.Sprites[\"IntroDarkGreenHighlight\"] = {\n    Sheet = \"Highlights\",\n    Bounds = { X = 1024, Y = 0, Width = 256, Height = 256 },\n};\n\nroot.Sprites[\"IntroCircularRainbowHighlight\"] = {\n    Sheet = \"Highlights\",\n    Bounds = { X = 512, Y = 0, Width = 512, Height = 512 },\n};\n\nroot.Sprites[\"TitleMenuBackground\"] = {\n    Sheet = \"TitleBg2\",\n    Bounds = { X = 0, Y = 0, Width = 1280, Height = 720 },\n};\n\nroot.Sprites[\"TitleMenuItemHighlight\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 915, Y = 951, Width = 244, Height = 36 },\n};\n\nroot.Sprites[\"TitleMenuItemHyperUpLine\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 1805, Y = 686, Width = 51, Height = 80 },\n};\n\nroot.Sprites[\"TitleMenuItemSuperUpLine\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 1805, Y = 776, Width = 51, Height = 54 },\n};\n\nroot.Sprites[\"TitleMenuItemUpLine\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 1805, Y = 845, Width = 51, Height = 28 },\n};\n\nroot.Sprites[\"TitleMenuItemStraightLine\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 1805, Y = 888, Width = 51, Height = 2 },\n};\n\nroot.Sprites[\"TitleMenuItemDownLine\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 1805, Y = 910, Width = 51, Height = 28 },\n};\n\nroot.Sprites[\"TitleMenuItemSuperDownLine\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 1805, Y = 959, Width = 51, Height = 54 },\n};\n\nroot.TitleMenu.LineEntriesSprites[#root.TitleMenu.LineEntriesSprites + 1] = \"TitleMenuItemHyperUpLine\";\nroot.TitleMenu.LineEntriesSprites[#root.TitleMenu.LineEntriesSprites + 1] = \"TitleMenuItemSuperUpLine\";\nroot.TitleMenu.LineEntriesSprites[#root.TitleMenu.LineEntriesSprites + 1] = \"TitleMenuItemUpLine\";\nroot.TitleMenu.LineEntriesSprites[#root.TitleMenu.LineEntriesSprites + 1] = \"TitleMenuItemStraightLine\";\nroot.TitleMenu.LineEntriesSprites[#root.TitleMenu.LineEntriesSprites + 1] = \"TitleMenuItemDownLine\";\nroot.TitleMenu.LineEntriesSprites[#root.TitleMenu.LineEntriesSprites + 1] = \"TitleMenuItemSuperDownLine\";\n\nroot.Sprites[\"TitleMenuItemLoadQuick\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 1369, Y = 684, Width = 216, Height = 20 },\n};\n\nroot.Sprites[\"TitleMenuItemLoad\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 1369, Y = 706, Width = 216, Height = 20 },\n};\n\nroot.Sprites[\"TitleMenuItemLoadQuickHighlighted\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 1151, Y = 684, Width = 216, Height = 20 },\n};\n\nroot.Sprites[\"TitleMenuItemLoadHighlighted\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 1151, Y = 706, Width = 216, Height = 20 },\n};\n\nroot.Sprites[\"TitleMenuSecondaryItemHighlight\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 915, Y = 989, Width = 285, Height = 34 },\n};\n\nroot.Sprites[\"ExitSprite\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 1370, Y = 944, Width = 72, Height = 23 },\n};\n\nroot.Sprites[\"ExitHighlightSprite\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 1370, Y = 920, Width = 72, Height = 23 },\n};"
  },
  {
    "path": "profiles/chlcc/hud/trophymenu.lua",
    "content": "root.TrophyMenu = {\n    DrawType = DrawComponentType.SystemMenu,\n    Type = TrophyMenuType.CHLCC,\n\n    BackgroundColor = 0xe8641b,\n\n    CircleSprite = \"CircleTrophy\",\n\n    MenuTitleTextRightPos = { X = 790, Y = 256 },\n    MenuTitleTextLeftPos = { X = 4, Y = 4 },\n    MenuTitleTextAngle = 4.45,\n    MenuTitleText = \"MenuTitleTextTrophy\",\n\n    ButtonPromptSprite = \"ButtonPromptTrophy\",\n    ButtonPromptPosition = { X = 1113, Y = 651 },\n\n    PlatinumTrophySprite = \"PlatinumTrophy\",\n    PlatinumTrophyPos = { X = 585, Y = 91 },\n    GoldTrophySprite = \"GoldTrophy\",\n    GoldTrophyPos = { X =  649, Y = 91 },\n    SilverTrophySprite = \"SilverTrophy\",\n    SilverTrophyPos = { X = 713, Y = 91 },\n    BronzeTrophySprite = \"BronzeTrophy\",\n    BronzeTrophyPos = { X = 777, Y = 91 },\n\n    DefaultTrophyIconSprite = \"DefaultTrophyIcon\",\n    TrophyEntryCardSprite = \"TrophyEntryCard\",\n    TrophyEntriesBorderSprite = \"TrophyEntriesBorder\",\n    TrophyPageCtBoxSprite = \"TrophyPageCtBox\",\n    TrophyPageCtPos = { X = 1090, Y = 60 },\n\n    EntriesPerPage = 6,\n    FirstEntryPos = { X = 0, Y = 130 },\n    EntryHeight = 74,\n    EntryCardOffset = { X = 91, Y = 1 },\n    EntryNameOffset = { X = 218, Y = 12 },\n    EntryNameFontSize = 26,\n    EntryDescriptionOffset = { X = 218, Y = 42 },\n    EntryDescriptionFontSize = 18,\n    EntryIconOffset = { X = 112, Y = 4 },\n    EntryDefaultNameTextTableId = 0,\n    EntryDefaultNameStringNum = 19,\n\n    CurrentPageNumPos = { X = 1133, Y = 65 },\n    PageNumSeparatorSlashSprite = \"PageNumSeparatorSlash\",\n    PageNumSeparatorSlashPos = { X = 1161, Y = 90 },\n    MaxPageNumPos = { X = 1179, Y = 90 },\n    PageNums = {},\n    ReachablePageNums = {},\n\n    TrophyCountHintTextTableId = 0,\n    TrophyCountHintStringNum = 20,\n    TrophyCountHintLabelPos = { X = 297, Y = 97 },\n    TrophyCountFontSize = 20\n};\n\nroot.Sprites[\"CircleTrophy\"] = {\n    Sheet = \"Trophy\",\n    Bounds = { X = 289, Y = 665, Width = 106, Height = 106 }\n}\n\nroot.Sprites[\"MenuTitleTextTrophy\"] = {\n    Sheet = \"Trophy\",\n    Bounds = { X = 964, Y = 4, Width = 116, Height = 664 }\n}\n\nroot.Sprites[\"ButtonPromptTrophy\"] = {\n    Sheet = \"Trophy\",\n    Bounds = { X = 2, Y = 802, Width = 167, Height = 28 }\n}\n\nroot.Sprites[\"DefaultTrophyIcon\"] = {\n    Sheet = \"Trophy\",\n    Bounds = { X = 1, Y = 665, Width = 64, Height = 64 }\n}\n\nroot.Sprites[\"TrophyEntryCard\"] = {\n    Sheet = \"Trophy\",\n    Bounds = { X = 1, Y = 577, Width = 830, Height = 70 }\n}\n\nroot.Sprites[\"PlatinumTrophy\"] = {\n    Sheet = \"Trophy\",\n    Bounds = { X = 33, Y = 737, Width = 30, Height = 30 }\n}\n\nroot.Sprites[\"GoldTrophy\"] = {\n    Sheet = \"Trophy\",\n    Bounds = { X = 65, Y = 737, Width = 30, Height = 30 }\n}\n\nroot.Sprites[\"SilverTrophy\"] = {\n    Sheet = \"Trophy\",\n    Bounds = { X = 97, Y = 737, Width = 30, Height = 30 }\n}\n\nroot.Sprites[\"BronzeTrophy\"] = {\n    Sheet = \"Trophy\",\n    Bounds = { X = 129, Y = 737, Width = 30, Height = 30 }\n}\n\nroot.Sprites[\"TrophyEntriesBorder\"] = {\n    Sheet = \"Trophy\",\n    Bounds = { X = 1, Y = 1, Width = 958, Height = 574 }\n}\n\nroot.Sprites[\"TrophyPageCtBox\"] = {\n    Sheet = \"Trophy\",\n    Bounds = { X = 1, Y = 833, Width =  204, Height = 57 }\n}\n\nfor i = 0, 9 do\n    root.Sprites[\"PageNum\" .. i] = {\n        Sheet = \"Data\",\n        Bounds = { X = i * 36 + 371, Y = 1, Width = 34, Height = 48 }\n    };\n    root.TrophyMenu.PageNums[#root.TrophyMenu.PageNums + 1] = \"PageNum\" .. i;\nend\n\nroot.Sprites[\"PageNumSeparatorSlash\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 173, Y = 67, Width = 16, Height = 22 }\n};\n\nfor i = 0, 9 do\n    root.Sprites[\"ReachablePageNum\" .. i] = {\n        Sheet = \"Data\",\n        Bounds = { X = 191 + 18 * i, Y = 67, Width = 16, Height = 22 }\n    };\n    root.TrophyMenu.ReachablePageNums[#root.TrophyMenu.ReachablePageNums + 1] = \"ReachablePageNum\" .. i;\nend\n\n"
  },
  {
    "path": "profiles/chlcc/savedata.lua",
    "content": "root.SaveData = {\n    Type = SaveDataType.CHLCC,\n    SaveFilePath = \"games/chlcc/savedata/SYSTEM.DAT\",\n    ThumbnailFilePath = \"games/chlcc/savedata/THUMNAIL.DAT\",\n\n    ScriptMessageData = { -- Pairs of line count and offset into read flag array\n        {0x008f, 0x0000}, {0x00a5, 0x008f}, {0x01fe, 0x0134}, {0x0000, 0x0332}, {0x00d2, 0x0332}, {0x01b4, 0x0404},\n        {0x016b, 0x05b8}, {0x00d4, 0x0723}, {0x00d9, 0x07f7}, {0x01a5, 0x08d0}, {0x0106, 0x0a75}, {0x004c, 0x0b7b},\n        {0x017d, 0x0bc7}, {0x01a6, 0x0d44}, {0x0065, 0x0eea}, {0x0159, 0x0f4f}, {0x0172, 0x10a8}, {0x0098, 0x121a},\n        {0x020c, 0x12b2}, {0x01ec, 0x14be}, {0x0180, 0x16aa}, {0x0176, 0x182a}, {0x0192, 0x19a0}, {0x01d2, 0x1b32},\n        {0x00d3, 0x1d04}, {0x00ed, 0x1dd7}, {0x008d, 0x1ec4}, {0x012b, 0x1f51}, {0x00d5, 0x207c}, {0x00d1, 0x2151},\n        {0x0153, 0x2222}, {0x00f8, 0x2375}, {0x004f, 0x246d}, {0x002f, 0x24bc}, {0x0113, 0x24eb}, {0x00b0, 0x25fe},\n        {0x0073, 0x26ae}, {0x012d, 0x2721}, {0x017a, 0x284e}, {0x00c6, 0x29c8}, {0x008f, 0x2a8e}, {0x00f7, 0x2b1d},\n        {0x004a, 0x2c14}, {0x006b, 0x2c5e}, {0x00e0, 0x2cc9}, {0x0183, 0x2da9}, {0x01d3, 0x2f2c}, {0x00c4, 0x30ff},\n        {0x0180, 0x31c3}, {0x00f0, 0x3343}, {0x008e, 0x3433}, {0x002e, 0x34c1}, {0x003a, 0x34ef}, {0x00eb, 0x3529},\n        {0x010c, 0x3614}, {0x008a, 0x3720}, {0x0122, 0x37aa}, {0x016a, 0x38cc}, {0x00de, 0x3a36}, {0x00b6, 0x3b14},\n        {0x003f, 0x3bca}, {0x003f, 0x3c09}, {0x01f7, 0x3c48}, {0x015e, 0x3e3f}, {0x0146, 0x3f9d}, {0x0168, 0x40e3},\n        {0x01a2, 0x424b}, {0x0052, 0x43ed}, {0x0057, 0x443f}, {0x019f, 0x4496}, {0x0062, 0x4635}, {0x0061, 0x4697},\n        {0x0104, 0x46f8}, {0x0104, 0x47fc}, {0x0120, 0x4900}, {0x011d, 0x4a20}, {0x00b1, 0x4b3d}, {0x00b2, 0x4bee},\n        {0x008d, 0x4ca0}, {0x0090, 0x4d2d}, {0x00c6, 0x4dbd}, {0x00ce, 0x4e83}, {0x0054, 0x4f51}, {0x0053, 0x4fa5},\n        {0x0041, 0x4ff8}, {0x0046, 0x5039}, {0x002c, 0x507f}, {0x000c, 0x50ab}, {0x0027, 0x50b7}, {0x0001, 0x50de},\n        {0x0001, 0x50df}, {0x0000, 0x50e0}, {0x0000, 0x50e0}, {0x0362, 0x50e0},\n    },\n\n    -- IDs in the save data EVflag array for all event CGs (+variants)\n    AlbumEvData = {\n        {41},\n        {42},\n        {43, 44},\n        {45, 46},\n        {47},\n        {49},\n        {50},\n        {51, 52, 53},\n        {29},\n        {30},\n        {31, 32},\n        {34},\n        {35},\n        {36, 37, 38},\n        {39},\n        {40},\n        {65, 66},\n        {67},\n        {68, 69},\n        {70},\n        {71},\n        {74},\n        {76, 77},\n        {80, 81},\n        {73, 72},\n        {75},\n        {78, 79},\n        {82, 83},\n        {0},\n        {1},\n        {2, 3, 4},\n        {5, 6},\n        {7},\n        {8, 9},\n        {11},\n        {12, 13},\n        {54},\n        {55},\n        {56},\n        {57, 58},\n        {59},\n        {60, 61, 62},\n        {63},\n        {64},\n        {14, 15, 16, 17},\n        {18},\n        {19, 20},\n        {21, 22},\n        {23, 24},\n        {25, 26},\n        {27},\n        {28},\n        {84, 85, 86},\n        {87},\n        {88},\n        {88},\n        {88},\n        {88},\n        {88},\n        {88},\n        {88},\n        {88},\n        {88}\n    },\n\n    -- CG IDs for all the event CGs (+variants) that show up in the Album\n    AlbumData = {\n        {{248}},\n        {{249}},\n        {{396, 395}, {398, 397}},\n        {{255}, {256}},\n        {{257}},\n        {{258}},\n        {{262, 263, 0x51D3}},\n        {{264}, {265}, {266}},\n        {{232, 231}},\n        {{233}},\n        {{392, 391}, {394, 393}},\n        {{239}},\n        {{241, 240}},\n        {{242}, {243}, {244}},\n        {{245}},\n        {{246, 247, 0x51D3}},\n        {{402, 401}, {404, 403}},\n        {{283}},\n        {{284}, {285}},\n        {{286}},\n        {{287}},\n        {{291, 290}},\n        {{294}, {295}},\n        {{298}, {299}},\n        {{289}, {288}},\n        {{293, 292}},\n        {{296}, {297}},\n        {{300}, {301}},\n        {{195}},\n        {{196}},\n        {{197}, {198}, {199}},\n        {{200}, {201}},\n        {{202}},\n        {{203}, {204}},\n        {{387, 388, 0x51D3}},\n        {{210}, {211}},\n        {{267}},\n        {{268}},\n        {{269}},\n        {{270}, {271}},\n        {{273, 272}},\n        {{420, 421, 0x51D3}, {422, 423, 0x51D3}, {424, 425, 0x51D3}},\n        {{277}},\n        {{278}},\n        {{212}, {213}, {214}, {215}},\n        {{389, 390, 0x51D3}},\n        {{219, 218}, {221, 220}},\n        {{222}, {223}},\n        {{225, 224}, {226}},\n        {{227}, {228}},\n        {{229}},\n        {{230}},\n        {{302}, {303}, {304}},\n        {{305}},\n        {{194}},\n        {{406, 405}},\n        {{408, 407}},\n        {{410, 409}},\n        {{412, 411}},\n        {{414, 413}},\n        {{416, 415}},\n        {{418, 417}},\n        {{419}}\n    }\n};"
  },
  {
    "path": "profiles/chlcc/scriptinput.lua",
    "content": "--[[\nlocal AllDirDirect = root.PADinput.PAD1UP | root.PADinput.PAD1DOWN | root.PADinput.PAD1LEFT | root.PADinput.PAD1RIGHT;\nlocal AllDirLS = root.PADinput.PAD1UP_LS | root.PADinput.PAD1DOWN_LS | root.PADinput.PAD1LEFT_LS | root.PADinput.PAD1RIGHT_LS;\n\nroot.Input.PADcustomA = {\n  root.PADinput.PAD1UP,\n  root.PADinput.PAD1DOWN,\n  root.PADinput.PAD1LEFT,\n  root.PADinput.PAD1RIGHT,\n  root.PADinput.PAD1A | root.PADinput.PAD1START,\n  root.PADinput.PAD1A,\n  root.PADinput.PAD1B,\n  root.PADinput.PAD1R1,\n  root.PADinput.PAD1START,\n  root.PADinput.PAD1SELECT,\n  root.PADinput.PAD1X,\n  0,\n  root.PADinput.PAD1Y,\n  0,\n  root.PADinput.PAD1A | root.PADinput.PAD1B | root.PADinput.PAD1START,\n  root.PADinput.PAD1A,\n  root.PADinput.PAD1B,\n  root.PADinput.PAD1X,\n  root.PADinput.PAD1Y,\n  root.PADinput.PAD1R1,\n  root.PADinput.PAD1START,\n  root.PADinput.PAD1L1,\n  root.PADinput.PAD1LEFT_RS | root.PADinput.PAD1L2,\n  root.PADinput.PAD1RIGHT_RS | root.PADinput.PAD1R2,\n  AllDirLS | AllDirDirect | root.PADinput.PAD1A,\n  root.PADinput.PAD1R1,\n  root.PADinput.PAD1L1,\n  root.PADinput.PAD1A,\n  root.PADinput.PAD1B,\n  root.PADinput.PAD1UP_LS | root.PADinput.PAD1UP_DIRECT,\n  root.PADinput.PAD1DOWN_LS | root.PADinput.PAD1DOWN_DIRECT,\n  root.PADinput.PAD1LEFT_LS | root.PADinput.PAD1LEFT_DIRECT,\n  root.PADinput.PAD1RIGHT_LS | root.PADinput.PAD1RIGHT_DIRECT,\n  root.PADinput.PAD1UP_RS,\n  root.PADinput.PAD1DOWN_RS,\n  root.PADinput.PAD1LEFT_RS,\n  root.PADinput.PAD1RIGHT_RS,\n}\n]]--"
  },
  {
    "path": "profiles/chlcc/scriptvars.lua",
    "content": "local sv = root.ScriptVars;\n\nsv.SW_MESNAMEID0 = 0; -- Disable\nsv.SW_TOTALPLAYTIME = 990;\nsv.SW_GAMESTATE = 1012;\nsv.SW_TITLEDISPCT = 1014;\nsv.SW_TITLEMASKALPHA = 1033;\nsv.SW_TITLEMASKCOLOR = 1034;\nsv.SW_SYSSEL = 1028;\nsv.SW_TITLE_PRI = 1037;\nsv.SW_SYSTEMMENUCHG = 1040;\nsv.SW_SYSTEMMENUALPHA = 1041;\nsv.SW_SYSMENUCT = 1042;\nsv.SW_FILEALPHA = 1043;\nsv.SW_MOVIEMODE_ALPHA = 1100;\nsv.SW_MOVIEMODE_CUR = 1101;\nsv.SW_ALBUM_ALPHA = 1105;\nsv.SW_LINEID = 1145;\nsv.SW_SCRIPTID = 1146;\nsv.SW_MASK1ALPHA_OFS = 1586;\nsv.SW_MASK2ALPHA_OFS = 1587;\nsv.SW_MASK3ALPHA_OFS = 1588;\nsv.SW_MAINTHDP = 1704;\nsv.SW_SAVEERRORCODE = 1733;\nsv.SW_SYSMENUCNO = 1738;\nsv.SW_TITLECUR1 = 1741;\nsv.SW_TITLECUR2 = 1742;\nsv.SW_SVSENO = 2001;\nsv.SW_SVBGMNO = 2005;\nsv.SW_SVSCRNO1 = 2006;\nsv.SW_SVSCRNO2 = 2007;\nsv.SW_SVSCRNO3 = 2008;\nsv.SW_SVSCRNO4 = 2009;\nsv.SW_SVBGNO1 = 2010;\nsv.SW_SVCHANO1 = 2018;\nsv.SW_TITLE = 2300;\nsv.SW_PLAYTIME = 2304;\nsv.SW_MESWINDOW_COLOR = 2308;\nsv.SW_BGMREQNO = 2310;\nsv.SW_SEREQNO = 2311;\nsv.SW_BGMVOL = 2314;\nsv.SW_SEVOL = 2315;\nsv.SW_SCRIPTNO0 = 2320;\nsv.SW_SCRIPTNO1 = 2321;\nsv.SW_SCRIPTNO2 = 2322;\nsv.SW_SCRIPTNO3 = 2323;\nsv.SW_SCRIPTNO4 = 2324;\nsv.SW_SCRIPTNO5 = 2325;\nsv.SW_SCRIPTNO6 = 2326;\nsv.SW_SCRIPTNO7 = 2327;\nsv.SW_MASK1COLOR = 2329;\nsv.SW_MASK1ALPHA = 2330;\nsv.SW_MASK1PRI = 2331;\nsv.SW_MASK1POSX = 2332;\nsv.SW_MASK1POSY = 2333;\nsv.SW_MASK1SIZEX = 2334;\nsv.SW_MASK1SIZEY = 2335;\nsv.SW_MOSAIC_PRI = 2350;\nsv.SW_MOSAIC = 2353;\nsv.SW_MESWIN0TYPE = 3172;\nsv.SW_MESMODE0 = 3173;\nsv.SW_CHA1POSX = 2600;\nsv.SW_CHA1POSY = 2601;\nsv.SW_CHA1ROTX = 2602;\nsv.SW_CHA1ROTY = 2603;\nsv.SW_CHA1ROTZ = 2604;\nsv.SW_CHA1SIZEX = 2605;\nsv.SW_CHA1SIZEY = 2606;\nsv.SW_CHA1ALPHA = 2607;\nsv.SW_CHA1FILTER = 2608;\nsv.SW_CHA1NO = 2609;\nsv.SW_CHA1PRI = 2610;\nsv.SW_CHA1PRI2 = 2610;\nsv.SW_CHA1POSE = 2611;\nsv.SW_CHA1FACE = 2612;\nsv.SW_CHA1EX = 2613;\nsv.SW_CHA1FADECT = 2614;\nsv.SW_CHA1FADETYPE = 2615;\nsv.SW_CHA1ANIME_EYE = 2618;\nsv.SW_CHA1ANIME_MOUTH = 2619;\nsv.SW_CHA1SURF = 1850;\nsv.SW_BG1POSX = 2400;\nsv.SW_BG1POSY = 2401;\nsv.SW_BG1SX = 2402;\nsv.SW_BG1SY = 2403;\nsv.SW_BG1SIZE = 2404;\nsv.SW_BG1LX = 2405;\nsv.SW_BG1LY = 2406;\nsv.SW_BG1NO = 2407;\nsv.SW_BG1PRI = 2408;\nsv.SW_BG1PRI2 = 2408; -- Effectively disable\nsv.SW_BG1DISPMODE = 2409;\nsv.SW_BG1FADECT = 2410;\nsv.SW_BG1FADETYPE = 2411;\nsv.SW_BG1ROTZ = 2412;\nsv.SW_BG1ALPHA = 2413;\nsv.SW_BG1MASKNO = 2414;\nsv.SW_BG1MASKFADERANGE = 2415;\nsv.SW_BG1FILTER = 0; -- CHLCC has no bg filter\nsv.SW_BG1POSX_OFS = 1200;\nsv.SW_BG1POSY_OFS = 1201;\nsv.SW_BG1SX_OFS = 1202;\nsv.SW_BG1SY_OFS = 1203;\nsv.SW_BG1SIZE_OFS = 1204;\nsv.SW_BG1LX_OFS = 1205;\nsv.SW_BG1LY_OFS = 1206;\nsv.SW_BG1ROTZ_OFS = 0; -- CHLCC has no RotZ offset\nsv.SW_BG1ALPHA_OFS = 1208;\nsv.SW_CHA1POSX_OFS = 1300;\nsv.SW_CHA1POSY_OFS = 1301;\nsv.SW_CHA1ROTX_OFS = 1302;\nsv.SW_CHA1ROTY_OFS = 1303;\nsv.SW_CHA1ROTZ_OFS = 1304;\nsv.SW_CHA1SIZEX_OFS = 1305;\nsv.SW_CHA1SIZEY_OFS = 1306;\nsv.SW_CHA1ALPHA_OFS = 1307;\nsv.SW_INTROVOICE = 1717\nsv.SW_BG1SURF = 1800;\nsv.SW_SAVEFILESTATUS = 2122;\nsv.SW_SAVEFILENO = 2123;\nsv.SW_FEATHERING_PRI=2351;\nsv.SW_FEATHERING2_PRI=2352;\nsv.SW_FEATHERING=2354;\nsv.SW_FEATHERING2=2355;\nsv.SW_BGLINK = 2580;\nsv.SW_BGLINK2 = 2581;\nsv.SW_BG1CLIP_X = 2582;\nsv.SW_BG1CLIP_Y = 2583;\nsv.SW_MASK_CAPTURE_PRI = 3267;\nsv.SW_EFF_CAP_PRI = 3268;\nsv.SW_EFF_CAP_BUF = 3269;\nsv.SW_EFF_CAP_PRI2 = 3270;\nsv.SW_EFF_CAP_BUF2 = 3271;\nsv.SW_MOVIEPRI = 3410;\nsv.SW_MOVIEPRI2 = 3400;\nsv.SW_EFF_WAVE_PRI = 3400; -- Same SW variable is reused as EFF_WAVE_PRI and SW_MOVIEPRI2 in older version of the engine\nsv.SW_MOVIEALPHA = 3411;\nsv.SW_MOVIE_PLAYNO = 3412;\nsv.SW_MOVIE_PLAYMODE = 3413;\nsv.SW_MOVIE_PLAYVIEW = 3414;\nsv.SW_MOVIE_LOADNO = 3415;\nsv.SW_MOVIEALPHA2 = 3421;\nsv.SW_DELUSION_STATE = 3430;\nsv.SW_DELUSION_POS_TXT_IDX = 3431;\nsv.SW_DELUSION_NEG_TXT_IDX = 3432;\nsv.SW_MONITOR_SCANLINE_PRI = 3433;\nsv.SW_MONITOR_SCANLINE_ENABLED = 3434;\nsv.SW_EYECATCH_COUNT = 3435;\nsv.SW_EYECATCH_BUF = 3436;\nsv.SW_EYECATCH_PRI = 3437;\nsv.SW_BUTTERFLY_COUNT = 3438;\nsv.SW_BUTTERFLY_PRI = 3439;\nsv.SW_BUTTERFLY_ALPHA = 3440;\nsv.SW_BUBBLES_COUNT = 3441;\nsv.SW_BUBBLES_PRI = 3442;\nsv.SW_BUBBLES_ALPHA = 3443;\n--FlagWork\nsv.SF_CLR_RIMI = 801;\nsv.SF_CLR_NANAMI = 802;\nsv.SF_CLR_YUA = 803;\nsv.SF_CLR_MIA = 804;\nsv.SF_CLR_AYASE = 805;\nsv.SF_CLR_SENA = 806;\nsv.SF_CLR_KOZUE = 807;\nsv.SF_CLR_SEIRA = 808;\nsv.SF_BACKLOGMENU = 1350;\nsv.SF_SAVEMENU = 1351;\nsv.SF_OPTIONMENU = 1352;\nsv.SF_SYSTEMMENU = 1353;\nsv.SF_TIPSMENU = 1354;\nsv.SF_ALBUMMENU = 1355;\nsv.SF_CLEARLISTMENU = 1356;\nsv.SF_SOUNDMENU = 1357;\nsv.SF_MOVIEMENU = 1358;\nsv.SF_ACHIEVEMENTMENU = 1359;\nsv.SF_MOVIEPLAY = 1819;\nsv.SF_MOVIECANCEL = 1822;\nsv.SF_EXTRA_ENA = 860;\nsv.SF_CAP1DISP = sv.SF_BG1DISP;\nsv.SF_MOVIEFL = 2485;\nsv.SF_DELUSIONACTIVE = 2510;\nsv.SF_DELUSION_UI_ANIM_WAIT = 2511;\nsv.SF_DELUSION_UI_ANIMSWITCH_WAIT = 2512;\n"
  },
  {
    "path": "profiles/chlcc/sprites.lua",
    "content": "root.SpriteSheets = {\n    [\"CG\"] = {\n        Path = { Mount = \"system\", Id = 0 },\n        DesignWidth = 2048,\n        DesignHeight = 1024\n    },\n    [\"AlbumThumbnailSheet\"] = {\n        Path = { Mount = \"system\", Id = 1 },\n        DesignWidth = 2048,\n        DesignHeight = 1024\n    },\n    [\"AlbumThumbnailSheet2\"] = {\n        Path = { Mount = \"system\", Id = 2 },\n        DesignWidth = 2048,\n        DesignHeight = 1024\n    },\n    [\"Backlog\"] = {\n        Path = { Mount = \"system\", Id = 3 },\n        DesignWidth = 2048,\n        DesignHeight = 1024\n    },\n    [\"ClearList\"] = {\n        Path = { Mount = \"system\", Id = 4 },\n        DesignWidth = 2048,\n        DesignHeight = 1024\n    },\n    [\"Data\"] = {\n        Path = { Mount = \"system\", Id = 5 },\n        DesignWidth = 2048,\n        DesignHeight = 1024\n    },\n    [\"Highlights\"] = {\n        Path = { Mount = \"system\", Id = 7 },\n        DesignWidth = 2048,\n        DesignHeight = 1024\n    },\n    [\"Menu\"] = {\n        Path = { Mount = \"system\", Id = 8 },\n        DesignWidth = 2048,\n        DesignHeight = 1024\n    },\n    [\"DelusionUnderlayer\"] = {\n        Path = { Mount = \"system\", Id = 9 },\n        DesignWidth = 1600,\n        DesignHeight = 720\n    },\n    [\"DelusionMask\"] = {\n        Path = { Mount = \"system\", Id = 10 },\n        DesignWidth = 1024,\n        DesignHeight = 1024\n    },\n    [\"Tips\"] = {\n        Path = { Mount = \"system\", Id = 11 },\n        DesignWidth = 2048,\n        DesignHeight = 1024\n    },\n    [\"Font\"] = {\n        Path = { Mount = \"system\", Id = 12 },\n        DesignWidth = 2048,\n        DesignHeight = 1184\n    },\n    [\"Main\"] = {\n        Path = { Mount = \"system\", Id = 13 },\n        DesignWidth = 2048,\n        DesignHeight = 1024,\n    },\n    [\"Movie\"] = {\n        Path = { Mount = \"system\", Id = 14 },\n        DesignWidth = 2048,\n        DesignHeight = 1024\n    },\n    [\"Sound\"] = {\n        Path = { Mount = \"system\", Id = 15 },\n        DesignWidth = 2048,\n        DesignHeight = 1024\n    },\n    [\"Options\"] = {\n        Path = { Mount = \"system\", Id = 16 },\n        DesignWidth = 2048,\n        DesignHeight = 1024\n    },\n    [\"Save\"] = {\n        Path = { Mount = \"system\", Id = 18 },\n        DesignWidth = 2048,\n        DesignHeight = 1024\n    },\n    [\"TitleBg1\"] = {\n        Path = { Mount = \"system\", Id = 19 },\n        DesignWidth = 1280,\n        DesignHeight = 720\n    },\n    [\"TitleBg2\"] = {\n        Path = { Mount = \"system\", Id = 20 },\n        DesignWidth = 1280,\n        DesignHeight = 720\n    },\n    [\"Title\"] = {\n        Path = { Mount = \"system\", Id = 21 },\n        DesignWidth = 2048,\n        DesignHeight = 1024\n    },\n    [\"DelusionText\"] = {\n        Path = { Mount = \"system\", Id = 23 },\n        DesignWidth = 1280,\n        DesignHeight = 1344\n    },\n    [\"Trophy\"] = {\n        Path = { Mount = \"system\", Id = 24 },\n        DesignWidth = 1120,\n        DesignHeight = 896\n    },\n};\n\nroot.Sprites = {};\n\nif root.Language ~= \"Japanese\" then\n    root.SpriteSheets.DelusionText.DesignWidth = 2730;\n    root.SpriteSheets.DelusionText.DesignHeight = 1408;\nend\n"
  },
  {
    "path": "profiles/chlcc/tipssystem.lua",
    "content": "local SortCategoryMappingJP = {\n    0x0,  0x0,  0x1,  0x1,\n    0x2,  0x2,  0x3,  0x3,\n    0x4,  0x4,  0x5,  0x5,\n    0x6,  0x6,  0x7,  0x7,\n    0x8,  0x8,  0x9,  0x9,\n    0xA,  0xA,  0xB,  0xB,\n    0xC,  0xC,  0xD,  0xD,\n    0xE,  0xE,  0xF,  0xF,\n    0x10, 0x10, 0x11, 0x11,\n    0x11, 0x12, 0x12, 0x13,\n    0x13, 0x14, 0x15, 0x16,\n    0x17, 0x18, 0x19, 0x19,\n    0x19, 0x1A, 0x1A, 0x1A,\n    0x1B, 0x1B, 0x1B, 0x1C,\n    0x1C, 0x1C, 0x1D, 0x1D,\n    0x1D, 0x1E, 0x1F, 0x20,\n    0x21, 0x22, 0x23, 0x23,\n    0x24, 0x24, 0x25, 0x25,\n    0x26, 0x27, 0x28, 0x29,\n    0x2A, 0x2B, 0x2C, 0x2D,\n    0x2E, 0x2E, 0x2F, 0x2F,\n    0x30, 0x30, 0x31, 0x31,\n    0x32, 0x32, 0x33, 0x33,\n    0x34, 0x34, 0x35, 0x35,\n    0x36, 0x36, 0x37, 0x37,\n    0x38, 0x38, 0x39, 0x39,\n    0x3A, 0x3A, 0x3B, 0x3B,\n    0x3C, 0x3C, 0x3D, 0x3D,\n    0x3E, 0x3E, 0x3F, 0x3F,\n    0x40, 0x40, 0x41, 0x41,\n    0x42, 0x42, 0x43, 0x43,\n    0x44, 0x44, 0x45, 0x45,\n    0x46, 0x46, 0x47, 0x47,\n    0x48, 0x49, 0x4A, 0x4B,\n    0x4C, 0x4D, 0x4E, 0x4F,\n    0x50, 0x51, 0x52, 0x53\n};\nlocal SortCategoryMappingEN = SortCategoryMappingJP; -- Todo\n\nroot.TipsSystem = {\n    Type = TipsSystemType.CHLCC,\n    MaxTipsCount = 200,\n    SortCategoryMapping = (root.Language == \"Japanese\") and SortCategoryMappingJP or SortCategoryMappingEN,\n};"
  },
  {
    "path": "profiles/chlcc/vfs.lua",
    "content": "root.Vfs = {\n    Mounts = {\n        [\"bg\"] = {\"games/chlcc/gamedata/BG.CPK\"},\n        [\"bgm\"] = {\"games/chlcc/gamedata/BGM.CPK\"},\n        [\"chara\"] = {\"games/chlcc/gamedata/CHARA.CPK\"},\n        [\"mask\"] = {\"games/chlcc/gamedata/MASK.CPK\"},\n        [\"movie\"] = {\"games/chlcc/gamedata/MOVIE.CPK\"},\n        [\"script\"] = {\"games/chlcc/gamedata/SCRIPT.CPK\"},\n        [\"se\"] = {\"games/chlcc/gamedata/SE.CPK\"},\n        [\"system\"] = {\"games/chlcc/gamedata/SYSTEM.CPK\"},\n        [\"voice\"] = {\"games/chlcc/gamedata/VOICE.CPK\"}\n    }\n};"
  },
  {
    "path": "profiles/chlcc/waveeffects.lua",
    "content": "root.WaveEffects = {\n    WaveMaxCount = 10;\n    BGWaveGridSize = { X = 160, Y = 90 }\n};\n"
  },
  {
    "path": "profiles/chn/charset.lua",
    "content": "root.Charset = {\n    Flags = {}\n};\n\nfor i = 0, (64 * 117) - 1 do root.Charset.Flags[i] = 0; end\n\nlocal spaces = {[0]=0, 63};\nfor i = 0, #spaces do\n    root.Charset.Flags[spaces[i]] = root.Charset.Flags[spaces[i]] | CharacterTypeFlags.Space;\nend\n\n-- 、 。 ． ， ？ ！ 〜 ” ー ） 〕 ］ ｝ 〉 》 」 』 】☆ ★ ♪ 々 ぁ ぃ ぅ ぇ\n-- ぉ っ ゃ ゅ ょ ァ ィ ゥ ェ ォ ッ ャ ュ ョ –\nlocal wordEndingPuncts = {\n    [0]=0x00BE, 0x00BF, 0x00C1,\n    0x00C0, 0x00C4, 0x00C5,\n    0x00E4, 0x00CB, 0x00E5,\n    0x00CD, 0x00CF, 0x00D1,\n    0x00D3, 0x00D5, 0x00D7,\n    0x00D9, 0x00DB, 0x00DD,\n    0x01A5, 0x01A6, 0x00E6,\n    0x0187, 0x00E8, 0x00E9,\n    0x00EA, 0x00EB, 0x00EC,\n    0x00ED, 0x00EE, 0x00EF,\n    0x00F0, 0x00F2, 0x00F3,\n    0x00F4, 0x00F5, 0x00F6,\n    0x00F7, 0x00F8, 0x00F9,\n    0x00FA, 0x0113\n};\nfor i = 0, #wordEndingPuncts do\n    root.Charset.Flags[wordEndingPuncts[i]] = root.Charset.Flags[wordEndingPuncts[i]] | CharacterTypeFlags.WordEndingPunct;\nend\n\n-- space(63) space(0) “ （ 〔 ［ ｛ 〈 《 「\nlocal wordStartingPuncts = {\n    [0]=63, 0, 0x00CA,\n    0x00CC, 0x00CE, 0x00D0,\n    0x00D2, 0x00D4, 0x00D6,\n    0x00D8, 0x00DA, 0x00DC\n};\nfor i = 0, #wordStartingPuncts do\n    root.Charset.Flags[wordStartingPuncts[i]] = root.Charset.Flags[wordStartingPuncts[i]] | CharacterTypeFlags.WordStartingPunct;\nend"
  },
  {
    "path": "profiles/chn/config.lua",
    "content": "root.Language = \"Japanese\";\nroot.ResolutionWidth = 1920;\nroot.ResolutionHeight = 1080;\nroot.Fullscreen = false;\nroot.DateFormat = DateFormatType.YMD;"
  },
  {
    "path": "profiles/chn/dialogue.lua",
    "content": "root.Sprites[\"ADVBox\"] = {\n    Sheet = \"MesBox\",\n    Bounds = { X = 0, Y = 0, Width = 1920, Height = 281 }\n};\n\nroot.Dialogue = {\n    REVBounds = { X = 0, Y = 0, Width = 960, Height = 400 },\n    REVNameFontSize = 24,\n    REVColor = 0,\n    REVNameColor = 24,\n    REVNameOffset = 34,\n    REVNameLocation = REVNameLocationType.TopLeft,\n    REVOutlineMode = 2,\n    REVNameOutlineMode = 2,\n    NVLBounds = { X = 188, Y = 128, Width = 1536, Height = 600 },\n    ADVBounds = { X = 295, Y = 828, Width = 1440, Height = 270 },\n    ADVBoxSprite = \"ADVBox\",\n    ADVBoxPos = { X = 0, Y = 781 },\n    FadeOutDuration = 0.33,\n    FadeInDuration = 0.33,\n    TextFadeInDuration = 0.33,\n    TextFadeOutDuration = 0.33,\n    DialogueBoxCurrentType = DialogueBoxType.Plain,\n    NVLBoxMaxOpacity = 0.55,\n\n    ADVNameAlignment = TextAlignment.Left,\n    ADVNameFontSize = 33,\n    ADVNamePos = { X = 173, Y = 773 },\n\n    NametagCurrentType = NametagType.None,\n\n    WaitIconSpriteAnim = \"WaitIconSpriteAnimDef\",\n    WaitIconCurrentType = WaitIconType.SpriteAnim,\n    WaitIconOffset = { X = 0, Y = 0 },\n    DialogueFont = \"Default\",\n    SetFontSizeRatio = 1000.0,\n    DefaultFontSize = 48,\n    RubyFontSize = 21,\n    RubyYOffset = -21,\n    ColorTable = {\n        {0xFFFFFF, 0x000000}, {0x5080FF, 0x000000},\n        {0xFF7080, 0x000000}, {0xFFA0F8, 0x000000},\n        {0x46FF80, 0x000000}, {0x90FFFF, 0x000000},\n        {0xFFFF70, 0x000000}, {0x80FFC0, 0x000000},\n        {0xFFB080, 0x000000}, {0xB080FF, 0x000000},\n        {0x000000, 0x808080}, {0x000000, 0x5080FF},\n        {0x000000, 0xFF7080}, {0x000000, 0xFFA0F8},\n        {0x000000, 0x268840}, {0x000000, 0x409999},\n        {0x000000, 0x888830}, {0x000000, 0x80FFC0},\n        {0x000000, 0xFFB080}, {0x000000, 0xB080FF},\n        {0xD0D0D0, 0x000000}, {0xD0D0FF, 0x000000},\n        {0xFFD0D0, 0x000000}, {0xFFD0FF, 0x000000},\n        {0xD0FFD0, 0x000000}, {0xD0FFFF, 0x000000},\n        {0xFFFFD0, 0x000000}, {0xE8FFD0, 0x000000},\n        {0xFFE8D0, 0x000000}, {0xD0E8FF, 0x000000},\n        {0xFFFFFF, 0x808080}, {0xFFFFFF, 0x5080FF},\n        {0xFFFFFF, 0xFF7080}, {0xFFFFFF, 0xFFA0F8},\n        {0xFFFFFF, 0x46FF80}, {0xFFFFFF, 0x90FFFF},\n        {0xFFFFFF, 0xFFFF70}, {0xFFFFFF, 0x80FFC0},\n        {0xFFFFFF, 0xFFB080}, {0xFFFFFF, 0xB080FF},\n        {0xFFEEEE, 0x000000}, {0xFFCCCC, 0x000000},\n        {0xFFAAAA, 0x000000}, {0xFF9999, 0x000000},\n        {0xFF8888, 0x000000}, {0xFFFF00, 0x000000},\n        {0xFEF000, 0x000000}, {0xFF7777, 0x000000},\n        {0xFF6666, 0x000000}, {0xFF5555, 0x000000},\n        {0xFF4444, 0x000000}, {0xFF3333, 0x000000},\n        {0xFF2222, 0x000000}, {0xFF0000, 0x000000},\n        {0xDD0000, 0x000000}, {0xBB0000, 0x000000},\n        {0xB00000, 0x000000}, {0xAA0000, 0x000000},\n        {0x950000, 0x000000}, {0x808080, 0x000000},\n        {0xAAAAAA, 0x000000}, {0xAAC1C9, 0x000000},\n        {0x000000, 0x000000}, {0x000000, 0x000000},\n        {0x000000, 0x000000}, {0x000000, 0x000000},\n        {0x000000, 0x000000}, {0xF80B0B, 0x000000},\n        {0xF8910B, 0x000000}, {0x33F12A, 0x000000}\n    },\n    MaxPageSize = 2000,\n    PageCount = 3,\n    ColorTagIsUint8 = false\n};\n\nMakeAnimation({\n    Name = \"WaitIconSpriteAnimDef\",\n    Sheet = \"MesBox\",\n    FirstFrameX = 0,\n    FirstFrameY = 1919,\n    FrameWidth = 64,\n    ColWidth = 64,\n    FrameHeight = 64,\n    RowHeight = 64,\n    Frames = 44,\n    Duration = 5.0,\n    Rows = 2,\n    Columns = 32,\n    PrimaryDirection = AnimationDirections.Right,\n    SecondaryDirection = AnimationDirections.Down\n});\n"
  },
  {
    "path": "profiles/chn/font-lb.lua",
    "content": "root.Fonts = {\n    [\"Default\"] = {\n        Type = FontType.LB,\n        ForegroundSheet = \"FontLBForeground\",\n        OutlineSheet = \"FontLBOutline\",\n        Columns = 64,\n        Rows = 50,\n        AdvanceWidths = \"games/rne/font-lb/widths.bin\",\n        AdvanceWidthsEmWidth = 48,\n        OutlineOffset = { X = -4, Y = -4 },\n        LineSpacing = 14,\n    }\n};\n\nroot.SpriteSheets[\"Font\"] = nil;\n\nroot.SpriteSheets[\"FontLBForeground\"] = {\n    Path = \"games/rne/font-lb/foreground.png\",\n    DesignWidth = 3072,\n    DesignHeight = 2400\n};\n\nroot.SpriteSheets[\"FontLBOutline\"] = {\n    Path = \"games/rne/font-lb/outline.png\",\n    DesignWidth = 3648,\n    DesignHeight = 2852\n};"
  },
  {
    "path": "profiles/chn/font.lua",
    "content": "root.Fonts = {\n    [\"Default\"] = {\n        Type = FontType.Basic,\n        Sheet = \"Font\",\n        Columns = 64,\n        Rows = 117,\n        AdvanceWidths = {},\n        AdvanceWidthsEmWidth = 32,\n        LineSpacing = 22,\n    }\n};\n\nlocal westernAdvanceWidths = {\n    [0]=0x20, 0x18, 0x0D, 0x18, 0x17, 0x1A, 0x16, 0x17, 0x17, 0x18, 0x17, 0x1C,\n        0x19, 0x1A, 0x1A, 0x16, 0x15, 0x1A, 0x1A, 0x08, 0x13, 0x1A, 0x17, 0x1E,\n        0x1A, 0x1C, 0x18, 0x1C, 0x1A, 0x18, 0x1C, 0x1A, 0x1C, 0x20, 0x1C, 0x1A,\n        0x1A, 0x14, 0x17, 0x16, 0x17, 0x16, 0x12, 0x16, 0x15, 0x09, 0x0F, 0x16,\n        0x08, 0x1E, 0x16, 0x16, 0x17, 0x17, 0x11, 0x14, 0x12, 0x16, 0x16, 0x1E,\n        0x16, 0x16, 0x14, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0C, 0x14, 0x08, 0x14,\n        0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,\n        0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x14, 0x11, 0x11, 0x11,\n        0x11, 0x11, 0x11, 0x11, 0x20, 0x15, 0x16, 0x16, 0x17, 0x17, 0x14, 0x11,\n        0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,\n        0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x18, 0x0D, 0x18, 0x17,\n        0x1A, 0x16, 0x17, 0x17, 0x18, 0x17, 0x1C, 0x19, 0x1A, 0x1A, 0x16, 0x15,\n        0x1A, 0x1A, 0x08, 0x13, 0x1A, 0x17, 0x1E, 0x1A, 0x1C, 0x18, 0x1C, 0x1A,\n        0x18, 0x1C, 0x1A, 0x1C, 0x20, 0x1C, 0x1A, 0x1A, 0x14, 0x17, 0x16, 0x17,\n        0x16, 0x12, 0x16, 0x15, 0x09, 0x0F, 0x16, 0x08, 0x1E, 0x16, 0x16, 0x17,\n        0x17, 0x11, 0x14, 0x12, 0x16, 0x16, 0x1E, 0x16, 0x16, 0x14, 0x14, 0x14,\n        0x14, 0x14, 0x09, 0x09, 0x14, 0x0C, 0x0B, 0x0A, 0x08, 0x08, 0x0D, 0x0C,\n        0x0C, 0x0E, 0x0D, 0x0C, 0x0C, 0x0D, 0x0C, 0x0C, 0x0F, 0x0F, 0x11, 0x11,\n        0x0D, 0x0D, 0x0E, 0x10, 0x0C, 0x0C, 0x1A, 0x1A, 0x0C, 0x0C, 0x10, 0x20,\n        0x20, 0x20, 0x1C, 0x20, 0x1A, 0x19, 0x16, 0x1A, 0x19, 0x18, 0x19, 0x19,\n        0x18, 0x19, 0x19, 0x18, 0x18, 0x1A, 0x1A, 0x19, 0x1A, 0x1A, 0x15, 0x16,\n        0x17, 0x1A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0C, 0x20, 0x20, 0x20, 0x20,\n        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20\n};\n\nfor i = 0, (64 * 117) - 1 do\n    if i <= #westernAdvanceWidths then\n        root.Fonts[\"Default\"].AdvanceWidths[i] = westernAdvanceWidths[i];\n    else\n        root.Fonts[\"Default\"].AdvanceWidths[i] = root.Fonts[\"Default\"].AdvanceWidthsEmWidth;\n    end\nend"
  },
  {
    "path": "profiles/chn/game.lua",
    "content": "root.ActiveRenderer = RendererType.OpenGL;\n\nroot.LayerCount = 100;\nroot.GameFeatures = GameFeature.Sc3VirtualMachine | GameFeature.Renderer2D | GameFeature.Input | GameFeature.Audio | GameFeature.Video;\nroot.DesignWidth = 1920;\nroot.DesignHeight = 1080;\n\nroot.WindowName = \"CHAOS;HEAD NOAH\";\nroot.WindowIconPath = \"games/chn/icondata/icon.png\";\nroot.CursorArrowPath = \"games/chn/icondata/cursor_arrow.png\";\nroot.CursorPointerPath = \"games/chn/icondata/cursor_pointer.png\";\n\nroot.CharaIsMvl = true;\n\nroot.ScreenCaptureCount = 2;\n\nroot.Vm = {\n    StartScript = 1,\n    StartScriptBuffer = 0,\n    GameInstructionSet = InstructionSet.CHN,\n    UseReturnIds = true,\n    UseMsbStrings = true,\n    UseSeparateMsbArchive = true,\n\n    ScrWorkChaStructSize = 40,\n    ScrWorkChaOffsetStructSize = 10,\n    ScrWorkBgStructSize = 40,\n    ScrWorkBgOffsetStructSize = 10,\n    ScrWorkCaptureStructSize = 20,\n    ScrWorkCaptureOffsetStructSize = 10,\n    ScrWorkCaptureEffectInfoStructSize = 3,\n    ScrWorkBgEffStructSize = 30,\n    ScrWorkBgEffOffsetStructSize = 20,\n\n    MaxLinkedBgBuffers = 2,\n};\n\ninclude('common/scriptinput.lua');\ninclude('common/scriptvars.lua');\ninclude('chn/config.lua');\ninclude('chn/scriptvars.lua');\ninclude('chn/savedata.lua');\ninclude('chn/tipssystem.lua');\ninclude('chn/vfs.lua');\ninclude('chn/sprites.lua');\ninclude('common/animation.lua');\ninclude('chn/charset.lua');\ninclude('chn/font.lua');\n--include('chn/font-lb.lua');\ninclude('chn/dialogue.lua');\ninclude('chn/hud/saveicon.lua');\ninclude('chn/hud/loadingdisplay.lua');\ninclude('chn/hud/datedisplay.lua');\ninclude('chn/hud/titlemenu.lua');\n--include('chn/hud/systemmenu.lua');\ninclude('chn/hud/backlogmenu.lua');\ninclude('chn/hud/sysmesboxdisplay.lua');\ninclude('chn/hud/selectiondisplay.lua');\ninclude('chn/hud/tipsmenu.lua');\ninclude('chn/hud/tipsnotification.lua');"
  },
  {
    "path": "profiles/chn/hud/backlogmenu.lua",
    "content": "root.BacklogMenu = {\n    Type = BacklogMenuType.None,\n    DrawType = DrawComponentType.ExtrasScenes,\n    BacklogBackgroundSprite = \"BacklogBackground\",\n    EntryHighlightSprite = \"EntryHighlight\",\n    EntryHighlightPadding = 0.0,\n    VoiceIconSprite = \"VoiceIcon\",\n    ScrollbarTrackSprite = \"ScrollbarTrack\",\n    ScrollbarThumbSprite = \"ScrollbarThumb\",\n    ScrollbarPosition = { X = 1165, Y = 98 },\n    EntriesStart = { X = 163, Y = 85 },\n    RenderingBounds = { X = 0, Y = 75, Width = 1280, Height = 615 },\n    EntryYPadding = 22,\n    FadeInDuration = 0.2,\n    FadeOutDuration = 0.2\n};\n\nroot.Sprites[\"BacklogBackground\"] = {\n    Sheet = \"Backlog\",\n    Bounds = { X = 0, Y = 0, Width = 1280, Height = 720 },\n};\n\nroot.Sprites[\"VoiceIcon\"] = {\n    Sheet = \"Backlog\",\n    Bounds = { X = 1281, Y = 1, Width = 30, Height = 30 },\n};\n\nroot.Sprites[\"EntryHighlight\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 65, Y = 1, Width = 94, Height = 30 }\n};\n\nroot.Sprites[\"ScrollbarThumb\"] = {\n    Sheet = \"Backlog\",\n    Bounds = { X = 1281, Y = 32, Width = 30, Height = 30 },\n};\n\nroot.Sprites[\"ScrollbarTrack\"] = {\n    Sheet = \"Backlog\",\n    Bounds = { X = 1500, Y = 0, Width = 8, Height = 567 },\n};"
  },
  {
    "path": "profiles/chn/hud/datedisplay.lua",
    "content": "local sheet = \"Data\";\nlocal name = \"Date\";\n\nlocal yearNumFirstX = 2287.5;\nlocal yearNumFirstY = 94.5;\nlocal yearNumWidth = 37.5;\nlocal yearNum1Width = 12;\nlocal yearNumHeight = 30;\n\nlocal numFirstX = 2286;\nlocal numFirstY = 49.5;\nlocal numWidth = 60;\nlocal num1Width = 18;\nlocal numHeight = 42;\n\nlocal weekFirstX = 2677.5;\nlocal weekFirstY = 94.5;\nlocal weekSecondX = 2287.5;\nlocal weekSecondY = 127.5;\nlocal weekWidth = 109.5;\nlocal weekFriWidth = 84;\nlocal weekHeight = 30;\n\nroot.DateDisplay = {\n    Type = DateDisplayType.RNE,\n    YearNumSprites = {},\n    MonthNumSprites = {},\n    DayNumSprites = {},\n    WeekSprites = {},\n    BackgroundStartPos = { X = 1632, Y = 109.5 },\n    BackgroundEndPos = { X = 1632 - 384, Y = 109.5 },\n    DateStartX = 1750.5,\n    YearWeekY = 90,\n    MonthDayY = 78,\n    Spacing = 1.5,\n    FadeInDuration = 0.5,\n    FadeOutDuration = 0.5\n};\n\nfor i = 0, 9 do\n    local yearW;\n    if i == 1 then\n        yearW = yearNum1Width;\n    else\n        yearW = yearNumWidth;\n    end\n\n    root.Sprites[name .. \"YearNum\" .. i] = {\n        Sheet = sheet,\n        Bounds = {\n            X = yearNumFirstX,\n            Y = yearNumFirstY,\n            Width = yearW,\n            Height = yearNumHeight\n        }\n    };\n    root.DateDisplay.YearNumSprites[#root.DateDisplay.YearNumSprites + 1] = name .. \"YearNum\" .. i;\n\n    local numW\n    if i == 1 then\n        numW = num1Width;\n    else\n        numW = numWidth;\n    end\n\n    root.Sprites[name .. \"Num\" .. i] = {\n        Sheet = sheet,\n        Bounds = {\n            X = numFirstX,\n            Y = numFirstY,\n            Width = numW,\n            Height = numHeight\n        }\n    };\n    root.DateDisplay.MonthNumSprites[#root.DateDisplay.MonthNumSprites + 1] = name .. \"Num\" .. i;\n    root.DateDisplay.DayNumSprites[#root.DateDisplay.DayNumSprites + 1] = name .. \"Num\" .. i;\n\n    if i == 1 then\n        yearNumFirstX = yearNumFirstX + yearNum1Width;\n        numFirstX = numFirstX + num1Width;\n    else\n        yearNumFirstX = yearNumFirstX + yearNumWidth;\n        numFirstX = numFirstX + numWidth;\n    end\nend\n\nfor i = 0, 6 do\n    if i > 1 then\n        local weekW;\n        if i == 5 then\n            weekW = weekFriWidth;\n        else\n            weekW = weekWidth;\n        end\n\n        root.Sprites[name .. \"Week\" .. i] = {\n            Sheet = sheet,\n            Bounds = {\n                X = weekSecondX,\n                Y = weekSecondY,\n                Width = weekW,\n                Height = weekHeight\n            }\n        };\n        if i == 5 then\n            weekSecondX = weekSecondX + weekFriWidth;\n        else\n            weekSecondX = weekSecondX + weekWidth;\n         end\n    else\n        root.Sprites[name .. \"Week\" .. i] = {\n            Sheet = sheet,\n            Bounds = {\n                X = weekFirstX,\n                Y = weekFirstY,\n                Width = weekWidth,\n                Height = weekHeight\n            }\n        };\n        weekFirstX = weekFirstX + weekWidth;\n    end\n    root.DateDisplay.WeekSprites[#root.DateDisplay.WeekSprites + 1] = name .. \"Week\" .. i;\nend\n\nroot.Sprites[name .. \"MonthDaySeparator\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 2845.5,\n        Y = 49.5,\n        Width = 15,\n        Height = 42\n    }\n};\nroot.DateDisplay.MDSeparatorSprite = name .. \"MonthDaySeparator\";\n\nroot.Sprites[name .. \"DayYearSeparator\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 2637,\n        Y = 94.5,\n        Width = 12,\n        Height = 30\n    }\n};\nroot.DateDisplay.DYSeparatorSprite = name .. \"DayYearSeparator\";\n\nroot.Sprites[name .. \"OpenBracket\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 2649,\n        Y = 94.5,\n        Width = 12,\n        Height = 30\n    }\n};\nroot.DateDisplay.OpenBracketSprite = name .. \"OpenBracket\";\n\nroot.Sprites[name .. \"CloseBracket\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 2664,\n        Y = 94.5,\n        Width = 12,\n        Height = 30\n    }\n};\nroot.DateDisplay.CloseBracketSprite = name .. \"CloseBracket\";\n\nroot.Sprites[name .. \"Background\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 2287.5,\n        Y = 2,\n        Width = 675,\n        Height = 42\n    }\n};\nroot.DateDisplay.BackgroundSprite = name .. \"Background\";"
  },
  {
    "path": "profiles/chn/hud/loadingdisplay.lua",
    "content": "root.LoadingDisplay = {\n    ResourceLoadBgAnim = \"ResourceLoadingBg\",\n    SaveLoadBgAnim = \"SaveLoadingBg\",\n    LoadingIconAnim = \"LoadingDisc\",\n    LoadingTextAnim = \"LoadingText\",\n    ResourceBgPos = { X = 1074, Y = 611 },\n    SaveBgPos = { X = 1019, Y = 598 },\n    IconPos = { X = 986, Y = 608 },\n    TextPos = { X = 1025, Y = 628 },\n    FadeInDuration = 0.66,\n    FadeOutDuration = 0.33\n};\n\nMakeAnimation({\n    Name = \"ResourceLoadingBg\",\n    Sheet = \"Data\",\n    FirstFrameX = 555,\n    FirstFrameY = 544,\n    FrameWidth = 206,\n    ColWidth = 206,\n    FrameHeight = 58,\n    RowHeight = 60,\n    Frames = 8,\n    Duration = 0.8,\n    Rows = 8,\n    Columns = 1,\n    PrimaryDirection = AnimationDirections.Down\n});\n\nMakeAnimation({\n    Name = \"SaveLoadingBg\",\n    Sheet = \"Data\",\n    FirstFrameX = 388,\n    FirstFrameY = 0,\n    FrameWidth = 277,\n    ColWidth = 279,\n    FrameHeight = 81,\n    RowHeight = 83,\n    Frames = 4,\n    Duration = 0.8,\n    Rows = 2,\n    Columns = 2,\n    PrimaryDirection = AnimationDirections.Down,\n    SecondaryDirection = AnimationDirections.Right\n});\n\nMakeAnimation({\n    Name = \"LoadingDisc\",\n    Sheet = \"Data\",\n    FirstFrameX = 172,\n    FirstFrameY = 1,\n    FrameWidth = 60,\n    ColWidth = 62,\n    FrameHeight = 60,\n    RowHeight = 62,\n    Frames = 3,\n    Duration = 0.8,\n    Rows = 1,\n    Columns = 3,\n    PrimaryDirection = AnimationDirections.Right\n});\nroot.Animations[\"LoadingDisc\"].Frames[#root.Animations[\"LoadingDisc\"].Frames + 1] = \"LoadingDisc1\";\n\nMakeAnimation({\n    Name = \"LoadingText\",\n    Sheet = \"Data\",\n    FirstFrameX = 173,\n    FirstFrameY = 91,\n    FrameWidth = 214,\n    ColWidth = 214,\n    FrameHeight = 21,\n    RowHeight = 23,\n    Frames = 5,\n    Duration = 0.8,\n    Rows = 5,\n    Columns = 1,\n    PrimaryDirection = AnimationDirections.Down\n});"
  },
  {
    "path": "profiles/chn/hud/saveicon.lua",
    "content": "root.SaveIcon = {\n    ForegroundAnimation = \"SaveIcon\",\n    DefaultPosition = { X = 1729.5, Y = 34.5 },\n    BackgroundSprite = \"SaveIconBg\",\n    BackgroundOffset = { X = -10.5, Y = -6 },\n    BackgroundMaxAlpha = 0.5,\n    FadeInDuration = 0.5,\n    FadeOutDuration = 0.25\n};\n\nMakeAnimation({\n    Name = \"SaveIcon\",\n    Sheet = \"Data\",\n    FirstFrameX = 2965.5,\n    FirstFrameY = 1.5,\n    FrameWidth = 105,\n    ColWidth = 105,\n    FrameHeight = 105,\n    RowHeight = 108,\n    Frames = 8,\n    Duration = 0.4,\n    Rows = 8,\n    Columns = 1,\n    PrimaryDirection = AnimationDirections.Down\n});\n\nroot.Sprites[\"SaveIconBg\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 2158.5, Y = 1.5, Width = 126, Height = 126 }\n};"
  },
  {
    "path": "profiles/chn/hud/selectiondisplay.lua",
    "content": "root.SelectionDisplay = {\n    DrawType = DrawComponentType.Text,\n    SelectionBackgroundSprite = \"SelectionBackground\",\n    PlainSelectionFrameTopLeftSprite = \"PlainSelectionFrameTopLeft\",\n    PlainSelectionFrameTopSideSprite = \"PlainSelectionFrameTopSide\",\n    PlainSelectionFrameTopRightSprite = \"PlainSelectionFrameTopRight\",\n    PlainSelectionFrameLeftSideSprite = \"PlainSelectionFrameLeftSide\",\n    PlainSelectionFrameBottomLeftSprite = \"PlainSelectionFrameBottomLeft\",\n    PlainSelectionFrameRightSideSprite = \"PlainSelectionFrameRightSide\",\n    PlainSelectionFrameBottomRightSprite = \"PlainSelectionFrameBottomRight\",\n    PlainSelectionFrameBottomSideSprite = \"PlainSelectionFrameBottomSide\",\n    PlainSelectionFrameMiddleSprite = \"PlainSelectionFrameMiddle\",\n    SelectionHighlightSprite = \"SelectionHighlight\",\n    SelectionMaxCount = 5,\n    SelectionBackgroundX = 228,\n    SelectionBackgroundY = {277, 277, 196, 129, 95},\n    SelectionYSpacing = 81,\n    PlainSelectionYSpacing = 10,\n    FadeAnimationDurationInOut = 0.2\n};\n\nroot.Sprites[\"SelectionBackground\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 1, Y = 535, Width = 829, Height = 69 }\n};\n\nroot.Sprites[\"PlainSelectionFrameTopLeft\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 0, Y = 0, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameTopSide\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 16, Y = 0, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameTopRight\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 32, Y = 0, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameLeftSide\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 0, Y = 16, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameBottomLeft\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 0, Y = 32, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameRightSide\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 32, Y = 16, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameBottomRight\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 32, Y = 32, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameBottomSide\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 16, Y = 32, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameMiddle\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 16, Y = 16, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"SelectionHighlight\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 65, Y = 33, Width = 94, Height = 30 }\n};\n"
  },
  {
    "path": "profiles/chn/hud/sysmesboxdisplay.lua",
    "content": "local sheet = \"SysMesBox\";\nlocal name = \"SysMesBox\";\n\nroot.SysMesBoxDisplay = {\n    Type = SysMesBoxType.CC,\n    DrawType = DrawComponentType.SystemMessage,\n    SumoSealSprites = {},\n    SumoSealCenterPosX = {\n        1175, 1269, 542, 1386, 970, 813, 725, 997\n    },\n    SumoSealCenterPosY = {\n        612, 454, 520, 606, 625, 425, 584, 461\n    },\n    ButtonYesCenterPosX = 1098,\n    ButtonYesCenterPosY = 846,\n    ButtonNoCenterPosX = 1211,\n    ButtonNoCenterPosY = 736,\n    ButtonOKCenterPosX = 1170,\n    ButtonOKCenterPosY = 824,\n    TextFontSize = 32,\n    TextMiddleY = 1096,\n    TextX = 1920,\n    TextLineHeight = 32,\n    TextMarginY = 48,\n    SpriteMargin = 3,\n    AnimationSpeed = 25,\n    AnimationProgressWidgetsStartOffset = 9,\n    WidgetsAlphaMultiplier = 0.0625,\n    ButtonYesAnimationProgressEnd = 12,\n    ButtonNoDisplayStart = 4,\n    ButtonNoAnimationProgressOffset = 13,\n    ButtonYesNoScaleMultiplier = 0.0416,\n    ButtonYesNoAlphaDivider = 12,\n    ButtonOKScaleMultiplier = 0.03125,\n    ButtonScaleMax = 1.5,\n    FadeInDuration = 0.4,\n    FadeOutDuration = 0.4\n};\n\nroot.Sprites[name .. \"ButtonYes\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 640,\n        Y = 1229,\n        Width = 232,\n        Height = 298\n    }\n};\nroot.SysMesBoxDisplay.ButtonYes = name .. \"ButtonYes\";\n\nroot.Sprites[name .. \"ButtonNo\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 1109,\n        Y = 1124,\n        Width = 244,\n        Height = 200\n    }\n};\nroot.SysMesBoxDisplay.ButtonNo = name .. \"ButtonNo\";\n\nroot.Sprites[name .. \"ButtonOK\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 0,\n        Y = 1229,\n        Width = 318,\n        Height = 298\n    }\n};\nroot.SysMesBoxDisplay.ButtonOK = name .. \"ButtonOK\";\n\nroot.Sprites[name .. \"ButtonYesHighlighted\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 874,\n        Y = 1229,\n        Width = 232,\n        Height = 298\n    }\n};\nroot.SysMesBoxDisplay.ButtonYesHighlighted = name .. \"ButtonYesHighlighted\";\n\nroot.Sprites[name .. \"ButtonNoHighlighted\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 1109,\n        Y = 1326,\n        Width = 244,\n        Height = 200\n    }\n};\nroot.SysMesBoxDisplay.ButtonNoHighlighted = name .. \"ButtonNoHighlighted\";\n\nroot.Sprites[name .. \"ButtonOKHighlighted\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 320,\n        Y = 1229,\n        Width = 318,\n        Height = 298\n    }\n};\nroot.SysMesBoxDisplay.ButtonOKHighlighted = name .. \"ButtonOKHighlighted\";\n\nlocal sealX = 0;\nlocal sealY = 0;\n\nfor i = 0, 7 do\n    root.Sprites[name .. \"SumoSeal\" .. i] = {\n        Sheet = sheet,\n        Bounds = {\n            X = sealX,\n            Y = sealY,\n            Width = 478,\n            Height = 538\n        }\n    };\n\n    sealX = sealX + 480;\n    if sealX == 1920 then\n        sealX = 0;\n        sealY = sealY + 540;\n    end\n\n    root.SysMesBoxDisplay.SumoSealSprites[#root.SysMesBoxDisplay.SumoSealSprites + 1] = name .. \"SumoSeal\" .. i;\nend"
  },
  {
    "path": "profiles/chn/hud/systemmenu.lua",
    "content": "root.SystemMenu = {\n    Type = SystemMenuType.RNE,\n    DrawType = DrawComponentType.SystemMenu,\n    Background = {\n        DurationIn = 0.75,\n        DurationOut = 1.0,\n        Sprite = \"SystemMenuBackground\",\n        Seed = 0,\n        Rows = 7,\n        Columns = 12,\n        CenterY = 0.7,\n        VanishingPointX = 0.66,\n        Depth = 2,\n        MaxAngle = 3.141592653 / 2  -- pi / 2\n    },\n    ButtonBackgroundSprite = \"SystemMenuButtonBackground\",\n    SkyBackgroundSprite = \"SystemMenuSkyBackground\",\n    SkyArrowSprite = \"SystemMenuSkyArrow\",\n    SkyTextSprite = \"SystemMenuSkyText\",\n    ButtonPromptsSprite = \"SystemMenuButtonPrompts\",\n    SkyBackgroundBeginX = -80,\n    SkyBackgroundY = 0,\n    SkyTextBeginX = 287,\n    SkyTextY = 69,\n    ButtonBackgroundStartX = 1257,\n    ButtonBackgroundX = 0,\n    ButtonBackgroundY = 681,\n    ButtonBackgroundTargetWidth = 943,\n    ButtonBackgroundSprStartX = 1499,\n    MenuEntriesSprites = {},\n    MenuEntriesHighlightedSprites = {},\n    MenuEntriesX = 0,\n    MenuEntriesXSkew = 20,\n    MenuEntriesXOffset = 100,\n    MenuEntriesFirstY = 220,\n    MenuEntriesYPadding = 50,\n    MenuEntriesTargetWidth = 417,\n    SkyInStartProgress = 0.285,\n    SkyOutStartProgress = 0.715,\n    SkyMoveDurationIn = 0.415,\n    SkyMoveDurationOut = 0.415,\n    EntriesMoveDurationIn = 0.4,\n    EntriesMoveDurationOut = 0.4,\n    HighlightDurationIn = 0.15,\n    HighlightDurationOut = 0.15\n};\n\nfor i = 0, 7 do\n    root.Sprites[\"SystemMenuEntry\" .. i] = {\n        Sheet = \"Data\",\n        Bounds = {\n            X = 964,\n            Y = 296 + i * 38,\n            Width = root.SystemMenu.MenuEntriesTargetWidth,\n            Height = 26\n        },\n        BaseScale = { X = 1280 / 960, Y = 720 / 544 }\n    };\n    root.SystemMenu.MenuEntriesSprites[#root.SystemMenu.MenuEntriesSprites + 1] = \"SystemMenuEntry\" .. i;\nend\n\nfor i = 0, 7 do\n    root.Sprites[\"SystemMenuEntryHighlighted\" .. i] = {\n        Sheet = \"Data\",\n        Bounds = {\n            X = 964,\n            Y = 2 + i * 38,\n            Width = root.SystemMenu.MenuEntriesTargetWidth,\n            Height = 26\n        },\n        BaseScale = { X = 1280 / 960, Y = 720 / 544 }\n    };\n    root.SystemMenu.MenuEntriesHighlightedSprites[#root.SystemMenu.MenuEntriesHighlightedSprites + 1] = \"SystemMenuEntryHighlighted\" .. i;\nend\n\nroot.Sprites[\"SystemMenuBackground\"] = {\n    Sheet = \"Menu\",\n    Bounds = { X = 0, Y = 1088, Width = 1920, Height = 1080 },\n};\n\nroot.Sprites[\"SystemMenuButtonBackground\"] = {\n    Sheet = \"Menu\",\n    Bounds = { X = root.SystemMenu.ButtonBackgroundSprStartX, Y = 975, Width = 0, Height = 30 },\n    BaseScale = { X = 1280 / 960, Y = 720 / 544 }\n};\n\nroot.Sprites[\"SystemMenuSkyBackground\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 1383, Y = 534, Width = 418, Height = 90 },\n    BaseScale = { X = 1280 / 960, Y = 720 / 544 }\n};\n\nroot.Sprites[\"SystemMenuSkyArrow\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 1802, Y = 534, Width = 70, Height = 90 },\n    BaseScale = { X = 1280 / 960, Y = 720 / 544 }\n};\n\nroot.Sprites[\"SystemMenuSkyText\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 1875, Y = 587, Width = 119, Height = 30 },\n    BaseScale = { X = 1280 / 960, Y = 720 / 544 }\n};\n\nroot.Sprites[\"SystemMenuButtonPrompts\"] = {\n    Sheet = \"Menu\",\n    Bounds = { X = 1231, Y = 528, Width = 400, Height = 21 },\n    BaseScale = { X = 1280 / 960, Y = 720 / 544 }\n};"
  },
  {
    "path": "profiles/chn/hud/tipsmenu.lua",
    "content": "root.TipsMenu = {\n    Type = TipsMenuType.None\n};\n"
  },
  {
    "path": "profiles/chn/hud/tipsnotification.lua",
    "content": "root.TipsNotification = {\n\tType = TipsNotificationType.None,\n};\n"
  },
  {
    "path": "profiles/chn/hud/titlemenu.lua",
    "content": "root.TitleMenu = {\n    Type = TitleMenuType.CC,\n    DrawType = DrawComponentType.SystemMenu,\n    PressToStartX = 816,\n    PressToStartY = 738,\n    PressToStartAnimDurationIn = 0.7,\n    PressToStartAnimDurationOut = 0.7,\n    PressToStartAnimFastDurationIn = 0.1,\n    PressToStartAnimFastDurationOut = 0.1,\n    BackgroundX = 0,\n    BackgroundY = 0,\n    BackgroundBoundsX = 580,\n    BackgroundBoundsYNormal = 1,\n    BackgroundBoundsYTrue = 1535,\n    BackgroundBoundsWidth = 1920,\n    BackgroundBoundsHeight = 318,\n    FenceX = 0,\n    FenceY = 288,\n    FenceBoundsX = 1536,\n    FenceBoundsYNormal = 1,\n    FenceBoundsYTrue = 865,\n    FenceBoundsWidth = 1920,\n    FenceBoundsHeight = 862,\n    CopyrightX = 640,\n    CopyrightY = 947,\n    CopyrightXMoveOffset = 1536,\n    SmokeX = 0,\n    SmokeY = 580,\n    SmokeBoundsX = 20,\n    SmokeBoundsY = 1800,\n    SmokeBoundsWidth = 1920,\n    SmokeBoundsHeight = 500,\n    SmokeAnimationBoundsXOffset = 20,\n    SmokeAnimationBoundsXMax = 1919,\n    SmokeOpacityNormal = 0.25,\n    SmokeAnimationDurationIn = 32,\n    SmokeAnimationDurationOut = 32,\n    MoveLeftAnimationDurationIn = 0.7,\n    MoveLeftAnimationDurationOut = 0.7,\n    MenuSprite = \"TitleMenuMenu\",\n    MenuX = 27,\n    MenuY = 26,\n    PressToStartSprite = \"TitleMenuPressToStart\",\n    BackgroundSprite = \"TitleMenuBackground\",\n    FenceSprite = \"TitleMenuFence\",\n    CopyrightSprite = \"TitleMenuCopyright\",\n    OverlaySprite = \"TitleMenuOverlay\",\n    SmokeSprite = \"TitleMenuSmoke\",\n    ItemHighlightSprite = \"TitleMenuItemHighlight\",\n    ItemHighlightOffsetX = 174,\n    ItemHighlightOffsetY = 7,\n    ItemHighlightPointerSprite = \"TitleMenuPointerItemHighlight\",\n    ItemHighlightPointerY = 89,\n    ItemPadding = 56,\n    ItemYBase = 335,\n    SecondaryFirstItemHighlightOffsetX = 225,\n    SecondarySecondItemHighlightOffsetX = 370,\n    SecondaryThirdItemHighlightOffsetX = 590,\n    LoadSprite = \"TitleMenuLoad\",\n    LoadHighlightSprite = \"TitleMenuLoadHighlight\",\n    QuickLoadSprite = \"TitleMenuQLoad\",\n    QuickLoadHighlightSprite = \"TitleMenuQLoadHighlight\",\n    TipsSprite = \"TitleMenuTips\",\n    TipsHighlightSprite = \"TitleMenuTipsHighlight\",\n    LibrarySprite = \"TitleMenuLibrary\",\n    LibraryHighlightSprite = \"TitleMenuLibraryHighlight\",\n    EndingListSprite = \"TitleMenuEndingList\",\n    EndingListHighlightSprite = \"TitleMenuEndingListHighlight\",\n    MenuEntriesSprites = {},\n    MenuEntriesHighlightedSprites = {},\n    MenuEntriesNum = 5\n};\n\nfor i = 0, 4 do\n    root.Sprites[\"TitleMenuEntry\" .. i] = {\n        Sheet = \"TitleChip\",\n        Bounds = {\n            X = 4,\n            Y = 324 + i * root.TitleMenu.ItemPadding,\n            Width = 220,\n            Height = 30\n        }\n    };\n    root.TitleMenu.MenuEntriesSprites[#root.TitleMenu.MenuEntriesSprites + 1] = \"TitleMenuEntry\" .. i;\nend\n\nfor i = 0, 4 do\n    root.Sprites[\"TitleMenuEntry\" .. i] = {\n        Sheet = \"TitleChip\",\n        Bounds = {\n            X = 4,\n            Y = 324 + i * root.TitleMenu.ItemPadding,\n            Width = 220,\n            Height = 30\n        }\n    };\n    root.TitleMenu.MenuEntriesHighlightedSprites[#root.TitleMenu.MenuEntriesHighlightedSprites + 1] = \"TitleMenuEntry\" .. i;\nend\n\nroot.Sprites[\"TitleMenuItemHighlight\"] = {\n    Sheet = \"TitleChip\",\n    Bounds = { X = 896, Y = 64, Width = 638, Height = 50 },\n};\n\nroot.Sprites[\"TitleMenuLoad\"] = {\n    Sheet = \"TitleChip\",\n    Bounds = { X = 417, Y = 288, Width = 114, Height = 39 },\n};\n\nroot.Sprites[\"TitleMenuLoadHighlight\"] = {\n    Sheet = \"TitleChip\",\n    Bounds = { X = 414, Y = 342, Width = 116, Height = 39 },\n};\n\nroot.Sprites[\"TitleMenuQLoad\"] = {\n    Sheet = \"TitleChip\",\n    Bounds = { X = 553, Y = 288, Width = 245, Height = 39 },\n};\n\nroot.Sprites[\"TitleMenuQLoadHighlight\"] = {\n    Sheet = \"TitleChip\",\n    Bounds = { X = 550, Y = 342, Width = 249, Height = 39 },\n};\n\nroot.Sprites[\"TitleMenuTips\"] = {\n    Sheet = \"TitleChip\",\n    Bounds = { X = 416, Y = 402, Width = 95, Height = 34 },\n};\n\nroot.Sprites[\"TitleMenuTipsHighlight\"] = {\n    Sheet = \"TitleChip\",\n    Bounds = { X = 417, Y = 457, Width = 96, Height = 36 },\n};\n\nroot.Sprites[\"TitleMenuLibrary\"] = {\n    Sheet = \"TitleChip\",\n    Bounds = { X = 535, Y = 402, Width = 178, Height = 34 },\n};\n\nroot.Sprites[\"TitleMenuLibraryHighlight\"] = {\n    Sheet = \"TitleChip\",\n    Bounds = { X = 535, Y = 457, Width = 180, Height = 36 },\n};\n\nroot.Sprites[\"TitleMenuEndingList\"] = {\n    Sheet = \"TitleChip\",\n    Bounds = { X = 736, Y = 401, Width = 258, Height = 35 },\n};\n\nroot.Sprites[\"TitleMenuEndingListHighlight\"] = {\n    Sheet = \"TitleChip\",\n    Bounds = { X = 736, Y = 457, Width = 260, Height = 36 },\n};\n\nroot.Sprites[\"TitleMenuPointerItemHighlight\"] = {\n    Sheet = \"TitleChip\",\n    Bounds = { X = 805, Y = 78, Width = 56, Height = 36 },\n};\n\nroot.Sprites[\"TitleMenuPressToStart\"] = {\n    Sheet = \"TitleChip\",\n    Bounds = { X = 1508, Y = 689, Width = 304, Height = 94 },\n};\n\nroot.Sprites[\"TitleMenuFence\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 1536, Y = 1, Width = 1920, Height = 862 },\n};\n\nroot.Sprites[\"TitleMenuCopyright\"] = {\n    Sheet = \"TitleChip\",\n    Bounds = { X = 1, Y = 667, Width = 860, Height = 20 },\n};\n\nroot.Sprites[\"TitleMenuMenu\"] = {\n    Sheet = \"TitleChip\",\n    Bounds = { X = 971, Y = 705, Width = 521, Height = 236 },\n};\n\nroot.Sprites[\"TitleMenuBackground\"] = {\n    Sheet = \"TitleChip\",\n    Bounds = { X = 580, Y = 1, Width = 1920, Height = 318 },\n};\n\nroot.Sprites[\"TitleMenuOverlay\"] = {\n    Sheet = \"MenuChip\",\n    Bounds = { X = 155, Y = 142, Width = 1898, Height = 1058 },\n};\n\nroot.Sprites[\"TitleMenuSmoke\"] = {\n    Sheet = \"MenuChip\",\n    Bounds = { X = 0, Y = 1900, Width = 2000, Height = 410 },\n};"
  },
  {
    "path": "profiles/chn/savedata.lua",
    "content": "root.SaveData = {\n    Type = SaveDataType.None,\n    SaveFilePath = \"games/cc/savedata/SYSTEM.DAT\",\n};"
  },
  {
    "path": "profiles/chn/scriptvars.lua",
    "content": "local sv = root.ScriptVars;\n\nsv.SW_BG1POSX_OFS = 2400;\nsv.SW_BG1POSY_OFS = 2401;\nsv.SW_BG1SX_OFS = 2402;\nsv.SW_BG1SY_OFS = 2403;\nsv.SW_BG1SIZE_OFS = 2404;\nsv.SW_BG1LX_OFS = 2405;\nsv.SW_BG1LY_OFS = 2406;\nsv.SW_BG1ROTZ_OFS = 2407;\nsv.SW_BG1ALPHA_OFS = 2408;\nsv.SW_MASK1COLOR = 4400;\nsv.SW_MASK1ALPHA = 4401;\nsv.SW_MASK1PRI = 4402;\nsv.SW_MASK1POSX = 4403;\nsv.SW_MASK1POSY = 4404;\nsv.SW_MASK1SIZEX = 4405;\nsv.SW_MASK1SIZEY = 4406;\nsv.SW_MESMODE0 = 4363;"
  },
  {
    "path": "profiles/chn/sprites.lua",
    "content": "root.SpriteSheets = {\n    [\"Data\"] = {\n        Path = { Mount = \"system\", Id = 18 },\n        DesignWidth = 3072,\n        DesignHeight = 1536\n    },\n    [\"MesBox\"] = {\n        Path = { Mount = \"system\", Id = 30 },\n        DesignWidth = 1920,\n        DesignHeight = 600\n    },\n    [\"Font\"] = {\n        Path = { Mount = \"system\", Id = 20 },\n        DesignWidth = 3072,\n        DesignHeight = 6000\n    },\n    [\"Menu\"] = {\n        Path = { Mount = \"system\", Id = 8 },\n        DesignWidth = 2048,\n        DesignHeight = 1024\n    },\n    [\"Title\"] = {\n        Path = { Mount = \"system\", Id = 29 },\n        DesignWidth = 3456,\n        DesignHeight = 1728\n    },\n    [\"TitleChip\"] = {\n        Path = { Mount = \"system\", Id = 30 },\n        DesignWidth = 2528,\n        DesignHeight = 2048\n    },\n    [\"MenuChip\"] = {\n        Path = { Mount = \"system\", Id = 29 },\n        DesignWidth = 4096,\n        DesignHeight = 4096\n    },\n    [\"SysMesBox\"] = {\n        Path = { Mount = \"system\", Id = 61 },\n        DesignWidth = 2048,\n        DesignHeight = 2048\n    },\n    [\"Backlog\"] = {\n        Path = {Mount = \"system\", Id = 6 },\n        DesignWidth = 2048,\n        DesignHeight = 720\n    }\n};\n\nroot.Sprites = {};"
  },
  {
    "path": "profiles/chn/tipssystem.lua",
    "content": "root.TipsSystem = {\n    Type = TipsSystemType.None,\n};"
  },
  {
    "path": "profiles/chn/vfs.lua",
    "content": "root.Vfs = {\n    Mounts = {\n        [\"script\"] = {\"games/chn/gamedata/script.cls\"},\n        [\"system\"] = {\"games/chn/gamedata/system.cls\"},\n        [\"bgm\"] = {\"games/chn/gamedata/bgm.mpk\"},\n        [\"se\"] = {\"games/chn/gamedata/se.mpk\"},\n        [\"sysse\"] = {\"games/chn/gamedata/sysse.mpk\"},\n        [\"voice\"] = {\"games/chn/gamedata/voice.mpk\"},\n        [\"bg\"] = {\"games/chn/gamedata/bg.cls\"},\n        [\"chara\"] = {\"games/chn/gamedata/chara.cls\"},\n        [\"mask\"] = {\"games/chn/gamedata/mask.cls\"},\n        [\"movie\"] = {\"games/chn/gamedata/movie.cls\"},\n        [\"mes\"] = {\"games/chn/gamedata/mes00.cls\"}\n    }\n};"
  },
  {
    "path": "profiles/common/animation.lua",
    "content": "if root.Animations == nil then root.Animations = {} end\nAnimationDirections = {\n    Left = 0,\n    Right = 1,\n    Down = 2,\n    Up = 3\n}\n\nfunction DeleteAnimation(name)\n    if root.Animations[name] ~= nil then\n        for i = 1, #root.Animations[name].Frames do\n            root.Sprites[root.Animations[name].Frames[i]] = nil\n        end\n        root.Animations[name] = nil\n    end\nend\n\nfunction MakeAnimation(desc)\n    DeleteAnimation(desc.Name)\n    local animation = {\n        Duration = desc.Duration,\n        Frames = {}\n    }\n    local currentRow = 0\n    local currentCol = 0\n    local needMoveSecondary = false\n    for i = 1, desc.Frames do\n        root.Sprites[desc.Name .. i] = {\n            Sheet = desc.Sheet,\n            Bounds = {\n                X = desc.FirstFrameX + currentCol * desc.ColWidth,\n                Y = desc.FirstFrameY + currentRow * desc.RowHeight,\n                Width = desc.FrameWidth,\n                Height = desc.FrameHeight\n            }\n        }\n        if desc.BaseScale ~= nil then\n            root.Sprites[desc.Name .. i].BaseScale = desc.BaseScale\n        end\n        animation.Frames[#animation.Frames + 1] = desc.Name .. i\n        if desc.PrimaryDirection == AnimationDirections.Left then\n            currentCol = currentCol - 1\n            if currentCol == -desc.Columns then\n                currentCol = 0\n                needMoveSecondary = true\n            end\n        elseif desc.PrimaryDirection == AnimationDirections.Right then\n            currentCol = currentCol + 1\n            if currentCol == desc.Columns then\n                currentCol = 0\n                needMoveSecondary = true\n            end\n        elseif desc.PrimaryDirection == AnimationDirections.Up then\n            currentRow = currentRow - 1\n            if currentRow == -desc.Rows then\n                currentRow = 0\n                needMoveSecondary = true\n            end\n        elseif desc.PrimaryDirection == AnimationDirections.Down then\n            currentRow = currentRow + 1\n            if currentRow == desc.Rows then\n                currentRow = 0\n                needMoveSecondary = true\n            end\n        end\n        if needMoveSecondary then\n            needMoveSecondary = false\n            if desc.SecondaryDirection == AnimationDirections.Left then\n                currentCol = currentCol - 1\n            elseif desc.SecondaryDirection == AnimationDirections.Right then\n                currentCol = currentCol + 1\n            elseif desc.SecondaryDirection == AnimationDirections.Up then\n                currentRow = currentRow - 1\n            elseif desc.SecondaryDirection == AnimationDirections.Down then\n                currentRow = currentRow + 1\n            end\n        end\n    end\n    root.Animations[desc.Name] = animation\nend\n"
  },
  {
    "path": "profiles/common/charset.lua",
    "content": "root.Charset = {\n    Flags = {},\n    CharacterToSc3 = {}\n};\n\nfunction AddCharsetFlag(characterString, flag)\n    if characterString == nil then return; end\n\n    for sourceBytePos, sourceCodePoint in utf8.codes(characterString) do\n        local charsetIndex = 0;\n        for targetBytePos, targetCodePoint in utf8.codes(root.CharsetInternal.CharsetStr) do\n            if sourceCodePoint == targetCodePoint then\n                root.Charset.Flags[charsetIndex] = root.Charset.Flags[charsetIndex] | flag;\n                break;\n            end\n            \n            charsetIndex = charsetIndex + 1;\n        end\n    end\nend\n\nif root.CharsetInternal ~= nil and root.CharsetInternal.CharsetStr ~= nil then\n    local i = 0\n    for p, c in utf8.codes(root.CharsetInternal.CharsetStr) do\n        local high_byte = 0x80 + math.floor(i / 256);\n        local low_byte = i % 256;\n        local code = high_byte << 8 | low_byte;\n        root.Charset.CharacterToSc3[utf8.char(c)] = code\n        i = i + 1\n    end\n\n    for i = 0, #root.CharsetInternal.CharsetStr do root.Charset.Flags[i] = 0; end\n\n    if root.CharsetInternal.Spaces ~= nil then\n        AddCharsetFlag(root.CharsetInternal.Spaces, CharacterTypeFlags.Space);\n    end\n    if root.CharsetInternal.WordEndingPuncts ~= nil then\n        AddCharsetFlag(root.CharsetInternal.WordEndingPuncts, CharacterTypeFlags.WordEndingPunct);\n    end\n    if root.CharsetInternal.WordStartingPunct ~= nil then\n        AddCharsetFlag(root.CharsetInternal.WordStartingPuncts, CharacterTypeFlags.WordStartingPunct);\n    end\nend\n"
  },
  {
    "path": "profiles/common/scriptinput.lua",
    "content": "root.PADinput = {\n    PAD1UP = 0x10000,\n    PAD1DOWN = 0x20000,\n    PAD1LEFT = 0x40000,\n    PAD1RIGHT = 0x80000,\n    PAD1START = 0x10,\n    PAD1SELECT = 0x20,\n    PAD1L3 = 0x40,\n    PAD1R3 = 0x80,\n    PAD1L1 = 0x100,\n    PAD1R1 = 0x200,\n    PAD1L2 = 0x400,\n    PAD1R2 = 0x800,\n    PAD1A = 0x1000,\n    PAD1B = 0x2000,\n    PAD1X = 0x4000,\n    PAD1Y = 0x8000,\n    PAD1UP_LS = 0x100000,\n    PAD1DOWN_LS = 0x200000,\n    PAD1LEFT_LS = 0x400000,\n    PAD1RIGHT_LS = 0x800000,\n    PAD1UP_RS = 0x1000000,\n    PAD1DOWN_RS = 0x2000000,\n    PAD1LEFT_RS = 0x4000000,\n    PAD1RIGHT_RS = 0x8000000,\n    PAD1UP_DIRECT = 0x1,\n    PAD1DOWN_DIRECT = 0x2,\n    PAD1LEFT_DIRECT = 0x4,\n    PAD1RIGHT_DIRECT = 0x8,\n};\n\nroot.Input = {\n    PADtoKBcustom  = {},\n    PADtoMS  = {},\n    PADtoGP  = {},\n    PADtoGPA = {},\n\n    PADcustomA = {},\n    PADcustomB = {},\n    KBcustom = {},\n\n}\n\nKbInputMode = {\n    Tap = 1,\n    Held = 2,\n    Any = 3,\n}\n\n-- Keyboard Custom Mapping\nroot.Input.PADtoKBcustom = {\n    [root.PADinput.PAD1UP]            = {{ Id =  0, Mode = KbInputMode.Any  }, { Id = 16, Mode = KbInputMode.Any }, { Id = 20, Mode = KbInputMode.Any }},\n    [root.PADinput.PAD1DOWN]          = {{ Id =  1, Mode = KbInputMode.Any  }, { Id = 17, Mode = KbInputMode.Any }, { Id = 21, Mode = KbInputMode.Any }},\n    [root.PADinput.PAD1LEFT]          = {{ Id =  2, Mode = KbInputMode.Any  }, { Id = 18, Mode = KbInputMode.Any }, { Id = 22, Mode = KbInputMode.Any }},\n    [root.PADinput.PAD1RIGHT]         = {{ Id =  3, Mode = KbInputMode.Any  }, { Id = 19, Mode = KbInputMode.Any }, { Id = 23, Mode = KbInputMode.Any }},\n    [root.PADinput.PAD1UP_DIRECT]     = {{ Id =  0, Mode = KbInputMode.Held }},\n    [root.PADinput.PAD1DOWN_DIRECT]   = {{ Id =  1, Mode = KbInputMode.Held }},\n    [root.PADinput.PAD1LEFT_DIRECT]   = {{ Id =  2, Mode = KbInputMode.Held }},\n    [root.PADinput.PAD1RIGHT_DIRECT]  = {{ Id =  3, Mode = KbInputMode.Held }},\n    [root.PADinput.PAD1A]             = {{ Id =  4, Mode = KbInputMode.Any  }},\n    [root.PADinput.PAD1B]             = {{ Id =  5, Mode = KbInputMode.Any  }},\n    [root.PADinput.PAD1X]             = {{ Id =  6, Mode = KbInputMode.Any  }},\n    [root.PADinput.PAD1Y]             = {{ Id =  7, Mode = KbInputMode.Any  }},\n    [root.PADinput.PAD1L1]            = {{ Id =  8, Mode = KbInputMode.Any  }},\n    [root.PADinput.PAD1L2]            = {{ Id =  9, Mode = KbInputMode.Any  }},\n    [root.PADinput.PAD1L3]            = {{ Id = 10, Mode = KbInputMode.Any  }},\n    [root.PADinput.PAD1R1]            = {{ Id = 11, Mode = KbInputMode.Any  }},\n    [root.PADinput.PAD1R2]            = {{ Id = 12, Mode = KbInputMode.Any  }},\n    [root.PADinput.PAD1R3]            = {{ Id = 13, Mode = KbInputMode.Any  }},\n    [root.PADinput.PAD1START]         = {{ Id = 14, Mode = KbInputMode.Any  }},\n    [root.PADinput.PAD1SELECT]        = {{ Id = 15, Mode = KbInputMode.Any  }},\n    [root.PADinput.PAD1UP_LS]         = {{ Id = 16, Mode = KbInputMode.Any  }},\n    [root.PADinput.PAD1DOWN_LS]       = {{ Id = 17, Mode = KbInputMode.Any  }},\n    [root.PADinput.PAD1LEFT_LS]       = {{ Id = 18, Mode = KbInputMode.Any  }},\n    [root.PADinput.PAD1RIGHT_LS]      = {{ Id = 19, Mode = KbInputMode.Any  }},\n    [root.PADinput.PAD1UP_RS]         = {{ Id = 20, Mode = KbInputMode.Any  }},\n    [root.PADinput.PAD1DOWN_RS]       = {{ Id = 21, Mode = KbInputMode.Any  }},\n    [root.PADinput.PAD1LEFT_RS]       = {{ Id = 22, Mode = KbInputMode.Any  }},\n    [root.PADinput.PAD1RIGHT_RS]      = {{ Id = 23, Mode = KbInputMode.Any  }},\n}\n\n-- Mouse\nroot.Input.PADtoMS = {\n    [root.PADinput.PAD1A]     = 1, -- Left\n    [root.PADinput.PAD1B]     = 3, -- Right\n    [root.PADinput.PAD1START] = 3, -- Right\n}\n\n-- Controller\nlocal BTN = ControllerButton;\nroot.Input.PADtoGP = {\n    [root.PADinput.PAD1A]            = BTN.A,\n    [root.PADinput.PAD1B]            = BTN.B,\n    [root.PADinput.PAD1X]            = BTN.X,\n    [root.PADinput.PAD1Y]            = BTN.Y,\n    [root.PADinput.PAD1SELECT]       = BTN.BACK,\n    [root.PADinput.PAD1START]        = BTN.START,\n    [root.PADinput.PAD1L3]           = BTN.LEFTSTICK,\n    [root.PADinput.PAD1R3]           = BTN.RIGHTSTICK,\n    [root.PADinput.PAD1L1]           = BTN.LEFTSHOULDER,\n    [root.PADinput.PAD1R1]           = BTN.RIGHTSHOULDER,\n    [root.PADinput.PAD1UP_DIRECT]    = BTN.DPAD_UP,\n    [root.PADinput.PAD1DOWN_DIRECT]  = BTN.DPAD_DOWN,\n    [root.PADinput.PAD1LEFT_DIRECT]  = BTN.DPAD_LEFT,\n    [root.PADinput.PAD1RIGHT_DIRECT] = BTN.DPAD_RIGHT,\n};\nlocal AXIS = ControllerAxis;\nlocal DIR = { POS = 1, NEG = -1 };\nroot.Input.PADtoGPA = {\n    -- { Axis Id, Direction }\n    [root.PADinput.PAD1UP_LS]    = {AXIS.LEFTY,       DIR.NEG},  -- Left stick Y axis\n    [root.PADinput.PAD1DOWN_LS]  = {AXIS.LEFTY,       DIR.POS},  -- Left stick Y axis\n    [root.PADinput.PAD1LEFT_LS]  = {AXIS.LEFTX,       DIR.NEG},  -- Left stick X axis\n    [root.PADinput.PAD1RIGHT_LS] = {AXIS.LEFTX,       DIR.POS},  -- Left stick X axis\n    [root.PADinput.PAD1UP_RS]    = {AXIS.RIGHTY,      DIR.NEG},  -- Right stick Y axis\n    [root.PADinput.PAD1DOWN_RS]  = {AXIS.RIGHTY,      DIR.POS},  -- Right stick Y axis\n    [root.PADinput.PAD1LEFT_RS]  = {AXIS.RIGHTX,      DIR.NEG},  -- Right stick X axis\n    [root.PADinput.PAD1RIGHT_RS] = {AXIS.RIGHTX,      DIR.POS},  -- Right stick X axis\n    [root.PADinput.PAD1L2]       = {AXIS.TRIGGERLEFT,  DIR.POS},  -- Left trigger\n    [root.PADinput.PAD1R2]       = {AXIS.TRIGGERRIGHT, DIR.POS},  -- Right trigger\n};\n\n\nlocal AllDir = root.PADinput.PAD1UP | root.PADinput.PAD1DOWN | root.PADinput.PAD1LEFT | root.PADinput.PAD1RIGHT;\n\n-- Default to cclcc profile\n\nroot.Input.PADcustomA = {\n    root.PADinput.PAD1UP,\n    root.PADinput.PAD1DOWN,\n    root.PADinput.PAD1LEFT,\n    root.PADinput.PAD1RIGHT,\n    root.PADinput.PAD1A | root.PADinput.PAD1START,\n    root.PADinput.PAD1A,\n    root.PADinput.PAD1B,\n    root.PADinput.PAD1L1,\n    root.PADinput.PAD1R1,\n    root.PADinput.PAD1X,\n    root.PADinput.PAD1START,\n    0,\n    root.PADinput.PAD1Y,\n    root.PADinput.PAD1R3,\n    root.PADinput.PAD1A | root.PADinput.PAD1B,\n    root.PADinput.PAD1A,\n    root.PADinput.PAD1B,\n    root.PADinput.PAD1X,\n    root.PADinput.PAD1Y,\n    0,\n    0,\n    root.PADinput.PAD1SELECT,\n    0,\n    root.PADinput.PAD1A,\n    0,\n    0,\n    0,\n    0,\n    root.PADinput.PAD1UP_LS | root.PADinput.PAD1UP_DIRECT,\n    root.PADinput.PAD1DOWN_LS | root.PADinput.PAD1DOWN_DIRECT,\n    root.PADinput.PAD1LEFT_LS | root.PADinput.PAD1LEFT_DIRECT,\n    root.PADinput.PAD1RIGHT_LS | root.PADinput.PAD1RIGHT_DIRECT,\n    root.PADinput.PAD1UP_RS,\n    root.PADinput.PAD1DOWN_RS,\n    root.PADinput.PAD1LEFT_RS,\n    root.PADinput.PAD1RIGHT_RS,\n    root.PADinput.PAD1L2,\n    root.PADinput.PAD1R2,\n    root.PADinput.PAD1LEFT | root.PADinput.PAD1L2,\n    root.PADinput.PAD1RIGHT | root.PADinput.PAD1R2,\n    AllDir | root.PADinput.PAD1A,\n    root.PADinput.PAD1L1,\n    root.PADinput.PAD1R1,\n}\n\nroot.Input.PADcustomB = {\n  root.PADinput.PAD1UP,\n  root.PADinput.PAD1DOWN,\n  root.PADinput.PAD1LEFT,\n  root.PADinput.PAD1RIGHT,\n  root.PADinput.PAD1A | root.PADinput.PAD1START,\n  root.PADinput.PAD1A,\n  root.PADinput.PAD1B,\n  root.PADinput.PAD1L2,\n  root.PADinput.PAD1R1,\n  root.PADinput.PAD1R2,\n  root.PADinput.PAD1START,\n  0,\n  root.PADinput.PAD1Y,\n  root.PADinput.PAD1R3,\n  root.PADinput.PAD1A | root.PADinput.PAD1B | root.PADinput.PAD1START,\n  root.PADinput.PAD1A,\n  root.PADinput.PAD1B,\n  root.PADinput.PAD1X,\n  root.PADinput.PAD1Y,\n  root.PADinput.PAD1R1,\n  root.PADinput.PAD1R2,\n  root.PADinput.PAD1L1,\n  0,\n  AllDir | root.PADinput.PAD1A,\n  root.PADinput.PAD1R1,\n  root.PADinput.PAD1L1,\n  root.PADinput.PAD1A,\n  root.PADinput.PAD1B,\n  root.PADinput.PAD1UP_LS | root.PADinput.PAD1UP_DIRECT,\n  root.PADinput.PAD1DOWN_LS | root.PADinput.PAD1DOWN_DIRECT,\n  root.PADinput.PAD1LEFT_LS | root.PADinput.PAD1LEFT_DIRECT,\n  root.PADinput.PAD1RIGHT_LS | root.PADinput.PAD1RIGHT_DIRECT,\n  root.PADinput.PAD1UP_RS,\n  root.PADinput.PAD1DOWN_RS,\n  root.PADinput.PAD1LEFT_RS,\n  root.PADinput.PAD1RIGHT_RS,\n};\n\nlocal SC = KeyboardScanCode;\nroot.Input.KBcustom = {\n     [0] = {SC._UP, SC._KP_8, SC._W},\n     [1] = {SC._DOWN, SC._KP_2, SC._S},\n     [2] = {SC._LEFT, SC._KP_4, SC._A},\n     [3] = {SC._RIGHT, SC._KP_6, SC._D},\n     [4] = {SC._RETURN, SC._KP_ENTER, SC._SPACE},\n     [5] = {SC._BACKSPACE, SC._X, SC._KP_0},\n     [6] = {SC._DELETE},\n     [7] = {SC._INSERT},\n     [8] = {SC._Q, SC._KP_7, SC._COMMA, SC._HOME},\n     [9] = {SC._Z, SC._KP_1, SC._END},\n    [11] = {SC._E, SC._KP_9, SC._PERIOD, SC._PAGEUP},\n    [12] = {SC._C, SC._KP_3, SC._PAGEDOWN},\n    [13] = {SC._F8},\n    [14] = {SC._1, SC._KP_DIVIDE},\n    [20] = {SC._R, SC._KP_MINUS, SC._LEFTBRACKET},\n    [21] = {SC._F, SC._KP_PLUS, SC._RIGHTBRACKET},\n    [50] = {SC._LCTRL, SC._RCTRL},\n    [51] = {SC._F1},\n    [52] = {SC._F2},\n    [53] = {SC._F3},\n    [54] = {SC._F4},\n    [55] = {SC._F5},\n    [56] = {SC._F6},\n    [57] = {SC._F9},\n    [58] = {SC._F7},\n    [59] = {SC._F11},\n    [60] = {SC._ESCAPE},\n    [61] = {SC._F10},\n};"
  },
  {
    "path": "profiles/common/scriptvars.lua",
    "content": "root.ScriptVars = {\n    SW_TITLECT=1016,\n    LR_DATE=1029,\n    SW_ACTORVOICE_ALPHA=1102,\n    SW_ACTORVOICE_CUR=1103,\n    SW_PLAYDATA_ALPHA=1131,\n    SW_MUSICMODE_ALPHA=1132,\n    SW_TOTALPLAYTIME=2001,\n    SW_CHARACTERIDMAPPING=2068,\n    SW_ANIME0CHANO=2100,\n    SW_ANIME1CHANO=2101,\n    SW_ANIME2CHANO=2102,\n    SW_MESWINDOW0ALPHA=2105,\n    SW_MESWINDOW1ALPHA=2106,\n    SW_MESWINDOW2ALPHA=2107,\n    SW_AUTOSAVERESTART=2112,\n    SW_GAMESTATE=2113,\n    SW_TITLEMODE=2115,\n    SW_TITLEMOVIECT=2116,\n    SW_TITLEDISPCT=2119,\n    SW_SYSSEL=2128,\n    SW_TITLEMASKALPHA=2133,\n    SW_TITLEMASKCOLOR=2134,\n    SW_TITLECGNO=2136,\n    SW_TITLECUR=2139,\n    SW_SYSTEMMENUCHG=2140,\n    SW_SYSTEMMENUALPHA=2141,\n    SW_SYSMENUCT=2142,\n    SW_SYSMENUALPHA=2143,\n    SW_SYSSUBMENUCT=2147,\n    SW_SYSSUBMENUNO=2148,\n    SW_SYSSUBMENUALPHA=2149,\n    SW_CLRALPHA=2151,\n    SW_SYSMESCOL1=2161,\n    SW_OPTIONCHAALPHA=2172,\n    SW_OPTIONCHANO=2173,\n    SW_TIMEYEAR=2180,\n    SW_TIMEMONTH=2181,\n    SW_TIMEDAY=2182,\n    SW_TIMEHOUR=2183,\n    SW_TIMEMINUTE=2184,\n    SW_TIMESECOND=2185,\n    SW_TIMEWEEK=2186,\n    SW_MOVIEMODE_ALPHA=2300,\n    SW_MOVIEMODE_CUR=2301,\n    SW_ALBUM_ALPHA=2305,\n    SW_ALBUM_LOADFILE=2306,\n    SW_ALBUM_LOADBUF=2307,\n    SW_LINEID=2345,\n    SW_SCRIPTID=2346,\n    SW_CAP1POSX_OFS=2280,\n    SW_CAP1POSY_OFS=2281,\n    SW_CAP1SX_OFS=2282,\n    SW_CAP1SY_OFS=2283,\n    SW_CAP1SIZE_OFS=2284,\n    SW_CAP1LX_OFS=2285,\n    SW_CAP1LY_OFS=2286,\n    SW_CAP1ROTZ_OFS=2287,\n    SW_CAP1ALPHA_OFS=2288,\n    SW_MESNAMEID0=2351,\n    SW_BG1POSX_OFS=2500,\n    SW_BG1POSY_OFS=2501,\n    SW_BG1SX_OFS=2502,\n    SW_BG1SY_OFS=2503,\n    SW_BG1SIZE_OFS=2504,\n    SW_BG1LX_OFS=2505,\n    SW_BG1LY_OFS=2506,\n    SW_BG1ROTZ_OFS=2507,\n    SW_BG1ALPHA_OFS=2508,\n    SW_CHA1POSX_OFS=2500,\n    SW_CHA1POSY_OFS=2501,\n    SW_CHA1ROTZ_OFS=2502,\n    SW_CHA1ROTX_OFS=2503,\n    SW_CHA1ROTY_OFS=2504,\n    SW_CHA1SIZEX_OFS=2505,\n    SW_CHA1SIZEY_OFS=2506,\n    SW_CHA1ALPHA_OFS=2507,\n    SW_MASK1ALPHA_OFS=2786,\n    SW_MASK2ALPHA_OFS=2787,\n    SW_MASK3ALPHA_OFS=2788,\n    SW_BGEFF1_OFSX=2800,\n    SW_BGEFF1_OFSY=2801,\n    SW_BGEFF1_SIZE_OFS=2802,\n    SW_BGEFF1_ROTX_OFS=2803,\n    SW_BGEFF1_ROTY_OFS=2804,\n    SW_BGEFF1_ROTZ_OFS=2805,\n    SW_BGEFF1_ALPHA_OFS=2806,\n    SW_BGEFF1_SX_OFS=2807,\n    SW_BGEFF1_SY_OFS=2808,\n    SW_BGEFF1_MASK_VERTEX1_OFSX=2809,\n    SW_BGEFF1_MASK_VERTEX1_OFSY=2810,\n    SW_MAINTHDP=3304,\n    SW_RESTARTMASK=3318,\n    SW_PLATFORM=3319,\n    SW_SAVEERRORCODE=3333,\n    SW_SYSMENUCNO=3338,\n    SW_TITLECUR1=3341,\n    SW_TITLECUR2=3342,\n    SW_SYSMESALPHA=3370,\n    SW_SYSMESANIMCTCUR=3371,\n    SW_SYSMESANIMCTF=3372,\n    SW_SINSTALL_ALL=3373,\n    SW_BG1SURF=3400,\n    SW_CHA1SURF=3450,\n    SW_FACE1SURF=3478,\n    SW_MENUCT=3503,\n    SW_OPTIONALPHA=3504,\n    SW_FILEALPHA=3505,\n    SW_TIPSALPHA=3506,\n    SW_SHORTCUT=3507,\n    SW_SVSENO=3601,\n    SW_SVBGMNO=3605,\n    SW_SVSCRNO1=3606,\n    SW_SVSCRNO2=3607,\n    SW_SVSCRNO3=3608,\n    SW_SVSCRNO4=3609,\n    SW_SVBGNO1=3610,\n    SW_SVCHANO1=3618,\n    SW_SVBGM2NO=3634,\n    SW_SAVEFILESTATUS=3722,\n    SW_SAVEFILENO=3723,\n    SW_SAVEFILETYPE=3724,\n    SW_TITLE=4300,\n    SW_PLAYTIME=4304,\n    SW_MESWINDOW_COLOR=4308,\n    SW_BGMREQNO=4310,\n    SW_SEREQNO=4311,\n    SW_BGMVOL=4314,\n    SW_SEVOL=4315,\n    SW_SELNO=4318,\n    SW_BGMREQNO2=4331,\n    SW_SCRIPTNO0=4340,\n    SW_SCRIPTNO1=4341,\n    SW_SCRIPTNO2=4342,\n    SW_SCRIPTNO3=4343,\n    SW_SCRIPTNO4=4344,\n    SW_SCRIPTNO5=4345,\n    SW_SCRIPTNO6=4346,\n    SW_SCRIPTNO7=4347,\n    SW_SCRIPTNO8=4348,\n    SW_SCRIPTNO9=4349,\n    SW_SCRIPTNO10=4350,\n    SW_SCRIPTNO11=4351,\n    SW_SCRIPTNO12=4352,\n    SW_SCRIPTNO13=4353,\n    SW_SCRIPTNO14=4354,\n    SW_SCRIPTNO15=4355,\n    SW_MASK1COLOR=4450,\n    SW_MASK1ALPHA=4451,\n    SW_MASK1PRI=4452,\n    SW_MASK1POSX=4453,\n    SW_MASK1POSY=4454,\n    SW_MASK1SIZEX=4455,\n    SW_MASK1SIZEY=4456,\n    SW_MESWIN0POSX=4420,\n    SW_MESWIN0POSY=4421,\n    SW_MESWIN0TYPE=4422,\n    SW_MESMODE0=4423,\n    SW_BGLINK=4490,\n    SW_BGLINK2=4491,\n    SW_BG1POSX=4500,\n    SW_BG1POSY=4501,\n    SW_BG1SX=4502,\n    SW_BG1SY=4503,\n    SW_BG1SIZE=4504,\n    SW_BG1LX=4505,\n    SW_BG1LY=4506,\n    SW_BG1NO=4507,\n    SW_BG1PRI=4508,\n    SW_BG1DISPMODE=4509,\n    SW_BG1FADECT=4510,\n    SW_BG1FADETYPE=4511,\n    SW_BG1ROTZ=4512,\n    SW_BG1ALPHA=4513,\n    SW_BG1MASKNO=4514,\n    SW_BG1MASKFADERANGE=4515,\n    SW_BG1CLIP_X=4516,\n    SW_BG1CLIP_Y=4517,\n    SW_BG1PRI2=4518,\n    SW_BG1FILTER=4519,\n    SW_BG1EFFPRI=4520,\n    SW_BG1EFFPRI2=4521,\n    SW_CAP1POSX=5000,\n    SW_CAP1POSY=5001,\n    SW_CAP1SX=5002,\n    SW_CAP1SY=5003,\n    SW_CAP1SIZE=5004,\n    SW_CAP1LX=5005,\n    SW_CAP1LY=5006,\n    SW_CAP1NO=5007,\n    SW_CAP1PRI=5008,\n    SW_CAP1DISPMODE=5009,\n    SW_CAP1FADECT=5010,\n    SW_CAP1FADETYPE=5011,\n    SW_CAP1ROTZ=5012,\n    SW_CAP1ALPHA=5013,\n    SW_CAP1MASKNO=5014,\n    SW_CAP1MASKFADERANGE=5015,\n    SW_CAP1PRI2=5016,\n    SW_CAP1FILTER=5017,\n    -- Since R;NE/DaSH don't have 2D character support, these have different names\n    SW_MDL1POSX=5100,\n    SW_MDL1POSY=5101,\n    SW_MDL1POSZ=5102,\n    SW_MDL1ROTX=5103,\n    SW_MDL1ROTY=5104,\n    SW_MDL1ROTZ=5105,\n    SW_MDL1FACENO=5106,\n    SW_MDL1ANIME=5108,\n    SW_MDL1FILENO=5109,\n    SW_MDL1ROTOY=5110,\n    SW_MDL1CENY=5111,\n    SW_MDL1TARDIR=5112,\n    SW_MDL1CHANO=5113,\n    SW_MDL1FACEDIR=5114,\n    SW_MDL1FACEROT=5115,\n    SW_MDL1FACEELV=5116,\n    SW_MDL1FACESLA=5117,\n    SW_MDL1ROT_INTER=5118,\n    SW_MDL1FROT_INTER=5119,\n    SW_MDL1POS_INTER=5120,\n    SW_MDL1EYEU=5121,\n    SW_MDL1EYEV=5122,\n    SW_MDL1EYEROT=5123,\n    SW_MDL1EYESIZE=5124,\n    SW_MDL1EYEANIMENO=5125,\n    SW_MDL1MOUTHANIMENO=5126,\n    SW_MDL1OPTPARTS=5107,\n    SW_MDL1LIGHTNO=5127,\n    -- R;NE/DaSH End\n    SW_CHA1POSX=5100,\n    SW_CHA1POSY=5101,\n    SW_CHA1ROTX=5102,\n    SW_CHA1ROTY=5103,\n    SW_CHA1ROTZ=5104,\n    SW_CHA1SIZEX=5105,\n    SW_CHA1SIZEY=5106,\n    SW_CHA1ALPHA=5107,\n    SW_CHA1FILTER=5108,\n    SW_CHA1NO=5109,\n    SW_CHA1PRI=5110,\n    SW_CHA1POSE=5111,\n    SW_CHA1FACE=5112,\n    SW_CHA1EX=5113,\n    SW_CHA1FADECT=5114,\n    SW_CHA1FADETYPE=5115,\n    SW_CHA1ANIME_EYE=5118,\n    SW_CHA1ANIME_MOUTH=5119,\n    SW_CHA1PRI2=5120,\n    SW_CHA1BASESIZE=5121,\n    SW_IRUOCAMERAPOSX=5400,\n    SW_IRUOCAMERAPOSY=5401,\n    SW_IRUOCAMERAPOSZ=5402,\n    SW_IRUOCAMERAROTX=5403,\n    SW_IRUOCAMERAROTY=5404,\n    SW_CAMSHTARDIR=5405,\n    SW_IRUOCAMERAHFOV=5408,\n    SW_IRUOCAMERAHFOVDELTA=5411,\n    SW_MAINCAMERAPOSX=5420,\n    SW_MAINCAMERAPOSY=5421,\n    SW_MAINCAMERAPOSZ=5422,\n    SW_MAINCAMERAROTX=5423,\n    SW_MAINCAMERAROTY=5424,\n    SW_MAINCAMERAHFOV=5428,\n    SW_MAINCAMERAHFOVDELTA=5431,\n    SW_MAINLIGHTCOLOR=5460,\n    SW_MAINLIGHTPOSX=5461,\n    SW_MAINLIGHTPOSY=5462,\n    SW_MAINLIGHTPOSZ=5463,\n    SW_MAINLIGHTWEIGHT=5464,\n    SW_MAINLIGHTDARKMODE=5465,\n    SW_LIGHT1_COLORR=5460,\n    SW_LIGHT1_COLORG=5461,\n    SW_LIGHT1_COLORB=5462,\n    SW_LIGHT1_POSX=5463,\n    SW_LIGHT1_POSY=5464,\n    SW_LIGHT1_POSZ=5465,\n    SW_FACEEX1NO=5990,\n    SW_FACEEX1FACE=5991,\n    SW_FACEEX1FILTER=5992,\n    SW_FACEPOSX=5980,\n    SW_FACEPOSY=5981,\n    SW_CAMSHROT_WORK=5980,\n    SW_CAMSHELV_WORK=5981,\n    SW_CAMSHPOSX_WORK=5982,\n    SW_CAMSHPOSY_WORK=5983,\n    SW_CAMSHPOSZ_WORK=5984,\n    SW_CAMROT_WORK=5990,\n    SW_CAMELV_WORK=5991,\n    SW_CAMPOSX_WORK=5992,\n    SW_CAMPOSY_WORK=5993,\n    SW_CAMPOSZ_WORK=5994,\n    SW_IRUOCAMERAHFOVCUR=5985,\n    SW_MAINCAMERAHFOVCUR=5995,\n    SW_MOSAIC=6300,\n    SW_MOSAIC_PRI=6301,\n    SW_FEATHERING=6302,\n    SW_FEATHERING_PRI=6303,\n    SW_FEATHERING2=6304,\n    SW_FEATHERING2_PRI=6305,\n    SW_MASK_CAPTURE_PRI=6310,\n    SW_EFF_CAP_PRI=6311,\n    SW_EFF_CAP_BUF=6313,\n    SW_EFF_CAP_PRI2=6314,\n    SW_EFF_CAP_BUF2=6316,\n    SW_EFF_WAVE_PRI=6317,\n    SW_EFF_WAVE_ALPHA=6324,\n    SW_MOVIEPRI=6330,\n    SW_ALPHAMOVIE_PRI=6331,\n    SW_ALPHAMOVIE_ALPHA=6332,\n    SW_MOVIE_PLAYNO=6333,\n    SW_MOVIE_PLAYMODE=6334,\n    SW_MOVIE_PLAYVIEW=6335,\n    SW_MOVIE_LOADNO=6336,\n    SW_ALPHAMOVIE_MODE=6337,\n    SW_MOVIEALPHA=6338,\n    SW_MOVIEPRI2=6339,\n    SW_MOVIEALPHA2=6340,\n    SW_MOVIEPRI3=6331,\n    SW_MOVIEALPHA3=6332,\n    SW_MOVIEPRI4=6341,\n    SW_MOVIEALPHA4=6342,\n    SW_MOVIEFRAME=6343,\n    SW_MOVIETOTALFRAME=6345,\n    SW_MAP_ALPHA=6360,\n    SW_MAP_PRI=6361,\n    SW_POKECON_OFSX=6378,\n    SW_POKECON_OFSY=6379,\n    SW_POKECON_PRI=6380,\n    SW_POKECON_BOOTANIMECT=6381,\n    SW_POKECON_SHUTDOWNANIMECT=6382,\n    SW_POKECON_MENUCUR=6383,\n    SW_POKECON_MENUSELANIMECT=6384,\n    SW_POKECON_MODE=6385,\n    SW_AR_POSX=6402,\n    SW_AR_POSY=6403,\n    SW_AR_ELV=6404,\n    SW_PHONE_DISP_CT=6404,\n    SW_DELUSION_OVERLAY_BUF=6410,\n    SW_DELUSION_CIRCLE_BUF=6411,\n    SW_DELUSION_STATE=6412,\n    SW_DELUSION_BG_COUNTER=6413,\n    SW_DELUSION_LIMIT=6414,\n    SW_DELUSION_SPIN_COUNTER=6415,\n    SW_DELUSION_PRI=6417,\n    SW_AR_ELVMIN=6410,\n    SW_AR_ELVMAX=6411,\n    SW_AR_ROTMIN=6412,\n    SW_AR_ROTMAX=6413,\n    SW_POKECOMIRUOHFOV=6428,\n    SW_BGEFF1_MASK_VERTEX1_X=6600,\n    SW_BGEFF1_MASK_VERTEX1_Y=6601,\n    SW_BGEFF1_SX=6608,\n    SW_BGEFF1_SY=6609,\n    SW_BGEFF1_SIZE=6610,\n    SW_BGEFF1_PRI=6611,\n    SW_BGEFF1_MASK_TYPE=6612,\n    SW_BGEFF1_FADECT=6613,\n    SW_BGEFF1_MODE=6614,\n    SW_BGEFF1_ROTX=6615,\n    SW_BGEFF1_ROTY=6616,\n    SW_BGEFF1_ROTZ=6617,\n    SW_BGEFF1_ALPHA=6618,\n    SW_BGEFF1_MASKNO=6619,\n    SW_BGEFF1_MASKFADERANGE=6620,\n    SW_BGEFF1_PRI2=6621,\n    SW_BGEFF1_FILTER=6622,\n    SW_BGEFF1_POSX=6624,\n    SW_BGEFF1_POSY=6625,\n    SW_RENDERTARGET=7000,\n    -- FlagWork\n    SF_CLR_FLAG=800,\n    SF_CLR_END1=801, -- 801 to (at least) 814 is reserved for endings in all the games.\n    SF_CLR_TRUE_CC=814,\n    SF_ACTORSVOICE_UNLOCK1=840,\n    SF_EXTRA_ENA=860,\n    SF_MOVIE_UNLOCK1=910,\n    SF_CONGRATULATED=1199,\n    SF_SAVECAPTURE=1206,\n    SF_CHAANIME=1213,\n    SF_SAVEICON=1226,\n    SF_SYSTEMMENUDIRECT=1228,\n    SF_MESSKIP=1233,\n    SF_MESALLSKIP=1234,\n    SF_MESREAD=1235,\n    SF_MOVIE_DRAWWAIT=1236,\n    SF_SELECTMODE=1237,\n    SF_TITLEMODE=1240,\n    SF_TITLEEND=1241,\n    SF_UIHIDDEN=1244,\n    SF_ALLCLEAR=1250,\n    SF_SHOWWAITICON=1251,\n    SF_LOADING=1264,\n    SF_BG1LOADEXEC=1270,\n    SF_SAVEDISABLE=1282,\n    SF_AUTOSAVEENABLE=1285,\n    SF_MESREVDISABLE = 1287,\n    SF_MESSAVEPOINT_SSP=1288,\n    SF_SYSMENUDISABLE=1286,\n    SF_SYSTEMMENUDISABLE=1221, -- i don't know SYS and SYSTEM exists\n    SF_SYSTEMMENUDISABLE2=1222,\n    SF_GAMEPAUSE=1223,\n    SF_SAVEPROTECTED = 1245;\n    SF_SAVEALLPROTECTED = 1246;\n    SF_SAVEPROTECTCHANGED = 1247;\n    SF_ALBUMRELOAD=1310,\n    SF_ALBUMEND=1311,\n    SF_ALBUMCHA1=1312,\n    SF_ALBUMLOAD=1341,\n    SF_ALBUMLOAD_COMPLETE=1342,\n    SF_HELPMENU=1345,\n    SF_BACKLOGMENU = 1350,\n    SF_SAVEMENU=1351,\n    SF_OPTIONMENU=1352,\n    SF_SYSTEMMENU=1353,\n    SF_TIPSMENU=1354,\n    SF_ALBUMMENU=1355,\n    SF_CLEARLISTMENU=1356,\n    SF_SOUNDMENU=1357,\n    SF_MOVIEMENU=1358,\n    SF_ACHIEVEMENTMENU=1359,\n    SF_RESTARTMASK = 1800,\n    SF_RETURNTITLE=1805,\n    SF_MOVIEPLAY=1823,\n    SF_SUBMENUEXIT=1830,\n    SF_LOADINGFROMSAVE=1835,\n    SF_BACKLOG_NOLOG=1837,\n    SF_MOVIECANCEL=1856,\n    SF_BG1DISP=2400,\n    SF_MDL1DISP=2410,\n    SF_CHA1DISP=2410,\n    SF_MDL1SHDISP=2430,\n    SF_CAP1DISP=2431,\n    SF_FACEEX1DISP=2427,\n    SF_CAP1NOALPHACHK=2458,\n    SF_BGEFF1DISP=2470,\n    SF_BGEXPLOSIONVISIBLE=2480,\n    SF_REVADDDISABLE=2483,\n    SF_MOVIELOADPLAYFL=2486,\n    SF_MASK_CAPTURE=2489,\n    SF_MOVIEFL=2493,\n    SF_MESWINDOW0OPENFL=2502,\n    SF_CHA1BGEFFECT=2510,\n    SF_Phone_Open=2600,\n    SF_DATEDISPLAY=2640,\n    SF_IRUOENABLE=2800,\n    SF_AR_SETUP_ADD_MDLBUF1=2808,\n    SF_IRUOAUTO=2806,\n    SF_IRUO=2816,\n    SF_NEKOMIMIICON=2818,\n    SF_DELUSIONACTIVE=2820,\n    SF_DELUSIONSELECTED=2821,\n    SF_Pokecon_Open=2900,\n    SF_Pokecon_Cancel=2901,\n    SF_Pokecon_End=2902,\n    SF_Pokecon_ManualMode=2903,\n    SF_Pokecon_Disable=2904,\n    SF_POKECOMENABLE=2900,\n    SF_SCN_CLR1=3680\n}"
  },
  {
    "path": "profiles/darling/charset.lua",
    "content": "-- WARNING, 0-INDEXED ARRAYS AHEAD\n\nroot.Charset = {\n    Flags = {}\n};\n\nfor i = 0, (64 * 43) - 1 do root.Charset.Flags[i] = 0; end\n\nlocal spaces = {[0]=0, 63};\nfor i = 0, #spaces do\n    root.Charset.Flags[spaces[i]] = root.Charset.Flags[spaces[i]] | CharacterTypeFlags.Space;\nend\n\n-- 、 。 ． ， ？ ！ 〜 ” ー ） 〕 ］ ｝ 〉 》 」 』 】☆ ★ ♪ 々 ぁ ぃ ぅ ぇ\n-- ぉ っ ゃ ゅ ょ ァ ィ ゥ ェ ォ ッ ャ ュ ョ –\nlocal wordEndingPuncts = {\n    [0]=0x00BE, 0x00BF, 0x00C1,\n    0x00C0, 0x00C4, 0x00C5,\n    0x00E4, 0x00CB, 0x00E5,\n    0x00CD, 0x00CF, 0x00D1,\n    0x00D3, 0x00D5, 0x00D7,\n    0x00D9, 0x00DB, 0x00DD,\n    0x01A5, 0x01A6, 0x00E6,\n    0x0187, 0x00E8, 0x00E9,\n    0x00EA, 0x00EB, 0x00EC,\n    0x00ED, 0x00EE, 0x00EF,\n    0x00F0, 0x00F2, 0x00F3,\n    0x00F4, 0x00F5, 0x00F6,\n    0x00F7, 0x00F8, 0x00F9,\n    0x00FA, 0x0113\n};\nfor i = 0, #wordEndingPuncts do\n    root.Charset.Flags[wordEndingPuncts[i]] = root.Charset.Flags[wordEndingPuncts[i]] | CharacterTypeFlags.WordEndingPunct;\nend\n\n-- space(63) space(0) “ （ 〔 ［ ｛ 〈 《 「\nlocal wordStartingPuncts = {\n    [0]=63, 0, 0x00CA,\n    0x00CC, 0x00CE, 0x00D0,\n    0x00D2, 0x00D4, 0x00D6,\n    0x00D8, 0x00DA, 0x00DC\n};\nfor i = 0, #wordStartingPuncts do\n    root.Charset.Flags[wordStartingPuncts[i]] = root.Charset.Flags[wordStartingPuncts[i]] | CharacterTypeFlags.WordStartingPunct;\nend\n"
  },
  {
    "path": "profiles/darling/config.lua",
    "content": "root.Language = \"Japanese\";\nroot.ResolutionWidth = 1280;\nroot.ResolutionHeight = 720;\nroot.Fullscreen = false;\nroot.DateFormat = DateFormatType.YMD;"
  },
  {
    "path": "profiles/darling/dialogue.lua",
    "content": "root.Sprites[\"ADVBox\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 767, Y = 806, Width = 1280, Height = 216 },\n};\n\nroot.Sprites[\"DialogueWaitIcon\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 1, Y = 97, Width = 32, Height = 32 }\n};\n\nroot.Dialogue = {\n    REVBounds = { X = 0, Y = 0, Width = 960, Height = 400 },\n    REVNameFontSize = 24,\n    REVColor = 0,\n    REVNameColor = 24,\n    REVNameOffset = 34,\n    REVNameLocation = REVNameLocationType.TopLeft,\n    REVOutlineMode = 2,\n    REVNameOutlineMode = 2,\n    NVLBounds = { X = 125, Y = 85, Width = 1024, Height = 400 },\n    ADVBounds = { X = 161, Y = 525, Width = 960, Height = 180 },\n    ADVBoxSprite = \"ADVBox\",\n    ADVBoxPos = { X = 0, Y = 504 },\n    FadeOutDuration = 0.33,\n    FadeInDuration = 0.33,\n    TextFadeInDuration = 0.33,\n    TextFadeOutDuration = 0.33,\n    DialogueBoxCurrentType = DialogueBoxType.Plain,\n    NVLBoxMaxOpacity = 0.55,\n\n    ADVNameAlignment = TextAlignment.Left,\n    ADVNameFontSize = 22,\n    ADVNamePos = { X = 600, Y = 672 },\n\n    NametagCurrentType = NametagType.TwoPiece,\n    NametagPosition = { X = 400, Y = 680 },\n    NametagLeftSprite = \"NametagLeftSprite\",\n    NametagRightSprite = \"NametagRightSprite\",\n\n    WaitIconCurrentType = WaitIconType.Rotate,\n    WaitIconSprite = \"DialogueWaitIcon\",\n    WaitIconOffset = { X = 4, Y = 4 },\n    WaitIconAnimationDuration = 3.2,\n    DialogueFont = \"Default\",\n    SetFontSizeRatio = 1000.0,\n    DefaultFontSize = 32,\n    RubyFontSize = 14,\n    RubyYOffset = -18,\n    ColorTable = {\n        {0xFFFFFF, 0x000000}, {0x5080FF, 0x000000},\n        {0xFF7080, 0x000000}, {0xFFA0F8, 0x000000},\n        {0x46FF80, 0x000000}, {0x90FFFF, 0x000000},\n        {0xFFFF70, 0x000000}, {0x80FFC0, 0x000000},\n        {0xFFB080, 0x000000}, {0xB080FF, 0x000000},\n        {0x000000, 0x808080}, {0x000000, 0x5080FF},\n        {0x000000, 0xFF7080}, {0x000000, 0xFFA0F8},\n        {0x000000, 0x268840}, {0x000000, 0x409999},\n        {0x000000, 0x888830}, {0x000000, 0x80FFC0},\n        {0x000000, 0xFFB080}, {0x000000, 0xB080FF},\n        {0xD0D0D0, 0x000000}, {0xD0D0FF, 0x000000},\n        {0xFFD0D0, 0x000000}, {0xFFD0FF, 0x000000},\n        {0xD0FFD0, 0x000000}, {0xD0FFFF, 0x000000},\n        {0xFFFFD0, 0x000000}, {0xE8FFD0, 0x000000},\n        {0xFFE8D0, 0x000000}, {0xD0E8FF, 0x000000},\n        {0xFFFFFF, 0x808080}, {0xFFFFFF, 0x5080FF},\n        {0xFFFFFF, 0xFF7080}, {0xFFFFFF, 0xFFA0F8},\n        {0xFFFFFF, 0x46FF80}, {0xFFFFFF, 0x90FFFF},\n        {0xFFFFFF, 0xFFFF70}, {0xFFFFFF, 0x80FFC0},\n        {0xFFFFFF, 0xFFB080}, {0xFFFFFF, 0xB080FF},\n        {0xFFEEEE, 0x000000}, {0xFFCCCC, 0x000000},\n        {0xFFAAAA, 0x000000}, {0xFF9999, 0x000000},\n        {0xFF8888, 0x000000}, {0xFFFF00, 0x000000},\n        {0xFEF000, 0x000000}, {0xFF7777, 0x000000},\n        {0xFF6666, 0x000000}, {0xFF5555, 0x000000},\n        {0xFF4444, 0x000000}, {0xFF3333, 0x000000},\n        {0xFF2222, 0x000000}, {0xFF0000, 0x000000},\n        {0xDD0000, 0x000000}, {0xBB0000, 0x000000},\n        {0xB00000, 0x000000}, {0xAA0000, 0x000000},\n        {0x950000, 0x000000}, {0x808080, 0x000000},\n        {0xAAAAAA, 0x000000}, {0xAAC1C9, 0x000000},\n        {0x000000, 0x000000}, {0x000000, 0x000000},\n        {0x000000, 0x000000}, {0x000000, 0x000000},\n        {0x000000, 0x000000}, {0xF80B0B, 0x000000},\n        {0xF8910B, 0x000000}, {0x33F12A, 0x000000}\n    },\n    MaxPageSize = 2000,\n    PageCount = 3,\n    ColorTagIsUint8 = false\n};\n\nroot.Sprites[\"NametagLeftSprite\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 768, Y = 787, Width = 195, Height = 11 }\n};\n\nroot.Sprites[\"NametagRightSprite\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 964, Y = 787, Width = 200, Height = 11 }\n};\n"
  },
  {
    "path": "profiles/darling/font.lua",
    "content": "-- WARNING, 0-INDEXED ARRAYS AHEAD\n\nroot.Fonts = {\n    [\"Default\"] = {\n        Type = FontType.Basic,\n        Sheet = \"Font\",\n        Columns = 64,\n        Rows = 43,\n        AdvanceWidths = {},\n        AdvanceWidthsEmWidth = 32,\n        LineSpacing = 22,\n    }\n};\n\nlocal westernAdvanceWidths = {\n    [0]=0x20, 0x16, 0x0E, 0x14, 0x15, 0x16, 0x16, 0x16, 0x15, 0x16, 0x16, 0x18,\n        0x16, 0x17, 0x17, 0x15, 0x14, 0x18, 0x16, 0x08, 0x0E, 0x17, 0x13, 0x1A,\n        0x16, 0x19, 0x15, 0x19, 0x16, 0x15, 0x15, 0x16, 0x17, 0x1E, 0x16, 0x15,\n        0x15, 0x14, 0x13, 0x13, 0x14, 0x14, 0x0D, 0x14, 0x13, 0x08, 0x09, 0x13,\n        0x08, 0x1A, 0x13, 0x15, 0x13, 0x14, 0x0D, 0x12, 0x0D, 0x13, 0x13, 0x1A,\n        0x13, 0x13, 0x12, 0x11, 0x0F, 0x08, 0x0F, 0x08, 0x08, 0x14, 0x08, 0x08,\n        0x1C, 0x17, 0x1E, 0x0F, 0x11, 0x19, 0x0C, 0x0D, 0x0D, 0x0C, 0x14, 0x13,\n        0x15, 0x12, 0x12, 0x12, 0x10, 0x10, 0x14, 0x19, 0x0E, 0x08, 0x0C, 0x0C,\n        0x14, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A,\n        0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A,\n        0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x19, 0x0F, 0x17, 0x18,\n        0x1A, 0x17, 0x18, 0x17, 0x19, 0x18, 0x19, 0x16, 0x17, 0x17, 0x14, 0x14,\n        0x18, 0x15, 0x08, 0x0E, 0x17, 0x13, 0x1A, 0x15, 0x19, 0x15, 0x19, 0x16,\n        0x15, 0x15, 0x15, 0x18, 0x1E, 0x17, 0x17, 0x15, 0x14, 0x14, 0x13, 0x14,\n        0x13, 0x0E, 0x14, 0x13, 0x09, 0x0C, 0x13, 0x08, 0x19, 0x13, 0x15, 0x14,\n        0x14, 0x0D, 0x12, 0x0E, 0x13, 0x14, 0x1B, 0x15, 0x14, 0x13, 0x12, 0x12,\n        0x08, 0x08, 0x09, 0x09, 0x15, 0x09, 0x0C, 0x0B, 0x08, 0x08, 0x0D, 0x0E,\n        0x0B, 0x0C, 0x0C, 0x0C, 0x0B, 0x0C, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11,\n        0x0C, 0x0D, 0x0D, 0x0E, 0x0B, 0x0C, 0x16, 0x17, 0x0B, 0x0C, 0x09, 0x1D,\n        0x1D, 0x1D, 0x14, 0x1E, 0x1A, 0x1A, 0x17, 0x19, 0x1A, 0x1A, 0x1B, 0x19,\n        0x19, 0x1B, 0x19, 0x18, 0x17, 0x19, 0x19, 0x19, 0x1A, 0x19, 0x16, 0x17,\n        0x18, 0x1B, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E,\n        0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E,\n        0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E,\n        0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E,\n        0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E,\n        0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E,\n        0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E,\n        0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E,\n        0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E,\n        0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E,\n        0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E,\n};\n\nfor i = 0, (64 * 43) - 1 do\n    if i <= #westernAdvanceWidths then\n        root.Fonts[\"Default\"].AdvanceWidths[i] = westernAdvanceWidths[i];\n    else\n        root.Fonts[\"Default\"].AdvanceWidths[i] = root.Fonts[\"Default\"].AdvanceWidthsEmWidth;\n    end\nend"
  },
  {
    "path": "profiles/darling/game.lua",
    "content": "root.ActiveRenderer = RendererType.OpenGL;\n\nroot.LayerCount = 100;\nroot.GameFeatures = GameFeature.Sc3VirtualMachine | GameFeature.Renderer2D | GameFeature.Input | GameFeature.Audio;\nroot.DesignWidth = 1280;\nroot.DesignHeight = 720;\n\nroot.WindowName = \"STEINS;GATE: My Darling's Embrace\";\nroot.WindowIconPath = \"games/darling/icondata/icon.png\";\nroot.CursorArrowPath = \"games/darling/icondata/cursor_arrow.png\";\nroot.CursorPointerPath = \"games/darling/icondata/cursor_pointer.png\";\n\nroot.CharaIsMvl = false;\nroot.LayFileBigEndian = true;\nroot.LayFileTexXMultiplier = 2048;\nroot.LayFileTexYMultiplier = 1024;\n\nroot.ScreenCaptureCount = 2;\n\nroot.Vm = {\n    StartScript = 2,\n    StartScriptBuffer = 0,\n    GameInstructionSet = InstructionSet.Darling,\n    UseReturnIds = true,\n\n    ScrWorkChaStructSize = 40,\n    ScrWorkChaOffsetStructSize = 10,\n    ScrWorkBgStructSize = 40,\n    ScrWorkBgOffsetStructSize = 10,\n    ScrWorkCaptureStructSize = 20,\n    ScrWorkCaptureOffsetStructSize = 10,\n    ScrWorkCaptureEffectInfoStructSize = 3,\n};\n\ninclude('common/scriptinput.lua');\ninclude('common/scriptvars.lua');\ninclude('darling/config.lua');\ninclude('darling/scriptvars.lua');\ninclude('darling/savedata.lua');\ninclude('darling/tipssystem.lua');\ninclude('darling/vfs.lua');\ninclude('darling/sprites.lua');\ninclude('common/animation.lua');\ninclude('darling/charset.lua');\ninclude('darling/font.lua');\ninclude('darling/dialogue.lua');\ninclude('darling/hud/saveicon.lua');\ninclude('darling/hud/loadingdisplay.lua');\ninclude('darling/hud/datedisplay.lua');\ninclude('darling/hud/titlemenu.lua');\ninclude('darling/hud/backlogmenu.lua');\n--include('darling/hud/systemmenu.lua');\ninclude('darling/hud/sysmesboxdisplay.lua');\ninclude('darling/hud/selectiondisplay.lua');\ninclude('darling/hud/tipsmenu.lua');\ninclude('darling/hud/tipsnotification.lua');"
  },
  {
    "path": "profiles/darling/hud/backlogmenu.lua",
    "content": "root.BacklogMenu = {\n    Type = BacklogMenuType.None,\n    DrawType = DrawComponentType.ExtrasScenes,\n    BacklogBackgroundSprite = \"BacklogBackground\",\n    EntryHighlightSprite = \"EntryHighlight\",\n    EntryHighlightPadding = 0.0,\n    VoiceIconSprite = \"VoiceIcon\",\n    ScrollbarTrackSprite = \"ScrollbarTrack\",\n    ScrollbarThumbSprite = \"ScrollbarThumb\",\n    ScrollbarPosition = { X = 1165, Y = 98 },\n    EntriesStart = { X = 163, Y = 85 },\n    RenderingBounds = { X = 87, Y = 83, Width = 1055, Height = 590 },\n    EntryYPadding = 22,\n    FadeInDuration = 0.2,\n    FadeOutDuration = 0.2\n};\n\nroot.Sprites[\"BacklogBackground\"] = {\n    Sheet = \"Backlog\",\n    Bounds = { X = 0, Y = 0, Width = 1280, Height = 720 },\n};\n\nroot.Sprites[\"VoiceIcon\"] = {\n    Sheet = \"Backlog\",\n    Bounds = { X = 1281, Y = 1, Width = 30, Height = 30 },\n};\n\nroot.Sprites[\"EntryHighlight\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 65, Y = 1, Width = 94, Height = 30 }\n};\n\nroot.Sprites[\"ScrollbarThumb\"] = {\n    Sheet = \"Backlog\",\n    Bounds = { X = 1281, Y = 32, Width = 30, Height = 30 },\n};\n\nroot.Sprites[\"ScrollbarTrack\"] = {\n    Sheet = \"Backlog\",\n    Bounds = { X = 1500, Y = 0, Width = 8, Height = 567 },\n};"
  },
  {
    "path": "profiles/darling/hud/datedisplay.lua",
    "content": "local sheet = \"Data\";\nlocal name = \"Date\";\n\nlocal yearNumFirstX = 1525;\nlocal yearNumFirstY = 63;\nlocal yearNumWidth = 25;\nlocal yearNum1Width = 8;\nlocal yearNumHeight = 20;\n\nlocal numFirstX = 1524;\nlocal numFirstY = 33;\nlocal numWidth = 40;\nlocal num1Width = 12;\nlocal numHeight = 28;\n\nlocal weekFirstX = 1785;\nlocal weekFirstY = 63;\nlocal weekSecondX = 1525;\nlocal weekSecondY = 85;\nlocal weekWidth = 73;\nlocal weekFriWidth = 56;\nlocal weekHeight = 20;\n\nroot.DateDisplay = {\n    Type = DateDisplayType.RNE,\n    YearNumSprites = {},\n    MonthNumSprites = {},\n    DayNumSprites = {},\n    WeekSprites = {},\n    BackgroundStartPos = { X = 1088, Y = 73 },\n    BackgroundEndPos = { X = 1088 - 256, Y = 73 },\n    DateStartX = 1167,\n    YearWeekY = 60,\n    MonthDayY = 52,\n    Spacing = 1,\n    FadeInDuration = 0.5,\n    FadeOutDuration = 0.5\n};\n\nfor i = 0, 9 do\n    local yearW;\n    if i == 1 then\n        yearW = yearNum1Width;\n    else\n        yearW = yearNumWidth;\n    end\n\n    root.Sprites[name .. \"YearNum\" .. i] = {\n        Sheet = sheet,\n        Bounds = {\n            X = yearNumFirstX,\n            Y = yearNumFirstY,\n            Width = yearW,\n            Height = yearNumHeight\n        }\n    };\n    root.DateDisplay.YearNumSprites[#root.DateDisplay.YearNumSprites + 1] = name .. \"YearNum\" .. i;\n\n    local numW\n    if i == 1 then\n        numW = num1Width;\n    else\n        numW = numWidth;\n    end\n\n    root.Sprites[name .. \"Num\" .. i] = {\n        Sheet = sheet,\n        Bounds = {\n            X = numFirstX,\n            Y = numFirstY,\n            Width = numW,\n            Height = numHeight\n        }\n    };\n    root.DateDisplay.MonthNumSprites[#root.DateDisplay.MonthNumSprites + 1] = name .. \"Num\" .. i;\n    root.DateDisplay.DayNumSprites[#root.DateDisplay.DayNumSprites + 1] = name .. \"Num\" .. i;\n\n    if i == 1 then\n        yearNumFirstX = yearNumFirstX + yearNum1Width;\n        numFirstX = numFirstX + num1Width;\n    else\n        yearNumFirstX = yearNumFirstX + yearNumWidth;\n        numFirstX = numFirstX + numWidth;\n    end\nend\n\nfor i = 0, 6 do\n    if i > 1 then\n        local weekW;\n        if i == 5 then\n            weekW = weekFriWidth;\n        else\n            weekW = weekWidth;\n        end\n\n        root.Sprites[name .. \"Week\" .. i] = {\n            Sheet = sheet,\n            Bounds = {\n                X = weekSecondX,\n                Y = weekSecondY,\n                Width = weekW,\n                Height = weekHeight\n            }\n        };\n        if i == 5 then\n            weekSecondX = weekSecondX + weekFriWidth;\n        else\n            weekSecondX = weekSecondX + weekWidth;\n         end\n    else\n        root.Sprites[name .. \"Week\" .. i] = {\n            Sheet = sheet,\n            Bounds = {\n                X = weekFirstX,\n                Y = weekFirstY,\n                Width = weekWidth,\n                Height = weekHeight\n            }\n        };\n        weekFirstX = weekFirstX + weekWidth;\n    end\n    root.DateDisplay.WeekSprites[#root.DateDisplay.WeekSprites + 1] = name .. \"Week\" .. i;\nend\n\nroot.Sprites[name .. \"MonthDaySeparator\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 1897,\n        Y = 33,\n        Width = 10,\n        Height = 28\n    }\n};\nroot.DateDisplay.MDSeparatorSprite = name .. \"MonthDaySeparator\";\n\nroot.Sprites[name .. \"DayYearSeparator\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 1758,\n        Y = 63,\n        Width = 8,\n        Height = 20\n    }\n};\nroot.DateDisplay.DYSeparatorSprite = name .. \"DayYearSeparator\";\n\nroot.Sprites[name .. \"OpenBracket\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 1766,\n        Y = 63,\n        Width = 8,\n        Height = 20\n    }\n};\nroot.DateDisplay.OpenBracketSprite = name .. \"OpenBracket\";\n\nroot.Sprites[name .. \"CloseBracket\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 1776,\n        Y = 63,\n        Width = 8,\n        Height = 20\n    }\n};\nroot.DateDisplay.CloseBracketSprite = name .. \"CloseBracket\";\n\nroot.Sprites[name .. \"Background\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 1525,\n        Y = 1,\n        Width = 450,\n        Height = 28\n    }\n};\nroot.DateDisplay.BackgroundSprite = name .. \"Background\";"
  },
  {
    "path": "profiles/darling/hud/loadingdisplay.lua",
    "content": "root.LoadingDisplay = {\n    ResourceLoadBgAnim = \"ResourceLoadingBg\",\n    SaveLoadBgAnim = \"SaveLoadingBg\",\n    LoadingIconAnim = \"LoadingDisc\",\n    LoadingTextAnim = \"LoadingText\",\n    ResourceBgPos = { X = 1074, Y = 611 },\n    SaveBgPos = { X = 1019, Y = 598 },\n    IconPos = { X = 986, Y = 608 },\n    TextPos = { X = 1025, Y = 628 },\n    FadeInDuration = 0.66,\n    FadeOutDuration = 0.33\n};\n\nMakeAnimation({\n    Name = \"ResourceLoadingBg\",\n    Sheet = \"Data\",\n    FirstFrameX = 555,\n    FirstFrameY = 544,\n    FrameWidth = 206,\n    ColWidth = 206,\n    FrameHeight = 58,\n    RowHeight = 60,\n    Frames = 8,\n    Duration = 0.8,\n    Rows = 8,\n    Columns = 1,\n    PrimaryDirection = AnimationDirections.Down\n});\n\nMakeAnimation({\n    Name = \"SaveLoadingBg\",\n    Sheet = \"Data\",\n    FirstFrameX = 388,\n    FirstFrameY = 0,\n    FrameWidth = 277,\n    ColWidth = 279,\n    FrameHeight = 81,\n    RowHeight = 83,\n    Frames = 4,\n    Duration = 0.8,\n    Rows = 2,\n    Columns = 2,\n    PrimaryDirection = AnimationDirections.Down,\n    SecondaryDirection = AnimationDirections.Right\n});\n\nMakeAnimation({\n    Name = \"LoadingDisc\",\n    Sheet = \"Data\",\n    FirstFrameX = 172,\n    FirstFrameY = 1,\n    FrameWidth = 60,\n    ColWidth = 62,\n    FrameHeight = 60,\n    RowHeight = 62,\n    Frames = 3,\n    Duration = 0.8,\n    Rows = 1,\n    Columns = 3,\n    PrimaryDirection = AnimationDirections.Right\n});\nroot.Animations[\"LoadingDisc\"].Frames[#root.Animations[\"LoadingDisc\"].Frames + 1] = \"LoadingDisc1\";\n\nMakeAnimation({\n    Name = \"LoadingText\",\n    Sheet = \"Data\",\n    FirstFrameX = 173,\n    FirstFrameY = 91,\n    FrameWidth = 214,\n    ColWidth = 214,\n    FrameHeight = 21,\n    RowHeight = 23,\n    Frames = 5,\n    Duration = 0.8,\n    Rows = 5,\n    Columns = 1,\n    PrimaryDirection = AnimationDirections.Down\n});"
  },
  {
    "path": "profiles/darling/hud/saveicon.lua",
    "content": "root.SaveIcon = {\n    ForegroundAnimation = \"SaveIcon\",\n    DefaultPosition = { X = 1153, Y = 23 },\n    BackgroundSprite = \"SaveIconBg\",\n    BackgroundOffset = { X = -7, Y = -4 },\n    BackgroundMaxAlpha = 0.5,\n    FadeInDuration = 0.5,\n    FadeOutDuration = 0.25\n};\n\nMakeAnimation({\n    Name = \"SaveIcon\",\n    Sheet = \"Data\",\n    FirstFrameX = 1977,\n    FirstFrameY = 1,\n    FrameWidth = 70,\n    ColWidth = 70,\n    FrameHeight = 70,\n    RowHeight = 72,\n    Frames = 8,\n    Duration = 0.4,\n    Rows = 8,\n    Columns = 1,\n    PrimaryDirection = AnimationDirections.Down\n});\n\nroot.Sprites[\"SaveIconBg\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 1439, Y = 1, Width = 84, Height = 84 }\n};"
  },
  {
    "path": "profiles/darling/hud/selectiondisplay.lua",
    "content": "root.SelectionDisplay = {\n    DrawType = DrawComponentType.Text,\n    SelectionBackgroundSprite = \"SelectionBackground\",\n    PlainSelectionFrameTopLeftSprite = \"PlainSelectionFrameTopLeft\",\n    PlainSelectionFrameTopSideSprite = \"PlainSelectionFrameTopSide\",\n    PlainSelectionFrameTopRightSprite = \"PlainSelectionFrameTopRight\",\n    PlainSelectionFrameLeftSideSprite = \"PlainSelectionFrameLeftSide\",\n    PlainSelectionFrameBottomLeftSprite = \"PlainSelectionFrameBottomLeft\",\n    PlainSelectionFrameRightSideSprite = \"PlainSelectionFrameRightSide\",\n    PlainSelectionFrameBottomRightSprite = \"PlainSelectionFrameBottomRight\",\n    PlainSelectionFrameBottomSideSprite = \"PlainSelectionFrameBottomSide\",\n    PlainSelectionFrameMiddleSprite = \"PlainSelectionFrameMiddle\",\n    SelectionHighlightSprite = \"SelectionHighlight\",\n    SelectionMaxCount = 5,\n    SelectionBackgroundX = 228,\n    SelectionBackgroundY = {277, 277, 196, 129, 95},\n    SelectionYSpacing = 81,\n    PlainSelectionYSpacing = 10,\n    FadeAnimationDurationInOut = 0.2\n};\n\nroot.Sprites[\"SelectionBackground\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 1, Y = 535, Width = 829, Height = 69 }\n};\n\nroot.Sprites[\"PlainSelectionFrameTopLeft\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 0, Y = 0, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameTopSide\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 16, Y = 0, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameTopRight\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 32, Y = 0, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameLeftSide\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 0, Y = 16, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameBottomLeft\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 0, Y = 32, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameRightSide\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 32, Y = 16, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameBottomRight\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 32, Y = 32, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameBottomSide\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 16, Y = 32, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameMiddle\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 16, Y = 16, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"SelectionHighlight\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 65, Y = 33, Width = 94, Height = 30 }\n};\n"
  },
  {
    "path": "profiles/darling/hud/sysmesboxdisplay.lua",
    "content": "local sheet = \"Data\";\nlocal name = \"SysMesBox\";\n\nroot.SysMesBoxDisplay = {\n    Type = SysMesBoxType.Darling,\n    DrawType = DrawComponentType.SystemMessage,\n    BoxX = 602,\n    BoxY = 224,\n    BoxMiddleBaseX = 604,\n    BoxMinimumWidth = 768,\n    BoxMiddleBaseWidth = 434,\n    BoxRightBaseWidth = 24,\n    BoxRightRemainPad = 48,\n    TextFontSize = 32,\n    TextMiddleY = 236,\n    TextX = 640,\n    TextLineHeight = 34,\n    TextMarginY = 14,\n    ChoicePadding = 40,\n    ChoiceY = 365,\n    ChoiceXBase = 680,\n    MinMaxMesWidth = 294,\n    AnimationSpeed = 55,\n    FadeInDuration = 0.33,\n    FadeOutDuration = 0.25\n};\n\nroot.Sprites[\"SelectionHighlight\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 65, Y = 33, Width = 94, Height = 30 }\n};\nroot.SysMesBoxDisplay.SelectionHighlight = \"SelectionHighlight\";\n\nroot.Sprites[name .. \"BoxPartLeft\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 1,\n        Y = 842,\n        Width = 48,\n        Height = 182\n    }\n};\nroot.SysMesBoxDisplay.BoxPartLeft = name .. \"BoxPartLeft\";\n\nroot.Sprites[name .. \"BoxPartRight\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 483,\n        Y = 842,\n        Width = 48,\n        Height = 182\n    }\n};\nroot.SysMesBoxDisplay.BoxPartRight = name .. \"BoxPartRight\";\n\nroot.Sprites[name .. \"BoxPartMiddle\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 49,\n        Y = 842,\n        Width = 434,\n        Height = 182\n    }\n};\nroot.SysMesBoxDisplay.BoxPartMiddle = name .. \"BoxPartMiddle\";\n\nroot.Sprites[name .. \"BoxDecoration\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 1383,\n        Y = 117,\n        Width = 592,\n        Height = 8\n    }\n};\nroot.SysMesBoxDisplay.BoxDecoration = name .. \"BoxDecoration\";"
  },
  {
    "path": "profiles/darling/hud/systemmenu.lua",
    "content": "root.SystemMenu = {\n    Type = SystemMenuType.None,\n    DrawType = DrawComponentType.SystemMenu,\n    ButtonBackgroundSprite = \"SystemMenuButtonBackground\",\n    ButtonPromptsSprite = \"SystemMenuButtonPrompts\",\n    ButtonBackgroundStartX = 1257,\n    ButtonBackgroundX = 0,\n    ButtonBackgroundY = 681,\n    ButtonBackgroundTargetWidth = 943,\n    ButtonBackgroundSprStartX = 1499,\n    MenuEntriesSprites = {},\n    MenuEntriesHighlightedSprites = {},\n    MenuEntriesX = 0,\n    MenuEntriesXOffset = 100,\n    MenuEntriesFirstY = 220,\n    MenuEntriesYPadding = 50,\n};\n\nfor i = 0, 7 do\n    root.Sprites[\"SystemMenuEntry\" .. i] = {\n        Sheet = \"Data\",\n        Bounds = {\n            X = 964,\n            Y = 296 + i * 38,\n            Width = root.SystemMenu.MenuEntriesTargetWidth,\n            Height = 26\n        },\n        BaseScale = { X = 1280 / 960, Y = 720 / 544 }\n    };\n    root.SystemMenu.MenuEntriesSprites[#root.SystemMenu.MenuEntriesSprites + 1] = \"SystemMenuEntry\" .. i;\nend\n\nfor i = 0, 7 do\n    root.Sprites[\"SystemMenuEntryHighlighted\" .. i] = {\n        Sheet = \"Data\",\n        Bounds = {\n            X = 964,\n            Y = 2 + i * 38,\n            Width = root.SystemMenu.MenuEntriesTargetWidth,\n            Height = 26\n        },\n        BaseScale = { X = 1280 / 960, Y = 720 / 544 }\n    };\n    root.SystemMenu.MenuEntriesHighlightedSprites[#root.SystemMenu.MenuEntriesHighlightedSprites + 1] = \"SystemMenuEntryHighlighted\" .. i;\nend\n\nroot.Sprites[\"SystemMenuBackground\"] = {\n    Sheet = \"Menu\",\n    Bounds = { X = 0, Y = 0, Width = 960, Height = 544 },\n    BaseScale = { X = 1280 / 960, Y = 720 / 544 }\n};\n\nroot.Sprites[\"SystemMenuButtonBackground\"] = {\n    Sheet = \"Menu\",\n    Bounds = { X = root.SystemMenu.ButtonBackgroundSprStartX, Y = 975, Width = 0, Height = 30 },\n    BaseScale = { X = 1280 / 960, Y = 720 / 544 }\n};\n\nroot.Sprites[\"SystemMenuButtonPrompts\"] = {\n    Sheet = \"Menu\",\n    Bounds = { X = 1231, Y = 528, Width = 400, Height = 21 },\n    BaseScale = { X = 1280 / 960, Y = 720 / 544 }\n};"
  },
  {
    "path": "profiles/darling/hud/tipsmenu.lua",
    "content": "root.TipsMenu = {\n    Type = TipsMenuType.None\n};\n"
  },
  {
    "path": "profiles/darling/hud/tipsnotification.lua",
    "content": "root.TipsNotification = {\n    Type = TipsNotificationType.None,\n};\n"
  },
  {
    "path": "profiles/darling/hud/titlemenu.lua",
    "content": "root.TitleMenu = {\n    Type = TitleMenuType.CHLCC,\n    DrawType = DrawComponentType.SystemMenu,\n    PressToStartX = 72,\n    PressToStartY = 595,\n    PressToStartAnimDurationIn = 0.5,\n    PressToStartAnimDurationOut = 0.5,\n    PressToStartSprite = \"TitleMenuPressToStart\",\n    IntroBackgroundSprite = \"TitleMenuIntroBackground\",\n    BackgroundSprite = \"TitleMenuBackground\",\n    DelusionADVUnderSprite = \"DelusionADVUnder\", -- \"DelusionADVUnderEnglish\" with the TLed assets, \"DelusionADVUnder\" with the original ones\n    DelusionADVUnderX = 78, --74 with the TLed assets, 78 with the original ones\n    DelusionADVUnderY = 394, --396 with the TLed assets, 394 with the original ones\n    DelusionADVSprite = \"DelusionADV\", -- \"DelusionADVEnglish\" with the TLed assets, \"DelusionADV\" with the original ones\n    DelusionADVX = 78, --74 with the TLed assets, 78 with the original ones\n    DelusionADVY = 394, --396 with the TLed assets, 394 with the original ones\n    SeiraUnderSprite = \"SeiraUnder\",\n    SeiraUnderX = 733,\n    SeiraUnderY = 0,\n    SeiraSprite = \"Seira\",\n    SeiraX = 728,\n    SeiraY = -47,\n    CHLogoSprite = \"CHLogo\",\n    CHLogoX = 61,\n    CHLogoY = 279,\n    LCCLogoUnderSprite = \"LCCLogoUnder\",\n    LCCLogoUnderX = 241,\n    LCCLogoUnderY = 327,\n    ChuLeftLogoSprite = \"ChuLeftLogo\",\n    ChuLeftLogoX = 353,\n    ChuLeftLogoY = 336,\n    ChuRightLogoSprite = \"ChuRightLogo\",\n    ChuRightLogoX = 500,\n    ChuRightLogoY = 316,\n    LoveLogoSprite = \"LoveLogo\",\n    LoveLogoX = 235, --231 with the TLed assets, 235 with the original ones\n    LoveLogoY = 336, --335 with the TLed assets, 336 with the original ones\n    StarLogoSprite = \"StarLogo\",\n    StarLogoX = 465,\n    StarLogoY = 316,\n    ExclMarkLogoSprite = \"ExclMarkLogo\",\n    ExclMarkLogoX = 614,\n    ExclMarkLogoY = 316,\n    CopyrightTextSprite = \"CopyrightText\",\n    CopyrightTextX = 72,\n    CopyrightTextY = 675,\n    SpinningCircleSprite = \"SpinningCircle\",\n    SpinningCircleX = 610.5,\n    SpinningCircleY = -285.5,\n    SpinningCircleAnimationDuration = 15,\n    ItemHighlightSprite = \"TitleMenuItemHighlight\",\n    ItemHighlightOffsetX = 73,\n    ItemHighlightOffsetY = 7,\n    ItemPadding = 40,\n    ItemYBase = 69,\n    ItemFadeInDuration = 0.3,\n    ItemFadeOutDuration = 0.6,\n    SecondaryItemFadeInDuration = 0.2,\n    SecondaryItemFadeOutDuration = 0.2,\n    PrimaryFadeInDuration = 0.3,\n    PrimaryFadeOutDuration = 0.3,\n    SecondaryFadeInDuration = 0.512,\n    SecondaryFadeOutDuration = 0.512,\n    ItemHyperUpLine = \"TitleMenuItemHyperUpLine\",\n    ItemSuperUpLine = \"TitleMenuItemSuperUpLine\",\n    ItemUpLine = \"TitleMenuItemUpLine\",\n    ItemStraightLine = \"TitleMenuItemStraightLine\",\n    ItemDownLine = \"TitleMenuItemDownLine\",\n    ItemSuperDownLine = \"TitleMenuItemSuperDownLine\",\n    ItemLoadQuickSprite = \"TitleMenuItemLoadQuick\",\n    SecondaryItemX = 320,\n    ItemLoadY = 109,\n    ItemLoadQuickY = 83,\n    ItemLoadSprite = \"TitleMenuItemLoad\",\n    ItemLoadQuickHighlightedSprite = \"TitleMenuItemLoadQuickHighlighted\",\n    ItemLoadHighlightedSprite = \"TitleMenuItemLoadHighlighted\",\n    SecondaryItemHighlightSprite = \"TitleMenuSecondaryItemHighlight\",\n    ItemClearListY = 71,\n    ItemCGLibraryY = 97,\n    ItemSoundLibraryY = 123,\n    ItemMovieLibraryY = 149,\n    ItemTipsY = 175,\n    ItemTrophyY = 201,\n    ItemConfigY = 163,\n    ItemSystemSaveY = 189,\n    SecondaryItemHighlightX = 286,\n    SecondaryMenuPaddingY = 26,\n    SecondaryMenuLoadOffsetY = 76,\n    SecondaryMenuLineX = 241,\n    SecondaryMenuLoadLineY = 93,\n    SecondaryMenuLoadQuickLineY = 119,\n    SecondaryMenuExtraClearY = 81,\n    SecondaryMenuExtraCGY = 107,\n    SecondaryMenuExtraSoundY = 133,\n    SecondaryMenuExtraMovieY = 159,\n    SecondaryMenuExtraTipsY = 159,\n    SecondaryMenuExtraTrophyY = 159,\n    SecondaryMenuSystemConfigY = 173,\n    SecondaryMenuSystemSaveY = 199,\n    MenuEntriesNum = 14,\n    MenuEntriesSprites = {},\n    MenuEntriesHighlightedSprites = {},\n    LineNum = 6,\n    LineEntriesSprites = {}\n};\n\nfor i = 0, 3 do\n    root.Sprites[\"TitleMenuEntry\" .. i] = {\n        Sheet = \"Title\",\n        Bounds = {\n            X = 1151,\n            Y = 101 + i * 25,\n            Width = 188,\n            Height = 23\n        }\n    };\n    root.TitleMenu.MenuEntriesSprites[#root.TitleMenu.MenuEntriesSprites + 1] = \"TitleMenuEntry\" .. i;\nend\n\nfor i = 0, 9 do\n    root.Sprites[\"TitleMenuEntry\" .. (i + 4)] = {\n        Sheet = \"Title\",\n        Bounds = {\n            X = 1369,\n            Y = 684 + i * 22,\n            Width = 216,\n            Height = 20\n        }\n    };\n    root.TitleMenu.MenuEntriesSprites[#root.TitleMenu.MenuEntriesSprites + 1] = \"TitleMenuEntry\" .. (i + 4);\nend\n\nfor i = 0, 3 do\n    root.Sprites[\"TitleMenuEntryHighlighted\" .. i] = {\n        Sheet = \"Title\",\n        Bounds = {\n            X = 1151,\n            Y = 1 + i * 25,\n            Width = 188,\n            Height = 23\n        }\n    };\n    root.TitleMenu.MenuEntriesHighlightedSprites[#root.TitleMenu.MenuEntriesHighlightedSprites + 1] = \"TitleMenuEntryHighlighted\" .. i;\nend\n\nfor i = 0, 9 do\n    root.Sprites[\"TitleMenuEntryHighlighted\" .. (i + 4)] = {\n        Sheet = \"Title\",\n        Bounds = {\n            X = 1151,\n            Y = 684 + i * 22,\n            Width = 216,\n            Height = 20\n        }\n    };\n    root.TitleMenu.MenuEntriesHighlightedSprites[#root.TitleMenu.MenuEntriesHighlightedSprites + 1] = \"TitleMenuEntryHighlighted\" .. (i + 4);\nend\n\nroot.Sprites[\"TitleMenuPressToStart\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 1, Y = 921, Width = 313, Height = 28 },\n};\n\nroot.Sprites[\"DelusionADVUnder\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 1863, Y = 772, Width = 163, Height = 27 },\n};\n\nroot.Sprites[\"DelusionADVUnderEnglish\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 1862, Y = 785, Width = 157, Height = 37 },\n};\n\nroot.Sprites[\"DelusionADV\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 1863, Y = 728, Width = 163, Height = 27 },\n};\n\nroot.Sprites[\"DelusionADVEnglish\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 1862, Y = 734, Width = 153, Height = 33 },\n};\n\nroot.Sprites[\"SeiraUnder\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 555, Y = 1, Width = 594, Height = 768 },\n};\n\nroot.Sprites[\"Seira\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 1, Y = 1, Width = 552, Height = 768 },\n};\n\nroot.Sprites[\"CHLogo\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 1, Y = 771, Width = 594, Height = 115 },\n};\n\nroot.Sprites[\"LCCLogoUnder\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 597, Y = 771, Width = 462, Height = 122 },\n};\n\nroot.Sprites[\"ChuLeftLogo\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 483, Y = 915, Width = 136, Height = 108 },\n};\n\nroot.Sprites[\"ChuRightLogo\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 693, Y = 895, Width = 136, Height = 128 },\n};\n\nroot.Sprites[\"LoveLogo\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 341, Y = 915, Width = 140, Height = 108 },\n};\n\nroot.Sprites[\"StarLogo\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 621, Y = 895, Width = 70, Height = 128 },\n};\n\nroot.Sprites[\"ExclMarkLogo\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 831, Y = 895, Width = 82, Height = 128 },\n};\n\nroot.Sprites[\"CopyrightText\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 193, Y = 891, Width = 380, Height = 24 },\n};\n\nroot.Sprites[\"SpinningCircle\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 1366, Y = 1, Width = 681, Height = 681 },\n};\n\nroot.Sprites[\"TitleMenuIntroBackground\"] = {\n    Sheet = \"TitleBg1\",\n    Bounds = { X = 0, Y = 0, Width = 1280, Height = 720 },\n};\n\nroot.Sprites[\"TitleMenuBackground\"] = {\n    Sheet = \"TitleBg2\",\n    Bounds = { X = 0, Y = 0, Width = 1280, Height = 720 },\n};\n\nroot.Sprites[\"TitleMenuItemHighlight\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 915, Y = 951, Width = 244, Height = 36 },\n};\n\nroot.Sprites[\"TitleMenuItemHyperUpLine\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 1805, Y = 686, Width = 51, Height = 80 },\n};\n\nroot.Sprites[\"TitleMenuItemSuperUpLine\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 1805, Y = 776, Width = 51, Height = 54 },\n};\n\nroot.Sprites[\"TitleMenuItemUpLine\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 1805, Y = 845, Width = 51, Height = 28 },\n};\n\nroot.Sprites[\"TitleMenuItemStraightLine\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 1805, Y = 888, Width = 51, Height = 2 },\n};\n\nroot.Sprites[\"TitleMenuItemDownLine\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 1805, Y = 910, Width = 51, Height = 28 },\n};\n\nroot.Sprites[\"TitleMenuItemSuperDownLine\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 1805, Y = 959, Width = 51, Height = 54 },\n};\n\nroot.TitleMenu.LineEntriesSprites[#root.TitleMenu.LineEntriesSprites + 1] = \"TitleMenuItemHyperUpLine\";\nroot.TitleMenu.LineEntriesSprites[#root.TitleMenu.LineEntriesSprites + 1] = \"TitleMenuItemSuperUpLine\";\nroot.TitleMenu.LineEntriesSprites[#root.TitleMenu.LineEntriesSprites + 1] = \"TitleMenuItemUpLine\";\nroot.TitleMenu.LineEntriesSprites[#root.TitleMenu.LineEntriesSprites + 1] = \"TitleMenuItemStraightLine\";\nroot.TitleMenu.LineEntriesSprites[#root.TitleMenu.LineEntriesSprites + 1] = \"TitleMenuItemDownLine\";\nroot.TitleMenu.LineEntriesSprites[#root.TitleMenu.LineEntriesSprites + 1] = \"TitleMenuItemSuperDownLine\";\n\nroot.Sprites[\"TitleMenuItemLoadQuick\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 1369, Y = 684, Width = 216, Height = 20 },\n};\n\nroot.Sprites[\"TitleMenuItemLoad\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 1369, Y = 706, Width = 216, Height = 20 },\n};\n\nroot.Sprites[\"TitleMenuItemLoadQuickHighlighted\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 1151, Y = 684, Width = 216, Height = 20 },\n};\n\nroot.Sprites[\"TitleMenuItemLoadHighlighted\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 1151, Y = 706, Width = 216, Height = 20 },\n};\n\nroot.Sprites[\"TitleMenuSecondaryItemHighlight\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 915, Y = 989, Width = 285, Height = 34 },\n};\n"
  },
  {
    "path": "profiles/darling/savedata.lua",
    "content": "root.SaveData = {\n    Type = SaveDataType.None,\n    SaveFilePath = \"games/darling/savedata/SYSTEM.DAT\",\n};"
  },
  {
    "path": "profiles/darling/scriptvars.lua",
    "content": "local sv = root.ScriptVars;\n\nsv.SW_BG1POSX_OFS = 2300;\nsv.SW_BG1POSY_OFS = 2301;\nsv.SW_BG1SX_OFS = 2302;\nsv.SW_BG1SY_OFS = 2303;\nsv.SW_BG1SIZE_OFS = 2304;\nsv.SW_BG1LX_OFS = 2305;\nsv.SW_BG1LY_OFS = 2306;\nsv.SW_BG1ALPHA_OFS = 2308;\nsv.SW_CHA1POSX_OFS = 2400;\nsv.SW_CHA1POSY_OFS = 2401;\nsv.SW_CHA1ALPHA_OFS = 2407;\nsv.SW_MASK1COLOR = 4400;\nsv.SW_MASK1ALPHA = 4401;\nsv.SW_MASK1PRI = 4402;\nsv.SW_MASK1POSX = 4403;\nsv.SW_MASK1POSY = 4404;\nsv.SW_MASK1SIZEX = 4405;\nsv.SW_MASK1SIZEY = 4406;"
  },
  {
    "path": "profiles/darling/sprites.lua",
    "content": "root.SpriteSheets = {\n    [\"Data\"] = {\n        Path = { Mount = \"system\", Id = 10 },\n        DesignWidth = 2048,\n        DesignHeight = 1024\n    },\n    [\"Font\"] = {\n        Path = { Mount = \"system\", Id = 14 },\n        DesignWidth = 2048,\n        DesignHeight = 1376\n    },\n    [\"Menu\"] = {\n        Path = { Mount = \"system\", Id = 8 },\n        DesignWidth = 2048,\n        DesignHeight = 1024\n    },\n    [\"Title\"] = {\n        Path = { Mount = \"system\", Id = 21 },\n        DesignWidth = 2048,\n        DesignHeight = 1024\n    },\n    [\"TitleBg1\"] = {\n        Path = { Mount = \"system\", Id = 19 },\n        DesignWidth = 1280,\n        DesignHeight = 720\n    },\n    [\"TitleBg2\"] = {\n        Path = { Mount = \"system\", Id = 20 },\n        DesignWidth = 1280,\n        DesignHeight = 720\n    },\n    [\"Backlog\"] = {\n        Path = {Mount = \"system\", Id = 6 },\n        DesignWidth = 2048,\n        DesignHeight = 720\n    }\n};\n\nroot.Sprites = {};"
  },
  {
    "path": "profiles/darling/tipssystem.lua",
    "content": "root.TipsSystem = {\n    Type = TipsSystemType.None,\n};"
  },
  {
    "path": "profiles/darling/vfs.lua",
    "content": "root.Vfs = {\n    Mounts = {\n        [\"script\"] = {\"games/darling/gamedata/script.cls\"},\n        [\"system\"] = {\"games/darling/gamedata/system.cpk\"},\n        [\"bgm\"] = {\"games/darling/gamedata/bgm.cpk\"},\n        [\"se\"] = {\"games/darling/gamedata/se.cpk\"},\n        [\"voice\"] = {\"games/darling/gamedata/voice.cpk\"},\n        [\"bg\"] = {\"games/darling/gamedata/bg.cpk\"},\n        [\"chara\"] = {\"games/darling/gamedata/chara.cpk\"},\n        [\"mask\"] = {\"games/darling/gamedata/mask.cpk\"},\n        [\"movie\"] = {\"games/darling/gamedata/movie.cpk\"}\n    }\n};"
  },
  {
    "path": "profiles/dash/charset.lua",
    "content": "root.Charset = {\n    Flags = {}\n};\n\nfor i = 0, (64 * 50) - 1 do root.Charset.Flags[i] = 0; end\n\nlocal spaces = {[0]=0, 63};\nfor i = 0, #spaces do\n    root.Charset.Flags[spaces[i]] = root.Charset.Flags[spaces[i]] | CharacterTypeFlags.Space;\nend\n\n-- 、 。 ． ， ？ ！ 〜 ” ー ） 〕 ］ ｝ 〉 》 」 』 】☆ ★ ♪ 々 ぁ ぃ ぅ ぇ\n-- ぉ っ ゃ ゅ ょ ァ ィ ゥ ェ ォ ッ ャ ュ ョ –\nlocal wordEndingPuncts = {\n    [0]=0x00BE, 0x00BF, 0x00C1,\n    0x00C0, 0x00C4, 0x00C5,\n    0x00E4, 0x00CB, 0x00E5,\n    0x00CD, 0x00CF, 0x00D1,\n    0x00D3, 0x00D5, 0x00D7,\n    0x00D9, 0x00DB, 0x00DD,\n    0x01A5, 0x01A6, 0x00E6,\n    0x0187, 0x00E8, 0x00E9,\n    0x00EA, 0x00EB, 0x00EC,\n    0x00ED, 0x00EE, 0x00EF,\n    0x00F0, 0x00F2, 0x00F3,\n    0x00F4, 0x00F5, 0x00F6,\n    0x00F7, 0x00F8, 0x00F9,\n    0x00FA, 0x0113\n};\nfor i = 0, #wordEndingPuncts do\n    root.Charset.Flags[wordEndingPuncts[i]] = root.Charset.Flags[wordEndingPuncts[i]] | CharacterTypeFlags.WordEndingPunct;\nend\n\n-- space(63) space(0) “ （ 〔 ［ ｛ 〈 《 「\nlocal wordStartingPuncts = {\n    [0]=63, 0, 0x00CA,\n    0x00CC, 0x00CE, 0x00D0,\n    0x00D2, 0x00D4, 0x00D6,\n    0x00D8, 0x00DA, 0x00DC\n};\nfor i = 0, #wordStartingPuncts do\n    root.Charset.Flags[wordStartingPuncts[i]] = root.Charset.Flags[wordStartingPuncts[i]] | CharacterTypeFlags.WordStartingPunct;\nend"
  },
  {
    "path": "profiles/dash/config.lua",
    "content": "root.Language = \"Japanese\";\nroot.ResolutionWidth = 1920;\nroot.ResolutionHeight = 1080;\nroot.Fullscreen = false;\nroot.DateFormat = DateFormatType.YMD;"
  },
  {
    "path": "profiles/dash/dialogue.lua",
    "content": "root.Sprites[\"ADVBox\"] = {\n    Sheet = \"MesBox\",\n    Bounds = { X = 0, Y = 864, Width = 1920, Height = 281 }\n};\n\nroot.Dialogue = {\n    REVBounds = { X = 0, Y = 0, Width = 960, Height = 400 },\n    REVNameFontSize = 24,\n    REVColor = 0,\n    REVNameColor = 24,\n    REVNameOffset = 34,\n    REVNameLocation = REVNameLocationType.TopLeft,\n    REVOutlineMode = 2,\n    REVNameOutlineMode = 2,\n    NVLBounds = { X = 188, Y = 128, Width = 1536, Height = 600 },\n    ADVBounds = { X = 295, Y = 828, Width = 1440, Height = 270 },\n    ADVBoxSprite = \"ADVBox\",\n    ADVBoxPos = { X = 0, Y = 781 },\n    FadeOutDuration = 0.33,\n    FadeInDuration = 0.33,\n    TextFadeInDuration = 0.33,\n    TextFadeOutDuration = 0.33,\n    DialogueBoxCurrentType = DialogueBoxType.Plain,\n    NVLBoxMaxOpacity = 0.55,\n\n    ADVNameAlignment = TextAlignment.Left,\n    ADVNameFontSize = 33,\n    ADVNamePos = { X = 173, Y = 773 },\n\n    NametagCurrentType = NametagType.ThreePiece,\n    NametagPosition = { X = 64, Y = 768 },\n    NametagLeftSprite = \"NametagLeftSprite\",\n    NametagMiddleSprite = \"NametagMiddleSprite\",\n    NametagRightSprite = \"NametagRightSprite\",\n    NametagMiddleBaseWidth = 0.0,\n\n    WaitIconSpriteAnim = \"WaitIconSpriteAnimDef\",\n    WaitIconCurrentType = WaitIconType.SpriteAnim,\n    WaitIconOffset = { X = 0, Y = 0 },\n    DialogueFont = \"Default\",\n    SetFontSizeRatio = 1000.0,\n    DefaultFontSize = 48,\n    RubyFontSize = 21,\n    RubyYOffset = -21,\n    ColorTable = {\n        {0xFFFFFF, 0x000000}, {0x5080FF, 0x000000},\n        {0xFF7080, 0x000000}, {0xFFA0F8, 0x000000},\n        {0x46FF80, 0x000000}, {0x90FFFF, 0x000000},\n        {0xFFFF70, 0x000000}, {0x80FFC0, 0x000000},\n        {0xFFB080, 0x000000}, {0xB080FF, 0x000000},\n        {0x000000, 0x808080}, {0x000000, 0x5080FF},\n        {0x000000, 0xFF7080}, {0x000000, 0xFFA0F8},\n        {0x000000, 0x268840}, {0x000000, 0x409999},\n        {0x000000, 0x888830}, {0x000000, 0x80FFC0},\n        {0x000000, 0xFFB080}, {0x000000, 0xB080FF},\n        {0xD0D0D0, 0x000000}, {0xD0D0FF, 0x000000},\n        {0xFFD0D0, 0x000000}, {0xFFD0FF, 0x000000},\n        {0xD0FFD0, 0x000000}, {0xD0FFFF, 0x000000},\n        {0xFFFFD0, 0x000000}, {0xE8FFD0, 0x000000},\n        {0xFFE8D0, 0x000000}, {0xD0E8FF, 0x000000},\n        {0xFFFFFF, 0x808080}, {0xFFFFFF, 0x5080FF},\n        {0xFFFFFF, 0xFF7080}, {0xFFFFFF, 0xFFA0F8},\n        {0xFFFFFF, 0x46FF80}, {0xFFFFFF, 0x90FFFF},\n        {0xFFFFFF, 0xFFFF70}, {0xFFFFFF, 0x80FFC0},\n        {0xFFFFFF, 0xFFB080}, {0xFFFFFF, 0xB080FF},\n        {0xFFEEEE, 0x000000}, {0xFFCCCC, 0x000000},\n        {0xFFAAAA, 0x000000}, {0xFF9999, 0x000000},\n        {0xFF8888, 0x000000}, {0xFFFF00, 0x000000},\n        {0xFEF000, 0x000000}, {0xFF7777, 0x000000},\n        {0xFF6666, 0x000000}, {0xFF5555, 0x000000},\n        {0xFF4444, 0x000000}, {0xFF3333, 0x000000},\n        {0xFF2222, 0x000000}, {0xFF0000, 0x000000},\n        {0xDD0000, 0x000000}, {0xBB0000, 0x000000},\n        {0xB00000, 0x000000}, {0xAA0000, 0x000000},\n        {0x950000, 0x000000}, {0x808080, 0x000000},\n        {0xAAAAAA, 0x000000}, {0xAAC1C9, 0x000000},\n        {0x000000, 0x000000}, {0x000000, 0x000000},\n        {0x000000, 0x000000}, {0x000000, 0x000000},\n        {0x000000, 0x000000}, {0xF80B0B, 0x000000},\n        {0xF8910B, 0x000000}, {0x33F12A, 0x000000}\n    },\n    MaxPageSize = 2000,\n    PageCount = 3,\n    ColorTagIsUint8 = false\n};\n\nMakeAnimation({\n    Name = \"WaitIconSpriteAnimDef\",\n    Sheet = \"MesBox\",\n    FirstFrameX = 0,\n    FirstFrameY = 1919,\n    FrameWidth = 64,\n    ColWidth = 64,\n    FrameHeight = 64,\n    RowHeight = 64,\n    Frames = 44,\n    Duration = 5.0,\n    Rows = 2,\n    Columns = 32,\n    PrimaryDirection = AnimationDirections.Right,\n    SecondaryDirection = AnimationDirections.Down\n});\n\nroot.Sprites[\"NametagLeftSprite\"] = {\n    Sheet = \"Menu\",\n    Bounds = { X = 3207, Y = 1097, Width = 168, Height = 65 },\n    BaseScale = { X = 41 / 65, Y = 41 / 65 }\n};\n\nroot.Sprites[\"NametagMiddleSprite\"] = {\n    Sheet = \"Menu\",\n    Bounds = { X = 3375, Y = 1097, Width = 467, Height = 65 },\n    BaseScale = { X = 41 / 65, Y = 41 / 65 }\n};\n\nroot.Sprites[\"NametagLeftSprite\"] = {\n    Sheet = \"Menu\",\n    Bounds = { X = 3842, Y = 1097, Width = 130, Height = 65 },\n    BaseScale = { X = 41 / 65, Y = 41 / 65 }\n};\n"
  },
  {
    "path": "profiles/dash/font-lb.lua",
    "content": "root.Fonts = {\n    [\"Default\"] = {\n        Type = FontType.LB,\n        ForegroundSheet = \"FontLBForeground\",\n        OutlineSheet = \"FontLBOutline\",\n        Columns = 64,\n        Rows = 50,\n        AdvanceWidths = \"games/rne/font-lb/widths.bin\",\n        AdvanceWidthsEmWidth = 48,\n        OutlineOffset = { X = -4, Y = -4 },\n        LineSpacing = 14,\n    }\n};\n\nroot.SpriteSheets[\"Font\"] = nil;\n\nroot.SpriteSheets[\"FontLBForeground\"] = {\n    Path = \"games/rne/font-lb/foreground.png\",\n    DesignWidth = 3072,\n    DesignHeight = 2400\n};\n\nroot.SpriteSheets[\"FontLBOutline\"] = {\n    Path = \"games/rne/font-lb/outline.png\",\n    DesignWidth = 3648,\n    DesignHeight = 2852\n};"
  },
  {
    "path": "profiles/dash/font.lua",
    "content": "-- WARNING, 0-INDEXED ARRAYS AHEAD\n\nroot.Fonts = {\n    [\"Default\"] = {\n        Type = FontType.Basic,\n        Sheet = \"Font\",\n        Columns = 64,\n        Rows = 50,\n        AdvanceWidths = {},\n        AdvanceWidthsEmWidth = 32,\n        LineSpacing = 22,\n    }\n};\n\nlocal westernAdvanceWidths = {\n    [0]=0x20, 0x16, 0x0E, 0x14, 0x15, 0x16, 0x16, 0x16, 0x15, 0x16, 0x16, 0x18,\n        0x16, 0x17, 0x17, 0x15, 0x14, 0x18, 0x16, 0x08, 0x0E, 0x17, 0x13, 0x1A,\n        0x16, 0x19, 0x15, 0x19, 0x16, 0x15, 0x15, 0x16, 0x17, 0x1E, 0x16, 0x15,\n        0x15, 0x14, 0x13, 0x13, 0x14, 0x14, 0x0D, 0x14, 0x13, 0x08, 0x09, 0x13,\n        0x08, 0x1A, 0x13, 0x15, 0x13, 0x14, 0x0D, 0x12, 0x0D, 0x13, 0x13, 0x1A,\n        0x13, 0x13, 0x12, 0x11, 0x0F, 0x08, 0x0F, 0x08, 0x08, 0x14, 0x08, 0x08,\n        0x1C, 0x17, 0x1E, 0x0F, 0x11, 0x19, 0x0C, 0x0D, 0x0D, 0x0C, 0x14, 0x13,\n        0x15, 0x12, 0x12, 0x12, 0x10, 0x10, 0x14, 0x19, 0x0E, 0x08, 0x0C, 0x0C,\n        0x14, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A,\n        0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A,\n        0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x19, 0x0F, 0x17, 0x18,\n        0x1A, 0x17, 0x18, 0x17, 0x19, 0x18, 0x19, 0x16, 0x17, 0x17, 0x14, 0x14,\n        0x18, 0x15, 0x08, 0x0E, 0x17, 0x13, 0x1A, 0x15, 0x19, 0x15, 0x19, 0x16,\n        0x15, 0x15, 0x15, 0x18, 0x1E, 0x17, 0x17, 0x15, 0x14, 0x14, 0x13, 0x14,\n        0x13, 0x0E, 0x14, 0x13, 0x09, 0x0C, 0x13, 0x08, 0x19, 0x13, 0x15, 0x14,\n        0x14, 0x0D, 0x12, 0x0E, 0x13, 0x14, 0x1B, 0x15, 0x14, 0x13, 0x12, 0x12,\n        0x08, 0x08, 0x09, 0x09, 0x15, 0x09, 0x0C, 0x0B, 0x08, 0x08, 0x0D, 0x0E,\n        0x0B, 0x0C, 0x0C, 0x0C, 0x0B, 0x0C, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11,\n        0x0C, 0x0D, 0x0D, 0x0E, 0x0B, 0x0C, 0x16, 0x17, 0x0B, 0x0C, 0x09, 0x1D,\n        0x1D, 0x1D, 0x14, 0x1E, 0x1A, 0x1A, 0x17, 0x19, 0x1A, 0x1A, 0x1B, 0x19,\n        0x19, 0x1B, 0x19, 0x18, 0x17, 0x19, 0x19, 0x19, 0x1A, 0x19, 0x16, 0x17,\n        0x18, 0x1B, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E,\n        0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E,\n        0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E,\n        0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E,\n        0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E,\n        0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E,\n        0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E,\n        0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E,\n        0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E,\n        0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E,\n        0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E,\n};\n\nfor i = 0, (64 * 50) - 1 do\n    if i <= #westernAdvanceWidths then\n        root.Fonts[\"Default\"].AdvanceWidths[i] = westernAdvanceWidths[i];\n    else\n        root.Fonts[\"Default\"].AdvanceWidths[i] = root.Fonts[\"Default\"].AdvanceWidthsEmWidth;\n    end\nend"
  },
  {
    "path": "profiles/dash/game.lua",
    "content": "root.ActiveRenderer = RendererType.OpenGL;\n\nroot.LayerCount = 100;\nroot.GameFeatures = GameFeature.Sc3VirtualMachine | GameFeature.Renderer2D | GameFeature.Scene3D | GameFeature.Input | GameFeature.Audio | GameFeature.Video;\nroot.DesignWidth = 1920;\nroot.DesignHeight = 1080;\n\nroot.WindowName = \"ROBOTICS;NOTES DaSH\";\nroot.WindowIconPath = \"games/dash/icondata/icon.png\";\nroot.CursorArrowPath = \"games/dash/icondata/cursor_arrow.png\";\nroot.CursorPointerPath = \"games/dash/icondata/cursor_pointer.png\";\n\nroot.ScreenCaptureCount = 2;\n\nroot.Vm = {\n    StartScript = 4,\n    StartScriptBuffer = 0,\n    GameInstructionSet = InstructionSet.Dash,\n    UseReturnIds = true,\n\n    ScrWorkChaStructSize = 40,\n    ScrWorkChaOffsetStructSize = 10,\n    ScrWorkBgStructSize = 40,\n    ScrWorkBgOffsetStructSize = 10,\n    ScrWorkCaptureStructSize = 20,\n    ScrWorkCaptureOffsetStructSize = 10,\n    ScrWorkCaptureEffectInfoStructSize = 3,\n    ScrWorkBgEffStructSize = 30,\n    ScrWorkBgEffOffsetStructSize = 20,\n\n    MaxLinkedBgBuffers = 2\n};\n\ninclude('common/scriptinput.lua');\ninclude('common/scriptvars.lua');\ninclude('dash/config.lua');\ninclude('dash/scriptvars.lua');\ninclude('dash/savedata.lua');\ninclude('dash/tipssystem.lua');\ninclude('dash/vfs.lua');\ninclude('dash/sprites.lua');\ninclude('common/animation.lua');\ninclude('dash/charset.lua');\n--include('dash/font.lua');\ninclude('dash/font-lb.lua');\ninclude('dash/dialogue.lua');\ninclude('dash/hud/saveicon.lua');\ninclude('dash/hud/loadingdisplay.lua');\ninclude('dash/hud/datedisplay.lua');\ninclude('dash/hud/titlemenu.lua');\ninclude('dash/hud/systemmenu.lua');\ninclude('dash/hud/backlogmenu.lua');\ninclude('dash/hud/sysmesboxdisplay.lua');\ninclude('dash/scene3d/scene3d.lua');\ninclude('dash/hud/selectiondisplay.lua');\ninclude('dash/hud/tipsmenu.lua');\ninclude('dash/hud/tipsnotification.lua');\ninclude('dash/gamespecific.lua');"
  },
  {
    "path": "profiles/dash/gamespecific.lua",
    "content": "root.GameSpecific = {\n  Type = GameSpecificType.Dash\n}"
  },
  {
    "path": "profiles/dash/hud/backlogmenu.lua",
    "content": "root.BacklogMenu = {\n    Type = BacklogMenuType.None,\n    DrawType = DrawComponentType.ExtrasScenes,\n    BacklogBackgroundSprite = \"BacklogBackground\",\n    EntryHighlightSprite = \"EntryHighlight\",\n    EntryHighlightPadding = 0.0,\n    VoiceIconSprite = \"VoiceIcon\",\n    ScrollbarTrackSprite = \"ScrollbarTrack\",\n    ScrollbarThumbSprite = \"ScrollbarThumb\",\n    ScrollbarPosition = { X = 1165, Y = 98 },\n    EntriesStart = { X = 163, Y = 85 },\n    RenderingBounds = { X = 87, Y = 83, Width = 1055, Height = 590 },\n    EntryYPadding = 22,\n    FadeInDuration = 0.2,\n    FadeOutDuration = 0.2\n};\n\nroot.Sprites[\"BacklogBackground\"] = {\n    Sheet = \"Backlog\",\n    Bounds = { X = 0, Y = 0, Width = 1280, Height = 720 },\n};\n\nroot.Sprites[\"VoiceIcon\"] = {\n    Sheet = \"Backlog\",\n    Bounds = { X = 1281, Y = 1, Width = 30, Height = 30 },\n};\n\nroot.Sprites[\"EntryHighlight\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 65, Y = 1, Width = 94, Height = 30 }\n};\n\nroot.Sprites[\"ScrollbarThumb\"] = {\n    Sheet = \"Backlog\",\n    Bounds = { X = 1281, Y = 32, Width = 30, Height = 30 },\n};\n\nroot.Sprites[\"ScrollbarTrack\"] = {\n    Sheet = \"Backlog\",\n    Bounds = { X = 1500, Y = 0, Width = 8, Height = 567 },\n};"
  },
  {
    "path": "profiles/dash/hud/datedisplay.lua",
    "content": "local sheet = \"Data\";\nlocal name = \"Date\";\n\nlocal yearNumFirstX = 2287.5;\nlocal yearNumFirstY = 94.5;\nlocal yearNumWidth = 37.5;\nlocal yearNum1Width = 12;\nlocal yearNumHeight = 30;\n\nlocal numFirstX = 2286;\nlocal numFirstY = 49.5;\nlocal numWidth = 60;\nlocal num1Width = 18;\nlocal numHeight = 42;\n\nlocal weekFirstX = 2677.5;\nlocal weekFirstY = 94.5;\nlocal weekSecondX = 2287.5;\nlocal weekSecondY = 127.5;\nlocal weekWidth = 109.5;\nlocal weekFriWidth = 84;\nlocal weekHeight = 30;\n\nroot.DateDisplay = {\n    Type = DateDisplayType.RNE,\n    YearNumSprites = {},\n    MonthNumSprites = {},\n    DayNumSprites = {},\n    WeekSprites = {},\n    BackgroundStartPos = { X = 1632, Y = 109.5 },\n    BackgroundEndPos = { X = 1632 - 384, Y = 109.5 },\n    DateStartX = 1750.5,\n    YearWeekY = 90,\n    MonthDayY = 78,\n    Spacing = 1.5,\n    FadeInDuration = 0.5,\n    FadeOutDuration = 0.5\n};\n\nfor i = 0, 9 do\n    local yearW;\n    if i == 1 then\n        yearW = yearNum1Width;\n    else\n        yearW = yearNumWidth;\n    end\n\n    root.Sprites[name .. \"YearNum\" .. i] = {\n        Sheet = sheet,\n        Bounds = {\n            X = yearNumFirstX,\n            Y = yearNumFirstY,\n            Width = yearW,\n            Height = yearNumHeight\n        }\n    };\n    root.DateDisplay.YearNumSprites[#root.DateDisplay.YearNumSprites + 1] = name .. \"YearNum\" .. i;\n\n    local numW\n    if i == 1 then\n        numW = num1Width;\n    else\n        numW = numWidth;\n    end\n\n    root.Sprites[name .. \"Num\" .. i] = {\n        Sheet = sheet,\n        Bounds = {\n            X = numFirstX,\n            Y = numFirstY,\n            Width = numW,\n            Height = numHeight\n        }\n    };\n    root.DateDisplay.MonthNumSprites[#root.DateDisplay.MonthNumSprites + 1] = name .. \"Num\" .. i;\n    root.DateDisplay.DayNumSprites[#root.DateDisplay.DayNumSprites + 1] = name .. \"Num\" .. i;\n\n    if i == 1 then\n        yearNumFirstX = yearNumFirstX + yearNum1Width;\n        numFirstX = numFirstX + num1Width;\n    else\n        yearNumFirstX = yearNumFirstX + yearNumWidth;\n        numFirstX = numFirstX + numWidth;\n    end\nend\n\nfor i = 0, 6 do\n    if i > 1 then\n        local weekW;\n        if i == 5 then\n            weekW = weekFriWidth;\n        else\n            weekW = weekWidth;\n        end\n\n        root.Sprites[name .. \"Week\" .. i] = {\n            Sheet = sheet,\n            Bounds = {\n                X = weekSecondX,\n                Y = weekSecondY,\n                Width = weekW,\n                Height = weekHeight\n            }\n        };\n        if i == 5 then\n            weekSecondX = weekSecondX + weekFriWidth;\n        else\n            weekSecondX = weekSecondX + weekWidth;\n         end\n    else\n        root.Sprites[name .. \"Week\" .. i] = {\n            Sheet = sheet,\n            Bounds = {\n                X = weekFirstX,\n                Y = weekFirstY,\n                Width = weekWidth,\n                Height = weekHeight\n            }\n        };\n        weekFirstX = weekFirstX + weekWidth;\n    end\n    root.DateDisplay.WeekSprites[#root.DateDisplay.WeekSprites + 1] = name .. \"Week\" .. i;\nend\n\nroot.Sprites[name .. \"MonthDaySeparator\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 2845.5,\n        Y = 49.5,\n        Width = 15,\n        Height = 42\n    }\n};\nroot.DateDisplay.MDSeparatorSprite = name .. \"MonthDaySeparator\";\n\nroot.Sprites[name .. \"DayYearSeparator\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 2637,\n        Y = 94.5,\n        Width = 12,\n        Height = 30\n    }\n};\nroot.DateDisplay.DYSeparatorSprite = name .. \"DayYearSeparator\";\n\nroot.Sprites[name .. \"OpenBracket\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 2649,\n        Y = 94.5,\n        Width = 12,\n        Height = 30\n    }\n};\nroot.DateDisplay.OpenBracketSprite = name .. \"OpenBracket\";\n\nroot.Sprites[name .. \"CloseBracket\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 2664,\n        Y = 94.5,\n        Width = 12,\n        Height = 30\n    }\n};\nroot.DateDisplay.CloseBracketSprite = name .. \"CloseBracket\";\n\nroot.Sprites[name .. \"Background\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 2287.5,\n        Y = 2,\n        Width = 675,\n        Height = 42\n    }\n};\nroot.DateDisplay.BackgroundSprite = name .. \"Background\";"
  },
  {
    "path": "profiles/dash/hud/loadingdisplay.lua",
    "content": "root.LoadingDisplay = {\n    ResourceLoadBgAnim = \"ResourceLoadingBg\",\n    SaveLoadBgAnim = \"SaveLoadingBg\",\n    LoadingIconAnim = \"LoadingDisc\",\n    LoadingTextAnim = \"LoadingText\",\n    ResourceBgPos = { X = 1074, Y = 611 },\n    SaveBgPos = { X = 1019, Y = 598 },\n    IconPos = { X = 986, Y = 608 },\n    TextPos = { X = 1025, Y = 628 },\n    FadeInDuration = 0.66,\n    FadeOutDuration = 0.33\n};\n\nMakeAnimation({\n    Name = \"ResourceLoadingBg\",\n    Sheet = \"Data\",\n    FirstFrameX = 555,\n    FirstFrameY = 544,\n    FrameWidth = 206,\n    ColWidth = 206,\n    FrameHeight = 58,\n    RowHeight = 60,\n    Frames = 8,\n    Duration = 0.8,\n    Rows = 8,\n    Columns = 1,\n    PrimaryDirection = AnimationDirections.Down\n});\n\nMakeAnimation({\n    Name = \"SaveLoadingBg\",\n    Sheet = \"Data\",\n    FirstFrameX = 388,\n    FirstFrameY = 0,\n    FrameWidth = 277,\n    ColWidth = 279,\n    FrameHeight = 81,\n    RowHeight = 83,\n    Frames = 4,\n    Duration = 0.8,\n    Rows = 2,\n    Columns = 2,\n    PrimaryDirection = AnimationDirections.Down,\n    SecondaryDirection = AnimationDirections.Right\n});\n\nMakeAnimation({\n    Name = \"LoadingDisc\",\n    Sheet = \"Data\",\n    FirstFrameX = 172,\n    FirstFrameY = 1,\n    FrameWidth = 60,\n    ColWidth = 62,\n    FrameHeight = 60,\n    RowHeight = 62,\n    Frames = 3,\n    Duration = 0.8,\n    Rows = 1,\n    Columns = 3,\n    PrimaryDirection = AnimationDirections.Right\n});\nroot.Animations[\"LoadingDisc\"].Frames[#root.Animations[\"LoadingDisc\"].Frames + 1] = \"LoadingDisc1\";\n\nMakeAnimation({\n    Name = \"LoadingText\",\n    Sheet = \"Data\",\n    FirstFrameX = 173,\n    FirstFrameY = 91,\n    FrameWidth = 214,\n    ColWidth = 214,\n    FrameHeight = 21,\n    RowHeight = 23,\n    Frames = 5,\n    Duration = 0.8,\n    Rows = 5,\n    Columns = 1,\n    PrimaryDirection = AnimationDirections.Down\n});"
  },
  {
    "path": "profiles/dash/hud/saveicon.lua",
    "content": "root.SaveIcon = {\n    ForegroundAnimation = \"SaveIcon\",\n    DefaultPosition = { X = 1729.5, Y = 34.5 },\n    BackgroundSprite = \"SaveIconBg\",\n    BackgroundOffset = { X = -10.5, Y = -6 },\n    BackgroundMaxAlpha = 0.5,\n    FadeInDuration = 0.5,\n    FadeOutDuration = 0.25\n};\n\nMakeAnimation({\n    Name = \"SaveIcon\",\n    Sheet = \"Data\",\n    FirstFrameX = 2965.5,\n    FirstFrameY = 1.5,\n    FrameWidth = 105,\n    ColWidth = 105,\n    FrameHeight = 105,\n    RowHeight = 108,\n    Frames = 8,\n    Duration = 0.4,\n    Rows = 8,\n    Columns = 1,\n    PrimaryDirection = AnimationDirections.Down\n});\n\nroot.Sprites[\"SaveIconBg\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 2158.5, Y = 1.5, Width = 126, Height = 126 }\n};"
  },
  {
    "path": "profiles/dash/hud/selectiondisplay.lua",
    "content": "root.SelectionDisplay = {\n    DrawType = DrawComponentType.Text,\n    SelectionBackgroundSprite = \"SelectionBackground\",\n    PlainSelectionFrameTopLeftSprite = \"PlainSelectionFrameTopLeft\",\n    PlainSelectionFrameTopSideSprite = \"PlainSelectionFrameTopSide\",\n    PlainSelectionFrameTopRightSprite = \"PlainSelectionFrameTopRight\",\n    PlainSelectionFrameLeftSideSprite = \"PlainSelectionFrameLeftSide\",\n    PlainSelectionFrameBottomLeftSprite = \"PlainSelectionFrameBottomLeft\",\n    PlainSelectionFrameRightSideSprite = \"PlainSelectionFrameRightSide\",\n    PlainSelectionFrameBottomRightSprite = \"PlainSelectionFrameBottomRight\",\n    PlainSelectionFrameBottomSideSprite = \"PlainSelectionFrameBottomSide\",\n    PlainSelectionFrameMiddleSprite = \"PlainSelectionFrameMiddle\",\n    SelectionHighlightSprite = \"SelectionHighlight\",\n    SelectionMaxCount = 5,\n    SelectionBackgroundX = 228,\n    SelectionBackgroundY = {277, 277, 196, 129, 95},\n    SelectionYSpacing = 81,\n    PlainSelectionYSpacing = 10,\n    FadeAnimationDurationInOut = 0.2\n};\n\nroot.Sprites[\"SelectionBackground\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 1, Y = 535, Width = 829, Height = 69 }\n};\n\nroot.Sprites[\"PlainSelectionFrameTopLeft\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 0, Y = 0, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameTopSide\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 16, Y = 0, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameTopRight\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 32, Y = 0, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameLeftSide\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 0, Y = 16, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameBottomLeft\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 0, Y = 32, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameRightSide\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 32, Y = 16, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameBottomRight\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 32, Y = 32, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameBottomSide\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 16, Y = 32, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameMiddle\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 16, Y = 16, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"SelectionHighlight\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 65, Y = 33, Width = 94, Height = 30 }\n};\n"
  },
  {
    "path": "profiles/dash/hud/sysmesboxdisplay.lua",
    "content": "local sheet = \"Data\";\nlocal name = \"SysMesBox\";\n\nroot.SysMesBoxDisplay = {\n    Type = SysMesBoxType.Dash,\n    DrawType = DrawComponentType.SystemMessage,\n    LinePositionXFirst = 2433,\n    LinePositionX = 2478,\n    LinePositionMultiplier = 30,\n    LineWidthFirst = 171,\n    LineWidthBase = 121.5,\n    LineWidthMultiplier = 60,\n    Line1SpriteY = 160.5,\n    Line2SpriteY = 175.5,\n    LineSpriteHeight = 12,\n    LineDisplayXBase = 961.5,\n    Line1DisplayY = 540,\n    Line2DisplayY = 528,\n    BoxDisplayStartCount = 9,\n    BoxHeightBase = 168,\n    BoxHeightMultiplier = 21,\n    BoxWidth = 900,\n    BoxTextFontSize = 42,\n    BoxTopYBase = 540,\n    BoxDisplayX = 510,\n    MessageLabelSpriteXBase = 2368,\n    MessageLabelSpriteY = 196.5,\n    MessageLabelSpriteHeight = 64,\n    MessageLabelSpriteMultiplier = 18.4,\n    ButtonYesDisplayXBase = 1350.5,\n    ButtonRightDisplayXBase = 1409,\n    ButtonSpriteY = 196.5,\n    ButtonWidth = 157.5, \n    ButtonSelectedSpriteY = 238.5,\n    ButtonYOffset = 50,\n    ButtonYWidthBase = 58.5,\n    ButtonRightWidthBase = -112.5,\n    TextDecorationStart = 56,\n    TextDecorationTopYOffset = 36,\n    TextDecorationBottomYOffset = 32,\n    TextFontSize = 39,\n    TextMiddleY = 627,\n    TextX = 555,\n    TextLineHeight = 42,\n    TextMarginY = 21,\n    SpriteMargin = 3,\n    AnimationSpeed = 55,\n    FadeInDuration = 0.33,\n    FadeOutDuration = 0.25\n};\n\nroot.Sprites[name .. \"BoxDecorationTop\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 2049,\n        Y = 400,\n        Width = 912,\n        Height = 61\n    }\n};\nroot.SysMesBoxDisplay.BoxDecorationTop = name .. \"BoxDecorationTop\";\n\nroot.Sprites[name .. \"BoxDecorationBottom\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 2049,\n        Y = 536,\n        Width = 912,\n        Height = 12\n    }\n};\nroot.SysMesBoxDisplay.BoxDecorationBottom = name .. \"BoxDecorationBottom\";\n\nroot.Sprites[name .. \"TextDecoration\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 2074.5,\n        Y = 127*1.5,\n        Width = 888,\n        Height = 2*1.5\n    }\n};\nroot.SysMesBoxDisplay.TextDecoration = name .. \"TextDecoration\";\n\nroot.Sprites[name .. \"MessageLabel\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 2074.5,\n        Y = 190.5,\n        Width = 888,\n        Height = 3\n    }\n};\nroot.SysMesBoxDisplay.MessageLabel = name .. \"MessageLabel\";\n\nroot.Sprites[name .. \"Line1\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 1622*1.5,\n        Y = 107*1.5,\n        Width = 114*1.5,\n        Height = 8*1.5\n    }\n};\nroot.SysMesBoxDisplay.Line1 = name .. \"Line1\";\n\nroot.Sprites[name .. \"Line2\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 1622*1.5,\n        Y = 117*1.5,\n        Width = 114*1.5,\n        Height = 8*1.5\n    }\n};\nroot.SysMesBoxDisplay.Line2 = name .. \"Line2\";\n\nroot.Sprites[name .. \"ButtonYes\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 1656 * 1.5,\n        Y = 131 * 1.5,\n        Width = 105 * 1.5,\n        Height = 26 * 1.5\n    }\n};\nroot.SysMesBoxDisplay.ButtonYes = name .. \"ButtonYes\";\n\nroot.Sprites[name .. \"ButtonNo\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 1763 * 1.5,\n        Y = 131 * 1.5,\n        Width = 105 * 1.5,\n        Height = 26 * 1.5\n    }\n};\nroot.SysMesBoxDisplay.ButtonNo = name .. \"ButtonNo\";\n\nroot.Sprites[name .. \"ButtonOK\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 1870 * 1.5,\n        Y = 131 * 1.5,\n        Width = 105 * 1.5,\n        Height = 26 * 1.5\n    }\n};\nroot.SysMesBoxDisplay.ButtonOK = name .. \"ButtonOK\";\n\nroot.Sprites[name .. \"ButtonYesHighlighted\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 1656,\n        Y = 159,\n        Width = 105,\n        Height = 26\n    }\n};\nroot.SysMesBoxDisplay.ButtonYesHighlighted = name .. \"ButtonYesHighlighted\";\n\nroot.Sprites[name .. \"ButtonNoHighlighted\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 1763,\n        Y = 159,\n        Width = 105,\n        Height = 26\n    }\n};\nroot.SysMesBoxDisplay.ButtonNoHighlighted = name .. \"ButtonNoHighlighted\";\n\nroot.Sprites[name .. \"ButtonOKHighlighted\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 1870,\n        Y = 159,\n        Width = 105,\n        Height = 26\n    }\n};\nroot.SysMesBoxDisplay.ButtonOKHighlighted = name .. \"ButtonOKHighlighted\";"
  },
  {
    "path": "profiles/dash/hud/systemmenu.lua",
    "content": "root.SystemMenu = {\n    Type = SystemMenuType.RNE,\n    DrawType = DrawComponentType.SystemMenu,\n    Background = {\n        DurationIn = 0.75,\n        DurationOut = 1.0,\n        Sprite = \"SystemMenuBackground\",\n        Seed = 0,\n        Rows = 7,\n        Columns = 12,\n        CenterY = 0.7,\n        VanishingPointX = 0.66,\n        Depth = 2,\n        MaxAngle = 3.141592653 / 2  -- pi / 2\n    },\n    ButtonBackgroundSprite = \"SystemMenuButtonBackground\",\n    SkyBackgroundSprite = \"SystemMenuSkyBackground\",\n    SkyArrowSprite = \"SystemMenuSkyArrow\",\n    SkyTextSprite = \"SystemMenuSkyText\",\n    ButtonPromptsSprite = \"SystemMenuButtonPrompts\",\n    SkyBackgroundBeginX = -80,\n    SkyBackgroundY = 0,\n    SkyTextBeginX = 287,\n    SkyTextY = 69,\n    ButtonBackgroundStartX = 1257,\n    ButtonBackgroundX = 0,\n    ButtonBackgroundY = 681,\n    ButtonBackgroundTargetWidth = 943,\n    ButtonBackgroundSprStartX = 1499,\n    MenuEntriesSprites = {},\n    MenuEntriesHighlightedSprites = {},\n    MenuEntriesX = 0,\n    MenuEntriesXSkew = 20,\n    MenuEntriesXOffset = 100,\n    MenuEntriesFirstY = 220,\n    MenuEntriesYPadding = 50,\n    MenuEntriesTargetWidth = 417,\n    SkyInStartProgress = 0.285,\n    SkyOutStartProgress = 0.715,\n    SkyMoveDurationIn = 0.415,\n    SkyMoveDurationOut = 0.415,\n    EntriesMoveDurationIn = 0.4,\n    EntriesMoveDurationOut = 0.4,\n    HighlightDurationIn = 0.15,\n    HighlightDurationOut = 0.15,\n    MenuEntriesNum = 0,\n    MenuEntriesHNum = 0\n};\n\nfor i = 0, 7 do\n    root.Sprites[\"SystemMenuEntry\" .. i] = {\n        Sheet = \"Data\",\n        Bounds = {\n            X = 964,\n            Y = 296 + i * 38,\n            Width = root.SystemMenu.MenuEntriesTargetWidth,\n            Height = 26\n        },\n        BaseScale = { X = 1280 / 960, Y = 720 / 544 }\n    };\n    root.SystemMenu.MenuEntriesSprites[#root.SystemMenu.MenuEntriesSprites + 1] = \"SystemMenuEntry\" .. i;\nend\n\nfor i = 0, 7 do\n    root.Sprites[\"SystemMenuEntryHighlighted\" .. i] = {\n        Sheet = \"Data\",\n        Bounds = {\n            X = 964,\n            Y = 2 + i * 38,\n            Width = root.SystemMenu.MenuEntriesTargetWidth,\n            Height = 26\n        },\n        BaseScale = { X = 1280 / 960, Y = 720 / 544 }\n    };\n    root.SystemMenu.MenuEntriesHighlightedSprites[#root.SystemMenu.MenuEntriesHighlightedSprites + 1] = \"SystemMenuEntryHighlighted\" .. i;\nend\n\nroot.Sprites[\"SystemMenuBackground\"] = {\n    Sheet = \"Menu\",\n    Bounds = { X = 0, Y = 1088, Width = 1920, Height = 1080 },\n};\n\nroot.Sprites[\"SystemMenuButtonBackground\"] = {\n    Sheet = \"Menu\",\n    Bounds = { X = root.SystemMenu.ButtonBackgroundSprStartX, Y = 975, Width = 0, Height = 30 },\n    BaseScale = { X = 1280 / 960, Y = 720 / 544 }\n};\n\nroot.Sprites[\"SystemMenuSkyBackground\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 1383, Y = 534, Width = 418, Height = 90 },\n    BaseScale = { X = 1280 / 960, Y = 720 / 544 }\n};\n\nroot.Sprites[\"SystemMenuSkyArrow\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 1802, Y = 534, Width = 70, Height = 90 },\n    BaseScale = { X = 1280 / 960, Y = 720 / 544 }\n};\n\nroot.Sprites[\"SystemMenuSkyText\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 1875, Y = 587, Width = 119, Height = 30 },\n    BaseScale = { X = 1280 / 960, Y = 720 / 544 }\n};\n\nroot.Sprites[\"SystemMenuButtonPrompts\"] = {\n    Sheet = \"Menu\",\n    Bounds = { X = 1231, Y = 528, Width = 400, Height = 21 },\n    BaseScale = { X = 1280 / 960, Y = 720 / 544 }\n};"
  },
  {
    "path": "profiles/dash/hud/tipsmenu.lua",
    "content": "root.TipsMenu = {\n    Type = TipsMenuType.None\n};\n"
  },
  {
    "path": "profiles/dash/hud/tipsnotification.lua",
    "content": "root.TipsNotification = {\n    Type = TipsNotificationType.None,\n};\n"
  },
  {
    "path": "profiles/dash/hud/titlemenu.lua",
    "content": "root.TitleMenu = {\n    Type = TitleMenuType.Dash,\n    DrawType = DrawComponentType.SystemMenu,\n    PressToStartX = 777,\n    PressToStartY = 611,\n    PressToStartAnimDurationIn = 0.7,\n    PressToStartAnimDurationOut = 0.7,\n    PressToStartSprite = \"TitleMenuPressToStart\",\n    BackgroundSprite = \"TitleMenuBackground\",\n    MenuEntriesNum = 0,\n};\n\nroot.Sprites[\"TitleMenuPressToStart\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 1947, Y = 983, Width = 365, Height = 38 },\n};\n\nroot.Sprites[\"TitleMenuBackground\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 0, Y = 0, Width = 1920, Height = 1080 },\n};"
  },
  {
    "path": "profiles/dash/savedata.lua",
    "content": "root.SaveData = {\n    Type = SaveDataType.None,\n    SaveFilePath = \"games/dash/savedata/SYSTEM.DAT\",\n};"
  },
  {
    "path": "profiles/dash/scene3d/characters.lua",
    "content": "-- Idle animation always loops, even if the data says OneShot\n\nroot.Scene3D.Characters = {\n    [1] = {\n        IdleAnimation = 15,\n        Models = {466, 467, 468, 469},\n        Animations = {\n            [15] = { LoopStart = 0, LoopEnd = 120 },\n            [16] = { OneShot = true },\n            [17] = { OneShot = true },\n            [18] = { OneShot = true },\n            [19] = { LoopStart = 30, LoopEnd = 150 },\n            [20] = { LoopStart = 35, LoopEnd = 115 },\n            [21] = { LoopStart = 35, LoopEnd = 115 },\n            [22] = { LoopStart = 40, LoopEnd = 140 },\n            [23] = { LoopStart = 55, LoopEnd = 120 },\n            [24] = { LoopStart = 15, LoopEnd = 70 },\n            [25] = { LoopStart = 113, LoopEnd = 180 },\n            [26] = { LoopStart = 75, LoopEnd = 160 },\n            [27] = { OneShot = true },\n            [28] = { OneShot = true },\n            [29] = { OneShot = true },\n            [30] = { OneShot = true },\n            [31] = { OneShot = true },\n            [32] = { OneShot = true },\n            [33] = { OneShot = true },\n            [34] = { OneShot = true },\n            [35] = { OneShot = true },\n            [36] = { OneShot = true },\n            [37] = { LoopStart = 0, LoopEnd = 40 },\n            [38] = { LoopStart = 0, LoopEnd = 40 }\n        }\n    },\n    [2] = {\n        IdleAnimation = 56,\n        Models = {470, 471, 472, 473, 474, 475, 476, 477, 478, 479},\n        Animations = {\n            [39] = { LoopStart = 20, LoopEnd = 70 },\n            [40] = { LoopStart = 39, LoopEnd = 99 },\n            [41] = { OneShot = true },\n            [42] = { LoopStart = 39, LoopEnd = 77 },\n            [43] = { LoopStart = 35, LoopEnd = 95 },\n            [44] = { OneShot = true },\n            [45] = { OneShot = true },\n            [46] = { OneShot = true },\n            [47] = { OneShot = true },\n            [48] = { OneShot = true },\n            [49] = { OneShot = true },\n            [50] = { LoopStart = 40, LoopEnd = 100 },\n            [51] = { OneShot = true },\n            [52] = { LoopStart = 38, LoopEnd = 98 },\n            [53] = { LoopStart = 45, LoopEnd = 125 },\n            [54] = { LoopStart = 55, LoopEnd = 115 },\n            [55] = { LoopStart = 45, LoopEnd = 105 },\n            [56] = { OneShot = true },\n            [57] = { LoopStart = 60, LoopEnd = 140 },\n            [58] = { OneShot = true },\n            [59] = { OneShot = true },\n            [60] = { OneShot = true },\n            [61] = { OneShot = true },\n            [62] = { LoopStart = 39, LoopEnd = 99 },\n            [63] = { LoopStart = 0, LoopEnd = 48 },\n            [64] = { LoopStart = 0, LoopEnd = 40 },\n            [65] = { LoopStart = 0, LoopEnd = 48 },\n            [66] = { OneShot = true }\n        }\n    },\n    [3] = {\n        IdleAnimation = 87,\n        Models = {480, 481, 482, 483, 484, 485, 486, 487},\n        Animations = {\n            [67] = { OneShot = true },\n            [68] = { LoopStart = 30, LoopEnd = 90 },\n            [69] = { LoopStart = 35, LoopEnd = 95 },\n            [70] = { LoopStart = 55, LoopEnd = 100 },\n            [71] = { OneShot = true },\n            [72] = { OneShot = true },\n            [73] = { OneShot = true },\n            [74] = { OneShot = true },\n            [75] = { OneShot = true },\n            [76] = { OneShot = true },\n            [77] = { LoopStart = 40, LoopEnd = 100 },\n            [78] = { LoopStart = 30, LoopEnd = 60 },\n            [79] = { LoopStart = 35, LoopEnd = 95 },\n            [80] = { LoopStart = 35, LoopEnd = 95 },\n            [81] = { LoopStart = 24, LoopEnd = 54 },\n            [82] = { LoopStart = 40, LoopEnd = 100 },\n            [83] = { LoopStart = 35, LoopEnd = 75 },\n            [84] = { LoopStart = 25, LoopEnd = 55 },\n            [85] = { OneShot = true },\n            [86] = { LoopStart = 20, LoopEnd = 60 },\n            [87] = { OneShot = true },\n            [88] = { OneShot = true },\n            [89] = { OneShot = true },\n            [90] = { OneShot = true },\n            [91] = { OneShot = true },\n            [92] = { LoopStart = 30, LoopEnd = 90 },\n            [93] = { LoopStart = 15, LoopEnd = 40 },\n            [94] = { LoopStart = 0, LoopEnd = 48 },\n            [95] = { LoopStart = 0, LoopEnd = 40 },\n            [96] = { LoopStart = 0, LoopEnd = 48 },\n            [97] = { OneShot = true }\n        }\n    },\n    [4] = {\n        IdleAnimation = 119,\n        Models = {488, 489, 490, 491, 492, 493, 494, 495, 496, 497},\n        Animations = {\n            [98]= { LoopStart = 15, LoopEnd = 45 },\n            [99]= { LoopStart = 20, LoopEnd = 80 },\n            [100] = { LoopStart = 33, LoopEnd = 51 },\n            [101] = { OneShot = true },\n            [102] = { OneShot = true },\n            [103] = { OneShot = true },\n            [104] = { OneShot = true },\n            [105] = { OneShot = true },\n            [106] = { OneShot = true },\n            [107] = { LoopStart = 33, LoopEnd = 51 },\n            [108] = { OneShot = true },\n            [109] = { LoopStart = 275, LoopEnd = 275 },\n            [110] = { LoopStart = 478, LoopEnd = 478 },\n            [111] = { LoopStart = 673, LoopEnd = 673 },\n            [112] = { LoopStart = 810, LoopEnd = 810 },\n            [113] = { LoopStart = 20, LoopEnd = 140 },\n            [114] = { LoopStart = 45, LoopEnd = 130 },\n            [115] = { LoopStart = 20, LoopEnd = 60 },\n            [116] = { OneShot = true },\n            [117] = { LoopStart = 20, LoopEnd = 40 },\n            [118] = { LoopStart = 20, LoopEnd = 140 },\n            [119] = { OneShot = true },\n            [120] = { LoopStart = 50, LoopEnd = 110 },\n            [121] = { OneShot = true },\n            [122] = { OneShot = true },\n            [123] = { OneShot = true },\n            [124] = { OneShot = true },\n            [125] = { LoopStart = 25, LoopEnd = 50 },\n            [126] = { LoopStart = 0, LoopEnd = 48 },\n            [127] = { LoopStart = 0, LoopEnd = 40 },\n            [128] = { LoopStart = 0, LoopEnd = 48 },\n            [129] = { OneShot = true }\n        }\n    },\n    [5] = {\n        IdleAnimation = 149,\n        Models = {498, 499, 500, 501, 502, 503},\n        Animations = {\n            [130] = { OneShot = true },\n            [131] = { LoopStart = 25, LoopEnd = 60 },\n            [132] = { OneShot = true },\n            [133] = { OneShot = true },\n            [134] = { OneShot = true },\n            [135] = { OneShot = true },\n            [136] = { OneShot = true },\n            [137] = { OneShot = true },\n            [138] = { LoopStart = 35, LoopEnd = 75 },\n            [139] = { OneShot = true },\n            [140] = { OneShot = true },\n            [141] = { OneShot = true },\n            [142] = { OneShot = true },\n            [143] = { LoopStart = 40, LoopEnd = 100 },\n            [144] = { LoopStart = 40, LoopEnd = 100 },\n            [145] = { LoopStart = 55, LoopEnd = 115 },\n            [146] = { LoopStart = 20, LoopEnd = 60 },\n            [147] = { LoopStart = 20, LoopEnd = 60 },\n            [148] = { LoopStart = 30, LoopEnd = 90 },\n            [149] = { OneShot = true },\n            [150] = { OneShot = true },\n            [151] = { OneShot = true },\n            [152] = { OneShot = true },\n            [153] = { OneShot = true },\n            [154] = { LoopStart = 0, LoopEnd = 48 },\n            [155] = { LoopStart = 0, LoopEnd = 40 },\n            [156] = { LoopStart = 0, LoopEnd = 48 }\n        }\n    },\n    [6] = {\n        IdleAnimation = 157,\n        Models = {504, 505, 506, 507, 508, 509},\n        Animations = {\n            [157] = { LoopStart = 90, LoopEnd = 815 },\n            [158] = { LoopStart = 56, LoopEnd = 160 },\n            [159] = { LoopStart = 370, LoopEnd = 790 },\n            [160] = { LoopStart = 220, LoopEnd = 370 },\n            [161] = { LoopStart = 450, LoopEnd = 720 },\n            [162] = { LoopStart = 240, LoopEnd = 705 },\n            [163] = { LoopStart = 235, LoopEnd = 720 },\n            [164] = { LoopStart = 220, LoopEnd = 700 },\n            [165] = { LoopStart = 220, LoopEnd = 735 },\n            [166] = { LoopStart = 255, LoopEnd = 790 },\n            [167] = { LoopStart = 265, LoopEnd = 745 },\n            [168] = { LoopStart = 260, LoopEnd = 740 },\n            [169] = { LoopStart = 260, LoopEnd = 740 },\n            [170] = { LoopStart = 70, LoopEnd = 140 },\n            [171] = { OneShot = true },\n            [172] = { OneShot = true },\n            [173] = { OneShot = true },\n            [174] = { OneShot = true },\n            [175] = { OneShot = true },\n            [176] = { OneShot = true },\n            [177] = { OneShot = true },\n            [178] = { OneShot = true },\n            [179] = { OneShot = true },\n            [180] = { OneShot = true },\n            [181] = { LoopStart = 0, LoopEnd = 40 },\n            [182] = { LoopStart = 0, LoopEnd = 40 }\n        }\n    },\n    [7] = {\n        IdleAnimation = 187,\n        Models = {510},\n        Animations = {\n            [183] = { OneShot = true },\n            [184] = { OneShot = true },\n            [185] = { OneShot = true },\n            [186] = { OneShot = true },\n            [187] = { OneShot = true },\n            [188] = { OneShot = true },\n            [189] = { OneShot = true }\n        }\n    },\n    [8] = {\n        IdleAnimation = 190,\n        Models = {511, 512, 513},\n        Animations = {\n            [190] = { OneShot = true }\n        }\n    },\n    [9] = {\n        IdleAnimation = 193,\n        Models = {514},\n        Animations = {\n            [191] = { LoopStart = 60, LoopEnd = 240 },\n            [192] = { OneShot = true },\n            [193] = { OneShot = true },\n            [194] = { LoopStart = 225, LoopEnd = 645 }\n        }\n    },\n    [10] = {\n        IdleAnimation = 198,\n        Models = {515},\n        Animations = {\n            [195] = { OneShot = true },\n            [196] = { LoopStart = 175, LoopEnd = 455 },\n            [197] = { OneShot = true },\n            [198] = { OneShot = true },\n            [199] = { OneShot = true }\n        }\n    },\n    [11] = {\n        IdleAnimation = 201,\n        Models = {516},\n        Animations = {\n            [200] = { LoopStart = 40, LoopEnd = 220 },\n            [201] = { OneShot = true },\n            [202] = { LoopStart = 260, LoopEnd = 720 }\n        }\n    },\n    [12] = {\n        IdleAnimation = 203,\n        Models = {517, 518},\n        Animations = {\n            [203] = { LoopStart = 170, LoopEnd = 540 },\n            [204] = { LoopStart = 60, LoopEnd = 240 }\n        }\n    },\n    [13] = {\n        IdleAnimation = 206,\n        Models = {519},\n        Animations = {\n            [205] = { OneShot = true },\n            [206] = { OneShot = true },\n            [207] = { OneShot = true },\n            [208] = { OneShot = true }\n        }\n    },\n    [14] = {\n        IdleAnimation = 210,\n        Models = {520},\n        Animations = {\n            [209] = { OneShot = true },\n            [210] = { OneShot = true }\n        }\n    },\n    [15] = {\n        IdleAnimation = 212,\n        Models = {521},\n        Animations = {\n            [211] = { LoopStart = 80, LoopEnd = 264 },\n            [212] = { OneShot = true }\n        }\n    },\n    [16] = {\n        IdleAnimation = 214,\n        Models = {522},\n        Animations = {\n            [213] = { OneShot = true },\n            [214] = { OneShot = true }\n        }\n    },\n    [17] = {\n        IdleAnimation = 229,\n        Models = {523, 524, 525, 526, 527},\n        Animations = {\n            [215] = { LoopStart = 20, LoopEnd = 60 },\n            [216] = { LoopStart = 35, LoopEnd = 85 },\n            [217] = { OneShot = true },\n            [218] = { OneShot = true },\n            [219] = { OneShot = true },\n            [220] = { OneShot = true },\n            [221] = { OneShot = true },\n            [222] = { OneShot = true },\n            [223] = { LoopStart = 50, LoopEnd = 110 },\n            [224] = { LoopStart = 20, LoopEnd = 60 },\n            [225] = { LoopStart = 40, LoopEnd = 60 },\n            [226] = { LoopStart = 20, LoopEnd = 60 },\n            [227] = { OneShot = true },\n            [228] = { LoopStart = 70, LoopEnd = 120 },\n            [229] = { OneShot = true },\n            [230] = { OneShot = true },\n            [231] = { OneShot = true },\n            [232] = { OneShot = true },\n            [233] = { OneShot = true },\n            [234] = { OneShot = true },\n            [235] = { LoopStart = 30, LoopEnd = 70 },\n            [236] = { LoopStart = 0, LoopEnd = 48 },\n            [237] = { LoopStart = 0, LoopEnd = 40 },\n            [238] = { LoopStart = 0, LoopEnd = 48 }\n        }\n    },\n    [18] = {\n        IdleAnimation = 239,\n        Models = {528, 529},\n        Animations = {\n            [239] = { OneShot = true },\n            [240] = { OneShot = true },\n            [241] = { LoopStart = 85, LoopEnd = 145 },\n            [242] = { LoopStart = 143, LoopEnd = 203 },\n            [243] = { LoopStart = 70, LoopEnd = 160 },\n            [244] = { LoopStart = 85, LoopEnd = 130 },\n            [245] = { LoopStart = 70, LoopEnd = 180 },\n            [246] = { OneShot = true },\n            [247] = { OneShot = true },\n            [248] = { OneShot = true },\n            [249] = { OneShot = true },\n            [250] = { OneShot = true },\n            [251] = { OneShot = true },\n            [252] = { OneShot = true },\n            [253] = { OneShot = true },\n            [254] = { OneShot = true },\n            [255] = { OneShot = true },\n            [256] = { OneShot = true },\n            [257] = { OneShot = true },\n            [258] = { OneShot = true },\n            [259] = { OneShot = true },\n            [260] = { OneShot = true },\n            [261] = { LoopStart = 0, LoopEnd = 40 },\n            [262] = { LoopStart = 0, LoopEnd = 40 }\n        }\n    },\n    [19] = {\n        IdleAnimation = 278,\n        Models = {530, 531, 532, 533, 534},\n        Animations = {\n            [263] = { LoopStart = 40, LoopEnd = 100 },\n            [264] = { LoopStart = 20, LoopEnd = 60 },\n            [265] = { LoopStart = 20, LoopEnd = 60 },\n            [266] = { LoopStart = 20, LoopEnd = 60 },\n            [267] = { OneShot = true },\n            [268] = { OneShot = true },\n            [269] = { OneShot = true },\n            [270] = { OneShot = true },\n            [271] = { OneShot = true },\n            [272] = { OneShot = true },\n            [273] = { LoopStart = 40, LoopEnd = 100 },\n            [274] = { OneShot = true },\n            [275] = { LoopStart = 30, LoopEnd = 70 },\n            [276] = { LoopStart = 40, LoopEnd = 100 },\n            [277] = { OneShot = true },\n            [278] = { OneShot = true },\n            [279] = { LoopStart = 20, LoopEnd = 80 },\n            [280] = { OneShot = true },\n            [281] = { OneShot = true },\n            [282] = { OneShot = true },\n            [283] = { OneShot = true },\n            [284] = { LoopStart = 0, LoopEnd = 48 },\n            [285] = { LoopStart = 0, LoopEnd = 40 },\n            [286] = { LoopStart = 0, LoopEnd = 48 }\n        }\n    }\n};"
  },
  {
    "path": "profiles/dash/scene3d/scene3d.lua",
    "content": "root.Scene3D = {\n    Version = LKMVersion.DaSH,\n    MaxRenderables = 8,\n    DefaultCamera = {\n        Position = {\n            X = 0,\n            Y = 1.25,\n            Z = 2.3\n        },\n        Target = {\n            X = 0,\n            Y = 1.25,\n            Z = 0\n        },\n        Fov = 3.141592653 / 8 -- pi / 8\n    },\n    AnimationDesignFrameRate = 30,\n    AnimationParseBlacklist = {}\n};\n\ninclude('dash/scene3d/characters.lua');"
  },
  {
    "path": "profiles/dash/scriptvars.lua",
    "content": "local sv = root.ScriptVars;\n\nsv.SW_IRUOCAMERAHFOV = 5407;\nsv.SW_MAINCAMERAHFOV = 5427;\nsv.SF_DATEDISPLAY = 1615;\nsv.SF_MOVIEPLAY = 1851;"
  },
  {
    "path": "profiles/dash/sprites.lua",
    "content": "root.SpriteSheets = {\n    [\"Data\"] = {\n        Path = { Mount = \"system\", Id = 7 },\n        DesignWidth = 4096,\n        DesignHeight = 2048\n    },\n    [\"MesBox\"] = {\n        Path = { Mount = \"system\", Id = 28 },\n        DesignWidth = 2048,\n        DesignHeight = 2048\n    },\n    [\"Font\"] = {\n        Path = { Mount = \"system\", Id = 10 },\n        DesignWidth = 3072,\n        DesignHeight = 3072\n    },\n    [\"Menu\"] = {\n        Path = { Mount = \"system\", Id = 29 },\n        DesignWidth = 4096,\n        DesignHeight = 2298\n    },\n    [\"Title\"] = {\n        Path = { Mount = \"system\", Id = 22 },\n        DesignWidth = 3282,\n        DesignHeight = 1104\n    },\n    [\"Backlog\"] = {\n        Path = {Mount = \"system\", Id = 6 },\n        DesignWidth = 2048,\n        DesignHeight = 720\n    }\n};\n\nroot.Sprites = {};"
  },
  {
    "path": "profiles/dash/tipssystem.lua",
    "content": "root.TipsSystem = {\n    Type = TipsSystemType.None,\n};"
  },
  {
    "path": "profiles/dash/vfs.lua",
    "content": "root.Vfs = {\n    Mounts = {\n        [\"script\"] = {\"games/dash/gamedata/script.cls\"},\n        [\"system\"] = {\"games/dash/gamedata/system.cls\"},\n        [\"bgm\"] = {\"games/dash/gamedata/bgm.cls\"},\n        [\"se\"] = {\"games/dash/gamedata/se.cls\"},\n        [\"sysse\"] = {\"games/dash/gamedata/sysse.cls\"},\n        [\"voice\"] = {\"games/dash/gamedata/voice.cls\"},\n        [\"model\"] = {\"games/dash/gamedata/model.cls\"},\n        [\"motion\"] = {\"games/dash/gamedata/motion.cls\"},\n        [\"bg\"] = {\"games/dash/gamedata/bg.cls\"},\n        [\"mask\"] = {\"games/dash/gamedata/mask.cls\"},\n        [\"movie\"] = {\"games/dash/gamedata/movie.cls\"}\n    }\n};"
  },
  {
    "path": "profiles/mo6tw/charset.lua",
    "content": "-- WARNING, 0-INDEXED ARRAYS AHEAD\n\nroot.Charset = {\n    Flags = {},\n    CharacterToSc3 = {\n        [\" \"] = 0x803F, [\"0\"] = 0x8001, [\"1\"] = 0x8002, [\"2\"] = 0x8003, [\"3\"] = 0x8004, [\"4\"] = 0x8005, [\"5\"] = 0x8006,\n        [\"6\"] = 0x8007, [\"7\"] = 0x8008, [\"8\"] = 0x8009, [\"9\"] = 0x800A, [\"A\"] = 0x800B, [\"B\"] = 0x800C, [\"C\"] = 0x800D,\n        [\"D\"] = 0x800E, [\"E\"] = 0x800F, [\"F\"] = 0x8010, [\"G\"] = 0x8011, [\"H\"] = 0x8012, [\"I\"] = 0x8013, [\"J\"] = 0x8014,\n        [\"K\"] = 0x8015, [\"L\"] = 0x8016, [\"M\"] = 0x8017, [\"N\"] = 0x8018, [\"O\"] = 0x8019, [\"P\"] = 0x801A, [\"Q\"] = 0x801B,\n        [\"R\"] = 0x801C, [\"S\"] = 0x801D, [\"T\"] = 0x801E, [\"U\"] = 0x801F, [\"V\"] = 0x8020, [\"W\"] = 0x8021, [\"X\"] = 0x8022,\n        [\"Y\"] = 0x8023, [\"Z\"] = 0x8024, [\"a\"] = 0x8025, [\"b\"] = 0x8026, [\"c\"] = 0x8027, [\"d\"] = 0x8028, [\"e\"] = 0x8029,\n        [\"f\"] = 0x802A, [\"g\"] = 0x802B, [\"h\"] = 0x802C, [\"i\"] = 0x802D, [\"j\"] = 0x802E, [\"k\"] = 0x802F, [\"l\"] = 0x8030,\n        [\"m\"] = 0x8031, [\"n\"] = 0x8032, [\"o\"] = 0x8033, [\"p\"] = 0x8034, [\"q\"] = 0x8035, [\"r\"] = 0x8036, [\"s\"] = 0x8037,\n        [\"t\"] = 0x8038, [\"u\"] = 0x8039, [\"v\"] = 0x803A, [\"w\"] = 0x803B, [\"x\"] = 0x803C, [\"y\"] = 0x803D, [\"z\"] = 0x803E,\n        [\"　\"] = 0x803F, [\"/\"] = 0x8040, [\":\"] = 0x8041, [\"-\"] = 0x8042, [\";\"] = 0x8043, [\"!\"] = 0x8044, [\"?\"] = 0x80C4,\n        [\"'\"] = 0x8046, [\".\"] = 0x80C1, [\"@\"] = 0x8048, [\"#\"] = 0x8049, [\"%\"] = 0x8112, [\"~\"] = 0x805B, [\"*\"] = 0x805C,\n        [\"【\"] = 0x80E0, [\"】\"] = 0x80E1\n    }\n};\n\nfor i = 0, (64 * 39) - 1 do root.Charset.Flags[i] = 0; end\n\nlocal spaces = {[0]=0, 63};\nfor i = 0, #spaces do\n    root.Charset.Flags[spaces[i]] = root.Charset.Flags[spaces[i]] | CharacterTypeFlags.Space;\nend\n\n-- 、 。 ． ， ？ ！ 〜 ” ー ） 〕 ］ ｝ 〉 》 」 』 】☆ ★ ♪ 々 ぁ ぃ ぅ ぇ\n-- ぉ っ ゃ ゅ ょ ァ ィ ゥ ェ ォ ッ ャ ュ ョ –\nlocal wordEndingPuncts = {\n    [0]=0x00BE, 0x00BF, 0x00C1,\n    0x00C0, 0x00C4, 0x00C5,\n    0x00E4, 0x00CB, 0x00E5,\n    0x00CD, 0x00CF, 0x00D1,\n    0x00D3, 0x00D5, 0x00D7,\n    0x00D9, 0x00DB, 0x00DD,\n    0x01A5, 0x01A6, 0x00E6,\n    0x0187, 0x00E8, 0x00E9,\n    0x00EA, 0x00EB, 0x00EC,\n    0x00ED, 0x00EE, 0x00EF,\n    0x00F0, 0x00F2, 0x00F3,\n    0x00F4, 0x00F5, 0x00F6,\n    0x00F7, 0x00F8, 0x00F9,\n    0x00FA, 0x0113\n};\nfor i = 0, #wordEndingPuncts do\n    root.Charset.Flags[wordEndingPuncts[i]] = root.Charset.Flags[wordEndingPuncts[i]] | CharacterTypeFlags.WordEndingPunct;\nend\n\n-- space(63) space(0) “ （ 〔 ［ ｛ 〈 《 「\nlocal wordStartingPuncts = {\n    [0]=63, 0, 0x00CA,\n    0x00CC, 0x00CE, 0x00D0,\n    0x00D2, 0x00D4, 0x00D6,\n    0x00D8, 0x00DA, 0x00DC\n};\nfor i = 0, #wordStartingPuncts do\n    root.Charset.Flags[wordStartingPuncts[i]] = root.Charset.Flags[wordStartingPuncts[i]] | CharacterTypeFlags.WordStartingPunct;\nend\n"
  },
  {
    "path": "profiles/mo6tw/config.lua",
    "content": "root.Language = \"Japanese\";\nroot.ResolutionWidth = 1280;\nroot.ResolutionHeight = 720;\nroot.Fullscreen = false;\nroot.DateFormat = DateFormatType.YMD;"
  },
  {
    "path": "profiles/mo6tw/dialogue.lua",
    "content": "root.Sprites[\"ADVBox\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 0, Y = 309, Width = 1027, Height = 206 }\n};\n\nroot.Sprites[\"DialogueWaitIcon\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 418, Y = 276, Width = 32, Height = 32 }\n};\n\nroot.Sprites[\"ADVBoxPartLeft\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 1028, Y = 309, Width = 28, Height = 180 }\n};\n\nroot.Sprites[\"ADVBoxPartRight\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 1058, Y = 309, Width = 28, Height = 180 }\n};\n\nroot.Sprites[\"ADVBoxDecoration\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 371, Y = 121, Width = 48, Height = 46 }\n};\n\nroot.Dialogue = {\n    TipsBounds = { X = 394, Y = 263, Width = 820, Height = 370 },\n    TipsColorIndex = 0,\n    REVBounds = { X = 163, Y = 83, Width = 960, Height = 590 },\n    REVNameFontSize = 24,\n    REVColor = 0,\n    REVNameColor = 24,\n    REVNameOffset = 34,\n    REVNameLocation = REVNameLocationType.TopLeft,\n    REVOutlineMode = 2,\n    REVNameOutlineMode = 2,\n    TipsLineSpacing = 5,\n    NVLBounds = { X = 125, Y = 85, Width = 1024, Height = 400 },\n    ADVBounds = { X = 161, Y = 522, Width = 960, Height = 180 },\n    ADVBoxSprite = \"ADVBox\",\n    ADVBoxPos = { X = 127, Y = 500 },\n    FadeOutDuration = 0.33,\n    FadeInDuration = 0.33,\n    TextFadeInDuration = 0.33,\n    TextFadeOutDuration = 0.33,\n    DialogueBoxCurrentType = DialogueBoxType.MO6TW,\n    ADVBoxPartLeft = \"ADVBoxPartLeft\",\n    ADVBoxPartRight = \"ADVBoxPartRight\",\n    ADVBoxPartLeftPos = { X = 101, Y = 500 },\n    ADVBoxPartRightPos = { X = 1153, Y = 500 },\n    ADVBoxDecoration = \"ADVBoxDecoration\",\n    ADVBoxDecorationPos = { X = 1001, Y = 646 },\n    NVLBoxMaxOpacity = 0.55,\n\n    ADVNameAlignment = TextAlignment.Left,\n    ADVNameFontSize = 32,\n    ADVNamePos = { X = 120, Y = 462 },\n\n    NametagCurrentType = NametagType.ThreePiece,\n    NametagPosition = { X = 100, Y = 458 },\n    NametagLeftSprite = \"NametagLeftSprite\",\n    NametagMiddleSprite = \"NametagMiddleSprite\",\n    NametagRightSprite = \"NametagRightSprite\",\n    NametagMiddleBaseWidth = 0.0,\n\n    WaitIconCurrentType = WaitIconType.RotateZ,\n    WaitIconSprite = \"DialogueWaitIcon\",\n    WaitIconOffset = { X = 4, Y = 4 },\n    WaitIconAnimationDuration = 3.2,\n    DialogueFont = \"Default\",\n    SetFontSizeRatio = 1000.0,\n    DefaultFontSize = 32,\n    RubyFontSize = 14,\n    RubyYOffset = -18,\n    ColorTable = {\n        {0xFFFFFF, 0x000000}, {0x5080FF, 0x000000},\n        {0xFF7080, 0x000000}, {0xFFA0F8, 0x000000},\n        {0x46FF80, 0x000000}, {0x90FFFF, 0x000000},\n        {0xFFFF70, 0x000000}, {0x80FFC0, 0x000000},\n        {0xFFB080, 0x000000}, {0xB080FF, 0x000000},\n        {0x000000, 0x808080}, {0x000000, 0x5080FF},\n        {0x000000, 0xFF7080}, {0x000000, 0xFFA0F8},\n        {0x000000, 0x268840}, {0x000000, 0x409999},\n        {0x000000, 0x888830}, {0x000000, 0x80FFC0},\n        {0x000000, 0xFFB080}, {0x000000, 0xB080FF},\n        {0xD0D0D0, 0x000000}, {0xD0D0FF, 0x000000},\n        {0xFFD0D0, 0x000000}, {0xFFD0FF, 0x000000},\n        {0xD0FFD0, 0x000000}, {0xD0FFFF, 0x000000},\n        {0xFFFFD0, 0x000000}, {0xE8FFD0, 0x000000},\n        {0xFFE8D0, 0x000000}, {0xD0E8FF, 0x000000},\n        {0xFFFFFF, 0x808080}, {0xFFFFFF, 0x5080FF},\n        {0xFFFFFF, 0xFF7080}, {0xFFFFFF, 0xFFA0F8},\n        {0xFFFFFF, 0x46FF80}, {0xFFFFFF, 0x90FFFF},\n        {0xFFFFFF, 0xFFFF70}, {0xFFFFFF, 0x80FFC0},\n        {0xFFFFFF, 0xFFB080}, {0xFFFFFF, 0xB080FF},\n        {0xFFEEEE, 0x000000}, {0xFFCCCC, 0x000000},\n        {0xFFAAAA, 0x000000}, {0xFF9999, 0x000000},\n        {0xFF8888, 0x000000}, {0xFFFF00, 0x000000},\n        {0xFEF000, 0x000000}, {0xFF7777, 0x000000},\n        {0xFF6666, 0x000000}, {0xFF5555, 0x000000},\n        {0xFF4444, 0x000000}, {0xFF3333, 0x000000},\n        {0xFF2222, 0x000000}, {0xFF0000, 0x000000},\n        {0xDD0000, 0x000000}, {0xBB0000, 0x000000},\n        {0xB00000, 0x000000}, {0xAA0000, 0x000000},\n        {0x950000, 0x000000}, {0x808080, 0x000000},\n        {0xAAAAAA, 0x000000}, {0xAAC1C9, 0x000000},\n        {0x000000, 0x000000}, {0x000000, 0x000000},\n        {0x000000, 0x000000}, {0x000000, 0x000000},\n        {0x000000, 0x000000}, {0xF80B0B, 0x000000},\n        {0xF8910B, 0x000000}, {0x33F12A, 0x000000},\n        {0x44DD66, 0x000000}, {0xAAFFAA, 0x000000}\n    },\n    MaxPageSize = 2000,\n    PageCount = 3,\n    ColorTagIsUint8 = true\n};\n\nroot.Sprites[\"NametagLeftSprite\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 1, Y = 266, Width = 23, Height = 41 }\n};\n\nroot.Sprites[\"NametagMiddleSprite\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 24, Y = 266, Width = 190, Height = 41 }\n};\n\nroot.Sprites[\"NametagRightSprite\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 214, Y = 266, Width = 23, Height = 41 }\n};\n"
  },
  {
    "path": "profiles/mo6tw/font.lua",
    "content": "-- WARNING, 0-INDEXED ARRAYS AHEAD\n\nroot.Fonts = {\n    [\"Default\"] = {\n        Type = FontType.Basic,\n        Sheet = \"Font\",\n        Columns = 64,\n        Rows = 39,\n        AdvanceWidths = {},\n        AdvanceWidthsEmWidth = 32,\n        LineSpacing = 22,\n    }\n};\n\nlocal westernAdvanceWidths = {\n    [0]=0x20, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,\n        0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,\n        0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,\n        0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,\n        0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,\n        0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x20, 0x20, 0x20, 0x20, 0x20,\n        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x13, 0x0B, 0x13, 0x14,\n        0x14, 0x12, 0x12, 0x13, 0x14, 0x13, 0x1A, 0x15, 0x17, 0x17, 0x14, 0x14,\n        0x18, 0x18, 0x08, 0x14, 0x17, 0x15, 0x1C, 0x17, 0x18, 0x15, 0x19, 0x17,\n        0x16, 0x18, 0x17, 0x19, 0x1E, 0x1A, 0x1A, 0x16, 0x13, 0x14, 0x13, 0x14,\n        0x13, 0x10, 0x14, 0x13, 0x08, 0x0D, 0x12, 0x08, 0x1A, 0x13, 0x15, 0x14,\n        0x13, 0x0E, 0x12, 0x0F, 0x13, 0x14, 0x1C, 0x16, 0x14, 0x11, 0x10, 0x13,\n        0x08, 0x08, 0x0A, 0x0A, 0x14, 0x0A, 0x0A, 0x09, 0x08, 0x08, 0x0D, 0x0D,\n        0x0C, 0x0C, 0x0A, 0x0A, 0x0B, 0x0B, 0x0A, 0x0A, 0x10, 0x10, 0x11, 0x11,\n        0x0F, 0x0F, 0x0F, 0x0F, 0x0C, 0x0C, 0x19, 0x19, 0x0C, 0x0C, 0x0C, 0x1E,\n        0x1E, 0x1C, 0x16, 0x1E, 0x1B, 0x1A, 0x18, 0x1C, 0x1C, 0x1B, 0x1C, 0x1C,\n        0x1A, 0x1C, 0x1A, 0x18, 0x1A, 0x1A, 0x1A, 0x19, 0x1B, 0x1C, 0x18, 0x1A,\n        0x1A, 0x1C, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n};\n\nfor i = 0, (64 * 39) - 1 do\n    if i <= #westernAdvanceWidths then\n        root.Fonts[\"Default\"].AdvanceWidths[i] = westernAdvanceWidths[i];\n    else\n        root.Fonts[\"Default\"].AdvanceWidths[i] = root.Fonts[\"Default\"].AdvanceWidthsEmWidth;\n    end\nend"
  },
  {
    "path": "profiles/mo6tw/game.lua",
    "content": "root.ActiveRenderer = RendererType.OpenGL;\n\nroot.LayerCount = 100;\nroot.GameFeatures = GameFeature.Sc3VirtualMachine | GameFeature.Renderer2D | GameFeature.Input | GameFeature.Audio | GameFeature.Video | GameFeature.DebugMenu;\nroot.DesignWidth = 1280;\nroot.DesignHeight = 720;\n\nroot.WindowName = \"Memories Off 6 ~T-Wave~\";\nroot.WindowIconPath = \"games/mo6tw/icondata/icon.png\";\nroot.CursorArrowPath = \"games/mo6tw/icondata/cursor_arrow.png\";\nroot.CursorPointerPath = \"games/mo6tw/icondata/cursor_pointer.png\";\n\nroot.CharaIsMvl = false;\nroot.LayFileBigEndian = true;\nroot.LayFileTexXMultiplier = 2048;\nroot.LayFileTexYMultiplier = 1024;\n\nroot.Vm = {\n    StartScript=0,\n    StartScriptBuffer=0,\n    GameInstructionSet=InstructionSet.MO6TW,\n    UseReturnIds=false,\n    RestartMaskUsesThreadAlpha=true,\n\n    ScrWorkChaStructSize = 20,\n    ScrWorkChaOffsetStructSize = 10,\n    ScrWorkBgStructSize = 20,\n    ScrWorkBgOffsetStructSize = 10,\n};\n\ninclude('common/scriptinput.lua');\ninclude('common/scriptvars.lua');\ninclude('mo6tw/config.lua');\ninclude('mo6tw/scriptinput.lua');\ninclude('mo6tw/scriptvars.lua');\ninclude('mo6tw/savedata.lua');\ninclude('mo6tw/tipssystem.lua');\ninclude('mo6tw/vfs.lua');\ninclude('mo6tw/sprites.lua');\ninclude('common/animation.lua');\ninclude('mo6tw/charset.lua');\ninclude('mo6tw/font.lua');\ninclude('mo6tw/dialogue.lua');\ninclude('mo6tw/hud/saveicon.lua');\ninclude('mo6tw/hud/loadingdisplay.lua');\ninclude('mo6tw/hud/datedisplay.lua');\ninclude('mo6tw/hud/titlemenu.lua');\ninclude('mo6tw/hud/systemmenu.lua');\ninclude('mo6tw/hud/savemenu.lua');\ninclude('mo6tw/hud/sysmesboxdisplay.lua');\ninclude('mo6tw/hud/selectiondisplay.lua');\ninclude('mo6tw/hud/backlogmenu.lua');\ninclude('mo6tw/hud/optionsmenu.lua');\ninclude('mo6tw/hud/tipsmenu.lua');\ninclude('mo6tw/hud/extramenus.lua');\ninclude('mo6tw/hud/tipsnotification.lua');\n"
  },
  {
    "path": "profiles/mo6tw/hud/backlogmenu.lua",
    "content": "root.BacklogMenu = {\n    Type = BacklogMenuType.MO6TW,\n    DrawType = DrawComponentType.ExtrasScenes,\n\n    BacklogBackgroundSprite = \"BacklogBackground\",\n\n    EntryHighlightSprite = \"EntryHighlight\",\n    EntryHighlightLocation = EntryHighlightLocationType.BottomLeftOfEntry,\n    EntryHighlightOffset = { X = 0, Y = 0 },\n    EntryHighlightPadding = 0.0,\n\n    VoiceIconSprite = \"VoiceIcon\",\n    VoiceIconOffset = { X = 0, Y = 0 },\n\n    ScrollbarTrackSprite = \"ScrollbarTrack\",\n    ScrollbarThumbSprite = \"ScrollbarThumb\",\n    ScrollbarPosition = { X = 1165, Y = 98 },\n    ScrollbarThumbLength = 30,\n\n    EntriesStart = { X = 163, Y = 90 },\n    RenderingBounds = { X = 83, Y = 83, Width = 1117, Height = 590 },\n    EntryYPadding = 22,\n\n    HoverBounds = { X = 130, Y = 83, Width = 992, Height = 590 },\n\n    FadeInDuration = 0.2,\n    FadeOutDuration = 0.2,\n\n    ScrollingSpeed = 600,\n    PageUpDownHeight = 520\n};\n\nroot.Sprites[\"BacklogBackground\"] = {\n    Sheet = \"Backlog\",\n    Bounds = { X = 0, Y = 0, Width = 1280, Height = 720 },\n};\n\nroot.Sprites[\"VoiceIcon\"] = {\n    Sheet = \"Backlog\",\n    Bounds = { X = 1281, Y = 1, Width = 30, Height = 30 },\n};\n\nroot.Sprites[\"EntryHighlight\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 65, Y = 1, Width = 94, Height = 30 }\n};\n\nroot.Sprites[\"ScrollbarThumb\"] = {\n    Sheet = \"Backlog\",\n    Bounds = { X = 1281, Y = 32, Width = 30, Height = 30 },\n};\n\nroot.Sprites[\"ScrollbarTrack\"] = {\n    Sheet = \"Backlog\",\n    Bounds = { X = 1500, Y = 0, Width = 8, Height = 567 },\n};"
  },
  {
    "path": "profiles/mo6tw/hud/datedisplay.lua",
    "content": "local sheet = \"Data\";\nlocal name = \"Date\";\n\nlocal yearNumFirstX = 1525;\nlocal yearNumFirstY = 63;\nlocal yearNumWidth = 25;\nlocal yearNum1Width = 8;\nlocal yearNumHeight = 20;\n\nlocal numFirstX = 1524;\nlocal numFirstY = 33;\nlocal numWidth = 40;\nlocal num1Width = 12;\nlocal numHeight = 28;\n\nlocal weekFirstX = 1785;\nlocal weekFirstY = 63;\nlocal weekSecondX = 1525;\nlocal weekSecondY = 85;\nlocal weekWidth = 73;\nlocal weekFriWidth = 56;\nlocal weekHeight = 20;\n\nroot.DateDisplay = {\n    Type = DateDisplayType.RNE,\n    YearNumSprites = {},\n    MonthNumSprites = {},\n    DayNumSprites = {},\n    WeekSprites = {},\n    BackgroundStartPos = { X = 1088, Y = 73 },\n    BackgroundEndPos = { X = 1088 - 256, Y = 73 },\n    DateStartX = 1167,\n    YearWeekY = 60,\n    MonthDayY = 52,\n    Spacing = 1,\n    FadeInDuration = 0.5,\n    FadeOutDuration = 0.5\n};\n\nfor i = 0, 9 do\n    local yearW;\n    if i == 1 then\n        yearW = yearNum1Width;\n    else\n        yearW = yearNumWidth;\n    end\n\n    root.Sprites[name .. \"YearNum\" .. i] = {\n        Sheet = sheet,\n        Bounds = {\n            X = yearNumFirstX,\n            Y = yearNumFirstY,\n            Width = yearW,\n            Height = yearNumHeight\n        }\n    };\n    root.DateDisplay.YearNumSprites[#root.DateDisplay.YearNumSprites + 1] = name .. \"YearNum\" .. i;\n\n    local numW\n    if i == 1 then\n        numW = num1Width;\n    else\n        numW = numWidth;\n    end\n\n    root.Sprites[name .. \"Num\" .. i] = {\n        Sheet = sheet,\n        Bounds = {\n            X = numFirstX,\n            Y = numFirstY,\n            Width = numW,\n            Height = numHeight\n        }\n    };\n    root.DateDisplay.MonthNumSprites[#root.DateDisplay.MonthNumSprites + 1] = name .. \"Num\" .. i;\n    root.DateDisplay.DayNumSprites[#root.DateDisplay.DayNumSprites + 1] = name .. \"Num\" .. i;\n\n    if i == 1 then\n        yearNumFirstX = yearNumFirstX + yearNum1Width;\n        numFirstX = numFirstX + num1Width;\n    else\n        yearNumFirstX = yearNumFirstX + yearNumWidth;\n        numFirstX = numFirstX + numWidth;\n    end\nend\n\nfor i = 0, 6 do\n    if i > 1 then\n        local weekW;\n        if i == 5 then\n            weekW = weekFriWidth;\n        else\n            weekW = weekWidth;\n        end\n\n        root.Sprites[name .. \"Week\" .. i] = {\n            Sheet = sheet,\n            Bounds = {\n                X = weekSecondX,\n                Y = weekSecondY,\n                Width = weekW,\n                Height = weekHeight\n            }\n        };\n        if i == 5 then\n            weekSecondX = weekSecondX + weekFriWidth;\n        else\n            weekSecondX = weekSecondX + weekWidth;\n         end\n    else\n        root.Sprites[name .. \"Week\" .. i] = {\n            Sheet = sheet,\n            Bounds = {\n                X = weekFirstX,\n                Y = weekFirstY,\n                Width = weekWidth,\n                Height = weekHeight\n            }\n        };\n        weekFirstX = weekFirstX + weekWidth;\n    end\n    root.DateDisplay.WeekSprites[#root.DateDisplay.WeekSprites + 1] = name .. \"Week\" .. i;\nend\n\nroot.Sprites[name .. \"MonthDaySeparator\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 1897,\n        Y = 33,\n        Width = 10,\n        Height = 28\n    }\n};\nroot.DateDisplay.MDSeparatorSprite = name .. \"MonthDaySeparator\";\n\nroot.Sprites[name .. \"DayYearSeparator\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 1758,\n        Y = 63,\n        Width = 8,\n        Height = 20\n    }\n};\nroot.DateDisplay.DYSeparatorSprite = name .. \"DayYearSeparator\";\n\nroot.Sprites[name .. \"OpenBracket\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 1766,\n        Y = 63,\n        Width = 8,\n        Height = 20\n    }\n};\nroot.DateDisplay.OpenBracketSprite = name .. \"OpenBracket\";\n\nroot.Sprites[name .. \"CloseBracket\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 1776,\n        Y = 63,\n        Width = 8,\n        Height = 20\n    }\n};\nroot.DateDisplay.CloseBracketSprite = name .. \"CloseBracket\";\n\nroot.Sprites[name .. \"Background\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 1525,\n        Y = 1,\n        Width = 450,\n        Height = 28\n    }\n};\nroot.DateDisplay.BackgroundSprite = name .. \"Background\";"
  },
  {
    "path": "profiles/mo6tw/hud/extramenus.lua",
    "content": "root.ExtraMenus = {\n    ClearListMenu = {\n        DrawType = DrawComponentType.PlayData,\n        Type = ClearListMenuType.MO6TW,\n        BackgroundSprite = \"ClearListBackground\",\n        WindowSprite = \"ClearListWindow\",\n        WindowPosition = { X = 85, Y = 115 },\n        WindowSpritePartLeft = \"ClearListSceneWindowLeft\",\n        WindowSpritePartRight = \"ClearListSceneWindowRight\",\n        FontSize = 32,\n        SeparatorTable = 0,\n        SeparatorEntry = 13,\n        LabelPosition = { X = 790, Y = 32 },\n        ClearListLabel = \"ClearListLabel\",\n        EndingsLabelPosition = { X = 64, Y = 116 },\n        EndingsLabel = \"EndingsLabel\",\n        EndingCountPosition =  { X = 560, Y = 172 },\n        ScenesLabelPosition = { X = 128, Y = 228 },\n        ScenesLabel = \"ScenesLabel\",\n        SceneCountPosition =  { X = 560, Y = 284 },\n        CompletionLabelPosition = { X = 96, Y = 340 },\n        CompletionLabel = \"CompletionLabel\",\n        CompletionPosition =  { X = 560, Y = 396 },\n        AlbumLabelPosition = { X = 128, Y = 452 },\n        AlbumLabel = \"AlbumLabel\",\n        AlbumCountPosition = { X = 560, Y = 508 },\n        PlayTimeLabelPosition = { X = 64, Y = 564 },\n        PlayTimeLabel = \"PlayTimeLabel\",\n        PlayTimeTextTable = 0,\n        PlayTimeSecondsTextEntry = 9,\n        PlayTimeMinutesTextEntry = 8,\n        PlayTimeHoursTextEntry = 10,\n        PlayTimeSecondsTextPosition =  { X = 562, Y = 620 },\n        PlayTimeMinutesTextPosition =  { X = 514, Y = 620 },\n        PlayTimeHoursTextPosition =  { X = 466, Y = 620 },\n        PlayTimeSecondsPosition =  { X = 560, Y = 620 },\n        PlayTimeMinutesPosition =  { X = 512, Y = 620 },\n        PlayTimeHoursPosition =  { X = 464, Y = 620 },\n        ClearListColorIndex = 0,\n        ClearListTextBackground = \"ClearListTextBackground\",\n        ClearListTextBGOffset = { X = 0, Y = 48 },\n        EndingListLabel = \"EndingListLabel\",\n        EndingCount = 14,\n        EndingsListNumberInitialPosition = { X = 108, Y = 142 },\n        EndingsListTextInitialPosition = { X = 156, Y = 142 },\n        EndingsListTextMargin = { X = 0, Y = 36 },\n        EndingsListTextFontSize = 28,\n        EndingsListTextLockedTable = 0,\n        EndingsListTextLockedEntry = 15,\n        EndingsListTextTable = 3,\n        EndingsListTextColorIndex = 0,\n        SceneTitleLabel = \"SceneTitleLabel\",\n        SceneCount = 158,\n        SceneListNumberInitialPosition = { X = 108, Y = 140 },\n        SceneListTextInitialPosition = { X = 156, Y = 140 },\n        SceneListTextMargin = { X = 0, Y = 36 },\n        SceneListFontSize = 24,\n        SceneListTextTable = 4,\n        SceneTitleItemsRenderingBounds = { X = 90, Y = 120, Width = 795, Height = 538 },\n        SceneListColorIndex = 0,\n        SceneTitleLockedTable = 0,\n        SceneTitleLockedEntry = 15,\n        SceneTitleItemsWidth = 838,\n        ScrollbarStart = 140,\n        ScrollAreaHeight = 500,\n        ScrollbarTrackSprite = \"ClearListScrollbarTrack\",\n        ScrollbarThumbSprite = \"ClearListScrollbarThumb\",\n        ScrollbarPosition = { X = 870, Y = 130 },\n        ArrowsAnimationDuration = 0.3,\n        ArrowLeft = \"ClearListArrowLeft\",\n        ArrowLeftPosition = { X = 58, Y = 337 },\n        ArrowRight = \"ClearListArrowRight\",\n        ArrowRightPosition = { X = 1190, Y = 337 },\n        FadeInDuration = 0.2,\n        FadeOutDuration = 0.2\n    },\n    MovieMenu = {\n        DrawType = DrawComponentType.ExtrasMovieMode,\n        Type = MovieMenuType.MO6TW,\n        BackgroundSprite = \"MovieMenuBackground\",\n        FirstOPTopPartSprite = \"MovieMenuFirstOPTopPart\",\n        FirstOPBottomPartSprite = \"MovieMenuFirstOPBottomPart\",\n        SecondOPTopPartSprite = \"MovieMenuSecondOPTopPart\",\n        SecondOPBottomPartSprite = \"MovieMenuSecondOPBottomPart\",\n        UnlockedMovieThumbnailSprites = {},\n        LockedMovieThumbnailSprites = {},\n        SelectionHighlightTopLeft = \"MovieMenuSelectionHighlightTopLeft\",\n        SelectionHighlightTopRight = \"MovieMenuSelectionHighlightTopRight\",\n        SelectionHighlightBottomLeft = \"MovieMenuSelectionHighlightBottomLeft\",\n        SelectionHighlightBottomRight = \"MovieMenuSelectionHighlightBottomRight\",\n        ItemCount = 12,\n        ItemsPerRow = 4,\n        InitialItemPosition = { X = 151, Y = 80 },\n        ItemOffset = { X = 250, Y = 206 },\n        HighlightAnimationDuration = 0.5,\n        HighlightTopLeftOffset = { X = 8, Y = 8 },\n        HighlightTopRightOffset = { X = 12, Y = 9 },\n        HighlightBottomLeftOffset = { X = 8, Y = 14 },\n        HighlightBottomRightOffset = { X = 13, Y = 14 },\n        FadeInDuration = 0.2,\n        FadeOutDuration = 0.2\n    },\n    ActorsVoiceMenu = {\n        DrawType = DrawComponentType.ExtrasActorsVoice,\n        Type = ActorsVoiceMenuType.MO6TW,\n        BackgroundSprite = \"ActorsVoiceBackground\",\n        UnlockedSprites = {},\n        LockedSprites = {},\n        UnlockedHighlightedSprites = {},\n        LockedHighlightedSprites = {},\n        InitialItemPosition = { X = 256, Y = 112 },\n        ItemOffset = { X = 0, Y = 60 },\n        CharacterBackgroundBufferId = 4,\n        FadeInDuration = 0.2,\n        FadeOutDuration = 0.2\n    },\n    MusicMenu = {\n        DrawType = DrawComponentType.ExtrasActorsVoice,\n        Type = MusicMenuType.MO6TW,\n        BackgroundSprite = \"MusicMenuBackground\",\n        ItemsWindow = \"MusicMenuItemsWindow\",\n        ItemsWindowPosition = { X = 685, Y = 233 },\n        ItemsWindowRenderingBounds = { X = 702, Y = 242, Width = 420, Height = 428 },\n        MusicListMargin = { X = 0, Y = 26 },\n        MusicListInitialPosition = { X = 723, Y = 252 },\n        PlaybackWindow = \"MusicMenuPlaybackWindow\",\n        PlaybackWindowPosition = { X = 217, Y = 98 },\n        PlaybackModeLabels = {},\n        PlaybackModeLabelPosition = { X = 886, Y = 178 },\n        CurrentlyPlayingLabelPosition = { X = 337, Y = 143 },\n        Thumbnails = {},\n        ThumbnailPosition = { X = 221, Y = 297 },\n        ItemNames = {},\n        ItemNameHighlightOffset = { X = 0, Y = -4 },\n        Playlist =\n            {\n                30, 0, 1, 2, 3, 4, 5, 6, 7, 10, 11, 8, 9, 25, 12,\n                13, 14, 15, 16, 17, 18, 19, 26, 27, 28, 29, 20,\n                24, 22, 31, 32, 33, 34, 35, 37, 39, 40, 36, 41\n            },\n        TimerChars = {},\n        TimerInitialPosition = { X = 825, Y = 141 },\n        TimerMargin = { X = 15, Y = 0 },\n        LockedItem = \"MusicMenuLockedItem\",\n        ScrollbarThumb = \"MusicMenuScrollbarThumb\",\n        ScrollbarTrack = \"MusicMenuScrollbarTrack\",\n        ScrollbarPosition = { X = 1134, Y = 244 },\n        ScrollbarStart = 252,\n        FadeInDuration = 0.2,\n        FadeOutDuration = 0.2\n    },\n    AlbumMenu = {\n        DrawType = DrawComponentType.Album,\n        Type = MusicMenuType.MO6TW,\n        CharacterButtons = {},\n        HighlightedCharacterButtons = {},\n        BackgroundSprite = \"AlbumMenuBackground\",\n        InitialButtonPosition = { X = 149, Y = 115 },\n        ButtonOddX = 421,\n        ButtonEvenX = 149,\n        ButtonMargin = { X = 0, Y = 90 },\n        HighlightAnimationDuration = 0.5,\n        YunoButtonIdx = 3,\n        SuzuButtonIdx = 4,\n        CharacterPortraits = {},\n        OthersPortraitTopPart = \"AlbumMenuOthersPortraitTop\",\n        OthersPortraitBottomPart = \"AlbumMenuOthersPortraitBottom\",\n        PortraitPosition = { X = 1282, Y = 80 },\n        OthersPortraitPosition = { X = 1282, Y = 124 },\n        ThumbnailsPerRow = 3,\n        ThumbnailsPerColumn = 3,\n        Thumbnails = {},\n        ThumbnailOffsets = {0, 20, 37, 53, 65, 78},\n        LockedThumbnail = \"AlbumMenuLockedThumbnail\",\n        ThumbnailBorder = \"AlbumMenuThumbnailBorder\",\n        ThumbnailHighlightTopLeft = \"AlbumMenuThumbnailHighlightTopLeft\",\n        ThumbnailHighlightTopRight = \"AlbumMenuThumbnailHighlightTopRight\",\n        ThumbnailHighlightBottomLeft = \"AlbumMenuThumbnailHighlightBottomLeft\",\n        ThumbnailHighlightBottomRight = \"AlbumMenuThumbnailHighlightBottomRight\",\n        ThumbnailGridFirstPosition = { X = 154, Y = 130 },\n        ThumbnailGridMargin = { X = 262, Y = 188 },\n        ThumbnailGridBounds = { X = 127, Y = 106, Width = 810, Height = 550 },\n        ArrowsAnimationDuration = 0.2,\n        ArrowUp = \"AlbumMenuArrowUp\",\n        ArrowUpPosition = { X = 519, Y = 84 },\n        ArrowDown = \"AlbumMenuArrowDown\",\n        ArrowDownPosition = { X = 519, Y = 665 },\n        ThumbnailButtonBorderOffset = { X = -8, Y = -9 },\n        ThumbnailButtonTextFontSize = 21,\n        ThumbnailButtonTextColorIndex = 0,\n        ThumbnailButtonTextOffset = { X = -6, Y = -6 },\n        FadeInDuration = 0.2,\n        FadeOutDuration = 0.2\n    }\n};\n\n-- Clear list\n\nroot.Sprites[\"ClearListBackground\"] = {\n    Sheet = \"ClearList\",\n    Bounds = { X = 0, Y = 0, Width = 1280, Height = 720 }\n};\n\nroot.Sprites[\"ClearListLabel\"] = {\n    Sheet = \"ClearList\",\n    Bounds = { X = 1291, Y = 659, Width = 410, Height = 62 }\n};\n\nroot.Sprites[\"EndingsLabel\"] = {\n    Sheet = \"ClearList\",\n    Bounds = { X = 105, Y = 725, Width = 576, Height = 48 }\n};\n\nroot.Sprites[\"ScenesLabel\"] = {\n    Sheet = \"ClearList\",\n    Bounds = { X = 105, Y = 774, Width = 576, Height = 48 }\n};\n\nroot.Sprites[\"CompletionLabel\"] = {\n    Sheet = \"ClearList\",\n    Bounds = { X = 105, Y = 823, Width = 576, Height = 48 }\n};\n\nroot.Sprites[\"AlbumLabel\"] = {\n    Sheet = \"ClearList\",\n    Bounds = { X = 105, Y = 872, Width = 576, Height = 48 }\n};\n\nroot.Sprites[\"PlayTimeLabel\"] = {\n    Sheet = \"ClearList\",\n    Bounds = { X = 105, Y = 921, Width = 576, Height = 48 }\n};\n\nroot.Sprites[\"ClearListTextBackground\"] = {\n    Sheet = \"ClearList\",\n    Bounds = { X = 105, Y = 970, Width = 576, Height = 48 }\n};\n\nroot.Sprites[\"EndingListLabel\"] = {\n    Sheet = \"ClearList\",\n    Bounds = { X = 1291, Y = 723, Width = 410, Height = 62 }\n};\n\nroot.Sprites[\"SceneTitleLabel\"] = {\n    Sheet = \"ClearList\",\n    Bounds = { X = 1291, Y = 595, Width = 410, Height = 62 }\n};\n\nroot.Sprites[\"ClearListWindow\"] = {\n    Sheet = \"ClearList\",\n    Bounds = { X = 1291, Y = 1, Width = 638, Height = 554 }\n};\n\nroot.Sprites[\"ClearListSceneWindowLeft\"] = {\n    Sheet = \"ClearList\",\n    Bounds = { X = 1291, Y = 1, Width = 538, Height = 554 }\n};\n\nroot.Sprites[\"ClearListSceneWindowRight\"] = {\n    Sheet = \"ClearList\",\n    Bounds = { X = 1629, Y = 1, Width = 300, Height = 554 }\n};\n\nroot.Sprites[\"ClearListScrollbarThumb\"] = {\n    Sheet = \"ClearList\",\n    Bounds = { X = 65, Y = 725, Width = 38, Height = 46 },\n};\n\nroot.Sprites[\"ClearListScrollbarTrack\"] = {\n    Sheet = \"ClearList\",\n    Bounds = { X = 1939, Y = 17, Width = 8, Height = 514 },\n};\n\nroot.Sprites[\"ClearListArrowLeft\"] = {\n    Sheet = \"ClearList\",\n    Bounds = { X = 1, Y = 725, Width = 30, Height = 46 },\n};\n\nroot.Sprites[\"ClearListArrowRight\"] = {\n    Sheet = \"ClearList\",\n    Bounds = { X = 33, Y = 725, Width = 30, Height = 46 },\n};\n\n-- Movie menu\n\nroot.Sprites[\"MovieMenuBackground\"] = {\n    Sheet = \"Movie\",\n    Bounds = { X = 0, Y = 0, Width = 1280, Height = 720 }\n};\n\nroot.Sprites[\"MovieMenuFirstOPTopPart\"] = {\n    Sheet = \"Movie\",\n    Bounds = { X = 465, Y = 913, Width = 230, Height = 95 }\n};\n\nroot.Sprites[\"MovieMenuFirstOPBottomPart\"] = {\n    Sheet = \"Movie\",\n    Bounds = { X = 697, Y = 913, Width = 230, Height = 95 }\n};\n\nroot.Sprites[\"MovieMenuSecondOPTopPart\"] = {\n    Sheet = \"Movie\",\n    Bounds = { X = 1, Y = 913, Width = 230, Height = 95 }\n};\n\nroot.Sprites[\"MovieMenuSecondOPBottomPart\"] = {\n    Sheet = \"Movie\",\n    Bounds = { X = 233, Y = 913, Width = 230, Height = 95 }\n};\n\nroot.Sprites[\"MovieMenuSelectionHighlightTopLeft\"] = {\n    Sheet = \"Movie\",\n    Bounds = { X = 1161, Y = 721, Width = 22, Height = 22 }\n};\n\nroot.Sprites[\"MovieMenuSelectionHighlightTopRight\"] = {\n    Sheet = \"Movie\",\n    Bounds = { X = 1185, Y = 721, Width = 22, Height = 22 }\n};\n\nroot.Sprites[\"MovieMenuSelectionHighlightBottomLeft\"] = {\n    Sheet = \"Movie\",\n    Bounds = { X = 1161, Y = 745, Width = 22, Height = 22 }\n};\n\nroot.Sprites[\"MovieMenuSelectionHighlightBottomRight\"] = {\n    Sheet = \"Movie\",\n    Bounds = { X = 1185, Y = 745, Width = 22, Height = 22 }\n};\n\nlocal firstX = 1281;\nlocal firstY = 1;\n\nfor i = 1, 10 do\n    root.Sprites[\"UnlockedMovieThumbnail\" .. i] = {\n        Sheet = \"Movie\",\n        Bounds = {\n            X = firstX,\n            Y = firstY,\n            Width = 230,\n            Height = 190\n        }\n    };\n    root.ExtraMenus.MovieMenu.UnlockedMovieThumbnailSprites[#root.ExtraMenus.MovieMenu.UnlockedMovieThumbnailSprites + 1] = \"UnlockedMovieThumbnail\" .. i;\n\n    if i % 3 == 0 then\n        firstX = 1281;\n        firstY = firstY + 191;\n    else\n        firstX = firstX + 231;\n    end\nend\n\nfirstX = 1;\nfirstY = 721;\n\nfor i = 1, 10 do\n    if i == 6 then\n        firstX = 1513;\n        firstY = 577;\n    elseif i == 8 then\n        firstX = 1281;\n        firstY = 769;\n    end\n\n    root.Sprites[\"LockedMovieThumbnail\" .. i] = {\n        Sheet = \"Movie\",\n        Bounds = {\n            X = firstX,\n            Y = firstY,\n            Width = 230,\n            Height = 190\n        }\n    };\n    root.ExtraMenus.MovieMenu.LockedMovieThumbnailSprites[#root.ExtraMenus.MovieMenu.LockedMovieThumbnailSprites + 1] = \"LockedMovieThumbnail\" .. i;\n\n    firstX = firstX + 231;\nend\n\n-- I don't fucking know, do you know?\n-- I don't, fuck.\nlocal temp = root.ExtraMenus.MovieMenu.UnlockedMovieThumbnailSprites[7];\nroot.ExtraMenus.MovieMenu.UnlockedMovieThumbnailSprites[7] = root.ExtraMenus.MovieMenu.UnlockedMovieThumbnailSprites[8];\nroot.ExtraMenus.MovieMenu.UnlockedMovieThumbnailSprites[8] = temp;\n\ntemp = root.ExtraMenus.MovieMenu.LockedMovieThumbnailSprites[7];\nroot.ExtraMenus.MovieMenu.LockedMovieThumbnailSprites[7] = root.ExtraMenus.MovieMenu.LockedMovieThumbnailSprites[8];\nroot.ExtraMenus.MovieMenu.LockedMovieThumbnailSprites[8] = temp;\n\n-- Actors Voice menu\n\nroot.Sprites[\"ActorsVoiceBackground\"] = {\n    Sheet = \"ActorsVoiceBackground\",\n    Bounds = { X = 0, Y = 0, Width = 1280, Height = 720 }\n};\n\nfirstX = 1;\nfirstY = 1;\n\nfor i = 1, 8 do\n    root.Sprites[\"LockedHighlightedSprites\" .. i] = {\n        Sheet = \"ActorsVoice\",\n        Bounds = {\n            X = firstX,\n            Y = firstY,\n            Width = 774,\n            Height = 50\n        }\n    };\n    root.ExtraMenus.ActorsVoiceMenu.LockedHighlightedSprites[#root.ExtraMenus.ActorsVoiceMenu.LockedHighlightedSprites + 1] = \"LockedHighlightedSprites\" .. i;\n\n    root.Sprites[\"UnlockedHighlightedSprites\" .. i] = {\n        Sheet = \"ActorsVoice\",\n        Bounds = {\n            X = firstX + 776,\n            Y = firstY,\n            Width = 774,\n            Height = 50\n        }\n    };\n    root.ExtraMenus.ActorsVoiceMenu.UnlockedHighlightedSprites[#root.ExtraMenus.ActorsVoiceMenu.UnlockedHighlightedSprites + 1] = \"UnlockedHighlightedSprites\" .. i;\n\n    root.Sprites[\"LockedSprites\" .. i] = {\n        Sheet = \"ActorsVoice\",\n        Bounds = {\n            X = firstX,\n            Y = firstY + 448,\n            Width = 774,\n            Height = 50\n        }\n    };\n    root.ExtraMenus.ActorsVoiceMenu.LockedSprites[#root.ExtraMenus.ActorsVoiceMenu.LockedSprites + 1] = \"LockedSprites\" .. i;\n\n    root.Sprites[\"UnlockedSprites\" .. i] = {\n        Sheet = \"ActorsVoice\",\n        Bounds = {\n            X = firstX + 776,\n            Y = firstY + 448,\n            Width = 774,\n            Height = 50\n        }\n    };\n    root.ExtraMenus.ActorsVoiceMenu.UnlockedSprites[#root.ExtraMenus.ActorsVoiceMenu.UnlockedSprites + 1] = \"UnlockedSprites\" .. i;\n\n    firstY = firstY + 56;\nend\n\n-- Music menu\n\nroot.Sprites[\"MusicMenuBackground\"] = {\n    Sheet = \"Music\",\n    Bounds = { X = 0, Y = 0, Width = 1280, Height = 720 }\n};\n\nroot.Sprites[\"MusicMenuItemsWindow\"] = {\n    Sheet = \"Music\",\n    Bounds = { X = 1281, Y = 1, Width = 480, Height = 454 }\n};\n\nroot.Sprites[\"MusicMenuPlaybackWindow\"] = {\n    Sheet = \"Music\",\n    Bounds = { X = 1, Y = 721, Width = 954, Height = 114 }\n};\n\nfirstX = 1;\nfirstY = 1;\nlocal sheetIdx = 0;\n\nfor i = 1, 39 do\n    root.Sprites[\"MusicMenuThumbnail\" .. i] = {\n        Sheet = \"MusicThumbnails\" .. sheetIdx,\n        Bounds = {\n            X = firstX,\n            Y = firstY,\n            Width = 400,\n            Height = 400\n        }\n    };\n    root.ExtraMenus.MusicMenu.Thumbnails[#root.ExtraMenus.MusicMenu.Thumbnails + 1] = \"MusicMenuThumbnail\" .. i;\n\n    if i % 10 == 0 then\n        sheetIdx = sheetIdx + 1;\n        firstX = 1;\n        firstY = 1;\n    elseif (i % 5 == 0) then\n        firstX = 1;\n        firstY = firstY + 402;\n    else\n        firstX = firstX + 402;\n    end\nend\n\nfirstX = 957;\nfirstY = 720;\n\nfor i = 0, 3 do\n    root.Sprites[\"MusicMenuPlaybackModeLabel\" .. i] = {\n        Sheet = \"Music\",\n        Bounds = {\n            X = firstX,\n            Y = firstY,\n            Width = 94,\n            Height = 24\n        }\n    };\n    root.ExtraMenus.MusicMenu.PlaybackModeLabels[#root.ExtraMenus.MusicMenu.PlaybackModeLabels + 1] = \"MusicMenuPlaybackModeLabel\" .. i;\n\n    firstY = firstY + 26;\nend\n\nfirstX = 1281;\nfirstY = 456;\n\nfor i = 1, 40 do\n    if i ~= 20 then\n        root.Sprites[\"MusicMenuItemName\" .. i] = {\n            Sheet = \"Music\",\n            Bounds = {\n                X = firstX,\n                Y = firstY,\n                Width = 358,\n                Height = 22\n            }\n        };\n        root.ExtraMenus.MusicMenu.ItemNames[#root.ExtraMenus.MusicMenu.ItemNames + 1] = \"MusicMenuItemName\" .. i;\n    end\n\n    if i % 20 == 0 then\n        firstX = 1640;\n        firstY = 456;\n    else\n        firstY = firstY + 24;\n    end\nend\n\nroot.Sprites[\"MusicMenuLockedItem\"] = {\n    Sheet = \"Music\",\n    Bounds = { X = 1281, Y = 912, Width = 358, Height = 22 }\n};\n\nroot.Sprites[\"MusicMenuScrollbarThumb\"] = {\n    Sheet = \"Music\",\n    Bounds = { X = 1, Y = 837, Width = 34, Height = 46 }\n};\n\nroot.Sprites[\"MusicMenuScrollbarTrack\"] = {\n    Sheet = \"Music\",\n    Bounds = { X = 1780, Y = 0, Width = 9, Height = 427 }\n};\n\nfirstX = 1053;\nfirstY = 721;\n\nfor i = 0, 10 do\n    root.Sprites[\"MusicMenuTimerChar\" .. i] = {\n        Sheet = \"Music\",\n        Bounds = {\n            X = firstX,\n            Y = firstY,\n            Width = 15,\n            Height = 24\n        }\n    };\n    root.ExtraMenus.MusicMenu.TimerChars[#root.ExtraMenus.MusicMenu.TimerChars + 1] = \"MusicMenuTimerChar\" .. i;\n\n    firstX = firstX + 17;\nend\n\n-- Album menu\n\nroot.Sprites[\"AlbumMenuBackground\"] = {\n    Sheet = \"Album\",\n    Bounds = { X = 0, Y = 0, Width = 1280, Height = 720 }\n};\n\nlocal buttonX = 1;\nlocal highlightedButtonX = 1281;\nlocal buttonY = 761;\nlocal highlightedButtonY = 1;\nlocal portraitX = 1;\nlocal portraitY = 1;\nlocal portraitWidths = {383, 413, 484, 355, 326};\n\nfor i = 1, 6 do\n    if i == 4 then\n        root.Sprites[\"AlbumMenuCharaButton\" .. i .. \"Locked\"] = {\n            Sheet = \"Album\",\n            Bounds = {\n                X = 1281,\n                Y = 529,\n                Width = 710,\n                Height = 86\n            }\n        };\n        root.Sprites[\"AlbumMenuCharaButtonHighlighted\" .. i .. \"Locked\"] = {\n            Sheet = \"Album\",\n            Bounds = {\n                X = 1281,\n                Y = 617,\n                Width = 710,\n                Height = 86\n            }\n        };\n        root.ExtraMenus.AlbumMenu.CharacterButtons[#root.ExtraMenus.AlbumMenu.CharacterButtons + 1] = \"AlbumMenuCharaButton\" .. i .. \"Locked\";\n        root.ExtraMenus.AlbumMenu.HighlightedCharacterButtons[#root.ExtraMenus.AlbumMenu.HighlightedCharacterButtons + 1] = \"AlbumMenuCharaButtonHighlighted\" .. i .. \"Locked\";\n    elseif i == 5 then\n        root.Sprites[\"AlbumMenuCharaButton\" .. i .. \"Locked\"] = {\n            Sheet = \"Album2\",\n            Bounds = {\n                X = 817,\n                Y = 813,\n                Width = 710,\n                Height = 86\n            }\n        };\n        root.Sprites[\"AlbumMenuCharaButtonHighlighted\" .. i .. \"Locked\"] = {\n            Sheet = \"Album2\",\n            Bounds = {\n                X = 817,\n                Y = 725,\n                Width = 710,\n                Height = 86\n            }\n        };\n        root.ExtraMenus.AlbumMenu.CharacterButtons[#root.ExtraMenus.AlbumMenu.CharacterButtons + 1] = \"AlbumMenuCharaButton\" .. i .. \"Locked\";\n        root.ExtraMenus.AlbumMenu.HighlightedCharacterButtons[#root.ExtraMenus.AlbumMenu.HighlightedCharacterButtons + 1] = \"AlbumMenuCharaButtonHighlighted\" .. i .. \"Locked\";\n    end\n\n    root.Sprites[\"AlbumMenuCharaButton\" .. i] = {\n        Sheet = \"Album\",\n        Bounds = {\n            X = buttonX,\n            Y = buttonY,\n            Width = 710,\n            Height = 86\n        }\n    };\n\n    root.Sprites[\"AlbumMenuCharaButtonHighlighted\" .. i] = {\n        Sheet = \"Album\",\n        Bounds = {\n            X = highlightedButtonX,\n            Y = highlightedButtonY,\n            Width = 710,\n            Height = 86\n        }\n    };\n\n    root.ExtraMenus.AlbumMenu.CharacterButtons[#root.ExtraMenus.AlbumMenu.CharacterButtons + 1] = \"AlbumMenuCharaButton\" .. i;\n    root.ExtraMenus.AlbumMenu.HighlightedCharacterButtons[#root.ExtraMenus.AlbumMenu.HighlightedCharacterButtons + 1] = \"AlbumMenuCharaButtonHighlighted\" .. i;\n\n    if i ~= 6 then\n        root.Sprites[\"AlbumMenuCharaPortrait\" .. i] = {\n            Sheet = \"Album2\",\n            Bounds = {\n                X = portraitX,\n                Y = portraitY,\n                Width = portraitWidths[i],\n                Height = 640\n            }\n        };\n        root.ExtraMenus.AlbumMenu.CharacterPortraits[#root.ExtraMenus.AlbumMenu.CharacterPortraits + 1] = \"AlbumMenuCharaPortrait\" .. i;\n        portraitX = portraitX + portraitWidths[i];\n    end\n\n    if i % 3 == 0 then\n        buttonX = 713;\n        buttonY = 761;\n        highlightedButtonY = highlightedButtonY + 88;\n    else\n        buttonY = buttonY + 88;\n        highlightedButtonY = highlightedButtonY + 88;\n    end\nend\n\ntemp = root.ExtraMenus.AlbumMenu.CharacterPortraits[4];\nroot.ExtraMenus.AlbumMenu.CharacterPortraits[4] = root.ExtraMenus.AlbumMenu.CharacterPortraits[5];\nroot.ExtraMenus.AlbumMenu.CharacterPortraits[5] = temp;\n\nroot.Sprites[\"AlbumMenuOthersPortraitTop\"] = {\n    Sheet = \"Album2\",\n    Bounds = { X = 409, Y = 725, Width = 406, Height = 298 }\n};\nroot.Sprites[\"AlbumMenuOthersPortraitBottom\"] = {\n    Sheet = \"Album2\",\n    Bounds = { X = 1, Y = 725, Width = 406, Height = 298 }\n};\n\nlocal column = 0;\nlocal row = 0;\nlocal block = 0;\nlocal thumbX = 1;\nlocal thumbY = 1;\nlocal thumbnailSheet = \"AlbumThumbnails0\";\n\nfor i = 0, 103 do\n    root.Sprites[\"AlbumMenuThumbnail\" .. i] = {\n        Sheet = thumbnailSheet,\n        Bounds = {\n            X = thumbX,\n            Y = thumbY,\n            Width = 240,\n            Height = 135\n        }\n    };\n\n    root.ExtraMenus.AlbumMenu.Thumbnails[#root.ExtraMenus.AlbumMenu.Thumbnails + 1] = \"AlbumMenuThumbnail\" .. i;\n\n    thumbX = thumbX + 242;\n    column = column + 1;\n    if column == 4 then\n        thumbX = (block * 968) + 1;\n        thumbY = thumbY + 137;\n        row = row + 1;\n        column = 0;\n        if row == 7 then\n            row = 0;\n            block = block + 1;\n            if block == 2 then\n                thumbnailSheet = \"AlbumThumbnails1\";\n                block = 0;\n                thumbX = 1;\n                thumbY = 1;\n            else\n                thumbX = block * 969;\n                thumbY = 1;\n            end\n        end\n    end\nend\n\nroot.Sprites[\"AlbumMenuThumbnailBorder\"] = {\n    Sheet = \"Album\",\n    Bounds = { X = 1425, Y = 721, Width = 262, Height = 158 }\n};\nroot.Sprites[\"AlbumMenuLockedThumbnail\"] = {\n    Sheet = \"Album\",\n    Bounds = { X = 1698, Y = 731, Width = 240, Height = 135 }\n};\n\nroot.Sprites[\"AlbumMenuThumbnailHighlightTopLeft\"] = {\n    Sheet = \"Album\",\n    Bounds = { X = 1425, Y = 881, Width = 22, Height = 22 }\n};\nroot.Sprites[\"AlbumMenuThumbnailHighlightTopRight\"] = {\n    Sheet = \"Album\",\n    Bounds = { X = 1449, Y = 881, Width = 22, Height = 22 }\n};\nroot.Sprites[\"AlbumMenuThumbnailHighlightBottomLeft\"] = {\n    Sheet = \"Album\",\n    Bounds = { X = 1425, Y = 905, Width = 22, Height = 22 }\n};\nroot.Sprites[\"AlbumMenuThumbnailHighlightBottomRight\"] = {\n    Sheet = \"Album\",\n    Bounds = { X = 1449, Y = 905, Width = 22, Height = 22 }\n};\n\nroot.Sprites[\"AlbumMenuArrowUp\"] = {\n    Sheet = \"Album\",\n    Bounds = { X = 1473, Y = 881, Width = 34, Height = 22 }\n};\nroot.Sprites[\"AlbumMenuArrowDown\"] = {\n    Sheet = \"Album\",\n    Bounds = { X = 1473, Y = 905, Width = 34, Height = 22 }\n};\n"
  },
  {
    "path": "profiles/mo6tw/hud/loadingdisplay.lua",
    "content": "root.LoadingDisplay = {\n    ResourceLoadBgAnim = \"ResourceLoadingBg\",\n    SaveLoadBgAnim = \"SaveLoadingBg\",\n    LoadingIconAnim = \"LoadingDisc\",\n    LoadingTextAnim = \"LoadingText\",\n    ResourceBgPos = { X = 1074, Y = 611 },\n    SaveBgPos = { X = 1019, Y = 598 },\n    IconPos = { X = 986, Y = 608 },\n    TextPos = { X = 1025, Y = 628 },\n    FadeInDuration = 0.66,\n    FadeOutDuration = 0.33\n};\n\nMakeAnimation({\n    Name = \"ResourceLoadingBg\",\n    Sheet = \"Data\",\n    FirstFrameX = 555,\n    FirstFrameY = 544,\n    FrameWidth = 206,\n    ColWidth = 206,\n    FrameHeight = 58,\n    RowHeight = 60,\n    Frames = 8,\n    Duration = 0.8,\n    Rows = 8,\n    Columns = 1,\n    PrimaryDirection = AnimationDirections.Down\n});\n\nMakeAnimation({\n    Name = \"SaveLoadingBg\",\n    Sheet = \"Data\",\n    FirstFrameX = 388,\n    FirstFrameY = 0,\n    FrameWidth = 277,\n    ColWidth = 279,\n    FrameHeight = 81,\n    RowHeight = 83,\n    Frames = 4,\n    Duration = 0.8,\n    Rows = 2,\n    Columns = 2,\n    PrimaryDirection = AnimationDirections.Down,\n    SecondaryDirection = AnimationDirections.Right\n});\n\nMakeAnimation({\n    Name = \"LoadingDisc\",\n    Sheet = \"Data\",\n    FirstFrameX = 172,\n    FirstFrameY = 1,\n    FrameWidth = 60,\n    ColWidth = 62,\n    FrameHeight = 60,\n    RowHeight = 62,\n    Frames = 3,\n    Duration = 0.8,\n    Rows = 1,\n    Columns = 3,\n    PrimaryDirection = AnimationDirections.Right\n});\nroot.Animations[\"LoadingDisc\"].Frames[#root.Animations[\"LoadingDisc\"].Frames + 1] = \"LoadingDisc1\";\n\nMakeAnimation({\n    Name = \"LoadingText\",\n    Sheet = \"Data\",\n    FirstFrameX = 173,\n    FirstFrameY = 91,\n    FrameWidth = 214,\n    ColWidth = 214,\n    FrameHeight = 21,\n    RowHeight = 23,\n    Frames = 5,\n    Duration = 0.8,\n    Rows = 5,\n    Columns = 1,\n    PrimaryDirection = AnimationDirections.Down\n});"
  },
  {
    "path": "profiles/mo6tw/hud/optionsmenu.lua",
    "content": "root.OptionsMenu = {\n    Type = OptionsMenuType.MO6TW,\n    DrawType = DrawComponentType.SystemMenu,\n    BackgroundSprite = \"OptionsBackground\",\n    SliderTrackSprite = \"OptionsSliderTrack\",\n    SliderFillSprite = \"OptionsSliderFill\",\n    SliderThumbSprite = \"OptionsSliderThumb\",\n    VoiceToggleEnabledSprites = {},\n    VoiceToggleDisabledSprites = {},\n    VoiceToggleHighlightSprite = \"OptionsVoiceToggleHighlight\",\n    SectionHeaderSprites = {},\n    CheckboxBoxSprite = \"OptionsCheckboxBox\",\n    CheckboxTickSprite = \"OptionsCheckboxTick\",\n    CheckboxLabelSprites = {},\n    VoiceToggleStart = { X = 80, Y = 465 },\n    VoiceTogglePadding = { X = 96, Y = 56 },\n    VoiceTogglePerLine = 5,\n    FirstPageSliderPos = { X = 94, Y = 88 },\n    FirstPageSliderMargin = 96,\n    SliderThumbOffset = { X = 6, Y = 14 },\n    CheckboxLabelOffset = { X = 24, Y = 3 },\n    CheckboxFirstPos = { X = 90, Y = 65 },\n    CheckboxFirstSectionPaddingX = 88,\n    CheckboxMargin = { X = 32, Y = 80 },\n    CheckboxSecondPos = { X = 90, Y = 385 },\n    CheckboxSecondSectionFirstPaddingX = 133,\n    AutoSaveTriggerXPos = { 122, 256, 390, 597 },\n    ScreenSizeSliderPos = { X = 158, Y = 551 },\n    TipsPos = { X = 186, Y = 625 },\n    FirstPageSectionHeaderPos = { X = 79, Y = 47 },\n    SecondPageSectionHeaderPos = { X = 79, Y = 31 },\n    FadeInDuration = 0.2,\n    FadeOutDuration = 0.2,\n\n};\n\nroot.Sprites[\"OptionsBackground\"] = {\n    Sheet = \"Options\",\n    Bounds = { X = 0, Y = 0, Width = 1280, Height = 720 }\n};\n\nroot.Sprites[\"OptionsSliderTrack\"] = {\n    Sheet = \"Options\",\n    Bounds = { X = 1, Y = 921, Width = 480, Height = 8 }\n};\n\nroot.Sprites[\"OptionsSliderFill\"] = {\n    Sheet = \"Options\",\n    Bounds = { X = 1, Y = 931, Width = 480, Height = 8 }\n};\n\nroot.Sprites[\"OptionsSliderThumb\"] = {\n    Sheet = \"Options\",\n    Bounds = { X = 963, Y = 807, Width = 42, Height = 42 }\n};\n\nlocal enabledFirstX = 486;\nlocal enabledFirstY = 753;\nlocal disabledFirstX = 4;\nlocal disabledFirstY = 753;\n\nfor i = 1, 14 do\n    root.Sprites[\"OptionsVoiceToggleDisabled\" .. i] = {\n        Sheet = \"Options\",\n        Bounds = {\n            X = disabledFirstX,\n            Y = disabledFirstY,\n            Width = 88,\n            Height = 48\n        }\n    };\n    root.OptionsMenu.VoiceToggleDisabledSprites[#root.OptionsMenu.VoiceToggleDisabledSprites + 1] = \"OptionsVoiceToggleDisabled\" .. i;\n\n    root.Sprites[\"OptionsVoiceToggleEnabled\" .. i] = {\n        Sheet = \"Options\",\n        Bounds = {\n            X = enabledFirstX,\n            Y = enabledFirstY,\n            Width = 88,\n            Height = 48\n        }\n    };\n    root.OptionsMenu.VoiceToggleEnabledSprites[#root.OptionsMenu.VoiceToggleEnabledSprites + 1] = \"OptionsVoiceToggleEnabled\" .. i;\n\n    if i % 5 == 0 then\n        enabledFirstX = 486;\n        disabledFirstX = 4;\n        enabledFirstY = enabledFirstY + 56;\n        disabledFirstY = disabledFirstY + 56;\n    else\n        disabledFirstX = disabledFirstX + 96;\n        enabledFirstX = enabledFirstX + 96;\n    end\nend\n\nroot.Sprites[\"OptionsVoiceToggleHighlight\"] = {\n    Sheet = \"Options\",\n    Bounds = { X = 968, Y = 753, Width = 88, Height = 48 }\n};\n\nlocal firstX = 1301;\nlocal firstY = 1;\nlocal highlightedFirstY = 443;\n\nfor i = 1, 13 do\n    root.Sprites[\"OptionsSectionHeader\" .. i] = {\n        Sheet = \"Options\",\n        Bounds = {\n            X = firstX,\n            Y = firstY,\n            Width = 400,\n            Height = 32\n        }\n    };\n    root.OptionsMenu.SectionHeaderSprites[#root.OptionsMenu.SectionHeaderSprites + 1] = \"OptionsSectionHeader\" .. i;\n\n    root.Sprites[\"OptionsSectionHeaderHighlighted\" .. i] = {\n        Sheet = \"Options\",\n        Bounds = {\n            X = firstX,\n            Y = highlightedFirstY,\n            Width = 400,\n            Height = 32\n        }\n    };\n    root.OptionsMenu.SectionHeaderSprites[#root.OptionsMenu.SectionHeaderSprites + 1] = \"OptionsSectionHeaderHighlighted\" .. i;\n\n    firstY = firstY + 34;\n    highlightedFirstY = highlightedFirstY + 34;\nend\n\nroot.Sprites[\"OptionsCheckboxBox\"] = {\n    Sheet = \"Options\",\n    Bounds = { X = 965, Y = 855, Width = 30, Height = 30 }\n};\n\nroot.Sprites[\"OptionsCheckboxTick\"] = {\n    Sheet = \"Options\",\n    Bounds = { X = 995, Y = 855, Width = 30, Height = 30 }\n};\n\nfirstX = 1735;\nfirstY = 1;\n\nfor i = 1, 14 do\n    root.Sprites[\"OptionsCheckboxLabel\" .. i] = {\n        Sheet = \"Options\",\n        Bounds = {\n            X = firstX,\n            Y = firstY,\n            Width = 200,\n            Height = 32\n        }\n    };\n    root.OptionsMenu.CheckboxLabelSprites[#root.OptionsMenu.CheckboxLabelSprites + 1] = \"OptionsCheckboxLabel\" .. i;\n\n    firstY = firstY + 32;\nend"
  },
  {
    "path": "profiles/mo6tw/hud/saveicon.lua",
    "content": "root.SaveIcon = {\n    ForegroundAnimation = \"SaveIcon\",\n    DefaultPosition = { X = 1072, Y = 36 },\n    BackgroundSprite = \"SaveIconBg\",\n    BackgroundOffset = { X = -7, Y = -4 },\n    BackgroundMaxAlpha = 0.5,\n    FadeInDuration = 0.25,\n    FadeOutDuration = 0.25\n};\n\nMakeAnimation({\n    Name = \"SaveIcon\",\n    Sheet = \"Data\",\n    FirstFrameX = 257,\n    FirstFrameY = 121,\n    FrameWidth = 96,\n    ColWidth = 96,\n    FrameHeight = 22,\n    RowHeight = 24,\n    Frames = 4,\n    Duration = 0.8,\n    Rows = 4,\n    Columns = 1,\n    PrimaryDirection = AnimationDirections.Down\n});\n\nroot.Sprites[\"SaveIconBg\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 1439, Y = 1, Width = 84, Height = 84 }\n};"
  },
  {
    "path": "profiles/mo6tw/hud/savemenu.lua",
    "content": "root.SaveMenu = {\n    Type = SaveMenuType.MO6TW,\n    DrawType = DrawComponentType.SystemMenu,\n    SaveMenuBackgroundSprite = \"SaveMenuBackground\",\n    EmptyThumbnailSprite = \"EmptyThumbnail\",\n    QuickLoadTextSprite = \"QuickLoadText\",\n    LoadTextSprite = \"LoadText\",\n    SaveTextSprite = \"SaveText\",\n    MenuTitleTextPos = { X = 62.0, Y = 33.0 },\n    EntryStartX = 153,\n    EntryXPadding = 512,\n    EntryStartY = 102,\n    EntryYPadding = 141,\n    QuickLoadEntrySprite = \"QuickLoadEntry\",\n    QuickLoadEntryHighlightedSprite = \"QuickLoadEntryHighlighted\",\n    SaveEntrySprite = \"SaveEntry\",\n    SaveEntryHighlightedSprite = \"SaveEntryHighlighted\",\n    LoadEntrySprite = \"LoadEntry\",\n    LoadEntryHighlightedSprite = \"LoadEntryHighlighted\",\n    FadeInDuration = 0.2,\n    FadeOutDuration = 0.2\n};\n\nroot.Sprites[\"SaveMenuBackground\"] = {\n    Sheet = \"Save\",\n    Bounds = { X = 0, Y = 0, Width = 1280, Height = 720 }\n};\n\nroot.Sprites[\"EmptyThumbnail\"] = {\n    Sheet = \"Save\",\n    Bounds = { X = 1281, Y = 1, Width = 178, Height = 108 }\n};\n\nroot.Sprites[\"QuickLoadText\"] = {\n    Sheet = \"Save\",\n    Bounds = { X = 1281, Y = 111, Width = 262, Height = 42 }\n};\nroot.Sprites[\"SaveText\"] = {\n    Sheet = \"Save\",\n    Bounds = { X = 1281, Y = 155, Width = 262, Height = 42 }\n};\nroot.Sprites[\"LoadText\"] = {\n    Sheet = \"Save\",\n    Bounds = { X = 1281, Y = 199, Width = 262, Height = 42 }\n};\n\nroot.Sprites[\"QuickLoadEntry\"] = {\n    Sheet = \"Save\",\n    Bounds = { X = 1, Y = 721, Width = 508, Height = 128 }\n};\n\nroot.Sprites[\"SaveEntry\"] = {\n    Sheet = \"Save\",\n    Bounds = { X = 511, Y = 721, Width = 508, Height = 128 }\n};\n\nroot.Sprites[\"LoadEntry\"] = {\n    Sheet = \"Save\",\n    Bounds = { X = 1021, Y = 721, Width = 508, Height = 128 }\n};\n\nroot.Sprites[\"QuickLoadEntryHighlighted\"] = {\n    Sheet = \"Save\",\n    Bounds = { X = 1, Y = 851, Width = 508, Height = 128 }\n};\n\nroot.Sprites[\"SaveEntryHighlighted\"] = {\n    Sheet = \"Save\",\n    Bounds = { X = 511, Y = 851, Width = 508, Height = 128 }\n};\n\nroot.Sprites[\"LoadEntryHighlighted\"] = {\n    Sheet = \"Save\",\n    Bounds = { X = 1021, Y = 851, Width = 508, Height = 128 }\n};"
  },
  {
    "path": "profiles/mo6tw/hud/selectiondisplay.lua",
    "content": "root.SelectionDisplay = {\n    DrawType = DrawComponentType.Text,\n    SelectionBackgroundSprite = \"SelectionBackground\",\n    PlainSelectionFrameTopLeftSprite = \"PlainSelectionFrameTopLeft\",\n    PlainSelectionFrameTopSideSprite = \"PlainSelectionFrameTopSide\",\n    PlainSelectionFrameTopRightSprite = \"PlainSelectionFrameTopRight\",\n    PlainSelectionFrameLeftSideSprite = \"PlainSelectionFrameLeftSide\",\n    PlainSelectionFrameBottomLeftSprite = \"PlainSelectionFrameBottomLeft\",\n    PlainSelectionFrameRightSideSprite = \"PlainSelectionFrameRightSide\",\n    PlainSelectionFrameBottomRightSprite = \"PlainSelectionFrameBottomRight\",\n    PlainSelectionFrameBottomSideSprite = \"PlainSelectionFrameBottomSide\",\n    PlainSelectionFrameMiddleSprite = \"PlainSelectionFrameMiddle\",\n    SelectionHighlightSprite = \"SelectionHighlight\",\n    SelectionMaxCount = 5,\n    SelectionBackgroundX = 228,\n    SelectionBackgroundY = {277, 277, 196, 129, 95},\n    SelectionYSpacing = 81,\n    PlainSelectionYSpacing = 10,\n    FadeAnimationDurationInOut = 0.2\n};\n\nroot.Sprites[\"SelectionBackground\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 1, Y = 535, Width = 829, Height = 69 }\n};\n\nroot.Sprites[\"PlainSelectionFrameTopLeft\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 0, Y = 0, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameTopSide\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 16, Y = 0, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameTopRight\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 32, Y = 0, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameLeftSide\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 0, Y = 16, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameBottomLeft\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 0, Y = 32, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameRightSide\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 32, Y = 16, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameBottomRight\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 32, Y = 32, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameBottomSide\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 16, Y = 32, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameMiddle\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 16, Y = 16, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"SelectionHighlight\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 65, Y = 33, Width = 94, Height = 30 }\n};\n"
  },
  {
    "path": "profiles/mo6tw/hud/sysmesboxdisplay.lua",
    "content": "local sheet = \"Data\";\nlocal name = \"SysMesBox\";\n\nroot.SysMesBoxDisplay = {\n    Type = SysMesBoxType.MO6TW,\n    DrawType = DrawComponentType.SystemMessage,\n    BoxX = 602,\n    BoxY = 204,\n    BoxMiddleBaseX = 783,\n    BoxMinimumWidth = 768,\n    BoxMiddleBaseWidth = 140,\n    BoxMiddleRemainBase = 154,\n    BoxRightBaseX = 370,\n    BoxRightRemainPad = 48,\n    TextFontSize = 32,\n    TextMiddleY = 236,\n    TextX = 640,\n    TextLineHeight = 34,\n    TextMarginY = 14,\n    ChoicePadding = 40,\n    ChoiceY = 365,\n    ChoiceXBase = 680,\n    MinMaxMesWidth = 294,\n    AnimationSpeed = 55,\n    FadeInDuration = 0.256,\n    FadeOutDuration = 0.256\n};\n\nroot.Sprites[\"SelectionHighlight\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 65, Y = 33, Width = 94, Height = 30 }\n};\nroot.SysMesBoxDisplay.SelectionHighlight = \"SelectionHighlight\";\n\nroot.Sprites[name .. \"BoxPartLeft\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 1,\n        Y = 606,\n        Width = 181,\n        Height = 212\n    }\n};\nroot.SysMesBoxDisplay.BoxPartLeft = name .. \"BoxPartLeft\";\n\nroot.Sprites[name .. \"BoxPartRight\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 322,\n        Y = 606,\n        Width = 181,\n        Height = 212\n    }\n};\nroot.SysMesBoxDisplay.BoxPartRight = name .. \"BoxPartRight\";\n\nroot.Sprites[name .. \"BoxPartMiddle\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 182,\n        Y = 606,\n        Width = 140,\n        Height = 212\n    }\n};\nroot.SysMesBoxDisplay.BoxPartMiddle = name .. \"BoxPartMiddle\";\n\nroot.Sprites[name .. \"BoxDecoration\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 1383,\n        Y = 117,\n        Width = 592,\n        Height = 8\n    }\n};\nroot.SysMesBoxDisplay.BoxDecoration = name .. \"BoxDecoration\";"
  },
  {
    "path": "profiles/mo6tw/hud/systemmenu.lua",
    "content": "root.SystemMenu = {\n    Type = SystemMenuType.MO6TW,\n    DrawType = DrawComponentType.SystemMenu,\n    SystemMenuBackgroundSprite = \"SystemMenuBackground\",\n    SystemMenuX = 993,\n    SystemMenuY = 114,\n    MenuEntriesSprites = {},\n    MenuEntriesHighlightedSprite = \"MenuEntriesHSprite\",\n    MenuEntriesNum = 7,\n    MenuEntriesHNum = 0,\n    MenuEntriesX = 1001,\n    MenuEntriesXOffset = 100,\n    MenuEntriesFirstY = 126,\n    MenuEntriesYPadding = 54,\n    MenuEntriesTargetWidth = 254,\n    FadeInDuration = 0.2,\n    FadeOutDuration = 0.2\n};\n\nfor i = 0, 6 do\n    root.Sprites[\"SystemMenuEntry\" .. i] = {\n        Sheet = \"Data\",\n        Bounds = {\n            X = 584,\n            Y = i * 32,\n            Width = root.SystemMenu.MenuEntriesTargetWidth,\n            Height = 30\n        }\n    };\n    root.SystemMenu.MenuEntriesSprites[#root.SystemMenu.MenuEntriesSprites + 1] = \"SystemMenuEntry\" .. i;\nend\n\nroot.Sprites[\"MenuEntriesHSprite\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 65, Y = 1, Width = 94, Height = 30 }\n};\n\nroot.Sprites[\"SystemMenuBackground\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 1765, Y = 1, Width = 282, Height = 380 }\n};\n"
  },
  {
    "path": "profiles/mo6tw/hud/tipsmenu.lua",
    "content": "root.TipsMenu = {\n    Type = TipsMenuType.MO6TW,\n    DrawType = DrawComponentType.SystemMenu,\n    BackgroundSprite = \"TipsMenuBackground\",\n    Thumbnails = {},\n    TextOnlyThumbnail = \"TipsTextOnlyThumbnail\",\n    ThumbnailPosition = { X = 413, Y = 45 },\n    CategoryString = \"【 】\",\n    DefaultColorIndex = 0,\n    UnreadColorIndex = 70,\n    NameInitialBounds = { X = 703, Y = 117, Width = 0, Height = 0 },\n    NameFontSize = 32,\n    PronounciationInitialBounds = { X = 703, Y = 87, Width = 0, Height = 0 },\n    PronounciationFontSize = 20,\n    CategoryInitialBounds = { X = 1135, Y = 206, Width = 0, Height = 0 },\n    CategoryEndX = 1200,\n    CategoryFontSize = 32,\n    SortStringTable = 2,\n    SortStringIndex = 5,\n    TipListInitialY = 87,\n    TipListCategoriesPerPage = 5,\n    TipListMaxPages = 10,\n    TipListEntryBounds = { X = 69, Y = 87, Width = 300, Height = 24 },\n    TipListEntryFontSize = 20,\n    TipListYPadding = 20,\n    TipListEntryHighlightOffset = { X = 0, Y = -9 },\n    TipListEntryNameXOffset = 50,\n    TipListEntryNewText = \"New\",\n    TipListEntryNewOffset = 261,\n    TipListEntryLockedTable = 2,\n    TipListEntryLockedIndex = 7,\n    NumberLabelStrTable = 2,\n    NumberLabelStrIndex = 8,\n    NumberLabelPosition = { X = 1070, Y = 45 },\n    NumberLabelFontSize = 32,\n    NumberBounds = { X = 1130, Y = 45, Width = 0, Height = 0 },\n    NumberFontSize = 32,\n    PageSeparatorTable = 2,\n    PageSeparatorIndex = 9,\n    PageSeparatorPosition = { X = 1154, Y = 645 },\n    PageSeparatorFontSize = 32,\n    CurrentPageBounds = { X = 1130, Y = 645, Width = 0, Height = 0 },\n    TotalPagesBounds = { X = 1184, Y = 645, Width = 0, Height = 0 },\n    FadeInDuration = 0.2,\n    FadeOutDuration = 0.2\n};\n\nroot.Sprites[\"TipsMenuBackground\"] = {\n    Sheet = \"Tips\",\n    Bounds = { X = 0, Y = 0, Width = 1280, Height = 720 }\n};\n\nroot.Sprites[\"TipsTextOnlyThumbnail\"] = {\n    Sheet = \"Tips\",\n    Bounds = { X = 1300, Y = 0, Width = 256, Height = 192 }\n};\n\nlocal firstX = 0;\nlocal firstY = 0;\n\nfor i = 1, 37 do\n    root.Sprites[\"TipThumbnail\" .. i] = {\n        Sheet = \"TipThumbnails\",\n        Bounds = {\n            X = firstX,\n            Y = firstY,\n            Width = 256,\n            Height = 192\n        }\n    };\n    root.TipsMenu.Thumbnails[#root.TipsMenu.Thumbnails + 1] = \"TipThumbnail\" .. i;\n\n    if i % 8 == 0 then\n        firstX = 0;\n        firstY = firstY + 192;\n    else\n        firstX = firstX + 256;\n    end\nend\n"
  },
  {
    "path": "profiles/mo6tw/hud/tipsnotification.lua",
    "content": "root.TipsNotification = {\n    Type = TipsNotificationType.MO6TW,\n    TextTableId = 2,\n    NotificationAlertMessageId = 0,\n    NotificationTextPart1MessageId = 1,\n    NotificationTextPart2MessageId = 2,\n    FadeInDuration = 0.5,\n    FadeOutDuration = 0.5,\n\n    AlertPosition = { X = 140, Y = 39 },\n    FinalNotificationPosition = { X = 425, Y = 39 },\n    InitialNotificationPosition = { X = 1045, Y = 39 },\n    NotificationRenderingBounds = { X = 323, Y = 23, Width = 690, Height = 60 },\n    TimerDuration = 5,\n    MoveAnimationDuration = 1,\n\n    AlertTextColorIndex = 71,\n    TipNameColorIndex = 2,\n    FontSize = 26\n};\n"
  },
  {
    "path": "profiles/mo6tw/hud/titlemenu.lua",
    "content": "root.TitleMenu = {\n    Type = TitleMenuType.MO6TW,\n    DrawType = DrawComponentType.TitleMenu,\n    PressToStartX = 497,\n    PressToStartY = 402,\n    PressToStartAnimDurationIn = 0.5,\n    PressToStartAnimDurationOut = 0.5,\n    PressToStartSprite = \"TitleMenuPressToStart\",\n    BackgroundSprite = \"TitleMenuBackground\",\n    CopyrightSprite = \"TitleMenuCopyright\",\n    LogoSprite = \"TitleMenuLogo\",\n    CopyrightX = 465,\n    CopyrightY = 662,\n    LogoX = 321,\n    LogoY = 24,\n    MenuBackgroundSprite = \"MenuBackground\",\n    MenuItemLockedSprite = \"MenuEntryLocked\",\n    MenuItemLockedSpriteH = \"MenuEntryLockedH\",\n    MenuEntriesNum = 23,\n    MenuEntriesSprites = {},\n    MenuEntriesHighlightedSprites = {},\n    MenuEntriesX = 513,\n    MenuEntriesFirstY = 269,\n    MenuEntriesYPadding = 32,\n    MenuEntriesTargetWidth = 254,\n    PrimaryFadeAnimDuration = 0.3,\n    SecondaryMenuAnimTarget = { X = 110, Y = 0 },\n    SecondaryMenuPadding = 20,\n    SecondaryMenuAnimDuration = 0.512,\n    SecondaryMenuAnimUnderX = 0,\n    ExtraStoryItemCount = 3,\n    ContinueItemCount = 2,\n    MemoriesItemCount = 5,\n    SystemItemCount = 2,\n};\n\nfor i = 0, 22 do\n    root.Sprites[\"MenuEntry\" .. i] = {\n        Sheet = \"Title\",\n        Bounds = {\n            X = 641,\n            Y = i * 32,\n            Width = root.TitleMenu.MenuEntriesTargetWidth,\n            Height = 30\n        }\n    };\n    root.TitleMenu.MenuEntriesSprites[#root.TitleMenu.MenuEntriesSprites + 1] = \"MenuEntry\" .. i;\nend\n\nfor i = 0, 22 do\n    root.Sprites[\"MenuEntryHighlighted\" .. i] = {\n        Sheet = \"Title\",\n        Bounds = {\n            X = 897,\n            Y = i * 32,\n            Width = root.TitleMenu.MenuEntriesTargetWidth,\n            Height = 30\n        }\n    };\n    root.TitleMenu.MenuEntriesHighlightedSprites[#root.TitleMenu.MenuEntriesHighlightedSprites + 1] = \"MenuEntryHighlighted\" .. i;\nend\n\nroot.Sprites[\"MenuEntryLocked\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 641, Y = 672, Width = 254, Height = 30 }\n};\n\nroot.Sprites[\"MenuEntryLockedH\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 897, Y = 672, Width = 254, Height = 30 }\n};\n\nroot.Sprites[\"MenuBackground\"] = {\n    Sheet = \"MainBg\",\n    Bounds = { X = 0, Y = 0, Width = 1280, Height = 720 }\n};\n\nroot.Sprites[\"TitleMenuBackground\"] = {\n    Sheet = \"Titlebg\",\n    Bounds = { X = 0, Y = 0, Width = 1280, Height = 720 },\n};\n\nroot.Sprites[\"TitleMenuPressToStart\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 257, Y = 193, Width = 302, Height = 30 },\n};\n\nroot.Sprites[\"TitleMenuCopyright\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 1, Y = 225, Width = 384, Height = 30 },\n};\n\nroot.Sprites[\"TitleMenuLogo\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 1, Y = 1, Width = 638, Height = 190 },\n};"
  },
  {
    "path": "profiles/mo6tw/savedata.lua",
    "content": "root.SaveData = {\n    Type=SaveDataType.MO6TW,\n    SaveFilePath=\"games/mo6tw/savedata/SYSTEM.DAT\",\n    StoryScriptIDs={\n        0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13,\n        0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E,\n        0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29,\n        0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, 0x34,\n        0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F,\n        0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A,\n        0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55,\n        0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, 0x60,\n        0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B,\n        0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76,\n        0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, 0x80, 0x81,\n        0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C,\n        0x8D, 0x8E, 0x8F, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97,\n        0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F, 0xA0, 0xA1, 0xA2,\n        0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD,\n        0xAE, 0xAF, 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8,\n        0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF\n    },\n    ScriptMessageData={ -- Pairs of line count and offset into read flags array\n        {0x76, 0x0}, {0x23D, 0x76}, {0x0, 0x2B3}, {0x51, 0x2B3}, {0xC8, 0x304},\n        {0x4, 0x3CC}, {0x0, 0x3D0}, {0x36, 0x3D0}, {0x0, 0x406}, {0x15A, 0x406},\n        {0x167, 0x560}, {0xF9, 0x6C7}, {0xD3, 0x7C0}, {0x114, 0x893}, {0x1E5, 0x9A7},\n        {0x41, 0xB8C}, {0xEA, 0xBCD}, {0xD6, 0xCB7}, {0x69, 0xD8D}, {0x88, 0xDF6},\n        {0x80, 0xE7E}, {0x104, 0xEFE}, {0x27A, 0x1002}, {0x1B, 0x127C}, {0x1C3, 0x1297},\n        {0xD7, 0x145A}, {0x26, 0x1531}, {0x2A, 0x1557}, {0x3D, 0x1581}, {0x18D, 0x15BE},\n        {0xC9, 0x174B}, {0x18, 0x1814}, {0x10A, 0x182C}, {0xDE, 0x1936}, {0x1E, 0x1A14},\n        {0x27, 0x1A32}, {0x2F, 0x1A59}, {0x19C, 0x1A88}, {0x179, 0x1C24}, {0x16C, 0x1D9D},\n        {0x181, 0x1F09}, {0x10D, 0x208A}, {0x86, 0x2197}, {0xE3, 0x221D}, {0x167, 0x2300},\n        {0xEA, 0x2467}, {0x10B, 0x2551}, {0xAD, 0x265C}, {0x146, 0x2709}, {0x88, 0x284F},\n        {0x166, 0x28D7}, {0xAD, 0x2A3D}, {0xDA, 0x2AEA}, {0x6C, 0x2BC4}, {0x6F, 0x2C30},\n        {0x62, 0x2C9F}, {0x96, 0x2D01}, {0x6B, 0x2D97}, {0x3D, 0x2E02}, {0xD1, 0x2E3F},\n        {0x93, 0x2F10}, {0x59, 0x2FA3}, {0x4E, 0x2FFC}, {0x2E, 0x304A}, {0x73, 0x3078},\n        {0x93, 0x30EB}, {0x56, 0x317E}, {0x87, 0x31D4}, {0x11, 0x325B}, {0xF5, 0x326C},\n        {0x5C, 0x3361}, {0x95, 0x33BD}, {0x117, 0x3452}, {0xB7, 0x3569}, {0x88, 0x3620},\n        {0x139, 0x36A8}, {0x75, 0x37E1}, {0x8C, 0x3856}, {0x70, 0x38E2}, {0x2F, 0x3952},\n        {0x95, 0x3981}, {0x76, 0x3A16}, {0x13, 0x3A8C}, {0x6A, 0x3A9F}, {0x9, 0x3B09},\n        {0x18D, 0x3B12}, {0x1E3, 0x3C9F}, {0x144, 0x3E82}, {0x1E9, 0x3FC6}, {0x15F, 0x41AF},\n        {0xF2, 0x430E}, {0x138, 0x4400}, {0xB2, 0x4538}, {0xC5, 0x45EA}, {0x93, 0x46AF},\n        {0x175, 0x4742}, {0x95, 0x48B7}, {0x97, 0x494C}, {0x43, 0x49E3}, {0xFF, 0x4A26},\n        {0x118, 0x4B25}, {0x6D, 0x4C3D}, {0x1CC, 0x4CAA}, {0x13E, 0x4E76}, {0x159, 0x4FB4},\n        {0xB6, 0x510D}, {0x142, 0x51C3}, {0x10C, 0x5305}, {0xE1, 0x5411}, {0x12B, 0x54F2},\n        {0x6D, 0x561D}, {0xF8, 0x568A}, {0x3D, 0x5782}, {0x66, 0x57BF}, {0x46, 0x5825},\n        {0x10A, 0x586B}, {0x6C, 0x5975}, {0x85, 0x59E1}, {0x2E, 0x5A66}, {0x2F, 0x5A94},\n        {0x43, 0x5AC3}, {0x89, 0x5B06}, {0x89, 0x5B8F}, {0x84, 0x5C18}, {0x51, 0x5C9C},\n        {0x46, 0x5CED}, {0x26, 0x5D33}, {0x3C, 0x5D59}, {0x25, 0x5D95}, {0x79, 0x5DBA},\n        {0x112, 0x5E33}, {0xA7, 0x5F45}, {0xA9, 0x5FEC}, {0xA7, 0x6095}, {0x99, 0x613C},\n        {0x69, 0x61D5}, {0x89, 0x623E}, {0x71, 0x62C7}, {0xB5, 0x6338}, {0xA9, 0x63ED},\n        {0x97, 0x6496}, {0xC1, 0x652D}, {0x183, 0x65EE}, {0x25, 0x6771}, {0x9B, 0x6796},\n        {0x124, 0x6831}, {0xE0, 0x6955}, {0x9F, 0x6A35}, {0x0, 0x6AD4}, {0x34, 0x6AD4},\n        {0xED, 0x6B08}, {0xD9, 0x6BF5}, {0xF4, 0x6CCE}, {0x50, 0x6DC2}, {0x4C, 0x6E12},\n        {0x2E, 0x6E5E}, {0xDB, 0x6E8C}, {0xBE, 0x6F67}, {0xA0, 0x7025}, {0x72, 0x70C5},\n        {0xE6, 0x7137}, {0x83, 0x721D}, {0x9D, 0x72A0}, {0x89, 0x733D}, {0xA3, 0x73C6},\n        {0xB4, 0x7469}, {0x6C, 0x751D}, {0x8B, 0x7589}, {0x18, 0x7614}, {0x3F, 0x762C},\n        {0x22, 0x766B}, {0xBD, 0x768D}, {0x45, 0x774A}, {0x62, 0x778F}, {0x7F, 0x77F1},\n        {0x13D, 0x7870}, {0xD1, 0x79AD}, {0x15A, 0x7A7E}, {0x127, 0x7BD8}, {0xBB, 0x7CFF},\n        {0x72, 0x7DBA}, {0x8F, 0x7E2C}, {0xD0, 0x7EBB}, {0x128, 0x7F8B}, {0xF0, 0x80B3},\n        {0x102, 0x81A3}, {0xCF, 0x82A5}, {0x157, 0x8374}, {0x62, 0x84CB}, {0x118, 0x852D},\n        {0x67, 0x8645}, {0x20, 0x86AC}, {0x44D, 0x86CC}\n    },\n    -- IDs in the save data EVflag array for all event CGs (+variants)\n    AlbumEvData={\n        {73,74,75,76},\n        {77,78},\n        {79,80,81,82},\n        {83,84},\n        {86,87},\n        {85},\n        {213,214,215},\n        {90,91,92},\n        {88,89},\n        {216,217},\n        {94,95},\n        {96,97,98,99,100,101,102},\n        {103},\n        {104,105},\n        {106,107},\n        {108,109,110},\n        {111,112,113,114,115},\n        {116,117},\n        {118},\n        {119,120,123,121},\n        {0,2,1,4,3},\n        {12,13,14},\n        {20,22,25,21,23,24},\n        {6,7,8},\n        {9,10,11},\n        {15,16,17},\n        {5},\n        {18},\n        {19},\n        {26,27},\n        {28,29,30},\n        {31},\n        {32},\n        {33},\n        {34,35},\n        {36},\n        {210,211},\n        {37},\n        {55,56,57,212},\n        {38,39},\n        {40,41,42},\n        {43,44,45,46},\n        {47,48,49,50,51,52},\n        {54},\n        {59,60,61},\n        {58},\n        {53},\n        {70,72,71},\n        {63},\n        {64,65},\n        {66,67},\n        {68},\n        {69},\n        {154,155,156,157},\n        {158,159},\n        {176},\n        {177,178},\n        {161,160,163},\n        {179,180},\n        {164,165},\n        {166,167},\n        {168,169,170},\n        {171,172},\n        {173,174,175},\n        {181,182},\n        {124,125},\n        {126},\n        {148},\n        {127,128,129},\n        {140,141,142,143},\n        {144,145,146},\n        {130},\n        {147},\n        {131,132},\n        {133,134,135,136,137,138,139},\n        {218},\n        {149,150,151},\n        {152,153},\n        {183},\n        {184},\n        {185},\n        {186},\n        {187},\n        {188},\n        {189},\n        {190},\n        {191},\n        {192},\n        {193},\n        {194},\n        {195},\n        {196},\n        {197},\n        {198},\n        {199},\n        {200},\n        {201},\n        {202},\n        {203,204,205},\n        {206},\n        {207},\n        {208},\n        {209},\n        {219}\n    },\n    -- CG IDs for all the event CGs (+variants) that show up in the Album\n    AlbumData={\n        {{432},{433},{434},{435}},\n        {{436},{437}},\n        {{438},{439},{440},{441}},\n        {{442},{443}},\n        {{445},{446}},\n        {{444}},\n        {{491},{492},{493}},\n        {{449},{450},{451}},\n        {{447},{448}},\n        {{494},{495}},\n        {{453},{454}},\n        {{455},{456},{457},{458},{459},{460},{461}},\n        {{462}},\n        {{463},{464}},\n        {{466},{468}},\n        {{471},{472},{473}},\n        {{474},{475},{476},{477},{478}},\n        {{480},{482}},\n        {{484}},\n        {{486,485},{487},{490},{488}},\n        {{344},{346},{345}},\n        {{356},{357},{358}},\n        {{364},{366},{369},{365},{367},{368}},\n        {{350},{351},{352}},\n        {{353},{354},{355}},\n        {{359},{360},{361}},\n        {{349}},\n        {{362}},\n        {{363}},\n        {{370},{371}},\n        {{372},{373},{374}},\n        {{375}},\n        {{376}},\n        {{377}},\n        {{378},{379}},\n        {{380}},\n        {{381},{382}},\n        {{383}},\n        {{413},{414},{415},{416}},\n        {{386,385},{391,390}},\n        {{398},{399},{400}},\n        {{401},{402},{403},{404}},\n        {{405},{406},{407},{408},{409},{410}},\n        {{412}},\n        {{418},{419},{420}},\n        {{417}},\n        {{411}},\n        {{429},{431},{430}},\n        {{422}},\n        {{423},{424}},\n        {{425},{426}},\n        {{427}},\n        {{428}},\n        {{561},{562},{563},{564}},\n        {{565},{566}},\n        {{583}},\n        {{584},{585}},\n        {{568},{567},{570}},\n        {{586},{587}},\n        {{571},{572}},\n        {{573},{574}},\n        {{575},{576},{577}},\n        {{578},{579}},\n        {{580},{581},{582}},\n        {{588},{589}},\n        {{529},{530}},\n        {{531}},\n        {{553}},\n        {{532},{533},{534}},\n        {{545},{546},{547},{548}},\n        {{549},{550},{551}},\n        {{535}},\n        {{552}},\n        {{536},{537}},\n        {{538},{539},{540},{541},{542},{543},{544}},\n        {{560}},\n        {{554},{555},{556}},\n        {{558,557},{559}},\n        {{497,496}},\n        {{499,498}},\n        {{501,500}},\n        {{502}},\n        {{504,503}},\n        {{506,505}},\n        {{190}},\n        {{192}},\n        {{194}},\n        {{196}},\n        {{198}},\n        {{508,507}},\n        {{510,509}},\n        {{512,511}},\n        {{514,513}},\n        {{516,515}},\n        {{517}},\n        {{518}},\n        {{520,519}},\n        {{522,521}},\n        {{200},{200},{200}},\n        {{202}},\n        {{524,523}},\n        {{526,525}},\n        {{528,527}},\n        {{8}}\n    }\n};"
  },
  {
    "path": "profiles/mo6tw/scriptinput.lua",
    "content": "local SC = KeyboardScanCode;\nroot.Input.KBcustom = {\n     [0] = {SC._RETURN},\n     [1] = {SC._BACKSPACE},\n     [2] = {SC._F3},\n     [3] = {SC._F2},\n     [4] = {SC._F4},\n     [5] = {SC._1},\n     [6] = {SC._UP},\n     [7] = {SC._DOWN},\n     [8] = {SC._LEFT},\n     [9] = {SC._RIGHT},\n    [10] = {SC._Q, SC._LCTRL},\n    [11] = {SC._Z},\n    [12] = {SC._3},\n    [13] = {SC._E},\n    [14] = {SC._C},\n    [15] = {SC._F8},\n    [16] = {SC._LEFTBRACKET},\n    [17] = {SC._RIGHTBRACKET},\n}\n\nroot.Input.PADtoKBcustom = {\n    [root.PADinput.PAD1A]       = {{ Id =  0, Mode = KbInputMode.Any  }},\n    [root.PADinput.PAD1B]       = {{ Id =  1, Mode = KbInputMode.Any  }},\n    [root.PADinput.PAD1X]       = {{ Id =  2, Mode = KbInputMode.Any  }},\n    [root.PADinput.PAD1Y]       = {{ Id =  3, Mode = KbInputMode.Any  }},\n    [root.PADinput.PAD1SELECT]  = {{ Id =  4, Mode = KbInputMode.Any  }},\n    [root.PADinput.PAD1START]   = {{ Id =  5, Mode = KbInputMode.Any  }},\n    [root.PADinput.PAD1UP]      = {{ Id =  6, Mode = KbInputMode.Any  }},\n    [root.PADinput.PAD1DOWN]    = {{ Id =  7, Mode = KbInputMode.Any  }},\n    [root.PADinput.PAD1LEFT]    = {{ Id =  8, Mode = KbInputMode.Any  }},\n    [root.PADinput.PAD1RIGHT]   = {{ Id =  9, Mode = KbInputMode.Any  }},\n    [root.PADinput.PAD1L1]      = {{ Id = 10, Mode = KbInputMode.Any  }},\n    [root.PADinput.PAD1L2]      = {{ Id = 11, Mode = KbInputMode.Any  }},\n    [root.PADinput.PAD1L3]      = {{ Id = 12, Mode = KbInputMode.Any  }},\n    [root.PADinput.PAD1R1]      = {{ Id = 13, Mode = KbInputMode.Any  }},\n    [root.PADinput.PAD1R2]      = {{ Id = 14, Mode = KbInputMode.Any  }},\n    [root.PADinput.PAD1R3]      = {{ Id = 15, Mode = KbInputMode.Any  }},\n    [root.PADinput.PAD1UP_RS]   = {{ Id = 16, Mode = KbInputMode.Any  }},\n    [root.PADinput.PAD1DOWN_RS] = {{ Id = 17, Mode = KbInputMode.Any  }},\n}\n\nlocal AllDir = root.PADinput.PAD1UP | root.PADinput.PAD1DOWN | root.PADinput.PAD1LEFT | root.PADinput.PAD1RIGHT;\n\nroot.Input.PADcustomA = {\n    root.PADinput.PAD1UP,\n    root.PADinput.PAD1DOWN,\n    root.PADinput.PAD1LEFT,\n    root.PADinput.PAD1RIGHT,\n    root.PADinput.PAD1A | root.PADinput.PAD1START,\n    root.PADinput.PAD1A,\n    root.PADinput.PAD1B,\n    root.PADinput.PAD1R1,\n    root.PADinput.PAD1START,\n    root.PADinput.PAD1SELECT,\n    root.PADinput.PAD1X,\n    0,\n    root.PADinput.PAD1L2,\n    root.PADinput.PAD1R3,\n    root.PADinput.PAD1A | root.PADinput.PAD1B | root.PADinput.PAD1START,\n    root.PADinput.PAD1A,\n    root.PADinput.PAD1B,\n    root.PADinput.PAD1X,\n    root.PADinput.PAD1Y,\n    root.PADinput.PAD1R1,\n    root.PADinput.PAD1R2,\n    root.PADinput.PAD1L1,\n    root.PADinput.PAD1Y,\n    AllDir | root.PADinput.PAD1A,\n    root.PADinput.PAD1R1,\n    root.PADinput.PAD1L1,\n    root.PADinput.PAD1A,\n    root.PADinput.PAD1B,\n    root.PADinput.PAD1UP_LS | root.PADinput.PAD1UP_DIRECT,\n    root.PADinput.PAD1DOWN_LS | root.PADinput.PAD1DOWN_DIRECT,\n    root.PADinput.PAD1LEFT_LS | root.PADinput.PAD1LEFT_DIRECT,\n    root.PADinput.PAD1RIGHT_LS | root.PADinput.PAD1RIGHT_DIRECT,\n    root.PADinput.PAD1UP_RS,\n    root.PADinput.PAD1DOWN_RS,\n    root.PADinput.PAD1LEFT_RS,\n    root.PADinput.PAD1RIGHT_RS,\n    0x20000000,\n    0x40000000,\n    0x80000000,\n    0,\n    0\n}"
  },
  {
    "path": "profiles/mo6tw/scriptvars.lua",
    "content": "local sv = root.ScriptVars;\n\nsv.SW_TOTALPLAYTIME = 990;\nsv.SW_AUTOSAVERESTART = 1003;\nsv.SW_GAMESTATE = 1012;\nsv.SW_TITLEDISPCT = 1014;\nsv.SW_SYSSEL = 1028;\nsv.SW_TITLEMASKALPHA = 1033;\nsv.SW_TITLEMASKCOLOR = 1034;\nsv.SW_MOVIEMODE_ALPHA=1100;\nsv.SW_MOVIEMODE_CUR=1101;\nsv.SW_ALBUM_ALPHA = 1105;\nsv.SW_MASK1ALPHA_OFS = 1586;\nsv.SW_MASK2ALPHA_OFS = 1587;\nsv.SW_MASK3ALPHA_OFS = 1588;\nsv.SW_MAINTHDP = 1704;\nsv.SW_SAVEERRORCODE = 1733;\nsv.SW_SYSMENUALPHA = 1736;\nsv.SW_SYSMENUCNO = 1738;\nsv.SW_TITLECUR1 = 1740;\nsv.SW_TITLECUR2 = 1741;\nsv.SW_MENUCT = 1903;\nsv.SW_OPTIONALPHA = 1904;\nsv.SW_FILEALPHA = 1905;\nsv.SW_TIPSALPHA = 1906;\nsv.SW_SVSENO = 2001;\nsv.SW_SVBGMNO = 2005;\nsv.SW_SVSCRNO1 = 2006;\nsv.SW_SVSCRNO2 = 2007;\nsv.SW_SVSCRNO3 = 2008;\nsv.SW_SVSCRNO4 = 2009;\nsv.SW_SVBGNO1 = 2010;\nsv.SW_SVCHANO1 = 2018;\nsv.SW_TITLE = 2300;\nsv.SW_PLAYTIME = 2304;\nsv.SW_MESWINDOW_COLOR = 2308;\nsv.SW_BGMREQNO = 2310;\nsv.SW_SEREQNO = 2311;\nsv.SW_BGMVOL = 2314;\nsv.SW_SEVOL = 2315;\nsv.SW_SCRIPTNO0 = 2320;\nsv.SW_SCRIPTNO1 = 2321;\nsv.SW_SCRIPTNO2 = 2322;\nsv.SW_SCRIPTNO3 = 2323;\nsv.SW_SCRIPTNO4 = 2324;\nsv.SW_SCRIPTNO5 = 2325;\nsv.SW_SCRIPTNO6 = 2326;\nsv.SW_SCRIPTNO7 = 2327;\nsv.SW_MASK1COLOR = 2329;\nsv.SW_MASK1ALPHA = 2330;\nsv.SW_MASK1PRI = 2331;\nsv.SW_MASK1POSX = 2332;\nsv.SW_MASK1POSY = 2333;\nsv.SW_MASK1SIZEX = 2334;\nsv.SW_MASK1SIZEY = 2335;\nsv.SW_MESMODE0 = 4423;\nsv.SW_CHA1POSX = 2600;\nsv.SW_CHA1POSY = 2601;\nsv.SW_CHA1ROTX = 2602;\nsv.SW_CHA1ROTY = 2603;\nsv.SW_CHA1ROTZ = 2604;\nsv.SW_CHA1SIZEX = 2605;\nsv.SW_CHA1SIZEY = 2606;\nsv.SW_CHA1ALPHA = 2607;\nsv.SW_CHA1FILTER = 2608;\nsv.SW_CHA1NO = 2609;\nsv.SW_CHA1PRI = 2610;\nsv.SW_CHA1POSE = 2611;\nsv.SW_CHA1FACE = 2612;\nsv.SW_CHA1EX = 2613;\nsv.SW_CHA1FADECT = 2614;\nsv.SW_CHA1FADETYPE = 2615;\nsv.SW_CHA1ANIME_EYE = 2618;\nsv.SW_CHA1ANIME_MOUTH = 2619;\nsv.SW_CHA1SURF = 1850;\nsv.SW_CAP1POSX = 2380;\nsv.SW_CAP1POSY = 2381;\nsv.SW_CAP1SX = 2382;\nsv.SW_CAP1SY = 2383;\nsv.SW_CAP1SIZE = 2384;\nsv.SW_CAP1LX = 2385;\nsv.SW_CAP1LY = 2386;\nsv.SW_CAP1DISPMODE = 2389;\nsv.SW_CAP1FADECT = 2410;\nsv.SW_CAP1FADETYPE = 2411;\nsv.SW_CAP1ROTZ = 2392;\nsv.SW_CAP1ALPHA = 2393;\nsv.SW_CAP1MASKNO = 2394;\nsv.SW_CAP1MASKFADERANGE = 2395;\nsv.SW_CAP1FILTER = 0; -- Disable filter\nsv.SW_CAP1NO = 2407; -- Same as bg\nsv.SW_CAP1PRI = 2408; -- Same as bg\nsv.SW_CAP1PRI2 = 2408; -- Effectively disable\nsv.SW_CAP1POSX_OFS = 1190;\nsv.SW_CAP1POSY_OFS = 1191;\nsv.SW_CAP1SX_OFS = 1192;\nsv.SW_CAP1SY_OFS = 1193;\nsv.SW_CAP1SIZE_OFS = 1194;\nsv.SW_CAP1LX_OFS = 1195;\nsv.SW_CAP1LY_OFS = 1196;\nsv.SW_CAP1ROTZ_OFS = 0; -- Disable\nsv.SW_CAP1ALPHA_OFS = 1198;\nsv.SW_BG1POSX = 2400;\nsv.SW_BG1POSY = 2401;\nsv.SW_BG1SX = 2402;\nsv.SW_BG1SY = 2403;\nsv.SW_BG1SIZE = 2404;\nsv.SW_BG1LX = 2405;\nsv.SW_BG1LY = 2406;\nsv.SW_BG1NO = 2407;\nsv.SW_BG1PRI = 2408;\nsv.SW_BG1DISPMODE = 2409;\nsv.SW_BG1FADECT = 2410;\nsv.SW_BG1FADETYPE = 2411;\nsv.SW_BG1ROTZ = 2412;\nsv.SW_BG1ALPHA = 2413;\nsv.SW_BG1MASKNO = 2414;\nsv.SW_BG1MASKFADERANGE = 2415;\nsv.SW_BG1FILTER = 0; -- Disable filter\nsv.SW_BG1POSX_OFS = 1200;\nsv.SW_BG1POSY_OFS = 1201;\nsv.SW_BG1SX_OFS = 1202;\nsv.SW_BG1SY_OFS = 1203;\nsv.SW_BG1SIZE_OFS = 1204;\nsv.SW_BG1LX_OFS = 1205;\nsv.SW_BG1LY_OFS = 1206;\nsv.SW_BG1ORTZ_OFS = 0; -- Disable RotZ offset\nsv.SW_BG1ALPHA_OFS = 1208;\nsv.SW_CHA1POSX_OFS = 1300;\nsv.SW_CHA1POSY_OFS = 1301;\nsv.SW_CHA1ROTX_OFS = 1302;\nsv.SW_CHA1ROTY_OFS = 1303;\nsv.SW_CHA1ROTZ_OFS = 1304;\nsv.SW_CHA1SIZEX_OFS = 1305;\nsv.SW_CHA1SIZEY_OFS = 1306;\nsv.SW_CHA1ALPHA_OFS = 1307;\nsv.SW_BG1SURF = 1800;\nsv.SW_SAVEFILESTATUS = 2192;\nsv.SW_SAVEFILENO = 2193;\nsv.SW_BGLINK = 2580;\nsv.SW_BGLINK2 = 2581;\nsv.SW_EFF_CAP_PRI = 3268;\nsv.SW_EFF_CAP_BUF = 3269;\nsv.SW_EFF_CAP_PRI2 = 3270;\nsv.SW_EFF_CAP_BUF2 = 3271;\n\nsv.SF_CAP1DISP = 2400; -- Same as BG1DISP\n"
  },
  {
    "path": "profiles/mo6tw/sprites.lua",
    "content": "root.SpriteSheets = {\n    Data={\n        Path={ Mount=\"system\", Id=8 },\n        DesignWidth=2048,\n        DesignHeight=1024\n    },\n    Font={\n        Path={ Mount=\"system\", Id=12 },\n        DesignWidth=2048,\n        DesignHeight=1184\n    },\n    Menu={\n        Path={ Mount=\"system\", Id=8 },\n        DesignWidth=2048,\n        DesignHeight=1024\n    },\n    Titlebg={\n        Path={ Mount=\"system\",Id=24 },\n        DesignWidth=1280,\n        DesignHeight=720\n    },\n    Title={\n        Path={ Mount=\"system\",Id=33 },\n        DesignWidth=2048,\n        DesignHeight=1024\n    },\n    MainBg={\n        Path={Mount=\"system\", Id=29 },\n        DesignWidth=1280,\n        DesignHeight=720\n    },\n    Save={\n        Path={Mount=\"system\", Id=22 },\n        DesignWidth=2048,\n        DesignHeight=1024\n    },\n    Backlog={\n        Path={Mount=\"system\", Id=6 },\n        DesignWidth=2048,\n        DesignHeight=720\n    },\n    Options={\n        Path={Mount=\"system\", Id=20 },\n        DesignWidth=2048,\n        DesignHeight=1024\n    },\n    Tips={\n        Path={Mount=\"system\", Id=10 },\n        DesignWidth=2048,\n        DesignHeight=1024\n    },\n    TipThumbnails={\n        Path={Mount=\"system\", Id=11 },\n        DesignWidth=2048,\n        DesignHeight=1024\n    },\n    ClearList={\n        Path={Mount=\"system\", Id=7 },\n        DesignWidth=2048,\n        DesignHeight=1024\n    },\n    Movie={\n        Path={Mount=\"system\", Id=14 },\n        DesignWidth=2048,\n        DesignHeight=1024\n    },\n    ActorsVoice={\n        Path={Mount=\"system\", Id=0 },\n        DesignWidth=2048,\n        DesignHeight=1024\n    },\n    ActorsVoiceBackground={\n        Path={Mount=\"system\", Id=1 },\n        DesignWidth=1280,\n        DesignHeight=720\n    },\n    Music={\n        Path={Mount=\"system\", Id=15 },\n        DesignWidth=2048,\n        DesignHeight=1024\n    },\n    MusicThumbnails0={\n        Path={Mount=\"system\", Id=16 },\n        DesignWidth=2048,\n        DesignHeight=1024\n    },\n    MusicThumbnails1={\n        Path={Mount=\"system\", Id=17 },\n        DesignWidth=2048,\n        DesignHeight=1024\n    },\n    MusicThumbnails2={\n        Path={Mount=\"system\", Id=18 },\n        DesignWidth=2048,\n        DesignHeight=1024\n    },\n    MusicThumbnails3={\n        Path={Mount=\"system\", Id=19 },\n        DesignWidth=2048,\n        DesignHeight=1024\n    },\n    Album={\n        Path={Mount=\"system\", Id=2 },\n        DesignWidth=2048,\n        DesignHeight=1024\n    },\n    Album2={\n        Path={Mount=\"system\", Id=3 },\n        DesignWidth=2048,\n        DesignHeight=1024\n    },\n    AlbumThumbnails0={\n        Path={Mount=\"system\", Id=4 },\n        DesignWidth=2048,\n        DesignHeight=1024\n    },\n    AlbumThumbnails1={\n        Path={Mount=\"system\", Id=5 },\n        DesignWidth=2048,\n        DesignHeight=1024\n    }\n};\n\nroot.Sprites = {};"
  },
  {
    "path": "profiles/mo6tw/tipssystem.lua",
    "content": "root.TipsSystem = {\n    Type=TipsSystemType.MO6TW,\n    MaxTipsCount=200,\n};"
  },
  {
    "path": "profiles/mo6tw/vfs.lua",
    "content": "root.Vfs = {\n    Mounts={\n        [\"bg\"]={\"games/mo6tw/gamedata/BG.CPK\"},\n        [\"bgm\"]={\"games/mo6tw/gamedata/BGM.CPK\"},\n        [\"chara\"]={\"games/mo6tw/gamedata/CHARA.CPK\"},\n        [\"mask\"]={\"games/mo6tw/gamedata/MASK.CPK\"},\n        [\"movie\"]={\"games/mo6tw/gamedata/MOVIE.CPK\"},\n        [\"script\"]={\"games/mo6tw/gamedata/SCRIPT.cls\"},\n        [\"se\"]={\"games/mo6tw/gamedata/SE.CPK\"},\n        [\"system\"]={\"games/mo6tw/gamedata/SYSTEM.CPK\"},\n        [\"voice\"]={\"games/mo6tw/gamedata/VOICE.CPK\"}\n    }\n};"
  },
  {
    "path": "profiles/mo7/charset.lua",
    "content": "-- WARNING, 0-INDEXED ARRAYS AHEAD\n\nroot.Charset = {\n    Flags = {}\n};\n\nfor i = 0, (64 * 43) - 1 do root.Charset.Flags[i] = 0; end\n\nlocal spaces = {[0]=0, 63};\nfor i = 0, #spaces do\n    root.Charset.Flags[spaces[i]] = root.Charset.Flags[spaces[i]] | CharacterTypeFlags.Space;\nend\n\n-- 、 。 ． ， ？ ！ 〜 ” ー ） 〕 ］ ｝ 〉 》 」 』 】☆ ★ ♪ 々 ぁ ぃ ぅ ぇ\n-- ぉ っ ゃ ゅ ょ ァ ィ ゥ ェ ォ ッ ャ ュ ョ –\nlocal wordEndingPuncts = {\n    [0]=0x00BE, 0x00BF, 0x00C1,\n    0x00C0, 0x00C4, 0x00C5,\n    0x00E4, 0x00CB, 0x00E5,\n    0x00CD, 0x00CF, 0x00D1,\n    0x00D3, 0x00D5, 0x00D7,\n    0x00D9, 0x00DB, 0x00DD,\n    0x01A5, 0x01A6, 0x00E6,\n    0x0187, 0x00E8, 0x00E9,\n    0x00EA, 0x00EB, 0x00EC,\n    0x00ED, 0x00EE, 0x00EF,\n    0x00F0, 0x00F2, 0x00F3,\n    0x00F4, 0x00F5, 0x00F6,\n    0x00F7, 0x00F8, 0x00F9,\n    0x00FA, 0x0113\n};\nfor i = 0, #wordEndingPuncts do\n    root.Charset.Flags[wordEndingPuncts[i]] = root.Charset.Flags[wordEndingPuncts[i]] | CharacterTypeFlags.WordEndingPunct;\nend\n\n-- space(63) space(0) “ （ 〔 ［ ｛ 〈 《 「\nlocal wordStartingPuncts = {\n    [0]=63, 0, 0x00CA,\n    0x00CC, 0x00CE, 0x00D0,\n    0x00D2, 0x00D4, 0x00D6,\n    0x00D8, 0x00DA, 0x00DC\n};\nfor i = 0, #wordStartingPuncts do\n    root.Charset.Flags[wordStartingPuncts[i]] = root.Charset.Flags[wordStartingPuncts[i]] | CharacterTypeFlags.WordStartingPunct;\nend\n"
  },
  {
    "path": "profiles/mo7/config.lua",
    "content": "root.Language = \"Japanese\";\nroot.ResolutionWidth = 1280;\nroot.ResolutionHeight = 720;\nroot.Fullscreen = false;\nroot.DateFormat = DateFormatType.YMD;"
  },
  {
    "path": "profiles/mo7/dialogue.lua",
    "content": "root.Sprites[\"ADVBox\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 769, Y = 818, Width = 1278, Height = 207 },\n};\n\nroot.Dialogue = {\n    REVBounds = { X = 0, Y = 0, Width = 960, Height = 400 },\n    REVNameFontSize = 24,\n    REVColor = 0,\n    REVNameColor = 24,\n    REVNameOffset = 34,\n    REVNameLocation = REVNameLocationType.TopLeft,\n    REVOutlineMode = 2,\n    REVNameOutlineMode = 2,\n    NVLBounds = { X = 125, Y = 85, Width = 1024, Height = 400 },\n    ADVBounds = { X = 159, Y = 546, Width = 960, Height = 180 },\n    ADVBoxSprite = \"ADVBox\",\n    ADVBoxPos = { X = 1, Y = 501 },\n    FadeOutDuration = 0.33,\n    FadeInDuration = 0.33,\n    TextFadeInDuration = 0.33,\n    TextFadeOutDuration = 0.33,\n    DialogueBoxCurrentType = DialogueBoxType.Plain,\n    NVLBoxMaxOpacity = 0.55,\n\n    ADVNameAlignment = TextAlignment.Left,\n    ADVNameFontSize = 29,\n    ADVNamePos = { X = 222, Y = 459 },\n\n    NametagCurrentType = NametagType.Sprite,\n    NametagPosition = { X = 82, Y = 443 },\n    NametagSprite = \"NametagSprite\",\n\n    WaitIconSpriteAnim = \"WaitIconSpriteAnimDef\",\n    WaitIconCurrentType = WaitIconType.SpriteAnim,\n    WaitIconOffset = { X = 4, Y = 4 },\n    DialogueFont = \"Default\",\n    SetFontSizeRatio = 1000.0,\n    DefaultFontSize = 32,\n    RubyFontSize = 14,\n    RubyYOffset = -18,\n    ColorTable = {\n        {0xFFFFFF, 0x000000}, {0x5080FF, 0x000000},\n        {0xFF7080, 0x000000}, {0xFFA0F8, 0x000000},\n        {0x46FF80, 0x000000}, {0x90FFFF, 0x000000},\n        {0xFFFF70, 0x000000}, {0x80FFC0, 0x000000},\n        {0xFFB080, 0x000000}, {0xB080FF, 0x000000},\n        {0x000000, 0x808080}, {0x000000, 0x5080FF},\n        {0x000000, 0xFF7080}, {0x000000, 0xFFA0F8},\n        {0x000000, 0x268840}, {0x000000, 0x409999},\n        {0x000000, 0x888830}, {0x000000, 0x80FFC0},\n        {0x000000, 0xFFB080}, {0x000000, 0xB080FF},\n        {0xD0D0D0, 0x000000}, {0xD0D0FF, 0x000000},\n        {0xFFD0D0, 0x000000}, {0xFFD0FF, 0x000000},\n        {0xD0FFD0, 0x000000}, {0xD0FFFF, 0x000000},\n        {0xFFFFD0, 0x000000}, {0xE8FFD0, 0x000000},\n        {0xFFE8D0, 0x000000}, {0xD0E8FF, 0x000000},\n        {0xFFFFFF, 0x808080}, {0xFFFFFF, 0x5080FF},\n        {0xFFFFFF, 0xFF7080}, {0xFFFFFF, 0xFFA0F8},\n        {0xFFFFFF, 0x46FF80}, {0xFFFFFF, 0x90FFFF},\n        {0xFFFFFF, 0xFFFF70}, {0xFFFFFF, 0x80FFC0},\n        {0xFFFFFF, 0xFFB080}, {0xFFFFFF, 0xB080FF},\n        {0xFFEEEE, 0x000000}, {0xFFCCCC, 0x000000},\n        {0xFFAAAA, 0x000000}, {0xFF9999, 0x000000},\n        {0xFF8888, 0x000000}, {0xFFFF00, 0x000000},\n        {0xFEF000, 0x000000}, {0xFF7777, 0x000000},\n        {0xFF6666, 0x000000}, {0xFF5555, 0x000000},\n        {0xFF4444, 0x000000}, {0xFF3333, 0x000000},\n        {0xFF2222, 0x000000}, {0xFF0000, 0x000000},\n        {0xDD0000, 0x000000}, {0xBB0000, 0x000000},\n        {0xB00000, 0x000000}, {0xAA0000, 0x000000},\n        {0x950000, 0x000000}, {0x808080, 0x000000},\n        {0xAAAAAA, 0x000000}, {0xAAC1C9, 0x000000},\n        {0x000000, 0x000000}, {0x000000, 0x000000},\n        {0x000000, 0x000000}, {0x000000, 0x000000},\n        {0x000000, 0x000000}, {0xF80B0B, 0x000000},\n        {0xF8910B, 0x000000}, {0x33F12A, 0x000000}\n    },\n    MaxPageSize = 2000,\n    PageCount = 3,\n    ColorTagIsUint8 = false\n};\n\nMakeAnimation({\n    Name = \"WaitIconSpriteAnimDef\",\n    Sheet = \"Data\",\n    FirstFrameX = 0,\n    FirstFrameY = 96,\n    FrameWidth = 34,\n    ColWidth = 34,\n    FrameHeight = 34,\n    RowHeight = 34,\n    Frames = 8,\n    Duration = 1.5,\n    Rows = 8,\n    Columns = 1,\n    PrimaryDirection = AnimationDirections.Down,\n});\n\nroot.Sprites[\"NametagSprite\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 1, Y = 961, Width = 350, Height = 62 }\n};\n"
  },
  {
    "path": "profiles/mo7/font.lua",
    "content": "-- WARNING, 0-INDEXED ARRAYS AHEAD\n\nroot.Fonts = {\n    [\"Default\"] = {\n        Type = FontType.Basic,\n        Sheet = \"Font\",\n        Columns = 64,\n        Rows = 43,\n        AdvanceWidths = {},\n        AdvanceWidthsEmWidth = 32,\n        LineSpacing = 22,\n    }\n};\n\nlocal westernAdvanceWidths = {\n    [0]=0x20, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,\n        0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,\n        0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,\n        0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,\n        0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,\n        0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,\n        0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,\n        0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,\n        0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,\n        0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,\n        0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x1A, 0x08, 0x18, 0x18,\n        0x1A, 0x18, 0x1A, 0x18, 0x1A, 0x19, 0x1C, 0x19, 0x18, 0x1A, 0x16, 0x16,\n        0x18, 0x18, 0x08, 0x12, 0x17, 0x16, 0x1E, 0x18, 0x1A, 0x16, 0x1A, 0x18,\n        0x18, 0x1A, 0x18, 0x1C, 0x1E, 0x1A, 0x1A, 0x18, 0x14, 0x14, 0x14, 0x14,\n        0x14, 0x12, 0x14, 0x12, 0x08, 0x0E, 0x14, 0x08, 0x1C, 0x14, 0x16, 0x14,\n        0x14, 0x0F, 0x14, 0x12, 0x14, 0x16, 0x1C, 0x16, 0x12, 0x14, 0x11, 0x11,\n        0x08, 0x08, 0x08, 0x09, 0x10, 0x08, 0x0B, 0x09, 0x08, 0x08, 0x0E, 0x0E,\n        0x0A, 0x0A, 0x0C, 0x0C, 0x0B, 0x0B, 0x0A, 0x0A, 0x0D, 0x0D, 0x10, 0x10,\n        0x0D, 0x0D, 0x0E, 0x0E, 0x0D, 0x0D, 0x18, 0x18, 0x0D, 0x0D, 0x0A, 0x20,\n        0x1C, 0x1A, 0x16, 0x1E, 0x18, 0x17, 0x16, 0x18, 0x1A, 0x18, 0x18, 0x18,\n        0x18, 0x18, 0x18, 0x18, 0x16, 0x18, 0x18, 0x18, 0x18, 0x18, 0x14, 0x16,\n        0x16, 0x18, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n};\n\nfor i = 0, (64 * 43) - 1 do\n    if i <= #westernAdvanceWidths then\n        root.Fonts[\"Default\"].AdvanceWidths[i] = westernAdvanceWidths[i];\n    else\n        root.Fonts[\"Default\"].AdvanceWidths[i] = root.Fonts[\"Default\"].AdvanceWidthsEmWidth;\n    end\nend"
  },
  {
    "path": "profiles/mo7/game.lua",
    "content": "root.ActiveRenderer = RendererType.OpenGL;\n\nroot.LayerCount = 100;\nroot.GameFeatures = GameFeature.Sc3VirtualMachine | GameFeature.Renderer2D | GameFeature.Input | GameFeature.Audio | GameFeature.Video;\nroot.DesignWidth = 1280;\nroot.DesignHeight = 720;\n\nroot.WindowName = \"Memories Off Yubikiri no Kioku\";\nroot.WindowIconPath = \"games/mo7/icondata/icon.png\";\nroot.CursorArrowPath = \"games/mo7/icondata/cursor_arrow.png\";\nroot.CursorPointerPath = \"games/mo7/icondata/cursor_pointer.png\";\n\nroot.CharaIsMvl = false;\nroot.LayFileBigEndian = true;\nroot.LayFileTexXMultiplier = 2048;\nroot.LayFileTexYMultiplier = 1024;\n\nroot.ScreenCaptureCount = 2;\n\nroot.Vm = {\n    StartScript = 0,\n    StartScriptBuffer = 0,\n    GameInstructionSet = InstructionSet.MO7,\n    UseReturnIds = true,\n\n    ScrWorkChaStructSize = 40,\n    ScrWorkChaOffsetStructSize = 10,\n    ScrWorkBgStructSize = 40,\n    ScrWorkBgOffsetStructSize = 10,\n    ScrWorkCaptureStructSize = 20,\n    ScrWorkCaptureOffsetStructSize = 10,\n    ScrWorkCaptureEffectInfoStructSize = 3,\n\n    MaxLinkedBgBuffers = 2\n};\n\ninclude('common/scriptinput.lua');\ninclude('common/scriptvars.lua');\ninclude('mo7/config.lua');\ninclude('mo7/scriptvars.lua');\ninclude('mo7/savedata.lua');\ninclude('mo7/tipssystem.lua');\ninclude('mo7/vfs.lua');\ninclude('mo7/sprites.lua');\ninclude('common/animation.lua');\ninclude('mo7/charset.lua');\ninclude('mo7/font.lua');\ninclude('mo7/dialogue.lua');\ninclude('mo7/hud/saveicon.lua');\ninclude('mo7/hud/loadingdisplay.lua');\ninclude('mo7/hud/datedisplay.lua');\n--include('mo7/hud/titlemenu.lua');\n--include('mo7/hud/systemmenu.lua');\ninclude('mo7/hud/backlogmenu.lua');\ninclude('mo7/hud/sysmesboxdisplay.lua');\ninclude('mo7/hud/selectiondisplay.lua');\ninclude('mo7/hud/tipsmenu.lua');\ninclude('mo7/hud/tipsnotification.lua');"
  },
  {
    "path": "profiles/mo7/hud/backlogmenu.lua",
    "content": "root.BacklogMenu = {\n    Type = BacklogMenuType.None,\n    DrawType = DrawComponentType.ExtrasScenes,\n    BacklogBackgroundSprite = \"BacklogBackground\",\n    EntryHighlightSprite = \"EntryHighlight\",\n    EntryHighlightPadding = 0.0,\n    VoiceIconSprite = \"VoiceIcon\",\n    ScrollbarTrackSprite = \"ScrollbarTrack\",\n    ScrollbarThumbSprite = \"ScrollbarThumb\",\n    ScrollbarPosition = { X = 1165, Y = 98 },\n    EntriesStart = { X = 163, Y = 85 },\n    RenderingBounds = { X = 87, Y = 83, Width = 1055, Height = 590 },\n    EntryYPadding = 22,\n    FadeInDuration = 0.2,\n    FadeOutDuration = 0.2\n};\n\nroot.Sprites[\"BacklogBackground\"] = {\n    Sheet = \"Backlog\",\n    Bounds = { X = 0, Y = 0, Width = 1280, Height = 720 },\n};\n\nroot.Sprites[\"VoiceIcon\"] = {\n    Sheet = \"Backlog\",\n    Bounds = { X = 1281, Y = 1, Width = 30, Height = 30 },\n};\n\nroot.Sprites[\"EntryHighlight\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 65, Y = 1, Width = 94, Height = 30 }\n};\n\nroot.Sprites[\"ScrollbarThumb\"] = {\n    Sheet = \"Backlog\",\n    Bounds = { X = 1281, Y = 32, Width = 30, Height = 30 },\n};\n\nroot.Sprites[\"ScrollbarTrack\"] = {\n    Sheet = \"Backlog\",\n    Bounds = { X = 1500, Y = 0, Width = 8, Height = 567 },\n};"
  },
  {
    "path": "profiles/mo7/hud/datedisplay.lua",
    "content": "local sheet = \"Data\";\nlocal name = \"Date\";\n\nlocal yearNumFirstX = 1525;\nlocal yearNumFirstY = 63;\nlocal yearNumWidth = 25;\nlocal yearNum1Width = 8;\nlocal yearNumHeight = 20;\n\nlocal numFirstX = 1524;\nlocal numFirstY = 33;\nlocal numWidth = 40;\nlocal num1Width = 12;\nlocal numHeight = 28;\n\nlocal weekFirstX = 1785;\nlocal weekFirstY = 63;\nlocal weekSecondX = 1525;\nlocal weekSecondY = 85;\nlocal weekWidth = 73;\nlocal weekFriWidth = 56;\nlocal weekHeight = 20;\n\nroot.DateDisplay = {\n    Type = DateDisplayType.RNE,\n    YearNumSprites = {},\n    MonthNumSprites = {},\n    DayNumSprites = {},\n    WeekSprites = {},\n    BackgroundStartPos = { X = 1088, Y = 73 },\n    BackgroundEndPos = { X = 1088 - 256, Y = 73 },\n    DateStartX = 1167,\n    YearWeekY = 60,\n    MonthDayY = 52,\n    Spacing = 1,\n    FadeInDuration = 0.5,\n    FadeOutDuration = 0.5\n};\n\nfor i = 0, 9 do\n    local yearW;\n    if i == 1 then\n        yearW = yearNum1Width;\n    else\n        yearW = yearNumWidth;\n    end\n\n    root.Sprites[name .. \"YearNum\" .. i] = {\n        Sheet = sheet,\n        Bounds = {\n            X = yearNumFirstX,\n            Y = yearNumFirstY,\n            Width = yearW,\n            Height = yearNumHeight\n        }\n    };\n    root.DateDisplay.YearNumSprites[#root.DateDisplay.YearNumSprites + 1] = name .. \"YearNum\" .. i;\n\n    local numW\n    if i == 1 then\n        numW = num1Width;\n    else\n        numW = numWidth;\n    end\n\n    root.Sprites[name .. \"Num\" .. i] = {\n        Sheet = sheet,\n        Bounds = {\n            X = numFirstX,\n            Y = numFirstY,\n            Width = numW,\n            Height = numHeight\n        }\n    };\n    root.DateDisplay.MonthNumSprites[#root.DateDisplay.MonthNumSprites + 1] = name .. \"Num\" .. i;\n    root.DateDisplay.DayNumSprites[#root.DateDisplay.DayNumSprites + 1] = name .. \"Num\" .. i;\n\n    if i == 1 then\n        yearNumFirstX = yearNumFirstX + yearNum1Width;\n        numFirstX = numFirstX + num1Width;\n    else\n        yearNumFirstX = yearNumFirstX + yearNumWidth;\n        numFirstX = numFirstX + numWidth;\n    end\nend\n\nfor i = 0, 6 do\n    if i > 1 then\n        local weekW;\n        if i == 5 then\n            weekW = weekFriWidth;\n        else\n            weekW = weekWidth;\n        end\n\n        root.Sprites[name .. \"Week\" .. i] = {\n            Sheet = sheet,\n            Bounds = {\n                X = weekSecondX,\n                Y = weekSecondY,\n                Width = weekW,\n                Height = weekHeight\n            }\n        };\n        if i == 5 then\n            weekSecondX = weekSecondX + weekFriWidth;\n        else\n            weekSecondX = weekSecondX + weekWidth;\n         end\n    else\n        root.Sprites[name .. \"Week\" .. i] = {\n            Sheet = sheet,\n            Bounds = {\n                X = weekFirstX,\n                Y = weekFirstY,\n                Width = weekWidth,\n                Height = weekHeight\n            }\n        };\n        weekFirstX = weekFirstX + weekWidth;\n    end\n    root.DateDisplay.WeekSprites[#root.DateDisplay.WeekSprites + 1] = name .. \"Week\" .. i;\nend\n\nroot.Sprites[name .. \"MonthDaySeparator\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 1897,\n        Y = 33,\n        Width = 10,\n        Height = 28\n    }\n};\nroot.DateDisplay.MDSeparatorSprite = name .. \"MonthDaySeparator\";\n\nroot.Sprites[name .. \"DayYearSeparator\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 1758,\n        Y = 63,\n        Width = 8,\n        Height = 20\n    }\n};\nroot.DateDisplay.DYSeparatorSprite = name .. \"DayYearSeparator\";\n\nroot.Sprites[name .. \"OpenBracket\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 1766,\n        Y = 63,\n        Width = 8,\n        Height = 20\n    }\n};\nroot.DateDisplay.OpenBracketSprite = name .. \"OpenBracket\";\n\nroot.Sprites[name .. \"CloseBracket\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 1776,\n        Y = 63,\n        Width = 8,\n        Height = 20\n    }\n};\nroot.DateDisplay.CloseBracketSprite = name .. \"CloseBracket\";\n\nroot.Sprites[name .. \"Background\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 1525,\n        Y = 1,\n        Width = 450,\n        Height = 28\n    }\n};\nroot.DateDisplay.BackgroundSprite = name .. \"Background\";"
  },
  {
    "path": "profiles/mo7/hud/loadingdisplay.lua",
    "content": "root.LoadingDisplay = {\n    ResourceLoadBgAnim = \"ResourceLoadingBg\",\n    SaveLoadBgAnim = \"SaveLoadingBg\",\n    LoadingIconAnim = \"LoadingDisc\",\n    LoadingTextAnim = \"LoadingText\",\n    ResourceBgPos = { X = 1074, Y = 611 },\n    SaveBgPos = { X = 1019, Y = 598 },\n    IconPos = { X = 986, Y = 608 },\n    TextPos = { X = 1025, Y = 628 },\n    FadeInDuration = 0.66,\n    FadeOutDuration = 0.33\n};\n\nMakeAnimation({\n    Name = \"ResourceLoadingBg\",\n    Sheet = \"Data\",\n    FirstFrameX = 555,\n    FirstFrameY = 544,\n    FrameWidth = 206,\n    ColWidth = 206,\n    FrameHeight = 58,\n    RowHeight = 60,\n    Frames = 8,\n    Duration = 0.8,\n    Rows = 8,\n    Columns = 1,\n    PrimaryDirection = AnimationDirections.Down\n});\n\nMakeAnimation({\n    Name = \"SaveLoadingBg\",\n    Sheet = \"Data\",\n    FirstFrameX = 388,\n    FirstFrameY = 0,\n    FrameWidth = 277,\n    ColWidth = 279,\n    FrameHeight = 81,\n    RowHeight = 83,\n    Frames = 4,\n    Duration = 0.8,\n    Rows = 2,\n    Columns = 2,\n    PrimaryDirection = AnimationDirections.Down,\n    SecondaryDirection = AnimationDirections.Right\n});\n\nMakeAnimation({\n    Name = \"LoadingDisc\",\n    Sheet = \"Data\",\n    FirstFrameX = 172,\n    FirstFrameY = 1,\n    FrameWidth = 60,\n    ColWidth = 62,\n    FrameHeight = 60,\n    RowHeight = 62,\n    Frames = 3,\n    Duration = 0.8,\n    Rows = 1,\n    Columns = 3,\n    PrimaryDirection = AnimationDirections.Right\n});\nroot.Animations[\"LoadingDisc\"].Frames[#root.Animations[\"LoadingDisc\"].Frames + 1] = \"LoadingDisc1\";\n\nMakeAnimation({\n    Name = \"LoadingText\",\n    Sheet = \"Data\",\n    FirstFrameX = 173,\n    FirstFrameY = 91,\n    FrameWidth = 214,\n    ColWidth = 214,\n    FrameHeight = 21,\n    RowHeight = 23,\n    Frames = 5,\n    Duration = 0.8,\n    Rows = 5,\n    Columns = 1,\n    PrimaryDirection = AnimationDirections.Down\n});"
  },
  {
    "path": "profiles/mo7/hud/saveicon.lua",
    "content": "root.SaveIcon = {\n    ForegroundAnimation = \"SaveIcon\",\n    DefaultPosition = { X = 1072, Y = 36 },\n    BackgroundSprite = \"SaveIconBg\",\n    BackgroundOffset = { X = -7, Y = -4 },\n    BackgroundMaxAlpha = 0.5,\n    FadeInDuration = 0.25,\n    FadeOutDuration = 0.25\n};\n\nMakeAnimation({\n    Name = \"SaveIcon\",\n    Sheet = \"Data\",\n    FirstFrameX = 257,\n    FirstFrameY = 121,\n    FrameWidth = 96,\n    ColWidth = 96,\n    FrameHeight = 22,\n    RowHeight = 24,\n    Frames = 4,\n    Duration = 0.8,\n    Rows = 4,\n    Columns = 1,\n    PrimaryDirection = AnimationDirections.Down\n});\n\nroot.Sprites[\"SaveIconBg\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 1439, Y = 1, Width = 84, Height = 84 }\n};"
  },
  {
    "path": "profiles/mo7/hud/selectiondisplay.lua",
    "content": "root.SelectionDisplay = {\n    DrawType = DrawComponentType.Text,\n    SelectionBackgroundSprite = \"SelectionBackground\",\n    PlainSelectionFrameTopLeftSprite = \"PlainSelectionFrameTopLeft\",\n    PlainSelectionFrameTopSideSprite = \"PlainSelectionFrameTopSide\",\n    PlainSelectionFrameTopRightSprite = \"PlainSelectionFrameTopRight\",\n    PlainSelectionFrameLeftSideSprite = \"PlainSelectionFrameLeftSide\",\n    PlainSelectionFrameBottomLeftSprite = \"PlainSelectionFrameBottomLeft\",\n    PlainSelectionFrameRightSideSprite = \"PlainSelectionFrameRightSide\",\n    PlainSelectionFrameBottomRightSprite = \"PlainSelectionFrameBottomRight\",\n    PlainSelectionFrameBottomSideSprite = \"PlainSelectionFrameBottomSide\",\n    PlainSelectionFrameMiddleSprite = \"PlainSelectionFrameMiddle\",\n    SelectionHighlightSprite = \"SelectionHighlight\",\n    SelectionMaxCount = 5,\n    SelectionBackgroundX = 228,\n    SelectionBackgroundY = {277, 277, 196, 129, 95},\n    SelectionYSpacing = 81,\n    PlainSelectionYSpacing = 10,\n    FadeAnimationDurationInOut = 0.2\n};\n\nroot.Sprites[\"SelectionBackground\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 1, Y = 535, Width = 829, Height = 69 }\n};\n\nroot.Sprites[\"PlainSelectionFrameTopLeft\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 0, Y = 0, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameTopSide\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 16, Y = 0, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameTopRight\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 32, Y = 0, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameLeftSide\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 0, Y = 16, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameBottomLeft\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 0, Y = 32, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameRightSide\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 32, Y = 16, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameBottomRight\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 32, Y = 32, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameBottomSide\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 16, Y = 32, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameMiddle\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 16, Y = 16, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"SelectionHighlight\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 65, Y = 33, Width = 94, Height = 30 }\n};\n"
  },
  {
    "path": "profiles/mo7/hud/sysmesboxdisplay.lua",
    "content": "local sheet = \"Data\";\nlocal name = \"SysMesBox\";\n\nroot.SysMesBoxDisplay = {\n    Type = SysMesBoxType.CHLCC,\n    DrawType = DrawComponentType.SystemMessage,\n    BoxX = 0,\n    BoxY = 215,\n    TextFontSize = 32,\n    TextMiddleY = 236,\n    TextX = 640,\n    TextLineHeight = 34,\n    TextMarginY = 14,\n    ChoicePadding = 40,\n    ChoiceY = 365,\n    ChoiceXBase = 680,\n    MinMaxMesWidth = 294,\n    MinHighlightWidth = 48,\n    HighlightBaseWidth = 144,\n    HighlightYOffset = 0,\n    HighlightXOffset = 0,\n    HighlightXBase = 658,\n    HighlightXStep = 132,\n    HighlightRightPartSpriteWidth = 24,\n    AnimationSpeed = 55,\n    FadeInDuration = 0.33,\n    FadeOutDuration = 0.25\n};\n\nroot.Sprites[name .. \"Box\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 767,\n        Y = 453,\n        Width = 1280,\n        Height = 203\n    }\n};\nroot.SysMesBoxDisplay.Box = name .. \"Box\";\n\nroot.Sprites[name .. \"BoxDecoration\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 1383,\n        Y = 117,\n        Width = 592,\n        Height = 8\n    }\n};\nroot.SysMesBoxDisplay.BoxDecoration = name .. \"BoxDecoration\";\n\nroot.Sprites[name .. \"SelectionLeftPart\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 502,\n        Y = 51,\n        Width = 144,\n        Height = 38\n    }\n};\nroot.SysMesBoxDisplay.SelectionLeftPart = name .. \"SelectionLeftPart\";\n\nroot.Sprites[name .. \"SelectionRightPart\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 634,\n        Y = 51,\n        Width = 24,\n        Height = 38\n    }\n};\nroot.SysMesBoxDisplay.SelectionRightPart = name .. \"SelectionRightPart\";\n\nroot.Sprites[name .. \"SelectionMiddlePart\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 515,\n        Y = 51,\n        Width = 132,\n        Height = 38\n    }\n};\nroot.SysMesBoxDisplay.SelectionMiddlePart = name .. \"SelectionMiddlePart\";"
  },
  {
    "path": "profiles/mo7/hud/tipsmenu.lua",
    "content": "root.TipsMenu = {\n    Type = TipsMenuType.None\n};\n"
  },
  {
    "path": "profiles/mo7/hud/tipsnotification.lua",
    "content": "root.TipsNotification = {\n    Type = TipsNotificationType.None,\n};\n"
  },
  {
    "path": "profiles/mo7/savedata.lua",
    "content": "root.SaveData = {\n    Type = SaveDataType.None,\n    SaveFilePath = \"games/mo7/savedata/SYSTEM.DAT\",\n};"
  },
  {
    "path": "profiles/mo7/scriptvars.lua",
    "content": "local sv = root.ScriptVars;\n\nsv.SW_BG1POSX_OFS = 2300;\nsv.SW_BG1POSY_OFS = 2301;\nsv.SW_BG1SX_OFS = 2302;\nsv.SW_BG1SY_OFS = 2303;\nsv.SW_BG1SIZE_OFS = 2304;\nsv.SW_BG1LX_OFS = 2305;\nsv.SW_BG1LY_OFS = 2306;\nsv.SW_BG1ALPHA_OFS = 2308;\nsv.SW_CHA1POSX_OFS = 2400;\nsv.SW_CHA1POSY_OFS = 2401;\nsv.SW_CHA1ALPHA_OFS = 2407;\nsv.SW_MASK1COLOR = 4400;\nsv.SW_MASK1ALPHA = 4401;\nsv.SW_MASK1PRI = 4402;\nsv.SW_MASK1POSX = 4403;\nsv.SW_MASK1POSY = 4404;\nsv.SW_MASK1SIZEX = 4405;\nsv.SW_MASK1SIZEY = 4406;"
  },
  {
    "path": "profiles/mo7/sprites.lua",
    "content": "root.SpriteSheets = {\n    [\"Data\"] = {\n        Path = { Mount = \"system\", Id = 26 },\n        DesignWidth = 2048,\n        DesignHeight = 1024\n    },\n    [\"Font\"] = {\n        Path = { Mount = \"system\", Id = 30 },\n        DesignWidth = 2048,\n        DesignHeight = 1376\n    },\n    [\"Menu\"] = {\n        Path = { Mount = \"system\", Id = 8 },\n        DesignWidth = 2048,\n        DesignHeight = 1024\n    },\n    [\"Title\"] = {\n        Path = { Mount = \"system\", Id = 21 },\n        DesignWidth = 2048,\n        DesignHeight = 1024\n    },\n    [\"TitleBg1\"] = {\n        Path = { Mount = \"system\", Id = 19 },\n        DesignWidth = 1280,\n        DesignHeight = 720\n    },\n    [\"TitleBg2\"] = {\n        Path = { Mount = \"system\", Id = 20 },\n        DesignWidth = 1280,\n        DesignHeight = 720\n    },\n    [\"Backlog\"] = {\n        Path = {Mount = \"system\", Id = 6 },\n        DesignWidth = 2048,\n        DesignHeight = 720\n    }\n};\n\nroot.Sprites = {};"
  },
  {
    "path": "profiles/mo7/tipssystem.lua",
    "content": "root.TipsSystem = {\n    Type = TipsSystemType.None,\n};"
  },
  {
    "path": "profiles/mo7/vfs.lua",
    "content": "root.Vfs = {\n    Mounts = {\n        [\"script\"] = {\"games/mo7/gamedata/script.cls\"},\n        [\"system\"] = {\"games/mo7/gamedata/system.cpk\"},\n        [\"bgm\"] = {\"games/mo7/gamedata/bgm.cpk\"},\n        [\"se\"] = {\"games/mo7/gamedata/se.cpk\"},\n        [\"voice\"] = {\"games/mo7/gamedata/voice.cpk\"},\n        [\"bg\"] = {\"games/mo7/gamedata/bg.cpk\"},\n        [\"chara\"] = {\"games/mo7/gamedata/chara.cpk\"},\n        [\"mask\"] = {\"games/mo7/gamedata/mask.cpk\"},\n        [\"movie\"] = {\"games/mo7/gamedata/movie.cpk\"}\n    }\n};"
  },
  {
    "path": "profiles/mo8/charset.lua",
    "content": "root.Charset = {\n    Flags = {}\n};\n\nfor i = 0, (64 * 116) - 1 do root.Charset.Flags[i] = 0; end\n\nlocal spaces = {[0]=0, 63};\nfor i = 0, #spaces do\n    root.Charset.Flags[spaces[i]] = root.Charset.Flags[spaces[i]] | CharacterTypeFlags.Space;\nend\n\n-- 、 。 ． ， ？ ！ 〜 ” ー ） 〕 ］ ｝ 〉 》 」 』 】☆ ★ ♪ 々 ぁ ぃ ぅ ぇ\n-- ぉ っ ゃ ゅ ょ ァ ィ ゥ ェ ォ ッ ャ ュ ョ –\nlocal wordEndingPuncts = {\n    [0]=0x00BE, 0x00BF, 0x00C1,\n    0x00C0, 0x00C4, 0x00C5,\n    0x00E4, 0x00CB, 0x00E5,\n    0x00CD, 0x00CF, 0x00D1,\n    0x00D3, 0x00D5, 0x00D7,\n    0x00D9, 0x00DB, 0x00DD,\n    0x01A5, 0x01A6, 0x00E6,\n    0x0187, 0x00E8, 0x00E9,\n    0x00EA, 0x00EB, 0x00EC,\n    0x00ED, 0x00EE, 0x00EF,\n    0x00F0, 0x00F2, 0x00F3,\n    0x00F4, 0x00F5, 0x00F6,\n    0x00F7, 0x00F8, 0x00F9,\n    0x00FA, 0x0113\n};\nfor i = 0, #wordEndingPuncts do\n    root.Charset.Flags[wordEndingPuncts[i]] = root.Charset.Flags[wordEndingPuncts[i]] | CharacterTypeFlags.WordEndingPunct;\nend\n\n-- space(63) space(0) “ （ 〔 ［ ｛ 〈 《 「\nlocal wordStartingPuncts = {\n    [0]=63, 0, 0x00CA,\n    0x00CC, 0x00CE, 0x00D0,\n    0x00D2, 0x00D4, 0x00D6,\n    0x00D8, 0x00DA, 0x00DC\n};\nfor i = 0, #wordStartingPuncts do\n    root.Charset.Flags[wordStartingPuncts[i]] = root.Charset.Flags[wordStartingPuncts[i]] | CharacterTypeFlags.WordStartingPunct;\nend"
  },
  {
    "path": "profiles/mo8/config.lua",
    "content": "root.Language = \"Japanese\";\nroot.ResolutionWidth = 1920;\nroot.ResolutionHeight = 1080;\nroot.Fullscreen = false;\nroot.DateFormat = DateFormatType.YMD;"
  },
  {
    "path": "profiles/mo8/dialogue.lua",
    "content": "root.Sprites[\"ADVBox\"] = {\n    Sheet = \"MesBox\",\n    Bounds = { X = 0, Y = 0, Width = 1920, Height = 330 }\n};\n\nroot.Dialogue = {\n    REVBounds = { X = 0, Y = 0, Width = 1440, Height = 600 },\n    REVNameFontSize = 24,\n    REVColor = 0,\n    REVNameColor = 24,\n    REVNameOffset = 34,\n    REVNameLocation = REVNameLocationType.TopLeft,\n    REVOutlineMode = 2,\n    REVNameOutlineMode = 2,\n    NVLBounds = { X = 188, Y = 128, Width = 1536, Height = 600 },\n    ADVBounds = { X = 518, Y = 838, Width = 1240, Height = 230 },\n    ADVBoxSprite = \"ADVBox\",\n    ADVBoxPos = { X = 0, Y = 760 },\n    FadeOutDuration = 0.33,\n    FadeInDuration = 0.33,\n    TextFadeInDuration = 0.33,\n    TextFadeOutDuration = 0.33,\n    DialogueBoxCurrentType = DialogueBoxType.Plain,\n    NVLBoxMaxOpacity = 0.55,\n\n    ADVNameAlignment = TextAlignment.Center,\n    ADVNameFontSize = 42,\n    ADVNamePos = { X = 168, Y = 974 },\n\n    NametagCurrentType = NametagType.Sprite,\n    NametagPosition = { X = 0, Y = 950 },\n    NametagSprite = \"NametagSprite\",\n\n    WaitIconSpriteAnim = \"WaitIconSpriteAnimDef\",\n    WaitIconCurrentType = WaitIconType.SpriteAnim,\n    WaitIconOffset = { X = 0, Y = 0 },\n    DialogueFont = \"Default\",\n    SetFontSizeRatio = 1000.0,\n    DefaultFontSize = 48,\n    RubyFontSize = 21,\n    RubyYOffset = -21,\n    ColorTable = {\n        {0xFFFFFF, 0x000000}, {0x5080FF, 0x000000},\n        {0xFF7080, 0x000000}, {0xFFA0F8, 0x000000},\n        {0x46FF80, 0x000000}, {0x90FFFF, 0x000000},\n        {0xFFFF70, 0x000000}, {0x80FFC0, 0x000000},\n        {0xFFB080, 0x000000}, {0xB080FF, 0x000000},\n        {0x000000, 0x808080}, {0x000000, 0x5080FF},\n        {0x000000, 0xFF7080}, {0x000000, 0xFFA0F8},\n        {0x000000, 0x268840}, {0x000000, 0x409999},\n        {0x000000, 0x888830}, {0x000000, 0x80FFC0},\n        {0x000000, 0xFFB080}, {0x000000, 0xB080FF},\n        {0xD0D0D0, 0x000000}, {0xD0D0FF, 0x000000},\n        {0xFFD0D0, 0x000000}, {0xFFD0FF, 0x000000},\n        {0xD0FFD0, 0x000000}, {0xD0FFFF, 0x000000},\n        {0xFFFFD0, 0x000000}, {0xE8FFD0, 0x000000},\n        {0xFFE8D0, 0x000000}, {0xD0E8FF, 0x000000},\n        {0xFFFFFF, 0x808080}, {0xFFFFFF, 0x5080FF},\n        {0xFFFFFF, 0xFF7080}, {0xFFFFFF, 0xFFA0F8},\n        {0xFFFFFF, 0x46FF80}, {0xFFFFFF, 0x90FFFF},\n        {0xFFFFFF, 0xFFFF70}, {0xFFFFFF, 0x80FFC0},\n        {0xFFFFFF, 0xFFB080}, {0xFFFFFF, 0xB080FF},\n        {0xFFEEEE, 0x000000}, {0xFFCCCC, 0x000000},\n        {0xFFAAAA, 0x000000}, {0xFF9999, 0x000000},\n        {0xFF8888, 0x000000}, {0xFFFF00, 0x000000},\n        {0xFEF000, 0x000000}, {0xFF7777, 0x000000},\n        {0xFF6666, 0x000000}, {0xFF5555, 0x000000},\n        {0xFF4444, 0x000000}, {0xFF3333, 0x000000},\n        {0xFF2222, 0x000000}, {0xFF0000, 0x000000},\n        {0xDD0000, 0x000000}, {0xBB0000, 0x000000},\n        {0xB00000, 0x000000}, {0xAA0000, 0x000000},\n        {0x950000, 0x000000}, {0x808080, 0x000000},\n        {0xAAAAAA, 0x000000}, {0xAAC1C9, 0x000000},\n        {0x000000, 0x000000}, {0x000000, 0x000000},\n        {0x000000, 0x000000}, {0x000000, 0x000000},\n        {0x000000, 0x000000}, {0xF80B0B, 0x000000},\n        {0xF8910B, 0x000000}, {0x33F12A, 0x000000}\n    },\n    MaxPageSize = 2000,\n    PageCount = 3,\n    ColorTagIsUint8 = false,\n    HasSpeakerPortraits = true,\n    \n    HasAutoButton = true,\n    AutoButtonSprite = \"AutoButton\",\n    AutoButtonActiveSprite = \"AutoButtonActive\",\n    AutoButtonPosition = { X = 1786, Y = 792 },\n    \n    HasSkipButton = true,\n    SkipButtonSprite = \"SkipButton\",\n    SkipButtonActiveSprite = \"SkipButtonActive\",\n    SkipButtonPosition = { X = 1786, Y = 860 },\n    \n    HasBacklogButton = true,\n    BacklogButtonSprite = \"BacklogButton\",\n    BacklogButtonActiveSprite = \"BacklogButtonActive\",\n    BacklogButtonPosition = { X = 1786, Y = 928 },\n    \n    HasMenuButton = true,\n    MenuButtonSprite = \"MenuButton\",\n    MenuButtonActiveSprite = \"MenuButtonActive\",\n    MenuButtonPosition = { X = 1786, Y = 996 }\n};\n\nMakeAnimation({\n    Name = \"WaitIconSpriteAnimDef\",\n    Sheet = \"MesBox\",\n    FirstFrameX = 0,\n    FirstFrameY = 440,\n    FrameWidth = 64,\n    ColWidth = 64,\n    FrameHeight = 64,\n    RowHeight = 64,\n    Frames = 3,\n    Duration = 9.0,\n    Rows = 1,\n    Columns = 3,\n    PrimaryDirection = AnimationDirections.Right,\n    SecondaryDirection = AnimationDirections.Down\n});\n\nroot.Sprites[\"AutoButton\"] = {\n    Sheet = \"MesBox\",\n    Bounds = { X = 770, Y = 330, Width = 68, Height = 66 },\n};\n\nroot.Sprites[\"AutoButtonActive\"] = {\n    Sheet = \"MesBox\",\n    Bounds = { X = 846, Y = 404, Width = 68, Height = 66 },\n};\n\nroot.Sprites[\"SkipButton\"] = {\n    Sheet = \"MesBox\",\n    Bounds = { X = 618, Y = 404, Width = 68, Height = 66 },\n};\n\nroot.Sprites[\"SkipButtonActive\"] = {\n    Sheet = \"MesBox\",\n    Bounds = { X = 618, Y = 330, Width = 68, Height = 66 },\n};\n\nroot.Sprites[\"BacklogButton\"] = {\n    Sheet = \"MesBox\",\n    Bounds = { X = 770, Y = 404, Width = 68, Height = 66 },\n};\n\nroot.Sprites[\"BacklogButtonActive\"] = {\n    Sheet = \"MesBox\",\n    Bounds = { X = 846, Y = 330, Width = 68, Height = 66 },\n};\n\nroot.Sprites[\"MenuButton\"] = {\n    Sheet = \"MesBox\",\n    Bounds = { X = 694, Y = 404, Width = 68, Height = 66 },\n};\n\nroot.Sprites[\"MenuButtonActive\"] = {\n    Sheet = \"MesBox\",\n    Bounds = { X = 694, Y = 330, Width = 68, Height = 66 },\n};\n\nroot.Sprites[\"NametagSprite\"] = {\n    Sheet = \"MesBox\",\n    Bounds = { X = 0, Y = 340, Width = 420, Height = 90 }\n};\n"
  },
  {
    "path": "profiles/mo8/font-lb.lua",
    "content": "root.Fonts = {\n    [\"Default\"] = {\n        Type = FontType.LB,\n        ForegroundSheet = \"FontLBForeground\",\n        OutlineSheet = \"FontLBOutline\",\n        Columns = 64,\n        Rows = 50,\n        AdvanceWidths = \"games/rne/font-lb/widths.bin\",\n        AdvanceWidthsEmWidth = 48,\n        OutlineOffset = { X = -4, Y = -4 },\n        LineSpacing = 14,\n    }\n};\n\nroot.SpriteSheets[\"Font\"] = nil;\n\nroot.SpriteSheets[\"FontLBForeground\"] = {\n    Path = \"games/rne/font-lb/foreground.png\",\n    DesignWidth = 3072,\n    DesignHeight = 2400\n};\n\nroot.SpriteSheets[\"FontLBOutline\"] = {\n    Path = \"games/rne/font-lb/outline.png\",\n    DesignWidth = 3648,\n    DesignHeight = 2852\n};"
  },
  {
    "path": "profiles/mo8/font.lua",
    "content": "root.Fonts = {\n    [\"Default\"] = {\n        Type = FontType.Basic,\n        Sheet = \"Font\",\n        Columns = 64,\n        Rows = 116,\n        AdvanceWidths = {},\n        AdvanceWidthsEmWidth = 48,\n        LineSpacing = 22,\n    }\n};\n\nlocal westernAdvanceWidths = {\n    [0]=0x20, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,\n        0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,\n        0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,\n        0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,\n        0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,\n        0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,\n        0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,\n        0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,\n        0x11, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x11, 0x11, 0x11, 0x11, 0x11, 0x13, 0x11,\n        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n        0x20\n};\n\nfor i = 0, (64 * 116) - 1 do\n    if i <= #westernAdvanceWidths then\n        root.Fonts[\"Default\"].AdvanceWidths[i] = westernAdvanceWidths[i] * 1.5;\n    else\n        root.Fonts[\"Default\"].AdvanceWidths[i] = root.Fonts[\"Default\"].AdvanceWidthsEmWidth;\n    end\nend"
  },
  {
    "path": "profiles/mo8/game.lua",
    "content": "root.ActiveRenderer = RendererType.OpenGL;\n\nroot.LayerCount = 100;\nroot.GameFeatures = GameFeature.Sc3VirtualMachine | GameFeature.Renderer2D | GameFeature.Input | GameFeature.Audio | GameFeature.Video | GameFeature.DebugMenu;\nroot.DesignWidth = 1920;\nroot.DesignHeight = 1080;\n\nroot.WindowName = \"Memories Off -Innocent Fille-\";\nroot.WindowIconPath = \"games/mo8/icondata/icon.png\";\nroot.CursorArrowPath = \"games/mo8/icondata/cursor_arrow.png\";\nroot.CursorPointerPath = \"games/mo8/icondata/cursor_pointer.png\";\n\nroot.CharaIsMvl = true;\nroot.UseMoviePriority = true;\n\nroot.ScreenCaptureCount = 2;\n\nroot.Vm = {\n    StartScript = 7,\n    StartScriptBuffer = 0,\n    GameInstructionSet = InstructionSet.MO8,\n    UseReturnIds = true,\n    UseMsbStrings = true,\n\n    ScrWorkChaStructSize = 40,\n    ScrWorkChaOffsetStructSize = 10,\n    ScrWorkBgStructSize = 40,\n    ScrWorkBgOffsetStructSize = 10,\n    ScrWorkCaptureStructSize = 20,\n    ScrWorkCaptureOffsetStructSize = 10,\n    ScrWorkCaptureEffectInfoStructSize = 3,\n\n    MaxLinkedBgBuffers = 2\n};\n\ninclude('common/scriptinput.lua');\ninclude('common/scriptvars.lua');\ninclude('mo8/config.lua');\ninclude('mo8/scriptvars.lua');\ninclude('mo8/savedata.lua');\ninclude('mo8/tipssystem.lua');\ninclude('mo8/vfs.lua');\ninclude('mo8/sprites.lua');\ninclude('common/animation.lua');\ninclude('mo8/charset.lua');\ninclude('mo8/font.lua');\n--include('mo8/font-lb.lua');\ninclude('mo8/dialogue.lua');\ninclude('mo8/hud/saveicon.lua');\ninclude('mo8/hud/loadingdisplay.lua');\ninclude('mo8/hud/datedisplay.lua');\ninclude('mo8/hud/titlemenu.lua');\ninclude('mo8/hud/systemmenu.lua');\ninclude('mo8/hud/backlogmenu.lua');\ninclude('mo8/hud/optionsmenu.lua');\ninclude('mo8/hud/savemenu.lua');\ninclude('mo8/hud/sysmesboxdisplay.lua');\ninclude('mo8/hud/selectiondisplay.lua');\ninclude('mo8/hud/tipsmenu.lua');\ninclude('mo8/hud/tipsnotification.lua');"
  },
  {
    "path": "profiles/mo8/hud/backlogmenu.lua",
    "content": "root.BacklogMenu = {\n    Type = BacklogMenuType.None,\n    DrawType = DrawComponentType.SystemMenu,\n    BacklogBackgroundSprite = \"BacklogBackground\",\n    EntryHighlightSprite = \"EntryHighlight\",\n    EntryHighlightPadding = 0.0,\n    VoiceIconSprite = \"VoiceIcon\",\n    ScrollbarTrackSprite = \"ScrollbarTrack\",\n    ScrollbarThumbSprite = \"ScrollbarThumb\",\n    ScrollbarPosition = { X = 1748, Y = 177 },\n    EntriesStart = { X = 245, Y = 167 },\n    RenderingBounds = { X = 87, Y = 145, Width = 1750, Height = 720 },\n    EntryYPadding = 22,\n    FadeInDuration = 0.2,\n    FadeOutDuration = 0.2\n};\n\nroot.Sprites[\"BacklogBackground\"] = {\n    Sheet = \"Backlog\",\n    Bounds = { X = 0, Y = 0, Width = 1920, Height = 1080 },\n};\n\nroot.Sprites[\"VoiceIcon\"] = {\n    Sheet = \"Backlog\",\n    Bounds = { X = 1281, Y = 1, Width = 30, Height = 30 },\n};\n\nroot.Sprites[\"EntryHighlight\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 65, Y = 1, Width = 94, Height = 30 }\n};\n\nroot.Sprites[\"ScrollbarThumb\"] = {\n    Sheet = \"Backlog\",\n    Bounds = { X = 1281, Y = 32, Width = 30, Height = 30 },\n};\n\nroot.Sprites[\"ScrollbarTrack\"] = {\n    Sheet = \"Backlog\",\n    Bounds = { X = 1500, Y = 0, Width = 8, Height = 567 },\n};"
  },
  {
    "path": "profiles/mo8/hud/datedisplay.lua",
    "content": "local sheet = \"Data\";\nlocal name = \"Date\";\n\nlocal yearNumFirstX = 2287.5;\nlocal yearNumFirstY = 94.5;\nlocal yearNumWidth = 37.5;\nlocal yearNum1Width = 12;\nlocal yearNumHeight = 30;\n\nlocal numFirstX = 2286;\nlocal numFirstY = 49.5;\nlocal numWidth = 60;\nlocal num1Width = 18;\nlocal numHeight = 42;\n\nlocal weekFirstX = 2677.5;\nlocal weekFirstY = 94.5;\nlocal weekSecondX = 2287.5;\nlocal weekSecondY = 127.5;\nlocal weekWidth = 109.5;\nlocal weekFriWidth = 84;\nlocal weekHeight = 30;\n\nroot.DateDisplay = {\n    Type = DateDisplayType.RNE,\n    YearNumSprites = {},\n    MonthNumSprites = {},\n    DayNumSprites = {},\n    WeekSprites = {},\n    BackgroundStartPos = { X = 1632, Y = 109.5 },\n    BackgroundEndPos = { X = 1632 - 384, Y = 109.5 },\n    DateStartX = 1750.5,\n    YearWeekY = 90,\n    MonthDayY = 78,\n    Spacing = 1.5,\n    FadeInDuration = 0.5,\n    FadeOutDuration = 0.5\n};\n\nfor i = 0, 9 do\n    local yearW;\n    if i == 1 then\n        yearW = yearNum1Width;\n    else\n        yearW = yearNumWidth;\n    end\n\n    root.Sprites[name .. \"YearNum\" .. i] = {\n        Sheet = sheet,\n        Bounds = {\n            X = yearNumFirstX,\n            Y = yearNumFirstY,\n            Width = yearW,\n            Height = yearNumHeight\n        }\n    };\n    root.DateDisplay.YearNumSprites[#root.DateDisplay.YearNumSprites + 1] = name .. \"YearNum\" .. i;\n\n    local numW\n    if i == 1 then\n        numW = num1Width;\n    else\n        numW = numWidth;\n    end\n\n    root.Sprites[name .. \"Num\" .. i] = {\n        Sheet = sheet,\n        Bounds = {\n            X = numFirstX,\n            Y = numFirstY,\n            Width = numW,\n            Height = numHeight\n        }\n    };\n    root.DateDisplay.MonthNumSprites[#root.DateDisplay.MonthNumSprites + 1] = name .. \"Num\" .. i;\n    root.DateDisplay.DayNumSprites[#root.DateDisplay.DayNumSprites + 1] = name .. \"Num\" .. i;\n\n    if i == 1 then\n        yearNumFirstX = yearNumFirstX + yearNum1Width;\n        numFirstX = numFirstX + num1Width;\n    else\n        yearNumFirstX = yearNumFirstX + yearNumWidth;\n        numFirstX = numFirstX + numWidth;\n    end\nend\n\nfor i = 0, 6 do\n    if i > 1 then\n        local weekW;\n        if i == 5 then\n            weekW = weekFriWidth;\n        else\n            weekW = weekWidth;\n        end\n\n        root.Sprites[name .. \"Week\" .. i] = {\n            Sheet = sheet,\n            Bounds = {\n                X = weekSecondX,\n                Y = weekSecondY,\n                Width = weekW,\n                Height = weekHeight\n            }\n        };\n        if i == 5 then\n            weekSecondX = weekSecondX + weekFriWidth;\n        else\n            weekSecondX = weekSecondX + weekWidth;\n         end\n    else\n        root.Sprites[name .. \"Week\" .. i] = {\n            Sheet = sheet,\n            Bounds = {\n                X = weekFirstX,\n                Y = weekFirstY,\n                Width = weekWidth,\n                Height = weekHeight\n            }\n        };\n        weekFirstX = weekFirstX + weekWidth;\n    end\n    root.DateDisplay.WeekSprites[#root.DateDisplay.WeekSprites + 1] = name .. \"Week\" .. i;\nend\n\nroot.Sprites[name .. \"MonthDaySeparator\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 2845.5,\n        Y = 49.5,\n        Width = 15,\n        Height = 42\n    }\n};\nroot.DateDisplay.MDSeparatorSprite = name .. \"MonthDaySeparator\";\n\nroot.Sprites[name .. \"DayYearSeparator\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 2637,\n        Y = 94.5,\n        Width = 12,\n        Height = 30\n    }\n};\nroot.DateDisplay.DYSeparatorSprite = name .. \"DayYearSeparator\";\n\nroot.Sprites[name .. \"OpenBracket\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 2649,\n        Y = 94.5,\n        Width = 12,\n        Height = 30\n    }\n};\nroot.DateDisplay.OpenBracketSprite = name .. \"OpenBracket\";\n\nroot.Sprites[name .. \"CloseBracket\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 2664,\n        Y = 94.5,\n        Width = 12,\n        Height = 30\n    }\n};\nroot.DateDisplay.CloseBracketSprite = name .. \"CloseBracket\";\n\nroot.Sprites[name .. \"Background\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 2287.5,\n        Y = 2,\n        Width = 675,\n        Height = 42\n    }\n};\nroot.DateDisplay.BackgroundSprite = name .. \"Background\";"
  },
  {
    "path": "profiles/mo8/hud/loadingdisplay.lua",
    "content": "root.LoadingDisplay = {\n    ResourceLoadBgAnim = \"ResourceLoadingBg\",\n    SaveLoadBgAnim = \"SaveLoadingBg\",\n    LoadingIconAnim = \"LoadingDisc\",\n    LoadingTextAnim = \"LoadingText\",\n    ResourceBgPos = { X = 1074, Y = 611 },\n    SaveBgPos = { X = 1019, Y = 598 },\n    IconPos = { X = 986, Y = 608 },\n    TextPos = { X = 1025, Y = 628 },\n    FadeInDuration = 0.66,\n    FadeOutDuration = 0.33\n};\n\nMakeAnimation({\n    Name = \"ResourceLoadingBg\",\n    Sheet = \"Data\",\n    FirstFrameX = 555,\n    FirstFrameY = 544,\n    FrameWidth = 206,\n    ColWidth = 206,\n    FrameHeight = 58,\n    RowHeight = 60,\n    Frames = 8,\n    Duration = 0.8,\n    Rows = 8,\n    Columns = 1,\n    PrimaryDirection = AnimationDirections.Down\n});\n\nMakeAnimation({\n    Name = \"SaveLoadingBg\",\n    Sheet = \"Data\",\n    FirstFrameX = 388,\n    FirstFrameY = 0,\n    FrameWidth = 277,\n    ColWidth = 279,\n    FrameHeight = 81,\n    RowHeight = 83,\n    Frames = 4,\n    Duration = 0.8,\n    Rows = 2,\n    Columns = 2,\n    PrimaryDirection = AnimationDirections.Down,\n    SecondaryDirection = AnimationDirections.Right\n});\n\nMakeAnimation({\n    Name = \"LoadingDisc\",\n    Sheet = \"Data\",\n    FirstFrameX = 172,\n    FirstFrameY = 1,\n    FrameWidth = 60,\n    ColWidth = 62,\n    FrameHeight = 60,\n    RowHeight = 62,\n    Frames = 3,\n    Duration = 0.8,\n    Rows = 1,\n    Columns = 3,\n    PrimaryDirection = AnimationDirections.Right\n});\nroot.Animations[\"LoadingDisc\"].Frames[#root.Animations[\"LoadingDisc\"].Frames + 1] = \"LoadingDisc1\";\n\nMakeAnimation({\n    Name = \"LoadingText\",\n    Sheet = \"Data\",\n    FirstFrameX = 173,\n    FirstFrameY = 91,\n    FrameWidth = 214,\n    ColWidth = 214,\n    FrameHeight = 21,\n    RowHeight = 23,\n    Frames = 5,\n    Duration = 0.8,\n    Rows = 5,\n    Columns = 1,\n    PrimaryDirection = AnimationDirections.Down\n});"
  },
  {
    "path": "profiles/mo8/hud/optionsmenu.lua",
    "content": "root.OptionsMenu = {\n    Type = OptionsMenuType.MO8,\n    DrawType = DrawComponentType.SystemMenu,\n    BackgroundSprite = \"OptionsBackground\",\n    SliderTrackSprite = \"OptionsSliderTrack\",\n    SliderFillSprite = \"OptionsSliderFill\",\n    SliderThumbSprite = \"OptionsSliderThumb\",\n    NextButtonSprite = \"OptionsNextButton\",\n    NextButtonHighlightedSprite = \"OptionsNextButtonHighlighted\",\n    NextButtonPosition = { X = 1586, Y = 972 },\n    BackButtonSprite = \"OptionsBackButton\",\n    BackButtonHighlightedSprite = \"OptionsBackButtonHighlighted\",\n    BackButtonPosition = { X = 30, Y = 972 },\n    BackButtonPositionVoice = { X = 808, Y = 972 },\n    ButtonHighlight = \"OptionsButtonHighlight\",\n    PageLabelPosition = { X = 336, Y = 62 },\n    ListStartingPosition = { X = 66, Y = 164 },\n    ListPadding = { X = 0, Y = 152 },\n    OptionGroupItemsOffset = { X = 538, Y = 10 },\n    OptionGroupSliderOffset = { X = 777, Y = 16 },\n\n    TextPageLabel = \"OptionsTextPageLabel\",\n    TextSpeedOptionsLabel = \"OptionsTextSpeedLabel\",\n    TextSpeedOptionsLabelH = \"OptionsTextSpeedLabelH\",\n    TextSpeedOptionsNum = 4,\n    TextSpeedOptionsSprites = {},\n    TextSpeedOptionsHSprites = {},\n\n    AutoModeOptionsLabel = \"OptionsAutoModeLabel\",\n    AutoModeOptionsLabelH = \"OptionsAutoModeLabelH\",\n    AutoModeOptionsNum = 3,\n    AutoModeOptionsSprites = {},\n    AutoModeOptionsHSprites = {},\n\n    SkipModeOptionsLabel = \"OptionsSkipModeLabel\",\n    SkipModeOptionsLabelH = \"OptionsSkipModeLabelH\",\n    SkipModeOptionsNum = 2,\n    SkipModeOptionsSprites = {},\n    SkipModeOptionsHSprites = {},\n\n    SoundModeOptionsNum = 2,\n    SoundPageLabel = \"OptionsSoundPageLabel\",\n\n    VoiceSyncOptionsLabel = \"OptionsVoiceSyncLabel\",\n    VoiceSyncOptionsLabelH = \"OptionsVoiceSyncLabelH\",\n\n    VoiceSkipOptionsLabel = \"OptionsVoiceSkipLabel\",\n    VoiceSkipOptionsLabelH = \"OptionsVoiceSkipLabelH\",\n\n    VoiceHighlightOptionsLabel = \"OptionsVoiceHighlightLabel\",\n    VoiceHighlightOptionsLabelH = \"OptionsVoiceHighlightLabelH\",\n\n    SoundModeOptionsSprites = {},\n    SoundModeOptionsHSprites = {},\n\n    BgmVolumeLabel = \"OptionsBgmVolumeLabel\",\n    BgmVolumeLabelH = \"OptionsBgmVolumeLabelH\",\n    VoiceVolumeLabel = \"OptionsVoiceVolumeLabel\",\n    VoiceVolumeLabelH = \"OptionsVoiceVolumeLabelH\",\n    SeVolumeLabel = \"OptionsSeVolumeLabel\",\n    SeVolumeLabelH = \"OptionsSeVolumeLabelH\",\n    SystemSeVolumeLabel = \"OptionsSystemSeVolumeLabel\",\n    SystemSeVolumeLabelH = \"OptionsSystemSeVolumeLabelH\",\n    CharacterVoiceVolumeLabel = \"OptionsCharacterVoiceVolumeLabel\",\n    CharacterVoiceVolumeLabelH = \"OptionsCharacterVoiceVolumeLabelH\",\n\n    OtherPageLabel = \"OptionsOtherPageLabel\",\n\n    QuickSaveOptionsLabel = \"OptionsQuickSaveOptionsLabel\",\n    QuickSaveOptionsLabelH = \"OptionsQuickSaveOptionsLabelH\",\n    QuickSaveOptionsNum = 3,\n    QuickSaveOptionsSprites = {},\n    QuickSaveOptionsHSprites = {},\n\n    FadeInDuration = 0.33,\n    FadeOutDuration = 0.33,\n\n};\n\nroot.Sprites[\"OptionsBackground\"] = {\n    Sheet = \"Options\",\n    Bounds = { X = 0, Y = 0, Width = 1920, Height = 1080 }\n};\n\nroot.Sprites[\"OptionsSliderTrack\"] = {\n    Sheet = \"Options\",\n    Bounds = { X = 61, Y = 4004, Width = 870, Height = 68 }\n};\n\nroot.Sprites[\"OptionsSliderFill\"] = {\n    Sheet = \"Options\",\n    Bounds = { X = 1061, Y = 4004, Width = 870, Height = 68 }\n};\n\nroot.Sprites[\"OptionsSliderThumb\"] = {\n    Sheet = \"Options\",\n    Bounds = { X = 963, Y = 807, Width = 42, Height = 42 }\n};\n\nroot.Sprites[\"OptionsBackButton\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 0, Y = 722, Width = 304, Height = 80 }\n};\n\nroot.Sprites[\"OptionsBackButtonHighlighted\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 0, Y = 898, Width = 304, Height = 80 }\n};\n\nroot.Sprites[\"OptionsNextButton\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 0, Y = 810, Width = 304, Height = 80 }\n};\n\nroot.Sprites[\"OptionsNextButtonHighlighted\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 0, Y = 634, Width = 304, Height = 80 }\n};\n\nroot.Sprites[\"OptionsButtonHighlight\"] = {\n    Sheet = \"Options\",\n    Bounds = { X = 2406, Y = 3256, Width = 304, Height = 80 }\n};\n\nroot.Sprites[\"OptionsTextPageLabel\"] = {\n    Sheet = \"Options\",\n    Bounds = { X = 1928, Y = 1008, Width = 192, Height = 42 }\n};\n\nroot.Sprites[\"OptionsSoundPageLabel\"] = {\n    Sheet = \"Options\",\n    Bounds = { X = 2128, Y = 1008, Width = 192, Height = 42 }\n};\n\nroot.Sprites[\"OptionsOtherPageLabel\"] = {\n    Sheet = \"Options\",\n    Bounds = { X = 2328, Y = 1008, Width = 192, Height = 42 }\n};\n\n-- Text speed\n\nroot.Sprites[\"OptionsTextSpeedLabel\"] = {\n    Sheet = \"Options\",\n    Bounds = { X = 0, Y = 1628, Width = 1786, Height = 100 }\n};\nroot.Sprites[\"OptionsTextSpeedLabelH\"] = {\n    Sheet = \"Options\",\n    Bounds = { X = 0, Y = 1520, Width = 1786, Height = 100 }\n};\n\nroot.Sprites[\"OptionsTextSpeedImmediate\"] = {\n    Sheet = \"Options\",\n    Bounds = {\n        X = 2718,\n        Y = 3256,\n        Width = 304,\n        Height = 80\n    }\n};\nroot.OptionsMenu.TextSpeedOptionsSprites[#root.OptionsMenu.TextSpeedOptionsSprites + 1] = \"OptionsTextSpeedImmediate\";\nroot.Sprites[\"OptionsTextSpeedImmediateH\"] = {\n    Sheet = \"Options\",\n    Bounds = {\n        X = 2540,\n        Y = 528,\n        Width = 304,\n        Height = 80\n    }\n};\nroot.OptionsMenu.TextSpeedOptionsHSprites[#root.OptionsMenu.TextSpeedOptionsHSprites + 1] = \"OptionsTextSpeedImmediateH\";\n\nroot.Sprites[\"OptionsTextSpeedSlow\"] = {\n    Sheet = \"Options\",\n    Bounds = {\n        X = 2406,\n        Y = 2992,\n        Width = 304,\n        Height = 80\n    }\n};\nroot.OptionsMenu.TextSpeedOptionsSprites[#root.OptionsMenu.TextSpeedOptionsSprites + 1] = \"OptionsTextSpeedSlow\";\nroot.Sprites[\"OptionsTextSpeedSlowH\"] = {\n    Sheet = \"Options\",\n    Bounds = {\n        X = 2406,\n        Y = 2904,\n        Width = 304,\n        Height = 80\n    }\n};\nroot.OptionsMenu.TextSpeedOptionsHSprites[#root.OptionsMenu.TextSpeedOptionsHSprites + 1] = \"OptionsTextSpeedSlowH\";\n\nroot.Sprites[\"OptionsTextSpeedMedium\"] = {\n    Sheet = \"Options\",\n    Bounds = {\n        X = 2406,\n        Y = 3080,\n        Width = 304,\n        Height = 80\n    }\n};\nroot.OptionsMenu.TextSpeedOptionsSprites[#root.OptionsMenu.TextSpeedOptionsSprites + 1] = \"OptionsTextSpeedMedium\";\nroot.Sprites[\"OptionsTextSpeedMediumH\"] = {\n    Sheet = \"Options\",\n    Bounds = {\n        X = 2540,\n        Y = 616,\n        Width = 304,\n        Height = 80\n    }\n};\nroot.OptionsMenu.TextSpeedOptionsHSprites[#root.OptionsMenu.TextSpeedOptionsHSprites + 1] = \"OptionsTextSpeedMediumH\";\n\nroot.Sprites[\"OptionsTextSpeedFast\"] = {\n    Sheet = \"Options\",\n    Bounds = {\n        X = 2540,\n        Y = 704,\n        Width = 304,\n        Height = 80\n    }\n};\nroot.OptionsMenu.TextSpeedOptionsSprites[#root.OptionsMenu.TextSpeedOptionsSprites + 1] = \"OptionsTextSpeedFast\";\nroot.Sprites[\"OptionsTextSpeedFastH\"] = {\n    Sheet = \"Options\",\n    Bounds = {\n        X = 2406,\n        Y = 3168,\n        Width = 304,\n        Height = 80\n    }\n};\nroot.OptionsMenu.TextSpeedOptionsHSprites[#root.OptionsMenu.TextSpeedOptionsHSprites + 1] = \"OptionsTextSpeedFastH\";\n\n-- Auto mode\n\nroot.Sprites[\"OptionsAutoModeLabel\"] = {\n    Sheet = \"Options\",\n    Bounds = { X = 0, Y = 1844, Width = 1786, Height = 100 }\n};\nroot.Sprites[\"OptionsAutoModeLabelH\"] = {\n    Sheet = \"Options\",\n    Bounds = { X = 0, Y = 1736, Width = 1786, Height = 100 }\n};\n\nroot.Sprites[\"OptionsAutoModeLong\"] = {\n    Sheet = \"Options\",\n    Bounds = {\n        X = 2718,\n        Y = 2904,\n        Width = 304,\n        Height = 80\n    }\n};\nroot.OptionsMenu.AutoModeOptionsSprites[#root.OptionsMenu.AutoModeOptionsSprites + 1] = \"OptionsAutoModeLong\";\nroot.Sprites[\"OptionsAutoModeLongH\"] = {\n    Sheet = \"Options\",\n    Bounds = {\n        X = 2718,\n        Y = 2992,\n        Width = 304,\n        Height = 80\n    }\n};\nroot.OptionsMenu.AutoModeOptionsHSprites[#root.OptionsMenu.AutoModeOptionsHSprites + 1] = \"OptionsAutoModeLongH\";\n\nroot.OptionsMenu.AutoModeOptionsSprites[#root.OptionsMenu.AutoModeOptionsSprites + 1] = \"OptionsTextSpeedMedium\";\nroot.OptionsMenu.AutoModeOptionsHSprites[#root.OptionsMenu.AutoModeOptionsHSprites + 1] = \"OptionsTextSpeedMediumH\";\n\nroot.Sprites[\"OptionsAutoModeFast\"] = {\n    Sheet = \"Options\",\n    Bounds = {\n        X = 2718,\n        Y = 3080,\n        Width = 304,\n        Height = 80\n    }\n};\nroot.OptionsMenu.AutoModeOptionsSprites[#root.OptionsMenu.AutoModeOptionsSprites + 1] = \"OptionsAutoModeFast\";\nroot.Sprites[\"OptionsAutoModeFastH\"] = {\n    Sheet = \"Options\",\n    Bounds = {\n        X = 2718,\n        Y = 3168,\n        Width = 304,\n        Height = 80\n    }\n};\nroot.OptionsMenu.AutoModeOptionsHSprites[#root.OptionsMenu.AutoModeOptionsHSprites + 1] = \"OptionsAutoModeFastH\";\n\n-- Skip mode\n\nroot.Sprites[\"OptionsSkipModeLabel\"] = {\n    Sheet = \"Options\",\n    Bounds = { X = 1794, Y = 1520, Width = 1786, Height = 100 }\n};\nroot.Sprites[\"OptionsSkipModeLabelH\"] = {\n    Sheet = \"Options\",\n    Bounds = { X = 0, Y = 1952, Width = 1786, Height = 100 }\n};\n\nroot.Sprites[\"OptionsSkipModeEverything\"] = {\n    Sheet = \"Options\",\n    Bounds = {\n        X = 2718,\n        Y = 2552,\n        Width = 304,\n        Height = 80\n    }\n};\nroot.OptionsMenu.SkipModeOptionsSprites[#root.OptionsMenu.SkipModeOptionsSprites + 1] = \"OptionsSkipModeEverything\";\nroot.Sprites[\"OptionsSkipModeEverythingH\"] = {\n    Sheet = \"Options\",\n    Bounds = {\n        X = 2718,\n        Y = 2640,\n        Width = 304,\n        Height = 80\n    }\n};\nroot.OptionsMenu.SkipModeOptionsHSprites[#root.OptionsMenu.SkipModeOptionsHSprites + 1] = \"OptionsSkipModeEverythingH\";\n\nroot.Sprites[\"OptionsSkipModeReadOnly\"] = {\n    Sheet = \"Options\",\n    Bounds = {\n        X = 2718,\n        Y = 2728,\n        Width = 304,\n        Height = 80\n    }\n};\nroot.OptionsMenu.SkipModeOptionsSprites[#root.OptionsMenu.SkipModeOptionsSprites + 1] = \"OptionsSkipModeReadOnly\";\nroot.Sprites[\"OptionsSkipModeReadOnlyH\"] = {\n    Sheet = \"Options\",\n    Bounds = {\n        X = 2718,\n        Y = 2816,\n        Width = 304,\n        Height = 80\n    }\n};\nroot.OptionsMenu.SkipModeOptionsHSprites[#root.OptionsMenu.SkipModeOptionsHSprites + 1] = \"OptionsSkipModeReadOnlyH\";\n\n-- Voice sync\n\nroot.Sprites[\"OptionsVoiceSyncLabel\"] = {\n    Sheet = \"Options\",\n    Bounds = { X = 0, Y = 2816, Width = 1786, Height = 100 }\n};\nroot.Sprites[\"OptionsVoiceSyncLabelH\"] = {\n    Sheet = \"Options\",\n    Bounds = { X = 0, Y = 2924, Width = 1786, Height = 100 }\n};\n\n-- Voice skip\n\nroot.Sprites[\"OptionsVoiceSkipLabel\"] = {\n    Sheet = \"Options\",\n    Bounds = { X = 1794, Y = 1304, Width = 1786, Height = 100 }\n};\nroot.Sprites[\"OptionsVoiceSkipLabelH\"] = {\n    Sheet = \"Options\",\n    Bounds = { X = 1794, Y = 1412, Width = 1786, Height = 100 }\n};\n\n-- Voice highlight\n\nroot.Sprites[\"OptionsVoiceHighlightLabel\"] = {\n    Sheet = \"Options\",\n    Bounds = { X = 1794, Y = 1088, Width = 1786, Height = 100 }\n};\nroot.Sprites[\"OptionsVoiceHighlightLabelH\"] = {\n    Sheet = \"Options\",\n    Bounds = { X = 1794, Y = 1196, Width = 1786, Height = 100 }\n};\n\nroot.Sprites[\"OptionsSoundOn\"] = {\n    Sheet = \"Options\",\n    Bounds = {\n        X = 2540,\n        Y = 968,\n        Width = 304,\n        Height = 80\n    }\n};\nroot.OptionsMenu.SoundModeOptionsSprites[#root.OptionsMenu.SoundModeOptionsSprites + 1] = \"OptionsSoundOn\";\nroot.Sprites[\"OptionsSoundOnH\"] = {\n    Sheet = \"Options\",\n    Bounds = {\n        X = 2612,\n        Y = 3960,\n        Width = 304,\n        Height = 80\n    }\n};\nroot.OptionsMenu.SoundModeOptionsHSprites[#root.OptionsMenu.SoundModeOptionsHSprites + 1] = \"OptionsSoundOnH\";\n\nroot.Sprites[\"OptionsSoundOff\"] = {\n    Sheet = \"Options\",\n    Bounds = {\n        X = 2540,\n        Y = 792,\n        Width = 304,\n        Height = 80\n    }\n};\nroot.OptionsMenu.SoundModeOptionsSprites[#root.OptionsMenu.SoundModeOptionsSprites + 1] = \"OptionsSoundOff\";\nroot.Sprites[\"OptionsSoundOffH\"] = {\n    Sheet = \"Options\",\n    Bounds = {\n        X = 2540,\n        Y = 880,\n        Width = 304,\n        Height = 80\n    }\n};\nroot.OptionsMenu.SoundModeOptionsHSprites[#root.OptionsMenu.SoundModeOptionsHSprites + 1] = \"OptionsSoundOffH\";\n\n-- Volume options\n\nroot.Sprites[\"OptionsBgmVolumeLabel\"] = {\n    Sheet = \"Options\",\n    Bounds = { X = 0, Y = 3788, Width = 1786, Height = 100 }\n};\nroot.Sprites[\"OptionsBgmVolumeLabelH\"] = {\n    Sheet = \"Options\",\n    Bounds = { X = 0, Y = 3896, Width = 1786, Height = 100 }\n};\n\nroot.Sprites[\"OptionsVoiceVolumeLabel\"] = {\n    Sheet = \"Options\",\n    Bounds = { X = 0, Y = 3572, Width = 1786, Height = 100 }\n};\nroot.Sprites[\"OptionsVoiceVolumeLabelH\"] = {\n    Sheet = \"Options\",\n    Bounds = { X = 0, Y = 3680, Width = 1786, Height = 100 }\n};\n\nroot.Sprites[\"OptionsSeVolumeLabel\"] = {\n    Sheet = \"Options\",\n    Bounds = { X = 0, Y = 3356, Width = 1786, Height = 100 }\n};\nroot.Sprites[\"OptionsSeVolumeLabelH\"] = {\n    Sheet = \"Options\",\n    Bounds = { X = 0, Y = 3464, Width = 1786, Height = 100 }\n};\n\nroot.Sprites[\"OptionsSystemSeVolumeLabel\"] = {\n    Sheet = \"Options\",\n    Bounds = { X = 0, Y = 3140, Width = 1786, Height = 100 }\n};\nroot.Sprites[\"OptionsSystemSeVolumeLabelH\"] = {\n    Sheet = \"Options\",\n    Bounds = { X = 0, Y = 3248, Width = 1786, Height = 100 }\n};\n\nroot.Sprites[\"OptionsCharacterVoiceVolumeLabel\"] = {\n    Sheet = \"Options\",\n    Bounds = { X = 0, Y = 1196, Width = 1786, Height = 100 }\n};\nroot.Sprites[\"OptionsCharacterVoiceVolumeLabelH\"] = {\n    Sheet = \"Options\",\n    Bounds = { X = 0, Y = 3032, Width = 1786, Height = 100 }\n};\n\n-- Quick save options\n\nroot.Sprites[\"OptionsQuickSaveOptionsLabel\"] = {\n    Sheet = \"Options\",\n    Bounds = { X = 0, Y = 2708, Width = 1786, Height = 100 }\n};\nroot.Sprites[\"OptionsQuickSaveOptionsLabelH\"] = {\n    Sheet = \"Options\",\n    Bounds = { X = 0, Y = 1088, Width = 1786, Height = 100 }\n};\n\nroot.Sprites[\"OptionsQuickSaveChoicesOnly\"] = {\n    Sheet = \"Options\",\n    Bounds = {\n        X = 2406,\n        Y = 2728,\n        Width = 304,\n        Height = 80\n    }\n};\nroot.OptionsMenu.QuickSaveOptionsSprites[#root.OptionsMenu.QuickSaveOptionsSprites + 1] = \"OptionsQuickSaveChoicesOnly\";\nroot.Sprites[\"OptionsQuickSaveChoicesOnlyH\"] = {\n    Sheet = \"Options\",\n    Bounds = {\n        X = 2406,\n        Y = 2816,\n        Width = 304,\n        Height = 80\n    }\n};\nroot.OptionsMenu.QuickSaveOptionsHSprites[#root.OptionsMenu.QuickSaveOptionsHSprites + 1] = \"OptionsQuickSaveChoicesOnlyH\";\n\nroot.Sprites[\"OptionsQuickSaveScenesOnly\"] = {\n    Sheet = \"Options\",\n    Bounds = {\n        X = 2540,\n        Y = 440,\n        Width = 304,\n        Height = 80\n    }\n};\nroot.OptionsMenu.QuickSaveOptionsSprites[#root.OptionsMenu.QuickSaveOptionsSprites + 1] = \"OptionsQuickSaveScenesOnly\";\nroot.Sprites[\"OptionsQuickSaveScenesOnlyH\"] = {\n    Sheet = \"Options\",\n    Bounds = {\n        X = 2718,\n        Y = 3344,\n        Width = 304,\n        Height = 80\n    }\n};\nroot.OptionsMenu.QuickSaveOptionsHSprites[#root.OptionsMenu.QuickSaveOptionsHSprites + 1] = \"OptionsQuickSaveScenesOnlyH\";\n\nroot.Sprites[\"OptionsQuickSaveChoicesScenes\"] = {\n    Sheet = \"Options\",\n    Bounds = {\n        X = 2540,\n        Y = 264,\n        Width = 304,\n        Height = 80\n    }\n};\nroot.OptionsMenu.QuickSaveOptionsSprites[#root.OptionsMenu.QuickSaveOptionsSprites + 1] = \"OptionsQuickSaveChoicesScenes\";\nroot.Sprites[\"OptionsQuickSaveChoicesScenesH\"] = {\n    Sheet = \"Options\",\n    Bounds = {\n        X = 2540,\n        Y = 352,\n        Width = 304,\n        Height = 80\n    }\n};\nroot.OptionsMenu.QuickSaveOptionsHSprites[#root.OptionsMenu.QuickSaveOptionsHSprites + 1] = \"OptionsQuickSaveChoicesScenesH\";\n"
  },
  {
    "path": "profiles/mo8/hud/saveicon.lua",
    "content": "root.SaveIcon = {\n    ForegroundAnimation = \"SaveIcon\",\n    DefaultPosition = { X = 1672, Y = 32 },\n    BackgroundSprite = \"SaveIconBg\",\n    BackgroundOffset = { X = -10.5, Y = -6 },\n    BackgroundMaxAlpha = 0.5,\n    FadeInDuration = 0.5,\n    FadeOutDuration = 0.25\n};\n\nMakeAnimation({\n    Name = \"SaveIcon\",\n    Sheet = \"Data\",\n    FirstFrameX = 312,\n    FirstFrameY = 634,\n    FrameWidth = 216,\n    ColWidth = 216,\n    FrameHeight = 56,\n    RowHeight = 64,\n    Frames = 4,\n    Duration = 0.7,\n    Rows = 4,\n    Columns = 1,\n    PrimaryDirection = AnimationDirections.Down\n});\n\nroot.Sprites[\"SaveIconBg\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 2158.5, Y = 1.5, Width = 0, Height = 0 }\n};"
  },
  {
    "path": "profiles/mo8/hud/savemenu.lua",
    "content": "root.SaveMenu = {\n    Type = SaveMenuType.MO8,\n    DrawType = DrawComponentType.SystemMenu,\n    SaveMenuBackgroundSprite = \"SaveMenuBackground\",\n    EmptyThumbnailSprite = \"EmptyThumbnail\",\n    QuickLoadTextSprite = \"QuickLoadText\",\n    LoadTextSprite = \"LoadText\",\n    SaveTextSprite = \"SaveText\",\n    MenuTitleTextPos = { X = 60, Y = 30 },\n    NextButtonSprite = \"SaveMenuNextButton\",\n    NextButtonHighlightedSprite = \"SaveMenuNextButtonHighlighted\",\n    NextButtonPosition = { X = 1586, Y = 972 },\n    BackButtonSprite = \"SaveMenuBackButton\",\n    BackButtonHighlightedSprite = \"SaveMenuBackButtonHighlighted\",\n    BackButtonPosition = { X = 30, Y = 972 },\n    BackButtonPositionVoice = { X = 808, Y = 972 },\n    EntryStartX = 153,\n    EntryXPadding = 512,\n    EntryStartY = 102,\n    EntryYPadding = 141,\n    FadeInDuration = 0.2,\n    FadeOutDuration = 0.2\n};\n\nroot.Sprites[\"SaveMenuBackground\"] = {\n    Sheet = \"Save\",\n    Bounds = { X = 0, Y = 0, Width = 1920, Height = 1080 }\n};\n\nroot.Sprites[\"EmptyThumbnail\"] = {\n    Sheet = \"Save\",\n    Bounds = { X = 1281, Y = 1, Width = 178, Height = 108 }\n};\n\nroot.Sprites[\"QuickLoadText\"] = {\n    Sheet = \"Save\",\n    Bounds = { X = 0, Y = 1848, Width = 438, Height = 90 }\n};\nroot.Sprites[\"SaveText\"] = {\n    Sheet = \"Save\",\n    Bounds = { X = 0, Y = 1652, Width = 438, Height = 90 }\n};\nroot.Sprites[\"LoadText\"] = {\n    Sheet = \"Save\",\n    Bounds = { X = 0, Y = 1750, Width = 438, Height = 90 }\n};\n\nroot.Sprites[\"SaveMenuBackButton\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 0, Y = 722, Width = 304, Height = 80 }\n};\n\nroot.Sprites[\"SaveMenuBackButtonHighlighted\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 0, Y = 898, Width = 304, Height = 80 }\n};\n\nroot.Sprites[\"SaveMenuNextButton\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 0, Y = 810, Width = 304, Height = 80 }\n};\n\nroot.Sprites[\"SaveMenuNextButtonHighlighted\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 0, Y = 634, Width = 304, Height = 80 }\n};\n"
  },
  {
    "path": "profiles/mo8/hud/selectiondisplay.lua",
    "content": "root.SelectionDisplay = {\n    DrawType = DrawComponentType.Text,\n    SelectionBackgroundSprite = \"SelectionBackground\",\n    PlainSelectionFrameTopLeftSprite = \"PlainSelectionFrameTopLeft\",\n    PlainSelectionFrameTopSideSprite = \"PlainSelectionFrameTopSide\",\n    PlainSelectionFrameTopRightSprite = \"PlainSelectionFrameTopRight\",\n    PlainSelectionFrameLeftSideSprite = \"PlainSelectionFrameLeftSide\",\n    PlainSelectionFrameBottomLeftSprite = \"PlainSelectionFrameBottomLeft\",\n    PlainSelectionFrameRightSideSprite = \"PlainSelectionFrameRightSide\",\n    PlainSelectionFrameBottomRightSprite = \"PlainSelectionFrameBottomRight\",\n    PlainSelectionFrameBottomSideSprite = \"PlainSelectionFrameBottomSide\",\n    PlainSelectionFrameMiddleSprite = \"PlainSelectionFrameMiddle\",\n    SelectionFocusedSprite = \"SelectionHighlight\",\n    HighlightTextOnly = false,\n    SelectionMaxCount = 5,\n    SelectionBackgroundX = 470,\n    SelectionBackgroundY = {332, 332, 210, 110, 59},\n    SelectionYSpacing = 93,\n    PlainSelectionYSpacing = 10,\n    FadeAnimationDurationInOut = 0.2\n};\n\nroot.Sprites[\"SelectionBackground\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 0, Y = 358, Width = 980, Height = 80 }\n};\n\nroot.Sprites[\"SelectionHighlight\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 0, Y = 543, Width = 980, Height = 80 }\n};\n\nroot.Sprites[\"PlainSelectionFrameTopLeft\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 754, Y = 634, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameTopSide\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 770, Y = 634, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameTopRight\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 786, Y = 634, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameLeftSide\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 754, Y = 650, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameBottomLeft\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 754, Y = 666, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameRightSide\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 786, Y = 650, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameBottomRight\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 786, Y = 666, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameBottomSide\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 770, Y = 666, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameMiddle\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 770, Y = 650, Width = 16, Height = 16 }\n};\n"
  },
  {
    "path": "profiles/mo8/hud/sysmesboxdisplay.lua",
    "content": "local sheet = \"Data\";\nlocal name = \"SysMesBox\";\n\nroot.SysMesBoxDisplay = {\n    Type = SysMesBoxType.CHLCC,\n    DrawType = DrawComponentType.SystemMessage,\n    BoxX = 383,\n    BoxY = 366,\n    TextFontSize = 42,\n    TextMiddleY = 412,\n    TextX = 942,\n    TextLineHeight = 34,\n    TextMarginY = 14,\n    ChoicePadding = 60,\n    ChoiceY = 647,\n    ChoiceXBase = 1211,\n    MinMaxMesWidth = 294,\n    MinHighlightWidth = 48,\n    HighlightBaseWidth = 144,\n    HighlightYOffset = 12,\n    HighlightXOffset = 0,\n    HighlightXBase = 658,\n    HighlightXStep = 132,\n    HighlightRightPartSpriteWidth = 24,\n    AnimationSpeed = 55,\n    FadeInDuration = 0.33,\n    FadeOutDuration = 0.25\n};\n\nroot.Sprites[name .. \"Box\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 0,\n        Y = 0,\n        Width = 1153,\n        Height = 350\n    }\n};\nroot.SysMesBoxDisplay.Box = name .. \"Box\";\n\nroot.Sprites[name .. \"BoxDecoration\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 1383,\n        Y = 117,\n        Width = 592,\n        Height = 8\n    }\n};\nroot.SysMesBoxDisplay.BoxDecoration = name .. \"BoxDecoration\";\n\nroot.Sprites[name .. \"SelectionLeftPart\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 820,\n        Y = 666,\n        Width = 91,\n        Height = 32\n    }\n};\nroot.SysMesBoxDisplay.SelectionLeftPart = name .. \"SelectionLeftPart\";\n\nroot.Sprites[name .. \"SelectionRightPart\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 820,\n        Y = 666,\n        Width = 91,\n        Height = 32\n    }\n};\nroot.SysMesBoxDisplay.SelectionRightPart = name .. \"SelectionRightPart\";\n\nroot.Sprites[name .. \"SelectionMiddlePart\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 820,\n        Y = 666,\n        Width = 91,\n        Height = 32\n    }\n};\nroot.SysMesBoxDisplay.SelectionMiddlePart = name .. \"SelectionMiddlePart\";"
  },
  {
    "path": "profiles/mo8/hud/systemmenu.lua",
    "content": "root.SystemMenu = {\n    Type = SystemMenuType.MO8,\n    DrawType = DrawComponentType.SystemMenu,\n    SystemMenuBackgroundSprite = \"SystemMenuBackground\",\n    SystemMenuX = 1496,\n    SystemMenuY = 54,\n    MenuEntriesSprites = {},\n    MenuEntriesHighlightedSprites = {},\n    MenuEntriesLockedSprites = {},\n    MenuEntriesNum = 8,\n    MenuEntriesHNum = 8,\n    MenuEntriesLNum = 8,\n    MenuEntriesX = 1512,\n    MenuEntriesXOffset = 100,\n    MenuEntriesFirstY = 84,\n    MenuEntriesYPadding = 72,\n    MenuEntriesTargetWidth = 400,\n    ExitMenuButtonId = 7,\n    FadeInDuration = 0.2,\n    FadeOutDuration = 0.2\n};\n\nroot.Sprites[\"SystemMenuEntryQuickSaveHighlighted\"] = {\n    Sheet = \"MenuChip\",\n    Bounds = { X = 840, Y = 0, Width = root.SystemMenu.MenuEntriesTargetWidth, Height = 72 }\n};\n\nroot.Sprites[\"SystemMenuEntryQuickSaveLocked\"] = {\n    Sheet = \"MenuChip\",\n    Bounds = { X = 840, Y = 80, Width = root.SystemMenu.MenuEntriesTargetWidth, Height = 72 }\n};\n\nroot.Sprites[\"SystemMenuEntryQuickSave\"] = {\n    Sheet = \"MenuChip\",\n    Bounds = { X = 840, Y = 160, Width = root.SystemMenu.MenuEntriesTargetWidth, Height = 72 }\n};\n\nroot.Sprites[\"SystemMenuEntryQuickLoadHighlighted\"] = {\n    Sheet = \"MenuChip\",\n    Bounds = { X = 816, Y = 720, Width = root.SystemMenu.MenuEntriesTargetWidth, Height = 72 }\n};\n\nroot.Sprites[\"SystemMenuEntryQuickLoadLocked\"] = {\n    Sheet = \"MenuChip\",\n    Bounds = { X = 816, Y = 800, Width = root.SystemMenu.MenuEntriesTargetWidth, Height = 72 }\n};\n\nroot.Sprites[\"SystemMenuEntryQuickLoad\"] = {\n    Sheet = \"MenuChip\",\n    Bounds = { X = 816, Y = 880, Width = root.SystemMenu.MenuEntriesTargetWidth, Height = 72 }\n};\n\nroot.Sprites[\"SystemMenuEntrySaveHighlighted\"] = {\n    Sheet = \"MenuChip\",\n    Bounds = { X = 432, Y = 480, Width = root.SystemMenu.MenuEntriesTargetWidth, Height = 72 }\n};\n\nroot.Sprites[\"SystemMenuEntrySaveLocked\"] = {\n    Sheet = \"MenuChip\",\n    Bounds = { X = 432, Y = 560, Width = root.SystemMenu.MenuEntriesTargetWidth, Height = 72 }\n};\n\nroot.Sprites[\"SystemMenuEntrySave\"] = {\n    Sheet = \"MenuChip\",\n    Bounds = { X = 816, Y = 640, Width = root.SystemMenu.MenuEntriesTargetWidth, Height = 72 }\n};\n\nroot.Sprites[\"SystemMenuEntryLoadHighlighted\"] = {\n    Sheet = \"MenuChip\",\n    Bounds = { X = 840, Y = 240, Width = root.SystemMenu.MenuEntriesTargetWidth, Height = 72 }\n};\n\nroot.Sprites[\"SystemMenuEntryLoadLocked\"] = {\n    Sheet = \"MenuChip\",\n    Bounds = { X = 432, Y = 320, Width = root.SystemMenu.MenuEntriesTargetWidth, Height = 72 }\n};\n\nroot.Sprites[\"SystemMenuEntryLoad\"] = {\n    Sheet = \"MenuChip\",\n    Bounds = { X = 432, Y = 400, Width = root.SystemMenu.MenuEntriesTargetWidth, Height = 72 }\n};\n\nroot.Sprites[\"SystemMenuEntryOptionsHighlighted\"] = {\n    Sheet = \"MenuChip\",\n    Bounds = { X = 432, Y = 0, Width = root.SystemMenu.MenuEntriesTargetWidth, Height = 72 }\n};\n\nroot.Sprites[\"SystemMenuEntryOptionsLocked\"] = {\n    Sheet = \"MenuChip\",\n    Bounds = { X = 432, Y = 80, Width = root.SystemMenu.MenuEntriesTargetWidth, Height = 72 }\n};\n\nroot.Sprites[\"SystemMenuEntryOptions\"] = {\n    Sheet = \"MenuChip\",\n    Bounds = { X = 432, Y = 160, Width = root.SystemMenu.MenuEntriesTargetWidth, Height = 72 }\n};\n\nroot.Sprites[\"SystemMenuEntryManualHighlighted\"] = {\n    Sheet = \"MenuChip\",\n    Bounds = { X = 432, Y = 240, Width = root.SystemMenu.MenuEntriesTargetWidth, Height = 72 }\n};\n\nroot.Sprites[\"SystemMenuEntryManualLocked\"] = {\n    Sheet = \"MenuChip\",\n    Bounds = { X = 408, Y = 824, Width = root.SystemMenu.MenuEntriesTargetWidth, Height = 72 }\n};\n\nroot.Sprites[\"SystemMenuEntryManual\"] = {\n    Sheet = \"MenuChip\",\n    Bounds = { X = 408, Y = 904, Width = root.SystemMenu.MenuEntriesTargetWidth, Height = 72 }\n};\n\nroot.Sprites[\"SystemMenuEntryTitleHighlighted\"] = {\n    Sheet = \"MenuChip\",\n    Bounds = { X = 0, Y = 904, Width = root.SystemMenu.MenuEntriesTargetWidth, Height = 72 }\n};\n\nroot.Sprites[\"SystemMenuEntryTitleLocked\"] = {\n    Sheet = \"MenuChip\",\n    Bounds = { X = 408, Y = 744, Width = root.SystemMenu.MenuEntriesTargetWidth, Height = 72 }\n};\n\nroot.Sprites[\"SystemMenuEntryTitle\"] = {\n    Sheet = \"MenuChip\",\n    Bounds = { X = 408, Y = 664, Width = root.SystemMenu.MenuEntriesTargetWidth, Height = 72 }\n};\n\nroot.Sprites[\"SystemMenuEntryCloseHighlighted\"] = {\n    Sheet = \"MenuChip\",\n    Bounds = { X = 0, Y = 664, Width = root.SystemMenu.MenuEntriesTargetWidth, Height = 72 }\n};\n\nroot.Sprites[\"SystemMenuEntryCloseLocked\"] = {\n    Sheet = \"MenuChip\",\n    Bounds = { X = 0, Y = 824, Width = root.SystemMenu.MenuEntriesTargetWidth, Height = 72 }\n};\n\nroot.Sprites[\"SystemMenuEntryClose\"] = {\n    Sheet = \"MenuChip\",\n    Bounds = { X = 0, Y = 744, Width = root.SystemMenu.MenuEntriesTargetWidth, Height = 72 }\n};\n\nroot.SystemMenu.MenuEntriesSprites[#root.SystemMenu.MenuEntriesSprites + 1] = \"SystemMenuEntryQuickSave\";\nroot.SystemMenu.MenuEntriesSprites[#root.SystemMenu.MenuEntriesSprites + 1] = \"SystemMenuEntryQuickLoad\";\nroot.SystemMenu.MenuEntriesSprites[#root.SystemMenu.MenuEntriesSprites + 1] = \"SystemMenuEntrySave\";\nroot.SystemMenu.MenuEntriesSprites[#root.SystemMenu.MenuEntriesSprites + 1] = \"SystemMenuEntryLoad\";\nroot.SystemMenu.MenuEntriesSprites[#root.SystemMenu.MenuEntriesSprites + 1] = \"SystemMenuEntryOptions\";\nroot.SystemMenu.MenuEntriesSprites[#root.SystemMenu.MenuEntriesSprites + 1] = \"SystemMenuEntryManual\";\nroot.SystemMenu.MenuEntriesSprites[#root.SystemMenu.MenuEntriesSprites + 1] = \"SystemMenuEntryTitle\";\nroot.SystemMenu.MenuEntriesSprites[#root.SystemMenu.MenuEntriesSprites + 1] = \"SystemMenuEntryClose\";\n\nroot.SystemMenu.MenuEntriesHighlightedSprites[#root.SystemMenu.MenuEntriesHighlightedSprites + 1] = \"SystemMenuEntryQuickSaveHighlighted\";\nroot.SystemMenu.MenuEntriesHighlightedSprites[#root.SystemMenu.MenuEntriesHighlightedSprites + 1] = \"SystemMenuEntryQuickLoadHighlighted\";\nroot.SystemMenu.MenuEntriesHighlightedSprites[#root.SystemMenu.MenuEntriesHighlightedSprites + 1] = \"SystemMenuEntrySaveHighlighted\";\nroot.SystemMenu.MenuEntriesHighlightedSprites[#root.SystemMenu.MenuEntriesHighlightedSprites + 1] = \"SystemMenuEntryLoadHighlighted\";\nroot.SystemMenu.MenuEntriesHighlightedSprites[#root.SystemMenu.MenuEntriesHighlightedSprites + 1] = \"SystemMenuEntryOptionsHighlighted\";\nroot.SystemMenu.MenuEntriesHighlightedSprites[#root.SystemMenu.MenuEntriesHighlightedSprites + 1] = \"SystemMenuEntryManualHighlighted\";\nroot.SystemMenu.MenuEntriesHighlightedSprites[#root.SystemMenu.MenuEntriesHighlightedSprites + 1] = \"SystemMenuEntryTitleHighlighted\";\nroot.SystemMenu.MenuEntriesHighlightedSprites[#root.SystemMenu.MenuEntriesHighlightedSprites + 1] = \"SystemMenuEntryCloseHighlighted\";\n\nroot.SystemMenu.MenuEntriesLockedSprites[#root.SystemMenu.MenuEntriesLockedSprites + 1] = \"SystemMenuEntryQuickSaveLocked\";\nroot.SystemMenu.MenuEntriesLockedSprites[#root.SystemMenu.MenuEntriesLockedSprites + 1] = \"SystemMenuEntryQuickLoadLocked\";\nroot.SystemMenu.MenuEntriesLockedSprites[#root.SystemMenu.MenuEntriesLockedSprites + 1] = \"SystemMenuEntrySaveLocked\";\nroot.SystemMenu.MenuEntriesLockedSprites[#root.SystemMenu.MenuEntriesLockedSprites + 1] = \"SystemMenuEntryLoadLocked\";\nroot.SystemMenu.MenuEntriesLockedSprites[#root.SystemMenu.MenuEntriesLockedSprites + 1] = \"SystemMenuEntryOptionsLocked\";\nroot.SystemMenu.MenuEntriesLockedSprites[#root.SystemMenu.MenuEntriesLockedSprites + 1] = \"SystemMenuEntryManualLocked\";\nroot.SystemMenu.MenuEntriesLockedSprites[#root.SystemMenu.MenuEntriesLockedSprites + 1] = \"SystemMenuEntryTitleLocked\";\nroot.SystemMenu.MenuEntriesLockedSprites[#root.SystemMenu.MenuEntriesLockedSprites + 1] = \"SystemMenuEntryCloseLocked\";\n\nroot.Sprites[\"SystemMenuBackground\"] = {\n    Sheet = \"MenuChip\",\n    Bounds = { X = 0, Y = 0, Width = 424, Height = 656 }\n};\n"
  },
  {
    "path": "profiles/mo8/hud/tipsmenu.lua",
    "content": "root.TipsMenu = {\n    Type = TipsMenuType.None\n};\n"
  },
  {
    "path": "profiles/mo8/hud/tipsnotification.lua",
    "content": "root.TipsNotification = {\n    Type = TipsNotificationType.None,\n};\n"
  },
  {
    "path": "profiles/mo8/hud/titlemenu.lua",
    "content": "root.TitleMenu = {\n    Type = TitleMenuType.MO8,\n    DrawType = DrawComponentType.SystemMenu,\n    PressToStartX = 745,\n    PressToStartY = 586,\n    LogoX = 560,\n    LogoY = 24,\n    MenuEntriesNum = 13,\n    MenuEntriesSprites = {},\n    MenuEntriesHighlightedSprites = {},\n    MenuEntriesX = 745,\n    MenuEntriesFirstY = 586,\n    MenuEntriesGalleryFirstY = 546,\n    MenuEntriesYPadding = 80,\n    NewGameSpriteIndex = 10,\n    ContinueSpriteIndex = 9,\n    OptionSpriteIndex = 8,\n    GallerySpriteIndex = 6,\n    AlbumSpriteIndex = 3,\n    MusicSpriteIndex = 2,\n    ClearListSpriteIndex = 4,\n    WarningSpriteIndex = 0,\n    AdditionalSpriteIndex = 8,\n    DLCSpriteIndex = 1,\n    LoadSpriteIndex = 5,\n    QuickLoadSpriteIndex = 12,\n    HasAdditional = false,\n    PressToStartAnimated = false,\n    PressToStartAnimDurationIn = 0,\n    PressToStartAnimDurationOut = 0,\n    PrimaryFadeAnimDuration = 0.5,\n    ItemFadeAnimDuration = 0.3,\n    PressToStartSprite = \"TitleMenuPressToStart\",\n    BackgroundSprite = \"TitleMenuBackground\",\n    LogoSprite = \"TitleMenuLogo\"\n};\n\nfor i = 1, 8 do\n    root.Sprites[\"MenuEntry\" .. i] = {\n        Sheet = \"TitleChip\",\n        Bounds = {\n            X = 0,\n            Y = 308 + (i * 88),\n            Width = 430,\n            Height = 80\n        }\n    };\n    root.TitleMenu.MenuEntriesSprites[#root.TitleMenu.MenuEntriesSprites + 1] = \"MenuEntry\" .. i;\nend\n\nfor i = 0, 4 do\n    root.Sprites[\"MenuEntry\" .. (i + 10)] = {\n        Sheet = \"TitleChip\",\n        Bounds = {\n            X = 438,\n            Y = 308 + (i * 88),\n            Width = 430,\n            Height = 80\n        }\n    };\n    root.TitleMenu.MenuEntriesSprites[#root.TitleMenu.MenuEntriesSprites + 1] = \"MenuEntry\" .. (i + 10);\nend\n\nroot.TitleMenu.MenuEntriesHighlightedSprites = root.TitleMenu.MenuEntriesSprites;\n\nroot.Sprites[\"TitleMenuPressToStart\"] = {\n    Sheet = \"TitleChip\",\n    Bounds = { X = 0, Y = 308, Width = 430, Height = 80 },\n};\n\nroot.Sprites[\"TitleMenuBackground\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 0, Y = 0, Width = 1920, Height = 1080 },\n};\n\nroot.Sprites[\"TitleMenuLogo\"] = {\n    Sheet = \"TitleChip\",\n    Bounds = { X = 0, Y = 0, Width = 810, Height = 300 },\n};"
  },
  {
    "path": "profiles/mo8/savedata.lua",
    "content": "root.SaveData = {\n    Type = SaveDataType.None,\n    SaveFilePath = \"games/mo8/savedata/SYSTEM.DAT\",\n};"
  },
  {
    "path": "profiles/mo8/scriptvars.lua",
    "content": "local sv = root.ScriptVars;\n\nsv.SW_BG1POSX_OFS = 2400;\nsv.SW_BG1POSY_OFS = 2401;\nsv.SW_BG1SX_OFS = 2402;\nsv.SW_BG1SY_OFS = 2403;\nsv.SW_BG1SIZE_OFS = 2404;\nsv.SW_BG1LX_OFS = 2405;\nsv.SW_BG1LY_OFS = 2406;\nsv.SW_BG1ROTZ_OFS = 2407;\nsv.SW_BG1ALPHA_OFS = 2408;\nsv.SW_MASK1COLOR = 4400;\nsv.SW_MASK1ALPHA = 4401;\nsv.SW_MASK1PRI = 4402;\nsv.SW_MASK1POSX = 4403;\nsv.SW_MASK1POSY = 4404;\nsv.SW_MASK1SIZEX = 4405;\nsv.SW_MASK1SIZEY = 4406;\nsv.SW_MESMODE0 = 4363;\n\nsv.SF_MOVIEPLAY = 1851;"
  },
  {
    "path": "profiles/mo8/sprites.lua",
    "content": "root.SpriteSheets = {\n    [\"Data\"] = {\n        Path = { Mount = \"system\", Id = 5 },\n        DesignWidth = 2048,\n        DesignHeight = 1024\n    },\n    [\"MesBox\"] = {\n        Path = { Mount = \"system\", Id = 13 },\n        DesignWidth = 2048,\n        DesignHeight = 512\n    },\n    [\"Font\"] = {\n        Path = { Mount = \"system\", Id = 9 },\n        DesignWidth = 3072,\n        DesignHeight = 5568\n    },\n    [\"Menu\"] = {\n        Path = { Mount = \"system\", Id = 8 },\n        DesignWidth = 2048,\n        DesignHeight = 1024\n    },\n    [\"Title\"] = {\n        Path = { Mount = \"system\", Id = 25 },\n        DesignWidth = 1920,\n        DesignHeight = 1080\n    },\n    [\"TitleChip\"] = {\n        Path = { Mount = \"system\", Id = 24 },\n        DesignWidth = 1024,\n        DesignHeight = 1024\n    },\n    [\"Backlog\"] = {\n        Path = {Mount = \"system\", Id = 2 },\n        DesignWidth = 2048,\n        DesignHeight = 2048\n    },\n    [\"MenuChip\"] = {\n        Path = {Mount = \"system\", Id = 12 },\n        DesignWidth = 2048,\n        DesignHeight = 1024\n    },\n    [\"Options\"] = {\n        Path = {Mount = \"system\", Id = 19 },\n        DesignWidth = 4096,\n        DesignHeight = 4096\n    },\n    [\"Save\"] = {\n        Path = {Mount = \"system\", Id = 20 },\n        DesignWidth = 2048,\n        DesignHeight = 2048\n    }\n};\n\nroot.Sprites = {};"
  },
  {
    "path": "profiles/mo8/tipssystem.lua",
    "content": "root.TipsSystem = {\n    Type = TipsSystemType.None,\n};"
  },
  {
    "path": "profiles/mo8/vfs.lua",
    "content": "root.Vfs = {\n    Mounts = {\n        [\"script\"] = {\"games/mo8/gamedata/script.cls\"},\n        [\"system\"] = {\"games/mo8/gamedata/system.cls\"},\n        [\"bgm\"] = {\"games/mo8/gamedata/bgm.cpk\"},\n        [\"se\"] = {\"games/mo8/gamedata/se.cpk\"},\n        [\"sysse\"] = {\"games/mo8/gamedata/sysse.cpk\"},\n        [\"voice\"] = {\"games/mo8/gamedata/voice.cpk\"},\n        [\"bg\"] = {\"games/mo8/gamedata/bg.cpk\"},\n        [\"chara\"] = {\"games/mo8/gamedata/chara.cpk\"},\n        [\"mask\"] = {\"games/mo8/gamedata/mask.cpk\"},\n        [\"movie\"] = {\"games/mo8/gamedata/movie.cls\"},\n        [\"face\"] = {\"games/mo8/gamedata/face.cpk\"}\n    }\n};"
  },
  {
    "path": "profiles/modelviewer/config.lua",
    "content": "root.Language = \"Japanese\";\nroot.ResolutionWidth = 1280;\nroot.ResolutionHeight = 720;\nroot.Fullscreen = false;\nroot.DateFormat = DateFormatType.YMD;"
  },
  {
    "path": "profiles/modelviewer/game.lua",
    "content": "include('rne/game.lua');\n\nroot.GameFeatures = GameFeature.Scene3D | GameFeature.ModelViewer | GameFeature.Audio | GameFeature.Input;\n\nroot.WindowName = \"Model Viewer ELITE\";"
  },
  {
    "path": "profiles/modelviewer-dash/config.lua",
    "content": "root.Language = \"Japanese\";\nroot.ResolutionWidth = 1920;\nroot.ResolutionHeight = 1080;\nroot.Fullscreen = false;\nroot.DateFormat = DateFormatType.YMD;"
  },
  {
    "path": "profiles/modelviewer-dash/game.lua",
    "content": "include('dash/game.lua');\n\nroot.GameFeatures = GameFeature.Scene3D | GameFeature.ModelViewer | GameFeature.Audio | GameFeature.Input;\n\nroot.WindowName = \"Model Viewer DaSH\";"
  },
  {
    "path": "profiles/rne/charset.lua",
    "content": "root.Charset = {\n    Flags = {}\n};\n\nfor i = 0, (64 * 50) - 1 do root.Charset.Flags[i] = 0; end\n\nlocal spaces = {[0]=0, 63};\nfor i = 0, #spaces do\n    root.Charset.Flags[spaces[i]] = root.Charset.Flags[spaces[i]] | CharacterTypeFlags.Space;\nend\n\n-- 、 。 ． ， ？ ！ 〜 ” ー ） 〕 ］ ｝ 〉 》 」 』 】☆ ★ ♪ 々 ぁ ぃ ぅ ぇ\n-- ぉ っ ゃ ゅ ょ ァ ィ ゥ ェ ォ ッ ャ ュ ョ –\nlocal wordEndingPuncts = {\n    [0]=0x00BE, 0x00BF, 0x00C1,\n    0x00C0, 0x00C4, 0x00C5,\n    0x00E4, 0x00CB, 0x00E5,\n    0x00CD, 0x00CF, 0x00D1,\n    0x00D3, 0x00D5, 0x00D7,\n    0x00D9, 0x00DB, 0x00DD,\n    0x01A5, 0x01A6, 0x00E6,\n    0x0187, 0x00E8, 0x00E9,\n    0x00EA, 0x00EB, 0x00EC,\n    0x00ED, 0x00EE, 0x00EF,\n    0x00F0, 0x00F2, 0x00F3,\n    0x00F4, 0x00F5, 0x00F6,\n    0x00F7, 0x00F8, 0x00F9,\n    0x00FA, 0x0113\n};\nfor i = 0, #wordEndingPuncts do\n    root.Charset.Flags[wordEndingPuncts[i]] = root.Charset.Flags[wordEndingPuncts[i]] | CharacterTypeFlags.WordEndingPunct;\nend\n\n-- space(63) space(0) “ （ 〔 ［ ｛ 〈 《 「\nlocal wordStartingPuncts = {\n    [0]=63, 0, 0x00CA,\n    0x00CC, 0x00CE, 0x00D0,\n    0x00D2, 0x00D4, 0x00D6,\n    0x00D8, 0x00DA, 0x00DC\n};\nfor i = 0, #wordStartingPuncts do\n    root.Charset.Flags[wordStartingPuncts[i]] = root.Charset.Flags[wordStartingPuncts[i]] | CharacterTypeFlags.WordStartingPunct;\nend"
  },
  {
    "path": "profiles/rne/config.lua",
    "content": "root.Language = \"Japanese\";\nroot.ResolutionWidth = 1280;\nroot.ResolutionHeight = 720;\nroot.Fullscreen = false;\nroot.DateFormat = DateFormatType.YMD;"
  },
  {
    "path": "profiles/rne/dialogue.lua",
    "content": "root.Sprites[\"ADVBox\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 768, Y = 807, Width = 1280, Height = 206 },\n    -- Even though the sprite is sized for 1280x720, they draw it unscaled at 960x544, cutting off the remainder\n    BaseScale = { X = 1280 / 960, Y = 720 / 544 }\n};\n\nroot.Sprites[\"DialogueWaitIcon\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 1, Y = 97, Width = 32, Height = 32 }\n};\n\nroot.Dialogue = {\n    REVBounds = { X = 0, Y = 0, Width = 960, Height = 400 },\n    REVNameFontSize = 24,\n    REVColor = 0,\n    REVNameColor = 24,\n    REVNameOffset = 34,\n    REVNameLocation = REVNameLocationType.TopLeft,\n    REVOutlineMode = 2,\n    REVNameOutlineMode = 2,\n    NVLBounds = { X = 125, Y = 85, Width = 1024, Height = 400 },\n    ADVBounds = { X = 161, Y = 546, Width = 960, Height = 180 },\n    ADVBoxSprite = \"ADVBox\",\n    ADVBoxPos = { X = 0, Y = 477.8 },\n    FadeOutDuration = 0.33,\n    FadeInDuration = 0.33,\n    TextFadeInDuration = 0.33,\n    TextFadeOutDuration = 0.33,\n    DialogueBoxCurrentType = DialogueBoxType.Plain,\n    NVLBoxMaxOpacity = 0.55,\n\n    ADVNameAlignment = TextAlignment.Left,\n    ADVNameFontSize = 22,\n    ADVNamePos = { X = 104, Y = 498 },\n\n    NametagCurrentType = NametagType.ThreePiece,\n    NametagPosition = { X = 0, Y = 503 },\n    NametagLeftSprite = \"NametagLeftSprite\",\n    NametagMiddleSprite = \"NametagMiddleSprite\",\n    NametagRightSprite = \"NametagRightSprite\",\n    NametagMiddleBaseWidth = 64.0,\n\n    WaitIconCurrentType = WaitIconType.Rotate,\n    WaitIconSprite = \"DialogueWaitIcon\",\n    WaitIconOffset = { X = 4, Y = 4 },\n    WaitIconAnimationDuration = 3.2,\n    DialogueFont = \"Default\",\n    SetFontSizeRatio = 1000.0,\n    DefaultFontSize = 32,\n    RubyFontSize = 14,\n    RubyYOffset = -18,\n    ColorTable = {\n        {0xFFFFFF, 0x000000}, {0x5080FF, 0x000000},\n        {0xFF7080, 0x000000}, {0xFFA0F8, 0x000000},\n        {0x46FF80, 0x000000}, {0x90FFFF, 0x000000},\n        {0xFFFF70, 0x000000}, {0x80FFC0, 0x000000},\n        {0xFFB080, 0x000000}, {0xB080FF, 0x000000},\n        {0x000000, 0x808080}, {0x000000, 0x5080FF},\n        {0x000000, 0xFF7080}, {0x000000, 0xFFA0F8},\n        {0x000000, 0x268840}, {0x000000, 0x409999},\n        {0x000000, 0x888830}, {0x000000, 0x80FFC0},\n        {0x000000, 0xFFB080}, {0x000000, 0xB080FF},\n        {0xD0D0D0, 0x000000}, {0xD0D0FF, 0x000000},\n        {0xFFD0D0, 0x000000}, {0xFFD0FF, 0x000000},\n        {0xD0FFD0, 0x000000}, {0xD0FFFF, 0x000000},\n        {0xFFFFD0, 0x000000}, {0xE8FFD0, 0x000000},\n        {0xFFE8D0, 0x000000}, {0xD0E8FF, 0x000000},\n        {0xFFFFFF, 0x808080}, {0xFFFFFF, 0x5080FF},\n        {0xFFFFFF, 0xFF7080}, {0xFFFFFF, 0xFFA0F8},\n        {0xFFFFFF, 0x46FF80}, {0xFFFFFF, 0x90FFFF},\n        {0xFFFFFF, 0xFFFF70}, {0xFFFFFF, 0x80FFC0},\n        {0xFFFFFF, 0xFFB080}, {0xFFFFFF, 0xB080FF},\n        {0xFFEEEE, 0x000000}, {0xFFCCCC, 0x000000},\n        {0xFFAAAA, 0x000000}, {0xFF9999, 0x000000},\n        {0xFF8888, 0x000000}, {0xFFFF00, 0x000000},\n        {0xFEF000, 0x000000}, {0xFF7777, 0x000000},\n        {0xFF6666, 0x000000}, {0xFF5555, 0x000000},\n        {0xFF4444, 0x000000}, {0xFF3333, 0x000000},\n        {0xFF2222, 0x000000}, {0xFF0000, 0x000000},\n        {0xDD0000, 0x000000}, {0xBB0000, 0x000000},\n        {0xB00000, 0x000000}, {0xAA0000, 0x000000},\n        {0x950000, 0x000000}, {0x808080, 0x000000},\n        {0xAAAAAA, 0x000000}, {0xAAC1C9, 0x000000},\n        {0x000000, 0x000000}, {0x000000, 0x000000},\n        {0x000000, 0x000000}, {0x000000, 0x000000},\n        {0x000000, 0x000000}, {0xF80B0B, 0x000000},\n        {0xF8910B, 0x000000}, {0x33F12A, 0x000000}\n    },\n    MaxPageSize = 2000,\n    PageCount = 3,\n    ColorTagIsUint8 = false\n};\n\nroot.Sprites[\"NametagLeftSprite\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 768, Y = 774, Width = 155, Height = 31 },\n    BaseScale = { X = 1280 / 960, Y = 720 / 544 }\n};\n\nroot.Sprites[\"NametagMiddleSprite\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 923, Y = 774, Width = 86, Height = 31 },\n    BaseScale = { X = 1280 / 960, Y = 720 / 544 }\n};\n\nroot.Sprites[\"NametagLeftSprite\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 1009, Y = 774, Width = 22, Height = 31 },\n    BaseScale = { X = 1280 / 960, Y = 720 / 544 }\n};\n"
  },
  {
    "path": "profiles/rne/font-lb.lua",
    "content": "root.Fonts = {\n    [\"Default\"] = {\n        Type = FontType.LB,\n        ForegroundSheet = \"FontLBForeground\",\n        OutlineSheet = \"FontLBOutline\",\n        Columns = 64,\n        Rows = 50,\n        AdvanceWidths = \"games/rne/font-lb/widths.bin\",\n        AdvanceWidthsEmWidth = 48,\n        OutlineOffset = { X = -4, Y = -4 },\n        LineSpacing = 14,\n    }\n};\n\nroot.SpriteSheets[\"Font\"] = nil;\n\nroot.SpriteSheets[\"FontLBForeground\"] = {\n    Path = \"games/rne/font-lb/foreground.png\",\n    DesignWidth = 3072,\n    DesignHeight = 2400\n};\n\nroot.SpriteSheets[\"FontLBOutline\"] = {\n    Path = \"games/rne/font-lb/outline.png\",\n    DesignWidth = 3648,\n    DesignHeight = 2852\n};"
  },
  {
    "path": "profiles/rne/font.lua",
    "content": "-- WARNING, 0-INDEXED ARRAYS AHEAD\n\nroot.Fonts = {\n    [\"Default\"] = {\n        Type = FontType.Basic,\n        Sheet = \"Font\",\n        Columns = 64,\n        Rows = 50,\n        AdvanceWidths = {},\n        AdvanceWidthsEmWidth = 32,\n        LineSpacing = 22,\n    }\n};\n\nlocal westernAdvanceWidths = {\n    [0]=0x20, 0x16, 0x0E, 0x14, 0x15, 0x16, 0x16, 0x16, 0x15, 0x16, 0x16, 0x18,\n        0x16, 0x17, 0x17, 0x15, 0x14, 0x18, 0x16, 0x08, 0x0E, 0x17, 0x13, 0x1A,\n        0x16, 0x19, 0x15, 0x19, 0x16, 0x15, 0x15, 0x16, 0x17, 0x1E, 0x16, 0x15,\n        0x15, 0x14, 0x13, 0x13, 0x14, 0x14, 0x0D, 0x14, 0x13, 0x08, 0x09, 0x13,\n        0x08, 0x1A, 0x13, 0x15, 0x13, 0x14, 0x0D, 0x12, 0x0D, 0x13, 0x13, 0x1A,\n        0x13, 0x13, 0x12, 0x11, 0x0F, 0x08, 0x0F, 0x08, 0x08, 0x14, 0x08, 0x08,\n        0x1C, 0x17, 0x1E, 0x0F, 0x11, 0x19, 0x0C, 0x0D, 0x0D, 0x0C, 0x14, 0x13,\n        0x15, 0x12, 0x12, 0x12, 0x10, 0x10, 0x14, 0x19, 0x0E, 0x08, 0x0C, 0x0C,\n        0x14, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A,\n        0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A,\n        0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x19, 0x0F, 0x17, 0x18,\n        0x1A, 0x17, 0x18, 0x17, 0x19, 0x18, 0x19, 0x16, 0x17, 0x17, 0x14, 0x14,\n        0x18, 0x15, 0x08, 0x0E, 0x17, 0x13, 0x1A, 0x15, 0x19, 0x15, 0x19, 0x16,\n        0x15, 0x15, 0x15, 0x18, 0x1E, 0x17, 0x17, 0x15, 0x14, 0x14, 0x13, 0x14,\n        0x13, 0x0E, 0x14, 0x13, 0x09, 0x0C, 0x13, 0x08, 0x19, 0x13, 0x15, 0x14,\n        0x14, 0x0D, 0x12, 0x0E, 0x13, 0x14, 0x1B, 0x15, 0x14, 0x13, 0x12, 0x12,\n        0x08, 0x08, 0x09, 0x09, 0x15, 0x09, 0x0C, 0x0B, 0x08, 0x08, 0x0D, 0x0E,\n        0x0B, 0x0C, 0x0C, 0x0C, 0x0B, 0x0C, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11,\n        0x0C, 0x0D, 0x0D, 0x0E, 0x0B, 0x0C, 0x16, 0x17, 0x0B, 0x0C, 0x09, 0x1D,\n        0x1D, 0x1D, 0x14, 0x1E, 0x1A, 0x1A, 0x17, 0x19, 0x1A, 0x1A, 0x1B, 0x19,\n        0x19, 0x1B, 0x19, 0x18, 0x17, 0x19, 0x19, 0x19, 0x1A, 0x19, 0x16, 0x17,\n        0x18, 0x1B, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E,\n        0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E,\n        0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E,\n        0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E,\n        0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E,\n        0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E,\n        0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E,\n        0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E,\n        0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E,\n        0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E,\n        0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E,\n};\n\nfor i = 0, (64 * 50) - 1 do\n    if i <= #westernAdvanceWidths then\n        root.Fonts[\"Default\"].AdvanceWidths[i] = westernAdvanceWidths[i];\n    else\n        root.Fonts[\"Default\"].AdvanceWidths[i] = root.Fonts[\"Default\"].AdvanceWidthsEmWidth;\n    end\nend"
  },
  {
    "path": "profiles/rne/game.lua",
    "content": "root.ActiveRenderer = RendererType.OpenGL;\n\nroot.LayerCount = 100;\nroot.GameFeatures = GameFeature.Sc3VirtualMachine | GameFeature.Renderer2D | GameFeature.Scene3D | GameFeature.Input | GameFeature.Audio | GameFeature.Video;\nroot.DesignWidth = 1280;\nroot.DesignHeight = 720;\n\nroot.WindowName = \"ROBOTICS;NOTES ELITE\";\nroot.WindowIconPath = \"games/rne/icondata/icon.png\";\nroot.CursorArrowPath = \"games/rne/icondata/cursor_arrow.png\";\nroot.CursorPointerPath = \"games/rne/icondata/cursor_pointer.png\";\n\nroot.ScreenCaptureCount = 2;\n\nroot.Vm = {\n    StartScript = 4,\n    StartScriptBuffer = 0,\n    GameInstructionSet = InstructionSet.RNE,\n    UseReturnIds = true,\n\n    ScrWorkChaStructSize = 40,\n    ScrWorkChaOffsetStructSize = 10,\n    ScrWorkBgStructSize = 40,\n    ScrWorkBgOffsetStructSize = 10,\n    ScrWorkCaptureStructSize = 20,\n    ScrWorkCaptureOffsetStructSize = 10,\n    ScrWorkCaptureEffectInfoStructSize = 3,\n\n    MaxLinkedBgBuffers = 2\n};\n\ninclude('common/scriptinput.lua')\ninclude('common/scriptvars.lua');\ninclude('rne/config.lua');\ninclude('rne/savedata.lua');\ninclude('rne/tipssystem.lua');\ninclude('rne/vfs.lua');\ninclude('rne/sprites.lua');\ninclude('common/animation.lua');\ninclude('rne/charset.lua');\n--include('rne/font.lua');\ninclude('rne/font-lb.lua');\ninclude('rne/dialogue.lua');\ninclude('rne/hud/saveicon.lua');\ninclude('rne/hud/loadingdisplay.lua');\ninclude('rne/hud/datedisplay.lua');\ninclude('rne/hud/titlemenu.lua');\ninclude('rne/hud/systemmenu.lua');\ninclude('rne/hud/backlogmenu.lua');\ninclude('rne/hud/sysmesboxdisplay.lua');\ninclude('rne/scene3d/scene3d.lua');\ninclude('rne/hud/selectiondisplay.lua');\ninclude('rne/hud/tipsmenu.lua');\ninclude('rne/hud/tipsnotification.lua');\ninclude('rne/gamespecific.lua');"
  },
  {
    "path": "profiles/rne/gamespecific.lua",
    "content": "root.GameSpecific = {\n  Type = GameSpecificType.RNE\n}"
  },
  {
    "path": "profiles/rne/hud/backlogmenu.lua",
    "content": "root.BacklogMenu = {\n    Type = BacklogMenuType.None,\n    DrawType = DrawComponentType.ExtrasScenes,\n    BacklogBackgroundSprite = \"BacklogBackground\",\n    EntryHighlightSprite = \"EntryHighlight\",\n    EntryHighlightPadding = 0.0,\n    VoiceIconSprite = \"VoiceIcon\",\n    ScrollbarTrackSprite = \"ScrollbarTrack\",\n    ScrollbarThumbSprite = \"ScrollbarThumb\",\n    ScrollbarPosition = { X = 1165, Y = 98 },\n    EntriesStart = { X = 163, Y = 85 },\n    RenderingBounds = { X = 87, Y = 83, Width = 1055, Height = 590 },\n    EntryYPadding = 22,\n    FadeInDuration = 0.2,\n    FadeOutDuration = 0.2\n};\n\nroot.Sprites[\"BacklogBackground\"] = {\n    Sheet = \"Backlog\",\n    Bounds = { X = 0, Y = 0, Width = 1280, Height = 720 },\n};\n\nroot.Sprites[\"VoiceIcon\"] = {\n    Sheet = \"Backlog\",\n    Bounds = { X = 1281, Y = 1, Width = 30, Height = 30 },\n};\n\nroot.Sprites[\"EntryHighlight\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 65, Y = 1, Width = 94, Height = 30 }\n};\n\nroot.Sprites[\"ScrollbarThumb\"] = {\n    Sheet = \"Backlog\",\n    Bounds = { X = 1281, Y = 32, Width = 30, Height = 30 },\n};\n\nroot.Sprites[\"ScrollbarTrack\"] = {\n    Sheet = \"Backlog\",\n    Bounds = { X = 1500, Y = 0, Width = 8, Height = 567 },\n};"
  },
  {
    "path": "profiles/rne/hud/datedisplay.lua",
    "content": "local sheet = \"Data\";\nlocal name = \"Date\";\n\nlocal yearNumFirstX = 1525;\nlocal yearNumFirstY = 63;\nlocal yearNumWidth = 25;\nlocal yearNum1Width = 8;\nlocal yearNumHeight = 20;\n\nlocal numFirstX = 1524;\nlocal numFirstY = 33;\nlocal numWidth = 40;\nlocal num1Width = 12;\nlocal numHeight = 28;\n\nlocal weekFirstX = 1785;\nlocal weekFirstY = 63;\nlocal weekSecondX = 1525;\nlocal weekSecondY = 85;\nlocal weekWidth = 73;\nlocal weekFriWidth = 56;\nlocal weekHeight = 20;\n\nroot.DateDisplay = {\n    Type = DateDisplayType.RNE,\n    YearNumSprites = {},\n    MonthNumSprites = {},\n    DayNumSprites = {},\n    WeekSprites = {},\n    BackgroundStartPos = { X = 1088, Y = 73 },\n    BackgroundEndPos = { X = 1088 - 256, Y = 73 },\n    DateStartX = 1167,\n    YearWeekY = 60,\n    MonthDayY = 52,\n    Spacing = 1,\n    FadeInDuration = 0.5,\n    FadeOutDuration = 0.5\n};\n\nfor i = 0, 9 do\n    local yearW;\n    if i == 1 then\n        yearW = yearNum1Width;\n    else\n        yearW = yearNumWidth;\n    end\n\n    root.Sprites[name .. \"YearNum\" .. i] = {\n        Sheet = sheet,\n        Bounds = {\n            X = yearNumFirstX,\n            Y = yearNumFirstY,\n            Width = yearW,\n            Height = yearNumHeight\n        }\n    };\n    root.DateDisplay.YearNumSprites[#root.DateDisplay.YearNumSprites + 1] = name .. \"YearNum\" .. i;\n\n    local numW\n    if i == 1 then\n        numW = num1Width;\n    else\n        numW = numWidth;\n    end\n\n    root.Sprites[name .. \"Num\" .. i] = {\n        Sheet = sheet,\n        Bounds = {\n            X = numFirstX,\n            Y = numFirstY,\n            Width = numW,\n            Height = numHeight\n        }\n    };\n    root.DateDisplay.MonthNumSprites[#root.DateDisplay.MonthNumSprites + 1] = name .. \"Num\" .. i;\n    root.DateDisplay.DayNumSprites[#root.DateDisplay.DayNumSprites + 1] = name .. \"Num\" .. i;\n\n    if i == 1 then\n        yearNumFirstX = yearNumFirstX + yearNum1Width;\n        numFirstX = numFirstX + num1Width;\n    else\n        yearNumFirstX = yearNumFirstX + yearNumWidth;\n        numFirstX = numFirstX + numWidth;\n    end\nend\n\nfor i = 0, 6 do\n    if i > 1 then\n        local weekW;\n        if i == 5 then\n            weekW = weekFriWidth;\n        else\n            weekW = weekWidth;\n        end\n\n        root.Sprites[name .. \"Week\" .. i] = {\n            Sheet = sheet,\n            Bounds = {\n                X = weekSecondX,\n                Y = weekSecondY,\n                Width = weekW,\n                Height = weekHeight\n            }\n        };\n        if i == 5 then\n            weekSecondX = weekSecondX + weekFriWidth;\n        else\n            weekSecondX = weekSecondX + weekWidth;\n         end\n    else\n        root.Sprites[name .. \"Week\" .. i] = {\n            Sheet = sheet,\n            Bounds = {\n                X = weekFirstX,\n                Y = weekFirstY,\n                Width = weekWidth,\n                Height = weekHeight\n            }\n        };\n        weekFirstX = weekFirstX + weekWidth;\n    end\n    root.DateDisplay.WeekSprites[#root.DateDisplay.WeekSprites + 1] = name .. \"Week\" .. i;\nend\n\nroot.Sprites[name .. \"MonthDaySeparator\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 1897,\n        Y = 33,\n        Width = 10,\n        Height = 28\n    }\n};\nroot.DateDisplay.MDSeparatorSprite = name .. \"MonthDaySeparator\";\n\nroot.Sprites[name .. \"DayYearSeparator\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 1758,\n        Y = 63,\n        Width = 8,\n        Height = 20\n    }\n};\nroot.DateDisplay.DYSeparatorSprite = name .. \"DayYearSeparator\";\n\nroot.Sprites[name .. \"OpenBracket\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 1766,\n        Y = 63,\n        Width = 8,\n        Height = 20\n    }\n};\nroot.DateDisplay.OpenBracketSprite = name .. \"OpenBracket\";\n\nroot.Sprites[name .. \"CloseBracket\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 1776,\n        Y = 63,\n        Width = 8,\n        Height = 20\n    }\n};\nroot.DateDisplay.CloseBracketSprite = name .. \"CloseBracket\";\n\nroot.Sprites[name .. \"Background\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 1525,\n        Y = 1,\n        Width = 450,\n        Height = 28\n    }\n};\nroot.DateDisplay.BackgroundSprite = name .. \"Background\";"
  },
  {
    "path": "profiles/rne/hud/loadingdisplay.lua",
    "content": "root.LoadingDisplay = {\n    ResourceLoadBgAnim = \"ResourceLoadingBg\",\n    SaveLoadBgAnim = \"SaveLoadingBg\",\n    LoadingIconAnim = \"LoadingDisc\",\n    LoadingTextAnim = \"LoadingText\",\n    ResourceBgPos = { X = 1074, Y = 611 },\n    SaveBgPos = { X = 1019, Y = 598 },\n    IconPos = { X = 986, Y = 608 },\n    TextPos = { X = 1025, Y = 628 },\n    FadeInDuration = 0.66,\n    FadeOutDuration = 0.33\n};\n\nMakeAnimation({\n    Name = \"ResourceLoadingBg\",\n    Sheet = \"Data\",\n    FirstFrameX = 555,\n    FirstFrameY = 544,\n    FrameWidth = 206,\n    ColWidth = 206,\n    FrameHeight = 58,\n    RowHeight = 60,\n    Frames = 8,\n    Duration = 0.8,\n    Rows = 8,\n    Columns = 1,\n    PrimaryDirection = AnimationDirections.Down\n});\n\nMakeAnimation({\n    Name = \"SaveLoadingBg\",\n    Sheet = \"Data\",\n    FirstFrameX = 388,\n    FirstFrameY = 0,\n    FrameWidth = 277,\n    ColWidth = 279,\n    FrameHeight = 81,\n    RowHeight = 83,\n    Frames = 4,\n    Duration = 0.8,\n    Rows = 2,\n    Columns = 2,\n    PrimaryDirection = AnimationDirections.Down,\n    SecondaryDirection = AnimationDirections.Right\n});\n\nMakeAnimation({\n    Name = \"LoadingDisc\",\n    Sheet = \"Data\",\n    FirstFrameX = 172,\n    FirstFrameY = 1,\n    FrameWidth = 60,\n    ColWidth = 62,\n    FrameHeight = 60,\n    RowHeight = 62,\n    Frames = 3,\n    Duration = 0.8,\n    Rows = 1,\n    Columns = 3,\n    PrimaryDirection = AnimationDirections.Right\n});\nroot.Animations[\"LoadingDisc\"].Frames[#root.Animations[\"LoadingDisc\"].Frames + 1] = \"LoadingDisc1\";\n\nMakeAnimation({\n    Name = \"LoadingText\",\n    Sheet = \"Data\",\n    FirstFrameX = 173,\n    FirstFrameY = 91,\n    FrameWidth = 214,\n    ColWidth = 214,\n    FrameHeight = 21,\n    RowHeight = 23,\n    Frames = 5,\n    Duration = 0.8,\n    Rows = 5,\n    Columns = 1,\n    PrimaryDirection = AnimationDirections.Down\n});"
  },
  {
    "path": "profiles/rne/hud/saveicon.lua",
    "content": "root.SaveIcon = {\n    ForegroundAnimation = \"SaveIcon\",\n    DefaultPosition = { X = 1153, Y = 23 },\n    BackgroundSprite = \"SaveIconBg\",\n    BackgroundOffset = { X = -7, Y = -4 },\n    BackgroundMaxAlpha = 0.5,\n    FadeInDuration = 0.5,\n    FadeOutDuration = 0.25\n};\n\nMakeAnimation({\n    Name = \"SaveIcon\",\n    Sheet = \"Data\",\n    FirstFrameX = 1977,\n    FirstFrameY = 1,\n    FrameWidth = 70,\n    ColWidth = 70,\n    FrameHeight = 70,\n    RowHeight = 72,\n    Frames = 8,\n    Duration = 0.4,\n    Rows = 8,\n    Columns = 1,\n    PrimaryDirection = AnimationDirections.Down\n});\n\nroot.Sprites[\"SaveIconBg\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 1439, Y = 1, Width = 84, Height = 84 }\n};"
  },
  {
    "path": "profiles/rne/hud/selectiondisplay.lua",
    "content": "root.SelectionDisplay = {\n    DrawType = DrawComponentType.Text,\n    SelectionBackgroundSprite = \"SelectionBackground\",\n    PlainSelectionFrameTopLeftSprite = \"PlainSelectionFrameTopLeft\",\n    PlainSelectionFrameTopSideSprite = \"PlainSelectionFrameTopSide\",\n    PlainSelectionFrameTopRightSprite = \"PlainSelectionFrameTopRight\",\n    PlainSelectionFrameLeftSideSprite = \"PlainSelectionFrameLeftSide\",\n    PlainSelectionFrameBottomLeftSprite = \"PlainSelectionFrameBottomLeft\",\n    PlainSelectionFrameRightSideSprite = \"PlainSelectionFrameRightSide\",\n    PlainSelectionFrameBottomRightSprite = \"PlainSelectionFrameBottomRight\",\n    PlainSelectionFrameBottomSideSprite = \"PlainSelectionFrameBottomSide\",\n    PlainSelectionFrameMiddleSprite = \"PlainSelectionFrameMiddle\",\n    SelectionHighlightSprite = \"SelectionHighlight\",\n    SelectionMaxCount = 5,\n    SelectionBackgroundX = 228,\n    SelectionBackgroundY = {277, 277, 196, 129, 95},\n    SelectionYSpacing = 81,\n    PlainSelectionYSpacing = 10,\n    FadeAnimationDurationInOut = 0.2\n};\n\nroot.Sprites[\"SelectionBackground\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 1, Y = 535, Width = 829, Height = 69 }\n};\n\nroot.Sprites[\"PlainSelectionFrameTopLeft\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 0, Y = 0, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameTopSide\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 16, Y = 0, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameTopRight\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 32, Y = 0, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameLeftSide\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 0, Y = 16, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameBottomLeft\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 0, Y = 32, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameRightSide\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 32, Y = 16, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameBottomRight\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 32, Y = 32, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameBottomSide\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 16, Y = 32, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameMiddle\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 16, Y = 16, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"SelectionHighlight\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 65, Y = 33, Width = 94, Height = 30 }\n};\n"
  },
  {
    "path": "profiles/rne/hud/sysmesboxdisplay.lua",
    "content": "local sheet = \"Data\";\nlocal name = \"SysMesBox\";\n\nroot.SysMesBoxDisplay = {\n    Type = SysMesBoxType.RNE,\n    DrawType = DrawComponentType.SystemMessage,\n    LinePositionXFirst = 1622,\n    LinePositionX = 1652,\n    LinePositionMultiplier = 30,\n    LineWidthFirst = 114,\n    LineWidthBase = 54,\n    LineWidthMultiplier = 60,\n    Line1SpriteY = 107,\n    Line2SpriteY = 117,\n    LineSpriteHeight = 8,\n    LineDisplayXBase = 639,\n    Line1DisplayY = 360,\n    Line2DisplayY = 352,\n    BoxDisplayStartCount = 9,\n    BoxHeightBase = 112,\n    BoxHeightMultiplier = 14,\n    BoxWidth = 592,\n    BoxTextFontSize = 28,\n    BoxTopYBase = 360,\n    BoxDisplayX = 344,\n    MessageLabelSpriteXBase = 1574,\n    MessageLabelSpriteY = 131,\n    MessageLabelSpriteHeight = 31,\n    MessageLabelSpriteMultiplier = 12,\n    ButtonYesDisplayXBase = 897,\n    ButtonRightDisplayXBase = 936,\n    ButtonWidth = 105,\n    ButtonYOffset = 29,\n    ButtonYWidthBase = 39,\n    ButtonRightWidthBase = -75,\n    TextDecorationStart = 56,\n    TextDecorationTopYOffset = 36,\n    TextDecorationBottomYOffset = 32,\n    TextFontSize = 26,\n    TextMiddleY = 418,\n    TextX = 370,\n    TextLineHeight = 28,\n    TextMarginY = 14,\n    SpriteMargin = 3,\n    AnimationSpeed = 55,\n    FadeInDuration = 0.33,\n    FadeOutDuration = 0.25\n};\n\nroot.Sprites[name .. \"BoxDecorationTop\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 1383,\n        Y = 107,\n        Width = 592,\n        Height = 8\n    }\n};\nroot.SysMesBoxDisplay.BoxDecorationTop = name .. \"BoxDecorationTop\";\n\nroot.Sprites[name .. \"BoxDecorationBottom\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 1383,\n        Y = 117,\n        Width = 592,\n        Height = 8\n    }\n};\nroot.SysMesBoxDisplay.BoxDecorationBottom = name .. \"BoxDecorationBottom\";\n\nroot.Sprites[name .. \"TextDecoration\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 1383,\n        Y = 127,\n        Width = 592,\n        Height = 2\n    }\n};\nroot.SysMesBoxDisplay.TextDecoration = name .. \"TextDecoration\";\n\nroot.Sprites[name .. \"MessageLabel\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 1383,\n        Y = 127,\n        Width = 592,\n        Height = 2\n    }\n};\nroot.SysMesBoxDisplay.MessageLabel = name .. \"MessageLabel\";\n\nroot.Sprites[name .. \"Line1\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 1622,\n        Y = 107,\n        Width = 114,\n        Height = 8\n    }\n};\nroot.SysMesBoxDisplay.Line1 = name .. \"Line1\";\n\nroot.Sprites[name .. \"Line2\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 1622,\n        Y = 117,\n        Width = 114,\n        Height = 8\n    }\n};\nroot.SysMesBoxDisplay.Line2 = name .. \"Line2\";\n\nroot.Sprites[name .. \"ButtonYes\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 1656,\n        Y = 131,\n        Width = 105,\n        Height = 26\n    }\n};\nroot.SysMesBoxDisplay.ButtonYes = name .. \"ButtonYes\";\n\nroot.Sprites[name .. \"ButtonNo\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 1763,\n        Y = 131,\n        Width = 105,\n        Height = 26\n    }\n};\nroot.SysMesBoxDisplay.ButtonNo = name .. \"ButtonNo\";\n\nroot.Sprites[name .. \"ButtonOK\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 1870,\n        Y = 131,\n        Width = 105,\n        Height = 26\n    }\n};\nroot.SysMesBoxDisplay.ButtonOK = name .. \"ButtonOK\";\n\nroot.Sprites[name .. \"ButtonYesHighlighted\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 1656,\n        Y = 159,\n        Width = 105,\n        Height = 26\n    }\n};\nroot.SysMesBoxDisplay.ButtonYesHighlighted = name .. \"ButtonYesHighlighted\";\n\nroot.Sprites[name .. \"ButtonNoHighlighted\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 1763,\n        Y = 159,\n        Width = 105,\n        Height = 26\n    }\n};\nroot.SysMesBoxDisplay.ButtonNoHighlighted = name .. \"ButtonNoHighlighted\";\n\nroot.Sprites[name .. \"ButtonOKHighlighted\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 1870,\n        Y = 159,\n        Width = 105,\n        Height = 26\n    }\n};\nroot.SysMesBoxDisplay.ButtonOKHighlighted = name .. \"ButtonOKHighlighted\";\n"
  },
  {
    "path": "profiles/rne/hud/systemmenu.lua",
    "content": "root.SystemMenu = {\n    Type = SystemMenuType.RNE,\n    DrawType = DrawComponentType.SystemMenu,\n    Background = {\n        DurationIn = 0.75,\n        DurationOut = 1.0,\n        Sprite = \"SystemMenuBackground\",\n        Seed = 0,\n        Rows = 7,\n        Columns = 12,\n        CenterY = 0.7,\n        VanishingPointX = 0.66,\n        Depth = 2,\n        MaxAngle = 3.141592653 / 2  -- pi / 2\n    },\n    ButtonBackgroundSprite = \"SystemMenuButtonBackground\",\n    SkyBackgroundSprite = \"SystemMenuSkyBackground\",\n    SkyArrowSprite = \"SystemMenuSkyArrow\",\n    SkyTextSprite = \"SystemMenuSkyText\",\n    ButtonPromptsSprite = \"SystemMenuButtonPrompts\",\n    SkyBackgroundBeginX = -80,\n    SkyBackgroundY = 0,\n    SkyTextBeginX = 287,\n    SkyTextY = 69,\n    ButtonBackgroundStartX = 1257,\n    ButtonBackgroundX = 0,\n    ButtonBackgroundY = 681,\n    ButtonBackgroundTargetWidth = 943,\n    ButtonBackgroundSprStartX = 1499,\n    MenuEntriesSprites = {},\n    MenuEntriesHighlightedSprites = {},\n    MenuEntriesNum = 8,\n    MenuEntriesHNum = 8,\n    MenuEntriesX = 0,\n    MenuEntriesXSkew = 20,\n    MenuEntriesXOffset = 100,\n    MenuEntriesFirstY = 220,\n    MenuEntriesYPadding = 50,\n    MenuEntriesTargetWidth = 417,\n    SkyInStartProgress = 0.285,\n    SkyOutStartProgress = 0.715,\n    SkyMoveDurationIn = 0.415,\n    SkyMoveDurationOut = 0.415,\n    EntriesMoveDurationIn = 0.4,\n    EntriesMoveDurationOut = 0.4,\n    HighlightDurationIn = 0.15,\n    HighlightDurationOut = 0.15,\n};\n\nfor i = 0, 7 do\n    root.Sprites[\"SystemMenuEntry\" .. i] = {\n        Sheet = \"Data\",\n        Bounds = {\n            X = 964,\n            Y = 296 + i * 38,\n            Width = root.SystemMenu.MenuEntriesTargetWidth,\n            Height = 26\n        },\n        BaseScale = { X = 1280 / 960, Y = 720 / 544 }\n    };\n    root.SystemMenu.MenuEntriesSprites[#root.SystemMenu.MenuEntriesSprites + 1] = \"SystemMenuEntry\" .. i;\nend\n\nfor i = 0, 7 do\n    root.Sprites[\"SystemMenuEntryHighlighted\" .. i] = {\n        Sheet = \"Data\",\n        Bounds = {\n            X = 964,\n            Y = 2 + i * 38,\n            Width = root.SystemMenu.MenuEntriesTargetWidth,\n            Height = 26\n        },\n        BaseScale = { X = 1280 / 960, Y = 720 / 544 }\n    };\n    root.SystemMenu.MenuEntriesHighlightedSprites[#root.SystemMenu.MenuEntriesHighlightedSprites + 1] = \"SystemMenuEntryHighlighted\" .. i;\nend\n\nroot.Sprites[\"SystemMenuBackground\"] = {\n    Sheet = \"Menu\",\n    Bounds = { X = 0, Y = 1088, Width = 1920, Height = 1080 },\n};\n\nroot.Sprites[\"SystemMenuButtonBackground\"] = {\n    Sheet = \"Menu\",\n    Bounds = { X = root.SystemMenu.ButtonBackgroundSprStartX, Y = 975, Width = 0, Height = 30 },\n    BaseScale = { X = 1280 / 960, Y = 720 / 544 }\n};\n\nroot.Sprites[\"SystemMenuSkyBackground\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 1383, Y = 534, Width = 418, Height = 90 },\n    BaseScale = { X = 1280 / 960, Y = 720 / 544 }\n};\n\nroot.Sprites[\"SystemMenuSkyArrow\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 1802, Y = 534, Width = 70, Height = 90 },\n    BaseScale = { X = 1280 / 960, Y = 720 / 544 }\n};\n\nroot.Sprites[\"SystemMenuSkyText\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 1875, Y = 587, Width = 119, Height = 30 },\n    BaseScale = { X = 1280 / 960, Y = 720 / 544 }\n};\n\nroot.Sprites[\"SystemMenuButtonPrompts\"] = {\n    Sheet = \"Menu\",\n    Bounds = { X = 1231, Y = 528, Width = 400, Height = 21 },\n    BaseScale = { X = 1280 / 960, Y = 720 / 544 }\n};"
  },
  {
    "path": "profiles/rne/hud/tipsmenu.lua",
    "content": "root.TipsMenu = {\n    Type = TipsMenuType.None\n};\n"
  },
  {
    "path": "profiles/rne/hud/tipsnotification.lua",
    "content": "root.TipsNotification = {\n    Type = TipsNotificationType.None,\n};\n"
  },
  {
    "path": "profiles/rne/hud/titlemenu.lua",
    "content": "root.TitleMenu = {\n    Type = TitleMenuType.RNE,\n    DrawType = DrawComponentType.SystemMenu,\n    Background = {\n        DurationIn = 1.0,\n        DurationOut = 1.0,\n        Sprite = \"TitleMenuBackground\",\n        Seed = 0,\n        Rows = 6,\n        Columns = 12,\n        CenterY = 0.7,\n        VanishingPointX = 0.66,\n        Depth = 2,\n        MaxAngle = 3.141592653 / 1.5 -- pi / 1.5\n    },\n    PreTitleAnimDurationIn = 0.4,\n    PreTitleAnimDurationOut = 0.4,\n    PressToStartAnimDurationIn = 0.7,\n    PressToStartAnimDurationOut = 0.7,\n    PressToStartSprite = \"TitleMenuPressToStart\",\n    LineSprite = \"TitleMenuLine\",\n    CopyrightSprite = \"TitleMenuCopyright\",\n    EliteSprite = \"TitleMenuElite\",\n    LogoSprite = \"TitleMenuLogo\",\n    LineWidth = 1280,\n    CopyrightWidth = 370,\n    LogoWidth = 524,\n    EliteHeight = 60,\n    PressToStartX = 329 * (1280 / 960),\n    PressToStartY = 394 * (720 / 544),\n    LineX = 0,\n    LineY = 272 * (720 / 544),\n    CopyrightX = 299 * (1280 / 960),\n    CopyrightY = 489 * (720 / 544),\n    EliteX = 488 * (1280 / 960),\n    EliteY = 275 * (720 / 544),\n    LogoX = 487 * (1280 / 960),\n    LogoY = 216 * (720 / 544),\n    MenuEntriesNum = 0\n};\n\nroot.Sprites[\"TitleMenuBackground\"] = {\n    Sheet = \"TitleBackground\",\n    Bounds = { X = 0, Y = 0, Width = 960, Height = 544 },\n    BaseScale = { X = 1280 / 960, Y = 720 / 544 }\n};\n\nroot.Sprites[\"TitleMenuPressToStart\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 0, Y = 160, Width = 320, Height = 26 },\n    BaseScale = { X = 1280 / 960, Y = 720 / 544 }\n};\n\nroot.Sprites[\"TitleMenuLine\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 0, Y = 1, Width = 1280, Height = 2 },\n};\n\nroot.Sprites[\"TitleMenuCopyright\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 0, Y = 188, Width = 370, Height = 20 },\n    BaseScale = { X = 1280 / 960, Y = 720 / 544 }\n};\n\nroot.Sprites[\"TitleMenuElite\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 0, Y = 66, Width = 280, Height = 60 },\n    BaseScale = { X = 1280 / 960, Y = 720 / 544 }\n};\n\nroot.Sprites[\"TitleMenuLogo\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 0, Y = 5, Width = 524, Height = 60 },\n    BaseScale = { X = 1280 / 960, Y = 720 / 544 }\n};"
  },
  {
    "path": "profiles/rne/savedata.lua",
    "content": "root.SaveData = {\n    Type = SaveDataType.None,\n    SaveFilePath = \"games/rne/savedata/SYSTEM.DAT\",\n};"
  },
  {
    "path": "profiles/rne/scene3d/characters.lua",
    "content": "-- Animation 1 is always the idle animation and thus loops, even if the data says OneShot\n\nroot.Scene3D.Characters = {\n    [1] = {\n        IdleAnimation = 1,\n        Models = {237, 238},\n        Animations = {\n            [1] = { OneShot = true },\n            [2] = { OneShot = true },\n            [3] = { OneShot = true },\n            [4] = { OneShot = true },\n            [5] = { OneShot = true },\n            [6] = { OneShot = true },\n            [7] = { OneShot = true },\n            [8] = { OneShot = true },\n            [9] = { OneShot = true },\n            [10] = { OneShot = true },\n            [11] = { OneShot = true },\n            [12] = { OneShot = true },\n            [13] = { OneShot = true },\n            [14] = { OneShot = true }\n        }\n    },\n    [2] = {\n        IdleAnimation = 1,\n        Models = {239, 240, 241, 242, 243, 244, 245},\n        Animations = {\n            [1] = { LoopStart = 0, LoopEnd = 730 },\n            [2] = { LoopStart = 60, LoopEnd = 270 },\n            [3] = { LoopStart = 210, LoopEnd = 490 },\n            [4] = { LoopStart = 85, LoopEnd = 335 },\n            [5] = { LoopStart = 240, LoopEnd = 520 },\n            [6] = { LoopStart = 150, LoopEnd = 430 },\n            [7] = { LoopStart = 250, LoopEnd = 530 },\n            [8] = { LoopStart = 190, LoopEnd = 470 },\n            [9] = { LoopStart = 170, LoopEnd = 450 },\n            [10] = { LoopStart = 95, LoopEnd = 525 },\n            [11] = { LoopStart = 180, LoopEnd = 460 },\n            [12] = { LoopStart = 210, LoopEnd = 490 },\n            [13] = { LoopStart = 300, LoopEnd = 580 },\n            [14] = { LoopStart = 260, LoopEnd = 540 },\n            [15] = { LoopStart = 150, LoopEnd = 430 },\n            [16] = { LoopStart = 90, LoopEnd = 220 },\n            [17] = { OneShot = true },\n            [18] = { LoopStart = 160, LoopEnd = 440 },\n            [19] = { LoopStart = 130, LoopEnd = 410 },\n            [20] = { LoopStart = 0, LoopEnd = 730 },\n            [21] = { OneShot = true },\n            [22] = { OneShot = true },\n            [23] = { OneShot = true },\n            [24] = { OneShot = true },\n            [25] = { OneShot = true },\n            [26] = { OneShot = true },\n            [27] = { OneShot = true },\n            [28] = { OneShot = true },\n            [29] = { OneShot = true },\n            [30] = { OneShot = true },\n            [31] = { LoopStart = 210, LoopEnd = 490 },\n            [32] = { LoopStart = 170, LoopEnd = 450 },\n            [33] = { LoopStart = 0, LoopEnd = 570 },\n            [34] = { LoopStart = 0, LoopEnd = 730 }\n        }\n    },\n    [3] = {\n        IdleAnimation = 1,\n        Models = {246, 247, 248, 249},\n        Animations = {\n            [1] = { LoopStart = 0, LoopEnd = 775 },\n            [2] = { LoopStart = 169, LoopEnd = 595 },\n            [3] = { LoopStart = 200, LoopEnd = 430 },\n            [4] = { LoopStart = 143, LoopEnd = 463 },\n            [5] = { LoopStart = 150, LoopEnd = 470 },\n            [6] = { LoopStart = 205, LoopEnd = 525 },\n            [7] = { LoopStart = 180, LoopEnd = 500 },\n            [8] = { LoopStart = 190, LoopEnd = 500 },\n            [9] = { LoopStart = 180, LoopEnd = 500 },\n            [10] = { LoopStart = 175, LoopEnd = 495 },\n            [11] = { LoopStart = 180, LoopEnd = 500 },\n            [12] = { LoopStart = 185, LoopEnd = 505 },\n            [13] = { LoopStart = 0, LoopEnd = 50 },\n            [14] = { LoopStart = 165, LoopEnd = 485 },\n            [15] = { LoopStart = 205, LoopEnd = 525 },\n            [16] = { LoopStart = 150, LoopEnd = 240 },\n            [17] = { OneShot = true },\n            [18] = { LoopStart = 175, LoopEnd = 395 },\n            [19] = { LoopStart = 180, LoopEnd = 420 },\n            [20] = { LoopStart = 210, LoopEnd = 530 },\n            [21] = { LoopStart = 0, LoopEnd = 730 },\n            [22] = { OneShot = true },\n            [23] = { LoopStart = 150, LoopEnd = 470 }\n        }\n    },\n    [4] = {\n        IdleAnimation = 1,\n        Models = {250, 251, 252, 253},\n        Animations = {\n            [1] = { LoopStart = 0, LoopEnd = 600 },\n            [2] = { LoopStart = 175, LoopEnd = 455 },\n            [3] = { LoopStart = 290, LoopEnd = 570 },\n            [4] = { LoopStart = 290, LoopEnd = 570 },\n            [5] = { LoopStart = 190, LoopEnd = 470 },\n            [6] = { LoopStart = 400, LoopEnd = 700 },\n            [7] = { LoopStart = 170, LoopEnd = 450 },\n            [8] = { LoopStart = 225, LoopEnd = 505 },\n            [9] = { LoopStart = 225, LoopEnd = 505 },\n            [10] = { LoopStart = 185, LoopEnd = 485 },\n            [11] = { LoopStart = 490, LoopEnd = 790 },\n            [12] = { LoopStart = 0, LoopEnd = 600 },\n            [13] = { LoopStart = 0, LoopEnd = 600 },\n            [14] = { LoopStart = 180, LoopEnd = 460 }\n        }\n    },\n    [5] = {\n        IdleAnimation = 1,\n        Models = {254, 255},\n        Animations = {\n            [1] = { LoopStart = 0, LoopEnd = 690 },\n            [2] = { LoopStart = 265, LoopEnd = 585 },\n            [3] = { LoopStart = 210, LoopEnd = 530 },\n            [4] = { LoopStart = 290, LoopEnd = 630 },\n            [5] = { LoopStart = 280, LoopEnd = 360 },\n            [6] = { LoopStart = 220, LoopEnd = 540 },\n            [7] = { LoopStart = 120, LoopEnd = 440 },\n            [8] = { LoopStart = 350, LoopEnd = 670 },\n            [9] = { LoopStart = 220, LoopEnd = 540 },\n            [10] = { LoopStart = 260, LoopEnd = 580 },\n            [11] = { LoopStart = 400, LoopEnd = 700 },\n            [12] = { LoopStart = 245, LoopEnd = 575 },\n            [13] = { LoopStart = 205, LoopEnd = 525 },\n            [14] = { LoopStart = 225, LoopEnd = 545 },\n            [15] = { LoopStart = 315, LoopEnd = 635 },\n            [16] = { LoopStart = 210, LoopEnd = 530 },\n            [17] = { OneShot = true },\n            [18] = { OneShot = true },\n            [19] = { LoopStart = 0, LoopEnd = 320 },\n            [20] = { OneShot = true },\n            [21] = { LoopStart = 0, LoopEnd = 80 },\n            [22] = { LoopStart = 0, LoopEnd = 80 },\n            [23] = { LoopStart = 0, LoopEnd = 120 },\n            [24] = { LoopStart = 0, LoopEnd = 80 },\n            [25] = { LoopStart = 310, LoopEnd = 430 },\n            [26] = { LoopStart = 210, LoopEnd = 330 },\n            [27] = { OneShot = true },\n            [28] = { LoopStart = 90, LoopEnd = 330 },\n            [29] = { LoopStart = 15, LoopEnd = 260 },\n            [30] = { LoopStart = 90, LoopEnd = 210 },\n            [31] = { LoopStart = 0, LoopEnd = 80 }\n        }\n    },\n    [6] = {\n        IdleAnimation = 1,\n        Models = {256, 257, 258, 259, 260, 261},\n        Animations = {\n            [1] = { LoopStart = 90, LoopEnd = 705 },\n            [2] = { LoopStart = 335, LoopEnd = 815 },\n            [3] = { LoopStart = 240, LoopEnd = 720 },\n            [4] = { LoopStart = 220, LoopEnd = 700 },\n            [5] = { LoopStart = 220, LoopEnd = 700 },\n            [6] = { LoopStart = 255, LoopEnd = 735 },\n            [7] = { LoopStart = 310, LoopEnd = 790 },\n            [8] = { LoopStart = 265, LoopEnd = 745 },\n            [9] = { LoopStart = 260, LoopEnd = 740 },\n            [10] = { LoopStart = 420, LoopEnd = 840 },\n            [11] = { LoopStart = 370, LoopEnd = 790 },\n            [12] = { LoopStart = 150, LoopEnd = 450 },\n            [13] = { LoopStart = 240, LoopEnd = 720 },\n            [14] = { LoopStart = 240, LoopEnd = 720 },\n            [15] = { LoopStart = 255, LoopEnd = 735 },\n            [16] = { OneShot = true },\n            [17] = { OneShot = true },\n            [18] = { OneShot = true }\n        }\n    },\n    [7] = {\n        IdleAnimation = 1,\n        Models = {262, 263, 264, 265},\n        Animations = {\n            [1] = { OneShot = true },\n            [2] = { LoopStart = 280, LoopEnd = 640 },\n            [3] = { OneShot = true },\n            [4] = { OneShot = true },\n            [5] = { OneShot = true },\n            [6] = { OneShot = true },\n            [7] = { OneShot = true },\n            [8] = { OneShot = true }\n        }\n    },\n    [8] = {\n        IdleAnimation = 1,\n        Models = {266, 267},\n        Animations = {\n            [1] = { LoopStart = 0, LoopEnd = 450 },\n            [2] = { OneShot = true },\n            [3] = { OneShot = true },\n            [4] = { OneShot = true },\n            [5] = { OneShot = true },\n            [6] = { OneShot = true },\n            [7] = { OneShot = true },\n            [8] = { OneShot = true },\n            [9] = { OneShot = true },\n            [10] = { OneShot = true },\n            [11] = { LoopStart = 45, LoopEnd = 260 }\n        }\n    },\n    [9] = {\n        IdleAnimation = 1,\n        Models = {268, 269},\n        Animations = {\n            [1] = { OneShot = true },\n            [2] = { LoopStart = 225, LoopEnd = 645 },\n            [3] = { OneShot = true },\n            [4] = { OneShot = true },\n            [5] = { OneShot = true },\n            [6] = { OneShot = true },\n            [7] = { OneShot = true },\n            [8] = { OneShot = true },\n            [9] = { OneShot = true },\n            [10] = { OneShot = true },\n            [11] = { LoopStart = 60, LoopEnd = 240 }\n        }\n    },\n    [10] = {\n        IdleAnimation = 1,\n        Models = {270, 271, 272},\n        Animations = {\n            [1] = { OneShot = true },\n            [2] = { LoopStart = 175, LoopEnd = 455 },\n            [3] = { OneShot = true },\n            [4] = { OneShot = true },\n            [5] = { OneShot = true },\n            [6] = { OneShot = true },\n            [7] = { OneShot = true },\n            [8] = { OneShot = true },\n            [9] = { OneShot = true },\n            [10] = { OneShot = true },\n            [11] = { OneShot = true },\n            [12] = { OneShot = true }\n        }\n    },\n    [11] = {\n        IdleAnimation = 1,\n        Models = {273},\n        Animations = {\n            [1] = { OneShot = true },\n            [2] = { LoopStart = 260, LoopEnd = 720 },\n            [3] = { OneShot = true },\n            [4] = { OneShot = true },\n            [5] = { OneShot = true },\n            [6] = { OneShot = true },\n            [7] = { OneShot = true },\n            [8] = { OneShot = true },\n            [9] = { OneShot = true },\n            [10] = { OneShot = true },\n            [11] = { LoopStart = 40, LoopEnd = 220 }\n        }\n    },\n    [12] = {\n        IdleAnimation = 1,\n        Models = {274},\n        Animations = {\n            [1] = { LoopStart = 170, LoopEnd = 540 },\n            [2] = { OneShot = true },\n            [3] = { OneShot = true },\n            [4] = { OneShot = true },\n            [5] = { OneShot = true },\n            [6] = { OneShot = true },\n            [7] = { OneShot = true },\n            [8] = { OneShot = true },\n            [9] = { OneShot = true },\n            [10] = { OneShot = true },\n            [11] = { LoopStart = 60, LoopEnd = 240 },\n            [12] = { OneShot = true },\n            [13] = { OneShot = true },\n            [14] = { OneShot = true },\n            [15] = { OneShot = true },\n            [16] = { OneShot = true },\n            [17] = { OneShot = true },\n            [18] = { OneShot = true },\n            [19] = { OneShot = true },\n            [20] = { OneShot = true },\n            [21] = { LoopStart = 170, LoopEnd = 540 },\n            [22] = { LoopStart = 170, LoopEnd = 540 },\n            [23] = { LoopStart = 170, LoopEnd = 540 },\n            [24] = { LoopStart = 170, LoopEnd = 540 }\n        }\n    },\n    [13] = {\n        IdleAnimation = 1,\n        Models = {275},\n        Animations = {\n            [1] = { OneShot = true },\n            [2] = { OneShot = true },\n            [3] = { OneShot = true },\n            [4] = { OneShot = true },\n            [5] = { OneShot = true },\n            [6] = { OneShot = true },\n            [7] = { OneShot = true },\n            [8] = { OneShot = true },\n            [9] = { OneShot = true },\n            [10] = { OneShot = true },\n            [11] = { LoopStart = 60, LoopEnd = 210 },\n            [12] = { OneShot = true },\n            [13] = { OneShot = true },\n            [14] = { OneShot = true },\n            [15] = { OneShot = true },\n            [16] = { OneShot = true },\n            [17] = { OneShot = true },\n            [18] = { OneShot = true },\n            [19] = { OneShot = true },\n            [20] = { OneShot = true },\n            [21] = { OneShot = true },\n            [22] = { OneShot = true },\n            [23] = { OneShot = true },\n            [24] = { OneShot = true }\n        }\n    },\n    [14] = {\n        IdleAnimation = 1,\n        Models = {276},\n        Animations = {\n            [1] = { OneShot = true },\n            [2] = { OneShot = true },\n            [3] = { OneShot = true },\n            [4] = { OneShot = true },\n            [5] = { OneShot = true },\n            [6] = { OneShot = true },\n            [7] = { OneShot = true },\n            [8] = { OneShot = true },\n            [9] = { OneShot = true },\n            [10] = { OneShot = true },\n            [11] = { OneShot = true }\n        }\n    },\n    [15] = {\n        IdleAnimation = 1,\n        Models = {277},\n        Animations = {\n            [1] = { OneShot = true },\n            [2] = { OneShot = true },\n            [3] = { OneShot = true },\n            [4] = { OneShot = true },\n            [5] = { OneShot = true },\n            [6] = { OneShot = true },\n            [7] = { OneShot = true },\n            [8] = { OneShot = true },\n            [9] = { OneShot = true },\n            [10] = { OneShot = true },\n            [11] = { LoopStart = 80, LoopEnd = 264 }\n        }\n    },\n    [16] = {\n        IdleAnimation = 1,\n        Models = {278},\n        Animations = {\n            [1] = { OneShot = true },\n            [2] = { OneShot = true },\n            [3] = { OneShot = true },\n            [4] = { OneShot = true },\n            [5] = { OneShot = true },\n            [6] = { OneShot = true },\n            [7] = { OneShot = true },\n            [8] = { OneShot = true },\n            [9] = { OneShot = true },\n            [10] = { OneShot = true },\n            [11] = { OneShot = true }\n        }\n    },\n    [17] = {\n        IdleAnimation = 1,\n        Models = {279, 280, 281, 282},\n        Animations = {\n            [1] = { OneShot = true },\n            [2] = { LoopStart = 215, LoopEnd = 535 },\n            [3] = { LoopStart = 265, LoopEnd = 585 },\n            [4] = { LoopStart = 300, LoopEnd = 620 },\n            [5] = { LoopStart = 225, LoopEnd = 545 },\n            [6] = { LoopStart = 180, LoopEnd = 500 },\n            [7] = { LoopStart = 215, LoopEnd = 535 },\n            [8] = { LoopStart = 280, LoopEnd = 600 },\n            [9] = { LoopStart = 230, LoopEnd = 550 },\n            [10] = { LoopStart = 330, LoopEnd = 650 },\n            [11] = { LoopStart = 260, LoopEnd = 580 },\n            [12] = { LoopStart = 270, LoopEnd = 590 },\n            [13] = { LoopStart = 250, LoopEnd = 570 },\n            [14] = { OneShot = true },\n            [15] = { OneShot = true }\n        }\n    }\n};"
  },
  {
    "path": "profiles/rne/scene3d/scene3d.lua",
    "content": "root.Scene3D = {\n    Version = LKMVersion.RNE,\n    MaxRenderables = 8,\n    DefaultCamera = {\n        Position = {\n            X = 0,\n            Y = 12.5,\n            Z = 23\n        },\n        Target = {\n            X = 0,\n            Y = 12.5,\n            Z = 0\n        },\n        Fov = 3.141592653 / 8 -- pi / 8\n    },\n    AnimationDesignFrameRate = 30,\n    -- Animation 22 of Model 273 (c11_010) has flat-out broken data (and isn't used by the game anyway)\n    AnimationParseBlacklist = {\n        [273] = {22}\n    }\n};\n\ninclude('rne/scene3d/characters.lua');"
  },
  {
    "path": "profiles/rne/sprites.lua",
    "content": "root.SpriteSheets = {\n    [\"Data\"] = {\n        Path = { Mount = \"system\", Id = 9 },\n        DesignWidth = 2048,\n        DesignHeight = 1024\n    },\n    [\"Font\"] = {\n        Path = { Mount = \"system\", Id = 12 },\n        DesignWidth = 2048,\n        DesignHeight = 1600\n    },\n    [\"Menu\"] = {\n        Path = { Mount = \"system\", Id = 8 },\n        DesignWidth = 2048,\n        DesignHeight = 1024\n    },\n    [\"Title\"] = {\n        Path = { Mount = \"system\", Id = 31 },\n        DesignWidth = 2048,\n        DesignHeight = 1024\n    },\n    [\"TitleBackground\"] = {\n        Path = { Mount = \"bg\", Id = 540 },\n        DesignWidth = 960,\n        DesignHeight = 544\n    },\n    [\"Backlog\"] = {\n        Path = {Mount = \"system\", Id = 6 },\n        DesignWidth = 2048,\n        DesignHeight = 720\n    }\n};\n\nroot.Sprites = {};"
  },
  {
    "path": "profiles/rne/tipssystem.lua",
    "content": "root.TipsSystem = {\n    Type = TipsSystemType.None,\n};"
  },
  {
    "path": "profiles/rne/vfs.lua",
    "content": "root.Vfs = {\n    Mounts = {\n        [\"script\"] = {\"games/rne/gamedata/script.cls\"},\n        [\"system\"] = {\"games/rne/gamedata/system.cpk\"},\n        [\"bgm\"] = {\"games/rne/gamedata/bgm.cpk\"},\n        [\"se\"] = {\"games/rne/gamedata/se.cpk\"},\n        [\"sysse\"] = {\"games/rne/gamedata/sysse.dat\"},\n        [\"voice\"] = {\"games/rne/gamedata/voice.cpk\"},\n        [\"model\"] = {\"games/rne/gamedata/model.cpk\"},\n        [\"bg\"] = {\"games/rne/gamedata/bg.cpk\"},\n        [\"mask\"] = {\"games/rne/gamedata/mask.cpk\"}\n    }\n};"
  },
  {
    "path": "profiles/sgps3/charset.lua",
    "content": "-- WARNING, 0-INDEXED ARRAYS AHEAD\n\nroot.Charset = {\n    Flags = {}\n};\n\nfor i = 0, (64 * 37) - 1 do root.Charset.Flags[i] = 0; end\n\nlocal spaces = {[0]=0, 63};\nfor i = 0, #spaces do\n    root.Charset.Flags[spaces[i]] = root.Charset.Flags[spaces[i]] | CharacterTypeFlags.Space;\nend\n\n-- 、 。 ． ， ？ ！ 〜 ” ー ） 〕 ］ ｝ 〉 》 」 』 】☆ ★ ♪ 々 ぁ ぃ ぅ ぇ\n-- ぉ っ ゃ ゅ ょ ァ ィ ゥ ェ ォ ッ ャ ュ ョ –\nlocal wordEndingPuncts = {\n    [0]=0x00BE, 0x00BF, 0x00C1,\n    0x00C0, 0x00C4, 0x00C5,\n    0x00E4, 0x00CB, 0x00E5,\n    0x00CD, 0x00CF, 0x00D1,\n    0x00D3, 0x00D5, 0x00D7,\n    0x00D9, 0x00DB, 0x00DD,\n    0x01A5, 0x01A6, 0x00E6,\n    0x0187, 0x00E8, 0x00E9,\n    0x00EA, 0x00EB, 0x00EC,\n    0x00ED, 0x00EE, 0x00EF,\n    0x00F0, 0x00F2, 0x00F3,\n    0x00F4, 0x00F5, 0x00F6,\n    0x00F7, 0x00F8, 0x00F9,\n    0x00FA, 0x0113\n};\nfor i = 0, #wordEndingPuncts do\n    root.Charset.Flags[wordEndingPuncts[i]] = root.Charset.Flags[wordEndingPuncts[i]] | CharacterTypeFlags.WordEndingPunct;\nend\n\n-- space(63) space(0) “ （ 〔 ［ ｛ 〈 《 「\nlocal wordStartingPuncts = {\n    [0]=63, 0, 0x00CA,\n    0x00CC, 0x00CE, 0x00D0,\n    0x00D2, 0x00D4, 0x00D6,\n    0x00D8, 0x00DA, 0x00DC\n};\nfor i = 0, #wordStartingPuncts do\n    root.Charset.Flags[wordStartingPuncts[i]] = root.Charset.Flags[wordStartingPuncts[i]] | CharacterTypeFlags.WordStartingPunct;\nend\n"
  },
  {
    "path": "profiles/sgps3/config.lua",
    "content": "root.Language = \"Japanese\";\nroot.ResolutionWidth = 1280;\nroot.ResolutionHeight = 720;\nroot.Fullscreen = false;\nroot.DateFormat = DateFormatType.YMD;"
  },
  {
    "path": "profiles/sgps3/dialogue.lua",
    "content": "root.Sprites[\"ADVBox\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 767, Y = 806, Width = 1280, Height = 216 },\n};\n\nroot.Sprites[\"DialogueWaitIcon\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 1, Y = 97, Width = 32, Height = 32 }\n};\n\nroot.Dialogue = {\n    REVBounds = { X = 0, Y = 0, Width = 960, Height = 400 },\n    REVNameFontSize = 24,\n    REVColor = 0,\n    REVNameColor = 24,\n    REVNameOffset = 34,\n    REVNameLocation = REVNameLocationType.TopLeft,\n    REVOutlineMode = 2,\n    REVNameOutlineMode = 2,\n    NVLBounds = { X = 125, Y = 85, Width = 1024, Height = 400 },\n    ADVBounds = { X = 161, Y = 525, Width = 960, Height = 180 },\n    ADVBoxSprite = \"ADVBox\",\n    ADVBoxPos = { X = 0, Y = 504 },\n    FadeOutDuration = 0.33,\n    FadeInDuration = 0.33,\n    TextFadeInDuration = 0.33,\n    TextFadeOutDuration = 0.33,\n    DialogueBoxCurrentType = DialogueBoxType.Plain,\n    NVLBoxMaxOpacity = 0.55,\n\n    ADVNameAlignment = TextAlignment.Left,\n    ADVNameFontSize = 22,\n    ADVNamePos = { X = 600, Y = 672 },\n\n    NametagCurrentType = NametagType.TwoPiece,\n    NametagPosition = { X = 400, Y = 680 },\n    NametagLeftSprite = \"NametagLeftSprite\",\n    NametagRightSprite = \"NametagRightSprite\",\n\n    WaitIconCurrentType = WaitIconType.Rotate,\n    WaitIconSprite = \"DialogueWaitIcon\",\n    WaitIconOffset = { X = 4, Y = 4 },\n    WaitIconAnimationDuration = 3.2,\n    DialogueFont = \"Default\",\n    SetFontSizeRatio = 1000.0,\n    DefaultFontSize = 32,\n    RubyFontSize = 14,\n    RubyYOffset = -18,\n    ColorTable = {\n        {0xFFFFFF, 0x000000}, {0x5080FF, 0x000000},\n        {0xFF7080, 0x000000}, {0xFFA0F8, 0x000000},\n        {0x46FF80, 0x000000}, {0x90FFFF, 0x000000},\n        {0xFFFF70, 0x000000}, {0x80FFC0, 0x000000},\n        {0xFFB080, 0x000000}, {0xB080FF, 0x000000},\n        {0x000000, 0x808080}, {0x000000, 0x5080FF},\n        {0x000000, 0xFF7080}, {0x000000, 0xFFA0F8},\n        {0x000000, 0x268840}, {0x000000, 0x409999},\n        {0x000000, 0x888830}, {0x000000, 0x80FFC0},\n        {0x000000, 0xFFB080}, {0x000000, 0xB080FF},\n        {0xD0D0D0, 0x000000}, {0xD0D0FF, 0x000000},\n        {0xFFD0D0, 0x000000}, {0xFFD0FF, 0x000000},\n        {0xD0FFD0, 0x000000}, {0xD0FFFF, 0x000000},\n        {0xFFFFD0, 0x000000}, {0xE8FFD0, 0x000000},\n        {0xFFE8D0, 0x000000}, {0xD0E8FF, 0x000000},\n        {0xFFFFFF, 0x808080}, {0xFFFFFF, 0x5080FF},\n        {0xFFFFFF, 0xFF7080}, {0xFFFFFF, 0xFFA0F8},\n        {0xFFFFFF, 0x46FF80}, {0xFFFFFF, 0x90FFFF},\n        {0xFFFFFF, 0xFFFF70}, {0xFFFFFF, 0x80FFC0},\n        {0xFFFFFF, 0xFFB080}, {0xFFFFFF, 0xB080FF},\n        {0xFFEEEE, 0x000000}, {0xFFCCCC, 0x000000},\n        {0xFFAAAA, 0x000000}, {0xFF9999, 0x000000},\n        {0xFF8888, 0x000000}, {0xFFFF00, 0x000000},\n        {0xFEF000, 0x000000}, {0xFF7777, 0x000000},\n        {0xFF6666, 0x000000}, {0xFF5555, 0x000000},\n        {0xFF4444, 0x000000}, {0xFF3333, 0x000000},\n        {0xFF2222, 0x000000}, {0xFF0000, 0x000000},\n        {0xDD0000, 0x000000}, {0xBB0000, 0x000000},\n        {0xB00000, 0x000000}, {0xAA0000, 0x000000},\n        {0x950000, 0x000000}, {0x808080, 0x000000},\n        {0xAAAAAA, 0x000000}, {0xAAC1C9, 0x000000},\n        {0x000000, 0x000000}, {0x000000, 0x000000},\n        {0x000000, 0x000000}, {0x000000, 0x000000},\n        {0x000000, 0x000000}, {0xF80B0B, 0x000000},\n        {0xF8910B, 0x000000}, {0x33F12A, 0x000000}\n    },\n    MaxPageSize = 2000,\n    PageCount = 3,\n    ColorTagIsUint8 = true\n};\n\nroot.Sprites[\"NametagLeftSprite\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 768, Y = 787, Width = 195, Height = 11 }\n};\n\nroot.Sprites[\"NametagRightSprite\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 964, Y = 787, Width = 200, Height = 11 }\n};\n"
  },
  {
    "path": "profiles/sgps3/font.lua",
    "content": "-- WARNING, 0-INDEXED ARRAYS AHEAD\n\nroot.Fonts = {\n    [\"Default\"] = {\n        Type = FontType.Basic,\n        Sheet = \"Font\",\n        Columns = 64,\n        Rows = 14,\n        AdvanceWidths = {},\n        AdvanceWidthsEmWidth = 32,\n        LineSpacing = 22,\n    }\n};\n\nlocal westernAdvanceWidths = {\n    [0]=0x20, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,\n        0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,\n        0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,\n        0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,\n        0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,\n        0x11, 0x11, 0x11, 0x0C, 0x11, 0x11, 0x0F, 0x11, 0x11, 0x11, 0x11, 0x11,\n        0x11, 0x11, 0x13, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,\n        0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,\n        0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,\n        0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,\n        0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x14, 0x10, 0x14, 0x14,\n        0x15, 0x14, 0x14, 0x14, 0x14, 0x14, 0x1A, 0x15, 0x17, 0x18, 0x15, 0x14,\n        0x19, 0x17, 0x0B, 0x14, 0x17, 0x15, 0x1E, 0x18, 0x1A, 0x16, 0x1B, 0x15,\n        0x14, 0x16, 0x17, 0x1A, 0x1F, 0x19, 0x1A, 0x14, 0x13, 0x14, 0x13, 0x15,\n        0x13, 0x11, 0x14, 0x14, 0x0A, 0x0C, 0x14, 0x0A, 0x1E, 0x14, 0x15, 0x14,\n        0x15, 0x0E, 0x12, 0x10, 0x14, 0x15, 0x1F, 0x15, 0x15, 0x11, 0x11, 0x11,\n        0x09, 0x09, 0x0A, 0x0A, 0x12, 0x0A, 0x0A, 0x09, 0x07, 0x07, 0x0E, 0x0E,\n        0x0B, 0x0B, 0x0C, 0x0C, 0x0C, 0x0C, 0x0B, 0x0B, 0x0E, 0x0D, 0x11, 0x10,\n        0x0C, 0x0E, 0x0C, 0x0E, 0x0A, 0x0C, 0x18, 0x17, 0x0A, 0x0A, 0x0E, 0x1E,\n        0x1C, 0x1C, 0x16, 0x1E, 0x19, 0x18, 0x14, 0x18, 0x1B, 0x1B, 0x1C, 0x1A,\n        0x18, 0x1B, 0x1A, 0x16, 0x18, 0x1C, 0x1C, 0x19, 0x1B, 0x1C, 0x16, 0x18,\n        0x19, 0x1A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n        0x20, 0x20, 0x20, 0x20, 0x20, 0x10, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n};\n\nfor i = 0, (64 * 14) - 1 do\n    if i <= #westernAdvanceWidths then\n        root.Fonts[\"Default\"].AdvanceWidths[i] = westernAdvanceWidths[i];\n    else\n        root.Fonts[\"Default\"].AdvanceWidths[i] = root.Fonts[\"Default\"].AdvanceWidthsEmWidth;\n    end\nend"
  },
  {
    "path": "profiles/sgps3/game.lua",
    "content": "root.ActiveRenderer = RendererType.OpenGL;\n\nroot.LayerCount = 100;\nroot.GameFeatures = GameFeature.Sc3VirtualMachine | GameFeature.Renderer2D | GameFeature.Input | GameFeature.Audio | GameFeature.Video;\nroot.DesignWidth = 1280;\nroot.DesignHeight = 720;\n\nroot.WindowName = \"STEINS;GATE\";\nroot.WindowIconPath = \"games/sgps3/icondata/icon.png\";\nroot.CursorArrowPath = \"games/sgps3/icondata/cursor_arrow.png\";\nroot.CursorPointerPath = \"games/sgps3/icondata/cursor_pointer.png\";\n\nroot.CharaIsMvl = false;\nroot.LayFileBigEndian = true;\nroot.LayFileTexXMultiplier = 2048;\nroot.LayFileTexYMultiplier = 1024;\n\nroot.Vm = {\n    StartScript = 2,\n    StartScriptBuffer = 0,\n    GameInstructionSet = InstructionSet.SGPS3,\n    UseReturnIds = false,\n\n    ScrWorkChaStructSize = 20,\n    ScrWorkChaOffsetStructSize = 10,\n    ScrWorkBgStructSize = 20,\n    ScrWorkBgOffsetStructSize = 10,\n};\n\ninclude('common/scriptinput.lua');\ninclude('common/scriptvars.lua');\ninclude('sgps3/config.lua');\ninclude('sgps3/scriptvars.lua');\ninclude('sgps3/savedata.lua');\ninclude('sgps3/tipssystem.lua');\ninclude('sgps3/vfs.lua');\ninclude('sgps3/sprites.lua');\ninclude('common/animation.lua');\ninclude('sgps3/charset.lua');\ninclude('sgps3/font.lua');\ninclude('sgps3/dialogue.lua');\ninclude('sgps3/hud/saveicon.lua');\ninclude('sgps3/hud/loadingdisplay.lua');\ninclude('sgps3/hud/datedisplay.lua');\ninclude('sgps3/hud/titlemenu.lua');\n--include('sgps3/hud/systemmenu.lua');\ninclude('sgps3/hud/backlogmenu.lua');\ninclude('sgps3/hud/sysmesboxdisplay.lua');\ninclude('sgps3/hud/selectiondisplay.lua');\ninclude('sgps3/hud/tipsmenu.lua');\ninclude('sgps3/hud/tipsnotification.lua');\n"
  },
  {
    "path": "profiles/sgps3/hud/backlogmenu.lua",
    "content": "root.BacklogMenu = {\n    Type = BacklogMenuType.None,\n    DrawType = DrawComponentType.ExtrasScenes,\n    BacklogBackgroundSprite = \"BacklogBackground\",\n    EntryHighlightSprite = \"EntryHighlight\",\n    EntryHighlightPadding = 0.0,\n    VoiceIconSprite = \"VoiceIcon\",\n    ScrollbarTrackSprite = \"ScrollbarTrack\",\n    ScrollbarThumbSprite = \"ScrollbarThumb\",\n    ScrollbarPosition = { X = 1165, Y = 98 },\n    EntriesStart = { X = 163, Y = 85 },\n    RenderingBounds = { X = 87, Y = 83, Width = 1055, Height = 590 },\n    EntryYPadding = 22,\n    FadeInDuration = 0.2,\n    FadeOutDuration = 0.2\n};\n\nroot.Sprites[\"BacklogBackground\"] = {\n    Sheet = \"Backlog\",\n    Bounds = { X = 0, Y = 0, Width = 1280, Height = 720 },\n};\n\nroot.Sprites[\"VoiceIcon\"] = {\n    Sheet = \"Backlog\",\n    Bounds = { X = 1281, Y = 1, Width = 30, Height = 30 },\n};\n\nroot.Sprites[\"EntryHighlight\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 65, Y = 1, Width = 94, Height = 30 }\n};\n\nroot.Sprites[\"ScrollbarThumb\"] = {\n    Sheet = \"Backlog\",\n    Bounds = { X = 1281, Y = 32, Width = 30, Height = 30 },\n};\n\nroot.Sprites[\"ScrollbarTrack\"] = {\n    Sheet = \"Backlog\",\n    Bounds = { X = 1500, Y = 0, Width = 8, Height = 567 },\n};"
  },
  {
    "path": "profiles/sgps3/hud/datedisplay.lua",
    "content": "local sheet = \"Data\";\nlocal name = \"Date\";\n\nlocal yearNumFirstX = 1525;\nlocal yearNumFirstY = 63;\nlocal yearNumWidth = 25;\nlocal yearNum1Width = 8;\nlocal yearNumHeight = 20;\n\nlocal numFirstX = 1524;\nlocal numFirstY = 33;\nlocal numWidth = 40;\nlocal num1Width = 12;\nlocal numHeight = 28;\n\nlocal weekFirstX = 1785;\nlocal weekFirstY = 63;\nlocal weekSecondX = 1525;\nlocal weekSecondY = 85;\nlocal weekWidth = 73;\nlocal weekFriWidth = 56;\nlocal weekHeight = 20;\n\nroot.DateDisplay = {\n    Type = DateDisplayType.RNE,\n    YearNumSprites = {},\n    MonthNumSprites = {},\n    DayNumSprites = {},\n    WeekSprites = {},\n    BackgroundStartPos = { X = 1088, Y = 73 },\n    BackgroundEndPos = { X = 1088 - 256, Y = 73 },\n    DateStartX = 1167,\n    YearWeekY = 60,\n    MonthDayY = 52,\n    Spacing = 1,\n    FadeInDuration = 0.5,\n    FadeOutDuration = 0.5\n};\n\nfor i = 0, 9 do\n    local yearW;\n    if i == 1 then\n        yearW = yearNum1Width;\n    else\n        yearW = yearNumWidth;\n    end\n\n    root.Sprites[name .. \"YearNum\" .. i] = {\n        Sheet = sheet,\n        Bounds = {\n            X = yearNumFirstX,\n            Y = yearNumFirstY,\n            Width = yearW,\n            Height = yearNumHeight\n        }\n    };\n    root.DateDisplay.YearNumSprites[#root.DateDisplay.YearNumSprites + 1] = name .. \"YearNum\" .. i;\n\n    local numW\n    if i == 1 then\n        numW = num1Width;\n    else\n        numW = numWidth;\n    end\n\n    root.Sprites[name .. \"Num\" .. i] = {\n        Sheet = sheet,\n        Bounds = {\n            X = numFirstX,\n            Y = numFirstY,\n            Width = numW,\n            Height = numHeight\n        }\n    };\n    root.DateDisplay.MonthNumSprites[#root.DateDisplay.MonthNumSprites + 1] = name .. \"Num\" .. i;\n    root.DateDisplay.DayNumSprites[#root.DateDisplay.DayNumSprites + 1] = name .. \"Num\" .. i;\n\n    if i == 1 then\n        yearNumFirstX = yearNumFirstX + yearNum1Width;\n        numFirstX = numFirstX + num1Width;\n    else\n        yearNumFirstX = yearNumFirstX + yearNumWidth;\n        numFirstX = numFirstX + numWidth;\n    end\nend\n\nfor i = 0, 6 do\n    if i > 1 then\n        local weekW;\n        if i == 5 then\n            weekW = weekFriWidth;\n        else\n            weekW = weekWidth;\n        end\n\n        root.Sprites[name .. \"Week\" .. i] = {\n            Sheet = sheet,\n            Bounds = {\n                X = weekSecondX,\n                Y = weekSecondY,\n                Width = weekW,\n                Height = weekHeight\n            }\n        };\n        if i == 5 then\n            weekSecondX = weekSecondX + weekFriWidth;\n        else\n            weekSecondX = weekSecondX + weekWidth;\n         end\n    else\n        root.Sprites[name .. \"Week\" .. i] = {\n            Sheet = sheet,\n            Bounds = {\n                X = weekFirstX,\n                Y = weekFirstY,\n                Width = weekWidth,\n                Height = weekHeight\n            }\n        };\n        weekFirstX = weekFirstX + weekWidth;\n    end\n    root.DateDisplay.WeekSprites[#root.DateDisplay.WeekSprites + 1] = name .. \"Week\" .. i;\nend\n\nroot.Sprites[name .. \"MonthDaySeparator\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 1897,\n        Y = 33,\n        Width = 10,\n        Height = 28\n    }\n};\nroot.DateDisplay.MDSeparatorSprite = name .. \"MonthDaySeparator\";\n\nroot.Sprites[name .. \"DayYearSeparator\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 1758,\n        Y = 63,\n        Width = 8,\n        Height = 20\n    }\n};\nroot.DateDisplay.DYSeparatorSprite = name .. \"DayYearSeparator\";\n\nroot.Sprites[name .. \"OpenBracket\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 1766,\n        Y = 63,\n        Width = 8,\n        Height = 20\n    }\n};\nroot.DateDisplay.OpenBracketSprite = name .. \"OpenBracket\";\n\nroot.Sprites[name .. \"CloseBracket\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 1776,\n        Y = 63,\n        Width = 8,\n        Height = 20\n    }\n};\nroot.DateDisplay.CloseBracketSprite = name .. \"CloseBracket\";\n\nroot.Sprites[name .. \"Background\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 1525,\n        Y = 1,\n        Width = 450,\n        Height = 28\n    }\n};\nroot.DateDisplay.BackgroundSprite = name .. \"Background\";"
  },
  {
    "path": "profiles/sgps3/hud/loadingdisplay.lua",
    "content": "root.LoadingDisplay = {\n    ResourceLoadBgAnim = \"ResourceLoadingBg\",\n    SaveLoadBgAnim = \"SaveLoadingBg\",\n    LoadingIconAnim = \"LoadingDisc\",\n    LoadingTextAnim = \"LoadingText\",\n    ResourceBgPos = { X = 1074, Y = 611 },\n    SaveBgPos = { X = 1019, Y = 598 },\n    IconPos = { X = 986, Y = 608 },\n    TextPos = { X = 1025, Y = 628 },\n    FadeInDuration = 0.66,\n    FadeOutDuration = 0.33\n};\n\nMakeAnimation({\n    Name = \"ResourceLoadingBg\",\n    Sheet = \"Data\",\n    FirstFrameX = 555,\n    FirstFrameY = 544,\n    FrameWidth = 206,\n    ColWidth = 206,\n    FrameHeight = 58,\n    RowHeight = 60,\n    Frames = 8,\n    Duration = 0.8,\n    Rows = 8,\n    Columns = 1,\n    PrimaryDirection = AnimationDirections.Down\n});\n\nMakeAnimation({\n    Name = \"SaveLoadingBg\",\n    Sheet = \"Data\",\n    FirstFrameX = 388,\n    FirstFrameY = 0,\n    FrameWidth = 277,\n    ColWidth = 279,\n    FrameHeight = 81,\n    RowHeight = 83,\n    Frames = 4,\n    Duration = 0.8,\n    Rows = 2,\n    Columns = 2,\n    PrimaryDirection = AnimationDirections.Down,\n    SecondaryDirection = AnimationDirections.Right\n});\n\nMakeAnimation({\n    Name = \"LoadingDisc\",\n    Sheet = \"Data\",\n    FirstFrameX = 172,\n    FirstFrameY = 1,\n    FrameWidth = 60,\n    ColWidth = 62,\n    FrameHeight = 60,\n    RowHeight = 62,\n    Frames = 3,\n    Duration = 0.8,\n    Rows = 1,\n    Columns = 3,\n    PrimaryDirection = AnimationDirections.Right\n});\nroot.Animations[\"LoadingDisc\"].Frames[#root.Animations[\"LoadingDisc\"].Frames + 1] = \"LoadingDisc1\";\n\nMakeAnimation({\n    Name = \"LoadingText\",\n    Sheet = \"Data\",\n    FirstFrameX = 173,\n    FirstFrameY = 91,\n    FrameWidth = 214,\n    ColWidth = 214,\n    FrameHeight = 21,\n    RowHeight = 23,\n    Frames = 5,\n    Duration = 0.8,\n    Rows = 5,\n    Columns = 1,\n    PrimaryDirection = AnimationDirections.Down\n});"
  },
  {
    "path": "profiles/sgps3/hud/saveicon.lua",
    "content": "root.SaveIcon = {\n    ForegroundAnimation = \"SaveIcon\",\n    DefaultPosition = { X = 1153, Y = 23 },\n    BackgroundSprite = \"SaveIconBg\",\n    BackgroundOffset = { X = -7, Y = -4 },\n    BackgroundMaxAlpha = 0.5,\n    FadeInDuration = 0.5,\n    FadeOutDuration = 0.25\n};\n\nMakeAnimation({\n    Name = \"SaveIcon\",\n    Sheet = \"Data\",\n    FirstFrameX = 1977,\n    FirstFrameY = 1,\n    FrameWidth = 70,\n    ColWidth = 70,\n    FrameHeight = 70,\n    RowHeight = 72,\n    Frames = 8,\n    Duration = 0.4,\n    Rows = 8,\n    Columns = 1,\n    PrimaryDirection = AnimationDirections.Down\n});\n\nroot.Sprites[\"SaveIconBg\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 1439, Y = 1, Width = 84, Height = 84 }\n};"
  },
  {
    "path": "profiles/sgps3/hud/selectiondisplay.lua",
    "content": "root.SelectionDisplay = {\n    DrawType = DrawComponentType.Text,\n    SelectionBackgroundSprite = \"SelectionBackground\",\n    PlainSelectionFrameTopLeftSprite = \"PlainSelectionFrameTopLeft\",\n    PlainSelectionFrameTopSideSprite = \"PlainSelectionFrameTopSide\",\n    PlainSelectionFrameTopRightSprite = \"PlainSelectionFrameTopRight\",\n    PlainSelectionFrameLeftSideSprite = \"PlainSelectionFrameLeftSide\",\n    PlainSelectionFrameBottomLeftSprite = \"PlainSelectionFrameBottomLeft\",\n    PlainSelectionFrameRightSideSprite = \"PlainSelectionFrameRightSide\",\n    PlainSelectionFrameBottomRightSprite = \"PlainSelectionFrameBottomRight\",\n    PlainSelectionFrameBottomSideSprite = \"PlainSelectionFrameBottomSide\",\n    PlainSelectionFrameMiddleSprite = \"PlainSelectionFrameMiddle\",\n    SelectionHighlightSprite = \"SelectionHighlight\",\n    SelectionMaxCount = 5,\n    SelectionBackgroundX = 228,\n    SelectionBackgroundY = {277, 277, 196, 129, 95},\n    SelectionYSpacing = 81,\n    PlainSelectionYSpacing = 10,\n    FadeAnimationDurationInOut = 0.2\n};\n\nroot.Sprites[\"SelectionBackground\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 1, Y = 535, Width = 829, Height = 69 }\n};\n\nroot.Sprites[\"PlainSelectionFrameTopLeft\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 0, Y = 0, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameTopSide\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 16, Y = 0, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameTopRight\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 32, Y = 0, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameLeftSide\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 0, Y = 16, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameBottomLeft\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 0, Y = 32, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameRightSide\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 32, Y = 16, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameBottomRight\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 32, Y = 32, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameBottomSide\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 16, Y = 32, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"PlainSelectionFrameMiddle\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 16, Y = 16, Width = 16, Height = 16 }\n};\n\nroot.Sprites[\"SelectionHighlight\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 65, Y = 33, Width = 94, Height = 30 }\n};\n"
  },
  {
    "path": "profiles/sgps3/hud/sysmesboxdisplay.lua",
    "content": "local sheet = \"Data\";\nlocal name = \"SysMesBox\";\n\nroot.SysMesBoxDisplay = {\n    Type = SysMesBoxType.CHLCC,\n    DrawType = DrawComponentType.SystemMessage,\n    BoxX = 0,\n    BoxY = 230,\n    TextFontSize = 32,\n    TextMiddleY = 236,\n    TextX = 640,\n    TextLineHeight = 34,\n    TextMarginY = 14,\n    ChoicePadding = 40,\n    ChoiceY = 365,\n    ChoiceXBase = 680,\n    MinMaxMesWidth = 294,\n    MinHighlightWidth = 48,\n    HighlightBaseWidth = 144,\n    HighlightYOffset = 0,\n    HighlightXOffset = 0,\n    HighlightXBase = 658,\n    HighlightXStep = 132,\n    HighlightRightPartSpriteWidth = 24,\n    AnimationSpeed = 55,\n    FadeInDuration = 0.33,\n    FadeOutDuration = 0.25\n};\n\nroot.Sprites[name .. \"Box\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 767,\n        Y = 637,\n        Width = 1280,\n        Height = 172\n    }\n};\nroot.SysMesBoxDisplay.Box = name .. \"Box\";\n\nroot.Sprites[name .. \"BoxDecoration\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 1383,\n        Y = 117,\n        Width = 592,\n        Height = 8\n    }\n};\nroot.SysMesBoxDisplay.BoxDecoration = name .. \"BoxDecoration\";\n\nroot.Sprites[name .. \"SelectionLeftPart\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 502,\n        Y = 51,\n        Width = 144,\n        Height = 38\n    }\n};\nroot.SysMesBoxDisplay.SelectionLeftPart = name .. \"SelectionLeftPart\";\n\nroot.Sprites[name .. \"SelectionRightPart\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 634,\n        Y = 51,\n        Width = 24,\n        Height = 38\n    }\n};\nroot.SysMesBoxDisplay.SelectionRightPart = name .. \"SelectionRightPart\";\n\nroot.Sprites[name .. \"SelectionMiddlePart\"] = {\n    Sheet = sheet,\n    Bounds = {\n        X = 515,\n        Y = 51,\n        Width = 132,\n        Height = 38\n    }\n};\nroot.SysMesBoxDisplay.SelectionMiddlePart = name .. \"SelectionMiddlePart\";"
  },
  {
    "path": "profiles/sgps3/hud/systemmenu.lua",
    "content": "root.SystemMenu = {\n    Type = SystemMenuType.RNE,\n    DrawType = DrawComponentType.SystemMenu,\n    Background = {\n        DurationIn = 0.75,\n        DurationOut = 1.0,\n        Sprite = \"SystemMenuBackground\",\n        Seed = 0,\n        Rows = 7,\n        Columns = 12,\n        CenterY = 0.7,\n        VanishingPointX = 0.66,\n        Depth = 2,\n        MaxAngle = 3.141592653 / 2  -- pi / 2\n    },\n    ButtonBackgroundSprite = \"SystemMenuButtonBackground\",\n    SkyBackgroundSprite = \"SystemMenuSkyBackground\",\n    SkyArrowSprite = \"SystemMenuSkyArrow\",\n    SkyTextSprite = \"SystemMenuSkyText\",\n    ButtonPromptsSprite = \"SystemMenuButtonPrompts\",\n    SkyBackgroundBeginX = -80,\n    SkyBackgroundY = 0,\n    SkyTextBeginX = 287,\n    SkyTextY = 69,\n    ButtonBackgroundStartX = 1257,\n    ButtonBackgroundX = 0,\n    ButtonBackgroundY = 681,\n    ButtonBackgroundTargetWidth = 943,\n    ButtonBackgroundSprStartX = 1499,\n    MenuEntriesSprites = {},\n    MenuEntriesHighlightedSprites = {},\n    MenuEntriesNum = 8,\n    MenuEntriesHNum = 8,\n    MenuEntriesX = 0,\n    MenuEntriesXSkew = 20,\n    MenuEntriesXOffset = 100,\n    MenuEntriesFirstY = 220,\n    MenuEntriesYPadding = 50,\n    MenuEntriesTargetWidth = 417,\n    SkyInStartProgress = 0.285,\n    SkyOutStartProgress = 0.715,\n    SkyMoveDurationIn = 0.415,\n    SkyMoveDurationOut = 0.415,\n    EntriesMoveDurationIn = 0.4,\n    EntriesMoveDurationOut = 0.4,\n    HighlightDurationIn = 0.15,\n    HighlightDurationOut = 0.15,\n};\n\nfor i = 0, 7 do\n    root.Sprites[\"SystemMenuEntry\" .. i] = {\n        Sheet = \"Data\",\n        Bounds = {\n            X = 964,\n            Y = 296 + i * 38,\n            Width = root.SystemMenu.MenuEntriesTargetWidth,\n            Height = 26\n        },\n        BaseScale = { X = 1280 / 960, Y = 720 / 544 }\n    };\n    root.SystemMenu.MenuEntriesSprites[#root.SystemMenu.MenuEntriesSprites + 1] = \"SystemMenuEntry\" .. i;\nend\n\nfor i = 0, 7 do\n    root.Sprites[\"SystemMenuEntryHighlighted\" .. i] = {\n        Sheet = \"Data\",\n        Bounds = {\n            X = 964,\n            Y = 2 + i * 38,\n            Width = root.SystemMenu.MenuEntriesTargetWidth,\n            Height = 26\n        },\n        BaseScale = { X = 1280 / 960, Y = 720 / 544 }\n    };\n    root.SystemMenu.MenuEntriesHighlightedSprites[#root.SystemMenu.MenuEntriesHighlightedSprites + 1] = \"SystemMenuEntryHighlighted\" .. i;\nend\n\nroot.Sprites[\"SystemMenuBackground\"] = {\n    Sheet = \"Menu\",\n    Bounds = { X = 0, Y = 1088, Width = 1920, Height = 1080 },\n};\n\nroot.Sprites[\"SystemMenuButtonBackground\"] = {\n    Sheet = \"Menu\",\n    Bounds = { X = root.SystemMenu.ButtonBackgroundSprStartX, Y = 975, Width = 0, Height = 30 },\n    BaseScale = { X = 1280 / 960, Y = 720 / 544 }\n};\n\nroot.Sprites[\"SystemMenuSkyBackground\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 1383, Y = 534, Width = 418, Height = 90 },\n    BaseScale = { X = 1280 / 960, Y = 720 / 544 }\n};\n\nroot.Sprites[\"SystemMenuSkyArrow\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 1802, Y = 534, Width = 70, Height = 90 },\n    BaseScale = { X = 1280 / 960, Y = 720 / 544 }\n};\n\nroot.Sprites[\"SystemMenuSkyText\"] = {\n    Sheet = \"Data\",\n    Bounds = { X = 1875, Y = 587, Width = 119, Height = 30 },\n    BaseScale = { X = 1280 / 960, Y = 720 / 544 }\n};\n\nroot.Sprites[\"SystemMenuButtonPrompts\"] = {\n    Sheet = \"Menu\",\n    Bounds = { X = 1231, Y = 528, Width = 400, Height = 21 },\n    BaseScale = { X = 1280 / 960, Y = 720 / 544 }\n};"
  },
  {
    "path": "profiles/sgps3/hud/tipsmenu.lua",
    "content": "root.TipsMenu = {\n    Type = TipsMenuType.None\n};\n"
  },
  {
    "path": "profiles/sgps3/hud/tipsnotification.lua",
    "content": "root.TipsNotification = {\n    Type = TipsNotificationType.None,\n};\n"
  },
  {
    "path": "profiles/sgps3/hud/titlemenu.lua",
    "content": "root.TitleMenu = {\n    Type = TitleMenuType.CHLCC,\n    DrawType = DrawComponentType.SystemMenu,\n    PressToStartX = 72,\n    PressToStartY = 595,\n    PressToStartAnimDurationIn = 0.5,\n    PressToStartAnimDurationOut = 0.5,\n    PressToStartSprite = \"TitleMenuPressToStart\",\n    IntroBackgroundSprite = \"TitleMenuIntroBackground\",\n    BackgroundSprite = \"TitleMenuBackground\",\n    DelusionADVUnderSprite = \"DelusionADVUnder\", -- \"DelusionADVUnderEnglish\" with the TLed assets, \"DelusionADVUnder\" with the original ones\n    DelusionADVUnderX = 78, --74 with the TLed assets, 78 with the original ones\n    DelusionADVUnderY = 394, --396 with the TLed assets, 394 with the original ones\n    DelusionADVSprite = \"DelusionADV\", -- \"DelusionADVEnglish\" with the TLed assets, \"DelusionADV\" with the original ones\n    DelusionADVX = 78, --74 with the TLed assets, 78 with the original ones\n    DelusionADVY = 394, --396 with the TLed assets, 394 with the original ones\n    SeiraUnderSprite = \"SeiraUnder\",\n    SeiraUnderX = 733,\n    SeiraUnderY = 0,\n    SeiraSprite = \"Seira\",\n    SeiraX = 728,\n    SeiraY = -47,\n    CHLogoSprite = \"CHLogo\",\n    CHLogoX = 61,\n    CHLogoY = 279,\n    LCCLogoUnderSprite = \"LCCLogoUnder\",\n    LCCLogoUnderX = 241,\n    LCCLogoUnderY = 327,\n    ChuLeftLogoSprite = \"ChuLeftLogo\",\n    ChuLeftLogoX = 353,\n    ChuLeftLogoY = 336,\n    ChuRightLogoSprite = \"ChuRightLogo\",\n    ChuRightLogoX = 500,\n    ChuRightLogoY = 316,\n    LoveLogoSprite = \"LoveLogo\",\n    LoveLogoX = 235, --231 with the TLed assets, 235 with the original ones\n    LoveLogoY = 336, --335 with the TLed assets, 336 with the original ones\n    StarLogoSprite = \"StarLogo\",\n    StarLogoX = 465,\n    StarLogoY = 316,\n    ExclMarkLogoSprite = \"ExclMarkLogo\",\n    ExclMarkLogoX = 614,\n    ExclMarkLogoY = 316,\n    CopyrightTextSprite = \"CopyrightText\",\n    CopyrightTextX = 72,\n    CopyrightTextY = 675,\n    SpinningCircleSprite = \"SpinningCircle\",\n    SpinningCircleX = 610.5,\n    SpinningCircleY = -285.5,\n    SpinningCircleAnimationDuration = 15,\n    ItemHighlightSprite = \"TitleMenuItemHighlight\",\n    ItemHighlightOffsetX = 73,\n    ItemHighlightOffsetY = 7,\n    ItemPadding = 40,\n    ItemYBase = 69,\n    ItemFadeInDuration = 0.3,\n    ItemFadeOutDuration = 0.6,\n    SecondaryItemFadeInDuration = 0.2,\n    SecondaryItemFadeOutDuration = 0.2,\n    PrimaryFadeInDuration = 0.3,\n    PrimaryFadeOutDuration = 0.3,\n    SecondaryFadeInDuration = 0.512,\n    SecondaryFadeOutDuration = 0.512,\n    ItemHyperUpLine = \"TitleMenuItemHyperUpLine\",\n    ItemSuperUpLine = \"TitleMenuItemSuperUpLine\",\n    ItemUpLine = \"TitleMenuItemUpLine\",\n    ItemStraightLine = \"TitleMenuItemStraightLine\",\n    ItemDownLine = \"TitleMenuItemDownLine\",\n    ItemSuperDownLine = \"TitleMenuItemSuperDownLine\",\n    ItemLoadQuickSprite = \"TitleMenuItemLoadQuick\",\n    SecondaryItemX = 320,\n    ItemLoadY = 109,\n    ItemLoadQuickY = 83,\n    ItemLoadSprite = \"TitleMenuItemLoad\",\n    ItemLoadQuickHighlightedSprite = \"TitleMenuItemLoadQuickHighlighted\",\n    ItemLoadHighlightedSprite = \"TitleMenuItemLoadHighlighted\",\n    SecondaryItemHighlightSprite = \"TitleMenuSecondaryItemHighlight\",\n    ItemClearListY = 71,\n    ItemCGLibraryY = 97,\n    ItemSoundLibraryY = 123,\n    ItemMovieLibraryY = 149,\n    ItemTipsY = 175,\n    ItemTrophyY = 201,\n    ItemConfigY = 163,\n    ItemSystemSaveY = 189,\n    SecondaryItemHighlightX = 286,\n    SecondaryMenuPaddingY = 26,\n    SecondaryMenuLoadOffsetY = 76,\n    SecondaryMenuLineX = 241,\n    SecondaryMenuLoadLineY = 93,\n    SecondaryMenuLoadQuickLineY = 119,\n    SecondaryMenuExtraClearY = 81,\n    SecondaryMenuExtraCGY = 107,\n    SecondaryMenuExtraSoundY = 133,\n    SecondaryMenuExtraMovieY = 159,\n    SecondaryMenuExtraTipsY = 159,\n    SecondaryMenuExtraTrophyY = 159,\n    SecondaryMenuSystemConfigY = 173,\n    SecondaryMenuSystemSaveY = 199,\n    MenuEntriesNum = 14,\n    MenuEntriesSprites = {},\n    MenuEntriesHighlightedSprites = {},\n    LineNum = 6,\n    LineEntriesSprites = {}\n};\n\nfor i = 0, 3 do\n    root.Sprites[\"TitleMenuEntry\" .. i] = {\n        Sheet = \"Title\",\n        Bounds = {\n            X = 1151,\n            Y = 101 + i * 25,\n            Width = 188,\n            Height = 23\n        }\n    };\n    root.TitleMenu.MenuEntriesSprites[#root.TitleMenu.MenuEntriesSprites + 1] = \"TitleMenuEntry\" .. i;\nend\n\nfor i = 0, 9 do\n    root.Sprites[\"TitleMenuEntry\" .. (i + 4)] = {\n        Sheet = \"Title\",\n        Bounds = {\n            X = 1369,\n            Y = 684 + i * 22,\n            Width = 216,\n            Height = 20\n        }\n    };\n    root.TitleMenu.MenuEntriesSprites[#root.TitleMenu.MenuEntriesSprites + 1] = \"TitleMenuEntry\" .. (i + 4);\nend\n\nfor i = 0, 3 do\n    root.Sprites[\"TitleMenuEntryHighlighted\" .. i] = {\n        Sheet = \"Title\",\n        Bounds = {\n            X = 1151,\n            Y = 1 + i * 25,\n            Width = 188,\n            Height = 23\n        }\n    };\n    root.TitleMenu.MenuEntriesHighlightedSprites[#root.TitleMenu.MenuEntriesHighlightedSprites + 1] = \"TitleMenuEntryHighlighted\" .. i;\nend\n\nfor i = 0, 9 do\n    root.Sprites[\"TitleMenuEntryHighlighted\" .. (i + 4)] = {\n        Sheet = \"Title\",\n        Bounds = {\n            X = 1151,\n            Y = 684 + i * 22,\n            Width = 216,\n            Height = 20\n        }\n    };\n    root.TitleMenu.MenuEntriesHighlightedSprites[#root.TitleMenu.MenuEntriesHighlightedSprites + 1] = \"TitleMenuEntryHighlighted\" .. (i + 4);\nend\n\nroot.Sprites[\"TitleMenuPressToStart\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 1, Y = 921, Width = 313, Height = 28 },\n};\n\nroot.Sprites[\"DelusionADVUnder\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 1863, Y = 772, Width = 163, Height = 27 },\n};\n\nroot.Sprites[\"DelusionADVUnderEnglish\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 1862, Y = 785, Width = 157, Height = 37 },\n};\n\nroot.Sprites[\"DelusionADV\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 1863, Y = 728, Width = 163, Height = 27 },\n};\n\nroot.Sprites[\"DelusionADVEnglish\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 1862, Y = 734, Width = 153, Height = 33 },\n};\n\nroot.Sprites[\"SeiraUnder\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 555, Y = 1, Width = 594, Height = 768 },\n};\n\nroot.Sprites[\"Seira\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 1, Y = 1, Width = 552, Height = 768 },\n};\n\nroot.Sprites[\"CHLogo\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 1, Y = 771, Width = 594, Height = 115 },\n};\n\nroot.Sprites[\"LCCLogoUnder\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 597, Y = 771, Width = 462, Height = 122 },\n};\n\nroot.Sprites[\"ChuLeftLogo\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 483, Y = 915, Width = 136, Height = 108 },\n};\n\nroot.Sprites[\"ChuRightLogo\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 693, Y = 895, Width = 136, Height = 128 },\n};\n\nroot.Sprites[\"LoveLogo\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 341, Y = 915, Width = 140, Height = 108 },\n};\n\nroot.Sprites[\"StarLogo\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 621, Y = 895, Width = 70, Height = 128 },\n};\n\nroot.Sprites[\"ExclMarkLogo\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 831, Y = 895, Width = 82, Height = 128 },\n};\n\nroot.Sprites[\"CopyrightText\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 193, Y = 891, Width = 380, Height = 24 },\n};\n\nroot.Sprites[\"SpinningCircle\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 1366, Y = 1, Width = 681, Height = 681 },\n};\n\nroot.Sprites[\"TitleMenuIntroBackground\"] = {\n    Sheet = \"TitleBg1\",\n    Bounds = { X = 0, Y = 0, Width = 1280, Height = 720 },\n};\n\nroot.Sprites[\"TitleMenuBackground\"] = {\n    Sheet = \"TitleBg2\",\n    Bounds = { X = 0, Y = 0, Width = 1280, Height = 720 },\n};\n\nroot.Sprites[\"TitleMenuItemHighlight\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 915, Y = 951, Width = 244, Height = 36 },\n};\n\nroot.Sprites[\"TitleMenuItemHyperUpLine\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 1805, Y = 686, Width = 51, Height = 80 },\n};\n\nroot.Sprites[\"TitleMenuItemSuperUpLine\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 1805, Y = 776, Width = 51, Height = 54 },\n};\n\nroot.Sprites[\"TitleMenuItemUpLine\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 1805, Y = 845, Width = 51, Height = 28 },\n};\n\nroot.Sprites[\"TitleMenuItemStraightLine\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 1805, Y = 888, Width = 51, Height = 2 },\n};\n\nroot.Sprites[\"TitleMenuItemDownLine\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 1805, Y = 910, Width = 51, Height = 28 },\n};\n\nroot.Sprites[\"TitleMenuItemSuperDownLine\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 1805, Y = 959, Width = 51, Height = 54 },\n};\n\nroot.TitleMenu.LineEntriesSprites[#root.TitleMenu.LineEntriesSprites + 1] = \"TitleMenuItemHyperUpLine\";\nroot.TitleMenu.LineEntriesSprites[#root.TitleMenu.LineEntriesSprites + 1] = \"TitleMenuItemSuperUpLine\";\nroot.TitleMenu.LineEntriesSprites[#root.TitleMenu.LineEntriesSprites + 1] = \"TitleMenuItemUpLine\";\nroot.TitleMenu.LineEntriesSprites[#root.TitleMenu.LineEntriesSprites + 1] = \"TitleMenuItemStraightLine\";\nroot.TitleMenu.LineEntriesSprites[#root.TitleMenu.LineEntriesSprites + 1] = \"TitleMenuItemDownLine\";\nroot.TitleMenu.LineEntriesSprites[#root.TitleMenu.LineEntriesSprites + 1] = \"TitleMenuItemSuperDownLine\";\n\nroot.Sprites[\"TitleMenuItemLoadQuick\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 1369, Y = 684, Width = 216, Height = 20 },\n};\n\nroot.Sprites[\"TitleMenuItemLoad\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 1369, Y = 706, Width = 216, Height = 20 },\n};\n\nroot.Sprites[\"TitleMenuItemLoadQuickHighlighted\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 1151, Y = 684, Width = 216, Height = 20 },\n};\n\nroot.Sprites[\"TitleMenuItemLoadHighlighted\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 1151, Y = 706, Width = 216, Height = 20 },\n};\n\nroot.Sprites[\"TitleMenuSecondaryItemHighlight\"] = {\n    Sheet = \"Title\",\n    Bounds = { X = 915, Y = 989, Width = 285, Height = 34 },\n};\n"
  },
  {
    "path": "profiles/sgps3/savedata.lua",
    "content": "root.SaveData = {\n    Type = SaveDataType.None,\n    SaveFilePath = \"games/sgps3/savedata/SYSTEM.DAT\",\n};"
  },
  {
    "path": "profiles/sgps3/scriptvars.lua",
    "content": "local sv = root.ScriptVars;\n\nsv.SW_TITLEDISPCT = 1014;\nsv.SW_TITLEMASKALPHA = 1033;\nsv.SW_TITLEMASKCOLOR = 1034;\nsv.SW_SYSSEL = 1028;\nsv.SW_SYSTEMMENUCHG = 1040;\nsv.SW_SYSTEMMENUALPHA = 1041;\nsv.SW_SYSMENUCT = 1042;\nsv.SW_MASK1ALPHA_OFS = 1586;\nsv.SW_MASK2ALPHA_OFS = 1587;\nsv.SW_MASK3ALPHA_OFS = 1588;\nsv.SW_MAINTHDP = 1704;\nsv.SW_SAVEERRORCODE = 1733;\nsv.SW_TITLECUR1 = 1740;\nsv.SW_TITLECUR2 = 1741;\nsv.SW_SVSENO = 2001;\nsv.SW_SVBGMNO = 2005;\nsv.SW_SVSCRNO1 = 2006;\nsv.SW_SVSCRNO2 = 2007;\nsv.SW_SVSCRNO3 = 2008;\nsv.SW_SVSCRNO4 = 2009;\nsv.SW_SVBGNO1 = 2010;\nsv.SW_SVCHANO1 = 2018;\nsv.SW_TITLE = 2300;\nsv.SW_PLAYTIME = 2304;\nsv.SW_MESWINDOW_COLOR = 7777;\nsv.SW_BGMREQNO = 2310;\nsv.SW_SEREQNO = 2311;\nsv.SW_BGMVOL = 2314;\nsv.SW_SEVOL = 2315;\nsv.SW_SCRIPTNO0 = 2320;\nsv.SW_SCRIPTNO1 = 2321;\nsv.SW_SCRIPTNO2 = 2322;\nsv.SW_SCRIPTNO3 = 2323;\nsv.SW_SCRIPTNO4 = 2324;\nsv.SW_SCRIPTNO5 = 2325;\nsv.SW_SCRIPTNO6 = 2326;\nsv.SW_SCRIPTNO7 = 2327;\nsv.SW_MASK1COLOR = 2329;\nsv.SW_MASK1ALPHA = 2330;\nsv.SW_MASK1PRI = 2331;\nsv.SW_MASK1POSX = 2332;\nsv.SW_MASK1POSY = 2333;\nsv.SW_MASK1SIZEX = 2334;\nsv.SW_MASK1SIZEY = 2335;\nsv.SW_MESMODE0 = 3173;\nsv.SW_CHA1POSX = 2600;\nsv.SW_CHA1POSY = 2601;\nsv.SW_CHA1ROTX = 2602;\nsv.SW_CHA1ROTY = 2603;\nsv.SW_CHA1ROTZ = 2604;\nsv.SW_CHA1ALPHA = 2607;\nsv.SW_CHA1NO = 2609;\nsv.SW_CHA1PRI = 2610;\nsv.SW_CHA1POSE = 2611;\nsv.SW_CHA1FACE = 2612;\nsv.SW_CHA1EX = 2613;\nsv.SW_CHA1FADECT = 2614;\nsv.SW_CHA1FADETYPE = 2615;\nsv.SW_CHA1SURF = 1850;\nsv.SW_BG1POSX = 2400;\nsv.SW_BG1POSY = 2401;\nsv.SW_BG1SX = 2402;\nsv.SW_BG1SY = 2403;\nsv.SW_BG1SIZE = 2404;\nsv.SW_BG1LX = 2405;\nsv.SW_BG1LY = 2406;\nsv.SW_BG1NO = 2407;\nsv.SW_BG1PRI = 2408;\nsv.SW_BG1DISPMODE = 2409;\nsv.SW_BG1FADECT = 2410;\nsv.SW_BG1FADETYPE = 2411;\nsv.SW_BG1ALPHA = 2413;\nsv.SW_BG1MASKNO = 2414;\nsv.SW_BG1MASKFADERANGE = 2415;\nsv.SW_BG1POSX_OFS = 1200;\nsv.SW_BG1POSY_OFS = 1201;\nsv.SW_BG1SX_OFS = 1202;\nsv.SW_BG1SY_OFS = 1203;\nsv.SW_BG1SIZE_OFS = 1204;\nsv.SW_BG1LX_OFS = 1205;\nsv.SW_BG1LY_OFS = 1206;\nsv.SW_BG1ALPHA_OFS = 1208;\nsv.SW_CHA1POSY_OFS = 1301;\nsv.SW_CHA1ALPHA_OFS = 1307;\nsv.SW_BG1SURF = 1800;\nsv.SW_SAVEFILESTATUS = 2122;\nsv.SW_SAVEFILENO = 2123;\nsv.SW_BGLINK = 2580;\nsv.SW_BGLINK2 = 2581;\nsv.SW_EFF_CAP_PRI = 3268;\nsv.SW_EFF_CAP_BUF = 3269;\nsv.SW_EFF_CAP_PRI2 = 3270;\nsv.SW_EFF_CAP_BUF2 = 3271;"
  },
  {
    "path": "profiles/sgps3/sprites.lua",
    "content": "root.SpriteSheets = {\n    [\"Data\"] = {\n        Path = { Mount = \"system\", Id = 7 },\n        DesignWidth = 2048,\n        DesignHeight = 1024\n    },\n    [\"Font\"] = {\n        Path = { Mount = \"system\", Id = 10 },\n        DesignWidth = 2048,\n        DesignHeight = 448\n    },\n    [\"Menu\"] = {\n        Path = { Mount = \"system\", Id = 8 },\n        DesignWidth = 2048,\n        DesignHeight = 1024\n    },\n    [\"Title\"] = {\n        Path = { Mount = \"system\", Id = 21 },\n        DesignWidth = 2048,\n        DesignHeight = 1024\n    },\n    [\"TitleBg1\"] = {\n        Path = { Mount = \"system\", Id = 19 },\n        DesignWidth = 1280,\n        DesignHeight = 720\n    },\n    [\"TitleBg2\"] = {\n        Path = { Mount = \"system\", Id = 20 },\n        DesignWidth = 1280,\n        DesignHeight = 720\n    },\n    [\"Backlog\"] = {\n        Path = {Mount = \"system\", Id = 6 },\n        DesignWidth = 2048,\n        DesignHeight = 720\n    }\n};\n\nroot.Sprites = {};"
  },
  {
    "path": "profiles/sgps3/tipssystem.lua",
    "content": "root.TipsSystem = {\n    Type = TipsSystemType.None,\n};"
  },
  {
    "path": "profiles/sgps3/vfs.lua",
    "content": "root.Vfs = {\n    Mounts = {\n        [\"script\"] = {\"games/sgps3/gamedata/SCRIPT.CPK\"},\n        [\"system\"] = {\"games/sgps3/gamedata/SYSTEM_US.CPK\"},\n        [\"bgm\"] = {\"games/sgps3/gamedata/BGM.CPK\"},\n        [\"se\"] = {\"games/sgps3/gamedata/SE.CPK\"},\n        [\"voice\"] = {\"games/sgps3/gamedata/VOICE.CPK\"},\n        [\"bg\"] = {\"games/sgps3/gamedata/BG.CPK\"},\n        [\"chara\"] = {\"games/sgps3/gamedata/CHARA.CPK\"},\n        [\"mask\"] = {\"games/sgps3/gamedata/MASK.CPK\"},\n        [\"movie\"] = {\"games/sgps3/gamedata/MOVIE.CPK\"}\n    }\n};"
  },
  {
    "path": "src/animation.cpp",
    "content": "#include \"animation.h\"\n\nnamespace Impacto {\n\nvoid Animation::AddDelta(float dt) {\n  float duration =\n      Direction == AnimationDirection::In ? DurationIn : DurationOut;\n\n  switch (LoopMode) {\n    case AnimationLoopMode::Stop: {\n      float endProgress = Direction == AnimationDirection::In ? 1.0f : 0.0f;\n\n      Progress = std::clamp(\n          Progress + static_cast<float>(Direction) * dt / duration, 0.0f, 1.0f);\n      if (Progress == endProgress) State = AnimationState::Stopped;\n      return;\n    }\n    case AnimationLoopMode::Loop: {\n      // E.g. Progress = 1.04 => Progress = 0.04\n      Progress += static_cast<float>(Direction) * dt / duration;\n      Progress -= std::floor(Progress);\n      return;\n    }\n    case AnimationLoopMode::ReverseDirection: {\n      // E.g. Progress = 1.04 => Progress = 0.96\n      float cycleDuration = DurationIn + DurationOut;\n\n      float time = Progress * duration;\n      if (Direction == AnimationDirection::Out) {\n        time = cycleDuration - time;\n      }\n\n      time = std::fmod(time + dt, cycleDuration);\n\n      if (time < DurationIn) {\n        Progress = time / DurationIn;\n        Direction = AnimationDirection::In;\n      } else {\n        Progress = 1.0f - (time - DurationIn) / DurationOut;\n        Direction = AnimationDirection::Out;\n      }\n\n      return;\n    }\n  }\n}\n\n}  // namespace Impacto"
  },
  {
    "path": "src/animation.h",
    "content": "#pragma once\n\n#include \"mem.h\"\n#include \"profile/scriptvars.h\"\n#include <magic_enum/magic_enum.hpp>\n\nnamespace Impacto {\n\nenum class AnimationState : uint8_t { Stopped = 0, Playing = 1 };\nenum class AnimationLoopMode : uint8_t {\n  Stop = 0,\n  ReverseDirection = 1,\n  Loop = 2\n};\n\nenum class AnimationDirection : int8_t { In = 1, Out = -1 };\nconstexpr AnimationDirection operator-(AnimationDirection direction) {\n  return static_cast<AnimationDirection>(static_cast<int>(direction) * -1);\n}\n\nclass Animation {\n public:\n  float DurationIn = 0;\n  float DurationOut = 0;\n  // 0 = fully out, 1 = fully in\n  float Progress = 0;\n  // 1 = in, -1 = out\n  AnimationDirection Direction = AnimationDirection::In;\n  AnimationState State = AnimationState::Stopped;\n  AnimationLoopMode LoopMode = AnimationLoopMode::Stop;\n  // Animation skips to the end when skip mode is enabled\n  bool SkipOnSkipMode = false;\n\n  void SetDuration(float duration) {\n    DurationIn = duration;\n    DurationOut = duration;\n  }\n  float GetDuration(AnimationDirection direction) const {\n    return direction == AnimationDirection::In ? DurationIn : DurationOut;\n  }\n\n  void Stop() { State = AnimationState::Stopped; }\n\n  void StartIn(bool reset = false) {\n    if (reset) Progress = 0.0f;\n    Direction = AnimationDirection::In;\n    State = AnimationState::Playing;\n    StartInImpl(reset);\n  }\n\n  void StartOut(bool reset = false) {\n    if (reset) Progress = 1.0f;\n    Direction = AnimationDirection::Out;\n    State = AnimationState::Playing;\n    StartOutImpl(reset);\n  }\n\n  void Start(AnimationDirection direction, bool reset = false) {\n    if (direction == AnimationDirection::In)\n      StartIn(reset);\n    else\n      StartOut(reset);\n  }\n\n  void Finish(std::optional<AnimationDirection> direction = std::nullopt) {\n    State = AnimationState::Stopped;\n    if (direction.has_value()) Direction = direction.value();\n    Progress = Direction == AnimationDirection::In ? 1.0f : 0.0f;\n    FinishImpl();\n  }\n\n  void Reset(std::optional<AnimationDirection> direction = std::nullopt) {\n    State = AnimationState::Stopped;\n    if (direction.has_value()) Direction = direction.value();\n    Progress = Direction == AnimationDirection::In ? 0.0f : 1.0f;\n    ResetImpl(direction);\n  }\n\n  void Update(float dt) {\n    if (State == AnimationState::Stopped) return;\n    UpdateImpl(dt);\n  }\n\n  virtual void Render() {}\n\n  bool IsOut() const {\n    return Progress == 0.0f && State == AnimationState::Stopped;\n  }\n  bool IsIn() const {\n    return Progress == 1.0f && State == AnimationState::Stopped;\n  }\n  bool IsFinished(AnimationDirection direction) const {\n    return direction == AnimationDirection::In ? IsIn() : IsOut();\n  }\n\n  bool IsPlaying() const { return State == AnimationState::Playing; }\n  bool IsStopped() const { return State == AnimationState::Stopped; }\n\n protected:\n  void AddDelta(float dt);\n  virtual void StartInImpl(bool reset = false) {}\n  virtual void StartOutImpl(bool reset = false) {}\n  virtual void ResetImpl(std::optional<AnimationDirection> direction) {};\n  virtual void FinishImpl() {};\n  virtual void ResetImpl() {};\n  virtual void UpdateImpl(float dt) {\n    if (SkipOnSkipMode && GetFlag(Profile::ScriptVars::SF_MESALLSKIP) &&\n        State != AnimationState::Stopped) {\n      Progress = Direction == AnimationDirection::In ? 1.0f : 0.0f;\n    }\n    AddDelta(dt);\n  }\n};\n\n}  // namespace Impacto"
  },
  {
    "path": "src/audio/adxaudiostream.cpp",
    "content": "#include \"adxaudiostream.h\"\n\n#include \"../util.h\"\n#include \"../log.h\"\n\n#include <algorithm>\n\nusing namespace Impacto::Io;\n\nnamespace Impacto {\nnamespace Audio {\n\nstruct AdxHeaderInfo {\n  int ChannelCount;\n  int SampleRate;\n  int SampleCount;\n  bool HasLoop;\n  int LoopStart;\n  int LoopEnd;\n\n  uint16_t Highpass;\n  uint8_t FrameSize;\n  int StreamDataOffset;\n\n  int16_t Hist1_L;\n  int16_t Hist1_R;\n  int16_t Hist2_L;\n  int16_t Hist2_R;\n};\n\n// Thanks https://github.com/kode54/vgmstream/blob/master/src/util.h\n\nstatic int nibble_to_int[16] = {0,  1,  2,  3,  4,  5,  6,  7,\n                                -8, -7, -6, -5, -4, -3, -2, -1};\n\nstatic inline int get_high_nibble_signed(uint8_t n) {\n  return nibble_to_int[n >> 4];\n}\n\nstatic inline int get_low_nibble_signed(uint8_t n) {\n  return nibble_to_int[n & 0xf];\n}\n\nstatic inline int clamp16(int32_t val) {\n  if (val > 32767) return 32767;\n  if (val < -32768) return -32768;\n  return val;\n}\n\nvoid AdxAudioStream::SetCoefficients(double cutoff, double sample_rate) {\n  // https://wiki.multimedia.cx/index.php/CRI_ADX_ADPCM#Coefficients\n\n  /* temps to keep the calculation simple */\n  double z, a, b, c;\n\n  z = cos(2.0 * std::numbers::pi * cutoff / sample_rate);\n\n  a = std::numbers::sqrt2 - z;\n  b = std::numbers::sqrt2 - 1.0;\n  c = (a - sqrt((a + b) * (a - b))) / b;\n\n  /* compute the coefficients as fixed point values, with 12 fractional bits */\n  Coef1 = (int16_t)floor(c * 8192);\n  Coef2 = (int16_t)floor(c * c * -4096);\n}\n\n// https://github.com/kode54/vgmstream/blob/master/src/coding/adx_decoder.c\nbool AdxAudioStream::DecodeBuffer() {\n  int16_t* output = DecodedBuffer;\n  uint8_t* input = EncodedBuffer;\n\n  for (int c = 0; c < ChannelCount; c++) {\n    /* the +1 becomes important on quiet ADXs */\n    int scale = SDL_SwapBE16(*(uint16_t*)input) + 1;\n    input += 2;\n\n    for (int i = 0; i < SamplesPerBuffer; i++) {\n      /* this byte contains nibbles for two samples */\n      uint8_t sample_byte = input[i / 2];\n\n      output[i * ChannelCount + c] = (int16_t)(clamp16(\n          (i & 1 ? get_low_nibble_signed(sample_byte)\n                 : get_high_nibble_signed(sample_byte)) *\n              scale +\n          (Coef1 * Hist1[c] >> 12) + (Coef2 * Hist2[c] >> 12)));\n\n      Hist2[c] = Hist1[c];\n      Hist1[c] = output[i * ChannelCount + c];\n    }\n    input += SamplesPerBuffer / 2;\n  }\n\n  return true;\n}\n\nstatic bool ParseAdxHeader(Stream* stream, AdxHeaderInfo* info) {\n  uint8_t header[0x34];\n  stream->Read(header, 0x34);\n\n  // first magic\n  if (SDL_SwapBE16(*(uint16_t*)header) != 0x8000) return false;\n\n  info->StreamDataOffset = SDL_SwapBE16(*(uint16_t*)(header + 2)) + 4;\n\n  // second magic\n  stream->Seek(info->StreamDataOffset - 6, RW_SEEK_SET);\n  char const magic[] = \"(c)CRI\";\n  char fileMagic[6];\n  stream->Read(fileMagic, 6);\n  if (memcmp(magic, fileMagic, 6) != 0) {\n    return false;\n  }\n\n  if (header[4] != 3) {\n    ImpLog(LogLevel::Error, LogChannel::Audio,\n           \"Encountered ADX file with unsupported encoding type format {:d}\\n\",\n           header[4]);\n    return false;\n  }\n\n  info->FrameSize = header[5];\n\n  if (header[6] != 4) {\n    ImpLog(LogLevel::Error, LogChannel::Audio,\n           \"Encountered ADX file with unknown bits per sample {:d}\\n\",\n           header[6]);\n    return false;\n  }\n\n  info->ChannelCount = header[7];\n  if (info->ChannelCount != 1 && info->ChannelCount != 2) {\n    ImpLog(LogLevel::Error, LogChannel::Audio,\n           \"Encountered ADX file with unsupported channel count {:d}\\n\",\n           info->ChannelCount);\n    return false;\n  }\n\n  info->SampleRate = SDL_SwapBE32(*(uint32_t*)(header + 8));\n\n  info->SampleCount = SDL_SwapBE32(*(uint32_t*)(header + 12));\n\n  info->Highpass = SDL_SwapBE16(*(uint16_t*)(header + 16));\n\n  if (header[18] != 4) {\n    ImpLog(LogLevel::Error, LogChannel::Audio,\n           \"Encountered ADX file with unsupported header version {:d}\\n\",\n           header[18]);\n    return false;\n  }\n\n  if (header[19] != 0) {\n    ImpLog(LogLevel::Error, LogChannel::Audio,\n           \"Encountered encrypted ADX file, type {:d}\\n\", header[19]);\n    return false;\n  }\n\n  int HistOffset = 0x18;\n  int HistSize = std::max(8, 4 * info->ChannelCount);\n\n  info->Hist1_L = SDL_SwapBE16(*(uint16_t*)(header + HistOffset));\n  info->Hist2_L = SDL_SwapBE16(*(uint16_t*)(header + HistOffset + 2));\n  info->Hist1_R = SDL_SwapBE16(*(uint16_t*)(header + HistOffset + 4));\n  info->Hist2_R = SDL_SwapBE16(*(uint16_t*)(header + HistOffset + 6));\n\n  int LoopOffset = HistOffset + HistSize;\n  int LoopSize = 0x18;\n  if (LoopOffset + LoopSize <= info->StreamDataOffset) {\n    info->HasLoop = true;\n    info->LoopStart = SDL_SwapBE32(*(uint32_t*)(header + LoopOffset + 8));\n    info->LoopEnd = SDL_SwapBE32(*(uint32_t*)(header + LoopOffset + 16));\n  } else {\n    info->HasLoop = false;\n  }\n\n  return true;\n}\n\nAudioStream* AdxAudioStream::Create(Stream* stream) {\n  AdxAudioStream* result = 0;\n\n  AdxHeaderInfo info = {0, 0, 0, false, 0, 0, 0, 0, 0, 0, 0, 0, 0};\n  if (!ParseAdxHeader(stream, &info)) goto fail;\n\n  result = new AdxAudioStream;\n  result->BaseStream = stream;\n  result->InitWithInfo(&info);\n\n  return result;\n\nfail:\n  if (result) {\n    result->BaseStream = 0;\n    delete result;\n  }\n  stream->Seek(0, RW_SEEK_SET);\n  return 0;\n}\nAdxAudioStream::~AdxAudioStream() {}\n\nvoid AdxAudioStream::InitWithInfo(AdxHeaderInfo* info) {\n  ChannelCount = info->ChannelCount;\n  SampleRate = info->SampleRate;\n  EncodedBytesPerBuffer = info->FrameSize * ChannelCount;\n  StreamDataOffset = info->StreamDataOffset;\n  Hist1[0] = info->Hist1_L;\n  Hist1[1] = info->Hist1_R;\n  Hist2[0] = info->Hist2_L;\n  Hist2[1] = info->Hist2_R;\n  SamplesPerBuffer = (info->FrameSize - 2) * 2;\n  Duration = info->SampleCount;\n  LoopStart = info->HasLoop ? info->LoopStart : 0;\n  LoopEnd = info->HasLoop ? info->LoopEnd : Duration;\n  SetCoefficients(info->Highpass, SampleRate);\n\n  BitDepth = 16;\n\n  BaseStream->Seek(StreamDataOffset, RW_SEEK_SET);\n}\n\nint AdxAudioStream::Read(void* buffer, int samples) {\n  return ReadBuffered((int16_t*)buffer, samples);\n}\n\nvoid AdxAudioStream::Seek(int samples) { SeekBuffered(samples); }\n\nbool AdxAudioStream::_registered =\n    AudioStream::AddAudioStreamCreator(&AdxAudioStream::Create);\n\n}  // namespace Audio\n}  // namespace Impacto"
  },
  {
    "path": "src/audio/adxaudiostream.h",
    "content": "#pragma once\n\n#include \"audiostream.h\"\n#include \"../impacto.h\"\n#include \"buffering.h\"\n\nnamespace Impacto {\nnamespace Audio {\n\nstruct AdxHeaderInfo;\n\nclass AdxAudioStream : public AudioStream,\n                       public Buffering<AdxAudioStream, int16_t> {\n  friend class Buffering<AdxAudioStream, int16_t>;\n\n public:\n  ~AdxAudioStream();\n\n  int Read(void* buffer, int samples) override;\n  void Seek(int samples) override;\n\n protected:\n  bool DecodeBuffer();\n  int16_t DecodedBuffer[127] = {0};\n  uint8_t EncodedBuffer[256] = {0};\n\n private:\n  static AudioStream* Create(Io::Stream* stream);\n  AdxAudioStream() {}\n  void InitWithInfo(AdxHeaderInfo* info);\n  void SetCoefficients(double cutoff, double sampleRate);\n\n  int32_t Coef1;\n  int32_t Coef2;\n\n  int32_t Hist1[2];\n  int32_t Hist2[2];\n\n  static bool _registered;\n};\n\n}  // namespace Audio\n}  // namespace Impacto"
  },
  {
    "path": "src/audio/atrac9audiostream.cpp",
    "content": "#include \"atrac9audiostream.h\"\n#include \"../log.h\"\n\nusing namespace Impacto::Io;\n\nnamespace Impacto {\nnamespace Audio {\n\nstruct At9ContainerInfo {\n  int ChannelCount;\n  int SampleRate;\n  int SampleCount;\n  int EncoderDelay;\n  bool HasLoop;\n  int LoopStart;\n  int LoopEnd;\n  uint8_t ConfigData[4];\n\n  int StreamDataOffset;\n};\n\nstatic const uint32_t RIFFMagic = 0x52494646u;\nstatic const uint32_t WAVEMagic = 0x57415645u;\nstatic const uint32_t fmtMagic = 0x666d7420u;\nstatic const uint32_t factMagic = 0x66616374u;\nstatic const uint32_t smplMagic = 0x736d706cu;\nstatic const uint32_t dataMagic = 0x64617461u;\n\nstatic const uint16_t WaveFormatExtensible = 0xFFFE;\nstatic const uint8_t Atrac9Guid[] = {0xD2, 0x42, 0xE1, 0x47, 0xBA, 0x36,\n                                     0x8D, 0x4D, 0x88, 0xFC, 0x61, 0x65,\n                                     0x4F, 0x8C, 0x83, 0x6C};\n\n// http://www.piclist.com/techref/io/serial/midi/wave.html\nstatic bool ParseAt9Riff(Stream* stream, At9ContainerInfo* info) {\n  if (ReadBE<uint32_t>(stream) != RIFFMagic) return false;\n  uint32_t remaining = ReadLE<uint32_t>(stream) - 4;\n  if (ReadBE<uint32_t>(stream) != WAVEMagic) return false;\n\n  bool gotFmt = false;\n  bool gotFact = false;\n\n  while (remaining) {\n    uint32_t chunkId = ReadBE<uint32_t>(stream);\n    uint32_t chunkSize = ReadLE<uint32_t>(stream);\n    remaining -= 8;\n    remaining -= chunkSize;\n    switch (chunkId) {\n      case fmtMagic: {\n        if (gotFmt) {\n          ImpLog(LogLevel::Error, LogChannel::Audio,\n                 \"Encountered ATRAC9 file with more than one fmt chunk\\n\");\n          return false;\n        }\n        gotFmt = true;\n        if (chunkSize != 52) return false;\n        uint8_t fmtData[52];\n        stream->Read(fmtData, 52);\n        if (SDL_SwapLE16(*(uint16_t*)fmtData) != WaveFormatExtensible)\n          return false;\n        if (memcmp(fmtData + 24, Atrac9Guid, sizeof(Atrac9Guid)) != 0)\n          return false;\n\n        // yep, it's an ATRAC9 file\n\n        info->ChannelCount = SDL_SwapLE16(*(uint16_t*)(fmtData + 2));\n        if (info->ChannelCount != 1 && info->ChannelCount != 2) {\n          ImpLog(\n              LogLevel::Error, LogChannel::Audio,\n              \"Encountered ATRAC9 file with unsupported channel count {:d}\\n\",\n              info->ChannelCount);\n          return false;\n        }\n\n        info->SampleRate = SDL_SwapLE32(*(uint32_t*)(fmtData + 4));\n        memcpy(info->ConfigData, fmtData + 44, 4);\n\n        break;\n      }\n      case factMagic: {\n        if (gotFact) {\n          ImpLog(LogLevel::Error, LogChannel::Audio,\n                 \"Encountered ATRAC9 file with more than one fact chunk\\n\");\n          return false;\n        }\n        gotFact = true;\n        if (chunkSize != 12) return false;\n        uint8_t factData[12];\n        stream->Read(factData, 12);\n\n        info->SampleCount = SDL_SwapLE32(*(uint32_t*)(factData));\n        info->EncoderDelay = SDL_SwapLE32(*(uint32_t*)(factData + 8));\n\n        break;\n      }\n      case smplMagic: {\n        if (chunkSize < 36) {\n          stream->Seek(chunkSize, RW_SEEK_CUR);\n          break;\n        }\n        uint8_t smplData[36];\n        stream->Read(smplData, 36);\n        int loopCount = SDL_SwapLE32(*(uint32_t*)(smplData + 28));\n        info->HasLoop = loopCount > 0;\n        if (info->HasLoop) {\n          uint8_t loopChunk[24];\n          stream->Read(loopChunk, 24);\n          info->LoopStart = SDL_SwapLE32(*(uint32_t*)(loopChunk + 8));\n          info->LoopEnd = SDL_SwapLE32(*(uint32_t*)(loopChunk + 12));\n        }\n        // skip further loop chunks\n        if (loopCount > 1) stream->Seek(24 * (loopCount - 1), RW_SEEK_CUR);\n        break;\n      }\n      case dataMagic: {\n        info->StreamDataOffset = (int)stream->Position;\n        goto breakLoop;\n      }\n      default: {\n        stream->Seek(chunkSize, RW_SEEK_CUR);\n        break;\n      }\n    }\n  }\n\nbreakLoop:\n  if (!gotFmt || !gotFact) {\n    ImpLog(LogLevel::Error, LogChannel::Audio,\n           \"Encountered incomplete ATRAC9 file\\n\");\n    return false;\n  }\n  return true;\n}\n\nAudioStream* Atrac9AudioStream::Create(Stream* stream) {\n  Atrac9AudioStream* result = 0;\n\n  At9ContainerInfo container;\n  int ret;\n  Atrac9CodecInfo codecinfo;\n\n  void* At9 = Atrac9GetHandle();\n  if (!At9) {\n    ImpLog(LogLevel::Error, LogChannel::Audio, \"Atrac9GetHandle failed\\n\");\n    goto fail;\n  }\n\n  container = {0, 0, 0, 0, false, 0, 0, {0, 0, 0, 0}, 0};\n  if (!ParseAt9Riff(stream, &container)) {\n    goto fail;\n  }\n\n  ret = Atrac9InitDecoder(At9, container.ConfigData);\n  if (ret != 0) {\n    ImpLog(LogLevel::Error, LogChannel::Audio,\n           \"Atrac9InitDecoder failed with {:d}\\n\", ret);\n    goto fail;\n  }\n\n  Atrac9GetCodecInfo(At9, &codecinfo);\n  if (codecinfo.channels != container.ChannelCount ||\n      codecinfo.samplingRate != container.SampleRate) {\n    ImpLog(LogLevel::Error, LogChannel::Audio,\n           \"Atrac9CodecInfo does not match container\\n\");\n    goto fail;\n  }\n\n  result = new Atrac9AudioStream;\n  result->At9 = At9;\n  result->BaseStream = stream;\n  result->InitWithInfo(&container, &codecinfo);\n\n  return result;\n\nfail:\n  if (At9) Atrac9ReleaseHandle(At9);\n  if (result) {\n    result->At9 = 0;\n    result->BaseStream = 0;\n    delete result;\n  }\n  stream->Seek(0, RW_SEEK_SET);\n  return 0;\n}\n\nvoid Atrac9AudioStream::InitWithInfo(At9ContainerInfo* container,\n                                     Atrac9CodecInfo* codecinfo) {\n  EncoderDelay = container->EncoderDelay;\n  Duration = container->SampleCount + container->EncoderDelay;\n  SampleRate = container->SampleRate;\n  ChannelCount = container->ChannelCount;\n  LoopStart = container->LoopStart;\n  LoopEnd = container->HasLoop ? container->LoopEnd : Duration;\n  StreamDataOffset = container->StreamDataOffset;\n\n  BitDepth = 16;\n\n  SamplesPerFrame = codecinfo->frameSamples;\n  FramesPerSuperframe = codecinfo->framesInSuperframe;\n  SamplesPerBuffer = SamplesPerFrame * FramesPerSuperframe;\n\n  DecodedBuffer =\n      (int16_t*)malloc(sizeof(int16_t) * ChannelCount * SamplesPerBuffer);\n  EncodedBytesPerBuffer = codecinfo->superframeSize;\n  EncodedBuffer = (uint8_t*)malloc(2048);\n\n  ImpLog(LogLevel::Debug, LogChannel::Audio,\n         \"Created ATRAC9 stream Duration={:d}, SampleRate={:d}, \"\n         \"ChannelCount={:d}, \"\n         \"LoopStart={:d}, LoopEnd={:d}\\n\",\n         Duration, SampleRate, ChannelCount, LoopStart, LoopEnd);\n\n  BaseStream->Seek(StreamDataOffset, RW_SEEK_SET);\n  Seek(EncoderDelay);\n}\n\nAtrac9AudioStream::~Atrac9AudioStream() {\n  if (At9) Atrac9ReleaseHandle(At9);\n  if (DecodedBuffer) free(DecodedBuffer);\n  if (EncodedBuffer) free(EncodedBuffer);\n}\n\n// TODO load more than one superframe at once? reading in <1KB chunks is\n// probably kinda slow\n\nint Atrac9AudioStream::Read(void* buffer, int samples) {\n  return ReadBuffered((int16_t*)buffer, samples);\n}\n\nbool Atrac9AudioStream::DecodeBuffer() {\n  uint8_t* encoded = EncodedBuffer;\n  int16_t* decoded = DecodedBuffer;\n  // Atrac9Decode always decodes one frame\n  for (int i = 0; i < FramesPerSuperframe; i++) {\n    int bytesUsed;\n    int err = Atrac9Decode(At9, encoded, decoded, &bytesUsed);\n    if (err != 0) {\n      ImpLog(LogLevel::Error, LogChannel::Audio, \"Atrac9Decode error {:d}\\n\",\n             err);\n      return false;\n    }\n    encoded += bytesUsed;\n    decoded += SamplesPerFrame * ChannelCount;\n  }\n  return true;\n}\n\nvoid Atrac9AudioStream::Seek(int samples) { return SeekBuffered(samples); }\n\nbool Atrac9AudioStream::_registered =\n    AudioStream::AddAudioStreamCreator(&Atrac9AudioStream::Create);\n\n}  // namespace Audio\n}  // namespace Impacto"
  },
  {
    "path": "src/audio/atrac9audiostream.h",
    "content": "#pragma once\n\n#include \"audiostream.h\"\n#include \"../impacto.h\"\n#include \"buffering.h\"\n#include <libatrac9/libatrac9.h>\n\nnamespace Impacto {\nnamespace Audio {\n\nstruct At9ContainerInfo;\n\nclass Atrac9AudioStream : public AudioStream,\n                          public Buffering<Atrac9AudioStream, int16_t> {\n  friend class Buffering<Atrac9AudioStream, int16_t>;\n\n public:\n  ~Atrac9AudioStream();\n\n  int Read(void* buffer, int samples) override;\n  void Seek(int samples) override;\n\n protected:\n  bool DecodeBuffer();\n\n private:\n  static AudioStream* Create(Io::Stream* stream);\n  Atrac9AudioStream() {}\n\n  void InitWithInfo(At9ContainerInfo* container, Atrac9CodecInfo* codecinfo);\n\n  void* At9 = 0;\n  int SamplesPerFrame;\n  int FramesPerSuperframe;\n  int EncoderDelay;\n\n  static bool _registered;\n};\n\n}  // namespace Audio\n}  // namespace Impacto"
  },
  {
    "path": "src/audio/audiobackend.h",
    "content": "#pragma once\n\n#include \"audiocommon.h\"\n\nnamespace Impacto {\nnamespace Audio {\n\nclass AudioBackend {\n public:\n  virtual bool Init() { return true; };\n\n  virtual void Shutdown() {};\n};\n\n}  // namespace Audio\n}  // namespace Impacto"
  },
  {
    "path": "src/audio/audiochannel.cpp",
    "content": "#include \"audiochannel.h\"\n#include \"audiostream.h\"\n\n#ifndef IMPACTO_DISABLE_OPENAL\n#include \"openal/openalaudiochannel.h\"\n#endif\n\n#include \"../io/stream.h\"\n#include \"../io/vfs.h\"\n#include \"../log.h\"\n#include \"../profile/game.h\"\n\nnamespace Impacto {\nnamespace Audio {\n\nstd::unique_ptr<AudioChannel> AudioChannel::Create(\n    AudioChannelId channelId, AudioChannelGroup channelGroup) {\n  switch (Profile::ActiveAudioBackend) {\n#ifndef IMPACTO_DISABLE_OPENAL\n    case AudioBackendType::OpenAL: {\n      return std::make_unique<OpenAL::OpenALAudioChannel>(channelId,\n                                                          channelGroup);\n    } break;\n#endif\n    case AudioBackendType::None:\n    default:\n      return std::make_unique<EmptyAudioChannel>(channelId, channelGroup);\n  }\n}\n\nvoid AudioChannel::Play(std::string const& mountpoint,\n                        std::string const& fileName, bool loop,\n                        float fadeInDuration) {\n  Io::Stream* stream;\n  IoError err = Io::VfsOpen(mountpoint, fileName, &stream);\n  if (err == IoError_OK) {\n    Play(std::unique_ptr<AudioStream>(AudioStream::Create(stream)), loop,\n         fadeInDuration);\n  } else {\n    ImpLog(LogLevel::Error, LogChannel::Audio,\n           \"Could not open audio file {:s} from mountpoint {:s}!\\n\", fileName,\n           mountpoint);\n  }\n}\n\nvoid AudioChannel::Play(std::string const& mountpoint, uint32_t fileId,\n                        bool loop, float fadeInDuration) {\n  Io::Stream* stream;\n  IoError err = Io::VfsOpen(mountpoint, fileId, &stream);\n  if (err == IoError_OK) {\n    Play(std::unique_ptr<AudioStream>(AudioStream::Create(stream)), loop,\n         fadeInDuration);\n  } else {\n    ImpLog(LogLevel::Error, LogChannel::Audio,\n           \"Could not open audio file with ID {:d} from mountpoint {:s}!\\n\",\n           fileId, mountpoint);\n  }\n}\n\nfloat AudioChannel::DurationInSeconds() const {\n  if (!Stream) return 0;\n  return (float)Stream->Duration / (float)Stream->SampleRate;\n}\n\n}  // namespace Audio\n}  // namespace Impacto"
  },
  {
    "path": "src/audio/audiochannel.h",
    "content": "#pragma once\n\n#include \"audiocommon.h\"\n#include \"audiostream.h\"\n#include <string>\n#include <cstdint>\n\nnamespace Impacto {\nnamespace Audio {\n\nclass AudioChannel {\n public:\n  virtual ~AudioChannel() = default;\n\n  static std::unique_ptr<AudioChannel> Create(AudioChannelId id,\n                                              AudioChannelGroup group);\n\n  virtual void Play(std::unique_ptr<AudioStream> stream, bool loop,\n                    float fadeInDuration) = 0;\n  void Play(std::string const& mountpoint, std::string const& fileName,\n            bool loop, float fadeInDuration);\n  void Play(std::string const& mountpoint, uint32_t fileId, bool loop,\n            float fadeInDuration);\n\n  virtual void Stop(float fadeOutDuration) = 0;\n  virtual void Pause() = 0;\n  virtual void Resume() = 0;\n\n  virtual void Update(float dt) = 0;\n  virtual float PositionInSeconds() const = 0;\n\n  void SetLooping(bool looping) { Looping = looping; }\n  bool IsLooping() { return Looping; }\n\n  AudioChannelState GetState() const { return State; }\n  AudioChannelGroup GetGroup() const { return Group; }\n  AudioChannelId GetId() const { return Id; }\n\n  float GetVolume() const { return Volume; }\n  void SetVolume(float volume) { Volume = volume; }\n\n  const AudioStream* GetStream() const { return Stream.get(); }\n\n  // may be negative for no fixed duration, 0 for no audio\n  float DurationInSeconds() const;\n\n protected:\n  AudioChannel(AudioChannelId id, AudioChannelGroup group)\n      : Id(id), Group(group) {}\n\n  AudioChannelId Id;\n  AudioChannelGroup Group;\n  AudioChannelState State = ACS_Stopped;\n\n  float Volume = 1.0f;\n  bool Looping = false;\n\n  std::unique_ptr<AudioStream> Stream;\n};\n\nclass EmptyAudioChannel : public AudioChannel {\n public:\n  EmptyAudioChannel(AudioChannelId id, AudioChannelGroup group)\n      : AudioChannel(id, group) {}\n\n  void Play(std::unique_ptr<AudioStream> stream, bool loop,\n            float fadeInDuration) override {\n    State = ACS_Playing;\n  }\n\n  void Stop(float fadeOutDuration) override {\n    if (State == ACS_Playing || State == ACS_FadingIn || State == ACS_FadingOut)\n      State = ACS_Stopped;\n  }\n\n  void Pause() override {\n    if (State == ACS_Playing || State == ACS_FadingIn || State == ACS_FadingOut)\n      State = ACS_Paused;\n  }\n\n  void Resume() override {\n    if (State == ACS_Paused) State = ACS_Playing;\n  }\n\n  void Update(float dt) override {}\n\n  float PositionInSeconds() const override { return 0; }\n};\n\n}  // namespace Audio\n}  // namespace Impacto\n"
  },
  {
    "path": "src/audio/audiocommon.h",
    "content": "#pragma once\n\n#include \"../util.h\"\nnamespace Impacto {\nnamespace Audio {\n\nenum AudioChannelGroup { ACG_BGM, ACG_SE, ACG_Voice, ACG_Movie, ACG_Count };\nenum AudioChannelId {\n  AC_SE0 = 0,\n  AC_SE1,\n  AC_SE2,\n  AC_VOICE0,\n  AC_VOICE1,\n  AC_VOICE2,\n  AC_REV,  // backlog\n  AC_BGM0,\n  AC_BGM1,\n  AC_BGM2,\n  AC_SSE0,  // \"system sound effect\"\n  AC_SSE1,\n  AC_SSE2,\n  AC_SSE3,\n  AC_Count\n};\n\nenum AudioChannelState {\n  ACS_Stopped,\n  ACS_Playing,\n  ACS_Paused,\n  ACS_FadingIn,\n  ACS_FadingOut\n};\n\n}  // namespace Audio\n}  // namespace Impacto"
  },
  {
    "path": "src/audio/audiostream.cpp",
    "content": "#include \"audiostream.h\"\n#include \"../log.h\"\n\nnamespace Impacto {\nnamespace Audio {\n\nstd::vector<AudioStream::AudioStreamCreator>& AudioStream::GetRegistry() {\n  static std::vector<AudioStreamCreator> registry;\n  return registry;\n}\n\nAudioStream* AudioStream::Create(Io::Stream* stream) {\n  for (auto f : GetRegistry()) {\n    AudioStream* result = f(stream);\n    if (result) return result;\n  }\n  ImpLog(LogLevel::Error, LogChannel::Audio, \"No audio decoder found\\n\");\n  return 0;\n}\n\nbool AudioStream::AddAudioStreamCreator(AudioStreamCreator c) {\n  GetRegistry().push_back(c);\n  return true;\n}\n\n}  // namespace Audio\n}  // namespace Impacto"
  },
  {
    "path": "src/audio/audiostream.h",
    "content": "#pragma once\n\n#include \"audiocommon.h\"\n#include \"../impacto.h\"\n#include \"../io/stream.h\"\n\n#include <vector>\n\nnamespace Impacto {\nnamespace Audio {\n\nclass AudioStream {\n public:\n  virtual ~AudioStream() {\n    if (BaseStream) delete BaseStream;\n  }\n\n  static AudioStream* Create(Io::Stream* stream);\n\n  // Returns number of samples read\n  virtual int Read(void* buffer, int samples) = 0;\n  virtual void Seek(int samples) = 0;\n\n  const Io::Stream* GetBaseStream() const { return BaseStream; }\n\n  int BytesPerSample() const { return (BitDepth / 8) * ChannelCount; }\n\n  int ChannelCount;\n  int SampleRate;\n  int BitDepth;\n\n  int LoopStart = 0;\n  int LoopEnd = 0;\n  // negative indicates no fixed length\n  int Duration = -1;\n  int ReadPosition = 0;\n\n protected:\n  using AudioStreamCreator = auto (*)(Io::Stream* stream) -> AudioStream*;\n  static bool AddAudioStreamCreator(AudioStreamCreator c);\n\n  AudioStream() {};\n\n  Io::Stream* BaseStream = 0;\n\n private:\n  static std::vector<AudioStreamCreator>& GetRegistry();\n};\n\n}  // namespace Audio\n}  // namespace Impacto"
  },
  {
    "path": "src/audio/audiosystem.cpp",
    "content": "#include \"audiosystem.h\"\n#include \"../log.h\"\n#include \"../profile/game.h\"\n#include \"../profile/scriptvars.h\"\n#include \"../profile/configsystem.h\"\n#include \"../text/dialoguepage.h\"\n\n#ifndef IMPACTO_DISABLE_OPENAL\n#include \"openal/audiobackend.h\"\n#endif\n\n#include <utility>\n\nnamespace Impacto {\nnamespace Audio {\n\nusing namespace Impacto::Profile::ScriptVars;\n\nstatic bool IsInit = false;\n\ntemplate <typename GroupDef>\nstatic AudioChannel* GetNextChannelInGroupImpl(GroupDef& groupDef) {\n  if constexpr (GroupDef::ChannelIds.size() == 0) {\n    return nullptr;\n  } else {\n    for (AudioChannelId id : GroupDef::ChannelIds) {\n      if (Channels[id] && Channels[id]->GetState() == ACS_Stopped) {\n        return Channels[id].get();\n      }\n    }\n\n    AudioChannelId selectedId = GroupDef::ChannelIds[groupDef.NextIndex];\n    groupDef.NextIndex = (groupDef.NextIndex + 1) % GroupDef::ChannelIds.size();\n    return Channels[selectedId].get();\n  }\n}\n\nAudioChannel* GetNextChannelInGroup(AudioChannelGroup group) {\n  switch (group) {\n    case ACG_BGM:\n      return GetNextChannelInGroupImpl(std::get<ACG_BGM>(ChannelGroups));\n    case ACG_SE:\n      return GetNextChannelInGroupImpl(std::get<ACG_SE>(ChannelGroups));\n    case ACG_Voice:\n      return GetNextChannelInGroupImpl(std::get<ACG_Voice>(ChannelGroups));\n    case ACG_Movie:\n      return GetNextChannelInGroupImpl(std::get<ACG_Movie>(ChannelGroups));\n    case ACG_Count:\n      return nullptr;\n  }\n  return nullptr;\n}\n\ntemplate <typename GroupDef>\nstatic void StopChannelGroupImpl(float fadeOut) {\n  for (AudioChannelId id : GroupDef::ChannelIds) {\n    if (Channels[id]) {\n      Channels[id]->Stop(fadeOut);\n    }\n  }\n}\n\nvoid StopChannelGroup(AudioChannelGroup group, float fadeOut) {\n  switch (group) {\n    case ACG_BGM:\n      StopChannelGroupImpl<\n          std::tuple_element_t<ACG_BGM, decltype(ChannelGroups)>>(fadeOut);\n      break;\n    case ACG_SE:\n      StopChannelGroupImpl<\n          std::tuple_element_t<ACG_SE, decltype(ChannelGroups)>>(fadeOut);\n      break;\n    case ACG_Voice:\n      StopChannelGroupImpl<\n          std::tuple_element_t<ACG_Voice, decltype(ChannelGroups)>>(fadeOut);\n      break;\n    case ACG_Movie:\n      StopChannelGroupImpl<\n          std::tuple_element_t<ACG_Movie, decltype(ChannelGroups)>>(fadeOut);\n      break;\n    case ACG_Count:\n      break;\n  }\n}\n\ntemplate <typename GroupDef>\nstatic void PauseChannelGroupImpl() {\n  for (AudioChannelId id : GroupDef::ChannelIds) {\n    if (Channels[id]) {\n      Channels[id]->Pause();\n    }\n  }\n}\n\nvoid PauseChannelGroup(AudioChannelGroup group) {\n  switch (group) {\n    case ACG_BGM:\n      PauseChannelGroupImpl<\n          std::tuple_element_t<ACG_BGM, decltype(ChannelGroups)>>();\n      break;\n    case ACG_SE:\n      PauseChannelGroupImpl<\n          std::tuple_element_t<ACG_SE, decltype(ChannelGroups)>>();\n      break;\n    case ACG_Voice:\n      PauseChannelGroupImpl<\n          std::tuple_element_t<ACG_Voice, decltype(ChannelGroups)>>();\n      break;\n    case ACG_Movie:\n      PauseChannelGroupImpl<\n          std::tuple_element_t<ACG_Movie, decltype(ChannelGroups)>>();\n      break;\n    case ACG_Count:\n      break;\n  }\n}\n\ntemplate <typename GroupDef>\nstatic void ResumeChannelGroupImpl() {\n  for (AudioChannelId id : GroupDef::ChannelIds) {\n    if (Channels[id]) {\n      Channels[id]->Resume();\n    }\n  }\n}\n\nvoid ResumeChannelGroup(AudioChannelGroup group) {\n  switch (group) {\n    case ACG_BGM:\n      ResumeChannelGroupImpl<\n          std::tuple_element_t<ACG_BGM, decltype(ChannelGroups)>>();\n      break;\n    case ACG_SE:\n      ResumeChannelGroupImpl<\n          std::tuple_element_t<ACG_SE, decltype(ChannelGroups)>>();\n      break;\n    case ACG_Voice:\n      ResumeChannelGroupImpl<\n          std::tuple_element_t<ACG_Voice, decltype(ChannelGroups)>>();\n      break;\n    case ACG_Movie:\n      ResumeChannelGroupImpl<\n          std::tuple_element_t<ACG_Movie, decltype(ChannelGroups)>>();\n      break;\n    case ACG_Count:\n      break;\n  }\n}\n\nvoid AudioShutdown() {\n  for (int i = 0; i < AC_Count; i++) {\n    Channels[i] = nullptr;\n  }\n  IsInit = false;\n  Backend->Shutdown();\n}\n\nvoid AudioInit() {\n  assert(IsInit == false);\n  ImpLog(LogLevel::Info, LogChannel::Audio, \"Initialising audio system\\n\");\n\n  switch (Profile::ActiveAudioBackend) {\n#ifndef IMPACTO_DISABLE_OPENAL\n    case AudioBackendType::OpenAL: {\n      Backend = new OpenAL::AudioBackend();\n    } break;\n#endif\n    case AudioBackendType::None:\n    default: {\n      ImpLog(LogLevel::Warning, LogChannel::Audio,\n             \"Unknown or unsupported audio backend selected! You will not hear \"\n             \"audio.\\n\");\n      Backend = new AudioBackend();\n    }\n  }\n\n  if (!Backend->Init()) return;\n  for (int i = AC_SE0; i <= AC_SE2; i++)\n    Channels[i] = AudioChannel::Create((AudioChannelId)i, ACG_SE);\n  for (int i = AC_VOICE0; i <= AC_REV; i++)\n    Channels[i] = AudioChannel::Create((AudioChannelId)i, ACG_Voice);\n  for (int i = AC_BGM0; i <= AC_BGM2; i++)\n    Channels[i] = AudioChannel::Create((AudioChannelId)i, ACG_BGM);\n\n  Channels[AC_SSE0] = AudioChannel::Create(AC_SSE0, ACG_SE);\n  Channels[AC_SSE1] = AudioChannel::Create(AC_SSE1, ACG_SE);\n  Channels[AC_SSE2] = AudioChannel::Create(AC_SSE2, ACG_SE);\n  Channels[AC_SSE3] = AudioChannel::Create(AC_SSE3, ACG_SE);\n\n  IsInit = true;\n}\n\nvoid AudioSubtitlesStart(AudioChannel* channel) {\n  using Profile::Subtitle::SubtitleMappings;\n  std::optional<Subtitle::SubtitlePlayer> player;\n\n  if (channel->GetGroup() != AudioChannelGroup::ACG_BGM) return;\n  AudioStream const* bgmStream = channel->GetStream();\n\n  auto bgmSubtitleMapItr = SubtitleMappings.find(\"bgm\");\n  if (bgmSubtitleMapItr == SubtitleMappings.end()) return;\n  auto mappingsItr =\n      bgmSubtitleMapItr->second.find(bgmStream->GetBaseStream()->Meta.FileName);\n  if (mappingsItr == bgmSubtitleMapItr->second.end()) {\n    mappingsItr =\n        bgmSubtitleMapItr->second.find(bgmStream->GetBaseStream()->Meta.Id);\n  }\n  if (mappingsItr == bgmSubtitleMapItr->second.end()) return;\n\n  SubtitlePlayers[channel->GetId() - AC_BGM0].emplace(Profile::DesignWidth,\n                                                      Profile::DesignHeight);\n  int trackId = 0;\n  for (auto const& subFile : mappingsItr->second) {\n    if (!subFile.Path) continue;\n    SubtitlePlayers[channel->GetId() - AC_BGM0]->AddTrackFile(\n        trackId++, subFile.Type, *subFile.Path, subFile.Config);\n  }\n}\n\nvoid PlayInGroup(AudioChannelGroup group, std::string const& mountpoint,\n                 uint32_t fileId, bool loop, float fadeIn) {\n  AudioChannel* channel = GetNextChannelInGroup(group);\n  if (channel) {\n    channel->Play(mountpoint, fileId, loop, fadeIn);\n  }\n}\n\nvoid PlayInGroup(AudioChannelGroup group, std::string const& mountpoint,\n                 std::string const& fileName, bool loop, float fadeIn) {\n  AudioChannel* channel = GetNextChannelInGroup(group);\n  if (channel) {\n    channel->Play(mountpoint, fileName, loop, fadeIn);\n  }\n}\n\nvoid AudioUpdate(float dt) {\n  // Set voice modifier for each voice channel\n  for (int i = AC_VOICE0; i <= AC_VOICE2; i++) {\n    const int charId = DialoguePages[i - AC_VOICE0].AnimationId;\n    const int mappedCharId = ScrWork[SW_CHARACTERIDMAPPING + charId];\n    const float voiceVolumeModifier =\n        Profile::ConfigSystem::VoiceMuted[mappedCharId]\n            ? 0.0f\n            : Profile::ConfigSystem::VoiceVolume[mappedCharId];\n    Channels[i]->SetVolume(voiceVolumeModifier);\n  }\n\n  for (int i = 0; i < AC_Count; i++) {\n    Channels[i]->Update(dt);\n  }\n}\n\nvoid AudioSubtitlesUpdate() {\n  for (int i = 0; i < ssize(SubtitlePlayers); i++) {\n    if (!SubtitlePlayers[i]) continue;\n    auto const* currentChannel = Channels[i + AC_BGM0].get();\n    switch (currentChannel->GetState()) {\n      case ACS_Stopped:\n        SubtitlePlayers[i].reset();\n        break;\n      case ACS_Paused:\n        break;\n      case ACS_Playing:\n      case ACS_FadingIn:\n      case ACS_FadingOut: {\n        auto durationSec =\n            std::chrono::duration<float>{currentChannel->PositionInSeconds()};\n        SubtitlePlayers[i]->UpdateElapsedTime(\n            std::chrono::duration_cast<Video::Clock::Microseconds>(\n                durationSec));\n      }\n    }\n  }\n}\n\nvoid AudioSubtitlesRender() {\n  for (auto& subtitlePlayer : SubtitlePlayers) {\n    if (subtitlePlayer) subtitlePlayer->Render();\n  }\n}\n\n}  // namespace Audio\n}  // namespace Impacto"
  },
  {
    "path": "src/audio/audiosystem.h",
    "content": "#pragma once\n\n#include <memory>\n\n#include \"../impacto.h\"\n#include \"../log.h\"\n#include \"audiochannel.h\"\n#include \"audiobackend.h\"\n#include \"../subtitle/subtitlesystem.h\"\n\nnamespace Impacto {\nnamespace Audio {\n\nvoid AudioInit();\nvoid AudioUpdate(float dt);\nvoid AudioShutdown();\n\nvoid AudioSubtitlesStart(AudioChannel* channel);\nvoid AudioSubtitlesUpdate();\nvoid AudioSubtitlesRender();\n\ninline AudioBackend* Backend = nullptr;\n\ninline float MasterVolume = 1.0f;\ninline std::array<float, ACG_Count> GroupVolumes;\ninline std::array<std::unique_ptr<AudioChannel>, AC_Count> Channels;\n\n// 3 subtitle players for 3 bgm channels\ninline std::array<std::optional<Subtitle::SubtitlePlayer>, 3> SubtitlePlayers;\n\ntemplate <AudioChannelId... Ids>\nstruct ChannelGroupDef {\n  static constexpr std::array<AudioChannelId, sizeof...(Ids)> ChannelIds{\n      Ids...};\n  size_t NextIndex = 0;\n};\n\ninline auto ChannelGroups = std::tuple{\n    ChannelGroupDef<AC_BGM0, AC_BGM1, AC_BGM2>{},\n    ChannelGroupDef<AC_SSE0, AC_SSE1, AC_SSE2, AC_SSE3>{},\n    ChannelGroupDef<AC_VOICE0, AC_VOICE1, AC_VOICE2, AC_REV>{},\n    ChannelGroupDef<>{},\n};\n\nAudioChannel* GetNextChannelInGroup(AudioChannelGroup group);\nvoid StopChannelGroup(AudioChannelGroup group, float fadeOut);\nvoid PauseChannelGroup(AudioChannelGroup group);\nvoid ResumeChannelGroup(AudioChannelGroup group);\n\nvoid PlayInGroup(AudioChannelGroup group, std::string const& mountpoint,\n                 uint32_t fileId, bool loop, float fadeIn);\nvoid PlayInGroup(AudioChannelGroup group, std::string const& mountpoint,\n                 std::string const& fileName, bool loop, float fadeIn);\n\n}  // namespace Audio\n}  // namespace Impacto"
  },
  {
    "path": "src/audio/buffering.h",
    "content": "#pragma once\n\n#include <algorithm>\n#include \"../impacto.h\"\n\nnamespace Impacto {\nnamespace Audio {\n\ntemplate <typename T, typename SampleType>\nclass Buffering {\n protected:\n  bool DecodeBuffer();\n  SampleType* DecodedBuffer = 0;\n  uint8_t* EncodedBuffer = 0;\n\n  int DecodedSamplesAvailable = 0;\n  int DecodedSamplesConsumed = 0;\n  int SamplesPerBuffer = 0;\n  int EncodedBytesPerBuffer = 0;\n  int StreamDataOffset;\n\n  int ReadBuffered(SampleType* out, int samples) {\n    T* stream = static_cast<T*>(this);\n    int read = 0;\n    while (samples) {\n      if (DecodedSamplesAvailable) {\n        // write out decoded samples\n        int samplesWrittenNow = std::min(samples, DecodedSamplesAvailable);\n        if (out) {\n          // null pointer => discard-seek\n          memcpy(out,\n                 stream->DecodedBuffer +\n                     DecodedSamplesConsumed * stream->ChannelCount,\n                 samplesWrittenNow * stream->ChannelCount * sizeof(SampleType));\n          out += samplesWrittenNow * stream->ChannelCount;\n        }\n        samples -= samplesWrittenNow;\n        read += samplesWrittenNow;\n        stream->ReadPosition += samplesWrittenNow;\n        DecodedSamplesAvailable -= samplesWrittenNow;\n        DecodedSamplesConsumed += samplesWrittenNow;\n      } else {\n        // decode more data\n        int samplesLeft = stream->Duration - stream->ReadPosition;\n        if (samplesLeft == 0)\n          //\n          return read;\n\n        DecodedSamplesAvailable = SamplesPerBuffer;\n        DecodedSamplesConsumed = 0;\n        stream->BaseStream->Read(stream->EncodedBuffer, EncodedBytesPerBuffer);\n        bool decodeSuccess = stream->DecodeBuffer();\n\n        DecodedSamplesAvailable =\n            std::min(DecodedSamplesAvailable, samplesLeft);\n        if (!decodeSuccess) return read;\n      }\n    }\n    return read;\n  }\n\n  void SeekBuffered(int samples) {\n    T* stream = static_cast<T*>(this);\n    int buffer = samples / SamplesPerBuffer;\n    int offset = samples % SamplesPerBuffer;\n    int currentBuffer = stream->ReadPosition / SamplesPerBuffer;\n    if (currentBuffer != buffer) {\n      // seek base stream\n      stream->BaseStream->Seek(\n          StreamDataOffset + buffer * EncodedBytesPerBuffer, RW_SEEK_SET);\n      stream->ReadPosition = buffer * SamplesPerBuffer;\n      DecodedSamplesAvailable = 0;\n      DecodedSamplesConsumed = 0;\n    }\n    // ensure available\n    if (!DecodedSamplesAvailable) {\n      ReadBuffered(NULL, 1);\n    }\n    // skip\n    stream->ReadPosition = samples;\n    DecodedSamplesAvailable = SamplesPerBuffer - offset;\n    DecodedSamplesConsumed = offset;\n  }\n};\n\n}  // namespace Audio\n}  // namespace Impacto"
  },
  {
    "path": "src/audio/ffmpegaudioplayer.h",
    "content": "#pragma once\n\n#include \"../video/ffmpegstream.h\"\n#include \"../video/clock.h\"\n\nnamespace Impacto {\n\nnamespace Video {\nclass FFmpegPlayer;\n}\nnamespace Audio {\n\nclass FFmpegAudioPlayer {\n public:\n  FFmpegAudioPlayer(Video::FFmpegPlayer* player) : Player(player) {}\n  virtual ~FFmpegAudioPlayer() = default;\n\n  virtual void Init() {};\n  virtual void InitConvertContext(AVCodecContext* codecCtx) {};\n  virtual void FillAudioBuffers() {};\n  virtual void Process() {};\n\n  virtual void Stop() {};\n  virtual void Unload() {};\n\n  Video::Clock& GetClock() { return AudioClock; };\n\n protected:\n  uint8_t** AudioBuffer = 0;\n  int AudioLinesize;\n\n  Video::Clock AudioClock;\n\n  SwrContext* AudioConvertContext = 0;\n\n  Video::FFmpegPlayer* Player;\n};\n\n}  // namespace Audio\n}  // namespace Impacto\n"
  },
  {
    "path": "src/audio/hcaaudiostream.cpp",
    "content": "#include \"hcaaudiostream.h\"\n\n#include \"../util.h\"\n#include \"../log.h\"\n\nusing namespace Impacto::Io;\n\nnamespace Impacto {\nnamespace Audio {\n\nAudioStream* HcaAudioStream::Create(Stream* stream) {\n  clHCA* Decoder = 0;\n  HcaAudioStream* result = 0;\n\n  clHCA_stInfo info;\n\n  uint8_t* header;\n\n  int err;\n\n  uint8_t headerStart[8];\n  stream->Read(headerStart, 8);\n  int headerSize = clHCA_isOurFile(headerStart, 8);\n  if (headerSize < 0) {\n    goto fail;\n  }\n  header = (uint8_t*)ImpStackAlloc(headerSize);\n  memcpy(header, headerStart, 8);\n  stream->Read(header + 8, headerSize - 8);\n\n  Decoder = clHCA_new();\n  err = clHCA_DecodeHeader(Decoder, header, headerSize);\n  ImpStackFree(header);\n  if (err) {\n    ImpLog(LogLevel::Error, LogChannel::Audio,\n           \"clHCA_DecodeHeader failed with code {:d}\\n\", err);\n    goto fail;\n  }\n\n  clHCA_getInfo(Decoder, &info);\n\n  if (info.channelCount != 1 && info.channelCount != 2) {\n    ImpLog(LogLevel::Error, LogChannel::Audio,\n           \"Unsupported channel count {:d} in HCA\\n\", info.channelCount);\n    goto fail;\n  }\n\n  result = new HcaAudioStream;\n  result->BaseStream = stream;\n  result->Decoder = Decoder;\n  result->InitWithInfo(&info);\n\n  return result;\n\nfail:\n  if (Decoder) clHCA_delete(Decoder);\n  if (result) {\n    result->Decoder = 0;\n    result->BaseStream = 0;\n    delete result;\n  }\n  stream->Seek(0, RW_SEEK_SET);\n  return 0;\n}\n\nvoid HcaAudioStream::InitWithInfo(clHCA_stInfo* info) {\n  SampleRate = info->samplingRate;\n  ChannelCount = info->channelCount;\n\n  Duration = info->blockCount * info->samplesPerBlock;\n  LoopStart = info->loopEnabled ? info->loopStartBlock * info->samplesPerBlock +\n                                      info->loopStartDelay\n                                : info->encoderDelay;\n  LoopEnd = info->loopEnabled\n                ? info->loopEndBlock * info->samplesPerBlock +\n                      (info->samplesPerBlock - info->loopEndPadding)\n                : Duration;\n  EncodedBytesPerBuffer = info->blockSize;\n  StreamDataOffset = info->headerSize;\n  SamplesPerBuffer = info->samplesPerBlock;\n\n  BitDepth = 16;\n\n  EncodedBuffer = (uint8_t*)malloc(EncodedBytesPerBuffer);\n  DecodedBuffer =\n      (int16_t*)malloc(sizeof(int16_t) * SamplesPerBuffer * ChannelCount);\n\n  clHCA_SetKey(Decoder, 0xCF222F1FE0748978u);  // default key\n\n  clHCA_DecodeReset(Decoder);\n  Seek(info->encoderDelay);\n}\n\nHcaAudioStream::~HcaAudioStream() {\n  if (EncodedBuffer) free(EncodedBuffer);\n  if (DecodedBuffer) free(DecodedBuffer);\n  if (Decoder) clHCA_delete(Decoder);\n}\n\nint HcaAudioStream::Read(void* buffer, int samples) {\n  return ReadBuffered((int16_t*)buffer, samples);\n}\n\nvoid HcaAudioStream::Seek(int samples) { SeekBuffered(samples); }\n\nbool HcaAudioStream::DecodeBuffer() {\n  // TODO clHCA_DecodeReset after seek?\n  int err = clHCA_DecodeBlock(Decoder, EncodedBuffer, EncodedBytesPerBuffer);\n  if (err < 0) return false;\n  clHCA_ReadSamples16(Decoder, DecodedBuffer);\n  return true;\n}\n\nbool HcaAudioStream::_registered =\n    AudioStream::AddAudioStreamCreator(&HcaAudioStream::Create);\n\n}  // namespace Audio\n}  // namespace Impacto"
  },
  {
    "path": "src/audio/hcaaudiostream.h",
    "content": "#pragma once\n\n#include \"audiostream.h\"\n#include \"../impacto.h\"\n#include \"buffering.h\"\n\n#include <clHCA/clHCA.h>\n\nnamespace Impacto {\nnamespace Audio {\n\nclass HcaAudioStream : public AudioStream,\n                       public Buffering<HcaAudioStream, int16_t> {\n  friend class Buffering<HcaAudioStream, int16_t>;\n\n public:\n  ~HcaAudioStream();\n\n  int Read(void* buffer, int samples) override;\n  void Seek(int samples) override;\n\n protected:\n  bool DecodeBuffer();\n\n private:\n  static AudioStream* Create(Io::Stream* stream);\n  HcaAudioStream() {}\n\n  void InitWithInfo(clHCA_stInfo* info);\n\n  clHCA* Decoder = 0;\n\n  static bool _registered;\n};\n\n}  // namespace Audio\n}  // namespace Impacto"
  },
  {
    "path": "src/audio/openal/audiobackend.cpp",
    "content": "#include \"audiobackend.h\"\n\n#include \"../../log.h\"\n\nnamespace Impacto {\nnamespace Audio {\nnamespace OpenAL {\n\nstatic ALCdevice* AlcDevice = 0;\nstatic ALCcontext* AlcContext = 0;\n\nbool AudioBackend::Init() {\n  AlcDevice = alcOpenDevice(NULL);\n  if (!AlcDevice) {\n    ImpLog(LogLevel::Fatal, LogChannel::Audio,\n           \"Could not create OpenAL device\\n\");\n    return false;\n  }\n  if (alIsExtensionPresent(\"AL_EXT_float32\") == AL_FALSE) {\n    ImpLog(LogLevel::Error, LogChannel::Audio,\n           \"No floating-point audio support, some decoders may fail\\n\");\n  }\n  AlcContext = alcCreateContext(AlcDevice, NULL);\n  if (!AlcContext || !alcMakeContextCurrent(AlcContext)) {\n    ImpLog(LogLevel::Fatal, LogChannel::Audio,\n           \"Failed to create OpenAL context\\n\");\n    alcCloseDevice(AlcDevice);\n    return false;\n  }\n  return true;\n}\n\nvoid AudioBackend::Shutdown() {\n  if (AlcContext) alcDestroyContext(AlcContext);\n  if (AlcDevice) alcCloseDevice(AlcDevice);\n}\n\n};  // namespace OpenAL\n};  // namespace Audio\n};  // namespace Impacto\n"
  },
  {
    "path": "src/audio/openal/audiobackend.h",
    "content": "#pragma once\n\n#include \"../audiobackend.h\"\n#include \"audiocommon.h\"\n\nnamespace Impacto {\nnamespace Audio {\nnamespace OpenAL {\n\nclass AudioBackend : public Audio::AudioBackend {\n public:\n  bool Init() override;\n\n  void Shutdown() override;\n};\n\n}  // namespace OpenAL\n}  // namespace Audio\n}  // namespace Impacto"
  },
  {
    "path": "src/audio/openal/audiocommon.h",
    "content": "#pragma once\n\n#include <AL/al.h>\n#include <AL/alc.h>\n\n#if IMPACTO_OPENAL_HAVE_ALEXT\n#include \"alext.h\"\n#else\n// emscripten has these but not alext.h /shrug\n#define AL_FORMAT_MONO_FLOAT32 0x10010\n#define AL_FORMAT_STEREO_FLOAT32 0x10011\n#endif\n\n#include \"../audiocommon.h\"\n"
  },
  {
    "path": "src/audio/openal/ffmpegaudioplayer.cpp",
    "content": "#include \"ffmpegaudioplayer.h\"\n#include \"../../video/ffmpegplayer.h\"\n#include \"../audiosystem.h\"\n#include <mutex>\n#include <avcpp/audioresampler.h>\n#include <avcpp/timestamp.h>\n\nextern \"C\" {\n#include <libavutil/avutil.h>\n#include <libavcodec/avcodec.h>\n#include <libavutil/time.h>\n#include <libswscale/swscale.h>\n#include <libswresample/swresample.h>\n#include <libavformat/avformat.h>\n};\n\nnamespace Impacto {\nnamespace Audio {\nnamespace OpenAL {\n\nFFmpegAudioPlayer::~FFmpegAudioPlayer() {\n  alSourceStop(ALSource);\n  av_freep(&AudioBuffer[0]);\n  swr_free(&AudioConvertContext);\n  alDeleteBuffers(AudioBufferCount, BufferIds);\n  alDeleteSources(1, &ALSource);\n}\n\nvoid FFmpegAudioPlayer::Init() {\n  alGenSources(1, &ALSource);\n  alSourcef(ALSource, AL_PITCH, 1);\n  alSourcef(ALSource, AL_GAIN, 1);\n  alSource3f(ALSource, AL_POSITION, 0, 0, 0);\n  alSource3f(ALSource, AL_VELOCITY, 0, 0, 0);\n  alSourcei(ALSource, AL_LOOPING, AL_FALSE);\n\n  alGenBuffers(AudioBufferCount, BufferIds);\n}\n\nvoid FFmpegAudioPlayer::InitConvertContext(AVCodecContext* codecCtx) {\n  AVChannelLayout stereoFormat;\n  av_channel_layout_default(&stereoFormat, 2);\n  int res = swr_alloc_set_opts2(&AudioConvertContext, &stereoFormat,\n                                AV_SAMPLE_FMT_S16, codecCtx->sample_rate,\n                                &stereoFormat, codecCtx->sample_fmt,\n                                codecCtx->sample_rate, 0, NULL);\n  if (res) {\n    ImpLog(LogLevel::Error, LogChannel::Video,\n           \"Could not create audio convert context!\\n\");\n    return;\n  }\n\n  swr_init(AudioConvertContext);\n  av_samples_alloc_array_and_samples(&AudioBuffer, &AudioLinesize,\n                                     codecCtx->ch_layout.nb_channels, 32000,\n                                     AV_SAMPLE_FMT_S16, 0);\n}\n\nvoid FFmpegAudioPlayer::Stop() { alSourceStop(ALSource); }\n\nvoid FFmpegAudioPlayer::Unload() {\n  Stop();\n  alSourcei(ALSource, AL_BUFFER, 0);\n  av_freep(&AudioBuffer[0]);\n  alDeleteSources(1, &ALSource);\n  alGenSources(1, &ALSource);\n  First = true;\n}\n\nvoid FFmpegAudioPlayer::FillAudioBuffers() {\n  while (FreeBufferCount && !Player->AbortRequest) {\n    int totalSize = 0;\n    bool firstFrame = true;\n    do {\n      Video::AVDecodedItem<AVMEDIA_TYPE_AUDIO> aFrame;\n      if (!Player->AudioStream->FrameQueue.try_dequeue(aFrame)) {\n        break;\n      }\n      if (aFrame.Serial == INT32_MIN) break;\n\n      if (firstFrame) {\n        BufferStartPositions[FirstFreeBuffer] =\n            (int)(aFrame.Frame.pts().timestamp() * aFrame.Frame.sampleRate() *\n                  Player->AudioStream->AvStream.timeBase().getNumerator() /\n                  Player->AudioStream->AvStream.timeBase().getDenominator());\n        firstFrame = false;\n      }\n\n      int64_t delay =\n          swr_get_delay(AudioConvertContext, aFrame.Frame.sampleRate());\n      int64_t samplesPerCh = av_rescale_rnd(\n          (int64_t)aFrame.Frame.samplesCount() + delay,\n          aFrame.Frame.sampleRate(), aFrame.Frame.sampleRate(), AV_ROUND_UP);\n      int outputSamples =\n          swr_convert(AudioConvertContext, AudioBuffer, (int)samplesPerCh,\n                      (const uint8_t**)aFrame.Frame.raw()->extended_data,\n                      aFrame.Frame.samplesCount());\n\n      int bufferSize = av_samples_get_buffer_size(NULL, 2, outputSamples,\n                                                  AV_SAMPLE_FMT_S16, 1);\n      memcpy(&HostBuffer[totalSize], AudioBuffer[0], bufferSize);\n      totalSize += bufferSize;\n    } while (totalSize <= 4096);\n\n    if (!First) {\n      alSourceUnqueueBuffers(ALSource, 1, &BufferIds[FirstFreeBuffer]);\n    }\n    FreeBufferCount--;\n\n    alBufferData(BufferIds[FirstFreeBuffer], AL_FORMAT_STEREO16, HostBuffer,\n                 totalSize, Player->AudioStream->CodecContext.sampleRate());\n\n    alSourceQueueBuffers(ALSource, 1, &BufferIds[FirstFreeBuffer]);\n\n    FirstFreeBuffer++;\n    FirstFreeBuffer %= AudioBufferCount;\n  }\n}\n\nvoid FFmpegAudioPlayer::Process() {\n  using namespace std::chrono_literals;\n  float gain = Audio::MasterVolume * Audio::GroupVolumes[Audio::ACG_Movie];\n  alSourcef(ALSource, AL_GAIN, gain);\n\n  if (Player->AudioStream->FrameQueue.peek() != nullptr) {\n    alGetSourcei(ALSource, AL_BUFFERS_PROCESSED, &FreeBufferCount);\n    if (First) {\n      FreeBufferCount = AudioBufferCount;\n    }\n\n    int currentlyPlayingBuffer =\n        (FirstFreeBuffer + FreeBufferCount) % AudioBufferCount;\n\n    int offset;\n    alGetSourcei(ALSource, AL_SAMPLE_OFFSET, &offset);\n    int samplePosition = BufferStartPositions[currentlyPlayingBuffer] + offset;\n    int sampleRate = Player->AudioStream->CodecContext.sampleRate();\n    samplePosition = std::min(samplePosition, Player->AudioStream->Duration);\n    auto audioTime = av::Timestamp(samplePosition, av::Rational(1, sampleRate));\n    auto audioS = audioTime.toDuration<Video::Clock::Microseconds>();\n    AudioClock.Set(audioS, 0);\n    ImpLogSlow(LogLevel::Trace, LogChannel::Video, \"samplePosition: {:f}\\n\",\n               std::chrono::duration<double>(audioS).count());\n\n    FillAudioBuffers();\n    ALint sourceState;\n    alGetSourcei(ALSource, AL_SOURCE_STATE, &sourceState);\n    if (sourceState == AL_STOPPED || sourceState == AL_INITIAL) {\n      alSourcePlay(ALSource);\n    }\n    First = false;\n  }\n}\n\n}  // namespace OpenAL\n}  // namespace Audio\n}  // namespace Impacto\n"
  },
  {
    "path": "src/audio/openal/ffmpegaudioplayer.h",
    "content": "#pragma once\n\n#include \"audiocommon.h\"\n\n#include \"../ffmpegaudioplayer.h\"\n\nnamespace Impacto {\nnamespace Audio {\nnamespace OpenAL {\n\nclass FFmpegAudioPlayer : public Audio::FFmpegAudioPlayer {\n public:\n  FFmpegAudioPlayer(Video::FFmpegPlayer* player)\n      : Audio::FFmpegAudioPlayer(player) {}\n  ~FFmpegAudioPlayer();\n\n  void Init() override;\n  void InitConvertContext(AVCodecContext* codecCtx) override;\n  void FillAudioBuffers() override;\n  void Process() override;\n\n  void Stop() override;\n  void Unload() override;\n\n private:\n  ALCuint ALSource;\n  static int constexpr AudioBufferSize = 256 * 8192;\n  static int constexpr AudioBufferCount = 3;\n  ALuint BufferIds[AudioBufferCount];\n  uint8_t HostBuffer[AudioBufferSize];\n  int FirstFreeBuffer = 0;\n  int FreeBufferCount = AudioBufferCount;\n  int BufferStartPositions[AudioBufferCount] = {};\n  bool First = true;\n};\n\n}  // namespace OpenAL\n}  // namespace Audio\n}  // namespace Impacto\n"
  },
  {
    "path": "src/audio/openal/openalaudiochannel.cpp",
    "content": "#include \"openalaudiochannel.h\"\n#include \"../audiosystem.h\"\n#include \"../audiostream.h\"\n\nnamespace Impacto {\nnamespace Audio {\nnamespace OpenAL {\n\nstatic ALenum ToALFormat(int channels, int bitdepth) {\n  if (channels == 2 && bitdepth == 16) return AL_FORMAT_STEREO16;\n  if (channels == 2 && bitdepth == 32) return AL_FORMAT_STEREO_FLOAT32;\n  if (channels == 1 && bitdepth == 16) return AL_FORMAT_MONO16;\n  if (channels == 1 && bitdepth == 32) return AL_FORMAT_MONO_FLOAT32;\n  if (channels == 2 && bitdepth == 8) return AL_FORMAT_STEREO8;\n  if (channels == 1 && bitdepth == 8) return AL_FORMAT_MONO8;\n\n  throw std::invalid_argument(fmt::format(\n      \"Unimplemented AL format with {} channels and a bit depth of {}\",\n      channels, bitdepth));\n}\n\nOpenALAudioChannel::OpenALAudioChannel(AudioChannelId id,\n                                       AudioChannelGroup group)\n    : AudioChannel(id, group),\n      HostBuffer(std::vector(AudioBufferSize, (uint8_t)0)) {\n  BufferStartPositions.fill(0);\n\n  alGenBuffers((ALsizei)AudioBuffers.size(), AudioBuffers.data());\n  alGenSources(1, &Source);\n\n  alSourcef(Source, AL_PITCH, 1);\n  alSource3f(Source, AL_POSITION, 0, 0, 0);\n  alSource3f(Source, AL_VELOCITY, 0, 0, 0);\n  alSourcei(Source, AL_LOOPING, AL_FALSE);\n  UpdateGain();\n}\n\nOpenALAudioChannel::~OpenALAudioChannel() {\n  alDeleteSources(1, &Source);\n  alDeleteBuffers((ALsizei)AudioBuffers.size(), AudioBuffers.data());\n}\n\nvoid OpenALAudioChannel::Play(std::unique_ptr<AudioStream> stream, bool loop,\n                              float fadeInDuration) {\n  EndPlayback();\n\n  Stream = std::move(stream);\n  FadeDuration = fadeInDuration;\n  FadeProgress = FadeDuration == 0 ? 1.0f : 0.0f;\n  State = FadeDuration == 0 ? ACS_Playing : ACS_FadingIn;\n  Looping = loop;\n\n  AudioSubtitlesStart(this);\n}\n\nvoid OpenALAudioChannel::Stop(float fadeOutDuration) {\n  if (State == ACS_Stopped) return;\n\n  if (fadeOutDuration == 0 || State == ACS_Paused) {\n    EndPlayback();\n    return;\n  }\n\n  const float baseGain = MasterVolume * GroupVolumes[Group] * Volume;\n  float currentGain;\n  alGetSourcef(Source, AL_GAIN, &currentGain);\n  FadeStartFactor = baseGain > 0.0f ? currentGain / baseGain : 0.0f;\n  State = ACS_FadingOut;\n  FadeDuration = fadeOutDuration;\n  FadeProgress = 0;\n}\n\nvoid OpenALAudioChannel::EndPlayback() {\n  alSourceStop(Source);\n\n  ALint queuedBuffers;\n  alGetSourcei(Source, AL_BUFFERS_QUEUED, &queuedBuffers);\n  UnqueueBuffers(queuedBuffers);\n\n  Stream = nullptr;\n  PlaybackStarted = false;\n  State = ACS_Stopped;\n  FadeProgress = 0;\n  FirstQueuedBufferIndex = 0;\n  BufferStartPositions.fill(0);\n}\n\nvoid OpenALAudioChannel::Pause() {\n  if (State != ACS_Playing && State != ACS_FadingIn && State != ACS_FadingOut) {\n    return;\n  }\n\n  State = ACS_Paused;\n}\n\nvoid OpenALAudioChannel::Resume() {\n  if (State != ACS_Paused) return;\n\n  // Should we fade here?\n  State = ACS_Playing;\n}\n\nfloat OpenALAudioChannel::PositionInSeconds() const {\n  if (Stream == nullptr) return 0.0f;\n\n  int offset;\n  alGetSourcei(Source, AL_SAMPLE_OFFSET, &offset);\n\n  size_t position = BufferStartPositions[FirstQueuedBufferIndex] + offset;\n  return (float)position / Stream->SampleRate;\n}\n\nvoid OpenALAudioChannel::Update(float dt) {\n  UpdateFade(dt);\n\n  if (State == ACS_Playing || State == ACS_FadingIn || State == ACS_FadingOut) {\n    // Continuously update the gain\n    // because the global state might have changed\n    UpdateGain();\n\n    ALint processedBuffers;\n    alGetSourcei(Source, AL_BUFFERS_PROCESSED, &processedBuffers);\n    UnqueueBuffers(processedBuffers);\n\n    // TODO do this on background thread\n    while (CanQueueBuffer()) {\n      int queuedBuffers;\n      alGetSourcei(Source, AL_BUFFERS_QUEUED, &queuedBuffers);\n\n      size_t firstFreeIndex =\n          (FirstQueuedBufferIndex + queuedBuffers) % AudioBuffers.size();\n      QueueBuffer(firstFreeIndex);\n    }\n  }\n\n  UpdatePlayback();\n}\n\nvoid OpenALAudioChannel::UpdateFade(float dt) {\n  if (State != ACS_FadingIn && State != ACS_FadingOut) return;\n\n  FadeProgress = std::min(1.0f, FadeProgress + dt / FadeDuration);\n  if (FadeProgress >= 1.0f) {\n    State = State == ACS_FadingIn ? ACS_Playing : ACS_Stopped;\n  }\n}\n\nvoid OpenALAudioChannel::UpdatePlayback() {\n  ALint alState;\n  ALint queued;\n  alGetSourcei(Source, AL_SOURCE_STATE, &alState);\n  alGetSourcei(Source, AL_BUFFERS_QUEUED, &queued);\n\n  // Stop playback when OpenAL finished processing but not when there's buffer\n  // underrun\n  if (alState == AL_STOPPED && queued == 0 && PlaybackStarted) {\n    EndPlayback();\n    return;\n  }\n\n  // Only propagate the latest state to OpenAL\n  // when the audio channel is updated to avoid \"blips\"\n  // when multiple state changes occur in one frame\n  if (alState == AL_PLAYING && State == ACS_Paused) {\n    alSourcePause(Source);\n  } else if (alState != AL_PLAYING &&\n             (State == ACS_Playing || State == ACS_FadingIn ||\n              State == ACS_FadingOut)) {\n    alSourcePlay(Source);\n    PlaybackStarted = true;\n  } else if ((alState == AL_PLAYING || alState == AL_PAUSED) &&\n             State == ACS_Stopped) {\n    EndPlayback();\n  }\n}\n\n// TODO what easing functions do we want for this?\nvoid OpenALAudioChannel::UpdateGain() {\n  float gain = MasterVolume * GroupVolumes[Group] * Volume;\n  switch (State) {\n    case ACS_Stopped:\n      return;\n    case ACS_Paused:\n    case ACS_Playing:\n      // Nothing\n      break;\n    case ACS_FadingIn:\n      gain *= powf(FadeProgress, 3.0f);\n      break;\n    case ACS_FadingOut:\n      gain *= FadeStartFactor * (1.0f - powf(FadeProgress, 3.0f));\n      break;\n  }\n\n  alSourcef(Source, AL_GAIN, gain);\n}\n\n// Unqueues `amount` buffers, starting at the first queued buffer\nvoid OpenALAudioChannel::UnqueueBuffers(size_t amount) {\n  for (size_t i = 0; i < amount; i++) {\n    size_t bufferIndex = FirstQueuedBufferIndex;\n    alSourceUnqueueBuffers(Source, 1, &AudioBuffers[bufferIndex]);\n    FirstQueuedBufferIndex = (bufferIndex + 1) % AudioBuffers.size();\n  }\n}\n\nbool OpenALAudioChannel::CanQueueBuffer() {\n  ALint queuedBuffers;\n  alGetSourcei(Source, AL_BUFFERS_QUEUED, &queuedBuffers);\n\n  if (queuedBuffers >= std::ssize(AudioBuffers)) return false;\n\n  return Looping || Stream->ReadPosition < Stream->Duration;\n}\n\nvoid OpenALAudioChannel::QueueBuffer(size_t bufferIndex) {\n  BufferStartPositions[bufferIndex] = Stream->ReadPosition;\n\n  int maxSamples = AudioBufferSize / Stream->BytesPerSample();\n  int samplesRead = 0;\n\n  while (samplesRead < maxSamples && Stream->ReadPosition < Stream->Duration) {\n    int end = Looping ? Stream->LoopEnd : Stream->Duration;\n    int samplesToRead =\n        std::min(maxSamples - samplesRead, end - Stream->ReadPosition);\n\n    uint8_t* dest = &HostBuffer[samplesRead * Stream->BytesPerSample()];\n    int samplesReadThisIteration = Stream->Read(dest, samplesToRead);\n    samplesRead += samplesReadThisIteration;\n\n    if (Looping && Stream->ReadPosition >= Stream->LoopEnd) {\n      Stream->Seek(Stream->LoopStart);\n    }\n\n    if (samplesReadThisIteration == 0) break;\n  }\n\n  ALuint buffer = AudioBuffers[bufferIndex];\n  ALenum format = ToALFormat(Stream->ChannelCount, Stream->BitDepth);\n\n  alBufferData(buffer, format, HostBuffer.data(),\n               samplesRead * Stream->BytesPerSample(), Stream->SampleRate);\n  alSourceQueueBuffers(Source, 1, &buffer);\n}\n\n}  // namespace OpenAL\n}  // namespace Audio\n}  // namespace Impacto"
  },
  {
    "path": "src/audio/openal/openalaudiochannel.h",
    "content": "#pragma once\n\n#include \"audiocommon.h\"\n#include \"../audiochannel.h\"\n\nnamespace Impacto {\nnamespace Audio {\nnamespace OpenAL {\n\nclass OpenALAudioChannel : public Audio::AudioChannel {\n public:\n  OpenALAudioChannel(AudioChannelId id, AudioChannelGroup group);\n  ~OpenALAudioChannel();\n\n  void Play(std::unique_ptr<AudioStream> stream, bool loop,\n            float fadeInDuration) override;\n  void Stop(float fadeOutDuration) override;\n  void Pause() override;\n  void Resume() override;\n\n  void Update(float dt) override;\n\n  float PositionInSeconds() const override;\n\n private:\n  static int constexpr AudioBufferSize = 64 * 1024;\n  static int constexpr AudioBufferCount = 3;\n\n  ALCuint Source = 0;\n  std::array<ALuint, AudioBufferCount> AudioBuffers;\n  std::array<size_t, AudioBufferCount> BufferStartPositions;\n  std::vector<uint8_t> HostBuffer;\n\n  size_t FirstQueuedBufferIndex = 0;\n\n  float FadeDuration = 0;\n  float FadeProgress = 0;\n  float FadeStartFactor = 0;\n\n  bool PlaybackStarted = false;\n\n  void EndPlayback();\n  void UpdateGain();\n  void UpdateFade(float dt);\n  void UnqueueBuffers(size_t amount);\n  bool CanQueueBuffer();\n  void QueueBuffer(size_t bufferIndex);\n  void UpdatePlayback();\n};\n\n}  // namespace OpenAL\n}  // namespace Audio\n}  // namespace Impacto"
  },
  {
    "path": "src/audio/vorbisaudiostream.cpp",
    "content": "#include \"vorbisaudiostream.h\"\n#include \"../log.h\"\n#include <algorithm>\n\nusing namespace Impacto::Io;\n\nnamespace Impacto {\nnamespace Audio {\n\nstatic size_t OvRwRead(void* ptr, size_t size, size_t nmemb, void* datasource) {\n  return ((Stream*)datasource)->Read(ptr, size * nmemb) / size;\n}\nstatic int OvRwSeek(void* datasource, ogg_int64_t offset, int whence) {\n  return (long)(((Stream*)datasource)->Seek(offset, whence));\n}\nstatic long OvRwTell(void* datasource) {\n  return (long)(((Stream*)datasource)->Position);\n}\nov_callbacks OvRwCallbacks = {OvRwRead, OvRwSeek, NULL, OvRwTell};\n\nAudioStream* VorbisAudioStream::Create(Stream* stream) {\n  VorbisAudioStream* result = 0;\n  OggVorbis_File Vf;\n  bool VfOpen = false;\n\n  vorbis_info* info;\n\n  int err = ov_open_callbacks(stream, &Vf, NULL, 0, OvRwCallbacks);\n  if (err < 0) {\n    goto fail;\n  }\n  VfOpen = true;\n  ImpLog(LogLevel::Info, LogChannel::Audio, \"Creating Vorbis stream\\n\");\n\n  info = ov_info(&Vf, -1);\n  if (info->channels != 1 && info->channels != 2) {\n    ImpLog(LogLevel::Error, LogChannel::Audio,\n           \"Got Vorbis file with unsupported channel count {:d}\\n\",\n           info->channels);\n    goto fail;\n  }\n\n  result = new VorbisAudioStream;\n  result->BaseStream = stream;\n  memcpy(&result->Vf, &Vf, sizeof(Vf));\n  result->VfOpen = true;\n\n  result->InitWithInfo(info);\n\n  return result;\n\nfail:\n  if (VfOpen) {\n    ov_clear(&Vf);\n  }\n  if (result) {\n    result->VfOpen = false;\n    result->BaseStream = 0;\n    delete result;\n  }\n  stream->Seek(0, RW_SEEK_SET);\n  return 0;\n}\n\nvoid VorbisAudioStream::InitWithInfo(vorbis_info* info) {\n  Duration = (int)ov_pcm_total(&Vf, -1);\n  LoopEnd = Duration;\n  ChannelCount = info->channels;\n\n  vorbis_comment* comments = ov_comment(&Vf, -1);\n  int looplength = 0;\n  for (int i = 0; i < comments->comments; i++) {\n    if (strncmp(comments->user_comments[i], \"LOOPSTART\",\n                std::min(comments->comment_lengths[i],\n                         (int)strlen(\"LOOPSTART\"))) == 0) {\n      LoopStart = atoi(comments->user_comments[i] + strlen(\"LOOPSTART\") + 1);\n    }\n    /*\n    // Not actually implemented in the reference...\n    if (strncmp(comments->user_comments[i], \"LOOPLENGTH\",\n                std::min(comments->comment_lengths[i],\n                         (int)strlen(\"LOOPLENGTH\"))) == 0) {\n      looplength = atoi(comments->user_comments[i] + strlen(\"LOOPLENGTH\") + 1);\n    }\n    */\n  }\n  if (looplength > 0) {\n    LoopEnd = LoopStart + looplength;\n  }\n\n  SampleRate = info->rate;\n  BitDepth = 16;\n}\n\nVorbisAudioStream::~VorbisAudioStream() {\n  if (VfOpen) {\n    ov_clear(&Vf);\n  }\n}\n\nint VorbisAudioStream::Read(void* buffer, int samples) {\n  int current_section;\n  int totalSamplesRead = 0;\n  while (samples > 0) {\n    long ret = ov_read(\n        &Vf, (char*)buffer, samples * (BitDepth / 8) * ChannelCount,\n        (SDL_BYTEORDER == SDL_BIG_ENDIAN), (BitDepth / 8), 1, &current_section);\n    if (ret <= 0) {\n      if (ret < 0) {\n        ImpLog(LogLevel::Error, LogChannel::Audio,\n               \"ov_read {:d} samples at {:d} failed with code {:d}\\n\", samples,\n               ReadPosition, ret);\n      }\n      // else ret == 0 => eof\n      break;\n    } else {\n      buffer = (char*)buffer + ret;\n      int samplesRead = ret / ((BitDepth / 8) * ChannelCount);\n      totalSamplesRead += samplesRead;\n      samples -= samplesRead;\n    }\n  }\n  ReadPosition += totalSamplesRead;\n  return totalSamplesRead;\n}\n\nvoid VorbisAudioStream::Seek(int samples) {\n  ov_pcm_seek(&Vf, samples);\n  ReadPosition = (int)ov_pcm_tell(&Vf);\n}\n\nbool VorbisAudioStream::_registered =\n    AudioStream::AddAudioStreamCreator(&VorbisAudioStream::Create);\n\n}  // namespace Audio\n}  // namespace Impacto"
  },
  {
    "path": "src/audio/vorbisaudiostream.h",
    "content": "#pragma once\n\n#include \"audiostream.h\"\n#include \"../impacto.h\"\n#include <vorbis/vorbisfile.h>\n\nnamespace Impacto {\nnamespace Audio {\n\nclass VorbisAudioStream : public AudioStream {\n public:\n  ~VorbisAudioStream();\n\n  int Read(void* buffer, int samples) override;\n  void Seek(int samples) override;\n\n  bool VfOpen = false;\n\n private:\n  static AudioStream* Create(Io::Stream* stream);\n  VorbisAudioStream() {}\n  void InitWithInfo(vorbis_info* info);\n\n  OggVorbis_File Vf;\n\n  static bool _registered;\n};\n\n}  // namespace Audio\n}  // namespace Impacto"
  },
  {
    "path": "src/background2d.cpp",
    "content": "#include \"background2d.h\"\n\n#include <ranges>\n#include <numeric>\n#include <glm/ext/quaternion_exponential.hpp>\n\n#include \"mask2d.h\"\n#include \"mem.h\"\n#include \"io/memorystream.h\"\n#include \"io/vfs.h\"\n#include \"util.h\"\n#include \"profile/scriptvars.h\"\n#include \"profile/vm.h\"\n// #include \"window.h\"\n#include \"renderer/renderer.h\"\n#include \"effects/wave.h\"\n\nnamespace Impacto {\n\nusing namespace Impacto::Profile::ScriptVars;\nusing namespace Impacto::Profile::Vm;\n\nvoid Background2D::InitFrameBuffers() {\n  [[maybe_unused]] RectF viewPort = Window->GetViewport();\n  for (size_t i = 0; i < Framebuffers.max_size(); i++) {\n    Framebuffers[i].BgSprite =\n        Sprite(SpriteSheet(Profile::DesignWidth, Profile::DesignHeight), 0.0f,\n               0.0f, Profile::DesignWidth, Profile::DesignHeight);\n    Framebuffers[i].BgSprite.Sheet.Texture =\n        Renderer->GetFramebufferTexture(i + 1);\n\n    Framebuffers[i].Status = LoadStatus::Loaded;\n    Framebuffers[i].BgSprite.Sheet.IsScreenCap = true;\n  }\n}\n\nvoid Background2D::Init() {\n  for (int i = 0; i < std::ssize(Backgrounds); i++) {\n    Backgrounds2D[i] = &Backgrounds[i];\n  }\n\n  const auto initCapture = [](Capture2D& capture) {\n    capture.BgSprite.Sheet.IsScreenCap = true;\n    capture.LoadSolidColor(0xFF000000, Window->WindowWidth,\n                           Window->WindowHeight);\n    capture.Status = LoadStatus::Loaded;\n  };\n  for (Capture2D& capture : Screencaptures) initCapture(capture);\n  initCapture(MaskCapture);\n\n  ShaderScreencapture.BgSprite.Sheet.IsScreenCap = true;\n  InitFrameBuffers();\n\n  for (size_t i = 0; i < Framebuffers.max_size(); i++) {\n    Framebuffers[i].BgSprite =\n        Sprite(SpriteSheet(Profile::DesignWidth, Profile::DesignHeight), 0.0f,\n               0.0f, Profile::DesignWidth, Profile::DesignHeight);\n    Framebuffers[i].BgSprite.Sheet.Texture =\n        Renderer->GetFramebufferTexture(i + 1);\n\n    Framebuffers[i].Status = LoadStatus::Loaded;\n    Framebuffers[i].BgSprite.Sheet.IsScreenCap = true;\n  }\n\n  ShaderScreencapture.LoadSolidColor(0xFF000000, Window->WindowWidth,\n                                     Window->WindowHeight);\n  ShaderScreencapture.Status = LoadStatus::Loaded;\n}\n\nbool Background2D::LoadSync(uint32_t bgId) {\n  bool error = false;\n\n  if (bgId & 0xFF000000) {\n    BgTexture.Load1x1((bgId >> 16) & 0xFF, (bgId >> 8) & 0xFF, bgId & 0xFF,\n                      0xFF);\n  } else {\n    Io::Stream* stream;\n    int64_t err = Io::VfsOpen(\"bg\", bgId, &stream);\n\n    if (err != IoError_OK) {\n      error = true;\n    } else {\n      BgTexture.Load(stream);\n      delete stream;\n    }\n\n    if (Profile::UseBgFrameEffects && BgEffTextureIdMap.contains(bgId)) {\n      std::for_each(FrameBgEffs.begin(), FrameBgEffs.end(),\n                    [](auto& bgEff) { bgEff.Loaded = false; });\n\n      const std::array<int, 4>& textureIds = BgEffTextureIdMap[bgId];\n\n      for (size_t i = 0; i < FrameBgEffs.size(); i++) {\n        if (textureIds[i] == 0) continue;\n\n        Io::Stream* bgEffStream;\n        err = Io::VfsOpen(\"bgeffect\", textureIds[i], &bgEffStream);\n\n        if (err != IoError::IoError_OK) {\n          error = true;\n        } else {\n          FrameBgEffs[i].BgEffTexture.Load(bgEffStream);\n          FrameBgEffs[i].Loaded = true;\n\n          delete bgEffStream;\n        }\n      }\n    }\n\n    if (Profile::UseBgChaEffects && BgEffTextureIdMap.contains(bgId)) {\n      ChaBgEff.Loaded = false;\n\n      const int textureId = BgEffTextureIdMap[bgId][3];\n      if (textureId != 0) {\n        Io::Stream* bgEffStream;\n        err = Io::VfsOpen(\"bgeffect\", textureId, &bgEffStream);\n\n        if (err != IoError::IoError_OK) {\n          error = true;\n        } else {\n          ChaBgEff.BgEffTexture.Load(bgEffStream);\n          ChaBgEff.Loaded = true;\n\n          delete bgEffStream;\n        }\n      }\n    }\n  }\n\n  return !error;\n}\n\nvoid Background2D::LoadSolidColor(uint32_t color, int width, int height) {\n  BgTexture.LoadSolidColor(width, height, color);\n  MainThreadOnLoad(true);\n}\n\nvoid Background2D::UnloadSync() {\n  Renderer->FreeTexture(BgSprite.Sheet.Texture);\n  BgSprite.Sheet.DesignHeight = 0.0f;\n  BgSprite.Sheet.DesignWidth = 0.0f;\n  BgSprite.Sheet.Texture = 0;\n  BgSprite.Sheet.IsScreenCap = false;\n\n  if (Profile::UseBgFrameEffects) {\n    for (BgEff& bgEff : FrameBgEffs) {\n      bgEff.Loaded = false;\n\n      Renderer->FreeTexture(bgEff.BgEffSprite.Sheet.Texture);\n      bgEff.BgEffSprite.Sheet.DesignHeight = 0.0f;\n      bgEff.BgEffSprite.Sheet.DesignWidth = 0.0f;\n      bgEff.BgEffSprite.Sheet.Texture = 0;\n    }\n  }\n\n  if (Profile::UseBgChaEffects) {\n    ChaBgEff.Loaded = false;\n\n    Renderer->FreeTexture(ChaBgEff.BgEffSprite.Sheet.Texture);\n    ChaBgEff.BgEffSprite.Sheet.DesignHeight = 0.0f;\n    ChaBgEff.BgEffSprite.Sheet.DesignWidth = 0.0f;\n    ChaBgEff.BgEffSprite.Sheet.Texture = 0;\n  }\n\n  Show = false;\n\n  std::fill(Layers.begin(), Layers.end(), -1);\n}\n\nvoid Background2D::MainThreadOnLoad(bool result) {\n  BgSprite.Sheet.Texture = BgTexture.Submit();\n\n  if ((BgTexture.Width == 1) && (BgTexture.Height == 1)) {\n    BgSprite.Sheet.DesignWidth = Profile::DesignWidth;\n    BgSprite.Sheet.DesignHeight = Profile::DesignHeight;\n  } else {\n    BgSprite.Sheet.DesignWidth = (float)BgTexture.Width;\n    BgSprite.Sheet.DesignHeight = (float)BgTexture.Height;\n  }\n\n  BgSprite.BaseScale = glm::vec2(1.0f);\n  BgSprite.Bounds = RectF(0.0f, 0.0f, BgSprite.Sheet.DesignWidth,\n                          BgSprite.Sheet.DesignHeight);\n\n  if (Profile::UseBgFrameEffects) {\n    for (BgEff& bgEff : FrameBgEffs) {\n      if (!bgEff.Loaded) continue;\n\n      bgEff.BgEffSprite.Sheet.Texture = bgEff.BgEffTexture.Submit();\n\n      bgEff.BgEffSprite.Sheet.DesignWidth = (float)bgEff.BgEffTexture.Width;\n      bgEff.BgEffSprite.Sheet.DesignHeight = (float)bgEff.BgEffTexture.Height;\n\n      bgEff.BgEffSprite.Bounds =\n          RectF(0.0f, 0.0f, bgEff.BgEffSprite.Sheet.DesignWidth,\n                bgEff.BgEffSprite.Sheet.DesignHeight);\n    }\n  }\n\n  if (Profile::UseBgChaEffects && ChaBgEff.Loaded) {\n    ChaBgEff.BgEffSprite.Sheet.Texture = ChaBgEff.BgEffTexture.Submit();\n\n    ChaBgEff.BgEffSprite.Sheet.DesignWidth = (float)ChaBgEff.BgEffTexture.Width;\n    ChaBgEff.BgEffSprite.Sheet.DesignHeight =\n        (float)ChaBgEff.BgEffTexture.Height;\n\n    ChaBgEff.BgEffSprite.Bounds =\n        RectF(0.0f, 0.0f, ChaBgEff.BgEffSprite.Sheet.DesignWidth,\n              ChaBgEff.BgEffSprite.Sheet.DesignHeight);\n  }\n\n  Show = false;\n\n  std::fill(Layers.begin(), Layers.end(), -1);\n}\n\nvoid Background2D::LinkBuffers(const int linkCode, const int currentBgId) {\n  const int srcBufId = (linkCode >> 8) & 0xFF;\n  if (GetBufferId(srcBufId) != currentBgId) return;\n\n  const LinkDirection dir = static_cast<LinkDirection>((linkCode >> 16) & 0xFF);\n\n  for (int i = 0; i < MaxLinkedBgBuffers; i++) {\n    int childBufId = (linkCode >> (i * 24)) & 0xFF;\n    if (childBufId == 0) continue;\n    childBufId = GetBufferId(childBufId);\n\n    Background2D* const childBuf =\n        Backgrounds2D[ScrWork[SW_BG1SURF + childBufId]];\n    if (childBuf == nullptr) continue;\n\n    childBuf->RenderSprite.BaseScale = RenderSprite.BaseScale;\n\n    Links[i].LinkedBuffer = childBuf;\n    Links[i].Direction = dir;\n\n    constexpr size_t overlapPixelCt = 2;\n    switch (dir) {\n      case LinkDirection::Up3: {\n        float offset =\n            i == 0 ? 0.0f\n                   : Links[i - 1].LinkedBuffer->RenderSprite.ScaledHeight();\n        Links[i].DisplayCoords =\n            TransformState.Position -\n            glm::vec2(0.0f, RenderSprite.ScaledHeight() + offset);\n      } break;\n\n      case LinkDirection::Up: {\n        Links[i].DisplayCoords =\n            TransformState.Position -\n            glm::vec2(0.0f, RenderSprite.ScaledHeight() - overlapPixelCt);\n      } break;\n\n      case LinkDirection::Down3: {\n        float offset =\n            i == 0 ? 0.0f\n                   : Links[i - 1].LinkedBuffer->RenderSprite.ScaledHeight();\n        Links[i].DisplayCoords =\n            TransformState.Position +\n            glm::vec2(0.0f, RenderSprite.ScaledHeight() + offset);\n      } break;\n\n      case LinkDirection::Down: {\n        Links[i].DisplayCoords =\n            TransformState.Position +\n            glm::vec2(0.0f, RenderSprite.ScaledHeight() - overlapPixelCt);\n      } break;\n\n      case LinkDirection::Left: {\n        Links[i].DisplayCoords =\n            TransformState.Position -\n            glm::vec2(RenderSprite.ScaledWidth() - overlapPixelCt, 0.0f);\n      } break;\n\n      case LinkDirection::Right: {\n        Links[i].DisplayCoords =\n            TransformState.Position +\n            glm::vec2(RenderSprite.ScaledWidth() - overlapPixelCt, 0.0f);\n      } break;\n\n      case LinkDirection::Off:\n        break;\n    }\n  }\n}\n\nBackground2D::BgTransformState\nBackground2D::BgTransformState::GetBgTransformState(int bgId) {\n  assert(ScrWorkBgStructSize >= 20);\n  assert(ScrWorkBgOffsetStructSize >= 10);\n\n  const glm::vec2 resolutionScale = {Profile::DesignWidth / 1280.0f,\n                                     Profile::DesignHeight / 720.0f};\n\n  const std::span<const int> bgStruct =\n      std::span(ScrWork.begin() + ScrWorkBgStructSize * bgId, ScrWork.end());\n  const std::span<const int> bgOfsStruct = std::span(\n      ScrWork.begin() + ScrWorkBgOffsetStructSize * bgId, ScrWork.end());\n\n  BgTransformState state;\n\n  state.Position =\n      glm::vec2(bgStruct[SW_BG1POSX] + bgOfsStruct[SW_BG1POSX_OFS],\n                bgStruct[SW_BG1POSY] + bgOfsStruct[SW_BG1POSY_OFS]) *\n      resolutionScale;\n\n  state.Scale = glm::vec2(static_cast<float>(bgStruct[SW_BG1SIZE] +\n                                             bgOfsStruct[SW_BG1SIZE_OFS])) /\n                1000.0f;\n\n  state.Rotation = ScrWorkAngleZToQuaternion(\n      Profile::Vm::ScrWorkBgStructSize >= 40\n          ? bgStruct[SW_BG1ROTZ] + bgOfsStruct[SW_BG1ROTZ_OFS]\n          : bgStruct[SW_BG1ROTZ]);\n\n  state.Subsection =\n      RectF(static_cast<float>(bgStruct[SW_BG1SX] + bgOfsStruct[SW_BG1SX_OFS]),\n            static_cast<float>(bgStruct[SW_BG1SY] + bgOfsStruct[SW_BG1SY_OFS]),\n            static_cast<float>(bgStruct[SW_BG1LX] + bgOfsStruct[SW_BG1LX_OFS]),\n            static_cast<float>(bgStruct[SW_BG1LY] + bgOfsStruct[SW_BG1LY_OFS]))\n          .Scale(resolutionScale, glm::vec2(0.0f));\n\n  return state;\n}\n\nBackground2D::BgTransformState\nBackground2D::BgTransformState::GetCapTransformState(int capId) {\n  assert(ScrWorkCaptureStructSize >= 20);\n  assert(ScrWorkCaptureOffsetStructSize >= 10);\n\n  const glm::vec2 resolutionScale = {Profile::DesignWidth / 1280.0f,\n                                     Profile::DesignHeight / 720.0f};\n\n  const std::span<const int> capStruct = std::span(\n      ScrWork.begin() + ScrWorkCaptureStructSize * capId, ScrWork.end());\n  const std::span<const int> capOfsStruct = std::span(\n      ScrWork.begin() + ScrWorkCaptureOffsetStructSize * capId, ScrWork.end());\n\n  BgTransformState state;\n\n  state.Position =\n      glm::vec2(capStruct[SW_CAP1POSX] + capOfsStruct[SW_CAP1POSX_OFS],\n                capStruct[SW_CAP1POSY] + capOfsStruct[SW_CAP1POSY_OFS]) *\n      resolutionScale;\n\n  state.Scale = glm::vec2(static_cast<float>(capStruct[SW_CAP1SIZE] +\n                                             capOfsStruct[SW_CAP1SIZE_OFS])) /\n                1000.0f;\n\n  state.Rotation = ScrWorkAngleZToQuaternion(\n      Profile::Vm::ScrWorkBgEffOffsetStructSize >= 20\n          ? capStruct[SW_CAP1ROTZ] + capOfsStruct[SW_CAP1ROTZ_OFS]\n          : capStruct[SW_CAP1ROTZ]);\n\n  state.Subsection = RectF(static_cast<float>(capStruct[SW_CAP1SX] +\n                                              capOfsStruct[SW_CAP1SX_OFS]),\n                           static_cast<float>(capStruct[SW_CAP1SY] +\n                                              capOfsStruct[SW_CAP1SY_OFS]),\n                           static_cast<float>(capStruct[SW_CAP1LX] +\n                                              capOfsStruct[SW_CAP1LX_OFS]),\n                           static_cast<float>(capStruct[SW_CAP1LY] +\n                                              capOfsStruct[SW_CAP1LY_OFS]))\n                         .Scale(resolutionScale, glm::vec2(0.0f));\n\n  return state;\n}\n\nvoid Background2D::SetTransformState(int dispMode, BgTransformState state) {\n  TransformState = BgTransformState();\n\n  const bool newEngine = ScrWorkBgStructSize >= 40;\n\n  const auto scaleToFullscreen = [&]() {\n    TransformState.Scale =\n        glm::vec2(Profile::DesignWidth, Profile::DesignHeight) /\n        RenderSprite.ScaledBounds().GetSize();\n  };\n\n  switch (dispMode) {\n    case 0: {\n      const glm::vec2 scaledPos = state.Position / RenderSprite.BaseScale;\n      const glm::vec2 newDimensions = RenderSprite.Bounds.GetSize() - scaledPos;\n      RenderSprite.Bounds.SetPos(scaledPos);\n      RenderSprite.Bounds.SetSize(newDimensions);\n    } break;\n\n    case 1: {\n      const RectF scaledSubsection = state.Subsection.Scale(\n          glm::vec2(1.0f) / RenderSprite.BaseScale, glm::vec2(0.0f));\n      RenderSprite.Bounds =\n          RectF::Intersection(RenderSprite.Bounds, scaledSubsection);\n\n      if (newEngine) scaleToFullscreen();\n    } break;\n\n    case 2: {\n      TransformState.Scale = state.Scale;\n\n      if (newEngine) {\n        TransformState.Position = state.Position - RenderSprite.Center();\n        TransformState.Origin = RenderSprite.Center();\n      } else {\n        TransformState.Position =\n            glm::vec2(Profile::DesignWidth, Profile::DesignHeight) / 2.0f -\n            state.Position * TransformState.Scale;\n      }\n    } break;\n\n    case 3: {\n      const RectF scaledSubsection = state.Subsection.Scale(\n          glm::vec2(1.0f) / RenderSprite.BaseScale, glm::vec2(0.0f));\n      RenderSprite.Bounds =\n          RectF::Intersection(RenderSprite.Bounds, scaledSubsection);\n\n      if (newEngine) {\n        TransformState.Position = state.Position;\n      } else {\n        scaleToFullscreen();\n      }\n    } break;\n\n    case 4: {\n      TransformState.Position = -state.Position / 1000.0f;\n    } break;\n\n    case 6: {\n      TransformState.Origin = RenderSprite.Center();\n      TransformState.Position = state.Position - TransformState.Origin;\n      TransformState.Scale = state.Scale;\n      TransformState.Rotation = state.Rotation;\n    } break;\n\n    default: {\n      RenderSprite.Bounds = RectF(0.0f, 0.0f, 0.0f, 0.0f);\n    } break;\n  }\n}\n\nvoid Background2D::UpdateState(const int bgId) {\n  const size_t structOffset = ScrWorkBgStructSize * bgId;\n  const size_t structOfsOffset = ScrWorkBgOffsetStructSize * bgId;\n\n  Layers = {ScrWork[SW_BG1PRI + structOffset],\n            ScrWork[SW_BG1PRI2 + structOffset]};\n  Show = GetFlag(SF_BG1DISP + bgId);\n  RenderType = ScrWork[SW_BG1FADETYPE + structOffset];\n\n  MaskNumber = ScrWork[SW_BG1MASKNO + structOffset];\n  FadeRange = ScrWork[SW_BG1MASKFADERANGE + structOffset];\n  FadeCount = ScrWork[SW_BG1FADECT + structOffset];\n\n  RenderSprite = BgSprite;\n  for (int i = static_cast<int>(Profile::ScreenCaptureCount) - 1; i >= 0; i--) {\n    const int capOffset = i * Profile::Vm::ScrWorkCaptureEffectInfoStructSize;\n    if (ScrWork[SW_EFF_CAP_BUF + capOffset] == GetScriptBufferId(bgId + 1)) {\n      RenderSprite = Screencaptures[i].BgSprite;\n    }\n  }\n\n  SetTransformState(ScrWork[SW_BG1DISPMODE + structOffset],\n                    BgTransformState::GetBgTransformState(bgId));\n\n  switch (GameInstructionSet) {\n    case Vm::InstructionSet::CC:\n      Tint = ScrWorkGetColor(SW_BG1FILTER + structOffset);\n      break;\n\n    default:\n      Tint = glm::vec4(1.0f);\n      break;\n  }\n  Tint.a = (ScrWork[SW_BG1ALPHA + structOffset] +\n            ScrWork[SW_BG1ALPHA_OFS + structOfsOffset]) /\n           256.0f;\n\n  if (Show) {\n    if (ScrWork[SW_BGLINK]) {\n      LinkBuffers(ScrWork[SW_BGLINK], bgId);\n    } else {\n      Links[0].Direction = LinkDirection::Off;\n      Links[0].LinkedBuffer = nullptr;\n    }\n    if (ScrWork[SW_BGLINK2]) {\n      LinkBuffers(ScrWork[SW_BGLINK2], bgId);\n    } else {\n      Links[1].Direction = LinkDirection::Off;\n      Links[1].LinkedBuffer = nullptr;\n    }\n  } else {\n    for (int i = 0; i < MaxLinkedBgBuffers; i++) {\n      Links[i].Direction = LinkDirection::Off;\n      Links[i].LinkedBuffer = nullptr;\n    }\n  }\n\n  if (Profile::UseBgChaEffects || Profile::UseBgFrameEffects) {\n    BgEffsLayers = {ScrWork[SW_BG1EFFPRI + structOffset],\n                    ScrWork[SW_BG1EFFPRI2 + structOffset]};\n  }\n\n  if (Profile::UseBgFrameEffects) {\n    const int bgNo = ScrWork[SW_BG1NO + structOffset];\n    for (size_t bgEffId = 0; bgEffId < FrameBgEffs.size(); bgEffId++) {\n      FrameBgEffs[bgEffId].Shader = GetBgEffShader(bgNo, bgEffId);\n    }\n  }\n\n  if (Profile::UseBgChaEffects) {\n    ChaBgEff.Shader = GetBgEffShader(ScrWork[SW_BG1NO + structOffset], 3);\n  }\n}\n\nvoid Background2D::Render(const int layer) {\n  if (!Show || !OnLayer(layer) ||\n      (Status != LoadStatus::Loaded &&\n       RenderSprite.Sheet.Texture == BgSprite.Sheet.Texture)) {\n    return;\n  }\n\n  if (Profile::UseBgChaEffects || Profile::UseBgFrameEffects) {\n    bool renderBgEffs = false;\n\n    for (size_t i = 0; i < BgEffsLayers.size(); i++) {\n      renderBgEffs |= BgEffsLayers[i] != 0 && BgEffsLayers[i] > Layers[i];\n    }\n\n    if (renderBgEffs) {\n      bool nonSpriteShader = false;\n\n      if (Profile::UseBgFrameEffects) {\n        nonSpriteShader |= std::any_of(\n            FrameBgEffs.begin(), FrameBgEffs.end() - 1, [](const auto bgEff) {\n              return bgEff.Shader != ShaderProgramType::Sprite;\n            });\n      }\n\n      if (Profile::UseBgChaEffects) {\n        nonSpriteShader |= ChaBgEff.Shader != ShaderProgramType::Sprite;\n      }\n\n      if (!nonSpriteShader) renderBgEffs = false;\n    }\n\n    LastRenderedBackground = renderBgEffs ? this : nullptr;\n  }\n\n  std::invoke(BackgroundRenderTable[RenderType], this);\n}\n\nvoid Background2D::RenderBgEff(const int layer) {\n  if (!Profile::UseBgFrameEffects || layer == 0 ||\n      std::find(BgEffsLayers.begin(), BgEffsLayers.end(), layer) ==\n          BgEffsLayers.end()) {\n    return;\n  }\n\n  static Sprite frameSprite{};\n  if (frameSprite.Sheet.Texture == 0) {\n    Texture frameTexture{};\n    frameTexture.LoadSolidColor(Window->WindowWidth, Window->WindowHeight,\n                                0x00000000);\n    frameSprite.Sheet.Texture = frameTexture.Submit();\n    // I am aware this leaks a texture at shutdown\n  }\n\n  const std::array<VertexBufferSprites, 4> vertices{\n      VertexBufferSprites{\n          .Position = {0.0f, Profile::DesignHeight},\n          .UV = {0.0f, 1.0f},\n          .MaskUV = {0.0f, 0.0f},\n      },\n      VertexBufferSprites{\n          .Position = {0.0f, 0.0f},\n          .UV = {0.0f, 0.0f},\n          .MaskUV = {0.0f, 1.0f},\n      },\n      VertexBufferSprites{\n          .Position = {Profile::DesignWidth, 0.0f},\n          .UV = {1.0f, 0.0f},\n          .MaskUV = {1.0f, 1.0f},\n      },\n      VertexBufferSprites{\n          .Position = {Profile::DesignWidth, Profile::DesignHeight},\n          .UV = {1.0f, 1.0f},\n          .MaskUV = {1.0f, 0.0f},\n      },\n  };\n  constexpr std::array<uint16_t, 6> indices = {0, 1, 2, 0, 2, 3};\n\n  // Render in reverse order\n  for (auto bgEff = FrameBgEffs.crbegin(); bgEff != FrameBgEffs.crend();\n       bgEff++) {\n    if (!bgEff->Loaded) continue;\n\n    Renderer->CaptureScreencap(frameSprite);\n    Renderer->DrawPrimitives(frameSprite.Sheet, &bgEff->BgEffSprite.Sheet,\n                             bgEff->Shader, vertices, indices);\n  }\n\n  LastRenderedBackground = nullptr;\n}\n\nvoid Capture2D::UpdateState(const int capId) {\n  const size_t structOffset = ScrWorkCaptureStructSize * capId;\n  const size_t structOfsOffset = ScrWorkCaptureOffsetStructSize * capId;\n\n  Layers = {ScrWork[SW_CAP1PRI + structOffset],\n            ScrWork[SW_CAP1PRI2 + structOffset]};\n  Show = GetFlag(SF_CAP1DISP + capId);\n  RenderType = ScrWork[SW_CAP1FADETYPE + structOffset];\n\n  MaskNumber = ScrWork[SW_CAP1MASKNO + structOffset];\n  FadeRange = ScrWork[SW_CAP1MASKFADERANGE + structOffset];\n  FadeCount = ScrWork[SW_CAP1FADECT + structOffset];\n\n  SetTransformState(ScrWork[SW_CAP1DISPMODE + structOffset],\n                    BgTransformState::GetCapTransformState(capId));\n\n  switch (GameInstructionSet) {\n    case Vm::InstructionSet::CC:\n      Tint = ScrWorkGetColor(SW_CAP1FILTER + structOffset);\n      break;\n\n    default:\n      Tint = glm::vec4(1.0f);\n      break;\n  }\n  Tint.a = (ScrWork[SW_CAP1ALPHA + structOffset] +\n            ScrWork[SW_CAP1ALPHA_OFS + structOfsOffset]) /\n           256.0f;\n}\n\nvoid Capture2D::Render(const int layer) {\n  if (Status != LoadStatus::Loaded || !OnLayer(layer) || !Show) {\n    return;\n  }\n\n  std::invoke(BackgroundRenderTable[RenderType], this);\n}\n\nBackground2D::BgTransformState\nBackground2D::BgTransformState::GetBgEffTransformState(int bgEffId) {\n  assert(ScrWorkBgEffStructSize >= 30);\n  assert(ScrWorkBgEffOffsetStructSize >= 20);\n\n  const glm::vec2 resolutionScale = {Profile::DesignWidth / 1280.0f,\n                                     Profile::DesignHeight / 720.0f};\n\n  const std::span<const int> bgEffStruct = std::span(\n      ScrWork.begin() + ScrWorkBgEffStructSize * bgEffId, ScrWork.end());\n  const std::span<const int> bgEffOfsStruct = std::span(\n      ScrWork.begin() + ScrWorkBgEffOffsetStructSize * bgEffId, ScrWork.end());\n\n  BgTransformState state;\n\n  state.Position =\n      glm::vec2(bgEffStruct[SW_BGEFF1_POSX] + bgEffOfsStruct[SW_BGEFF1_OFSX],\n                bgEffStruct[SW_BGEFF1_POSY] + bgEffOfsStruct[SW_BGEFF1_OFSY]) *\n      glm::vec2(Profile::DesignWidth / 1280.0f, Profile::DesignHeight / 720.0f);\n\n  state.Scale =\n      glm::vec2(static_cast<float>(bgEffStruct[SW_BGEFF1_SIZE] +\n                                   bgEffOfsStruct[SW_BGEFF1_SIZE_OFS])) /\n      1000.0f;\n\n  state.Rotation = ScrWorkAnglesToQuaternion(\n      bgEffStruct[SW_BGEFF1_ROTX] + bgEffOfsStruct[SW_BGEFF1_ROTX_OFS],\n      bgEffStruct[SW_BGEFF1_ROTY] + bgEffOfsStruct[SW_BGEFF1_ROTY_OFS],\n      bgEffStruct[SW_BGEFF1_ROTZ] + bgEffOfsStruct[SW_BGEFF1_ROTZ_OFS]);\n\n  state.Subsection = RectF(static_cast<float>(bgEffStruct[SW_BGEFF1_SX] +\n                                              bgEffOfsStruct[SW_BGEFF1_SX_OFS]),\n                           static_cast<float>(bgEffStruct[SW_BGEFF1_SY] +\n                                              bgEffOfsStruct[SW_BGEFF1_SY_OFS]),\n                           1280.0f, 720.0f)\n                         .Scale(resolutionScale, glm::vec2(0.0f));\n\n  return state;\n}\n\nvoid BackgroundEffect2D::SetTransformState(const int dispMode,\n                                           BgTransformState state) {\n  TransformState = state;\n  RenderSprite = BgSprite;\n\n  state.Subsection.Scale(glm::vec2(1.0f) / RenderSprite.BaseScale,\n                         glm::vec2(0.0f));\n  RenderSprite.Bounds = {state.Subsection.X, state.Subsection.Y,\n                         RenderSprite.Bounds.Width - state.Subsection.X,\n                         RenderSprite.Bounds.Height - state.Subsection.Y};\n\n  const glm::vec2 centerOfMass =\n      std::reduce(Vertices.begin(), Vertices.begin() + VertexCount) /\n      (float)VertexCount;\n\n  TransformState.Origin = centerOfMass - Vertices[0];\n\n  StencilTransformation = TransformationMatrix(\n      centerOfMass, TransformState.Scale, {centerOfMass, 0.0f},\n      TransformState.Rotation, -Vertices[0] + state.Position);\n}\n\nvoid BackgroundEffect2D::UpdateState(const int bgId) {\n  const int structOffset = ScrWorkBgEffStructSize * bgId;\n  const int structOfsOffset = ScrWorkBgEffOffsetStructSize * bgId;\n\n  Show = GetFlag(SF_BGEFF1DISP + bgId);\n  Layers = {ScrWork[SW_BGEFF1_PRI + structOffset],\n            ScrWork[SW_BGEFF1_PRI2 + structOffset]};\n  RenderType = ScrWork[SW_BGEFF1_MODE + structOffset];\n\n  MaskNumber = ScrWork[SW_BGEFF1_MASKNO + structOffset];\n  FadeRange = ScrWork[SW_BGEFF1_MASKFADERANGE + structOffset];\n  FadeCount = ScrWork[SW_BGEFF1_FADECT + structOffset];\n\n  // Set tint\n  switch (GameInstructionSet) {\n    case Vm::InstructionSet::CC:\n      Tint = ScrWorkGetColor(SW_BGEFF1_FILTER + structOffset);\n      break;\n\n    default:\n      Tint = glm::vec4(1.0f);\n      break;\n  }\n  Tint.a = (ScrWork[SW_BGEFF1_ALPHA + structOffset] +\n            ScrWork[SW_BGEFF1_ALPHA_OFS + structOfsOffset]) /\n           256.0f;\n\n  // Get position coordinates\n  const int maskType = ScrWork[SW_BGEFF1_MASK_TYPE + structOffset];\n  const glm::vec2 resolutionScale = {Profile::DesignWidth / 1280.0f,\n                                     Profile::DesignHeight / 720.0f};\n\n  VertexCount = maskType == 0 ? 4 : 3;\n  for (size_t i = 0; i < VertexCount; i++) {\n    const int x =\n        ScrWork[SW_BGEFF1_MASK_VERTEX1_X + structOffset + i * 2] +\n        ScrWork[SW_BGEFF1_MASK_VERTEX1_OFSX + structOfsOffset + i * 2];\n    const int y =\n        ScrWork[SW_BGEFF1_MASK_VERTEX1_Y + structOffset + i * 2] +\n        ScrWork[SW_BGEFF1_MASK_VERTEX1_OFSY + structOfsOffset + i * 2];\n    Vertices[i] = glm::vec2((float)x, (float)y) * resolutionScale;\n  }\n  if (VertexCount == 4) std::swap(Vertices[1], Vertices[2]);\n\n  SetTransformState(maskType, BgTransformState::GetBgEffTransformState(bgId));\n}\n\nvoid BackgroundEffect2D::Render(const int layer) {\n  if (Status != LoadStatus::Loaded || !OnLayer(layer) || !Show) {\n    return;\n  }\n\n  // Draw\n  Renderer->SetStencilMode(StencilBufferMode::Write);\n  Renderer->ClearStencilBuffer();\n\n  Renderer->DrawConvexShape(\n      std::span(Vertices.begin(), Vertices.begin() + VertexCount),\n      StencilTransformation, glm::vec4(1.0f));\n\n  Renderer->SetStencilMode(StencilBufferMode::Test);\n\n  std::invoke(BackgroundRenderTable[RenderType], this);\n\n  Renderer->SetStencilMode(StencilBufferMode::Off);\n}\n\ntemplate <bool PhaseZero>\nvoid Background2D::RenderBgWave() {\n  if (FadeCount == 0) {\n    return;\n  }\n\n  const float alpha = (Tint.a * FadeCount) / 256.0f;\n  if constexpr (PhaseZero) {\n    Effects::WaveBG.CalcPos(0, alpha);\n  } else {\n    // maybe that's it, maybe not\n    Effects::WaveBG.CalcPos(MaskNumber, alpha);\n  }\n\n  PrimitiveData primitives = Effects::WaveBG.GetPrimitives();\n\n  Renderer->DrawPrimitives(\n      RenderSprite.Sheet, &Masks2D[MaskNumber].MaskSprite.Sheet,\n      ShaderProgramType::MaskedSprite, primitives.Vertices, primitives.Indices,\n      TransformState.ToMatrix(), glm::mat4(1.0f), false,\n      TopologyMode::TriangleStrips);\n}\n\nvoid Background2D::RenderRegular() {\n  Renderer->DrawSprite(RenderSprite, TransformState.ToMatrix(), Tint);\n\n  for (int i = 0; i < MaxLinkedBgBuffers; i++) {\n    if (Links[i].Direction != LinkDirection::Off &&\n        Links[i].LinkedBuffer != nullptr) {\n      const glm::mat4 linkTransformation =\n          TransformationMatrix(TransformState.Origin, TransformState.Scale,\n                               {TransformState.Origin, 0.0f},\n                               TransformState.Rotation, Links[i].DisplayCoords);\n      Renderer->DrawSprite(Links[i].LinkedBuffer->RenderSprite,\n                           linkTransformation, Tint, false);\n    }\n  }\n}\n\nvoid Background2D::RenderMasked() {\n  Renderer->DrawMaskedSprite(RenderSprite, Masks2D[MaskNumber].MaskSprite,\n                             FadeCount, FadeRange, TransformState.ToMatrix(),\n                             Tint, false, false);\n}\n\nvoid Background2D::RenderCaptureMasked() {\n  Renderer->DrawMaskedBinarySprite(RenderSprite, MaskCapture.BgSprite,\n                                   TransformState.ToMatrix(), std::nullopt,\n                                   Tint, false);\n  for (int i = 0; i < MaxLinkedBgBuffers; i++) {\n    if (Links[i].Direction != LinkDirection::Off &&\n        Links[i].LinkedBuffer != nullptr) {\n      const glm::mat4 linkTransformation =\n          TransformationMatrix(TransformState.Origin, TransformState.Scale,\n                               {TransformState.Origin, 0.0f},\n                               TransformState.Rotation, Links[i].DisplayCoords);\n      Renderer->DrawMaskedBinarySprite(Links[i].LinkedBuffer->RenderSprite,\n                                       MaskCapture.BgSprite, linkTransformation,\n                                       std::nullopt, Tint, false);\n    }\n  }\n}\n\nvoid Background2D::RenderMaskedInverted() {\n  Renderer->DrawMaskedSprite(RenderSprite, Masks2D[MaskNumber].MaskSprite,\n                             FadeCount, FadeRange, TransformState.ToMatrix(),\n                             Tint, true, false);\n}\n\nvoid Background2D::RenderFade() {\n  Tint.a *= FadeCount / 256.0f;\n\n  RenderRegular();\n}\n\nconstexpr size_t ExplodeGridWidth = 64;\nconstexpr size_t ExplodeGridHeight = 36;\nstruct ExplodeTri {\n  std::array<glm::vec2, 3> SpritePositions{};\n  std::array<glm::vec2, 3> VertexOffsets{};\n\n  glm::quat Rotation = {1.0f, 0.0f, 0.0f, 0.0f};\n  glm::quat RotationSpeed = {1.0f, 0.0f, 0.0f, 0.0f};\n\n  glm::vec2 DisplayPosition{};\n  glm::vec2 InitialDisplayPosition{};\n  glm::vec2 TranslationSpeed{};\n\n  float Alpha = 1.0f;\n  uint8_t StartFadeOutTime = 0;\n};\nstatic std::array<ExplodeTri, ExplodeGridWidth * ExplodeGridHeight * 2>\n    ExplodeTris;\n\nvoid ResetExplodeTris(const Sprite& renderSprite) {\n  const glm::vec2 renderDimensions = renderSprite.ScaledBounds().GetSize();\n  const glm::vec2 cellDimensions =\n      renderDimensions / glm::vec2(ExplodeGridWidth, ExplodeGridHeight);\n\n  // Initialize gridpoint positions\n  std::array<glm::vec2, (ExplodeGridWidth + 1) * (ExplodeGridHeight + 1)> grid;\n\n  for (size_t y = 0; y <= ExplodeGridHeight; y++) {\n    for (size_t x = 0; x <= ExplodeGridWidth; x++) {\n      const glm::ivec2 randomOffsetLimits =\n          glm::ivec2(cellDimensions + glm::vec2(0.5f)) * 10;\n\n      const int randomOffsetX = CALCrnd(randomOffsetLimits.x);\n      const int randomOffsetY = CALCrnd(randomOffsetLimits.y);\n      const glm::vec2 randomOffset =\n          glm::vec2(randomOffsetX, randomOffsetY) / 10.0f;\n\n      glm::vec2 gridPointPos =\n          cellDimensions * (glm::vec2(x, y) - glm::vec2(0.5f)) + randomOffset;\n\n      // Clamp edge grid points to the edge of the sprite\n      if (x == 0)\n        gridPointPos.x = 0.0f;\n      else if (x == ExplodeGridWidth)\n        gridPointPos.x = renderDimensions.x;\n\n      if (y == 0)\n        gridPointPos.y = 0.0f;\n      else if (y == ExplodeGridHeight)\n        gridPointPos.y = renderDimensions.y;\n\n      const size_t idx = y * (ExplodeGridWidth + 1) + x;\n      grid[idx] = glm::clamp(gridPointPos, {0.0f, 0.0f}, renderDimensions);\n    }\n  }\n\n  const glm::vec2 renderSpritePos = renderSprite.Bounds.GetPos();\n  const glm::vec2 renderSpriteSheetDimensions =\n      renderSprite.Sheet.GetDimensions();\n\n  // Set the UVs for each vertex by \"overlaying\" the vertex grid onto the\n  // sprite's texture\n  const auto getSpritePosition = [&](glm::vec2 gridPosition) {\n    const glm::vec2 texturePos =\n        gridPosition / renderSprite.BaseScale + renderSpritePos;\n    const glm::vec2 normalizedTexturePos =\n        texturePos / renderSpriteSheetDimensions;\n    return normalizedTexturePos;\n  };\n\n  // Fill the tri array\n  ExplodeTris.fill(ExplodeTri());\n  for (size_t y = 0; y < ExplodeGridHeight; y++) {\n    for (size_t x = 0; x < ExplodeGridWidth; x++) {\n      for (uint8_t triIdxInSquare = 0; triIdxInSquare < 2; triIdxInSquare++) {\n        const size_t triIdx = (y * ExplodeGridWidth + x) * 2 + triIdxInSquare;\n        ExplodeTri& tri = ExplodeTris[triIdx];\n\n        tri.VertexOffsets =\n            triIdxInSquare\n                ? std::array{grid[y * (ExplodeGridWidth + 1) + x + 1],\n                             grid[(y + 1) * (ExplodeGridWidth + 1) + x],\n                             grid[(y + 1) * (ExplodeGridWidth + 1) + x + 1]}\n                : std::array{grid[y * (ExplodeGridWidth + 1) + x],\n                             grid[y * (ExplodeGridWidth + 1) + x + 1],\n                             grid[(y + 1) * (ExplodeGridWidth + 1) + x]};\n\n        std::ranges::transform(tri.VertexOffsets, tri.SpritePositions.begin(),\n                               getSpritePosition);\n\n        // Set the InitialDisplayPosition to the center of the bounding square\n        // and make VertexOffsets the offsets from this center\n        const glm::vec2 topLeft =\n            glm::min(tri.VertexOffsets[0],\n                     glm::min(tri.VertexOffsets[1], tri.VertexOffsets[2]));\n        const glm::vec2 bottomRight =\n            glm::max(tri.VertexOffsets[0],\n                     glm::max(tri.VertexOffsets[1], tri.VertexOffsets[2]));\n        const glm::vec2 center = (topLeft + bottomRight) / 2.0f;\n        tri.InitialDisplayPosition = center;\n        std::ranges::transform(\n            tri.VertexOffsets, tri.VertexOffsets.begin(),\n            [center](glm::vec2 pos) { return pos - center; });\n\n        // TranslationSpeed is not based on design dimensions in the\n        // MAGES. engine either\n        tri.TranslationSpeed.x = CALCrnd(1200) / 100.0f - 6.0f;\n        tri.TranslationSpeed.y = CALCrnd(1200) / 100.0f - 6.0f;\n\n        const int rotationSpeedX = CALCrnd(0x400) - 0x200;\n        const int rotationSpeedY = CALCrnd(0x400) - 0x200;\n        const int rotationSpeedZ = CALCrnd(0x400) - 0x200;\n        tri.RotationSpeed = ScrWorkAnglesToQuaternion(\n            rotationSpeedX, rotationSpeedY, rotationSpeedZ);\n\n        tri.StartFadeOutTime = static_cast<uint8_t>(CALCrnd(20) + 10);\n        // Fade out duration is 32 fade counts, and the max fade count is 64\n        assert(tri.StartFadeOutTime + 32.0f <= 64.0f);\n      }\n    }\n  }\n}\n\nstatic void UpdateExplode(float fadeCount) {\n  // Don't run this every frame; only when the fadeCount changed\n  static float lastFadeCount = -1.0f;\n  if (lastFadeCount == fadeCount) return;\n  lastFadeCount = fadeCount;\n\n  bool anyTriVisible = false;\n  for (ExplodeTri& tri : ExplodeTris) {\n    tri.DisplayPosition =\n        tri.InitialDisplayPosition + tri.TranslationSpeed * fadeCount;\n    tri.Rotation = glm::pow(tri.RotationSpeed, fadeCount);\n\n    static constexpr float fadeOutDuration = 32.0f;\n    const float fadeOutProgress =\n        (fadeCount - tri.StartFadeOutTime) / fadeOutDuration;\n    tri.Alpha = std::clamp(1.0f - fadeOutProgress, 0.0f, 1.0f);\n\n    anyTriVisible |= tri.Alpha > 0.0f;\n  }\n\n  SetFlag(SF_BGEXPLOSIONVISIBLE, anyTriVisible);\n}\n\nvoid Background2D::RenderExplode() {\n  if (FadeCount == -1) {\n    ResetExplodeTris(RenderSprite);\n    return;\n  }\n\n  UpdateExplode(static_cast<float>(FadeCount));\n\n  static std::array<VertexBufferSprites, ExplodeTris.size() * 3> vertices;\n  size_t vertexIdx = 0;\n  for (const ExplodeTri& tri : ExplodeTris) {\n    const glm::mat4 transformationMatrix =\n        TransformationMatrix({0.0f, 0.0f}, {1.0f, 1.0f}, glm::vec3(0.0f),\n                             tri.Rotation, tri.DisplayPosition);\n\n    for (size_t i = 0; i < 3; i++) {\n      vertices[vertexIdx + i] = VertexBufferSprites{\n          .Position = transformationMatrix *\n                      glm::vec4(tri.VertexOffsets[i], 0.0f, 1.0f),\n          .UV = tri.SpritePositions[i],\n          .Tint = {1.0f, 1.0f, 1.0f, tri.Alpha},\n      };\n    }\n\n    vertexIdx += 3;\n  }\n\n  const static std::array<uint16_t, vertices.size()> indices = []() {\n    std::array<uint16_t, vertices.size()> tempIndices;\n    std::iota(tempIndices.begin(), tempIndices.end(), 0);\n    return tempIndices;\n  }();\n\n  Renderer->DrawPrimitives(RenderSprite.Sheet, ShaderProgramType::Sprite,\n                           vertices, indices, glm::mat4(1.0f));\n}\n\nbool IsBgWaveEffectActive() {\n  return std::ranges::any_of(Backgrounds, [](const auto& currentBg) {\n    return currentBg.RenderType == 14 || currentBg.RenderType == 28;\n  });\n}\n\n}  // namespace Impacto"
  },
  {
    "path": "src/background2d.h",
    "content": "#pragma once\n\n#include <ankerl/unordered_dense.h>\n#include <map>\n#include \"texture/texture.h\"\n#include \"spritesheet.h\"\n#include \"loadable.h\"\n#include \"renderer/renderer.h\"\n#include \"profile/game.h\"\n\nnamespace Impacto {\n\nenum class LinkDirection { Off, Up, Down, Left, Right, Up3, Down3 };\n\nclass Background2D;\n\nstruct LinkState {\n  Background2D* LinkedBuffer;\n  LinkDirection Direction;\n  glm::vec2 DisplayCoords;\n};\n\nstruct BgEff {\n  bool Loaded = false;\n  Texture BgEffTexture;\n  Sprite BgEffSprite;\n  ShaderProgramType Shader = ShaderProgramType::Sprite;\n};\n\nclass Background2D : public Loadable<Background2D, bool, uint32_t> {\n  friend class Loadable<Background2D, bool, uint32_t>;\n\n public:\n  static void Init();\n  static void InitFrameBuffers();\n\n  Sprite BgSprite;\n  Sprite RenderSprite = BgSprite;\n\n  std::array<int, 2> BgEffsLayers = {0, 0};\n  std::array<BgEff, 3> FrameBgEffs;\n  BgEff ChaBgEff;\n\n  glm::vec4 Tint = glm::vec4(1.0f);\n  int MaskNumber = 0;\n\n  int FadeCount = 0;\n  int FadeRange = 0;\n\n  bool Show = false;\n  int RenderType = 0;\n  std::array<int, 2> Layers;\n  std::array<LinkState, 2> Links;\n\n  inline static Background2D* LastRenderedBackground = nullptr;\n\n  inline static ankerl::unordered_dense::map<int,\n                                             std::array<ShaderProgramType, 4>>\n      BgEffShaderMap;\n  static ShaderProgramType GetBgEffShader(int bgId, size_t bgEffId) {\n    const auto found = BgEffShaderMap.find(bgId);\n    assert(found == BgEffShaderMap.end() || bgEffId < found->second.size());\n\n    // Unmapped means sprite\n    return found != BgEffShaderMap.end() ? found->second[bgEffId]\n                                         : ShaderProgramType::Sprite;\n  }\n\n  inline static ankerl::unordered_dense::map<int, std::array<int, 4>>\n      BgEffTextureIdMap;\n\n  virtual void Render(int layer);\n  void RenderBgEff(int layer);\n  virtual void UpdateState(int bgId);\n\n  void LoadSolidColor(uint32_t color, int width, int height);\n\n  struct BgTransformState {\n   public:\n    glm::vec2 Position{};\n    glm::vec2 Scale = glm::vec2(1.0f);\n    glm::quat Rotation{};\n    glm::vec2 Origin{};\n\n    RectF Subsection =\n        RectF(0.0f, 0.0f, Profile::DesignWidth, Profile::DesignHeight);\n\n    static BgTransformState GetBgTransformState(int bgId);\n    static BgTransformState GetCapTransformState(int capId);\n    static BgTransformState GetBgEffTransformState(int bgEffId);\n\n    glm::mat4 ToMatrix() const {\n      return TransformationMatrix(Origin, Scale, {Origin, 0.0f}, Rotation,\n                                  Position);\n    }\n  };\n  BgTransformState TransformState;\n\n protected:\n  Texture BgTexture;\n\n  bool LoadSync(uint32_t bgId);\n  void UnloadSync();\n  void MainThreadOnLoad(bool result);\n\n  void LinkBuffers(int linkCode, int currentBufferId);\n\n  virtual void SetTransformState(int dispMode, BgTransformState state);\n\n  bool OnLayer(int layer) {\n    return std::find(Layers.begin(), Layers.end(), layer) != Layers.end();\n  }\n\n  using BackgroundRenderProc = auto (Background2D::*)() -> void;\n\n  void RenderRegular();\n  void RenderMasked();\n  void RenderCaptureMasked();\n  void RenderMaskedInverted();\n  void RenderFade();\n  void RenderExplode();\n  template <bool PhaseZero>\n  void RenderBgWave();\n\n  // RenderRegular at positions 2-27 are used as stubs, engine has separate\n  // calls for those\n  std::array<BackgroundRenderProc, 40> constexpr static BackgroundRenderTable =\n      {\n          &Background2D::RenderRegular,         // 0\n          &Background2D::RenderFade,            // 1\n          &Background2D::RenderExplode,         // 2\n          &Background2D::RenderRegular,         // 3\n          &Background2D::RenderRegular,         // 4\n          &Background2D::RenderRegular,         // 5\n          &Background2D::RenderRegular,         // 6\n          &Background2D::RenderRegular,         // 7\n          &Background2D::RenderRegular,         // 8\n          &Background2D::RenderRegular,         // 9\n          &Background2D::RenderRegular,         // 10\n          &Background2D::RenderRegular,         // 11\n          &Background2D::RenderRegular,         // 12\n          &Background2D::RenderRegular,         // 13\n          &Background2D::RenderBgWave<true>,    // 14\n          &Background2D::RenderMasked,          // 15\n          &Background2D::RenderMaskedInverted,  // 16\n          &Background2D::RenderRegular,         // 17\n          &Background2D::RenderRegular,         // 18\n          &Background2D::RenderRegular,         // 19\n          &Background2D::RenderRegular,         // 20\n          &Background2D::RenderRegular,         // 21\n          &Background2D::RenderRegular,         // 22\n          &Background2D::RenderCaptureMasked,   // 23\n          &Background2D::RenderRegular,         // 24\n          &Background2D::RenderRegular,         // 25\n          &Background2D::RenderRegular,         // 26\n          &Background2D::RenderRegular,         // 27\n          &Background2D::RenderBgWave<false>,   // 28\n          &Background2D::RenderRegular,         // 29\n          &Background2D::RenderRegular,         // 30\n          &Background2D::RenderRegular,         // 31\n          &Background2D::RenderRegular,         // 32\n          &Background2D::RenderRegular,         // 33\n          &Background2D::RenderRegular,         // 34\n          &Background2D::RenderRegular,         // 35\n          &Background2D::RenderRegular,         // 36\n          &Background2D::RenderRegular,         // 37\n          &Background2D::RenderRegular,         // 38\n          &Background2D::RenderRegular,         // 39\n      };\n};\n\nclass Capture2D : public Background2D {\n public:\n  void Render(int layer) override;\n  void UpdateState(int capId) override;\n};\n\nclass BackgroundEffect2D : public Background2D {\n public:\n  void Render(int layer) override;\n  void UpdateState(int bgId) override;\n\n private:\n  void SetTransformState(int dispMode, BgTransformState state) override;\n\n  size_t VertexCount = 4;\n  std::array<glm::vec2, 4> Vertices;\n\n  glm::mat4 StencilTransformation = glm::mat4(1.0f);\n};\n\ninline std::array<Background2D, 8> Backgrounds;\ninline std::array<Capture2D, 2> Screencaptures;\ninline Capture2D MaskCapture;\ninline std::array<BackgroundEffect2D, MaxFramebuffers> Framebuffers;\ninline Background2D ShaderScreencapture;\n\ninline ankerl::unordered_dense::map<int, Background2D*> Backgrounds2D;\n\nbool IsBgWaveEffectActive();\nvoid ResetExplodeTris(const Sprite& renderSprite);\n\n}  // namespace Impacto"
  },
  {
    "path": "src/character2d.cpp",
    "content": "#include \"character2d.h\"\n\n#include \"mem.h\"\n#include \"io/io.h\"\n#include \"io/vfs.h\"\n#include \"util.h\"\n#include \"profile/scriptvars.h\"\n#include \"profile/vm.h\"\n#include \"profile/configsystem.h\"\n#include \"voicetable.h\"\n#include \"background2d.h\"\n#include \"text/dialoguepage.h\"\n#include \"audio/audiochannel.h\"\n#include \"audio/audiosystem.h\"\n\nnamespace Impacto {\n\nusing namespace Impacto::Profile::ScriptVars;\nusing namespace Impacto::Profile::Vm;\n\nbool Character2D::LoadSync(uint32_t charaId) {\n  int fileId = charaId & 0xFFFF;\n\n  Io::Stream* stream;\n  int64_t err = Io::VfsOpen(MountPoint, fileId, &stream);\n  if (err != IoError_OK) return false;\n  CharaTexture.Load(stream);\n  delete stream;\n\n  if (Profile::CharaIsMvl) {\n    // MVL format\n    Face = charaId;\n\n    err = Io::VfsOpen(MountPoint, fileId + 1, &stream);\n    if (err != IoError_OK) return false;\n\n    Position = glm::vec2(Profile::DesignWidth, Profile::DesignHeight) / 2.0f;\n\n    Io::ReadLE<int>(stream);\n    int stateCount = Io::ReadLE<int>(stream);\n\n    // Skip to state data\n    stream->Seek(0x68, SEEK_CUR);\n\n    size_t vertexCount = static_cast<size_t>(Io::ReadLE<int>(stream));\n    size_t vertexOffset = static_cast<size_t>(Io::ReadLE<int>(stream));\n\n    for (int i = 0; i < stateCount; i++) {\n      int count = Io::ReadLE<int>(stream);\n      int start = Io::ReadLE<int>(stream);\n\n      // I... couldn't come up with anything better\n      char name[32];\n      Io::ReadArrayLE<char>(name, stream, 32);\n      int id = 0;\n      int idx = (int)strlen(name) - 2;\n      if (isdigit(name[idx])) {\n        id = atoi(&name[idx]);\n      } else if (name[idx] == 'L') {  // Lip\n        idx -= 2;\n        id = atoi(&name[idx]);\n        idx += 3;\n        id = (0x40000000 | (id << 8)) | (atoi(&name[idx]) - 1);\n      } else if (name[idx] == 'E') {  // Eye\n        idx -= 2;\n        id = atoi(&name[idx]);\n        idx += 3;\n        id = (0x30000000 | (id << 8)) | (atoi(&name[idx]) - 1);\n      }\n      int64_t back = stream->Position;\n\n      stream->Seek(start, SEEK_SET);\n      auto [stateItr, inserted] =\n          States.try_emplace(id, count, Profile::CharaIsMvl);\n      auto mvlIndicesPtr =\n          std::get_if<Character2DState::MVLData>(&stateItr->second.Data)\n              ->Indices.get();\n      Io::ReadArrayLE<uint16_t>(mvlIndicesPtr, stream, count);\n\n      stream->Seek(back, SEEK_SET);\n      stream->Seek(0x18, SEEK_CUR);\n    }\n\n    // They seem to use the whole vertex array in all states, so... read it once\n    // and forget about it?\n    stream->Seek(vertexOffset, SEEK_SET);\n    std::vector<float> mvlVertexInfo(vertexCount * 5);\n    Io::ReadArrayLE<float>(mvlVertexInfo.data(), stream, vertexCount * 5);\n\n    MvlVertices.resize(vertexCount);\n    for (size_t i = 0; i < vertexCount; i++) {\n      MvlVertices[i] = VertexBufferSprites{\n          .Position = {mvlVertexInfo[i * 5], mvlVertexInfo[i * 5 + 1]},\n          .UV = glm::vec2(mvlVertexInfo[i * 5 + 3], mvlVertexInfo[i * 5 + 4]),\n          .Tint = Tint,\n      };\n    }\n\n    delete stream;\n  } else {\n    // LAY format\n    err = Io::VfsOpen(\"chara\", fileId + 1, &stream);\n    if (err != IoError_OK) return false;\n\n    Face = charaId;\n\n    Position = glm::vec2(Profile::DesignWidth, Profile::DesignHeight) / 2.0f;\n\n    using StreamReadInt_t = auto (*)(Io::Stream*)->int;\n    using StreamReadFloat_t = auto (*)(Io::Stream*)->float;\n    StreamReadInt_t StreamReadInt;\n    StreamReadFloat_t StreamReadFloat;\n\n    if (Profile::LayFileBigEndian) {\n      StreamReadInt = &Io::ReadBE<int>;\n      StreamReadFloat = &Io::ReadBE<float>;\n    } else {\n      StreamReadInt = &Io::ReadLE<int>;\n      StreamReadFloat = &Io::ReadLE<float>;\n    }\n    int stateCount = StreamReadInt(stream);\n    StreamReadInt(stream);\n\n    for (int i = 0; i < stateCount; i++) {\n      int id = StreamReadInt(stream);\n      int start = StreamReadInt(stream);\n      int count = StreamReadInt(stream);\n\n      int64_t back = stream->Position;\n      auto [stateItr, inserted] =\n          States.try_emplace(id, count, Profile::CharaIsMvl);\n      stream->Seek(12 * (stateCount) + 8 + (start * 16), SEEK_SET);\n      for (int j = 0; j < count; j++) {\n        glm::vec2 screenCoords;\n        glm::vec2 txtCoords;\n        screenCoords.x = StreamReadFloat(stream);\n        screenCoords.y = StreamReadFloat(stream);\n\n        txtCoords.x = StreamReadFloat(stream) * Profile::LayFileTexXMultiplier;\n        txtCoords.y = StreamReadFloat(stream) * Profile::LayFileTexYMultiplier;\n        auto* layDataPtr =\n            std::get_if<Character2DState::LAYData>(&stateItr->second.Data);\n        layDataPtr->ScreenCoords[j] = screenCoords;\n        layDataPtr->TextureCoords[j] = txtCoords;\n      }\n\n      stream->Seek(back, SEEK_SET);\n    }\n\n    delete stream;\n  }\n  return true;\n}\n\nvoid Character2D::UnloadSync() {\n  Renderer->FreeTexture(CharaSpriteSheet.Texture);\n  CharaSpriteSheet.DesignHeight = 0.0f;\n  CharaSpriteSheet.DesignWidth = 0.0f;\n  CharaSpriteSheet.Texture = 0;\n  Show = false;\n  std::fill(Layers.begin(), Layers.end(), -1);\n  MvlVertices.clear();\n  MvlIndices.clear();\n  States.clear();\n  StatesToDraw.clear();\n}\n\nvoid Character2D::MainThreadOnLoad(bool result) {\n  CharaSpriteSheet.Texture = CharaTexture.Submit();\n  CharaSpriteSheet.DesignWidth = (float)CharaTexture.Width;\n  CharaSpriteSheet.DesignHeight = (float)CharaTexture.Height;\n  CharaSprite.Sheet = CharaSpriteSheet;\n  CharaSprite.BaseScale = glm::vec2(1.0f);\n  CharaSprite.Bounds = RectF(0.0f, 0.0f, CharaSpriteSheet.DesignWidth,\n                             CharaSpriteSheet.DesignHeight);\n  Show = false;\n  std::fill(Layers.begin(), Layers.end(), -1);\n}\n\nvoid Character2D::UpdateStatesToDraw() {\n  if (Status != LoadStatus::Loaded) return;\n\n  if (Profile::CharaIsMvl) {\n    MvlIndices.clear();\n    StatesToDraw.clear();\n    StatesToDraw.push_back((Face & 0xFFFF0000) >> 16);  // face\n    StatesToDraw.push_back(0x40000000 | ((Face & 0xFFFF0000) >> 8) |\n                           LipFrame);  // lip\n    StatesToDraw.push_back(0x30000000 | ((Face & 0xFFFF0000) >> 8) |\n                           EyeFrame);  // eye\n\n    for (auto id : StatesToDraw) {\n      if (auto stateItr = States.find(id); stateItr != States.end()) {\n        Character2DState const& state = stateItr->second;\n        auto& stateIndices =\n            std::get_if<Character2DState::MVLData>(&state.Data)->Indices;\n        MvlIndices.insert(MvlIndices.end(), stateIndices.get(),\n                          stateIndices.get() + state.Count);\n      }\n    }\n\n  } else {\n    StatesToDraw.clear();\n    StatesToDraw.push_back(1);\n    StatesToDraw.push_back(0x20000000 |\n                           ((Face & 0xFFFF0000) >> 16));  // Just face\n    StatesToDraw.push_back(0x40000000 | ((Face & 0xFFFF0000) >> 8) |\n                           LipFrame);  // Just mouth\n    StatesToDraw.push_back(\n        0x60000000 | ((Face & 0xFFFF0000) >> 8) |\n        (3 * EyeFrame + LipFrame));  // Both eyes and mouth (3 frames of\n                                     // mouth with each frame of the eyes)\n    StatesToDraw.push_back(0x30000000 | ((Face & 0xFFFF0000) >> 8));\n  }\n}\n\nvoid Character2D::UpdateEyeMouth() {\n  // Pause check\n  if ((ScrWork[SW_GAMESTATE] & 0b100) != 0) return;\n\n  static std::array<int, CurEyeFrame.size()> eyeCounter{};\n  static std::array<int, CurMouthIndex.size()> mouthCounter{};\n\n  for (size_t i = 0; i < eyeCounter.size(); i++) {\n    // Every four game frames, an eye frame updates\n    if (eyeCounter[i] == 0) {\n      eyeCounter[i] = 4;\n\n      if (++CurEyeFrame[i] == 3) {\n        CurEyeFrame[i] = 0;\n\n        // There's a 5% chance of two consecutive blinks\n        if (CALCrnd(100) < 95) {\n          // The number of frames between two blinks is\n          // uniformly distributed in [200, 430)\n          eyeCounter[i] = CALCrnd(230) + 200;\n        }\n      }\n    } else {\n      eyeCounter[i]--;\n    }\n  }\n\n  for (size_t i = 0; i < mouthCounter.size(); i++) {\n    /* TODO: Add the reset flags for the counters from scrcommessync once it's\n     * implemented*/\n\n    if (mouthCounter[i] == 0) {\n      if (++CurMouthIndex[i] == 20) {\n        CurMouthIndex[i] = 0;\n      }\n      mouthCounter[i] = AnimeTable[CurMouthIndex[i]].second;\n    } else {\n      mouthCounter[i]--;\n    }\n  }\n}\n\nstatic uint8_t GetSoundLevel(Audio::AudioChannelId channelId) {\n  if (channelId < Audio::AC_VOICE0 || Audio::AC_REV < channelId) return 0;\n  const auto& channel = *Audio::Channels[channelId];\n\n  if (channel.GetState() != Audio::ACS_Playing ||\n      channel.DurationInSeconds() - channel.PositionInSeconds() < FLT_EPSILON) {\n    return 0;\n  }\n\n  const int audioPos = (int)(channel.PositionInSeconds() * 6.0f);\n\n  const int fileId = channel.GetStream()->GetBaseStream()->Meta.Id;\n  const uint8_t voiceData = VoiceTableData.GetVoiceData(fileId, audioPos >> 2);\n\n  const uint8_t result = (voiceData >> ((audioPos & 0b11) << 1)) & 0b11;\n  return result;\n}\n\nvoid Character2D::UpdateState(const int chaId) {\n  using namespace Impacto::Audio;\n\n  const size_t structOffset = ScrWorkChaStructSize * chaId;\n  const size_t structOfsOffset = ScrWorkChaOffsetStructSize * chaId;\n\n  const glm::vec2 resolutionScale = {Profile::DesignWidth / 1280.0f,\n                                     Profile::DesignHeight / 720.0f};\n\n  if (Profile::Vm::GameInstructionSet == Vm::InstructionSet::MO6TW) {\n    // If I don't do this it tries to access a label with an index of 65535,\n    // which is... not good. I have no idea why this happens, the script code\n    // does actually seem to do this on purpose, so... HACK (for now)\n    if (ScrWork[SW_CHA1NO + structOffset] == 65535)\n      ScrWork[SW_CHA1NO + structOffset] = 0;\n  }\n\n  Layers = {ScrWork[SW_CHA1PRI + structOffset],\n            ScrWork[SW_CHA1PRI2 + structOffset]};\n  Show = GetFlag(SF_CHA1DISP + chaId) &&\n         ScrWork[SW_CHA1NO + structOffset] != 0xFFFFFF;\n  UseBgEffect = GetFlag(SF_CHA1BGEFFECT + chaId);\n\n  Position = glm::vec2(ScrWork[SW_CHA1POSX + structOffset] +\n                           ScrWork[SW_CHA1POSX_OFS + structOfsOffset],\n                       ScrWork[SW_CHA1POSY + structOffset] +\n                           ScrWork[SW_CHA1POSY_OFS + structOfsOffset]) *\n             resolutionScale;\n\n  if (Profile::Vm::GameInstructionSet == Vm::InstructionSet::MO8) {\n    constexpr std::array<float, 7> BaseScaleValues = {\n        1.3f, 1.0f, 0.6f, 0.4f, 0.13f, 0.8f, 0.7f,\n    };\n\n    const size_t baseScaleIndex = ScrWork[SW_CHA1BASESIZE + structOffset];\n    const float baseScale = baseScaleIndex < BaseScaleValues.size()\n                                ? BaseScaleValues[baseScaleIndex]\n                                : 1.0f;\n\n    Scale = baseScale *\n            glm::vec2(ScrWork[SW_CHA1SIZEX + structOffset],\n                      ScrWork[SW_CHA1SIZEY + structOffset]) /\n            1000.0f;\n\n    Rotation = ScrWorkAnglesToQuaternion(ScrWork[SW_CHA1ROTX + structOffset],\n                                         ScrWork[SW_CHA1ROTY + structOffset],\n                                         ScrWork[SW_CHA1ROTZ + structOffset]);\n\n    // More magic, wouldn't be Mage... I'll excuse myself\n    Position.y -= 228.0f + (baseScale * 1030.0f);\n\n  } else {\n    Scale = glm::vec2(ScrWork[SW_CHA1SIZEX + structOffset] +\n                          ScrWork[SW_CHA1SIZEX_OFS + structOfsOffset],\n                      ScrWork[SW_CHA1SIZEY + structOffset] +\n                          ScrWork[SW_CHA1SIZEY_OFS + structOfsOffset]) /\n            1000.0f;\n\n    Rotation = ScrWorkAnglesToQuaternion(\n        ScrWork[SW_CHA1ROTX + structOffset] +\n            ScrWork[SW_CHA1ROTX_OFS + structOfsOffset],\n        ScrWork[SW_CHA1ROTY + structOffset] +\n            ScrWork[SW_CHA1ROTY_OFS + structOfsOffset],\n        ScrWork[SW_CHA1ROTZ + structOffset] +\n            ScrWork[SW_CHA1ROTZ_OFS + structOfsOffset]);\n  }\n\n  Tint = ScrWorkGetColor(SW_CHA1FILTER + structOffset);\n\n  Tint.a = (ScrWork[SW_CHA1ALPHA + structOffset] +\n            ScrWork[SW_CHA1ALPHA_OFS + structOfsOffset]) /\n           256.0f;\n  if (ScrWork[SW_CHA1FADETYPE + structOffset] == 1) {\n    Tint.a *= ScrWork[SW_CHA1FADECT + structOffset] / 256.0f;\n  }\n\n  Face = ScrWork[SW_CHA1FACE + structOffset] << 16;\n\n  if (ScrWork[SW_CHA1ANIME_EYE + structOffset] == 0xFF) {\n    EyeFrame = CurEyeFrame[chaId];\n  } else {\n    EyeFrame = ScrWork[SW_CHA1ANIME_EYE + structOffset];\n  }\n\n  if (ScrWork[SW_CHA1ANIME_MOUTH + structOffset] != 0xFF) {\n    LipFrame = ScrWork[SW_CHA1ANIME_MOUTH + structOffset];\n  } else {\n    bool charSpeaking = false;\n    const uint32_t chaIndexMask = 1 << (chaId & 0x1F);\n\n    const bool skipping =\n        SkipModeEnabled &&\n        (!Profile::ConfigSystem::SkipRead || GetFlag(SF_MESREAD));\n    if (!skipping) {\n      for (uint8_t i = 0; i < 3; i++) {\n        if (!GetFlag(SF_CHAANIME + i) ||\n            (chaIndexMask & ScrWork[SW_ANIME0CHANO + i]) == 0) {\n          continue;\n        }\n\n        LipFrame = GetSoundLevel(static_cast<AudioChannelId>(AC_VOICE0 + i));\n        if (DialoguePages[i].CurrentLineVoiced) {\n          const AudioChannelState voiceState =\n              Channels[AC_VOICE0 + i]->GetState();\n          const bool voicePlaying =\n              voiceState != AudioChannelState::ACS_Paused &&\n              voiceState != AudioChannelState::ACS_Stopped;\n\n          if (!voicePlaying) {\n            LipFrame = 0;\n          } else if (LipFrame != 0) {\n            LipFrame = AnimeTable[CurMouthIndex[i]].first;\n          }\n        }\n\n        charSpeaking = true;\n        break;\n      }\n    }\n\n    if (!charSpeaking) LipFrame = 0;\n  }\n\n  UpdateStatesToDraw();\n}\n\nvoid Character2D::Render(const int layer) {\n  if (Status != LoadStatus::Loaded || !OnLayer(layer) || !Show) return;\n\n  if (Profile::CharaIsMvl) {\n    std::transform(MvlVertices.begin(), MvlVertices.end(), MvlVertices.begin(),\n                   [this](VertexBufferSprites vertex) {\n                     vertex.Tint = Tint;\n                     vertex.MaskUV = vertex.Position;\n                     return vertex;\n                   });\n\n    const glm::mat4 transformation = TransformationMatrix(\n        {0.0f, 0.0f}, Scale, glm::vec3(0.0f), Rotation, Position);\n\n    const bool renderWithBgEffect =\n        Profile::UseBgChaEffects && Background2D::LastRenderedBackground &&\n        Background2D::LastRenderedBackground->ChaBgEff.Loaded && UseBgEffect;\n    if (!renderWithBgEffect) {\n      Renderer->DrawPrimitives(CharaSpriteSheet, ShaderProgramType::Sprite,\n                               MvlVertices, MvlIndices, transformation);\n\n    } else {\n      const BgEff& bgEff = Background2D::LastRenderedBackground->ChaBgEff;\n      const glm::mat4 maskTransformation =\n          glm::scale(glm::mat4(1.0f), {1.0f / Profile::DesignWidth,\n                                       1.0f / Profile::DesignHeight, 1.0f}) *\n          transformation;\n\n      Renderer->DrawPrimitives(CharaSpriteSheet, &bgEff.BgEffSprite.Sheet,\n                               bgEff.Shader, MvlVertices, MvlIndices,\n                               transformation, maskTransformation);\n    }\n\n  } else {\n    const glm::mat4 transformation = TransformationMatrix(\n        Position, Scale, glm::vec3(Position, 0.0f), Rotation);\n    for (auto id : StatesToDraw) {\n      if (auto stateItr = States.find(id); stateItr != States.end()) {\n        Character2DState const& state = stateItr->second;\n        const auto& layData =\n            *std::get_if<Character2DState::LAYData>(&state.Data);\n        for (int i = 0; i < state.Count; i++) {\n          CharaSprite.Bounds = RectF(layData.TextureCoords[i].x,\n                                     layData.TextureCoords[i].y, 30.0f, 30.0f);\n          const glm::vec2 pos = layData.ScreenCoords[i] + Position;\n          const RectF dest(pos.x, pos.y, 30.0f, 30.0f);\n\n          Renderer->DrawSprite(CharaSprite, dest, transformation, Tint);\n        }\n      }\n    }\n  }\n}\n\nvoid CharacterPortrait2D::UpdateState(const int chaId) {\n  const glm::vec2 resolutionScale = {Profile::DesignWidth / 1280.0f,\n                                     Profile::DesignHeight / 720.0f};\n\n  Show = GetFlag(SF_FACEEX1DISP + chaId);\n  Face = ScrWork[SW_FACEEX1FACE + 5 * chaId] << 16;\n  Position =\n      glm::vec2(ScrWork[SW_FACEPOSX], ScrWork[SW_FACEPOSY]) * resolutionScale;\n\n  Scale = {1.0f, 1.0f};\n  Rotation = glm::quat();\n}\n\n}  // namespace Impacto"
  },
  {
    "path": "src/character2d.h",
    "content": "#pragma once\n\n#include <ankerl/unordered_dense.h>\n#include \"renderer/renderer.h\"\n#include \"texture/texture.h\"\n#include \"spritesheet.h\"\n#include \"loadable.h\"\n#include \"io/stream.h\"\n#include \"profile/game.h\"\n#include <vector>\n#include <memory>\n#include <variant>\n\nnamespace Impacto {\nstruct Character2DState {\n  struct LAYData {\n    LAYData() = default;\n    LAYData(int count)\n        : ScreenCoords(std::make_unique<glm::vec2[]>(count)),\n          TextureCoords(std::make_unique<glm::vec2[]>(count)) {}\n    std::unique_ptr<glm::vec2[]> ScreenCoords;\n    std::unique_ptr<glm::vec2[]> TextureCoords;\n  };\n  struct MVLData {\n    MVLData() = default;\n    MVLData(size_t count) : Indices(std::make_unique<uint16_t[]>(count)) {}\n    std::unique_ptr<uint16_t[]> Indices;\n  };\n\n  int Count;\n  std::variant<LAYData, MVLData> Data;\n\n  Character2DState() = default;\n  Character2DState(int count, bool isMVL)\n      : Count(count), Data(ConstructData(count, isMVL)) {}\n\n private:\n  static auto ConstructData(int count, bool isMVL)\n      -> std::variant<LAYData, MVLData> {\n    if (isMVL) {\n      return std::variant<LAYData, MVLData>{std::in_place_type<MVLData>, count};\n    } else {\n      return std::variant<LAYData, MVLData>{std::in_place_type<LAYData>, count};\n    }\n  }\n};\n\nconstexpr size_t MaxCharacters2D = 16;\n\nclass Character2D : public Loadable<Character2D, bool, uint32_t> {\n  friend Loadable<Character2D, bool, uint32_t>;\n\n public:\n  std::string MountPoint = \"chara\";\n\n  bool Show;\n  bool UseBgEffect = false;\n  std::array<int, 2> Layers;\n\n  Sprite CharaSprite;\n\n  int Face;\n  int LipFrame;\n  int EyeFrame;\n\n  glm::vec2 Position = {0.0f, 0.0f};\n  glm::quat Rotation = glm::quat();\n  glm::vec2 Scale = {1.0f, 1.0f};\n\n  glm::vec4 Tint = glm::vec4(1.0f);\n\n  virtual void UpdateState(int chaId);\n  static void UpdateEyeMouth();\n  void UpdateStatesToDraw();\n\n  void Render(int layer);\n\n protected:\n  bool LoadSync(uint32_t charaId);\n  void UnloadSync();\n  void MainThreadOnLoad(bool result);\n\n  bool OnLayer(int layer) {\n    return std::find(Layers.begin(), Layers.end(), layer) != Layers.end();\n  }\n\n  static inline std::array<int, MaxCharacters2D> CurEyeFrame{};\n  static inline std::array<int, 3> CurMouthIndex{19, 19, 19};\n  constexpr static std::array<std::pair<int, int>, 20> AnimeTable = {\n      std::pair{1, 10}, std::pair{2, 5}, std::pair{1, 10}, std::pair{2, 4},\n      std::pair{1, 7},  std::pair{2, 6}, std::pair{1, 9},  std::pair{2, 8},\n      std::pair{1, 15}, std::pair{0, 2}, std::pair{1, 9},  std::pair{2, 3},\n      std::pair{1, 7},  std::pair{0, 2}, std::pair{1, 10}, std::pair{2, 5},\n      std::pair{1, 7},  std::pair{2, 5}, std::pair{1, 7},  std::pair{2, 3},\n  };\n\n  Texture CharaTexture;\n  SpriteSheet CharaSpriteSheet;\n\n  ankerl::unordered_dense::map<int, Character2DState> States;\n  std::vector<int> StatesToDraw;\n\n  std::vector<VertexBufferSprites> MvlVertices;\n  std::vector<uint16_t> MvlIndices;\n};\n\nclass CharacterPortrait2D : public Character2D {\n public:\n  void UpdateState(int chaId) override;\n};\n\ninline std::array<Character2D, MaxCharacters2D> Characters2D;\ninline std::array<CharacterPortrait2D, 2> SpeakerPortraits;\n\n}  // namespace Impacto"
  },
  {
    "path": "src/characterviewer.cpp",
    "content": "#include \"io/io.h\"\n#include \"log.h\"\n#include \"modelviewer.h\"\n#include \"game.h\"\n\n// #include \"window.h\"\n#include \"renderer/renderer.h\"\n#include \"audio/audiosystem.h\"\n#include \"audio/audiostream.h\"\n#include \"audio/audiochannel.h\"\n#include \"background2d.h\"\n#include \"character2d.h\"\n\n#include <cstdint>\n\nnamespace Impacto {\nnamespace CharacterViewer {\n\nstatic void EnumerateBgm();\nstatic void EnumerateBackgrounds();\nstatic void EnumerateCharacters();\n\nstatic uint32_t CurrentCharacter;\nstatic uint32_t CurrentBackground;\nstatic uint32_t CurrentBgm;\nstatic int UiWindowWidth;\nstatic int UiWindowHeight;\nstatic int UiMsaaCount;\n\nstatic std::vector<std::string> BgmNames;\nstatic std::vector<uint32_t> BgmIds;\nstatic bool BgmChangeQueued;\n\nstatic float BgmFadeOut;\nstatic float BgmFadeIn;\nstatic bool BgmLoop;\n\nstatic std::vector<std::string> BackgroundNames;\nstatic std::vector<uint32_t> BackgroundIds;\n\nstatic std::vector<std::string> CharacterNames;\nstatic std::vector<uint32_t> CharacterIds;\n\nvoid Init() {\n  EnumerateBgm();\n  EnumerateBackgrounds();\n  EnumerateCharacters();\n\n  CurrentCharacter = 0;\n  CurrentBackground = 0;\n  CurrentBgm = 0;\n  BgmChangeQueued = false;\n\n  BgmFadeOut = 0.0f;\n  BgmFadeIn = 0.0f;\n  BgmLoop = true;\n\n  UiWindowWidth = Window->WindowWidth;\n  UiWindowHeight = Window->WindowHeight;\n  UiMsaaCount = Window->MsaaCount;\n}\n\nvoid Update(float dt) {\n  if (Window->WindowDimensionsChanged) {\n    UiWindowWidth = Window->WindowWidth;\n    UiWindowHeight = Window->WindowHeight;\n    UiMsaaCount = Window->MsaaCount;\n  }\n\n#ifndef IMPACTO_DISABLE_IMGUI\n  if (ImGui::Begin(\"Scene\", nullptr,\n                   ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove |\n                       ImGuiWindowFlags_NoResize)) {\n    ImGui::SetWindowSize(ImVec2(300.0f, Window->WindowHeight - 40.0f),\n                         ImGuiCond_Once);\n    ImGui::SetWindowPos(ImVec2(20.0f, 20.0f), ImGuiCond_Once);\n    ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x);\n\n    ImGui::Text(\"%.3f ms/frame (%.1f FPS)\", 1000.0f / ImGui::GetIO().Framerate,\n                ImGui::GetIO().Framerate);\n    ImGui::Dummy(ImVec2(0.0f, 20.0f));\n\n    if (ImGui::CollapsingHeader(\"Window\")) {\n      ImGui::Spacing();\n      ImGui::Text(\"Width\");\n      ImGui::DragInt(\"##Width\", &UiWindowWidth, 0.005f, 0, 8192, \"%d\",\n                     ImGuiSliderFlags_AlwaysClamp);\n      ImGui::Spacing();\n      ImGui::Text(\"Height\");\n      ImGui::DragInt(\"##Height\", &UiWindowHeight, 0.005f, 0, 8192, \"%d\",\n                     ImGuiSliderFlags_AlwaysClamp);\n      ImGui::Spacing();\n      ImGui::Text(\"MSAA\");\n      ImGui::DragInt(\"##MSAA\", &UiMsaaCount, 0.005f, 0, 16, \"%d\",\n                     ImGuiSliderFlags_AlwaysClamp);\n      ImGui::Spacing();\n      if (ImGui::Button(\"Resize\",\n                        ImVec2(ImGui::GetContentRegionAvail().x, 0.0f))) {\n        Window->SetDimensions(UiWindowWidth, UiWindowHeight, UiMsaaCount,\n                              Window->RenderScale);\n      }\n    }\n\n    ImGui::Spacing();\n\n    if (ImGui::CollapsingHeader(\"Background\", ImGuiTreeNodeFlags_DefaultOpen)) {\n      uint32_t LastBackground = CurrentBackground;\n\n      std::string const& comboPreviewValue = BackgroundNames[CurrentBackground];\n\n      ImGui::Spacing();\n      if (ImGui::BeginCombo(\"##backgroundCombo\", comboPreviewValue.c_str())) {\n        for (uint32_t i = 0; i < BackgroundNames.size(); i++) {\n          ImGui::PushID(i);\n          const bool isSelected = (CurrentBackground == i);\n          if (ImGui::Selectable(BackgroundNames[i].c_str(), isSelected))\n            CurrentBackground = i;\n          if (isSelected) ImGui::SetItemDefaultFocus();\n          ImGui::PopID();\n        }\n        ImGui::EndCombo();\n      }\n\n      if (LastBackground != CurrentBackground ||\n          Characters2D[0].Status == LoadStatus::Unloaded) {\n        Backgrounds2D[0]->LoadAsync(BackgroundIds[CurrentBackground]);\n      }\n    }\n\n    if (ImGui::CollapsingHeader(\"Character\", ImGuiTreeNodeFlags_DefaultOpen)) {\n      uint32_t LastCharacter = CurrentCharacter;\n\n      std::string const& comboPreviewValue = CharacterNames[CurrentCharacter];\n\n      ImGui::Spacing();\n      if (ImGui::BeginCombo(\"##characterCombo\", comboPreviewValue.c_str())) {\n        for (uint32_t i = 0; i < CharacterNames.size(); i++) {\n          ImGui::PushID(i);\n          const bool isSelected = (CurrentCharacter == i);\n          if (ImGui::Selectable(CharacterNames[i].c_str(), isSelected))\n            CurrentCharacter = i;\n          if (isSelected) ImGui::SetItemDefaultFocus();\n          ImGui::PopID();\n        }\n        ImGui::EndCombo();\n      }\n\n      if (LastCharacter != CurrentCharacter ||\n          Characters2D[0].Status == LoadStatus::Unloaded) {\n        Characters2D[0].LoadAsync(CharacterIds[CurrentCharacter] | 0x10000);\n      }\n      if (Characters2D[0].Status == LoadStatus::Loaded)\n        Characters2D[0].Show = true;\n\n      ImGui::Spacing();\n      ImGui::Text(\"Character X\");\n      ImGui::SliderFloat(\"##characterX\", &Characters2D[0].Position.x, -5000.0f,\n                         5000.0f);\n      ImGui::Spacing();\n      ImGui::Text(\"Character Y\");\n      ImGui::SliderFloat(\"##characterY\", &Characters2D[0].Position.y, -5000.0f,\n                         5000.0f);\n      ImGui::Spacing();\n      Characters2D[0].Face >>= 16;\n      ImGui::Text(\"Character Face\");\n      ImGui::InputInt(\"##characterFace\", &Characters2D[0].Face);\n      Characters2D[0].Face <<= 16;\n      ImGui::Spacing();\n      ImGui::Text(\"Character Eye\");\n      ImGui::InputInt(\"##characterEye\", &Characters2D[0].EyeFrame);\n      ImGui::Spacing();\n      ImGui::Text(\"Character Lip\");\n      ImGui::InputInt(\"##characterLip\", &Characters2D[0].LipFrame);\n    }\n\n    ImGui::Spacing();\n\n    if (ImGui::CollapsingHeader(\"BGM\", ImGuiTreeNodeFlags_DefaultOpen)) {\n      std::string const& comboPreviewValue = BgmNames[CurrentBgm];\n\n      ImGui::Spacing();\n      if (ImGui::BeginCombo(\"##bgmCombo\", comboPreviewValue.c_str())) {\n        for (uint32_t i = 0; i < BgmNames.size(); i++) {\n          ImGui::PushID(i);\n          const bool isSelected = (CurrentBgm == i);\n          if (ImGui::Selectable(BgmNames[i].c_str(), isSelected))\n            CurrentBgm = i;\n          if (isSelected) ImGui::SetItemDefaultFocus();\n          ImGui::PopID();\n        }\n        ImGui::EndCombo();\n      }\n      ImGui::Spacing();\n      ImGui::Checkbox(\"Loop (takes effect on switch)\", &BgmLoop);\n      ImGui::Spacing();\n      if (ImGui::Button(\"Switch\")) {\n        BgmChangeQueued = true;\n        Audio::Channels[Audio::AC_BGM0]->Stop(BgmFadeOut);\n      }\n      ImGui::Spacing();\n      ImGui::Text(\"Master volume\");\n      ImGui::SliderFloat(\"##masterVolume\", &Audio::MasterVolume, 0.0f, 1.0f);\n      ImGui::Spacing();\n      ImGui::Text(\"BGM volume\");\n      ImGui::SliderFloat(\"##bgmVolume\", &Audio::GroupVolumes[Audio::ACG_BGM],\n                         0.0f, 1.0f);\n      ImGui::Spacing();\n      ImGui::Text(\"Fade out duration\");\n      ImGui::SliderFloat(\"##fadeOutDur\", &BgmFadeOut, 0.0f, 5.0f);\n      ImGui::Spacing();\n      ImGui::Text(\"Fade in duration\");\n      ImGui::SliderFloat(\"##fadeInDur\", &BgmFadeIn, 0.0f, 5.0f);\n    }\n\n    ImGui::Spacing();\n  }\n\n  ImGui::End();\n#else\n  if (Characters2D[0].Status == LoadStatus::Unloaded) {\n    Backgrounds2D[0]->LoadAsync(BackgroundIds[0]);\n  }\n\n  if (Characters2D[0].Status == LoadStatus::Unloaded) {\n    Characters2D[0].LoadAsync(CharacterIds[0] | 0x10000);\n  }\n\n  if (Characters2D[0].Status == LoadStatus::Loaded) Characters2D[0].Show = true;\n#endif\n\n  if (BgmChangeQueued &&\n      Audio::Channels[Audio::AC_BGM0]->GetState() == Audio::ACS_Stopped) {\n    Audio::Channels[Audio::AC_BGM0]->Play(\"bgm\", BgmIds[CurrentBgm], BgmLoop,\n                                          BgmFadeIn);\n    BgmChangeQueued = false;\n  }\n}\n\nstatic void EnumerateBackgrounds() {\n  std::map<uint32_t, std::string> listing;\n  IoError err = Io::VfsListFiles(\"bg\", listing);\n  if (err != IoError_OK) {\n    ImpLog(LogLevel::Warning, LogChannel::General,\n           \"Failed to open backgrounds archive, aborting enumeration!\\n\");\n    return;\n  }\n\n  BackgroundNames.reserve(listing.size());\n  BackgroundIds.reserve(listing.size());\n\n  for (auto const& file : listing) {\n    BackgroundIds.push_back(file.first);\n    BackgroundNames.push_back(std::move(file.second));\n  }\n}\n\nstatic void EnumerateCharacters() {\n  std::map<uint32_t, std::string> listing;\n  IoError err = Io::VfsListFiles(\"chara\", listing);\n  if (err != IoError_OK) {\n    ImpLog(LogLevel::Warning, LogChannel::General,\n           \"Failed to open character archive, aborting enumeration!\\n\");\n    return;\n  }\n\n  CharacterNames.reserve(listing.size() / 2);\n  CharacterIds.reserve(listing.size() / 2);\n\n  for (auto const& file : listing) {\n    if (file.first % 2 == 0) {\n      CharacterIds.push_back(file.first);\n      CharacterNames.push_back(std::move(file.second));\n    }\n  }\n}\n\nstatic void EnumerateBgm() {\n  std::map<uint32_t, std::string> listing;\n  IoError err = Io::VfsListFiles(\"bgm\", listing);\n  if (err != IoError_OK) {\n    ImpLog(LogLevel::Warning, LogChannel::General,\n           \"Failed to open BGM archive, aborting enumeration!\\n\");\n    return;\n  }\n\n  BgmNames.reserve(listing.size());\n  BgmIds.reserve(listing.size());\n\n  for (auto const& file : listing) {\n    BgmIds.push_back(file.first);\n    BgmNames.push_back(std::move(file.second));\n  }\n}\n\n}  // namespace CharacterViewer\n}  // namespace Impacto"
  },
  {
    "path": "src/characterviewer.h",
    "content": "#pragma once\n\n#include \"impacto.h\"\n\n#include <glm/glm.hpp>\n\nnamespace Impacto {\nnamespace CharacterViewer {\n\nvoid Init();\nvoid Update(float dt);\n\n}  // namespace CharacterViewer\n}  // namespace Impacto"
  },
  {
    "path": "src/config.h.in",
    "content": "#pragma once\n\n#cmakedefine01 IMPACTO_ENABLE_SLOW_LOG\n#cmakedefine01 IMPACTO_GL_DEBUG\n#cmakedefine01 IMPACTO_HAVE_THREADS\n#cmakedefine01 IMPACTO_USE_SDL_HIGHDPI\n#cmakedefine IMPACTO_DISABLE_VULKAN\n#cmakedefine IMPACTO_DISABLE_DX9\n#cmakedefine IMPACTO_DISABLE_OPENGL\n#cmakedefine IMPACTO_DISABLE_FFMPEG\n#cmakedefine IMPACTO_DISABLE_OPENAL\n#cmakedefine IMPACTO_DISABLE_MSPACK\n#cmakedefine IMPACTO_DISABLE_IMGUI\n#cmakedefine IMPACTO_DISABLE_MMAP\n#cmakedefine IMPACTO_DISABLE_LIBASS"
  },
  {
    "path": "src/data/achievementsystem.cpp",
    "content": "#include \"achievementsystem.h\"\n\n#include \"../mem.h\"\n#include \"../profile/data/achievementsystem.h\"\n#include \"../profile/scriptvars.h\"\n\nnamespace Impacto {\nnamespace AchievementSystem {\n\nusing namespace Impacto::Profile::AchievementSystem;\nusing namespace Impacto::Profile::ScriptVars;\n\nvoid Init() { Impacto::Profile::AchievementSystem::Configure(); }\n\nclass AchievementFileLoader\n    : public Loadable<AchievementFileLoader, AchievementError> {\n  friend Loadable<AchievementFileLoader, AchievementError>;\n\n protected:\n  void UnloadSync() {}\n  AchievementError LoadSync() {\n    if (Implementation) {\n      return Implementation->MountAchievementFile(MainThreadCallback);\n    }\n\n    return AchievementError::Failed;\n  }\n\n  void MainThreadOnLoad(AchievementError result) {\n    if (MainThreadCallback) {\n      MainThreadCallback();\n      MainThreadCallback = nullptr;\n    }\n\n    // Let's not report errors until we finalize the implementation\n    result = AchievementError::OK;\n\n    ScrWork[SW_SAVEERRORCODE] = (int)result;\n  }\n\n private:\n  std::function<void(void)> MainThreadCallback;\n};\n\nstatic AchievementFileLoader Loader;\n\nLoadStatus GetLoadStatus() { return Loader.Status; }\n\nvoid MountAchievementFile() {\n  AchievementError result = Loader.LoadAsync() ? AchievementError::InProgress\n                                               : AchievementError::Failed;\n\n  ScrWork[SW_SAVEERRORCODE] = (int)result;\n}\n\nconst Achievement* GetAchievement(int id) {\n  if (!Implementation) return nullptr;\n  return Implementation->GetAchievement(id);\n}\n\nsize_t GetAchievementCount() {\n  if (!Implementation) return 0;\n  return Implementation->GetAchievementCount();\n}\n}  // namespace AchievementSystem\n}  // namespace Impacto"
  },
  {
    "path": "src/data/achievementsystem.h",
    "content": "#pragma once\n\n#include <magic_enum/magic_enum.hpp>\n#include \"../spritesheet.h\"\n#include \"../loadable.h\"\n\nnamespace Impacto {\nnamespace AchievementSystem {\n\nenum class AchievementDataType : int {\n  None,\n  PS3,\n};\nenum class AchievementError {\n  OK = 0,\n  InProgress = 1,\n  OutOfDiskSpace = 4,\n  Failed = 100,\n};\n\nclass Achievement {\n public:\n  Achievement(std::string name, std::string description, bool hidden,\n              const Sprite& icon)\n      : name(std::move(name)),\n        description(std::move(description)),\n        hidden(hidden),\n        icon(icon) {}\n\n  std::string const& Name() const { return name; }\n  std::string const& Description() const { return description; }\n  bool Hidden() const { return hidden; }\n  Sprite const& Icon() const { return icon; }\n\n protected:\n  std::string name;\n  std::string description;\n  bool hidden;\n  Sprite icon;\n};\n\nclass AchievementSystemBase {\n public:\n  virtual AchievementError MountAchievementFile(\n      std::function<void(void)>& mainThreadCallback) = 0;\n  //  virtual bool UnlockAchievement(int id) = 0;\n  virtual const Achievement* GetAchievement(int id) = 0;\n  virtual size_t GetAchievementCount() const = 0;\n};\n\ninline AchievementSystemBase* Implementation = nullptr;\n\nvoid Init();\n\nLoadStatus GetLoadStatus();\nvoid MountAchievementFile();\nconst Achievement* GetAchievement(int id);\nsize_t GetAchievementCount();\n\n}  // namespace AchievementSystem\n}  // namespace Impacto"
  },
  {
    "path": "src/data/achievementsystemps3.cpp",
    "content": "#include \"achievementsystemps3.h\"\n\n#include <sstream>\n#include <vector>\n#include \"pugixml.hpp\"\n\n#include \"../io/physicalfilestream.h\"\n#include \"../io/uncompressedstream.h\"\n#include \"../profile/data/achievementsystem.h\"\n#include \"../log.h\"\n\nint constexpr ICON_START = 3;\nint constexpr ICON_SIZE = 240;\n\nusing namespace Impacto::Profile::AchievementSystem;\n\nnamespace Impacto {\nnamespace AchievementSystem {\n\nsize_t AchievementSystemPS3::GetAchievementCount() const {\n  return Trophies.size();\n}\n\nAchievementError AchievementSystemPS3::MountAchievementFile(\n    std::function<void(void)>& mainThreadCallback) {\n  Io::Stream* baseStream;\n  IoError err =\n      Io::PhysicalFileStream::Create(AchievementDataPath, &baseStream);\n\n  if (err != IoError_OK) {\n    ImpLog(LogLevel::Error, LogChannel::IO, \"Couldn't open TROPHY.TRP\\n\");\n    return AchievementError::Failed;\n  }\n\n  TrophyDataHeader tdh{};\n  tdh.magic = Io::ReadBE<uint32_t>(baseStream);\n  tdh.version = Io::ReadBE<uint32_t>(baseStream);\n  tdh.file_size = Io::ReadBE<uint64_t>(baseStream);\n  tdh.file_count = Io::ReadBE<uint32_t>(baseStream);\n  tdh.entry_size = Io::ReadBE<uint32_t>(baseStream);\n  tdh.dev_flag = Io::ReadBE<uint32_t>(baseStream);\n\n  if (tdh.magic != PS3_MAGIC) {\n    ImpLog(LogLevel::Error, LogChannel::IO, \"Wrong magic in TROPHY.TRP\\n\");\n    delete baseStream;\n    return AchievementError::Failed;\n  }\n  TrophyDataEntries.resize(tdh.file_count);\n\n  // Skipping header padding\n  baseStream->Seek(0x24, RW_SEEK_CUR);\n\n  TrophyDataEntry* tropEntry = nullptr;\n\n  for (auto& entry : TrophyDataEntries) {\n    Io::ReadArrayWithoutSwap<32>((int8_t*)entry.name, baseStream);\n\n    tropEntry = strncmp(entry.name, \"TROP.SFM\", 32) == 0 ? &entry : tropEntry;\n\n    entry.offset = Io::ReadBE<uint64_t>(baseStream);\n    entry.size = Io::ReadBE<uint64_t>(baseStream);\n    baseStream->Seek(tdh.entry_size - sizeof(TrophyDataEntry), SEEK_CUR);\n  }\n\n  if (tropEntry == nullptr) {\n    ImpLog(LogLevel::Error, LogChannel::IO,\n           \"TROP.SFM entry not found in TROPHY.TRP\\n\");\n    delete baseStream;\n    return AchievementError::Failed;\n  }\n\n  baseStream->Seek(tropEntry->offset, RW_SEEK_SET);\n\n  // std::string is immutable and Io::Read uses memcpy\n  // Variable-length arrays aren't supported in MSVC\n  std::vector<char> rawTrop(tropEntry->size + 1, 0);\n\n  Io::ReadArrayBE<int8_t>((int8_t*)rawTrop.data(), baseStream, tropEntry->size);\n\n  pugi::xml_document trop;\n  pugi::xml_parse_result result = trop.load_string(rawTrop.data());\n\n  if (!result) {\n    ImpLog(LogLevel::Error, LogChannel::IO, \"Unable to load TROP.SFM\\n\");\n    delete baseStream;\n    return AchievementError::Failed;\n  }\n\n  std::vector<QueuedTrophy> queuedTrophies;\n\n  for (pugi::xml_node trophy = trop.first_child().child(\"trophy\"); trophy;\n       trophy = trophy.next_sibling()) {\n    QueuedTrophy queuedTrophy;\n    queuedTrophy.id = trophy.attribute(\"id\").as_int();\n    queuedTrophy.hidden = trophy.attribute(\"hidden\").as_bool();\n    queuedTrophy.name = trophy.child(\"name\").text().as_string();\n    queuedTrophy.description = trophy.child(\"detail\").text().as_string();\n    const char* ttypeStr = trophy.attribute(\"ttype\").as_string();\n\n    if (strlen(ttypeStr) == 0) {\n      ImpLog(LogLevel::Error, LogChannel::IO,\n             \"Missing trophy type in TROP.SFM\\n\");\n      delete baseStream;\n      return AchievementError::Failed;\n    }\n\n    const char ttype = ttypeStr[0];\n    if (strchr(TROPHY_TYPES, ttype) == nullptr) {\n      ImpLog(LogLevel::Error, LogChannel::IO,\n             \"Invalid trophy type in TROP.SFM\\n\");\n      delete baseStream;\n      return AchievementError::Failed;\n    }\n\n    queuedTrophy.ttype = (TrophyType)ttype;\n\n    auto& entry = TrophyDataEntries[queuedTrophy.id + ICON_START];\n\n    Io::Stream* iconStream;\n    err = Io::UncompressedStream::Create(baseStream, entry.offset, entry.size,\n                                         &iconStream);\n\n    if (err != IoError_OK) {\n      ImpLog(LogLevel::Error, LogChannel::IO,\n             \"Couldn't open icon for TROP{:03d}\\n\", queuedTrophy.id);\n      delete baseStream;\n      return AchievementError::Failed;\n    }\n\n    queuedTrophy.texture.Init(TexFmt_RGBA, ICON_SIZE, ICON_SIZE);\n\n    if (!queuedTrophy.texture.Load(iconStream)) {\n      ImpLog(LogLevel::Error, LogChannel::IO,\n             \"Unable to load texture for TROP{:03d}.PNG\\n\", queuedTrophy.id);\n      delete iconStream;\n      delete baseStream;\n      return AchievementError::Failed;\n    }\n    delete iconStream;\n\n    queuedTrophies.push_back(std::move(queuedTrophy));\n  }\n\n  delete baseStream;\n\n  mainThreadCallback = [this,\n                        queuedTrophies = std::move(queuedTrophies)]() mutable {\n    for (QueuedTrophy& trophy : queuedTrophies) {\n      SpriteSheet sheet;\n      sheet.DesignWidth = sheet.DesignHeight = ICON_SIZE;\n      sheet.Texture = trophy.texture.Submit();\n\n      Sprite icon = Sprite(sheet, 0, 0, ICON_SIZE, ICON_SIZE);\n      // Ensure the vector is large enough\n      if (std::ssize(Trophies) <= trophy.id) {\n        Trophies.resize(trophy.id + 1);\n      }\n      Trophies[trophy.id] = std::make_unique<Trophy>(\n          std::move(trophy.name), std::move(trophy.description), trophy.hidden,\n          trophy.ttype, icon);\n    }\n  };\n\n  return AchievementError::OK;\n}\n\nconst Achievement* AchievementSystemPS3::GetAchievement(int id) {\n  if (id < 0 || id >= std::ssize(Trophies)) return nullptr;\n  return Trophies[id].get();\n}\n}  // namespace AchievementSystem\n}  // namespace Impacto"
  },
  {
    "path": "src/data/achievementsystemps3.h",
    "content": "#pragma once\n\n#include <cstdint>\n\n#include \"achievementsystem.h\"\n#include \"../texture/texture.h\"\n#include <vector>\n#include <memory>\n\n#define PS3_MAGIC 0xDCA24D00\n#define TROPHY_TYPES \"BSGP\"\n\nnamespace Impacto {\nnamespace AchievementSystem {\n\ntypedef enum {\n  Bronze = 'B',\n  Silver = 'S',\n  Gold = 'G',\n  Platinum = 'P'\n} TrophyType;\n\n// From: https://www.psdevwiki.com/ps3/TROPHY.TRP\nstruct TrophyDataHeader {\n  uint32_t magic = 0;\n  uint32_t version = 0;  // 1\n  uint64_t file_size = 0;\n  uint32_t file_count = 0;\n  uint32_t entry_size = 0;\n  uint32_t dev_flag = 0;  // 1: dev\n};\n\nstruct TrophyDataEntry {\n  char name[32];\n  uint64_t offset;\n  uint64_t size;\n};\n\n// A trophy to be created on the main thread\nstruct QueuedTrophy {\n  int id;\n  std::string name;\n  std::string description;\n  bool hidden;\n  TrophyType ttype;\n  Texture texture;\n};\n\nclass Trophy : public Achievement {\n private:\n  [[maybe_unused]] TrophyType ttype;\n\n public:\n  Trophy(std::string name, std::string description, bool hidden,\n         TrophyType ttype, const Sprite icon)\n      : Achievement(std::move(name), std::move(description), hidden, icon),\n        ttype(ttype) {}\n};\n\nclass AchievementSystemPS3 : public AchievementSystemBase {\n public:\n  AchievementError MountAchievementFile(\n      std::function<void(void)>& mainThreadCallback) override;\n  const Achievement* GetAchievement(int id) override;\n  size_t GetAchievementCount() const override;\n\n private:\n  std::vector<TrophyDataEntry> TrophyDataEntries;\n  std::vector<std::unique_ptr<Trophy>> Trophies;\n};\n\n}  // namespace AchievementSystem\n}  // namespace Impacto"
  },
  {
    "path": "src/data/savesystem.cpp",
    "content": "#include \"savesystem.h\"\n\n#include \"../profile/data/savesystem.h\"\n#include \"../profile/scriptvars.h\"\n#include \"../profile/vm.h\"\n#include \"../mem.h\"\n\n#include <cstdint>\n#include <ctime>\n\nnamespace Impacto {\nnamespace SaveSystem {\n\nusing namespace Impacto::Profile::SaveSystem;\nusing namespace Impacto::Profile::ScriptVars;\n\nvoid Init() { Impacto::Profile::SaveSystem::Configure(); }\n\nclass SaveFileLoader : public Loadable<SaveFileLoader, SaveError> {\n  friend Loadable<SaveFileLoader, SaveError>;\n\n protected:\n  void UnloadSync() {}\n  SaveError LoadSync() {\n    return Implementation ? Implementation->MountSaveFile(QueuedTextures)\n                          : SaveError::Failed;\n  }\n\n  void MainThreadOnLoad(SaveError result) {\n    // Texture submission has to happen on the main thread\n    for (QueuedTexture& texture : QueuedTextures) {\n      texture.Id.get() = texture.Tex.Submit();\n    }\n\n    QueuedTextures.clear();\n\n    // Let's not report errors until we finalize the implementation\n    if (Profile::Vm::GameInstructionSet != Vm::InstructionSet::CC &&\n        Profile::Vm::GameInstructionSet != Vm::InstructionSet::CHLCC) {\n      result = SaveError::OK;\n    }\n\n    ScrWork[SW_SAVEERRORCODE] = (int)result;\n  }\n\n private:\n  std::vector<QueuedTexture> QueuedTextures;\n};\n\nclass SaveFileChecker : public Loadable<SaveFileChecker, SaveError> {\n  friend Loadable<SaveFileChecker, SaveError>;\n\n protected:\n  void UnloadSync() {}\n  SaveError LoadSync() {\n    return Implementation ? Implementation->CheckSaveFile() : SaveError::Failed;\n  }\n\n  void MainThreadOnLoad(SaveError result) {\n    // Let's not report errors until we finalize the implementation\n    if (Profile::Vm::GameInstructionSet != Vm::InstructionSet::CC &&\n        Profile::Vm::GameInstructionSet != Vm::InstructionSet::CHLCC) {\n      result = SaveError::OK;\n    }\n\n    ScrWork[SW_SAVEERRORCODE] = (int)result;\n  }\n};\n\nclass SaveFileWriter : public Loadable<SaveFileWriter, SaveError> {\n  friend Loadable<SaveFileWriter, SaveError>;\n\n protected:\n  void UnloadSync() {}\n  SaveError LoadSync() {\n    return Implementation ? Implementation->WriteSaveFile() : SaveError::Failed;\n  }\n\n  void MainThreadOnLoad(SaveError result) {\n    // Let's not report errors until we finalize the implementation\n    if (Profile::Vm::GameInstructionSet != Vm::InstructionSet::CC &&\n        Profile::Vm::GameInstructionSet != Vm::InstructionSet::CHLCC) {\n      result = SaveError::OK;\n    }\n\n    ScrWork[SW_SAVEERRORCODE] = (int)result;\n  }\n};\n\nstatic std::variant<SaveFileLoader, SaveFileChecker, SaveFileWriter> Loader;\n\ntemplate <typename TLoader>\nvoid ExecuteLoader() {\n  SaveError result = Loader.emplace<TLoader>().LoadAsync()\n                         ? SaveError::InProgress\n                         : SaveError::Failed;\n\n  ScrWork[SW_SAVEERRORCODE] = (int)result;\n}\n\nLoadStatus GetLoadStatus() {\n  return std::visit([](auto& loader) -> LoadStatus { return loader.Status; },\n                    Loader);\n}\n\nvoid MountSaveFile() { ExecuteLoader<SaveFileLoader>(); }\nvoid CheckSaveFile() { ExecuteLoader<SaveFileChecker>(); }\nvoid WriteSaveFile() { ExecuteLoader<SaveFileWriter>(); }\n\nvoid InitializeSystemData() {\n  if (Implementation) Implementation->InitializeSystemData();\n}\n\nvoid SaveSystemData() {\n  if (Implementation) Implementation->SaveSystemData();\n}\n\nSaveError LoadSystemData() {\n  return Implementation ? Implementation->LoadSystemData() : SaveError::Failed;\n}\n\nvoid SaveThumbnailData() {\n  if (Implementation) Implementation->SaveThumbnailData();\n}\n\nvoid SaveMemory() {\n  if (Implementation) Implementation->SaveMemory();\n}\n\nvoid LoadEntry(SaveType type, int id) {\n  if (Implementation) Implementation->LoadEntry(type, id);\n}\n\nvoid LoadMemoryNew(LoadProcess load) {\n  if (Implementation) Implementation->LoadMemoryNew(load);\n}\n\nvoid FlushWorkingSaveEntry(SaveType type, int id, int autoSaveType) {\n  if (Implementation)\n    Implementation->FlushWorkingSaveEntry(type, id, autoSaveType);\n}\n\nuint32_t GetSavePlayTime(SaveType type, int id) {\n  if (Implementation) return Implementation->GetSavePlayTime(type, id);\n  ImpLog(LogLevel::Warning, LogChannel::VMStub,\n         \"{:s}: save system not implemented, returning 0\\n\", __func__);\n  return 0;\n}\n\nuint8_t GetSaveFlags(SaveType type, int id) {\n  if (Implementation) return Implementation->GetSaveFlags(type, id);\n  ImpLog(LogLevel::Warning, LogChannel::VMStub,\n         \"{:s}: save system not implemented, returning 0\\n\", __func__);\n  return 0;\n}\n\nvoid SetSaveFlags(SaveType type, int id, uint8_t flags) {\n  if (Implementation) return Implementation->SetSaveFlags(type, id, flags);\n  ImpLog(LogLevel::Warning, LogChannel::VMStub,\n         \"{:s}: save system not implemented, returning 0\\n\", __func__);\n}\n\ntm const& GetSaveDate(SaveType type, int id) {\n  const static tm t = [] {\n    tm tmStruct{};\n    tmStruct.tm_mday = 1;\n    return tmStruct;\n  }();\n\n  if (Implementation) return Implementation->GetSaveDate(type, id);\n  ImpLog(LogLevel::Warning, LogChannel::VMStub,\n         \"{:s}: save system not implemented, returning dummy time\\n\", __func__);\n  return t;\n}\n\nuint8_t GetSaveStatus(SaveType type, int id) {\n  if (Implementation) return Implementation->GetSaveStatus(type, id);\n  ImpLog(LogLevel::Warning, LogChannel::VMStub,\n         \"{:s}: save system not implemented, returning 0\\n\", __func__);\n  return 0;\n}\n\nint GetSaveTitle(SaveType type, int id) {\n  if (Implementation) return Implementation->GetSaveTitle(type, id);\n  ImpLog(LogLevel::Warning, LogChannel::VMStub,\n         \"{:s}: save system not implemented, returning 0\\n\", __func__);\n  return 0;\n}\n\nuint32_t GetTipStatus(size_t tipId) {\n  if (Implementation) return Implementation->GetTipStatus(tipId);\n  ImpLog(LogLevel::Warning, LogChannel::VMStub,\n         \"{:s}: save system not implemented, returning 0\\n\", __func__);\n  return 0;\n}\n\nvoid SetTipStatus(size_t tipId, bool isLocked, bool isUnread, bool isNew) {\n  if (Implementation)\n    return Implementation->SetTipStatus(tipId, isLocked, isUnread, isNew);\n  ImpLog(LogLevel::Warning, LogChannel::VMStub,\n         \"{:s}: save system not implemented\\n\", __func__);\n}\n\nvoid SetLineRead(int scriptId, int lineId) {\n  if (Implementation) return Implementation->SetLineRead(scriptId, lineId);\n  ImpLog(LogLevel::Warning, LogChannel::VMStub,\n         \"{:s}: save system not implemented\\n\", __func__);\n}\n\nbool IsLineRead(int scriptId, int lineId) {\n  if (Implementation) return Implementation->IsLineRead(scriptId, lineId);\n  ImpLog(LogLevel::Warning, LogChannel::VMStub,\n         \"{:s}: save system not implemented, returing false\\n\", __func__);\n  return false;\n}\n\nvoid GetReadMessagesCount(int* totalMessageCount, int* readMessageCount) {\n  if (Implementation)\n    return Implementation->GetReadMessagesCount(totalMessageCount,\n                                                readMessageCount);\n  ImpLog(LogLevel::Warning, LogChannel::VMStub,\n         \"{:s}: save system not implemented, returning 0\\n\", __func__);\n  *totalMessageCount = 0;\n  *readMessageCount = 0;\n}\n\nvoid GetViewedEVsCount(int* totalEVCount, int* viewedEVCount) {\n  if (Implementation)\n    return Implementation->GetViewedEVsCount(totalEVCount, viewedEVCount);\n  ImpLog(LogLevel::Warning, LogChannel::VMStub,\n         \"{:s}: save system not implemented, returning 0\\n\", __func__);\n  *totalEVCount = 0;\n  *viewedEVCount = 0;\n}\n\nvoid GetEVStatus(int evId, int* totalVariations, int* viewedVariations) {\n  if (Implementation)\n    return Implementation->GetEVStatus(evId, totalVariations, viewedVariations);\n  ImpLog(LogLevel::Warning, LogChannel::VMStub,\n         \"{:s}: save system not implemented, returning 0\\n\", __func__);\n  *totalVariations = 0;\n  *viewedVariations = 0;\n}\n\nvoid SetEVStatus(int id) {\n  if (Implementation) return Implementation->SetEVStatus(id);\n  ImpLog(LogLevel::Warning, LogChannel::VMStub,\n         \"{:s}: save system not implemented\\n\", __func__);\n}\n\nbool GetEVVariationIsUnlocked(size_t evId, size_t variationIdx) {\n  if (Implementation)\n    return Implementation->GetEVVariationIsUnlocked(evId, variationIdx);\n  ImpLog(LogLevel::Warning, LogChannel::VMStub,\n         \"{:s}: save system not implemented, returing false\\n\", __func__);\n  return false;\n}\n\nbool GetBgmFlag(int id) {\n  if (Implementation) return Implementation->GetBgmFlag(id);\n  ImpLog(LogLevel::Warning, LogChannel::VMStub,\n         \"{:s}: save system not implemented, returing false\\n\", __func__);\n  return false;\n}\n\nvoid SetBgmFlag(int id, bool flag) {\n  if (Implementation) {\n    Implementation->SetBgmFlag(id, flag);\n    return;\n  }\n  ImpLog(LogLevel::Warning, LogChannel::VMStub,\n         \"{:s}: save system not implemented\\n\", __func__);\n}\n\nvoid SetCheckpointId(int id) {\n  if (Implementation) Implementation->SetCheckpointId(id);\n}\n\nstd::optional<uint8_t> GetQuickSaveOpenSlot() {\n  if (Implementation) return Implementation->GetQuickSaveOpenSlot();\n  ImpLog(LogLevel::Warning, LogChannel::VMStub,\n         \"{:s}: save system not implemented, returning nullopt\\n\", __func__);\n  return std::nullopt;\n}\n\nvoid UpdateQuickSaveRecentSortedId(int openSlot) {\n  if (Implementation)\n    return Implementation->UpdateQuickSaveRecentSortedId(openSlot);\n}\n\nSprite& GetSaveThumbnail(SaveType type, int id) {\n  if (Implementation) return Implementation->GetSaveThumbnail(type, id);\n  ImpLog(LogLevel::Warning, LogChannel::VMStub,\n         \"{:s}: save system not implemented, returning dummy sprite\\n\",\n         __func__);\n  static Sprite dummy;\n  return dummy;\n}\n\nSprite& GetWorkingSaveThumbnail() {\n  if (Implementation) return Implementation->GetWorkingSaveThumbnail();\n  ImpLog(LogLevel::Warning, LogChannel::VMStub,\n         \"{:s}: save system not implemented, returning dummy sprite\\n\",\n         __func__);\n  static Sprite dummy;\n  return dummy;\n}\n\nbool HasQSavedOnCurrentLine() {\n  if (Implementation) return Implementation->HasQSavedOnCurrentLine();\n  ImpLog(LogLevel::Warning, LogChannel::VMStub,\n         \"{:s}: save system not implemented, returning -1\\n\", __func__);\n  return false;\n}\n\nvoid SetQSavedOnCurrentLine(bool value) {\n  if (Implementation) Implementation->SetQSavedOnCurrentLine(value);\n}\n\nint SaveSystemBase::GetLockedQuickSaveCount() const {\n  return LockedQuickSaveCount;\n}\n\nvoid SaveSystemBase::SetLockedQuickSaveCount(int value) {\n  LockedQuickSaveCount = value;\n}\n\nbool SaveSystemBase::HasQSavedOnCurrentLine() const {\n  return QuickSavedOnCurrentLine;\n}\n\nvoid SaveSystemBase::SetQSavedOnCurrentLine(bool value) {\n  QuickSavedOnCurrentLine = value;\n}\n\nstd::optional<uint8_t> SaveSystemBase::GetQuickSaveOpenSlot() const {\n  // First unsaved slot\n  for (int i = 0; i < MaxSaveEntries; i++) {\n    if (QuickSaveEntries[QuickSaveRecentSortedId[i]]->Status == 0) {\n      return i;\n    };\n  }\n\n  // Oldest unlocked slot,\n  for (int i = MaxSaveEntries - 1; i >= 0; --i) {\n    if ((QuickSaveEntries[QuickSaveRecentSortedId[i]]->Flags & WriteProtect) ==\n        0) {\n      return i;\n    }\n  }\n\n  return std::nullopt;\n}\n\nvoid SaveSystemBase::UpdateQuickSaveRecentSortedId(int replacementSlot) {\n  auto replacementSlotId = QuickSaveRecentSortedId[replacementSlot];\n  std::shift_right(QuickSaveRecentSortedId.begin(),\n                   QuickSaveRecentSortedId.begin() + replacementSlot + 1, 1);\n  QuickSaveRecentSortedId[0] = replacementSlotId;\n}\n\n}  // namespace SaveSystem\n}  // namespace Impacto"
  },
  {
    "path": "src/data/savesystem.h",
    "content": "#pragma once\n#include \"../impacto.h\"\n#include \"../vm/vm.h\"\n\n#include <string>\n#include <magic_enum/magic_enum.hpp>\n#include <ctime>\n#include <numeric>\n#include \"../log.h\"\n#include \"../spritesheet.h\"\n#include \"../texture/texture.h\"\n#include \"../loadable.h\"\n\nnamespace Impacto {\nnamespace SaveSystem {\n\nenum class SaveDataType : int {\n  None,\n  CHLCC,\n  CCLCC,\n  MO6TW,\n};\nenum SaveFlagsMode { WriteProtect = 1 };\n\nenum class SaveError {\n  OK = 0,\n  InProgress = 1,\n  NotFound = 2,\n  WrongUser = 3,\n  Restart = 4,\n  OutOfDiskSpace = 4,\n  CorruptedFixing = 5,\n  Restart10 = 10,\n  Failed = 100,\n  Corrupted = 255\n};\n\nenum class SaveType { Full = 0, Quick = 1 };\n\nenum class LoadProcess { Vars = 0, Thread = 1 };\n\nint constexpr MaxSaveEntries = 48;\n\nconstexpr std::array<uint8_t, 8> Flbit = {1, 2, 4, 8, 0x10, 0x20, 0x40, 0x80};\n\nstruct ScriptMessageDataPair {\n  uint32_t LineCount;\n  uint32_t SaveDataOffset;\n};\n\n// A delayed Texture::Submit() operation to be executed on the main thread\nstruct QueuedTexture {\n  std::reference_wrapper<uint32_t> Id;\n  Texture Tex{};\n};\n\nclass SaveFileEntryBase {\n public:\n  uint8_t Status = 0;\n  uint32_t Checksum = 0;\n  std::tm SaveDate{};\n  uint32_t PlayTime = 0;\n  uint32_t SwTitle = 0;\n  uint8_t Flags = 0;\n  uint32_t SaveType = 0;\n  uint32_t MainThreadExecPriority = 0;\n  uint32_t MainThreadGroupId = 0;\n  uint32_t MainThreadWaitCounter = 0;\n  uint32_t MainThreadScriptParam = 0;\n  uint32_t MainThreadIp = 0;\n  uint32_t MainThreadLoopCounter = 0;\n  uint32_t MainThreadLoopAdr = 0;\n  uint32_t MainThreadCallStackDepth = 0;\n  union {\n    std::array<uint32_t, 8> MainThreadReturnAddresses{};\n    std::array<uint32_t, 8> MainThreadReturnIds;\n  };\n  std::array<uint32_t, 8> MainThreadReturnBufIds{};\n  uint32_t MainThreadScriptBufferId = 0;\n  std::array<int, 16> MainThreadVariables{};\n  uint32_t MainThreadDialoguePageId = 0;\n  Sprite SaveThumbnail{};\n};\n\nstruct SaveRecencyComparator {\n  bool operator()(SaveFileEntryBase const& entryA,\n                  SaveFileEntryBase const& entryB) const {\n    const int statusA = entryA.Status;\n    const int statusB = entryB.Status;\n    if (statusA == statusB) {\n      std::tm const& ta = entryA.SaveDate;\n      std::tm const& tb = entryB.SaveDate;\n      return timegm(ta) > timegm(tb);\n    }\n    return statusA > statusB;\n  }\n};\n\nclass SaveSystemBase {\n public:\n  virtual SaveError CheckSaveFile() const = 0;\n  virtual SaveError MountSaveFile(std::vector<QueuedTexture>& textures) = 0;\n\n  virtual void SaveMemory() = 0;\n  virtual void LoadEntry(SaveType type, int id) = 0;\n  virtual void LoadMemoryNew(LoadProcess){};\n  virtual void FlushWorkingSaveEntry(SaveType type, int id,\n                                     int autoSaveType) = 0;\n\n  // Reads and writes to system data are only safe if no other VM threads are\n  // executing. This data should be copied to a buffer so that\n  // flushing can happen without blocking other VM threads.\n  virtual void SaveSystemData() = 0;\n  virtual SaveError LoadSystemData() = 0;\n  virtual void InitializeSystemData() = 0;\n\n  virtual void SaveThumbnailData() = 0;\n  virtual SaveError WriteSaveFile() = 0;\n  virtual uint32_t GetSavePlayTime(SaveType type, int id) const = 0;\n  virtual uint8_t GetSaveFlags(SaveType type, int id) const = 0;\n  virtual void SetSaveFlags(SaveType type, int id, uint8_t flags) = 0;\n  virtual tm const& GetSaveDate(SaveType type, int id) const = 0;\n  virtual uint8_t GetSaveStatus(SaveType type, int id) const = 0;\n  virtual int GetSaveTitle(SaveType type, int id) const = 0;\n  virtual uint32_t GetTipStatus(size_t tipId) const = 0;\n  virtual void SetTipStatus(size_t tipId, bool isLocked, bool isUnread,\n                            bool isNew) = 0;\n  virtual void SetLineRead(int scriptId, int lineId) = 0;\n  virtual bool IsLineRead(int scriptId, int MessageId) const = 0;\n  virtual void GetReadMessagesCount(int* totalMessageCount,\n                                    int* readMessageCount) const = 0;\n  virtual void GetViewedEVsCount(int* totalEVCount,\n                                 int* viewedEVCount) const = 0;\n  virtual void GetEVStatus(int evId, int* totalVariations,\n                           int* viewedVariations) const = 0;\n  virtual void SetEVStatus(int id) = 0;\n  virtual bool GetEVVariationIsUnlocked(size_t evId,\n                                        size_t variationIdx) const = 0;\n  virtual bool GetBgmFlag(int id) const = 0;\n  virtual void SetBgmFlag(int id, bool flag) = 0;\n  virtual void SetCheckpointId(int id) = 0;\n  virtual Sprite& GetSaveThumbnail(SaveType type, int id) = 0;\n  std::optional<uint8_t> GetQuickSaveOpenSlot() const;\n  void UpdateQuickSaveRecentSortedId(int replacedSlot);\n  Sprite& GetWorkingSaveThumbnail() { return WorkingSaveThumbnail; }\n  int GetLockedQuickSaveCount() const;\n  void SetLockedQuickSaveCount(int value);\n  bool HasQSavedOnCurrentLine() const;\n  void SetQSavedOnCurrentLine(bool value);\n\n  template <typename T>\n  T* GetSaveEntry(SaveType type, int position) const\n    requires(std::derived_from<T, SaveFileEntryBase>)\n  {\n    switch (type) {\n      case SaveType::Full:\n        return static_cast<T*>(FullSaveEntries[position]);\n      case SaveType::Quick:\n        return static_cast<T*>(\n            QuickSaveEntries[QuickSaveRecentSortedId[position]]);\n    }\n    throw std::invalid_argument(fmt::format(\n        \"Invalid save type provided to GetSaveEntry: {}\", (int)type));\n  }\n\n protected:\n  SaveFileEntryBase* FullSaveEntries[MaxSaveEntries];\n  SaveFileEntryBase* QuickSaveEntries[MaxSaveEntries];\n  std::array<uint8_t, MaxSaveEntries> QuickSaveRecentSortedId = [] {\n    std::array<uint8_t, MaxSaveEntries> tmp;\n    std::iota(tmp.begin(), tmp.end(), 0);\n    return tmp;\n  }();\n  Sprite WorkingSaveThumbnail;\n  int LockedQuickSaveCount;\n  bool QuickSavedOnCurrentLine;\n};\n\ninline SaveSystemBase* Implementation = nullptr;\n\nvoid Init();\n\nLoadStatus GetLoadStatus();\nvoid CheckSaveFile();\nvoid MountSaveFile();\nvoid SaveMemory();\nvoid SaveThumbnailData();\nvoid SaveSystemData();\nSaveError LoadSystemData();\nvoid InitializeSystemData();\nvoid LoadEntry(SaveType type, int id);\nvoid LoadMemoryNew(LoadProcess process);\nvoid FlushWorkingSaveEntry(SaveType type, int id, int autoSaveType = 0);\nvoid WriteSaveFile();\nuint32_t GetSavePlayTime(SaveType type, int id);\nuint8_t GetSaveFlags(SaveType type, int id);\nvoid SetSaveFlags(SaveType type, int id, uint8_t flags);\ntm const& GetSaveDate(SaveType type, int id);\nuint8_t GetSaveStatus(SaveType type, int id);\nint GetSaveTitle(SaveType type, int id);\nuint32_t GetTipStatus(size_t tipId);\nvoid SetTipStatus(size_t tipId, bool isLocked, bool isUnread, bool isNew);\nvoid SetLineRead(int scriptId, int lineId);\nbool IsLineRead(int scriptId, int lineId);\nvoid GetReadMessagesCount(int* totalMessageCount, int* readMessageCount);\nvoid GetViewedEVsCount(int* totalEVCount, int* viewedEVCount);\nvoid GetEVStatus(int evId, int* totalVariations, int* viewedVariations);\nvoid SetEVStatus(int id);\nbool GetEVVariationIsUnlocked(size_t evId, size_t variationIdx);\nbool GetBgmFlag(int id);\nvoid SetBgmFlag(int id, bool setFlag);\nvoid SetCheckpointId(int id);\nstd::optional<uint8_t> GetQuickSaveOpenSlot();\nvoid UpdateQuickSaveRecentSortedId(int replacedSlot);\nSprite& GetSaveThumbnail(SaveType type, int id);\nSprite& GetWorkingSaveThumbnail();\nbool HasQSavedOnCurrentLine();\nvoid SetQSavedOnCurrentLine(bool value);\n\n}  // namespace SaveSystem\n}  // namespace Impacto\n\ntemplate <>\nstruct magic_enum::customize::enum_range<Impacto::SaveSystem::SaveError> {\n  static constexpr int min = 0;\n  static constexpr int max = 255;\n};"
  },
  {
    "path": "src/data/tipssystem.cpp",
    "content": "#include \"tipssystem.h\"\n\n#include \"../profile/data/tipssystem.h\"\n#include \"../vm/vm.h\"\n#include <vector>\n\nnamespace Impacto {\nnamespace TipsSystem {\n\nusing namespace Impacto::Profile::TipsSystem;\n\nTipsComparator::TipsComparator(uint32_t tipsTableId, uint32_t sortStringIndex,\n                               int tipIdStrIndex)\n    : SortString(nullptr), TipIdStrIndex(tipIdStrIndex) {\n  auto [scrBufId, offset] =\n      Vm::ScriptGetTextTableStrAddress(tipsTableId, sortStringIndex);\n  SortString = &Vm::ScriptBuffers[scrBufId][offset];\n  int i = 0;\n  int distance = 0;\n  while (SortString[i] != 0xFF) {\n    if (SortString[i] & 0x80) {\n      uint16_t sc3Char = SDL_SwapBE16(UnalignedRead<uint16_t>(SortString + i));\n      Sc3SortMap[sc3Char] = distance++;\n      i += 2;\n    } else {\n      ImpLogSlow(LogLevel::Warning, LogChannel::VM,\n                 \"TipsComparator: SC3 Tag Found in Sort String\\n\",\n                 SortString[i]);\n      i++;\n    }\n  }\n}\n\nbool TipsComparator::operator()(int a, int b) const {\n  auto* aRecord = TipsSystem::GetTipRecord(a);\n  auto* bRecord = TipsSystem::GetTipRecord(b);\n  uint32_t tipsScrBufId = TipsSystem::GetTipsScriptBufferId();\n  uint8_t* aString =\n      &Vm::ScriptBuffers[tipsScrBufId][aRecord->StringAdr[TipIdStrIndex]];\n  uint8_t* bString =\n      &Vm::ScriptBuffers[tipsScrBufId][bRecord->StringAdr[TipIdStrIndex]];\n\n  int aIndex = 0;\n  int bIndex = 0;\n\n  while (aString[aIndex] != 0xff && bString[bIndex] != 0xff) {\n    if ((aString[aIndex] & 0x80) == 0) {\n      aIndex++;\n      continue;\n    }\n    if ((bString[bIndex] & 0x80) == 0) {\n      bIndex++;\n      continue;\n    }\n    uint16_t aSc3Char = SDL_SwapBE16(UnalignedRead<uint16_t>(aString + aIndex));\n    aIndex += 2;\n\n    uint16_t bSc3Char = SDL_SwapBE16(UnalignedRead<uint16_t>(bString + bIndex));\n    bIndex += 2;\n    if (aSc3Char != bSc3Char) {\n      auto aSortValue = Sc3SortMap.find(aSc3Char);\n      auto bSortValue = Sc3SortMap.find(bSc3Char);\n      if (aSortValue != Sc3SortMap.end() && bSortValue != Sc3SortMap.end()) {\n        return aSortValue->second < bSortValue->second;\n      } else {\n        ImpLogSlow(LogLevel::Error, LogChannel::VM,\n                   \"TipsComparator: Character Not Found in Sort String\\n\",\n                   aSc3Char, bSc3Char);\n        return aSc3Char < bSc3Char;\n      }\n    }\n  }\n  // If strings are all the same, return the shorter one\n  return aString[aIndex] == 0xff && bString[bIndex] != 0xff;\n}\n\nvoid Init() { Configure(); }\n\nvoid DataInit(uint32_t scriptBufferId, uint32_t tipsDataAdr,\n              uint32_t tipsDataSize) {\n  if (Implementation)\n    return Implementation->DataInit(scriptBufferId, tipsDataAdr, tipsDataSize);\n\n  ImpLog(LogLevel::Warning, LogChannel::VMStub,\n         \"{:s}: tips system not implemented, doing nothing\\n\", __func__);\n}\n\nvoid UpdateTipRecords() {\n  if (Implementation) return Implementation->UpdateTipRecords();\n  ImpLog(LogLevel::Warning, LogChannel::VMStub,\n         \"{:s}: tips system not implemented, doing nothing\\n\", __func__);\n}\n\nvoid SetTipLockedState(size_t id, bool state) {\n  if (Implementation) return Implementation->SetTipLockedState(id, state);\n  ImpLog(LogLevel::Warning, LogChannel::VMStub,\n         \"{:s}: tips system not implemented, doing nothing\\n\", __func__);\n}\n\nvoid SetTipUnreadState(size_t id, bool state) {\n  if (Implementation) return Implementation->SetTipUnreadState(id, state);\n  ImpLog(LogLevel::Warning, LogChannel::VMStub,\n         \"{:s}: tips system not implemented, doing nothing\\n\", __func__);\n}\n\nvoid SetTipNewState(size_t id, bool state) {\n  if (Implementation) return Implementation->SetTipNewState(id, state);\n  ImpLog(LogLevel::Warning, LogChannel::VMStub,\n         \"{:s}: tips system not implemented, doing nothing\\n\", __func__);\n}\n\nbool GetTipLockedState(size_t id) {\n  if (Implementation) return Implementation->GetTipLockedState(id);\n  ImpLog(LogLevel::Warning, LogChannel::VMStub,\n         \"{:s}: tips system not implemented, returning false\\n\", __func__);\n  return false;\n}\n\nstd::vector<TipsDataRecord>* GetTipRecords() {\n  if (Implementation) return &Implementation->Records;\n  ImpLog(LogLevel::Warning, LogChannel::VMStub,\n         \"{:s}: tips system not implemented, returning NULL\\n\", __func__);\n  return nullptr;\n}\n\nTipsDataRecord* GetTipRecord(size_t id) {\n  if (Implementation) return &Implementation->Records[id];\n  ImpLog(LogLevel::Warning, LogChannel::VMStub,\n         \"{:s}: tips system not implemented, returning NULL\\n\", __func__);\n  return nullptr;\n}\n\nsize_t GetTipCount() {\n  if (Implementation) return Implementation->TipEntryCount;\n  ImpLog(LogLevel::Warning, LogChannel::VMStub,\n         \"{:s}: tips system not implemented, returning 0\\n\", __func__);\n  return 0;\n}\n\nuint8_t GetTipsScriptBufferId() {\n  if (Implementation) return Implementation->ScriptBufferId;\n  ImpLog(LogLevel::Warning, LogChannel::VMStub,\n         \"{:s}: tips system not implemented, returning 0\\n\", __func__);\n  return 0;\n}\n\nstd::vector<uint16_t>& GetNewTipsIndices() {\n  if (Implementation) return Implementation->NewTipsIndices;\n  ImpLog(LogLevel::Warning, LogChannel::VMStub,\n         \"{:s}: tips system not implemented, returning empty vector\\n\",\n         __func__);\n  static std::vector<uint16_t> empty;\n  return empty;\n}\n}  // namespace TipsSystem\n}  // namespace Impacto"
  },
  {
    "path": "src/data/tipssystem.h",
    "content": "#pragma once\n#include \"../impacto.h\"\n\n#include <vector>\n#include <array>\n#include <magic_enum/magic_enum.hpp>\n#include \"../log.h\"\n\nnamespace Impacto {\nnamespace TipsSystem {\n\nenum class TipsSystemType : int {\n  None,\n  MO6TW,\n  CHLCC,\n  CCLCC,\n};\nint constexpr MaxTipStrings = 10;\n\nstruct TipsDataRecord {\n  uint16_t Id = 0;\n  uint16_t CategoryLetterIndex = 0;\n  uint16_t ThumbnailIndex = 0;\n  uint16_t NumberOfContentStrings = 0;\n  std::array<uint32_t, MaxTipStrings> StringAdr = {};\n  bool IsLocked = true;\n  bool IsUnread = true;\n  bool IsNew = true;\n};\n\nclass TipsSystemBase {\n public:\n  TipsSystemBase(size_t maxTipsCount) : Records(maxTipsCount) {}\n  virtual ~TipsSystemBase() {}\n  virtual void DataInit(uint32_t scriptBufferId, uint32_t tipsDataAdr,\n                        uint32_t tipsDataSize) = 0;\n  virtual void UpdateTipRecords() = 0;\n  virtual void SetTipLockedState(size_t id, bool state) = 0;\n  virtual void SetTipUnreadState(size_t id, bool state) = 0;\n  virtual void SetTipNewState(size_t id, bool state) = 0;\n\n  virtual bool GetTipLockedState(size_t id) = 0;\n\n  std::vector<TipsDataRecord> Records;\n  std::vector<uint16_t> NewTipsIndices;\n  size_t TipEntryCount = 0;\n  uint8_t ScriptBufferId = 0;\n};\n\nstruct TipsComparator {\n  TipsComparator(uint32_t tipsTableId, uint32_t sortStringIndex,\n                 int tipIdStrIndex);\n  bool operator()(int a, int b) const;\n  uint8_t* SortString;\n  int TipIdStrIndex;\n  ankerl::unordered_dense::map<uint16_t, int> Sc3SortMap;\n};\n\ninline std::unique_ptr<TipsSystemBase> Implementation;\n\nvoid Init();\nvoid DataInit(uint32_t scriptBufferId, uint32_t tipsDataAdr,\n              uint32_t tipsDataSize);\nvoid UpdateTipRecords();\nvoid SetTipLockedState(size_t id, bool state);\nvoid SetTipUnreadState(size_t id, bool state);\nvoid SetTipNewState(size_t id, bool state);\nuint8_t GetTipsScriptBufferId();\nbool GetTipLockedState(size_t id);\n\nstd::vector<TipsDataRecord>* GetTipRecords();\nTipsDataRecord* GetTipRecord(size_t id);\nsize_t GetTipCount();\nstd::vector<uint16_t>& GetNewTipsIndices();\n\n}  // namespace TipsSystem\n}  // namespace Impacto"
  },
  {
    "path": "src/debugmenu.cpp",
    "content": "#include \"debugmenu.h\"\n\n#include <imgui.h>\n#include <sstream>\n#include <limits>\n\n#include \"game.h\"\n#include \"mem.h\"\n#include \"log.h\"\n#include \"inputsystem.h\"\n#include \"vm/vm.h\"\n#include \"io/vfs.h\"\n#include \"background2d.h\"\n#include \"character2d.h\"\n#include \"profile/sprites.h\"\n#include \"profile/vm.h\"\n\nnamespace Impacto {\nnamespace DebugMenu {\n\nstatic int ScrWorkNumberFormat = 0;\nstatic int ScrWorkIndexStart = 0;\nstatic int ScrWorkIndexEnd = 0;\nstatic int FlagWorkIndexStart = 0;\nstatic int FlagWorkIndexEnd = 0;\n\nstatic std::map<uint32_t, std::map<int, int>> ScriptDebugByteCodePosToLine;\nstatic std::map<uint32_t, std::map<int, int>> ScriptDebugLineToByteCodePos;\nstatic std::map<uint32_t, std::vector<std::string>> ScriptDebugSource;\n\nstatic bool ScriptVariablesEditorShown = false;\nstatic bool ObjectViewerShown = false;\nstatic bool UiViewerShown = false;\nstatic bool ScriptDebuggerShown = false;\n\nstatic void HelpMarker(const char* desc) {\n  ImGui::TextDisabled(\"(?)\");\n  if (ImGui::BeginItemTooltip()) {\n    ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f);\n    ImGui::TextUnformatted(desc);\n    ImGui::PopTextWrapPos();\n    ImGui::EndTooltip();\n  }\n}\n\nstatic void ImageTooltip(ImVec2 pos, ImTextureID textureId, float texWidth,\n                         float texHeight) {\n  if (ImGui::BeginItemTooltip()) {\n    float regionSz = 32.0f;\n    float regionX = ImGui::GetIO().MousePos.x - pos.x - regionSz * 0.5f;\n    float regionY = ImGui::GetIO().MousePos.y - pos.y - regionSz * 0.5f;\n    float zoom = 6.0f;\n    if (regionX < 0.0f) {\n      regionX = 0.0f;\n    } else if (regionX > texWidth - regionSz) {\n      regionX = texWidth - regionSz;\n    }\n    if (regionY < 0.0f) {\n      regionY = 0.0f;\n    } else if (regionY > texHeight - regionSz) {\n      regionY = texHeight - regionSz;\n    }\n    ImGui::Text(\"Min: (%.2f, %.2f)\", regionX, regionY);\n    ImGui::Text(\"Max: (%.2f, %.2f)\", regionX + regionSz, regionY + regionSz);\n    ImVec2 uv0 = ImVec2((regionX) / texWidth, (regionY) / texHeight);\n    ImVec2 uv1 = ImVec2((regionX + regionSz) / texWidth,\n                        (regionY + regionSz) / texHeight);\n    ImGui::Image(textureId, ImVec2(regionSz * zoom, regionSz * zoom), uv0, uv1);\n    ImGui::EndTooltip();\n  }\n}\n\nstatic void ParseScriptDebugData(uint32_t scriptId) {\n  if (ScriptDebugSource.find(scriptId) != ScriptDebugSource.end()) return;\n\n  Io::Stream* stream;\n  if (Io::VfsOpen(\"scriptdbg\", scriptId, &stream) != IoError_OK) return;\n\n  std::map<int, int> byteCodePosToLine;\n  std::map<int, int> lineToByteCodePos;\n  std::vector<std::string> sourceLines;\n\n  std::string content;\n  content.resize(stream->Meta.Size);\n  int64_t size = stream->Read(&content[0], stream->Meta.Size);\n  content.resize(size);\n\n  std::istringstream ss = std::istringstream(content, std::ios::in);\n  int lineId = 0;\n  for (std::string line; std::getline(ss, line); lineId++) {\n    if (line.empty()) continue;\n\n    if (line[line.size() - 1] == '\\r') line.pop_back();\n    size_t firstColLength = line.find(',');\n\n    if (firstColLength == std::string::npos ||\n        firstColLength == line.length() - 1)\n      continue;\n\n    size_t secondColLength = line.find(',', firstColLength + 1);\n    if (secondColLength == std::string::npos ||\n        secondColLength == line.length() - 1)\n      continue;\n\n    uint32_t byteCodePos = std::atoi(line.substr(0, firstColLength).c_str());\n\n    std::string sourceLine =\n        line.substr(secondColLength + 2, line.length() - secondColLength);\n\n    sourceLines.push_back(sourceLine);\n    byteCodePosToLine[byteCodePos] = lineId;\n    lineToByteCodePos[lineId] = byteCodePos;\n  }\n\n  ScriptDebugByteCodePosToLine[scriptId] = byteCodePosToLine;\n  ScriptDebugLineToByteCodePos[scriptId] = lineToByteCodePos;\n  ScriptDebugSource[scriptId] = sourceLines;\n\n  delete stream;\n}\n\nvoid ShowSingleWindow() {\n  if (ImGui::Begin(\"Debug Menu\", &DebugMenuShown)) {\n    ImGui::Text(\"%.3f ms/frame (%.1f FPS)\", 1000.0f / ImGui::GetIO().Framerate,\n                ImGui::GetIO().Framerate);\n    ImGui::Text(\"Cursor Pos: (%.1f,%.1f)\", ImGui::GetIO().MousePos.x,\n                ImGui::GetIO().MousePos.y);\n\n    if (ImGui::BeginTabBar(\"DebugTabBar\", ImGuiTabBarFlags_None)) {\n      if (ImGui::BeginTabItem(\"\\\"Debug Editer\\\"\")) {\n        ShowScriptVariablesEditor();\n        ImGui::EndTabItem();\n      }\n      if (ImGui::BeginTabItem(\"Objects\")) {\n        ShowObjects();\n        ImGui::EndTabItem();\n      }\n      if (ImGui::BeginTabItem(\"UI\")) {\n        ImGui::EndTabItem();\n      }\n      if (ImGui::BeginTabItem(\"Script Debugger\")) {\n        ShowScriptDebugger();\n        ImGui::EndTabItem();\n      }\n      ImGui::EndTabBar();\n    }\n  }\n\n  ImGui::End();\n}\n\nvoid ShowDockableArea() {\n  if (ImGui::Begin(\"Debug Menu##DebugMenuDockArea\", &DebugMenuShown,\n                   ImGuiWindowFlags_MenuBar)) {\n    if (ImGui::BeginMenuBar()) {\n      if (ImGui::BeginMenu(\"Tools\")) {\n        ImGui::MenuItem(\"\\\"Debug Editer\\\"\", NULL, &ScriptVariablesEditorShown);\n        ImGui::MenuItem(\"Objects\", NULL, &ObjectViewerShown);\n        ImGui::MenuItem(\"UI\", NULL, &UiViewerShown);\n        ImGui::MenuItem(\"Script Debugger\", NULL, &ScriptDebuggerShown);\n        ImGui::EndMenu();\n      }\n      ImGui::EndMenuBar();\n    }\n\n    ImGui::Text(\"%.3f ms/frame (%.1f FPS)\", 1000.0f / ImGui::GetIO().Framerate,\n                ImGui::GetIO().Framerate);\n  }\n  ImGui::End();\n\n  if (ScriptVariablesEditorShown) {\n    if (ImGui::Begin(\"\\\"Debug Editer\\\"##ScriptVarEditorWindow\"),\n        ScriptVariablesEditorShown) {\n      ShowScriptVariablesEditor();\n    }\n    ImGui::End();\n  }\n\n  if (ObjectViewerShown) {\n    if (ImGui::Begin(\"Objects##ObjectViewerWindow\"), ObjectViewerShown) {\n      ShowObjects();\n    }\n    ImGui::End();\n  }\n\n  if (UiViewerShown) {\n    if (ImGui::Begin(\"UI##UIViewerWindow\"), UiViewerShown) {\n      ImGui::Text(\"Not available\");\n    }\n    ImGui::End();\n  }\n\n  if (ScriptDebuggerShown) {\n    if (ImGui::Begin(\"Script Debugger##ScriptDebuggerWindow\"),\n        ScriptDebuggerShown) {\n      ShowScriptDebugger();\n    }\n    ImGui::End();\n  }\n\n  if (!DebugMenuShown) {\n    ScriptVariablesEditorShown = false;\n    ObjectViewerShown = false;\n    UiViewerShown = false;\n    ScriptDebuggerShown = false;\n  }\n}\n\nvoid Show() {\n  if (((Input::KeyboardButtonIsDown[SDL_SCANCODE_LALT] &&\n        Input::KeyboardButtonWentDown[SDL_SCANCODE_D]) ||\n       (Input::ControllerButtonWentDown[SDL_CONTROLLER_BUTTON_RIGHTSHOULDER] &&\n        Input::ControllerButtonWentDown[SDL_CONTROLLER_BUTTON_Y])) &&\n      !DebugMenuShown)\n    DebugMenuShown = true;\n\n  if (DebugMenuShown) {\n    if (ImGui::GetIO().ConfigFlags & ImGuiConfigFlags_DockingEnable) {\n      ShowDockableArea();\n    } else {\n      ShowSingleWindow();\n    }\n  } else {\n    Vm::DebugThreadId = std::numeric_limits<uint32_t>::max();\n  }\n}\n\nvoid ShowScriptVariablesEditor() {\n  ImGui::PushItemWidth(10.0f * ImGui::GetFontSize());\n\n  if (ImGui::TreeNode(\"ScrWork Config\")) {\n    ImGui::Text(\"Display number format\");\n    ImGui::SameLine();\n    ImGui::RadioButton(\"DEC\", &ScrWorkNumberFormat, 0);\n    ImGui::SameLine();\n    ImGui::RadioButton(\"HEX\", &ScrWorkNumberFormat, 1);\n\n    ImGui::PushButtonRepeat(true);\n    ImGui::Spacing();\n    ImGui::Text(\"Start index\");\n    ImGui::DragInt(\"##ScrWorkStartIndex\", &ScrWorkIndexStart, 0.5f, 0, 8000,\n                   \"%04d\", ImGuiSliderFlags_AlwaysClamp);\n    ImGui::SameLine();\n    if (ImGui::Button(\"+1##IncreaseScrWorkStartIndex\")) ScrWorkIndexStart += 1;\n    ImGui::SameLine();\n    if (ImGui::Button(\"-1##DecreaseScrWorkStartIndex\")) ScrWorkIndexStart -= 1;\n    ImGui::SameLine();\n    if (ImGui::Button(\"+100##IncreaseScrWorkStartIndex1\"))\n      ScrWorkIndexStart += 100;\n    ImGui::SameLine();\n    if (ImGui::Button(\"-100##DecreaseScrWorkStartIndex1\"))\n      ScrWorkIndexStart -= 100;\n    ImGui::SameLine();\n    if (ImGui::Button(\"+1000##IncreaseScrWorkStartIndex2\"))\n      ScrWorkIndexStart += 1000;\n    ImGui::SameLine();\n    if (ImGui::Button(\"-1000##DecreaseScrWorkStartIndex2\"))\n      ScrWorkIndexStart -= 1000;\n    ImGui::PopButtonRepeat();\n\n    ImGui::Spacing();\n    ImGui::Text(\"End index\");\n    ImGui::DragInt(\"##ScrWorkEndIndex\", &ScrWorkIndexEnd, 0.5f, 0, 8000, \"%04d\",\n                   ImGuiSliderFlags_AlwaysClamp);\n\n    ImGui::PushButtonRepeat(true);\n    ImGui::SameLine();\n    if (ImGui::Button(\"+1##IncreaseScrWorkEndIndex\")) ScrWorkIndexEnd += 1;\n    ImGui::SameLine();\n    if (ImGui::Button(\"-1##DecreaseScrWorkEndIndex\")) ScrWorkIndexEnd -= 1;\n    ImGui::SameLine();\n    if (ImGui::Button(\"+100##IncreaseScrWorkEndIndex1\")) ScrWorkIndexEnd += 100;\n    ImGui::SameLine();\n    if (ImGui::Button(\"-100##DecreaseScrWorkEndIndex1\")) ScrWorkIndexEnd -= 100;\n    ImGui::SameLine();\n    if (ImGui::Button(\"+1000##IncreaseScrWorkEndIndex2\"))\n      ScrWorkIndexEnd += 1000;\n    ImGui::SameLine();\n    if (ImGui::Button(\"-1000##DecreaseScrWorkEndIndex2\"))\n      ScrWorkIndexEnd -= 1000;\n    ImGui::PopButtonRepeat();\n\n    if (ScrWorkIndexEnd < ScrWorkIndexStart)\n      ScrWorkIndexEnd = ScrWorkIndexStart;\n\n    ImGui::TreePop();\n  }\n\n  ImGui::Spacing();\n\n  if (ImGui::TreeNode(\"FlagWork Config\")) {\n    ImGui::Text(\"Start index\");\n    ImGui::DragInt(\"##FlagWorkStartIndex\", &FlagWorkIndexStart, 0.5f, 0, 7000,\n                   \"%04d\", ImGuiSliderFlags_AlwaysClamp);\n\n    ImGui::PushButtonRepeat(true);\n    ImGui::SameLine();\n    if (ImGui::Button(\"+1##IncreaseFlagWorkStartIndex\"))\n      FlagWorkIndexStart += 1;\n    ImGui::SameLine();\n    if (ImGui::Button(\"-1##DecreaseFlagWorkStartIndex\"))\n      FlagWorkIndexStart -= 1;\n    ImGui::SameLine();\n    if (ImGui::Button(\"+100##IncreaseFlagWorkStartIndex1\"))\n      FlagWorkIndexStart += 100;\n    ImGui::SameLine();\n    if (ImGui::Button(\"-100##DecreaseFlagWorkStartIndex1\"))\n      FlagWorkIndexStart -= 100;\n    ImGui::SameLine();\n    if (ImGui::Button(\"+1000##IncreaseFlagWorkStartIndex2\"))\n      FlagWorkIndexStart += 1000;\n    ImGui::SameLine();\n    if (ImGui::Button(\"-1000##DecreaseFlagWorkStartIndex2\"))\n      FlagWorkIndexStart -= 1000;\n    ImGui::PopButtonRepeat();\n\n    ImGui::Spacing();\n    ImGui::Text(\"End index\");\n    ImGui::DragInt(\"##FlagWorkEndIndex\", &FlagWorkIndexEnd, 0.5f, 0, 7000,\n                   \"%04d\", ImGuiSliderFlags_AlwaysClamp);\n\n    ImGui::PushButtonRepeat(true);\n    ImGui::SameLine();\n    if (ImGui::Button(\"+1##IncreaseFlagWorkEndIndex\")) FlagWorkIndexEnd += 1;\n    ImGui::SameLine();\n    if (ImGui::Button(\"-1##DecreaseFlagWorkEndIndex\")) FlagWorkIndexEnd -= 1;\n    ImGui::SameLine();\n    if (ImGui::Button(\"+100##IncreaseFlagWorkEndIndex1\"))\n      FlagWorkIndexEnd += 100;\n    ImGui::SameLine();\n    if (ImGui::Button(\"-100##DecreaseFlagWorkEndIndex1\"))\n      FlagWorkIndexEnd -= 100;\n    ImGui::SameLine();\n    if (ImGui::Button(\"+1000##IncreaseFlagWorkEndIndex2\"))\n      FlagWorkIndexEnd += 1000;\n    ImGui::SameLine();\n    if (ImGui::Button(\"-1000##DecreaseFlagWorkEndIndex2\"))\n      FlagWorkIndexEnd -= 1000;\n    ImGui::PopButtonRepeat();\n\n    if (FlagWorkIndexEnd < FlagWorkIndexStart)\n      FlagWorkIndexEnd = FlagWorkIndexStart;\n\n    ImGui::TreePop();\n  }\n\n  ImGuiStyle& style = ImGui::GetStyle();\n  float windowVisibleX2 =\n      ImGui::GetWindowPos().x + ImGui::GetWindowContentRegionMax().x;\n\n  if (ImGui::CollapsingHeader(\"ScrWork Editor\")) {\n    for (int i = ScrWorkIndexStart; i <= ScrWorkIndexEnd; i++) {\n      ImGui::PushID(i);\n      char buf[32];\n      snprintf(buf, 32, \"ScrWork[%04d]\", i);\n      int one = 1;\n      int ten = 10;\n      ImGui::InputScalar(buf, ImGuiDataType_S32, &ScrWork[i], &one, &ten,\n                         ScrWorkNumberFormat == 1 ? \"%08lX\" : \"%d\");\n      float lastX2 = ImGui::GetItemRectMax().x;\n      float nextButtonX2 =\n          lastX2 + style.ItemSpacing.x + ImGui::GetItemRectSize().x;\n      if (i + 1 <= ScrWorkIndexEnd && nextButtonX2 < windowVisibleX2)\n        ImGui::SameLine();\n      ImGui::PopID();\n    }\n  }\n\n  if (ImGui::CollapsingHeader(\"FlagWork Editor\")) {\n    for (int i = FlagWorkIndexStart; i <= FlagWorkIndexEnd; i++) {\n      ImGui::PushID(i);\n      char buf[32];\n      snprintf(buf, 32, \"Flags[%04d]\", i);\n      bool flagVal = GetFlag(i);\n      if (ImGui::Checkbox(buf, &flagVal)) SetFlag(i, flagVal);\n      float lastX2 = ImGui::GetItemRectMax().x;\n      float nextButtonX2 =\n          lastX2 + style.ItemSpacing.x + ImGui::GetItemRectSize().x;\n      if (i + 1 <= FlagWorkIndexEnd && nextButtonX2 < windowVisibleX2)\n        ImGui::SameLine();\n      ImGui::PopID();\n    }\n  }\n\n  ImGui::PopItemWidth();\n}\n\nstatic int ScriptDebuggerSelectedThreadId = -1;\nstatic int ScriptDebuggerSelectedScriptId = -1;\nstatic int ThreadVarsNumberFormat = 0;\nstatic bool AutoScrollSourceView = true;\nstatic std::map<uint32_t, std::string> ScriptFilesListing;\n\nvoid ShowScriptDebugger() {\n  if (ScriptDebuggerSelectedThreadId == -1 ||\n      Vm::ThreadPool[ScriptDebuggerSelectedThreadId].IpOffset == 0) {\n    int firstThreadId = 0;\n    for (int i = 0; i < Vm::MaxThreads; i++) {\n      if (Vm::ThreadPool[i].IpOffset != 0) {\n        firstThreadId = i;\n        break;\n      }\n    }\n    ScriptDebuggerSelectedThreadId = firstThreadId;\n    ScriptDebuggerSelectedScriptId =\n        Vm::LoadedScriptMetas[Vm::ThreadPool[ScriptDebuggerSelectedThreadId]\n                                  .ScriptBufferId]\n            .Id;\n  }\n\n  char comboPreviewValue[128];\n  auto groupType = magic_enum::enum_cast<ThreadGroupType>(\n      (uint8_t)Vm::ThreadPool[ScriptDebuggerSelectedThreadId].GroupId);\n  if (groupType) {\n    snprintf(\n        comboPreviewValue, 128, \"[%s][%d] %s\",\n        magic_enum::enum_name(*groupType).data(),\n        Vm::ThreadPool[ScriptDebuggerSelectedThreadId].Id,\n        Vm::LoadedScriptMetas[Vm::ThreadPool[ScriptDebuggerSelectedThreadId]\n                                  .ScriptBufferId]\n            .FileName.c_str());\n  } else {\n    snprintf(\n        comboPreviewValue, 128, \"[%d][%d] %s\",\n        Vm::ThreadPool[ScriptDebuggerSelectedThreadId].GroupId,\n        Vm::ThreadPool[ScriptDebuggerSelectedThreadId].Id,\n        Vm::LoadedScriptMetas[Vm::ThreadPool[ScriptDebuggerSelectedThreadId]\n                                  .ScriptBufferId]\n            .FileName.c_str());\n  }\n\n  if (ImGui::BeginCombo(\"Thread##vmThreadCombo\", comboPreviewValue,\n                        ImGuiComboFlags_WidthFitPreview)) {\n    for (int i = 0; i < Vm::MaxThreads; i++) {\n      if (Vm::ThreadPool[i].IpOffset != 0) {\n        const bool isSelected = (ScriptDebuggerSelectedThreadId == i);\n        groupType = magic_enum::enum_cast<ThreadGroupType>(\n            (uint8_t)Vm::ThreadPool[i].GroupId);\n        if (groupType) {\n          snprintf(comboPreviewValue, 128, \"[%s][%d] %s\",\n                   magic_enum::enum_name(*groupType).data(),\n                   Vm::ThreadPool[i].Id,\n                   Vm::LoadedScriptMetas[Vm::ThreadPool[i].ScriptBufferId]\n                       .FileName.c_str());\n        } else {\n          snprintf(comboPreviewValue, 128, \"[%d][%d] %s\",\n                   Vm::ThreadPool[i].GroupId, Vm::ThreadPool[i].Id,\n                   Vm::LoadedScriptMetas[Vm::ThreadPool[i].ScriptBufferId]\n                       .FileName.c_str());\n        }\n        if (ImGui::Selectable(comboPreviewValue, isSelected)) {\n          ScriptDebuggerSelectedThreadId = i;\n          AutoScrollSourceView = true;\n        }\n        if (isSelected) ImGui::SetItemDefaultFocus();\n      }\n    }\n    ImGui::EndCombo();\n  }\n\n  ImGui::SameLine();\n  if (ScriptFilesListing.size() == 0) {\n    IoError err = Io::VfsListFiles(\"script\", ScriptFilesListing);\n    if (err != IoError_OK) {\n      ImpLog(LogLevel::Warning, LogChannel::General,\n             \"Failed to open script archive!\\n\");\n      return;\n    }\n  }\n\n  snprintf(comboPreviewValue, 128, \"[%d] %s\", ScriptDebuggerSelectedScriptId,\n           ScriptFilesListing[ScriptDebuggerSelectedScriptId].c_str());\n  if (ImGui::BeginCombo(\"Script##vmScriptCombo\", comboPreviewValue,\n                        ImGuiComboFlags_WidthFitPreview)) {\n    for (auto const& file : ScriptFilesListing) {\n      const bool isSelected =\n          (static_cast<uint32_t>(ScriptDebuggerSelectedScriptId) == file.first);\n      snprintf(comboPreviewValue, 128, \"[%d] %s\", file.first,\n               file.second.c_str());\n      if (ImGui::Selectable(comboPreviewValue, isSelected)) {\n        ScriptDebuggerSelectedScriptId = file.first;\n        if (static_cast<uint32_t>(ScriptDebuggerSelectedScriptId) !=\n            Vm::LoadedScriptMetas[Vm::ThreadPool[ScriptDebuggerSelectedThreadId]\n                                      .ScriptBufferId]\n                .Id) {\n          AutoScrollSourceView = false;\n        }\n      }\n      if (isSelected) {\n        ImGui::SetItemDefaultFocus();\n      }\n    }\n    ImGui::EndCombo();\n  }\n\n  if (AutoScrollSourceView) {\n    ScriptDebuggerSelectedScriptId =\n        Vm::LoadedScriptMetas[Vm::ThreadPool[ScriptDebuggerSelectedThreadId]\n                                  .ScriptBufferId]\n            .Id;\n  }\n\n  ImGui::SeparatorText(\"Thread flags:\");\n  if (ImGui::BeginTable(\"tableThreadFlags\", 3, ImGuiTableFlags_None)) {\n    ImGui::TableNextColumn();\n    if (ImGui::CheckboxFlags(\n            \"Destroy\", &Vm::ThreadPool[ScriptDebuggerSelectedThreadId].Flags,\n            Vm::TF_Destroy))\n      ScriptDebuggerSelectedThreadId = 0;\n    ImGui::SameLine();\n    HelpMarker(\"Setting this will DESTROY the thread IMMEDIATELY!\");\n    ImGui::CheckboxFlags(\"Animate\",\n                         &Vm::ThreadPool[ScriptDebuggerSelectedThreadId].Flags,\n                         Vm::TF_Animate);\n    ImGui::TableNextColumn();\n    ImGui::CheckboxFlags(\"Display\",\n                         &Vm::ThreadPool[ScriptDebuggerSelectedThreadId].Flags,\n                         Vm::TF_Display);\n    ImGui::CheckboxFlags(\"Pause\",\n                         &Vm::ThreadPool[ScriptDebuggerSelectedThreadId].Flags,\n                         Vm::TF_Pause);\n    ImGui::TableNextColumn();\n    ImGui::CheckboxFlags(\"Message\",\n                         &Vm::ThreadPool[ScriptDebuggerSelectedThreadId].Flags,\n                         (unsigned int)Vm::TF_Message);\n    ImGui::EndTable();\n  }\n\n  ImGui::SeparatorText(\"Thread data:\");\n  if (ImGui::BeginTable(\"tableThreadData\", 3, ImGuiTableFlags_None)) {\n    ImGui::TableNextColumn();\n    ImGui::Text(\"ID: %d\", Vm::ThreadPool[ScriptDebuggerSelectedThreadId].Id);\n    ImGui::Text(\"IP: %d\",\n                Vm::ThreadPool[ScriptDebuggerSelectedThreadId].IpOffset);\n    ImGui::Text(\"ExecPriority: %d\",\n                Vm::ThreadPool[ScriptDebuggerSelectedThreadId].ExecPriority);\n\n    ImGui::TableNextColumn();\n    auto drawType = magic_enum::enum_cast<Game::DrawComponentType>(\n        Vm::ThreadPool[ScriptDebuggerSelectedThreadId].DrawType);\n    if (drawType) {\n      ImGui::Text(\"DrawType: %s\", magic_enum::enum_name(*drawType).data());\n    } else {\n      ImGui::Text(\"DrawType: %d\",\n                  Vm::ThreadPool[ScriptDebuggerSelectedThreadId].DrawType);\n    }\n    ImGui::Text(\"DrawPriority: %d\",\n                Vm::ThreadPool[ScriptDebuggerSelectedThreadId].DrawPriority);\n    ImGui::Text(\"Alpha: %d\",\n                Vm::ThreadPool[ScriptDebuggerSelectedThreadId].Alpha);\n\n    ImGui::TableNextColumn();\n    groupType = magic_enum::enum_cast<ThreadGroupType>(\n        (uint8_t)Vm::ThreadPool[ScriptDebuggerSelectedThreadId].GroupId);\n    if (groupType) {\n      ImGui::Text(\"Group: %s\", magic_enum::enum_name(*groupType).data());\n    } else {\n      ImGui::Text(\"Group: %d\",\n                  Vm::ThreadPool[ScriptDebuggerSelectedThreadId].GroupId);\n    }\n    ImGui::Text(\"DialoguePageId: %d\",\n                Vm::ThreadPool[ScriptDebuggerSelectedThreadId].DialoguePageId);\n    ImGui::Text(\"WaitCounter: %d\",\n                Vm::ThreadPool[ScriptDebuggerSelectedThreadId].WaitCounter);\n\n    ImGui::EndTable();\n  }\n\n  ImGui::Spacing();\n  if (ImGui::TreeNode(\"Source View\")) {\n    uint32_t scriptId = ScriptDebuggerSelectedScriptId;\n    uint32_t scriptIp = Vm::ThreadPool[ScriptDebuggerSelectedThreadId].IpOffset;\n\n    ParseScriptDebugData(scriptId);\n    if (ScriptDebugSource.find(scriptId) != ScriptDebugSource.end()) {\n      auto byteCodePosTable = ScriptDebugByteCodePosToLine[scriptId];\n      auto lineToByteCodePosTable = ScriptDebugLineToByteCodePos[scriptId];\n      auto currentLine = byteCodePosTable.lower_bound(scriptIp);\n      int currentLineNum = -1;\n      if (currentLine != byteCodePosTable.end()) {\n        currentLineNum = currentLine->second;\n      }\n\n      Vm::DebugThreadId = ScriptDebuggerSelectedThreadId;\n      ImGui::Checkbox(\"Auto scroll source view\", &AutoScrollSourceView);\n      ImGui::SameLine();\n      if (Vm::DebuggerBreak) {\n        if (ImGui::Button(\"Continue\")) {\n          AutoScrollSourceView = true;\n          Vm::DebuggerContinueRequest = true;\n        }\n      } else {\n        if (ImGui::Button(\"Break\")) {\n          AutoScrollSourceView = true;\n          Vm::DebuggerBreak = true;\n        }\n      }\n      ImGui::SameLine();\n      ImGui::BeginDisabled(!Vm::DebuggerBreak);\n      if (ImGui::Button(\"Step\")) {\n        AutoScrollSourceView = true;\n        Vm::DebuggerStepRequest = true;\n      }\n      ImGui::EndDisabled();\n      ImGui::SameLine();\n      if (ImGui::Button(\"Clear breakpoints\")) {\n        Vm::DebuggerBreakpoints.clear();\n      }\n      ImGui::SameLine();\n      ImGui::Checkbox(\"Always block thread\", &Vm::DebuggerAlwaysBlock);\n\n      if (ImGui::TreeNode(\"Breakpoint List\")) {\n        if (ImGui::BeginTable(\n                \"BreakpointList\", 1,\n                ImGuiTableFlags_NoSavedSettings | ImGuiTableFlags_Borders)) {\n          char buf[256];\n          for (auto it = Vm::DebuggerBreakpoints.cbegin(), nextIt = it;\n               it != Vm::DebuggerBreakpoints.cend(); it = nextIt) {\n            ++nextIt;\n            ImGui::TableNextRow();\n            ImGui::TableNextColumn();\n            Io::FileMeta scriptMeta;\n            Io::VfsGetMeta(\"script\", it->second.first, &scriptMeta);\n            bool isBreakpoint = true;\n            fmt::format_to_n(buf, 256, \"%08X - %d - %s - %s\", it->second.second,\n                             it->first, scriptMeta.FileName,\n                             ScriptDebugSource[it->second.first][it->first]);\n            ImGui::Selectable(buf, &isBreakpoint,\n                              ImGuiSelectableFlags_SpanAllColumns);\n            if (!isBreakpoint) {\n              Vm::DebuggerBreakpoints.erase(it);\n            }\n          }\n          ImGui::EndTable();\n        }\n\n        ImGui::TreePop();\n      }\n\n      if (ImGui::BeginTable(\n              \"Source\", 2,\n              ImGuiTableFlags_NoSavedSettings | ImGuiTableFlags_Borders |\n                  ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_ScrollY,\n              ImVec2(ImGui::GetContentRegionAvail().x,\n                     ImGui::GetContentRegionAvail().y * 0.9f))) {\n        ImGuiListClipper clipper;\n        clipper.Begin((int)ScriptDebugSource[scriptId].size());\n        while (clipper.Step()) {\n          for (int row = clipper.DisplayStart; row < clipper.DisplayEnd;\n               row++) {\n            ImGui::PushID(row);\n            ImGui::TableNextRow();\n            ImGui::TableNextColumn();\n            ImGui::Text(\"%d\", row + 1);\n            ImGui::TableNextColumn();\n            ImGui::PushStyleColor(ImGuiCol_Header,\n                                  ImVec4(1.0f, 0.0f, 0.0f, 0.65f));\n            bool isBreakpoint = Vm::DebuggerBreakpoints.find(row) !=\n                                Vm::DebuggerBreakpoints.end();\n            isBreakpoint =\n                isBreakpoint &&\n                Vm::DebuggerBreakpoints.find(row)->second.first == scriptId;\n            ImGui::Selectable(ScriptDebugSource[scriptId][row].c_str(),\n                              &isBreakpoint,\n                              ImGuiSelectableFlags_SpanAllColumns);\n            if (isBreakpoint) {\n              Vm::DebuggerBreakpoints[row] =\n                  std::make_pair(scriptId, lineToByteCodePosTable[row]);\n            } else if (Vm::DebuggerBreakpoints.find(row) !=\n                           Vm::DebuggerBreakpoints.end() &&\n                       Vm::DebuggerBreakpoints.find(row)->second.first ==\n                           scriptId) {\n              Vm::DebuggerBreakpoints.erase(row);\n            }\n            ImGui::PopStyleColor();\n            if (scriptId == Vm::LoadedScriptMetas\n                                [Vm::ThreadPool[ScriptDebuggerSelectedThreadId]\n                                     .ScriptBufferId]\n                                    .Id &&\n                currentLineNum == row) {\n              ImGui::TableSetBgColor(\n                  ImGuiTableBgTarget_RowBg0,\n                  ImGui::GetColorU32(ImVec4(0.0f, 0.7f, 0.0f, 0.65f)));\n            }\n            ImGui::PopID();\n          }\n        }\n        if (AutoScrollSourceView)\n          ImGui::SetScrollY((currentLineNum - 5) *\n                            ImGui::GetTextLineHeightWithSpacing());\n        ImGui::EndTable();\n      }\n    }\n    ImGui::TreePop();\n  }\n\n  ImGui::Spacing();\n  if (ImGui::TreeNode(\"Call Stack\")) {\n    for (int i =\n             Vm::ThreadPool[ScriptDebuggerSelectedThreadId].CallStackDepth - 1;\n         i >= 0; i--) {\n      ImGui::PushID(i);\n      auto& thd = Vm::ThreadPool[ScriptDebuggerSelectedThreadId];\n      uint32_t returnAddress =\n          (Profile::Vm::UseReturnIds)\n              ? Vm::ScriptGetRetAddress(thd.ReturnScriptBufferIds[i],\n                                        thd.ReturnIds[i])\n              : thd.ReturnAddresses[i];\n\n      ImGui::Text(\n          \"%s - %08X\",\n          Vm::LoadedScriptMetas[thd.ReturnScriptBufferIds[i]].FileName.c_str(),\n          returnAddress);\n      ImGui::PopID();\n    }\n\n    ImGui::TreePop();\n  }\n  ImGui::Spacing();\n\n  ImGuiStyle& style = ImGui::GetStyle();\n  float windowVisibleX2 =\n      ImGui::GetWindowPos().x + ImGui::GetWindowContentRegionMax().x;\n  ImGui::PushItemWidth(10.0f * ImGui::GetFontSize());\n\n  if (ImGui::TreeNode(\"Variables\")) {\n    ImGui::Text(\"Display number format\");\n    ImGui::SameLine();\n    ImGui::RadioButton(\"DEC\", &ThreadVarsNumberFormat, 0);\n    ImGui::SameLine();\n    ImGui::RadioButton(\"HEX\", &ThreadVarsNumberFormat, 1);\n\n    for (int i = 0; i < Vm::MaxThreadVars; i++) {\n      ImGui::PushID(i);\n      char buf[32];\n      snprintf(buf, 32, \"Vars[%02d]\", i);\n      int one = 1;\n      ImGui::InputScalar(\n          buf, ImGuiDataType_S32,\n          &Vm::ThreadPool[ScriptDebuggerSelectedThreadId].Variables[i], &one, 0,\n          ThreadVarsNumberFormat == 1 ? \"%08X\" : \"%d\");\n      float lastX2 = ImGui::GetItemRectMax().x;\n      float nextButtonX2 =\n          lastX2 + style.ItemSpacing.x + ImGui::GetItemRectSize().x;\n      if (i + 1 < Vm::MaxThreadVars && nextButtonX2 < windowVisibleX2)\n        ImGui::SameLine();\n      ImGui::PopID();\n    }\n\n    ImGui::TreePop();\n  }\n\n  ImGui::PopItemWidth();\n}\n\nstatic ankerl::unordered_dense::map<uint32_t, std::vector<std::string>>\n    SpritesBySpriteSheet;\n\nstatic void ShowSprite(const Sprite* sprite) {\n  if (Profile::ActiveRenderer == RendererType::OpenGL) {\n    float texWidth = sprite->Sheet.DesignWidth;\n    float texHeight = sprite->Sheet.DesignHeight;\n    ImGui::Image(\n        (ImTextureID)(intptr_t)sprite->Sheet.Texture,\n        ImVec2(sprite->Bounds.Width, sprite->Bounds.Height),\n        ImVec2(sprite->Bounds.X / texWidth, sprite->Bounds.Y / texHeight),\n        ImVec2((sprite->Bounds.X + sprite->Bounds.Width) / texWidth,\n               (sprite->Bounds.Y + sprite->Bounds.Height) / texHeight));\n  }\n}\n\nvoid ShowObjects() {\n  ImGui::ShowDemoWindow();\n  ImGui::PushItemWidth(10.0f * ImGui::GetFontSize());\n\n  if (SpritesBySpriteSheet.size() == 0) {\n    for (const auto& sprite : Profile::Sprites) {\n      SpritesBySpriteSheet[sprite.second.Sheet.Texture].push_back(sprite.first);\n    }\n  }\n\n  if (ImGui::TreeNode(\"SpriteSheets\")) {\n    for (const auto& spriteSheet : Profile::SpriteSheets) {\n      if (ImGui::TreeNode(spriteSheet.first.c_str())) {\n        ImGui::PushID(spriteSheet.second.Texture);\n        float texWidth = spriteSheet.second.DesignWidth * 0.4f;\n        float texHeight = spriteSheet.second.DesignHeight * 0.4f;\n        // Only OpenGL for now\n        if (Profile::ActiveRenderer == RendererType::OpenGL) {\n          ImVec2 pos = ImGui::GetCursorScreenPos();\n          ImGui::Image((ImTextureID)(intptr_t)spriteSheet.second.Texture,\n                       ImVec2(texWidth, texHeight));\n          ImageTooltip(pos, (ImTextureID)(intptr_t)spriteSheet.second.Texture,\n                       texWidth, texHeight);\n        }\n\n        ImGui::Spacing();\n        ImGui::BulletText(\"Texture: (width: %f, height: %f)\",\n                          spriteSheet.second.DesignWidth,\n                          spriteSheet.second.DesignHeight);\n\n        if (ImGui::TreeNode(\"Sprites\")) {\n          for (const auto& spriteName :\n               SpritesBySpriteSheet[spriteSheet.second.Texture]) {\n            const auto& sprite = Profile::Sprites[spriteName];\n            if (ImGui::TreeNode(spriteName.c_str())) {\n              ShowSprite(&sprite);\n              ImGui::Spacing();\n              ImGui::BulletText(\"Bounds: (x: %f, y: %f, width: %f, height: %f)\",\n                                sprite.Bounds.X, sprite.Bounds.Y,\n                                sprite.Bounds.Width, sprite.Bounds.Height);\n\n              ImGui::TreePop();\n            }\n          }\n          ImGui::TreePop();\n        }\n\n        ImGui::PopID();\n\n        ImGui::TreePop();\n      }\n    }\n    ImGui::TreePop();\n  }\n\n  HelpMarker(\n      \"These values are read-only as everything here is controlled by \"\n      \"scripts.\");\n  if (ImGui::TreeNode(\"Backgrounds\")) {\n    for (size_t i = 0; i < Backgrounds.size(); i++) {\n      ImGui::PushID(static_cast<int>(i));\n      if (ImGui::TreeNode(\"Background\", \"Background %zu\", i)) {\n        if (Backgrounds[i].Status == LoadStatus::Loaded) {\n          float texWidth = Backgrounds[i].BgSprite.Sheet.DesignWidth * 0.4f;\n          float texHeight = Backgrounds[i].BgSprite.Sheet.DesignHeight * 0.4f;\n          // Only OpenGL for now\n          if (Profile::ActiveRenderer == RendererType::OpenGL) {\n            ImVec2 pos = ImGui::GetCursorScreenPos();\n            ImGui::Image(\n                (ImTextureID)(intptr_t)Backgrounds[i].BgSprite.Sheet.Texture,\n                ImVec2(texWidth, texHeight));\n            ImageTooltip(\n                pos,\n                (ImTextureID)(intptr_t)Backgrounds[i].BgSprite.Sheet.Texture,\n                texWidth, texHeight);\n          }\n        }\n\n        ImGui::Spacing();\n        ImGui::BulletText(\"Status: %d\", (int)Backgrounds[i].Status);\n        ImGui::Spacing();\n        ImGui::BulletText(\"Texture: (width: %f, height: %f)\",\n                          Backgrounds[i].BgSprite.Sheet.DesignWidth,\n                          Backgrounds[i].BgSprite.Sheet.DesignHeight);\n        ImGui::Spacing();\n        ImGui::BulletText(\"IsShown: %d\", Backgrounds[i].Show);\n        ImGui::Spacing();\n        ImGui::BulletText(\"Layers: %d, %d\", Backgrounds[i].Layers[0],\n                          Backgrounds[i].Layers[1]);\n        ImGui::Spacing();\n        ImGui::BulletText(\"Display coords: (x: %f, y: %f)\",\n                          Backgrounds[i].TransformState.Position.x,\n                          Backgrounds[i].TransformState.Position.y);\n        ImGui::Spacing();\n        ImGui::BulletText(\"Sprite: (x: %f, y: %f, width: %f, height: %f)\",\n                          Backgrounds[i].BgSprite.Bounds.X,\n                          Backgrounds[i].BgSprite.Bounds.Y,\n                          Backgrounds[i].BgSprite.Bounds.Width,\n                          Backgrounds[i].BgSprite.Bounds.Height);\n        ImGui::Spacing();\n        ImGui::BulletText(\"Scale: (x: %f, y: %f)\",\n                          Backgrounds[i].BgSprite.BaseScale.x,\n                          Backgrounds[i].BgSprite.BaseScale.y);\n\n        ImGui::TreePop();\n      }\n      ImGui::PopID();\n    }\n    ImGui::TreePop();\n  }\n\n  if (ImGui::TreeNode(\"Characters\")) {\n    for (size_t i = 0; i < Characters2D.size(); i++) {\n      ImGui::PushID(static_cast<int>(i));\n      if (ImGui::TreeNode(\"Character\", \"Character %zu\", i)) {\n        if (Characters2D[i].Status == LoadStatus::Loaded) {\n          float texWidth = Characters2D[i].CharaSprite.Sheet.DesignWidth * 0.4f;\n          float texHeight =\n              Characters2D[i].CharaSprite.Sheet.DesignHeight * 0.4f;\n          // Only OpenGL for now\n          if (Profile::ActiveRenderer == RendererType::OpenGL) {\n            ImVec2 pos = ImGui::GetCursorScreenPos();\n            ImGui::Image((ImTextureID)(intptr_t)Characters2D[i]\n                             .CharaSprite.Sheet.Texture,\n                         ImVec2(texWidth, texHeight));\n            ImageTooltip(pos,\n                         (ImTextureID)(intptr_t)Characters2D[i]\n                             .CharaSprite.Sheet.Texture,\n                         texWidth, texHeight);\n          }\n        }\n\n        ImGui::Spacing();\n        ImGui::BulletText(\"Status: %d\", (int)Characters2D[i].Status);\n        ImGui::Spacing();\n        ImGui::BulletText(\"Texture: (width: %f, height: %f)\",\n                          Characters2D[i].CharaSprite.Sheet.DesignWidth,\n                          Characters2D[i].CharaSprite.Sheet.DesignHeight);\n        ImGui::Spacing();\n        ImGui::BulletText(\"IsShown: %d\", Characters2D[i].Show);\n        ImGui::Spacing();\n        ImGui::BulletText(\"Layers: %d, %d\", Characters2D[i].Layers[0],\n                          Characters2D[i].Layers[1]);\n        ImGui::Spacing();\n        ImGui::BulletText(\"Display coords: (x: %f, y: %f)\",\n                          Characters2D[i].Position.x,\n                          Characters2D[i].Position.y);\n        ImGui::Spacing();\n        ImGui::BulletText(\"Scale: (x: %f, y: %f)\", Characters2D[i].Scale.x,\n                          Characters2D[i].Scale.y);\n        ImGui::Spacing();\n        ImGui::BulletText(\"Face: %d\", Characters2D[i].Face);\n        ImGui::Spacing();\n\n        ImGui::TreePop();\n      }\n      ImGui::PopID();\n    }\n    ImGui::TreePop();\n  }\n\n  ImGui::PopItemWidth();\n}\n\n}  // namespace DebugMenu\n}  // namespace Impacto\n"
  },
  {
    "path": "src/debugmenu.h",
    "content": "#pragma once\n\n#include \"impacto.h\"\n\n#include <magic_enum/magic_enum.hpp>\n\nnamespace Impacto {\nnamespace DebugMenu {\n\ninline bool DebugMenuShown = false;\n\n// This is only for debug info\nenum class ThreadGroupType : uint8_t {\n  Root = 0x0,\n  System = 0x1,\n  GameSys = 0x2,\n  GameSys2 = 0x3,\n  GameSys3 = 0x4,\n  Script = 0x5,\n  Script2 = 0x6,\n  Script3 = 0x7,\n  Script4 = 0x8,\n  None = 0xFF,\n};\nvoid Show();\nvoid ShowDockableArea();\nvoid ShowSingleWindow();\nvoid ShowScriptVariablesEditor();\nvoid ShowScriptDebugger();\nvoid ShowObjects();\n\n}  // namespace DebugMenu\n}  // namespace Impacto\n"
  },
  {
    "path": "src/effects/blur.cpp",
    "content": "#include \"blur.h\"\n#include \"../texture/texture.h\"\n#include \"../profile/game.h\"\n#include \"../profile/vm.h\"\n#include \"../renderer/renderer.h\"\n#include \"../vm/vm.h\"\n#include \"../profile/scriptvars.h\"\n\nnamespace Impacto {\nnamespace Effects {\n\nusing namespace Impacto::Profile::ScriptVars;\n\nBlurEffect::BlurEffect()\n    : BlurSprite(SpriteSheet(Profile::DesignWidth, Profile::DesignHeight), 0.0f,\n                 0.0f, Profile::DesignWidth, Profile::DesignHeight) {\n  BlurSprite.Sheet.IsScreenCap = true;\n}\n\nvoid BlurEffect::Init() {\n  if (BlurSprite.Sheet.Texture) {\n    Renderer->FreeTexture(BlurSprite.Sheet.Texture);\n  }\n\n  Texture texture{};\n  texture.LoadSolidColor(static_cast<int>(Profile::DesignWidth),\n                         static_cast<int>(Profile::DesignHeight), 0);\n  BlurSprite.Sheet.Texture = texture.Submit();\n}\n\nBlurEffect::~BlurEffect() {\n  if (Renderer) Renderer->FreeTexture(BlurSprite.Sheet.Texture);\n}\n\nvoid BlurEffect::Render(int iterations) {\n  if (Profile::Vm::GameInstructionSet == Vm::InstructionSet::CHLCC) {\n    iterations = std::min(iterations / 2, 8);\n  } else {\n    iterations = std::min(iterations, 32);\n  }\n\n  for (int i = 0; i < iterations; i++) {\n    RendererBlurDirection direction = i % 2 == 0\n                                          ? RendererBlurDirection::Vertical\n                                          : RendererBlurDirection::Horizontal;\n\n    Renderer->CaptureScreencap(BlurSprite);\n    Renderer->DrawBlurredSprite(\n        BlurSprite,\n        RectF{0.0f, 0.0f, Profile::DesignWidth, Profile::DesignHeight},\n        glm::mat4(1.0f), direction, glm::vec4(1.0f));\n  }\n}\n\n}  // namespace Effects\n}  // namespace Impacto"
  },
  {
    "path": "src/effects/blur.h",
    "content": "#include \"../spritesheet.h\"\n\nnamespace Impacto {\nnamespace Effects {\n\nclass BlurEffect {\n public:\n  void Render(int iterations);\n  void Init();\n\n  BlurEffect();\n  ~BlurEffect();\n\n private:\n  Sprite BlurSprite;\n};\n\ninline BlurEffect Blur;\n\n}  // namespace Effects\n}  // namespace Impacto"
  },
  {
    "path": "src/effects/chlcc/bubbleseffect.cpp",
    "content": "#include \"bubbleseffect.h\"\n#include \"../../profile/ui/gamespecific.h\"\n\n#include \"../../profile/game.h\"\n\nusing namespace Impacto::Profile::GameSpecific;\nusing namespace Impacto::Profile::ScriptVars;\n\nnamespace Impacto {\nnamespace UI {\nnamespace CHLCC {\n\nBubble::Bubble() { Init(); }\n\nvoid Bubble::Init(bool loop) {\n  Size = static_cast<uint16_t>(CALCrnd(64) + 10);\n  Position.x = static_cast<float>(CALCrnd(1480) - 100);\n  Position.y = static_cast<float>(loop ? (Size + 720) : CALCrnd(820) - 100);\n  if (!loop) RandAngle = static_cast<uint16_t>(CALCrnd(16384) << 2);\n}\n\nvoid Bubble::Update(float dt) {\n  Position.y += dt * 60.0f * -(Size * 0.25f);\n  RandAngle += 2048;\n\n  if (Position.y + Size < 0.0f) {\n    Init(true);\n  }\n}\n\nBubblesEffect::BubblesEffect() {\n  FadeAnimation.DurationIn = BubbleFadeDuration;\n}\n\nvoid Bubble::Render(float alphaMultiplier) {\n  const float sizeF = static_cast<float>(Size);\n  const float sine = std::sin(ScrWorkAngleToRad(RandAngle));\n  const float xPos = Position.x + sizeF * sine * 0.5f - sizeF / 2.0f;\n  RectF dest{xPos, Position.y - sizeF / 2.0f, sizeF, sizeF};\n  dest.Scale({Profile::DesignWidth / 1280.0f, Profile::DesignHeight / 720.0f},\n             {0.0f, 0.0f});\n  const float alpha = (ScrWork[SW_BUBBLES_ALPHA] * alphaMultiplier) / 256.0f;\n  if (Size < 70) {\n    Renderer->DrawSprite(BubbleSpriteSmall, dest, {glm::vec3{1.0f}, alpha});\n  } else {\n    Renderer->DrawSprite(BubbleSpriteBig, dest, {glm::vec3{1.0f}, alpha});\n  }\n}\n\nvoid BubblesEffect::Init() {\n  for (auto& bubble : Bubbles) {\n    bubble.Init();\n  }\n}\n\nvoid BubblesEffect::Update(float dt) {\n  if (!ScrWork[SW_BUBBLES_ALPHA]) return;\n  if (!ScrWork[SW_BUBBLES_COUNT]) {\n    if (FadeAnimation.Direction != AnimationDirection::Out) {\n      FadeAnimation.StartOut();\n    }\n  }\n\n  if (FadeAnimation.IsOut()) {\n    FadeAnimation.StartIn();\n  }\n\n  FadeAnimation.Update(dt);\n  for (auto& bubble : Bubbles) {\n    bubble.Update(dt);\n  }\n}\nvoid BubblesEffect::Render() {\n  for (auto& bubble : Bubbles) {\n    bubble.Render(FadeAnimation.Progress);\n  }\n}\n\n}  // namespace CHLCC\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/effects/chlcc/bubbleseffect.h",
    "content": "#pragma once\n\n#include <cstdint>\n#include \"../../animation.h\"\nnamespace Impacto {\nnamespace UI {\nnamespace CHLCC {\n\nstruct Bubble {\n  glm::vec2 Position{};\n  uint16_t RandAngle{};\n  uint16_t Size{};\n\n  Bubble();\n  void Init(bool loop = false);\n  void Update(float dt);\n  void Render(float alpha);\n};\n\nclass BubblesEffect {\n  BubblesEffect();\n\n public:\n  void Init();\n  void Update(float dt);\n  void Render();\n\n  static BubblesEffect& GetInstance() {\n    static BubblesEffect impl;\n    return impl;\n  };\n\n private:\n  Animation FadeAnimation;\n  std::array<Bubble, 400> Bubbles;\n};\n}  // namespace CHLCC\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/effects/chlcc/butterflyeffect.cpp",
    "content": "#include \"butterflyeffect.h\"\n#include \"../../profile/ui/gamespecific.h\"\n#include \"../../profile/game.h\"\n\nusing namespace Impacto::Profile::GameSpecific;\nusing namespace Impacto::Profile::ScriptVars;\n\nnamespace Impacto {\nnamespace UI {\nnamespace CHLCC {\n\nButterfly::Butterfly() {\n  Flap.DurationIn = ButterflyFlapFrameDuration * ButterflyFrameCount;\n  Flap.LoopMode = AnimationLoopMode::Loop;\n  Init();\n}\n\nvoid Butterfly::Init(bool loop) {\n  Size = static_cast<uint16_t>(CALCrnd(34) + 40);\n\n  Position.x = static_cast<float>(CALCrnd(1480) - 100);\n  Position.y = static_cast<float>(loop ? (Size + 720) : CALCrnd(820) - 100);\n\n  const float angle = ((CALCrnd(60) - 30)) * std::numbers::pi_v<float> / 180.0f;\n  Velocity.x = std::sin(angle) * Size * 0.25f;\n  Velocity.y = -std::cos(angle) * Size * 0.25f;\n\n  const float maxFrames =\n      std::floor(ButterflyFlapFrameDuration * ButterflyFrameCount * 60);\n  const int randomProgress = CALCrnd(static_cast<int>(maxFrames));\n  Flap.Progress = randomProgress / maxFrames;\n  Flap.StartIn();\n}\n\nvoid Butterfly::Update(float dt) {\n  Flap.Update(dt);\n\n  Position += dt * 60.0f * Velocity;\n  if (Position.y + Size < 0.0f) {\n    Init(true);\n  }\n}\n\nButterflyEffect::ButterflyEffect() {\n  FadeAnimation.DurationIn = ButterflyFadeDuration;\n}\n\nvoid Butterfly::Render(float alphaMultiplier) {\n  const uint8_t butterflyIndex =\n      static_cast<uint8_t>(Flap.Progress * ButterflyFrameCount);\n  const float sizeF = static_cast<float>(Size);\n  RectF dest{Position.x - sizeF / 2.0f, Position.y - sizeF / 2.0f, sizeF,\n             sizeF};\n  dest.Scale({Profile::DesignWidth / 1280.0f, Profile::DesignHeight / 720.0f},\n             {0.0f, 0.0f});\n  const float alpha = (ScrWork[SW_BUTTERFLY_ALPHA] * alphaMultiplier) *\n                      (Size / 148.0f + 1 / 2.0f) / 256.0f;\n  constexpr static auto butterflyFrameSpriteMap =\n      std::to_array({0, 1, 2, 1, 0, 3, 4, 3});\n  Renderer->DrawSprite(\n      ButterflySprites[butterflyFrameSpriteMap[butterflyIndex]], dest,\n      {glm::vec3{1.0f}, alpha});\n}\n\nvoid ButterflyEffect::Init() {\n  for (auto& butterfly : Butterflies) {\n    butterfly.Init();\n  }\n}\n\nvoid ButterflyEffect::Update(float dt) {\n  if (!ScrWork[SW_BUTTERFLY_ALPHA]) return;\n  if (!ScrWork[SW_BUTTERFLY_COUNT]) {\n    if (FadeAnimation.Direction != AnimationDirection::Out) {\n      FadeAnimation.StartOut();\n    }\n  }\n\n  if (FadeAnimation.IsOut()) {\n    FadeAnimation.StartIn();\n  }\n\n  FadeAnimation.Update(dt);\n  for (auto& butterfly : Butterflies) {\n    butterfly.Update(dt);\n  }\n}\nvoid ButterflyEffect::Render() {\n  for (auto& butterfly : Butterflies) {\n    butterfly.Render(FadeAnimation.Progress);\n  }\n}\n\n}  // namespace CHLCC\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/effects/chlcc/butterflyeffect.h",
    "content": "#pragma once\n\n// Hey this isn't Steins;Gate\n\n#include <cstdint>\n#include \"../../animation.h\"\nnamespace Impacto {\nnamespace UI {\nnamespace CHLCC {\n\nstruct Butterfly {\n  glm::vec2 Position{};\n  glm::vec2 Velocity{};\n  Animation Flap;\n  uint16_t Size{};\n\n  Butterfly();\n  void Init(bool loop = false);\n  void Update(float dt);\n  void Render(float alpha);\n};\n\nclass ButterflyEffect {\n  ButterflyEffect();\n\n public:\n  void Init();\n  void Update(float dt);\n  void Render();\n\n  static ButterflyEffect& GetInstance() {\n    static ButterflyEffect impl;\n    return impl;\n  };\n\n private:\n  Animation FadeAnimation;\n  std::array<Butterfly, 400> Butterflies;\n};\n}  // namespace CHLCC\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/effects/chlcc/eyecatch.cpp",
    "content": "#include \"eyecatch.h\"\n\n#include \"../../profile/ui/gamespecific.h\"\n#include \"../../renderer/renderer.h\"\n#include \"../../spritesheet.h\"\n#include \"../../profile/game.h\"\n#include \"../../background2d.h\"\n#include \"../../util.h\"\n\nusing namespace Impacto::Profile::ScriptVars;\nusing namespace Impacto::Profile::GameSpecific;\n\nnamespace Impacto {\nnamespace UI {\nnamespace CHLCC {\n\nEyecatchEffect::EyecatchEffect() {\n  Texture textureStarsMask{};\n  textureStarsMask.LoadSolidColor(static_cast<int>(Profile::DesignWidth),\n                                  static_cast<int>(Profile::DesignHeight), 0);\n  SpriteSheet sheetStarsMask(Profile::DesignWidth, Profile::DesignHeight);\n  sheetStarsMask.Texture = textureStarsMask.Submit();\n  sheetStarsMask.IsScreenCap = true;\n  StarsMask =\n      Sprite(sheetStarsMask, 0, 0, Profile::DesignWidth, Profile::DesignHeight);\n}\n\nvoid EyecatchEffect::RenderMain() {\n  if (!ScrWork[SW_EYECATCH_COUNT]) return;\n  Renderer->Clear(glm::vec4(0.0f));\n  const auto scaleRatio =\n      glm::vec2{Profile::DesignWidth / 1280.0f, Profile::DesignHeight / 720.0f};\n  for (int i = 0; i < 4; ++i) {\n    int scaleOffset = 2 * i;\n    for (int j = 0; j < 7; ++j) {\n      if (scaleOffset + 1 <= ScrWork[SW_EYECATCH_COUNT]) {\n        const int scale =\n            std::min((ScrWork[SW_EYECATCH_COUNT] - (scaleOffset + 1)) * 18,\n                     400) *\n            280 / 106;\n        const float y = scaleRatio.y * (20.0f + 200 * i);\n        const float x = scaleRatio.x * (20.0f + 200 * j);\n        RectF dest{x, y, EyecatchStar.ScaledWidth(),\n                   EyecatchStar.ScaledHeight()};\n        dest.ScaleAroundCenter(glm::vec2{scale / 256.0f});\n        Renderer->DrawSprite(EyecatchStar, dest, glm::vec4(1.0f),\n                             glm::vec3{1.0f});\n      }\n      scaleOffset += 2;\n    }\n  }\n  Renderer->CaptureScreencap(StarsMask);\n}\n\nvoid EyecatchEffect::RenderLayer(int layer) {\n  if (ScrWork[SW_EYECATCH_BUF] != 0 && ScrWork[SW_EYECATCH_COUNT] != 0 &&\n      layer == ScrWork[SW_EYECATCH_PRI]) {\n    const int childBufId = GetBufferId(ScrWork[SW_EYECATCH_BUF]);\n    if (ScrWork[SW_EYECATCH_COUNT] < 64) {\n      Renderer->Clear(glm::vec4(0.0f));\n      Renderer->DrawMaskedSprite(\n          Backgrounds2D[ScrWork[SW_BG1SURF + childBufId]]->BgSprite, StarsMask,\n          255, 256, glm::vec2{0.0f, 0.0f}, glm::vec2{0.0f, 0.0f},\n          glm::vec4(1.0f), false, false);\n\n    } else {\n      Renderer->DrawSprite(\n          Backgrounds2D[ScrWork[SW_BG1SURF + childBufId]]->BgSprite,\n          RectF{0, 0, Profile::DesignWidth, Profile::DesignHeight},\n          glm::vec4{1.0f});\n    }\n  }\n}\n\n}  // namespace CHLCC\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/effects/chlcc/eyecatch.h",
    "content": "#pragma once\n\n#include \"../../spritesheet.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace CHLCC {\n\nclass EyecatchEffect {\n  EyecatchEffect();\n\n public:\n  Sprite StarsMask;\n\n  void RenderMain();\n  void RenderLayer(int layer);\n\n  static EyecatchEffect& GetInstance() {\n    static EyecatchEffect impl;\n    return impl;\n  };\n};\n\n}  // namespace CHLCC\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/effects/mosaic.cpp",
    "content": "#include \"mosaic.h\"\n\n#include \"../profile/game.h\"\n#include \"../renderer/renderer.h\"\n\nnamespace Impacto {\nnamespace Effects {\n\nMosaicEffect::MosaicEffect()\n    : CaptureSprite(SpriteSheet(Profile::DesignWidth, Profile::DesignHeight),\n                    0.0f, 0.0f, Profile::DesignWidth, Profile::DesignHeight) {\n  CaptureSprite.Sheet.IsScreenCap = true;\n}\n\nMosaicEffect::~MosaicEffect() {\n  if (Renderer) Renderer->FreeTexture(CaptureSprite.Sheet.Texture);\n}\n\nvoid MosaicEffect::Init() {\n  if (CaptureSprite.Sheet.Texture != 0) return;\n\n  Texture texture{};\n  texture.LoadSolidColor(static_cast<int>(Profile::DesignWidth),\n                         static_cast<int>(Profile::DesignHeight), 0);\n  CaptureSprite.Sheet.Texture = texture.Submit();\n}\n\nvoid MosaicEffect::Render(float tileSize) {\n  if (tileSize <= 1.0f) return;\n\n  tileSize = std::min(tileSize, 20.0f);\n\n  assert(CaptureSprite.Sheet.Texture != 0);\n  Renderer->CaptureScreencap(CaptureSprite);\n  Renderer->DrawMosaic(\n      CaptureSprite,\n      RectF{0.0f, 0.0f, Profile::DesignWidth, Profile::DesignHeight}, tileSize,\n      glm::mat4(1.0f), glm::vec4(1.0f));\n}\n\n}  // namespace Effects\n}  // namespace Impacto\n"
  },
  {
    "path": "src/effects/mosaic.h",
    "content": "#pragma once\n\n#include \"../spritesheet.h\"\n\nnamespace Impacto {\nnamespace Effects {\n\nclass MosaicEffect {\n public:\n  MosaicEffect();\n  ~MosaicEffect();\n\n  void Init();\n\n  void Render(float tileSize);\n\n private:\n  Sprite CaptureSprite;\n};\n\ninline MosaicEffect Mosaic;\n\n}  // namespace Effects\n}  // namespace Impacto"
  },
  {
    "path": "src/effects/wave.cpp",
    "content": "#include \"wave.h\"\n\n#include \"../profile/game.h\"\n#include \"../util.h\"\n\nnamespace Impacto {\nnamespace Effects {\n\nusing namespace Impacto::Profile::WaveEffects;\n\nvoid Init() {\n  WaveBG.Init();\n  WaveEFF.Init();\n  WaveCHA.Init();\n}\n\nvoid BGWave::Init() {\n  WaveData.resize(WaveMaxCount);\n  WavePos.resize((size_t)BGWaveGridSize.x * (size_t)BGWaveGridSize.y);\n  Vertices.resize((size_t)BGWaveGridSize.x * (size_t)BGWaveGridSize.y);\n  Indices.resize((BGWaveGridSize.x * 2 + 2) * (BGWaveGridSize.y - 1) - 2);\n\n  // triangle strips with degenerate triangles for restarting a row\n  size_t i = 0;\n  for (uint16_t y = 0; y < BGWaveGridSize.y - 1; y++) {\n    for (uint16_t x = 0; x < BGWaveGridSize.x; x++) {\n      const uint16_t firstIndex = y * BGWaveGridSize.x + x;\n      Indices[i++] = firstIndex;\n      Indices[i++] = firstIndex + BGWaveGridSize.x;\n    }\n\n    if (y != BGWaveGridSize.y - 2) {\n      const uint16_t lastIndex =\n          (y + 1) * BGWaveGridSize.x + (BGWaveGridSize.x - 1);\n      const uint16_t nextIndex = (y + 1) * BGWaveGridSize.x;\n\n      Indices[i++] = lastIndex;\n      Indices[i++] = nextIndex;\n    }\n  }\n}\n\nvoid Wave::ClearWaves() { WaveCount = 0; }\n\nvoid Wave::AddWave(WaveParams params) { WaveData[WaveCount++] = params; }\n\nvoid Wave::SetWave(int index, WaveParams params) {\n  if (params.Flags > -1) {\n    WaveData[index].Flags = params.Flags;\n  }\n  if (params.Amplitude > -1) {\n    WaveData[index].Amplitude = params.Amplitude;\n  }\n  if (params.TemporalFrequency > -1) {\n    WaveData[index].TemporalFrequency = params.TemporalFrequency;\n  }\n  if (params.Phase > -1) {\n    WaveData[index].Phase = params.Phase;\n  }\n  if (params.SpatialFrequency > -1) {\n    WaveData[index].SpatialFrequency = params.SpatialFrequency;\n  }\n}\n\nvoid BGWave::CalcPos(int startPhase, std::optional<float> alpha) {\n  std::vector<float> left = std::vector(BGWaveGridSize.y, 0.0f);\n  std::vector<float> right =\n      std::vector(BGWaveGridSize.y, (float)Profile::DesignWidth);\n\n  std::vector<float> top = std::vector(BGWaveGridSize.x, 0.0f);\n  std::vector<float> bottom =\n      std::vector(BGWaveGridSize.x, (float)Profile::DesignHeight);\n\n  const uint16_t maxGridDimension =\n      std::max(BGWaveGridSize.x, BGWaveGridSize.y);\n\n  for (size_t i = 0; i < WaveCount; i++) {\n    int currentPhase = WaveData[i].Phase + startPhase;\n    const float amplitude = static_cast<float>(WaveData[i].Amplitude);\n    const int frequency = WaveData[i].SpatialFrequency;\n    const int flags = WaveData[i].Flags;\n    const bool topFlag = flags & 4;\n    const bool bottomFlag = flags & 8;\n    const bool leftFlag = flags & 1;\n    const bool rightFlag = flags & 2;\n\n    for (size_t j = 0; j < maxGridDimension; j++) {\n      currentPhase = (currentPhase + frequency) & 0xFFFF;\n      const float ampedSin =\n          amplitude * std::sin(ScrWorkAngleToRad(currentPhase));\n      if (j < BGWaveGridSize.y) {\n        if (leftFlag) {\n          left[j] += ampedSin - amplitude;\n        }\n\n        if (rightFlag) {\n          right[j] += ampedSin + amplitude;\n        }\n      }\n\n      // top and bottom are switched since original engine has an upside-down\n      // mesh\n      if (j < BGWaveGridSize.x) {\n        if (topFlag) {\n          top[j] += ampedSin - amplitude;\n        }\n\n        if (bottomFlag) {\n          bottom[j] += ampedSin + amplitude;\n        }\n      }\n    }\n  }\n\n  std::vector<float> lerpStepX = std::vector(BGWaveGridSize.y, 0.0f);\n  std::vector<float> lerpStepY = std::vector(BGWaveGridSize.x, 0.0f);\n  const float HorzIntervalCountInverted = 1.0f / (BGWaveGridSize.x - 1.0f);\n  const float VertIntervalCountInverted = 1.0f / (BGWaveGridSize.y - 1.0f);\n\n  for (size_t i = 0; i < BGWaveGridSize.x; i++) {\n    lerpStepY[i] = (bottom[i] - top[i]) * VertIntervalCountInverted;\n  }\n\n  for (size_t i = 0; i < BGWaveGridSize.y; i++) {\n    lerpStepX[i] = (right[i] - left[i]) * HorzIntervalCountInverted;\n  }\n\n  for (size_t i = 0; i < BGWaveGridSize.y; i++) {\n    float currentVal = left[i];\n    const float step = lerpStepX[i];\n    size_t rowStart = i * BGWaveGridSize.x;\n\n    for (size_t j = 0; j < BGWaveGridSize.x; j++) {\n      WavePos[rowStart + j].x = currentVal;\n      currentVal += step;\n    }\n  }\n\n  for (size_t j = 0; j < BGWaveGridSize.x; j++) {\n    float currentVal = top[j];\n    const float step = lerpStepY[j];\n\n    for (size_t i = 0; i < BGWaveGridSize.y; i++) {\n      WavePos[i * BGWaveGridSize.x + j].y = currentVal;\n      currentVal += step;\n    }\n  }\n\n  const glm::vec4 tint = glm::vec4(1.0f, 1.0f, 1.0f, alpha.value_or(1.0f));\n  // update vertices\n  for (size_t y = 0; y < BGWaveGridSize.y; y++) {\n    float yUV =\n        static_cast<float>(y) / static_cast<float>(BGWaveGridSize.y - 1);\n    size_t row_start = y * BGWaveGridSize.x;\n    for (size_t x = 0; x < BGWaveGridSize.x; x++) {\n      float xUV =\n          static_cast<float>(x) / static_cast<float>(BGWaveGridSize.x - 1);\n\n      size_t index = row_start + x;\n      Vertices[index] = VertexBufferSprites{\n          .Position = WavePos[index], .UV = {xUV, yUV}, .Tint = tint};\n    }\n  }\n}\n\nvoid EFFWave::Render(const Sprite& mask) {\n  PrimitiveData primitives = GetPrimitives();\n  Renderer->DrawPrimitives(mask.Sheet, nullptr, ShaderProgramType::Sprite,\n                           primitives.Vertices, primitives.Indices,\n                           glm::mat4(1.0f), glm::mat4(1.0f), false,\n                           TopologyMode::TriangleStrips);\n}\n\n// TODO: Not implemented\nvoid CHAWave::CalcPos(int startPhase, std::optional<float> alpha) {}\n}  // namespace Effects\n}  // namespace Impacto"
  },
  {
    "path": "src/effects/wave.h",
    "content": "#include <vector>\n#include \"../renderer/renderer.h\"\n#include \"../profile/data/waveeffects.h\"\n\nnamespace Impacto {\nnamespace Effects {\n\nstruct WaveParams {\n  int Flags;\n  int Amplitude;\n  int TemporalFrequency;\n  int Phase;\n  int SpatialFrequency;\n};\n\nclass Wave {\n public:\n  std::vector<WaveParams> WaveData{};\n  std::vector<glm::vec2> WavePos{};\n  std::vector<VertexBufferSprites> Vertices{};\n  std::vector<uint16_t> Indices{};\n  uint32_t WaveCount = 0;\n\n  void ClearWaves();\n  void AddWave(WaveParams params);\n  void SetWave(int index, WaveParams params);\n\n  inline void Update(float dt) {\n    // temporal frequency update was meant to be called every frame on capped\n    // 60fps\n    const float coefficient = dt * 60.0f;\n    for (size_t i = 0; i < WaveCount; i++) {\n      WaveData[i].Phase +=\n          static_cast<int>(WaveData[i].TemporalFrequency * coefficient);\n    }\n  }\n\n  virtual void Init() { WaveData.resize(Profile::WaveEffects::WaveMaxCount); };\n  virtual void CalcPos(int startPhase,\n                       std::optional<float> alpha = std::nullopt) = 0;\n  PrimitiveData GetPrimitives() {\n    return {std::span(Vertices), std::span(Indices)};\n  };\n};\n\nclass BGWave : public Wave {\n public:\n  void Init() override;\n  void CalcPos(int startPhase,\n               std::optional<float> alpha = std::nullopt) override;\n};\n\nclass EFFWave : public BGWave {\n public:\n  void Render(const Sprite& sprite);\n};\n\nclass CHAWave : public Wave {\n public:\n  // maybe\n  inline void Init() override {\n    WaveData.resize(Profile::WaveEffects::WaveMaxCount);\n    WavePos.resize(28 * 20);\n  }\n  void CalcPos(int startPhase,\n               std::optional<float> alpha = std::nullopt) override;\n};\n\ninline BGWave WaveBG = BGWave();\ninline EFFWave WaveEFF = EFFWave();\ninline CHAWave WaveCHA = CHAWave();\n\nvoid Init();\n\n}  // namespace Effects\n}  // namespace Impacto"
  },
  {
    "path": "src/font.h",
    "content": "#pragma once\n\n#include <vector>\n\n#include <magic_enum/magic_enum.hpp>\n\n#include \"spritesheet.h\"\n\nnamespace Impacto {\n\nenum class FontType : int {\n  Basic,\n  LB,\n};\nclass Font {\n public:\n  Font(FontType type) : Type(type) {}\n\n  FontType Type;\n\n  uint8_t Columns;\n  uint8_t Rows;\n  std::vector<float> AdvanceWidths;\n\n  float CellHeight;\n  float CellWidth;\n\n  float BitmapEmWidth;\n  float BitmapEmHeight;\n\n  float LineSpacing;\n\n  virtual void CalculateDefaultSizes() = 0;\n};\n\nclass BasicFont : public Font {\n public:\n  BasicFont() : Font(FontType::Basic) {}\n\n  SpriteSheet Sheet;\n\n  void CalculateDefaultSizes() override {\n    CellHeight = Sheet.DesignHeight / (float)Rows;\n    CellWidth = Sheet.DesignWidth / (float)Columns;\n  }\n\n  Sprite Glyph(uint8_t row, uint8_t col) { return Glyph(row * Columns + col); }\n\n  Sprite Glyph(uint16_t id) {\n    uint8_t row = (uint8_t)(id / Columns);\n    uint8_t col = id % Columns;\n    float width = AdvanceWidths[id];\n    return Sprite(Sheet, col * CellWidth + 1, row * CellHeight + 1, width - 2,\n                  BitmapEmHeight - 2);\n  }\n};\n\nclass LBFont : public Font {\n public:\n  LBFont() : Font(FontType::LB) {}\n\n  SpriteSheet ForegroundSheet;\n  SpriteSheet OutlineSheet;\n\n  float OutlineCellHeight;\n  float OutlineCellWidth;\n\n  glm::vec2 ForegroundOffset;\n  glm::vec2 OutlineOffset;\n\n  void CalculateDefaultSizes() override {\n    CellHeight = ForegroundSheet.DesignHeight / (float)Rows;\n    CellWidth = ForegroundSheet.DesignWidth / (float)Columns;\n\n    OutlineCellHeight = OutlineSheet.DesignHeight / (float)Rows;\n    OutlineCellWidth = OutlineSheet.DesignWidth / (float)Columns;\n  }\n\n  Sprite Glyph(uint8_t row, uint8_t col) { return Glyph(row * Columns + col); }\n\n  Sprite Glyph(uint16_t id) {\n    uint8_t row = (uint8_t)(id / Columns);\n    uint8_t col = id % Columns;\n    return Sprite(ForegroundSheet, col * CellWidth, row * CellHeight, CellWidth,\n                  CellHeight);\n  }\n\n  Sprite OutlineGlyph(uint8_t row, uint8_t col) {\n    return OutlineGlyph(row * Columns + col);\n  }\n\n  Sprite OutlineGlyph(uint16_t id) {\n    uint8_t row = (uint8_t)(id / Columns);\n    uint8_t col = id % Columns;\n    return Sprite(OutlineSheet, col * OutlineCellWidth, row * OutlineCellHeight,\n                  OutlineCellWidth, OutlineCellHeight);\n  }\n};\n\n}  // namespace Impacto"
  },
  {
    "path": "src/game.cpp",
    "content": "#include \"game.h\"\n\n#include \"text/text.h\"\n#include \"text/dialoguepage.h\"\n#include \"workqueue.h\"\n#include \"modelviewer.h\"\n#include \"characterviewer.h\"\n#include \"log.h\"\n#include \"inputsystem.h\"\n#include \"debugmenu.h\"\n#include \"renderer/opengl/glc.h\"\n\n#include \"ui/ui.h\"\n#include \"ui/gamespecific.h\"\n\n#include \"data/savesystem.h\"\n#include \"data/achievementsystem.h\"\n#include \"data/tipssystem.h\"\n#include \"audio/audiosystem.h\"\n#include \"video/videosystem.h\"\n#include \"subtitle/subtitlesystem.h\"\n#include \"background2d.h\"\n#include \"mask2d.h\"\n#include \"character2d.h\"\n#include \"renderer/3d/scene.h\"\n#include \"mem.h\"\n#include \"vm/interface/input.h\"\n#include \"vm/inst_dialogue.h\"\n#include \"renderer/window.h\"\n#include \"hud/datedisplay.h\"\n#include \"hud/saveicondisplay.h\"\n#include \"hud/loadingdisplay.h\"\n#include \"hud/tipsnotification.h\"\n#include \"games/cclcc/systemmenu.h\"\n#include \"effects/wave.h\"\n#include \"effects/blur.h\"\n#include \"effects/mosaic.h\"\n\n#include \"profile/profile.h\"\n#include \"profile/game.h\"\n#include \"profile/sprites.h\"\n#include \"profile/charset.h\"\n#include \"profile/fonts.h\"\n#include \"profile/dialogue.h\"\n#include \"profile/animations.h\"\n#include \"profile/scene3d.h\"\n#include \"profile/vm.h\"\n#include \"profile/scriptvars.h\"\n#include \"profile/configsystem.h\"\n#include \"profile/hud/saveicon.h\"\n#include \"profile/ui/commonmenu.h\"\n#include \"profile/ui/selectionmenu.h\"\n#include \"profile/ui/sysmesbox.h\"\n#include \"profile/ui/systemmenu.h\"\n#include \"profile/ui/titlemenu.h\"\n#include \"profile/ui/savemenu.h\"\n#include \"profile/ui/backlogmenu.h\"\n#include \"profile/ui/optionsmenu.h\"\n#include \"profile/ui/tipsmenu.h\"\n#include \"profile/ui/extramenus.h\"\n#include \"profile/ui/trophymenu.h\"\n#include \"profile/ui/helpmenu.h\"\n#include \"profile/data/bgeff.h\"\n#include \"profile/ui/gamespecific.h\"\n#include \"profile/subtitle.h\"\n\nnamespace Impacto {\n\nusing namespace Profile::ScriptVars;\n\nnamespace Game {\n\nstatic void Init() {\n  WorkQueue::Init();\n\n  Profile::LoadGameFromLua();\n\n  Io::VfsInit();\n\n#ifndef IMPACTO_DISABLE_IMGUI\n  IMGUI_CHECKVERSION();\n  ImGui::CreateContext();\n  ImGuiIO& io = ImGui::GetIO();\n  io.IniFilename = NULL;\n  io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;\n  io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;\n  if (+Profile::GameFeatures & +GameFeature::DebugMenu &&\n      +Profile::GameFeatures & +GameFeature::DebugMenuMultiViewport) {\n    io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable;\n    io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;\n  }\n#endif\n\n  InitRenderer();\n  InitCursors();\n\n  std::fill(std::begin(DrawComponents), std::end(DrawComponents),\n            DrawComponentType::None);\n\n  Profile::ConfigSystem::Configure();\n\n  if (+Profile::GameFeatures & +GameFeature::Audio) {\n    Audio::AudioInit();\n  }\n\n  if (+Profile::GameFeatures & +GameFeature::Video) {\n    Video::VideoInit();\n  }\n\n  if (+Profile::GameFeatures & +GameFeature::Subtitles) {\n    Profile::Subtitle::Configure();\n    Subtitle::SubtitleInit();\n  }\n\n  ScrWork = {};\n  FlagWork = {};\n\n  if (+Profile::GameFeatures & +GameFeature::Renderer2D) {\n    Profile::LoadSpritesheets();\n    Profile::Charset::Load();\n    Profile::LoadFonts();\n    Profile::LoadAnimations();\n    DialoguePage::Init();\n\n    Background2D::Init();\n    Mask2D::Init();\n\n    if (Profile::UseBgChaEffects || Profile::UseBgFrameEffects) {\n      Profile::BgEff::Load();\n    }\n\n    if (Profile::UseWaveEffects) {\n      Profile::WaveEffects::Load();\n      Effects::Init();\n    }\n\n    Effects::Blur.Init();\n  }\n\n  SetWindowIcon(Window->SDLWindow);\n\n  if (+Profile::GameFeatures & +GameFeature::ModelViewer) {\n    ModelViewer::Init();\n  }\n\n  if (+Profile::GameFeatures & +GameFeature::CharacterViewer) {\n    CharacterViewer::Init();\n  }\n\n  if (+Profile::GameFeatures & +GameFeature::Sc3VirtualMachine) {\n    Vm::Init();\n\n    Profile::CommonMenu::Configure();\n    Profile::SelectionMenu::Configure();\n\n    SaveSystem::Init();\n    AchievementSystem::Init();\n    TipsSystem::Init();\n    SaveIconDisplay::Init();\n    LoadingDisplay::Init();\n    Profile::GameSpecific::Configure();\n    Profile::SysMesBox::Configure();\n    Profile::TitleMenu::Configure();\n    Profile::SystemMenu::Configure();\n    Profile::SaveMenu::Configure();\n    Profile::BacklogMenu::Configure();\n    Profile::OptionsMenu::Configure();\n    Profile::TrophyMenu::Configure();\n    Profile::HelpMenu::Configure();\n    Profile::TipsMenu::Configure();\n    Profile::ExtraMenus::Configure();\n    DateDisplay::Init();\n    TipsNotification::Init();\n    // Default controls\n    Vm::Interface::UpdatePADcustomType(0);\n\n    SaveSystem::InitializeSystemData();\n\n    Effects::Mosaic.Init();\n  }\n\n  Profile::ClearProfile();\n}\n\nvoid InitFromProfile(std::string const& name) {\n  Profile::MakeLuaProfile(name);\n  Init();\n}\n\nvoid Shutdown() {\n  if (+Profile::GameFeatures & +GameFeature::Audio) {\n    Audio::AudioShutdown();\n  }\n\n  if (+Profile::GameFeatures & +GameFeature::Video) {\n    Video::VideoShutdown();\n  }\n\n  if (+Profile::GameFeatures & +GameFeature::Renderer2D) {\n    Renderer->Shutdown();\n  }\n  WorkQueue::StopWorkQueue();\n  Window->Shutdown();\n}\n\nvoid UpdateGameState(float dt) {\n  static float UpdateSecondCounter = 0.0f;\n  UpdateSecondCounter += dt;\n  if (UpdateSecondCounter >= 1) {\n    constexpr int maxPlayTime = (999 * 60 + 59) * 60 + 59;\n    constexpr int maxTotalPlayTime = (99999 * 60 + 59) * 60 + 59;\n    ScrWork[SW_PLAYTIME] = std::min(ScrWork[SW_PLAYTIME] + 1, maxPlayTime);\n    ScrWork[SW_TOTALPLAYTIME] =\n        std::min(ScrWork[SW_TOTALPLAYTIME] + 1, maxTotalPlayTime);\n\n    UpdateSecondCounter -= 1.0f;\n  }\n\n  if ((ScrWork[SW_GAMESTATE] & 5) == 1 && !GetFlag(SF_SYSTEMMENUDISABLE) &&\n      !GetFlag(SF_GAMEPAUSE) && !GetFlag(SF_SYSMENUDISABLE) &&\n      Vm::Interface::GetControlState(8)) {\n    // Some more stuff here?\n    if ((GetFlag(SF_MESWINDOW0OPENFL) &&\n         DialoguePages[0].TextIsFullyOpaque()) ||\n        (GetFlag(SF_SHOWWAITICON) || GetFlag(SF_SHOWWAITICON + 1) ||\n         GetFlag(SF_SHOWWAITICON + 2))) {\n      SetFlag(SF_UIHIDDEN, !GetFlag(SF_UIHIDDEN));\n    }\n  }\n  Vm::ChkMesSkip();\n  if (Profile::Vm::GameInstructionSet == Vm::InstructionSet::CC) {\n    UI::GameSpecific::UpdateCCButtonGuide(dt);\n  }\n}\n\nvoid UpdateSystem(float dt) {\n  constexpr float updateInterval = 1.0f / 60.0f;\n\n  static float UpdateSecondCounter = 0.0f;\n  UpdateSecondCounter += dt;\n  if (UpdateSecondCounter < updateInterval) {\n    return;\n  }\n\n  SDL_Event e;\n  if (+Profile::GameFeatures & +GameFeature::Input) {\n    Input::BeginFrame();\n    RequestCursor(CursorType::Default);\n  }\n\n  while (SDL_PollEvent(&e)) {\n    if (e.type == SDL_QUIT) {\n      if (Profile::HasScriptedExitLogic) {\n        Input::KeyboardButtonWentDown[SDL_SCANCODE_ESCAPE] = true;\n      } else {\n        ShouldQuit = true;\n      }\n    }\n\n#ifndef IMPACTO_DISABLE_IMGUI\n    ImGuiIO& io = ImGui::GetIO();\n    if (ImGui_ImplSDL2_ProcessEvent(&e) &&\n        (io.WantCaptureKeyboard || io.WantCaptureMouse))\n      continue;\n#endif\n\n    if (+Profile::GameFeatures & +GameFeature::Input) {\n      if (Input::HandleEvent(&e)) continue;\n    }\n\n    WorkQueue::HandleEvent(&e);\n  }\n  if (+Profile::GameFeatures & +GameFeature::Sc3VirtualMachine) {\n    Vm::Interface::UpdatePADInput();\n    Vm::Interface::UpdatePADHoldInput(updateInterval);\n    Vm::Interface::UpdateKBHoldInput(updateInterval);\n    UpdateGameState(updateInterval);\n\n    for (DrawComponentType value :\n         magic_enum::enum_values<DrawComponentType>()) {\n      for (auto const& menu : UI::Menus[value]) {\n        menu->Update(updateInterval);\n      }\n    }\n\n    UI::GameSpecific::NonGameplayUpdate(updateInterval);\n    SaveIconDisplay::Update(updateInterval);\n    LoadingDisplay::Update(updateInterval);\n    DateDisplay::Update(updateInterval);\n    if (ScrWork[SW_GAMESTATE] & 5 && !GetFlag(SF_GAMEPAUSE)) {\n      UI::GameSpecific::Update(updateInterval);\n\n      if (Profile::UseWaveEffects) {\n        if (IsBgWaveEffectActive()) {\n          Effects::WaveBG.Update(updateInterval);\n        }\n        const bool isCC =\n            +Profile::Vm::GameInstructionSet == +Vm::InstructionSet::CC;\n        if (GetFlag(SF_BGEFF1DISP) && (!isCC || ScrWork[SW_EFF_WAVE_ALPHA])) {\n          Effects::WaveEFF.Update(updateInterval);\n        }\n      }\n\n      if (!GetFlag(SF_MOVIEPLAY)) {\n        TipsNotification::Update(updateInterval);\n      }\n    }\n\n    Vm::Update(updateInterval);\n\n    ApplyCursorForFrame();\n  }\n  UpdateSecondCounter -= updateInterval;\n}\n\nvoid Update(float dt) {\n  UpdateSystem(dt);\n\n#ifndef IMPACTO_DISABLE_IMGUI\n  Renderer->ImGuiBeginFrame();\n#endif\n\n  if (+Profile::GameFeatures & +GameFeature::ModelViewer) {\n    ModelViewer::Update(dt);\n  }\n\n  if (+Profile::GameFeatures & +GameFeature::CharacterViewer) {\n    CharacterViewer::Update(dt);\n  }\n\n  if (+Profile::GameFeatures & +GameFeature::Audio) {\n    Audio::AudioUpdate(dt);\n  }\n\n  if (+Profile::GameFeatures & +GameFeature::Video) {\n    Video::VideoUpdate(dt);\n  }\n\n  if (+Profile::GameFeatures & +GameFeature::Audio &&\n      +Profile::GameFeatures & +GameFeature::Subtitles) {\n    Audio::AudioSubtitlesUpdate();\n  }\n\n  if (+Profile::GameFeatures & +GameFeature::Scene3D) {\n    Renderer->Scene->Update(dt);\n  }\n\n  if (+Profile::GameFeatures & +GameFeature::Renderer2D) {\n    for (DialoguePage& page : DialoguePages) page.Update(dt);\n  }\n}\n\nstatic void RenderMain() {\n  Background2D::LastRenderedBackground = nullptr;\n  UI::GameSpecific::RenderEarlyMain();\n  for (uint32_t layer = 0; layer <= Profile::LayerCount; layer++) {\n    if (Profile::Vm::GameInstructionSet == Vm::InstructionSet::CC) {\n      const int renderTarget = ScrWork[SW_RENDERTARGET + layer];\n      if (0 <= renderTarget && renderTarget <= MaxFramebuffers) {\n        Renderer->SetFramebuffer(renderTarget);\n      }\n    }\n\n    for (int bgId = 0; bgId < std::ssize(Backgrounds); bgId++) {\n      int bufId = ScrWork[SW_BG1SURF + bgId];\n      Backgrounds2D[bufId]->UpdateState(bgId);\n      Backgrounds2D[bufId]->Render(layer);\n    }\n\n    // Games with <= 2 don't render their captures separately\n    if (Profile::Vm::ScrWorkCaptureStructSize > 0) {\n      for (int capId = 0; capId < static_cast<int>(Profile::ScreenCaptureCount);\n           capId++) {\n        Screencaptures[capId].UpdateState(capId);\n        Screencaptures[capId].Render(layer);\n      }\n    }\n\n    if (Background2D::LastRenderedBackground != nullptr) {\n      Background2D::LastRenderedBackground->RenderBgEff(layer);\n    }\n\n    if ((+Profile::GameFeatures & +GameFeature::Renderer2D) &&\n        !(+Profile::GameFeatures & +GameFeature::Scene3D)) {\n      for (int chaId = 0; chaId < std::ssize(Characters2D); chaId++) {\n        int bufId = ScrWork[SW_CHA1SURF + chaId];\n        Characters2D[bufId].UpdateState(chaId);\n        Characters2D[bufId].Render(layer);\n      }\n    }\n\n    if (Profile::Vm::ScrWorkBgEffStructSize > 0) {\n      for (int bgId = 0; bgId < std::ssize(Backgrounds); bgId++) {\n        Framebuffers[0].UpdateState(bgId);\n        Framebuffers[0].Render(layer);\n      }\n    }\n\n    for (int i = 0; i < 2; ++i) {\n      constexpr static int maskOffset = 7;\n      if (ScrWork[SW_MASK1PRI + maskOffset * i] == static_cast<int>(layer)) {\n        const int maskAlpha = ScrWork[SW_MASK1ALPHA_OFS + i] +\n                              ScrWork[SW_MASK1ALPHA + maskOffset * i];\n\n        if (maskAlpha > 0) {\n          RectF maskRect = {(float)ScrWork[SW_MASK1POSX + maskOffset * i],\n                            (float)ScrWork[SW_MASK1POSY + maskOffset * i],\n                            (float)ScrWork[SW_MASK1SIZEX + maskOffset * i],\n                            (float)ScrWork[SW_MASK1SIZEY + maskOffset * i]};\n          if (maskRect.Width == 0.0f || maskRect.Height == 0.0f) {\n            maskRect = {0.0f, 0.0f, Profile::DesignWidth,\n                        Profile::DesignHeight};\n          }\n\n          glm::vec4 col = ScrWorkGetColor(SW_MASK1COLOR + maskOffset * i);\n          col.a = glm::min(maskAlpha / 256.0f, 1.0f);\n\n          Renderer->DrawQuad(maskRect, col);\n        }\n      }\n    }\n\n    if (Profile::UseMoviePriority &&\n        (+Profile::GameFeatures & +GameFeature::Video)) {\n      int videoAlpha = 0;\n      if (Profile::Vm::GameInstructionSet == Vm::InstructionSet::CHLCC) {\n        if (ScrWork[SW_MOVIEALPHA] > 0 &&\n            ScrWork[SW_MOVIEPRI] == static_cast<int>(layer)) {\n          videoAlpha = ScrWork[SW_MOVIEALPHA];\n        } else if (ScrWork[SW_MOVIEALPHA] == 0 &&\n                   ScrWork[SW_MOVIEPRI2] == static_cast<int>(layer)) {\n          videoAlpha = ScrWork[SW_MOVIEALPHA2];\n        }\n      } else {\n        if (ScrWork[SW_MOVIEPRI] == static_cast<int>(layer)) {\n          videoAlpha = ScrWork[SW_MOVIEALPHA];\n        } else if (ScrWork[SW_MOVIEPRI2] == static_cast<int>(layer)) {\n          videoAlpha = ScrWork[SW_MOVIEALPHA2];\n        } else if (ScrWork[SW_MOVIEPRI3] == static_cast<int>(layer)) {\n          videoAlpha = ScrWork[SW_MOVIEALPHA3];\n        } else if (ScrWork[SW_MOVIEPRI4] == static_cast<int>(layer)) {\n          videoAlpha = ScrWork[SW_MOVIEALPHA4];\n        }\n      }\n      if (videoAlpha > 0) {\n        Video::VideoRender(videoAlpha / 256.0f);\n      }\n    }\n\n    if (ScrWork[SW_FEATHERING_PRI] == static_cast<int>(layer) &&\n        ScrWork[SW_FEATHERING] > 0) {\n      Effects::Blur.Render(ScrWork[SW_FEATHERING]);\n    }\n\n    if (ScrWork[SW_FEATHERING2_PRI] == static_cast<int>(layer) &&\n        ScrWork[SW_FEATHERING2] > 0) {\n      Effects::Blur.Render(ScrWork[SW_FEATHERING2]);\n    }\n\n    if (Profile::UseWaveEffects) {\n      const bool isCC =\n          +Profile::Vm::GameInstructionSet == +Vm::InstructionSet::CC;\n      if (GetFlag(SF_BGEFF1DISP) && (!isCC || ScrWork[SW_EFF_WAVE_ALPHA])) {\n        if (ScrWork[SW_EFF_WAVE_PRI] == static_cast<int>(layer)) {\n          std::optional<float> alpha = std::nullopt;\n          if (isCC) {\n            alpha = static_cast<uint32_t>(ScrWork[SW_EFF_WAVE_ALPHA]) / 256.0f;\n          }\n          Renderer->CaptureScreencap(Screencaptures[0].BgSprite);\n          Screencaptures[0].Status = LoadStatus::Loaded;\n          Effects::WaveEFF.CalcPos(0, alpha);\n          Effects::WaveEFF.Render(Screencaptures[0].BgSprite);\n        }\n      }\n    }\n\n    if (ScrWork[SW_MOSAIC_PRI] == static_cast<int>(layer)) {\n      // There is no normalizing around the usual 720p ScrWork vars *by design*!\n      // Lower-resolution games actually show larger tile sizes than\n      // the higher-resolution ones.\n      Effects::Mosaic.Render(static_cast<float>(ScrWork[SW_MOSAIC]));\n    }\n\n    UI::GameSpecific::RenderLayer(layer);\n\n    for (size_t capId = 0; capId < Profile::ScreenCaptureCount; capId++) {\n      const size_t capOffset =\n          capId * Profile::Vm::ScrWorkCaptureEffectInfoStructSize;\n      if (ScrWork[SW_EFF_CAP_BUF + capOffset] == 0 ||\n          static_cast<uint32_t>(ScrWork[SW_EFF_CAP_PRI + capOffset]) != layer) {\n        continue;\n      }\n\n      Renderer->CaptureScreencap(Screencaptures[capId].BgSprite);\n      Screencaptures[capId].Status = LoadStatus::Loaded;\n    }\n\n    if (GetFlag(SF_MASK_CAPTURE) &&\n        ScrWork[SW_MASK_CAPTURE_PRI] == static_cast<int>(layer)) {\n      Renderer->CaptureScreencap(MaskCapture.BgSprite);\n      MaskCapture.Status = LoadStatus::Loaded;\n    }\n  }\n  UI::GameSpecific::RenderMain();\n\n  Renderer->SetFramebuffer(0);\n\n  if (SaveSystem::Implementation) {\n    Renderer->CaptureScreencap(SaveSystem::GetWorkingSaveThumbnail());\n  }\n\n  DateDisplay::Render();\n\n  // MO8 uses those huge layer indexes for movie menu, it doesn't\n  // actually have 4000 layers\n  if ((+Profile::GameFeatures & +GameFeature::Video) &&\n      (!Profile::UseMoviePriority ||\n       (Profile::Vm::GameInstructionSet == Vm::InstructionSet::MO8 &&\n        (ScrWork[SW_MOVIEPRI] == 3000 || ScrWork[SW_MOVIEPRI] == 4000)))) {\n    Video::VideoRender(ScrWork[SW_MOVIEALPHA] / 256.0f);\n  }\n\n  if (+Profile::GameFeatures & +GameFeature::Audio &&\n      +Profile::GameFeatures & +GameFeature::Subtitles) {\n    Audio::AudioSubtitlesRender();\n  }\n}\n\nvoid Render() {\n  Window->Update();\n\n  Renderer->BeginFrame();\n\n  if (+Profile::GameFeatures & +GameFeature::Scene3D) {\n    Renderer->Scene->Render();\n  }\n\n  Renderer->BeginFrame2D();\n\n#ifndef IMPACTO_DISABLE_IMGUI\n  if (+Profile::GameFeatures & +GameFeature::DebugMenu) {\n    DebugMenu::Show();\n  }\n#endif\n\n  if (+Profile::GameFeatures & +GameFeature::Renderer2D) {\n    if (Window->WindowDimensionsChanged) {\n      Background2D::InitFrameBuffers();\n    }\n    for (int i = 0; i < Vm::MaxThreads; i++) {\n      if (DrawComponents[i] == DrawComponentType::None) break;\n\n      switch (DrawComponents[i]) {\n        case DrawComponentType::Text: {\n          if (!GetFlag(SF_UIHIDDEN) &&\n              (!GetFlag(SF_SELECTMODE) || GetFlag(SF_SYSTEMMENUCAPTURE))) {\n            // Dialogue pages drawn in reverse order, at least for cclcc\n            for (auto pageIt = DialoguePages.rbegin();\n                 pageIt != DialoguePages.rend(); pageIt++) {\n              // TODO: Consistently just use the ScrWork variable internally\n              // after tips and backlog entry pages have been refactored out of\n              // DialoguePage\n              pageIt->Mode = static_cast<DialoguePageMode>(\n                  ScrWork[SW_MESMODE0 + 10 * pageIt->Id]);\n              pageIt->Render();\n            }\n          }\n          // System menu capture\n          if (Profile::Vm::GameInstructionSet == Vm::InstructionSet::CC &&\n              GetFlag(SF_SYSTEMMENUCAPTURE)) {\n            Renderer->CaptureScreencap(\n                static_cast<UI::CCLCC::SystemMenu*>(UI::SystemMenuPtr)\n                    ->ScreenCap);\n            SetFlag(SF_SYSTEMMENUCAPTURE, false);\n          }\n\n          if (!GetFlag(SF_UIHIDDEN) && !GetFlag(SF_MOVIEPLAY)) {\n            TipsNotification::Render();\n          }\n          break;\n        }\n        case DrawComponentType::Main: {\n          if (Profile::Vm::GameInstructionSet == Vm::InstructionSet::CC &&\n              !(!GetFlag(SF_SELECTMODE) ||\n                (GetFlag(SF_SYSTEMMENUCAPTURE) &&\n                 ScrWork[SW_RESTARTMASK] != 0x100))) {\n            break;\n          }\n          RenderMain();\n          break;\n        }\n        case DrawComponentType::ExtrasScenes: {\n          break;\n        }\n        case DrawComponentType::Mask: {\n          Renderer->DrawQuad(\n              RectF(0.0f, 0.0f, Profile::DesignWidth, Profile::DesignHeight),\n              glm::vec4(0.0f, 0.0f, 0.0f, (ScrWork[SW_RESTARTMASK] / 256.0f)));\n          break;\n        }\n        case DrawComponentType::SaveMenu: {\n          break;\n        }\n        case DrawComponentType::SystemIcons: {\n          if (Profile::Vm::GameInstructionSet == Vm::InstructionSet::CC) {\n            UI::GameSpecific::RenderCCButtonGuide();\n          }\n\n          LoadingDisplay::Render();\n          SaveIconDisplay::Render();\n          break;\n        }\n        case DrawComponentType::TitleMenu: {\n          break;\n        }\n        case DrawComponentType::SystemMenu: {\n          break;\n        }\n        case DrawComponentType::PlayData: {\n          break;\n        }\n        case DrawComponentType::SystemMessage: {\n          break;\n        }\n        case DrawComponentType::SaveIcon: {\n          if (Profile::SaveIcon::SaveIconMenuOverlay) {\n            SaveIconDisplay::Render();\n          }\n          break;\n        }\n        default: {\n          ImpLogSlow(LogLevel::Warning, LogChannel::General,\n                     \"Encountered unknown draw component type {}\\n\",\n                     DrawComponents[i]);\n          break;\n        }\n      }\n\n      for (auto const& menu : UI::Menus[DrawComponents[i]]) {\n        menu->Render();\n      }\n    }\n  }\n\n  if (+Profile::GameFeatures & +GameFeature::CharacterViewer) {\n    if (Backgrounds2D[0]->Status == LoadStatus::Loaded) {\n      Renderer->DrawSprite(\n          Backgrounds2D[0]->BgSprite,\n          RectF(0.0f, 0.0f, Backgrounds2D[0]->BgSprite.ScaledWidth(),\n                Backgrounds2D[0]->BgSprite.ScaledHeight()));\n    }\n    if (Characters2D[0].Status == LoadStatus::Loaded) {\n      Characters2D[0].Layers[0] = 0;\n      ScrWork[SW_CHA1ALPHA] = 256;\n\n      Characters2D[0].UpdateState(0);\n      Characters2D[0].Render(0);\n    }\n  }\n  Renderer->EndFrame();\n\n  Window->Draw();\n}\n\n}  // namespace Game\n\n}  // namespace Impacto"
  },
  {
    "path": "src/game.h",
    "content": "#pragma once\n\n#include \"impacto.h\"\n#include \"vm/vm.h\"\n#include \"io/vfs.h\"\n#include \"renderer/renderer.h\"\n#include <magic_enum/magic_enum.hpp>\n\n#include <string>\n\nnamespace Impacto {\n\nenum class RendererType : int {\n  OpenGL,\n  Vulkan,\n  DirectX9,\n};\n\nenum class GameFeature : int {\n  DebugMenu = (1 << 0),\n  Scene3D = (1 << 1),\n  ModelViewer = (1 << 2),\n  Sc3VirtualMachine = (1 << 3),\n  Renderer2D = (1 << 4),\n  Input = (1 << 5),\n  Audio = (1 << 6),\n  CharacterViewer = (1 << 7),\n  Video = (1 << 8),\n  Subtitles = (1 << 9),\n  DebugMenuMultiViewport = (1 << 10),\n};\n\nenum class VideoPlayerType : int {\n  None,\n  FFmpeg,\n};\n\nenum class AudioBackendType : int {\n  None,\n  OpenAL,\n};\n\nenum class SubtitleAssBackendType : int {\n  None,\n  LibAss,\n};\n\nenum class SubtitleTextBackendType : int {\n  None,\n};\n\nenum class SubtitleBmpBackendType : int {\n  None,\n};\n\nnamespace Game {\n\nenum class DrawComponentType : uint8_t {\n  Text = 0x0,\n  Main = 0x1,\n  ExtrasScenes = 0x2,\n  Mask = 0x3,\n  SystemText = 0x3,\n  SaveMenu = 0x4,\n  SaveMenu05 = 0x5,\n  SystemIcons = 0x6,\n  TitleMenu = 0x7,\n  Option = 0x9,\n  SystemMenu = 0xA,\n  SystemMessage = 0xB,\n  PlayData = 0xC,\n  Album = 0xD,\n  ExtrasMusicMode = 0xE,\n  DictionaryMode = 0xF,\n  ExtrasMovieMode = 0x10,\n  ExtrasActorsVoice = 0x11,\n  SaveIcon = 0x12,\n  GlobalSystemMessage = 0x15,\n  InstallInfo = 0x16,\n  SystemMessage17 = 0x17,\n  DebugEditor = 0x1E,\n  None = 0xFF,\n};\nvoid InitFromProfile(std::string const& name);\n\nvoid Shutdown();\n\nvoid Update(float dt);\nvoid Render();\n\ninline DrawComponentType DrawComponents[Vm::MaxThreads];\n\ninline bool ShouldQuit = false;\n}  // namespace Game\n\n}  // namespace Impacto\n\ntemplate <>\nstruct magic_enum::customize::enum_range<Impacto::GameFeature> {\n  static constexpr bool is_flags = true;\n};\n\ntemplate <>\nstruct magic_enum::customize::enum_range<Impacto::Game::DrawComponentType> {\n  static constexpr int min = 0;\n  static constexpr int max = 0xff;\n};"
  },
  {
    "path": "src/games/cc/backlogmenu.cpp",
    "content": "#include \"backlogmenu.h\"\n\n#include \"../../profile/game.h\"\n#include \"../../ui/backlogmenu.h\"\n#include \"../../ui/widgets/cc/backlogentry.h\"\n#include \"../../profile/ui/backlogmenu.h\"\n#include \"../../profile/games/cc/backlogmenu.h\"\n#include \"../../profile/scriptvars.h\"\n#include \"../../audio/audiosystem.h\"\n\n#include \"../cclcc/systemmenu.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace CC {\n\nusing namespace Impacto::Profile::BacklogMenu;\nusing namespace Impacto::Profile::CC::BacklogMenu;\nusing namespace Impacto::UI::Widgets::CC;\nusing namespace Impacto::Profile::ScriptVars;\n\nvoid BacklogMenu::MenuButtonOnClick(Widgets::BacklogEntry* target) {\n  UI::BacklogMenu::MenuButtonOnClick(target);\n\n  if (target->AudioId == -1)\n    Audio::PlayInGroup(Audio::ACG_SE, \"sysse\", 4, false, 0.0f);\n}\n\nvoid BacklogMenu::Show() {\n  if (State == Hidden) {\n    if (ScrWork[SW_SYSMENUALPHA] == 0x100) {\n      FadeAnimation.DurationIn = FadeInDuration;\n      FadeAnimation.DurationOut = FadeOutDuration;\n    } else {\n      FadeAnimation.DurationIn = FadeInDirectDuration;\n      FadeAnimation.DurationOut = FadeOutDirectDuration;\n    }\n  }\n\n  UI::BacklogMenu::Show();\n}\n\nvoid BacklogMenu::Hide() {\n  if (State == Shown) {\n    if (ScrWork[SW_SYSMENUALPHA] == 0x100) {\n      FadeAnimation.DurationIn = FadeInDuration;\n      FadeAnimation.DurationOut = FadeOutDuration;\n    } else {\n      FadeAnimation.DurationIn = FadeInDirectDuration;\n      FadeAnimation.DurationOut = FadeOutDirectDuration;\n    }\n  }\n  Audio::Channels[Audio::AC_REV]->Stop(0.0f);\n  UI::BacklogMenu::Hide();\n}\n\nvoid BacklogMenu::UpdateVisibility() {\n  if (ScrWork[SW_SYSSUBMENUCT] < 32 && State == Shown) {\n    Hide();\n  } else if (ScrWork[SW_SYSSUBMENUCT] > 0 && State == Hidden &&\n             ScrWork[SW_SYSSUBMENUNO] == 1) {\n    Show();\n  }\n\n  if (FadeAnimation.IsIn() && ScrWork[SW_SYSSUBMENUCT] == 32) {\n    State = Shown;\n    IsFocused = true;\n  } else if (State == Hiding && FadeAnimation.IsOut() &&\n             ScrWork[SW_SYSSUBMENUCT] == 0) {\n    State = Hidden;\n    IsFocused = false;\n    if (UI::FocusedMenu) UI::FocusedMenu->IsFocused = true;\n\n    MainItems->Hide();\n  }\n}\n\nvoid BacklogMenu::Update(float dt) {\n  float prevScrollPos = *MainScrollbar->Value;\n  UI::BacklogMenu::Update(dt);\n  if (IsFocused && prevScrollPos != *MainScrollbar->Value) {\n    if (auto* menu = dynamic_cast<UI::CCLCC::SystemMenu*>(UI::SystemMenuPtr)) {\n      menu->BGPosition.y += (prevScrollPos - *MainScrollbar->Value) * 0.5f;\n    }\n  }\n}\n\nvoid BacklogMenu::Render() {\n  if (State == Hidden) return;\n\n  float opacity = glm::smoothstep(0.0f, 1.0f, FadeAnimation.Progress);\n  glm::vec4 transition(1.0f, 1.0f, 1.0f, opacity);\n\n  glm::vec4 maskTint = transition;\n  maskTint.a *= (float)0xa0 / 0x100;\n\n  MainItems->Tint = transition;\n  MainScrollbar->Tint = transition;\n\n  int repeatHeight = BacklogBackgroundRepeatHeight;\n  float backgroundY =\n      (float)fmod(PageY - EntryYPadding - RenderingBounds.Y, repeatHeight);\n  Renderer->DrawSprite(BacklogBackground, glm::vec2(0.0f, backgroundY),\n                       transition);\n  Renderer->DrawSprite(BacklogBackground,\n                       glm::vec2(0.0f, backgroundY + repeatHeight), transition);\n\n  RenderHighlight();\n  Renderer->DrawSprite(BacklogHeaderSprite, BacklogHeaderPosition, transition);\n  MainItems->Render();\n  MainScrollbar->Render();\n\n  Renderer->DrawSprite(\n      MenuMaskSprite,\n      RectF(0.0f, 0.0f, Profile::DesignWidth, Profile::DesignHeight), maskTint);\n\n  Renderer->DrawSprite(BacklogControlsSprite, BacklogControlsPosition,\n                       transition);\n}\n\n}  // namespace CC\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/cc/backlogmenu.h",
    "content": "#pragma once\n\n#include \"../../ui/backlogmenu.h\"\n#include \"../../ui/widgets/cc/backlogentry.h\"\n#include \"../../spritesheet.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace CC {\n\nclass BacklogMenu : public UI::BacklogMenu {\n public:\n  void Show() override;\n  void Hide() override;\n  void Render() override;\n  void Update(float dt) override;\n\n  Widgets::BacklogEntry* CreateBacklogEntry(\n      int id, Vm::BufferOffsetContext scrCtx, int audioId, int characterId,\n      glm::vec2 pos, const RectF& hoverBounds) const override {\n    return new Widgets::CC::BacklogEntry(id, scrCtx, audioId, characterId, pos,\n                                         hoverBounds);\n  }\n\n  void MenuButtonOnClick(Widgets::BacklogEntry* target) override;\n\n private:\n  void UpdateVisibility() override;\n};\n\n}  // namespace CC\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/cc/sysmesbox.cpp",
    "content": "#include \"sysmesbox.h\"\n\n#include \"../../profile/ui/sysmesbox.h\"\n#include \"../../profile/games/cc/sysmesbox.h\"\n#include \"../../profile/dialogue.h\"\n#include \"../../profile/game.h\"\n#include \"../../profile/scriptvars.h\"\n#include \"../../mem.h\"\n#include \"../../renderer/renderer.h\"\n#include \"../../vm/interface/input.h\"\n#include \"../../audio/audiosystem.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace CC {\n\nusing namespace Impacto::UI::Widgets;\nusing namespace Impacto::Profile::ScriptVars;\nusing namespace Impacto::Profile::SysMesBox;\nusing namespace Impacto::Profile::CC::SysMesBox;\nusing namespace Impacto::Vm::Interface;\n\nstatic float BoxAnimCount = 0.0f;\n\nvoid SysMesBox::ChoiceItemOnClick(Button* target) {\n  ScrWork[SW_SYSSEL] = target->Id;\n  Audio::PlayInGroup(Audio::ACG_SE, \"sysse\", 2, false, 0);\n  ChoiceMade = true;\n}\n\nvoid SysMesBox::Show() {\n  MessageItems = new Widgets::Group(this);\n  ChoiceItems = new Widgets::Group(this);\n\n  Sprite nullSprite = Sprite();\n  nullSprite.Bounds = RectF(0.0f, 0.0f, 0.0f, 0.0f);\n\n  auto onClick = [this](auto* btn) { return ChoiceItemOnClick(btn); };\n\n  float textBeginY = (TextMiddleY - TextMarginY * MessageCount) / 2.0f;\n  float diff;\n  float maxWidth = FLT_MIN;\n  for (int i = 0; i < MessageCount; i++) {\n    if (maxWidth < MessageWidths[i]) maxWidth = MessageWidths[i];\n  }\n\n  for (int i = 0; i < MessageCount; i++) {\n    if (Messages[i].empty()) continue;\n\n    diff = Messages[i][0].DestRect.X - ((TextX - maxWidth) / 2.0f);\n    for (size_t j = 0; j < Messages[i].size(); j++) {\n      Messages[i][j].DestRect.X -= diff;\n      Messages[i][j].DestRect.Y = textBeginY + (TextLineHeight * i);\n    }\n\n    Label* message = new Label(Messages[i], MessageWidths[i], TextFontSize,\n                               RendererOutlineMode::Full);\n\n    MessageItems->Add(message, FDIR_DOWN);\n  }\n\n  if (ChoiceCount == 1) {\n    WidgetOK =\n        new Button(0, ButtonOK, ButtonOKHighlighted, nullSprite,\n                   glm::vec2(ButtonOKCenterPosX - (ButtonOK.Bounds.Width / 2),\n                             ButtonOKCenterPosY - (ButtonOK.Bounds.Height / 2)),\n                   ButtonOkHoverBounds);\n    WidgetOK->OnClickHandler = onClick;\n    ChoiceItems->Add(WidgetOK, FDIR_RIGHT);\n\n  } else if (ChoiceCount == 2) {\n    WidgetYes = new Button(\n        0, ButtonYes, ButtonYesHighlighted, nullSprite,\n        glm::vec2(ButtonYesCenterPosX - (ButtonYes.Bounds.Width / 2),\n                  ButtonYesCenterPosY - (ButtonYes.Bounds.Height / 2)),\n        ButtonYesHoverBounds);\n    WidgetYes->OnClickHandler = onClick;\n    ChoiceItems->Add(WidgetYes, FDIR_RIGHT);\n    WidgetNo =\n        new Button(1, ButtonNo, ButtonNoHighlighted, nullSprite,\n                   glm::vec2(ButtonNoCenterPosX - (ButtonNo.Bounds.Width / 2),\n                             ButtonNoCenterPosY - (ButtonNo.Bounds.Height / 2)),\n                   ButtonNoHoverBounds);\n    WidgetNo->OnClickHandler = onClick;\n    ChoiceItems->Add(WidgetNo, FDIR_LEFT);\n  }\n\n  FadeAnimation.StartIn();\n  MessageItems->Show();\n  MessageItems->HasFocus = false;\n  if (ChoiceCount != 0) ChoiceItems->Show();\n  State = Showing;\n\n  if (UI::FocusedMenu != 0) {\n    LastFocusedMenu = UI::FocusedMenu;\n    LastFocusedMenu->IsFocused = false;\n  }\n  IsFocused = true;\n  UI::FocusedMenu = this;\n}\n\nvoid SysMesBox::Hide() {\n  FadeAnimation.StartOut();\n  State = Hiding;\n  if (LastFocusedMenu != 0) {\n    UI::FocusedMenu = LastFocusedMenu;\n    LastFocusedMenu->IsFocused = true;\n  } else {\n    UI::FocusedMenu = 0;\n  }\n  IsFocused = false;\n}\n\nvoid SysMesBox::UpdateInput(float dt) {\n  if (!IsFocused) return;\n\n  const auto* const prevSelected = CurrentlyFocusedElement;\n  Menu::UpdateInput(dt);\n  ChoiceItems->UpdateInput(dt);\n  if (CurrentlyFocusedElement && prevSelected != CurrentlyFocusedElement) {\n    Audio::PlayInGroup(Audio::ACG_SE, \"sysse\", 1, false, 0.0f);\n  }\n}\n\nvoid SysMesBox::Update(float dt) {\n  UpdateInput(dt);\n\n  FadeAnimation.Update(dt);\n\n  if (State == Hiding) {\n    BoxAnimCount = FadeAnimation.Progress * ScrWork[SW_SYSMESANIMCTF];\n    if (BoxAnimCount <= 0.0f) {\n      BoxAnimCount = 0.0f;\n      State = Hidden;\n    }\n  } else if (State == Showing) {\n    BoxAnimCount = FadeAnimation.Progress * ScrWork[SW_SYSMESANIMCTF];\n    if (BoxAnimCount >= ScrWork[SW_SYSMESANIMCTF]) {\n      BoxAnimCount = (float)ScrWork[SW_SYSMESANIMCTF];\n      State = Shown;\n    }\n  }\n\n  ScrWork[SW_SYSMESANIMCTCUR] = (int)std::floor(BoxAnimCount);\n\n  float animationProgress = FadeAnimation.Progress * AnimationSpeed;\n\n  if (animationProgress > SealSpriteCount) {\n    if (ChoiceCount == 2) {\n      float offsetProgress =\n          std::min(animationProgress - AnimationProgressWidgetsStartOffset,\n                   ButtonYesAnimationProgressEnd);\n      float scaleYes =\n          ButtonScaleMax - (offsetProgress * ButtonYesNoScaleMultiplier);\n      WidgetYes->NormalSprite.BaseScale = glm::vec2(scaleYes);\n      WidgetYes->FocusedSprite.BaseScale = glm::vec2(scaleYes);\n      WidgetYes->MoveTo(glm::vec2(\n          ButtonYesCenterPosX - (WidgetYes->NormalSprite.ScaledWidth() * 0.5f),\n          ButtonYesCenterPosY -\n              (WidgetYes->NormalSprite.ScaledHeight() * 0.5f)));\n      WidgetYes->Tint =\n          glm::vec4(1.0f, 1.0f, 1.0f, offsetProgress / ButtonYesNoAlphaDivider);\n\n      if (animationProgress - AnimationProgressWidgetsStartOffset >\n          ButtonNoDisplayStart) {\n        offsetProgress = animationProgress - ButtonNoAnimationProgressOffset;\n        float scaleNo =\n            ButtonScaleMax - (offsetProgress * ButtonYesNoScaleMultiplier);\n        WidgetNo->NormalSprite.BaseScale = glm::vec2(scaleNo);\n        WidgetNo->FocusedSprite.BaseScale = glm::vec2(scaleNo);\n        WidgetNo->MoveTo(glm::vec2(\n            ButtonNoCenterPosX - (WidgetNo->NormalSprite.ScaledWidth() * 0.5f),\n            ButtonNoCenterPosY -\n                (WidgetNo->NormalSprite.ScaledHeight() * 0.5f)));\n        WidgetNo->Tint = glm::vec4(1.0f, 1.0f, 1.0f,\n                                   offsetProgress / ButtonYesNoAlphaDivider);\n      } else {\n        WidgetNo->Tint = glm::vec4(1.0f, 1.0f, 1.0f, 0.0f);\n      }\n    } else if (ChoiceCount == 1) {\n      float offsetProgress =\n          (animationProgress - AnimationProgressWidgetsStartOffset);\n      float scaleOk =\n          ButtonScaleMax - (offsetProgress * ButtonOKScaleMultiplier);\n      WidgetOK->NormalSprite.BaseScale = glm::vec2(scaleOk);\n      WidgetOK->FocusedSprite.BaseScale = glm::vec2(scaleOk);\n      WidgetOK->MoveTo(glm::vec2(\n          ButtonOKCenterPosX - (WidgetOK->NormalSprite.ScaledWidth() * 0.5f),\n          ButtonOKCenterPosY - (WidgetOK->NormalSprite.ScaledHeight() * 0.5f)));\n      WidgetOK->Tint =\n          glm::vec4(1.0f, 1.0f, 1.0f, WidgetsAlphaMultiplier * offsetProgress);\n    }\n  }\n\n  if (State != Hidden) {\n    if (IsFocused) {\n      MessageItems->Update(dt);\n      ChoiceItems->Update(dt);\n    }\n  }\n}\n\nvoid SysMesBox::Render() {\n  if (State == Hidden) return;\n\n  int animationFrame = (int)std::ceil(FadeAnimation.Progress * AnimationSpeed);\n  int totalSeals =\n      animationFrame > SealSpriteCount ? SealSpriteCount : animationFrame;\n  for (int i = 0; i < totalSeals; i++) {\n    float alpha = glm::clamp(\n        0.125f * ((FadeAnimation.Progress * AnimationSpeed) - i), 0.0f, 1.0f);\n    glm::vec4 col(1.0f, 1.0f, 1.0f, alpha);\n    float scale = (1.0f - alpha) + 1.0f;\n    float width = SumoSealSprites[i].Bounds.Width * scale;\n    float height = SumoSealSprites[i].Bounds.Height * scale;\n    Renderer->DrawSprite(\n        SumoSealSprites[i],\n        RectF(SumoSealCenterPosX[i] - (width * 0.5f),\n              SumoSealCenterPosY[i] - (height * 0.5f), width, height),\n        col);\n  }\n\n  if (animationFrame > SealSpriteCount) {\n    MessageItems->Tint.a =\n        WidgetsAlphaMultiplier * ((FadeAnimation.Progress * AnimationSpeed) -\n                                  AnimationProgressWidgetsStartOffset);\n    MessageItems->Render();\n\n    ChoiceItems->Render();\n  }\n}\n\nvoid SysMesBox::Init() {\n  ChoiceMade = false;\n  MessageCount = 0;\n  ChoiceCount = 0;\n\n  FadeAnimation.DurationIn = FadeInDuration;\n  FadeAnimation.DurationOut = FadeOutDuration;\n  FadeAnimation.Direction = AnimationDirection::In;\n  FadeAnimation.LoopMode = AnimationLoopMode::Stop;\n}\n\nvoid SysMesBox::AddMessage(Vm::BufferOffsetContext ctx) {\n  Impacto::Vm::Sc3VmThread dummy;\n  dummy.IpOffset = ctx.IpOffset;\n  dummy.ScriptBufferId = ctx.ScriptBufferId;\n\n  Messages[MessageCount] =\n      TextLayoutPlainLine(&dummy, 255, Profile::Dialogue::DialogueFont,\n                          TextFontSize, Profile::Dialogue::ColorTable[0], 1.0f,\n                          glm::vec2(TextX, 0.0f), TextAlignment::Left);\n  if (!Messages[MessageCount].empty()) {\n    float mesLen = 0.0f;\n    for (size_t i = 0; i < Messages[MessageCount].size(); i++) {\n      mesLen += Messages[MessageCount][i].DestRect.Width;\n    }\n    MessageWidths[MessageCount] = mesLen;\n    MessageCount++;\n  }\n}\n\nvoid SysMesBox::AddChoice(Vm::BufferOffsetContext ctx) {\n  Impacto::Vm::Sc3VmThread dummy;\n  dummy.IpOffset = ctx.IpOffset;\n  dummy.ScriptBufferId = ctx.ScriptBufferId;\n  Choices[ChoiceCount] =\n      TextLayoutPlainLine(&dummy, 255, Profile::Dialogue::DialogueFont,\n                          TextFontSize, Profile::Dialogue::ColorTable[0], 1.0f,\n                          glm::vec2(TextX, 0.0f), TextAlignment::Left);\n  if (!Choices[ChoiceCount].empty()) {\n    float mesLen = 0.0f;\n    for (size_t i = 0; i < Choices[ChoiceCount].size(); i++) {\n      mesLen += Choices[ChoiceCount][i].DestRect.Width;\n    }\n    ChoiceWidths[ChoiceCount] = mesLen;\n    ChoiceCount++;\n  }\n}\n\n}  // namespace CC\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/cc/sysmesbox.h",
    "content": "#pragma once\n\n#include \"../../ui/sysmesbox.h\"\n#include \"../../ui/widgets/button.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace CC {\n\nclass SysMesBox : public UI::SysMesBox {\n public:\n  virtual void Show() override;\n  virtual void Hide() override;\n  virtual void Update(float dt) override;\n  virtual void UpdateInput(float dt) override;\n  virtual void Render() override;\n\n  virtual void Init() override;\n  virtual void AddMessage(Vm::BufferOffsetContext ctx) override;\n  virtual void AddChoice(Vm::BufferOffsetContext ctx) override;\n\n private:\n  void ChoiceItemOnClick(UI::Widgets::Button* target);\n  UI::Widgets::Button* WidgetOK;\n  UI::Widgets::Button* WidgetYes;\n  UI::Widgets::Button* WidgetNo;\n};\n\n}  // namespace CC\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/cc/titlemenu.cpp",
    "content": "#include \"titlemenu.h\"\n\n#include \"../../texture/texture.h\"\n#include \"../../spritesheet.h\"\n#include \"../../io/vfs.h\"\n\n#include \"../../profile/ui/titlemenu.h\"\n#include \"../../profile/games/cc/titlemenu.h\"\n#include \"../../renderer/renderer.h\"\n#include \"../../texture/texture.h\"\n#include \"../../spritesheet.h\"\n#include \"../../audio/audiosystem.h\"\n#include \"../../audio/audiostream.h\"\n#include \"../../audio/audiochannel.h\"\n#include \"../../io/vfs.h\"\n#include \"../../mem.h\"\n#include \"../../profile/scriptvars.h\"\n#include \"../../profile/game.h\"\n#include \"../../background2d.h\"\n#include \"../../inputsystem.h\"\n#include \"../../ui/widgets/label.h\"\n#include \"../../vm/interface/input.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace CC {\n\nusing namespace Impacto::Profile::TitleMenu;\nusing namespace Impacto::Profile::CC::TitleMenu;\nusing namespace Impacto::Profile::ScriptVars;\n\nusing namespace Impacto::Vm::Interface;\n\nusing namespace Impacto::UI::Widgets::CC;\n\nvoid TitleMenu::MenuButtonOnClick(Widgets::Button* target) {\n  target->Hovered = false;\n  ScrWork[SW_TITLECUR1] = target->Id;\n  SetFlag(SF_TITLEEND, 1);\n  AllowsScriptInput = true;\n}\n\nvoid TitleMenu::ContinueButtonOnClick(Widgets::Button* target) {\n  target->Hovered = false;\n  CurrentSubMenu = ContinueItems;\n  AllowsScriptInput = false;\n}\n\nvoid TitleMenu::ExtraButtonOnClick(Widgets::Button* target) {\n  target->Hovered = false;\n  CurrentSubMenu = ExtraItems;\n  AllowsScriptInput = false;\n}\n\nTitleMenu::TitleMenu() {\n  MainItems = new Widgets::Group(this);\n  ContinueItems = new Widgets::Group(this);\n  ContinueItems->WrapFocus = false;\n  ExtraItems = new Widgets::Group(this);\n  ExtraItems->WrapFocus = false;\n\n  auto onClick = [this](auto* btn) { return MenuButtonOnClick(btn); };\n  auto continueOnClick = [this](auto* btn) {\n    return ContinueButtonOnClick(btn);\n  };\n  auto extraOnClick = [this](auto* btn) { return ExtraButtonOnClick(btn); };\n\n  Sprite nullSprite = Sprite();\n  nullSprite.Bounds = RectF(0.0f, 0.0f, 0.0f, 0.0f);\n\n  // NewGame menu button\n  NewGame = new TitleButton(\n      0, MenuEntriesSprites[0], MenuEntriesHSprites[0], ItemHighlightSprite,\n      glm::vec2(((ItemHighlightOffsetX)-1.0f) + ItemHighlightOffsetX,\n                (ItemYBase + (0 * ItemPadding))));\n  NewGame->OnClickHandler = onClick;\n  MainItems->Add(NewGame, FDIR_DOWN);\n\n  // Continue menu button\n  Continue = new TitleButton(\n      1, MenuEntriesSprites[1], MenuEntriesHSprites[1], ItemHighlightSprite,\n      glm::vec2(((ItemHighlightOffsetX)) + ItemHighlightOffsetX,\n                (ItemYBase + (1 * ItemPadding))));\n  Continue->OnClickHandler = continueOnClick;\n  MainItems->Add(Continue, FDIR_DOWN);\n\n  // Extra menu button\n  Extra = new TitleButton(\n      2, MenuEntriesSprites[2], MenuEntriesHSprites[2], ItemHighlightSprite,\n      glm::vec2(((ItemHighlightOffsetX)) + ItemHighlightOffsetX,\n                (ItemYBase + (2 * ItemPadding))));\n  Extra->OnClickHandler = extraOnClick;\n  MainItems->Add(Extra, FDIR_DOWN);\n\n  // Config menu button\n  Config = new TitleButton(\n      20, MenuEntriesSprites[3], MenuEntriesHSprites[3], ItemHighlightSprite,\n      glm::vec2(((ItemHighlightOffsetX)) + ItemHighlightOffsetX,\n                (ItemYBase + (3 * ItemPadding))));\n  Config->OnClickHandler = onClick;\n  MainItems->Add(Config, FDIR_DOWN);\n\n  // Help menu button\n  Help = new TitleButton(\n      40, MenuEntriesSprites[4], MenuEntriesHSprites[4], ItemHighlightSprite,\n      glm::vec2(((ItemHighlightOffsetX)) + ItemHighlightOffsetX,\n                (ItemYBase + (4 * ItemPadding))));\n  Help->OnClickHandler = onClick;\n  MainItems->Add(Help, FDIR_DOWN);\n\n  // Load secondary Continue menu button\n  Load = new TitleButton(10, LoadSprite, LoadHighlightSprite, nullSprite,\n                         glm::vec2(((SecondaryFirstItemHighlightOffsetX)) +\n                                       SecondaryFirstItemHighlightOffsetX,\n                                   (ItemYBase + (2 * ItemPadding))));\n  Load->OnClickHandler = onClick;\n  Load->IsSubButton = true;\n  ContinueItems->Add(Load, FDIR_RIGHT);\n\n  // QuickLoad secondary Continue menu button\n  QuickLoad =\n      new TitleButton(11, QuickLoadSprite, QuickLoadHighlightSprite, nullSprite,\n                      glm::vec2(((SecondarySecondItemHighlightOffsetX)) +\n                                    SecondarySecondItemHighlightOffsetX,\n                                (ItemYBase + (2 * ItemPadding))));\n  QuickLoad->OnClickHandler = onClick;\n  QuickLoad->IsSubButton = true;\n  ContinueItems->Add(QuickLoad, FDIR_RIGHT);\n\n  // Tips secondary Extra menu button\n  Tips = new TitleButton(30, TipsSprite, TipsHighlightSprite, nullSprite,\n                         glm::vec2(((SecondaryFirstItemHighlightOffsetX)) +\n                                       SecondaryFirstItemHighlightOffsetX,\n                                   (ItemYBase + (3 * ItemPadding))));\n  Tips->OnClickHandler = onClick;\n  Tips->IsSubButton = true;\n  ExtraItems->Add(Tips, FDIR_RIGHT);\n\n  // Library secondary Extra menu button\n  Library =\n      new TitleButton(31, LibrarySprite, LibraryHighlightSprite, nullSprite,\n                      glm::vec2(((SecondarySecondItemHighlightOffsetX)) +\n                                    SecondarySecondItemHighlightOffsetX,\n                                (ItemYBase + (3 * ItemPadding))));\n  Library->OnClickHandler = onClick;\n  Library->IsSubButton = true;\n  ExtraItems->Add(Library, FDIR_RIGHT);\n\n  // EndingList secondary Extra menu button\n  EndingList = new TitleButton(\n      32, EndingListSprite, EndingListHighlightSprite, nullSprite,\n      glm::vec2(((SecondaryThirdItemHighlightOffsetX)) +\n                    SecondaryThirdItemHighlightOffsetX,\n                (ItemYBase + (3 * ItemPadding))));\n  EndingList->OnClickHandler = onClick;\n  EndingList->IsSubButton = true;\n  ExtraItems->Add(EndingList, FDIR_RIGHT);\n}\n\nvoid TitleMenu::Show() {\n  if (State != Shown) {\n    State = Shown;\n    if (UI::FocusedMenu != 0) {\n      LastFocusedMenu = UI::FocusedMenu;\n      LastFocusedMenu->IsFocused = false;\n    }\n    IsFocused = true;\n    UI::FocusedMenu = this;\n    AllowsScriptInput = true;\n    if (PressToStartAnimation.State == AnimationState::Stopped) {\n      PressToStartAnimation.StartIn();\n      SmokeAnimation.StartIn();\n    }\n  }\n}\nvoid TitleMenu::Hide() {\n  if (State != Hidden) {\n    State = Hidden;\n    MainItems->Hide();\n    if (LastFocusedMenu != 0) {\n      UI::FocusedMenu = LastFocusedMenu;\n      LastFocusedMenu->IsFocused = true;\n    } else {\n      UI::FocusedMenu = 0;\n    }\n    IsFocused = false;\n    AllowsScriptInput = true;\n  }\n}\n\nvoid TitleMenu::UpdateInput(float dt) {\n  Menu::UpdateInput(dt);\n  MainItems->UpdateInput(dt);\n  if (CurrentSubMenu) {\n    CurrentSubMenu->UpdateInput(dt);\n    if ((PADinputButtonWentDown & PAD1B || PADinputMouseWentDown & PAD1B) &&\n        CurrentSubMenu->VisibilityState != Hidden) {\n      if (CurrentSubMenu == ContinueItems) {\n        // HideContinueItems();\n      }\n      if (CurrentSubMenu == ExtraItems) {\n        // HideExtraItems();\n      }\n      AllowsScriptInput = true;\n    }\n  }\n}\n\nvoid TitleMenu::Update(float dt) {\n  UpdateInput(dt);\n\n  PressToStartAnimation.Update(dt);\n  SmokeAnimation.Update(dt);\n  MoveLeftAnimation.Update(dt);\n  if (GetFlag(SF_TITLEMODE)) {\n    Show();\n  } else {\n    Hide();\n  }\n\n  if (State != Hidden && GetFlag(SF_TITLEMODE)) {\n    switch (ScrWork[SW_TITLEMODE]) {\n      case 1: {\n        PressToStartAnimation.DurationIn = PressToStartAnimDurationIn;\n        PressToStartAnimation.DurationOut = PressToStartAnimDurationOut;\n      } break;\n      case 2: {\n        PressToStartAnimation.DurationIn = PressToStartAnimFastDurationIn;\n        PressToStartAnimation.DurationOut = PressToStartAnimFastDurationOut;\n      } break;\n      case 3: {\n        MainItems->Update(dt);\n        ContinueItems->Update(dt);\n        ExtraItems->Update(dt);\n        if (MoveLeftAnimation.IsOut() && ScrWork[SW_TITLEDISPCT] > 0) {\n          MoveLeftAnimation.StartIn();\n        } else if (MoveLeftAnimation.IsIn() && ScrWork[SW_TITLEDISPCT] < 90) {\n          MoveLeftAnimation.StartOut();\n        }\n        if (MainItems->VisibilityState == Hidden) {\n          MainItems->Show();\n          CurrentlyFocusedElement = NewGame;\n          NewGame->HasFocus = true;\n        }\n      } break;\n    }\n  }\n}\n\nvoid TitleMenu::Render() {\n  if (State != Hidden && GetFlag(SF_TITLEMODE)) {\n    switch (ScrWork[SW_TITLEMODE]) {\n      case 1: {  // Press to start\n        DrawMainBackground(true);\n        DrawStartButton();\n        Renderer->DrawSprite(\n            OverlaySprite,\n            RectF(0.0f, 0.0f, Profile::DesignWidth, Profile::DesignHeight));\n        DrawSmoke(SmokeOpacityNormal);\n        Renderer->DrawQuad(\n            RectF(0.0f, 0.0f, Profile::DesignWidth, Profile::DesignHeight),\n            glm::vec4(1.0f, 1.0f, 1.0f,\n                      1.0f - ScrWork[SW_TITLEDISPCT] / 60.0f));\n      } break;\n      case 2: {  // Transition between Press to start and menus\n        DrawMainBackground(true);\n        DrawStartButton();\n        Renderer->DrawSprite(\n            OverlaySprite,\n            RectF(0.0f, 0.0f, Profile::DesignWidth, Profile::DesignHeight));\n        DrawSmoke(SmokeOpacityNormal);\n      } break;\n      case 3: {  // Main Menu Fade In\n        float backgroundBoundsY = BackgroundBoundsYNormal,\n              fenceBoundsY = FenceBoundsYNormal;\n        if (GetFlag(SF_CLR_TRUE_CC)) {\n          backgroundBoundsY = BackgroundBoundsYTrue;\n          fenceBoundsY = FenceBoundsYTrue;\n        }\n        BackgroundSprite.Bounds = RectF(\n            BackgroundBoundsX -\n                (BackgroundBoundsX * MoveLeftAnimation.Progress),\n            backgroundBoundsY, BackgroundBoundsWidth, BackgroundBoundsHeight);\n        FenceSprite.Bounds =\n            RectF(FenceBoundsX - (FenceBoundsX * MoveLeftAnimation.Progress),\n                  fenceBoundsY, FenceBoundsWidth, FenceBoundsHeight);\n        Renderer->DrawSprite(BackgroundSprite,\n                             glm::vec2(BackgroundX, BackgroundY));\n        Renderer->DrawSprite(FenceSprite, glm::vec2(FenceX, FenceY));\n        Renderer->DrawSprite(\n            CopyrightSprite,\n            glm::vec2(CopyrightX +\n                          (CopyrightXMoveOffset * MoveLeftAnimation.Progress),\n                      CopyrightY));\n        Renderer->DrawSprite(\n            OverlaySprite,\n            RectF(0.0f, 0.0f, Profile::DesignWidth, Profile::DesignHeight));\n        Renderer->DrawSprite(MenuSprite, glm::vec2(MenuX, MenuY));\n        if (!GetFlag(SF_CLR_TRUE_CC)) {\n          DrawSmoke(SmokeOpacityNormal);\n        }\n        MainItems->Render();\n        ContinueItems->Render();\n        ExtraItems->Render();\n      } break;\n      case 4: {\n      } break;\n      case 7:\n      case 8: {\n      } break;\n      case 11: {  // Initial Fade In\n        DrawMainBackground(ScrWork[SW_TITLEDISPCT] / 32.0f);\n        Renderer->DrawSprite(\n            OverlaySprite,\n            RectF(0.0f, 0.0f, Profile::DesignWidth, Profile::DesignHeight));\n        DrawSmoke(ScrWork[SW_TITLEDISPCT] / 128.0f);\n      } break;\n      case 12: {\n      } break;\n    }\n  }\n}\n\nvoid TitleMenu::DrawMainBackground(float opacity) {\n  glm::vec4 col = glm::vec4(1.0f);\n  col.a = opacity;\n  Renderer->DrawSprite(BackgroundSprite, glm::vec2(BackgroundX, BackgroundY),\n                       col);\n  Renderer->DrawSprite(FenceSprite, glm::vec2(FenceX, FenceY), col);\n  Renderer->DrawSprite(CopyrightSprite, glm::vec2(CopyrightX, CopyrightY), col);\n}\n\nvoid TitleMenu::DrawStartButton() {\n  glm::vec4 col = glm::vec4(1.0f);\n  col.a = glm::smoothstep(0.0f, 1.0f, PressToStartAnimation.Progress);\n  Renderer->DrawSprite(PressToStartSprite,\n                       glm::vec2(PressToStartX, PressToStartY), col);\n}\n\nvoid TitleMenu::DrawSmoke(float opacity) {\n  glm::vec4 col = glm::vec4(1.0f);\n  col.a = opacity;\n  SmokeSprite.Bounds = RectF(\n      SmokeBoundsWidth - (SmokeAnimationBoundsXMax * SmokeAnimation.Progress) +\n          SmokeAnimationBoundsXOffset,\n      SmokeBoundsY,\n      SmokeBoundsWidth -\n          (SmokeAnimationBoundsXMax * (1.0f - SmokeAnimation.Progress)),\n      SmokeBoundsHeight);\n  Renderer->DrawSprite(SmokeSprite, glm::vec2(SmokeX, SmokeY), col);\n  SmokeSprite.Bounds = RectF(\n      SmokeBoundsX, SmokeBoundsY,\n      SmokeBoundsWidth - (SmokeAnimationBoundsXMax * SmokeAnimation.Progress),\n      SmokeBoundsHeight);\n  Renderer->DrawSprite(\n      SmokeSprite,\n      glm::vec2(SmokeBoundsWidth - (SmokeAnimationBoundsXMax *\n                                    (1.0f - SmokeAnimation.Progress)),\n                SmokeY),\n      col);\n}\n\n}  // namespace CC\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/cc/titlemenu.h",
    "content": "#pragma once\n\n#include \"../../animation.h\"\n#include \"../../ui/menu.h\"\n#include \"../../ui/widgets/group.h\"\n#include \"../../ui/widgets/button.h\"\n#include \"../../ui/widgets/cc/titlebutton.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace CC {\n\nclass TitleMenu : public Menu {\n public:\n  TitleMenu();\n\n  void Show() override;\n  void Hide() override;\n  void UpdateInput(float dt) override;\n  void Update(float dt) override;\n  void Render() override;\n\n  Animation PressToStartAnimation;\n  Animation ItemsFadeInAnimation;\n  Animation SecondaryItemsFadeInAnimation;\n  Animation SmokeAnimation;\n  Animation MoveLeftAnimation;\n\n  void MenuButtonOnClick(Widgets::Button* target);\n  void ContinueButtonOnClick(Widgets::Button* target);\n  void ExtraButtonOnClick(Widgets::Button* target);\n\n private:\n  Widgets::Group* CurrentSubMenu = 0;\n\n  Widgets::Group* MainItems;\n  Widgets::CC::TitleButton* NewGame;\n  Widgets::CC::TitleButton* Continue;\n  Widgets::CC::TitleButton* Extra;\n  Widgets::CC::TitleButton* Config;\n  Widgets::CC::TitleButton* Help;\n\n  Widgets::Group* ContinueItems;\n  Widgets::CC::TitleButton* Load;\n  Widgets::CC::TitleButton* QuickLoad;\n  // void ShowContinueItems();\n  // void HideContinueItems();\n\n  Widgets::Group* ExtraItems;\n  Widgets::CC::TitleButton* Tips;\n  Widgets::CC::TitleButton* Library;\n  Widgets::CC::TitleButton* EndingList;\n  // void ShowExtraItems();\n  // void HideExtraItems();\n\n  void DrawMainBackground(float opacity = 1.0f);\n  void DrawStartButton();\n  void DrawSmoke(float opacity);\n};\n\n}  // namespace CC\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/cclcc/albummenu.cpp",
    "content": "#include <ranges>\n\n#include \"albummenu.h\"\n#include \"../../profile/games/cclcc/librarymenu.h\"\n#include \"../../data/savesystem.h\"\n#include \"../../vm/interface/input.h\"\n#include \"../../inputsystem.h\"\n#include \"../../profile/scriptvars.h\"\n#include \"../../profile/data/savesystem.h\"\n#include \"../../profile/game.h\"\n#include \"../../background2d.h\"\n#include \"../../audio/audiosystem.h\"\n\nusing namespace Impacto::Profile::CCLCC::LibraryMenu;\nusing namespace Impacto::Profile::ScriptVars;\nnamespace Impacto::UI::CCLCC {\n\nvoid AlbumMenu::SetThumbnailDirections() {\n  // Next\n  static constexpr uint8_t grid[][4][4] = {\n      {{0x09, 0x0B, 0x63, 0x63},\n       {0x04, 0x05, 0x06, 0x63},\n       {0x03, 0x07, 0x63, 0x63},\n       {0x01, 0x05, 0x0A, 0x63}},\n      {{0x0A, 0x09, 0x0B, 0x63},\n       {0x05, 0x04, 0x06, 0x63},\n       {0x00, 0x04, 0x08, 0x09},\n       {0x02, 0x06, 0x0B, 0x63}},\n      {{0x0B, 0x09, 0x63, 0x63},\n       {0x06, 0x05, 0x04, 0x63},\n       {0x01, 0x05, 0x0A, 0x63},\n       {0x03, 0x07, 0x63, 0x63}},\n      {{0x0B, 0x09, 0x63, 0x63},\n       {0x06, 0x05, 0x04, 0x63},\n       {0x02, 0x06, 0x0B, 0x63},\n       {0x00, 0x04, 0x08, 0x09}},\n      {{0x00, 0x01, 0x02, 0x03},\n       {0x08, 0x0A, 0x07, 0x63},\n       {0x03, 0x07, 0x63, 0x63},\n       {0x05, 0x01, 0x0A, 0x63}},\n      {{0x01, 0x00, 0x02, 0x03},\n       {0x0A, 0x08, 0x07, 0x63},\n       {0x04, 0x08, 0x00, 0x09},\n       {0x06, 0x02, 0x0B, 0x63}},\n      {{0x02, 0x03, 0x01, 0x00},\n       {0x07, 0x0A, 0x08, 0x63},\n       {0x05, 0x01, 0x0A, 0x63},\n       {0x03, 0x07, 0x63, 0x63}},\n      {{0x06, 0x05, 0x04, 0x63},\n       {0x0B, 0x09, 0x63, 0x63},\n       {0x0B, 0x06, 0x02, 0x63},\n       {0x08, 0x09, 0x04, 0x00}},\n      {{0x04, 0x05, 0x06, 0x63},\n       {0x09, 0x0B, 0x63, 0x63},\n       {0x07, 0x03, 0x63, 0x63},\n       {0x0A, 0x05, 0x01, 0x63}},\n      {{0x08, 0x0A, 0x07, 0x63},\n       {0x00, 0x01, 0x02, 0x03},\n       {0x07, 0x03, 0x63, 0x63},\n       {0x0A, 0x05, 0x01, 0x63}},\n      {{0x05, 0x01, 0x63, 0x63},\n       {0x01, 0x05, 0x63, 0x63},\n       {0x08, 0x09, 0x04, 0x00},\n       {0x0B, 0x06, 0x02, 0x63}},\n      {{0x07, 0x0A, 0x08, 0x63},\n       {0x02, 0x03, 0x01, 0x00},\n       {0x0A, 0x05, 0x01, 0x63},\n       {0x07, 0x03, 0x63, 0x63}},\n  };\n  enum GridDirection {\n    UP = 0,\n    DOWN = 1,\n    LEFT = 2,\n    RIGHT = 3,\n  };\n  const auto convertDirection = [](FocusDirection dir) {\n    switch (dir) {\n      case FDIR_UP:\n        return UP;\n      case FDIR_DOWN:\n        return DOWN;\n      case FDIR_LEFT:\n        return LEFT;\n      case FDIR_RIGHT:\n        return RIGHT;\n    }\n    ImpLog(LogLevel::Fatal, LogChannel::General,\n           \"Invalid FocusDirection value: {}\", static_cast<int>(dir));\n    exit(1);\n  };\n\n  for (auto& page : ThumbnailPages) {\n    for (auto& thumbnail : page) {\n      if (!thumbnail) continue;\n      const auto setFocusDirection = [&](FocusDirection dir) {\n        uint8_t compareId = thumbnail->GridId;\n        for (int i = 0; i < 4; ++i) {\n          const int directionIndex = convertDirection(dir);\n          for (int j = 0; j < 4; ++j) {\n            uint8_t nextGridId = grid[compareId][directionIndex][j];\n            if (nextGridId == 99 || nextGridId == compareId) break;\n            if (page.size() <= nextGridId || !page[nextGridId]) continue;\n            if (!page[nextGridId]->Variants.empty()) {\n              thumbnail->SetFocus(page[nextGridId], dir);\n              return;\n            }\n          }\n          // Move to the next compareId in this direction to try another chain\n          compareId = grid[compareId][directionIndex][0];  // chain traversal\n        }\n      };\n      setFocusDirection(FDIR_UP);\n      setFocusDirection(FDIR_DOWN);\n      setFocusDirection(FDIR_LEFT);\n      setFocusDirection(FDIR_RIGHT);\n    }\n  }\n}\n\nAlbumThumbnail::AlbumThumbnail(int id, uint8_t gridId,\n                               glm::vec2 gridDispPosition,\n                               AlbumMenu const& albumMenu)\n    : Widgets::Button(),\n      GridPos(gridDispPosition),\n      GridId(gridId),\n      Menu(albumMenu) {\n  Id = id;\n  IndexInPage = AlbumData[id].IndexInPage;\n  Page = AlbumData[id].PageNumber;\n};\n\nvoid AlbumThumbnail::UpdateInput(float dt) {\n  if (Menu.ActivePage == Page) {\n    Button::UpdateInput(dt);\n  }\n}\n\nvoid AlbumThumbnail::Update(float dt) {\n  Button::Update(dt);\n  if (Menu.AlbumPgChangeAnimation.IsPlaying() ||\n      Menu.AlbumModeChangeAnimation.IsPlaying()) {\n    return;\n  }\n  if (State == DisplayState::Showing) {\n    if (Menu.AlbumPgChangeAnimation.Progress == 0.0f ||\n        Menu.AlbumModeChangeAnimation.Progress == 0.0f) {\n      State = DisplayState::Shown;\n    }\n  } else if (State == DisplayState::Hiding) {\n    if (Menu.AlbumPgChangeAnimation.Progress == 1.0f ||\n        Menu.AlbumModeChangeAnimation.Progress == 1.0f) {\n      State = DisplayState::Hidden;\n    }\n  }\n}\n\nvoid AlbumThumbnail::Show() {\n  if (State == DisplayState::Hidden && Menu.ActivePage == Page) {\n    State = DisplayState::Showing;\n  }\n}\n\nvoid AlbumThumbnail::Hide() {\n  if (State == DisplayState::Shown) {\n    State = DisplayState::Hiding;\n    HasFocus = false;\n    Hovered = false;\n  }\n}\n\nvoid AlbumThumbnail::Render() {\n  if (State == DisplayState::Hidden) return;\n\n  // 0 = default, 1 = faded and zoomed\n  const float animProgress = [this]() {\n    const bool isClickedThumbnail =\n        Menu.CGViewer && &Menu.CGViewer->ClickedThumbnail.get() == this;\n    constexpr static float frameTime = 1 / 60.0f;\n    if (Menu.AlbumPgChangeAnimation.IsPlaying()) {\n      const float itemDuration = AlbumItemFadeDuration / 2;\n      const float changeTime =\n          Menu.AlbumPgChangeAnimation.Progress * AlbumPgChangeDuration;\n      const int revId = static_cast<int>(\n          Menu.ThumbnailPages[Menu.ActivePage].size() - GridId - 1);\n      const float staggerTime =\n          AlbumPgChangeDuration - (revId * frameTime + itemDuration);\n      return std::clamp(changeTime - staggerTime, 0.0f, itemDuration) /\n             itemDuration;\n    } else if (isClickedThumbnail &&\n               (Menu.AlbumModeChangeAnimation.IsPlaying() ||\n                Menu.AlbumModeChangeAnimation.IsIn())) {\n      float changeTime =\n          Menu.AlbumModeChangeAnimation.Progress * AlbumModeChangeDuration;\n\n      if (changeTime < AlbumItemFadeDuration)  // Delay portion\n        return 0.0f;\n      else {  // Fade portion\n        return (changeTime - AlbumItemFadeDuration) / AlbumItemFadeDuration;\n      }\n    }\n\n    return 0.0f;\n  }();\n\n  glm::vec4 tint = Tint;\n  tint.a *= (1.0f - animProgress);\n  if (tint.a <= 0.0f) return;\n  if (Enabled) {\n    for (const auto& spriteInfo : Variants) {\n      Sprite thumbnailSprite = spriteInfo.ThumbnailSprite;\n      thumbnailSprite.Bounds += glm::vec2{1.0f, 1.0f};\n      thumbnailSprite.Bounds.SetSize(thumbnailSprite.Bounds.GetSize() -\n                                     glm::vec2{2.0f, 2.0f});\n      const glm::vec2 picTopLeft =\n          GridPos - glm::vec2(thumbnailSprite.Bounds.Width / 2, 0);\n      const float scaleFactor = animProgress + 1.0f;\n\n      float angle = ScrWorkAngleToRad(spriteInfo.Angle);\n      if (angle < std::numbers::pi_v<float>)\n        angle *= (1.0f - animProgress);\n      else {\n        angle =\n            glm::mix(-angle, 2.0f * std::numbers::pi_v<float>, animProgress);\n      }\n      const auto matrix =\n          TransformationMatrix(spriteInfo.Origin, {scaleFactor, scaleFactor},\n                               spriteInfo.Origin, angle, picTopLeft);\n      Renderer->DrawSprite(thumbnailSprite, matrix, tint);\n    }\n    if (HasFocus && Menu.AlbumPgChangeAnimation.IsOut() &&\n        Menu.AlbumModeChangeAnimation.IsOut()) {\n      const glm::vec2 thumbTopLeft =\n          GridPos - glm::vec2(AlbumThumbnailThumbSprite.Bounds.Width / 2, 0);\n      glm::vec4 thumbTint = RgbIntToFloat(\n          0x10101 * (uint32_t)(0xFF * Menu.ThumbnailThumbBlink.Progress),\n          tint.a);\n      Renderer->DrawSprite(AlbumThumbnailThumbSprite, thumbTopLeft, thumbTint);\n    }\n  }\n  const auto& pinSprite = AlbumThumbnailPinSprites[IndexInPage];\n  const glm::vec2 pinOffset = animProgress * AlbumThumbnailPinRemoveOffset;\n  const glm::vec2 pinTopLeft =\n      GridPos - glm::vec2(pinSprite.Bounds.Width / 2, 0) - pinOffset;\n  Renderer->DrawSprite(pinSprite, pinTopLeft, tint);\n}\n\nAlbumMenu::AlbumMenu() : LibrarySubmenu() {\n  AlbumPgChangeAnimation.SetDuration(AlbumPgChangeDuration);\n  ThumbnailThumbBlink.SetDuration(AlbumThumbnailThumbBlinkDuration);\n  AlbumModeChangeAnimation.SetDuration(AlbumModeChangeDuration);\n  ThumbnailThumbBlink.LoopMode = AnimationLoopMode::ReverseDirection;\n}\n\nvoid AlbumMenu::Init() {\n  MainItems.Clear();\n  ThumbnailPages.clear();\n  int i = 0;\n  const auto getMainAngle = [](int itemIndex) {\n    switch (itemIndex) {\n      case 0:\n      case 6:\n      case 7:\n        return CALCrnd(910);\n\n      case 1:\n      case 2:\n      case 4:\n      case 8:\n      case 9:\n      case 10:\n        return CALCrnd(1820) - 910;\n      case 3:\n      case 5:\n\n        return 65536 - CALCrnd(910);\n      default:  // yes, the 12th item will always have no angle\n        return 0;\n    }\n  };\n  size_t index = 0;\n  for (const auto& thumbnailEntry : AlbumData) {\n    uint8_t itemCountInPage = 0;\n    if (ThumbnailPages.size() <= thumbnailEntry.PageNumber) {\n      ThumbnailPages.resize(thumbnailEntry.PageNumber + 1, {});\n      itemCountInPage = 0;\n    }\n    auto& page = ThumbnailPages[thumbnailEntry.PageNumber];\n    const glm::vec2 gridDispPosition =\n        AlbumThumbDispPos[thumbnailEntry.IndexInPage];\n    auto* thumbnailWidget = new AlbumThumbnail(i++, thumbnailEntry.IndexInPage,\n                                               gridDispPosition, *this);\n    thumbnailWidget->OnClickHandler = [this, thumbnailWidget,\n                                       index](Widgets::Button*) {\n      CGViewer.emplace(*thumbnailWidget);\n\n      const int bufId = CGViewer->ViewBufId[1] ? 2 : 1;\n      ScrWork[SW_ALBUM_LOADFILE] = Profile::SaveSystem::AlbumData[index][0][0];\n      ScrWork[SW_ALBUM_LOADBUF] = bufId;\n      SetFlag(SF_ALBUMLOAD, 1);\n      SetFlag(SF_ALBUMLOAD_COMPLETE, 0);\n      CGViewer->ActiveThumbnailIndex = static_cast<int>(index);\n      CGViewer->ActiveVariantIndex = 0;\n      CGViewer->PageSwapAnimation.SetDuration(AlbumItemFadeDuration);\n      CGViewer->TransitionDuration.SetDuration(AlbumItemFadeDuration);\n      IsFocused = false;\n      AlbumModeChangeAnimation.StartIn();\n      Audio::PlayInGroup(Audio::ACG_SE, \"sysse\", 2, false, 0);\n    };\n    const int mainAngle = getMainAngle(itemCountInPage);\n    int variantAngleOffset = 0;\n    RectF maxBounds;\n    thumbnailWidget->IsLocked = true;\n    size_t variantIndex = 0;\n    for (const auto& sprite :\n         thumbnailEntry.ThumbnailSprites | std::views::reverse) {\n      if (SaveSystem::GetEVVariationIsUnlocked(index, variantIndex++))\n        thumbnailWidget->IsLocked = false;\n      else\n        continue;\n\n      variantAngleOffset += 546 + CALCrnd(182);\n      const glm::vec2 origin = {(sprite.Bounds.Width - 10.0) / 2.0f + 2.0f,\n                                15.0f};\n      const int variantAngle = mainAngle - variantAngleOffset / 2;\n      thumbnailWidget->Variants.push_back(AlbumThumbnailSpriteInfo{\n          .ThumbnailSprite = sprite, .Origin = origin, .Angle = variantAngle});\n      CornersQuad variantBounds =\n          RectF{gridDispPosition.x - sprite.Bounds.Width / 2,\n                gridDispPosition.y, sprite.Bounds.Width, sprite.Bounds.Height};\n      if (maxBounds.Width == 0 || maxBounds.Height == 0) {\n        maxBounds.X = variantBounds.Center().x;\n        maxBounds.Y = variantBounds.Center().y;\n      }\n      variantBounds.Rotate(ScrWorkAngleToRad(variantAngle),\n                           gridDispPosition + origin);\n      maxBounds = RectF::BoundingBox(variantBounds, maxBounds);\n    }\n    thumbnailWidget->Enabled = !thumbnailWidget->IsLocked;\n    thumbnailWidget->Bounds = maxBounds;\n    if (page.size() <= thumbnailEntry.IndexInPage) {\n      page.resize(thumbnailEntry.IndexInPage + 1, nullptr);\n    }\n    page[thumbnailEntry.IndexInPage] = thumbnailWidget;\n    MainItems.Add(thumbnailWidget);\n    itemCountInPage++;\n    index++;\n  }\n  for (const auto& thum : ThumbnailPages[ActivePage]) {\n    if (!thum) continue;\n    thum->Show();\n    if (!CurrentlyFocusedElement) {\n      CurrentlyFocusedElement = thum;\n      CurrentlyFocusedElement->HasFocus = true;\n    }\n  }\n  SetThumbnailDirections();\n}\n\nvoid AlbumMenu::UpdateCGViewer(float dt) {\n  using namespace Vm::Interface;\n  const int activeSurface = CGViewer->ViewBufId[1];\n  const int bufId = ScrWork[SW_BG1SURF + activeSurface];\n  const auto* background2D = Backgrounds2D[bufId];\n  const auto heightRatio =\n      Profile::DesignHeight / background2D->BgSprite.Sheet.DesignHeight;\n  if (GetFlag(SF_ALBUMLOAD) && GetFlag(SF_ALBUMLOAD_COMPLETE)) {\n    SetFlag(SF_ALBUMLOAD, 0);\n    SetFlag(SF_ALBUMLOAD_COMPLETE, 0);\n\n    // Fit to height\n    CGViewer->DestRect[1].Height =\n        Backgrounds2D[bufId]->BgSprite.ScaledHeight() * heightRatio;\n    CGViewer->DestRect[1].Width =\n        Backgrounds2D[bufId]->BgSprite.ScaledWidth() * heightRatio;\n    CGViewer->DestRect[1].X =\n        (Profile::DesignWidth - CGViewer->DestRect[1].Width) / 2;\n    CGViewer->DestRect[1].Y =\n        (Profile::DesignHeight - CGViewer->DestRect[1].Height) / 2;\n\n    return;\n  }\n  if (CGViewer->PageSwapAnimation.State == AnimationState::Playing) return;\n  if (CGViewer->TransitionDuration.State == AnimationState::Playing) return;\n\n  if (CGViewer->TransitionDuration.IsOut() && AlbumModeChangeAnimation.IsIn()) {\n    if (CGViewer->StartHide)\n      AlbumModeChangeAnimation.StartOut();\n    else\n      CGViewer->TransitionDuration.StartIn();\n    return;\n  }\n  if (!GetFlag(SF_ALBUMLOAD) && AlbumModeChangeAnimation.IsOut()) {\n    CGViewer = std::nullopt;\n    IsFocused = true;\n    return;\n  }\n\n  if (Vm::Interface::PADinputButtonWentDown & Vm::Interface::PAD1X) {\n    CGViewer->EnableGuide = !CGViewer->EnableGuide;\n  }\n  CGViewer->CGViewerPanZoom(dt);\n\n  if (CGViewer->PageSwapAnimation.IsIn()) {\n    CGViewer->DestRect[0] = RectF{};\n    CGViewer->PageSwapAnimation.Progress = 0.0f;\n  }\n  const bool controllerADown = PADinputButtonWentDown & PAD1A;\n  const bool mouseWentUp =\n      (Vm::Interface::PADinputMouseWentDown & PAD1A) == 0 &&\n      (Vm::Interface::PADinputMouseIsDown & PAD1A) == 0 && CGViewer->WasClicked;\n\n  if (controllerADown || (mouseWentUp && CGViewer->ClickHoldTime < 0.1)) {\n    const auto& variants = CGViewer->ClickedThumbnail.get().Variants;\n    CGViewer->ActiveVariantIndex++;\n    if (CGViewer->ActiveVariantIndex < std::ssize(variants)) {\n      ScrWork[SW_ALBUM_LOADFILE] =\n          Profile::SaveSystem::AlbumData[CGViewer->ActiveThumbnailIndex]\n                                        [CGViewer->ActiveVariantIndex][0];\n      std::swap(CGViewer->ViewBufId[0], CGViewer->ViewBufId[1]);\n      std::swap(CGViewer->DestRect[0], CGViewer->DestRect[1]);\n      ScrWork[SW_ALBUM_LOADBUF] = CGViewer->ViewBufId[1] ? 2 : 1;\n      SetFlag(SF_ALBUMLOAD, 1);\n      SetFlag(SF_ALBUMLOAD_COMPLETE, 0);\n      CGViewer->PageSwapAnimation.StartIn();\n    } else {\n      CGViewer->TransitionDuration.StartOut();\n      CGViewer->StartHide = true;\n      return;\n    }\n  } else if (Vm::Interface::PADinputButtonWentDown & Vm::Interface::PAD1B ||\n             Vm::Interface::PADinputMouseWentDown & Vm::Interface::PAD1B) {\n    CGViewer->TransitionDuration.StartOut();\n    CGViewer->StartHide = true;\n    return;\n  }\n  // Click release delay handling\n  if ((Vm::Interface::PADinputMouseWentDown & PAD1A) && !CGViewer->WasClicked) {\n    CGViewer->WasClicked = true;\n  }\n  if (Vm::Interface::PADinputMouseIsDown & Vm::Interface::PAD1A) {\n    CGViewer->ClickHoldTime += dt;\n  }\n  if (mouseWentUp) {\n    CGViewer->ClickHoldTime = 0.0f;\n    CGViewer->WasClicked = false;\n  }\n}\n\nvoid AlbumCGViewer::CGViewerPanZoom(float dt) {\n  using namespace Vm::Interface;\n  const int activeSurface = ViewBufId[1];\n  const int bufId = ScrWork[SW_BG1SURF + activeSurface];\n\n  const glm::vec2 screenCenter = {Profile::DesignWidth / 2.0f,\n                                  Profile::DesignHeight / 2.0f};\n\n  float& x = DestRect[1].X;\n  float& y = DestRect[1].Y;\n  float& w = DestRect[1].Width;\n  float& h = DestRect[1].Height;\n\n  glm::vec2 scaleOrigin = screenCenter;\n\n  const bool touchingLeft = w > Profile::DesignWidth && x >= 0;\n  const bool touchingRight =\n      w > Profile::DesignWidth && x + w <= Profile::DesignWidth;\n  const bool touchingTop = h > Profile::DesignHeight && y >= 0;\n  const bool touchingBottom =\n      h > Profile::DesignHeight && y + h <= Profile::DesignHeight;\n\n  const bool scrollUp = Input::MouseWheelDeltaY > 0;\n  const bool scrollDown = Input::MouseWheelDeltaY < 0;\n\n  const bool zoomingOut = (PADinputButtonIsDown & PADcustom[36]) || scrollUp;\n  const bool zoomingIn = (PADinputButtonIsDown & PADcustom[37]) || scrollDown;\n\n  if (zoomingOut) {\n    if (touchingLeft && touchingTop && touchingRight && touchingBottom)\n      scaleOrigin = screenCenter;\n    else if (((touchingLeft && touchingTop) || (touchingRight && touchingTop) ||\n              (touchingLeft && touchingBottom) ||\n              (touchingRight && touchingBottom)))\n      scaleOrigin = {x, y};\n    if (touchingLeft)\n      scaleOrigin = glm::vec2(x, scaleOrigin.y);\n    else if (touchingRight)\n      scaleOrigin = glm::vec2(x + w, scaleOrigin.y);\n    else if (touchingTop)\n      scaleOrigin = glm::vec2(scaleOrigin.x, y);\n    else if (touchingBottom)\n      scaleOrigin = glm::vec2(scaleOrigin.x, y + DestRect[1].Height);\n  }\n\n  const bool isWidthSnapped = std::abs(Profile::DesignWidth - w) < 5;\n  const bool waitSnappedWidth =\n      isWidthSnapped && (PADinputButtonIsDown & PADcustom[36] ||\n                         PADinputButtonIsDown & PADcustom[37]);\n  if (waitSnappedWidth) {  // Width match snap delay\n    SnapWidthHoldTime += dt;\n  } else if (WasZoomHeld) {\n    SnapWidthHoldTime = 0.0f;\n  }\n\n  const float initScaleFactorOut =\n      (scrollUp || w < Profile::DesignWidth) ? 0.97f : 0.99f;\n  const float initScaleFactorIn =\n      (scrollDown || w < Profile::DesignWidth) ? 1.03f : 1.01f;\n  // Scaling controls\n  if (zoomingOut) {  // Zoom out\n    if (h > Profile::DesignHeight &&\n        !(WasZoomHeld && waitSnappedWidth && SnapWidthHoldTime < 0.2f)) {\n      float scaleFactor = initScaleFactorOut;\n      const float nextHeight = h * scaleFactor;\n      const float nextWidth = w * scaleFactor;\n      if (nextHeight < Profile::DesignHeight) {  // Max height\n        scaleFactor = Profile::DesignHeight / h;\n      }\n      if (w > Profile::DesignWidth && nextWidth < Profile::DesignWidth) {\n        scaleFactor = Profile::DesignWidth / w;  // Snap to width\n      }\n      DestRect[1].Scale({scaleFactor, scaleFactor}, scaleOrigin);\n    }\n  } else if (zoomingIn) {  // Zoom in\n    const float maxScale = 2.0f;\n    const float currentScale = w / Backgrounds2D[bufId]->BgSprite.Bounds.Width;\n    if (currentScale < maxScale &&\n        !(WasZoomHeld && waitSnappedWidth && SnapWidthHoldTime < 0.2f)) {\n      float scaleFactor = initScaleFactorIn;\n      float nextScale = currentScale * scaleFactor;\n      const float nextWidth = w * scaleFactor;\n      if (nextScale > maxScale) {  // Max width scaling\n        scaleFactor = maxScale / currentScale;\n      }\n      if (w < Profile::DesignWidth && nextWidth > Profile::DesignWidth) {\n        scaleFactor = Profile::DesignWidth / w;  // Snap to width\n      }\n      DestRect[1].Scale({scaleFactor, scaleFactor}, scaleOrigin);\n    }\n  }\n  WasZoomHeld = zoomingOut || zoomingIn;\n  // Pan controls\n  if (PADinputButtonIsDown & PADcustom[0]) y += 30.0f;  // UP\n  if (PADinputButtonIsDown & PADcustom[1]) y -= 30.0f;  // DOWN\n  if (PADinputButtonIsDown & PADcustom[2]) x += 30.0f;  // LEFT\n  if (PADinputButtonIsDown & PADcustom[3]) x -= 30.0f;  // RIGHT\n\n  if (ClickHoldTime > 0.1 && PADinputMouseIsDown & PAD1A) {\n    const glm::vec2 mouseDelta = Input::CurMousePos - Input::PrevMousePos;\n    x += mouseDelta.x;\n    y += mouseDelta.y;\n    RequestCursor(CursorType::Pointer);\n  }\n\n  // --- Clamp or center after zoom and pan ---\n\n  // Horizontal adjustment\n  if (w <= Profile::DesignWidth) {\n    // Center if smaller\n    x = (Profile::DesignWidth - w) * 0.5f;\n  } else {\n    // Clamp if larger\n    if (x > 0) x = 0;\n    if (x + w < Profile::DesignWidth) x = Profile::DesignWidth - w;\n  }\n\n  // Vertical adjustment\n  if (h <= Profile::DesignHeight) {\n    // Center if smaller\n    y = (Profile::DesignHeight - h) * 0.5f;\n  } else {\n    // Clamp if larger\n    if (y > 0) y = 0;\n    if (y + h < Profile::DesignHeight) y = Profile::DesignHeight - h;\n  }\n}\n\nvoid AlbumMenu::UpdateThumbnail(float dt) {\n  using namespace Vm::Interface;\n  LibrarySubmenu::Update(dt);\n  ThumbnailThumbBlink.Update(dt);\n  if (!IsFocused) return;\n  const auto updatePages = [this](uint8_t prevPg, uint8_t nextPg) {\n    if (prevPg != nextPg) {\n      if (CurrentlyFocusedElement) {\n        CurrentlyFocusedElement = nullptr;\n      }\n      const auto& prevPageThumbnails = ThumbnailPages[prevPg];\n      for (const auto& thum : prevPageThumbnails) {\n        if (!thum) continue;\n        thum->Hide();\n      }\n      AlbumPgChangeAnimation.StartIn();\n    }\n  };\n  const uint8_t prevPg = ActivePage;\n  if (AlbumPgChangeAnimation.IsOut()) {\n    PrevPage = prevPg;\n    if (PADinputButtonWentDown & PADcustom[7] || Input::MouseWheelDeltaY > 0) {\n      ActivePage = (ActivePage == 0)\n                       ? static_cast<uint8_t>(ThumbnailPages.size()) - 1\n                       : ActivePage - 1;\n      updatePages(PrevPage, ActivePage);\n    } else if (PADinputButtonWentDown & PADcustom[8] ||\n               Input::MouseWheelDeltaY < 0) {\n      ActivePage = (ActivePage + 1) % ThumbnailPages.size();\n      updatePages(PrevPage, ActivePage);\n    }\n  } else if (AlbumPgChangeAnimation.IsIn()) {\n    const auto& curPage = ThumbnailPages[ActivePage];\n    for (const auto& thum : curPage) {\n      if (!thum) continue;\n      thum->Show();\n      if (!CurrentlyFocusedElement) {\n        CurrentlyFocusedElement = thum;\n        CurrentlyFocusedElement->HasFocus = true;\n      }\n    }\n    AlbumPgChangeAnimation.StartOut();\n  }\n  if (!CurrentlyFocusedElement) {\n    auto thumItr = std::find_if(ThumbnailPages[ActivePage].begin(),\n                                ThumbnailPages[ActivePage].end(),\n                                [](const auto& btn) { return btn; });\n    if (thumItr != ThumbnailPages[ActivePage].end()) {\n      CurrentlyFocusedElement = *thumItr;\n      CurrentlyFocusedElement->HasFocus = true;\n    }\n  }\n}\n\nvoid AlbumMenu::Update(float dt) {\n  MainItems.Tint.a = FadeAnimation.Progress;\n  const auto* prevBtn = CurrentlyFocusedElement;\n  AlbumPgChangeAnimation.Update(dt);\n  AlbumModeChangeAnimation.Update(dt);\n  if (!CGViewer) {\n    UpdateThumbnail(dt);\n    if (CurrentlyFocusedElement != prevBtn) {\n      if (State == Shown && IsFocused && prevBtn != nullptr) {\n        Audio::PlayInGroup(Audio::ACG_SE, \"sysse\", 1, false, 0);\n      }\n      ThumbnailThumbBlink.StartIn();\n    }\n  } else {\n    CGViewer->PageSwapAnimation.Update(dt);\n    CGViewer->TransitionDuration.Update(dt);\n    UpdateCGViewer(dt);\n  }\n}\n\nvoid AlbumMenu::RenderCGViewer() {\n  if (!CGViewer) return;\n  const glm::vec4 tint(1.0f, 1.0f, 1.0f, CGViewer->TransitionDuration.Progress);\n  Renderer->DrawSprite(\n      Sprite{}, RectF{0, 0, Profile::DesignWidth, Profile::DesignHeight}, tint);\n  const int fadingSurface = CGViewer->ViewBufId[0];\n  const int activeSurface = CGViewer->ViewBufId[1];\n  if (CGViewer->PageSwapAnimation.State == AnimationState::Playing) {\n    const float swapTint = CGViewer->PageSwapAnimation.Progress;\n    Renderer->DrawSprite(\n        Backgrounds2D[ScrWork[SW_BG1SURF + fadingSurface]]->BgSprite,\n        CGViewer->DestRect[0], tint * (1 - swapTint));\n    Renderer->DrawSprite(\n        Backgrounds2D[ScrWork[SW_BG1SURF + activeSurface]]->BgSprite,\n        CGViewer->DestRect[1], tint * swapTint);\n  } else {\n    Renderer->DrawSprite(\n        Backgrounds2D[ScrWork[SW_BG1SURF + activeSurface]]->BgSprite,\n        CGViewer->DestRect[1], tint);\n  }\n}\n\nvoid AlbumMenu::Render() {\n  LibrarySubmenu::Render();\n  const glm::vec4 pgBtnTint =\n      glm::vec4{1.0f, 1.0f, 1.0f, FadeAnimation.Progress};\n  const uint8_t dispPrevPg = PrevPage + 1;\n  const uint8_t dispActivePg = ActivePage + 1;\n  Renderer->DrawSprite(AlbumPageNumberSprites[dispActivePg / 10],\n                       AlbumPageNumberPositions[0], pgBtnTint);\n  if (dispActivePg != dispPrevPg) {\n    const float totalProgress =\n        static_cast<int>(AlbumPgChangeAnimation.Direction) +\n                static_cast<int>(AnimationDirection::In)\n            ? AlbumPgChangeAnimation.Progress / 2.0f\n            : (1.0f + (1.0f - AlbumPgChangeAnimation.Progress)) / 2.0f;\n    const float alpha = pgBtnTint.a * totalProgress;\n    const float prevAlpha = 1.0f - alpha;\n    Renderer->DrawSprite(AlbumPageNumberSprites[dispPrevPg % 10],\n                         AlbumPageNumberPositions[1],\n                         pgBtnTint * glm::vec4{1.0f, 1.0f, 1.0f, prevAlpha});\n    Renderer->DrawSprite(AlbumPageNumberSprites[dispActivePg % 10],\n                         AlbumPageNumberPositions[1],\n                         pgBtnTint * glm::vec4{1.0f, 1.0f, 1.0f, alpha});\n\n  } else {\n    Renderer->DrawSprite(AlbumPageNumberSprites[dispActivePg % 10],\n                         AlbumPageNumberPositions[1], pgBtnTint);\n  }\n  Renderer->DrawSprite(AlbumCameraPageIconSprite, AlbumCameraPageIconPosition,\n                       pgBtnTint);\n}\n}  // namespace Impacto::UI::CCLCC"
  },
  {
    "path": "src/games/cclcc/albummenu.h",
    "content": "#pragma once\n\n#include \"librarysubmenus.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace CCLCC {\nclass AlbumMenu;\n\nstruct AlbumThumbnailSpriteInfo {\n  std::reference_wrapper<const Sprite> ThumbnailSprite;\n  glm::vec2 Origin;\n  int Angle;\n};\n\nstruct AlbumThumbnail : public Widgets::Button {\n  enum class DisplayState : uint8_t {\n    Hidden,\n    Hiding,\n    Showing,\n    Shown,\n  };\n\n  AlbumThumbnail(int id, uint8_t gridId, glm::vec2 gridPos,\n                 AlbumMenu const& albumMenu);\n  void Update(float dt) override;\n  void UpdateInput(float dt) override;\n  void Render() override;\n  void Show() override;\n  void Hide() override;\n\n  DisplayState State = DisplayState::Hidden;\n  glm::vec2 GridPos;\n  uint8_t Page;\n  uint8_t IndexInPage;\n  uint8_t GridId;\n  AlbumMenu const& Menu;\n  std::vector<AlbumThumbnailSpriteInfo> Variants;\n};\n\nstruct AlbumCGViewer {\n  bool EnableGuide = true;\n  RectF DestRect[2];\n  int ViewBufId[2]{0, 1};\n  int ActiveThumbnailIndex{};\n  int ActiveVariantIndex{};\n  std::reference_wrapper<AlbumThumbnail> ClickedThumbnail;\n  Animation PageSwapAnimation;\n  Animation TransitionDuration;\n  bool WasClicked = false;\n  float ClickHoldTime = 0.0f;\n  float SnapWidthHoldTime = 0.0f;\n  bool WasZoomHeld = false;\n  bool StartHide = false;\n  AlbumCGViewer(AlbumThumbnail& thumbnail) : ClickedThumbnail(thumbnail) {}\n  void CGViewerPanZoom(float dt);\n};\n\nclass AlbumMenu : public LibrarySubmenu {\n public:\n  AlbumMenu();\n  void Init() override;\n  void Update(float dt) override;\n  void Render() override;\n  void RenderCGViewer();\n\n  std::vector<std::vector<AlbumThumbnail*>> ThumbnailPages;\n  uint8_t PrevPage = 0;\n  uint8_t ActivePage = 0;\n  Animation AlbumPgChangeAnimation;\n  Animation AlbumModeChangeAnimation;\n  Animation ThumbnailThumbBlink;\n\n  std::optional<AlbumCGViewer> CGViewer;\n\n private:\n  void SetThumbnailDirections();\n  void UpdateThumbnail(float dt);\n  void UpdateCGViewer(float dt);\n};\n}  // namespace CCLCC\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/cclcc/clearlistmenu.cpp",
    "content": "#include \"clearlistmenu.h\"\n\n#include \"../../profile/games/cclcc/clearlistmenu.h\"\n#include \"../../profile/ui/backlogmenu.h\"\n#include \"../../profile/dialogue.h\"\n#include \"../../renderer/renderer.h\"\n#include \"../../mem.h\"\n#include \"../../profile/scriptvars.h\"\n#include \"../../vm/interface/input.h\"\n#include \"../../profile/game.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace CCLCC {\n\nusing namespace Impacto::Profile::CCLCC::ClearListMenu;\nusing namespace Impacto::Profile::ScriptVars;\n\nusing namespace Impacto::Vm::Interface;\n\nusing namespace Impacto::UI::Widgets;\n\nClearListMenu::ClearListMenu() {\n  FadeAnimation.Direction = AnimationDirection::In;\n  FadeAnimation.LoopMode = AnimationLoopMode::Stop;\n  FadeAnimation.DurationIn = FadeInDuration;\n  FadeAnimation.DurationOut = FadeOutDuration;\n}\n\nvoid ClearListMenu::Show() {\n  if (State != Showing) {\n    State = Showing;\n\n    if (UI::FocusedMenu != 0) {\n      LastFocusedMenu = UI::FocusedMenu;\n      LastFocusedMenu->IsFocused = false;\n    }\n    UI::FocusedMenu = this;\n\n    FadeAnimation.StartIn();\n  }\n}\n\nvoid ClearListMenu::Hide() {\n  if (State != Hiding) {\n    State = Hiding;\n    FadeAnimation.StartOut();\n    if (LastFocusedMenu != 0) {\n      UI::FocusedMenu = LastFocusedMenu;\n    } else {\n      UI::FocusedMenu = 0;\n    }\n  }\n}\n\nvoid ClearListMenu::Update(float dt) {\n  if (ScrWork[SW_SYSSUBMENUCT] < 32 && State == Shown) {\n    Hide();\n  } else if (ScrWork[SW_SYSSUBMENUCT] > 0 && State == Hidden &&\n             (ScrWork[SW_SYSSUBMENUNO] == 7)) {\n    Show();\n  }\n\n  if (State != Hidden) {\n    FadeAnimation.Update(dt);\n  }\n\n  if (State == Shown && ScrWork[SW_SYSSUBMENUNO] == 7) {\n    UpdateInput(dt);\n  }\n\n  if (State == Showing && FadeAnimation.Progress == 1.0f &&\n      ScrWork[SW_SYSSUBMENUCT] == 32) {\n    State = Shown;\n    IsFocused = true;\n  } else if (State == Hiding && FadeAnimation.Progress == 0.0f &&\n             ScrWork[SW_SYSSUBMENUCT] == 0) {\n    State = Hidden;\n    IsFocused = false;\n    if (UI::FocusedMenu) UI::FocusedMenu->IsFocused = true;\n  }\n}\n\nvoid ClearListMenu::Render() {\n  if (State == Hidden) return;\n\n  glm::vec4 transition(1.0f, 1.0f, 1.0f, FadeAnimation.Progress);\n  glm::vec4 maskTint = glm::vec4(1.0f);\n  maskTint.a = 0.85f;\n  Renderer->DrawSprite(ClearListBookLayerSprite, glm::vec2(0.0f, MenuOffsetY),\n                       transition);\n  DrawEndingSprites(transition);\n  Renderer->DrawSprite(\n      ClearListMaskSprite,\n      RectF(0.0f, 0.0f, Profile::DesignWidth, Profile::DesignHeight), maskTint);\n  Renderer->DrawSprite(ClearListGuideSprite, ClearListGuidePosition,\n                       transition);\n}\n\nint mappedAlphas[] = {\n    4,  // 801\n    1,  // 802\n    6,  // 803\n    2,  // 804\n    5,  // 805\n    3,  // 806\n    7,  // 807\n    8,  // 808\n    0,  // 809\n    9   // 810\n};\n\nvoid ClearListMenu::DrawEndingSprites(const glm::vec4& transition) {\n  int alphas[10]{};\n  int tmp = ScrWork[SW_CLRALPHA] << 3;\n  for (int i = 0; i < Endings; i++) {\n    alphas[i] = std::clamp(tmp, 0, 256) * ScrWork[SW_SYSSUBMENUALPHA] >> 8;\n    tmp -= 32;\n  }\n\n  for (int i = 0; i < Endings; i++) {\n    if (GetFlag(SF_CLR_END1 + i)) {\n      Renderer->DrawSprite(\n          EndingSprites[i],\n          glm::vec2(\n              EndingSprites[i].Bounds.X,\n              EndingSprites[i].Bounds.Y - EndingSpriteOffsetY + MenuOffsetY),\n          glm::vec4{\n              glm::vec3{transition},\n              transition.a *\n                  (State != Hiding ? alphas[mappedAlphas[i]] / 256.0f : 1.0f)});\n    }\n  }\n}\n\n}  // namespace CCLCC\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/cclcc/clearlistmenu.h",
    "content": "#pragma once\n\n#include \"../../ui/menu.h\"\n#include \"../../ui/widgets/label.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace CCLCC {\n\nclass ClearListMenu : public Menu {\n public:\n  ClearListMenu();\n\n  void Show() override;\n  void Hide() override;\n  void Update(float dt) override;\n  void Render() override;\n\n  void DrawEndingSprites(const glm::vec4& transition);\n\n  Animation FadeAnimation;\n};\n\n}  // namespace CCLCC\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/cclcc/delusiontrigger.cpp",
    "content": "#include \"delusiontrigger.h\"\n#include \"../../profile/games/cclcc/delusiontrigger.h\"\n#include \"../../vm/interface/input.h\"\n#include \"../../profile/scriptvars.h\"\n#include \"../../src/video/videosystem.h\"\n#include \"../../profile/configsystem.h\"\n#include \"../../background2d.h\"\n#include \"../../text/dialoguepage.h\"\n#include \"../../audio/audiosystem.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace CCLCC {\nusing namespace Impacto::Profile::CCLCC::DelusionTrigger;\nusing namespace Impacto::Profile::ScriptVars;\nusing namespace Impacto::Vm::Interface;\n\nDelusionTrigger::DelusionTrigger() : DelusionState(ScrWork[SW_DELUSION_STATE]) {\n  PositiveDelusionSprite.Bounds.X = 1024;\n  PositiveDelusionSprite.Bounds.Y = 0;\n  PositiveDelusionSprite.Bounds.Width = 1024;\n  PositiveDelusionSprite.Bounds.Height = 1024;\n\n  NegativeDelusionSprite.Bounds.X = 0;\n  NegativeDelusionSprite.Bounds.Y = 0;\n  NegativeDelusionSprite.Bounds.Width = 1024;\n  NegativeDelusionSprite.Bounds.Height = 1024;\n}\n\nbool DelusionTrigger::Show(int bgOverlayBgBufferId, int circlesBgBufferId,\n                           int availableDelusions) {\n  if (Profile::ConfigSystem::TriggerStopSkip) SkipModeEnabled = false;\n  if (GetFlag(SF_MOVIEPLAY) && GetFlag(SF_MOVIE_DRAWWAIT)) {\n    return false;\n  }\n\n  ScrWork[SW_DELUSION_OVERLAY_BUF] = bgOverlayBgBufferId;\n  ScrWork[SW_DELUSION_CIRCLE_BUF] = circlesBgBufferId;\n\n  DelusionState = DS_Neutral;\n  ScrWork[SW_DELUSION_BG_COUNTER] = 0;\n  LastDelusionState = 0xff;\n  ScrWork[SW_DELUSION_SPIN_COUNTER] = 0x40;\n  ScrWork[SW_DELUSION_LIMIT] = availableDelusions;\n  SetFlag(SF_DELUSIONACTIVE, 1);\n  ScrWork[6344] = 48;\n  SetFlag(SF_DELUSIONSELECTED, 0);\n  ScrWork[6418] = 960;\n  if (!Video::Players[0]->IsPlaying) {\n    ScrWork[SW_DELUSION_BG_COUNTER] = 32;\n  }\n  return true;\n}\n\nvoid DelusionTrigger::Hide() {\n  SetFlag(SF_DELUSIONACTIVE, 0);\n  ScrWork[6344] = 0;\n}\n\nbool DelusionTrigger::CheckTransitionAnimationComplete() {\n  return LastDelusionState == DelusionState;\n}\n\nbool DelusionTrigger::CheckStartTransitionComplete() {\n  return LastDelusionState != 0xff;\n}\n\nvoid DelusionTrigger::Update(float dt) {\n  auto onRightTrigger = [this]() {\n    if (DelusionState == DS_Neutral) {\n      if (ScrWork[SW_DELUSION_LIMIT] == Delusion_Both ||\n          ScrWork[SW_DELUSION_LIMIT] == Delusion_NegOnly) {\n        Audio::PlayInGroup(Audio::ACG_SE, \"sysse\", 11, false, 0.0f);\n        ScrWork[SW_DELUSION_SPIN_COUNTER] = 64;\n        DelusionState = DS_Negative;\n      }\n    } else if (DelusionState == DS_Positive) {\n      Audio::PlayInGroup(Audio::ACG_SE, \"sysse\", 12, false, 0.0f);\n      ScrWork[SW_DELUSION_SPIN_COUNTER] = 64;\n      DelusionState = DS_Neutral;\n    }\n  };\n\n  auto onLeftTrigger = [this]() {\n    if (DelusionState == DS_Neutral) {\n      if (ScrWork[SW_DELUSION_LIMIT] == Delusion_Both ||\n          ScrWork[SW_DELUSION_LIMIT] == Delusion_PosOnly) {\n        Audio::PlayInGroup(Audio::ACG_SE, \"sysse\", 10, false, 0.0f);\n        ScrWork[SW_DELUSION_SPIN_COUNTER] = 64;\n        DelusionState = DS_Positive;\n      }\n    } else if (DelusionState == DS_Negative) {\n      Audio::PlayInGroup(Audio::ACG_SE, \"sysse\", 12, false, 0.0f);\n      ScrWork[SW_DELUSION_SPIN_COUNTER] = 64;\n      DelusionState = DS_Neutral;\n    }\n  };\n\n  if (!GetFlag(SF_DELUSIONACTIVE)) {\n    if ((GetFlag(SF_MOVIEPLAY) && GetFlag(SF_MOVIE_DRAWWAIT)) ||\n        ScrWork[SW_DELUSION_BG_COUNTER] == 0) {\n      if (BgOverlaySprite.Sheet.Texture) BgOverlaySprite = Sprite{};\n      if (PositiveDelusionSprite.Sheet.Texture)\n        PositiveDelusionSprite.Sheet = SpriteSheet{};\n      if (NegativeDelusionSprite.Sheet.Texture)\n        NegativeDelusionSprite.Sheet = SpriteSheet{};\n      return;\n    }\n    if (GetFlag(SF_MESALLSKIP)) {\n      ScrWork[SW_DELUSION_BG_COUNTER] = 0;\n    }\n    if (ScrWork[SW_DELUSION_BG_COUNTER] > 0) ScrWork[SW_DELUSION_BG_COUNTER]--;\n    return;\n  } else if (ScrWork[SW_DELUSION_BG_COUNTER]) {\n    MtrgAlphaCt = (MtrgAlphaCt + 1) & 0x1f;\n    MtrgAng = (MtrgAng + 100) & 0xffff;\n\n    if (BgOverlaySprite.Sheet.Texture == 0) {\n      int bgOverlayBGIdx =\n          ScrWork[SW_BG1SURF + GetBufferId(ScrWork[SW_DELUSION_OVERLAY_BUF])];\n      BgOverlaySprite = Backgrounds2D[bgOverlayBGIdx]->BgSprite;\n    }\n\n    int delusionCirclesBGIdx =\n        ScrWork[SW_BG1SURF + GetBufferId(ScrWork[SW_DELUSION_CIRCLE_BUF])];\n    if (PositiveDelusionSprite.Sheet.Texture == 0) {\n      PositiveDelusionSprite.Sheet =\n          Backgrounds2D[delusionCirclesBGIdx]->BgSprite.Sheet;\n    }\n    if (NegativeDelusionSprite.Sheet.Texture == 0) {\n      NegativeDelusionSprite.Sheet =\n          Backgrounds2D[delusionCirclesBGIdx]->BgSprite.Sheet;\n    }\n  }\n\n  if (ScrWork[SW_DELUSION_BG_COUNTER] < 32) {\n    ScrWork[SW_DELUSION_BG_COUNTER]++;\n    return;\n  }\n  if (GetFlag(SF_DELUSIONSELECTED) || ScrWork[SW_SYSSUBMENUCT] != 0 ||\n      ScrWork[SW_SYSMENUCT] != 0) {\n    return;\n  }\n\n  if (PADcustom[14] & PADinputButtonWentDown || (GetFlag(SF_MESALLSKIP)) != 0) {\n    Video::Players[0]->Stop();\n  }\n  if (LastDelusionState == 0xff && GetFlag(SF_MOVIEPLAY) &&\n      ScrWork[SW_MOVIETOTALFRAME] - 13 < ScrWork[SW_MOVIEFRAME] &&\n      LastDelusionState != DelusionState &&\n      ScrWork[SW_DELUSION_SPIN_COUNTER] == 0) {\n    if (ScrWork[SW_DELUSION_SPIN_COUNTER] != 0) {\n      ScrWork[SW_DELUSION_SPIN_COUNTER] -= 2;\n    }\n    if (ScrWork[SW_DELUSION_SPIN_COUNTER] == 0) {\n      LastDelusionState = DelusionState;\n    }\n  }\n  if (Video::Players[0]->IsPlaying) return;\n  if ((ScrWork[SW_MOVIE_PLAYNO] == 0xffff) ||\n      (ScrWork[SW_MOVIE_PLAYNO] == 0xffff)) {\n    SetFlag(SF_MOVIELOADPLAYFL, 0);\n    ScrWork[SW_MOVIE_PLAYNO] = 0xffff;\n    ScrWork[SW_MOVIE_LOADNO] = 0xffff;\n  }\n  auto leftTrigger = GetControlState(CT_DelusionTriggerL);\n  auto rightTrigger = GetControlState(CT_DelusionTriggerR);\n\n  if (LastDelusionState == DelusionState) {\n    if (!leftTrigger && !rightTrigger) return;\n    if (!leftTrigger && rightTrigger) {\n      onRightTrigger();\n    }\n    if (leftTrigger && !rightTrigger) {\n      onLeftTrigger();\n    }\n  } else {\n    if (ScrWork[SW_DELUSION_SPIN_COUNTER] != 0) {\n      ScrWork[SW_DELUSION_SPIN_COUNTER] -= 2;\n    }\n    if (ScrWork[SW_DELUSION_SPIN_COUNTER] == 0) {\n      LastDelusionState = DelusionState;\n    }\n  }\n}\n\nvoid DelusionTrigger::RenderStartTransition(float spinAngle, int spinAlpha) {\n  const glm::vec2 scale((ScrWork[SW_DELUSION_SPIN_COUNTER] + 64) / 64.0f);\n  const glm::vec4 tint = {\n      1.0f, 1.0f, 1.0f,\n      ((64 - ScrWork[SW_DELUSION_SPIN_COUNTER]) * spinAlpha >> 6) / 256.0f};\n\n  if ((ScrWork[SW_DELUSION_LIMIT] == Delusion_Both) ||\n      (ScrWork[SW_DELUSION_LIMIT] == Delusion_PosOnly)) {\n    const glm::vec2 offset = {-800, -109};\n    const glm::vec2 origin = {600, 557};\n    const glm::mat4 transformation =\n        TransformationMatrix(origin, scale, origin, spinAngle, offset);\n    Renderer->DrawSprite(PositiveDelusionSprite, transformation, tint);\n  }\n\n  if ((ScrWork[SW_DELUSION_LIMIT] == Delusion_Both) ||\n      (ScrWork[SW_DELUSION_LIMIT] == Delusion_NegOnly)) {\n    const glm::vec2 offset = {1696, -109};\n    const glm::vec2 origin = {424, 557};\n    const glm::mat4 transformation =\n        TransformationMatrix(origin, scale, origin, spinAngle, offset);\n    Renderer->DrawSprite(NegativeDelusionSprite, transformation, tint);\n  }\n}\n\nvoid DelusionTrigger::RenderStable(float spinAngle, int spinAlpha) {\n  const glm::vec4 tint = {1.0f, 1.0f, 1.0f, spinAlpha / 256.0f};\n\n  if (DelusionState == DS_Neutral) {\n    if ((ScrWork[SW_DELUSION_LIMIT] == Delusion_Both) ||\n        (ScrWork[SW_DELUSION_LIMIT] == Delusion_PosOnly)) {\n      const CornersQuad dest = PositiveDelusionSprite.ScaledBounds()\n                                   .Rotate(spinAngle, {600, 557})\n                                   .Translate({-800, -109});\n      Renderer->DrawSprite(PositiveDelusionSprite, dest, tint);\n    }\n\n    if ((ScrWork[SW_DELUSION_LIMIT] == Delusion_Both) ||\n        (ScrWork[SW_DELUSION_LIMIT] == Delusion_NegOnly)) {\n      const CornersQuad dest = PositiveDelusionSprite.ScaledBounds()\n                                   .Rotate(spinAngle, {424, 557})\n                                   .Translate({1696, -109});\n      Renderer->DrawSprite(NegativeDelusionSprite, dest, tint);\n    }\n\n  } else if (DelusionState == DS_Positive) {\n    const glm::mat4 transformation = TransformationMatrix(\n        {600, 557}, {1.8f, 1.8f}, {600, 557}, spinAngle, {448, -109});\n    Renderer->DrawSprite(PositiveDelusionSprite, transformation, tint);\n\n  } else if (DelusionState == DS_Negative) {\n    const glm::mat4 transformation = TransformationMatrix(\n        {424, 557}, {1.8f, 1.8f}, {424, 557}, spinAngle, {448, -109});\n    Renderer->DrawSprite(NegativeDelusionSprite, transformation, tint);\n  }\n}\n\nvoid DelusionTrigger::RenderNeutralToPositiveTransition(float spinAngle,\n                                                        int spinAlpha) {\n  const glm::vec4 tint = {1.0f, 1.0f, 1.0f, spinAlpha / 256.0f};\n\n  const glm::vec2 offset = {\n      448 - ((ScrWork[SW_DELUSION_SPIN_COUNTER] * 1248) >> 6), -109};\n  const glm::vec2 scale(1.8f -\n                        ScrWork[SW_DELUSION_SPIN_COUNTER] * 0.8f / 64.0f);\n  const glm::mat4 transformation =\n      TransformationMatrix({600, 557}, scale, {600, 557}, spinAngle, offset);\n  Renderer->DrawSprite(PositiveDelusionSprite, transformation, tint);\n\n  if (ScrWork[SW_DELUSION_LIMIT] == Delusion_Both) {\n    const glm::vec2 center = {\n        2944 - ((ScrWork[SW_DELUSION_SPIN_COUNTER] * 1248) >> 6), -109};\n    const CornersQuad dest = NegativeDelusionSprite.ScaledBounds()\n                                 .Rotate(spinAngle, {424, 557})\n                                 .Translate(center);\n    Renderer->DrawSprite(NegativeDelusionSprite, dest, tint);\n  }\n}\n\nvoid DelusionTrigger::RenderNeutralToNegativeTransition(float spinAngle,\n                                                        int spinAlpha) {\n  const glm::vec4 tint = {1.0f, 1.0f, 1.0f, spinAlpha / 256.0f};\n\n  const glm::vec2 offset = {\n      ((ScrWork[SW_DELUSION_SPIN_COUNTER] * 1248) >> 6) + 448, -109};\n  const glm::vec2 scale(1.8f -\n                        ScrWork[SW_DELUSION_SPIN_COUNTER] * 0.8f / 64.0f);\n  const glm::mat4 transformation =\n      TransformationMatrix({424, 557}, scale, {424, 557}, spinAngle, offset);\n  Renderer->DrawSprite(NegativeDelusionSprite, transformation, tint);\n\n  if (ScrWork[SW_DELUSION_LIMIT] == Delusion_Both) {\n    const glm::vec2 offsetPositive = {\n        ((ScrWork[SW_DELUSION_SPIN_COUNTER] * 1248) >> 6) - 2048, -109};\n    const CornersQuad dest = PositiveDelusionSprite.ScaledBounds()\n                                 .Rotate(spinAngle, {600, 557})\n                                 .Translate(offsetPositive);\n    Renderer->DrawSprite(PositiveDelusionSprite, dest, tint);\n  }\n}\n\nvoid DelusionTrigger::RenderPositiveToNeutralTransition(float spinAngle,\n                                                        int spinAlpha) {\n  const glm::vec4 tint = {1.0f, 1.0f, 1.0f, spinAlpha / 256.0f};\n\n  const glm::vec2 offset = {\n      ((ScrWork[SW_DELUSION_SPIN_COUNTER] * 1248) >> 6) - 800, -109};\n  const glm::vec2 scale(1.0f +\n                        ScrWork[SW_DELUSION_SPIN_COUNTER] * 0.8f / 64.0f);\n  const glm::mat4 transformation =\n      TransformationMatrix({600, 557}, scale, {600, 557}, spinAngle, offset);\n  Renderer->DrawSprite(PositiveDelusionSprite, transformation, tint);\n\n  if (ScrWork[SW_DELUSION_LIMIT] == Delusion_Both) {\n    const glm::vec2 center = {\n        ((ScrWork[SW_DELUSION_SPIN_COUNTER] * 1248) >> 6) + 1696, -109};\n    const CornersQuad dest = NegativeDelusionSprite.ScaledBounds()\n                                 .Rotate(spinAngle, {424, 557})\n                                 .Translate(center);\n    Renderer->DrawSprite(NegativeDelusionSprite, dest, tint);\n  }\n}\n\nvoid DelusionTrigger::RenderNegativeToNeutralTransition(float spinAngle,\n                                                        int spinAlpha) {\n  const glm::vec4 tint = {1.0f, 1.0f, 1.0f, spinAlpha / 256.0f};\n\n  const glm::vec2 offset = {\n      1696 - ((ScrWork[SW_DELUSION_SPIN_COUNTER] * 1248) >> 6), -109};\n  const glm::vec2 scale(1.0f +\n                        ScrWork[SW_DELUSION_SPIN_COUNTER] * 0.8f / 64.0f);\n  const glm::mat4 transformation =\n      TransformationMatrix({424, 557}, scale, {424, 557}, spinAngle, offset);\n  Renderer->DrawSprite(NegativeDelusionSprite, transformation, tint);\n\n  if (ScrWork[SW_DELUSION_LIMIT] == Delusion_Both) {\n    const glm::vec2 center = {\n        -800 - ((ScrWork[SW_DELUSION_SPIN_COUNTER] * 1248) >> 6), -109};\n    const CornersQuad dest = PositiveDelusionSprite.ScaledBounds()\n                                 .Rotate(spinAngle, {600, 557})\n                                 .Translate(center);\n    Renderer->DrawSprite(PositiveDelusionSprite, dest, tint);\n  }\n}\n\nvoid DelusionTrigger::RenderEndNeutralTransition(float spinAngle,\n                                                 int spinAlpha) {\n  const glm::vec4 tint = {1.0f, 1.0f, 1.0f, spinAlpha / 256.0f};\n\n  if ((ScrWork[SW_DELUSION_LIMIT] == Delusion_Both) ||\n      (ScrWork[SW_DELUSION_LIMIT] == Delusion_PosOnly)) {\n    const glm::vec2 offset = {\n        ((ScrWork[SW_DELUSION_BG_COUNTER] * 1248) >> 5) - 2048, -109};\n    const CornersQuad dest = PositiveDelusionSprite.ScaledBounds()\n                                 .Rotate(spinAngle, {600, 557})\n                                 .Translate(offset);\n    Renderer->DrawSprite(PositiveDelusionSprite, dest, tint);\n  }\n\n  if ((ScrWork[SW_DELUSION_LIMIT] == Delusion_Both) ||\n      (ScrWork[SW_DELUSION_LIMIT] == Delusion_NegOnly)) {\n    const glm::vec2 offset = {\n        2944 - ((ScrWork[SW_DELUSION_BG_COUNTER] * 1248) >> 5), -109};\n    const CornersQuad dest = PositiveDelusionSprite.ScaledBounds()\n                                 .Rotate(spinAngle, {424, 557})\n                                 .Translate(offset);\n    Renderer->DrawSprite(NegativeDelusionSprite, dest, tint);\n  }\n}\n\nvoid DelusionTrigger::Render() {\n  if (ScrWork[SW_DELUSION_BG_COUNTER] == 0) {\n    return;\n  }\n  const glm::vec4 mtrgSelTint = {\n      1.0f, 1.0f, 1.0f,\n      ((ScrWork[SW_DELUSION_BG_COUNTER] * 8) & 0xffffff) / 256.0f};\n  if (ScrWork[SW_DELUSION_BG_COUNTER] < 32) {\n    const glm::vec2 scale(2.0f - (ScrWork[SW_DELUSION_BG_COUNTER] / 32.0f));\n    const CornersQuad dest =\n        BgOverlaySprite.ScaledBounds().Scale(scale, {960, 413});\n    Renderer->DrawSprite(BgOverlaySprite, dest, mtrgSelTint);\n  } else {\n    Renderer->DrawSprite(BgOverlaySprite, RectF{0, 0, 1920, 1080});\n  }\n\n  float spinAngle = MtrgAng / 65535.0f * 2.0f * std::numbers::pi_v<float>;\n\n  int spinAlpha = (MtrgAlphaCt < 16) ? MtrgAlphaCt : 32 - MtrgAlphaCt;\n  spinAlpha = (spinAlpha * 192 >> 4) + 64;\n\n  if (GetFlag(SF_DELUSIONACTIVE)) {\n    if (LastDelusionState == DelusionState) {\n      RenderStable(spinAngle, spinAlpha);\n    } else if (LastDelusionState == 0xff) {\n      RenderStartTransition(spinAngle, spinAlpha);\n    } else if (LastDelusionState == DS_Neutral) {\n      if (DelusionState == DS_Negative) {\n        RenderNeutralToNegativeTransition(spinAngle, spinAlpha);\n      } else if (DelusionState == DS_Positive) {\n        RenderNeutralToPositiveTransition(spinAngle, spinAlpha);\n      }\n\n    } else if (LastDelusionState == DS_Positive) {\n      RenderPositiveToNeutralTransition(spinAngle, spinAlpha);\n    } else if (LastDelusionState == DS_Negative) {\n      RenderNegativeToNeutralTransition(spinAngle, spinAlpha);\n    }\n  } else if (DelusionState == DS_Neutral) {\n    RenderEndNeutralTransition(spinAngle, spinAlpha);\n  }\n}\n\n};  // namespace CCLCC\n}  // namespace UI\n};  // namespace Impacto"
  },
  {
    "path": "src/games/cclcc/delusiontrigger.h",
    "content": "#pragma once\n\n#include \"../../spriteanimation.h\"\n#include \"../../ui/menu.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace CCLCC {\n\nclass DelusionTrigger {\n public:\n  enum DelusionState { DS_Neutral, DS_Positive, DS_Negative };\n  enum DelusionsShown {\n    Delusion_Both = 0,\n    Delusion_PosOnly = 1,\n    Delusion_NegOnly = 2\n  };\n\n  DelusionTrigger();\n\n  void Hide();\n  void Update(float dt);\n  void Render();\n  bool Show(int bgOverlayBgBufferId, int circlesBgBufferId,\n            int availableDelusions);\n  bool CheckTransitionAnimationComplete();\n  bool CheckStartTransitionComplete();\n\n  static DelusionTrigger& GetInstance() {\n    static DelusionTrigger impl;\n    return impl;\n  };\n\n private:\n  int MtrgAlphaCt = 0;\n  int MtrgAng = 0;\n  int& DelusionState;\n  int LastDelusionState = 0xFF;\n  Sprite PositiveDelusionSprite;\n  Sprite NegativeDelusionSprite;\n  Sprite BgOverlaySprite;\n\n  void RenderPositiveToNeutralTransition(float spinAngle, int spinAlpha);\n  void RenderNegativeToNeutralTransition(float spinAngle, int spinAlpha);\n  void RenderNeutralToNegativeTransition(float spinAngle, int spinAlpha);\n  void RenderNeutralToPositiveTransition(float spinAngle, int spinAlpha);\n  void RenderStartTransition(float spinAngle, int spinAlpha);\n  void RenderEndNeutralTransition(float spinAngle, int spinAlpha);\n  void RenderStable(float spinAngle, int spinAlpha);\n};\n\n}  // namespace CCLCC\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/cclcc/helpmenu.cpp",
    "content": "#include \"helpmenu.h\"\n\n#include \"../../profile/games/cclcc/helpmenu.h\"\n#include \"../../profile/ui/backlogmenu.h\"\n#include \"../../profile/dialogue.h\"\n#include \"../../renderer/renderer.h\"\n#include \"../../mem.h\"\n#include \"../../profile/scriptvars.h\"\n#include \"../../vm/interface/input.h\"\n#include \"../../profile/game.h\"\n#include \"../../inputsystem.h\"\n#include \"../../audio/audiosystem.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace CCLCC {\n\nusing namespace Impacto::Profile::CCLCC::HelpMenu;\nusing namespace Impacto::Profile::ScriptVars;\n\nusing namespace Impacto::Vm::Interface;\n\nusing namespace Impacto::UI::Widgets;\n\nHelpMenu::HelpMenu() {\n  FadeAnimation.Direction = AnimationDirection::In;\n  FadeAnimation.LoopMode = AnimationLoopMode::Stop;\n  FadeAnimation.DurationIn = FadeInDuration;\n  FadeAnimation.DurationOut = FadeOutDuration;\n  NextPageAnimation.DurationIn = NextPageInDuration;\n  NextPageAnimation.DurationOut = NextPageOutDuration;\n}\n\nvoid HelpMenu::Show() {\n  if (State != Showing) {\n    State = Showing;\n\n    if (UI::FocusedMenu != 0) {\n      LastFocusedMenu = UI::FocusedMenu;\n      LastFocusedMenu->IsFocused = false;\n    }\n    UI::FocusedMenu = this;\n\n    FadeAnimation.StartIn();\n    NextPageAnimation.StartIn();\n  }\n}\n\nvoid HelpMenu::Hide() {\n  if (State != Hiding) {\n    State = Hiding;\n    FadeAnimation.StartOut();\n    NextPageAnimation.StartOut();\n    Audio::PlayInGroup(Audio::ACG_SE, \"sysse\", 3, false, 0);\n    if (LastFocusedMenu != 0) {\n      UI::FocusedMenu = LastFocusedMenu;\n    } else {\n      UI::FocusedMenu = 0;\n    }\n  }\n}\n\nvoid HelpMenu::Update(float dt) {\n  if (ScrWork[SW_SYSSUBMENUCT] < 32 && State == Shown) {\n    Hide();\n  } else if (ScrWork[SW_SYSSUBMENUCT] > 0 && State == Hidden &&\n             (ScrWork[SW_SYSSUBMENUNO] == 11)) {\n    Show();\n  }\n\n  if (State != Hidden) {\n    FadeAnimation.Update(dt);\n    NextPageAnimation.Update(dt);\n  }\n\n  if (State == Shown && ScrWork[SW_SYSSUBMENUNO] == 11) {\n    UpdateInput(dt);\n  }\n\n  if (State == Showing && FadeAnimation.Progress == 1.0f &&\n      ScrWork[SW_SYSSUBMENUCT] == 32) {\n    State = Shown;\n    IsFocused = true;\n  } else if (State == Hiding && FadeAnimation.Progress == 0.0f &&\n             ScrWork[SW_SYSSUBMENUCT] == 0) {\n    State = Hidden;\n    IsFocused = false;\n    if (UI::FocusedMenu) UI::FocusedMenu->IsFocused = true;\n  }\n}\n\nvoid HelpMenu::UpdateInput(float dt) {\n  bool backBtnPressed =\n      (PADinputButtonWentDown | PADinputMouseWentDown) & PAD1B;\n  if (State == Shown && backBtnPressed) {\n    PreviousPage = -1;\n    SetFlag(SF_SUBMENUEXIT, 1);\n  }\n\n  bool prevBtnPressed =\n      ((PADinputButtonWentDown | PADinputMouseWentDown) & PADcustom[38]) ||\n      Input::MouseWheelDeltaY < 0;\n  if (State == Shown && prevBtnPressed && FadeAnimation.Progress == 1.0f) {\n    PreviousPage = CurrentPage;\n    CurrentPage =\n        (int)((CurrentPage - 1 + ManualPages.size()) % ManualPages.size());\n    FadeAnimation.StartIn(true);\n    NextPageAnimation.StartIn(true);\n    IsGoingNext = false;\n  }\n\n  bool nextBtnPressed =\n      ((PADinputButtonWentDown | PADinputMouseWentDown) & PADcustom[39]) ||\n      Input::MouseWheelDeltaY > 0;\n  if (State == Shown && nextBtnPressed && FadeAnimation.Progress == 1.0f) {\n    PreviousPage = CurrentPage;\n    CurrentPage = (CurrentPage + 1) % ManualPages.size();\n    FadeAnimation.StartIn(true);\n    NextPageAnimation.StartIn(true);\n    IsGoingNext = true;\n  }\n}\n\nvoid HelpMenu::Render() {\n  if (State == Hidden) return;\n\n  glm::vec4 transition(1.0f, 1.0f, 1.0f, FadeAnimation.Progress);\n  int alpha = (ScrWork[SW_SYSSUBMENUCT] * ScrWork[SW_SYSSUBMENUALPHA]) >> 5;\n  float topLeftX = 0.0f;\n\n  if (PreviousPage != -1) {\n    Renderer->DrawSprite(ManualPages[PreviousPage], glm::vec2(0.0f, 0.0f),\n                         glm::vec4{1.0f});\n\n    transition = {1.0f, 1.0f, 1.0f, NextPageAnimation.Progress};\n    topLeftX = IsGoingNext ? NextPageAnimation.Progress * 1920.0f - 1920.0f\n                           : (1.0f - NextPageAnimation.Progress) * 1920.0f;\n    Renderer->DrawSprite(\n        ManualPages[CurrentPage], glm::vec2(topLeftX, 0.0f),\n        glm::vec4{glm::vec3{transition}, transition.a * alpha / 256.0f});\n  } else {\n    topLeftX = FadeAnimation.Progress * 32 * 200 * 0.0625f - 400.0f;\n    Renderer->DrawSprite(\n        ManualPages[CurrentPage], glm::vec2(topLeftX, 0.0f),\n        glm::vec4{glm::vec3{transition}, transition.a * alpha / 256.0f});\n  }\n\n  Renderer->DrawSprite(\n      HelpMaskSprite,\n      RectF(0.0f, 0.0f, Profile::DesignWidth, Profile::DesignHeight),\n      glm::vec4{glm::vec3{transition}, 0.85f});\n}\n\n}  // namespace CCLCC\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/cclcc/helpmenu.h",
    "content": "#pragma once\n\n#include \"../../ui/menu.h\"\n#include \"../../ui/widgets/label.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace CCLCC {\n\nclass HelpMenu : public Menu {\n public:\n  HelpMenu();\n\n  void Show() override;\n  void Hide() override;\n  void Update(float dt) override;\n  void UpdateInput(float dt) override;\n  void Render() override;\n\n  Animation FadeAnimation;\n  Animation NextPageAnimation;\n\n private:\n  int CurrentPage = 0;\n  int PreviousPage = -1;\n  bool IsGoingNext = true;\n};\n\n}  // namespace CCLCC\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/cclcc/librarymenu.cpp",
    "content": "#include \"librarymenu.h\"\n\n#include \"clearlistmenu.h\"\n\n#include \"../../profile/games/cclcc/librarymenu.h\"\n#include \"../../profile/ui/backlogmenu.h\"\n#include \"../../profile/dialogue.h\"\n#include \"../../renderer/renderer.h\"\n#include \"../../mem.h\"\n#include \"../../video/videosystem.h\"\n#include \"../../profile/scriptvars.h\"\n#include \"../../vm/interface/input.h\"\n#include \"../../profile/game.h\"\n#include \"../../ui/widgets/cclcc/librarymenubutton.h\"\n#include \"../../audio/audiosystem.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace CCLCC {\n\nusing namespace Impacto::Profile::CCLCC::LibraryMenu;\nusing namespace Impacto::Profile::ScriptVars;\n\nusing namespace Impacto::Vm::Interface;\n\nusing namespace Impacto::UI::Widgets;\nusing namespace Impacto::UI::Widgets::CCLCC;\n\nLibrarySubmenu& LibraryMenu::GetMenuFromType(\n    LibraryMenuPageType menuType) const {\n  switch (menuType) {\n    case LibraryMenuPageType::Album:\n      return static_cast<LibrarySubmenu&>(*UI::AlbumMenuPtr);\n    case LibraryMenuPageType::Sound:\n      return static_cast<LibrarySubmenu&>(*UI::MusicMenuPtr);\n    case LibraryMenuPageType::Movie:\n      return static_cast<LibrarySubmenu&>(*UI::MovieMenuPtr);\n  }\n\n  throw std::invalid_argument(\n      fmt::format(\"Unknown library submenu type {}\", (int)menuType));\n}\n\nLibraryMenuPageType LibraryMenu::GetMenuTypeFromButton(Widget* btn) const {\n  const auto& btnArr = MainItems.Children;\n  if (btn == btnArr[+LibraryMenuPageType::Album]) {\n    return LibraryMenuPageType::Album;\n\n  } else if (btn == btnArr[+LibraryMenuPageType::Sound]) {\n    return LibraryMenuPageType::Sound;\n\n  } else if (btn == btnArr[+LibraryMenuPageType::Movie]) {\n    return LibraryMenuPageType::Movie;\n  }\n\n  throw std::invalid_argument(\"Unknown library menu button type\");\n}\n\nLibraryMenu::LibraryMenu() : MainItems(this) {\n  auto readyExistingMenu = [this](LibraryMenuPageType clickedType,\n                                  LibraryMenuButton* btn) -> bool {\n    if (CurrentLibraryMenu == clickedType) return !btn->Selected;\n    auto& submenu = GetMenuFromType(CurrentLibraryMenu);\n    if (submenu.State != UI::MenuState::Shown) return false;\n    submenu.Hide();\n    return true;\n  };\n  auto libraryMenuOnClickCommon = [this,\n                                   readyExistingMenu](Widgets::Button* target) {\n    auto clickedType =\n        magic_enum::enum_cast<LibraryMenuPageType>(target->Id).value();\n    auto* button = static_cast<LibraryMenuButton*>(target);\n    if (!readyExistingMenu(clickedType, button)) return;\n    GetMenuFromType(clickedType).Show();\n    auto* prevButton = static_cast<LibraryMenuButton*>(\n        MainItems.Children.at(+CurrentLibraryMenu));\n    prevButton->Selected = false;\n    Audio::PlayInGroup(Audio::ACG_SE, \"sysse\", 2, false, 0);\n    CurrentLibraryMenu = clickedType;\n    AllowsScriptInput = false;\n    button->Selected = true;\n  };\n\n  auto* album =\n      new LibraryMenuButton(0, SnapPhotoSpriteHover, SnapPhotoSpriteSelect,\n                            SnapPhotoPos, ButtonBlinkAnimation);\n  album->OnClickHandler = libraryMenuOnClickCommon;\n\n  auto* sound =\n      new LibraryMenuButton(1, HitSongsSpriteHover, HitSongsSpriteSelect,\n                            HitSongsPos, ButtonBlinkAnimation);\n  sound->OnClickHandler = libraryMenuOnClickCommon;\n  auto* movie =\n      new LibraryMenuButton(2, LoveMovieSpriteHover, LoveMovieSpriteSelect,\n                            LoveMoviePos, ButtonBlinkAnimation);\n  movie->OnClickHandler = libraryMenuOnClickCommon;\n\n  MainItems.Add(album, FDIR_DOWN);\n  MainItems.Add(sound, FDIR_DOWN);\n  MainItems.Add(movie, FDIR_DOWN);\n  MainItems.MoveTo({-LibraryTransitionPositionOffset, 0.0f});\n  FocusStart[FDIR_DOWN] = album;\n  FocusStart[FDIR_UP] = movie;\n\n  FadeAnimation.Direction = AnimationDirection::In;\n  FadeAnimation.LoopMode = AnimationLoopMode::Stop;\n  FadeAnimation.DurationIn = FadeInDuration;\n  FadeAnimation.DurationOut = FadeOutDuration;\n  ButtonBlinkAnimation.Direction = AnimationDirection::In;\n  ButtonBlinkAnimation.LoopMode = AnimationLoopMode::ReverseDirection;\n  ButtonBlinkAnimation.SetDuration(ButtonBlinkDuration);\n}\n\nvoid LibraryMenu::Init() { SetFlag(SF_ALBUMEND, false); }\nvoid LibraryMenu::Show() {\n  if (State != Shown) {\n    State = Showing;\n    FadeAnimation.StartIn();\n    if (UI::FocusedMenu != 0) {\n      LastFocusedMenu = UI::FocusedMenu;\n      LastFocusedMenu->IsFocused = false;\n    }\n    UI::FocusedMenu = this;\n    MainItems.Show();\n    MainItems.Move({LibraryTransitionPositionOffset, 0.0f},\n                   FadeAnimation.DurationIn);\n  }\n}\n\nvoid LibraryMenu::Hide() {\n  if (State != Hidden) {\n    State = Hiding;\n    FadeAnimation.StartOut();\n    if (LastFocusedMenu != 0) {\n      UI::FocusedMenu = LastFocusedMenu;\n      LastFocusedMenu->IsFocused = true;\n    } else {\n      UI::FocusedMenu = 0;\n    }\n    MainItems.Hide();\n    // todo: should be set to \"Hiding\", when fade transition will be implemented\n    MainItems.VisibilityState = Shown;\n    MainItems.Move({-LibraryTransitionPositionOffset, 0.0f},\n                   FadeAnimation.DurationOut);\n    IsFocused = false;\n  }\n}\n\nvoid LibraryMenu::Update(float dt) {\n  auto* prevBtn = static_cast<LibraryMenuButton*>(CurrentlyFocusedElement);\n  const bool wasHovered = prevBtn && prevBtn->Hovered;\n  if (ScrWork[SW_SYSSUBMENUCT] < 32 && State == Shown) {\n    Hide();\n  } else if (ScrWork[SW_SYSSUBMENUCT] > 0 && State == Hidden &&\n             (ScrWork[SW_SYSSUBMENUNO] == 8)) {\n    Show();\n  }\n  const auto* albumMenuPtr = static_cast<AlbumMenu*>(UI::AlbumMenuPtr);\n  const bool moviePlaying = CurrentLibraryMenu == LibraryMenuPageType::Movie &&\n                            Video::Players[0]->IsPlaying;\n  const bool cgViewerActive =\n      CurrentLibraryMenu == LibraryMenuPageType::Album &&\n      albumMenuPtr->CGViewer;\n  if (State == Shown && ScrWork[SW_SYSSUBMENUNO] == 8) {\n    if (!moviePlaying && !cgViewerActive) {\n      UpdateInput(dt);\n      if ((Vm::Interface::PADinputButtonWentDown & Vm::Interface::PAD1B) ||\n          (Vm::Interface::PADinputMouseWentDown & Vm::Interface::PAD1B)) {\n        Audio::PlayInGroup(Audio::ACG_SE, \"sysse\", 3, false, 0);\n        if (!IsFocused) {  // unfocus submenu\n          if (CurrentLibraryMenu != LibraryMenuPageType::Album ||\n              !albumMenuPtr->CGViewer) {\n            auto* activeButton = static_cast<LibraryMenuButton*>(\n                MainItems.Children.at(+CurrentLibraryMenu));\n            auto& submenu = GetMenuFromType(CurrentLibraryMenu);\n            submenu.Unfocus();\n            IsFocused = true;\n            activeButton->Selected = false;\n            if (!CurrentlyFocusedElement) activeButton->HasFocus = true;\n          }\n        } else {\n          UI::AlbumMenuPtr->Hide();\n          UI::MusicMenuPtr->Hide();\n          UI::MovieMenuPtr->Hide();\n          SetFlag(SF_ALBUMEND, 1);\n        }\n      }\n    }\n  }\n  if (State == Hidden) return;\n\n  FadeAnimation.Update(dt);\n  UI::AlbumMenuPtr->Update(dt);\n  UI::MusicMenuPtr->Update(dt);\n  if (!moviePlaying) UI::MovieMenuPtr->Update(dt);\n  ButtonBlinkAnimation.Update(dt);\n  if (!moviePlaying && !cgViewerActive) {\n    MainItems.Update(dt);\n    if (State == Shown) {\n      MainItems.UpdateInput(dt);\n    }\n  }\n\n  if (CurrentlyFocusedElement) {\n    auto* activeBtn = static_cast<LibraryMenuButton*>(CurrentlyFocusedElement);\n    if (activeBtn->Selected)\n      ButtonBlinkAnimation.Stop();\n    else if (ButtonBlinkAnimation.State == AnimationState::Stopped)\n      ButtonBlinkAnimation.StartIn();\n    if (!IsFocused && !activeBtn->Hovered &&\n        wasHovered) {  // Stop blink when mouse out on submenu\n      ButtonBlinkAnimation.Stop();\n      ButtonBlinkAnimation.Progress = 0.0f;\n      CurrentlyFocusedElement->HasFocus = false;\n    }\n    if (prevBtn != CurrentlyFocusedElement) {\n      Audio::PlayInGroup(Audio::ACG_SE, \"sysse\", 1, false, 0);\n    }\n  }\n\n  if (State == Showing && FadeAnimation.Progress == 1.0f &&\n      ScrWork[SW_SYSSUBMENUCT] == 32) {\n    State = Shown;\n    if (!CurrentlyFocusedElement) {\n      CurrentlyFocusedElement = MainItems.Children.at(+CurrentLibraryMenu);\n      GetMenuFromType(CurrentLibraryMenu).Show();\n      static_cast<LibraryMenuButton*>(CurrentlyFocusedElement)->Selected = true;\n    };\n  } else if (State == Hiding && FadeAnimation.Progress == 0.0f &&\n             ScrWork[SW_SYSSUBMENUCT] == 0) {\n    State = Hidden;\n    MainItems.VisibilityState = Hidden;\n    IsFocused = false;\n    if (UI::FocusedMenu) UI::FocusedMenu->IsFocused = true;\n  }\n}\n\nvoid LibraryMenu::Render() {\n  if (State != Hidden && ScrWork[SW_SYSSUBMENUCT] > 0 &&\n      ScrWork[SW_SYSSUBMENUNO] == 8) {\n    const glm::vec4 col(1.0f, 1.0f, 1.0f, FadeAnimation.Progress);\n\n    const glm::vec2 leftSpritesOffset{\n        (1.0f - FadeAnimation.Progress) * -LibraryTransitionPositionOffset,\n        0.0f};\n\n    const glm::vec2 rightSpritesOffset{\n        (1.0f - FadeAnimation.Progress) * LibraryTransitionPositionOffset,\n        0.0f};\n\n    const float submenuFadeProgress =\n        GetMenuFromType(CurrentLibraryMenu).FadeAnimation.Progress;\n    const glm::vec4 libBgCol =\n        (CurrentLibraryMenu == LibraryMenuPageType::Sound)\n            ? col * glm::vec4{glm::vec3(1.0f), 1 - submenuFadeProgress}\n            : col;\n    Renderer->DrawSprite(LibraryBackgroundSprite,\n                         LibraryBackgroundPosition + rightSpritesOffset,\n                         libBgCol);\n    GetMenuFromType(LibraryMenuPageType::Album).Render();\n    GetMenuFromType(LibraryMenuPageType::Sound).Render();\n    GetMenuFromType(LibraryMenuPageType::Movie).Render();\n    auto* albumMenuPtr = static_cast<AlbumMenu*>(UI::AlbumMenuPtr);\n    const bool cgViewerActive =\n        CurrentLibraryMenu == LibraryMenuPageType::Album &&\n        albumMenuPtr->CGViewer;\n    Renderer->DrawSprite(LibraryIndexSprite,\n                         LibraryIndexPosition + leftSpritesOffset, col);\n    MainItems.Render();\n    Renderer->DrawSprite(\n        LibraryMaskSprite,\n        RectF(0.0f, 0.0f, Profile::DesignWidth, Profile::DesignHeight),\n        glm::vec4(1.0f, 1.0f, 1.0f, FadeAnimation.Progress * LibraryMaskAlpha));\n\n    const auto* const submenuGuideSprite = [&]() -> Sprite* {\n      switch (CurrentLibraryMenu) {\n        case LibraryMenuPageType::Album:\n          if (cgViewerActive) {\n            if (albumMenuPtr->CGViewer->EnableGuide)\n              return &AlbumMenuCGViewerGuideSprite;\n            break;\n          } else\n            return &AlbumMenuGuideSprite;\n        case LibraryMenuPageType::Sound:\n          return &MusicMenuGuideSprite;\n        case LibraryMenuPageType::Movie:\n          return &MovieMenuGuideSprite;\n      }\n      return nullptr;\n    }();\n    if (cgViewerActive) {\n      albumMenuPtr->RenderCGViewer();\n    }\n\n    if (submenuGuideSprite) {\n      Renderer->DrawSprite(\n          *submenuGuideSprite, LibraryButtonGuidePosition + leftSpritesOffset,\n          col * glm::vec4{glm::vec3(1.0f), submenuFadeProgress});\n    }\n\n    // This is technically a double render but menus always render after videos\n    // so what can you do.\n    if (CurrentLibraryMenu == LibraryMenuPageType::Movie &&\n        Video::Players[0]->IsPlaying) {\n      Video::VideoRender(1);\n    }\n  }\n}\n\n}  // namespace CCLCC\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/cclcc/librarymenu.h",
    "content": "#pragma once\n\n#include \"../../ui/menu.h\"\n#include \"../../ui/widgets/button.h\"\n#include \"../../ui/widgets/group.h\"\n#include \"../../profile/games/cclcc/librarymenu.h\"\n#include \"librarysubmenus.h\"\n#include \"musicmenu.h\"\n#include \"moviemenu.h\"\n#include \"albummenu.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace CCLCC {\n\nclass LibraryMenu : public Menu {\n public:\n  LibraryMenu();\n\n  void Init() override;\n  void Show() override;\n  void Hide() override;\n  void Update(float dt) override;\n  void Render() override;\n\n  Animation FadeAnimation;\n  Animation ButtonBlinkAnimation;\n\n  void LibraryMenuButtonOnClick(Widgets::Button* target);\n\n private:\n  using LibraryMenuPageType = Profile::CCLCC::LibraryMenu::LibraryMenuPageType;\n  LibrarySubmenu& GetMenuFromType(LibraryMenuPageType menuType) const;\n  LibraryMenuPageType GetMenuTypeFromButton(Widget* btn) const;\n\n  Widgets::Group MainItems;\n\n  LibraryMenuPageType CurrentLibraryMenu = LibraryMenuPageType::Album;\n};\n\n}  // namespace CCLCC\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/cclcc/librarysubmenus.cpp",
    "content": "#include \"librarysubmenus.h\"\n\n#include \"../../ui/ui.h\"\n#include \"../../profile/games/cclcc/librarymenu.h\"\n#include \"../../video/videosystem.h\"\n#include \"../../vm/interface/input.h\"\n#include \"../../inputsystem.h\"\n#include \"../../text/text.h\"\n#include \"../../profile/dialogue.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace CCLCC {\n\nusing namespace Impacto::Profile::CCLCC::LibraryMenu;\nusing namespace Impacto::Profile::ScriptVars;\n\nLibrarySubmenu::LibrarySubmenu() : Menu(), MainItems(this) {\n  FadeAnimation.DurationIn = SubMenuFadeInDuration;\n  FadeAnimation.DurationOut = SubMenuFadeOutDuration;\n};\n\nvoid LibrarySubmenu::Show() {\n  if (State != Shown) {\n    State = Showing;\n    FadeAnimation.StartIn();\n    MainItems.Show();\n  }\n  if (UI::FocusedMenu != 0 && UI::FocusedMenu != this) {\n    LastFocusedMenu = UI::FocusedMenu;\n    LastFocusedMenu->IsFocused = false;\n  }\n  if (State == Shown) {\n    IsFocused = true;\n  }\n  UI::FocusedMenu = this;\n}\nvoid LibrarySubmenu::Hide() {\n  if (State != Hidden) {\n    State = Hiding;\n    FadeAnimation.StartOut();\n    Unfocus();\n  }\n}\n\nvoid LibrarySubmenu::Update(float dt) {\n  FadeAnimation.Update(dt);\n  MainItems.HasFocus = IsFocused;\n  MainItems.Update(dt);\n  UpdateInput(dt);\n  if (CurrentlyFocusedElement) {\n    CurrentlyFocusedElement->Update(dt);\n  }\n  if (State == Showing && FadeAnimation.IsIn()) {\n    State = Shown;\n    IsFocused = true;\n  } else if (State == Hiding && FadeAnimation.IsOut()) {\n    State = Hidden;\n    IsFocused = false;\n    MainItems.Hide();\n    if (UI::FocusedMenu) UI::FocusedMenu->IsFocused = true;\n  }\n}\n\nvoid LibrarySubmenu::Render() {\n  if (State == Hidden) return;\n\n  MainItems.Tint = glm::vec4(1.0f, 1.0f, 1.0f, FadeAnimation.Progress);\n  MainItems.Render();\n}\n\nvoid LibrarySubmenu::Unfocus() {\n  if (!IsFocused) return;\n\n  for (auto* child : MainItems.Children) {\n    child->Hovered = false;\n  }\n\n  if (UI::FocusedMenu == this) {\n    if (LastFocusedMenu != 0) {\n      UI::FocusedMenu = LastFocusedMenu;\n    } else {\n      UI::FocusedMenu = 0;\n    }\n  }\n  IsFocused = false;\n}\n}  // namespace CCLCC\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/cclcc/librarysubmenus.h",
    "content": "#pragma once\n\n#include \"../../ui/widgets/button.h\"\n#include \"../../ui/widgets/group.h\"\n#include \"../../ui/widgets/label.h\"\n#include <vector>\n#include <memory>\n\nnamespace Impacto {\nnamespace UI {\nnamespace CCLCC {\nclass LibrarySubmenu : public Menu {\n public:\n  LibrarySubmenu();\n\n  void Show() override;\n  void Hide() override;\n  void Update(float dt) override;\n  virtual void UpdateInput(float dt) override {\n    Menu::UpdateInput(dt);\n    MainItems.UpdateInput(dt);\n  };\n  void Render() override;\n  // void Move(glm::vec2 offset);\n  // void MoveTo(glm::vec2 pos);\n  virtual void Unfocus();\n  Animation FadeAnimation;\n  Widgets::Group MainItems{this};\n};\n\n}  // namespace CCLCC\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/cclcc/mapsystem.cpp",
    "content": "#include \"mapsystem.h\"\n\n#include <array>\n#include <algorithm>\n#include <cstdint>\n\n#include \"../../log.h\"\n#include \"../../util.h\"\n#include \"../../mem.h\"\n#include \"../../inputsystem.h\"\n#include \"../../audio/audiosystem.h\"\n#include \"../../vm/interface/input.h\"\n#include \"../../profile/scriptvars.h\"\n#include \"../../profile/games/cclcc/mapsystem.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace CCLCC {\nusing namespace Impacto::Vm::Interface;\nusing namespace Impacto::UI::Widgets;\nusing namespace Impacto::Profile::CCLCC::MapSystem;\nusing namespace Impacto::Profile::ScriptVars;\n\ninline float toFlt(double d) { return static_cast<float>(d); }\ninline float toFlt(int d) { return static_cast<float>(d); }\ninline int toInt(float f) { return static_cast<int>(f); }\n\n// TODO, add the scrwork and flag indices to the scriptvars file\n\n// TODO, move this crap to the lua file\nconstexpr int MapPhotoIdMap[11][4] = {\n    // index 0 is map part for photos\n    // index 3 is map part for article\n\n    {0, 1, 2, 0x3e8},       {3, 4, 5, 0x3e9},       {6, 7, 8, 0x3ea},\n    {9, 10, 11, 0x3eb},     {10, 0xff, 0xff, 0xff}, {12, 0xff, 0xff, 0xff},\n    {13, 0xff, 0xff, 0xff}, {14, 0xff, 0xff, 0xff}, {15, 0xff, 0xff, 0xff},\n    {16, 0xff, 0xff, 0xff}, {17, 0xff, 0xff, 0xff},\n};\n\nconstexpr float MapPoolPosOffsets[][2] = {\n    {2709.0, 200.0}, {2981.0, 214.0}, {3222.0, 203.0},  {2717.0, 332.0},\n    {2984.0, 349.0}, {3236.0, 342.0}, {2713.0, 459.0},  {2974.0, 482.0},\n    {3222.0, 485.0}, {2734.0, 596.0}, {2740.0, 784.0},  {3047.0, 787.0},\n    {2754.0, 930.0}, {3066.0, 930.0}, {2755.0, 1072.0}, {3064.0, 1073.0},\n};\n\nconstexpr float MapPoolArticleOffsets[][2] = {\n    {2699.0, 200.0}, {3071.0, 214.0}, {2707.0, 482.0}, {3074.0, 499.0}};\n\nconstexpr float MapArticleOffsets[][12]{\n    {1095.0, 813.0, 1143.0, 745.0, 1169.0, 676.0, 1048.0, 735.0, 898.0, 744.0,\n     882.0, 849.0},\n    {1095.0, 813.0, 1143.0, 745.0, 302.0, 1259.0, 390.0, 1093.0, 226.0, 982.0,\n     218.0, 1094.0},\n    {439.0, 1189.0, 481.0, 1113.0, 756.0, 1005.0, 607.0, 869.0, 478.0, 953.0,\n     451.0, 843.0},\n    {645.0, 975.0, 707.0, 881.0, 756.0, 1005.0, 607.0, 869.0, 478.0, 953.0,\n     451.0, 843.0},\n    {645.0, 975.0, 707.0, 881.0, 842.0, 621.0, 680.0, 564.0, 698.0, 432.0,\n     864.0, 424.0},\n    {680.0, 564.0, 779.0, 647.0, 842.0, 621.0, 680.0, 564.0, 698.0, 432.0,\n     864.0, 424.0},\n    {680.0, 564.0, 779.0, 647.0, 1361.0, 628.0, 1466.0, 484.0, 0.0, 0.0, 0.0,\n     0.0},\n    {0.0, 0.0, 0.0, 0.0, 155.0, 550.0, 308.0, 440.0, 0.0, 0.0, 0.0, 0.0},\n    {0.0, 0.0, 0.0, 0.0, 1037.0, 1355.0, 1203.0, 1320.0, 0.0, 0.0, 0.0, 0.0},\n    {1113.0, 1419.0, 1298.0, 1324.0, 1188.0, 1043.0, 1294.0, 934.0, 0.0, 0.0,\n     0.0, 0.0},\n    {1431.0, 980.0, 1390.0, 952.0, 515.0, 1412.0, 645.0, 1499.0, 0.0, 0.0, 0.0,\n     0.0},\n    {788.0, 1553.0, 750.0, 1511.0, 1132.0, 1139.0, 984.0, 1175.0, 0.0, 0.0, 0.0,\n     0.0},\n    {832.0, 1183.0, 1082.0, 1194.0, 1277.0, 52.0, 1430.0, 170.0, 0.0, 0.0, 0.0,\n     0.0},\n    {1589.0, 205.0, 1512.0, 183.0, 1339.0, 1243.0, 1536.0, 1264.0, 0.0, 0.0,\n     0.0, 0.0},\n    {1644.0, 1359.0, 1638.0, 1284.0, 1508.0, 1411.0, 1357.0, 1490.0, 0.0, 0.0,\n     0.0, 0.0},\n    {1306.0, 1596.0, 1453.0, 1509.0, 0.0, 0.0, 2658.0, 913.0, 2998.0, 915.0,\n     3184.0, 921.0},\n    {2803.0, 931.0, 2641.0, 866.0, 1298.0, 1257.0, 1458.0, 1131.0, 0.0, 0.0,\n     0.0, 0.0},\n    {1627.0, 1126.0, 1309.0, 1284.0, 0.0, 0.0, 205.0, 680.0, 205.0, 680.0,\n     205.0, 680.0},\n};\n\nconstexpr float PartsOffsetData[][3][4] = {{{1169.0, 676.0, 1048.0, 735.0},\n                                            {898.0, 744.0, 882.0, 849.0},\n                                            {1095.0, 813.0, 1143.0, 745.0}},\n\n                                           {{302.0, 1259.0, 390.0, 1093.0},\n                                            {226.0, 982.0, 218.0, 1094.0},\n                                            {439.0, 1189.0, 481.0, 1113.0}},\n\n                                           {{756.0, 1005.0, 607.0, 869.0},\n                                            {478.0, 953.0, 451.0, 843.0},\n                                            {645.0, 975.0, 707.0, 881.0}},\n\n                                           {{842.0, 621.0, 680.0, 564.0},\n                                            {698.0, 432.0, 864.0, 424.0},\n                                            {620.0, 650.0, 779.0, 647.0}},\n\n                                           {{756.0, 1005.0, 607.0, 869.0},\n                                            {478.0, 953.0, 451.0, 843.0},\n                                            {645.0, 975.0, 707.0, 881.0}},\n\n                                           {{756.0, 1005.0, 607.0, 869.0},\n                                            {478.0, 953.0, 451.0, 843.0},\n                                            {645.0, 975.0, 707.0, 881.0}},\n\n                                           {{756.0, 1005.0, 607.0, 869.0},\n                                            {478.0, 953.0, 451.0, 843.0},\n                                            {645.0, 975.0, 707.0, 881.0}},\n\n                                           {{756.0, 1005.0, 607.0, 869.0},\n                                            {478.0, 953.0, 451.0, 843.0},\n                                            {645.0, 975.0, 707.0, 881.0}}};\n\nconstexpr int PartsIdMap[][4] = {{2, 0xff, 0xff, 0xff}};\n\nconstexpr uint32_t Tints[] = {\n    0xB43C3C, 0xB43C3C, 0xCA2ECC, 0x387A21, 0xEFA918, 0xEFA918, 0x40D08A,\n    0xABABAB, 0xE26E10, 0x1761F5, 0xABABAB, 0xE26E10, 0x1761F5, 0x387A21,\n    0xB43C3C, 0xCA2ECC, 0xEFA0DE, 0x387A21, 0xCA2ECC, 0x40D08A, 0xEFA0DE,\n    0xABABAB, 0xE26E10, 0xEFA918, 0x40D08A, 0xEFA0DE, 0x1761F5, 0x387A21,\n    0x387A21, 0x1761F5, 0xEFA918, 0xEFA918, 0x40D08A, 0x40D08A, 0xEFA0DE,\n    0xABABAB, 0x387A21, 0x1761F5, 0xEFA918, 0x40D08A, 0xEFA0DE, 0xABABAB,\n    0xE26E10, 0xE26E10, 0xCA2ECC, 0xCA2ECC, 0x387A21, 0x1761F5, 0xEFA918,\n    0x40D08A, 0xCA2ECC, 0xCA2ECC, 0xCA2ECC, 0xCA2ECC, 0xCA2ECC, 0xCA2ECC,\n    0xCA2ECC, 0xCA2ECC, 0xCA2ECC, 0xCA2ECC, 0xCA2ECC, 0xCA2ECC, 0xCA2ECC,\n    0xCA2ECC, 0xCA2ECC, 0xCA2ECC, 0xCA2ECC, 0xCA2ECC, 0xCA2ECC, 0xCA2ECC,\n    0xCA2ECC, 0xCA2ECC, 0xE26E10, 0xE26E10, 0xE26E10, 0xE26E10, 0xE26E10,\n    0xE26E10, 0xE26E10, 0xE26E10, 0xE26E10, 0x40D08A, 0xEFA0DE, 0xE26E10,\n    0x40D08A, 0x40D08A};\n\nconstexpr uint32_t LineColors1[] = {\n    0x10F050, 0x10F050, 0x10F050, 0x10F050, 0x10F050, 0x10F050, 0x10F050,\n    0x10F050, 0x10F050, 0x10F050, 0x10F050, 0x10F050, 0x10F050, 0x10F050,\n    0x10F050, 0x10F050, 0x0,      0xFDC000, 0xFDC000, 0x0,\n};\n\nconstexpr uint32_t LineColors2[] = {\n    0x1030F0, 0x1030F0, 0x1030F0, 0x1030F0, 0x1030F0, 0x1030F0, 0x1030F0,\n    0x1030F0, 0x1030F0, 0xF01030, 0xF01030, 0xF01030, 0xF01030, 0xF01030,\n    0xF01030, 0xF01030, 0x0,      0xFDC000, 0xFDC000, 0x0,      0x0,\n    0x0,      0x0,      0xFDC000, 0x0,      0x0,      0x0,      0x0,\n    0xDC0FA,  0xDC0FA,  0xDC0FA,  0xDC0FA,  0xDC0FA,  0xDC0FA,  0xDC0FA,\n    0xDC0FA,  0xDC0FA,  0xDC0FA,  0xDC0FA,  0xDC0FA,  0xDC0FA,  0xDC0FA,\n    0xDC0FA,  0xDC0FA,  0xDC0FA,\n};\n\nvoid MapSystem::MapInit() {\n  // Silence unused var warning, this table is probably going to be useful later\n  (void)MapArticleOffsets;\n\n  ScrWork[6369] = 0;\n  ScrWork[6370] = 0;\n  ScrWork[6371] = 0;\n\n  MapPartsMax = 0;\n\n  MapResetPoolAll(0);\n  for (auto itr = MapPartsDisp.begin(); itr != MapPartsDisp.end(); ++itr) {\n    *itr = MapPartsDispStruct{0xff, 0, Animation{}, Hidden, 0, 0, 0};\n    itr->fadeAnim.DurationIn = FadeAnimationDuration;\n    itr->fadeAnim.DurationOut = FadeAnimationDuration;\n    itr->fadeAnim.SkipOnSkipMode = true;\n  }\n  for (size_t i = 0; i < MapGroup.size(); i++) {\n    int i_i = static_cast<int>(i);\n    MapGroup[i] = MapGroupStruct{i_i, i_i, i_i};\n  }\n  SetFlag(2801, 0);\n  SetFlag(2802, 0);\n  SetFlag(2803, 0);\n  SetFlag(2804, 0);\n  SetFlag(2805, 0);\n}\n\nint MapSystem::MapLoad(uint8_t* data) {\n  int dataSize = 0;\n  memmove(&MapGroup, data, sizeof(MapGroup));\n  dataSize += sizeof(MapGroup);\n  for (size_t i = 0; i < MapPartsDisp.size(); i++) {\n    MapPartsDisp[i].partId = data[dataSize];\n    dataSize += 4;\n    MapPartsDisp[i].type = data[dataSize];\n    dataSize += 4;\n    int shown = data[dataSize];\n    dataSize += 4;\n\n    int inOrOut = data[dataSize];\n    dataSize += 4;\n    if (shown == 1 && inOrOut == 1) {\n      MapPartsDisp[i].state = Shown;\n    } else if (shown == 0 && inOrOut == 1) {\n      MapPartsDisp[i].state = Showing;\n    } else if (shown == 1 && inOrOut == 0) {\n      MapPartsDisp[i].state = Hiding;\n    } else {\n      MapPartsDisp[i].state = Hidden;\n    }\n    int progress = data[dataSize];\n    dataSize += 4;\n    float conversionFactor = FadeAnimationDuration * 60.0f;\n    MapPartsDisp[i].fadeAnim.Progress =\n        (MapPartsDisp[i].state == Hiding || MapPartsDisp[i].state == Hidden)\n            ? progress / conversionFactor\n        : (MapPartsDisp[i].state == Showing || MapPartsDisp[i].state == Shown)\n            ? 1 - progress / conversionFactor\n            : 0;\n    MapPartsDisp[i].fadeAnim.Direction =\n        inOrOut ? AnimationDirection::In : AnimationDirection::Out;\n    MapPartsDisp[i].delay = data[dataSize];\n    dataSize += 4;\n    MapPartsDisp[i].angle = data[dataSize];\n    dataSize += 4;\n    MapPartsDisp[i].dist = data[dataSize];\n    dataSize += 4;\n  }\n  dataSize += (int)(800 - MapPartsDisp.size()) * 32;\n  assert(dataSize == 0x65e0);\n  for (size_t i = 0; i < MapPoolDisp.size(); i++) {\n    int show = data[dataSize];\n    dataSize += 4;\n    int inOrOut = data[dataSize];\n    dataSize += 4;\n    if (show == 1 && inOrOut == 1) {\n      MapPoolDisp[i].state = Shown;\n    } else if (show == 0 && inOrOut == 1) {\n      MapPoolDisp[i].state = Showing;\n    } else if (show == 1 && inOrOut == 0) {\n      MapPoolDisp[i].state = Hiding;\n    } else {\n      MapPoolDisp[i].state = Hidden;\n    }\n\n    int progress = data[dataSize];\n    dataSize += 4;\n    float conversionFactor = FadeAnimationDuration * 60.0f;\n    MapPoolDisp[i].fadeAnim.Progress =\n        (MapPoolDisp[i].state == Hiding || MapPoolDisp[i].state == Shown)\n            ? progress / conversionFactor\n        : (MapPoolDisp[i].state == Showing || MapPoolDisp[i].state == Hidden)\n            ? 1 - progress / conversionFactor\n            : 0;\n    MapPoolDisp[i].fadeAnim.Direction =\n        inOrOut ? AnimationDirection::In : AnimationDirection::Out;\n    MapPoolDisp[i].delay = data[dataSize];\n    dataSize += 4;\n    MapPoolDisp[i].angle = data[dataSize];\n    dataSize += 4;\n    dataSize += 4;  // padding?\n  }\n  assert(dataSize == 0x69a0);\n  for (size_t i = 0; i < MapPool.size(); i++) {\n    MapPool[i].id = data[dataSize];\n    dataSize += 4;\n    MapPool[i].type = data[dataSize];\n    dataSize += 4;\n    MapPool[i].button.Enabled = false;\n  }\n  assert(dataSize == 0x6a40);\n  memmove(&MapPoolCurCt, data + dataSize, sizeof(MapPoolCurCt));\n  dataSize += sizeof(MapPoolCurCt);\n  assert(dataSize == 0x6a90);\n  dataSize += 24;  // MapMoveAnimeInit and MapMoveAnimeMain which uses\n                   // MapMoveAnime are not called in cclcc\n  assert(dataSize == 0x6aa8);\n  MapPartsMax = data[dataSize];\n  dataSize += 4;\n  HoverMapPoolIdx = data[dataSize];\n  dataSize += 4;\n  SelectedMapPoolIdx = data[dataSize];\n  dataSize += 4;\n  assert(dataSize == 0x6ab4);\n  dataSize += 20;  // Variables used by MapMoveAnimeInit and MapMoveAnimeMain\n  assert(dataSize == 0x6ac8);\n  return dataSize;\n}\n\nint MapSystem::MapSave(uint8_t* data) {\n  int dataSize = 0;\n  memmove(data, &MapGroup, sizeof(MapGroup));\n  dataSize += sizeof(MapGroup);\n  for (size_t i = 0; i < MapPartsDisp.size(); i++) {\n    data[dataSize] = (uint8_t)MapPartsDisp[i].partId;\n    dataSize += 4;\n    data[dataSize] = (uint8_t)MapPartsDisp[i].type;\n    dataSize += 4;\n    switch (MapPartsDisp[i].state) {\n      case Shown:\n        data[dataSize] = 1;\n        dataSize += 4;\n        data[dataSize] = 1;\n        break;\n      case Showing:\n        data[dataSize] = 0;\n        dataSize += 4;\n        data[dataSize] = 1;\n        break;\n      case Hiding:\n        data[dataSize] = 1;\n        dataSize += 4;\n        data[dataSize] = 0;\n        break;\n      case Hidden:\n        data[dataSize] = 0;\n        dataSize += 4;\n        data[dataSize] = 0;\n        break;\n    }\n    dataSize += 4;\n    data[dataSize] =\n        (MapPartsDisp[i].state == Hiding || MapPartsDisp[i].state == Hidden)\n            ? (uint8_t)(MapPartsDisp[i].fadeAnim.Progress *\n                        FadeAnimationDuration * 60.0f)\n        : (MapPartsDisp[i].state == Showing || MapPartsDisp[i].state == Shown)\n            ? (uint8_t)((1 - MapPartsDisp[i].fadeAnim.Progress) *\n                        FadeAnimationDuration * 60.0f)\n            : 0;\n    dataSize += 4;\n    data[dataSize] = (uint8_t)MapPartsDisp[i].delay;\n    dataSize += 4;\n    data[dataSize] = (uint8_t)MapPartsDisp[i].angle;\n    dataSize += 4;\n    data[dataSize] = (uint8_t)MapPartsDisp[i].dist;\n    dataSize += 4;\n  }\n  dataSize += (int)(800 - MapPartsDisp.size()) * 32;\n  assert(dataSize == 0x65e0);\n  for (size_t i = 0; i < MapPoolDisp.size(); i++) {\n    switch (MapPoolDisp[i].state) {\n      case Shown:\n        data[dataSize] = 1;\n        dataSize += 4;\n        data[dataSize] = 1;\n        break;\n      case Showing:\n        data[dataSize] = 0;\n        dataSize += 4;\n        data[dataSize] = 1;\n        break;\n      case Hiding:\n        data[dataSize] = 1;\n        dataSize += 4;\n        data[dataSize] = 0;\n        break;\n      case Hidden:\n        data[dataSize] = 0;\n        dataSize += 4;\n        data[dataSize] = 0;\n        break;\n    }\n    dataSize += 4;\n    data[dataSize] =\n        (MapPoolDisp[i].state == Hiding || MapPoolDisp[i].state == Shown)\n            ? (uint8_t)(MapPoolDisp[i].fadeAnim.Progress *\n                        FadeAnimationDuration * 60.0f)\n        : (MapPoolDisp[i].state == Showing || MapPoolDisp[i].state == Hidden)\n            ? (uint8_t)((1 - MapPoolDisp[i].fadeAnim.Progress) *\n                        FadeAnimationDuration * 60.0f)\n            : 0;\n    dataSize += 4;\n    data[dataSize] = (uint8_t)MapPoolDisp[i].delay;\n    dataSize += 4;\n    data[dataSize] = (uint8_t)MapPoolDisp[i].angle;\n    dataSize += 4;\n    dataSize += 4;  // padding?\n  }\n  assert(dataSize == 0x69a0);\n  for (size_t i = 0; i < MapPool.size(); i++) {\n    data[dataSize] = (uint8_t)MapPool[i].id;\n    dataSize += 4;\n    data[dataSize] = (uint8_t)MapPool[i].type;\n    dataSize += 4;\n  }\n  assert(dataSize == 0x6a40);\n  memmove(data + dataSize, &MapPoolCurCt, sizeof(MapPoolCurCt));\n  dataSize += sizeof(MapPoolCurCt);\n  assert(dataSize == 0x6a90);\n  dataSize += 24;  // MapMoveAnimeInit and MapMoveAnimeMain which uses\n                   // MapMoveAnime are not called in cclcc\n  assert(dataSize == 0x6aa8);\n  data[dataSize] = (uint8_t)MapPartsMax;\n  dataSize += 4;\n  data[dataSize] = (uint8_t)HoverMapPoolIdx;\n  dataSize += 4;\n  data[dataSize] = (uint8_t)SelectedMapPoolIdx;\n  dataSize += 4;\n  assert(dataSize == 0x6ab4);\n  dataSize += 20;  // Variables used by MapMoveAnimeInit and MapMoveAnimeMain\n  assert(dataSize == 0x6ac8);\n  return dataSize;\n}\nvoid MapSystem::MapSetFadein(int partId, int partType) {\n  if (MapPartsMax != 0) {\n    for (int i = 0; i < MapPartsMax; ++i) {\n      if (MapPartsDisp[i].partId == partId &&\n          MapPartsDisp[i].type == partType) {\n        return;\n      }\n    }\n  }\n  MapPartsDisp[MapPartsMax].partId = partId;\n  MapPartsDisp[MapPartsMax].type = partType;\n  MapPartsDisp[MapPartsMax].state = Showing;\n  MapPartsDisp[MapPartsMax].fadeAnim.Progress = 0;\n  MapPartsDisp[MapPartsMax].delay = 0;\n\n  if (partType == 0) {\n    MapPartsDisp[MapPartsMax].angle = CALCrnd(12);\n    MapPartsMax++;\n  } else if (partType >= 1 && partType <= 4) {\n    MapSetFadein_PhotoArticle(partId, partType);\n  } else if (partType == 5) {\n    MapPartsDisp[MapPartsMax].angle = CALCrnd(1820) - 910;\n    MapPartsMax++;\n  } else if (partType == 6 || partType == 7 || partType == 12 ||\n             partType == 13 || partType == 14) {\n    MapSetFadein_Line(partId, partType);\n  }\n}\nvoid MapSystem::MapSetFadein_PhotoArticle(int partId, int partType) {\n  MapPartsDisp[MapPartsMax].angle = CALCrnd(3640) - 1820;\n  MapPartsMax++;\n  if (partType > 0 && partType < 4 &&\n      MapPhotoIdMap[partId][partType - 1] > 999) {\n    return;\n  }\n  MapPartsDisp[MapPartsMax].partId = partId;\n  MapPartsDisp[MapPartsMax].type = partType + 7;\n  MapPartsDisp[MapPartsMax].state = Showing;\n  MapPartsDisp[MapPartsMax].fadeAnim.Progress = 0;\n  MapPartsDisp[MapPartsMax].delay = 16;\n  MapPartsDisp[MapPartsMax].angle = CALCrnd(12);\n  MapPartsMax++;\n}\nvoid MapSystem::MapSetFadein_Line(int partId, int partType) {\n  if (partType == 7) {\n    float pos1 = PartsOffsetData[partId][0][0] - PartsOffsetData[partId][0][2];\n    float pos2 = PartsOffsetData[partId][0][1] - PartsOffsetData[partId][0][3];\n    MapPartsDisp[MapPartsMax].dist =\n        toInt(sqrt(pos1 * pos1 + pos2 * pos2) / 200.0f * 16.0f);\n    if (MapPartsDisp[MapPartsMax].dist < 16) {\n      MapPartsDisp[MapPartsMax].dist = 16;\n    }\n    MapPartsDisp[MapPartsMax].fadeAnim.DurationIn =\n        MapPartsDisp[MapPartsMax].dist / 60.0f;\n    MapPartsMax++;\n    for (int i = 0; i < MapPartsMax; ++i) {\n      if (MapPartsDisp[i].partId == partId && MapPartsDisp[i].type == 0) {\n        MapPartsDisp[i].delay = MapPartsDisp[MapPartsMax - 1].dist - 16;\n        MapPartsDisp[i].state = Hiding;\n        MapPartsDisp[i].fadeAnim.Progress = 1;\n        break;\n      }\n    }\n\n    MapPartsDisp[MapPartsMax].partId = partId;\n    MapPartsDisp[MapPartsMax].type = 0;\n    MapPartsDisp[MapPartsMax].state = Showing;\n    MapPartsDisp[MapPartsMax].fadeAnim.Progress = 0;\n    MapPartsDisp[MapPartsMax].angle = CALCrnd(12);\n    MapPartsDisp[MapPartsMax].delay = MapPartsDisp[MapPartsMax - 1].dist;\n    MapPartsMax++;\n    return;\n  } else if (partType == 6 || (partType > 11 && partType < 15)) {\n    int index = (partType == 6) ? 0 : partType - 11;\n    int mappedId =\n        (static_cast<size_t>(partId) < sizeof(PartsIdMap) / sizeof(float[4]))\n            ? static_cast<int>(PartsIdMap[partId][index])\n            : 0;\n    if (mappedId == 0xff) return;\n    float pos1 =\n        PartsOffsetData[mappedId][0][2] - PartsOffsetData[partId][0][2];\n    float pos2 =\n        PartsOffsetData[mappedId][0][3] - PartsOffsetData[partId][0][3];\n    MapPartsDisp[MapPartsMax].dist =\n        toInt(sqrt(pos1 * pos1 + pos2 * pos2) / 200.0f * 16.0f);\n    if (MapPartsDisp[MapPartsMax].dist < 16) {\n      MapPartsDisp[MapPartsMax].dist = 16;\n    }\n    MapPartsDisp[MapPartsMax].fadeAnim.DurationIn =\n        MapPartsDisp[MapPartsMax].dist / 60.0f;\n    MapPartsMax++;\n    for (int i = 0; i < MapPartsMax; i++) {\n      if (MapPartsDisp[i].partId == PartsIdMap[partId][mappedId] &&\n          MapPartsDisp[i].type == 8) {\n        MapPartsDisp[i].delay = MapPartsDisp[MapPartsMax - 1].dist - 16;\n        MapPartsDisp[i].state = Hiding;\n        MapPartsDisp[i].fadeAnim.Progress = 1;\n        break;\n      }\n    }\n    MapPartsDisp[MapPartsMax].partId = PartsIdMap[partId][mappedId];\n    MapPartsDisp[MapPartsMax].type = 8;\n    MapPartsDisp[MapPartsMax].state = Showing;\n    MapPartsDisp[MapPartsMax].fadeAnim.Progress = 0;\n    MapPartsDisp[MapPartsMax].delay = MapPartsDisp[MapPartsMax - 1].dist;\n    MapPartsDisp[MapPartsMax].angle = CALCrnd(12);\n    MapPartsMax++;\n    return;\n  }\n}\nvoid MapSystem::MapSetGroup(int index, int mappedId1, int mappedId2,\n                            int mappedId3) {\n  if (index >= 40) return;\n  MapGroup[index].groupId1 = mappedId1;\n  MapGroup[index].groupId2 = mappedId2;\n  MapGroup[index].groupId3 = mappedId3;\n}\nvoid MapSystem::MapSetFadeout(int partId, int partType) {\n  bool flag = false;\n  if (partType > 0 && partType <= 4) {\n    if (MapPartsMax) {\n      for (int i = 0; i < MapPartsMax; ++i) {\n        if (MapPartsDisp[i].partId == partId &&\n            MapPartsDisp[i].type == partType + 7) {\n          MapPartsDisp[i].state = Hiding;\n          MapPartsDisp[i].fadeAnim.Progress = 1;\n          MapPartsDisp[i].delay = 0;\n          flag = true;\n          break;\n        }\n      }\n    }\n  } else if (partType == 7) {\n    for (int i = 0; i < MapPartsMax; ++i) {\n      if (MapPartsDisp[i].partId == partId && MapPartsDisp[i].type == 0) {\n        MapPartsDisp[i].state = Hiding;\n        MapPartsDisp[i].fadeAnim.Progress = 1;\n        MapPartsDisp[i].delay = 0;\n        flag = true;\n        break;\n      }\n    }\n  } else if (partType == 0) {\n    for (int i = 0; i < MapPartsMax; ++i) {\n      if (MapPartsDisp[i].partId == partId && MapPartsDisp[i].type == 7) {\n        MapPartsDisp[i].state = Hiding;\n        MapPartsDisp[i].fadeAnim.Progress = 1;\n        MapPartsDisp[i].fadeAnim.DurationOut = MapPartsDisp[i].dist / 60.0f;\n        MapPartsDisp[i].delay = 16;\n        break;\n      }\n    }\n  }\n\n  for (int i = 0; i < MapPartsMax; ++i) {\n    if (MapPartsDisp[i].partId == partId && MapPartsDisp[i].type == partType) {\n      MapPartsDisp[i].state = Hiding;\n      MapPartsDisp[i].fadeAnim.Progress = 1;\n      MapPartsDisp[i].delay = flag ? 16 : 0;\n      MapPartsDisp[i].fadeAnim.DurationOut =\n          (partType == 7) ? MapPartsDisp[i].dist / 60.0f : 16.0f / 60.0f;\n      return;\n    }\n  }\n}\nvoid MapSystem::MapSetDisp(int partId, int partType) {\n  size_t partIdSt = static_cast<size_t>(partId);\n  if (MapPartsMax != 0) {\n    for (int i = 0; i < MapPartsMax; ++i) {\n      if (MapPartsDisp[i].partId == partId &&\n          MapPartsDisp[i].type == partType) {\n        return;\n      }\n    }\n  }\n  MapPartsDisp[MapPartsMax].partId = partId;\n  MapPartsDisp[MapPartsMax].type = partType;\n  MapPartsDisp[MapPartsMax].state = Shown;\n  MapPartsDisp[MapPartsMax].delay = 0;\n\n  if (partType == 0) {\n    MapPartsDisp[MapPartsMax].angle = CALCrnd(12);\n    MapPartsMax++;\n  } else if (partType >= 1 && partType <= 4) {\n    MapPartsDisp[MapPartsMax].angle = CALCrnd(3640) - 1820;\n    MapPartsMax++;\n    if (MapPhotoIdMap[partId][partType - 1] >= 1000) {\n      return;\n    }\n    MapPartsDisp[MapPartsMax].partId = partId;\n    MapPartsDisp[MapPartsMax].type = partType + 7;\n    MapPartsDisp[MapPartsMax].state = Showing;\n    MapPartsDisp[MapPartsMax].fadeAnim.Progress = 0;\n    MapPartsDisp[MapPartsMax].delay = 0;\n    MapPartsDisp[MapPartsMax].angle = CALCrnd(12);\n    MapPartsMax++;\n  } else if (partType == 5) {\n    MapPartsDisp[MapPartsMax].angle = CALCrnd(1820) - 910;\n    MapPartsMax++;\n  } else if ((partType > 11 && partType < 15) || partType == 6) {\n    int index = (partType == 6) ? 0 : partType - 11;\n    int mappedId = (partIdSt < sizeof(PartsIdMap) / sizeof(float[4]))\n                       ? static_cast<int>(PartsIdMap[partId][index])\n                       : 0;\n    if (mappedId != 0xff) {\n      float pos1 =\n          PartsOffsetData[mappedId][0][2] - PartsOffsetData[partId][0][2];\n      float pos2 =\n          PartsOffsetData[mappedId][0][3] - PartsOffsetData[partId][0][3];\n      MapPartsDisp[MapPartsMax].dist =\n          toInt(sqrt(pos1 * pos1 + pos2 * pos2) / 200.0f * 16.0f);\n    }\n    if (MapPartsDisp[MapPartsMax].dist < 16) {\n      MapPartsDisp[MapPartsMax].dist = 16;\n    }\n    MapPartsDisp[MapPartsMax].fadeAnim.DurationOut =\n        MapPartsDisp[MapPartsMax].dist / 60.0f;\n    MapPartsMax++;\n  } else if (partType == 7) {\n    float pos1 = PartsOffsetData[partId][0][0] - PartsOffsetData[partId][0][2];\n    float pos2 = PartsOffsetData[partId][0][1] - PartsOffsetData[partId][0][3];\n    MapPartsDisp[MapPartsMax].dist =\n        toInt(sqrt(pos1 * pos1 + pos2 * pos2) / 200.0f * 16.0f);\n    if (MapPartsDisp[MapPartsMax].dist < 16) {\n      MapPartsDisp[MapPartsMax].dist = 16;\n    }\n    MapPartsDisp[MapPartsMax].fadeAnim.DurationOut =\n        MapPartsDisp[MapPartsMax].dist / 60.0f;\n    MapPartsMax++;\n    for (int i = 0; i < MapPartsMax; ++i) {\n      if (MapPartsDisp[i].partId == partId && MapPartsDisp[i].type == 0) {\n        return;\n      }\n    }\n\n    MapPartsDisp[MapPartsMax].partId = partId;\n    MapPartsDisp[MapPartsMax].type = 0;\n    MapPartsDisp[MapPartsMax].state = Shown;\n    MapPartsDisp[MapPartsMax].angle = CALCrnd(12);\n    MapPartsDisp[MapPartsMax].delay = 0;\n    MapPartsMax++;\n  }\n}\nvoid MapSystem::MapSetHide(int arg1, int arg2) {}\nbool MapSystem::MapPoolFadeEndChk_Wait() {\n  for (int i = 0; i < 20; ++i) {\n    if (MapPool[i].id == 0xff) continue;\n    if (MapPoolDisp[i * 2].state == Showing ||\n        MapPoolDisp[i * 2].state == Hiding)\n      return false;\n    if (MapPoolDisp[i * 2 + 1].state == Showing ||\n        MapPoolDisp[i * 2 + 1].state == Hiding)\n      return false;\n    if (MapPoolDisp[i * 2].delay != 0) return false;\n    if (MapPoolDisp[i * 2 + 1].delay != 0) return false;\n  }\n  return true;\n}\nvoid MapSystem::MapMoveAnimeInit(int arg1, int arg2, int arg3) {}\nbool MapSystem::MapMoveAnimeMain() { return true; }\nvoid MapSystem::MapGetPos(int partId, int partType, int& getX, int& getY) {\n  switch (partType) {\n    case 0:\n      getX = toInt(PartsOffsetData[partId][0][0]);\n      getY = toInt(PartsOffsetData[partId][0][1]);\n      break;\n    case 1:\n    case 8:\n      getX = toInt(PartsOffsetData[partId][0][2]);\n      getY = toInt(PartsOffsetData[partId][0][3]);\n      break;\n    case 2:\n    case 9:\n      getX = toInt(PartsOffsetData[partId][1][0]);\n      getY = toInt(PartsOffsetData[partId][1][1]);\n      break;\n    case 3:\n    case 10:\n      getX = toInt(PartsOffsetData[partId][1][2]);\n      getY = toInt(PartsOffsetData[partId][1][3]);\n      break;\n    case 5:\n      getX = toInt(PartsOffsetData[partId][2][2]);\n      getY = toInt(PartsOffsetData[partId][2][3]);\n      break;\n    case 11:\n      getX = toInt(PartsOffsetData[partId][2][0]);\n      getY = toInt(PartsOffsetData[partId][2][1]);\n      break;\n    default:\n      break;\n  }\n}\nvoid MapSystem::MapSetPool(int index, int id, int type) {\n  MapPool[index].id = id;\n  MapPool[index].type = type;\n}\nvoid MapSystem::MapResetPoolAll(int arg1) {\n  for (int i = 0; i < 20; ++i) {\n    MapPool[i].id = 0xff;\n    MapPool[i].type = 0xff;\n    MapPool[i].button.Enabled = false;\n    MapPool[i].button.Id = 0xff;\n    MapPool[i].button.HasFocus = false;\n    MapPool[i].button.Bounds = {0, 0, 0, 0};\n  }\n  for (int i = 0; i < 40; ++i) {\n    MapPoolDisp[i].state = Hidden;\n    MapPoolDisp[i].fadeAnim.DurationIn = FadeAnimationDuration;\n    MapPoolDisp[i].fadeAnim.DurationOut = FadeAnimationDuration;\n    MapPoolDisp[i].fadeAnim.SkipOnSkipMode = true;\n    MapPoolDisp[i].angle = 0;\n  }\n}\nbool MapSystem::MapFadeEndChk_Wait() {\n  for (int i = 0; i < MapPartsMax; ++i) {\n    if (MapPartsDisp[i].state == Showing || MapPartsDisp[i].state == Hiding)\n      return false;\n    if (MapPartsDisp[i].delay != 0) return false;\n  }\n  return true;\n}\nvoid MapSystem::MapPoolShuffle(int param_1) {\n  auto start = MapPool.begin() + param_1 * 10;\n  auto end = start;\n\n  // Find the end of the range to shuffle\n  while (end != MapPool.end() && end->id != 255) {\n    ++end;\n  }\n\n  std::random_device rd;\n  std::mt19937 g(rd());\n\n  // Why reinvent the wheel\n  std::shuffle(start, end, g);\n}\n\nvoid MapSystem::MapPoolSetDisp(int arg1, int arg2) {}\nvoid MapSystem::MapPoolSetHide(int arg1, int arg2) {}\nvoid MapSystem::MapPoolSetFadein(int unused, int poolIdx) {\n  // arg1 always 0 for cclcc?\n\n  int id = MapPool[poolIdx].id;\n  if (id != 0xff) {\n    if (MapPoolDisp[poolIdx * 2].state == Hidden) {\n      MapPoolDisp[poolIdx * 2].state = Showing;\n      MapPoolDisp[poolIdx * 2].fadeAnim.Progress = 0;\n      MapPoolDisp[poolIdx * 2].delay = 0;\n      MapPoolDisp[poolIdx * 2].angle = CALCrnd(3640) - 1820;\n    }\n    int poolPinIndex = poolIdx * 2 + 1;\n    if (MapPhotoIdMap[MapPool[poolIdx].id][MapPool[poolIdx].type] < 1000) {\n      if (MapPoolDisp[poolPinIndex].state == Hidden) {\n        MapPoolDisp[poolPinIndex].state = Showing;\n        MapPoolDisp[poolPinIndex].fadeAnim.Progress = 0;\n        MapPoolDisp[poolPinIndex].delay = 16;\n        MapPoolDisp[poolPinIndex].pinId = CALCrnd(12);\n      }\n    } else {\n      MapPoolDisp[poolPinIndex].state = Hidden;\n      MapPoolDisp[poolPinIndex].delay = 0;\n      MapPoolDisp[poolPinIndex].fadeAnim.Progress = 0;\n    }\n  }\n}\nvoid MapSystem::MapPoolSetFadeout(int unused, int poolIdx) {\n  if (MapPoolDisp[poolIdx * 2].state == Shown ||\n      MapPoolDisp[poolIdx * 2].state == Showing) {\n    MapPoolDisp[poolIdx * 2].delay = 0x10;\n    MapPoolDisp[poolIdx * 2].state = Hiding;\n    MapPoolDisp[poolIdx * 2].fadeAnim.Progress = 1;\n  }\n  int poolPinIndex = poolIdx * 2 | 1;\n  if (MapPoolDisp[poolPinIndex].state == Shown ||\n      MapPoolDisp[poolPinIndex].state == Showing) {\n    MapPoolDisp[poolPinIndex].delay = 0;\n    MapPoolDisp[poolPinIndex].state = Hiding;\n    MapPoolDisp[poolPinIndex].fadeAnim.Progress = 1;\n  }\n  return;\n}\n\nvoid MapSystem::HandlePoolUpDownNav(int maxPoolRow, int poolType, bool isUp) {\n  if (HoverMapPoolIdx == 0xff) {\n    for (int i = 0; i < 10; ++i) {\n      if (MapPool[i].id != 0xff && MapPoolDisp[i * 2].state == Shown) {\n        HoverMapPoolIdx = i;\n        return;\n      }\n    }\n  } else {\n    int tmpPoolIdx;\n    do {\n      if (poolType == 3) {\n        HoverMapPoolIdx =\n            (HoverMapPoolIdx < 2) ? HoverMapPoolIdx + 2 : HoverMapPoolIdx - 2;\n      } else {\n        if (isUp)\n          HoverMapPoolIdx =\n              (HoverMapPoolIdx < 3) ? HoverMapPoolIdx + 6 : HoverMapPoolIdx - 3;\n        else\n          HoverMapPoolIdx =\n              (HoverMapPoolIdx < 6) ? HoverMapPoolIdx + 3 : HoverMapPoolIdx - 6;\n        if (MapPoolDisp[18].state == Shown && MapPool[9].id != 0xff) {\n          HoverMapPoolIdx = 9;\n          return;\n        }\n      }\n      if (MapPool[HoverMapPoolIdx].id != 0xff &&\n          MapPoolDisp[HoverMapPoolIdx * 2].state == Shown) {\n        return;\n      }\n      int colNum = HoverMapPoolIdx % maxPoolRow;\n      int rowNum = HoverMapPoolIdx / maxPoolRow;\n      tmpPoolIdx = rowNum * maxPoolRow;\n\n      if (colNum >= 0 && colNum <= 2) {\n        constexpr int offsets[3][2] = {{1, 2}, {0, 2}, {1, 0}};\n\n        for (int i = 0; i < 2; ++i) {\n          if ((MapPool[tmpPoolIdx + offsets[colNum][i]].id != 0xff &&\n               MapPoolDisp[(tmpPoolIdx + offsets[colNum][i]) * 2].state ==\n                   Shown)) {\n            tmpPoolIdx += offsets[colNum][i];\n            break;\n          }\n          if (colNum < 2 && poolType == 3) break;\n        }\n      }\n    } while (MapPool[tmpPoolIdx].id == 0xff ||\n             MapPoolDisp[tmpPoolIdx * 2].state != Shown);\n    HoverMapPoolIdx = tmpPoolIdx;\n  }\n}\n\nvoid MapSystem::HandlePoolLeftRightNav(int maxPoolRow, int poolType,\n                                       bool isLeft) {\n  if (HoverMapPoolIdx == 0xff) {\n    for (int i = 0; i < 10; ++i) {\n      if (MapPool[i].id != 0xff && MapPoolDisp[i * 2].state == Shown) {\n        HoverMapPoolIdx = i;\n        return;\n      }\n    }\n  } else {\n    int mapId = HoverMapPoolIdx;\n    do {\n      if (poolType == 3) {\n        if (HoverMapPoolIdx != 3)\n          mapId = (HoverMapPoolIdx == 0 || HoverMapPoolIdx == 2)\n                      ? HoverMapPoolIdx + 1\n                      : HoverMapPoolIdx - 1;\n        else\n          mapId = 2;\n      } else {\n        if (HoverMapPoolIdx == 9) {\n          mapId = (isLeft) ? 6 : 8;\n        } else {\n          if (isLeft) {\n            if (HoverMapPoolIdx % 3 == 0) {\n              mapId = HoverMapPoolIdx + 2;\n            } else {\n              mapId = HoverMapPoolIdx - 1;\n            }\n          } else {\n            if (HoverMapPoolIdx % 3 == 2) {\n              mapId = HoverMapPoolIdx - 2;\n            } else {\n              mapId = HoverMapPoolIdx + 1;\n            }\n          }\n        }\n      }\n      HoverMapPoolIdx = mapId;\n      if (MapPool[HoverMapPoolIdx].id != 0xff &&\n          MapPoolDisp[HoverMapPoolIdx * 2].state == Shown)\n        break;\n\n      int rowNum = mapId / maxPoolRow;\n\n      if (rowNum >= 0 && rowNum <= 2) {\n        int offsets[3][2] = {{maxPoolRow, 2 * maxPoolRow},\n                             {-maxPoolRow, maxPoolRow},\n                             {-maxPoolRow, -2 * maxPoolRow}};\n\n        for (int i = 0; i < 2; ++i) {\n          mapId = HoverMapPoolIdx + offsets[rowNum][i];\n\n          if (MapPool[mapId].id != 0xff &&\n              MapPoolDisp[mapId * 2].state == Shown) {\n            break;\n          }\n        }\n\n        if ((rowNum == 2 && HoverMapPoolIdx == maxPoolRow * 2) ||\n            (rowNum == 1 && HoverMapPoolIdx == 3) ||\n            (rowNum == 0 && HoverMapPoolIdx == 0)) {\n          if (MapPoolDisp[18].state == Shown && MapPool[9].id != 0xff) {\n            mapId = 9;\n            break;\n          }\n        }\n      }\n    } while (MapPool[mapId].id == 0xff ||\n             MapPoolDisp[mapId * 2].state != Shown);\n    HoverMapPoolIdx = mapId;\n  }\n}\n\nbool MapSystem::MapPlayerPhotoSelect(int unused) {\n  ScrWork[6500] = 2;\n  int oldHover = HoverMapPoolIdx;\n  if (unused == 0) {\n    int poolType = MapPool[0].type;\n    int maxPoolRow =\n        (poolType == 3) ? 2 : 3;  // 3 per row for photo, 2 for articles\n\n    if (Impacto::Input::CurrentInputDevice == Input::Device::Mouse) {\n      for (size_t i = 0; i < MapPool.size(); i++) {\n        if (MapPool[i].id != 0xff && MapPool[i].button.Enabled) {\n          MapPool[i].button.UpdateInput(0.0f);\n          MapPool[i].button.Update(0.0f);\n          if (MapPool[i].button.Hovered) {\n            HoverMapPoolIdx = (int)i;\n          }\n        }\n      }\n    }\n\n    if (PADinputButtonWentDown & PAD1UP) {\n      HandlePoolUpDownNav(maxPoolRow, poolType, true);\n    } else if (PADinputButtonWentDown & PAD1DOWN) {\n      HandlePoolUpDownNav(maxPoolRow, poolType, false);\n    } else if (PADinputButtonWentDown & PAD1LEFT) {\n      HandlePoolLeftRightNav(maxPoolRow, poolType, true);\n    } else if (PADinputButtonWentDown & PAD1RIGHT) {\n      HandlePoolLeftRightNav(maxPoolRow, poolType, false);\n    } else if (PADinputButtonWentDown & PAD1A && (HoverMapPoolIdx != 0xff) &&\n               (MapPoolCurCt[HoverMapPoolIdx] == 16)) {\n      SelectedMapPoolIdx = HoverMapPoolIdx;\n    }\n\n    if (oldHover != HoverMapPoolIdx) {\n      if (oldHover != 0xff) {\n        MapPool[oldHover].button.HasFocus = false;\n      }\n      if (HoverMapPoolIdx != 0xff) {\n        MapPool[HoverMapPoolIdx].button.HasFocus = true;\n      }\n    }\n\n    if (SelectedMapPoolIdx != 0xff) {\n      ScrWork[6367] = SelectedMapPoolIdx;\n      ScrWork[6368] = MapPool[SelectedMapPoolIdx].id;\n      ScrWork[6381] = MapPool[SelectedMapPoolIdx].type;\n      ScrWork[6500] = 0;\n      MapPool[SelectedMapPoolIdx].button.HasFocus = false;\n      HoverMapPoolIdx = 0xff;\n      SelectedMapPoolIdx = 0xff;\n      return true;\n    }\n  } else {\n    ImpLogSlow(LogLevel::Debug, LogChannel::VMStub,\n               \"MapPlayerPhotoSelect: arg1 != 0\\n\");\n  }\n  return false;\n}\n\nvoid MapSystem::MapResetPool(int poolIdx) {\n  MapPool[poolIdx].id = 0xff;\n  MapPool[poolIdx].type = 0xff;\n  MapPool[poolIdx].button.Enabled = false;\n  MapPool[poolIdx].button.Id = 0xff;\n  MapPool[poolIdx].button.HasFocus = false;\n  MapPool[poolIdx].button.Bounds = {0, 0, 0, 0};\n}\n\nvoid getMapPos(float newSize, float newX, float newY, float& setX, float& setY,\n               float& setSize, float scaleX, float scaleY) {\n  setSize = (newSize < 100) ? 100 : (newSize > 800) ? 800 : newSize;\n  float mapWidth = MapBgSprite.Sheet.DesignWidth * 100.0f / setSize;\n  float mapHeight = MapBgSprite.Sheet.DesignHeight * 100.0f / setSize;\n  float offsetMapX = newX - mapWidth * 0.5f;\n  float offsetMapY = newY - mapHeight * 0.5f;\n\n  if (!GetFlag(2805)) {\n    if (offsetMapX > MapBgSprite.Sheet.DesignWidth - mapWidth)\n      offsetMapX = MapBgSprite.Sheet.DesignWidth - mapWidth;\n\n    if (offsetMapX < 0) offsetMapX = 0;\n\n    if (offsetMapY > MapBgSprite.Sheet.DesignHeight - mapHeight)\n      offsetMapY = MapBgSprite.Sheet.DesignHeight - mapHeight;\n\n    if (offsetMapY < 0) offsetMapY = 0;\n  }\n\n  setX = mapWidth * scaleX + offsetMapX;\n  setY = mapHeight * scaleY + offsetMapY;\n}\n\nvoid MapSystem::MapSetGroupEx(int index, int type, int mappedId) {\n  if (index >= 40) return;\n  switch (type) {\n    case 0:\n      MapGroup[index].groupId1 = mappedId;\n      break;\n    case 1:\n      MapGroup[index].groupId2 = mappedId;\n      break;\n    case 2:\n      MapGroup[index].groupId3 = mappedId;\n      break;\n  }\n}\nvoid MapSystem::MapZoomInit(int mapX, int mapY, int size) {\n  MapPosTransitions[MapZoom].End = {toFlt(mapX) * 1920.0f / 1280.0f,\n                                    toFlt(mapY) * 1080.0f / 720.0f,\n                                    toFlt(size * 4)};\n\n  float zoomSize = 0.0f;\n  getMapPos(toFlt(ScrWork[6362]), toFlt(ScrWork[6363]), toFlt(ScrWork[6364]),\n            MapPosTransitions[MapZoom].Start.x,\n            MapPosTransitions[MapZoom].Start.y, zoomSize, toFlt(mapX) / 1280.0f,\n            toFlt(mapY) / 720.0f);\n\n  MapPosTransitions[MapZoom].Start.size = zoomSize * 4.0f;\n  MapPosTransitions[MapZoom].Current = MapPosTransitions[MapZoom].Start;\n  int steps = 0;\n  if (MapPosTransitions[MapZoom].Current.size <\n      MapPosTransitions[MapZoom].End.size) {\n    for (int ticks = toInt(MapPosTransitions[MapZoom].Current.size);\n         ticks < MapPosTransitions[MapZoom].End.size; ticks = ticks / 400 + 1) {\n      steps = steps + 1;\n    }\n  } else if (MapPosTransitions[MapZoom].Current.size >\n             MapPosTransitions[MapZoom].End.size) {\n    for (int ticks = toInt(MapPosTransitions[MapZoom].Current.size);\n         ticks < MapPosTransitions[MapZoom].End.size;\n         ticks = (ticks - ticks / 400) - 1) {\n      steps = steps + 1;\n    }\n  }\n\n  MapZoomCtAcc.fill(0);\n\n  for (int i = 6; i > 0; --i) {\n    int uVar10 = 0;\n    if (i != 0) {\n      uVar10 = 3 * i * (i + 1);  // Sum of first i multiples of 6\n    }\n\n    if (uVar10 <= steps) {\n      std::fill(MapZoomCtAcc.begin(), MapZoomCtAcc.begin() + i, 6);\n      int ivar9 = (steps - uVar10) / (i + 1);\n      MapZoomCtAcc[i] = ivar9;\n      steps = (steps - uVar10) - (ivar9 * i + ivar9);\n      if (steps != 0) {\n        MapZoomCtAcc[steps - 1]++;\n      }\n      return;\n    };\n  }\n  MapZoomCtAcc[0] = steps;\n  return;\n}\n\nbool MapSystem::MapZoomMain() {\n  int max = (GetFlag(SF_MESALLSKIP) == 0) ? 1 : 4;\n\n  int zoomCounter = 0;\n  for (int i = 0; i < max; i++) {\n    int j = 0;\n    while (j < 13 && MapZoomCtAcc[j] != 0) {\n      j++;\n    }\n    if (j != 13) {\n      MapZoomCtAcc[j] -= 1;\n      int inc = (j + 1 < 8) ? j + 1 : 13 - j;\n      zoomCounter += inc;\n    }\n  }\n  if (MapPosTransitions[MapZoom].Current.size <\n      MapPosTransitions[MapZoom].End.size) {\n    for (int i = 0; i < zoomCounter; ++i) {\n      MapPosTransitions[MapZoom].Current.size =\n          MapPosTransitions[MapZoom].Current.size + 1 +\n          MapPosTransitions[MapZoom].Current.size / 400;\n    }\n    if (MapPosTransitions[MapZoom].Current.size >\n        MapPosTransitions[MapZoom].End.size)\n      MapPosTransitions[MapZoom].Current.size =\n          MapPosTransitions[MapZoom].End.size;\n  } else if (MapPosTransitions[MapZoom].Current.size >\n             MapPosTransitions[MapZoom].End.size) {\n    for (int i = 0; i < zoomCounter; ++i) {\n      MapPosTransitions[MapZoom].Current.size =\n          (MapPosTransitions[MapZoom].Current.size - 1) -\n          MapPosTransitions[MapZoom].Current.size / 400;\n    }\n    if (MapPosTransitions[MapZoom].End.size >\n        MapPosTransitions[MapZoom].Current.size)\n      MapPosTransitions[MapZoom].Current.size =\n          MapPosTransitions[MapZoom].End.size;\n  }\n  ScrWork[6362] = (toInt(MapPosTransitions[MapZoom].Current.size) + 5) / 4;\n  float clampedSize = (ScrWork[6362] < 100)   ? 100.0f\n                      : (ScrWork[6362] > 800) ? 800.0f\n                                              : ScrWork[6362];\n  float newMapPosX = MapPosTransitions[MapZoom].Current.x -\n                     (MapPosTransitions[MapZoom].End.x / 1920.0f + -0.5f) *\n                         (MapBgSprite.Sheet.DesignWidth * 100.0f) / clampedSize;\n\n  float newMapPosY = MapPosTransitions[MapZoom].Current.y -\n                     (MapPosTransitions[MapZoom].End.y / 1080.0f + -0.5f) *\n                         (MapBgSprite.Sheet.DesignHeight * 100.0f) /\n                         clampedSize;\n\n  ScrWork[6363] = static_cast<int>(newMapPosX);\n  ScrWork[6364] = static_cast<int>(newMapPosY);\n  return toInt(MapPosTransitions[MapZoom].End.size) ==\n         toInt(MapPosTransitions[MapZoom].Current.size);\n}\nvoid MapSystem::MapZoomInit2(int arg1, int arg2) {}\nbool MapSystem::MapZoomMain3() {\n  int max = (GetFlag(SF_MESALLSKIP) == 0) ? 1 : 4;\n\n  int zoomCounter = 0;\n  for (int i = 0; i < max; i++) {\n    int j =\n        static_cast<int>(std::find_if(MapZoomCtAcc.begin(), MapZoomCtAcc.end(),\n                                      [](int x) { return x != 0; }) -\n                         MapZoomCtAcc.begin());\n    if (j != 13) {\n      MapZoomCtAcc[j] -= 1;\n      int inc = (j + 1 < 8) ? j + 1 : 13 - j;\n      zoomCounter += inc;\n    }\n  }\n  int newZoomCt = MapZoomCt + zoomCounter;\n  if (MapZoomMode == 2 && MapZoomCt < 40) {\n    zoomCounter = (newZoomCt < 40) ? 0 : newZoomCt - 40;\n  }\n\n  MapZoomCt = newZoomCt;\n\n  if (MapZoomCt > MapZoomCtMax) {\n    MapZoomCt = MapZoomCtMax;\n  }\n\n  if (MapPosTransitions[MapZoom3].Start.size <\n      MapPosTransitions[MapZoom3].End.size) {\n    for (int i = 0; i < zoomCounter; ++i) {\n      MapPosTransitions[MapZoom3].Current.size +=\n          1 + MapPosTransitions[MapZoom3].Current.size / 1000;\n    }\n    if (MapPosTransitions[MapZoom3].Current.size >\n        MapPosTransitions[MapZoom3].End.size)\n      MapPosTransitions[MapZoom3].Current.size =\n          MapPosTransitions[MapZoom3].End.size;\n  } else if (MapPosTransitions[MapZoom3].Start.size >\n             MapPosTransitions[MapZoom3].End.size) {\n    for (int i = 0; i < zoomCounter; ++i) {\n      MapPosTransitions[MapZoom3].Current.size -=\n          1 + MapPosTransitions[MapZoom3].Current.size / 1000;\n    }\n    if (MapPosTransitions[MapZoom3].Current.size <\n        MapPosTransitions[MapZoom3].End.size)\n      MapPosTransitions[MapZoom3].Current.size =\n          MapPosTransitions[MapZoom3].End.size;\n  }\n\n  MapPosTransitions[MapZoom3].Current.x =\n      MapPosTransitions[MapZoom3].Start.x +\n      (MapPosTransitions[MapZoom3].End.x -\n       MapPosTransitions[MapZoom3].Start.x) *\n          ((float)MapZoomCt / MapZoomCtMax);\n\n  MapPosTransitions[MapZoom3].Current.y =\n      MapPosTransitions[MapZoom3].Start.y +\n      (MapPosTransitions[MapZoom3].End.y -\n       MapPosTransitions[MapZoom3].Start.y) *\n          ((float)MapZoomCt / MapZoomCtMax);\n\n  ScrWork[6362] =\n      static_cast<int>((MapPosTransitions[MapZoom3].Current.size + 5) / 10);\n  ScrWork[6363] = static_cast<int>(MapPosTransitions[MapZoom3].Current.x);\n  ScrWork[6364] = static_cast<int>(MapPosTransitions[MapZoom3].Current.y);\n  return MapZoomCt == MapZoomCtMax;\n}\nbool MapSystem::MapZoomInit3(int setMapX, int setMapY, int setMapSize,\n                             bool halfZoom) {\n  if (halfZoom) {\n    MapZoomMode = 1;\n  } else {\n    MapZoomMode = 0;\n  }\n  MapZoomCt = 0;\n  float zoomSize = 0.0f;\n  float mapX = 0.0f;\n  float mapY = 0.0f;\n  getMapPos(toFlt(ScrWork[6362]), toFlt(ScrWork[6363]), toFlt(ScrWork[6364]),\n            mapX, mapY, zoomSize, 0.5, 0.5);\n  MapPosTransitions[MapZoom3].Start.size = 10 * zoomSize;\n  MapPosTransitions[MapZoom3].Start.x = mapX;\n  MapPosTransitions[MapZoom3].Start.y = mapY;\n\n  if (setMapSize != 0) {\n    getMapPos(toFlt(setMapSize), toFlt(setMapX), toFlt(setMapY), mapX, mapY,\n              zoomSize, 0.5, 0.5);\n  } else {\n    getMapPos(zoomSize, toFlt(setMapX), toFlt(setMapY), mapX, mapY, zoomSize,\n              0.5, 0.5);\n  }\n\n  MapPosTransitions[MapZoom3].End.size = 10 * zoomSize;\n  MapPosTransitions[MapZoom3].End.x = mapX;\n  MapPosTransitions[MapZoom3].End.y = mapY;\n\n  MapPosTransitions[MapZoom3].Current.size =\n      MapPosTransitions[MapZoom3].Start.size;\n  MapPosTransitions[MapZoom3].Current.x = MapPosTransitions[MapZoom3].Start.x;\n  MapPosTransitions[MapZoom3].Current.y = MapPosTransitions[MapZoom3].Start.y;\n\n  if (MapPosTransitions[MapZoom3].End.x ==\n          MapPosTransitions[MapZoom3].Start.x &&\n      MapPosTransitions[MapZoom3].End.y ==\n          MapPosTransitions[MapZoom3].Start.y &&\n      MapPosTransitions[MapZoom3].End.size ==\n          MapPosTransitions[MapZoom3].Start.size) {\n    return false;\n  }\n\n  int steps = 0;\n  MapZoomCtMax = (halfZoom) ? 40 : 0;\n  if (setMapSize == 0) {\n    float mapPosDiag = sqrt((MapPosTransitions[MapZoom3].End.x -\n                             MapPosTransitions[MapZoom3].Start.x) *\n                                (MapPosTransitions[MapZoom3].End.x -\n                                 MapPosTransitions[MapZoom3].Start.x) +\n                            (MapPosTransitions[MapZoom3].End.y -\n                             MapPosTransitions[MapZoom3].Start.y) *\n                                (MapPosTransitions[MapZoom3].End.y -\n                                 MapPosTransitions[MapZoom3].Start.y));\n    float mapBGNewHeight = MapBgSprite.Sheet.DesignHeight * 100.0f / zoomSize;\n    float mapBGNewWidth = MapBgSprite.Sheet.DesignWidth * 100.0f / zoomSize;\n    float mapBGDiag =\n        sqrt(mapBGNewHeight * mapBGNewHeight + mapBGNewWidth * mapBGNewWidth);\n\n    steps = toInt(mapPosDiag / (mapBGDiag / 400.0f));\n    if (steps == 0) {\n      MapZoomCtMax = 1;\n      MapZoomCtAcc[0] = 1;\n      std::fill(MapZoomCtAcc.begin() + 1, MapZoomCtAcc.end(), 1);\n      return true;\n    }\n    MapZoomCtMax += steps;\n  } else if (MapPosTransitions[MapZoom3].Start.size <\n             MapPosTransitions[MapZoom3].End.size) {\n    for (int ticks = toInt(MapPosTransitions[MapZoom3].Start.size);\n         ticks < MapPosTransitions[MapZoom3].End.size;\n         ticks += ticks / 1000 + 1) {\n      steps = steps + 1;\n    }\n  } else if (MapPosTransitions[MapZoom3].Start.size >\n             MapPosTransitions[MapZoom3].End.size) {\n    for (int ticks = toInt(MapPosTransitions[MapZoom3].Start.size);\n         ticks > MapPosTransitions[MapZoom3].End.size;\n         ticks -= ticks / 1000 + 1) {\n      steps = steps + 1;\n    }\n  }\n\n  MapZoomCtMax += steps;\n\n  std::fill(MapZoomCtAcc.begin(), MapZoomCtAcc.end(), 0);\n\n  for (int i = 6; i > 0; --i) {\n    int sixSum = 0;\n    if (i != 0) {\n      sixSum = 3 * i * (i + 1);  // Sum of first i multiples of 6\n    }\n    if (!halfZoom) sixSum *= 2;\n    if (sixSum <= MapZoomCtMax) {\n      std::fill(MapZoomCtAcc.begin(), MapZoomCtAcc.begin() + i, 6);\n      if (!halfZoom)\n        std::fill(MapZoomCtAcc.end() - i - 1, MapZoomCtAcc.end(), 6);\n      int peakAcc = (MapZoomCtMax - sixSum) / (i + 1);\n      MapZoomCtAcc[i] = peakAcc;\n      int index = (MapZoomCtMax - sixSum) - peakAcc * (i + 1);\n      if (index != 0) {\n        MapZoomCtAcc[index - 1]++;\n      }\n      return true;\n    };\n  }\n  MapZoomCtAcc[0] = MapZoomCtMax;\n  return true;\n}\n\nbool MapSystem::MapMoveAnimeInit2(int setMapX, int setMapY,\n                                  int setTransitionSize) {\n  MapMoveMode = 0;\n  if (!setTransitionSize || ScrWork[6362] < setTransitionSize) {\n    setTransitionSize = ScrWork[6362];\n  }\n  getMapPos(toFlt(ScrWork[6362]), toFlt(ScrWork[6363]), toFlt(ScrWork[6364]),\n            MapPosTransitions[MapMove2].Start.x,\n            MapPosTransitions[MapMove2].Start.y,\n            MapPosTransitions[MapMove2].Start.size, 0.5f, 0.5f);\n  getMapPos(toFlt(ScrWork[6362]), toFlt(setMapX), toFlt(setMapY),\n            MapPosTransitions[MapMove2].End.x,\n            MapPosTransitions[MapMove2].End.y,\n            MapPosTransitions[MapMove2].End.size, 0.5, 0.5);\n  if (setTransitionSize == ScrWork[6362]) {\n    MapMoveMode = 99;\n    return MapZoomInit3(static_cast<int>(MapPosTransitions[MapMove2].End.x),\n                        static_cast<int>(MapPosTransitions[MapMove2].End.y), 0);\n  } else {\n    float MapMoveCenterPosX = (setMapX + ScrWork[6363]) * 0.5f;\n    float MapMoveCenterPosY = (setMapY + ScrWork[6364]) * 0.5f;\n    float MapMoveLimitSize = 0;\n    getMapPos(toFlt(setTransitionSize), MapMoveCenterPosX, MapMoveCenterPosY,\n              MapMoveCenterPosX, MapMoveCenterPosY, MapMoveLimitSize, 0.5, 0.5);\n    return MapZoomInit3(static_cast<int>(MapMoveCenterPosX),\n                        static_cast<int>(MapMoveCenterPosY),\n                        static_cast<int>(MapMoveLimitSize), true);\n  }\n}\n\nbool MapSystem::MapMoveAnimeMain2() {\n  bool ret = MapZoomMain3();\n  if (MapMoveMode != 0) {\n    return ret;\n  }\n  if (ret) {\n    if (MapMoveMode == 99) {\n      return true;\n    }\n    MapZoomInit3(static_cast<int>(MapPosTransitions[MapMove2].End.x),\n                 static_cast<int>(MapPosTransitions[MapMove2].End.y),\n                 static_cast<int>(MapPosTransitions[MapMove2].End.size), true);\n    for (int i = 0; i < 6; ++i) {\n      MapZoomCtAcc[MapZoomCtAcc.size() - i - 1] = MapZoomCtAcc[i];\n    }\n    std::fill(MapZoomCtAcc.begin(), MapZoomCtAcc.begin() + 6, 0);\n    MapMoveMode++;\n    MapZoomMode = 2;\n  }\n  return false;\n}\nvoid MapSystem::MapPlayerPotalSelectInit() {}\nbool MapSystem::MapPlayerPotalSelect() { return false; }\nvoid MapSystem::MapSystem_28() {}\nvoid MapSystem::MapDispPhoto(int id, int photoGroupId) {\n  int alpha = 256;\n  float shadowZoom = 1.0f, zoomMulti = 1.0f;\n  if (MapPartsDisp[id].state == Hiding && !MapPartsDisp[id].delay) {\n    zoomMulti = 16.0f - MapPartsDisp[id].fadeAnim.Progress * 60.0f *\n                            FadeAnimationDuration;\n    shadowZoom = (zoomMulti) / 24.0f + 1.0f;\n    zoomMulti = zoomMulti / 32.0f + 1.0f;\n    alpha = toInt(MapPartsDisp[id].fadeAnim.Progress * 60.0f *\n                  FadeAnimationDuration * 16);\n  } else if (MapPartsDisp[id].state == Showing) {\n    zoomMulti =\n        -MapPartsDisp[id].fadeAnim.Progress * 60.0f * FadeAnimationDuration;\n    shadowZoom = (zoomMulti) / 24.0f + 1.6666f;\n    zoomMulti = zoomMulti / 32.0f + 1.5f;\n    alpha = toInt(MapPartsDisp[id].fadeAnim.Progress * 60.0f *\n                  FadeAnimationDuration * 16);\n  }\n\n  float xOffset = toFlt(id);\n  float yOffset = toFlt(id);\n  int mappedId = id;\n  int index = MapPartsDisp[id].partId;\n  if (photoGroupId == 3) {\n    index = (index < 40) ? MapGroup[index].groupId3 : index;\n    xOffset = PartsOffsetData[MapPartsDisp[id].partId][1][2];\n    yOffset = PartsOffsetData[MapPartsDisp[id].partId][1][3];\n    mappedId = MapPhotoIdMap[index][2];\n  } else if (photoGroupId == 2) {\n    index = (index < 40) ? MapGroup[index].groupId2 : index;\n    xOffset = PartsOffsetData[MapPartsDisp[id].partId][1][0];\n    yOffset = PartsOffsetData[MapPartsDisp[id].partId][1][1];\n    mappedId = MapPhotoIdMap[index][1];\n  } else if (photoGroupId == 1) {\n    index = (index < 40) ? MapGroup[index].groupId1 : index;\n    xOffset = PartsOffsetData[MapPartsDisp[id].partId][0][2];\n    yOffset = PartsOffsetData[MapPartsDisp[id].partId][0][3];\n    mappedId = MapPhotoIdMap[index][0];\n  }\n\n  if (mappedId == 0xff) {\n    return;\n  }\n  mappedId = (mappedId < 1000) ? mappedId : mappedId - 1000;\n\n  const float scaledFactor = 1080.0f / MapBGHeight;\n  zoomMulti *= scaledFactor;\n  shadowZoom *= scaledFactor;\n\n  Sprite displayedSprite = MapPartsPhotoSprites[mappedId];\n  xOffset = xOffset - 16.0f - 82.0f;\n  yOffset = yOffset - 10.0f;\n\n  float xMapEdge = MapPosX + MapBGWidth;\n  float yMapEdge = MapPosY + MapBGHeight;\n\n  if (MapPosX <= xOffset + displayedSprite.Bounds.Width &&\n      MapPosY <= yOffset + displayedSprite.Bounds.Height &&\n      xOffset <= xMapEdge && yOffset <= yMapEdge) {\n    float angle =\n        toFlt(MapPartsDisp[id].angle * 2.0 * std::numbers::pi / 65536.0);\n\n    xOffset = xOffset - MapPosX;\n    yOffset = yOffset - MapPosY;\n\n    float scaledPosOffsetX = (xOffset + 82) * scaledFactor;\n    float scaledPosOffsetY = (yOffset + 10) * scaledFactor;\n\n    // Shadow\n    const glm::vec2 shadowPos = {(xOffset + 82 + 3) * scaledFactor - 82,\n                                 (yOffset + 10 + 3) * scaledFactor - 10};\n    const glm::mat4 shadowTransformation = TransformationMatrix(\n        {82, 10}, {shadowZoom, shadowZoom}, {82, 10}, angle, shadowPos);\n    Renderer->DrawSprite(displayedSprite, shadowTransformation,\n                         {0.0f, 0.0f, 0.0f, (alpha / 2) / 256.0f});\n\n    // Photo\n    const glm::mat4 photoTransformation =\n        TransformationMatrix({82, 10}, {zoomMulti, zoomMulti}, {82, 10}, angle,\n                             {scaledPosOffsetX - 82, scaledPosOffsetY - 10});\n    Renderer->DrawSprite(displayedSprite, photoTransformation,\n                         {1.0f, 1.0f, 1.0f, alpha / 256.0f});\n  }\n}\n\nvoid MapSystem::MapPoolDispPhoto(int poolId) {\n  int mappedMapPoolId = MapPhotoIdMap[MapPool[poolId].id][MapPool[poolId].type];\n  if (mappedMapPoolId == 0xff) return;\n\n  Sprite displayedSprite;\n  float xOffset;\n  float yOffset;\n\n  // Display Photo\n  if (mappedMapPoolId < 1000) {\n    displayedSprite = MapPartsPhotoSprites[mappedMapPoolId];\n    xOffset = MapPoolPosOffsets[poolId][0];\n    yOffset = MapPoolPosOffsets[poolId][1];\n  }\n  // Display Article\n  else {\n    displayedSprite = MapPartsArticleSprites[mappedMapPoolId - 1000];\n    xOffset = MapPoolArticleOffsets[poolId][0];\n    yOffset = MapPoolArticleOffsets[poolId][1];\n  }\n\n  xOffset = xOffset - 16.0f - 82.0f;\n  yOffset = yOffset - 10.0f;\n\n  int alpha = 256;\n  float shadowZoom = 1.0f, zoomMulti = 1.0f;\n  if (MapPoolDisp[poolId * 2].state == Hiding &&\n      !MapPoolDisp[poolId * 2].delay) {\n    zoomMulti = 16.0f - MapPoolDisp[poolId * 2].fadeAnim.Progress * 60.0f *\n                            FadeAnimationDuration;\n    shadowZoom = (zoomMulti) / 24.0f + 1.0f;\n    zoomMulti = zoomMulti / 32.0f + 1.0f;\n    alpha = toInt(MapPoolDisp[poolId * 2].fadeAnim.Progress * 60.0f *\n                  FadeAnimationDuration * 16);\n  } else if (MapPoolDisp[poolId * 2].state == Showing) {\n    zoomMulti = -MapPoolDisp[poolId * 2].fadeAnim.Progress * 60.0f *\n                FadeAnimationDuration;\n    shadowZoom = (zoomMulti) / 24.0f + 1.6666f;\n    zoomMulti = zoomMulti / 32.0f + 1.5f;\n    alpha = toInt(MapPoolDisp[poolId * 2].fadeAnim.Progress * 60.0f *\n                  FadeAnimationDuration * 16);\n  }\n  float scaledFactor = 1080.0f / MapBGHeight;\n  zoomMulti *= scaledFactor;\n  shadowZoom *= scaledFactor;\n  float xMapEdge = MapPosX + MapBGWidth;\n  float yMapEdge = MapPosY + MapBGHeight;\n\n  if (MapPosX <= xOffset + displayedSprite.Bounds.Width &&\n      MapPosY <= yOffset + displayedSprite.Bounds.Height &&\n      xOffset <= xMapEdge && yOffset <= yMapEdge) {\n    float angle =\n        toFlt(MapPoolDisp[poolId * 2].angle * 2.0 * std::numbers::pi / 65536.0);\n\n    xOffset = xOffset - MapPosX;\n    yOffset = yOffset - MapPosY;\n\n    float scaledPosOffsetX = (xOffset + 82) * scaledFactor;\n    float scaledPosOffsetY = (yOffset + 10) * scaledFactor;\n\n    if (MapPoolCurCt[poolId] != 0) {\n      float selectedTagXOffset =\n          (MapPoolCurCt[poolId] * 50.0f / 16.0f + 78.0f) * 2;\n\n      // HoverTag Tag\n      const glm::vec2 origin = {selectedTagXOffset, 11};\n      const glm::mat4 transformation = TransformationMatrix(\n          origin, glm::vec2(zoomMulti / 2.0f), origin, angle,\n          {scaledPosOffsetX - selectedTagXOffset, scaledPosOffsetY - 18});\n      Renderer->DrawSprite(SelectedMapPoolTagSprite, transformation,\n                           {1.0f, 1.0f, 1.0f, alpha / 256.0f});\n    }\n    // Shadow\n    const glm::mat4 shadowTransformation =\n        TransformationMatrix({82, 10}, glm::vec2(shadowZoom), {82, 10}, angle,\n                             {(xOffset + 82 + 3) * scaledFactor - 82,\n                              (yOffset + 10 + 3) * scaledFactor - 10});\n    Renderer->DrawSprite(displayedSprite, shadowTransformation,\n                         {0.0f, 0.0f, 0.0f, (alpha >> 1) / 256.0f});\n    // Photo\n    const glm::mat4 photoTransformation =\n        TransformationMatrix({82, 10}, glm::vec2(zoomMulti), {82, 10}, angle,\n                             {scaledPosOffsetX - 82, scaledPosOffsetY - 10});\n    Renderer->DrawSprite(displayedSprite, photoTransformation,\n                         {1.0f, 1.0f, 1.0f, alpha / 256.0f});\n    if (!MapPool[poolId].button.Enabled) {\n      MapPool[poolId].button.Enabled = true;\n      MapPool[poolId].button.Id = poolId;\n      MapPool[poolId].button.OnClickHandler = [this](Button* button) {\n        SelectedMapPoolIdx = button->Id;\n      };\n    }\n\n    MapPool[poolId].button.Bounds = {\n        zoomMulti * displayedSprite.Bounds.Width * -0.25f +\n            (scaledPosOffsetX - 82),\n        scaledPosOffsetY - 10, zoomMulti * displayedSprite.Bounds.Width,\n        zoomMulti * displayedSprite.Bounds.Height};\n  }\n}\n\nvoid MapSystem::MapPoolDispPin(int poolId) {\n  int poolDispIndex = poolId * 2 + 1;\n  Sprite displayedSprite = MapPartsPinSprites[MapPoolDisp[poolDispIndex].pinId];\n\n  int alpha = 256;\n  float zoomMulti = 1.0f;\n  if (MapPoolDisp[poolDispIndex].state == Hiding &&\n      !MapPoolDisp[poolDispIndex].delay) {\n    zoomMulti = 16.0f - MapPoolDisp[poolDispIndex].fadeAnim.Progress * 60.0f *\n                            FadeAnimationDuration;\n    zoomMulti = zoomMulti / 32.0f + 1.0f;\n    alpha = toInt(MapPoolDisp[poolDispIndex].fadeAnim.Progress * 60.0f *\n                  FadeAnimationDuration * 16);\n  } else if (MapPoolDisp[poolDispIndex].state == Showing) {\n    zoomMulti = -MapPoolDisp[poolDispIndex].fadeAnim.Progress * 60.0f *\n                FadeAnimationDuration;\n    zoomMulti = zoomMulti / 32.0f + 1.5f;\n    alpha = toInt(MapPoolDisp[poolDispIndex].fadeAnim.Progress * 60.0f *\n                  FadeAnimationDuration * 16);\n  }\n  float scaledFactor = 1080.0f / MapBGHeight;\n  zoomMulti *= scaledFactor;\n  float xMapEdge = MapPosX + MapBGWidth;\n  float yMapEdge = MapPosY + MapBGHeight;\n\n  float xOffset = MapPoolPosOffsets[poolId][0] - 16.0f;\n  float yOffset = MapPoolPosOffsets[poolId][1] - 29.0f;\n\n  if (MapPosX <= xOffset + displayedSprite.Bounds.Width &&\n      MapPosY <= yOffset + displayedSprite.Bounds.Height &&\n      xOffset <= xMapEdge && yOffset <= yMapEdge) {\n    xOffset = xOffset - MapPosX;\n    yOffset = yOffset - MapPosY;\n\n    float scaledPosOffsetX = (xOffset + 16.0f) * scaledFactor;\n    float scaledPosOffsetY = (yOffset + 29.0f) * scaledFactor;\n\n    glm::vec4 color = RgbIntToFloat(Tints[poolId]);\n    color[3] = alpha / 256.0f;\n\n    const RectF dest =\n        displayedSprite.ScaledBounds()\n            .Scale({zoomMulti, zoomMulti}, {16.0f, 29.0f})\n            .Translate({scaledPosOffsetX - 16.0f, scaledPosOffsetY - 29.0f});\n    Renderer->DrawSprite(displayedSprite, dest, color);\n  }\n}\n\nvoid MapSystem::MapDispPin(int id) {\n  Sprite displayedSprite = MapPartsPinSprites[MapPartsDisp[id].angle];\n\n  int alpha = 256;\n  float zoomMulti = 1.0f;\n  if (MapPartsDisp[id].state == Hiding && !MapPartsDisp[id].delay) {\n    zoomMulti = 16.0f - MapPartsDisp[id].fadeAnim.Progress * 60.0f *\n                            FadeAnimationDuration;\n    zoomMulti = zoomMulti / 32.0f + 1.0f;\n    alpha = toInt(MapPartsDisp[id].fadeAnim.Progress * 60.0f *\n                  FadeAnimationDuration * 16);\n  } else if (MapPartsDisp[id].state == Showing) {\n    zoomMulti =\n        -MapPartsDisp[id].fadeAnim.Progress * 60.0f * FadeAnimationDuration;\n    zoomMulti = zoomMulti / 32.0f + 1.5f;\n    alpha = toInt(MapPartsDisp[id].fadeAnim.Progress * 60.0f *\n                  FadeAnimationDuration * 16);\n  }\n\n  float scaledFactor = 1080.0f / MapBGHeight;\n  zoomMulti *= scaledFactor;\n  float xMapEdge = MapPosX + MapBGWidth;\n  float yMapEdge = MapPosY + MapBGHeight;\n\n  int mappedId = MapPartsDisp[id].partId;\n  float xOffset = 0.0;\n  float yOffset = 0.0;\n  switch (MapPartsDisp[id].type) {\n    case 0:\n      xOffset = PartsOffsetData[mappedId][0][0];\n      yOffset = PartsOffsetData[mappedId][0][1];\n      break;\n    case 8:\n      xOffset = PartsOffsetData[mappedId][0][2];\n      yOffset = PartsOffsetData[mappedId][0][3];\n      break;\n    case 9:\n      xOffset = PartsOffsetData[mappedId][1][0];\n      yOffset = PartsOffsetData[mappedId][1][1];\n      break;\n    case 10:\n      xOffset = PartsOffsetData[mappedId][1][2];\n      yOffset = PartsOffsetData[mappedId][1][3];\n      break;\n    case 11:\n      xOffset = PartsOffsetData[mappedId][2][0];\n      yOffset = PartsOffsetData[mappedId][2][1];\n      break;\n    default:\n      break;\n  }\n  xOffset = xOffset - 16.0f;\n  yOffset = yOffset - 29.0f;\n\n  if (MapPosX <= xOffset + displayedSprite.Bounds.Width &&\n      MapPosY <= yOffset + displayedSprite.Bounds.Height &&\n      xOffset <= xMapEdge && yOffset <= yMapEdge) {\n    xOffset = xOffset - MapPosX;\n    yOffset = yOffset - MapPosY;\n\n    float scaledPosOffsetX = (xOffset + 16.0f) * scaledFactor;\n    float scaledPosOffsetY = (yOffset + 29.0f) * scaledFactor;\n\n    glm::vec4 color = RgbIntToFloat(Tints[mappedId + 27]);\n    color[3] = alpha / 256.0f;\n\n    const RectF dest =\n        displayedSprite.ScaledBounds()\n            .Scale({zoomMulti, zoomMulti}, {16, 29})\n            .Translate({scaledPosOffsetX - 16.0f, scaledPosOffsetY - 29.0f});\n    Renderer->DrawSprite(displayedSprite, dest, color);\n  }\n}\n\nvoid MapSystem::MapDispArticle(int id) {\n  int partId = MapPartsDisp[id].partId;\n\n  int alpha = 256;\n  float shadowZoom = 1.0f;\n  float zoomMulti = 1.0f;\n  if (MapPartsDisp[id].state == Hiding && !MapPartsDisp[id].delay) {\n    zoomMulti = 16.0f - MapPartsDisp[id].fadeAnim.Progress * 60.0f *\n                            FadeAnimationDuration;\n    shadowZoom = (zoomMulti) / 24.0f + 1.0f;\n    zoomMulti = zoomMulti / 32.0f + 1.0f;\n    alpha = toInt(MapPartsDisp[id].fadeAnim.Progress * 60.0f *\n                  FadeAnimationDuration * 16);\n  } else if (MapPartsDisp[id].state == Showing) {\n    zoomMulti =\n        -MapPartsDisp[id].fadeAnim.Progress * 60.0f * FadeAnimationDuration;\n    shadowZoom = (zoomMulti) / 24.0f + 1.6666f;\n    zoomMulti = zoomMulti / 32.0f + 1.5f;\n    alpha = toInt(MapPartsDisp[id].fadeAnim.Progress * 60.0f *\n                  FadeAnimationDuration * 16);\n  }\n\n  float xPartOffset = PartsOffsetData[partId][2][0] - 166;\n  float yPartOffset = PartsOffsetData[partId][2][1] - 16;\n\n  if (MapPosX <= xPartOffset + 354.0 && MapPosY <= yPartOffset + 247.0) {\n    float scaledFactor = 1080.0f / MapBGHeight;\n    float angle =\n        toFlt(MapPartsDisp[id].angle * 2 * std::numbers::pi / 65536.0);\n\n    xPartOffset = xPartOffset - MapPosX;\n    yPartOffset = yPartOffset - MapPosY;\n\n    float displayShadowPhotoX = (xPartOffset + 166 + 3) * scaledFactor - 166;\n    float displayShadowPhotoY = (yPartOffset + 16 + 3) * scaledFactor - 16;\n\n    float scaledPosOffsetX = (xPartOffset + 166) * scaledFactor;\n    float scaledPosOffsetY = (yPartOffset + 16) * scaledFactor;\n    float displayPhotoX = scaledPosOffsetX - 166;\n    float displayPhotoY = scaledPosOffsetY - 16;\n\n    // Shadow\n    const glm::mat4 shadowTransformation = TransformationMatrix(\n        {166.0f, 16.0f}, glm::vec2(shadowZoom * 0.5f * scaledFactor),\n        {166.0f, 16.0f}, angle, {displayShadowPhotoX, displayShadowPhotoY});\n    Renderer->DrawSprite(MapPartsArticleSprites[partId], shadowTransformation,\n                         {0.0f, 0.0f, 0.0f, (alpha >> 1) / 256.0f});\n    // Photo\n    const glm::mat4 photoTransformation = TransformationMatrix(\n        {166.0f, 16.0f}, glm::vec2(zoomMulti * 0.5f * scaledFactor),\n        {166.0f, 16.0f}, angle, {displayPhotoX, displayPhotoY});\n    Renderer->DrawSprite(MapPartsArticleSprites[partId], photoTransformation);\n  }\n}\n\nvoid MapSystem::MapDispTag(int id) {\n  Sprite displayedSprite = MapPartsTagSprites[MapPartsDisp[id].partId];\n\n  int alpha = 256;\n  float zoomMulti = 1.0f;\n  float shadowZoom = 1.0f;\n  if (MapPartsDisp[id].state == Hiding && !MapPartsDisp[id].delay) {\n    zoomMulti = 16.0f - MapPartsDisp[id].fadeAnim.Progress * 60.0f *\n                            FadeAnimationDuration;\n    shadowZoom = (zoomMulti) / 24.0f + 1.0f;\n    zoomMulti = zoomMulti / 32.0f + 1.0f;\n    alpha = toInt(MapPartsDisp[id].fadeAnim.Progress * 60.0f *\n                  FadeAnimationDuration * 16);\n  } else if (MapPartsDisp[id].state == Showing) {\n    zoomMulti =\n        -MapPartsDisp[id].fadeAnim.Progress * 60.0f * FadeAnimationDuration;\n    shadowZoom = (zoomMulti) / 24.0f + 1.6666f;\n    zoomMulti = zoomMulti / 32.0f + 1.5f;\n    alpha = toInt(MapPartsDisp[id].fadeAnim.Progress * 60.0f *\n                  FadeAnimationDuration * 16);\n  }\n  float scaledFactor = 1080.0f / MapBGHeight;\n  zoomMulti *= scaledFactor;\n  shadowZoom *= scaledFactor;\n  float xMapEdge = MapPosX + MapBGWidth;\n  float yMapEdge = MapPosY + MapBGHeight;\n\n  float xOffset = PartsOffsetData[MapPartsDisp[id].partId][2][2] - 47.0f;\n  float yOffset = PartsOffsetData[MapPartsDisp[id].partId][2][3] - 17.0f;\n\n  if (MapPosX <= xOffset + displayedSprite.Bounds.Width &&\n      MapPosY <= yOffset + displayedSprite.Bounds.Height &&\n      xOffset <= xMapEdge && yOffset <= yMapEdge) {\n    xOffset = xOffset - MapPosX;\n    yOffset = yOffset - MapPosY;\n    float angle =\n        toFlt(MapPartsDisp[id].angle * 2 * std::numbers::pi / 65536.0);\n\n    float shadowScaledPosOffsetX = (xOffset + 2.0f + 46.0f) * scaledFactor;\n    float shadowScaledPosOffsetY = (yOffset + 2.0f + 16.0f) * scaledFactor;\n    // Shadow\n    const glm::mat4 shadowTransformation = TransformationMatrix(\n        {46, 16}, glm::vec2(shadowZoom), {46, 16}, angle,\n        {shadowScaledPosOffsetX - 46.0f, shadowScaledPosOffsetY - 16.0f});\n    Renderer->DrawSprite(displayedSprite, shadowTransformation,\n                         {0.0f, 0.0f, 0.0f, (alpha >> 1) / 256.0f});\n\n    // Tag\n    const glm::vec2 scaledPosOffset =\n        glm::vec2(xOffset + 46.0f, yOffset + 16.0f) * scaledFactor;\n    const glm::mat4 tagTransformation =\n        TransformationMatrix({46, 16}, {zoomMulti, zoomMulti}, {46, 16}, angle,\n                             scaledPosOffset - glm::vec2(46.0f, 16.0f));\n    Renderer->DrawSprite(displayedSprite, tagTransformation,\n                         {1.0f, 1.0f, 1.0f, alpha / 256.0f});\n  }\n}\n\nvoid MapSystem::MapDispLine(int id, int partType) {\n  size_t index = static_cast<size_t>(MapPartsDisp[id].partId);\n  float xOffset1 = PartsOffsetData[index][0][2];\n  float yOffset1 = PartsOffsetData[index][0][3];\n  float xOffset2, yOffset2;\n  glm::vec4 color;\n  if (partType != 7 && index <= sizeof(PartsIdMap) / sizeof(int)) {\n    index = (partType == 6) ? PartsIdMap[index][0]\n            : (partType > 11 && partType < 15)\n                ? PartsIdMap[index][partType - 11]\n                : index;\n  } else if (index > sizeof(PartsIdMap) / sizeof(int)) {\n    index = 0;\n  }\n  if (partType == 7) {\n    xOffset2 = PartsOffsetData[index][0][0];\n    yOffset2 = PartsOffsetData[index][0][1];\n    color = RgbIntToFloat(LineColors2[MapPartsDisp[id].partId]);\n  } else {\n    xOffset2 = PartsOffsetData[index][0][2];\n    yOffset2 = PartsOffsetData[index][0][3];\n    color = RgbIntToFloat(LineColors1[MapPartsDisp[id].partId]);\n  }\n\n  float linePercent = 1.0f;\n  if (MapPartsDisp[id].state == Hiding && !MapPartsDisp[id].delay) {\n    linePercent = toFlt(MapPartsDisp[id].fadeAnim.Progress * 60.0f *\n                        FadeAnimationDuration) /\n                  MapPartsDisp[id].dist;\n  } else if (MapPartsDisp[id].state == Showing) {\n    linePercent = toFlt(MapPartsDisp[id].fadeAnim.Progress * 60.0f *\n                        FadeAnimationDuration) /\n                  MapPartsDisp[id].dist;\n  }\n  float scaledFactor = 1080.0f / MapBGHeight;\n\n  float xDist = xOffset2 - xOffset1;\n  float yDist = yOffset1 - yOffset2;\n  float dist = sqrt(xDist * xDist + yDist * yDist);\n\n  float xMapEdge = MapPosX + MapBGWidth;\n  float yMapEdge = MapPosY + MapBGHeight;\n  float angle = atan2(yDist, xDist);\n\n  if (MapPosX <= xOffset2 + 20 && MapPosY <= yOffset2 + 20 &&\n      xOffset2 <= xMapEdge && yOffset2 <= yMapEdge) {\n    float lineWidth = abs(dist) * linePercent;\n    glm::vec4 shadowTint = {0.0f, 0.0f, 0.0f, 0.5f};\n    float lineX2 = xOffset1 + cosf(angle) * dist * linePercent;\n    float lineY2 = yOffset1 - sinf(angle) * dist * linePercent;\n    float xPos = 0.5f * (xOffset1 + lineX2 - dist * linePercent) - MapPosX;\n    float yPos = (yOffset1 + lineY2) / 2.0f - MapPosY - 5.0f;\n    float yPosShadow = (xDist < 0 && yDist < 0)\n                           ? (yOffset1 + lineY2 - 5.0f) / 2.0f - MapPosY - 5.0f\n                           : (yOffset1 + lineY2 + 5.0f) / 2.0f - MapPosY - 5.0f;\n\n    Sprite shortenedLine = MapLine;\n    shortenedLine.Bounds.Width = lineWidth;\n\n    RectF lineShadowRect(xPos * scaledFactor, yPosShadow * scaledFactor,\n                         lineWidth * scaledFactor, 10.0f * scaledFactor);\n    Renderer->DrawSprite(shortenedLine,\n                         lineShadowRect.RotateAroundCenter(-angle), shadowTint);\n    RectF lineRect(xPos * scaledFactor, yPos * scaledFactor,\n                   lineWidth * scaledFactor, 10.0f * scaledFactor);\n    Renderer->DrawSprite(shortenedLine, lineRect.RotateAroundCenter(-angle),\n                         color);\n  }\n}\n\nvoid MapSystem::MapPartsSort() {\n  if (MapPartsMax == 0) return;\n  auto partsEnd = std::remove_if(\n      MapPartsDisp.begin(), MapPartsDisp.begin() + MapPartsMax,\n      [](MapPartsDispStruct& part) { return part.state == Hidden; });\n  for (auto it = partsEnd; it != MapPartsDisp.begin() + MapPartsMax; ++it) {\n    it->partId = 0xff;\n  }\n  MapPartsMax = static_cast<int>(partsEnd - MapPartsDisp.begin());\n}\n\nvoid MapSystem::MapSetPos(float dt) {\n  float mapScaler = (ScrWork[6362] < 100)   ? 100.0f\n                    : (ScrWork[6362] > 800) ? 800.0f\n                                            : toFlt(ScrWork[6362]);\n  float MapSheetWidth = MapBgSprite.Sheet.DesignWidth;\n  float MapSheetHeight = MapBgSprite.Sheet.DesignHeight;\n\n  MapBGWidth = MapBgSprite.Sheet.DesignWidth * 100.0f / mapScaler;\n  MapBGHeight = MapBgSprite.Sheet.DesignHeight * 100.0f / mapScaler;\n\n  MapPosX = ScrWork[6363] - MapBGWidth / 2;\n  MapPosY = ScrWork[6364] - MapBGHeight / 2;\n\n  if (!GetFlag(2805)) {\n    if (MapPosX > MapSheetWidth - MapBGWidth) {\n      MapPosX = MapSheetWidth - MapBGWidth;\n    }\n    if (MapPosY > MapSheetHeight - MapBGHeight) {\n      MapPosY = MapSheetHeight - MapBGHeight;\n    }\n    if (MapPosX < 0.0) {\n      MapPosX = 0.0;\n    }\n    if (MapPosY < 0.0) {\n      MapPosY = 0.0;\n    }\n  }\n\n  // MapScaleMode use??\n  MapFadeMain(dt);\n\n  for (size_t i = 0; i < MapPoolCurCt.size(); ++i) {\n    if (static_cast<int>(i) == HoverMapPoolIdx) {\n      MapPoolCurCt[i] += (MapPoolCurCt[i] >= 16) ? 0 : 2;\n    } else if (MapPoolCurCt[i]) {\n      MapPoolCurCt[i] -= 2;\n    }\n  }\n}\n\nvoid MapSystem::MapFadeMain(float dt) {\n  for (int i = 0; i < MapPartsMax; ++i) {\n    auto& partsDispElem = MapPartsDisp[i];\n    if (partsDispElem.state == Shown || partsDispElem.state == Hidden) {\n      continue;\n    }\n    if (!GetFlag(SF_MESALLSKIP) &&\n        (partsDispElem.delay == 0 || --partsDispElem.delay == 0)) {\n      if (partsDispElem.state == Showing) {\n        if (partsDispElem.fadeAnim.IsIn()) {\n          partsDispElem.state = Shown;\n        } else if (partsDispElem.fadeAnim.State == AnimationState::Stopped) {\n          if (partsDispElem.type == 0 ||\n              (partsDispElem.type >= 8 && partsDispElem.type <= 11)) {\n            Audio::PlayInGroup(Audio::ACG_SE, \"sysse\", 7, false, 0.0f);\n          }\n          partsDispElem.fadeAnim.StartIn(true);\n        }\n      } else if (partsDispElem.state == Hiding) {\n        if (partsDispElem.fadeAnim.IsOut()) {\n          partsDispElem.state = Hidden;\n        } else if (partsDispElem.fadeAnim.State == AnimationState::Stopped) {\n          if (partsDispElem.type == 0 ||\n              (partsDispElem.type >= 8 && partsDispElem.type <= 11)) {\n            Audio::PlayInGroup(Audio::ACG_SE, \"sysse\", 9, false, 0.0f);\n          }\n          partsDispElem.fadeAnim.StartOut(true);\n        }\n      }\n      partsDispElem.fadeAnim.Update(dt);\n\n    } else if (GetFlag(SF_MESALLSKIP)) {\n      partsDispElem.state = partsDispElem.state == Showing ? Shown : Hidden;\n      partsDispElem.delay = 0;\n    }\n  }\n  MapPartsSort();\n\n  for (size_t i = 0; i < MapPoolDisp.size(); ++i) {\n    auto& poolDispElem = MapPoolDisp[i];\n    if (poolDispElem.state == Shown || poolDispElem.state == Hidden) {\n      continue;\n    }\n    // MapFadeFl = 1;\n    if (GetFlag(SF_MESALLSKIP)) {\n      poolDispElem.state = poolDispElem.state == Showing ? Shown : Hidden;\n      poolDispElem.delay = 0;\n      continue;\n    }\n    if (poolDispElem.delay == 0 || --poolDispElem.delay == 0) {\n      if (poolDispElem.state == Showing) {\n        if (poolDispElem.fadeAnim.IsIn()) {\n          poolDispElem.state = Shown;\n        } else if (poolDispElem.fadeAnim.State == AnimationState::Stopped) {\n          if (i % 2 == 1) {\n            Audio::PlayInGroup(Audio::ACG_SE, \"sysse\", 7, false, 0.0f);\n          }\n          poolDispElem.fadeAnim.StartIn(true);\n        }\n\n      } else if (poolDispElem.state == Hiding) {\n        if (poolDispElem.fadeAnim.IsOut()) {\n          poolDispElem.state = Hidden;\n        } else if (poolDispElem.fadeAnim.State == AnimationState::Stopped) {\n          if (i % 2 == 1) {\n            Audio::PlayInGroup(Audio::ACG_SE, \"sysse\", 9, false, 0.0f);\n          }\n          poolDispElem.fadeAnim.StartOut(true);\n        }\n      }\n      poolDispElem.fadeAnim.Update(dt);\n    }\n  }\n}\n\nvoid MapSystem::Update(float dt) {\n  if (ScrWork[SW_MAP_ALPHA] && GetFlag(2800)) {\n    MapSetPos(dt);\n  }\n}\n\nvoid MapSystem::Render() {\n  if (!GetFlag(2800)) {\n    return;\n  }\n  // Render map bg\n  MapBgSprite.Bounds =\n      Rect(static_cast<int>(MapPosX), static_cast<int>(MapPosY),\n           static_cast<int>(MapBGWidth), static_cast<int>(MapBGHeight));\n  Renderer->DrawSprite(MapBgSprite, Rect(0, 0, 1920, 1080));\n\n  // Render map parts\n  for (int i = 0; i < MapPartsMax; ++i) {\n    if (MapPartsDisp[i].state == Shown ||\n        (MapPartsDisp[i].state == Showing && MapPartsDisp[i].delay == 0) ||\n        MapPartsDisp[i].state == Hiding) {\n      switch (MapPartsDisp[i].type) {\n        case 1:\n          MapDispPhoto(i, 1);\n          break;\n        case 2:\n          if (PartsOffsetData[MapPartsDisp[i].partId][1][0] ||\n              PartsOffsetData[MapPartsDisp[i].partId][1][1]) {\n            MapDispPhoto(i, 2);\n          }\n          break;\n        case 3:\n          if (PartsOffsetData[MapPartsDisp[i].partId][1][2] ||\n              PartsOffsetData[MapPartsDisp[i].partId][1][3]) {\n            MapDispPhoto(i, 3);\n          }\n          break;\n        case 4:\n          MapDispArticle(i);\n          break;\n        case 5:\n          MapDispTag(i);\n          break;\n        case 6:\n          if (MapPartsDisp[i].partId > 0 ||\n              PartsIdMap[MapPartsDisp[i].partId][0] != 0xff)\n            MapDispLine(i, 6);\n          break;\n        case 7:\n          MapDispLine(i, 7);\n          break;\n        case 12:\n          if (MapPartsDisp[i].partId > 0 ||\n              PartsIdMap[MapPartsDisp[i].partId][1] != 0xff)\n            MapDispLine(i, 12);\n          break;\n        case 13:\n          if (MapPartsDisp[i].partId > 0 ||\n              PartsIdMap[MapPartsDisp[i].partId][2] != 0xff)\n            MapDispLine(i, 13);\n          break;\n        case 14:\n          if (MapPartsDisp[i].partId > 0 ||\n              PartsIdMap[MapPartsDisp[i].partId][3] != 0xff)\n            MapDispLine(i, 14);\n          break;\n        default:\n          break;\n      }\n    }\n  }\n  for (int i = 0; i < MapPartsMax; ++i) {\n    if (MapPartsDisp[i].state == Shown ||\n        (MapPartsDisp[i].state == Showing && MapPartsDisp[i].delay == 0) ||\n        MapPartsDisp[i].state == Hiding) {\n      if (MapPartsDisp[i].type == 0 || MapPartsDisp[i].type == 8 ||\n          MapPartsDisp[i].type == 11 ||\n          (MapPartsDisp[i].type == 9 &&\n           (PartsOffsetData[MapPartsDisp[i].partId][1][0] ||\n            PartsOffsetData[MapPartsDisp[i].partId][1][1])) ||\n          (MapPartsDisp[i].type == 10 &&\n           (PartsOffsetData[MapPartsDisp[i].partId][1][2] ||\n            PartsOffsetData[MapPartsDisp[i].partId][1][3]))) {\n        MapDispPin(i);\n      }\n    }\n  }\n  // Render map pool\n  for (int i = 0; i < 20; ++i) {\n    if (MapPoolDisp[i * 2].state == Shown ||\n        MapPoolDisp[i * 2].state == Hiding ||\n        (MapPoolDisp[i * 2].state == Showing &&\n         MapPoolDisp[i * 2].delay == 0)) {\n      MapPoolDispPhoto(i);\n    }\n    if (MapPoolDisp[i * 2 + 1].state == Shown ||\n        MapPoolDisp[i * 2 + 1].state == Hiding ||\n        (MapPoolDisp[i * 2 + 1].state == Showing &&\n         MapPoolDisp[i * 2 + 1].delay == 0)) {\n      MapPoolDispPin(i);\n    }\n  }\n}\n}  // namespace CCLCC\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/cclcc/mapsystem.h",
    "content": "#pragma once\n#include \"../../animation.h\"\n#include \"../../ui/widgets/button.h\"\n#include <array>\n\nnamespace Impacto {\nnamespace UI {\nnamespace CCLCC {\nclass MapSystem {\n  enum DisplayState { Hidden, Hiding, Showing, Shown };\n\n public:\n  void MapInit();\n  int MapLoad(uint8_t* data);\n  int MapSave(uint8_t* data);\n  void MapSetFadein(int partId, int partType);\n  void MapSetGroup(int index, int mappedId1, int mappedId2, int mappedId3);\n  void MapSetFadeout(int partId, int partType);\n  void MapSetDisp(int partId, int partType);\n  void MapSetHide(int arg1, int arg2);\n  bool MapPoolFadeEndChk_Wait();\n  void MapMoveAnimeInit(int arg1, int arg2, int arg3);\n  bool MapMoveAnimeMain();\n  void MapGetPos(int partId, int partType, int& getX, int& getY);\n  void MapSetPool(int index, int id, int type);\n  void MapResetPoolAll(int arg1);\n  bool MapFadeEndChk_Wait();\n  void MapPoolShuffle(int param_1);\n  void MapPoolSetDisp(int arg1, int arg2);\n  void MapPoolSetHide(int arg1, int arg2);\n  void MapPoolSetFadein(int unused, int poolIdx);\n  void MapPoolSetFadeout(int unused, int poolIdx);\n  bool MapPlayerPhotoSelect(int unused);\n  void MapResetPool(int poolIdx);\n  void MapSetGroupEx(int index, int type, int mappedId);\n  void MapZoomInit(int mapX, int mapY, int size);\n  bool MapZoomMain();\n  void MapZoomInit2(int arg1, int arg2);\n  bool MapZoomMain3();\n  bool MapZoomInit3(int setMapX, int setMapY, int setMapSize,\n                    bool halfZoom = false);\n  bool MapMoveAnimeInit2(int setMapX, int setMapY, int setTransitionSize);\n  bool MapMoveAnimeMain2();\n  void MapPlayerPotalSelectInit();\n  bool MapPlayerPotalSelect();\n  void MapSystem_28();\n  void Update(float dt);\n  void Render();\n\n  struct MapPoolStruct {\n    int id;\n    int type;\n    Widgets::Button button;\n  };\n\n  struct MapGroupStruct {\n    int groupId1;\n    int groupId2;\n    int groupId3;\n  };\n\n  struct MapPoolDispStruct {\n    Animation fadeAnim;\n    DisplayState state;\n    int delay;\n    union {\n      int angle;\n      int pinId;\n    };\n  };\n\n  struct MapPartsDispStruct {\n    int partId;\n    int type;\n    Animation fadeAnim;\n    DisplayState state;\n    int delay;\n    int angle;\n    int dist;\n  };\n\n  struct MapPositionState {\n    float x;\n    float y;\n    float size;\n  };\n\n  struct MapPositionTransitions {\n    MapPositionState Start;\n    MapPositionState Current;\n    MapPositionState End;\n  };\n\n  static MapSystem& GetInstance() {\n    static MapSystem impl;\n    return impl;\n  }\n\n private:\n  enum MapPosEnum { MapZoom, MapZoom3, MapMove2, MapPosTransitionsMax };\n  void MapFadeMain(float dt);\n  void MapSetPos(float dt);\n  void MapDispPhoto(int id, int arg2);\n  void MapDispArticle(int id);\n  void MapDispTag(int id);\n  void MapDispLine(int id, int type);\n  void MapDispPin(int id);\n  void MapPartsSort();\n\n  void MapPoolDispPhoto(int poolId);\n  void MapPoolDispPin(int poolId);\n\n  void HandlePoolUpDownNav(int maxPoolRow, int poolType, bool isUp);\n  void HandlePoolLeftRightNav(int maxPoolRow, int poolType, bool isLeft);\n\n  void MapSetFadein_PhotoArticle(int arg1, int arg2);\n  void MapSetFadein_Line(int arg1, int arg2);\n\n  std::array<MapSystem::MapPoolStruct, 20> MapPool = {};\n  std::array<MapSystem::MapPoolDispStruct, 40> MapPoolDisp = {};\n\n  std::array<MapSystem::MapPartsDispStruct, 40> MapPartsDisp = {};\n  std::array<MapSystem::MapGroupStruct, 40> MapGroup = {};\n  int MapPartsMax = 0;\n\n  std::array<MapSystem::MapPositionTransitions, 4> MapPosTransitions;\n  std::array<int, 13> MapZoomCtAcc = {};\n  std::array<int, 20> MapPoolCurCt = {};\n\n  int MapZoomMode = 0;\n  int MapZoomCt = 0;\n  int MapZoomCtMax = 0;\n\n  int SelectedMapPoolIdx = 0xff;\n  int HoverMapPoolIdx = 0xff;\n  float MapBGWidth;\n  float MapBGHeight;\n  float MapPosX;\n  float MapPosY;\n  int MapMoveMode = 0;\n};\n\n}  // namespace CCLCC\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/cclcc/moviemenu.cpp",
    "content": "#include \"moviemenu.h\"\n\n#include <ranges>\n#include \"../../ui/ui.h\"\n#include \"../../audio/audiosystem.h\"\n#include \"../../profile/games/cclcc/librarymenu.h\"\n#include \"../../vm/interface/input.h\"\n\nnamespace Impacto::UI::CCLCC {\nusing namespace Impacto::Profile::CCLCC::LibraryMenu;\nusing namespace Impacto::Profile::ScriptVars;\n\nstatic bool IsExtraMoviesPresent() {\n  static bool isPresent = [] {\n    if (!MovieExtraVideosEnabled) return false;\n    std::map<uint32_t, std::string> listing;\n    Io::VfsListFiles(\"movie\", listing);\n    // There are 62 videos in the base game.\n    return listing.size() > 62;\n  }();\n  return isPresent;\n}\n\nvoid MovieMenu::UpdateFirstMovieDiskVisuals() const {\n  auto* disk = static_cast<Widgets::Button*>(MainItems.Children[0]);\n  disk->NormalSprite =\n      IsExtraMovieModeOn ? MovieDiskExtraOp : MovieDiskSprites[0];\n  disk->FocusedSprite = IsExtraMovieModeOn ? MovieDiskExtraOpHighlight\n                                           : MovieDiskHighlightSprites[0];\n}\n\nMovieMenu::MovieMenu() : LibrarySubmenu() {\n  for (size_t i = 0; i < MovieDiskSprites.size(); ++i) {\n    const auto& diskSprite = MovieDiskSprites[i];\n    const auto& diskHighlightSprite = MovieDiskHighlightSprites[i];\n    auto movieOnclick = [this](Widgets::Button* target) {\n      target->Hovered = false;\n      ScrWork[SW_MOVIEMODE_CUR] = IsExtraMovieModeOn && target->Id == 0\n                                      ? MovieDiskExtraOpPlayId\n                                      : MovieDiskPlayIds[target->Id];\n      LibraryMenuPtr->AllowsScriptInput = true;\n      Audio::PlayInGroup(Audio::ACG_SE, \"sysse\", 2, false, 0);\n      Audio::Channels[Audio::AC_BGM0]->Stop(0.0f);\n    };\n    auto disk = new Widgets::Button(static_cast<int>(i), diskSprite,\n                                    diskHighlightSprite, Sprite(),\n                                    MovieDiskDisplayPositions[i]);\n    disk->OnClickHandler = movieOnclick;\n    MainItems.Add(disk, FDIR_RIGHT);\n  }\n\n  auto setFocus = [](Widget* btn, Widget* btn2, FocusDirection dir) {\n    FocusDirection oppositeDir = [dir] {\n      switch (dir) {\n        case FDIR_LEFT:\n          return FDIR_RIGHT;\n        case FDIR_RIGHT:\n          return FDIR_LEFT;\n        case FDIR_UP:\n          return FDIR_DOWN;\n        case FDIR_DOWN:\n          return FDIR_UP;\n      }\n      return FDIR_LEFT;  // unreachable\n    }();\n    btn->SetFocus(btn2, dir);\n    btn2->SetFocus(btn, oppositeDir);\n  };\n\n  setFocus(MainItems.Children[0], MainItems.Children[2], FDIR_RIGHT);\n  setFocus(MainItems.Children[2], MainItems.Children[1], FDIR_RIGHT);\n  setFocus(MainItems.Children[1], MainItems.Children[3], FDIR_RIGHT);\n  setFocus(MainItems.Children[3], MainItems.Children[0], FDIR_RIGHT);\n  setFocus(MainItems.Children[2], MainItems.Children[1], FDIR_UP);\n  setFocus(MainItems.Children[2], MainItems.Children[1], FDIR_DOWN);\n}\n\nvoid MovieMenu::Init() {\n  // Check once in initialization that the ExtraMovieMode is usable, i.e: read\n  // VFS once.\n  IsExtraMoviesPresent();\n  ScrWork[SW_MOVIEMODE_CUR] = 0xff;\n  MainItems.Children[0]->Enabled = true;\n  MainItems.Children[1]->Enabled = GetFlag(SF_CLR_TRUE_CCLCC);\n  auto const endingFlags =\n      std::views::iota(1, 10) |\n      std::views::transform([](int i) { return GetFlag(SF_CLR_END1 + i); });\n  MainItems.Children[2]->Enabled =\n      std::ranges::any_of(endingFlags, [](bool flag) { return flag; });\n  MainItems.Children[3]->Enabled = GetFlag(SF_CLR_END1);\n  UpdateFirstMovieDiskVisuals();\n}\n\nvoid MovieMenu::UpdateInput(float dt) {\n  using namespace Vm::Interface;\n  LibrarySubmenu::UpdateInput(dt);\n  if (State == Shown && IsFocused) {\n    if (IsExtraMoviesPresent() && (PADinputButtonWentDown & PAD1Y)) {\n      IsExtraMovieModeOn = !IsExtraMovieModeOn;\n      UpdateFirstMovieDiskVisuals();\n    }\n  }\n}\n\n}  // namespace Impacto::UI::CCLCC\n"
  },
  {
    "path": "src/games/cclcc/moviemenu.h",
    "content": "#pragma once\n\n#include \"librarysubmenus.h\"\n\nnamespace Impacto::UI::CCLCC {\n\nclass MovieMenu : public LibrarySubmenu {\n public:\n  MovieMenu();\n  void Init() override;\n  void UpdateInput(float dt) override;\n\n private:\n  void UpdateFirstMovieDiskVisuals() const;\n  bool IsExtraMovieModeOn = false;\n};\n\n}  // namespace Impacto::UI::CCLCC\n"
  },
  {
    "path": "src/games/cclcc/musicmenu.cpp",
    "content": "#include \"musicmenu.h\"\n\n#include <numeric>\n#include \"../../ui/ui.h\"\n#include \"../../profile/games/cclcc/librarymenu.h\"\n#include \"../../video/videosystem.h\"\n#include \"../../vm/interface/input.h\"\n#include \"../../inputsystem.h\"\n#include \"../../text/text.h\"\n#include \"../../profile/dialogue.h\"\n#include \"../../data/savesystem.h\"\n#include \"../../audio/audiosystem.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace CCLCC {\n\nusing namespace Impacto::Profile::CCLCC::LibraryMenu;\nusing namespace Impacto::Profile::ScriptVars;\n\nMusicTrackButton::MusicTrackButton(int id, int position, glm::vec2 pos)\n    : Button() {\n  Id = id;\n  Bounds = RectF(pos.x, pos.y + MusicButtonTextYOffset, MusicButtonBounds.Width,\n                 MusicButtonBounds.Height);\n  IsLocked = !SaveSystem::GetBgmFlag(MusicBGMFlagIds[Id]);\n  uint32_t trackTextIndex = 2 * Id;\n  auto const trackText =\n      Vm::ScriptGetTextTableStrAddress(MusicStringTableId, trackTextIndex + 6);\n  SetText(trackText, MusicTrackNameSize, RendererOutlineMode::None,\n          {MusicButtonTextColor, MusicButtonTextOutlineColor});\n  Bounds =\n      RectF(pos.x, pos.y, MusicButtonBounds.Width, MusicButtonBounds.Height);\n  HighlightSprite = MusicButtonHoverSprite;\n  FocusedSprite = MusicButtonSelectSprite;\n  HoverBounds = Bounds;\n  for (size_t i = 0; i < Text.size(); i++) {\n    Text[i].DestRect.X += MusicTrackNameOffsetX;\n  }\n  auto const lockedSc3Text = Vm::ScriptGetTextTableStrAddress(\n      MusicStringTableId, MusicStringLockedIndex);\n  Vm::Sc3VmThread dummy;\n  dummy.SetIp(lockedSc3Text);\n  TextLayoutPlainLine(&dummy, 6, LockedText, Profile::Dialogue::DialogueFont,\n                      MusicTrackNameSize,\n                      {MusicButtonTextColor, MusicButtonTextOutlineColor}, 1.0f,\n                      glm::vec2(Bounds.X + MusicTrackNameOffsetX,\n                                Bounds.Y + MusicButtonTextYOffset),\n                      TextAlignment::Left);\n  ArtistName = Widgets::Label(\n      Vm::ScriptGetTextTableStrAddress(MusicStringTableId, trackTextIndex + 7),\n      glm::vec2(Bounds.X + MusicTrackArtistOffsetX,\n                Bounds.Y + MusicButtonTextYOffset),\n      (float)MusicTrackArtistSize, RendererOutlineMode::None,\n      {MusicButtonTextColor, MusicButtonTextOutlineColor});\n  TextLayoutPlainString(fmt::format(\"{:02}\", position), NumberText,\n                        Profile::Dialogue::DialogueFont, MusicTrackNameSize,\n                        {0xfffffff, 0}, 1.0f,\n                        glm::vec2(Bounds.X + MusicTrackNumberOffsetX,\n                                  Bounds.Y + MusicButtonTextYOffset),\n                        TextAlignment::Center);\n}\n\nvoid MusicTrackButton::Show() {\n  Button::Show();\n  ArtistName.Show();\n  // todo check locked from bgm flags\n}\n\nvoid MusicTrackButton::Move(glm::vec2 relativePos) {\n  const auto maxY = MusicButtonBounds.Height * MusicPlayIds.size();\n  const float sum = Bounds.Y + relativePos.y;\n  float newY = sum;\n  if (relativePos.y > 0 && newY > maxY - MusicButtonBounds.Height) {\n    newY -= maxY;\n  } else if (relativePos.y < 0 && newY < -MusicButtonBounds.Height) {\n    newY += maxY;\n  }\n\n  const float yDiff = (newY - Bounds.Y);\n\n  const auto moveGlyphs = [&](std::span<ProcessedTextGlyph> glyphs,\n                              glm::vec2 offset) {\n    for (auto& glyph : glyphs) {\n      glyph.DestRect.X += offset.x;\n      glyph.DestRect.Y += offset.y;\n    }\n  };\n  const auto move = [&](glm::vec2 offset) {\n    Button::Move(offset);\n    ArtistName.Move(offset);\n    HoverBounds = Bounds;\n\n    moveGlyphs(NumberText, offset);\n    moveGlyphs(LockedText, offset);\n  };\n  move({relativePos.x, yDiff});\n}\n\nvoid MusicTrackButton::Update(float dt) {\n  const int alpha = ((ScrWork[SW_SYSSUBMENUCT] * 32 - 768) * 224) >> 8;\n  Tint = glm::vec4(1.0f, 1.0f, 1.0f, alpha / 255.0f);\n  Button::Update(dt);\n  if (HasFocus && !PrevFocusState) {\n    if (Input::CurrentInputDevice != Input::Device::Mouse) {\n      Audio::PlayInGroup(Audio::ACG_SE, \"sysse\", 1, false, 0);\n    }\n  }\n  if (PrevFocusState != HasFocus) {\n    PrevFocusState = HasFocus;\n  }\n}\n\nvoid MusicTrackButton::Render() {\n  if (Enabled) {\n    ArtistName.Tint = Tint;\n    if (Selected) {\n      Renderer->DrawSprite(\n          MusicButtonPlayingSprite,\n          MusicButtonPlayingDispOffset + glm::vec2(Bounds.X, Bounds.Y), Tint);\n      Renderer->DrawSprite(FocusedSprite, glm::vec2(Bounds.X + 113, Bounds.Y),\n                           Tint);\n    } else {\n      Renderer->DrawProcessedText(NumberText, Profile::Dialogue::DialogueFont,\n                                  Tint.a);\n    }\n    if (HasFocus) {\n      Renderer->DrawSprite(HighlightSprite, glm::vec2(Bounds.X + 113, Bounds.Y),\n                           Tint);\n    }\n    if (IsLocked) {\n      Renderer->DrawProcessedText(LockedText, Profile::Dialogue::DialogueFont,\n                                  Tint.a);\n    } else {\n      Renderer->DrawProcessedText(Text, Profile::Dialogue::DialogueFont,\n                                  Tint.a);\n      ArtistName.Render();\n    }\n  }\n}\n\nMusicModeButton::MusicModeButton(MusicMenuPlayingMode& mode)\n    : Widgets::Button(), PlayMode(mode) {\n  OnClickHandler = [](Widgets::Button* btn) {\n    auto* modeBtn = static_cast<MusicModeButton*>(btn);\n    ++modeBtn->PlayMode;\n    Audio::PlayInGroup(Audio::ACG_SE, \"sysse\", 1, false, 0);\n  };\n}\n\nvoid MusicModeButton::Update(float dt) {\n  Widgets::Button::Update(dt);\n  NormalSprite = MusicPlayingModeSprites[PlayMode];\n  FocusedSprite = NormalSprite;\n  Bounds = MusicPlayingModeDisplayBounds[PlayMode];\n}\n\nMusicMenu::MusicMenu() : LibrarySubmenu(), ModeButton(PlayMode) {\n  MainItems.RenderingBounds = MusicRenderingBounds;\n  MainItems.HoverBounds = MusicHoverBounds;\n  NowPlayingFadeAnimation.DurationIn = MusicNowPlayingNotificationFadeIn;\n  NowPlayingFadeAnimation.DurationOut = MusicNowPlayingNotificationFadeOut;\n}\n\nvoid MusicMenu::Show() {\n  Audio::Channels[Audio::AC_BGM0]->Stop(0.0f);\n  LibrarySubmenu::Show();\n}\n\nvoid MusicMenu::Init() {\n  const auto musicOnclick = [this](Widgets::Button* target) {\n    auto* musicBtn = static_cast<MusicTrackButton*>(target);\n    if (musicBtn->IsLocked) {\n      Audio::PlayInGroup(Audio::ACG_SE, \"sysse\", 4, false, 0);\n      return;\n    }\n    if (ModeButton.Hovered) return;\n    if (CurrentlyPlayingBtn) CurrentlyPlayingBtn->Selected = false;\n    PlayTrack(musicBtn->Id);\n    if (PlayMode == MusicMenuPlayingMode::Shuffle) {\n      ResetShuffle();\n    }\n    musicBtn->Selected = true;\n    Audio::PlayInGroup(Audio::ACG_SE, \"sysse\", 2, false, 0);\n  };\n  const float maxY = MusicPlayIds.size() * MusicButtonBounds.Height;\n\n  // Small indie company please understand\n  SaveSystem::SetBgmFlag(101, true);\n  SaveSystem::SetBgmFlag(131, true);\n  MainItems.Clear();\n  for (size_t pos = 1; pos <= MusicPlayIds.size(); ++pos) {\n    const size_t i = (pos + MusicPlayIds.size() - 1) % MusicPlayIds.size();\n    const float btnY =\n        fmod(MusicButtonBounds.Y + MusicButtonBounds.Height * pos +\n                 MusicButtonBounds.Height * 8,\n             maxY);\n    const glm::vec2 btnPos = {MusicButtonBounds.X, btnY};\n    const auto musicItem = new MusicTrackButton((int)i, (int)pos, btnPos);\n\n    musicItem->OnClickHandler = musicOnclick;\n    MainItems.Add(musicItem, FDIR_DOWN);\n  }\n  ResetShuffle();\n}\n\nvoid MusicMenu::StopMusic() {\n  if (CurrentlyPlayingBtn) {\n    Audio::Channels[Audio::AC_BGM0]->Stop(0.0f);\n    CurrentlyPlayingBtn->Selected = false;\n    CurrentlyPlayingBtn = nullptr;\n    NowPlayingFadeAnimation.StartOut();\n    NowPlayingTrackName.ClearText();\n  }\n}\n\nvoid MusicMenu::Update(float dt) {\n  const auto getNextUnlockedTrack =\n      [](size_t current) -> std::optional<size_t> {\n    size_t next = (current + 1) % MusicPlayIds.size();\n    while (!SaveSystem::GetBgmFlag(MusicBGMFlagIds[next]) && next != current) {\n      next = (next + 1) % MusicPlayIds.size();\n    }\n    return next == current ? std::nullopt : std::make_optional(next);\n  };\n  if (IsFocused && CurrentlyPlayingBtn &&\n      Audio::Channels[Audio::AC_BGM0]->GetState() ==\n          Audio::AudioChannelState::ACS_Stopped) {\n    switch (PlayMode) {\n      case MusicMenuPlayingMode::RepeatOne:\n        PlayTrack(CurrentlyPlayingBtn->Id);\n        break;\n      case MusicMenuPlayingMode::PlayAll: {\n        if (CurrentlyPlayingBtn->Id != std::ssize(MusicPlayIds) - 1) {\n          auto nextTrack = getNextUnlockedTrack(CurrentlyPlayingBtn->Id);\n          if (nextTrack) {\n            PlayTrack(*nextTrack);\n            break;\n          }\n        }\n        StopMusic();\n      } break;\n      case MusicMenuPlayingMode::RepeatAll: {\n        auto nextTrack = getNextUnlockedTrack(CurrentlyPlayingBtn->Id);\n        PlayTrack(nextTrack.value_or(CurrentlyPlayingBtn->Id));\n      } break;\n      case MusicMenuPlayingMode::Shuffle: {\n        if (ShuffleTrackIndices.empty()) {\n          ResetShuffle();\n        }\n        PlayTrack(ShuffleTrackIndices.back());\n        ShuffleTrackIndices.pop_back();\n      } break;\n    }\n  }\n  LibrarySubmenu::Update(dt);\n  ModeButton.HasFocus = ModeButton.Hovered;\n  BGWidget.Update(dt);\n  NowPlayingFadeAnimation.Update(dt);\n  NowPlayingTrackName.Update(dt);\n  ModeButton.Update(dt);\n  const int alpha = ((ScrWork[SW_SYSSUBMENUCT] * 32 - 768) * 224) >> 8;\n  const auto tint =\n      glm::vec4(1.0f, 1.0f, 1.0f, alpha / 255.0f * FadeAnimation.Progress);\n  MainItems.Tint = tint;\n  BGWidget.Tint = tint;\n  NowPlayingTrackName.Tint = tint;\n  ModeButton.Tint = tint;\n}\n\nvoid MusicMenu::Unfocus() {\n  if (!IsFocused) return;\n  StopMusic();\n  Audio::Channels[Audio::AC_BGM0]->Play(\"bgm\", 101, true, 0.0f);\n  Audio::PlayInGroup(Audio::ACG_SE, \"sysse\", 3, false, 0);\n  LibrarySubmenu::Unfocus();\n}\n\nvoid MusicMenu::Hide() {\n  if (CurrentlyFocusedElement) CurrentlyFocusedElement = nullptr;\n  MainItems.MoveTo(glm::vec2(0, 0));\n  LibrarySubmenu::Hide();\n}\n\nvoid MusicMenu::UpdateInput(float dt) {\n  using namespace Vm::Interface;\n  if (State == Shown && IsFocused) {\n    MainItems.UpdateInput(dt);\n    if (PADinputButtonWentDown & PADcustom[17]) {\n      StopMusic();\n    }\n\n    if (PADinputButtonWentDown & PADcustom[18]) {\n      ++PlayMode;\n    }\n    ModeButton.UpdateInput(dt);\n\n    const uint32_t btnUp = PADcustom[0];\n    const uint32_t btnDown = PADcustom[1];\n    const bool upScroll = Input::MouseWheelDeltaY > 0;\n    const bool downScroll = Input::MouseWheelDeltaY < 0;\n\n    const uint32_t directionShouldFire =\n        PADinputButtonRepeatDown & (btnUp | btnDown);\n    const bool directionMovement =\n        (bool)(directionShouldFire & btnUp || upScroll) ^\n        (bool)(directionShouldFire & btnDown || downScroll);\n\n    if ((PADinputButtonWentDown & (btnUp | btnDown)) != 0) HoldTimer = 0.0f;\n    if (directionShouldFire) HoldTimer += dt;\n\n    if (directionMovement) {\n      const bool dirDown = directionShouldFire & btnDown || downScroll;\n      QueuedMove =\n          (dirDown ? FocusDirection::FDIR_DOWN : FocusDirection::FDIR_UP);\n    } else if (TurboMoved) {\n      QueuedMove.reset();\n    }\n\n    if (QueuedMove.has_value() &&\n        (MainItems.MoveAnimation.State != AnimationState::Playing &&\n         BGWidget.MoveAnimation.State != AnimationState::Playing)) {\n      float deltaY = 0;\n      const bool dirDown = *QueuedMove == FocusDirection::FDIR_DOWN;\n      deltaY += dirDown ? MusicButtonBounds.Height : -MusicButtonBounds.Height;\n      TurboMoved =\n          (HoldTimer > MusicDirectionalHoldTime) || upScroll || downScroll;\n      const float animationSpeed =\n          TurboMoved ? MusicDirectionalFocusTimeInterval : 0.3f;\n\n      MainItems.Move({0.0f, -deltaY}, animationSpeed);\n      BGWidget.Move({0.0f, -deltaY}, animationSpeed);\n      if (dirDown)\n        AdvanceFocus(FocusDirection::FDIR_DOWN);\n      else\n        AdvanceFocus(FocusDirection::FDIR_UP);\n      QueuedMove.reset();\n    }\n  }\n}\n\nvoid MusicBGs::Move(glm::vec2 relativePos) {\n  const auto maxY = MusicItemsBackgroundRepeatHeight;\n  const float sum = Bounds.Y + relativePos.y;\n  float newY = sum;\n  if (relativePos.y > 0 && newY > maxY - MusicButtonBounds.Height) {\n    newY -= maxY;\n  } else if (relativePos.y < 0 && newY < -MusicButtonBounds.Height) {\n    newY += maxY;\n  }\n\n  const float yDiff = (newY - Bounds.Y);\n  Widget::Move({relativePos.x, yDiff});\n}\n\nvoid MusicBGs::Render() {\n  glm::vec2 topSplitPos{\n      MusicRenderingBounds.X,\n      MusicRenderingBounds.Y + Bounds.Y - MusicItemsBackgroundRepeatHeight};\n  glm::vec2 botSplitPos(MusicRenderingBounds.X,\n                        MusicRenderingBounds.Y + Bounds.Y);\n  Renderer->DrawSprite(MusicItemsBackgroundSprite, topSplitPos, Tint);\n  Renderer->DrawSprite(MusicItemsBackgroundSprite, botSplitPos, Tint);\n\n  Renderer->DrawSprite(MusicItemsOverlaySprite, topSplitPos, Tint);\n  Renderer->DrawSprite(MusicItemsOverlaySprite, botSplitPos, Tint);\n}\n\nvoid MusicMenu::Render() {\n  if (State == Hidden) return;\n\n  BGWidget.Render();\n  LibrarySubmenu::Render();\n  Renderer->DrawSprite(\n      MusicNowPlayingNotificationSprite, MusicNowPlayingNotificationPos,\n      glm::vec4{glm::vec3{1.0f}, NowPlayingFadeAnimation.Progress});\n  NowPlayingTrackName.Render();\n  ModeButton.Render();\n}\n\nvoid MusicMenu::PlayTrack(size_t index) {\n  if (index >= MusicPlayIds.size()) return;\n  if (CurrentlyPlayingBtn) {\n    CurrentlyPlayingBtn->Selected = false;\n    CurrentlyPlayingBtn = nullptr;\n  }\n  CurrentlyPlayingBtn =\n      static_cast<MusicTrackButton*>(MainItems.Children[index]);\n  CurrentlyPlayingBtn->Selected = true;\n  NowPlayingFadeAnimation.StartIn();\n\n  const glm::vec2 playingNamePos =\n      MusicNowPlayingNotificationPos + MusicNowPlayingNotificationTrackOffset;\n  NowPlayingTrackName.Bounds.X = playingNamePos.x;\n  NowPlayingTrackName.Bounds.Y = playingNamePos.y;\n  NowPlayingTrackName.SetText(\n      Vm::ScriptGetTextTableStrAddress(MusicStringTableId,\n                                       (uint32_t)(2 * index + 6)),\n      (float)MusicNowPlayingNotificationTrackFontSize,\n      RendererOutlineMode::None,\n      {MusicNowPlayingTextColor, MusicNowPlayingTextOutlineColor});\n  NowPlayingTrackName.Show();\n  Audio::Channels[Audio::AC_BGM0]->Play(\"bgm\", MusicPlayIds[index], false,\n                                        0.0f);\n}\n\nvoid MusicMenu::ResetShuffle() {\n  static std::random_device randomDevice{};\n  if (PlayMode == MusicMenuPlayingMode::Shuffle) {\n    ShuffleTrackIndices.clear();\n    for (size_t i = 0; i < MusicPlayIds.size(); ++i) {\n      if (static_cast<MusicTrackButton*>(MainItems.Children[i])->IsLocked)\n        continue;\n      ShuffleTrackIndices.push_back(i);\n    }\n    std::shuffle(ShuffleTrackIndices.begin(), ShuffleTrackIndices.end(),\n                 std::mt19937{randomDevice()});\n  }\n}\n}  // namespace CCLCC\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/cclcc/musicmenu.h",
    "content": "#pragma once\n\n#include <optional>\n#include \"librarysubmenus.h\"\n#include \"../../profile/games/cclcc/librarymenu.h\"\n#include \"../../ui/widget.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace CCLCC {\n\nstruct MusicBGs : public UI::Widget {\n  void UpdateInput(float dt) override {};\n  void Render() override;\n\n  using Widget::Move;\n  void Move(glm::vec2 relativePos) override;\n};\n\nclass MusicTrackButton : public Widgets::Button {\n public:\n  MusicTrackButton(int id, int position, glm::vec2 pos);\n\n  void Show() override;\n  void Update(float dt) override;\n  void Render() override;\n\n  using Widget::Move;\n  void Move(glm::vec2 relativePosition) override;\n\n  bool Selected = false;\n  bool PrevFocusState = false;\n\n private:\n  std::array<ProcessedTextGlyph, 6> LockedText;\n  std::array<ProcessedTextGlyph, 4> NumberText;\n  Widgets::Label ArtistName;\n};\n\nclass MusicModeButton : public Widgets::Button {\n public:\n  MusicModeButton(Profile::CCLCC::LibraryMenu::MusicMenuPlayingMode& mode);\n\n  void Update(float dt) override;\n\n private:\n  Profile::CCLCC::LibraryMenu::MusicMenuPlayingMode& PlayMode;\n};\n\nclass MusicMenu : public LibrarySubmenu {\n public:\n  MusicMenu();\n  void Init() override;\n  void UpdateInput(float dt) override;\n  void Update(float dt) override;\n  void Render() override;\n  void Show() override;\n  void Hide() override;\n  void Unfocus() override;\n\n private:\n  Profile::CCLCC::LibraryMenu::MusicMenuPlayingMode PlayMode =\n      Profile::CCLCC::LibraryMenu::MusicMenuPlayingMode::RepeatAll;\n  MusicTrackButton* CurrentlyPlayingBtn = nullptr;\n  std::optional<FocusDirection> QueuedMove;\n  std::vector<size_t> ShuffleTrackIndices;\n  MusicBGs BGWidget;\n  Animation NowPlayingFadeAnimation;\n  Widgets::Label NowPlayingTrackName;\n  MusicModeButton ModeButton;\n  float HoldTimer = 0.0f;\n  bool TurboMoved = false;\n  void PlayTrack(size_t index);\n  void StopMusic();\n  void ResetShuffle();\n};\n\n}  // namespace CCLCC\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/cclcc/optionsmenu.cpp",
    "content": "#include \"optionsmenu.h\"\n\n#include \"../../profile/configsystem.h\"\n#include \"../../profile/game.h\"\n#include \"../../profile/ui/optionsmenu.h\"\n#include \"../../profile/games/cclcc/optionsmenu.h\"\n#include \"../../profile/scriptinput.h\"\n#include \"../../vm/interface/input.h\"\n#include \"../../ui/widgets/cclcc/optionsbinarybutton.h\"\n#include \"../../ui/widgets/cclcc/optionsslider.h\"\n#include \"../../ui/widgets/cclcc/optionsvoiceslider.h\"\n#include \"../../audio/audiosystem.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace CCLCC {\n\nusing namespace Impacto::Profile::OptionsMenu;\nusing namespace Impacto::Profile::CCLCC::OptionsMenu;\nusing namespace Impacto::Profile::ScriptVars;\nusing namespace Impacto::Profile::ConfigSystem;\nusing namespace Impacto::UI::Widgets;\nusing namespace Impacto::UI::Widgets::CCLCC;\nusing namespace Impacto::Vm::Interface;\n\nstd::unique_ptr<Group> OptionsMenu::CreateBasicPage(\n    const std::function<void(OptionsEntry*)>& select,\n    const std::function<void(Widget*)>& highlight) {\n  const glm::vec4 highlightTint(HighlightColor, 1.0f);\n  std::unique_ptr<Group> basicPage = std::make_unique<Group>(this);\n\n  basicPage->Add(\n      new OptionsBinaryButton(ShowTipsNotification, BinaryBoxSprite, OnSprite,\n                              OffSprite, LabelSprites[0], EntriesStartPosition,\n                              highlightTint, select, highlight),\n      FDIR_DOWN);\n  basicPage->Add(\n      new OptionsBinaryButton(\n          AdvanceTextOnDirectionalInput, BinaryBoxSprite, OnSprite, OffSprite,\n          LabelSprites[1],\n          EntriesStartPosition + glm::vec2(0.0f, EntriesVerticalOffset),\n          highlightTint, select, highlight),\n      FDIR_DOWN);\n  basicPage->Add(\n      new OptionsBinaryButton(\n          DirectionalInputForTrigger, BinaryBoxSprite, OnSprite, OffSprite,\n          LabelSprites[2],\n          EntriesStartPosition + glm::vec2(0.0f, EntriesVerticalOffset * 2),\n          highlightTint, select, highlight),\n      FDIR_DOWN);\n  basicPage->Add(\n      new OptionsBinaryButton(\n          TriggerStopSkip, BinaryBoxSprite, OnSprite, OffSprite,\n          LabelSprites[3],\n          EntriesStartPosition + glm::vec2(0.0f, EntriesVerticalOffset * 3),\n          highlightTint, select, highlight),\n      FDIR_DOWN);\n\n  return basicPage;\n}\n\nstd::unique_ptr<Group> OptionsMenu::CreateTextPage(\n    const std::function<void(OptionsEntry*)>& select,\n    const std::function<void(Widget*)>& highlight) {\n  const glm::vec4 highlightTint(HighlightColor, 1.0f);\n  std::unique_ptr<Group> textPage = std::make_unique<Group>(this);\n\n  textPage->Add(new OptionsSlider(\n                    TextSpeed, TextSpeedBounds[0], TextSpeedBounds[1],\n                    SliderTrackSprite, LabelSprites[4], EntriesStartPosition,\n                    highlightTint, SliderSpeed, select, highlight),\n                FDIR_DOWN);\n  textPage->Add(\n      new OptionsSlider(\n          AutoSpeed, AutoSpeedBounds[0], AutoSpeedBounds[1], SliderTrackSprite,\n          LabelSprites[5],\n          EntriesStartPosition + glm::vec2(0.0f, EntriesVerticalOffset),\n          highlightTint, SliderSpeed, select, highlight),\n      FDIR_DOWN);\n  textPage->Add(\n      new OptionsBinaryButton(\n          SkipRead, BinaryBoxSprite, SkipReadSprite, SkipAllSprite,\n          LabelSprites[6],\n          EntriesStartPosition + glm::vec2(0.0f, EntriesVerticalOffset * 2),\n          highlightTint, select, highlight),\n      FDIR_DOWN);\n\n  return textPage;\n}\n\nstd::unique_ptr<Group> OptionsMenu::CreateSoundPage(\n    const std::function<void(OptionsEntry*)>& select,\n    const std::function<void(Widget*)>& highlight) {\n  const glm::vec4 highlightTint(HighlightColor, 1.0f);\n  std::unique_ptr<Group> soundPage = std::make_unique<Group>(this);\n\n  soundPage->Add(new OptionsSlider(Audio::GroupVolumes[Audio::ACG_Voice], 0.0f,\n                                   1.0f, SliderTrackSprite, LabelSprites[7],\n                                   SoundEntriesStartPosition, highlightTint,\n                                   SliderSpeed, select, highlight),\n                 FDIR_DOWN);\n  soundPage->Add(\n      new OptionsSlider(Audio::GroupVolumes[Audio::ACG_BGM], 0.0f, 0.5f,\n                        SliderTrackSprite, LabelSprites[8],\n                        SoundEntriesStartPosition +\n                            glm::vec2(0.0f, SoundEntriesVerticalOffset),\n                        highlightTint, SliderSpeed, select, highlight),\n      FDIR_DOWN);\n  soundPage->Add(\n      new OptionsSlider(Audio::GroupVolumes[Audio::ACG_SE], 0.0f, 1.0f,\n                        SliderTrackSprite, LabelSprites[9],\n                        SoundEntriesStartPosition +\n                            glm::vec2(0.0f, SoundEntriesVerticalOffset * 2),\n                        highlightTint, SliderSpeed, select, highlight),\n      FDIR_DOWN);\n  soundPage->Add(\n      new OptionsSlider(Audio::GroupVolumes[Audio::ACG_Movie], 0.0f, 1.0f,\n                        SliderTrackSprite, LabelSprites[10],\n                        SoundEntriesStartPosition +\n                            glm::vec2(0.0f, SoundEntriesVerticalOffset * 3),\n                        highlightTint, SliderSpeed, select, highlight),\n      FDIR_DOWN);\n  soundPage->Add(\n      new OptionsBinaryButton(\n          SyncVoice, BinaryBoxSprite, YesSprite, NoSprite, LabelSprites[11],\n          SoundEntriesStartPosition +\n              glm::vec2(0.0f, SoundEntriesVerticalOffset * 4),\n          highlightTint, select, highlight),\n      FDIR_DOWN);\n  soundPage->Add(\n      new OptionsBinaryButton(\n          SkipVoice, BinaryBoxSprite, YesSprite, NoSprite, LabelSprites[12],\n          SoundEntriesStartPosition +\n              glm::vec2(0.0f, SoundEntriesVerticalOffset * 5),\n          highlightTint, select, highlight),\n      FDIR_DOWN);\n\n  return soundPage;\n}\n\nstd::unique_ptr<Group> OptionsMenu::CreateVoicePage(\n    const std::function<void(OptionsEntry*)>& select,\n    const std::function<void(Widget*)>& highlight) {\n  const glm::vec4 highlightTint(HighlightColor, 1.0f);\n  std::unique_ptr<Group> voicePage = std::make_unique<Group>(this);\n\n  constexpr int columns = 3;\n  constexpr int entries = 12;\n  for (int i = 0; i < entries; i++) {\n    const glm::vec2 pos =\n        VoicePosition +\n        VoiceEntriesOffset * glm::vec2(i % columns, i / columns);\n\n    Widget* widget = new OptionsVoiceSlider(\n        VoiceVolume[i], 0.0f, 1.0f, VoiceMuted[i], VoiceSliderTrackSprite,\n        NametagSprites[i], PortraitSprites[2 * i], PortraitSprites[2 * i + 1],\n        pos, highlightTint, SliderSpeed, select, highlight);\n    voicePage->Add(widget, FDIR_RIGHT);\n  }\n\n  // Loop separately to overwrite the direction set at initial adding\n  // First entry won't set anything; skip\n  for (int i = 1; i < entries; i++) {\n    Widget* const widget = voicePage->Children[i];\n\n    if (i % columns != 0) {  // Not on first column\n      Widget* const leftWidget = voicePage->Children[i - 1];\n      widget->SetFocus(leftWidget, FDIR_LEFT);\n      leftWidget->SetFocus(widget, FDIR_RIGHT);\n\n      if (i % columns == columns - 1) {  // On last column\n        Widget* const rowStart = voicePage->Children[i - columns + 1];\n        widget->SetFocus(rowStart, FDIR_RIGHT);\n        rowStart->SetFocus(widget, FDIR_LEFT);\n      }\n    }\n    if (i >= columns) {  // Not on first row\n      Widget* const upWidget = voicePage->Children[i - columns];\n      widget->SetFocus(upWidget, FDIR_UP);\n      upWidget->SetFocus(widget, FDIR_DOWN);\n\n      if (i >= entries - columns) {  // On last layer\n        Widget* const columnStart = voicePage->Children[i % columns];\n        widget->SetFocus(columnStart, FDIR_DOWN);\n        columnStart->SetFocus(widget, FDIR_UP);\n      }\n    }\n  }\n\n  return voicePage;\n}\n\nOptionsMenu::OptionsMenu() : UI::OptionsMenu() {\n  PoleAnimation = Profile::CCLCC::OptionsMenu::PoleAnimation.Instantiate();\n\n  PageButtons.reserve(PageCount);\n  for (int i = 0; i < PageCount; i++) {\n    PageButtons.emplace_back(i, PagePanelHoverBounds[i]);\n  }\n\n  std::function<void(OptionsEntry*)> select = [this](auto* btn) {\n    return Select(btn);\n  };\n  std::function<void(Widget*)> highlight = [this](auto* btn) {\n    return Highlight(btn);\n  };\n\n  Pages.reserve(PageCount);\n  Pages.emplace_back(CreateBasicPage(select, highlight));\n  Pages.emplace_back(CreateTextPage(select, highlight));\n  Pages.emplace_back(CreateSoundPage(select, highlight));\n  Pages.emplace_back(CreateVoicePage(select, highlight));\n\n  Highlight(Pages[CurrentPage]->GetFirstFocusableChild());\n}\n\nvoid OptionsMenu::Show() {\n  UI::OptionsMenu::Show();\n\n  if (State != Shown) PoleAnimation.StartIn();\n}\n\nvoid OptionsMenu::Hide() {\n  if (State != Hidden) {\n    PoleAnimation.StartOut();\n\n    if (CurrentlyFocusedElement)\n      static_cast<OptionsEntry*>(CurrentlyFocusedElement)->Selected = false;\n  }\n\n  UI::OptionsMenu::Hide();\n}\n\nvoid OptionsMenu::UpdateVisibility() {\n  if (ScrWork[SW_SYSSUBMENUCT] < 32 && State == Shown) {\n    Hide();\n  } else if (ScrWork[SW_SYSSUBMENUCT] > 0 && State == Hidden &&\n             ScrWork[SW_SYSSUBMENUNO] == 5) {\n    Show();\n  }\n\n  if (FadeAnimation.IsIn() && ScrWork[SW_SYSSUBMENUCT] == 32) {\n    State = Shown;\n  } else if (State == Hiding && FadeAnimation.IsOut() &&\n             ScrWork[SW_SYSSUBMENUCT] == 0) {\n    State = Hidden;\n    Pages[CurrentPage]->Hide();\n  }\n}\n\nvoid OptionsMenu::Update(float dt) {\n  UI::OptionsMenu::Update(dt);\n  PoleAnimation.Update(dt);\n\n  if (!FadeAnimation.IsIn() && !FadeAnimation.IsOut()) {\n    const glm::vec2 backgroundPosition =\n        glm::vec2(0.0f, glm::mix(BackgroundFadeStartPosition.y,\n                                 BackgroundPosition.y, FadeAnimation.Progress));\n    for (std::unique_ptr<Group>& page : Pages) {\n      page->MoveTo(backgroundPosition);\n    }\n  }\n\n  AllowsScriptInput = !AnyEntrySelected();\n}\n\nvoid OptionsMenu::PageButtonOnHover(size_t pageNumber) {\n  if (pageNumber != CurrentPage || !CurrentlyFocusedElement)\n    Audio::PlayInGroup(Audio::ACG_SE, \"sysse\", 1, false, 0.0f);\n\n  if (pageNumber == CurrentPage && CurrentlyFocusedElement) return;\n\n  GoToPage(pageNumber);\n  Highlight(Pages[CurrentPage]->GetFirstFocusableChild());\n}\n\nvoid OptionsMenu::UpdatePageInput(float dt) {\n  // Mouse input\n  for (ClickArea& button : PageButtons) {\n    const bool wasHovered = button.Hovered;\n    button.UpdateInput(dt);\n    if (!wasHovered && button.Hovered) PageButtonOnHover(button.Id);\n  }\n\n  const size_t lastPage = CurrentPage;\n  UI::OptionsMenu::UpdatePageInput(dt);\n\n  if (CurrentPage != lastPage)\n    Audio::PlayInGroup(Audio::ACG_SE, \"sysse\", 1, false, 0.0f);\n}\n\nvoid OptionsMenu::UpdateEntryMovementInput(float dt) {\n  const Widget* const lastHighlight = CurrentlyFocusedElement;\n  UI::OptionsMenu::UpdateEntryMovementInput(dt);\n\n  if (CurrentlyFocusedElement != lastHighlight)\n    Audio::PlayInGroup(Audio::ACG_SE, \"sysse\", 1, false, 0.0f);\n}\n\nvoid OptionsMenu::UpdateInput(float dt) {\n  bool backBtnPressed = (PADinputMouseWentDown & PAD1B) ||\n                        (AllowsScriptInput && GetControlState(CT_Back));\n  if (State == Shown && backBtnPressed) {\n    if (!GetFlag(SF_SUBMENUEXIT))\n      Audio::PlayInGroup(Audio::ACG_SE, \"sysse\", 3, false, 0.0f);\n    SetFlag(SF_SUBMENUEXIT, 1);\n    return;\n  }\n\n  UpdatePageInput(dt);\n\n  // If something is selected, the option entry takes full control\n  if (!AllowsScriptInput) return;\n\n  UpdateEntryMovementInput(dt);\n}\n\nvoid OptionsMenu::Render() {\n  if (State != Hidden && ScrWork[SW_SYSSUBMENUCT] > 0 &&\n      ScrWork[SW_SYSSUBMENUNO] == 5) {\n    const glm::vec4 col(1.0f, 1.0f, 1.0f, FadeAnimation.Progress);\n    const glm::vec4 maskTint =\n        col * glm::vec4{glm::vec3{1.0f}, (float)0xa0 / 0x100};\n\n    const glm::vec2 backgroundAnimationOffset =\n        glm::vec2(0.0f, FadeAnimation.Progress * BackgroundPosition.y +\n                            (1.0f - FadeAnimation.Progress) *\n                                BackgroundFadeStartPosition.y);\n    const glm::vec2 pagePanelPosition =\n        PagePanelPosition * FadeAnimation.Progress +\n        (1.0f - FadeAnimation.Progress) * PagePanelFadeStartPosition;\n    const glm::vec2 guidePosition =\n        GuidePosition * FadeAnimation.Progress +\n        (1.0f - FadeAnimation.Progress) * GuideFadeStartPosition;\n\n    Renderer->DrawSprite(BackgroundSprite,\n                         BackgroundPosition + backgroundAnimationOffset, col);\n    Renderer->DrawSprite(HeaderSprite,\n                         HeaderPosition + backgroundAnimationOffset, col);\n\n    Renderer->DrawSprite(PageHeaderSprites[CurrentPage],\n                         PageHeaderPosition + backgroundAnimationOffset, col);\n    Pages[CurrentPage]->Tint = col;\n    Pages[CurrentPage]->Render();\n\n    Renderer->DrawSprite(PoleAnimation.CurrentSprite(), pagePanelPosition, col);\n    if (PoleAnimation.IsIn()) {\n      Renderer->DrawSprite(\n          PagePanelSprites[2 * CurrentPage],\n          PagePanelPosition + PagePanelIconOffsets[CurrentPage], col);\n    }\n\n    Renderer->DrawSprite(\n        MenuMaskSprite,\n        RectF(0.0f, 0.0f, Profile::DesignWidth, Profile::DesignHeight),\n        maskTint);\n\n    const Sprite& guideSprite =\n        CurrentPage == +PageType::Voice ? VoiceGuideSprite : GuideSprite;\n    Renderer->DrawSprite(guideSprite, guidePosition, col);\n  }\n}\n\nvoid OptionsMenu::Select(OptionsEntry* toSelect) {\n  for (Widget* entry : Pages[CurrentPage]->Children) {\n    static_cast<OptionsEntry*>(entry)->Selected = false;\n    entry->HasFocus = false;\n  }\n\n  toSelect->Selected = true;\n  toSelect->HasFocus = true;\n  CurrentlyFocusedElement = toSelect;\n}\n\nvoid OptionsMenu::Highlight(Widget* toHighlight) {\n  UI::OptionsMenu::Highlight(toHighlight);\n\n  for (Widget* entry : Pages[CurrentPage]->Children) {\n    static_cast<OptionsEntry*>(entry)->Selected = false;\n  }\n}\n\nvoid OptionsMenu::ResetToDefault() {\n  switch (CurrentPage) {\n    case +PageType::Basic: {\n      ShowTipsNotification = Default::ShowTipsNotification;\n      AdvanceTextOnDirectionalInput = Default::AdvanceTextOnDirectionalInput;\n      DirectionalInputForTrigger = Default::DirectionalInputForTrigger;\n      TriggerStopSkip = Default::TriggerStopSkip;\n      break;\n    }\n    case +PageType::Text: {\n      TextSpeed = Default::TextSpeed;\n      AutoSpeed = Default::AutoSpeed;\n      SkipRead = Default::SkipRead;\n      break;\n    }\n    case +PageType::Sound: {\n      std::ranges::copy(Default::GroupVolumes, Audio::GroupVolumes.begin());\n      SyncVoice = Default::SyncVoice;\n      SkipVoice = Default::SkipVoice;\n      break;\n    }\n    case +PageType::Voice: {\n      std::ranges::copy(Default::VoiceMuted, VoiceMuted.begin());\n      std::ranges::copy(Default::VoiceVolume, VoiceVolume.begin());\n      break;\n    }\n    default:\n      break;\n  }\n\n  UpdateValues();\n}\n\n}  // namespace CCLCC\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/cclcc/optionsmenu.h",
    "content": "#pragma once\n\n#include \"../../ui/optionsmenu.h\"\n#include \"../../spriteanimation.h\"\n#include \"../../ui/widgets/clickarea.h\"\n#include \"../../ui/widgets/cclcc/optionsentry.h\"\n#include \"../../ui/widgets/group.h\"\n\nusing namespace Impacto::UI::Widgets::CCLCC;\n\nnamespace Impacto {\nnamespace UI {\nnamespace CCLCC {\n\nenum class PageType : uint32_t {\n  Basic = 0,\n  Text = 1,\n  Sound = 2,\n  Voice = 3,\n};\nclass OptionsMenu : public UI::OptionsMenu {\n public:\n  OptionsMenu();\n\n  void Show() override;\n  void Hide() override;\n  void Update(float dt) override;\n  void UpdateInput(float dt) override;\n  void Render() override;\n  void ResetToDefault() override;\n\n private:\n  std::unique_ptr<Widgets::Group> CreateBasicPage(\n      const std::function<void(OptionsEntry*)>& select,\n      const std::function<void(Widget*)>& highlight);\n  std::unique_ptr<Widgets::Group> CreateTextPage(\n      const std::function<void(OptionsEntry*)>& select,\n      const std::function<void(Widget*)>& highlight);\n  std::unique_ptr<Widgets::Group> CreateSoundPage(\n      const std::function<void(OptionsEntry*)>& select,\n      const std::function<void(Widget*)>& highlight);\n  std::unique_ptr<Widgets::Group> CreateVoicePage(\n      const std::function<void(OptionsEntry*)>& select,\n      const std::function<void(Widget*)>& highlight);\n\n  void PageButtonOnHover(size_t pageNumber);\n\n  void Select(OptionsEntry* entry);\n  void Highlight(Widget* entry) override;\n  bool AnyEntrySelected() {\n    return CurrentlyFocusedElement &&\n           static_cast<OptionsEntry*>(CurrentlyFocusedElement)->Selected;\n  }\n\n  void UpdatePageInput(float dt) override;\n  void UpdateEntryMovementInput(float dt) override;\n  void UpdateVisibility() override;\n\n  SpriteAnimation PoleAnimation;\n\n  std::vector<Widgets::ClickArea> PageButtons;\n};\n\n}  // namespace CCLCC\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/cclcc/savemenu.cpp",
    "content": "#include \"savemenu.h\"\n\n#include \"../../profile/ui/savemenu.h\"\n#include \"../../renderer/renderer.h\"\n#include \"../../mem.h\"\n#include \"../../profile/scriptvars.h\"\n#include \"../../inputsystem.h\"\n#include \"../../vm/interface/input.h\"\n#include \"../../ui/widgets/button.h\"\n#include \"../../ui/widgets/cclcc/saveentrybutton.h\"\n#include \"../../data/savesystem.h\"\n#include \"../../profile/game.h\"\n#include \"../../games/cclcc/savesystem.h\"\n#include \"../../audio/audiosystem.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace CCLCC {\n\nusing namespace Impacto::Profile::SaveMenu;\nusing namespace Impacto::Profile::CCLCC::SaveMenu;\nusing namespace Impacto::Profile::ScriptVars;\nusing namespace Impacto::UI::Widgets::CCLCC;\n\nWidget* EntryGrid[Pages][RowsPerPage][EntriesPerRow];\n\nvoid SaveMenu::MenuButtonOnClick(Widgets::Button* target) {\n  Impacto::SaveSystem::SaveType saveType =\n      *ActiveMenuType == SaveMenuPageType::QuickLoad\n          ? SaveSystem::SaveType::Quick\n          : SaveSystem::SaveType::Full;\n  int SaveStatus = SaveSystem::GetSaveStatus(saveType, target->Id);\n  if (SaveStatus == 1 || *ActiveMenuType == SaveMenuPageType::Save) {\n    ScrWork[SW_SAVEFILENO] = target->Id;\n    ScrWork[SW_SAVEFILETYPE] = (int)saveType;\n    ScrWork[SW_SAVEFILESTATUS] =\n        SaveSystem::GetSaveStatus(saveType, ScrWork[SW_SAVEFILENO]);\n\n    ChoiceMade = true;\n    SetFlag(SF_SAVEPROTECTED,\n            SaveSystem::GetSaveFlags(saveType, ScrWork[SW_SAVEFILENO]) &\n                SaveSystem::SaveFlagsMode::WriteProtect);\n  }\n  if ((*ActiveMenuType == SaveMenuPageType::Load ||\n       *ActiveMenuType == SaveMenuPageType::QuickLoad) &&\n      SaveStatus == 0) {\n    Audio::PlayInGroup(Audio::ACG_SE, \"sysse\", 4, false, 0);\n    return;\n  }\n}\n\nSaveMenu::SaveMenu() : UI::SaveMenu() {\n  FadeAnimation.Direction = AnimationDirection::In;\n  FadeAnimation.LoopMode = AnimationLoopMode::Stop;\n  FadeAnimation.DurationIn = FadeInDuration;\n  FadeAnimation.DurationOut = FadeOutDuration;\n  PageAnimation.LoopMode = AnimationLoopMode::Stop;\n  PageAnimation.DurationIn = PageSwapDuration;\n}\n\nvoid SaveMenu::Show() {\n  auto onClick = [this](auto* btn) { return MenuButtonOnClick(btn); };\n\n  if (State != Showing) {\n    HasCleared = false;\n    State = Showing;\n    FadeAnimation.StartIn();\n    int id = 0;\n    Impacto::SaveSystem::SaveType saveType =\n        *ActiveMenuType == SaveMenuPageType::QuickLoad\n            ? SaveSystem::SaveType::Quick\n            : SaveSystem::SaveType::Full;\n\n    for (int p = 0; p < Pages; ++p) {\n      MainItems[p] = new Widgets::Group(this);\n      MainItems[p]->WrapFocus = false;\n      MainItems[p]->FocusLock = false;\n\n      for (int i = 0; i < RowsPerPage; i++) {\n        // Start on right col\n        for (int j = EntriesPerRow - 1; j >= 0; j--) {\n          glm::vec2 buttonPos =\n              (j == 0)\n                  ? glm::vec2{EntryStartXL, EntryStartYL + (i * EntryYPadding)}\n                  : glm::vec2{EntryStartXR, EntryStartYR + (i * EntryYPadding)};\n          SaveEntryButton* saveEntryButton = new SaveEntryButton(\n              id, EntryHighlightedBoxSprite[*ActiveMenuType],\n              EntryHighlightedTextSprite[*ActiveMenuType], p, buttonPos,\n              SlotLockedSprite[*ActiveMenuType], saveType,\n              NoDataSprite[*ActiveMenuType], BrokenDataSprite[*ActiveMenuType]);\n\n          saveEntryButton->OnClickHandler = onClick;\n          id++;\n\n          EntryGrid[p][i][j] = saveEntryButton;\n          MainItems[p]->Add(saveEntryButton);\n\n          saveEntryButton->RefreshInfo();\n        }\n      }\n    }\n    for (int p = 0; p < Pages; ++p) {\n      for (int j = EntriesPerRow - 1; j >= 0; j--) {\n        for (int i = 0; i < RowsPerPage; i++) {\n          // Right to left, top to bottom, page wrap\n          if (i == 0) {\n            int nextPage = (p == 0) ? Pages - 1 : p - 1;\n            EntryGrid[p][i][j]->SetFocus(\n                EntryGrid[nextPage][RowsPerPage - 1][j], FDIR_UP);\n            EntryGrid[nextPage][RowsPerPage - 1][j]->SetFocus(\n                EntryGrid[p][i][j], FDIR_DOWN);\n            EntryGrid[p][i][j]->SetFocus(EntryGrid[p][i + 1][j], FDIR_DOWN);\n            EntryGrid[p][i + 1][j]->SetFocus(EntryGrid[p][i][j], FDIR_UP);\n            if (j == 1) {\n              EntryGrid[p][i][j]->SetFocus(\n                  EntryGrid[nextPage][RowsPerPage - 1][0], FDIR_RIGHT);\n              EntryGrid[nextPage][RowsPerPage - 1][0]->SetFocus(\n                  EntryGrid[p][i][j], FDIR_LEFT);\n              EntryGrid[p][i][j]->SetFocus(EntryGrid[p][i][j - 1], FDIR_LEFT);\n              EntryGrid[p][i][j - 1]->SetFocus(EntryGrid[p][i][j], FDIR_RIGHT);\n            } else {\n              EntryGrid[p][i][j]->SetFocus(EntryGrid[p][i][j + 1], FDIR_RIGHT);\n              EntryGrid[p][i][j + 1]->SetFocus(EntryGrid[p][i][j], FDIR_LEFT);\n              EntryGrid[p][i][j]->SetFocus(EntryGrid[p][i + 1][j + 1],\n                                           FDIR_LEFT);\n              EntryGrid[p][i + 1][j + 1]->SetFocus(EntryGrid[p][i][j],\n                                                   FDIR_RIGHT);\n            }\n          } else if (i == RowsPerPage - 1) {\n            int nextPage = (p + 1) % Pages;\n            EntryGrid[p][i][j]->SetFocus(EntryGrid[nextPage][0][j], FDIR_DOWN);\n            EntryGrid[nextPage][0][j]->SetFocus(EntryGrid[p][i][j], FDIR_UP);\n            EntryGrid[p][i][j]->SetFocus(EntryGrid[p][i - 1][j], FDIR_UP);\n            EntryGrid[p][i - 1][j]->SetFocus(EntryGrid[p][i][j], FDIR_DOWN);\n            if (j == 0) {\n              EntryGrid[p][i][j]->SetFocus(EntryGrid[nextPage][0][1],\n                                           FDIR_LEFT);\n              EntryGrid[nextPage][0][1]->SetFocus(EntryGrid[p][i][j],\n                                                  FDIR_RIGHT);\n              EntryGrid[p][i][j]->SetFocus(EntryGrid[p][i][j + 1], FDIR_RIGHT);\n              EntryGrid[p][i][j + 1]->SetFocus(EntryGrid[p][i][j], FDIR_LEFT);\n            } else {\n              EntryGrid[p][i][j]->SetFocus(EntryGrid[p][i][j - 1], FDIR_LEFT);\n              EntryGrid[p][i][j - 1]->SetFocus(EntryGrid[p][i][j], FDIR_RIGHT);\n              EntryGrid[p][i][j]->SetFocus(EntryGrid[p][i - 1][j - 1],\n                                           FDIR_RIGHT);\n              EntryGrid[p][i - 1][j - 1]->SetFocus(EntryGrid[p][i][j],\n                                                   FDIR_LEFT);\n            }\n          } else {\n            int nextCol = (j + 1) % EntriesPerRow;\n            int nextRow = j == 0 ? i + 1 : i;\n            int prevRow = j == 1 ? i - 1 : i;\n            EntryGrid[p][i][j]->SetFocus(EntryGrid[p][nextRow][nextCol],\n                                         FDIR_LEFT);\n            EntryGrid[p][nextRow][nextCol]->SetFocus(EntryGrid[p][i][j],\n                                                     FDIR_RIGHT);\n            EntryGrid[p][i][j]->SetFocus(EntryGrid[p][prevRow][nextCol],\n                                         FDIR_RIGHT);\n            EntryGrid[p][prevRow][nextCol]->SetFocus(EntryGrid[p][i][j],\n                                                     FDIR_LEFT);\n            EntryGrid[p][i][j]->SetFocus(EntryGrid[p][i + 1][j], FDIR_DOWN);\n            EntryGrid[p][i + 1][j]->SetFocus(EntryGrid[p][i][j], FDIR_UP);\n            EntryGrid[p][i][j]->SetFocus(EntryGrid[p][i - 1][j], FDIR_UP);\n            EntryGrid[p][i - 1][j]->SetFocus(EntryGrid[p][i][j], FDIR_DOWN);\n          }\n        }\n      }\n    }\n    MainItems[CurrentPage]->Show();\n    CurrentlyFocusedElement = MainItems[CurrentPage]->Children[0];\n    CurrentlyFocusedElement->HasFocus = true;\n    if (UI::FocusedMenu != 0) {\n      LastFocusedMenu = UI::FocusedMenu;\n      LastFocusedMenu->IsFocused = false;\n    }\n    UI::FocusedMenu = this;\n  }\n}\nvoid SaveMenu::Hide() {\n  if (State != Hiding) {\n    State = Hiding;\n    FadeAnimation.StartOut();\n    if (LastFocusedMenu != 0) {\n      UI::FocusedMenu = LastFocusedMenu;\n      LastFocusedMenu->IsFocused = true;\n    } else {\n      UI::FocusedMenu = 0;\n    }\n    IsFocused = false;\n  }\n}\n\nvoid SaveMenu::UpdateInput(float dt) {\n  using namespace Vm::Interface;\n  Menu::UpdateInput(dt);\n  const auto updatePage = [&](int nextPage) {\n    PrevPage = CurrentPage;\n    if (CurrentlyFocusedElement) {\n      CurrentlyFocusedElement->HasFocus = false;\n      CurrentlyFocusedElement->Hovered = false;\n    }\n    CurrentPage = nextPage;\n    MainItems[CurrentPage]->Show();\n    PageAnimation.StartIn();\n    Audio::PlayInGroup(Audio::ACG_SE, \"sysse\", 1, false, 0);\n  };\n  if (IsFocused) {\n    MainItems[CurrentPage]->UpdateInput(dt);\n    if (Input::MouseWheelDeltaY < 0 || PADinputButtonWentDown & PADcustom[8]) {\n      updatePage((CurrentPage + 1) % Pages);\n      CurrentlyFocusedElement = MainItems[CurrentPage]->GetFocus(FDIR_UP);\n      IsFocused = false;\n    } else if (Input::MouseWheelDeltaY > 0 ||\n               PADinputButtonWentDown & PADcustom[7]) {\n      updatePage((CurrentPage - 1 + Pages) % Pages);\n      CurrentlyFocusedElement = MainItems[CurrentPage]->GetFocus(FDIR_DOWN);\n      IsFocused = false;\n    } else {\n      if (PADinputButtonWentDown & (PAD1DOWN | PAD1UP | PAD1RIGHT | PAD1LEFT)) {\n        Audio::PlayInGroup(Audio::ACG_SE, \"sysse\", 1, false, 0);\n      }\n    }\n\n    if (CurrentlyFocusedElement && (PADinputButtonWentDown & PAD1Y)) {\n      Impacto::SaveSystem::SaveType saveType =\n          *ActiveMenuType == SaveMenuPageType::QuickLoad\n              ? SaveSystem::SaveType::Quick\n              : SaveSystem::SaveType::Full;\n      auto saveButton = static_cast<SaveEntryButton*>(CurrentlyFocusedElement);\n      if (SaveSystem::GetSaveStatus(saveType, saveButton->Id) == 1) {\n        saveButton->ToggleLock();\n        saveButton->RefreshInfo();\n        Audio::PlayInGroup(Audio::ACG_SE, \"sysse\", 2, false, 0);\n      } else {\n        Audio::PlayInGroup(Audio::ACG_SE, \"sysse\", 4, false, 0);\n      }\n    }\n  }\n}\n\nvoid SaveMenu::Update(float dt) {\n  if (ScrWork[SW_SYSSUBMENUCT] < 32 && State == Shown) {\n    Hide();\n  } else if (ScrWork[SW_SYSSUBMENUCT] > 0 && State == Hidden &&\n             (ScrWork[SW_SYSSUBMENUNO] == 0 || ScrWork[SW_SYSSUBMENUNO] == 3 ||\n              ScrWork[SW_SYSSUBMENUNO] == 4)) {\n    Show();\n  }\n\n  if (State != Hidden) {\n    FadeAnimation.Update(dt);\n    PageAnimation.Update(dt);\n  }\n\n  if (State == Shown &&\n      (ScrWork[SW_SYSSUBMENUNO] == 0 || ScrWork[SW_SYSSUBMENUNO] == 3 ||\n       ScrWork[SW_SYSSUBMENUNO] == 4)) {\n    UpdateInput(dt);\n  }\n\n  if (State == Showing && FadeAnimation.Progress == 1.0f &&\n      ScrWork[SW_SYSSUBMENUCT] == 32) {\n    State = Shown;\n    IsFocused = true;\n  } else if (State == Hiding && FadeAnimation.Progress == 0.0f &&\n             ScrWork[SW_SYSSUBMENUCT] == 0) {\n    State = Hidden;\n    IsFocused = false;\n    if (UI::FocusedMenu) UI::FocusedMenu->IsFocused = true;\n  }\n\n  if (State == Shown && IsFocused && CurrentlyFocusedElement) {\n    int oldPage = CurrentPage;\n    MainItems[CurrentPage]->Update(dt);\n    CurrentPage =\n        dynamic_cast<SaveEntryButton*>(CurrentlyFocusedElement)->GetPage();\n    if (CurrentPage != oldPage) {\n      auto focusedElem = CurrentlyFocusedElement;\n      PageAnimation.StartIn();\n      MainItems[CurrentPage]->Show();\n      CurrentlyFocusedElement = focusedElem;\n      CurrentlyFocusedElement->HasFocus = true;\n      IsFocused = false;\n    }\n  }\n  if (State == Hidden && !HasCleared) {\n    for (int p = 0; p < Pages; ++p) {\n      MainItems[p]->Clear();\n      delete MainItems[p];\n      MainItems[p] = nullptr;\n    }\n    CurrentlyFocusedElement = nullptr;\n    HasCleared = true;\n  }\n  if (PageAnimation.IsIn()) {\n    MainItems[PrevPage]->MoveTo({0, 0});\n    MainItems[PrevPage]->Hide();\n    PageAnimation.Progress = 0.0f;\n    PrevPage = CurrentPage;\n    IsFocused = true;\n    if (CurrentlyFocusedElement) CurrentlyFocusedElement->HasFocus = true;\n  }\n}\n\nvoid SaveMenu::Render() {\n  if (State == Hidden) return;\n\n  const glm::vec4 col(1.0f, 1.0f, 1.0f, FadeAnimation.Progress);\n  const glm::vec2 transitionOffset = {\n      FadeAnimation.Progress * 32 * 200 * 0.0625 - 400, 0};\n  const glm::vec4 maskTint = glm::vec4(1.0f);\n\n  Renderer->DrawSprite(MenuTextSprite[*ActiveMenuType],\n                       MenuTextPosition + transitionOffset, col);\n  MainItems[CurrentPage]->Tint = col;\n  if (PageAnimation.State == AnimationState::Playing) {\n    bool isNextBelow = ((PrevPage + 1) % Pages) == CurrentPage;\n    const float currentYPos =\n        (1.0f - PageAnimation.Progress) *\n        (isNextBelow ? Profile::DesignHeight : -Profile::DesignHeight);\n    const float prevYPos = isNextBelow ? currentYPos - Profile::DesignHeight\n                                       : currentYPos + Profile::DesignHeight;\n    const glm::vec2 currentOffset{0, currentYPos};\n    const glm::vec2 prevOffset{0, prevYPos};\n\n    Renderer->DrawSprite(\n        EntrySlotsSprite[*ActiveMenuType],\n        SlotsBackgroundPosition + transitionOffset + prevOffset, col);\n    Renderer->DrawSprite(\n        EntrySlotsSprite[*ActiveMenuType],\n        SlotsBackgroundPosition + transitionOffset + currentOffset, col);\n    MainItems[PrevPage]->MoveTo(transitionOffset + prevOffset);\n    MainItems[CurrentPage]->MoveTo(transitionOffset + currentOffset);\n    MainItems[PrevPage]->Render();\n    MainItems[CurrentPage]->Render();\n\n    const glm::vec2 prevPgNumPos =\n        PageNumberPosition + transitionOffset + prevOffset;\n    const glm::vec2 curPgNumPos =\n        PageNumberPosition + transitionOffset + currentOffset;\n    Renderer->DrawSprite(PageNumSprite[*ActiveMenuType][PrevPage], prevPgNumPos,\n                         col);\n    Renderer->DrawSprite(PageNumSprite[*ActiveMenuType][CurrentPage],\n                         curPgNumPos, col);\n  } else {\n    Renderer->DrawSprite(EntrySlotsSprite[*ActiveMenuType],\n                         SlotsBackgroundPosition + transitionOffset, col);\n    MainItems[CurrentPage]->MoveTo(transitionOffset);\n    MainItems[CurrentPage]->Render();\n    Renderer->DrawSprite(PageNumSprite[*ActiveMenuType][CurrentPage],\n                         PageNumberPosition + transitionOffset, col);\n  }\n\n  Renderer->DrawSprite(\n      SaveMenuMaskSprite,\n      RectF(0.0f, 0.0f, Profile::DesignWidth, Profile::DesignHeight), maskTint);\n  Renderer->DrawSprite(ButtonGuideSprite[*ActiveMenuType], {0, 989}, col);\n}\n\nvoid SaveMenu::RefreshCurrentEntryInfo() {\n  if (!CurrentlyFocusedElement) return;\n  static_cast<SaveEntryButton*>(CurrentlyFocusedElement)->RefreshInfo();\n}\n\n}  // namespace CCLCC\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/cclcc/savemenu.h",
    "content": "#pragma once\n\n#include \"../../ui/menu.h\"\n#include \"../../ui/widgets/group.h\"\n#include \"../../ui/widgets/button.h\"\n#include \"../../ui/savemenu.h\"\n#include \"../../profile/games/cclcc/savemenu.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace CCLCC {\n\nclass SaveMenu : public UI::SaveMenu {\n public:\n  SaveMenu();\n\n  void Show() override;\n  void Hide() override;\n  void Update(float dt) override;\n  void UpdateInput(float dt) override;\n  void Render() override;\n\n  void RefreshCurrentEntryInfo() override;\n\n  void MenuButtonOnClick(Widgets::Button* target);\n\n private:\n  int PrevPage = 0;\n  int CurrentPage = 0;\n  Widgets::Group* MainItems[Profile::CCLCC::SaveMenu::Pages]{};\n  Animation FadeAnimation;\n  Animation PageAnimation;\n  bool HasCleared = true;\n};\n\n}  // namespace CCLCC\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/cclcc/savesystem.cpp",
    "content": "#include \"savesystem.h\"\n\n#include \"../../io/stream.h\"\n#include \"../../io/physicalfilestream.h\"\n#include \"../../mem.h\"\n#include \"../../vm/vm.h\"\n#include \"../../profile/data/savesystem.h\"\n#include \"../../profile/scriptvars.h\"\n#include \"../../profile/vm.h\"\n#include \"../../renderer/renderer.h\"\n#include \"../../profile/configsystem.h\"\n#include \"../../effects/wave.h\"\n#include \"../../audio/audiosystem.h\"\n\n#include \"mapsystem.h\"\n#include \"yesnotrigger.h\"\n\n#include <cstdint>\n#include <ctime>\n#include <system_error>\n#include <ranges>\n\nnamespace Impacto {\nnamespace CCLCC {\n\nusing namespace Impacto::Vm;\nusing namespace Impacto::Effects;\nusing namespace Impacto::Profile::SaveSystem;\nusing namespace Impacto::Profile::ScriptVars;\nusing namespace Impacto::Profile::Vm;\nusing namespace Impacto::Profile::ConfigSystem;\n\nuint32_t CalculateChecksum(std::span<const uint8_t> bufferData,\n                           uint16_t initSum = 0, uint16_t initXor = 0,\n                           bool swapSrcBytes = false) {\n  // Initialize checksum variables\n\n  uint32_t checksumSum = initSum;\n\n  uint32_t checksumXor = initXor;\n\n  for (size_t i = 0; i < bufferData.size() - 1; i += 2) {\n    uint16_t dataShort;\n    memcpy(&dataShort, &bufferData[i], 2);\n    if (swapSrcBytes) {\n      dataShort = SDL_Swap16(dataShort);\n    }\n    checksumSum = checksumSum + dataShort;\n    checksumXor = checksumXor ^ dataShort;\n  }\n\n  uint32_t result = (checksumSum << 16) | (checksumXor & 0xFFFF);\n  return result;\n}\n\nSaveError SaveSystem::CheckSaveFile() const {\n  std::error_code ec;\n\n  IoError existsState = Io::PathExists(SaveFilePath);\n  if (existsState == IoError_NotFound) {\n    return SaveError::NotFound;\n  } else if (existsState == IoError_Fail) {\n    ImpLog(LogLevel::Error, LogChannel::IO,\n           \"Failed to check if save file exists, error: \\\"{:s}\\\"\\n\",\n           ec.message());\n    return SaveError::Failed;\n  }\n\n  auto saveFileSize = Io::GetFileSize(SaveFilePath);\n  if (saveFileSize == IoError_Fail) {\n    ImpLog(LogLevel::Error, LogChannel::IO,\n           \"Failed to get save file size, error: \\\"{:s}\\\"\\n\", ec.message());\n    return SaveError::Failed;\n  } else if (saveFileSize != SaveFileSize) {\n    return SaveError::Corrupted;\n  }\n\n  Io::FilePermissionsFlags perms;\n  IoError permsState = Io::GetFilePermissions(SaveFilePath, perms);\n  using enum Io::FilePermissionsFlags;\n  if (permsState == IoError_Fail) {\n    ImpLog(LogLevel::Error, LogChannel::IO,\n           \"Failed to get save file permissions, error: \\\"{:s}\\\"\\n\",\n           ec.message());\n    return SaveError::Failed;\n  } else if ((perms & owner_read) == none || (perms & owner_write) == none) {\n    return SaveError::WrongUser;\n  }\n\n  return SaveError::OK;\n}\n\nvoid SaveSystem::InitializeSystemData() {\n  std::fill(SystemData.begin(), SystemData.end(), 0x00);\n\n  Io::MemoryStream stream =\n      Io::MemoryStream(SystemData.data(), SystemData.size(), false);\n\n  stream.Seek(0x8AC, SEEK_SET);\n  Io::WriteLE(&stream, (Uint16)(Default::TextSpeed * 60));\n  Io::WriteLE(&stream, (Uint16)(Default::AutoSpeed * 60));\n  Io::WriteLE(&stream, (Uint8)(Default::GroupVolumes[Audio::ACG_Voice] *\n                               128));  // VOICE2vol\n  Io::WriteLE(&stream, (Uint8)(Default::GroupVolumes[Audio::ACG_Voice] *\n                               128));  // VOICEvol\n  Io::WriteLE(&stream, (Uint8)(Default::GroupVolumes[Audio::ACG_BGM] * 256));\n  Io::WriteLE(&stream,\n              (Uint8)(Default::GroupVolumes[Audio::ACG_SE] * 128));  // SEvol\n  Io::WriteLE(\n      &stream,\n      (Uint8)(Default::GroupVolumes[Audio::ACG_SE] * 0.6 * 128));  // SYSSEvol\n  Io::WriteLE(&stream, (Uint8)(Default::GroupVolumes[Audio::ACG_Movie] * 128));\n  Io::WriteLE(&stream, Default::SyncVoice);\n  Io::WriteLE(&stream, !Default::SkipRead);\n\n  stream.Seek(0x8BE, SEEK_SET);\n  for (size_t i = 0; i < 33; i++) Io::WriteLE(&stream, !Default::VoiceMuted[i]);\n  for (size_t i = 0; i < 33; i++)\n    Io::WriteLE(&stream, (Uint8)(Default::VoiceVolume[i] * 128));\n\n  stream.Seek(0x901, SEEK_SET);\n  Io::WriteLE(&stream, Default::SkipVoice);\n  Io::WriteLE(&stream, Default::ShowTipsNotification);\n\n  stream.Seek(0x905, SEEK_SET);\n  Io::WriteLE(&stream, Default::AdvanceTextOnDirectionalInput);\n  Io::WriteLE(&stream, Default::DirectionalInputForTrigger);\n  Io::WriteLE(&stream, Default::TriggerStopSkip);\n\n  std::for_each_n(QuickSaveEntries, MaxSaveEntries,\n                  [](auto& ptr) { ptr = new SaveFileEntry(); });\n  std::for_each_n(FullSaveEntries, MaxSaveEntries,\n                  [](auto& ptr) { ptr = new SaveFileEntry(); });\n  WorkingSaveEntry = SaveFileEntry();\n\n  WorkingSaveThumbnail.Sheet =\n      SpriteSheet(static_cast<float>(Window->WindowWidth),\n                  static_cast<float>(Window->WindowHeight));\n  WorkingSaveThumbnail.Bounds =\n      RectF(0.0f, 0.0f, static_cast<float>(Window->WindowWidth),\n            static_cast<float>(Window->WindowHeight));\n\n  Texture workingSaveTexture = Texture();\n  workingSaveTexture.LoadSolidColor(\n      static_cast<int>(WorkingSaveThumbnail.Bounds.Width),\n      static_cast<int>(WorkingSaveThumbnail.Bounds.Height), 0x000000);\n  WorkingSaveThumbnail.Sheet.Texture = workingSaveTexture.Submit();\n}\n\nvoid SaveSystem::LoadEntryBuffer(Io::MemoryStream& stream, SaveFileEntry& entry,\n                                 SaveType saveType, Texture& tex) {\n  entry.Status = Io::ReadLE<uint8_t>(&stream);\n  Io::ReadLE<uint8_t>(&stream);\n  uint16_t checksumSum = Io::ReadLE<uint16_t>(&stream);\n  uint16_t checksumXor = Io::ReadLE<uint16_t>(&stream);\n\n  entry.Checksum = checksumSum << 16 | checksumXor;\n  Io::ReadLE<uint16_t>(&stream);\n  uint16_t saveYear = Io::ReadLE<uint16_t>(&stream);\n  uint8_t saveDay = Io::ReadLE<uint8_t>(&stream);\n  uint8_t saveMonth = Io::ReadLE<uint8_t>(&stream);\n  uint8_t saveSecond = Io::ReadLE<uint8_t>(&stream);\n  uint8_t saveMinute = Io::ReadLE<uint8_t>(&stream);\n  uint8_t saveHour = Io::ReadLE<uint8_t>(&stream);\n  std::tm t{};\n  t.tm_sec = saveSecond;\n  t.tm_min = saveMinute;\n  t.tm_hour = saveHour;\n  t.tm_mday = saveDay;\n  t.tm_mon = saveMonth - 1;\n  t.tm_year = saveYear - 1900;\n  entry.SaveDate = t;\n\n  Io::ReadLE<uint8_t>(&stream);\n  entry.PlayTime = Io::ReadLE<uint32_t>(&stream);\n  entry.SwTitle = Io::ReadLE<uint32_t>(&stream);\n  Io::ReadLE<uint32_t>(&stream);\n  entry.Flags = Io::ReadLE<uint8_t>(&stream);\n  stream.Seek(7, SEEK_CUR);\n  entry.SaveType = Io::ReadLE<uint32_t>(&stream);\n  assert(stream.Position == 0x28);\n  stream.Seek(0x58, SEEK_CUR);\n  Io::ReadArrayLE<uint8_t>(entry.FlagWorkScript1.data(), &stream,\n                           entry.FlagWorkScript1.size());\n  assert(stream.Position == 178);\n  Io::ReadArrayLE<uint8_t>(entry.FlagWorkScript2.data(), &stream,\n                           entry.FlagWorkScript2.size());\n  Io::ReadLE<uint16_t>(&stream);\n  assert(stream.Position == 280);\n  Io::ReadArrayLE<int>(entry.ScrWorkScript1.data(), &stream,\n                       entry.ScrWorkScript1.size());\n  assert(stream.Position == 2680);\n  Io::ReadArrayLE<int>(entry.ScrWorkScript2.data(), &stream,\n                       entry.ScrWorkScript2.size());\n\n  assert(stream.Position == 0x3958);\n  entry.MainThreadExecPriority = Io::ReadLE<uint32_t>(&stream);\n  entry.MainThreadGroupId = Io::ReadLE<uint32_t>(&stream);\n  entry.MainThreadWaitCounter = Io::ReadLE<uint32_t>(&stream);\n  entry.MainThreadScriptParam = Io::ReadLE<uint32_t>(&stream);\n  entry.MainThreadIp = Io::ReadLE<uint32_t>(&stream);\n  entry.MainThreadLoopCounter = Io::ReadLE<uint32_t>(&stream);\n  entry.MainThreadLoopAdr = Io::ReadLE<uint32_t>(&stream);\n  entry.MainThreadCallStackDepth = Io::ReadLE<uint32_t>(&stream);\n  for (int j = 0; j < 8; j++) {\n    entry.MainThreadReturnIds[j] = Io::ReadLE<uint32_t>(&stream);\n  }\n  for (int j = 0; j < 8; j++) {\n    entry.MainThreadReturnBufIds[j] = Io::ReadLE<uint32_t>(&stream);\n  }\n  Io::ReadLE<uint32_t>(&stream);\n  assert(stream.Position == 0x39bc);\n  entry.MainThreadScriptBufferId = Io::ReadLE<uint32_t>(&stream);\n  Io::ReadArrayBE<int>(entry.MainThreadVariables.data(), &stream, 16);\n  entry.MainThreadDialoguePageId = Io::ReadLE<uint32_t>(&stream);\n  assert(stream.Position == 0x3a04);\n  Io::ReadArrayLE<int>(entry.WaveData.data(), &stream, entry.WaveData.size());\n  assert(stream.Position == 0x3ec0);\n  Io::ReadArrayLE<uint8_t>(entry.MapLoadData.data(), &stream,\n                           entry.MapLoadData.size());\n  Io::ReadArrayLE<uint8_t>(entry.YesNoData.data(), &stream,\n                           entry.YesNoData.size());\n\n  Sprite& thumbnail = entry.SaveThumbnail;\n  thumbnail.Sheet = SpriteSheet(SaveThumbnailWidth, SaveThumbnailHeight);\n  thumbnail.Bounds = RectF(0.0f, 0.0f, SaveThumbnailWidth, SaveThumbnailHeight);\n\n  tex.Init(TexFmt_RGB, SaveThumbnailWidth, SaveThumbnailHeight);\n\n  int thumbnailPadding = 0xA14;\n  stream.Seek(thumbnailPadding, SEEK_CUR);\n  Io::ReadArrayLE<uint8_t>(entry.ThumbnailData.data(), &stream,\n                           entry.ThumbnailData.size());\n\n  for (size_t i = 0; i < entry.ThumbnailData.size() / 2; i++) {\n    uint16_t pixel =\n        entry.ThumbnailData[i * 2] | (entry.ThumbnailData[i * 2 + 1] << 8);\n    uint8_t r = (pixel & 0xF800) >> 8;\n    uint8_t g = (uint8_t)((pixel & 0x07E0) >> 3);\n    uint8_t b = (pixel & 0x001F) << 3;\n    tex.Buffer[3 * i] = r;\n    tex.Buffer[3 * i + 1] = g;\n    tex.Buffer[3 * i + 2] = b;\n  }\n}\n\nSaveError SaveSystem::MountSaveFile(std::vector<QueuedTexture>& textures) {\n  Io::Stream* stream;\n  IoError err = Io::PhysicalFileStream::Create(SaveFilePath, &stream);\n  switch (err) {\n    case IoError_NotFound:\n      return SaveError::NotFound;\n    case IoError_Fail:\n    case IoError_Eof:\n      return SaveError::Corrupted;\n    case IoError_OK:\n      break;\n  };\n  WorkingSaveEntry = std::optional<SaveFileEntry>(SaveFileEntry());\n  WorkingSaveThumbnail.Sheet =\n      SpriteSheet((float)Window->WindowWidth, (float)Window->WindowHeight);\n  WorkingSaveThumbnail.Bounds = RectF(0.0f, 0.0f, (float)Window->WindowWidth,\n                                      (float)Window->WindowHeight);\n\n  QueuedTexture txt{\n      .Id = std::ref(WorkingSaveThumbnail.Sheet.Texture),\n  };\n  txt.Tex.LoadSolidColor((int)WorkingSaveThumbnail.Bounds.Width,\n                         (int)WorkingSaveThumbnail.Bounds.Height, 0x000000);\n  textures.push_back(txt);\n\n  Io::ReadArrayLE<uint8_t>(SystemData.data(), stream, SystemData.size());\n  /*\n  uint32_t systemSaveChecksum =\n      CalculateChecksum(std::span(SystemData).subspan(4));\n  */\n\n  int lockedQuickSaveSlots = 0;\n  textures.reserve(MaxSaveEntries * 2);\n  for (auto& entryArray : {FullSaveEntries, QuickSaveEntries}) {\n    SaveType saveType =\n        (entryArray == QuickSaveEntries) ? SaveType::Quick : SaveType::Full;\n    [[maybe_unused]] int64_t saveDataPos = stream->Position;\n    for (int i = 0; i < MaxSaveEntries; i++) {\n      assert(stream->Position - saveDataPos ==\n             static_cast<int>(SaveEntrySize) * i);\n      entryArray[i] = new SaveFileEntry();\n\n      std::array<uint8_t, SaveEntrySize> entrySlotBuf;\n      Io::ReadArrayLE<uint8_t>(entrySlotBuf.data(), stream,\n                               entrySlotBuf.size());\n      Io::MemoryStream saveEntryDataStream(entrySlotBuf.data(),\n                                           entrySlotBuf.size(), false);\n\n      QueuedTexture tex{\n          .Id = std::ref(entryArray[i]->SaveThumbnail.Sheet.Texture),\n      };\n      LoadEntryBuffer(saveEntryDataStream,\n                      static_cast<SaveFileEntry&>(*entryArray[i]), saveType,\n                      tex.Tex);\n      if (saveType == SaveType::Quick) {\n        lockedQuickSaveSlots +=\n            static_cast<SaveFileEntry&>(*entryArray[i]).Flags & WriteProtect;\n      }\n      textures.push_back(tex);\n\n      // Todo, validate checksum?\n    }\n  }\n  SetLockedQuickSaveCount(lockedQuickSaveSlots);\n  SetFlag(SF_SAVEALLPROTECTED, LockedQuickSaveCount == MaxSaveEntries);\n\n  delete stream;\n  return SaveError::OK;\n}\n\nvoid SaveSystem::FlushWorkingSaveEntry(SaveType type, int id,\n                                       int autoSaveType) {\n  auto* entry = GetSaveEntry<SaveFileEntry>(type, id);\n  if (entry != nullptr && !(entry->Flags & WriteProtect)) {\n    Renderer->FreeTexture(entry->SaveThumbnail.Sheet.Texture);\n    uint8_t savedFlags = entry->Flags;\n    *entry = *WorkingSaveEntry;\n    entry->Flags = savedFlags;\n    if (type == SaveType::Quick) {\n      entry->SaveType = autoSaveType;\n      UpdateQuickSaveRecentSortedId(id);\n    }\n    entry->SaveDate = CurrentDateTime();\n    auto captureBuffer =\n        Renderer->GetSpriteSheetImage(WorkingSaveThumbnail.Sheet);\n\n    Texture tex;\n    tex.Init(TexFmt_RGBA, SaveThumbnailWidth, SaveThumbnailHeight);\n\n    entry->SaveThumbnail.Sheet =\n        SpriteSheet(SaveThumbnailWidth, SaveThumbnailHeight);\n    entry->SaveThumbnail.Bounds =\n        RectF(0.0f, 0.0f, SaveThumbnailWidth, SaveThumbnailHeight);\n\n    int result =\n        ResizeImage(WorkingSaveThumbnail.Bounds, entry->SaveThumbnail.Bounds,\n                    captureBuffer, tex.Buffer);\n    if (result < 0) {\n      ImpLog(LogLevel::Error, LogChannel::General,\n             \"Failed to resize save thumbnail\\n\");\n    }\n    entry->SaveThumbnail.Sheet.Texture = tex.Submit();\n  }\n}\n\nvoid SaveSystem::SaveEntryBuffer(Io::MemoryStream& memoryStream,\n                                 SaveFileEntry& entry, SaveType saveType) {\n  Io::WriteLE<uint16_t>(&memoryStream, entry.Status);\n  Io::WriteLE<uint32_t>(&memoryStream, entry.Checksum);\n  Io::WriteLE<uint16_t>(&memoryStream, 0);\n\n  Io::WriteLE<uint16_t>(&memoryStream,\n                        (uint16_t)(entry.SaveDate.tm_year + 1900));\n  Io::WriteLE<uint8_t>(&memoryStream, (uint8_t)entry.SaveDate.tm_mday);\n  Io::WriteLE<uint8_t>(&memoryStream, (uint8_t)(entry.SaveDate.tm_mon + 1));\n  Io::WriteLE<uint8_t>(&memoryStream, (uint8_t)entry.SaveDate.tm_sec);\n  Io::WriteLE<uint8_t>(&memoryStream, (uint8_t)entry.SaveDate.tm_min);\n  Io::WriteLE<uint8_t>(&memoryStream, (uint8_t)entry.SaveDate.tm_hour);\n  Io::WriteLE<uint8_t>(&memoryStream, 0);\n\n  Io::WriteLE<uint32_t>(&memoryStream, entry.PlayTime);\n  Io::WriteLE<uint32_t>(&memoryStream, entry.SwTitle);\n  Io::WriteLE<uint32_t>(&memoryStream, 0);\n  Io::WriteLE<uint8_t>(&memoryStream, entry.Flags);\n  memoryStream.Seek(7, SEEK_CUR);\n  Io::WriteLE<uint32_t>(&memoryStream, entry.SaveType);\n  assert(memoryStream.Position == 0x28);\n  memoryStream.Seek(0x58, SEEK_CUR);\n  Io::WriteArrayLE<uint8_t>(entry.FlagWorkScript1.data(), &memoryStream,\n                            entry.FlagWorkScript1.size());\n  assert(memoryStream.Position == 178);\n  Io::WriteArrayLE<uint8_t>(entry.FlagWorkScript2.data(), &memoryStream,\n                            entry.FlagWorkScript2.size());\n  Io::WriteLE<uint16_t>(&memoryStream, 0);\n  assert(memoryStream.Position == 280);\n  Io::WriteArrayLE<int>(entry.ScrWorkScript1.data(), &memoryStream,\n                        entry.ScrWorkScript1.size());\n  assert(memoryStream.Position == 2680);\n  Io::WriteArrayLE<int>(entry.ScrWorkScript2.data(), &memoryStream,\n                        entry.ScrWorkScript2.size());\n\n  assert(memoryStream.Position == 0x3958);\n  Io::WriteLE<uint32_t>(&memoryStream, entry.MainThreadExecPriority);\n  Io::WriteLE<uint32_t>(&memoryStream, entry.MainThreadGroupId);\n  Io::WriteLE<uint32_t>(&memoryStream, entry.MainThreadWaitCounter);\n  Io::WriteLE<uint32_t>(&memoryStream, entry.MainThreadScriptParam);\n  Io::WriteLE<uint32_t>(&memoryStream, entry.MainThreadIp);\n  Io::WriteLE<uint32_t>(&memoryStream, entry.MainThreadLoopCounter);\n  Io::WriteLE<uint32_t>(&memoryStream, entry.MainThreadLoopAdr);\n  Io::WriteLE<uint32_t>(&memoryStream, entry.MainThreadCallStackDepth);\n  for (int j = 0; j < 8; j++) {\n    Io::WriteLE<uint32_t>(&memoryStream, entry.MainThreadReturnIds[j]);\n  }\n  for (int j = 0; j < 8; j++) {\n    Io::WriteLE<uint32_t>(&memoryStream, entry.MainThreadReturnBufIds[j]);\n  }\n  Io::WriteLE<uint32_t>(&memoryStream, 0);\n  assert(memoryStream.Position == 0x39bc);\n  Io::WriteLE<uint32_t>(&memoryStream, entry.MainThreadScriptBufferId);\n  Io::WriteArrayBE<int>(entry.MainThreadVariables.data(), &memoryStream, 16);\n  Io::WriteLE<uint32_t>(&memoryStream, entry.MainThreadDialoguePageId);\n  assert(memoryStream.Position == 0x3a04);\n  Io::WriteArrayLE<int>(entry.WaveData.data(), &memoryStream,\n                        entry.WaveData.size());\n  assert(memoryStream.Position == 0x3ec0);\n  Io::WriteArrayLE<uint8_t>(entry.MapLoadData.data(), &memoryStream,\n                            entry.MapLoadData.size());\n  Io::WriteArrayLE<uint8_t>(entry.YesNoData.data(), &memoryStream,\n                            entry.YesNoData.size());\n\n  int thumbnailPadding = 0xA14;\n  memoryStream.Seek(thumbnailPadding, SEEK_CUR);\n\n  Io::WriteArrayLE<uint8_t>(entry.ThumbnailData.data(), &memoryStream,\n                            entry.ThumbnailData.size());\n}\n\nvoid SaveSystem::SaveThumbnailData() {\n  std::vector<uint8_t> thumbnailBuffer(SaveThumbnailSize * 2);\n\n  for (auto* entryArray : {FullSaveEntries, QuickSaveEntries}) {\n    for (int i = 0; i < MaxSaveEntries; i++) {\n      SaveFileEntry* entry = (SaveFileEntry*)entryArray[i];\n      if (entry->Status == 0) continue;\n\n      Renderer->GetSpriteSheetImage(entry->SaveThumbnail.Sheet,\n                                    thumbnailBuffer);\n\n      std::array<uint8_t, SaveThumbnailSize>& thumbnailData =\n          entry->ThumbnailData;\n\n      for (size_t j = 0; j < thumbnailBuffer.size(); j += 4) {\n        uint8_t r = thumbnailBuffer[j];\n        uint8_t g = thumbnailBuffer[j + 1];\n        uint8_t b = thumbnailBuffer[j + 2];\n        uint16_t pixel = ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3);\n        pixel = SDL_SwapBE16(pixel);\n        thumbnailData[j / 2] = pixel >> 8;\n        thumbnailData[j / 2 + 1] = pixel & 0xFF;\n      }\n    }\n  }\n}\n\nSaveError SaveSystem::LoadSystemData() {\n  Io::MemoryStream stream =\n      Io::MemoryStream(SystemData.data(), SystemData.size(), false);\n\n  /*\n  uint16_t systemSum = Io::ReadLE<uint16_t>(&stream);\n  uint16_t systemXor = Io::ReadLE<uint16_t>(&stream);\n  */\n  stream.Seek(0x14, SEEK_SET);\n\n  stream.Seek(0x80, SEEK_SET);\n  Io::ReadArrayLE<uint8_t>(&FlagWork[100], &stream, 50);\n  Io::ReadArrayLE<uint8_t>(&FlagWork[460], &stream, 40);\n  stream.Seek(0xDC, SEEK_SET);\n  Io::ReadArrayLE<int>(&ScrWork[1600], &stream, 400);\n  Io::ReadArrayLE<int>(&ScrWork[2000], &stream, 100);\n\n  // Config settings\n  stream.Seek(0x8AC, SEEK_SET);\n  TextSpeed = Io::ReadLE<Uint16>(&stream) / 60.0f;\n  AutoSpeed = Io::ReadLE<Uint16>(&stream) / 60.0f;\n  stream.Seek(1, SEEK_CUR);  // VOICE2vol\n  Audio::GroupVolumes[Audio::ACG_Voice] = Io::ReadLE<Uint8>(&stream) / 128.0f;\n  Audio::GroupVolumes[Audio::ACG_BGM] = Io::ReadLE<Uint8>(&stream) / 256.0f;\n  Audio::GroupVolumes[Audio::ACG_SE] = Io::ReadLE<Uint8>(&stream) / 128.0f;\n  stream.Seek(1, SEEK_CUR);  // SYSSEvol\n  Audio::GroupVolumes[Audio::ACG_Movie] = Io::ReadLE<Uint8>(&stream) / 128.0f;\n  SyncVoice = Io::ReadLE<bool>(&stream);\n  SkipRead = !Io::ReadLE<bool>(&stream);\n\n  stream.Seek(0x8BE, SEEK_SET);\n  for (size_t i = 0; i < 33; i++) VoiceMuted[i] = !Io::ReadLE<bool>(&stream);\n  for (size_t i = 0; i < 33; i++)\n    VoiceVolume[i] = Io::ReadLE<Uint8>(&stream) / 128.0f;\n\n  stream.Seek(0x901, SEEK_SET);\n  SkipVoice = Io::ReadLE<bool>(&stream);\n  ShowTipsNotification = Io::ReadLE<bool>(&stream);\n\n  stream.Seek(0x905, SEEK_SET);\n  AdvanceTextOnDirectionalInput = Io::ReadLE<bool>(&stream);\n  DirectionalInputForTrigger = Io::ReadLE<bool>(&stream);\n  TriggerStopSkip = Io::ReadLE<bool>(&stream);\n\n  stream.Seek(0xbce, SEEK_SET);\n  Io::ReadArrayLE<uint8_t>(QuickSaveRecentSortedId.data(), &stream,\n                           QuickSaveRecentSortedId.size());\n  std::array<uint8_t, MaxSaveEntries> sortedIdFreq{};\n\n  // backwards compat with older saves\n  for (uint8_t qsSlotId : QuickSaveRecentSortedId) {\n    if (sortedIdFreq[qsSlotId]++ > 1) {\n      std::iota(QuickSaveRecentSortedId.begin(), QuickSaveRecentSortedId.end(),\n                0);\n      std::ranges::sort(QuickSaveRecentSortedId,\n                        Impacto::SaveSystem::SaveRecencyComparator(),\n                        [this](int id) { return *QuickSaveEntries[id]; });\n      break;\n    }\n  }\n\n  // EV Flags\n  stream.Seek(0xC0E, SEEK_SET);\n  for (int i = 0; i < 150; i++) {\n    auto val = Io::ReadU8(&stream);\n    EVFlags[8 * i] = val & 1;\n    EVFlags[8 * i + 1] = (val & 2) != 0;\n    EVFlags[8 * i + 2] = (val & 4) != 0;\n    EVFlags[8 * i + 3] = (val & 8) != 0;\n    EVFlags[8 * i + 4] = (val & 0x10) != 0;\n    EVFlags[8 * i + 5] = (val & 0x20) != 0;\n    EVFlags[8 * i + 6] = (val & 0x40) != 0;\n    EVFlags[8 * i + 7] = val >> 7;\n  }\n\n  stream.Seek(0xCA4, SEEK_SET);\n  Io::ReadArrayLE<uint8_t>(BGMFlags, &stream, 200);\n\n  stream.Seek(0xd6c, SEEK_SET);\n  Io::ReadArrayLE<uint8_t>(MessageFlags, &stream, 10000);\n\n  // EPnewList goes here\n\n  stream.Seek(0x347c, SEEK_SET);\n  Io::ReadArrayLE<uint8_t>(GameExtraData, &stream, 1024);\n\n  return SaveError::OK;\n}\n\nvoid SaveSystem::SaveSystemData() {\n  Io::MemoryStream systemSaveStream =\n      Io::MemoryStream(SystemData.data(), SystemData.size(), false);\n\n  systemSaveStream.Seek(0x14, SEEK_SET);\n\n  systemSaveStream.Seek(0x80, SEEK_SET);\n  Io::WriteArrayLE<uint8_t>(&FlagWork[100], &systemSaveStream, 50);\n  Io::WriteArrayLE<uint8_t>(&FlagWork[460], &systemSaveStream, 40);\n  systemSaveStream.Seek(0xDC, SEEK_SET);\n  Io::WriteArrayLE<int>(&ScrWork[1600], &systemSaveStream, 400);\n  Io::WriteArrayLE<int>(&ScrWork[2000], &systemSaveStream, 100);\n\n  // Config settings\n  systemSaveStream.Seek(0x8AC, SEEK_SET);\n  Io::WriteLE(&systemSaveStream, (Uint16)(TextSpeed * 60));\n  Io::WriteLE(&systemSaveStream, (Uint16)(AutoSpeed * 60));\n  Io::WriteLE(\n      &systemSaveStream,\n      (Uint8)(Audio::GroupVolumes[Audio::ACG_Voice] * 128));  // VOICE2vol\n  Io::WriteLE(\n      &systemSaveStream,\n      (Uint8)(Audio::GroupVolumes[Audio::ACG_Voice] * 128));  // VOICEvol\n  Io::WriteLE(&systemSaveStream,\n              (Uint8)(Audio::GroupVolumes[Audio::ACG_BGM] * 256));\n  Io::WriteLE(&systemSaveStream,\n              (Uint8)(Audio::GroupVolumes[Audio::ACG_SE] * 128));  // SEvol\n  Io::WriteLE(\n      &systemSaveStream,\n      (Uint8)(Audio::GroupVolumes[Audio::ACG_SE] * 0.6 * 128));  // SYSSEvol\n  Io::WriteLE(&systemSaveStream,\n              (Uint8)(Audio::GroupVolumes[Audio::ACG_Movie] * 128));\n  Io::WriteLE(&systemSaveStream, SyncVoice);\n  Io::WriteLE(&systemSaveStream, !SkipRead);\n\n  systemSaveStream.Seek(0x8BE, SEEK_SET);\n  for (size_t i = 0; i < 33; i++)\n    Io::WriteLE(&systemSaveStream, !VoiceMuted[i]);\n  for (size_t i = 0; i < 33; i++)\n    Io::WriteLE(&systemSaveStream, (Uint8)(VoiceVolume[i] * 128));\n\n  systemSaveStream.Seek(0x901, SEEK_SET);\n  Io::WriteLE(&systemSaveStream, SkipVoice);\n  Io::WriteLE(&systemSaveStream, ShowTipsNotification);\n\n  systemSaveStream.Seek(0x905, SEEK_SET);\n  Io::WriteLE(&systemSaveStream, AdvanceTextOnDirectionalInput);\n  Io::WriteLE(&systemSaveStream, DirectionalInputForTrigger);\n  Io::WriteLE(&systemSaveStream, TriggerStopSkip);\n\n  systemSaveStream.Seek(0xbce, SEEK_SET);\n  Io::WriteArrayLE<uint8_t>(QuickSaveRecentSortedId.data(), &systemSaveStream,\n                            QuickSaveRecentSortedId.size());\n\n  // EV Flags\n  systemSaveStream.Seek(0xC0E, SEEK_SET);\n  for (int i = 0; i < 150; i++) {\n    const uint8_t evByte = (static_cast<uint8_t>(EVFlags[8 * i])) |\n                           (static_cast<uint8_t>(EVFlags[8 * i + 1]) << 1) |\n                           (static_cast<uint8_t>(EVFlags[8 * i + 2]) << 2) |\n                           (static_cast<uint8_t>(EVFlags[8 * i + 3]) << 3) |\n                           (static_cast<uint8_t>(EVFlags[8 * i + 4]) << 4) |\n                           (static_cast<uint8_t>(EVFlags[8 * i + 5]) << 5) |\n                           (static_cast<uint8_t>(EVFlags[8 * i + 6]) << 6) |\n                           (static_cast<uint8_t>(EVFlags[8 * i + 7]) << 7);\n    Io::WriteLE<uint8_t>(&systemSaveStream, evByte);\n  }\n\n  systemSaveStream.Seek(0xCA4, SEEK_SET);\n  Io::WriteArrayLE<uint8_t>(BGMFlags, &systemSaveStream, 200);\n\n  systemSaveStream.Seek(0xd6c, SEEK_SET);\n  Io::WriteArrayLE<uint8_t>(MessageFlags, &systemSaveStream, 10000);\n\n  // EPnewList goes here\n\n  systemSaveStream.Seek(0x347c, SEEK_SET);\n  Io::WriteArrayLE<uint8_t>(GameExtraData, &systemSaveStream, 1024);\n}\n\nSaveError SaveSystem::WriteSaveFile() {\n  using CF = Io::PhysicalFileStream::CreateFlagsMode;\n  Io::Stream* stream;\n  IoError err = Io::PhysicalFileStream::Create(\n      SaveFilePath, &stream, CF::CREATE | CF::CREATE_DIRS | CF::WRITE);\n  if (err != IoError_OK) {\n    ImpLog(LogLevel::Error, LogChannel::IO,\n           \"Failed to open save file for writing\\n\");\n    return SaveError::Failed;\n  }\n\n  stream->Seek(0, SEEK_SET);\n  Io::MemoryStream systemSaveStream =\n      Io::MemoryStream(SystemData.data(), SystemData.size(), false);\n  uint32_t systemChecksum = CalculateChecksum(std::span(SystemData).subspan(4));\n  systemSaveStream.Seek(0, SEEK_SET);\n  Io::WriteLE<uint16_t>(&systemSaveStream, systemChecksum >> 16);\n  Io::WriteLE<uint16_t>(&systemSaveStream, systemChecksum & 0xFFFF);\n  Io::WriteArrayLE<uint8_t>(SystemData.data(), stream, SystemData.size());\n  // End system data\n  for (auto* entryArray : {FullSaveEntries, QuickSaveEntries}) {\n    SaveType saveType =\n        (entryArray == QuickSaveEntries) ? SaveType::Quick : SaveType::Full;\n    [[maybe_unused]] int64_t saveDataPos = stream->Position;\n    for (int i = 0; i < MaxSaveEntries; i++) {\n      SaveFileEntry* entry = (SaveFileEntry*)entryArray[i];\n      if (entry == nullptr || entry->Status == 0) {\n        Io::WriteLE<uint8_t>(stream, 0, SaveEntrySize);\n      } else {\n        assert(stream->Position - saveDataPos ==\n               static_cast<int>(SaveEntrySize) * i);\n        std::array<uint8_t, SaveEntrySize> entrySlotBuf{};\n        Io::MemoryStream saveEntryMemoryStream(entrySlotBuf.data(),\n                                               entrySlotBuf.size(), false);\n        SaveEntryBuffer(saveEntryMemoryStream, *entry, saveType);\n        uint32_t entryCheckSum = CalculateChecksum(\n            std::span(entrySlotBuf).subspan(6, 23029 * 2), 18198, 5250, false);\n        saveEntryMemoryStream.Seek(2, SEEK_SET);\n        Io::WriteLE<uint16_t>(&saveEntryMemoryStream, entryCheckSum >> 16);\n        Io::WriteLE<uint16_t>(&saveEntryMemoryStream, entryCheckSum & 0xFFFF);\n        Io::WriteArrayLE<uint8_t>(entrySlotBuf.data(), stream,\n                                  entrySlotBuf.size());\n      }\n    }\n  }\n  delete stream;\n  return SaveError::OK;\n}\n\nuint32_t SaveSystem::GetSavePlayTime(SaveType type, int id) const {\n  return GetSaveEntry<SaveFileEntry>(type, id)->PlayTime;\n}\n\nuint8_t SaveSystem::GetSaveFlags(SaveType type, int id) const {\n  return GetSaveEntry<SaveFileEntry>(type, id)->Flags;\n}\n\nvoid SaveSystem::SetSaveFlags(SaveType type, int id, uint8_t flags) {\n  auto* entry = GetSaveEntry<SaveFileEntry>(type, id);\n\n  if (type == SaveType::Quick) {\n    uint8_t currentFlags = entry->Flags;\n    if ((currentFlags ^ flags) & WriteProtect) {\n      if (flags & WriteProtect) {\n        LockedQuickSaveCount++;\n      } else {\n        LockedQuickSaveCount--;\n      }\n\n      SetFlag(SF_SAVEALLPROTECTED, LockedQuickSaveCount == MaxSaveEntries);\n    }\n  }\n  entry->Flags = flags;\n}\n\ntm const& SaveSystem::GetSaveDate(SaveType type, int id) const {\n  return GetSaveEntry<SaveFileEntry>(type, id)->SaveDate;\n}\n\nvoid SaveSystem::SaveMemory() {\n  if (WorkingSaveEntry) {\n    WorkingSaveEntry->Status = 1;\n\n    const tm timeinfo = CurrentDateTime();\n    WorkingSaveEntry->SaveDate = timeinfo;\n    WorkingSaveEntry->PlayTime = ScrWork[SW_PLAYTIME];\n    WorkingSaveEntry->SwTitle = ScrWork[SW_TITLE];\n\n    std::copy(FlagWork.begin() + 50,\n              FlagWork.begin() + 50 + WorkingSaveEntry->FlagWorkScript1.size(),\n              WorkingSaveEntry->FlagWorkScript1.begin());\n    std::copy(FlagWork.begin() + 300,\n              FlagWork.begin() + 300 + WorkingSaveEntry->FlagWorkScript2.size(),\n              WorkingSaveEntry->FlagWorkScript2.begin());\n    std::copy(ScrWork.begin() + 1000,\n              ScrWork.begin() + 1000 + WorkingSaveEntry->ScrWorkScript1.size(),\n              WorkingSaveEntry->ScrWorkScript1.begin());\n    std::copy(ScrWork.begin() + 4300,\n              ScrWork.begin() + 4300 + WorkingSaveEntry->ScrWorkScript2.size(),\n              WorkingSaveEntry->ScrWorkScript2.begin());\n\n    int threadId = ScrWork[SW_MAINTHDP];\n    Sc3VmThread* thd = &ThreadPool[threadId & 0x7FFFFFFF];\n    if (thd->GroupId - 5 < 3) {\n      WorkingSaveEntry->MainThreadExecPriority = thd->ExecPriority;\n      WorkingSaveEntry->MainThreadWaitCounter = thd->WaitCounter;\n      WorkingSaveEntry->MainThreadScriptParam = thd->ScriptParam;\n      WorkingSaveEntry->MainThreadGroupId = thd->GroupId;\n      WorkingSaveEntry->MainThreadScriptBufferId = thd->ScriptBufferId;\n      // Checkpoint id should already be set by SetCheckpointId\n      WorkingSaveEntry->MainThreadCallStackDepth = thd->CallStackDepth;\n      for (size_t i = 0; i < thd->CallStackDepth; i++) {\n        WorkingSaveEntry->MainThreadReturnBufIds[i] =\n            thd->ReturnScriptBufferIds[i];\n        WorkingSaveEntry->MainThreadReturnIds[i] = thd->ReturnIds[i];\n      }\n      memcpy(WorkingSaveEntry->MainThreadVariables.data(), thd->Variables,\n             16 * sizeof(int));\n      WorkingSaveEntry->MainThreadDialoguePageId = thd->DialoguePageId;\n    }\n    UI::CCLCC::MapSystem::GetInstance().MapSave(\n        WorkingSaveEntry->MapLoadData.data());\n    UI::CCLCC::YesNoTrigger::GetInstance().Save(\n        WorkingSaveEntry->YesNoData.data());\n    WaveSave(std::span(WorkingSaveEntry->WaveData));\n  }\n}\n\nvoid SaveSystem::LoadEntry(SaveType type, int id) {\n  if (!WorkingSaveEntry) {\n    ImpLog(LogLevel::Error, LogChannel::IO,\n           \"Failed to load save memory: no working save\\n\");\n    return;\n  }\n  WorkingSaveEntry = *GetSaveEntry<SaveFileEntry>(type, id);\n}\n\nvoid SaveSystem::LoadMemoryNew(LoadProcess load) {\n  if (!WorkingSaveEntry || WorkingSaveEntry->Status == 0) {\n    ImpLog(LogLevel::Error, LogChannel::IO,\n           \"Failed to load entry: save is empty\\n\");\n    return;\n  }\n  if (load == LoadProcess::Vars) {\n    ScrWork[SW_PLAYTIME] = WorkingSaveEntry->PlayTime;\n    ScrWork[SW_TITLE] = WorkingSaveEntry->SwTitle;\n    ScrWork[SW_AUTOSAVERESTART] = WorkingSaveEntry->SaveType;\n\n    std::ranges::copy(WorkingSaveEntry->FlagWorkScript1, FlagWork.begin() + 50);\n    std::ranges::copy(WorkingSaveEntry->FlagWorkScript2,\n                      FlagWork.begin() + 300);\n    std::ranges::copy(WorkingSaveEntry->ScrWorkScript1, ScrWork.begin() + 1000);\n    std::ranges::copy(WorkingSaveEntry->ScrWorkScript2, ScrWork.begin() + 4300);\n\n    UI::CCLCC::MapSystem::GetInstance().MapLoad(\n        WorkingSaveEntry->MapLoadData.data());\n    UI::CCLCC::YesNoTrigger::GetInstance().Load(\n        WorkingSaveEntry->YesNoData.data());\n    WaveLoad(std::span(WorkingSaveEntry->WaveData));\n\n    // TODO: What to do about this mess I wonder...\n    ScrWork[SW_SVSENO] = ScrWork[SW_SEREQNO];\n    ScrWork[SW_SVSENO + 1] = ScrWork[SW_SEREQNO + 1];\n    ScrWork[SW_SVSENO + 2] = ScrWork[SW_SEREQNO + 2];\n    ScrWork[SW_SVBGMNO] = ScrWork[SW_BGMREQNO];\n    ScrWork[SW_SVBGM2NO] = ScrWork[SW_BGMREQNO2];\n    ScrWork[SW_SVSCRNO1] = ScrWork[SW_SCRIPTNO2];\n    ScrWork[SW_SVSCRNO2] = ScrWork[SW_SCRIPTNO3];\n    ScrWork[SW_SVSCRNO3] = ScrWork[SW_SCRIPTNO4];\n    ScrWork[SW_SVSCRNO4] = ScrWork[SW_SCRIPTNO5];\n    for (int i = 0; i < 8; i++) {\n      ScrWork[SW_SVBGNO1 + i] = ScrWork[SW_BG1NO + i * ScrWorkBgStructSize];\n      ScrWork[SW_SVCHANO1 + i] = ScrWork[SW_CHA1NO + i * ScrWorkChaStructSize];\n    }\n  } else if (ScrWork[SW_MAINTHDP] != 0) {\n    int threadId = ScrWork[SW_MAINTHDP];\n    Sc3VmThread* thd = &ThreadPool[threadId & 0x7FFFFFFF];\n\n    if (thd->GroupId - 5 < 3) {\n      thd->ExecPriority = WorkingSaveEntry->MainThreadExecPriority;\n      thd->WaitCounter = WorkingSaveEntry->MainThreadWaitCounter;\n      thd->ScriptParam = WorkingSaveEntry->MainThreadScriptParam;\n      thd->GroupId = WorkingSaveEntry->MainThreadGroupId;\n      thd->ScriptBufferId = WorkingSaveEntry->MainThreadScriptBufferId;\n      thd->IpOffset =\n          ScriptGetRetAddress(WorkingSaveEntry->MainThreadScriptBufferId,\n                              WorkingSaveEntry->MainThreadIp);\n      thd->CallStackDepth = WorkingSaveEntry->MainThreadCallStackDepth;\n\n      for (size_t i = 0; i < thd->CallStackDepth; i++) {\n        thd->ReturnScriptBufferIds[i] =\n            WorkingSaveEntry->MainThreadReturnBufIds[i];\n        thd->ReturnIds[i] = (uint16_t)WorkingSaveEntry->MainThreadReturnIds[i];\n      }\n\n      memcpy(thd->Variables, WorkingSaveEntry->MainThreadVariables.data(),\n             16 * sizeof(int));\n      thd->DialoguePageId = WorkingSaveEntry->MainThreadDialoguePageId;\n    }\n  }\n}\n\nuint8_t SaveSystem::GetSaveStatus(SaveType type, int id) const {\n  return GetSaveEntry<SaveFileEntry>(type, id)->Status;\n}\n\nint SaveSystem::GetSaveTitle(SaveType type, int id) const {\n  return GetSaveEntry<SaveFileEntry>(type, id)->SwTitle;\n}\n\nuint32_t SaveSystem::GetTipStatus(size_t tipId) const {\n  tipId *= 3;\n  uint8_t lockStatus = (GameExtraData[tipId >> 3] & Flbit[tipId & 7]) != 0;\n  uint8_t newStatus =\n      (GameExtraData[(tipId + 1) >> 3] & Flbit[(tipId + 1) & 7]) != 0;\n  uint8_t unreadStatus =\n      (GameExtraData[(tipId + 2) >> 3] & Flbit[(tipId + 2) & 7]) != 0;\n  return (lockStatus | (unreadStatus << 1)) | (newStatus << 2);\n}\n\nvoid SaveSystem::SetTipStatus(size_t tipId, bool isLocked, bool isUnread,\n                              bool isNew) {\n  tipId *= 3;\n  if (isLocked) {\n    GameExtraData[tipId >> 3] &= ~(Flbit[tipId & 7]);\n  } else {\n    GameExtraData[tipId >> 3] |= Flbit[tipId & 7];\n  }\n  if (isUnread) {\n    GameExtraData[(tipId + 2) >> 3] &= ~(Flbit[(tipId + 2) & 7]);\n  } else {\n    GameExtraData[(tipId + 2) >> 3] |= Flbit[(tipId + 2) & 7];\n  }\n  if (isNew) {\n    GameExtraData[(tipId + 1) >> 3] &= ~(Flbit[(tipId + 1) & 7]);\n  } else {\n    GameExtraData[(tipId + 1) >> 3] |= Flbit[(tipId + 1) & 7];\n  }\n}\n\nvoid SaveSystem::SetLineRead(int scriptId, int lineId) {\n  if (scriptId >= 255) return;\n\n  uint32_t offset = ScriptMessageData[scriptId].SaveDataOffset + lineId;\n  if (offset == 0xFFFFFFFF) return;\n\n  // TODO: update some ScrWorks (2003, 2005 & 2006)\n\n  MessageFlags[offset >> 3] |= Flbit[offset & 0b111];\n}\n\nbool SaveSystem::IsLineRead(int scriptId, int lineId) const {\n  if (scriptId >= 255) return false;\n\n  uint32_t offset = ScriptMessageData[scriptId].SaveDataOffset + lineId;\n  uint8_t flbit = Flbit[offset & 0b111];\n  uint8_t viewed = MessageFlags[offset >> 3];\n\n  return (bool)(flbit & viewed);\n}\n\nvoid SaveSystem::GetReadMessagesCount(int* totalMessageCount,\n                                      int* readMessageCount) const {\n  *totalMessageCount = 0;\n  *readMessageCount = 0;\n\n  for (int scriptId = 0; scriptId < StoryScriptCount; scriptId++) {\n    ScriptMessageDataPair script = ScriptMessageData[scriptId];\n    *totalMessageCount += script.LineCount;\n\n    for (size_t lineId = 0; lineId < script.LineCount; lineId++) {\n      *readMessageCount += IsLineRead(scriptId, (int)lineId);\n    }\n  }\n}\n\nvoid SaveSystem::GetViewedEVsCount(int* totalEVCount,\n                                   int* viewedEVCount) const {\n  for (int i = 0; i < MaxAlbumEntries; i++) {\n    if (AlbumEvData[i][0] == 0xFFFF) break;\n    for (int j = 0; j < MaxAlbumSubEntries; j++) {\n      if (AlbumEvData[i][j] == 0xFFFF) break;\n      *totalEVCount += 1;\n      *viewedEVCount += EVFlags[AlbumEvData[i][j]];\n    }\n  }\n}\nvoid SaveSystem::GetEVStatus(int evId, int* totalVariations,\n                             int* viewedVariations) const {\n  *totalVariations = 0;\n  *viewedVariations = 0;\n  for (int i = 0; i < MaxAlbumSubEntries; i++) {\n    if (AlbumEvData[evId][i] == 0xFFFF) break;\n    *totalVariations += 1;\n    *viewedVariations += EVFlags[AlbumEvData[evId][i]];\n  }\n}\n\nvoid SaveSystem::SetEVStatus(int id) { EVFlags[id] = true; }\n\nbool SaveSystem::GetEVVariationIsUnlocked(size_t evId,\n                                          size_t variationIdx) const {\n  if (AlbumEvData[evId][variationIdx] == 0xFFFF) return false;\n  return EVFlags[AlbumEvData[evId][variationIdx]];\n}\n\nbool SaveSystem::GetBgmFlag(int id) const { return BGMFlags[id]; }\nvoid SaveSystem::SetBgmFlag(int id, bool flag) { BGMFlags[id] = flag; }\n\nvoid SaveSystem::SetCheckpointId(int id) {\n  if (WorkingSaveEntry) WorkingSaveEntry->MainThreadIp = id;\n}\n\nSprite& SaveSystem::GetSaveThumbnail(SaveType type, int id) {\n  return GetSaveEntry<SaveFileEntry>(type, id)->SaveThumbnail;\n}\n\nvoid SaveSystem::WaveSave(std::span<int> data) {\n  size_t offset = 0;\n  for (size_t i = 0; i < 20; i++) {\n    data[offset++] = WaveBG.WaveData[i].Flags;\n    data[offset++] = WaveBG.WaveData[i].Amplitude;\n    data[offset++] = WaveBG.WaveData[i].TemporalFrequency;\n    data[offset++] = WaveBG.WaveData[i].Phase;\n    data[offset++] = WaveBG.WaveData[i].SpatialFrequency;\n  }\n  for (size_t i = 0; i < 20; i++) {\n    data[offset++] = WaveEFF.WaveData[i].Flags;\n    data[offset++] = WaveEFF.WaveData[i].Amplitude;\n    data[offset++] = WaveEFF.WaveData[i].TemporalFrequency;\n    data[offset++] = WaveEFF.WaveData[i].Phase;\n    data[offset++] = WaveEFF.WaveData[i].SpatialFrequency;\n  }\n  for (size_t i = 0; i < 20; i++) {\n    data[offset++] = WaveCHA.WaveData[i].Flags;\n    data[offset++] = WaveCHA.WaveData[i].Amplitude;\n    data[offset++] = WaveCHA.WaveData[i].TemporalFrequency;\n    data[offset++] = WaveCHA.WaveData[i].Phase;\n    data[offset++] = WaveCHA.WaveData[i].SpatialFrequency;\n  }\n\n  data[offset++] = WaveBG.WaveCount;\n  data[offset++] = WaveEFF.WaveCount;\n  data[offset++] = WaveCHA.WaveCount;\n}\n\nvoid SaveSystem::WaveLoad(std::span<const int> data) const {\n  size_t offset = 0;\n  for (size_t i = 0; i < 20; i++) {\n    WaveBG.WaveData[i].Flags = data[offset++];\n    WaveBG.WaveData[i].Amplitude = data[offset++];\n    WaveBG.WaveData[i].TemporalFrequency = data[offset++];\n    WaveBG.WaveData[i].Phase = data[offset++];\n    WaveBG.WaveData[i].SpatialFrequency = data[offset++];\n  }\n  for (size_t i = 0; i < 20; i++) {\n    WaveEFF.WaveData[i].Flags = data[offset++];\n    WaveEFF.WaveData[i].Amplitude = data[offset++];\n    WaveEFF.WaveData[i].TemporalFrequency = data[offset++];\n    WaveEFF.WaveData[i].Phase = data[offset++];\n    WaveEFF.WaveData[i].SpatialFrequency = data[offset++];\n  }\n  for (size_t i = 0; i < 20; i++) {\n    WaveCHA.WaveData[i].Flags = data[offset++];\n    WaveCHA.WaveData[i].Amplitude = data[offset++];\n    WaveCHA.WaveData[i].TemporalFrequency = data[offset++];\n    WaveCHA.WaveData[i].Phase = data[offset++];\n    WaveCHA.WaveData[i].SpatialFrequency = data[offset++];\n  }\n\n  WaveBG.WaveCount = data[offset++];\n  WaveEFF.WaveCount = data[offset++];\n  WaveCHA.WaveCount = data[offset++];\n}\n\n}  // namespace CCLCC\n}  // namespace Impacto"
  },
  {
    "path": "src/games/cclcc/savesystem.h",
    "content": "#pragma once\n\n#include \"../../data/savesystem.h\"\n#include \"../../texture/texture.h\"\n#include \"../../io/memorystream.h\"\n#include \"../../spritesheet.h\"\n#include <optional>\n\nnamespace Impacto {\nnamespace CCLCC {\n\nusing namespace Impacto::SaveSystem;\n\nconstexpr size_t SaveEntrySize = 0x1b110;\nconstexpr int SaveFileSize = SaveEntrySize * MaxSaveEntries * 2 + 0x387c;\n\nconstexpr int SaveThumbnailWidth = 240;\nconstexpr int SaveThumbnailHeight = 135;\n// CCLCC PS4 Save thumbnails are 240x135 RGB16\nconstexpr int SaveThumbnailSize =\n    SaveThumbnailWidth * SaveThumbnailHeight * 4 / 2;\n\nclass SaveFileEntry : public SaveFileEntryBase {\n public:\n  std::array<uint8_t, 50> FlagWorkScript1;   // 50 bytes from &FlagWork[50]\n  std::array<uint8_t, 100> FlagWorkScript2;  // 100 bytes from &FlagWork[300]\n  std::array<int, 600> ScrWorkScript1;       // 2400 bytes from &ScrWork[1000]\n  std::array<int, 3000> ScrWorkScript2;      // 12000 bytes from &ScrWork[4300]\n  std::array<uint8_t, 0x6ac8> MapLoadData;\n  std::array<uint8_t, 0x54> YesNoData;\n  std::array<int, 303>\n      WaveData;  // 3 wave types * 20 waves * 5 fields + 3 counts\n  std::array<uint8_t, SaveThumbnailSize> ThumbnailData;\n};\n\nclass SaveSystem : public SaveSystemBase {\n public:\n  SaveError CheckSaveFile() const override;\n  SaveError MountSaveFile(std::vector<QueuedTexture>& textures) override;\n\n  SaveError LoadSystemData() override;\n  void SaveSystemData() override;\n  void InitializeSystemData() override;\n\n  void SaveThumbnailData() override;\n  Sprite& GetSaveThumbnail(SaveType type, int id) override;\n\n  void LoadEntryBuffer(Io::MemoryStream& memoryStream, SaveFileEntry& entry,\n                       SaveType saveType, Texture& tex);\n  void SaveEntryBuffer(Io::MemoryStream& memoryStream, SaveFileEntry& entry,\n                       SaveType saveType);\n  void LoadEntry(SaveType type, int id) override;\n  void FlushWorkingSaveEntry(SaveType type, int id, int autoSaveType) override;\n\n  void SaveMemory() override;\n  void LoadMemoryNew(LoadProcess load) override;\n\n  SaveError WriteSaveFile() override;\n  uint32_t GetSavePlayTime(SaveType type, int id) const override;\n  uint8_t GetSaveFlags(SaveType type, int id) const override;\n  void SetSaveFlags(SaveType type, int id, uint8_t flags) override;\n  tm const& GetSaveDate(SaveType type, int id) const override;\n  uint8_t GetSaveStatus(SaveType type, int id) const override;\n  int GetSaveTitle(SaveType type, int id) const override;\n\n  uint32_t GetTipStatus(size_t tipId) const override;\n  void SetTipStatus(size_t tipId, bool isLocked, bool isUnread,\n                    bool isNew) override;\n\n  void SetLineRead(int scriptId, int lineId) override;\n  bool IsLineRead(int scriptId, int lineId) const override;\n  void GetReadMessagesCount(int* totalMessageCount,\n                            int* readMessageCount) const override;\n\n  void GetViewedEVsCount(int* totalEVCount, int* viewedEVCount) const override;\n  void GetEVStatus(int evId, int* totalVariations,\n                   int* viewedVariations) const override;\n  void SetEVStatus(int id) override;\n  bool GetEVVariationIsUnlocked(size_t evId,\n                                size_t variationIdx) const override;\n\n  bool GetBgmFlag(int id) const override;\n  void SetBgmFlag(int id, bool flag) override;\n\n  void SetCheckpointId(int id) override;\n\n  void WaveSave(std::span<int> data);\n  void WaveLoad(std::span<const int> data) const;\n\n private:\n  uint8_t GameExtraData[1024];\n  uint8_t MessageFlags[10000];\n  std::array<uint8_t, 0x387c> SystemData;\n  bool EVFlags[1200];\n  uint8_t BGMFlags[200];\n  std::optional<SaveFileEntry> WorkingSaveEntry;\n};\n\n}  // namespace CCLCC\n}  // namespace Impacto"
  },
  {
    "path": "src/games/cclcc/systemmenu.cpp",
    "content": "#include \"systemmenu.h\"\n#include <glm/common.hpp>\n#include \"../../profile/games/cclcc/systemmenu.h\"\n#include \"../../renderer/renderer.h\"\n#include \"../../ui/ui.h\"\n#include \"../../vm/interface/input.h\"\n#include \"../../inputsystem.h\"\n#include \"../../profile/ui/systemmenu.h\"\n#include \"../../profile/game.h\"\n#include \"../../ui/widgets/cclcc/sysmenubutton.h\"\n#include \"../../audio/audiosystem.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace CCLCC {\n\nusing namespace Impacto::Profile::CCLCC::SystemMenu;\nusing namespace Impacto::Profile::ScriptVars;\nusing namespace Impacto::Vm::Interface;\nusing namespace Impacto::Profile::SystemMenu;\nusing namespace Impacto::UI::Widgets::CCLCC;\nusing namespace Impacto::Input;\n\nvoid SystemMenu::MenuButtonOnClick(Widgets::Button* target) {\n  bool targetButtonLocked = static_cast<SysMenuButton*>(target)->IsLocked;\n  Audio::PlayInGroup(Audio::ACG_SE, \"sysse\", targetButtonLocked ? 4 : 2, false,\n                     0);\n  if (targetButtonLocked) {\n    // Yep, that's similar to how it's done in the binary\n    // Binary checks for button state and if it's locked, PADone is modified\n    // like button press never happened, and then script reads inputs\n    PADinputButtonWentDown = PADinputButtonWentDown & ~PAD1A;\n    PADinputMouseWentDown = PADinputMouseWentDown & ~PAD1A;\n    return;\n  }\n\n  ScrWork[SW_SYSMENUCNO] = target->Id;\n  // Make the Id match the save menu mode (5th button would be Quick Load\n  // which is case 0)\n  auto newMenuType = magic_enum::enum_cast<SaveMenuPageType>(target->Id % 4);\n  if (newMenuType) {\n    SaveMenuPtr->ActiveMenuType = newMenuType;\n  }\n  ChoiceMade = true;\n}\n\nvoid SystemMenu::UpdateInput(float dt) {\n  if (!IsFocused) return;\n  const auto* const prevSelected = CurrentlyFocusedElement;\n  Menu::UpdateInput(dt);\n  MainItems->UpdateInput(dt);\n  if (CurrentlyFocusedElement && prevSelected != CurrentlyFocusedElement) {\n    Audio::PlayInGroup(Audio::ACG_SE, \"sysse\", 1, false, 0);\n  }\n}\n\nSystemMenu::SystemMenu() {\n  MenuTransition.Direction = AnimationDirection::In;\n  MenuTransition.LoopMode = AnimationLoopMode::Stop;\n  MenuTransition.DurationIn = MoveInDuration;\n  MenuTransition.DurationOut = MoveOutDuration;\n\n  MenuFade.Direction = AnimationDirection::In;\n  MenuFade.LoopMode = AnimationLoopMode::Stop;\n  MenuFade.DurationIn = FadeInDuration;\n  MenuFade.DurationOut = FadeOutDuration;\n\n  ItemsFade.Direction = AnimationDirection::In;\n  ItemsFade.LoopMode = AnimationLoopMode::Stop;\n  ItemsFade.DurationIn = ItemsFadeInDuration;\n  ItemsFade.DurationOut = ItemsFadeOutDuration;\n\n  auto onClick = [this](auto* btn) { return MenuButtonOnClick(btn); };\n\n  MainItems = new Widgets::Group(this);\n\n  ScreenCap.Sheet =\n      SpriteSheet((float)Window->WindowWidth, (float)Window->WindowHeight);\n  Texture tex;\n  tex.LoadSolidColor(Window->WindowWidth, Window->WindowHeight, 0x000000);\n  ScreenCap.Sheet.Texture = tex.Submit();\n  ScreenCap.Bounds.Width = ScreenCap.Sheet.DesignWidth;\n  ScreenCap.Bounds.Height = ScreenCap.Sheet.DesignHeight;\n\n  for (int i = 0; i < MenuEntriesNum; i++) {\n    SysMenuButton* menuButton = new SysMenuButton(\n        i, MenuEntriesSprites[i], Sprite(), MenuEntriesHSprites[i],\n        MenuEntriesPositions[i], MenuEntriesButtonBounds[i]);\n\n    menuButton->OnClickHandler = onClick;\n    MainItems->Add(menuButton, FDIR_DOWN);\n  }\n  MainItems->Children[0]->SetFocus(MainItems->Children[MenuEntriesNum - 1],\n                                   FDIR_UP);\n  MainItems->Children[MenuEntriesNum - 1]->SetFocus(MainItems->Children[0],\n                                                    FDIR_DOWN);\n}\nvoid SystemMenu::Show() {\n  if (State != Shown) {\n    State = Showing;\n    MenuTransition.StartIn();\n    MenuFade.StartIn();\n    // If the function was called due to a submenu opening directly,\n    // then don't take over focus\n    if (!((ScrWork[SW_SYSMENUCT] == 32 && ScrWork[SW_SYSSUBMENUCT]) ||\n          ScrWork[SW_CLRALPHA])) {\n      if (UI::FocusedMenu != 0) {\n        LastFocusedMenu = UI::FocusedMenu;\n        LastFocusedMenu->IsFocused = false;\n      }\n      IsFocused = true;\n      UI::FocusedMenu = this;\n      ItemsFade.StartIn();\n      MainItems->Show();\n      if (LastFocusedButtonId && *LastFocusedButtonId < MenuEntriesNum) {\n        CurrentlyFocusedElement = MainItems->Children[*LastFocusedButtonId];\n        CurrentlyFocusedElement->HasFocus = true;\n      } else if (!CurrentlyFocusedElement) {\n        AdvanceFocus(FDIR_DOWN);\n      }\n    }\n  }\n}\n\nvoid SystemMenu::Hide() {\n  if (State != Hidden) {\n    if (CurrentlyFocusedElement) {\n      auto* btn = static_cast<Widgets::Button*>(CurrentlyFocusedElement);\n      if (btn) {\n        LastFocusedButtonId = btn->Id;\n      }\n    }\n    State = Hiding;\n    MenuFade.StartOut();\n    MenuTransition.StartOut();\n    ItemsFade.StartOut();\n    if (LastFocusedMenu != 0) {\n      UI::FocusedMenu = LastFocusedMenu;\n      LastFocusedMenu->IsFocused = true;\n    } else {\n      UI::FocusedMenu = 0;\n    }\n    IsFocused = false;\n  }\n}\n\nvoid SystemMenu::Update(float dt) {\n  UpdateInput(dt);\n\n  if (State == Shown &&\n      ((GetFlag(SF_TITLEMODE) || ScrWork[SW_SYSMENUCT] < 32) ||\n       ScrWork[SW_SYSMENUALPHA] == 0)) {\n    Hide();\n    return;\n  }\n  if (State == Hidden && !GetFlag(SF_TITLEMODE) &&\n      ((ScrWork[SW_SYSMENUCT] > 0) || ScrWork[SW_SYSMENUALPHA] > 0)) {\n    Show();\n    return;\n  }\n  if (State == Showing && ScrWork[SW_SYSMENUCT] == 32) {\n    State = Shown;\n    return;\n  } else if (State == Hiding && MenuFade.IsOut() && MenuTransition.IsOut() &&\n             ItemsFade.IsOut() && ScrWork[SW_SYSMENUCT] == 0) {\n    State = Hidden;\n    MainItems->Hide();\n    return;\n  }\n\n  if (State != Hidden) {\n    MenuTransition.Update(dt);\n    MenuFade.Update(dt);\n    if (ItemsFade.IsIn() && ScrWork[SW_SYSSUBMENUCT] > 0 && State == Shown &&\n        ItemsFadeComplete) {\n      ItemsFade.StartOut();\n      ItemsFadeComplete = false;\n    } else if (ItemsFade.IsOut() && ScrWork[SW_SYSSUBMENUCT] < 32 &&\n               State == Shown && ItemsFadeComplete) {\n      ItemsFade.StartIn();\n      ItemsFadeComplete = false;\n    }\n    ItemsFade.Update(dt);\n    if (!ItemsFadeComplete) {\n      if (ItemsFade.IsIn() && ScrWork[SW_SYSSUBMENUCT] == 0) {\n        ItemsFadeComplete = true;\n      } else if (ItemsFade.IsOut() && ScrWork[SW_SYSSUBMENUCT] == 32) {\n        ItemsFadeComplete = true;\n      }\n    }\n\n    bool savesDisabled = GetFlag(SF_SAVEDISABLE);\n    bool noFreeSlots = SaveSystem::MaxSaveEntries ==\n                       SaveSystem::Implementation->GetLockedQuickSaveCount();\n    bool quickSaveLockState =\n        savesDisabled || noFreeSlots || SaveSystem::HasQSavedOnCurrentLine();\n    static_cast<UI::CCLCC::SysMenuButton*>(\n        MainItems->Children[static_cast<size_t>(MenuItems::QuickSave)])\n        ->IsLocked = quickSaveLockState;\n    static_cast<UI::CCLCC::SysMenuButton*>(\n        MainItems->Children[static_cast<size_t>(MenuItems::Save)])\n        ->IsLocked = savesDisabled;\n  }\n\n  if (State == Shown && IsFocused) {\n    MainItems->Update(dt);\n\n    if ((CurrentInputDevice == Device::Mouse ||\n         CurrentInputDevice == Device::Touch) &&\n        ((PADinputMouseWentDown & PAD1A))) {\n      bool noButtonsHovered = true;\n      for (auto child : MainItems->Children) {\n        auto button = static_cast<UI::CCLCC::SysMenuButton*>(child);\n        if (button->Hovered) {\n          noButtonsHovered = false;\n          break;\n        }\n      }\n\n      if (noButtonsHovered) {\n        PADinputMouseWentDown = PADinputMouseWentDown & ~PAD1A;\n        PADinputButtonWentDown = PADinputButtonWentDown & ~PAD1A;\n      }\n    }\n  }\n}\n\nstatic auto GenerateMatrix(CornersQuad const& corners) {\n  auto get2DIndex = [](int x, int y) { return x + (GridColCount + 1) * y; };\n  constexpr int xVerticesCount = GridColCount + 1;\n  constexpr int yVerticesCount = GridRowCount + 1;\n  std::array<float, xVerticesCount> topRowX;\n  std::array<float, xVerticesCount> bottomRowX;\n  const float topRowWidthDelta =\n      (corners.TopRight.x - corners.TopLeft.x) / GridColCount;\n  const float bottomRowWidthDelta =\n      (corners.BottomRight.x - corners.BottomLeft.x) / GridColCount;\n  for (int i = 0; i < xVerticesCount; i++) {\n    topRowX[i] = corners.TopLeft.x + topRowWidthDelta * i;\n    bottomRowX[i] = corners.BottomLeft.x + bottomRowWidthDelta * i;\n  }\n  std::array<float, yVerticesCount> leftColY;\n  std::array<float, yVerticesCount> rightColY;\n  const float leftColHeightDelta =\n      (corners.BottomLeft.y - corners.TopLeft.y) / GridRowCount;\n  const float rightColHeightDelta =\n      (corners.BottomRight.y - corners.TopRight.y) / GridRowCount;\n  for (int i = 0; i < yVerticesCount; i++) {\n    leftColY[i] = corners.TopLeft.y + leftColHeightDelta * i;\n    rightColY[i] = corners.TopRight.y + rightColHeightDelta * i;\n  }\n\n  std::array<float, xVerticesCount> xDeltas;\n  for (int col = 0; col < std::ssize(xDeltas); col++) {\n    xDeltas[col] = (bottomRowX[col] - topRowX[col]) / GridRowCount;\n  }\n\n  std::array<float, yVerticesCount> yDeltas;\n  for (int row = 0; row < std::ssize(yDeltas); row++) {\n    yDeltas[row] = (rightColY[row] - leftColY[row]) / GridColCount;\n  }\n\n  std::array<glm::vec2, yVerticesCount * xVerticesCount> matrix;\n  for (int row = 0; row < yVerticesCount; row++) {\n    for (int col = 0; col < xVerticesCount; col++) {\n      glm::vec2 pos = {topRowX[col] + xDeltas[col] * row,\n                       leftColY[row] + yDeltas[row] * col};\n      matrix[get2DIndex(col, row)] = pos;\n    }\n  }\n  return matrix;\n}\n\nstatic glm::vec2 TransformImageVertex(const glm::vec2 vertex,\n                                      glm::mat4 const& transformation,\n                                      glm::vec2 const& origin) {\n  glm::vec4 transformedVertex = {vertex, 0.0f, 1.0f};\n  transformedVertex = transformation * transformedVertex;\n  const float perspective = (transformedVertex.z / 2000.0f) + 1.0f;\n  transformedVertex *= perspective;\n  transformedVertex += glm::vec4(origin, 0.0f, 0.0f);\n\n  return transformedVertex;\n}\n\nstatic void TransformImage(CornersQuad const& sprCorners,\n                           CornersQuad const& destCorners,\n                           glm::mat4 const& transformation, glm::vec2 origin,\n                           glm::vec2 sheetBounds,\n                           SystemMenu::GridVertices& vertices) {\n  auto spriteVertices = GenerateMatrix(sprCorners);\n  auto displayVertices = GenerateMatrix(destCorners);\n\n  for (size_t i = 0; i < vertices.size(); i++) {\n    vertices[i] = VertexBufferSprites{\n        .Position =\n            TransformImageVertex(displayVertices[i], transformation, origin),\n        .UV = spriteVertices[i] / sheetBounds,\n        .Tint = glm::vec4(1.0f),\n    };\n  }\n}\n\nvoid SystemMenu::Render() {\n  if (State != Hidden && !GetFlag(SF_TITLEMODE)) {\n    if (MenuTransition.IsIn()) {\n    }\n    glm::vec3 tint = {1.0f, 1.0f, 1.0f};\n    // Alpha goes from 0 to 1 in half the time\n    float alpha = MenuFade.Progress;\n    // Renderer->DrawSprite(BackgroundFilter, RectF(0.0f, 0.0f, 1280.0f,\n    // 720.0f),\n    //                      glm::vec4(tint, alpha));\n    float bgOffset = (ScrWork[SW_SYSSUBMENUCT] * 3000.0f * 0.03125f);\n    RectF bgSpriteBounds = SystemMenuBG.Bounds;\n    bgSpriteBounds.Translate(\n        glm::vec2(BGPosition.x - 0.5f * bgOffset, BGPosition.y));\n\n    const float scale =\n        (1000.0f - (ScrWork[SW_SYSMENUCT] * 400.0f / 32.0f)) / 1000.0f;\n    const glm::quat rotation = ScrWorkAnglesToQuaternion(\n        ((int)(ScrWork[SW_SYSMENUCT] * AngleMultiplier.x)),\n        ((int)(ScrWork[SW_SYSMENUCT] * AngleMultiplier.y)),\n        ((int)(ScrWork[SW_SYSMENUCT] * AngleMultiplier.z)));\n    const glm::mat4 transformation = TransformationMatrix(\n        BGTranslationOffset, {scale, scale}, {BGTranslationOffset, 0.0f},\n        rotation, -BGTranslationOffset);\n    const CornersQuad bgDisp = {\n        BGDispOffsetTopLeft,\n        BGDispOffsetBottomLeft,\n        BGDispOffsetTopRight,\n        BGDispOffsetBottomRight,\n    };\n    TransformImage(bgSpriteBounds, bgDisp, transformation, BGTranslationOffset,\n                   SystemMenuBG.Sheet.GetDimensions(), Vertices);\n\n    static constexpr auto indices = []() {\n      constexpr uint16_t width = GridColCount + 1;\n      constexpr int totalSize =\n          GridRowCount * width * 2 + (GridRowCount - 1) * 2;\n\n      std::array<uint16_t, totalSize> result{};\n      size_t index = 0;\n\n      for (uint16_t row = 0; row < GridRowCount; row++) {\n        // degenerate triangle here\n        if (row > 0) {\n          result[index] = result[index - 1];\n          index++;\n          result[index++] = row * width;\n        }\n        for (uint16_t col = 0; col < width; col++) {\n          result[index++] = row * width + col;\n          result[index++] = (row + 1) * width + col;\n        }\n      }\n      return result;\n    }();\n\n    Renderer->DrawPrimitives(SystemMenuBG.Sheet, nullptr,\n                             ShaderProgramType::Sprite, Vertices, indices,\n                             glm::mat4(1.0f), glm::mat4(1.0f), false,\n                             TopologyMode::TriangleStrips, true);\n\n    if (!GetFlag(SF_SYSTEMMENUDIRECT)) {\n      CornersQuad frameDisp = {\n          glm::vec2{bgOffset, 0} + FrameOffsetTopLeft,\n          glm::vec2{bgOffset, 0} + FrameOffsetBottomLeft,\n          glm::vec2{bgOffset, 0} + FrameOffsetTopRight,\n          glm::vec2{bgOffset, 0} + FrameOffsetBottomRight,\n      };\n      frameDisp.Transform([&](glm::vec2 corner) {\n        return TransformImageVertex(corner, transformation,\n                                    BGTranslationOffset);\n      });\n      Renderer->DrawSprite(SystemMenuFrame, frameDisp);\n\n      CornersQuad screenCapDisp = {\n          glm::vec2{bgOffset + 0, 0},\n          glm::vec2{bgOffset + 0, Profile::DesignHeight},\n          glm::vec2{bgOffset + Profile::DesignWidth, 0},\n          glm::vec2{bgOffset + Profile::DesignWidth, Profile::DesignHeight},\n      };\n      screenCapDisp.Transform([&](glm::vec2 corner) {\n        return TransformImageVertex(corner, transformation,\n                                    BGTranslationOffset);\n      });\n      Renderer->DrawSprite(ScreenCap, screenCapDisp, glm::vec4(1.0f),\n                           glm::vec3(0.0f), false, true);\n    }\n    Renderer->DrawSprite(\n        SystemMenuMask,\n        RectF{0, 0, Profile::DesignWidth, Profile::DesignHeight},\n        glm::vec4{tint, alpha});\n\n    MainItems->Tint =\n        glm::vec4(tint, glm::smoothstep(0.0f, 1.0f, ItemsFade.Progress));\n    MainItems->Render();\n  }\n}\n\nvoid SystemMenu::Init() {\n  BGPosition = {CALCrnd((int)BGRandPosRange.x), CALCrnd((int)BGRandPosRange.y)};\n  SetFlag(SF_SYSTEMMENUCAPTURE, true);\n\n  bool backlogLockState =\n      GetFlag(SF_BACKLOG_NOLOG) || GetFlag(SF_MESREVDISABLE);\n  static_cast<SysMenuButton*>(\n      MainItems->Children[static_cast<size_t>(MenuItems::Backlog)])\n      ->IsLocked = backlogLockState;\n\n  // these flag need to be recalculated after loading a game\n  bool noFreeSlots = SaveSystem::MaxSaveEntries ==\n                     SaveSystem::Implementation->GetLockedQuickSaveCount();\n  SetFlag(SF_SAVEALLPROTECTED, noFreeSlots);\n}\n\n}  // namespace CCLCC\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/cclcc/systemmenu.h",
    "content": "#pragma once\n\n#include <optional>\n#include <glm/fwd.hpp>\n#include \"../../ui/menu.h\"\n#include \"../../ui/widgets/group.h\"\n#include \"../../ui/widgets/button.h\"\n#include \"../../profile/games/cclcc/systemmenu.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace CCLCC {\n\nclass SystemMenu : public Menu {\n public:\n  using GridVertices =\n      std::array<VertexBufferSprites,\n                 (Profile::CCLCC::SystemMenu::GridRowCount + 1) *\n                     (Profile::CCLCC::SystemMenu::GridColCount + 1)>;\n\n  SystemMenu();\n\n  void Init() override;\n  void Show() override;\n  void Hide() override;\n  void UpdateInput(float dt) override;\n  void Update(float dt) override;\n  void Render() override;\n\n  void MenuButtonOnClick(Widgets::Button* target);\n  Sprite ScreenCap;\n  glm::vec2 BGPosition{};\n\n private:\n  Widgets::Group* MainItems;\n  Animation MenuTransition;\n  Animation MenuFade;\n  Animation ItemsFade;\n  bool ItemsFadeComplete = false;\n  std::optional<int> LastFocusedButtonId;\n\n  GridVertices Vertices;\n\n  enum class MenuItems : size_t {\n    Backlog,\n    QuickSave,\n    QuickLoad,\n    Save,\n    Load,\n    TipsList,\n    Config,\n    Help,\n    ReturnTitle\n  };\n};\n\n}  // namespace CCLCC\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/cclcc/tipsmenu.cpp",
    "content": "#include \"tipsmenu.h\"\n\n#include \"../../renderer/renderer.h\"\n#include \"../../mem.h\"\n#include \"../../vm/vm.h\"\n#include \"../../inputsystem.h\"\n#include \"../../profile/scriptvars.h\"\n#include \"../../profile/dialogue.h\"\n#include \"../../profile/ui/backlogmenu.h\"\n#include \"../../profile/ui/tipsmenu.h\"\n#include \"../../profile/games/cclcc/tipsmenu.h\"\n#include \"../../io/memorystream.h\"\n#include \"../../data/tipssystem.h\"\n#include \"../../vm/interface/input.h\"\n#include \"../../profile/game.h\"\n#include \"../../audio/audiosystem.h\"\n\n#include \"../../ui/widgets/cclcc/tipstabgroup.h\"\nnamespace Impacto {\nnamespace UI {\nnamespace CCLCC {\n\nusing namespace Impacto::Profile::TipsMenu;\nusing namespace Impacto::Profile::CCLCC::TipsMenu;\nusing namespace Impacto::Profile::ScriptVars;\n\nusing namespace Impacto::Vm::Interface;\n\nusing namespace Impacto::UI::Widgets;\nusing namespace Impacto::UI::Widgets::CCLCC;\n\nTipsMenu::TipsMenu() : TipViewItems(this) {\n  FadeAnimation.Direction = AnimationDirection::In;\n  FadeAnimation.LoopMode = AnimationLoopMode::Stop;\n  FadeAnimation.DurationIn = FadeInDuration;\n  FadeAnimation.DurationOut = FadeOutDuration;\n  TransitionAnimation.DurationIn = TransitionInDuration;\n  TransitionAnimation.DurationOut = TransitionOutDuration;\n\n  Name = new Label();\n  Pronounciation = new Label();\n  Category = new Label();\n  Number = new Label();\n\n  Name->Bounds.X = NamePos.x;\n  Name->Bounds.Y = NamePos.y;\n\n  Pronounciation->Bounds.X = PronounciationPos.x;\n  Pronounciation->Bounds.Y = PronounciationPos.y;\n\n  Category->Bounds.X = CategoryPos.x;\n  Category->Bounds.Y = CategoryPos.y;\n\n  Number->Bounds.X = NumberPos.x;\n  Number->Bounds.Y = NumberPos.y;\n\n  TipViewItems.Add(Name);\n  TipViewItems.Add(Pronounciation);\n  TipViewItems.Add(Category);\n  TipViewItems.Add(Number);\n\n  TextPage.Clear();\n  TextPage.Mode = DPM_TIPS;\n  TextPage.FadeAnimation.Progress = 1.0f;\n\n  TipsScrollStartPos = {TipsScrollDetailsX, TipsScrollYStart};\n\n  TipsScrollTrackBounds = {TipsScrollThumbSprite.Bounds.Width,\n                           TipsScrollYEnd - TipsScrollYStart};\n}\n\nvoid TipsMenu::Show() {\n  if (State != Shown) {\n    LastYPos = 0;\n    State = Showing;\n\n    if (UI::FocusedMenu != 0) {\n      LastFocusedMenu = UI::FocusedMenu;\n      LastFocusedMenu->IsFocused = false;\n    }\n    UI::FocusedMenu = this;\n\n    for (int i = 0; i < TabCount; i++) {\n      TipsTabs[i]->UpdateTipsEntries(SortedTipIds);\n      if (TipsTabs[i]->GetTipEntriesCount() > 0) {\n        CurrentTabType = static_cast<TipsTabType>(i);\n      }\n    }\n\n    Name->Bounds.X = NamePos.x;\n    Name->Bounds.Y = NamePos.y;\n\n    Pronounciation->Bounds.X = PronounciationPos.x;\n    Pronounciation->Bounds.Y = PronounciationPos.y;\n\n    Category->Bounds.X = CategoryPos.x;\n    Category->Bounds.Y = CategoryPos.y;\n\n    Number->Bounds.X = NumberPos.x;\n    Number->Bounds.Y = NumberPos.y;\n\n    TipsTabs[CurrentTabType]->Show();\n    TipViewItems.Show();\n    if (ScrWork[SW_SYSSUBMENUCT] != 32) {\n      TipsTabs[CurrentTabType]->Move({0, Profile::DesignHeight / 2});\n      TipViewItems.Move({0, Profile::DesignHeight / 2});\n      TextPage.Move({0, Profile::DesignHeight / 2});\n      TransitionAnimation.StartIn();\n    } else {\n      TransitionAnimation.Progress = 1.0f;\n      LastYPos = Profile::DesignHeight / 2;\n    }\n    FadeAnimation.StartIn();\n  }\n}\nvoid TipsMenu::Hide() {\n  if (State != Hidden) {\n    State = Hiding;\n    FadeAnimation.StartOut();\n    Audio::PlayInGroup(Audio::ACG_SE, \"sysse\", 3, false, 0);\n    if (ScrWork[SW_SYSSUBMENUCT] != 0) {\n      TransitionAnimation.StartOut();\n    } else {\n      TransitionAnimation.Progress = 0.0f;\n    }\n    if (LastFocusedMenu != 0) {\n      UI::FocusedMenu = LastFocusedMenu;\n    } else {\n      UI::FocusedMenu = 0;\n    }\n  }\n}\n\nvoid TipsMenu::UpdateInput(float dt) {\n  if (State == Shown) {\n    Menu::UpdateInput(dt);\n    if (PADinputButtonWentDown & PAD1R1) {\n      TipsTabType type =\n          static_cast<TipsTabType>((CurrentTabType + 1) % TabCount);\n      while (!TipsTabs[type]->GetTipEntriesCount()) {\n        type = static_cast<TipsTabType>((type + 1) % TabCount);\n      }\n      SetActiveTab(type);\n    } else if (PADinputButtonWentDown & PAD1L1) {\n      TipsTabType type =\n          static_cast<TipsTabType>((CurrentTabType + TabCount - 1) % TabCount);\n      while (!TipsTabs[type]->GetTipEntriesCount()) {\n        type = static_cast<TipsTabType>((type + TabCount - 1) % TabCount);\n      }\n      SetActiveTab(type);\n    }\n\n    if (CurrentlyDisplayedTipId != -1) {\n      int scrollDistance = 10;\n      if (Input::CurMousePos != Input::PrevMousePos) {\n        MouseInTextBounds =\n            TextPage.BoxBounds.ContainsPoint(Input::CurMousePos);\n      }\n\n      bool upScroll = PADinputButtonIsDown & PADcustom[32];\n      bool downScroll = PADinputButtonIsDown & PADcustom[33];\n\n      int remainingScroll = (int)(TipsScrollbar->EndValue - TipPageY);\n      if (upScroll && downScroll) {\n      } else if (upScroll && TipPageY > 0) {\n        if (scrollDistance > TipPageY) {\n          scrollDistance = (int)TipPageY;\n        }\n        TipPageY -= scrollDistance;\n\n      } else if (downScroll && remainingScroll > 0) {\n        if (scrollDistance > remainingScroll) {\n          scrollDistance = remainingScroll;\n        }\n        TipPageY += scrollDistance;\n      }\n    }\n  }\n}\n\nvoid TipsMenu::Update(float dt) {\n  if (!HasInitialized) return;\n  if (ScrWork[SW_SYSSUBMENUCT] < 32 && State == Shown) {\n    Hide();\n  } else if (ScrWork[SW_SYSSUBMENUCT] > 0 && State == Hidden &&\n             (ScrWork[SW_SYSSUBMENUNO] == 2)) {\n    Show();\n  }\n\n  if (State != Hidden) {\n    FadeAnimation.Update(dt);\n    TransitionAnimation.Update(dt);\n    if (State == Shown) {\n      for (int i = 0; i < TabCount; i++) {\n        TipsTabs[i]->Update(dt);\n      }\n    }\n  }\n\n  if (State == Shown && ScrWork[SW_SYSSUBMENUNO] == 2) {\n    float oldPageY = TipPageY;\n    UpdateInput(dt);\n    if (TipsScrollbar) {\n      TipsScrollbar->UpdateInput(dt);\n      if ((PADinputMouseWentDown & PAD1A) && TipsScrollbar->IsScrollHeld()) {\n        Audio::PlayInGroup(Audio::ACG_SE, \"sysse\", 2, false, 0);\n      }\n      TipsScrollbar->Update(dt);\n      if (oldPageY != TipPageY) {\n        TextPage.Move({0, oldPageY - TipPageY});\n      }\n    }\n  }\n\n  if (State == Showing && FadeAnimation.Progress == 1.0f &&\n      TransitionAnimation.Progress == 1.0f && ScrWork[SW_SYSSUBMENUCT] == 32) {\n    State = Shown;\n    IsFocused = true;\n  } else if (State == Hiding && FadeAnimation.Progress == 0.0f &&\n             TransitionAnimation.Progress == 0.0f &&\n             ScrWork[SW_SYSSUBMENUCT] == 0) {\n    State = Hidden;\n    IsFocused = false;\n    if (UI::FocusedMenu) UI::FocusedMenu->IsFocused = true;\n    CurrentlyDisplayedTipId = -1;\n    TipsTabs[CurrentTabType]->Hide();\n    TipViewItems.Hide();\n    TipsScrollbar.reset();\n  }\n\n  auto Move = [this](glm::vec2 offset) {\n    LastYPos += offset.y;\n    TipViewItems.Move(-offset);\n    TipsTabs[CurrentTabType]->Move(-offset);\n    TextPage.Move(-offset);\n    if (TipsScrollbar) {\n      TipsScrollbar->Move(-offset);\n    }\n  };\n\n  if (TransitionAnimation.State == AnimationState::Playing) {\n    float move = glm::mix(0.0f, Profile::DesignHeight / 2,\n                          TransitionAnimation.Progress) -\n                 LastYPos;\n    Move({0, move});\n\n  } else if (TransitionAnimation.IsIn() && LastYPos != 0) {\n    float move = Profile::DesignHeight / 2 - LastYPos;\n    Move({0, move});\n  } else if (TransitionAnimation.IsOut() &&\n             LastYPos != Profile::DesignHeight / 2 && LastYPos != 0) {\n    float move = -LastYPos;\n    Move({0, move});\n  }\n}\n\nvoid TipsMenu::Render() {\n  if (!HasInitialized) return;\n  if (State == Hidden) return;\n\n  glm::vec4 fade(1.0f, 1.0f, 1.0f,\n                 glm::smoothstep(0.0f, 1.0f, FadeAnimation.Progress));\n  glm::vec4 maskTint = fade;\n  maskTint.a *= 0.85f;\n\n  Renderer->DrawSprite(BackgroundSprite,\n                       glm::vec2(0.0f, Profile::DesignHeight / 2 - LastYPos),\n                       fade);\n  TipsTabs[CurrentTabType]->Tint.a = fade.a;\n  TipsTabs[CurrentTabType]->Render();\n\n  if (CurrentlyDisplayedTipId != -1) {\n    TipViewItems.Tint.a = fade.a;\n    TipViewItems.Render();\n\n    Renderer->DrawProcessedText(\n        TextPage.Glyphs, Profile::Dialogue::DialogueFont,\n        FadeAnimation.Progress, FadeAnimation.Progress,\n        RendererOutlineMode::None, true, &TipsMaskSheet);\n\n    TipsScrollbar->Render();\n  }\n\n  Renderer->DrawSprite(\n      TipsMaskSprite,\n      RectF(0.0f, 0.0f, Profile::DesignWidth, Profile::DesignHeight), maskTint);\n  Renderer->DrawSprite(\n      TipsGuideSprite,\n      glm::vec2(TipsGuideX, TipsGuideY + Profile::DesignHeight / 2 - LastYPos),\n      fade);\n}\n\nvoid TipsMenu::Init() {\n  auto* TipRecords = TipsSystem::GetTipRecords();\n  std::transform(\n      TipRecords->begin(), TipRecords->end(), std::back_inserter(SortedTipIds),\n      [](TipsSystem::TipsDataRecord const& record) { return record.Id; });\n  std::sort(SortedTipIds.begin(), SortedTipIds.end(),\n            TipsSystem::TipsComparator(TipsTextTableIndex,\n                                       TipsTextSortStringIndex, 3));\n  for (int i = 0; i < TabCount; i++) {\n    TipsTabType type = static_cast<TipsTabType>(i);\n    TipsTabs[i] = new TipsTabGroup(\n        type, [this, type](Button*) { SetActiveTab(type); },\n        [this](Button* target) { SwitchToTipId(target->Id); });\n  }\n  HasInitialized = true;\n}\n\nvoid TipsMenu::SwitchToTipId(int id) {\n  if (id - 1 == CurrentlyDisplayedTipId) return;\n  int actualId = SortedTipIds[id - 1];\n  auto* record = TipsSystem::GetTipRecord(actualId);\n  uint32_t tipsScrBufId = TipsSystem::GetTipsScriptBufferId();\n\n  if (record->IsLocked) {\n    Audio::PlayInGroup(Audio::ACG_SE, \"sysse\", 4, false, 0);\n    return;\n  }\n  CurrentlyDisplayedTipId = id - 1;\n\n  TipsSystem::SetTipUnreadState(actualId, false);\n  Category->SetText(\n      {.ScriptBufferId = tipsScrBufId, .IpOffset = record->StringAdr[0]},\n      (float)CategoryFontSize, RendererOutlineMode::None,\n      {TipsMenuDarkTextColor, 0});\n  Name->SetText(\n      {.ScriptBufferId = tipsScrBufId, .IpOffset = record->StringAdr[1]},\n      (float)NameFontSize, RendererOutlineMode::None,\n      {TipsMenuDarkTextColor, 0});\n  Pronounciation->SetText(\n      {.ScriptBufferId = tipsScrBufId, .IpOffset = record->StringAdr[2]},\n      (float)PronounciationFontSize, RendererOutlineMode::None, 0);\n\n  {\n    uint16_t sc3StringBuffer[5];\n    TextGetSc3String(fmt::format(\"{:3d}\", id), sc3StringBuffer);\n    Vm::Sc3Stream stream(sc3StringBuffer);\n    float numberWidth = TextGetPlainLineWidth(\n        stream, Profile::Dialogue::DialogueFont, (float)NumberFontSize);\n    Number->Bounds.X = NumberPos.x - numberWidth;\n    Number->Bounds.Y = NumberPos.y;\n    stream = Vm::Sc3Stream(sc3StringBuffer);\n    Number->SetText(stream, (float)NumberFontSize, RendererOutlineMode::None,\n                    0);\n  }\n\n  Vm::Sc3VmThread dummy;\n  dummy.IpOffset = record->StringAdr[4];\n  dummy.ScriptBufferId = tipsScrBufId;\n  TextPage.Clear();\n  TextPage.AddString(&dummy);\n  TipViewItems.HasFocus = true;\n\n  auto& lastGlyph = TextPage.Glyphs.back();\n  int scrollDistance =\n      (int)(lastGlyph.DestRect.Y + lastGlyph.DestRect.Height -\n            (TextPage.BoxBounds.Y + TextPage.BoxBounds.Height) +\n            lastGlyph.DestRect.Height);\n\n  TipPageY = 0;\n  TipsScrollbar = std::make_unique<Widgets::Scrollbar>(\n      0, TipsScrollStartPos, 0.0f, std::max(0.0f, (float)scrollDistance),\n      &TipPageY, SBDIR_VERTICAL, TipsScrollThumbSprite, TipsScrollTrackBounds,\n      TipsScrollThumbLength, TextPage.BoxBounds, 5.0f);\n  TipsScrollbar->HasFocus = false;  // We want to manually control kb/pad input\n\n  Audio::PlayInGroup(Audio::ACG_SE, \"sysse\", 2, false, 0);\n}\n\nvoid TipsMenu::SetActiveTab(TipsTabType type) {\n  if (type == CurrentTabType || !TipsTabs[type]->GetTipEntriesCount()) return;\n\n  if (PADinputMouseWentDown & PAD1A) {\n    Audio::PlayInGroup(Audio::ACG_SE, \"sysse\", 2, false, 0);\n  } else {\n    Audio::PlayInGroup(Audio::ACG_SE, \"sysse\", 1, false, 0);\n  }\n\n  TipsTabs[CurrentTabType]->Hide();\n  TipsTabs[type]->Show();\n  CurrentTabType = type;\n}\n\n}  // namespace CCLCC\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/cclcc/tipsmenu.h",
    "content": "#pragma once\n\n#include \"../../text/dialoguepage.h\"\n\n#include \"../../ui/tipsmenu.h\"\n#include \"../../ui/widgets/group.h\"\n#include \"../../ui/widgets/button.h\"\n#include \"../../ui/widgets/carousel.h\"\n#include \"../../ui/widgets/label.h\"\n#include \"../../data/tipssystem.h\"\n#include \"../../ui/widgets/cclcc/tipsentrybutton.h\"\n#include \"../../ui/widgets/scrollbar.h\"\n#include <vector>\n#include <memory>\n\nnamespace Impacto::UI::Widgets::CCLCC {\nclass TipsTabGroup;\n}\nnamespace Impacto {\nnamespace UI {\nnamespace CCLCC {\n\nenum TipsTabType {\n  AllTips,\n  UnlockedTips,\n  UnreadTips,\n  NewTips,\n};\n\nclass TipsMenu : public UI::TipsMenu {\n public:\n  TipsMenu();\n\n  void Init();\n\n  void Show();\n  void Hide();\n  void UpdateInput(float dt);\n  void Update(float dt);\n  void Render();\n\n  void TipOnClick(Widgets::Button* target);\n\n protected:\n  void SwitchToTipId(int id);\n\n private:\n  constexpr int static TabCount = 4;\n  int CurrentTabIdx = 0;\n  float TipPageY = 0;\n  glm::vec2 TipsScrollStartPos;\n  glm::vec2 TipsScrollTrackBounds;\n\n  TipsTabType CurrentTabType;\n  std::vector<int> SortedTipIds;\n  std::array<Widgets::CCLCC::TipsTabGroup*, TabCount> TipsTabs;\n\n  std::unique_ptr<Widgets::Scrollbar> TipsScrollbar;\n  Widgets::Group TipViewItems;\n\n  int ScrollWheelYDelta = 0;\n  bool MouseInTextBounds = false;\n\n  float ScrollPercentage = 0.0f;\n  bool HasInitialized = false;\n  void SetActiveTab(TipsTabType type);\n\n  float LastYPos = 0.0f;\n\n  Animation TransitionAnimation;\n};\n\n}  // namespace CCLCC\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/cclcc/tipssystem.cpp",
    "content": "#include \"tipssystem.h\"\n\n#include \"../../data/savesystem.h\"\n#include \"../../vm/vm.h\"\n#include \"../../io/memorystream.h\"\n#include \"../../profile/data/tipssystem.h\"\n#include \"../../ui/ui.h\"\n\nnamespace Impacto {\nnamespace CCLCC {\n\nusing namespace Impacto::Vm;\nusing namespace Impacto::Profile::TipsSystem;\nusing namespace Impacto::Io;\n\nvoid TipsSystem::DataInit(uint32_t scriptBufferId, uint32_t tipsDataAdr,\n                          uint32_t tipsDataSize) {\n  ScriptBufferId = (uint8_t)scriptBufferId;\n  auto scriptBuffer = ScriptBuffers[scriptBufferId];\n\n  TipEntryCount = 0;\n\n  // Read tips data from the script and create UI elements for each tip\n  MemoryStream stream =\n      MemoryStream(&scriptBuffer[tipsDataAdr], tipsDataSize, false);\n  int numberOfContentStrings = ReadLE<uint16_t>(&stream);\n  while (numberOfContentStrings != 255) {\n    if (TipEntryCount >= MaxTipsCount) {\n      ImpLog(LogLevel::Error, LogChannel::VM, \"Too many tips in tips data\\n\");\n      break;\n    }\n    // Read tip entry from the data array\n    TipsDataRecord record;\n    record.Id = (uint16_t)TipEntryCount;\n    record.NumberOfContentStrings = (uint16_t)numberOfContentStrings;\n    for (int i = 0; i < numberOfContentStrings + 4; i++) {\n      record.StringAdr[i] =\n          ScriptGetStrAddress(scriptBufferId, ReadLE<uint16_t>(&stream));\n    }\n    Records[TipEntryCount] = std::move(record);\n\n    // Next tip entry from the data array\n    numberOfContentStrings = ReadLE<uint16_t>(&stream);\n    TipEntryCount += 1;\n  }\n  Records.resize(TipEntryCount);\n}\n\nvoid TipsSystem::UpdateTipRecords() {\n  if (TipEntryCount != 0) {\n    for (size_t i = 0; i < TipEntryCount; i++) {\n      auto& record = Records[i];\n      auto tipStatus = SaveSystem::GetTipStatus(record.Id);\n      record.IsLocked = (tipStatus & 1) == 0;\n      record.IsUnread = (tipStatus & 2) == 0;\n      record.IsNew = (tipStatus & 4) == 0;\n    }\n  }\n}\n\nvoid TipsSystem::SetTipLockedState(size_t id, bool state) {\n  Records[id].IsLocked = state;\n  SaveSystem::SetTipStatus(id, Records[id].IsLocked, Records[id].IsUnread,\n                           Records[id].IsNew);\n}\n\nvoid TipsSystem::SetTipUnreadState(size_t id, bool state) {\n  Records[id].IsUnread = state;\n  SaveSystem::SetTipStatus(id, Records[id].IsLocked, Records[id].IsUnread,\n                           Records[id].IsNew);\n}\n\nvoid TipsSystem::SetTipNewState(size_t id, bool state) {\n  Records[id].IsNew = state;\n  SaveSystem::SetTipStatus(id, Records[id].IsLocked, Records[id].IsUnread,\n                           Records[id].IsNew);\n}\n\nbool TipsSystem::GetTipLockedState(size_t id) { return Records[id].IsLocked; }\n\n}  // namespace CCLCC\n}  // namespace Impacto"
  },
  {
    "path": "src/games/cclcc/tipssystem.h",
    "content": "#pragma once\n\n#include \"../../data/tipssystem.h\"\n\nnamespace Impacto {\nnamespace CCLCC {\n\nusing namespace Impacto::TipsSystem;\n\nclass TipsSystem : public TipsSystemBase {\n public:\n  TipsSystem(size_t maxTipsCount) : TipsSystemBase(maxTipsCount) {};\n\n  void DataInit(uint32_t scriptBufferId, uint32_t tipsData,\n                uint32_t tipsDataSize) override;\n  void UpdateTipRecords() override;\n  void SetTipLockedState(size_t id, bool state) override;\n  void SetTipUnreadState(size_t id, bool state) override;\n  void SetTipNewState(size_t id, bool state) override;\n\n  bool GetTipLockedState(size_t id) override;\n};\n\n}  // namespace CCLCC\n}  // namespace Impacto"
  },
  {
    "path": "src/games/cclcc/titlemenu.cpp",
    "content": "#include \"titlemenu.h\"\n\n#include \"../../spritesheet.h\"\n\n#include \"../../renderer/renderer.h\"\n#include \"../../mem.h\"\n#include \"../../inputsystem.h\"\n#include \"../../ui/widgets/label.h\"\n#include \"../../profile/ui/titlemenu.h\"\n#include \"../../profile/games/cclcc/titlemenu.h\"\n#include \"../../profile/scriptvars.h\"\n#include \"../../profile/game.h\"\n#include \"../../vm/interface/input.h\"\n#include \"../../audio/audiosystem.h\"\n#include \"../../profile/scriptinput.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace CCLCC {\n\nusing namespace Impacto::Profile::TitleMenu;\nusing namespace Impacto::Profile::CCLCC::TitleMenu;\nusing namespace Impacto::Profile::ScriptVars;\nusing namespace Impacto::Profile;\n\nusing namespace Impacto::Vm::Interface;\n\nusing namespace Impacto::UI::Widgets::CCLCC;\n\nvoid TitleMenu::MenuButtonOnClick(Widgets::Button* target) {\n  TitleButton* button = static_cast<TitleButton*>(target);\n  Audio::PlayInGroup(Audio::ACG_SE, \"sysse\", 2, false, 0);\n  button->ChoiceBlinkAnimation.StartIn();\n  AllowsScriptInput = false;\n  InputLocked = true;\n}\n\nvoid TitleMenu::ContinueButtonOnClick(Widgets::Button* target) {\n  target->Hovered = false;\n  if (CurrentSubMenu == ContinueItems) return;\n  Audio::PlayInGroup(Audio::ACG_SE, \"sysse\", 2, false, 0);\n  CurrentSubMenu = ContinueItems;\n  CurrentSubMenu->HasFocus = true;\n  AllowsScriptInput = false;\n  InputLocked = true;\n  ShowContinueItems();\n}\n\nvoid TitleMenu::ExtraButtonOnClick(Widgets::Button* target) {\n  target->Hovered = false;\n  if (CurrentSubMenu == ExtraItems) return;\n  // SetFlag(SF_CLR_FLAG, true); // Uncomment for testing\n  if (!GetFlag(SF_CLR_FLAG)) {\n    Audio::PlayInGroup(Audio::ACG_SE, \"sysse\", 4, false, 0);\n    return;\n  } else {\n    Audio::PlayInGroup(Audio::ACG_SE, \"sysse\", 2, false, 0);\n  }\n  CurrentSubMenu = ExtraItems;\n  CurrentSubMenu->HasFocus = true;\n  AllowsScriptInput = false;\n  InputLocked = true;\n  ShowExtraItems();\n}\n\nvoid TitleMenu::ExitButtonOnClick(Widgets::Button* target) {\n  target->Hovered = false;\n  Audio::PlayInGroup(Audio::ACG_SE, \"sysse\", 2, false, 0);\n  AllowsScriptInput = false;\n  InputLocked = true;\n  Input::KeyboardButtonWentDown[SDL_SCANCODE_ESCAPE] = true;\n}\n\nTitleMenu::TitleMenu() {\n  MainItems = new Widgets::Group(this);\n  ContinueItems = new Widgets::Group(this);\n  ContinueItems->WrapFocus = false;\n  ExtraItems = new Widgets::Group(this);\n  ExtraItems->WrapFocus = false;\n\n  Sprite nullSprite = Sprite();\n  nullSprite.Bounds = RectF(0.0f, 0.0f, 0.0f, 0.0f);\n\n  MenuLabel = new Widgets::Label(MenuSprite, glm::vec2(MenuX, MenuY));\n\n  auto onClick = [this](Widgets::Button* target) {\n    return MenuButtonOnClick(target);\n  };\n\n  auto btnOnAnimComplete = [this](Widgets::Button* target) {\n    TitleButton* button = static_cast<TitleButton*>(target);\n    button->ChoiceBlinkAnimation.StartOut();\n    ScrWork[SW_TITLECUR1] = target->Id;\n    SetFlag(SF_TITLEEND, true);\n    AllowsScriptInput = true;\n    PADinputButtonWentDown &= ~PADcustom[6];\n    IsFocused = false;\n  };\n\n  auto setupBtn = [&](TitleButton* btn, auto onClickHandler,\n                      Widgets::Group* parent, Impacto::UI::FocusDirection dir) {\n    btn->OnClickHandler = onClickHandler;\n    btn->OnClickAnimCompleteHandler = btnOnAnimComplete;\n    btn->IsSubButton = parent != MainItems;\n    parent->Add(btn, dir);\n  };\n\n  // NewGame menu button\n  NewGame = new TitleButton(0, MenuEntriesSprites[0], MenuEntriesHSprites[0],\n                            ItemHighlightSprite,\n                            glm::vec2((-1.0f) + ItemHighlightOffsetX,\n                                      (ItemYBase + (0 * ItemPadding))));\n  setupBtn(NewGame, onClick, MainItems, FDIR_DOWN);\n\n  // Continue menu button\n  Continue = new TitleButton(\n      1, MenuEntriesSprites[1], MenuEntriesHSprites[1], ItemHighlightSprite,\n      glm::vec2(ItemHighlightOffsetX, (ItemYBase + (1 * ItemPadding))));\n  setupBtn(\n      Continue,\n      [this](Widgets::Button* target) { return ContinueButtonOnClick(target); },\n      MainItems, FDIR_DOWN);\n\n  // Extra menu button\n  Extra = new TitleButton(\n      2, MenuEntriesSprites[2], MenuEntriesHSprites[2], ItemHighlightSprite,\n      glm::vec2(ItemHighlightOffsetX, (ItemYBase + (2 * ItemPadding))));\n  setupBtn(\n      Extra,\n      [this](Widgets::Button* target) { return ExtraButtonOnClick(target); },\n      MainItems, FDIR_DOWN);\n\n  // Config menu button\n  Config = new TitleButton(\n      30, MenuEntriesSprites[3], MenuEntriesHSprites[3], ItemHighlightSprite,\n      glm::vec2(ItemHighlightOffsetX, (ItemYBase + (3 * ItemPadding))));\n  setupBtn(Config, onClick, MainItems, FDIR_DOWN);\n\n  // Help menu button\n  Help = new TitleButton(\n      40, MenuEntriesSprites[4], MenuEntriesHSprites[4], ItemHighlightSprite,\n      glm::vec2(ItemHighlightOffsetX, (ItemYBase + (4 * ItemPadding))));\n  setupBtn(Help, onClick, MainItems, FDIR_DOWN);\n\n  if (HasScriptedExitLogic) {\n    // Exit menu button (Configuration/Patch driven)\n    auto* const exitPtr = new TitleButton(\n        5, ExitSprite, ExitSprite, ItemHighlightSprite,\n        glm::vec2(ItemHighlightOffsetX, (ItemYBase + (5 * ItemPadding))));\n    Exit.emplace(*exitPtr);\n    setupBtn(\n        exitPtr,\n        [this](Widgets::Button* target) { return ExitButtonOnClick(target); },\n        MainItems, FDIR_DOWN);\n  }\n\n  // Load secondary Continue menu button\n  Load = new TitleButton(10, LoadSprite, LoadHighlightSprite, nullSprite,\n                         glm::vec2(SecondaryFirstItemHighlightOffsetX,\n                                   (ItemYBase + (2 * ItemPadding))));\n  setupBtn(Load, onClick, ContinueItems, FDIR_RIGHT);\n\n  // QuickLoad secondary Continue menu button\n  QuickLoad =\n      new TitleButton(11, QuickLoadSprite, QuickLoadHighlightSprite, nullSprite,\n                      glm::vec2(SecondarySecondItemHighlightOffsetX,\n                                (ItemYBase + (2 * ItemPadding))));\n  setupBtn(QuickLoad, onClick, ContinueItems, FDIR_RIGHT);\n\n  // Tips secondary Extra menu button\n  Tips = new TitleButton(20, TipsSprite, TipsHighlightSprite, nullSprite,\n                         glm::vec2(SecondaryFirstItemHighlightOffsetX,\n                                   (ItemYBase + (3 * ItemPadding))));\n  setupBtn(Tips, onClick, ExtraItems, FDIR_RIGHT);\n\n  // Library secondary Extra menu button\n  Library =\n      new TitleButton(21, LibrarySprite, LibraryHighlightSprite, nullSprite,\n                      glm::vec2(SecondarySecondItemHighlightOffsetX,\n                                (ItemYBase + (3 * ItemPadding))));\n  setupBtn(Library, onClick, ExtraItems, FDIR_RIGHT);\n\n  // EndingList secondary Extra menu button\n  EndingList = new TitleButton(22, EndingListSprite, EndingListHighlightSprite,\n                               nullSprite,\n                               glm::vec2(SecondaryThirdItemHighlightOffsetX,\n                                         (ItemYBase + (3 * ItemPadding))));\n  setupBtn(EndingList, onClick, ExtraItems, FDIR_RIGHT);\n\n  // Start menu items offscreen\n  MainItems->Move({-Profile::DesignWidth / 2, 0.0f});\n  MenuLabel->Move({-Profile::DesignWidth / 2, 0.0f});\n  ContinueItems->Move({-Profile::DesignWidth / 2, 0.0f});\n  ExtraItems->Move({-Profile::DesignWidth / 2, 0.0f});\n\n  PressToStartAnimation.DurationIn = PressToStartAnimDurationIn;\n  PressToStartAnimation.DurationOut = PressToStartAnimDurationOut;\n  PrimaryFadeAnimation.DurationIn = PrimaryFadeInDuration;\n  PrimaryFadeAnimation.DurationOut = PrimaryFadeOutDuration;\n  SecondaryFadeAnimation.DurationIn = SecondaryFadeInDuration;\n  SecondaryFadeAnimation.DurationOut = SecondaryFadeOutDuration;\n  SmokeAnimation.DurationIn = SmokeAnimationDurationIn;\n  SmokeAnimation.DurationOut = SmokeAnimationDurationOut;\n  SlideItemsAnimation.DurationIn = SlideItemsAnimationDurationIn;\n  SlideItemsAnimation.DurationOut = SlideItemsAnimationDurationOut;\n}\n\nvoid TitleMenu::Show() {\n  if (State != Shown) {\n    State = Shown;\n    if (UI::FocusedMenu != 0) {\n      LastFocusedMenu = UI::FocusedMenu;\n      LastFocusedMenu->IsFocused = false;\n    }\n    IsFocused = true;\n    UI::FocusedMenu = this;\n    AllowsScriptInput = true;\n    if (PressToStartAnimation.State == AnimationState::Stopped) {\n      PressToStartAnimation.StartIn(true);\n      SmokeAnimation.StartIn();\n    }\n  }\n}\nvoid TitleMenu::Hide() {\n  if (State != Hidden) {\n    State = Hidden;\n    MainItems->Hide();\n    if (SlideItemsAnimation.IsIn()) {\n      SlideItemsAnimation.Progress = 0.0f;\n      MainItems->Move({-Profile::DesignWidth / 2, 0.0f});\n      MenuLabel->Move({-Profile::DesignWidth / 2, 0.0f});\n      if (CurrentSubMenu) {\n        CurrentSubMenu->Move({-Profile::DesignWidth / 2, 0.0f});\n      }\n    }\n    if (CurrentSubMenu) {\n      CurrentSubMenu->HasFocus = false;\n      if (CurrentSubMenu == ContinueItems) {\n        HideContinueItems();\n      } else if (CurrentSubMenu == ExtraItems) {\n        HideExtraItems();\n      }\n    }\n    if (PrimaryFadeAnimation.IsIn()) {\n      PrimaryFadeAnimation.Progress = 0.0f;\n    }\n    if (SecondaryFadeAnimation.IsIn()) {\n      SecondaryFadeAnimation.Progress = 0.0f;\n    }\n    MenuLabel->Hide();\n    if (LastFocusedMenu != nullptr) {\n      UI::FocusedMenu = LastFocusedMenu;\n      LastFocusedMenu->IsFocused = true;\n    } else {\n      UI::FocusedMenu = nullptr;\n    }\n    IsFocused = false;\n    AllowsScriptInput = true;\n    SubMenuState = Hidden;\n  }\n}\n\nvoid TitleMenu::UpdateInput(float dt) {\n  if (ScrWork[SW_TITLEMODE] == 5 || ScrWork[SW_TITLEMODE] == 13 ||\n      ScrWork[SW_TITLEMODE] == 3) {\n    if (!InputLocked && !PrevInputLocked) {\n      if (SlideItemsAnimation.State == AnimationState::Playing ||\n          SecondaryFadeAnimation.State == AnimationState::Playing ||\n          PrimaryFadeAnimation.State == AnimationState::Playing ||\n          TitleAnimation.State == AnimationState::Playing ||\n          (CurrentlyFocusedElement != nullptr &&\n           (static_cast<TitleButton*>(CurrentlyFocusedElement)\n                    ->ChoiceBlinkAnimation.State == AnimationState::Playing ||\n            static_cast<TitleButton*>(CurrentlyFocusedElement)\n                    ->HighlightAnimation.State == AnimationState::Playing)) ||\n          SubMenuState != Hidden) {\n        InputLocked = true;\n      }\n    } else if (InputLocked && PrevInputLocked) {\n      if (SlideItemsAnimation.State == AnimationState::Stopped &&\n          SecondaryFadeAnimation.State == AnimationState::Stopped &&\n          PrimaryFadeAnimation.State == AnimationState::Stopped &&\n          TitleAnimation.State == AnimationState::Stopped &&\n          (CurrentlyFocusedElement != nullptr &&\n           (static_cast<TitleButton*>(CurrentlyFocusedElement)\n                    ->ChoiceBlinkAnimation.State == AnimationState::Stopped &&\n            static_cast<TitleButton*>(CurrentlyFocusedElement)\n                    ->HighlightAnimation.State == AnimationState::Stopped)) &&\n          SubMenuState == Hidden) {\n        InputLocked = false;\n      }\n    }\n\n    if (InputLocked != PrevInputLocked) {\n      for (auto* menu : {MainItems, ContinueItems, ExtraItems}) {\n        for (auto& item : menu->Children) {\n          item->Enabled = !InputLocked;\n        }\n      }\n      PrevInputLocked = InputLocked;\n      if (CurrentSubMenu != nullptr) {\n        CurrentSubMenu->HasFocus = !InputLocked;\n      } else {\n        MainItems->HasFocus = !InputLocked;\n        AllowsScriptInput = !InputLocked;\n      }\n    }\n  }\n  if (CurrentSubMenu && !CurrentSubMenu->HasFocus) return;\n\n  const bool buttonHighlightAnimationPlaying =\n      CurrentlyFocusedElement &&\n      static_cast<TitleButton*>(CurrentlyFocusedElement)\n          ->HighlightAnimation.IsPlaying();\n  if (IsFocused && !buttonHighlightAnimationPlaying) {\n    Menu::UpdateInput(dt);\n    if (MainItems->HasFocus && !CurrentSubMenu) {\n      MainItems->UpdateInput(dt);\n    }\n    if (CurrentSubMenu) {\n      CurrentSubMenu->UpdateInput(dt);\n    }\n  }\n\n  if (CurrentSubMenu && SecondaryFadeAnimation.IsIn()) {\n    if ((PADinputButtonWentDown & PAD1B || PADinputMouseWentDown & PAD1B) &&\n        CurrentSubMenu->VisibilityState != Hidden && CurrentSubMenu->HasFocus) {\n      Audio::PlayInGroup(Audio::ACG_SE, \"sysse\", 3, false, 0);\n      CurrentlyFocusedElement->Enabled = false;\n      SecondaryFadeAnimation.StartOut();\n      if (CurrentSubMenu == ContinueItems) {\n        ContinueItems->Move(glm::vec2(-Profile::DesignWidth / 2, 0.0f),\n                            SecondaryFadeAnimation.DurationOut);\n      }\n      if (CurrentSubMenu == ExtraItems) {\n        ExtraItems->Move(glm::vec2(-Profile::DesignWidth / 2, 0.0f),\n                         SecondaryFadeAnimation.DurationOut);\n      }\n    }\n  }\n}\n\nvoid TitleMenu::Update(float dt) {\n  UpdateInput(dt);\n  PressToStartAnimation.Update(dt);\n  PrimaryFadeAnimation.Update(dt);\n  SecondaryFadeAnimation.Update(dt);\n  SmokeAnimation.Update(dt);\n  TitleAnimation.Update(dt);\n  TitleAnimationSprite.Position = {0.0f, 0.0f};\n  SlideItemsAnimation.Update(dt);\n\n  if (!IsFocused) {\n    MainItems->HasFocus = false;\n    ContinueItems->HasFocus = false;\n    ExtraItems->HasFocus = false;\n  }\n\n  MainItems->Update(dt);\n  MenuLabel->Update(dt);\n  ContinueItems->Update(dt);\n  ExtraItems->Update(dt);\n\n  if (GetFlag(SF_TITLEMODE)) {\n    Show();\n  } else {\n    Hide();\n  }\n\n  if (State != Hidden && GetFlag(SF_TITLEMODE)) {\n    switch (ScrWork[SW_TITLEMODE]) {\n      case 1: {\n        if (PressToStartAnimation.LoopMode !=\n            AnimationLoopMode::ReverseDirection) {\n          PressToStartAnimation.LoopMode = AnimationLoopMode::ReverseDirection;\n          PressToStartAnimation.StartOut();\n        }\n      } break;\n      case 2: {\n        ExplodeScreenUpdate();\n      } break;\n      case 3: {  // Main Menu Fade In\n        MainMenuUpdate();\n      } break;\n      case 4: {\n        ReturnToMenuUpdate();\n      } break;\n      // TODO check if that's true\n      case 5:\n      case 13: {\n        SubMenuUpdate();\n      } break;\n      case 10: {\n        ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                   \"TitleMenu::Update: Unimplemented title mode {:d}\\n\",\n                   ScrWork[SW_TITLEMODE]);\n      } break;\n    }\n    if (SubMenuState == Hiding && ScrWork[SW_SYSSUBMENUCT] == 0) {\n      SubMenuState = Hidden;\n    } else if (SubMenuState == Showing && ScrWork[SW_SYSSUBMENUCT] == 32) {\n      SubMenuState = Shown;\n      IsFocused = true;\n    }\n    if (ScrWork[SW_TITLEMODE] != 2) IsExploding = false;\n  }\n}\n\nvoid TitleMenu::ExplodeScreenUpdate() {\n  if (SlideItemsAnimation.IsIn()) {\n    SlideItemsAnimation.Progress = 0.0f;\n    MenuLabel->Move({-Profile::DesignWidth / 2, 0.0f});\n    MainItems->Move({-Profile::DesignWidth / 2, 0.0f});\n    MainItems->HasFocus = false;\n  }\n  if (PrimaryFadeAnimation.IsIn()) {\n    PrimaryFadeAnimation.Progress = 0.0f;\n  }\n  if (SecondaryFadeAnimation.IsIn()) {\n    SecondaryFadeAnimation.Progress = 0.0f;\n  }\n\n  if (PressToStartAnimation.Direction != AnimationDirection::Out ||\n      PressToStartAnimation.LoopMode != AnimationLoopMode::Stop) {\n    PressToStartAnimation.Direction = AnimationDirection::Out;\n    PressToStartAnimation.LoopMode = AnimationLoopMode::Stop;\n    PressToStartAnimation.StartOut();\n  }\n\n  if (TitleAnimation.IsOut() && !IsExploding) {\n    TitleAnimation.StartIn();\n    IsExploding = true;\n    EverExploded = true;\n  }\n  if (TitleAnimation.IsIn() && !IsExploding) {\n    TitleAnimation.StartOut();\n    IsExploding = true;\n  }\n  TitleAnimationSprite.Show = true;\n  TitleAnimationSprite.Face =\n      (int)((TitleAnimationStartFrame +\n             (TitleAnimationFrameCount * TitleAnimation.Progress)) *\n            65536);\n  TitleAnimationSprite.UpdateStatesToDraw();\n}\n\nvoid TitleMenu::ReturnToMenuUpdate() {\n  if (MainItems->VisibilityState == Hidden) {\n    InputLocked = false;\n    CurrentlyFocusedElement = NewGame;\n    for (auto& item : MainItems->Children) {\n      if (item == NewGame) continue;  // Skip NewGame button\n      item->HasFocus = false;\n    }\n    MainItems->Show();\n    IsFocused = true;\n  }\n  PrimaryFadeAnimation.Progress = 1.0f;\n  if (SlideItemsAnimation.IsOut()) {\n    MainItems->Move({Profile::DesignWidth / 2, 0.0f});\n    static_cast<Widget*>(MenuLabel)->Move({Profile::DesignWidth / 2, 0.0f});\n    SlideItemsAnimation.Progress = 1.0f;\n  }\n}\n\nvoid TitleMenu::MainMenuUpdate() {\n  if (IsFocused) {\n    MainItems->HasFocus = true;\n  }\n  MainItems->Tint.a =\n      glm::smoothstep(0.0f, 1.0f, PrimaryFadeAnimation.Progress);\n  ContinueItems->Tint.a =\n      glm::smoothstep(0.0f, 1.0f, SecondaryFadeAnimation.Progress);\n  ExtraItems->Tint.a =\n      glm::smoothstep(0.0f, 1.0f, SecondaryFadeAnimation.Progress);\n\n  TitleAnimationSprite.Show = false;\n\n  if (SlideItemsAnimation.IsOut()) {\n    MainItems->Move({Profile::DesignWidth / 2, 0.0f},\n                    SlideItemsAnimation.DurationIn);\n    static_cast<Widget*>(MenuLabel)->Move({Profile::DesignWidth / 2, 0.0f},\n                                          SlideItemsAnimation.DurationIn);\n    SlideItemsAnimation.StartIn();\n  }\n\n  if (PrimaryFadeAnimation.IsOut()) {\n    PrimaryFadeAnimation.StartIn();\n  }\n\n  if (MainItems->VisibilityState == Hidden) {\n    MainItems->FocusLock = false;\n    MainItems->Show();\n  }\n\n  if (SlideItemsAnimation.IsIn() && PrimaryFadeAnimation.IsIn() &&\n      CurrentlyFocusedElement == nullptr) {\n    MainItems->FocusLock = true;\n    NewGame->Enabled = true;\n    NewGame->HasFocus = true;\n    NewGame->HighlightAnimation.Progress = 1.0f;\n    NewGame->PrevFocusState = true;\n    CurrentlyFocusedElement = NewGame;\n    InputLocked = false;\n    IsFocused = true;\n  }\n\n  if (SecondaryFadeAnimation.IsOut() && CurrentSubMenu) {\n    if (CurrentSubMenu == ContinueItems) {\n      HideContinueItems();\n    } else if (CurrentSubMenu == ExtraItems) {\n      HideExtraItems();\n    }\n  }\n}\n\nvoid TitleMenu::SubMenuUpdate() {\n  MainItems->HasFocus = false;\n  MainItems->Tint.a =\n      glm::smoothstep(0.0f, 1.0f, PrimaryFadeAnimation.Progress);\n  ContinueItems->Tint.a =\n      glm::smoothstep(0.0f, 1.0f, SecondaryFadeAnimation.Progress);\n  ExtraItems->Tint.a =\n      glm::smoothstep(0.0f, 1.0f, SecondaryFadeAnimation.Progress);\n  if (ScrWork[SW_SYSSUBMENUCT] == 0 && SubMenuState == Hidden) {\n    PrimaryFadeAnimation.StartOut();\n    SecondaryFadeAnimation.StartOut();\n    AllowsScriptInput = false;\n    if (SlideItemsAnimation.IsIn()) {\n      SlideItemsAnimation.StartOut();\n      MainItems->Move({-Profile::DesignWidth / 2, 0.0f},\n                      SlideItemsAnimation.DurationOut);\n      static_cast<Widget*>(MenuLabel)->Move({-Profile::DesignWidth / 2, 0.0f},\n                                            SlideItemsAnimation.DurationOut);\n      if (CurrentSubMenu) {\n        CurrentSubMenu->Move({-Profile::DesignWidth / 2, 0.0f},\n                             SlideItemsAnimation.DurationOut);\n        CurrentSubMenu->HasFocus = false;\n      }\n    }\n\n    SubMenuState = Showing;\n  } else if (ScrWork[SW_SYSSUBMENUCT] < 32 && SubMenuState == Shown) {\n    SlideItemsAnimation.StartIn();\n    PrimaryFadeAnimation.StartIn();\n    SecondaryFadeAnimation.StartIn();\n    AllowsScriptInput = false;\n    MainItems->Move({Profile::DesignWidth / 2, 0.0f},\n                    SlideItemsAnimation.DurationIn);\n    static_cast<Widget*>(MenuLabel)->Move({Profile::DesignWidth / 2, 0.0f},\n                                          SlideItemsAnimation.DurationIn);\n    if (CurrentSubMenu) {\n      CurrentSubMenu->HasFocus = false;\n      CurrentSubMenu->Move({Profile::DesignWidth / 2, 0.0f},\n                           SlideItemsAnimation.DurationIn);\n    }\n    SubMenuState = Hiding;\n  }\n}\n\nvoid TitleMenu::Render() {\n  if (State != Hidden && GetFlag(SF_TITLEMODE)) {\n    switch (ScrWork[SW_TITLEMODE]) {\n      case 1: {  // Press to start\n        DrawDISwordBackground();\n        DrawStartButton();\n        DrawSmoke(SmokeOpacityNormal);\n        Renderer->DrawSprite(CopyrightTextSprite,\n                             glm::vec2(CopyrightTextX, CopyrightTextY));\n        Renderer->DrawQuad(\n            RectF(0.0f, 0.0f, Profile::DesignWidth, Profile::DesignHeight),\n            glm::vec4(1.0f, 1.0f, 1.0f,\n                      1.0f - ScrWork[SW_TITLEDISPCT] / 60.0f));\n      } break;\n      case 2: {  // Transition between Press to start and menus\n        if (IsExploding || EverExploded) {\n          DrawMainMenuBackGraphics();\n        } else {\n          DrawDISwordBackground();\n        }\n        DrawStartButton();\n        TitleAnimationSprite.Render(-1);\n        DrawSmoke(SmokeOpacityNormal);\n      } break;\n      case 3: {  // MenuItems Fade In\n        DrawMainMenuBackGraphics();\n        DrawSmoke(SmokeOpacityNormal);\n        Extra->Tint = (GetFlag(SF_CLR_FLAG)) ? MainItems->Tint\n                                             : RgbIntToFloat(ExtraDisabledTint);\n\n        MenuLabel->Render();\n        MainItems->Render();\n        ContinueItems->Render();\n        ExtraItems->Render();\n      } break;\n      case 4: {\n        DrawMainMenuBackGraphics();\n        DrawSmoke(SmokeOpacityNormal);\n        Extra->Tint = (GetFlag(SF_CLR_FLAG)) ? MainItems->Tint\n                                             : RgbIntToFloat(ExtraDisabledTint);\n        MenuLabel->Render();\n        MainItems->Render();\n        ContinueItems->Render();\n        ExtraItems->Render();\n        Renderer->DrawQuad(\n            RectF(0.0f, 0.0f, Profile::DesignWidth, Profile::DesignHeight),\n            {0.0f, 0.0f, 0.0f, ScrWork[SW_TITLEDISPCT] / 32.0f});\n      } break;\n      // TODO check if that's true\n      case 5:\n      case 13: {\n        DrawMainMenuBackGraphics();\n        DrawSmoke(SmokeOpacityNormal);\n        MenuLabel->Render();\n        MainItems->Render();\n        ContinueItems->Render();\n        ExtraItems->Render();\n      } break;\n      case 10: {\n        ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                   \"TitleMenu::Render: Unimplemented title mode {:d}\\n\",\n                   ScrWork[SW_TITLEMODE]);\n      } break;\n      case 11: {  // Initial Fade In\n        DrawDISwordBackground(ScrWork[SW_TITLEDISPCT] / 32.0f);\n        DrawSmoke(ScrWork[SW_TITLEDISPCT] / 128.0f);\n        Renderer->DrawSprite(CopyrightTextSprite,\n                             glm::vec2(CopyrightTextX, CopyrightTextY));\n      } break;\n    }\n\n    int maskAlpha = ScrWork[SW_TITLEMASKALPHA];\n    glm::vec4 col = ScrWorkGetColor(SW_TITLEMASKCOLOR);\n    col.a = glm::min(maskAlpha / 255.0f, 1.0f);\n    Renderer->DrawQuad(\n        RectF(0.0f, 0.0f, Profile::DesignWidth, Profile::DesignHeight), col);\n  }\n}\n\nvoid TitleMenu::DrawDISwordBackground(float opacity) {\n  Renderer->DrawSprite(BackgroundSprite, glm::vec2(0.0f));\n  Renderer->DrawSprite(\n      OverlaySprite,\n      RectF(0.0f, 0.0f, Profile::DesignWidth, Profile::DesignHeight),\n      glm::vec4(1.0f));\n}\n\nvoid TitleMenu::DrawStartButton() {\n  glm::vec4 col = glm::vec4(1.0f);\n  col.a = glm::smoothstep(0.0f, 1.0f, PressToStartAnimation.Progress);\n  Renderer->DrawSprite(PressToStartSprite,\n                       glm::vec2(PressToStartX, PressToStartY), col);\n}\n\nvoid TitleMenu::DrawMainMenuBackGraphics() {\n  Renderer->DrawSprite(MainBackgroundSprite, glm::vec2(0.0f));\n  Renderer->DrawSprite(\n      OverlaySprite,\n      RectF(0.0f, 0.0f, Profile::DesignWidth, Profile::DesignHeight),\n      glm::vec4(1.0f));\n}\n\nvoid TitleMenu::DrawSmoke(float opacity) {\n  glm::vec4 col = glm::vec4(1.0f);\n  col.a = opacity;\n  SmokeSprite.Bounds = RectF(\n      SmokeBoundsWidth - (SmokeAnimationBoundsXMax * SmokeAnimation.Progress) +\n          SmokeAnimationBoundsXOffset,\n      SmokeBoundsY,\n      SmokeBoundsWidth -\n          (SmokeAnimationBoundsXMax * (1.0f - SmokeAnimation.Progress)),\n      SmokeBoundsHeight);\n  Renderer->DrawSprite(SmokeSprite, glm::vec2(SmokeX, SmokeY), col);\n  SmokeSprite.Bounds = RectF(\n      SmokeBoundsX, SmokeBoundsY,\n      SmokeBoundsWidth - (SmokeAnimationBoundsXMax * SmokeAnimation.Progress),\n      SmokeBoundsHeight);\n  Renderer->DrawSprite(\n      SmokeSprite,\n      glm::vec2(SmokeBoundsWidth - (SmokeAnimationBoundsXMax *\n                                    (1.0f - SmokeAnimation.Progress)),\n                SmokeY),\n      col);\n}\n\nvoid TitleMenu::ShowContinueItems() {\n  ContinueItems->Show();\n  ContinueItems->Tint.a = 0.0f;\n  MainItems->HasFocus = false;\n  CurrentlyFocusedElement = Load;\n  CurrentSubMenu = ContinueItems;\n  Load->HasFocus = true;\n  Load->Enabled = false;\n  QuickLoad->Enabled = false;\n  Continue->Enabled = false;\n  SecondaryFadeAnimation.StartIn();\n\n  Extra->Move(glm::vec2(0.0f, ItemPadding));\n  Config->Move(glm::vec2(0.0f, ItemPadding));\n  Help->Move(glm::vec2(0.0f, ItemPadding));\n  if (Exit.has_value()) Exit->get().Move(glm::vec2(0.0f, ItemPadding));\n  ContinueItems->Move(glm::vec2(Profile::DesignWidth / 2, 0.0f),\n                      SecondaryFadeAnimation.DurationOut);\n}\n\nvoid TitleMenu::HideContinueItems() {\n  ContinueItems->Hide();\n  MainItems->HasFocus = true;\n  CurrentlyFocusedElement = Continue;\n  CurrentSubMenu = nullptr;\n  AllowsScriptInput = true;\n  Extra->Move(glm::vec2(0.0f, -ItemPadding));\n  Config->Move(glm::vec2(0.0f, -ItemPadding));\n  Help->Move(glm::vec2(0.0f, -ItemPadding));\n  if (Exit.has_value()) Exit->get().Move(glm::vec2(0.0f, -ItemPadding));\n}\n\nvoid TitleMenu::ShowExtraItems() {\n  ExtraItems->Show();\n  ExtraItems->Tint.a = 0.0f;\n  MainItems->HasFocus = false;\n  CurrentlyFocusedElement = Tips;\n  CurrentSubMenu = ExtraItems;\n  Tips->HasFocus = true;\n  Tips->Enabled = false;\n  Extra->Enabled = false;\n  SecondaryFadeAnimation.StartIn();\n\n  Config->Move(glm::vec2(0, ItemPadding));\n  Help->Move(glm::vec2(0, ItemPadding));\n  if (Exit.has_value()) Exit->get().Move(glm::vec2(0, ItemPadding));\n  ExtraItems->Move({Profile::DesignWidth / 2, 0.0f},\n                   SecondaryFadeAnimation.DurationIn);\n}\n\nvoid TitleMenu::HideExtraItems() {\n  ExtraItems->Hide();\n  MainItems->HasFocus = true;\n  CurrentlyFocusedElement = Extra;\n  Extra->HasFocus = true;\n  CurrentSubMenu = nullptr;\n  AllowsScriptInput = true;\n  Config->Move(glm::vec2(0, -ItemPadding));\n  Help->Move(glm::vec2(0, -ItemPadding));\n  if (Exit.has_value()) Exit->get().Move(glm::vec2(0, -ItemPadding));\n  Config->Enabled = true;\n  Help->Enabled = true;\n}\n\n}  // namespace CCLCC\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/cclcc/titlemenu.h",
    "content": "#pragma once\n\n#include \"../../animation.h\"\n#include \"../../character2d.h\"\n#include \"../../ui/menu.h\"\n#include \"../../ui/widgets/label.h\"\n#include \"../../ui/widgets/group.h\"\n#include \"../../ui/widgets/button.h\"\n#include \"../../ui/widgets/cclcc/titlebutton.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace CCLCC {\n\nclass TitleMenu : public Menu {\n public:\n  TitleMenu();\n\n  void Show() override;\n  void Hide() override;\n  void UpdateInput(float dt) override;\n  void Update(float dt) override;\n  void Render() override;\n\n  Animation PressToStartAnimation;\n  Animation PrimaryFadeAnimation;\n  Animation SecondaryFadeAnimation;\n  Animation SmokeAnimation;\n  Animation TitleAnimation;\n  Animation SlideItemsAnimation;\n  Character2D TitleAnimationSprite;\n\n  void MenuButtonOnClick(Widgets::Button* target);\n  void ContinueButtonOnClick(Widgets::Button* target);\n  void ExtraButtonOnClick(Widgets::Button* target);\n  void ExitButtonOnClick(Widgets::Button* target);\n\n private:\n  Widgets::Group* CurrentSubMenu = 0;\n\n  Widgets::Group* MainItems;\n  Widgets::CCLCC::TitleButton* NewGame;\n  Widgets::CCLCC::TitleButton* Continue;\n  Widgets::CCLCC::TitleButton* Extra;\n  Widgets::CCLCC::TitleButton* Config;\n  Widgets::CCLCC::TitleButton* Help;\n  std::optional<std::reference_wrapper<Widgets::CCLCC::TitleButton>> Exit;\n  Widgets::Label* MenuLabel;\n\n  Widgets::Group* ContinueItems;\n  Widgets::CCLCC::TitleButton* Load;\n  Widgets::CCLCC::TitleButton* QuickLoad;\n\n  void ShowContinueItems();\n  void HideContinueItems();\n\n  Widgets::Group* ExtraItems;\n  Widgets::CCLCC::TitleButton* Tips;\n  Widgets::CCLCC::TitleButton* Library;\n  Widgets::CCLCC::TitleButton* EndingList;\n  void ShowExtraItems();\n  void HideExtraItems();\n\n  void DrawDISwordBackground(float opacity = 1.0f);\n  void DrawStartButton();\n  void DrawMainMenuBackGraphics();\n  void DrawSmoke(float opacity);\n\n  void MainMenuUpdate();\n  void SubMenuUpdate();\n  void ExplodeScreenUpdate();\n  void ReturnToMenuUpdate();\n  MenuState SubMenuState = Hidden;\n  bool EverExploded = false;\n  bool IsExploding = false;\n  bool InputLocked = false;\n  bool PrevInputLocked = false;\n};\n\n}  // namespace CCLCC\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/cclcc/yesnotrigger.cpp",
    "content": "#include \"yesnotrigger.h\"\n#include \"../../profile/games/cclcc/yesnotrigger.h\"\n#include \"../../vm/interface/input.h\"\n#include \"../../audio/audiosystem.h\"\n#include \"../../renderer/renderer.h\"\n#include \"../../inputsystem.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace CCLCC {\nusing namespace Profile::CCLCC::YesNoTrigger;\nusing namespace Vm::Interface;\nusing namespace Impacto::Profile::ScriptVars;\n\nint YesNoTrigger::Load(uint8_t* data) {\n  int dataSize = 0;\n  dataSize += 4;\n  memcpy(&BgType, data + dataSize, sizeof(BGType));\n  dataSize += 12;\n  Display = data[dataSize++];\n  HasStarted = data[dataSize++];\n  DispSel = data[dataSize++];\n  AllowInput = data[dataSize++];\n  GoToNextQuestion = data[dataSize++];\n  dataSize += 3;\n  memcpy(&Selection, data + dataSize, sizeof(YesNoSelect));\n  dataSize += 8;\n  float bgSpritePosX, bgSpritePosY;\n  memcpy(&bgSpritePosX, data + dataSize, sizeof(float));\n  dataSize += 4;\n  memcpy(&bgSpritePosY, data + dataSize, sizeof(float));\n  dataSize += 4;\n  BgSpritePos = glm::vec2(bgSpritePosX, bgSpritePosY);\n  memcpy(&BgSpriteScale, data + dataSize, sizeof(float));\n  dataSize += 16;\n  memcpy(&BgTransition, data + dataSize, sizeof(float));\n  dataSize += 4;\n  memcpy(&State, data + dataSize, sizeof(YesNoState));\n  dataSize += 4;\n  // PS4 has 64-bit pointers, vita has 32-bit pointers...\n  dataSize += sizeof(void*);\n  memcpy(&CurArrIndex, data + dataSize, sizeof(int));\n  dataSize += 4;\n  memcpy(&TargetArrIndex, data + dataSize, sizeof(int));\n  dataSize += 4;\n  dataSize += sizeof(void*);  // buffer pointer in their struct\n  dataSize += 8;\n  assert(dataSize == 0x60);\n  if (State != YesNoState::None && State != YesNoState::Complete) {\n    switch (BgType) {\n      case BGType::BG0:\n        ActiveBackground = YesNoBackground0;\n        DispPosArr = YesNoData1;\n        ActiveBlur = YNChipBlurBg0;\n        break;\n      case BGType::BG1:\n        ActiveBackground = YesNoBackground1;\n        DispPosArr = YesNoData1;\n        ActiveBlur = YNChipBlurBg1;\n        break;\n      case BGType::BG2:\n        ActiveBackground = YesNoBackground2;\n        DispPosArr = YesNoData2;\n        ActiveBlur = YNChipBlurBg2;\n        break;\n      case BGType::BG3:\n        ActiveBackground = YesNoBackground3;\n        DispPosArr = YesNoData2;\n        ActiveBlur = YNChipBlurBg2;\n        break;\n    }\n  }\n  return dataSize;\n}\n\nint YesNoTrigger::Save(uint8_t* data) {\n  int dataSize = 0;\n  dataSize += 4;\n  memcpy(data + dataSize, &BgType, sizeof(BGType));\n  dataSize += 12;\n  data[dataSize++] = Display;\n  data[dataSize++] = HasStarted;\n  data[dataSize++] = DispSel;\n  data[dataSize++] = AllowInput;\n  data[dataSize++] = GoToNextQuestion;\n  dataSize += 3;\n  memcpy(data + dataSize, &Selection, sizeof(YesNoSelect));\n  dataSize += 8;\n  memcpy(data + dataSize, &BgSpritePos.x, sizeof(float));\n  dataSize += 4;\n  memcpy(data + dataSize, &BgSpritePos.y, sizeof(float));\n  dataSize += 4;\n  memcpy(data + dataSize, &BgSpriteScale, sizeof(float));\n  dataSize += 16;\n  memcpy(data + dataSize, &BgTransition, sizeof(float));\n  dataSize += 4;\n  memcpy(data + dataSize, &State, sizeof(YesNoState));\n  dataSize += 4;\n  dataSize += sizeof(void*);\n  memcpy(data + dataSize, &CurArrIndex, sizeof(int));\n  dataSize += 4;\n  memcpy(data + dataSize, &TargetArrIndex, sizeof(int));\n  dataSize += 4;\n  dataSize += sizeof(void*);  // buffer pointer in their struct\n  dataSize += 8;\n\n  assert(dataSize == 0x60);\n  return dataSize;\n}\n\nvoid YesNoTrigger::UpdateYesNoPos(float startX, float startY, float startScale,\n                                  float targetX, float targetY,\n                                  float targetScale, float transition) {\n  float boundedScale = std::clamp(targetScale, 1.0f, 3.0f);\n\n  float smoothTransition = cos(transition * std::numbers::pi_v<float>);\n  smoothTransition = (1.0f - smoothTransition) * 0.5f;\n\n  BgSpritePos.x = startX + smoothTransition * (targetX - startX);\n  BgSpritePos.y = startY + smoothTransition * (targetY - startY);\n  BgSpriteScale = startScale + smoothTransition * (boundedScale - startScale);\n}\n\nvoid YesNoTrigger::Update(float dt) {\n  if (ScrWork[SW_SYSSUBMENUCT] != 0 || ScrWork[SW_SYSMENUCT] != 0 ||\n      ScrWork[6433] == 0 || !HasStarted) {\n    return;\n  }\n\n  YesClickArea.Enabled = AllowInput;\n  YesClickArea.UpdateInput(dt);\n  YesClickArea.Update(dt);\n  NoClickArea.Enabled = AllowInput;\n  NoClickArea.UpdateInput(dt);\n  NoClickArea.Update(dt);\n\n  StarAnimation.Update(dt);\n  switch (State) {\n    case YesNoState::Init:\n      State = YesNoState::ZoomStart;\n      break;\n    case YesNoState::ZoomStart:\n      BgTransition += 0.01f;\n      UpdateYesNoPos(0.0f, 0.0f, 1.0f, DispPosArr[0].BgPos.x,\n                     DispPosArr[0].BgPos.y, 3.0f, BgTransition);\n      if (BgTransition >= 1.0f) {\n        BgTransition = 1.0f;\n        State = YesNoState::MainInput;\n      }\n      break;\n    case YesNoState::MainInput:\n      if (AllowInput) {\n        DispSel = true;\n        if (Input::CurrentInputDevice == Input::Device::Mouse) {\n          if (NoClickArea.Hovered) {\n            // folded that way on purpose,if two areas are both hovered\n            // they will be switching constantly\n            if (Selection != YesNoSelect::NO) {\n              Selection = YesNoSelect::NO;\n              Audio::PlayInGroup(Audio::ACG_SE, \"sysse\", 1, false, 0.0f);\n            }\n          } else if (YesClickArea.Hovered && Selection != YesNoSelect::YES &&\n                     !IsChoiceBlocked(YesNoSelect::YES)) {\n            Selection = YesNoSelect::YES;\n            Audio::PlayInGroup(Audio::ACG_SE, \"sysse\", 1, false, 0.0f);\n          }\n        }\n\n        if (PADinputButtonWentDown &\n            (PADcustom[0] | PADcustom[1] | PADcustom[2] | PADcustom[3])) {\n          if (Selection == YesNoSelect::NONE) {\n            Selection = YesNoSelect::YES;\n          } else if (Selection == YesNoSelect::YES) {\n            Selection = YesNoSelect::NO;\n          } else {\n            Selection = YesNoSelect::YES;\n          }\n          if (IsChoiceBlocked(YesNoSelect::YES)) {\n            Selection = YesNoSelect::NO;\n            Audio::PlayInGroup(Audio::ACG_SE, \"sysse\", 4, false, 0.0f);\n          } else {\n            Audio::PlayInGroup(Audio::ACG_SE, \"sysse\", 1, false, 0.0f);\n          }\n        }\n        if (PADinputButtonWentDown & PADcustom[5] &&\n            Selection != YesNoSelect::NONE) {\n          ChooseSelected();\n        }\n      }\n      break;\n    case YesNoState::PanToNext:\n      if (GoToNextQuestion) {\n        BgTransition += 0.03f;\n        UpdateYesNoPos(DispPosArr[CurArrIndex].BgPos.x,\n                       DispPosArr[CurArrIndex].BgPos.y, 3.0f,\n                       DispPosArr[TargetArrIndex].BgPos.x,\n                       DispPosArr[TargetArrIndex].BgPos.y, 3.0f, BgTransition);\n        if (BgTransition >= 1.0f) {\n          BgTransition = 1.0f;\n          if (DispPosArr[TargetArrIndex].NextYesIndex == 0 ||\n              DispPosArr[TargetArrIndex].NextNoIndex == 0) {\n            State = YesNoState::Complete;\n          } else {\n            State = YesNoState::MainInput;\n          }\n          GoToNextQuestion = false;\n        }\n      }\n      break;\n    default:\n      break;\n  }\n  BgSpriteScale = std::clamp(BgSpriteScale, 1.0f, 3.0f);\n  ScrWork[6434] = TargetArrIndex;\n}\n\nvoid YesNoTrigger::Render() {\n  if (ScrWork[6433] == 0 || Display == false) {\n    return;\n  }\n\n  const glm::vec2 bgSize = ActiveBackground.Sheet.GetDimensions();\n  const float bgWidth = bgSize.x / BgSpriteScale;\n  const float bgHeight = bgSize.y / BgSpriteScale;\n  const float bgXOffset = (bgSize.x - bgWidth) / 2.0f;\n  const float bgYOffset =\n      (bgSize.y - bgHeight) / 2.0f - 46.0f * (BgSpriteScale - 1.0f) * 0.5f;\n  const float alpha =\n      std::clamp(static_cast<float>(ScrWork[6433] << 3), 0.0f, 255.0f);\n  const glm::vec4 bgtint = glm::vec4(1.0f, 1.0f, 1.0f, alpha / 255.0f);\n  ActiveBackground.Bounds =\n      Rect(static_cast<int>(BgSpritePos.x + bgXOffset),\n           static_cast<int>(BgSpritePos.y + bgYOffset),\n           static_cast<int>(bgWidth), static_cast<int>(bgHeight));\n\n  Renderer->DrawSprite(ActiveBackground,\n                       RectF(0, 0, bgSize.x * 0.5f, bgSize.y * 0.5f), bgtint);\n\n  const glm::vec2 blurScale = BgSpriteScale * 0.5f *\n                              ActiveBackground.Sheet.GetDimensions() /\n                              ActiveBlur.Bounds.GetSize();\n  // center after zooming\n  glm::vec2 blurTranslation =\n      (1.0f - BgSpriteScale) * ActiveBlur.Bounds.GetSize();\n  // apply scaled BgSpritePos offset\n  blurTranslation -= BgSpritePos * BgSpriteScale * 0.5f;\n  // correction for \"- 46.0f\" from ActiveBackground\n  blurTranslation.y += 46.0f * (BgSpriteScale - 1.0f) * 0.75f;\n\n  const glm::mat4 blurTransform =\n      TransformationMatrix(glm::vec2(0.0f), blurScale, glm::vec3(0.0f),\n                           glm::quat(), blurTranslation);\n\n  // rescaled to [0.5;3]\n  const float scale = 0.5f + 1.25f * (BgSpriteScale - 1.0f);\n  const glm::vec3 maskScale = glm::vec3(scale);\n  // calc as offset from the center\n  glm::vec2 maskTranslate =\n      DispPosArr[TargetArrIndex].BlurMaskPos - bgSize * 0.5f;\n  // correction for \"- 46.0f\" from ActiveBackground with scaling \"scale\" to\n  // [0;3] to zero out 46 at BgSpriteScale == 1\n  maskTranslate.y += 46.0f * 0.5f * (scale - 0.5f) * 1.2f;\n  // normalize for the sprite basis\n  maskTranslate *= -1.0f * scale / bgSize;\n\n  const glm::mat4 maskTransform = TransformationMatrix(\n      glm::vec2(0.5f), maskScale, glm::vec3(0.0f), glm::quat(), maskTranslate);\n\n  // Can't simply use ActiveBlur.Bounds due to some sprites having a\n  // non-zero Y coordinate, so you don't want to additionally correct for it\n  // via matrix\n  Renderer->DrawMaskedSpriteOverlay(\n      ActiveBlur, YesNoBlurMask,\n      RectF(0.0f, 0.0f, ActiveBlur.Bounds.Width, ActiveBlur.Bounds.Height),\n      YesNoBlurMask.Bounds, static_cast<int>(alpha), 255, blurTransform,\n      maskTransform, glm::vec4(1.0f), true, true);\n\n  if (DispSel) {\n    glm::vec2 yesChipPos = (DispPosArr[TargetArrIndex].ChipYesPos -\n                            ActiveBackground.Bounds.GetPos()) *\n                           0.5f * BgSpriteScale;\n    glm::vec2 noChipPos = (DispPosArr[TargetArrIndex].ChipNoPos -\n                           ActiveBackground.Bounds.GetPos()) *\n                          0.5f * BgSpriteScale;\n    Sprite* activeYesChip;\n    Sprite* activeNoChip;\n    glm::vec2 selectedChipSmallSize;\n    const glm::vec4 chipTint = glm::vec4(1.0f, 1.0f, 1.0f, alpha / 255.0f);\n\n    if (BgType == BGType::BG0 || BgType == BGType::BG1) {\n      activeYesChip =\n          (Selection == YesNoSelect::YES) ? &YN1YesChipLarge : &YN1YesChipSmall;\n      activeNoChip =\n          (Selection == YesNoSelect::NO) ? &YN1NoChipLarge : &YN1NoChipSmall;\n      selectedChipSmallSize =\n          ((Selection == YesNoSelect::YES) ? YN1YesChipSmall : YN1NoChipSmall)\n              .Bounds.GetSize();\n    } else /* if (BgType == BGType::BG2 || BgType == BGType::BG3) */ {\n      activeYesChip =\n          (Selection == YesNoSelect::YES) ? &YN2YesChipLarge : &YN2YesChipSmall;\n      activeNoChip =\n          (Selection == YesNoSelect::NO) ? &YN2NoChipLarge : &YN2NoChipSmall;\n      selectedChipSmallSize =\n          ((Selection == YesNoSelect::YES) ? YN2YesChipSmall : YN2NoChipSmall)\n              .Bounds.GetSize();\n    }\n    const glm::vec2 yesChipSize =\n        0.5f * BgSpriteScale * activeYesChip->Bounds.GetSize();\n    const glm::vec2 noChipSize =\n        0.5f * BgSpriteScale * activeNoChip->Bounds.GetSize();\n    RectF yesChipDest =\n        RectF(yesChipPos.x, yesChipPos.y, yesChipSize.x, yesChipSize.y);\n    RectF noChipDest =\n        RectF(noChipPos.x, noChipPos.y, noChipSize.x, noChipSize.y);\n\n    if (Selection != YesNoSelect::NONE) {\n      const glm::vec2 selectedChipPos =\n          (Selection == YesNoSelect::YES) ? yesChipPos : noChipPos;\n      const glm::vec2 activeSmallChipCenter =\n          selectedChipPos + selectedChipSmallSize * 0.5f * 0.5f * BgSpriteScale;\n\n      if (Selection == YesNoSelect::YES) {\n        yesChipDest.SetPos(activeSmallChipCenter - yesChipSize * 0.5f);\n      } else {\n        noChipDest.SetPos(activeSmallChipCenter - noChipSize * 0.5f);\n      }\n\n      const glm::vec2 starTopLeft =\n          activeSmallChipCenter - StarChip.Bounds.GetSize() * 0.5f;\n      const CornersQuad starDest =\n          StarChip.ScaledBounds()\n              .RotateAroundCenter(StarAnimation.Progress * 2.0f *\n                                  std::numbers::pi_v<float>)\n              .Translate(starTopLeft);\n      Renderer->DrawSprite(StarChip, starDest, chipTint);\n    }\n\n    YesClickArea.Bounds = yesChipDest;\n    NoClickArea.Bounds = noChipDest;\n    Renderer->DrawSprite(*activeYesChip, yesChipDest, chipTint);\n    Renderer->DrawSprite(*activeNoChip, noChipDest, chipTint);\n  }\n  constexpr glm::vec4 maskTint = glm::vec4(1.0f, 1.0f, 1.0f, 160 / 256.0f);\n  Renderer->DrawSprite(YesNoBgOverlay,\n                       RectF(0, 0, bgSize.x * 0.5f, bgSize.y * 0.5f), maskTint);\n}\n\nvoid YesNoTrigger::Start(int type, int bgBufId, int chipsBufId) {\n  // bgBufId & chipsBufId are unused\n  HasStarted = false;\n  Display = false;\n  State = YesNoState::Init;\n  if (type == 0 || type == 1) {\n    DispPosArr = YesNoData1;\n  } else if (type == 2 || type == 3) {\n    DispPosArr = YesNoData2;\n  }\n\n  BgType = static_cast<BGType>(type);\n  switch (BgType) {\n    case BGType::BG0: {\n      ActiveBackground = YesNoBackground0;\n      ActiveBlur = YNChipBlurBg0;\n      break;\n    }\n    case BGType::BG1: {\n      ActiveBackground = YesNoBackground1;\n      ActiveBlur = YNChipBlurBg1;\n      break;\n    }\n    case BGType::BG2: {\n      ActiveBackground = YesNoBackground2;\n      ActiveBlur = YNChipBlurBg2;\n      break;\n    }\n    case BGType::BG3: {\n      ActiveBackground = YesNoBackground3;\n      ActiveBlur = YNChipBlurBg2;\n      break;\n    }\n  }\n\n  DispSel = false;\n  AllowInput = false;\n  GoToNextQuestion = false;\n  StarAnimation.Direction = AnimationDirection::In;\n  StarAnimation.LoopMode = AnimationLoopMode::Loop;\n  StarAnimation.SkipOnSkipMode = false;\n  StarAnimation.SetDuration(StarRotationPeriod);\n  auto onClick = [this](auto* area) { return AreaClick(area); };\n  YesClickArea =\n      Widgets::ClickArea(static_cast<int>(YesNoSelect::YES), RectF(), onClick);\n  NoClickArea =\n      Widgets::ClickArea(static_cast<int>(YesNoSelect::NO), RectF(), onClick);\n}\n\nvoid YesNoTrigger::Show() {\n  BgTransition = 0.0f;\n  TargetArrIndex = 0;\n  Selection = YesNoSelect::NONE;\n  Display = true;\n  HasStarted = true;\n  StarAnimation.StartIn();\n}\n\nvoid YesNoTrigger::Reset() {\n  BgTransition = 0.0f;\n  float newX = DispPosArr[0].BgPos.x;\n  float newY = DispPosArr[0].BgPos.y;\n  Display = true;\n  TargetArrIndex = 0;\n  Selection = YesNoSelect::NONE;\n  UpdateYesNoPos(0.0f, 0.0f, 1.0f, newX, newY, 3.0f, 0.0f);\n}\n\nvoid YesNoTrigger::Hide() {\n  Display = false;\n  HasStarted = false;\n  State = YesNoState::Complete;\n  StarAnimation.Stop();\n}\n\nvoid YesNoTrigger::AreaClick(Widgets::ClickArea* area) {\n  YesNoSelect selected = static_cast<YesNoSelect>(area->Id);\n  if (IsChoiceBlocked(selected)) {\n    Audio::PlayInGroup(Audio::ACG_SE, \"sysse\", 4, false, 0.0f);\n    return;\n  }\n  Selection = selected;\n  ChooseSelected();\n  area->Hovered = false;\n}\n\nbool YesNoTrigger::IsChoiceBlocked(YesNoSelect choice) {\n  return BgType == BGType::BG1 && choice == YesNoSelect::YES &&\n         (TargetArrIndex == 11 || TargetArrIndex == 12);\n}\n\nvoid YesNoTrigger::ChooseSelected() {\n  State = YesNoState::PanToNext;\n  CurArrIndex = TargetArrIndex;\n  if (Selection == YesNoSelect::YES) {\n    TargetArrIndex = DispPosArr[CurArrIndex].NextYesIndex;\n  } else {\n    TargetArrIndex = DispPosArr[CurArrIndex].NextNoIndex;\n  }\n  BgTransition = 0.0f;\n  DispSel = false;\n  AllowInput = false;\n  Selection = YesNoSelect::NONE;\n  ScrWork[6432] = to_underlying(Selection);\n  Audio::PlayInGroup(Audio::ACG_SE, \"sysse\", 2, false, 0.0f);\n}\n\n}  // namespace CCLCC\n}  // namespace UI\n}  // namespace Impacto\n"
  },
  {
    "path": "src/games/cclcc/yesnotrigger.h",
    "content": "#pragma once\n#include \"../../animation.h\"\n#include \"../../spritesheet.h\"\n\n#include <array>\n#include <magic_enum/magic_enum.hpp>\n#include \"../../ui/widgets/clickarea.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace CCLCC {\nclass YesNoTrigger {\n public:\n  enum class BGType : int8_t { BG0, BG1, BG2, BG3 };\n  enum class YesNoSelect : int8_t { NONE = -1, YES = 0, NO = 1 };\n  enum class YesNoState : int8_t {\n    None,\n    Init,\n    ZoomStart,\n    MainInput,\n    PanToNext,\n    Complete = 6\n  };\n  struct YesNoPositions {\n    glm::vec2 BgPos;\n    int NextYesIndex;\n    int NextNoIndex;\n    glm::vec2 ChipYesPos;\n    glm::vec2 ChipNoPos;\n    glm::vec2 BlurMaskPos;\n  };\n\n  void Start(int bgType, int bgBufId, int chipsBufId);\n  void Show();\n  void Hide();\n  void Update(float dt);\n  void Render();\n  void Reset();\n  int Load(uint8_t* data);\n  int Save(uint8_t* data);\n\n  Widgets::ClickArea YesClickArea;\n  Widgets::ClickArea NoClickArea;\n  Sprite ActiveBackground;\n  Sprite ActiveBlur;\n  Animation StarAnimation;\n  std::span<YesNoPositions> DispPosArr;\n  glm::vec2 BgSpritePos;\n  float BgSpriteScale = 1.0f;\n  float BgTransition = 0.0f;\n\n  int CurArrIndex = 0;\n  int TargetArrIndex = 0;\n\n  bool Display = false;\n  bool HasStarted = false;\n  bool AllowInput = false;\n  bool GoToNextQuestion = false;\n  bool DispSel = false;\n\n  YesNoState State = YesNoState::None;\n  YesNoSelect Selection = YesNoSelect::NONE;\n  BGType BgType;\n\n  static YesNoTrigger& GetInstance() {\n    static YesNoTrigger impl;\n    return impl;\n  }\n\n private:\n  void UpdateYesNoPos(float startX, float startY, float startScale,\n                      float targetX, float targetY, float targetScale,\n                      float transition);\n  void AreaClick(Widgets::ClickArea* clickArea);\n  bool IsChoiceBlocked(YesNoSelect choice);\n  void ChooseSelected();\n};\n\n}  // namespace CCLCC\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/chlcc/albummenu.cpp",
    "content": "#include \"albummenu.h\"\n\n#include \"../../profile/games/chlcc/albummenu.h\"\n#include \"../../profile/scriptvars.h\"\n#include \"../../profile/profile_internal.h\"\n#include \"../../renderer/renderer.h\"\n#include \"../../ui/ui.h\"\n#include \"../../inputsystem.h\"\n#include \"../../data/savesystem.h\"\n#include \"../../ui/widgets/chlcc/albumthumbnailbutton.h\"\n#include \"../../profile/games/chlcc/commonmenu.h\"\n#include \"../../vm/interface/input.h\"\n\n#include \"../../ui/widgets/group.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace CHLCC {\nusing namespace Impacto::Profile::CHLCC::CommonMenu;\n\nusing namespace Impacto::Profile::CHLCC::AlbumMenu;\nusing namespace Impacto::Profile::ScriptVars;\n\nusing namespace Impacto::UI::Widgets;\n\nusing namespace Impacto::Vm::Interface;\nusing namespace Impacto::UI::Widgets::CHLCC;\n\nvoid AlbumMenu::OnCgVariationEnd(Widgets::CgViewer* target) {\n  CgViewerWidget->Hide();\n  ButtonGuideFade.StartIn();\n}\n\nvoid AlbumMenu::CgOnClick(Widgets::Button* target) {\n  if (!target->IsLocked) {\n    int total, viewed = 0;\n    SaveSystem::GetEVStatus(target->Id, &total, &viewed);\n\n    ShowCgViewer = true;\n    CgViewerWidget->LoadCgSprites((size_t)target->Id, \"bg\",\n                                  Profile::SaveSystem::AlbumData[target->Id]);\n    ButtonGuideFade.StartOut();\n  }\n}\n\nAlbumMenu::AlbumMenu() : CommonMenu(false) {\n  CurrentPage = 0;\n  MaxReachablePage = 0;\n\n  RedBarSprite = InitialRedBarSprite;\n  RedBarPosition = InitialRedBarPosition;\n\n  ButtonGuideFade.SetDuration(CgFadeDuration);\n  ButtonGuideFade.Finish();\n\n  CgViewerWidget = new Widgets::CgViewer(CgFadeDuration);\n  CgViewerWidget->OnVariationEndHandler = [this](auto* btn) {\n    return OnCgVariationEnd(btn);\n  };\n  CgViewerGroup = new Group(this);\n  CgViewerGroup->Add(CgViewerWidget, FDIR_DOWN);\n\n  auto cgOnClick = [this](auto* btn) { return CgOnClick(btn); };\n\n  for (int k = 0; k <= AlbumPages; k++) {\n    auto page = new Group(this);\n    for (int i = 0; i < 3; i++) {\n      for (int j = 0; j < 3; j++) {\n        auto button = new AlbumThumbnailButton(\n            (EntriesPerPage * k + 3 * i + j),\n            (AlbumThumbnails[EntriesPerPage * k + 3 * i + j]),\n            ThumbnailHighlight, ThumbnailHighlight,\n            glm::vec2(ThumbnailTemplatePosition.x + ThumbnailOffset.x * j,\n                      ThumbnailTemplatePosition.y + ThumbnailOffset.y * i),\n            0, 0, VariationUnlocked, VariationLocked, VariationTemplateOffset,\n            LockedCG, SelectionMarkerSprite, SelectionMarkerRelativePos);\n        button->OnClickHandler = cgOnClick;\n        page->Add(button);\n      }\n    }\n    Pages.push_back(page);\n  }\n}\n\nvoid AlbumMenu::Init() {\n  if (!GetFlag(SF_CONGRATULATED)) {\n    return;\n  }\n\n  const auto start = AlbumPages * EntriesPerPage;\n  for (int i = start; i < start + EntriesPerPage; i++) {\n    SaveSystem::SetEVStatus(i);\n  }\n}\n\nvoid AlbumMenu::Show() {\n  if (State != Shown) {\n    if (State != Showing) {\n      MenuTransition.StartIn();\n      SelectAnimation.StartIn(true);\n    };\n    UpdatePages();\n    Pages[CurrentPage]->Show();\n    Pages[CurrentPage]->HasFocus = false;\n    State = Showing;\n    if (UI::FocusedMenu != 0) {\n      LastFocusedMenu = UI::FocusedMenu;\n      LastFocusedMenu->IsFocused = false;\n    }\n    IsFocused = true;\n    UI::FocusedMenu = this;\n    Pages[CurrentPage]->Children.front()->HasFocus = true;\n    CurrentlyFocusedElement = Pages[CurrentPage]->Children.front();\n  }\n}\n\nvoid AlbumMenu::Hide() {\n  if (State != Hidden) {\n    if (State != Hiding) {\n      AlbumThumbnailButton::FocusedAlphaFadeReset();\n      MenuTransition.StartOut();\n    }\n    Pages[CurrentPage]->HasFocus = false;\n    State = Hiding;\n    if (LastFocusedMenu != 0) {\n      UI::FocusedMenu = LastFocusedMenu;\n      LastFocusedMenu->IsFocused = true;\n    } else {\n      UI::FocusedMenu = 0;\n    }\n    IsFocused = false;\n  }\n}\n\nvoid AlbumMenu::Render() {\n  if (State == Hidden) return;\n  CommonMenu::DrawSubmenu(BackgroundColor, CircleSprite, AlbumMenuTitle,\n                          AlbumMenuTitleAngle, true);\n\n  if (MenuTransition.Progress < 0.22f) return;\n\n  DrawPage();\n  CgViewerGroup->Render();\n  // show only when CGViewer is hidden, hiding, showing\n  if (!CgViewerWidget->IsShown()) {\n    CommonMenu::DrawButtonPrompt(ButtonGuide, ButtonGuidePos);\n  }\n  if (CgViewerWidget->IsFadingBetweenVariations() &&\n      CgViewerWidget->IsOnLastVariation()) {\n    float progress = CgViewerWidget->GetVariationFadeProgress();\n    float alpha = glm::smoothstep(1.0f, 0.0f, progress);\n\n    Renderer->DrawSprite(CgViewerButtonGuideVariation, CgViewerButtonGuidePos,\n                         glm::vec4(1.0f, 1.0f, 1.0f, alpha));\n    Renderer->DrawSprite(CgViewerButtonGuideNoVariation, CgViewerButtonGuidePos,\n                         glm::vec4(1.0f, 1.0f, 1.0f, 1.0f - alpha));\n  } else {\n    float alpha = glm::smoothstep(0.0f, 1.0f, 1.0f - ButtonGuideFade.Progress);\n    auto sprite = CgViewerWidget->IsOnLastVariation()\n                      ? CgViewerButtonGuideNoVariation\n                      : CgViewerButtonGuideVariation;\n    Renderer->DrawSprite(sprite, CgViewerButtonGuidePos,\n                         glm::vec4(1.0f, 1.0f, 1.0f, alpha));\n  }\n}\n\nvoid AlbumMenu::UpdateInput(float dt) {\n  using namespace Vm::Interface;\n  if (!ShowCgViewer) Menu::UpdateInput(dt);\n  if (State == Shown) {\n    CgViewerGroup->UpdateInput(dt);\n    if (!ShowCgViewer) Pages[CurrentPage]->UpdateInput(dt);\n    if (PADinputButtonWentDown & PAD1B || PADinputMouseWentDown & PAD1B) {\n      if (CgViewerGroup->VisibilityState != Hidden) {\n        CgViewerWidget->Hide();\n        ButtonGuideFade.StartIn();\n      } else {\n        SetFlag(SF_ALBUMEND, true);\n      }\n    }\n    const auto updatePage = [&](int nextPage) {\n      PrevPage = CurrentPage;\n      if (CurrentlyFocusedElement) {\n        CurrentlyFocusedElement->HasFocus = false;\n        CurrentlyFocusedElement->Hovered = false;\n      }\n      CurrentPage = nextPage;\n      Pages[CurrentPage]->Show();\n      Pages[CurrentPage]->Children.front()->HasFocus = true;\n      CurrentlyFocusedElement = Pages[CurrentPage]->Children.front();\n    };\n    if (IsFocused && !ShowCgViewer) {\n      const auto albumPages = AlbumPages + GetFlag(SF_CONGRATULATED);\n      if (Input::MouseWheelDeltaY < 0 ||\n          PADinputButtonWentDown & PADcustom[8]) {\n        updatePage((CurrentPage + 1) % albumPages);\n      } else if (Input::MouseWheelDeltaY > 0 ||\n                 PADinputButtonWentDown & PADcustom[7]) {\n        updatePage((CurrentPage - 1 + albumPages) % albumPages);\n      }\n    }\n  }\n}\n\nvoid AlbumMenu::Update(float dt) {\n  if ((!GetFlag(SF_ALBUMMENU) || ScrWork[SW_SYSMENUCT] < 10000) &&\n      State == Shown) {\n    Hide();\n  } else if (GetFlag(SF_ALBUMMENU) && ScrWork[SW_SYSMENUCT] > 0 &&\n             State == Hidden) {\n    Show();\n  }\n\n  if (CgViewerWidget->IsHidden() && ShowCgViewer) {\n    CgViewerGroup->Hide();\n    ShowCgViewer = false;\n  }\n\n  if (MenuTransition.IsOut() &&\n      (ScrWork[SW_SYSMENUCT] == 0 || GetFlag(SF_SYSTEMMENU)) &&\n      State == Hiding) {\n    State = Hidden;\n    Pages[CurrentPage]->Hide();\n  } else if (MenuTransition.IsIn() && ScrWork[SW_SYSMENUCT] == 10000 &&\n             State == Showing) {\n    State = Shown;\n    Pages[CurrentPage]->HasFocus = true;\n    AlbumThumbnailButton::FocusedAlphaFadeStart();\n  }\n\n  if (State != Hidden) {\n    ButtonGuideFade.Update(dt);\n    MenuTransition.Update(dt);\n    SelectAnimation.Update(dt);\n    if (MenuTransition.Direction == AnimationDirection::Out &&\n        MenuTransition.Progress <= 0.72f) {\n      TitleFade.StartOut();\n    } else if (MenuTransition.IsIn() &&\n               (TitleFade.Direction == AnimationDirection::In ||\n                TitleFade.IsOut())) {\n      TitleFade.StartIn();\n    }\n    if (State == Shown) {\n      UpdateInput(dt);\n    }\n    TitleFade.Update(dt);\n    CommonMenu::UpdateTitles(AlbumMenuTitleRightPos, AlbumMenuTitleLeftPos);\n\n    AlbumThumbnailButton::UpdateFocusedAlphaFade(dt);\n\n    if (ShowCgViewer) {\n      if (CgViewerGroup->VisibilityState == Hidden) CgViewerGroup->Show();\n      CgViewerGroup->Update(dt);\n    } else {\n      auto button = static_cast<AlbumThumbnailButton*>(CurrentlyFocusedElement);\n      if (button != nullptr && (button->Id / EntriesPerPage != CurrentPage)) {\n        Pages[CurrentPage]->Hide();\n        CurrentPage = button->Id / EntriesPerPage;\n        Pages[CurrentPage]->Show();\n        button->HasFocus = true;\n        CurrentlyFocusedElement = button;\n      }\n      Pages[CurrentPage]->Update(dt);\n    }\n  }\n}\n\nvoid AlbumMenu::UpdatePages() {\n  int totalVariations = 0;\n  int viewedVariations = 0;\n  int lastNonEmptyPage = 0;\n  const int32_t albumPages = AlbumPages + GetFlag(SF_CONGRATULATED);\n  for (int i = 0; i < albumPages * EntriesPerPage; i++) {\n    SaveSystem::GetEVStatus(i, &totalVariations, &viewedVariations);\n    const size_t currentPage = i / EntriesPerPage;\n    static_cast<AlbumThumbnailButton*>(\n        Pages[currentPage]->Children[i % EntriesPerPage])\n        ->UpdateVariations(totalVariations, viewedVariations);\n\n    if (viewedVariations > 0) {\n      lastNonEmptyPage = static_cast<int>(currentPage);\n    }\n  }\n  MaxReachablePage = lastNonEmptyPage + 1;\n\n  for (int i = 0; i < EntriesPerPage * MaxReachablePage; i++) {\n    int nextChild = ((i + 1) + ((i + 1) % 3 == 0) * 6) %\n                    (EntriesPerPage * MaxReachablePage);\n    Pages[i / EntriesPerPage]->Children[i % EntriesPerPage]->SetFocus(\n        Pages[nextChild / EntriesPerPage]->Children[nextChild % EntriesPerPage],\n        FDIR_RIGHT);\n\n    nextChild = (i - 1) - (i % 3 == 0) * 6;\n    nextChild = (nextChild < 0) * EntriesPerPage * MaxReachablePage + nextChild;\n    Pages[i / EntriesPerPage]->Children[i % EntriesPerPage]->SetFocus(\n        Pages[nextChild / EntriesPerPage]->Children[nextChild % EntriesPerPage],\n        FDIR_LEFT);\n\n    nextChild =\n        (i + 3) % EntriesPerPage + (i / EntriesPerPage) * EntriesPerPage;\n    Pages[i / EntriesPerPage]->Children[i % EntriesPerPage]->SetFocus(\n        Pages[i / EntriesPerPage]->Children[nextChild % EntriesPerPage],\n        FDIR_DOWN);\n\n    nextChild =\n        (i - 3 < EntriesPerPage * (i / EntriesPerPage)) * EntriesPerPage + i -\n        3;\n    Pages[i / EntriesPerPage]->Children[i % EntriesPerPage]->SetFocus(\n        Pages[i / EntriesPerPage]->Children[nextChild % EntriesPerPage],\n        FDIR_UP);\n  }\n}\n\ninline void AlbumMenu::DrawPage() {\n  glm::vec2 offset = MenuTransition.GetPageOffset();\n  Renderer->DrawSprite(PageCountLabel, PageLabelPosition + offset);\n  Renderer->DrawSprite(PageNums[CurrentPage + 1], CurrentPageNumPos + offset);\n  Renderer->DrawSprite(PageNumSeparatorSlash,\n                       PageNumSeparatorSlashPos + offset);\n  Renderer->DrawSprite(ReachablePageNums[MaxReachablePage],\n                       MaxPageNumPos + offset);\n\n  for (int i = 0; i < 3; i++) {\n    for (int j = 0; j < 3; j++) {\n      Renderer->DrawSprite(\n          CGBox, glm::vec2(CGBoxTemplatePosition.x + ThumbnailOffset.x * i,\n                           CGBoxTemplatePosition.y + ThumbnailOffset.y * j +\n                               offset.y));\n    }\n  }\n\n  Renderer->DrawSprite(CGList, CGListPosition + offset);\n  Pages[CurrentPage]->MoveTo(offset);\n  Pages[CurrentPage]->Render();\n  SelectAnimation.Draw(SelectDataSprites, SelectDataPos, offset);\n}\n\n}  // namespace CHLCC\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/chlcc/albummenu.h",
    "content": "#pragma once\n\n#include \"commonmenu.h\"\n#include \"../../ui/menu.h\"\n#include \"../../ui/widgets/group.h\"\n#include \"../../ui/widgets/button.h\"\n#include \"../../ui/widgets/cgviewer.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace CHLCC {\n\nclass AlbumMenu : public Menu, public CommonMenu {\n public:\n  AlbumMenu();\n\n  void Init() override;\n  void Show() override;\n  void Hide() override;\n  void UpdateInput(float dt) override;\n  void Update(float dt) override;\n  void Render() override;\n\n private:\n  std::vector<Widgets::Group*> Pages;\n\n  void DrawPage();\n\n  void UpdatePages();\n\n  void CgOnClick(Widgets::Button* target);\n  void OnCgVariationEnd(Widgets::CgViewer* target);\n\n  Widgets::Group* CgViewerGroup;\n  Widgets::CgViewer* CgViewerWidget;\n  Animation ButtonGuideFade = Animation();\n\n  int PrevPage = 0;\n  int CurrentPage;\n  int MaxReachablePage;\n\n  bool ShowCgViewer = false;\n};\n\n}  // namespace CHLCC\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/chlcc/animations/menutransition.cpp",
    "content": "#include \"menutransition.h\"\n\n#include \"../../../profile/game.h\"\n#include \"../../../profile/games/chlcc/commonmenu.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace CHLCC {\n\nusing namespace Impacto::Profile;\nusing namespace Impacto::Profile::CHLCC::CommonMenu;\n\nMenuTransitionAnimation::MenuTransitionAnimation() {\n  SetDuration(MenuTransitionDuration);\n}\n\nglm::vec2 MenuTransitionAnimation::GetPageOffset() const {\n  glm::vec2 position = {0.0f, 0.0f};\n\n  if (IsIn()) return position;\n\n  const float startProgress =\n      ShowPageAnimationStartTime / MenuTransitionDuration;\n  const float endProgress =\n      startProgress + ShowPageAnimationDuration / MenuTransitionDuration;\n\n  if (startProgress < Progress && Progress < endProgress) {\n    const float progress =\n        (Progress - startProgress) / (endProgress - startProgress);\n    const float angle = progress * std::numbers::pi_v<float> * 0.5f;\n\n    position = {0.0f, (std::sin(angle) - 1.0f) * DesignHeight};\n\n  } else if (Progress <= startProgress) {\n    position = {0.0f, -DesignHeight};\n  }\n  return position;\n}\n\n}  // namespace CHLCC\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/chlcc/animations/menutransition.h",
    "content": "#pragma once\n\n#include \"../../../animation.h\"\n#include \"../../../renderer/renderer.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace CHLCC {\n\nclass MenuTransitionAnimation : public Animation {\n public:\n  MenuTransitionAnimation();\n  glm::vec2 GetPageOffset() const;\n};\n}  // namespace CHLCC\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/chlcc/animations/saveicon.cpp",
    "content": "#include \"saveicon.h\"\n#include \"../../../profile/hud/saveicon.h\"\n#include \"../../../renderer/renderer.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace CHLCC {\n\nSaveIconAnimation::SaveIconAnimation(\n    float activeAnimationDuration, float fadeInDuration, float fadeOutDuration,\n    std::span<const Sprite, Profile::SaveIcon::CHLCC_SAVE_ICON_SPRITES>\n        sprites) {\n  std::ranges::copy(sprites, Sprites.begin());\n  ActiveAnimation.SetDuration(activeAnimationDuration);\n  ActiveAnimation.LoopMode = AnimationLoopMode::Loop;\n\n  FadeAnimation.DurationIn = fadeInDuration;\n  FadeAnimation.DurationOut = fadeOutDuration;\n}\nvoid SaveIconAnimation::Update(float dt) {\n  if (FadeAnimation.IsIn() && !ActiveAnimation.IsPlaying()) {\n    ActiveAnimation.StartIn(true);\n  }\n\n  FadeAnimation.Update(dt);\n  ActiveAnimation.Update(dt);\n}\n\nvoid SaveIconAnimation::Render(glm::vec2 position) const {\n  if (FadeAnimation.IsOut()) return;\n\n  glm::vec4 col(1.0f);\n  col.a = FadeAnimation.Progress;\n  Renderer->DrawSprite(CurrentSprite(), position, col);\n}\n\nSprite SaveIconAnimation::CurrentSprite() const {\n  if (FadeAnimation.IsIn()) {\n    // alter between two sprites\n    return ActiveAnimation.Progress < 0.5 ? Sprites[0] : Sprites[1];\n  }\n  return Sprites[2];\n}\n\nvoid SaveIconAnimation::Show() {\n  FadeAnimation.StartIn();\n  ActiveAnimation.Stop();\n}\n\nvoid SaveIconAnimation::Hide() {\n  FadeAnimation.StartOut();\n  ActiveAnimation.Stop();\n}\n}  // namespace CHLCC\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/chlcc/animations/saveicon.h",
    "content": "#pragma once\n\n#include \"../../../animation.h\"\n#include \"../../../spritesheet.h\"\n#include \"../../../profile/hud/saveicon.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace CHLCC {\n\nclass SaveIconAnimation {\n  Animation ActiveAnimation;\n  Animation FadeAnimation;\n  std::array<Sprite, Profile::SaveIcon::CHLCC_SAVE_ICON_SPRITES> Sprites = {};\n\n public:\n  SaveIconAnimation(\n      float activeAnimationDuration, float fadeInDuration,\n      float fadeOutDuration,\n      std::span<const Sprite, Profile::SaveIcon::CHLCC_SAVE_ICON_SPRITES>\n          sprites);\n  void Update(float dt);\n  void Render(glm::vec2 position) const;\n  Sprite CurrentSprite() const;\n\n  void Show();\n  void Hide();\n};\n}  // namespace CHLCC\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/chlcc/animations/selectprompt.cpp",
    "content": "#include \"selectprompt.h\"\n#include \"../../../profile/games/chlcc/commonmenu.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace CHLCC {\n\nusing namespace Impacto::Profile::CHLCC::CommonMenu;\n\nSelectPromptAnimation::SelectPromptAnimation() {\n  Direction = AnimationDirection::In;\n  LoopMode = AnimationLoopMode::Loop;\n  DurationIn = MenuSelectPromptDuration;\n  DurationOut = 0.0f;\n}\n\nvoid SelectPromptAnimation::Draw(const std::span<const Sprite> sprites,\n                                 const std::span<const glm::vec2> positions,\n                                 const glm::vec2 offset) {\n  const auto currentLetter = static_cast<size_t>(\n      Progress * MenuSelectPromptDuration / MenuSelectPromptInterval);\n  for (size_t i = 0; i < sprites.size(); i++) {\n    if (currentLetter < i) break;\n    const glm::vec2 pos = positions[i];\n    const float alpha = i == currentLetter\n                            ? (Progress * MenuSelectPromptDuration -\n                               i * MenuSelectPromptInterval) /\n                                  MenuSelectPromptInterval\n                            : 1.0f;\n    Renderer->DrawSprite(sprites[i], pos + offset,\n                         glm::vec4(1.0f, 1.0f, 1.0f, alpha));\n  }\n}\n\n}  // namespace CHLCC\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/chlcc/animations/selectprompt.h",
    "content": "#pragma once\n\n#include <vector>\n\n#include \"../../../animation.h\"\n#include \"../../../spritesheet.h\"\n#include \"../../../renderer/renderer.h\"\n#include \"../../../profile/ui/gamespecific.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace CHLCC {\nusing namespace Impacto::Profile::GameSpecific;\n\nclass SelectPromptAnimation : public Animation {\n public:\n  SelectPromptAnimation();\n  void Draw(std::span<const Sprite> sprites,\n            std::span<const glm::vec2> positions, glm::vec2 offset);\n};\n\n}  // namespace CHLCC\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/chlcc/backlogmenu.cpp",
    "content": "#include \"backlogmenu.h\"\n\n#include \"../../profile/games/chlcc/backlogmenu.h\"\n#include \"../../profile/games/chlcc/commonmenu.h\"\n#include \"../../ui/widgets/chlcc/backlogentry.h\"\n#include \"../../profile/scriptvars.h\"\n#include \"../../profile/profile_internal.h\"\n#include \"../../renderer/renderer.h\"\n#include \"../../ui/ui.h\"\n#include \"../../audio/audiosystem.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace CHLCC {\n\nusing namespace Impacto::Profile::CHLCC::BacklogMenu;\nusing namespace Impacto::Profile::CHLCC::CommonMenu;\nusing namespace Impacto::UI::Widgets::CHLCC;\nusing namespace Impacto::Profile::ScriptVars;\n\nBacklogMenu::BacklogMenu() : CommonMenu(true) {}\n\nvoid BacklogMenu::Show() {\n  if (State != Shown) {\n    if (State != Showing) {\n      MenuTransition.StartIn();\n      FromSystemMenuTransition->StartIn();\n    }\n    IsFocused = true;\n    UI::BacklogMenu::Show();\n  }\n}\n\nvoid BacklogMenu::Hide() {\n  if (State != Hidden) {\n    if (State != Hiding) {\n      MenuTransition.StartOut();\n      FromSystemMenuTransition->StartOut();\n    }\n    State = Hiding;\n    if (LastFocusedMenu != 0) {\n      UI::FocusedMenu = LastFocusedMenu;\n      LastFocusedMenu->IsFocused = true;\n    } else {\n      UI::FocusedMenu = 0;\n    }\n    IsFocused = false;\n    Audio::Channels[Audio::AC_REV]->Stop(0.0f);\n  }\n}\n\nvoid BacklogMenu::Render() {\n  if (State == Hidden) return;\n  CommonMenu::DrawSubmenu(BackgroundColor, CircleSprite, MenuTitleText,\n                          MenuTitleTextAngle);\n\n  if (MenuTransition.Progress < 0.22f) return;\n\n  float yOffset = 0;\n\n  yOffset = MenuTransition.GetPageOffset().y;\n  Renderer->DrawSprite(BacklogBackgroundSprite, {0.0f, 0.0f + yOffset});\n  CommonMenu::DrawButtonPrompt(ButtonPromptSprite, ButtonPromptPosition);\n\n  MainItems->RenderingBounds.Y += yOffset;\n  MainItems->Move({0.0f, yOffset});\n  MainItems->Render();\n  MainItems->Move({0.0f, -yOffset});\n  MainItems->RenderingBounds.Y -= yOffset;\n\n  MainScrollbar->Move({0.0f, yOffset});\n  MainScrollbar->Render();\n  MainScrollbar->Move({0.0f, -yOffset});\n  if (MenuTransition.Progress > 0.34f) {\n    Renderer->EnableScissor();\n    Renderer->SetScissorRect(BacklogBackgroundSprite.Bounds);\n    Renderer->DrawSprite(MenuTitleText, LeftTitlePos);\n    Renderer->DisableScissor();\n    RenderHighlight({0.0f, yOffset});\n  }\n  MainItems->RenderingBounds.Y += yOffset;\n  MainItems->Move({0.0f, yOffset});\n  MainItems->Render();\n  MainItems->Move({0.0f, -yOffset});\n  MainItems->RenderingBounds.Y -= yOffset;\n\n  MainScrollbar->Move({0.0f, yOffset});\n  MainScrollbar->Render();\n  MainScrollbar->Move({0.0f, -yOffset});\n}\n\nvoid BacklogMenu::Update(float dt) {\n  const int sysMenuCt = ScrWork[SW_SYSMENUCT];\n  const int systemMenuCHG = ScrWork[SW_SYSTEMMENUCHG];\n\n  if ((!GetFlag(SF_BACKLOGMENU) || sysMenuCt < 10000 ||\n       (sysMenuCt == 10000 && systemMenuCHG != 0 && systemMenuCHG != 64)) &&\n      State == Shown) {\n    Hide();\n  } else if (GetFlag(SF_BACKLOGMENU) && sysMenuCt > 0 && State == Hidden) {\n    Show();\n  }\n\n  if (MenuTransition.IsOut() && !GetFlag(SF_BACKLOGMENU) &&\n      systemMenuCHG == 0 && (sysMenuCt == 0 || GetFlag(SF_SYSTEMMENU)) &&\n      State == Hiding) {\n    State = Hidden;\n    MainItems->Hide();\n  } else if (MenuTransition.IsIn() && sysMenuCt == 10000 &&\n             (systemMenuCHG == 0 || systemMenuCHG == 64) &&\n             GetFlag(SF_BACKLOGMENU) && State == Showing) {\n    State = Shown;\n    MainItems->HasFocus = true;\n  }\n\n  if (State != Hidden) {\n    UI::BacklogMenu::Update(dt);\n    MenuTransition.Update(dt);\n    FromSystemMenuTransition->Update(dt);\n    if (MenuTransition.Direction == AnimationDirection::Out &&\n        MenuTransition.Progress <= 0.72f) {\n      TitleFade.StartOut();\n    } else if (MenuTransition.IsIn() &&\n               (TitleFade.Direction == AnimationDirection::In ||\n                TitleFade.IsOut())) {\n      TitleFade.StartIn();\n    }\n    TitleFade.Update(dt);\n    UpdateTitles(MenuTitleTextRightPosition, MenuTitleTextLeftPosition, false);\n  }\n}\n}  // namespace CHLCC\n}  // namespace UI\n}  // namespace Impacto\n"
  },
  {
    "path": "src/games/chlcc/backlogmenu.h",
    "content": "#pragma once\n\n#include \"commonmenu.h\"\n#include \"../../ui/backlogmenu.h\"\n#include \"../../ui/widgets/chlcc/backlogentry.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace CHLCC {\n\nclass BacklogMenu : public UI::BacklogMenu, public CommonMenu {\n public:\n  BacklogMenu();\n\n  void Show() override;\n  void Hide() override;\n  void Update(float dt) override;\n  void Render() override;\n\n  Widgets::BacklogEntry* CreateBacklogEntry(\n      int id, Vm::BufferOffsetContext scrCtx, int audioId, int characterId,\n      glm::vec2 pos, const RectF& hoverBounds) const override {\n    return new Widgets::CHLCC::BacklogEntry(id, scrCtx, audioId, characterId,\n                                            pos, hoverBounds);\n  }\n};\n\n}  // namespace CHLCC\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/chlcc/clearlistmenu.cpp",
    "content": "#include \"clearlistmenu.h\"\n\n#include \"../../profile/games/chlcc/clearlistmenu.h\"\n#include \"../../profile/scriptvars.h\"\n#include \"../../profile/profile_internal.h\"\n#include \"../../renderer/renderer.h\"\n#include \"../../ui/ui.h\"\n#include \"../../data/savesystem.h\"\n#include \"../../data/tipssystem.h\"\n#include \"../../profile/games/chlcc/commonmenu.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace CHLCC {\n\nusing namespace Impacto::Profile::CHLCC::CommonMenu;\nusing namespace Impacto::Profile::CHLCC::ClearListMenu;\nusing namespace Impacto::Profile::ScriptVars;\nusing namespace Impacto::Profile::GameSpecific;\nusing namespace Impacto::UI::Widgets;\nusing namespace Impacto::TipsSystem;\nusing namespace Impacto::Profile;\n\nClearListMenu::ClearListMenu() : CommonMenu(false) {}\n\nvoid ClearListMenu::Show() {\n  if (State != Shown) {\n    if (State != Showing) {\n      MenuTransition.StartIn();\n    }\n    State = Showing;\n    if (UI::FocusedMenu != 0) {\n      LastFocusedMenu = UI::FocusedMenu;\n      LastFocusedMenu->IsFocused = false;\n    }\n    IsFocused = true;\n    UI::FocusedMenu = this;\n  }\n}\n\nvoid ClearListMenu::Hide() {\n  if (State != Hidden) {\n    if (State != Hiding) {\n      MenuTransition.StartOut();\n    }\n    State = Hiding;\n    if (LastFocusedMenu != 0) {\n      UI::FocusedMenu = LastFocusedMenu;\n      LastFocusedMenu->IsFocused = true;\n    } else {\n      UI::FocusedMenu = 0;\n    }\n    IsFocused = false;\n  }\n}\n\nvoid ClearListMenu::Render() {\n  if (State == Hidden) return;\n  CommonMenu::DrawSubmenu(BackgroundColor, CircleSprite, MenuTitleText,\n                          MenuTitleTextAngle, true);\n\n  if (MenuTransition.Progress < 0.22f) return;\n  float yOffset = MenuTransition.GetPageOffset().y;\n\n  Renderer->DrawSprite(ClearListLabel,\n                       glm::vec2(LabelPosition.x, LabelPosition.y + yOffset));\n  DrawPlayTime(yOffset);\n  DrawEndingCount(yOffset);\n  DrawTIPSCount(yOffset);\n  DrawAlbumCompletion(yOffset);\n  DrawEndingTree(yOffset);\n  CommonMenu::DrawButtonPrompt(ButtonPromptSprite, ButtonPromptPosition);\n}\n\nvoid ClearListMenu::Update(float dt) {\n  if ((!GetFlag(SF_CLEARLISTMENU) || ScrWork[SW_SYSMENUCT] < 10000) &&\n      State == Shown) {\n    Hide();\n  } else if (GetFlag(SF_CLEARLISTMENU) && ScrWork[SW_SYSMENUCT] > 0 &&\n             State == Hidden) {\n    Show();\n  }\n\n  if (MenuTransition.IsOut() &&\n      (ScrWork[SW_SYSMENUCT] == 0 || GetFlag(SF_SYSTEMMENU)) &&\n      State == Hiding) {\n    State = Hidden;\n  } else if (MenuTransition.IsIn() && ScrWork[SW_SYSMENUCT] == 10000 &&\n             State == Showing) {\n    State = Shown;\n  }\n\n  if (State != Hidden) {\n    MenuTransition.Update(dt);\n    if (MenuTransition.Direction == AnimationDirection::Out &&\n        MenuTransition.Progress <= 0.72f) {\n      TitleFade.StartOut();\n    } else if (MenuTransition.IsIn() &&\n               (TitleFade.Direction == AnimationDirection::In ||\n                TitleFade.IsOut())) {\n      TitleFade.StartIn();\n    }\n    TitleFade.Update(dt);\n    UpdateTitles(MenuTitleTextRightPosition, MenuTitleTextLeftPosition);\n  }\n}\n\ninline void ClearListMenu::DrawPlayTime(float yOffset) {\n  int totalSeconds = ScrWork[SW_TOTALPLAYTIME];\n  int hours = totalSeconds / 3600;\n  int minutes = (totalSeconds % 3600) / 60;\n  int seconds = (totalSeconds % 3600) % 60;\n\n  if (hours > 99) {\n    hours = 99;\n    minutes = 59;\n    seconds = 59;\n  }\n  int time[6] = {hours / 10,   hours % 10,   minutes / 10,\n                 minutes % 10, seconds / 10, seconds % 10};\n\n  for (int idx = 0; idx < 6; idx++) {\n    if (!(idx == 0 && time[idx] == 0)) {\n      glm::vec2 position(TimePositions[idx].x, TimePositions[idx].y + yOffset);\n      Renderer->DrawSprite(Digits[time[idx]], position);\n    }\n  }\n}\n\ninline void ClearListMenu::DrawEndingCount(float yOffset) {\n  int unlockedEndingCount = 0;\n  for (int i = 0; i < Endings; i++) {\n    unlockedEndingCount += GetFlag(SF_CLR_END1 + i);\n  }\n  glm::vec2 position(EndingCountPosition.x, EndingCountPosition.y + yOffset);\n  Renderer->DrawSprite(Digits[unlockedEndingCount], position);\n}\n\ninline void ClearListMenu::DrawTIPSCount(float yOffset) {\n  int unlockedTipsCount = 0;\n  size_t totalTips = GetTipCount();\n\n  for (size_t idx = 0; idx < totalTips; idx++) {\n    unlockedTipsCount += GetTipLockedState(idx) ? 0 : 1;\n  }\n\n  const int hundreds = unlockedTipsCount / 100;\n  const int tens = (unlockedTipsCount / 10) % 10;\n  const int ones = unlockedTipsCount % 10;\n\n  if (hundreds > 0) {\n    Renderer->DrawSprite(\n        Digits[hundreds],\n        glm::vec2(TIPSCountPositions[0].x, TIPSCountPositions[0].y + yOffset));\n  }\n  if (hundreds > 0 || tens > 0) {\n    Renderer->DrawSprite(\n        Digits[tens],\n        glm::vec2(TIPSCountPositions[1].x, TIPSCountPositions[1].y + yOffset));\n  }\n  Renderer->DrawSprite(\n      Digits[ones],\n      glm::vec2(TIPSCountPositions[2].x, TIPSCountPositions[2].y + yOffset));\n}\n\ninline void ClearListMenu::DrawAlbumCompletion(float yOffset) {\n  int totalCount = 0, unlockedCount = 0;\n  SaveSystem::GetViewedEVsCount(&totalCount, &unlockedCount);\n  // The 9 bonus CGs after 100% completion don't count\n  totalCount -= 9;\n  unlockedCount = unlockedCount <= totalCount ? unlockedCount : totalCount;\n  int percentage = unlockedCount * 100 / totalCount;\n  if (percentage == 0 && (unlockedCount) != 0) {\n    percentage = 1;\n  }\n  if (percentage / 100 != 0) {\n    Renderer->DrawSprite(\n        Digits[percentage / 100],\n        glm::vec2(AlbumPositions[0].x, AlbumPositions[0].y + yOffset));\n    Renderer->DrawSprite(\n        Digits[(percentage / 10) % 10],\n        glm::vec2(AlbumPositions[1].x, AlbumPositions[1].y + yOffset));\n  } else if (percentage / 10 != 0) {\n    Renderer->DrawSprite(\n        Digits[(percentage / 10) % 10],\n        glm::vec2(AlbumPositions[1].x, AlbumPositions[1].y + yOffset));\n  }\n  Renderer->DrawSprite(\n      Digits[percentage % 10],\n      glm::vec2(AlbumPositions[2].x, AlbumPositions[2].y + yOffset));\n}\n\ninline void ClearListMenu::DrawEndingTree(float yOffset) {\n  for (int i = 0; i < Endings; i++) {\n    glm::vec2 boxPosition(BoxPositions[i].x, BoxPositions[i].y + yOffset);\n    glm::vec2 thumbnailPosition(ThumbnailPositions[i].x,\n                                ThumbnailPositions[i].y + yOffset);\n    Renderer->DrawSprite(EndingBox, boxPosition);\n    // Flag for the 1st ending, they are contiguous\n    if (GetFlag(SF_CLR_END1 + i)) {\n      Renderer->DrawSprite(EndingThumbnails[i], thumbnailPosition);\n    } else {\n      Renderer->DrawSprite(LockedThumbnail, thumbnailPosition);\n    }\n  }\n  glm::vec2 listPosition(ListPosition.x, ListPosition.y + yOffset);\n  Renderer->DrawSprite(EndingList, listPosition);\n}\n\n}  // namespace CHLCC\n}  // namespace UI\n}  // namespace Impacto\n"
  },
  {
    "path": "src/games/chlcc/clearlistmenu.h",
    "content": "#pragma once\n\n#include \"commonmenu.h\"\n#include \"../../ui/menu.h\"\n#include \"../../profile/games/chlcc/clearlistmenu.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace CHLCC {\n\nclass ClearListMenu : public Menu, public CommonMenu {\n public:\n  ClearListMenu();\n\n  void Show() override;\n  void Hide() override;\n  void Update(float dt) override;\n  void Render() override;\n\n private:\n  void DrawPlayTime(float yOffset);\n  void DrawEndingCount(float yOffset);\n  void DrawTIPSCount(float yOffset);\n  void DrawAlbumCompletion(float yOffset);\n  void DrawEndingTree(float yOffset);\n};\n\n}  // namespace CHLCC\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/chlcc/commonmenu.cpp",
    "content": "#include \"commonmenu.h\"\n\n#include \"../../renderer/renderer.h\"\n#include \"../../profile/games/chlcc/commonmenu.h\"\n#include \"../../vm/interface/input.h\"\n#include \"../../profile/scriptvars.h\"\n#include \"../../profile/game.h\"\n#include \"../../background2d.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace CHLCC {\n\nusing namespace Impacto::Profile::CHLCC::CommonMenu;\nusing namespace Impacto::Profile::ScriptVars;\nusing namespace Impacto::Vm::Interface;\n\nCommonMenu::CommonMenu(bool canGoFromSysMenu) {\n  TitleFade.Direction = AnimationDirection::In;\n  TitleFade.LoopMode = AnimationLoopMode::Stop;\n  TitleFade.DurationIn = TitleFadeInDuration;\n  TitleFade.DurationOut = TitleFadeOutDuration;\n\n  // FromSystemMenuTransition if nullopt if canGoFromSysMenu == false\n  if (canGoFromSysMenu) {\n    FromSystemMenuTransition = Animation();\n    FromSystemMenuTransition->Direction = AnimationDirection::In;\n    FromSystemMenuTransition->LoopMode = AnimationLoopMode::Stop;\n    FromSystemMenuTransition->DurationIn = TitleFadeInDuration;\n    FromSystemMenuTransition->DurationOut = TitleFadeOutDuration;\n  }\n\n  RedBarSprite = InitialRedBarSprite;\n  RedBarPosition = InitialRedBarPosition;\n}\n\nvoid CommonMenu::DrawSubmenu(uint32_t backgroundColor,\n                             const Sprite& currentCircleSprite,\n                             const Sprite& menuTitleSprite,\n                             float menuTitleAngle, bool drawLeftTitle) {\n  const bool fullDraw = !GetFlag(SF_SYSTEMMENU);\n  // Those elements should not be drawn when sub menu is open from sys menu, sys\n  // menu will draw them\n  if (fullDraw) {\n    if (MenuTransition.IsIn()) {\n      Renderer->DrawQuad(\n          RectF(0.0f, 0.0f, Profile::DesignWidth, Profile::DesignHeight),\n          RgbIntToFloat(backgroundColor));\n    } else {\n      DrawCircles(currentCircleSprite);\n    }\n\n    DrawErin();\n    DrawRedBar();\n  }\n\n  DrawRightTitle(menuTitleSprite, menuTitleAngle);\n  if (fullDraw) {\n    DrawBackgroundFilter();\n  }\n  // For menus with no left title or the when title is drawn over something else\n  if (drawLeftTitle) {\n    DrawLeftTitle(menuTitleSprite);\n  }\n}\n\nvoid CommonMenu::DrawSysMenu(glm::vec4 backgroundColor,\n                             const Sprite& currentCircleSprite,\n                             const Sprite& menuTitleSprite,\n                             float menuTitleAngle) {\n  if (MenuTransition.IsIn()) {\n    Renderer->DrawQuad(\n        RectF(0.0f, 0.0f, Profile::DesignWidth, Profile::DesignHeight),\n        backgroundColor);\n  } else {\n    DrawCircles(currentCircleSprite);\n  }\n\n  DrawErin();\n  DrawRedBar();\n\n  DrawRightTitle(menuTitleSprite, menuTitleAngle);\n  DrawBackgroundFilter();\n}\n\nvoid CommonMenu::DrawCircles(const Sprite& currentCircleSprite) const {\n  float y = CircleStartPosition.y;\n  int resetCounter = 0;\n  // Give the whole range that mimics ScrWork[SW_SYSMENUCT] given that the\n  // duration is totalframes/60\n  float progress = MenuTransition.Progress * MenuTransitionDuration * 60.0f;\n  const float scaleMultiplier = currentCircleSprite.ScaledHeight() / 106.0f;\n  for (int line = 0; line < 4; line++) {\n    int counter = resetCounter;\n    float x = CircleStartPosition.x;\n    for (int col = 0; col < 7; col++) {\n      if (counter + 1 <= progress) {\n        float scale = (progress - (counter + 1.0f)) * 16.0f;\n        scale = std::min(scale, 320.0f) * scaleMultiplier;\n        Renderer->DrawSprite(\n            currentCircleSprite,\n            RectF(x + (currentCircleSprite.ScaledWidth() - scale) / 2.0f,\n                  y + (currentCircleSprite.ScaledHeight() - scale) / 2.0f,\n                  scale, scale));\n        x += CircleOffset;\n      }\n      counter += 2;\n    }\n    y += CircleOffset;\n    resetCounter += 2;\n  }\n}\n\nvoid CommonMenu::DrawRedBar() const {\n  if (MenuTransition.IsIn()) {\n    Renderer->DrawSprite(InitialRedBarSprite, InitialRedBarPosition);\n  } else if (MenuTransition.Progress > 0.70f) {\n    // Give the whole range that mimics ScrWork[SW_SYSMENUCT] given that the\n    // duration is totalframes/60\n    const float progress =\n        MenuTransition.Progress * MenuTransitionDuration * 60.0f;\n    const float pixelPerAdvanceLeft = RedBarBaseX * (progress - 47.0f) / 17.0f;\n    RedBarSprite.Bounds.X = RedBarDivision - pixelPerAdvanceLeft;\n    RedBarSprite.Bounds.Width = pixelPerAdvanceLeft;\n    RedBarPosition.x = RedBarBaseX - pixelPerAdvanceLeft;\n    Renderer->DrawSprite(RedBarSprite, RedBarPosition);\n    const float pixelPerAdvanceRight = 13.0f * (progress - 47.0f);\n    RedBarSprite.Bounds.X = RedBarDivision;\n    RedBarSprite.Bounds.Width = pixelPerAdvanceRight;\n    RedBarPosition = RightRedBarPosition;\n    Renderer->DrawSprite(RedBarSprite, RedBarPosition);\n  }\n}\n\nvoid CommonMenu::DrawButtonPrompt(const Sprite& buttonPromptSprite,\n                                  glm::vec2 buttonPromptPos,\n                                  std::optional<Animation> animation) {\n  const Animation promptAnimation = animation.value_or(MenuTransition);\n  const float startProgress =\n      ButtonPromptAnimationStartTime / MenuTransitionDuration;\n  const float progressDuration =\n      ButtonPromptAnimationDuration / MenuTransitionDuration;\n  const float endProgress = startProgress + progressDuration;\n  if (promptAnimation.Progress >= endProgress) {\n    Renderer->DrawSprite(buttonPromptSprite, buttonPromptPos);\n  } else if (promptAnimation.Progress >= startProgress) {\n    const float progress =\n        (promptAnimation.Progress - startProgress) / progressDuration;\n    Renderer->DrawSprite(\n        buttonPromptSprite,\n        glm::mix(ButtonPromptStartPosition, buttonPromptPos, progress));\n  }\n}\n\nvoid CommonMenu::DrawErin() const {\n  float y = ErinPosition.y;\n  if (MenuTransition.Progress < 0.78f) {\n    y = ErinSprite.Bounds.Height;\n    if (MenuTransition.Progress > 0.22f) {\n      // Approximation from the original function, which was a bigger mess\n      y = glm::mix(\n          -19.0f, Profile::DesignHeight + ErinPosition.y,\n          0.998938f -\n              0.998267f * sin(3.97835f - 3.27549f * MenuTransition.Progress));\n    }\n  }\n  Renderer->DrawSprite(ErinSprite, glm::vec2(ErinPosition.x, y));\n}\n\nvoid CommonMenu::DrawRightTitle(const Sprite& titleSprite,\n                                float titleAngle) const {\n  if (MenuTransition.Progress > 0.34f) {\n    Renderer->DrawSprite(RedBarLabel, RedTitleLabelPos);\n\n    const CornersQuad titleDest = titleSprite.ScaledBounds()\n                                      .RotateAroundCenter(titleAngle)\n                                      .Translate(RightTitlePos);\n    Renderer->DrawSprite(titleSprite, titleDest);\n  }\n}\n\nvoid CommonMenu::DrawLeftTitle(const Sprite& titleSprite,\n                               glm::vec4 tint) const {\n  if (MenuTransition.Progress > 0.34f) {\n    Renderer->DrawSprite(titleSprite, LeftTitlePos, tint);\n  }\n}\n\nvoid CommonMenu::UpdateTitles(glm::vec2 rightTitlepos, glm::vec2 leftTitlePos,\n                              bool vertical) {\n  if (MenuTransition.Progress <= 0.34f) return;\n\n  RedTitleLabelPos = RedBarLabelPosition;\n  RightTitlePos = rightTitlepos;\n  LeftTitlePos = leftTitlePos;\n\n  if (!TitleFade.IsIn()) {\n    if (vertical) {\n      const float delta =\n          glm::mix(leftTitlePos.y, Profile::DesignHeight + leftTitlePos.y,\n                   1.01011f * std::sin(1.62223f * TitleFade.Progress + 3.152f) +\n                       1.01012f);\n      LeftTitlePos.y += delta;\n    } else {\n      const float delta = glm::mix(\n          Profile::DesignWidth, 0.0f,\n          std::sin(TitleFade.Progress * std::numbers::pi_v<float> * 0.5f));\n      LeftTitlePos.x += delta;\n    }\n  }\n\n  if (MenuTransition.Progress >= 0.73f) return;\n\n  const auto delta = glm::mix(DiagonalTitlesOffsetStart,\n                              DiagonalTitlesOffsetEnd, MenuTransition.Progress);\n  // RedTitleLabelPos should not update when submenu is open from sys menu\n  if (!GetFlag(SF_SYSTEMMENU)) {\n    RedTitleLabelPos += delta;\n  }\n  RightTitlePos += delta;\n}\n\nvoid CommonMenu::DrawBackgroundFilter() {\n  Renderer->CaptureScreencap(ShaderScreencapture.BgSprite);\n  Renderer->DrawCHLCCMenuBackground(\n      ShaderScreencapture.BgSprite, BackgroundFilter,\n      RectF(0.0f, 0.0f, Profile::DesignWidth, Profile::DesignHeight),\n      std::clamp(MenuTransition.Progress * 2.0f, 0.0f, 1.0f));\n}\n\n}  // namespace CHLCC\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/chlcc/commonmenu.h",
    "content": "#pragma once\n\n#include <vector>\n#include \"../../impacto.h\"\n#include \"../../animation.h\"\n#include \"../../spritesheet.h\"\n\n#include \"animations/selectprompt.h\"\n#include \"animations/menutransition.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace CHLCC {\n\nclass CommonMenu {\n public:\n  CommonMenu(bool canGoFromSysMenu);\n\n protected:\n  void DrawSubmenu(uint32_t backgroundColor, const Sprite& currentCircleSprite,\n                   const Sprite& menuTitleSprite, float menuTitleAngle,\n                   bool drawLeftTitle = false);\n  void DrawSysMenu(glm::vec4 backgroundColor, const Sprite& currentCircleSprite,\n                   const Sprite& menuTitleSprite, float menuTitleAngle);\n  void DrawCircles(const Sprite& currentCircleSprite) const;\n  void DrawErin() const;\n  void DrawRedBar() const;\n  void DrawRightTitle(const Sprite& titleSprite, float titleAngle) const;\n  void DrawLeftTitle(const Sprite& titleSprite,\n                     glm::vec4 tint = glm::vec4(1.0f)) const;\n  void DrawBackgroundFilter();\n\n  virtual void DrawButtonPrompt(\n      const Sprite& buttonPromptSprite, glm::vec2 buttonPromptPos,\n      std::optional<Animation> animation = std::nullopt);\n  virtual void UpdateTitles(glm::vec2 rightTitlepos, glm::vec2 leftTitlePos,\n                            bool vertical = true);\n\n  Animation TitleFade;\n  std::optional<Animation> FromSystemMenuTransition = std::nullopt;\n  SelectPromptAnimation SelectAnimation;\n  MenuTransitionAnimation MenuTransition;\n\n  glm::vec2 RedTitleLabelPos;\n  glm::vec2 RightTitlePos;\n  glm::vec2 LeftTitlePos;\n};\n\n}  // namespace CHLCC\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/chlcc/delusiontrigger.cpp",
    "content": "#include \"delusiontrigger.h\"\n#include \"../../renderer/renderer.h\"\n#include \"../../vm/interface/input.h\"\n#include \"../../mem.h\"\n#include \"../../profile/games/chlcc/delusiontrigger.h\"\n#include \"../../profile/scriptvars.h\"\n#include \"../../profile/configsystem.h\"\n#include \"../../profile/vm.h\"\n#include \"../../profile/game.h\"\n#include \"../../audio/audiosystem.h\"\n#include \"../../text/dialoguepage.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace CHLCC {\n\nusing namespace Impacto::Profile::CHLCC::DelusionTrigger;\nusing namespace Impacto::Profile::ScriptVars;\nusing namespace Impacto::Vm::Interface;\n\nusing enum MenuState;\n\nDelusionTrigger::DelusionTrigger()\n    : DelusionState(ScrWork[SW_DELUSION_STATE]) {}\n\nvoid DelusionTrigger::Show() {\n  if (Profile::ConfigSystem::TriggerStopSkip) SkipModeEnabled = false;\n  if (State != Shown && State != Showing) {\n    State = Showing;\n    DelusionState = DS_Neutral;\n    MaskScaleFactor = 131072;\n    SpinAngle = 0;\n    SpinRate = 3072;\n    UnderlayerAlpha = 0;\n    BackgroundAlpha = 0;\n    AnimationState = 0;\n    AnimCounter = 0;\n    TriggerOnTintAlpha = 0;\n    UnderlayerXOffset = 20000;\n    UnderlayerXRate = 400;\n    ShakeState = 0;\n    MaskOffsetX = 0;\n    SetFlag(SF_DELUSION_UI_ANIM_WAIT, 1);\n    SetFlag(SF_DELUSION_UI_ANIMSWITCH_WAIT, 0);\n  }\n}\nvoid DelusionTrigger::Hide() {\n  if (State != Hidden && State != Hiding) {\n    State = Hiding;\n    AnimationState = 0;\n    SetFlag(SF_DELUSION_UI_ANIM_WAIT, 1);\n    SetFlag(SF_DELUSION_UI_ANIMSWITCH_WAIT, 0);\n    TextSystem.DelusionTextFade.StartOut();\n  }\n}\n\nvoid DelusionTrigger::UpdateHiding(float dt) {\n  auto setHidden = [this] {\n    AnimCounter = 0;\n    AnimationState = 0;\n    SetFlag(SF_DELUSION_UI_ANIM_WAIT, 0);\n    State = Hidden;\n    Reset();\n  };\n  if (AnimationState < 13) {\n    AnimCounter++;\n    switch (AnimationState) {\n      case 0:\n      case 4: {\n        MaskScaleFactor -= 1760;\n        if (AnimCounter == 5) {\n          AnimationState++;\n          AnimCounter = 0;\n        }\n      } break;\n      default: {\n        if (AnimCounter == 5) {\n          AnimationState++;\n          AnimCounter = 0;\n        }\n      } break;\n      case 2:\n      case 6: {\n        MaskScaleFactor += 1760;\n        if (AnimCounter == 5) {\n          AnimationState++;\n          AnimCounter = 0;\n        }\n      } break;\n      case 8: {\n        MaskScaleFactor -= 3072;\n        if (AnimCounter == 14) {\n          AnimationState++;\n          AnimCounter = 0;\n        }\n      } break;\n      case 9: {\n        if (AnimCounter == 30) {\n          AnimCounter = 0;\n          AnimationState = (DelusionState == DS_Positive)   ? 11\n                           : (DelusionState == DS_Negative) ? 12\n                                                            : 10;\n        }\n      } break;\n      case 10: {\n        MaskScaleFactor += 3072;\n        if (AnimCounter > 17) {\n          BackgroundAlpha -= 16;\n        }\n        if (AnimCounter == 50) {\n          setHidden();\n        }\n      } break;\n      case 11: {\n        SpinRate += 24;\n        MaskScaleFactor += 1536;\n        if (AnimCounter > 67) {\n          BackgroundAlpha -= 8;\n        }\n        if (AnimCounter == 100) {\n          setHidden();\n        }\n      } break;\n      case 12: {\n        SpinRate -= 24;\n        MaskScaleFactor += 1536;\n        if (AnimCounter > 67) {\n          BackgroundAlpha -= 8;\n        }\n        if (AnimCounter == 100) {\n          setHidden();\n        }\n      } break;\n    }\n  }\n}\n\nvoid DelusionTrigger::UpdateShowing(float dt) {\n  switch (AnimationState) {\n    case 0: {\n      SpinRate -= 24;\n      MaskScaleFactor -= 736;\n\n      if (UnderlayerAlpha < 256) {\n        UnderlayerAlpha += 16;\n      }\n      if (BackgroundAlpha < 256) {\n        BackgroundAlpha += 16;\n      }\n\n      if (SpinRate == 0) {\n        AnimationState += 1;\n        SpinRate = -1024;\n      }\n    } break;\n    case 1:\n    case 3:\n    case 5: {\n      AnimCounter += 1;\n      if (AnimCounter == 5) {\n        AnimationState += 1;\n        SpinRate = 0;\n        AnimCounter = 0;\n      }\n    } break;\n    case 2: {\n      AnimCounter += 1;\n      if (AnimCounter == 15) {\n        AnimationState += 1;\n        AnimCounter = 0;\n        SpinRate = 1024;\n      }\n    } break;\n    case 4: {\n      AnimCounter += 1;\n      if (AnimCounter == 15) {\n        AnimationState += 1;\n        AnimCounter = 0;\n        SpinRate = -1024;\n      }\n    } break;\n    case 6: {\n      AnimCounter += 1;\n      if (AnimCounter == 20) {\n        AnimationState += 1;\n        AnimCounter = 0;\n        SpinRate = 5;\n      }\n    } break;\n    case 7: {\n      MaskScaleFactor -= 1536;\n      AnimCounter += 1;\n      if (AnimCounter == 10) {\n        AnimationState += 1;\n        AnimCounter = 0;\n      }\n    } break;\n    case 8: {\n      MaskScaleFactor += 3072;\n      if (MaskScaleFactor > 79999) {\n        AnimationState = 0;\n        AnimCounter = 0;\n        SetFlag(SF_DELUSION_UI_ANIM_WAIT, 0);\n      }\n    } break;\n    default:\n      break;\n  }\n}\n\nvoid DelusionTrigger::PlayClickSound() {\n  Audio::Channels[Audio::AC_SE0 + 0]->SetVolume(\n      (ScrWork[SW_SEVOL + 0] / 100.0f) * 0.3f);\n  Audio::Channels[Audio::AC_SE0 + 0]->Play(\"se\", 19, false, 0.0f);\n}\n\nvoid DelusionTrigger::UpdateShown(float dt) {\n  MaskOffsetX = (ShakeState == 1)   ? 0\n                : (ShakeState == 2) ? -16\n                : (ShakeState == 3) ? 0\n                : (ShakeState == 4) ? 18\n                : (ShakeState == 5) ? 0\n                : (ShakeState == 6) ? -20\n                                    : 0;\n  ShakeState = (ShakeState == 0) ? 0 : ShakeState - 1;\n  bool anim = ShakeState != 0;\n  if ((PADinputButtonWentDown & PAD1L2) &&\n      TextSystem.DelusionTextFade.IsStopped()) {\n    switch (ScrWork[SW_DELUSION_STATE]) {\n      case DS_Neutral:\n        ScrWork[SW_DELUSION_STATE] = DS_Positive;\n        PlayClickSound();\n        ShakeState = 6;\n        break;\n      case DS_Negative:\n        ScrWork[SW_DELUSION_STATE] = DS_Neutral;\n        break;\n      case DS_Positive:\n      default:\n        break;\n    }\n  } else if ((PADinputButtonWentDown & PAD1R2) &&\n             TextSystem.DelusionTextFade.IsStopped()) {\n    switch (ScrWork[SW_DELUSION_STATE]) {\n      case DS_Neutral:\n        ScrWork[SW_DELUSION_STATE] = DS_Negative;\n        PlayClickSound();\n        ShakeState = 6;\n        break;\n      case DS_Positive:\n        ScrWork[SW_DELUSION_STATE] = DS_Neutral;\n        break;\n      case DS_Negative:\n      default:\n        break;\n    }\n  }\n\n  if (ScrWork[SW_DELUSION_STATE] != DS_Neutral) {\n    if (TriggerOnTintAlpha < 104) {\n      TriggerOnTintAlpha = TriggerOnTintAlpha + 4;\n    }\n    if (ScrWork[SW_DELUSION_STATE] == DS_Positive) {\n      TriggerOnTint = RgbIntToFloat(0xffb0ce);\n      if (SpinRate < 40) {\n        SpinRate = SpinRate + 2;\n        anim = true;\n      }\n      if (UnderlayerXRate < 2400) {\n        UnderlayerXRate += 100;\n        anim = true;\n        if (UnderlayerXRate == 2400 && ScrWork[SW_DELUSION_POS_TXT_IDX] != 0) {\n          TextSystem.DelusionSelectedLine =\n              (ScrWork[SW_DELUSION_POS_TXT_IDX] - 1) * 3;\n          TextSystem.Init();\n        }\n      }\n    } else if (ScrWork[SW_DELUSION_STATE] == DS_Negative) {\n      TriggerOnTint = ScrWork[SW_DELUSION_NEG_TXT_IDX]\n                          ? RgbIntToFloat(0xffb0ce)\n                          : RgbIntToFloat(0x2242e3);\n      if (SpinRate > -40) {\n        SpinRate = SpinRate - 2;\n        anim = true;\n      }\n      if (UnderlayerXRate > -2400) {\n        UnderlayerXRate -= 100;\n        anim = true;\n        if (UnderlayerXRate == -2400 && ScrWork[SW_DELUSION_NEG_TXT_IDX] != 0) {\n          TextSystem.DelusionSelectedLine =\n              (ScrWork[SW_DELUSION_NEG_TXT_IDX] - 1) * 3;\n          TextSystem.Init();\n        }\n      }\n    }\n  } else {\n    if ((TriggerOnTintAlpha != 0)) {\n      TriggerOnTintAlpha -= 4;\n    }\n    if (TextSystem.DelusionTextFade.IsIn()) {\n      TextSystem.DelusionTextFade.StartOut();\n    }\n    if (SpinRate < -5) {\n      SpinRate = SpinRate + 2;\n      anim = true;\n    } else if (SpinRate > 5) {\n      SpinRate = SpinRate - 2;\n      anim = true;\n    }\n    if (UnderlayerXRate < -400) {\n      UnderlayerXRate += 100;\n      anim = true;\n    } else if (UnderlayerXRate > 400) {\n      UnderlayerXRate -= 100;\n      anim = true;\n    }\n  }\n\n  SetFlag(SF_DELUSION_UI_ANIM_WAIT, anim);\n  SetFlag(SF_DELUSION_UI_ANIMSWITCH_WAIT, anim);\n}\n\nvoid DelusionTrigger::Update(float dt) {\n  if (State == Showing) {\n    UpdateShowing(dt);\n  } else if (State == Hiding) {\n    UpdateHiding(dt);\n  } else if (State == Shown) {\n    UpdateShown(dt);\n  }\n  TextSystem.Update(dt);\n\n  if (State != Hidden) {\n    UnderlayerXOffset += UnderlayerXRate;\n\n    constexpr static int minOffset = 10000;\n    constexpr static int range = 20000;\n\n    int diff = UnderlayerXOffset - minOffset;\n    int rem = diff % range;\n    if (rem < 0) rem += range;\n    UnderlayerXOffset = rem + minOffset;\n\n    SpinAngle = ((SpinAngle + SpinRate) & 0xffff);\n  }\n}\n\nvoid DelusionTrigger::Render() {\n  if (State == Hidden) return;\n  Sprite ScaledMask = BackgroundSpriteMask;\n  constexpr float aspect_ratio = 1280.0f / 720.0f;\n\n  float newWidth = BackgroundSpriteMask.Bounds.Width * 65535.0f /\n                   MaskScaleFactor * aspect_ratio * 0.7f;\n  float newHeight =\n      BackgroundSpriteMask.Bounds.Height * 65535.0f / MaskScaleFactor * 0.7f;\n\n  float deltaWidth = newWidth - BackgroundSpriteMask.Bounds.Width;\n  float deltaHeight = newHeight - BackgroundSpriteMask.Bounds.Height;\n\n  ScaledMask.Bounds.Width = newWidth;\n  ScaledMask.Bounds.Height = newHeight;\n\n  ScaledMask.Bounds.X =\n      MaskOffsetX + BackgroundSpriteMask.Bounds.X - deltaWidth / 2.0f;\n  ScaledMask.Bounds.Y = BackgroundSpriteMask.Bounds.Y - deltaHeight / 2.0f;\n\n  TriggerOnTint[3] = TriggerOnTintAlpha * BackgroundAlpha / 65536.0f;\n  Renderer->DrawQuad(\n      RectF(0.0f, 0.0f, Profile::DesignWidth, Profile::DesignHeight),\n      TriggerOnTint);\n\n  Sprite mask = ScreenMask;\n  mask.Bounds.X = UnderlayerXOffset / 1000.0f;\n\n  const RectF spriteDest = {0.0f, 0.0f, Profile::DesignWidth,\n                            Profile::DesignHeight};\n  const CornersQuad maskDest =\n      ScaledMask.Bounds.RotateAroundCenter(ScrWorkAngleToRad(SpinAngle));\n\n  Renderer->DrawSprite(\n      mask, spriteDest,\n      glm::vec4(1.0f, 1.0f, 1.0f, (BackgroundAlpha * 160) / 65536.0f));\n\n  TextSystem.Render();\n  Renderer->DrawMaskedSpriteOverlay(BackgroundSprite, ScaledMask, spriteDest,\n                                    maskDest, (BackgroundAlpha * 160) >> 8, 20,\n                                    glm::mat4(1.0f), glm::vec4(1.0f), true);\n}\n\nvoid DelusionTrigger::Load() {\n  State = Shown;\n  ShakeState = 0;\n  MaskOffsetX = 0;\n  MaskScaleFactor = 82944;\n\n  UnderlayerXOffset = 20000;\n  AnimCounter = 0;\n  SpinAngle = 0;\n  SpinRate = 5;\n  BackgroundAlpha = 256;\n  TextSystem.Clear();\n  switch (DelusionState) {\n    case DS_Neutral:\n      TriggerOnTintAlpha = 0;\n      TriggerOnTint = glm::vec4(0.0f);\n      UnderlayerXRate = 400;\n      break;\n    case DS_Positive:\n      TriggerOnTintAlpha = 104;\n      TriggerOnTint = RgbIntToFloat(0xffb0ce);\n      UnderlayerXRate = 2400;\n      if (ScrWork[SW_DELUSION_POS_TXT_IDX] != 0) {\n        TextSystem.DelusionSelectedLine =\n            (ScrWork[SW_DELUSION_POS_TXT_IDX] - 1) * 3;\n        TextSystem.Init();\n      }\n      break;\n    case DS_Negative:\n      TriggerOnTintAlpha = 104;\n      UnderlayerXRate = -2400;\n      TriggerOnTint = RgbIntToFloat(0x2242e3);\n      if (ScrWork[SW_DELUSION_NEG_TXT_IDX] != 0) {\n        TriggerOnTint = RgbIntToFloat(0xffb0ce);\n        TextSystem.DelusionSelectedLine =\n            (ScrWork[SW_DELUSION_NEG_TXT_IDX] - 1) * 3;\n        TextSystem.Init();\n      }\n      break;\n  }\n  auto bufId = GetBufferId(ScrWork[SW_EFF_CAP_BUF2]);\n  ScrWork[SW_CAP1FADECT + (bufId - 1) * Profile::Vm::ScrWorkBgStructSize] = 256;\n}\n\nvoid DelusionTrigger::Reset() {\n  State = Hidden;\n  SpinAngle = 0;\n  SpinRate = 0;\n  UnderlayerAlpha = 0;\n  UnderlayerXOffset = 0;\n  UnderlayerXRate = 0;\n  TriggerOnTint = glm::vec4{};\n  TriggerOnTintAlpha = 0;\n  AnimCounter = 0;\n  AnimationState = 0;\n  ShakeState = 0;\n  MaskScaleFactor = 65536;\n  TextSystem.Clear();\n}\n\nDelusionTextSystem::DelusionTextSystem() {\n  DelusionTextFade.SetDuration(DelusionTextFadeDuration);\n}\n\nvoid DelusionTextSystem::Init() {\n  InitLineOffsets();\n  InitLines();\n  DelusionTextFade.StartIn();\n}\n\nvoid DelusionTextSystem::InitLineOffsets() {\n  // Line offsets [0,1,2,0,1,2,0,1,2,...]\n  for (size_t i = 0; i < LineOffsets.size(); ++i) {\n    size_t computed = i / (LineOffsets.size() / GlyphLines.size());\n    LineOffsets[i] = static_cast<uint8_t>(computed);\n  }\n  TextLineXOffset = 0;\n  TextIndex = 0;\n  std::shuffle(LineOffsets.begin(), LineOffsets.end(),\n               std::mt19937{std::random_device{}()});\n}\n\nvoid DelusionTextSystem::InitLines() {\n  using namespace Profile::CHLCC::DelusionTrigger;\n\n  for (auto& line : GlyphLines) line.fill(nullptr);\n  for (size_t lineIdx = 0; lineIdx < GlyphLines.size(); ++lineIdx) {\n    const int pastScreenStartIndex = static_cast<int>(\n        std::ceil(Profile::DesignWidth / DelusionScaledGlyphWidth));\n\n    size_t charOffset = pastScreenStartIndex;\n    if (lineIdx != 1) charOffset = (std::rand() % 8) + pastScreenStartIndex * 2;\n\n    FillLine(charOffset, GlyphLines[lineIdx]);\n  }\n}\n\nvoid DelusionTextSystem::ScrollLine(size_t lineIdx) {\n  auto& line = GlyphLines[lineIdx];\n\n  std::shift_left(line.begin(), line.end(), 1);\n  line[MaxCharsPerLine - 1] = nullptr;\n\n  // Find the last non-empty glyph position, fill after it\n  int lastGlyph = MaxCharsPerLine - 1;\n  for (; lastGlyph >= 0; lastGlyph--) {\n    if (line[lastGlyph] != nullptr) break;\n  }\n  size_t counter = lastGlyph + 1;\n  FillLine(counter, line);\n}\n\nvoid DelusionTextSystem::FillLine(size_t& outStartIdx,\n                                  std::span<const Sprite*> line) {\n  while (true) {\n    size_t offsetIndex = LineOffsets[TextIndex] + *DelusionSelectedLine;\n    size_t charCnt = DelusionTextGlyphs[offsetIndex].size();\n    if (outStartIdx + charCnt >= MaxCharsPerLine) break;\n\n    TextIndex = (TextIndex + 1) % LineOffsets.size();\n    outStartIdx++;\n\n    const auto& glyphSrc = DelusionTextGlyphs[offsetIndex];\n    for (size_t c = 0; c < charCnt && outStartIdx + c < MaxCharsPerLine; ++c)\n      line[outStartIdx + c] = &glyphSrc[c];\n\n    outStartIdx += charCnt;\n  }\n}\n\nvoid DelusionTextSystem::Update(float dt) {\n  DelusionTextFade.Update(dt);\n  if (DelusionTextFade.IsOut()) {\n    if (DelusionSelectedLine) DelusionSelectedLine.reset();\n    return;\n  };\n\n  TextLineXOffset -= DelusionTextXVelocity * 60 * dt;\n\n  const float wrapAmount = DelusionScaledGlyphWidth - DelusionTextXVelocity;\n  if (TextLineXOffset < -wrapAmount) {\n    TextLineXOffset += wrapAmount;\n    for (size_t i = 0; i < GlyphLines.size(); ++i) ScrollLine(i);\n  }\n}\n\nvoid DelusionTextSystem::Render() {\n  if (DelusionTextFade.IsOut()) return;\n  for (size_t i = 0; i < GlyphLines.size(); i++) {\n    for (size_t j = 0; j < GlyphLines[i].size(); j++) {\n      const Sprite* glyph = GlyphLines[i][j];\n      if (!glyph) continue;\n      const glm::vec2 translation{\n          TextLineXOffset + DelusionScaledGlyphWidth * j,\n          -DelusionTextLineSpacing +\n              (DelusionScaledGlyphHeight + DelusionTextLineSpacing) * i};\n      CornersQuad quad{{\n          .TL = {-DelusionScaledGlyphWidth / 2, -DelusionScaledGlyphHeight / 2},\n          .BL = {-DelusionScaledGlyphWidth / 2, DelusionScaledGlyphHeight / 2},\n          .TR = {DelusionScaledGlyphWidth / 2, -DelusionScaledGlyphHeight / 2},\n          .BR = {DelusionScaledGlyphWidth / 2, DelusionScaledGlyphHeight / 2},\n      }};\n      const float zRots[] = {\n          ScrWorkAngleToRad(\n              static_cast<int>(translation.x * 38 + translation.y * 28965)) /\n              2,\n          ScrWorkAngleToRad(\n              static_cast<int>(translation.x * 65 + translation.y * 14708)) /\n              2,\n          ScrWorkAngleToRad(\n              static_cast<int>(translation.x * 34 + translation.y * 5651)) /\n              2,\n          ScrWorkAngleToRad(\n              static_cast<int>(translation.x * 52 + translation.y * 18518)) /\n              2,\n      };\n      glm::vec2* const corners[] = {&quad.TopLeft, &quad.BottomLeft,\n                                    &quad.TopRight, &quad.BottomRight};\n\n      for (int cornerIndex = 0; cornerIndex < 4; ++cornerIndex) {\n        glm::vec2 result =\n            Rotate2D(zRots[cornerIndex]) * glm::vec2{20.0f, 20.0f};\n        *corners[cornerIndex] += glm::vec2(result) + translation;\n      }\n      Renderer->DrawSprite(\n          *glyph, quad,\n          RgbIntToFloat(0xffa0e0, (DelusionTextFade.Progress * 3.0f / 10.0f)));\n    }\n  }\n}\n\nvoid DelusionTextSystem::Clear() {\n  for (auto& line : GlyphLines) {\n    line.fill(nullptr);\n  }\n\n  TextIndex = 0;\n  TextLineXOffset = 0;\n  DelusionTextFade.Stop();\n  DelusionTextFade.Progress = 0.0f;\n  DelusionSelectedLine.reset();\n}\n\n}  // namespace CHLCC\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/chlcc/delusiontrigger.h",
    "content": "#pragma once\n\n#include \"../../spriteanimation.h\"\n#include \"../../ui/menu.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace CHLCC {\nclass DelusionTextSystem {\n public:\n  static int constexpr MaxCharsPerLine = 90;\n  std::array<std::array<const Sprite*, MaxCharsPerLine>, 3> GlyphLines;\n  std::array<uint8_t, 300> LineOffsets{};\n\n  std::optional<int> DelusionSelectedLine;\n  float TextLineXOffset = 0;\n  size_t TextIndex = 0;\n  Animation DelusionTextFade;\n\n  DelusionTextSystem();\n\n  void Init();\n  void Update(float dt);\n  void Render();\n  void Clear();\n\n private:\n  void InitLineOffsets();\n  void InitLines();\n  void ScrollLine(size_t lineIndex);\n  void FillLine(size_t& counter, std::span<const Sprite*> line);\n};\n\nclass DelusionTrigger {\n public:\n  enum DelusionState { DS_Neutral, DS_Positive, DS_Negative };\n  DelusionTrigger();\n\n  void Show();\n  void Hide();\n  void Update(float dt);\n  void Render();\n  void Load();\n  void Reset();\n\n  static DelusionTrigger& GetInstance() {\n    static DelusionTrigger impl;\n    return impl;\n  };\n\n  UI::MenuState State = UI::Hidden;\n\n protected:\n  void UpdateShowing(float dt);\n  void UpdateShown(float dt);\n  void UpdateHiding(float dt);\n  void PlayClickSound();\n\n  int& DelusionState;\n\n  int MaskScaleFactor;\n  int SpinAngle;\n  int SpinRate;\n  int UnderlayerAlpha;\n  int BackgroundAlpha;\n\n  int AnimCounter;\n  int AnimationState;\n\n  glm::vec4 TriggerOnTint;\n  int TriggerOnTintAlpha;\n\n  int UnderlayerXOffset, UnderlayerXRate;\n  int ShakeState;\n  int MaskOffsetX;\n\n  DelusionTextSystem TextSystem;\n\n  friend DelusionTextSystem;\n};\n\n}  // namespace CHLCC\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/chlcc/introsequence.cpp",
    "content": "#include \"introsequence.h\"\n#include \"../../renderer/renderer.h\"\n#include \"../../profile/game.h\"\n#include \"../../profile/games/chlcc/titlemenu.h\"\n#include \"../../background2d.h\"\n#include \"../../audio/audiosystem.h\"\n\nusing namespace Impacto::Profile;\nusing namespace Impacto::Profile::CHLCC::TitleMenu;\nusing namespace Impacto::Audio;\n\nnamespace Impacto {\nnamespace CHLCC {\n\nIntroSequence::IntroSequence() {\n  Texture fallingStarsMaskTexture{};\n  fallingStarsMaskTexture.LoadSolidColor(Window->WindowWidth,\n                                         Window->WindowHeight, 0);\n  SpriteSheet fallingStarsMaskSheet(static_cast<float>(Window->WindowWidth),\n                                    static_cast<float>(Window->WindowHeight));\n  fallingStarsMaskSheet.Texture = fallingStarsMaskTexture.Submit();\n  fallingStarsMaskSheet.IsScreenCap = true;\n  FallingStarsMask =\n      Sprite(fallingStarsMaskSheet, 0, 0, DesignWidth, DesignHeight);\n\n  // Randomize falling stars\n  for (size_t i = 0; i < FallingStarSeeds.size(); i++) {\n    auto& [origin, angle] = FallingStarSeeds[i];\n\n    int random = CALCrnd(100);\n    origin.x = (float)(-120 + i * 110 + (random + 10) * 10);\n    origin.y = float((random + 10) * -10);\n\n    angle = (float)CALCrnd(8192) / 8192.0f * std::numbers::pi_v<float> * 2.0f;\n  }\n\n  PanningAnimation.SetDuration(IntroPanningAnimationDuration);\n  IntroAnimation.AddAnimation(PanningAnimation);\n\n  float starBounceTime =\n      IntroAnimation.DurationIn + IntroAfterPanningWaitDuration;\n  StarBounceAnimation.SetDuration(IntroStarBounceAnimationDuration);\n  IntroAnimation.AddAnimation(StarBounceAnimation, starBounceTime);\n\n  float explodingStarAnimationTime = IntroAnimation.DurationIn;\n  ExplodingStarAnimation.SetDuration(IntroExplodingStarAnimationDuration);\n  IntroAnimation.AddAnimation(ExplodingStarAnimation);\n\n  ExplodingStarRotationAnimation.LoopMode = AnimationLoopMode::Loop;\n  ExplodingStarRotationAnimation.SetDuration(\n      IntroExplodingStarRotationAnimationDuration);\n  IntroAnimation.AddAnimation(ExplodingStarRotationAnimation,\n                              explodingStarAnimationTime,\n                              ExplodingStarAnimation.DurationIn);\n\n  float fallingStarAnimationTime = IntroAnimation.DurationIn;\n  FallingStarsAnimation.SetDuration(IntroFallingStarsAnimationDuration);\n  IntroAnimation.AddAnimation(FallingStarsAnimation);\n\n  FallingStarsRotationAnimation.LoopMode = AnimationLoopMode::Loop;\n  FallingStarsRotationAnimation.SetDuration(\n      IntroFallingStarsRotationAnimationDuration);\n  IntroAnimation.AddAnimation(FallingStarsRotationAnimation,\n                              fallingStarAnimationTime,\n                              FallingStarsAnimation.DurationIn);\n\n  LogoFadeAnimation.SetDuration(IntroLogoFadeAnimationDuration);\n  IntroAnimation.AddAnimation(LogoFadeAnimation);\n\n  float lccLogoAnimationTime = IntroAnimation.DurationIn;\n  LCCLogoAnimation.SetDuration(IntroLCCLogoAnimationDuration);\n  IntroAnimation.AddAnimation(LCCLogoAnimation);\n\n  DelusionADVAnimation.SetDuration(IntroDelusionADVAnimationDuration);\n  IntroAnimation.AddAnimation(DelusionADVAnimation, lccLogoAnimationTime,\n                              DelusionADVAnimation.DurationIn);\n\n  LogoStarHighlightAnimation.SetDuration(\n      IntroLogoStarHighlightAnimationDuration);\n  IntroAnimation.AddAnimation(LogoStarHighlightAnimation);\n\n  float seiraAnimationTime = IntroAnimation.DurationIn;\n  SeiraAnimation.SetDuration(IntroSeiraAnimationDuration);\n  IntroAnimation.AddAnimation(SeiraAnimation);\n\n  DelusionADVHighlightAnimation.SetDuration(\n      IntroDelusionADVHighlightAnimationDuration);\n  IntroAnimation.AddAnimation(DelusionADVHighlightAnimation, seiraAnimationTime,\n                              DelusionADVHighlightAnimation.DurationIn);\n\n  LogoPopOutAnimation.SetDuration(IntroLogoPopOutAnimationDuration);\n  IntroAnimation.AddAnimation(\n      LogoPopOutAnimation, seiraAnimationTime + IntroLogoPopOutAnimationDelay,\n      LogoPopOutAnimation.DurationIn);\n\n  CopyrightAnimation.SetDuration(IntroCopyrightAnimationDuration);\n  IntroAnimation.AddAnimation(CopyrightAnimation);\n}\n\nIntroSequence::~IntroSequence() {\n  Renderer->FreeTexture(FallingStarsMask.Sheet.Texture);\n}\n\nvoid IntroSequence::Reset() {\n  Renderer->FreeTexture(FallingStarsMask.Sheet.Texture);\n\n  Texture fallingStarsMaskTexture{};\n  fallingStarsMaskTexture.LoadSolidColor(static_cast<int>(Window->WindowWidth),\n                                         static_cast<int>(Window->WindowHeight),\n                                         0);\n  FallingStarsMask.Sheet.Texture = fallingStarsMaskTexture.Submit();\n\n  IntroAnimation.Reset();\n}\n\nvoid IntroSequence::Update(float dt) {\n  if (StarBounceAnimation.Progress >= 0.357f &&\n      Audio::Channels[Audio::AC_SE0]->GetState() == ACS_Paused &&\n      !IntroAnimation.IsIn()) {\n    // Should still skip ahead playback in case of desync\n    Audio::Channels[Audio::AC_SE0]->Resume();\n  }\n\n  IntroAnimation.Update(dt);\n}\n\nvoid IntroSequence::Render() {\n  if (FallingStarsAnimation.IsOut()) {\n    DrawBackground();\n  }\n\n  if (StarBounceAnimation.State == AnimationState::Playing) {\n    DrawBouncingStar();\n  } else if (ExplodingStarAnimation.State == AnimationState::Playing) {\n    DrawExplodingStars();\n  } else if (FallingStarsAnimation.State == AnimationState::Playing) {\n    // Make sure the mask is the only thing in the color buffer\n    Renderer->Clear(glm::vec4(0.0f));\n    Renderer->DrawSprite(FallingStarsMask,\n                         RectF{0.0f, 0.0f, DesignWidth, DesignHeight});\n\n    // Draw the stars over the mask\n    // effectively obtaining the union\n    DrawFallingStars();\n\n    // Update the mask by rendering to the texture\n    Renderer->CaptureScreencap(FallingStarsMask);\n\n    // Draw the original background\n    // this will be visible through the holes in the mask\n    DrawBackground();\n\n    // Draw the new background with the mask\n    Renderer->DrawMaskedSprite(BackgroundSprite, FallingStarsMask,\n                               RectF{0.0f, 0.0f, DesignWidth, DesignHeight},\n                               255, 256);\n\n    // Draw the stars again, not to the mask this time\n    DrawFallingStars();\n  } else if (FallingStarsAnimation.IsIn()) {\n    if (!LogoFadeAnimation.IsOut()) {\n      DrawCHLogo();\n    }\n\n    if (!LCCLogoAnimation.IsOut()) {\n      DrawLCCLogo();\n    }\n\n    if (!DelusionADVAnimation.IsOut()) {\n      DrawDelusionADVText();\n    }\n\n    if (!SeiraAnimation.IsOut()) {\n      DrawSeira();\n    }\n\n    if (!CopyrightAnimation.IsOut()) {\n      float alpha = CopyrightAnimation.Progress;\n      Renderer->DrawSprite(CopyrightTextSprite, CopyrightTextPosition,\n                           {1.0f, 1.0f, 1.0f, alpha});\n    }\n  }\n}\n\nvoid IntroSequence::DrawBackground() const {\n  float progress =\n      std::sin(PanningAnimation.Progress * std::numbers::pi_v<float> / 2.0f);\n  glm::vec2 designDimensions(DesignWidth, DesignHeight);\n\n  Renderer->DrawQuad(RectF{0, 0, DesignWidth, DesignHeight}, glm::vec4(1.0f));\n  Renderer->DrawSprite(IntroBackgroundSprite, glm::vec2(0.0f),\n                       {1.0f, 1.0f, 1.0f, PanningAnimation.Progress});\n\n  glm::vec2 zoomFactor =\n      designDimensions / 16.0f + designDimensions / 16.0f * 7.0f * progress;\n\n  Renderer->SetBlendMode(RendererBlendMode::Additive);\n\n  for (size_t i = 0; i < IntroHighlightCount; i++) {\n    constexpr float scale = 1.5f;\n    const Sprite& sprite = IntroHighlightSprites[i];\n    float offset = IntroHighlightPositions[i];\n    glm::vec2 position = (offset + 1.0f) * zoomFactor -\n                         (sprite.ScaledBounds().GetSize() / 2.0f) * scale;\n\n    RectF dest = sprite.ScaledBounds()\n                     .Scale(glm::vec2(scale), glm::vec2(0.0f))\n                     .Translate(position);\n\n    Renderer->DrawSprite(sprite, dest, glm::vec4(1.0f));\n  }\n\n  Renderer->SetBlendMode(RendererBlendMode::Normal);\n\n  Renderer->CaptureScreencap(ShaderScreencapture.BgSprite);\n\n  // Cross-fade from black\n  Renderer->DrawQuad(RectF{0.0f, 0.0f, DesignWidth, DesignHeight},\n                     {0.0f, 0.0f, 0.0f, 1.0f});\n\n  const float scale = 4 / (progress * 3 + 1);\n  RectF dest = ShaderScreencapture.BgSprite.ScaledBounds().Scale(\n      glm::vec2(scale), glm::vec2(0.0f));\n\n  Renderer->DrawSprite(ShaderScreencapture.BgSprite, dest,\n                       {1.0f, 1.0f, 1.0f, progress});\n}\n\nvoid IntroSequence::DrawBouncingStar() const {\n  // These are the normalized frame timings & positions\n  float x = DesignWidth / 2.0f - IntroBouncingStarSprite.Bounds.Width / 2.0f +\n            (1.0f - StarBounceAnimation.Progress) * 0.61f * DesignWidth;\n\n  float y = DesignHeight / 2 + IntroBouncingStarSprite.Bounds.Height;\n  if (StarBounceAnimation.Progress < 0.357f) {\n    float progress = StarBounceAnimation.Progress / 0.357f;\n    y -= std::sin(progress * std::numbers::pi_v<float>) * 0.664f * DesignHeight;\n  } else if (StarBounceAnimation.Progress < 0.536f) {\n    float progress =\n        (StarBounceAnimation.Progress - 0.357f) / (0.536f - 0.357f);\n    y -= std::sin(progress * std::numbers::pi_v<float>) * 0.094f * DesignHeight;\n  } else if (StarBounceAnimation.Progress < 0.714f) {\n    float progress =\n        (StarBounceAnimation.Progress - 0.536f) / (0.714f - 0.536f);\n    y -= std::sin(progress * std::numbers::pi_v<float>) * 0.094f * DesignHeight;\n  } else {\n    float progress = (StarBounceAnimation.Progress - 0.714f) / (1.0f - 0.714f);\n    y -= std::sin(progress * 0.8f * std::numbers::pi_v<float>) * 0.475f *\n         DesignHeight;\n  }\n\n  Renderer->DrawSprite(StarLogoSprite, glm::vec2(x, y));\n}\n\nvoid IntroSequence::DrawExplodingStars() const {\n  Renderer->SetBlendMode(RendererBlendMode::Additive);\n\n  glm::vec2 origin = glm::vec2(DesignWidth, DesignHeight) / 2.0f -\n                     IntroExplodingStarSprite.Bounds.GetSize() / 2.0f;\n\n  constexpr size_t numStars = 5;\n  for (size_t i = 0; i < numStars; i++) {\n    float rayAngle = std::numbers::pi_v<float> / 2.0f -\n                     (std::numbers::pi_v<float> * 2.0f / numStars) * i;\n    glm::vec2 directionVector(std::cos(rayAngle), -std::sin(rayAngle));\n    glm::vec2 displacement = directionVector * ExplodingStarAnimation.Progress *\n                             IntroExplodingStarAnimationDistance;\n    glm::vec2 position = origin + displacement;\n\n    float opacity =\n        std::min(2.0f - ExplodingStarAnimation.Progress * 2.0f, 1.0f);\n    float angle = std::numbers::pi_v<float> * 2.0f *\n                  ExplodingStarRotationAnimation.Progress;\n    if (i >= 3) angle = -angle;\n\n    CornersQuad dest = IntroExplodingStarSprite.ScaledBounds()\n                           .Translate(position)\n                           .RotateAroundCenter(angle);\n\n    Renderer->DrawSprite(IntroExplodingStarSprite, dest,\n                         {1.0f, 1.0f, 1.0f, opacity});\n  }\n\n  Renderer->SetBlendMode(RendererBlendMode::Normal);\n}\n\nvoid IntroSequence::DrawFallingStars() const {\n  for (auto [origin, initialAngle] : FallingStarSeeds) {\n    glm::vec2 displacement = IntroFallingStarsAnimationDirection *\n                             IntroFallingStarsAnimationDistance *\n                             FallingStarsAnimation.Progress;\n\n    glm::vec2 position = origin + displacement;\n    float angle = initialAngle + std::numbers::pi_v<float> * 2.0f *\n                                     FallingStarsRotationAnimation.Progress;\n\n    CornersQuad dest = IntroFallingStarSprite.ScaledBounds()\n                           .RotateAroundCenter(angle)\n                           .Translate(position);\n\n    Renderer->DrawSprite(IntroFallingStarSprite, dest);\n  }\n}\n\nvoid IntroSequence::DrawCHLogo() const {\n  if (LogoFadeAnimation.IsIn()) {\n    Renderer->DrawSprite(CHLogoSprite, CHLogoPosition);\n    return;\n  }\n\n  float dy = IntroCHLogoFadeAnimationStartY - CHLogoPosition.y;\n\n  float y = IntroCHLogoFadeAnimationStartY - dy * LogoFadeAnimation.Progress;\n  float opacity = LogoFadeAnimation.Progress;\n  Renderer->DrawSprite(CHLogoSprite, {CHLogoPosition.x, y},\n                       {1.0f, 1.0f, 1.0f, opacity});\n\n  y = CHLogoPosition.y + dy * LogoFadeAnimation.Progress;\n  opacity =\n      std::sin(LogoFadeAnimation.Progress * std::numbers::pi_v<float>) / 2.0f;\n  Renderer->DrawSprite(CHLogoSprite, {CHLogoPosition.x, y},\n                       {1.0f, 1.0f, 1.0f, opacity});\n}\n\nvoid IntroSequence::DrawLCCLogo() const {\n  if (LogoStarHighlightAnimation.IsIn()) {\n    Renderer->DrawSprite(LCCLogoUnderSprite, LCCLogoUnderPosition);\n  }\n\n  glm::vec2 popOutOffset =\n      (1 - LogoPopOutAnimation.Progress) * -IntroLogoPopOutOffset;\n\n  float animationStep;\n  float progress =\n      std::modf(LCCLogoAnimation.Progress * LCCLogoSpriteCount, &animationStep);\n\n  for (size_t index : LCCLogoDrawOrder) {\n    if (index > animationStep) continue;\n\n    float opacity = index < animationStep ? 1.0f : progress;\n    float scale = index < animationStep ? 1.0f : 2 - progress;\n\n    const Sprite& sprite = LCCLogoSprites[index];\n    glm::vec2 position = LCCLogoPositions[index] + popOutOffset -\n                         sprite.Bounds.GetSize() * (scale - 1) / 2.0f;\n\n    RectF dest = sprite.ScaledBounds();\n    dest.Scale(glm::vec2(scale), glm::vec2(0.0f)).Translate(position);\n    Renderer->DrawSprite(sprite, dest, {1.0f, 1.0f, 1.0f, opacity});\n  }\n\n  if (LogoStarHighlightAnimation.Progress > 0.5f) {\n    Renderer->DrawSprite(StarLogoSprite, StarLogoPosition + popOutOffset);\n  }\n\n  if (LogoStarHighlightAnimation.State == AnimationState::Playing) {\n    float opacity = 1 - std::abs(1 - 2 * LogoStarHighlightAnimation.Progress);\n    Renderer->DrawSprite(IntroLogoStarHighlightSprite,\n                         IntroLogoStarHighlightPosition,\n                         {1.0f, 1.0f, 1.0f, opacity});\n  }\n}\n\nvoid IntroSequence::DrawDelusionADVText() const {\n  if (SeiraAnimation.IsOut()) {\n    float animationStep;\n    float progress =\n        std::modf(DelusionADVAnimation.Progress * IntroDelusionADVSpriteCount,\n                  &animationStep);\n\n    size_t lastVisibleStep = std::min((size_t)IntroDelusionADVSpriteCount,\n                                      (size_t)(animationStep + 1));\n    for (size_t index = 0; index < lastVisibleStep; index++) {\n      if (index > animationStep) continue;\n\n      float opacity = index < animationStep ? 1.0f : progress;\n      float scale = index < animationStep ? 1.0f : 2 - progress;\n\n      const Sprite& sprite = IntroDelusionADVSprites[index];\n      glm::vec2 position = IntroDelusionADVPositions[index] -\n                           sprite.Bounds.GetSize() * (scale - 1) / 2.0f;\n\n      RectF dest = sprite.ScaledBounds();\n      dest.Scale(glm::vec2(scale), glm::vec2(0.0f)).Translate(position);\n      Renderer->DrawSprite(sprite, dest, {1.0f, 1.0f, 1.0f, opacity});\n    }\n  } else if (DelusionADVHighlightAnimation.State == AnimationState::Playing) {\n    float intensity =\n        1 - std::abs(1 - 2 * DelusionADVHighlightAnimation.Progress);\n    glm::vec3 colorShift(intensity);\n    glm::vec2 position = DelusionADVPosition - DelusionADVPopoutOffset;\n    RectF dest{position.x, position.y, DelusionADVSprite.Bounds.Width,\n               DelusionADVSprite.Bounds.Height};\n\n    Renderer->DrawSprite(DelusionADVSprite, dest, glm::vec4(1.0f), colorShift);\n  } else if (DelusionADVAnimation.IsIn()) {\n    Renderer->DrawSprite(DelusionADVUnderSprite,\n                         DelusionADVPosition - DelusionADVPopoutOffset);\n\n    glm::vec2 offset =\n        -DelusionADVPopoutOffset * (1 - LogoPopOutAnimation.Progress);\n    Renderer->DrawSprite(DelusionADVSprite, DelusionADVPosition + offset);\n  }\n}\n\nvoid IntroSequence::DrawSeira() const {\n  float progress = SeiraAnimation.Progress;\n  glm::vec4 tint = {1.0f, 1.0f, 1.0f, progress};\n\n  glm::vec2 seiraPosition = SeiraPosition - (1 - progress) * SeiraPopoutOffset;\n  glm::vec2 seiraUnderPosition =\n      SeiraUnderPosition + (1 - progress) * SeiraPopoutOffset;\n\n  Renderer->DrawSprite(SeiraUnderSprite, seiraUnderPosition, tint);\n  Renderer->DrawSprite(SeiraSprite, seiraPosition, tint);\n}\n\n}  // namespace CHLCC\n}  // namespace Impacto"
  },
  {
    "path": "src/games/chlcc/introsequence.h",
    "content": "#pragma once\n\n#include \"../../animation.h\"\n#include \"../../sequencedanimation.h\"\n#include \"../../spritesheet.h\"\n#include <array>\n\nnamespace Impacto {\nnamespace CHLCC {\n\nclass IntroSequence {\n public:\n  IntroSequence();\n  ~IntroSequence();\n\n  void Update(float dt);\n  void Reset();\n\n  SequencedAnimation IntroAnimation;\n  Animation PanningAnimation;\n  Animation StarBounceAnimation;\n  Animation ExplodingStarAnimation;\n  Animation ExplodingStarRotationAnimation;\n  Animation FallingStarsAnimation;\n  Animation FallingStarsRotationAnimation;\n  Animation LogoFadeAnimation;\n  Animation LCCLogoAnimation;\n  Animation LogoStarHighlightAnimation;\n  Animation DelusionADVAnimation;\n  Animation DelusionADVHighlightAnimation;\n  Animation SeiraAnimation;\n  Animation LogoPopOutAnimation;\n  Animation CopyrightAnimation;\n\n  void Render();\n\n private:\n  void DrawBackground() const;\n  void DrawFallingStars() const;\n  void DrawExplodingStars() const;\n  void DrawBouncingStar() const;\n  void DrawCHLogo() const;\n  void DrawLCCLogo() const;\n  void DrawDelusionADVText() const;\n  void DrawSeira() const;\n\n  std::array<std::pair<glm::vec2, float>, 20> FallingStarSeeds;\n  Sprite FallingStarsMask;\n};\n\n}  // namespace CHLCC\n}  // namespace Impacto"
  },
  {
    "path": "src/games/chlcc/moviemenu.cpp",
    "content": "#include \"moviemenu.h\"\n\n#include \"../../profile/games/chlcc/moviemenu.h\"\n#include \"../../profile/scriptvars.h\"\n#include \"../../profile/profile_internal.h\"\n#include \"../../renderer/renderer.h\"\n#include \"../../ui/ui.h\"\n#include \"../../vm/interface/input.h\"\n#include \"../../audio/audiosystem.h\"\n#include \"../../profile/games/chlcc/commonmenu.h\"\n#include \"../../ui/widgets/chlcc/moviemenuentrybutton.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace CHLCC {\nusing namespace Impacto::Profile::CHLCC::CommonMenu;\nusing namespace Impacto::Profile::CHLCC::MovieMenu;\nusing namespace Impacto::Profile::ScriptVars;\n\nusing namespace Impacto::Vm::Interface;\n\nusing namespace Impacto::UI::Widgets::CHLCC;\n\nconstexpr std::array<MovieButtonEntry, 10> MovieButtonMap{{\n    {10, 18},  // 0: PS3 OP / PSP OP\n    {0, 19},   // 1: X360 OP / Switch OP\n    {1, 11},   // 2: Rimi ED\n    {2, 12},   // 3: Nanami ED\n    {4, 14},   // 4: Yua ED\n    {5, 15},   // 5: Mia ED\n    {6, 16},   // 6: Ayase ED\n    {3, 13},   // 7: Sena ED\n    {7, 17},   // 8: Kozue ED\n    {8, 8}     // 9: Seira ED\n}};\n\nstatic bool IsExtraMoviesPresent() {\n  static bool isPresent = [] {\n    if (!MovieExtraVideosEnabled) return false;\n    std::map<uint32_t, std::string> listing;\n    Io::VfsListFiles(\"movie\", listing);\n    // There are 11 videos in the base game: the 10 ones in this menu + the\n    // smoke one. If there's more than that, it means the PSP videos are in.\n    return listing.size() > Movies + 1;\n  }();\n  return isPresent;\n}\n\nvoid MovieMenu::MovieButtonOnClick(Widgets::Button* target) {\n  auto movieButton = static_cast<MovieMenuEntryButton*>(target);\n  if (!movieButton->IsLocked) {\n    ChoiceMade = true;\n    IsChoiceMadeOnce = true;\n    ScrWork[SW_MOVIEMODE_CUR] =\n        IsExtraMovieModeOn ? MovieButtonMap.at(movieButton->Id).ExtraId\n                           : MovieButtonMap.at(movieButton->Id).PhysicalId;\n    Audio::Channels[Audio::AC_BGM0]->Stop(0.0f);\n  }\n}\n\nMovieMenu::MovieMenu() : CommonMenu(false) {\n  // Check once in constructor that the ExtraMovieMode is usable, i.e: read VFS\n  // once.\n  IsExtraMoviesPresent();\n\n  auto onClick = [this](auto* btn) { return MovieButtonOnClick(btn); };\n\n  // Movie Buttons initialization\n  MovieItems = new Widgets::Group(this);\n\n  for (int i = 0; i < 10; i++) {\n    glm::vec2 thumbnailPosition(ThumbnailPositions[i].x,\n                                ThumbnailPositions[i].y);\n    glm::vec2 boxPosition(BoxPositions[i].x, BoxPositions[i].y);\n    MovieMenuEntryButton* movieMenuEntryButton = new MovieMenuEntryButton(\n        i, MoviesThumbnails[i], LockedThumbnail, thumbnailPosition, boxPosition,\n        IsExtraMovieModeOn);\n    movieMenuEntryButton->OnClickHandler = onClick;\n    MovieItems->Add(movieMenuEntryButton, FDIR_DOWN);\n  }\n\n  auto setFocus = [](Widget* btn, Widget* btn2, FocusDirection dir) {\n    FocusDirection oppositeDir = [dir] {\n      switch (dir) {\n        case FDIR_LEFT:\n          return FDIR_RIGHT;\n        case FDIR_RIGHT:\n          return FDIR_LEFT;\n        case FDIR_UP:\n          return FDIR_DOWN;\n        case FDIR_DOWN:\n          return FDIR_UP;\n      }\n      return FDIR_LEFT;  // unreachable\n    }();\n    btn->SetFocus(btn2, dir);\n    btn2->SetFocus(btn, oppositeDir);\n  };\n  // Vertical\n  setFocus(MovieItems->Children[3], MovieItems->Children[0], FDIR_DOWN);\n  setFocus(MovieItems->Children[6], MovieItems->Children[4], FDIR_DOWN);\n  setFocus(MovieItems->Children[9], MovieItems->Children[7], FDIR_DOWN);\n  // Horizontal\n  setFocus(MovieItems->Children[0], MovieItems->Children[4], FDIR_RIGHT);\n  setFocus(MovieItems->Children[4], MovieItems->Children[7], FDIR_RIGHT);\n  setFocus(MovieItems->Children[7], MovieItems->Children[0], FDIR_RIGHT);\n\n  setFocus(MovieItems->Children[1], MovieItems->Children[5], FDIR_RIGHT);\n  setFocus(MovieItems->Children[5], MovieItems->Children[8], FDIR_RIGHT);\n  setFocus(MovieItems->Children[8], MovieItems->Children[1], FDIR_RIGHT);\n\n  setFocus(MovieItems->Children[3], MovieItems->Children[6], FDIR_RIGHT);\n  setFocus(MovieItems->Children[2], MovieItems->Children[6], FDIR_RIGHT);\n  setFocus(MovieItems->Children[6], MovieItems->Children[9], FDIR_RIGHT);\n  setFocus(MovieItems->Children[9], MovieItems->Children[2], FDIR_RIGHT);\n}\n\nvoid MovieMenu::Show() {\n  if (State != Shown) {\n    if (State != Showing) {\n      if (ChoiceMade) {\n        MenuTransition.Progress = 1.0f;\n      }\n      MenuTransition.StartIn();\n      SelectAnimation.StartIn();\n    }\n    MovieItems->Show();\n    MovieItems->HasFocus = false;\n    State = Showing;\n    ChoiceMade = false;\n    UpdateMovieEntries();\n    ScrWork[SW_MOVIEMODE_CUR] = 255;\n    if (UI::FocusedMenu != 0) {\n      LastFocusedMenu = UI::FocusedMenu;\n      LastFocusedMenu->IsFocused = false;\n    }\n    IsFocused = true;\n    UI::FocusedMenu = this;\n    MovieItems->Children.front()->HasFocus = true;\n    CurrentlyFocusedElement = MovieItems->Children.front();\n  }\n}\n\nvoid MovieMenu::Hide() {\n  if (State != Hidden) {\n    if (State != Hiding) {\n      if (ChoiceMade) {\n        MenuTransition.Progress = 0.0f;\n      }\n      MenuTransition.StartOut();\n    }\n    MovieItems->HasFocus = false;\n    State = Hiding;\n    if (LastFocusedMenu != 0) {\n      UI::FocusedMenu = LastFocusedMenu;\n      LastFocusedMenu->IsFocused = true;\n    } else {\n      UI::FocusedMenu = 0;\n    }\n    IsFocused = false;\n  }\n}\n\nvoid MovieMenu::Render() {\n  if (State == Hidden) return;\n  CommonMenu::DrawSubmenu(BackgroundColor, CircleSprite, MenuTitleText,\n                          MenuTitleTextAngle, true);\n\n  if (MenuTransition.Progress <= 0.22f) return;\n\n  glm::vec2 pageOffset = MenuTransition.GetPageOffset();\n\n  MovieItems->MoveTo(pageOffset);\n  MovieItems->Render();\n  glm::vec2 listPosition = ListPosition + pageOffset;\n  Renderer->DrawSprite(MovieList, listPosition);\n  DrawButtonPrompt();\n  SelectAnimation.Draw(SelectMovie, SelectMoviePos, pageOffset);\n}\n\nvoid MovieMenu::UpdateInput(float dt) {\n  Menu::UpdateInput(dt);\n  if (State == Shown) {\n    MovieItems->UpdateInput(dt);\n    if (PADinputButtonWentDown & PAD1B || PADinputMouseWentDown & PAD1B) {\n      IsChoiceMadeOnce = false;\n    }\n    if (IsExtraMoviesPresent() && (PADinputButtonWentDown & PAD1Y)) {\n      IsExtraMovieModeOn = !IsExtraMovieModeOn;\n    }\n  }\n}\n\nvoid MovieMenu::Update(float dt) {\n  if ((!GetFlag(SF_MOVIEMENU) || ScrWork[SW_SYSMENUCT] < 10000) &&\n      State == Shown) {\n    Hide();\n  } else if (GetFlag(SF_MOVIEMENU) && ScrWork[SW_SYSMENUCT] > 0 &&\n             State == Hidden) {\n    Show();\n  }\n\n  if (MenuTransition.IsOut() &&\n      (ScrWork[SW_SYSMENUCT] == 0 || GetFlag(SF_SYSTEMMENU)) &&\n      State == Hiding) {\n    State = Hidden;\n    MovieItems->Hide();\n  } else if (MenuTransition.IsIn() && ScrWork[SW_SYSMENUCT] == 10000 &&\n             State == Showing) {\n    State = Shown;\n    MovieItems->HasFocus = true;\n  }\n\n  if (State != Hidden) {\n    MenuTransition.Update(dt);\n    SelectAnimation.Update(dt);\n    if (MenuTransition.Direction == AnimationDirection::Out &&\n        MenuTransition.Progress <= 0.72f) {\n      if (IsChoiceMadeOnce) {\n        TitleFade.Progress = 0.0f;\n      }\n      TitleFade.StartOut();\n    } else if (MenuTransition.IsIn() &&\n               (TitleFade.Direction == AnimationDirection::In ||\n                TitleFade.IsOut())) {\n      if (IsChoiceMadeOnce) {\n        TitleFade.Progress = 1.0f;\n      }\n      TitleFade.StartIn();\n    }\n    if (State == Shown) {\n      UpdateInput(dt);\n    }\n    MovieItems->Update(dt);\n    TitleFade.Update(dt);\n    UpdateTitles(MenuTitleTextRightPosition, MenuTitleTextLeftPosition);\n  }\n}\n\nvoid MovieMenu::DrawButtonPrompt() {\n  // If ExtraMovieMode is usable, change the button prompt to indicate the\n  // PAD1Y action is available.\n  const Sprite finalButtonPromptSprite =\n      IsExtraMoviesPresent() ? MovieButtonExtraPrompt : ButtonPromptSprite;\n  const glm::vec2 finalButtonPromptPosition =\n      IsExtraMoviesPresent() ? MovieButtonExtraPromptPosition\n                             : ButtonPromptPosition;\n\n  CommonMenu::DrawButtonPrompt(finalButtonPromptSprite,\n                               finalButtonPromptPosition);\n}\n\nvoid MovieMenu::UpdateMovieEntries() {\n  for (auto el : MovieItems->Children) {\n    auto movieButton = dynamic_cast<MovieMenuEntryButton*>(el);\n    if (movieButton->Id == 0 || movieButton->Id == 1) {\n      movieButton->IsLocked = false;\n    } else {\n      movieButton->IsLocked = !(GetFlag(SF_MOVIE_UNLOCK1) ||\n                                GetFlag(SF_CLR_END1 + movieButton->Id - 2));\n    }\n  }\n}\n\n}  // namespace CHLCC\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/chlcc/moviemenu.h",
    "content": "#pragma once\n\n#include \"commonmenu.h\"\n#include \"../../ui/menu.h\"\n#include \"../../ui/widgets/group.h\"\n#include \"../../ui/widgets/button.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace CHLCC {\n\nstruct MovieButtonEntry {\n  int PhysicalId;\n  int ExtraId;\n};\nclass MovieMenu : public Menu, public CommonMenu {\n public:\n  MovieMenu();\n\n  void Show() override;\n  void Hide() override;\n  void UpdateInput(float dt) override;\n  void Update(float dt) override;\n  void Render() override;\n\n  void MovieButtonOnClick(Widgets::Button* target);\n\n private:\n  Widgets::Group* MovieItems;\n\n  using CommonMenu::DrawButtonPrompt;\n  void DrawButtonPrompt();\n  void UpdateMovieEntries();\n\n  bool IsChoiceMadeOnce = false;\n  bool IsExtraMovieModeOn = false;\n};\n\n}  // namespace CHLCC\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/chlcc/musicmenu.cpp",
    "content": "#include \"musicmenu.h\"\n\n#include \"../../profile/games/chlcc/musicmenu.h\"\n#include \"../../profile/scriptvars.h\"\n#include \"../../renderer/renderer.h\"\n#include \"../../ui/ui.h\"\n#include \"../../data/savesystem.h\"\n#include \"../../vm/interface/input.h\"\n#include \"../../profile/game.h\"\n#include \"../../inputsystem.h\"\n#include \"../../audio/audiosystem.h\"\n#include \"../../ui/widgets/chlcc/trackselectbutton.h\"\n#include \"../../profile/games/chlcc/commonmenu.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace CHLCC {\n\nusing namespace Impacto::Profile::CHLCC::CommonMenu;\nusing namespace Impacto::Profile::CHLCC::MusicMenu;\nusing namespace Impacto::Profile::ScriptVars;\n\nusing namespace Impacto::UI::Widgets;\n\nusing namespace Impacto::Vm::Interface;\n\nconstexpr bool isRepeat(MusicPlaybackMode playbackMode) {\n  return (playbackMode & MusicPlaybackMode::RepeatOne) ==\n         MusicPlaybackMode::RepeatOne;\n}\n\nconstexpr bool isPlaylist(MusicPlaybackMode playbackMode) {\n  return (playbackMode & MusicPlaybackMode::Playlist) ==\n         MusicPlaybackMode::Playlist;\n}\n\nvoid MusicMenu::MusicButtonOnClick(Button* target) {\n  if (target->IsLocked) return;\n\n  SwitchToTrack(target->Id);\n}\n\nvoid MusicMenu::ToggleRepeatMode() {\n  PlaybackMode ^= MusicPlaybackMode::RepeatOne;\n  PlayModeRepeatSprite =\n      isRepeat(PlaybackMode) ? PlaymodeRepeatHighlight : PlaymodeRepeat;\n  UpdateLooping();\n}\n\nvoid MusicMenu::ToggleAllMode() {\n  PlaybackMode ^= MusicPlaybackMode::Playlist;\n  PlayModeAllSprite =\n      isPlaylist(PlaybackMode) ? PlaymodeAllHighlight : PlaymodeAll;\n  UpdateLooping();\n}\n\nvoid MusicMenu::UpdateLooping() {\n  if (PlaybackMode == MusicPlaybackMode::RepeatOne) {\n    Audio::Channels[Audio::AC_BGM0]->SetLooping(true);\n  } else {\n    Audio::Channels[Audio::AC_BGM0]->SetLooping(false);\n  }\n}\n\nstatic float GetEndScroll(Group* itemsGroup) {\n  if (itemsGroup->Children.empty() || itemsGroup->Children.size() == 1)\n    return 0.0f;\n  const Widget* lastItem = itemsGroup->Children.back();\n  const float lastItemEndPos = lastItem->Bounds.Height + lastItem->Bounds.Y;\n  const float pagePos =\n      lastItemEndPos - TrackListBounds.Height - TrackListBounds.Y;\n  if (pagePos < 0.0f) return 0.0f;\n  return round(pagePos / TrackOffset.y) * TrackOffset.y;\n}\n\nMusicMenu::MusicMenu() : CommonMenu(false) {\n  NowPlayingAnimation.Direction = AnimationDirection::In;\n  NowPlayingAnimation.LoopMode = AnimationLoopMode::Stop;\n  NowPlayingAnimation.SetDuration(NowPlayingAnimationDuration);\n\n  auto toggleRepeatClick = [this](auto* btn) { return ToggleRepeatMode(); };\n  PlayModeRepeatClickArea =\n      ClickArea(0,\n                {PlaymodeRepeatPos.x, PlaymodeRepeatPos.y,\n                 PlaymodeRepeat.ScaledWidth(), PlaymodeRepeat.ScaledHeight()},\n                toggleRepeatClick);\n\n  auto toggleAllClick = [this](auto* btn) { return ToggleAllMode(); };\n  PlayModeAllClickArea =\n      ClickArea(1,\n                {PlaymodeAllPos.x, PlaymodeAllPos.y, PlaymodeAll.ScaledWidth(),\n                 PlaymodeAll.ScaledHeight()},\n                toggleAllClick);\n\n  MainItems = new Group(this);\n\n  for (int idx = 0; idx < MusicTrackCount; idx++) {\n    auto button = new Widgets::CHLCC::TrackSelectButton(\n        idx, TrackHighlight, TrackButtonPosTemplate + (float)idx * TrackOffset,\n        TrackNumRelativePos, TrackNameOffset, ArtistOffset);\n    MainItems->Add(button, FDIR_DOWN);\n\n    // Page scrolling\n    if (idx >= SelectableItemsPerPage) {\n      button->SetFocus(MainItems->Children[idx - SelectableItemsPerPage],\n                       FDIR_LEFT);\n      MainItems->Children[idx - SelectableItemsPerPage]->SetFocus(button,\n                                                                  FDIR_RIGHT);\n    } else {\n      button->SetFocus(MainItems->Children.front(), FDIR_LEFT);\n    }\n  }\n\n  MainScrollbar = Scrollbar(\n      0, ScrollbarPosition, 0.0f, GetEndScroll(MainItems), &ScrollY,\n      ScrollbarDirection::SBDIR_VERTICAL, ScrollThumbSprite, ScrollTrackBounds,\n      ScrollThumbSprite.ScaledHeight(), TrackListBounds, 1.0f);\n  MainScrollbar.Step = TrackOffset.y;\n\n  // Everything in last page points to last element\n  for (int idx = MusicTrackCount - 1 - SelectableItemsPerPage;\n       idx < MusicTrackCount; idx++) {\n    MainItems->Children[idx]->SetFocus(MainItems->Children.back(), FDIR_RIGHT);\n  }\n\n  MainItems->Children.front()->SetFocus(MainItems->Children.back(), FDIR_UP);\n  MainItems->Children.front()->SetFocus(MainItems->Children.back(), FDIR_LEFT);\n  MainItems->Children.back()->SetFocus(MainItems->Children.front(), FDIR_DOWN);\n  MainItems->Children.back()->SetFocus(MainItems->Children.front(), FDIR_RIGHT);\n}\n\nvoid MusicMenu::Init() {\n  for (const auto presetFlag : PresetBgmFlags) {\n    SaveSystem::SetBgmFlag(presetFlag, true);\n  }\n  if (GetFlag(SF_CLR_AYASE)) {\n    SaveSystem::SetBgmFlag(AyaseEndingBgmId, true);\n  }\n\n  const std::array<int, 7> endingFlags = {\n      SF_CLR_RIMI, SF_CLR_NANAMI, SF_CLR_YUA,  SF_CLR_MIA,\n      SF_CLR_SENA, SF_CLR_KOZUE,  SF_CLR_SEIRA};\n  const bool gotRequiredEndings = std::ranges::all_of(endingFlags, GetFlag);\n  if (gotRequiredEndings) {\n    SaveSystem::SetBgmFlag(NormalEndingBgmId, true);\n  }\n\n  for (size_t i = 0; i < DstBgmPairedFlag.size(); i++) {\n    const int dstFlagId = DstBgmPairedFlag[i];\n    const bool flagValue = SaveSystem::GetBgmFlag(dstFlagId) ||\n                           SaveSystem::GetBgmFlag(SrcBgmPairedFlag[i]);\n    SaveSystem::SetBgmFlag(dstFlagId, flagValue);\n  }\n}\n\nvoid MusicMenu::Show() {\n  if (State != Shown) {\n    if (State != Showing) {\n      MenuTransition.StartIn();\n      SelectAnimation.StartIn(true);\n    }\n    State = Showing;\n    UpdateEntries();\n    MainItems->Show();\n    CurrentlyPlayingTrackName.Show();\n    CurrentlyPlayingTrackArtist.Show();\n    if (UI::FocusedMenu != 0) {\n      LastFocusedMenu = UI::FocusedMenu;\n      LastFocusedMenu->IsFocused = false;\n    }\n    ScrollY = 0.0f;\n    IsFocused = true;\n    UI::FocusedMenu = this;\n    MainItems->Children.front()->HasFocus = true;\n    CurrentlyFocusedElement = MainItems->Children.front();\n    PlayModeRepeatSprite = PlaymodeRepeat;\n    PlayModeAllSprite = PlaymodeAll;\n    PlaybackMode = MusicPlaybackMode::One;\n  }\n}\n\nvoid MusicMenu::Hide() {\n  if (State != Hidden) {\n    if (State != Hiding) {\n      MenuTransition.StartOut();\n      MenuTransition.StartOut();\n    }\n    CurrentlyPlayingTrackId.reset();\n    NowPlayingAnimation.StartOut();\n    Audio::Channels[Audio::AC_BGM0]->Stop(0.0f);\n    State = Hiding;\n    if (LastFocusedMenu != 0) {\n      UI::FocusedMenu = LastFocusedMenu;\n      LastFocusedMenu->IsFocused = true;\n    } else {\n      UI::FocusedMenu = 0;\n    }\n    IsFocused = false;\n    InputEnabled = false;\n  }\n}\n\nvoid MusicMenu::Render() {\n  if (State == Hidden) return;\n  CommonMenu::DrawSubmenu(BackgroundColor, CircleSprite, SoundLibraryTitle,\n                          SoundLibraryTitleAngle);\n\n  if (MenuTransition.Progress <= 0.22f) return;\n\n  glm::vec2 offset = MenuTransition.GetPageOffset();\n  if (MenuTransition.IsPlaying()) {\n    glm::vec2 offsetScroll = glm::vec2(0.0f, -ScrollY) + offset;\n\n    // add a little 1-2 pixel offsets so out of bounds elements won't pass\n    // Intersection check in case they are lying on the bound\n    const RectF bounds =\n        RectF(0.0f, TrackButtonPosTemplate.y + offset.y + 1,\n              Profile::DesignWidth, VisibleItemsPerPage * TrackOffset.y - 2);\n    MainItems->RenderingBounds = bounds;\n    MainItems->HoverBounds = bounds;\n\n    MainScrollbar.MoveTo(ScrollbarPosition + offset);\n\n    MainItems->MoveTo(offsetScroll);\n    for (auto button : MainItems->Children)\n      static_cast<Widgets::CHLCC::TrackSelectButton*>(button)->MoveTracks(\n          offsetScroll);\n  }\n  MainItems->Render();\n  MainScrollbar.Render();\n\n  DrawLeftTitle(SoundLibraryTitle);\n\n  SelectAnimation.Draw(SelectSoundSprites, SelectSoundPos, offset);\n\n  Renderer->DrawSprite(TrackTree, TrackTreePos + offset);\n  Renderer->DrawSprite(PlayModeRepeatSprite, PlaymodeRepeatPos + offset);\n  Renderer->DrawSprite(PlayModeAllSprite, PlaymodeAllPos + offset);\n  glm::vec2 nowPlayingOffset =\n      glm::vec2(15 * 16 * (1 - NowPlayingAnimation.Progress), offset.y) +\n      NowPlayingPos;\n  glm::vec4 tint(glm::vec3(1.0f), NowPlayingAnimation.Progress);\n  CurrentlyPlayingTrackName.MoveTo(nowPlayingOffset + PlayingTrackOffset);\n  CurrentlyPlayingTrackName.Tint = tint;\n  CurrentlyPlayingTrackName.Render();\n  CurrentlyPlayingTrackArtist.MoveTo(nowPlayingOffset +\n                                     PlayingTrackArtistOffset);\n  CurrentlyPlayingTrackArtist.Tint = tint;\n  CurrentlyPlayingTrackArtist.Render();\n  Renderer->DrawSprite(NowPlaying, nowPlayingOffset, tint);\n  if (CurrentlyPlayingTrackId.has_value()) {\n    auto currentTrackId = CurrentlyPlayingTrackId.value();\n    auto targetCenter = MainItems->Children[currentTrackId]->Bounds.Center();\n    auto targetPos = MainItems->Children[currentTrackId]->Bounds.GetPos() +\n                     HighlightStarRelativePos;\n    if (MainItems->RenderingBounds.ContainsPoint(targetCenter)) {\n      Renderer->DrawSprite(HighlightStar, targetPos);\n    }\n  }\n  DrawButtonPrompt(ButtonPromptSprite, ButtonPromptPosition);\n}\n\nvoid MusicMenu::Update(float dt) {\n  if ((!GetFlag(SF_SOUNDMENU) || ScrWork[SW_SYSMENUCT] < 10000) &&\n      State == Shown) {\n    Hide();\n  } else if (GetFlag(SF_SOUNDMENU) && ScrWork[SW_SYSMENUCT] > 0 &&\n             State == Hidden) {\n    Show();\n  }\n\n  if (MenuTransition.IsOut() &&\n      (ScrWork[SW_SYSMENUCT] == 0 || GetFlag(SF_SYSTEMMENU)) &&\n      State == Hiding) {\n    MainItems->Hide();\n    CurrentlyPlayingTrackName.Hide();\n    CurrentlyPlayingTrackArtist.Hide();\n    State = Hidden;\n  } else if (MenuTransition.IsIn() && ScrWork[SW_SYSMENUCT] == 10000 &&\n             State == Showing) {\n    State = Shown;\n    InputEnabled = true;\n  }\n\n  if (State != Hidden) {\n    SelectAnimation.Update(dt);\n    MenuTransition.Update(dt);\n    if (MenuTransition.Direction == AnimationDirection::Out &&\n        MenuTransition.Progress <= 0.72f) {\n      TitleFade.StartOut();\n    } else if (MenuTransition.IsIn() &&\n               (TitleFade.Direction == AnimationDirection::In ||\n                TitleFade.IsOut())) {\n      TitleFade.StartIn();\n    }\n    if (State == Shown) {\n      UpdateInput(dt);\n    }\n    TitleFade.Update(dt);\n    NowPlayingAnimation.Update(dt);\n    MainScrollbar.Update(dt);\n    PlayModeRepeatClickArea.Update(dt);\n    PlayModeAllClickArea.Update(dt);\n    UpdateTitles(SoundLibraryTitleRightPos, SoundLibraryTitleLeftPos);\n    if (InputEnabled) MainItems->Update(dt);\n    if (!CurrentlyPlayingTrackId.has_value()) return;\n    if (PlaybackMode != MusicPlaybackMode::RepeatOne &&\n        CurrentlyPlayingTrackId < 40 &&\n        abs(Audio::Channels[Audio::AC_BGM0]->PositionInSeconds() -\n            PreviousPosition) > 1.0f) {\n      CurrentlyPlayingTrackId.reset();\n      NowPlayingAnimation.StartOut();\n      Audio::Channels[Audio::AC_BGM0]->Stop(2.0f);\n    }\n    if (Audio::Channels[Audio::AC_BGM0]->GetState() == Audio::ACS_Stopped) {\n      std::optional<int> trackId;\n      if (PlaybackMode == MusicPlaybackMode::One) {\n        trackId = std::nullopt;\n      } else {\n        trackId = GetNextTrackId(CurrentlyPlayingTrackId.value() + 1);\n        if (trackId == MusicTrackCount) {\n          if (PlaybackMode == MusicPlaybackMode::RepeatPlaylist) {\n            trackId = GetNextTrackId(0);\n          } else if (PlaybackMode == MusicPlaybackMode::Playlist) {\n            trackId = std::nullopt;\n          }\n        }\n      }\n      SwitchToTrack(trackId);\n    }\n    PreviousPosition = Audio::Channels[Audio::AC_BGM0]->PositionInSeconds();\n  }\n}\n\nvoid MusicMenu::UpdateInput(float dt) {\n  auto* prevFocus = CurrentlyFocusedElement;\n  if (!MainScrollbar.IsScrollHeld()) {\n    Menu::UpdateInput(dt);\n  }\n  if (State == Shown) {\n    const float prevScrollPos = ScrollY;\n    auto checkScrollBounds = [&]() {\n      return !TrackListBounds.Intersects(CurrentlyFocusedElement->Bounds);\n    };\n\n    if (CurrentlyFocusedElement != prevFocus && checkScrollBounds()) {\n      if (CurrentlyFocusedElement == MainItems->GetFirstFocusableChild()) {\n        ScrollY = 0;\n      } else if (CurrentlyFocusedElement == MainItems->Children.back()) {\n        ScrollY = MainScrollbar.EndValue;\n      }\n    }\n\n    // imitate gamepad's d-pad/keyboard controls behavior, when scrolling out of\n    // bounds\n    bool dirButtonPressed =\n        PADinputButtonIsDown & (FDIR_DOWN | FDIR_UP | FDIR_LEFT | FDIR_RIGHT);\n    if (Input::CurrentInputDevice != Input::Device::Mouse && dirButtonPressed) {\n      int currentLowerBound = (int)(ScrollY / TrackOffset.y);\n      int currentUpperBound = currentLowerBound + SelectableItemsPerPage;\n      auto btnId = static_cast<Widgets::CHLCC::TrackSelectButton*>(\n                       CurrentlyFocusedElement)\n                       ->Id;\n      if (btnId == currentLowerBound && btnId != 0) {\n        ScrollY -= TrackOffset.y;\n      }\n\n      if (btnId == currentUpperBound && btnId != MusicTrackCount - 1) {\n        ScrollY += TrackOffset.y;\n      }\n      MainScrollbar.ClampValue();\n    }\n\n    MainScrollbar.UpdateInput(dt);\n    PlayModeRepeatClickArea.UpdateInput(dt);\n    PlayModeAllClickArea.UpdateInput(dt);\n\n    if (prevScrollPos != ScrollY) {\n      float delta = prevScrollPos - ScrollY;\n      if (std::fmod(std::abs(delta), TrackOffset.y) >\n          std::numeric_limits<float>::epsilon()) {\n        const float newDelta =\n            std::round(delta / TrackOffset.y) * TrackOffset.y;\n        ScrollY = prevScrollPos - newDelta;\n        delta = newDelta;\n      }\n      glm::vec2 itemsPos = {0, -ScrollY};\n      MainItems->MoveTo(itemsPos);\n      for (auto child : MainItems->Children) {\n        auto button = static_cast<Widgets::CHLCC::TrackSelectButton*>(child);\n        button->MoveTracks(itemsPos);\n      }\n\n      if (MainScrollbar.IsScrollHeld() && CurrentlyFocusedElement) {\n        // advance focus during drag\n        FocusDirection dir = (delta < 0) ? FDIR_DOWN : FDIR_UP;\n        int steps = static_cast<int>(std::abs(delta) / TrackOffset.y);\n        for (int i = 0; i < steps; i++) {\n          AdvanceFocus(dir);\n        }\n      }\n    }\n\n    if (!MainScrollbar.IsScrollHeld()) MainItems->UpdateInput(dt);\n\n    if (PADinputButtonWentDown & PAD1Y) {\n      ++PlaybackMode;\n      PlayModeAllSprite =\n          isPlaylist(PlaybackMode) ? PlaymodeAllHighlight : PlaymodeAll;\n      PlayModeRepeatSprite =\n          isRepeat(PlaybackMode) ? PlaymodeRepeatHighlight : PlaymodeRepeat;\n      UpdateLooping();\n    }\n\n    if (PADinputButtonWentDown & PAD1X) {\n      SwitchToTrack(std::nullopt);\n    }\n  }\n}\n\nvoid MusicMenu::UpdateEntries() {\n  auto onClick = [this](auto* btn) { return MusicButtonOnClick(btn); };\n  for (size_t idx = 0; idx < MainItems->Children.size(); idx++) {\n    auto button = static_cast<Widgets::CHLCC::TrackSelectButton*>(\n        MainItems->Children[idx]);\n\n    if (!SaveSystem::GetBgmFlag(Playlist[button->Id])) {\n      button->SetTrackText(Vm::ScriptGetTextTableStrAddress(0, 15));\n      continue;\n    }\n\n    button->OnClickHandler = onClick;\n    button->SetTrackText(Vm::ScriptGetTextTableStrAddress(4, (int)idx * 3));\n    button->SetArtistText(\n        Vm::ScriptGetTextTableStrAddress(4, (int)idx * 3 + 1));\n  }\n}\n\nvoid MusicMenu::SwitchToTrack(std::optional<int> id) {\n  if (CurrentlyPlayingTrackId == id) {\n    return;\n  }\n\n  CurrentlyPlayingTrackId = id;\n  if (!id.has_value()) {\n    NowPlayingAnimation.StartOut();\n    Audio::Channels[Audio::AC_BGM0]->Stop(0.5f);\n    return;\n  }\n  if (!NowPlayingAnimation.IsIn()) NowPlayingAnimation.StartIn();\n  CurrentlyPlayingTrackName = Label(\n      Vm::ScriptGetTextTableStrAddress(4, CurrentlyPlayingTrackId.value() * 3),\n      NowPlayingPos + PlayingTrackOffset, 32, RendererOutlineMode::None, 0);\n  CurrentlyPlayingTrackArtist =\n      Label(Vm::ScriptGetTextTableStrAddress(\n                4, CurrentlyPlayingTrackId.value() * 3 + 2),\n            NowPlayingPos + PlayingTrackArtistOffset, 20,\n            RendererOutlineMode::None, 0);\n  PreviousPosition = 0.0f;\n  Audio::Channels[Audio::AC_BGM0]->Play(\n      \"bgm\", Playlist[id.value()],\n      id >= 40 ? (PlaybackMode == MusicPlaybackMode::RepeatOne) : true, 0.5f);\n}\n\ninline int MusicMenu::GetNextTrackId(int id) {\n  while (!SaveSystem::GetBgmFlag(Playlist[id])) {\n    id += 1;\n    if (id == MusicTrackCount) {\n      if (PlaybackMode == MusicPlaybackMode::RepeatPlaylist) {\n        id = 0;\n      } else if (PlaybackMode == MusicPlaybackMode::Playlist) {\n        id = -1;\n        break;\n      }\n    }\n  }\n  return id;\n}\n\n}  // namespace CHLCC\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/chlcc/musicmenu.h",
    "content": "#pragma once\n\n#include \"commonmenu.h\"\n\n#include \"../../ui/menu.h\"\n#include \"../../util.h\"\n#include \"../../ui/widgets/group.h\"\n#include \"../../ui/widgets/clickarea.h\"\n#include \"../../ui/widgets/button.h\"\n#include \"../../ui/widgets/label.h\"\n#include \"../../ui/widgets/scrollbar.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace CHLCC {\n\nenum class MusicPlaybackMode : uint8_t {\n  One = 0b00,\n  Playlist = 0b01,\n  RepeatOne = 0b10,\n  RepeatPlaylist = 0b11,\n};\n\nconstexpr MusicPlaybackMode operator&(MusicPlaybackMode mode,\n                                      MusicPlaybackMode other) {\n  return static_cast<MusicPlaybackMode>(to_underlying(mode) &\n                                        to_underlying(other));\n}\n\nconstexpr MusicPlaybackMode& operator++(MusicPlaybackMode& mode) {\n  mode = static_cast<MusicPlaybackMode>(\n      (+mode + 1) % magic_enum::enum_count<MusicPlaybackMode>());\n  return mode;\n}\n\nconstexpr MusicPlaybackMode operator^(MusicPlaybackMode mode,\n                                      MusicPlaybackMode other) {\n  return static_cast<MusicPlaybackMode>(to_underlying(mode) ^\n                                        to_underlying(other));\n}\n\nconstexpr MusicPlaybackMode& operator^=(MusicPlaybackMode& mode,\n                                        MusicPlaybackMode other) {\n  mode = mode ^ other;\n  return mode;\n}\n\nclass MusicMenu : public Menu, public CommonMenu {\n public:\n  MusicMenu();\n\n  void Init() override;\n  void Show() override;\n  void Hide() override;\n  void UpdateInput(float dt) override;\n  void Update(float dt) override;\n  void Render() override;\n\n private:\n  Widgets::Group* MainItems;\n\n  void UpdateEntries();\n  void MusicButtonOnClick(Widgets::Button* target);\n  void SwitchToTrack(std::optional<int> id);\n  int GetNextTrackId(int id);\n  void ToggleRepeatMode();\n  void ToggleAllMode();\n  void UpdateLooping();\n\n  Animation NowPlayingAnimation;\n\n  Sprite PlayModeRepeatSprite;\n  Sprite PlayModeAllSprite;\n\n  Widgets::Label CurrentlyPlayingTrackName;\n  Widgets::Label CurrentlyPlayingTrackArtist;\n\n  Widgets::Scrollbar MainScrollbar;\n\n  Widgets::ClickArea PlayModeRepeatClickArea;\n  Widgets::ClickArea PlayModeAllClickArea;\n\n  std::optional<int> CurrentlyPlayingTrackId = std::nullopt;\n  float PreviousPosition = 0.0f;\n\n  float ScrollY = 0.0f;\n\n  bool InputEnabled = false;\n  MusicPlaybackMode PlaybackMode = MusicPlaybackMode::One;\n};\n\n}  // namespace CHLCC\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/chlcc/optionsmenu.cpp",
    "content": "#include \"optionsmenu.h\"\n\n#include \"../../ui/widgets/chlcc/optionsbutton.h\"\n#include \"../../ui/widgets/chlcc/optionsslider.h\"\n\n#include \"../../renderer/renderer.h\"\n#include \"../../mem.h\"\n#include \"../../profile/scriptvars.h\"\n#include \"../../profile/ui/backlogmenu.h\"\n#include \"../../profile/ui/optionsmenu.h\"\n#include \"../../profile/games/chlcc/optionsmenu.h\"\n#include \"../../profile/games/chlcc/commonmenu.h\"\n#include \"../../data/tipssystem.h\"\n#include \"../../vm/interface/input.h\"\n#include \"../../inputsystem.h\"\n#include \"../../profile/game.h\"\n\n#include \"../../profile/configsystem.h\"\n#include \"../../audio/audiosystem.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace CHLCC {\n\nusing namespace Impacto::Profile::CHLCC::CommonMenu;\nusing namespace Impacto::Profile::OptionsMenu;\nusing namespace Impacto::Profile::CHLCC::OptionsMenu;\nusing namespace Impacto::Profile::ScriptVars;\nusing namespace Impacto::Profile::ConfigSystem;\n\nusing namespace Impacto::Vm::Interface;\n\nusing namespace Impacto::UI::Widgets;\nusing namespace Impacto::UI::Widgets::CHLCC;\n\nstatic std::unique_ptr<Widgets::Group> CreateTextPage(\n    OptionsMenu* const menu,\n    const std::function<void(OptionsEntry*)>& highlight) {\n  std::unique_ptr<Group> textPage = std::make_unique<Group>(menu);\n  RectF highlightBounds(0.0f, 0.0f, SelectedLabelSprite.ScaledWidth(),\n                        SelectedLabelSprite.ScaledHeight());\n\n  const auto addButton =\n      [&]<typename T>(\n          size_t id, T& value, std::span<const T> values,\n          std::span<const std::reference_wrapper<const Sprite>> sprites) {\n        highlightBounds.X = TextPageEntryPositions[id].x;\n        highlightBounds.Y = TextPageEntryPositions[id].y;\n        textPage->Add(\n            new OptionsButton<T>(value, values, sprites,\n                                 highlightBounds.GetPos() +\n                                     glm::vec2(highlightBounds.Width, 0.0f),\n                                 highlightBounds, highlight),\n            FDIR_DOWN);\n      };\n\n  // Basic settings\n  {\n    constexpr static std::array<std::reference_wrapper<const Sprite>, 2>\n        sprites{SettingDoSprite, SettingDontSprite};\n    addButton(0, TriggerStopSkip, std::span<const bool>(TriggerStopSkipValues),\n              sprites);\n  }\n  {\n    constexpr static std::array<std::reference_wrapper<const Sprite>, 2>\n        sprites{SettingDoSprite, SettingDontSprite};\n    addButton(1, ShowTipsNotification,\n              std::span<const bool>(ShowTipsNotificationValues), sprites);\n  }\n  {\n    constexpr static std::array<std::reference_wrapper<const Sprite>, 4>\n        sprites{SettingOnTriggerAndSceneSprite, SettingDontSprite,\n                SettingOnTriggerSprite, SettingOnSceneSprite};\n    addButton(2, AutoQuickSave, std::span<const uint8_t>(AutoQuickSaveValues),\n              sprites);\n  }\n  {\n    constexpr static std::array<std::reference_wrapper<const Sprite>, 2>\n        sprites{SettingTypeASprite, SettingTypeBSprite};\n    addButton(3, ControllerType, std::span<const uint8_t>(ControllerTypeValues),\n              sprites);\n  }\n\n  highlightBounds.X = TextPageEntryPositions[4].x;\n  highlightBounds.Y = TextPageEntryPositions[4].y;\n  textPage->Add(\n      new OptionsSlider(\n          ImageSize, 0.0f, 1.0f, SliderBarBaseSprite, SliderBarFillSprite,\n          highlightBounds.GetPos() + glm::vec2(highlightBounds.Width, 0.0f),\n          highlightBounds, highlight),\n      FDIR_DOWN);\n\n  // Text settings\n  {\n    constexpr static std::array<std::reference_wrapper<const Sprite>, 4>\n        sprites{SettingNormalSprite, SettingFastSprite, SettingInstantSprite,\n                SettingSlowSprite};\n    addButton(5, TextSpeed, std::span<const float>(TextSpeedValues), sprites);\n  }\n  {\n    constexpr static std::array<std::reference_wrapper<const Sprite>, 3>\n        sprites{SettingNormalSprite, SettingFastSprite, SettingSlowSprite};\n    addButton(6, AutoSpeed, std::span<const float>(AutoSpeedValues), sprites);\n  }\n  {\n    constexpr static std::array<std::reference_wrapper<const Sprite>, 2>\n        sprites{SettingReadSprite, SettingAllSprite};\n    addButton(7, SkipRead, std::span<const bool>(SkipReadValues), sprites);\n  }\n\n  return textPage;\n}\n\nstatic std::unique_ptr<Widgets::Group> CreateSoundPage(\n    OptionsMenu* const menu,\n    const std::function<void(OptionsEntry*)>& highlight) {\n  std::unique_ptr<Group> soundPage = std::make_unique<Group>(menu);\n  RectF highlightBounds(0.0f, 0.0f, SelectedLabelSprite.ScaledWidth(),\n                        SelectedLabelSprite.ScaledHeight());\n\n  const auto addButton =\n      [&]<typename T>(\n          size_t id, T& value, std::span<const T> values,\n          std::span<const std::reference_wrapper<const Sprite>> sprites) {\n        highlightBounds.X = SoundPageEntryPositions[id].x;\n        highlightBounds.Y = SoundPageEntryPositions[id].y;\n        const glm::vec2 topRight =\n            highlightBounds.GetPos() + glm::vec2(highlightBounds.Width, 0.0f);\n        soundPage->Add(new OptionsButton<T>(value, values, sprites, topRight,\n                                            highlightBounds, highlight),\n                       FDIR_DOWN);\n      };\n  const auto addSlider = [&](size_t id, float& value, float min, float max) {\n    highlightBounds.X = SoundPageEntryPositions[id].x;\n    highlightBounds.Y = SoundPageEntryPositions[id].y;\n    const glm::vec2 topRight =\n        highlightBounds.GetPos() + glm::vec2(highlightBounds.Width, 0.0f);\n    soundPage->Add(new OptionsSlider(value, min, max, SliderBarBaseSprite,\n                                     SliderBarFillSprite, topRight,\n                                     highlightBounds, highlight),\n                   FDIR_DOWN);\n  };\n\n  addSlider(0, Audio::GroupVolumes[Audio::ACG_Voice], 0.0f, 1.0f);\n  addSlider(1, Audio::GroupVolumes[Audio::ACG_BGM], 0.0f, 0.5f);\n  addSlider(2, Audio::GroupVolumes[Audio::ACG_SE], 0.0f, 1.0f);\n  addSlider(3, Audio::GroupVolumes[Audio::ACG_Movie], 0.0f, 1.0f);\n  {\n    constexpr static std::array<std::reference_wrapper<const Sprite>, 2>\n        sprites{SettingDoSprite, SettingDontSprite};\n    addButton(4, SyncVoice, std::span<const bool>(SyncVoiceValues), sprites);\n  }\n  {\n    constexpr static std::array<std::reference_wrapper<const Sprite>, 2>\n        sprites{SettingDontSprite, SettingDoSprite};\n    addButton(5, SkipVoice, std::span<const bool>(SkipVoiceValues), sprites);\n  }\n\n  return soundPage;\n}\n\nstatic std::unique_ptr<Widgets::Group> CreateVoicePage(\n    OptionsMenu* const menu,\n    const std::function<void(OptionsEntry*)>& highlight) {\n  std::unique_ptr<Group> voicePage = std::make_unique<Group>(menu);\n  RectF highlightBounds(0.0f, 0.0f, SelectedLabelSprite.ScaledWidth(),\n                        SelectedLabelSprite.ScaledHeight());\n\n  for (size_t i = 0; i < VoicePageEntryPositions.size(); i++) {\n    highlightBounds.X = VoicePageEntryPositions[i].x;\n    highlightBounds.Y = VoicePageEntryPositions[i].y;\n    const glm::vec2 topRight =\n        highlightBounds.GetPos() + glm::vec2(highlightBounds.Width, 0.0f);\n\n    voicePage->Add(\n        new OptionsSlider(Profile::ConfigSystem::VoiceVolume[i], 0.0f, 1.0f,\n                          SliderBarBaseSprite, SliderBarFillSprite, topRight,\n                          highlightBounds, highlight, VoiceMutedSprite),\n        FDIR_DOWN);\n  }\n\n  return voicePage;\n}\n\nOptionsMenu::OptionsMenu() : UI::OptionsMenu(), CommonMenu(true) {\n  RememberLastPage = true;\n  RememberHighlightedEntries = true;\n\n  PageTransitionAnimation.SetDuration(PageTransitionDuration);\n  PageTransitionAnimation.SkipOnSkipMode = false;\n\n  SelectedAnimation.DurationIn = SelectedSlideDuration;\n  SelectedAnimation.LoopMode = AnimationLoopMode::Loop;\n  SelectedAnimation.SkipOnSkipMode = false;\n  SelectedAnimation.StartIn();\n\n  std::function<void(OptionsEntry*)> highlight = [this](auto* entry) {\n    return Highlight(entry);\n  };\n\n  Pages.reserve(3);\n  Pages.emplace_back(CreateTextPage(this, highlight));\n  Pages.emplace_back(CreateSoundPage(this, highlight));\n  Pages.emplace_back(CreateVoicePage(this, highlight));\n\n  HighlightedEntriesPerPage.reserve(Pages.size());\n  for (std::unique_ptr<Group>& page : Pages) {\n    HighlightedEntriesPerPage.push_back(page->GetFirstFocusableChild());\n  }\n\n  SelectedLabelPos = HighlightedEntriesPerPage[CurrentPage]->Bounds.GetPos();\n}\n\nvoid OptionsMenu::Show() {\n  if (State == Hidden) {\n    MenuTransition.StartIn();\n    FromSystemMenuTransition->StartIn();\n  }\n\n  UI::OptionsMenu::Show();\n}\n\nvoid OptionsMenu::Hide() {\n  if (State == Shown) {\n    SetFlag(SF_SUBMENUEXIT, true);\n    MenuTransition.StartOut();\n    FromSystemMenuTransition->StartOut();\n  }\n\n  UI::OptionsMenu::Hide();\n\n  if (State == Hiding) {\n    Pages[CurrentPage]->VisibilityState = Shown;\n  }\n}\n\nvoid OptionsMenu::RenderPage(const size_t pageId, const glm::vec2 offset) {\n  switch (pageId) {\n    case static_cast<int>(PageType::Text):\n      Renderer->DrawSprite(BasicSettingsSprite, BasicSettingsPos + offset);\n      Renderer->DrawSprite(TextSettingsSprite, TextSettingsPos + offset);\n      break;\n    case static_cast<int>(PageType::Sound):\n      Renderer->DrawSprite(SoundSettingsSprite, SoundSettingsPos + offset);\n      break;\n    case static_cast<int>(PageType::Voice):\n      Renderer->DrawSprite(VoiceSettingsSprite, VoiceSettingsPos + offset);\n      break;\n    default:\n      ImpLogSlow(LogLevel::Warning, LogChannel::General,\n                 \"Unexpected options menu page {:d}\", pageId);\n      break;\n  }\n\n  Pages[pageId]->Move(offset);\n  Pages[pageId]->Render();\n  Pages[pageId]->Move(-offset);\n}\n\nvoid OptionsMenu::Render() {\n  if (State == Hidden) return;\n  CommonMenu::DrawSubmenu(BackgroundColor, CircleSprite, MenuTitleText,\n                          MenuTitleTextAngle);\n  if (FadeAnimation.Progress < 0.22f) return;\n\n  DrawButtonPrompt(ButtonPromptSprite, ButtonPromptPosition);\n\n  glm::vec2 showPageOffset = MenuTransition.GetPageOffset();\n  if ((CurrentlyFocusedElement != nullptr || !MenuTransition.IsIn()) &&\n      PageTransitionAnimation.IsStopped()) {\n    for (float x = SelectedSprite.ScaledWidth() * -SelectedAnimation.Progress;\n         x < Profile::DesignWidth; x += SelectedSprite.ScaledWidth()) {\n      Renderer->DrawSprite(SelectedSprite,\n                           glm::vec2(x, SelectedLabelPos.y) + showPageOffset);\n    }\n\n    Renderer->DrawSprite(SelectedLabelSprite,\n                         SelectedLabelPos + showPageOffset);\n    const glm::vec2 dotOffset =\n        CurrentPage == static_cast<size_t>(PageType::Voice)\n            ? SelectedDotVoicesOffset\n            : SelectedDotOffset;\n    Renderer->DrawSprite(SelectedDotSprite,\n                         SelectedLabelPos + dotOffset + showPageOffset);\n  }\n\n  if (PageTransitionAnimation.IsStopped()) {\n    RenderPage(CurrentPage, showPageOffset);\n  } else {\n    Pages[PreviousPage]->VisibilityState = Shown;\n    RenderPage(PreviousPage, PageTransitionGoingOffset + showPageOffset);\n    RenderPage(CurrentPage, PageTransitionComingOffset + showPageOffset);\n  }\n}\n\nvoid OptionsMenu::UpdatePageInput(float dt) {\n  UI::OptionsMenu::UpdatePageInput(dt);\n\n  if (PageTransitionAnimation.IsPlaying()) return;\n\n  if (Input::MouseWheelDeltaY > 0.0f) {\n    GoToPage((CurrentPage + Pages.size() - 1) % Pages.size());\n  } else if (Input::MouseWheelDeltaY < 0.0f) {\n    GoToPage((CurrentPage + 1) % Pages.size());\n  }\n}\n\nvoid OptionsMenu::UpdateVisibility() {\n  const int sysMenuCt = ScrWork[SW_SYSMENUCT];\n  const int systemMenuCHG = ScrWork[SW_SYSTEMMENUCHG];\n\n  if ((!GetFlag(SF_OPTIONMENU) || sysMenuCt < 10000 ||\n       (sysMenuCt == 10000 && systemMenuCHG != 0 && systemMenuCHG != 64)) &&\n      State == Shown) {\n    Hide();\n  } else if (GetFlag(SF_OPTIONMENU) && sysMenuCt > 0 && State == Hidden) {\n    Show();\n  }\n\n  if (FadeAnimation.IsOut() && !GetFlag(SF_OPTIONMENU) && systemMenuCHG == 0 &&\n      (sysMenuCt == 0 || GetFlag(SF_SYSTEMMENU)) && State == Hiding) {\n    State = Hidden;\n  } else if (FadeAnimation.IsIn() && sysMenuCt == 10000 &&\n             (systemMenuCHG == 0 || systemMenuCHG == 64) &&\n             GetFlag(SF_OPTIONMENU) && State == Showing) {\n    State = Shown;\n  }\n}\n\nvoid OptionsMenu::UpdateSelectedLabel(float dt) {\n  SelectedAnimation.Update(dt);\n\n  if (CurrentlyFocusedElement == nullptr ||\n      SelectedLabelPos.y == CurrentlyFocusedElement->Bounds.Y)\n    return;\n\n  const float currentY = SelectedLabelPos.y;\n  const float targetY = CurrentlyFocusedElement->Bounds.Y;\n\n  // Not exactly how the binary does it, but this is smoother\n  const float entriesLeft =\n      std::abs(currentY - targetY) / SelectedLabelModalDistancePerEntry;\n  const float speedMultiplier =\n      1.0f + 3.0f * glm::smoothstep(0.0f, 4.0f, entriesLeft);\n  const float pixelsMoved = SelectedLabelBaseSpeed * speedMultiplier * dt;\n\n  if (SelectedLabelPos.y < CurrentlyFocusedElement->Bounds.Y) {\n    SelectedLabelPos.y = std::min(SelectedLabelPos.y + pixelsMoved,\n                                  CurrentlyFocusedElement->Bounds.Y);\n  } else {\n    SelectedLabelPos.y = std::max(SelectedLabelPos.y - pixelsMoved,\n                                  CurrentlyFocusedElement->Bounds.Y);\n  }\n}\n\nvoid OptionsMenu::UpdatePageTransitionAnimation(float dt) {\n  PageTransitionAnimation.Update(dt);\n\n  if (PageTransitionAnimation.IsStopped()) return;\n\n  constexpr glm::vec2 anchor = {1.0f, 0.0f};\n\n  float angle = (1.0f - PageTransitionAnimation.Progress) * PageRotationAngle;\n  PageTransitionComingOffset =\n      (glm::vec2(std::cos(angle), std::sin(angle)) - anchor) *\n      Profile::DesignHeight;\n\n  angle = -PageTransitionAnimation.Progress * PageRotationAngle;\n  PageTransitionGoingOffset =\n      (glm::vec2(std::cos(angle), std::sin(angle)) - anchor) *\n      Profile::DesignHeight;\n\n  if (PageTransitionAnimation.Direction == AnimationDirection::Out) {\n    std::swap(PageTransitionGoingOffset, PageTransitionComingOffset);\n  }\n}\n\nvoid OptionsMenu::Update(float dt) {\n  UI::OptionsMenu::Update(dt);\n\n  if (State != Hidden) {\n    FromSystemMenuTransition->Update(dt);\n    if (FadeAnimation.Direction == AnimationDirection::Out &&\n        FadeAnimation.Progress <= 0.72f) {\n      TitleFade.StartOut();\n      FromSystemMenuTransition->StartOut();\n    } else if (FadeAnimation.IsIn() &&\n               (TitleFade.Direction == AnimationDirection::In ||\n                TitleFade.IsOut())) {\n      TitleFade.StartIn();\n      FromSystemMenuTransition->StartIn();\n    }\n    TitleFade.Update(dt);\n\n    UpdateTitles(MenuTitleTextRightPosition, glm::vec2(0));\n    MenuTransition.Update(dt);\n    UpdatePageTransitionAnimation(dt);\n    UpdateSelectedLabel(dt);\n  }\n}\n\nvoid OptionsMenu::UpdateValues() {\n  for (std::unique_ptr<Group>& page : Pages) {\n    for (Widget* entry : page->Children) {\n      static_cast<OptionsEntry*>(entry)->UpdateValue();\n    }\n  }\n}\n\nvoid OptionsMenu::UpdateInput(float dt) {\n  if (State != Shown || PageTransitionAnimation.IsPlaying()) return;\n\n  UI::OptionsMenu::UpdateInput(dt);\n}\n\nvoid OptionsMenu::GoToPage(size_t pageNumber) {\n  if (pageNumber == CurrentPage || PageTransitionAnimation.IsPlaying()) return;\n\n  PreviousPage = CurrentPage;\n  UI::OptionsMenu::GoToPage(pageNumber);\n\n  AnimationDirection direction = PreviousPage > CurrentPage\n                                     ? AnimationDirection::In\n                                     : AnimationDirection::Out;\n  if (PreviousPage == Pages.size() - 1 && CurrentPage == 0)\n    direction = AnimationDirection::Out;\n  else if (PreviousPage == 0 && CurrentPage == Pages.size() - 1)\n    direction = AnimationDirection::In;\n\n  PageTransitionAnimation.Start(direction, true);\n}\n\nvoid OptionsMenu::ResetToDefault() {\n  switch (CurrentPage) {\n    case static_cast<size_t>(PageType::Text): {\n      TriggerStopSkip = Default::TriggerStopSkip;\n      ShowTipsNotification = Default::ShowTipsNotification;\n      AutoQuickSave = Default::AutoQuickSave;\n      ControllerType = Default::ControllerType;\n      ImageSize = Default::ImageSize;\n\n      TextSpeed = Default::TextSpeed;\n      AutoSpeed = Default::AutoSpeed;\n      SkipRead = Default::SkipRead;\n    } break;\n\n    case static_cast<size_t>(PageType::Sound): {\n      std::ranges::copy(Default::GroupVolumes, Audio::GroupVolumes.begin());\n      SyncVoice = Default::SyncVoice;\n      SkipVoice = Default::SkipVoice;\n    } break;\n\n    case static_cast<size_t>(PageType::Voice): {\n      std::ranges::copy(Default::VoiceVolume, VoiceVolume.begin());\n    } break;\n\n    default:\n      break;\n  }\n\n  UpdateValues();\n}\n\n}  // namespace CHLCC\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/chlcc/optionsmenu.h",
    "content": "#pragma once\n\n#include \"commonmenu.h\"\n\n#include \"../../ui/optionsmenu.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace CHLCC {\n\nclass OptionsMenu : public UI::OptionsMenu, public CommonMenu {\n public:\n  OptionsMenu();\n\n  void Show() override;\n  void Hide() override;\n  void Update(float dt) override;\n  void UpdateInput(float dt) override;\n  void Render() override;\n  void ResetToDefault() override;\n\n private:\n  void RenderPage(size_t pageId, glm::vec2 offset);\n  void GoToPage(size_t pageNumber) override;\n\n  void UpdatePageInput(float dt) override;\n  void UpdateValues() override;\n\n  void UpdatePageShowAnimation(float dt);\n  void UpdatePageTransitionAnimation(float dt);\n  void UpdateSelectedLabel(float dt);\n\n  void UpdateVisibility() override;\n\n  size_t PreviousPage = 0;\n\n  Animation PageTransitionAnimation;\n  glm::vec2 PageTransitionComingOffset{0.0f, 0.0f};\n  glm::vec2 PageTransitionGoingOffset{0.0f, 0.0f};\n\n  Animation SelectedAnimation;\n  glm::vec2 SelectedLabelPos;\n\n  enum class PageType { Text = 0, Sound = 1, Voice = 2 };\n};\n\n}  // namespace CHLCC\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/chlcc/savemenu.cpp",
    "content": "#include \"savemenu.h\"\n\n#include \"../../profile/ui/savemenu.h\"\n#include \"../../profile/games/chlcc/savemenu.h\"\n#include \"../../profile/games/chlcc/commonmenu.h\"\n#include \"../../renderer/renderer.h\"\n#include \"../../mem.h\"\n#include \"../../profile/scriptvars.h\"\n#include \"../../inputsystem.h\"\n#include \"../../vm/interface/input.h\"\n#include \"../../ui/widgets/button.h\"\n#include \"../../ui/widgets/chlcc/saveentrybutton.h\"\n#include \"../../data/savesystem.h\"\n#include \"../../profile/game.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace CHLCC {\n\nusing namespace Impacto::Profile::SaveMenu;\nusing namespace Impacto::Profile::CHLCC::SaveMenu;\nusing namespace Impacto::Profile::CHLCC::CommonMenu;\nusing namespace Impacto::Profile::ScriptVars;\n\nusing namespace Impacto::UI::Widgets::CHLCC;\n\nWidget* EntryGrid[EntriesPerPage];\n\nvoid SaveMenu::MenuButtonOnClick(Widgets::Button* target) {\n  if ((SaveSystem::GetSaveStatus(EntryType, target->Id) == 1) ||\n      *ActiveMenuType == SaveMenuPageType::Save) {\n    ScrWork[SW_SAVEFILENO] = target->Id;\n    ScrWork[SW_SAVEFILESTATUS] =\n        SaveSystem::GetSaveStatus(EntryType, ScrWork[SW_SAVEFILENO]);\n\n    SetFlag(SF_SAVEPROTECTED,\n            SaveSystem::GetSaveFlags(EntryType, ScrWork[SW_SAVEFILENO]) &\n                SaveSystem::SaveFlagsMode::WriteProtect);\n    ChoiceMade = true;\n  }\n}\n\nSaveMenu::SaveMenu() : UI::SaveMenu(), CommonMenu(true) {\n  auto onClick = [this](auto* btn) { return MenuButtonOnClick(btn); };\n\n  // Quick Save Pages initialization\n\n  for (int i = 0; i < Pages; i++) {\n    Widgets::Group* mainItems = new Widgets::Group(this);\n    mainItems->WrapFocus = false;\n\n    for (int j = 0; j < EntriesPerPage; j++) {\n      SaveEntryButton* saveEntryButton =\n          new SaveEntryButton(i * EntriesPerPage + j, QuickLoadEntrySprite,\n                              EntryHighlightedSprite, QuickLoadEntrySprite,\n                              EntryPositions[j], i, false, LockedSymbolSprite);\n      saveEntryButton->OnClickHandler = onClick;\n      saveEntryButton->AddThumbnail(EmptyThumbnailSprite, ThumbnailRelativePos);\n      mainItems->Add(saveEntryButton);\n      EntryGrid[j] = saveEntryButton;\n    }\n\n    // Yes I know this is bad...\n    for (int k = 0; k < EntriesPerPage - 2; k++) {\n      if (k % 3 == 0) {\n        EntryGrid[k]->SetFocus(EntryGrid[(k % 2) + 4], FDIR_RIGHT);\n        EntryGrid[k + 1]->SetFocus(EntryGrid[(k % 2) + 4], FDIR_RIGHT);\n        EntryGrid[(k % 2) + 4]->SetFocus(EntryGrid[k], FDIR_LEFT);\n      }\n      EntryGrid[k]->SetFocus(EntryGrid[(k + 1) % 4], FDIR_DOWN);\n      EntryGrid[(k + 1) % 4]->SetFocus(EntryGrid[k], FDIR_UP);\n    }\n    EntryGrid[4]->SetFocus(EntryGrid[5], FDIR_DOWN);\n    EntryGrid[4]->SetFocus(EntryGrid[5], FDIR_UP);\n    EntryGrid[5]->SetFocus(EntryGrid[4], FDIR_UP);\n    EntryGrid[5]->SetFocus(EntryGrid[4], FDIR_DOWN);\n\n    QuickSavePages.push_back(mainItems);\n  }\n  // Maintaining focus across pages\n  for (auto pageItr = QuickSavePages.begin(); pageItr != QuickSavePages.end();\n       pageItr++) {\n    auto prevItr = pageItr;\n    auto nextItr = pageItr;\n    // Page 1 leads to page 8 to the left\n    if (pageItr == QuickSavePages.begin()) {\n      prevItr = QuickSavePages.end();\n    }\n    prevItr--;\n    // Page 8 leads to page 1 to the left\n    if (pageItr == --QuickSavePages.end()) {\n      nextItr = QuickSavePages.begin();\n    } else {\n      nextItr++;\n    }\n    Widgets::Group* curr = *pageItr;\n    Widgets::Group* prev = *prevItr;\n    Widgets::Group* next = *nextItr;\n    for (int k = 0; k < EntriesPerPage - 2; k++) {\n      curr->Children[k]->SetFocus(prev->Children[4 + k / 2], FDIR_LEFT);\n    }\n    curr->Children[4]->SetFocus(next->Children[0], FDIR_RIGHT);\n    curr->Children[5]->SetFocus(next->Children[3], FDIR_RIGHT);\n  }\n\n  // Full Save Pages initialization\n\n  for (int i = 0; i < Pages; i++) {\n    Widgets::Group* mainItems = new Widgets::Group(this);\n    mainItems->WrapFocus = false;\n\n    for (int j = 0; j < EntriesPerPage; j++) {\n      SaveEntryButton* saveEntryButton = new SaveEntryButton(\n          i * EntriesPerPage + j, SaveEntrySprite, EntryHighlightedSprite,\n          SaveEntrySprite, EntryPositions[j], i, true, LockedSymbolSprite);\n      saveEntryButton->OnClickHandler = onClick;\n      mainItems->Add(saveEntryButton);\n      EntryGrid[j] = saveEntryButton;\n    }\n\n    // Yes I know this is bad...\n    for (int k = 0; k < EntriesPerPage - 2; k++) {\n      if (k % 3 == 0) {\n        EntryGrid[k]->SetFocus(EntryGrid[(k % 2) + 4], FDIR_RIGHT);\n        EntryGrid[k + 1]->SetFocus(EntryGrid[(k % 2) + 4], FDIR_RIGHT);\n        EntryGrid[(k % 2) + 4]->SetFocus(EntryGrid[k], FDIR_LEFT);\n      }\n      EntryGrid[k]->SetFocus(EntryGrid[(k + 1) % 4], FDIR_DOWN);\n      EntryGrid[(k + 1) % 4]->SetFocus(EntryGrid[k], FDIR_UP);\n    }\n    EntryGrid[4]->SetFocus(EntryGrid[5], FDIR_DOWN);\n    EntryGrid[4]->SetFocus(EntryGrid[5], FDIR_UP);\n    EntryGrid[5]->SetFocus(EntryGrid[4], FDIR_UP);\n    EntryGrid[5]->SetFocus(EntryGrid[4], FDIR_DOWN);\n\n    FullSavePages.push_back(mainItems);\n  }\n\n  // Maintaining focus across pages\n  for (auto pageItr = FullSavePages.begin(); pageItr != FullSavePages.end();\n       pageItr++) {\n    auto prevItr = pageItr;\n    auto nextItr = pageItr;\n    // Page 1 leads to page 8 to the left\n    if (pageItr == FullSavePages.begin()) {\n      prevItr = FullSavePages.end();\n    }\n    prevItr--;\n    // Page 8 leads to page 1 to the right\n    if (pageItr == --FullSavePages.end()) {\n      nextItr = FullSavePages.begin();\n    } else {\n      nextItr++;\n    }\n    Widgets::Group* curr = *pageItr;\n    Widgets::Group* prev = *prevItr;\n    Widgets::Group* next = *nextItr;\n    for (int k = 0; k < EntriesPerPage - 2; k++) {\n      curr->Children[k]->SetFocus(prev->Children[4 + k / 2], FDIR_LEFT);\n    }\n    curr->Children[4]->SetFocus(next->Children[0], FDIR_RIGHT);\n    curr->Children[5]->SetFocus(next->Children[3], FDIR_RIGHT);\n  }\n}\n\nvoid SaveMenu::Init() {\n  SetFlag(SF_SAVEPROTECTCHANGED, 0);\n\n  switch (*ActiveMenuType) {\n    case SaveMenuPageType::QuickLoad:\n      EntryType = SaveSystem::SaveType::Quick;\n      SavePages = &QuickSavePages;\n      BackgroundColor = QuickLoadBackgroundColor;\n      CircleSprite = QuickLoadCircle;\n      MenuTitleTextSprite = QuickLoadTextSprite;\n      CurrentPage = &CurrentQuickSavePage;\n      break;\n    case SaveMenuPageType::Save:\n      EntryType = SaveSystem::SaveType::Full;\n      SavePages = &FullSavePages;\n      BackgroundColor = SaveBackgroundColor;\n      CircleSprite = SaveCircle;\n      MenuTitleTextSprite = SaveTextSprite;\n      CurrentPage = &CurrentFullSavePage;\n      break;\n    case SaveMenuPageType::Load:\n      EntryType = SaveSystem::SaveType::Full;\n      SavePages = &FullSavePages;\n      BackgroundColor = LoadBackgroundColor;\n      CircleSprite = LoadCircle;\n      MenuTitleTextSprite = LoadTextSprite;\n      CurrentPage = &CurrentFullSavePage;\n      break;\n  }\n\n  for (auto mainItems : *SavePages) {\n    mainItems->Bounds =\n        RectF(0.0f, 0.0f, Profile::DesignWidth, Profile::DesignHeight);\n    for (auto widget : mainItems->Children) {\n      static_cast<SaveEntryButton*>(widget)->RefreshInfo(EntryType);\n    }\n  }\n}\n\nvoid SaveMenu::Show() {\n  if (State != Shown) {\n    if (State != Showing) {\n      MenuTransition.StartIn();\n      FromSystemMenuTransition->StartIn();\n      SelectAnimation.StartIn(true);\n    }\n    SavePages->at(*CurrentPage)->Show();\n    SavePages->at(*CurrentPage)->HasFocus = false;\n    State = Showing;\n    if (UI::FocusedMenu != 0) {\n      LastFocusedMenu = UI::FocusedMenu;\n      LastFocusedMenu->IsFocused = false;\n    }\n    IsFocused = true;\n    UI::FocusedMenu = this;\n    SavePages->at(*CurrentPage)->Children.front()->HasFocus = true;\n    CurrentlyFocusedElement = SavePages->at(*CurrentPage)->Children.front();\n  }\n}\nvoid SaveMenu::Hide() {\n  if (State != Hidden) {\n    const bool isLoading = GetFlag(SF_RESTARTMASK);\n    if (isLoading) {\n      State = Hidden;\n      MenuTransition.Finish(AnimationDirection::Out);\n      FromSystemMenuTransition->Finish(AnimationDirection::Out);\n      CurrentlyFocusedElement->Hovered = false;\n      CurrentlyFocusedElement->HasFocus = false;\n    } else {\n      if (State != Hiding) {\n        SaveEntryButton::FocusedAlphaFadeReset();\n        MenuTransition.StartOut();\n        FromSystemMenuTransition->StartOut();\n      }\n      State = Hiding;\n    }\n    SavePages->at(*CurrentPage)->HasFocus = false;\n    if (LastFocusedMenu != 0) {\n      UI::FocusedMenu = LastFocusedMenu;\n      LastFocusedMenu->IsFocused = true;\n    } else {\n      UI::FocusedMenu = 0;\n    }\n    IsFocused = false;\n  }\n}\n\nvoid SaveMenu::UpdateInput(float dt) {\n  using namespace Vm::Interface;\n  Menu::UpdateInput(dt);\n  const auto updatePage = [&](int nextPage) {\n    PrevPage = *CurrentPage;\n    if (CurrentlyFocusedElement) {\n      CurrentlyFocusedElement->HasFocus = false;\n      CurrentlyFocusedElement->Hovered = false;\n    }\n    *CurrentPage = nextPage;\n    SavePages->at(*CurrentPage)->Show();\n    SavePages->at(*CurrentPage)->Children.front()->HasFocus = true;\n    CurrentlyFocusedElement = SavePages->at(*CurrentPage)->Children.front();\n  };\n  if (IsFocused) {\n    SavePages->at(*CurrentPage)->UpdateInput(dt);\n    if (Input::MouseWheelDeltaY < 0 || PADinputButtonWentDown & PADcustom[8]) {\n      updatePage((*CurrentPage + 1) % Pages);\n    } else if (Input::MouseWheelDeltaY > 0 ||\n               PADinputButtonWentDown & PADcustom[7]) {\n      updatePage((*CurrentPage - 1 + Pages) % Pages);\n    }\n\n    if (CurrentlyFocusedElement && (PADinputButtonWentDown & PAD1Y)) {\n      auto saveButton = static_cast<SaveEntryButton*>(CurrentlyFocusedElement);\n      if (SaveSystem::GetSaveStatus(EntryType, saveButton->Id) == 1) {\n        saveButton->ToggleLock(EntryType);\n        saveButton->RefreshInfo(EntryType);\n        SetFlag(SF_SAVEPROTECTCHANGED, 1);\n      }\n    }\n  }\n}\n\nvoid SaveMenu::Update(float dt) {\n  const int sysMenuCt = ScrWork[SW_SYSMENUCT];\n  const int systemMenuCHG = ScrWork[SW_SYSTEMMENUCHG];\n\n  if ((!GetFlag(SF_SAVEMENU) || sysMenuCt < 10000 ||\n       (sysMenuCt == 10000 && systemMenuCHG != 0 && systemMenuCHG != 64)) &&\n      State == Shown) {\n    Hide();\n  } else if (GetFlag(SF_SAVEMENU) && sysMenuCt > 0 && State == Hidden) {\n    Show();\n  }\n\n  if (MenuTransition.IsOut() && !GetFlag(SF_SAVEMENU) && systemMenuCHG == 0 &&\n      (sysMenuCt == 0 || GetFlag(SF_SYSTEMMENU)) && State == Hiding) {\n    State = Hidden;\n    SavePages->at(*CurrentPage)->Hide();\n  } else if (MenuTransition.IsIn() && sysMenuCt == 10000 &&\n             (systemMenuCHG == 0 || systemMenuCHG == 64) &&\n             GetFlag(SF_SAVEMENU) && State == Showing) {\n    State = Shown;\n    SavePages->at(*CurrentPage)->HasFocus = true;\n    SaveEntryButton::FocusedAlphaFadeStart();\n  }\n\n  if (State != Hidden) {\n    MenuTransition.Update(dt);\n    SelectAnimation.Update(dt);\n    FromSystemMenuTransition->Update(dt);\n    if (MenuTransition.Direction == AnimationDirection::Out &&\n        MenuTransition.Progress <= 0.72f) {\n      TitleFade.StartOut();\n    } else if (MenuTransition.IsIn() &&\n               (TitleFade.Direction == AnimationDirection::In ||\n                TitleFade.IsOut())) {\n      TitleFade.StartIn();\n    }\n    TitleFade.Update(dt);\n    const glm::vec2 rightTitlePos = {\n        MenuTitleTextRightPos.x,\n        MenuTitleTextRightPos.y -\n            (MenuTitleTextSprite.Bounds.Height + 2.0f) / 2.0f};\n    UpdateTitles(rightTitlePos, MenuTitleTextLeftPos);\n    if (IsFocused) {\n      SavePages->at(*CurrentPage)->Update(dt);\n      SaveEntryButton::UpdateFocusedAlphaFade(dt);\n      auto currentlyFocusedButton =\n          static_cast<SaveEntryButton*>(CurrentlyFocusedElement);\n      if (currentlyFocusedButton) {\n        int newPage = currentlyFocusedButton->GetPage();\n        if (newPage != *CurrentPage) {\n          SavePages->at(*CurrentPage)->Hide();\n          *CurrentPage = newPage;\n          SavePages->at(*CurrentPage)->Show();\n          currentlyFocusedButton->HasFocus = true;\n          CurrentlyFocusedElement = currentlyFocusedButton;\n        }\n      }\n    } else {\n      // We want to keep the fade even when the confirmation prompt appears\n      SaveEntryButton::UpdateFocusedAlphaFade(dt);\n    }\n  }\n  if (State == Shown) {\n    UpdateInput(dt);\n  }\n}\n\nvoid SaveMenu::Render() {\n  if (State == Hidden) return;\n  CommonMenu::DrawSubmenu(BackgroundColor, CircleSprite, MenuTitleTextSprite,\n                          MenuTitleTextAngle, true);\n  if (MenuTransition.Progress < 0.22f) return;\n  glm::vec2 offset = MenuTransition.GetPageOffset();\n\n  SavePages->at(*CurrentPage)->MoveTo(offset);\n  SavePages->at(*CurrentPage)->Render();\n  Renderer->DrawSprite(SaveListSprite, SaveListPosition + offset);\n  DrawPageNumber(offset.y);\n  CommonMenu::DrawButtonPrompt(ButtonPromptSprite, ButtonPromptPosition);\n  SelectAnimation.Draw(SelectDataTextSprites, SelectDataTextPositions, offset);\n}\n\ninline void SaveMenu::DrawPageNumber(float yOffset) {\n  Renderer->DrawSprite(\n      PageNumBackgroundSprite,\n      glm::vec2(PageNumBackgroundPos.x, PageNumBackgroundPos.y + yOffset));\n  Renderer->DrawSprite(\n      BigDigits[*CurrentPage + 1],\n      glm::vec2(CurrentPageNumPos.x, CurrentPageNumPos.y + yOffset));\n  Renderer->DrawSprite(PageNumSeparatorSlashSprite,\n                       glm::vec2(PageNumSeparatorSlashPos.x,\n                                 PageNumSeparatorSlashPos.y + yOffset));\n  Renderer->DrawSprite(MaxPageNumSprite,\n                       glm::vec2(MaxPageNumPos.x, MaxPageNumPos.y + yOffset));\n}\n\nvoid SaveMenu::RefreshCurrentEntryInfo() {\n  if (!CurrentlyFocusedElement) return;\n  static_cast<SaveEntryButton*>(CurrentlyFocusedElement)\n      ->RefreshInfo(EntryType);\n}\n\n}  // namespace CHLCC\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/chlcc/savemenu.h",
    "content": "#pragma once\n\n#include \"commonmenu.h\"\n#include \"../../ui/savemenu.h\"\n#include \"../../ui/widgets/group.h\"\n#include \"../../ui/widgets/button.h\"\n#include \"../../data/savesystem.h\"\n#include \"../../ui/widgets/chlcc/saveentrybutton.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace CHLCC {\n\nusing namespace Impacto::UI::Widgets::CHLCC;\n\nclass SaveMenu : public UI::SaveMenu, public CommonMenu {\n public:\n  SaveMenu();\n\n  void Init() override;\n  void Show() override;\n  void Hide() override;\n  void Update(float dt) override;\n  void UpdateInput(float dt) override;\n  void Render() override;\n\n  void RefreshCurrentEntryInfo() override;\n\n  void MenuButtonOnClick(Widgets::Button* target);\n\n private:\n  void DrawPageNumber(float yOffset);\n\n  int CurrentFullSavePage = 0;\n  int CurrentQuickSavePage = 0;\n  int* CurrentPage;\n  int PrevPage = 0;\n\n  std::vector<Widgets::Group*> FullSavePages;\n  std::vector<Widgets::Group*> QuickSavePages;\n  std::vector<Widgets::Group*>* SavePages;\n  SaveSystem::SaveType EntryType;\n};\n\n}  // namespace CHLCC\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/chlcc/savesystem.cpp",
    "content": "#include \"savesystem.h\"\n\n#include \"../../io/memorystream.h\"\n#include \"../../io/physicalfilestream.h\"\n#include \"../../mem.h\"\n#include \"../../vm/vm.h\"\n#include \"../../profile/data/savesystem.h\"\n#include \"../../profile/scriptvars.h\"\n#include \"../../renderer/renderer.h\"\n#include \"../../profile/configsystem.h\"\n#include \"../../data/tipssystem.h\"\n#include \"../../effects/wave.h\"\n#include \"../../audio/audiosystem.h\"\n\n#include <cstdint>\n#include <numeric>\n\nnamespace Impacto {\nnamespace CHLCC {\n\nusing namespace Impacto::Vm;\nusing namespace Impacto::Effects;\nusing namespace Impacto::Profile::SaveSystem;\nusing namespace Impacto::Profile::ScriptVars;\nusing namespace Impacto::Profile::ConfigSystem;\n\nSaveFileEntry* WorkingSaveEntry = nullptr;\n\nconstexpr std::array<float, 4> TextSpeeds = {0x100 / 60.0f, 0x300 / 60.0f,\n                                             0x600 / 60.0f, 0xfff0000 / 60.0f};\nconstexpr uint8_t TextSpeedToSettingIndex(const float speed) {\n  for (uint8_t i = 0; i < std::ssize(TextSpeeds) - 1; i++) {\n    if (speed <= std::midpoint(TextSpeeds[i], TextSpeeds[i + 1])) return i;\n  }\n\n  return std::ssize(TextSpeeds) - 1;\n}\n\nconstexpr std::array<float, 3> AutoSpeeds = {0x100 / 60.0f, 0x300 / 60.0f,\n                                             0x600 / 60.0f};\nconstexpr uint8_t AutoSpeedToSettingIndex(const float speed) {\n  for (uint8_t i = 0; i < std::ssize(AutoSpeeds) - 1; i++) {\n    if (speed <= std::midpoint(AutoSpeeds[i], AutoSpeeds[i + 1])) return i;\n  }\n\n  return std::ssize(AutoSpeeds) - 1;\n}\n\nconstexpr uint8_t AutoQuickSaveSettingToIndex(const uint8_t setting) {\n  const bool onScene = setting & +AutoQuickSaveType::OnScene;\n  const bool onTrigger = setting & +AutoQuickSaveType::OnTrigger;\n\n  constexpr uint8_t onTriggerIndex = 0;\n  constexpr uint8_t onSceneIndex = 1;\n  constexpr uint8_t onTriggerAndSceneIndex = 2;\n  constexpr uint8_t neverIndex = 3;\n\n  if (onScene && onTrigger) {\n    return onTriggerAndSceneIndex;\n  } else if (onScene && !onTrigger) {\n    return onSceneIndex;\n  } else if (!onScene && onTrigger) {\n    return onTriggerIndex;\n  } else {\n    return neverIndex;\n  }\n}\n\nconstexpr std::underlying_type_t<AutoQuickSaveType> AutoQuickSaveIndexToSetting(\n    const uint8_t index) {\n  switch (index) {\n    case 0:\n      return +AutoQuickSaveType::OnTrigger;\n    case 1:\n      return +AutoQuickSaveType::OnScene;\n    case 2:\n      return +AutoQuickSaveType::OnTrigger | +AutoQuickSaveType::OnScene;\n    case 3:\n      return +AutoQuickSaveType::Never;\n  }\n\n  ImpLog(LogLevel::Warning, LogChannel::IO,\n         \"Unexpected auto quick save index {:d}\", index);\n  return +AutoQuickSaveType::OnTrigger | +AutoQuickSaveType::OnScene;\n}\n\nstd::pair<uint8_t, uint8_t> CalculateFileChecksum(\n    std::span<const uint8_t> bufferData, uint8_t initSum = 0,\n    uint8_t initXor = 0) {\n  uint8_t checksumSum = initSum;\n  uint8_t checksumXor = initXor;\n\n  for (uint8_t byte : bufferData) {\n    checksumSum += byte;\n    checksumXor ^= byte;\n  }\n\n  return {checksumSum, checksumXor};\n}\n\nstd::pair<uint8_t, uint8_t> CalculateEntryChecksum(\n    std::span<const uint8_t> bufferData, uint16_t initSum = 0,\n    uint8_t initXor = 0) {\n  uint16_t checksumSum = initSum;\n  uint8_t checksumXor = initXor;\n\n  for (uint8_t byte : bufferData) {\n    checksumSum += byte;\n    checksumXor ^= byte;\n  }\n\n  return {(checksumSum >> 4) & 0xFF, checksumXor};\n}\n\nstd::pair<uint16_t, uint16_t> CalculateSystemChecksum(\n    std::span<const uint8_t> bufferData, uint16_t initSum = 0,\n    uint16_t initXor = 0) {\n  uint32_t checksumSum = initSum;\n  uint16_t checksumXor = initXor;\n\n  for (size_t i = 0; i < bufferData.size() - 1; i += 2) {\n    const uint16_t dataShort = (bufferData[i] << 8) | bufferData[i + 1];\n    checksumSum += dataShort;\n    checksumXor ^= dataShort;\n  }\n\n  return {checksumSum & 0xFFFF, checksumXor};\n}\n\nvoid SaveSystem::InitializeSystemData() {\n  std::ranges::fill(SystemData, 0x00);\n\n  Io::MemoryStream stream =\n      Io::MemoryStream(SystemData.data(), SystemData.size(), false);\n\n  // Config settings\n  stream.Seek(0x76c, SEEK_SET);\n  Io::WriteLE(&stream, (Uint8)(Default::GroupVolumes[Audio::ACG_Voice] *\n                               128));  // VOICE2vol\n  Io::WriteLE(&stream, (Uint8)(Default::GroupVolumes[Audio::ACG_Voice] *\n                               128));  // VOICEvol\n  Io::WriteLE(&stream, (Uint8)(Default::GroupVolumes[Audio::ACG_BGM] * 256));\n  Io::WriteLE(&stream,\n              (Uint8)(Default::GroupVolumes[Audio::ACG_SE] * 128));  // SEvol\n  Io::WriteLE(&stream, (Uint8)(Default::GroupVolumes[Audio::ACG_SE] * 0.75f *\n                               128));  // SYSSEvol\n  Io::WriteLE(&stream, (Uint8)(Default::GroupVolumes[Audio::ACG_Movie] * 128));\n  Io::WriteLE(&stream, TextSpeedToSettingIndex(Default::TextSpeed));\n  Io::WriteLE(&stream, AutoSpeedToSettingIndex(Default::AutoSpeed));\n  Io::WriteLE(&stream, Default::SyncVoice);\n  Io::WriteLE(&stream, !Default::SkipRead);\n  Io::WriteLE(&stream, AutoQuickSaveSettingToIndex(Default::AutoQuickSave));\n\n  stream.Seek(0x77c, SEEK_SET);\n  Io::WriteLE(&stream, Default::SkipVoice);\n  Io::WriteLE(&stream, Default::ShowTipsNotification);\n  Io::WriteLE(&stream, Default::ControllerType);\n  Io::WriteLE(&stream, Default::TriggerStopSkip);\n  Io::WriteLE(&stream, (Uint8)(Default::ImageSize * 128));\n\n  stream.Seek(0x794, SEEK_SET);\n  static_assert(Default::VoiceMuted.size() >= 32);\n  for (size_t i = 0; i < 32; i++) {\n    Io::WriteLE(&stream, !Default::VoiceMuted[i]);\n  }\n\n  static_assert(Default::VoiceVolume.size() >= 20);\n  for (size_t i = 0; i < 20; i++) {\n    Io::WriteLE(&stream, (Uint8)(Default::VoiceVolume[i] * 128));\n  }\n\n  std::for_each_n(QuickSaveEntries, MaxSaveEntries,\n                  [](auto& ptr) { ptr = new SaveFileEntry(); });\n  std::for_each_n(FullSaveEntries, MaxSaveEntries,\n                  [](auto& ptr) { ptr = new SaveFileEntry(); });\n  WorkingSaveEntry = new SaveFileEntry();\n\n  WorkingSaveThumbnail.Sheet =\n      SpriteSheet(static_cast<float>(Window->WindowWidth),\n                  static_cast<float>(Window->WindowHeight));\n  WorkingSaveThumbnail.Bounds =\n      RectF(0.0f, 0.0f, static_cast<float>(Window->WindowWidth),\n            static_cast<float>(Window->WindowHeight));\n\n  Texture workingSaveTexture = Texture();\n  workingSaveTexture.LoadSolidColor(\n      static_cast<int>(WorkingSaveThumbnail.Bounds.Width),\n      static_cast<int>(WorkingSaveThumbnail.Bounds.Height), 0x000000);\n  WorkingSaveThumbnail.Sheet.Texture = workingSaveTexture.Submit();\n}\n\nSaveError SaveSystem::CheckSaveFile() const {\n  const static auto checkFile = [](const std::string& filePath, size_t fileSize,\n                                   std::string_view logName) {\n    std::error_code ec;\n\n    IoError existsState = Io::PathExists(filePath);\n    if (existsState == IoError_NotFound) {\n      return SaveError::NotFound;\n    } else if (existsState == IoError_Fail) {\n      ImpLog(LogLevel::Error, LogChannel::IO,\n             \"Failed to check if {:s} exists, error: \\\"{:s}\\\"\\n\", logName,\n             ec.message());\n      return SaveError::Failed;\n    }\n\n    auto saveFileSize = Io::GetFileSize(filePath);\n    if (saveFileSize == IoError_Fail) {\n      ImpLog(LogLevel::Error, LogChannel::IO,\n             \"Failed to get {:s} size, error: \\\"{:s}\\\"\\n\", logName,\n             ec.message());\n      return SaveError::Failed;\n    } else if (static_cast<size_t>(saveFileSize) != fileSize) {\n      return SaveError::Corrupted;\n    }\n\n    Io::FilePermissionsFlags perms;\n    IoError permsState = Io::GetFilePermissions(filePath, perms);\n    using enum Io::FilePermissionsFlags;\n    if (permsState == IoError_Fail) {\n      ImpLog(LogLevel::Error, LogChannel::IO,\n             \"Failed to get {:s} permissions, error: \\\"{:s}\\\"\\n\", logName,\n             ec.message());\n      return SaveError::Failed;\n    } else if ((perms & owner_read) == none || (perms & owner_write) == none) {\n      return SaveError::WrongUser;\n    }\n\n    return SaveError::OK;\n  };\n\n  SaveError error = checkFile(SaveFilePath, SaveFileSize, \"save file\");\n  if (error != SaveError::OK) return error;\n\n  error = checkFile(*ThumbnailFilePath, ThumbnailFileSize, \"thumbnail file\");\n\n  return error;\n}\n\nvoid SaveSystem::SaveSystemData() {\n  Io::MemoryStream stream =\n      Io::MemoryStream(SystemData.data(), SystemData.size(), false);\n\n  stream.Seek(0xa, SEEK_SET);\n\n  Io::WriteArrayLE<uint8_t>(&FlagWork[100], &stream, 50);\n  Io::WriteArrayLE<uint8_t>(&FlagWork[460], &stream, 40);\n  Io::WriteArrayBE<int>(&ScrWork[600], &stream, 400);\n\n  stream.Seek(0x76b, SEEK_SET);\n  Io::WriteLE(&stream, QuickSaveCount);\n\n  // Config settings\n  stream.Seek(0x76c, SEEK_SET);\n  Io::WriteLE(&stream, (Uint8)(Audio::GroupVolumes[Audio::ACG_Voice] *\n                               128));  // VOICE2vol\n  Io::WriteLE(&stream, (Uint8)(Audio::GroupVolumes[Audio::ACG_Voice] *\n                               128));  // VOICEvol\n  Io::WriteLE(&stream, (Uint8)(Audio::GroupVolumes[Audio::ACG_BGM] * 256));\n  Io::WriteLE(&stream,\n              (Uint8)(Audio::GroupVolumes[Audio::ACG_SE] * 128));  // SEvol\n  Io::WriteLE(&stream, (Uint8)(Audio::GroupVolumes[Audio::ACG_SE] * 0.75f *\n                               128));  // SYSSEvol\n  Io::WriteLE(&stream, (Uint8)(Audio::GroupVolumes[Audio::ACG_Movie] * 128));\n  Io::WriteLE(&stream, TextSpeedToSettingIndex(TextSpeed));\n  Io::WriteLE(&stream, AutoSpeedToSettingIndex(AutoSpeed));\n  Io::WriteLE(&stream, SyncVoice);\n  Io::WriteLE(&stream, !SkipRead);\n  Io::WriteLE(&stream, AutoQuickSaveSettingToIndex(AutoQuickSave));\n\n  stream.Seek(0x77c, SEEK_SET);\n  Io::WriteLE(&stream, SkipVoice);\n  Io::WriteLE(&stream, ShowTipsNotification);\n  Io::WriteLE(&stream, ControllerType);\n  Io::WriteLE(&stream, TriggerStopSkip);\n  Io::WriteLE(&stream, (Uint8)(ImageSize * 128));\n\n  stream.Seek(0x794, SEEK_SET);\n  static_assert(VoiceMuted.size() >= 32);\n  for (size_t i = 0; i < 32; i++) {\n    Io::WriteLE(&stream, !VoiceMuted[i]);\n  }\n\n  static_assert(VoiceVolume.size() >= 20);\n  for (size_t i = 0; i < 20; i++) {\n    Io::WriteLE(&stream, (Uint8)(VoiceVolume[i] * 128));\n  }\n\n  // EV Flags\n  stream.Seek(0x7d0, SEEK_SET);\n  for (int i = 0; i < 150; i++) {\n    uint8_t val = (EVFlags[8 * i] & 1) | ((EVFlags[8 * i + 1] ? 1 : 0) << 1) |\n                  ((EVFlags[8 * i + 2] ? 1 : 0) << 2) |\n                  ((EVFlags[8 * i + 3] ? 1 : 0) << 3) |\n                  ((EVFlags[8 * i + 4] ? 1 : 0) << 4) |\n                  ((EVFlags[8 * i + 5] ? 1 : 0) << 5) |\n                  ((EVFlags[8 * i + 6] ? 1 : 0) << 6) |\n                  (EVFlags[8 * i + 7] << 7);\n    Io::WriteU8(&stream, val);\n  }\n\n  stream.Seek(0xbb8, SEEK_SET);\n  Io::WriteArrayLE<uint8_t>(BGMFlags.data(), &stream, BGMFlags.size());\n\n  stream.Seek(0xc1c, SEEK_SET);\n  Io::WriteArrayLE<uint8_t>(MessageFlags.data(), &stream, MessageFlags.size());\n\n  stream.Seek(0x332c, SEEK_SET);\n  Io::WriteArrayBE<uint16_t>(TipsSystem::GetNewTipsIndices().data(), &stream,\n                             TipsSystem::GetNewTipsIndices().size());\n  Io::WriteBE<uint16_t>(&stream, 0,\n                        299 - TipsSystem::GetNewTipsIndices().size());\n  stream.Seek(0x3582, SEEK_SET);\n  Io::WriteLE<uint16_t>(\n      &stream, static_cast<uint16_t>(TipsSystem::GetNewTipsIndices().size()));\n\n  stream.Seek(0x3584, SEEK_SET);\n  Io::WriteArrayLE<uint8_t>(GameExtraData.data(), &stream,\n                            GameExtraData.size());\n}\n\nvoid SaveSystem::LoadEntryBuffer(Io::MemoryStream& memoryStream,\n                                 SaveFileEntry& entry) {\n  entry.Status = Io::ReadLE<uint8_t>(&memoryStream);\n  entry.Checksum = Io::ReadLE<uint16_t>(&memoryStream);\n  Io::ReadLE<uint8_t>(&memoryStream);\n\n  const uint8_t saveMonth = Io::ReadLE<uint8_t>(&memoryStream);\n  const uint8_t saveDay = Io::ReadLE<uint8_t>(&memoryStream);\n  const uint8_t saveHour = Io::ReadLE<uint8_t>(&memoryStream);\n  const uint8_t saveMinute = Io::ReadLE<uint8_t>(&memoryStream);\n  const uint8_t saveYear = Io::ReadLE<uint8_t>(&memoryStream);\n  const uint8_t saveSecond = Io::ReadLE<uint8_t>(&memoryStream);\n  entry.SaveDate = tm{};\n  entry.SaveDate.tm_sec = saveSecond;\n  entry.SaveDate.tm_min = saveMinute;\n  entry.SaveDate.tm_hour = saveHour;\n  entry.SaveDate.tm_mday = saveDay;\n  entry.SaveDate.tm_mon = saveMonth - 1;\n  entry.SaveDate.tm_year = saveYear + 2000 - 1900;\n\n  Io::ReadLE<uint16_t>(&memoryStream);\n  entry.PlayTime = Io::ReadLE<uint32_t>(&memoryStream);\n  entry.SwTitle = Io::ReadLE<uint16_t>(&memoryStream);\n\n  Io::ReadLE<uint8_t>(&memoryStream);\n  entry.Flags = Io::ReadLE<uint8_t>(&memoryStream);\n\n  memoryStream.Seek(30, SEEK_CUR);\n  Io::ReadArrayLE<uint8_t>(entry.FlagWorkScript1.data(), &memoryStream,\n                           entry.FlagWorkScript1.size());\n  Io::ReadArrayLE<uint8_t>(entry.FlagWorkScript2.data(), &memoryStream,\n                           entry.FlagWorkScript2.size());\n  Io::ReadArrayBE<int>(entry.ScrWorkScript1.data(), &memoryStream,\n                       entry.ScrWorkScript1.size());\n  Io::ReadArrayBE<int>(entry.ScrWorkScript2.data(), &memoryStream,\n                       entry.ScrWorkScript2.size());\n\n  entry.MainThreadExecPriority = Io::ReadBE<uint32_t>(&memoryStream);\n  entry.MainThreadGroupId = Io::ReadBE<uint32_t>(&memoryStream);\n  entry.MainThreadWaitCounter = Io::ReadBE<uint32_t>(&memoryStream);\n  entry.MainThreadScriptParam = Io::ReadBE<uint32_t>(&memoryStream);\n  entry.MainThreadIp = Io::ReadBE<uint32_t>(&memoryStream);\n  entry.MainThreadLoopCounter = Io::ReadBE<uint32_t>(&memoryStream);\n  entry.MainThreadLoopAdr = Io::ReadBE<uint32_t>(&memoryStream);\n  entry.MainThreadCallStackDepth = Io::ReadBE<uint32_t>(&memoryStream);\n\n  for (int j = 0; j < 8; j++) {\n    entry.MainThreadReturnAddresses[j] = Io::ReadBE<uint32_t>(&memoryStream);\n    entry.MainThreadReturnBufIds[j] = Io::ReadBE<uint32_t>(&memoryStream);\n  }\n\n  Io::ReadArrayBE<int>(entry.MainThreadVariables.data(), &memoryStream, 16);\n  entry.MainThreadDialoguePageId = Io::ReadBE<uint32_t>(&memoryStream);\n\n  Io::ReadArrayLE<uint16_t>(entry.WaveData.data(), &memoryStream,\n                            entry.WaveData.size());\n  memoryStream.Seek(1224, SEEK_CUR);\n}\n\nvoid SaveSystem::SaveEntryBuffer(Io::MemoryStream& memoryStream,\n                                 SaveFileEntry& entry) {\n  Io::WriteLE<uint8_t>(&memoryStream, entry.Status);\n  Io::WriteLE<uint16_t>(&memoryStream, (uint16_t)entry.Checksum);\n\n  Io::WriteLE<uint8_t>(&memoryStream, 0);\n  Io::WriteLE<uint8_t>(&memoryStream, (uint8_t)(entry.SaveDate.tm_mon + 1));\n  Io::WriteLE<uint8_t>(&memoryStream, (uint8_t)entry.SaveDate.tm_mday);\n  Io::WriteLE<uint8_t>(&memoryStream, (uint8_t)entry.SaveDate.tm_hour);\n  Io::WriteLE<uint8_t>(&memoryStream, (uint8_t)entry.SaveDate.tm_min);\n  Io::WriteLE<uint8_t>(&memoryStream,\n                       (uint8_t)(entry.SaveDate.tm_year + 1900 - 2000));\n  Io::WriteLE<uint8_t>(&memoryStream, (uint8_t)entry.SaveDate.tm_sec);\n\n  Io::WriteLE<uint16_t>(&memoryStream, 0);\n  Io::WriteLE<uint32_t>(&memoryStream, entry.PlayTime);\n  Io::WriteLE<uint16_t>(&memoryStream, (uint16_t)entry.SwTitle);\n\n  Io::WriteLE<uint8_t>(&memoryStream, 0);\n  Io::WriteLE<uint8_t>(&memoryStream, entry.Flags);\n\n  memoryStream.Seek(30, SEEK_CUR);\n  Io::WriteArrayLE<uint8_t>(entry.FlagWorkScript1.data(), &memoryStream,\n                            entry.FlagWorkScript1.size());\n  Io::WriteArrayLE<uint8_t>(entry.FlagWorkScript2.data(), &memoryStream,\n                            entry.FlagWorkScript2.size());\n  Io::WriteArrayBE<int>(entry.ScrWorkScript1.data(), &memoryStream,\n                        entry.ScrWorkScript1.size());\n  Io::WriteArrayBE<int>(entry.ScrWorkScript2.data(), &memoryStream,\n                        entry.ScrWorkScript2.size());\n\n  Io::WriteBE<uint32_t>(&memoryStream, entry.MainThreadExecPriority);\n  Io::WriteBE<uint32_t>(&memoryStream, entry.MainThreadGroupId);\n  Io::WriteBE<uint32_t>(&memoryStream, entry.MainThreadWaitCounter);\n  Io::WriteBE<uint32_t>(&memoryStream, entry.MainThreadScriptParam);\n  Io::WriteBE<uint32_t>(&memoryStream, entry.MainThreadIp);\n  Io::WriteBE<uint32_t>(&memoryStream, entry.MainThreadLoopCounter);\n  Io::WriteBE<uint32_t>(&memoryStream, entry.MainThreadLoopAdr);\n  Io::WriteBE<uint32_t>(&memoryStream, entry.MainThreadCallStackDepth);\n\n  for (int j = 0; j < 8; j++) {\n    Io::WriteBE<uint32_t>(&memoryStream, entry.MainThreadReturnAddresses[j]);\n    Io::WriteBE<uint32_t>(&memoryStream, entry.MainThreadReturnBufIds[j]);\n  }\n\n  Io::WriteArrayBE<int>(entry.MainThreadVariables.data(), &memoryStream, 16);\n  Io::WriteBE<uint32_t>(&memoryStream, entry.MainThreadDialoguePageId);\n\n  Io::WriteArrayLE<uint16_t>(entry.WaveData.data(), &memoryStream,\n                             entry.WaveData.size());\n  memoryStream.Seek(1224, SEEK_CUR);\n}\n\nSaveError SaveSystem::LoadSystemData() {\n  Io::MemoryStream stream =\n      Io::MemoryStream(SystemData.data(), SystemData.size(), false);\n\n  stream.Seek(0xa, SEEK_SET);\n\n  Io::ReadArrayLE<uint8_t>(&FlagWork[100], &stream, 50);\n  Io::ReadArrayLE<uint8_t>(&FlagWork[460], &stream, 40);\n  Io::ReadArrayBE<int>(&ScrWork[600], &stream, 400);\n\n  stream.Seek(0x76b, SEEK_SET);\n  QuickSaveCount = Io::ReadLE<Uint8>(&stream);\n  // Config settings\n  stream.Seek(0x76c, SEEK_SET);\n  stream.Seek(1, SEEK_CUR);  // VOICE2vol\n  Audio::GroupVolumes[Audio::ACG_Voice] = Io::ReadLE<Uint8>(&stream) / 128.0f;\n  Audio::GroupVolumes[Audio::ACG_BGM] = Io::ReadLE<Uint8>(&stream) / 256.0f;\n  Audio::GroupVolumes[Audio::ACG_SE] = Io::ReadLE<Uint8>(&stream) / 128.0f;\n  stream.Seek(1, SEEK_CUR);  // SYSSEvol\n  Audio::GroupVolumes[Audio::ACG_Movie] = Io::ReadLE<Uint8>(&stream) / 128.0f;\n  TextSpeed = TextSpeeds[Io::ReadLE<Uint8>(&stream)];\n  AutoSpeed = AutoSpeeds[Io::ReadLE<Uint8>(&stream)];\n  SyncVoice = Io::ReadLE<bool>(&stream);\n  SkipRead = !Io::ReadLE<bool>(&stream);\n  AutoQuickSave = AutoQuickSaveIndexToSetting(Io::ReadLE<Uint8>(&stream));\n\n  stream.Seek(0x77c, SEEK_SET);\n  SkipVoice = Io::ReadLE<bool>(&stream);\n  ShowTipsNotification = Io::ReadLE<bool>(&stream);\n  ControllerType = Io::ReadLE<Uint8>(&stream);\n  TriggerStopSkip = Io::ReadLE<bool>(&stream);\n  ImageSize = Io::ReadLE<Uint8>(&stream) / 128.0f;\n\n  stream.Seek(0x794, SEEK_SET);\n  static_assert(VoiceMuted.size() >= 32);\n  for (size_t i = 0; i < 32; i++) {\n    VoiceMuted[i] = !Io::ReadLE<bool>(&stream);\n  }\n\n  static_assert(VoiceVolume.size() >= 20);\n  for (size_t i = 0; i < 20; i++) {\n    VoiceVolume[i] = Io::ReadLE<Uint8>(&stream) / 128.0f;\n  }\n\n  // EV Flags\n  stream.Seek(0x7d0, SEEK_SET);\n  for (int i = 0; i < 150; i++) {\n    auto val = Io::ReadU8(&stream);\n    EVFlags[8 * i] = val & 1;\n    EVFlags[8 * i + 1] = (val & 2) != 0;\n    EVFlags[8 * i + 2] = (val & 4) != 0;\n    EVFlags[8 * i + 3] = (val & 8) != 0;\n    EVFlags[8 * i + 4] = (val & 0x10) != 0;\n    EVFlags[8 * i + 5] = (val & 0x20) != 0;\n    EVFlags[8 * i + 6] = (val & 0x40) != 0;\n    EVFlags[8 * i + 7] = val >> 7;\n  }\n\n  stream.Seek(0xbb8, SEEK_SET);\n  Io::ReadArrayLE<uint8_t>(BGMFlags.data(), &stream, BGMFlags.size());\n\n  stream.Seek(0xc1c, SEEK_SET);\n  Io::ReadArrayLE<uint8_t>(MessageFlags.data(), &stream, MessageFlags.size());\n\n  stream.Seek(0x3582, SEEK_SET);\n  uint16_t newTipsCount = Io::ReadLE<uint16_t>(&stream);\n  TipsSystem::GetNewTipsIndices() = std::vector<uint16_t>(newTipsCount);\n  stream.Seek(0x332c, SEEK_SET);\n  Io::ReadArrayBE<uint16_t>(TipsSystem::GetNewTipsIndices().data(), &stream,\n                            newTipsCount);\n\n  stream.Seek(0x3584, SEEK_SET);\n  Io::ReadArrayLE<uint8_t>(GameExtraData.data(), &stream, GameExtraData.size());\n\n  return SaveError::OK;\n}\n\nSaveError SaveSystem::MountSaveFile(std::vector<QueuedTexture>& textures) {\n  Io::Stream* stream;\n  IoError err = Io::PhysicalFileStream::Create(SaveFilePath, &stream);\n  switch (err) {\n    case IoError_NotFound:\n      return SaveError::NotFound;\n    case IoError_Fail:\n    case IoError_Eof:\n      return SaveError::Corrupted;\n    case IoError_OK:\n      break;\n  };\n\n  WorkingSaveEntry = new SaveFileEntry();\n  WorkingSaveThumbnail.Sheet =\n      SpriteSheet((float)Window->WindowWidth, (float)Window->WindowHeight);\n  WorkingSaveThumbnail.Bounds = RectF(0.0f, 0.0f, (float)Window->WindowWidth,\n                                      (float)Window->WindowHeight);\n\n  QueuedTexture txt{\n      .Id = std::ref(WorkingSaveThumbnail.Sheet.Texture),\n  };\n  txt.Tex.LoadSolidColor((int)WorkingSaveThumbnail.Bounds.Width,\n                         (int)WorkingSaveThumbnail.Bounds.Height, 0x000000);\n  textures.push_back(txt);\n\n  stream->Seek(0x0, SEEK_SET);\n  const uint8_t readFileSystemChecksumSum = Io::ReadU8(stream);\n  const uint8_t readFileSystemChecksumXor = Io::ReadU8(stream);\n  const uint8_t readFileEntriesChecksumSum = Io::ReadU8(stream);\n  const uint8_t readFileEntriesChecksumXor = Io::ReadU8(stream);\n  const uint8_t readFileThumbnailsChecksumSum = Io::ReadU8(stream);\n  const uint8_t readFileThumbnailsChecksumXor = Io::ReadU8(stream);\n\n  stream->Seek(0xa, SEEK_SET);\n  Io::ReadArrayLE<uint8_t>(SystemData.data(), stream, SystemData.size());\n\n  const auto [calcFileSystemChecksumSum, calcFileSystemChecksumXor] =\n      CalculateFileChecksum(SystemData);\n  if (readFileSystemChecksumSum != calcFileSystemChecksumSum ||\n      readFileSystemChecksumXor != calcFileSystemChecksumXor) {\n    ImpLog(LogLevel::Error, LogChannel::IO,\n           \"Save file system data checksum doesn't match\\n\");\n  }\n\n  const uint16_t readSystemChecksumSum = (SystemData[1] << 8) | SystemData[0];\n  const uint16_t readSystemChecksumXor = (SystemData[3] << 8) | SystemData[2];\n  const auto [calcSystemChecksumSum, calcSystemChecksumXor] =\n      CalculateSystemChecksum(\n          std::span(SystemData).subspan(10, 0x1cbd * sizeof(uint16_t)));\n\n  if (readSystemChecksumSum != calcSystemChecksumSum ||\n      readSystemChecksumXor != calcSystemChecksumXor) {\n    ImpLog(LogLevel::Error, LogChannel::IO,\n           \"System data checksum doesn't match\\n\");\n  }\n\n  uint8_t calcFileEntriesChecksumSum = 0;\n  uint8_t calcFileEntriesChecksumXor = 0;\n  int lockedQuickSaveSlots = 0;\n  for (auto& entryArray : {QuickSaveEntries, FullSaveEntries}) {\n    SaveType saveType =\n        (entryArray == QuickSaveEntries) ? SaveType::Quick : SaveType::Full;\n    for (int i = 0; i < MaxSaveEntries; i++) {\n      entryArray[i] = new SaveFileEntry();\n\n      std::array<uint8_t, SaveEntrySize> entrySlotBuf;\n      Io::ReadArrayLE<uint8_t>(entrySlotBuf.data(), stream,\n                               entrySlotBuf.size());\n\n      std::tie(calcFileEntriesChecksumSum, calcFileEntriesChecksumXor) =\n          CalculateFileChecksum(entrySlotBuf, calcFileEntriesChecksumSum,\n                                calcFileEntriesChecksumXor);\n\n      const uint8_t readEntryChecksumSum = entrySlotBuf[1];\n      const uint8_t readEntryChecksumXor = entrySlotBuf[2];\n      if (readEntryChecksumSum != 0 || readEntryChecksumXor != 0) {\n        const auto [calcEntryChecksumSum, calcEntryChecksumXor] =\n            CalculateEntryChecksum(std::span(entrySlotBuf).subspan(4), 0x76,\n                                   0x12);\n\n        const bool entryChecksumOk =\n            readEntryChecksumSum == calcEntryChecksumSum &&\n            readEntryChecksumXor == calcEntryChecksumXor;\n        entrySlotBuf[0] = entryChecksumOk ? 1 : 2;\n      }\n\n      Io::MemoryStream saveEntryDataStream(entrySlotBuf.data(),\n                                           entrySlotBuf.size(), false);\n\n      LoadEntryBuffer(saveEntryDataStream,\n                      static_cast<SaveFileEntry&>(*entryArray[i]));\n      if (saveType == SaveType::Quick) {\n        lockedQuickSaveSlots +=\n            static_cast<SaveFileEntry&>(*entryArray[i]).Flags & WriteProtect;\n      }\n    }\n  }\n\n  SetLockedQuickSaveCount(lockedQuickSaveSlots);\n\n  // CHLCC doesn't use a separate recents list\n  std::ranges::sort(QuickSaveRecentSortedId,\n                    Impacto::SaveSystem::SaveRecencyComparator(),\n                    [this](int id) { return *QuickSaveEntries[id]; });\n\n  if (readFileEntriesChecksumSum != calcFileEntriesChecksumSum ||\n      readFileEntriesChecksumXor != calcFileEntriesChecksumXor) {\n    ImpLog(LogLevel::Error, LogChannel::IO,\n           \"Save file entries checksum doesn't match\\n\");\n  }\n\n  delete stream;\n\n  // Load thumbnails\n\n  err = Io::PhysicalFileStream::Create(*ThumbnailFilePath, &stream);\n  switch (err) {\n    case IoError_NotFound:\n      return SaveError::NotFound;\n    case IoError_Fail:\n    case IoError_Eof:\n      return SaveError::Corrupted;\n    case IoError_OK:\n      break;\n  };\n\n  constexpr size_t thumbnailPaddingSize =\n      SaveThumbnailWidth * SaveThumbnailHeight;\n\n  uint8_t calcFileThumbnailsChecksumSum = 0;\n  uint8_t calcFileThumbnailsChecksumXor = 0;\n\n  for (auto& entryArray : {QuickSaveEntries, FullSaveEntries}) {\n    for (int i = 0; i < MaxSaveEntries; i++) {\n      SaveFileEntry* const entry = static_cast<SaveFileEntry*>(entryArray[i]);\n\n      if (entry->Status == 0) {\n        stream->Seek(SaveThumbnailSize + thumbnailPaddingSize, SEEK_CUR);\n        continue;\n      }\n\n      QueuedTexture texture{\n          .Id = std::ref(entry->SaveThumbnail.Sheet.Texture),\n      };\n      texture.Tex.Init(TexFmt_RGB, SaveThumbnailWidth, SaveThumbnailHeight);\n\n      Sprite& thumbnail = entry->SaveThumbnail;\n      thumbnail.Sheet = SpriteSheet(SaveThumbnailWidth, SaveThumbnailHeight);\n      thumbnail.Bounds =\n          RectF(0.0f, 0.0f, SaveThumbnailWidth, SaveThumbnailHeight);\n\n      Io::ReadArrayLE<uint8_t>(entry->ThumbnailData.data(), stream,\n                               entry->ThumbnailData.size());\n      stream->Seek(thumbnailPaddingSize, SEEK_CUR);\n\n      texture.Tex.Buffer.assign(entry->ThumbnailData.begin(),\n                                entry->ThumbnailData.end());\n      textures.push_back(texture);\n\n      std::tie(calcFileThumbnailsChecksumSum, calcFileThumbnailsChecksumXor) =\n          CalculateFileChecksum(entry->ThumbnailData,\n                                calcFileThumbnailsChecksumSum,\n                                calcFileThumbnailsChecksumXor);\n    }\n  }\n\n  delete stream;\n\n  if (readFileThumbnailsChecksumSum != calcFileThumbnailsChecksumSum ||\n      readFileThumbnailsChecksumXor != calcFileThumbnailsChecksumXor) {\n    ImpLog(LogLevel::Error, LogChannel::IO,\n           \"Save file thumbnails checksum doesn't match\\n\");\n  }\n\n  return SaveError::OK;\n}\n\nvoid SaveSystem::FlushWorkingSaveEntry(SaveType type, int id,\n                                       int autoSaveType) {\n  auto* entry = GetSaveEntry<SaveFileEntry>(type, id);\n  if (WorkingSaveEntry != nullptr && entry != nullptr &&\n      !(entry->Flags & WriteProtect)) {\n    if (type == SaveType::Quick) {\n      entry->SaveType = autoSaveType;\n      UpdateQuickSaveRecentSortedId(id);\n      if (QuickSaveCount != MaxSaveEntries) {\n        QuickSaveCount++;\n      }\n    }\n    uint8_t savedFlags = entry->Flags;\n    *entry = *WorkingSaveEntry;\n    entry->Flags = savedFlags;\n\n    entry->SaveDate = CurrentDateTime();\n\n    std::vector<uint8_t> captureBuffer =\n        Renderer->GetSpriteSheetImage(WorkingSaveThumbnail.Sheet);\n    Texture tex;\n    tex.Init(TexFmt_RGBA, SaveThumbnailWidth, SaveThumbnailHeight);\n\n    entry->SaveThumbnail.Sheet =\n        SpriteSheet(SaveThumbnailWidth, SaveThumbnailHeight);\n    entry->SaveThumbnail.Bounds =\n        RectF(0.0f, 0.0f, SaveThumbnailWidth, SaveThumbnailHeight);\n\n    if (ResizeImage(WorkingSaveThumbnail.Bounds, entry->SaveThumbnail.Bounds,\n                    captureBuffer, tex.Buffer) < 0) {\n      ImpLog(LogLevel::Error, LogChannel::General,\n             \"Failed to resize save thumbnail\\n\");\n    }\n    entry->SaveThumbnail.Sheet.Texture = tex.Submit();\n  }\n}\n\nSaveError SaveSystem::WriteSaveFile() {\n  using CF = Io::PhysicalFileStream::CreateFlagsMode;\n  Io::Stream* stream;\n  IoError err = Io::PhysicalFileStream::Create(\n      SaveFilePath, &stream, CF::CREATE | CF::CREATE_DIRS | CF::WRITE);\n  if (err != IoError_OK) {\n    ImpLog(LogLevel::Error, LogChannel::IO,\n           \"Failed to open save file for writing\\n\");\n    return SaveError::Failed;\n  }\n\n  const auto [systemChecksumSum, systemChecksumXor] = CalculateSystemChecksum(\n      std::span(SystemData).subspan(10, 0x1cbd * sizeof(uint16_t)));\n  SystemData[0] = systemChecksumSum & 0xFF;\n  SystemData[1] = systemChecksumSum >> 8;\n  SystemData[2] = systemChecksumXor & 0xFF;\n  SystemData[3] = systemChecksumXor >> 8;\n\n  const auto [fileSystemChecksumSum, fileSystemChecksumXor] =\n      CalculateFileChecksum(SystemData);\n\n  stream->Seek(0x0, SEEK_SET);\n  Io::WriteU8(stream, fileSystemChecksumSum);\n  Io::WriteU8(stream, fileSystemChecksumXor);\n\n  stream->Seek(0xa, SEEK_SET);\n  Io::WriteArrayLE<uint8_t>(SystemData.data(), stream, SystemData.size());\n\n  uint8_t fileEntriesChecksumSum = 0;\n  uint8_t fileEntriesChecksumXor = 0;\n\n  [[maybe_unused]] int64_t saveDataPos = stream->Position;\n  auto writeEntry = [&](SaveFileEntry* entry, int i) {\n    assert(stream->Position - saveDataPos == (int64_t)(i * SaveEntrySize));\n\n    if (entry == nullptr || entry->Status == 0) {\n      Io::WriteLE<uint8_t>(stream, 0, SaveEntrySize);\n    } else {\n      std::array<uint8_t, SaveEntrySize> entrySlotBuf{};\n      Io::MemoryStream saveEntryMemoryStream(entrySlotBuf.data(),\n                                             entrySlotBuf.size(), false);\n      SaveEntryBuffer(saveEntryMemoryStream, *entry);\n\n      const auto [entryChecksumSum, entryChecksumXor] = CalculateEntryChecksum(\n          std::span(entrySlotBuf).subspan(4), 0x76, 0x12);\n      entrySlotBuf[0] = 1;\n      entrySlotBuf[1] = entryChecksumSum;\n      entrySlotBuf[2] = entryChecksumXor;\n\n      std::tie(fileEntriesChecksumSum, fileEntriesChecksumXor) =\n          CalculateFileChecksum(entrySlotBuf, fileEntriesChecksumSum,\n                                fileEntriesChecksumXor);\n\n      Io::WriteArrayLE<uint8_t>(entrySlotBuf.data(), stream,\n                                entrySlotBuf.size());\n    }\n  };\n\n  // Chlcc stores quick saves from oldest to newest, with no separate recency\n  // array.\n  for (int i = 0; i < MaxSaveEntries; ++i) {\n    int reverseI = MaxSaveEntries - i - 1;\n    auto* entry = GetSaveEntry<SaveFileEntry>(SaveType::Quick, reverseI);\n    writeEntry(entry, i);\n  }\n  saveDataPos = stream->Position;\n  for (int i = 0; i < MaxSaveEntries; ++i) {\n    auto* entry = GetSaveEntry<SaveFileEntry>(SaveType::Full, i);\n    writeEntry(entry, i);\n  }\n\n  stream->Seek(0x2, SEEK_SET);\n  Io::WriteU8(stream, fileEntriesChecksumSum);\n  Io::WriteU8(stream, fileEntriesChecksumXor);\n\n  // Write thumbnails\n\n  Io::Stream* thumbnailsStream;\n  err =\n      Io::PhysicalFileStream::Create(*ThumbnailFilePath, &thumbnailsStream,\n                                     CF::CREATE | CF::CREATE_DIRS | CF::WRITE);\n  if (err != IoError_OK) {\n    ImpLog(LogLevel::Error, LogChannel::IO,\n           \"Failed to open thumbnail file for writing\\n\");\n    return SaveError::Failed;\n  }\n\n  uint8_t fileThumbnailsChecksumSum = 0;\n  uint8_t fileThumbnailsChecksumXor = 0;\n\n  for (auto& entryArray : {QuickSaveEntries, FullSaveEntries}) {\n    for (size_t i = 0; i < MaxSaveEntries; i++) {\n      SaveFileEntry* entry = static_cast<SaveFileEntry*>(entryArray[i]);\n\n      if (entry == nullptr || entry->Status == 0) {\n        Io::WriteLE<uint8_t>(thumbnailsStream, 0,\n                             SaveThumbnailSize + ThumbnailPaddingSize);\n      } else {\n        Io::WriteArrayLE<uint8_t>(entry->ThumbnailData.data(), thumbnailsStream,\n                                  entry->ThumbnailData.size());\n        Io::WriteLE<uint8_t>(thumbnailsStream, 0, ThumbnailPaddingSize);\n\n        std::tie(fileThumbnailsChecksumSum, fileThumbnailsChecksumXor) =\n            CalculateFileChecksum(entry->ThumbnailData,\n                                  fileThumbnailsChecksumSum,\n                                  fileThumbnailsChecksumXor);\n      }\n    }\n  }\n\n  stream->Seek(0x4, SEEK_SET);\n  Io::WriteU8(stream, fileThumbnailsChecksumSum);\n  Io::WriteU8(stream, fileThumbnailsChecksumXor);\n\n  // Four empty bytes between the end of checksums and the start of SystemData\n  Io::WriteLE<uint8_t>(stream, 0x00, 4);\n\n  delete stream;\n  delete thumbnailsStream;\n\n  return SaveError::OK;\n}\n\nuint32_t SaveSystem::GetSavePlayTime(SaveType type, int id) const {\n  return GetSaveEntry<SaveFileEntry>(type, id)->PlayTime;\n}\n\nuint8_t SaveSystem::GetSaveFlags(SaveType type, int id) const {\n  return GetSaveEntry<SaveFileEntry>(type, id)->Flags;\n}\n\nvoid SaveSystem::SetSaveFlags(SaveType type, int id, uint8_t flags) {\n  auto* entry = GetSaveEntry<SaveFileEntry>(type, id);\n  if (type == SaveType::Quick) {\n    uint8_t currentFlags = entry->Flags;\n    if ((currentFlags ^ flags) & WriteProtect) {\n      if (flags & WriteProtect) {\n        LockedQuickSaveCount++;\n      } else {\n        LockedQuickSaveCount--;\n      }\n\n      SetFlag(SF_SAVEALLPROTECTED, LockedQuickSaveCount == MaxSaveEntries);\n    }\n  }\n  entry->Flags = flags;\n}\n\ntm const& SaveSystem::GetSaveDate(SaveType type, int id) const {\n  return GetSaveEntry<SaveFileEntry>(type, id)->SaveDate;\n}\n\nvoid SaveSystem::SaveMemory() {\n  if (WorkingSaveEntry == nullptr) return;\n\n  WorkingSaveEntry->Status = 1;\n\n  const tm timeInfo = CurrentDateTime();\n  WorkingSaveEntry->SaveDate = timeInfo;\n  WorkingSaveEntry->PlayTime = ScrWork[SW_PLAYTIME];\n  WorkingSaveEntry->SwTitle = ScrWork[SW_TITLE];\n\n  std::copy(FlagWork.begin() + 50,\n            FlagWork.begin() + 50 + WorkingSaveEntry->FlagWorkScript1.size(),\n            WorkingSaveEntry->FlagWorkScript1.begin());\n  std::copy(FlagWork.begin() + 300,\n            FlagWork.begin() + 300 + WorkingSaveEntry->FlagWorkScript2.size(),\n            WorkingSaveEntry->FlagWorkScript2.begin());\n  std::copy(ScrWork.begin() + 300,\n            ScrWork.begin() + 300 + WorkingSaveEntry->ScrWorkScript1.size(),\n            WorkingSaveEntry->ScrWorkScript1.begin());\n  std::copy(ScrWork.begin() + 2300,\n            ScrWork.begin() + 2300 + WorkingSaveEntry->ScrWorkScript2.size(),\n            WorkingSaveEntry->ScrWorkScript2.begin());\n\n  int threadId = ScrWork[SW_MAINTHDP];\n  Sc3VmThread* thd = &ThreadPool[threadId & 0x7FFFFFFF];\n  if (thd != nullptr && 4 <= thd->GroupId && thd->GroupId <= 6) {\n    WorkingSaveEntry->MainThreadExecPriority = thd->ExecPriority;\n    WorkingSaveEntry->MainThreadWaitCounter = thd->WaitCounter;\n    WorkingSaveEntry->MainThreadScriptParam = thd->ScriptParam;\n    WorkingSaveEntry->MainThreadGroupId = thd->GroupId << 16;\n    WorkingSaveEntry->MainThreadGroupId |= thd->ScriptBufferId;\n    WorkingSaveEntry->MainThreadIp = thd->IpOffset;\n    WorkingSaveEntry->MainThreadCallStackDepth = thd->CallStackDepth;\n\n    for (uint32_t i = 0; i < thd->CallStackDepth; i++) {\n      WorkingSaveEntry->MainThreadReturnAddresses[i] = thd->ReturnAddresses[i];\n      WorkingSaveEntry->MainThreadReturnBufIds[i] =\n          thd->ReturnScriptBufferIds[i];\n    }\n\n    memcpy(WorkingSaveEntry->MainThreadVariables.data(), thd->Variables, 64);\n    WorkingSaveEntry->MainThreadDialoguePageId = thd->DialoguePageId;\n  }\n  WaveSave(std::span(WorkingSaveEntry->WaveData));\n}\n\nvoid SaveSystem::LoadEntry(SaveType type, int id) {\n  const auto* entry = GetSaveEntry<SaveFileEntry>(type, id);\n\n  if (entry->Status) {\n    ScrWork[SW_PLAYTIME] = entry->PlayTime;\n    ScrWork[SW_TITLE] = entry->SwTitle;\n\n    std::ranges::copy(entry->FlagWorkScript1, FlagWork.begin() + 50);\n    std::ranges::copy(entry->FlagWorkScript2, FlagWork.begin() + 300);\n    std::ranges::copy(entry->ScrWorkScript1, ScrWork.begin() + 300);\n    std::ranges::copy(entry->ScrWorkScript2, ScrWork.begin() + 2300);\n\n    // TODO: What to do about this mess I wonder...\n    ScrWork[SW_SVSENO] = ScrWork[SW_SEREQNO];\n    ScrWork[SW_SVSENO + 1] = ScrWork[SW_SEREQNO + 1];\n    ScrWork[SW_SVSENO + 2] = ScrWork[SW_SEREQNO + 2];\n    ScrWork[SW_SVBGMNO] = ScrWork[SW_BGMREQNO];\n    ScrWork[SW_SVSCRNO1] = ScrWork[SW_SCRIPTNO2];\n    ScrWork[SW_SVSCRNO2] = ScrWork[SW_SCRIPTNO3];\n    ScrWork[SW_SVSCRNO3] = ScrWork[SW_SCRIPTNO4];\n    ScrWork[SW_SVSCRNO4] = ScrWork[SW_SCRIPTNO5];\n    ScrWork[SW_SVBGNO1] = ScrWork[SW_BG1NO];\n    ScrWork[SW_SVBGNO1 + 1] = ScrWork[2427];\n    ScrWork[SW_SVBGNO1 + 2] = ScrWork[2447];\n    ScrWork[SW_SVBGNO1 + 3] = ScrWork[2467];\n    ScrWork[SW_SVBGNO1 + 4] = ScrWork[2487];\n    ScrWork[SW_SVBGNO1 + 5] = ScrWork[2507];\n    ScrWork[SW_SVBGNO1 + 6] = ScrWork[2527];\n    ScrWork[SW_SVBGNO1 + 7] = ScrWork[2547];\n    ScrWork[SW_SVCHANO1] = ScrWork[SW_CHA1NO];\n    ScrWork[SW_SVCHANO1 + 1] = ScrWork[2629];\n    ScrWork[SW_SVCHANO1 + 2] = ScrWork[2649];\n    ScrWork[SW_SVCHANO1 + 3] = ScrWork[2669];\n    ScrWork[SW_SVCHANO1 + 4] = ScrWork[2689];\n    ScrWork[SW_SVCHANO1 + 5] = ScrWork[2709];\n    ScrWork[SW_SVCHANO1 + 6] = ScrWork[2729];\n    ScrWork[SW_SVCHANO1 + 7] = ScrWork[2749];\n    ScrWork[SW_SVCHANO1 + 8] = ScrWork[2769];\n    ScrWork[SW_SVCHANO1 + 9] = ScrWork[2789];\n    ScrWork[SW_SVCHANO1 + 10] = ScrWork[2809];\n    ScrWork[SW_SVCHANO1 + 11] = ScrWork[2829];\n    ScrWork[SW_SVCHANO1 + 12] = ScrWork[2849];\n    ScrWork[SW_SVCHANO1 + 13] = ScrWork[2869];\n    ScrWork[SW_SVCHANO1 + 14] = ScrWork[2889];\n    ScrWork[SW_SVCHANO1 + 15] = ScrWork[2909];\n    ScrWork[2034] = ScrWork[3200];\n    ScrWork[2035] = ScrWork[3201];\n    ScrWork[2036] = ScrWork[3202];\n    ScrWork[2037] = ScrWork[3203];\n    ScrWork[2038] = ScrWork[3204];\n    ScrWork[2039] = ScrWork[3205];\n    ScrWork[2040] = ScrWork[3206];\n    ScrWork[2041] = ScrWork[3207];\n    ScrWork[2042] = ScrWork[3208];\n    ScrWork[2043] = ScrWork[3209];\n    ScrWork[2044] = ScrWork[3210];\n    ScrWork[2045] = ScrWork[3211];\n    ScrWork[2046] = ScrWork[3212];\n    ScrWork[2047] = ScrWork[3213];\n    ScrWork[2048] = ScrWork[3214];\n    ScrWork[2049] = ScrWork[3215];\n    ScrWork[2050] = ScrWork[3216];\n    ScrWork[2051] = ScrWork[3220];\n    ScrWork[2052] = ScrWork[3221];\n    ScrWork[2053] = ScrWork[3222];\n    ScrWork[2054] = ScrWork[3223];\n    ScrWork[2055] = ScrWork[3224];\n    ScrWork[2056] = ScrWork[3225];\n    ScrWork[2057] = ScrWork[3226];\n    ScrWork[2058] = ScrWork[3227];\n    ScrWork[2059] = ScrWork[3228];\n    ScrWork[2060] = ScrWork[3229];\n    ScrWork[2061] = ScrWork[3230];\n    ScrWork[2062] = ScrWork[3231];\n    ScrWork[2063] = ScrWork[3232];\n    ScrWork[2064] = ScrWork[3233];\n    ScrWork[2065] = ScrWork[3234];\n    ScrWork[2066] = ScrWork[3235];\n    ScrWork[2067] = ScrWork[3236];\n\n    int threadId = ScrWork[SW_MAINTHDP];\n    Sc3VmThread* thd = &ThreadPool[threadId & 0x7FFFFFFF];\n    if (thd != nullptr &&\n        (thd->GroupId == 4 || thd->GroupId == 5 || thd->GroupId == 6)) {\n      thd->ExecPriority = entry->MainThreadExecPriority;\n      thd->WaitCounter = entry->MainThreadWaitCounter;\n      thd->ScriptParam = entry->MainThreadScriptParam;\n      thd->GroupId = entry->MainThreadGroupId >> 16;\n      thd->ScriptBufferId = entry->MainThreadGroupId & 0xFFFF;\n      thd->IpOffset = entry->MainThreadIp;\n      thd->CallStackDepth = entry->MainThreadCallStackDepth;\n\n      for (size_t i = 0; i < thd->CallStackDepth; i++) {\n        thd->ReturnScriptBufferIds[i] = entry->MainThreadReturnBufIds[i];\n        thd->ReturnAddresses[i] = entry->MainThreadReturnAddresses[i];\n      }\n\n      memcpy(thd->Variables, entry->MainThreadVariables.data(),\n             16 * sizeof(int));\n      thd->DialoguePageId = entry->MainThreadDialoguePageId;\n    }\n    WaveLoad(std::span(entry->WaveData));\n  }\n}\n\nuint8_t SaveSystem::GetSaveStatus(SaveType type, int id) const {\n  return GetSaveEntry<SaveFileEntry>(type, id)->Status;\n}\n\nint SaveSystem::GetSaveTitle(SaveType type, int id) const {\n  return GetSaveEntry<SaveFileEntry>(type, id)->SwTitle;\n}\n\nuint32_t SaveSystem::GetTipStatus(size_t tipId) const {\n  tipId *= 3;\n  uint8_t lockStatus = (GameExtraData[tipId >> 3] & Flbit[tipId & 7]) != 0;\n  uint8_t newStatus =\n      (GameExtraData[(tipId + 1) >> 3] & Flbit[(tipId + 1) & 7]) != 0;\n  uint8_t unreadStatus =\n      (GameExtraData[(tipId + 2) >> 3] & Flbit[(tipId + 2) & 7]) != 0;\n  return (lockStatus | (unreadStatus << 1)) | (newStatus << 2);\n}\n\nvoid SaveSystem::SetTipStatus(size_t tipId, bool isLocked, bool isUnread,\n                              bool isNew) {\n  tipId *= 3;\n  if (isLocked) {\n    GameExtraData[tipId >> 3] &= ~(Flbit[tipId & 7]);\n  } else {\n    GameExtraData[tipId >> 3] |= Flbit[tipId & 7];\n  }\n  if (isUnread) {\n    GameExtraData[(tipId + 2) >> 3] &= ~(Flbit[(tipId + 2) & 7]);\n  } else {\n    GameExtraData[(tipId + 2) >> 3] |= Flbit[(tipId + 2) & 7];\n  }\n  if (isNew) {\n    GameExtraData[(tipId + 1) >> 3] &= ~(Flbit[(tipId + 1) & 7]);\n  } else {\n    GameExtraData[(tipId + 1) >> 3] |= Flbit[(tipId + 1) & 7];\n  }\n}\n\nvoid SaveSystem::SetLineRead(int scriptId, int lineId) {\n  if (std::ssize(ScriptMessageData) <= scriptId) return;\n\n  uint32_t offset = ScriptMessageData[scriptId].SaveDataOffset + lineId;\n  if (offset == 0xFFFFFFFF) return;\n\n  // TODO: update some ScrWorks (2003, 2005 & 2006)\n\n  MessageFlags[offset >> 3] |= Flbit[offset & 0b111];\n}\n\nbool SaveSystem::IsLineRead(int scriptId, int lineId) const {\n  if (std::ssize(ScriptMessageData) <= scriptId) return false;\n\n  uint32_t offset = ScriptMessageData[scriptId].SaveDataOffset + lineId;\n  uint8_t flbit = Flbit[offset & 0b111];\n  uint8_t viewed = MessageFlags[offset >> 3];\n\n  return (bool)(flbit & viewed);\n}\n\nvoid SaveSystem::GetReadMessagesCount(int* totalMessageCount,\n                                      int* readMessageCount) const {\n  *totalMessageCount = 0;\n  *readMessageCount = 0;\n\n  for (int scriptId = 0; scriptId < StoryScriptCount; scriptId++) {\n    ScriptMessageDataPair script = ScriptMessageData[scriptId];\n    *totalMessageCount += script.LineCount;\n\n    for (size_t lineId = 0; lineId < script.LineCount; lineId++) {\n      *readMessageCount += IsLineRead(scriptId, (int)lineId);\n    }\n  }\n}\n\nvoid SaveSystem::GetViewedEVsCount(int* totalEVCount,\n                                   int* viewedEVCount) const {\n  for (int i = 0; i < MaxAlbumEntries; i++) {\n    if (AlbumEvData[i][0] == 0xFFFF) break;\n    for (int j = 0; j < MaxAlbumSubEntries; j++) {\n      if (AlbumEvData[i][j] == 0xFFFF) break;\n      *totalEVCount += 1;\n      *viewedEVCount += EVFlags[AlbumEvData[i][j]];\n    }\n  }\n}\nvoid SaveSystem::GetEVStatus(int evId, int* totalVariations,\n                             int* viewedVariations) const {\n  *totalVariations = 0;\n  *viewedVariations = 0;\n  for (int i = 0; i < MaxAlbumSubEntries; i++) {\n    if (AlbumEvData[evId][i] == 0xFFFF) break;\n    *totalVariations += 1;\n    *viewedVariations += EVFlags[AlbumEvData[evId][i]];\n  }\n}\n\nvoid SaveSystem::SetEVStatus(int id) { EVFlags[id] = true; }\n\nbool SaveSystem::GetEVVariationIsUnlocked(size_t evId,\n                                          size_t variationIdx) const {\n  if (AlbumEvData[evId][variationIdx] == 0xFFFF) return false;\n  return EVFlags[AlbumEvData[evId][variationIdx]];\n}\n\nbool SaveSystem::GetBgmFlag(int id) const { return BGMFlags[id]; }\nvoid SaveSystem::SetBgmFlag(int id, bool flag) { BGMFlags[id] = flag; }\n\nvoid SaveSystem::SaveThumbnailData() {\n  // Renderer expects RGB32\n  std::vector<uint8_t> thumbnailBuffer(SaveThumbnailWidth *\n                                       SaveThumbnailHeight * 4);\n\n  for (auto* entryArray : {FullSaveEntries, QuickSaveEntries}) {\n    for (int i = 0; i < MaxSaveEntries; i++) {\n      SaveFileEntry* const entry = static_cast<SaveFileEntry*>(entryArray[i]);\n      if (entry->Status == 0) continue;\n\n      Renderer->GetSpriteSheetImage(entry->SaveThumbnail.Sheet,\n                                    thumbnailBuffer);\n\n      std::array<uint8_t, SaveThumbnailSize>& thumbnailData =\n          entry->ThumbnailData;\n\n      for (size_t pixelId = 0; pixelId < thumbnailBuffer.size() / 4;\n           pixelId++) {\n        thumbnailData[pixelId * 3 + 0] = thumbnailBuffer[pixelId * 4 + 0];  // r\n        thumbnailData[pixelId * 3 + 1] = thumbnailBuffer[pixelId * 4 + 1];  // g\n        thumbnailData[pixelId * 3 + 2] = thumbnailBuffer[pixelId * 4 + 2];  // b\n      }\n    }\n  }\n}\n\nSprite& SaveSystem::GetSaveThumbnail(SaveType type, int id) {\n  return GetSaveEntry<SaveFileEntry>(type, id)->SaveThumbnail;\n}\n\nvoid SaveSystem::WaveSave(std::span<uint16_t> data) {\n  size_t dataOff = 0;\n  for (size_t i = 0; i < 10; i++) {\n    data[dataOff++] = static_cast<uint16_t>(WaveBG.WaveData[i].Flags);\n    data[dataOff++] = static_cast<uint16_t>(WaveEFF.WaveData[i].Flags);\n\n    data[dataOff++] = static_cast<uint16_t>(WaveBG.WaveData[i].Amplitude);\n    data[dataOff++] = static_cast<uint16_t>(WaveEFF.WaveData[i].Amplitude);\n\n    data[dataOff++] =\n        static_cast<uint16_t>(WaveBG.WaveData[i].TemporalFrequency);\n    data[dataOff++] =\n        static_cast<uint16_t>(WaveEFF.WaveData[i].TemporalFrequency);\n\n    data[dataOff++] = static_cast<uint16_t>(WaveBG.WaveData[i].Phase);\n    data[dataOff++] = static_cast<uint16_t>(WaveEFF.WaveData[i].Phase);\n\n    data[dataOff++] =\n        static_cast<uint16_t>(WaveBG.WaveData[i].SpatialFrequency);\n    data[dataOff++] =\n        static_cast<uint16_t>(WaveEFF.WaveData[i].SpatialFrequency);\n  }\n\n  data[dataOff++] = static_cast<uint16_t>(WaveBG.WaveCount);\n  data[dataOff++] = static_cast<uint16_t>(WaveEFF.WaveCount);\n}\n\nvoid SaveSystem::WaveLoad(std::span<const uint16_t> data) const {\n  size_t dataOff = 0;\n  for (size_t i = 0; i < 10; i++) {\n    WaveBG.WaveData[i].Flags = static_cast<int>(data[dataOff++]);\n    WaveEFF.WaveData[i].Flags = static_cast<int>(data[dataOff++]);\n\n    WaveBG.WaveData[i].Amplitude = static_cast<int>(data[dataOff++]);\n    WaveEFF.WaveData[i].Amplitude = static_cast<int>(data[dataOff++]);\n\n    WaveBG.WaveData[i].TemporalFrequency = static_cast<int>(data[dataOff++]);\n    WaveEFF.WaveData[i].TemporalFrequency = static_cast<int>(data[dataOff++]);\n\n    WaveBG.WaveData[i].Phase = static_cast<int>(data[dataOff++]);\n    WaveEFF.WaveData[i].Phase = static_cast<int>(data[dataOff++]);\n\n    WaveBG.WaveData[i].SpatialFrequency = static_cast<int>(data[dataOff++]);\n    WaveEFF.WaveData[i].SpatialFrequency = static_cast<int>(data[dataOff++]);\n  }\n\n  WaveBG.WaveCount = static_cast<uint32_t>(data[dataOff++]);\n  WaveEFF.WaveCount = static_cast<uint32_t>(data[dataOff++]);\n}\n\n}  // namespace CHLCC\n}  // namespace Impacto"
  },
  {
    "path": "src/games/chlcc/savesystem.h",
    "content": "#pragma once\n\n#include \"../../data/savesystem.h\"\n#include \"../../io/memorystream.h\"\n\nnamespace Impacto {\nnamespace CHLCC {\n\nusing namespace Impacto::SaveSystem;\n\nconstexpr size_t SaveEntrySize = 0x2000;\nconstexpr size_t SaveFileSize = SaveEntrySize * MaxSaveEntries * 2 + 0x3b06;\n\n// CHLCC PS3 Save thumbnails are 160x90 RGB24\nconstexpr size_t SaveThumbnailWidth = 160;\nconstexpr size_t SaveThumbnailHeight = 90;\nconstexpr size_t SaveThumbnailSize =\n    SaveThumbnailWidth * SaveThumbnailHeight * 3;\n// Each entry has padding of Width * Height for some reason\nconstexpr size_t ThumbnailPaddingSize =\n    SaveThumbnailWidth * SaveThumbnailHeight;\nconstexpr size_t ThumbnailFileSize =\n    (SaveThumbnailSize + ThumbnailPaddingSize) * MaxSaveEntries * 2;\n\nclass SaveFileEntry : public SaveFileEntryBase {\n public:\n  std::array<uint8_t, 50> FlagWorkScript1{};   // 50 bytes from &FlagWork[50]\n  std::array<uint8_t, 100> FlagWorkScript2{};  // 100 bytes from &FlagWork[300]\n  std::array<int, 300> ScrWorkScript1{};       // 1200 bytes from &ScrWork[300]\n  std::array<int, 1300> ScrWorkScript2{};      // 5200 bytes from &ScrWork[2300]\n  std::array<uint16_t, 102>\n      WaveData{};  // 2 wave types * 10 waves * 5 fields + 2 counts\n  std::array<uint8_t, SaveThumbnailSize> ThumbnailData;\n};\n\nclass SaveSystem : public SaveSystemBase {\n public:\n  SaveError CheckSaveFile() const override;\n  SaveError MountSaveFile(std::vector<QueuedTexture>& textures) override;\n\n  SaveError LoadSystemData() override;\n  void SaveSystemData() override;\n  void InitializeSystemData() override;\n\n  void SaveThumbnailData() override;\n  Sprite& GetSaveThumbnail(SaveType type, int id) override;\n\n  void LoadEntryBuffer(Io::MemoryStream& memoryStream, SaveFileEntry& entry);\n  void SaveEntryBuffer(Io::MemoryStream& memoryStream, SaveFileEntry& entry);\n  void LoadEntry(SaveType type, int id) override;\n  void FlushWorkingSaveEntry(SaveType type, int id, int autoSaveType) override;\n\n  void SaveMemory() override;\n\n  SaveError WriteSaveFile() override;\n  uint32_t GetSavePlayTime(SaveType type, int id) const override;\n  uint8_t GetSaveFlags(SaveType type, int id) const override;\n  void SetSaveFlags(SaveType type, int id, uint8_t flags) override;\n  tm const& GetSaveDate(SaveType type, int id) const override;\n  uint8_t GetSaveStatus(SaveType type, int id) const override;\n  int GetSaveTitle(SaveType type, int id) const override;\n\n  uint32_t GetTipStatus(size_t tipId) const override;\n  void SetTipStatus(size_t tipId, bool isLocked, bool isUnread,\n                    bool isNew) override;\n\n  void SetLineRead(int scriptId, int lineId) override;\n  bool IsLineRead(int scriptId, int lineId) const override;\n  void GetReadMessagesCount(int* totalMessageCount,\n                            int* readMessageCount) const override;\n\n  void GetViewedEVsCount(int* totalEVCount, int* viewedEVCount) const override;\n  void GetEVStatus(int evId, int* totalVariations,\n                   int* viewedVariations) const override;\n  void SetEVStatus(int id) override;\n  bool GetEVVariationIsUnlocked(size_t evId,\n                                size_t variationIdx) const override;\n\n  bool GetBgmFlag(int id) const override;\n  void SetBgmFlag(int id, bool flag) override;\n\n  void SetCheckpointId(int id) override {}\n  void WaveSave(std::span<uint16_t> data);\n  void WaveLoad(std::span<const uint16_t> data) const;\n\n private:\n  std::array<uint8_t, 1024> GameExtraData;\n  std::array<uint8_t, 10000> MessageFlags;\n  std::array<uint8_t, 0x3afc> SystemData;\n  std::array<bool, 1200> EVFlags;\n  std::array<uint8_t, 100> BGMFlags;\n  uint8_t QuickSaveCount{};\n};\n\n}  // namespace CHLCC\n}  // namespace Impacto"
  },
  {
    "path": "src/games/chlcc/sysmesbox.cpp",
    "content": "#include \"sysmesbox.h\"\n\n#include \"../../profile/ui/sysmesbox.h\"\n#include \"../../profile/games/chlcc/sysmesbox.h\"\n#include \"../../profile/dialogue.h\"\n#include \"../../profile/scriptvars.h\"\n#include \"../../mem.h\"\n#include \"../../renderer/renderer.h\"\n#include \"../../util.h\"\n#include \"../../ui/widgets/chlcc/systemmessagebutton.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace CHLCC {\n\nusing namespace Impacto::UI::Widgets;\nusing namespace Impacto::Profile::ScriptVars;\nusing namespace Impacto::Profile::SysMesBox;\nusing namespace Impacto::Profile::CHLCC::SysMesBox;\nusing namespace Impacto::UI::Widgets::CHLCC;\n\nSysMesBox::SysMesBox() {\n  LoadingStarsFadeAnimation.DurationIn = LoadingStarsFadeDuration;\n}\n\nvoid SysMesBox::ChoiceItemOnClick(Button* target) {\n  ScrWork[SW_SYSSEL] = target->Id;\n  ChoiceMade = true;\n}\n\nvoid SysMesBox::Show() {\n  MessageItems = new Widgets::Group(this);\n  ChoiceItems = new Widgets::Group(this);\n\n  Sprite nullSprite = Sprite();\n  nullSprite.Bounds = RectF(0.0f, 0.0f, 0.0f, 0.0f);\n\n  auto onClick = [this](auto* btn) { return ChoiceItemOnClick(btn); };\n\n  float diff = 0.0f;\n  float maxWidth = MinHighlightWidth;\n  for (int i = 0; i < MessageCount; i++) {\n    maxWidth = std::max(maxWidth, MessageWidths[i]);\n  }\n\n  for (int i = 0; i < MessageCount; i++) {\n    if (Messages[i].empty()) continue;\n\n    diff = Messages[i][0].DestRect.X - (TextX - (maxWidth / 2.0f));\n    for (ProcessedTextGlyph& glyph : Messages[i]) {\n      glyph.DestRect.X -= diff;\n      glyph.DestRect.Y = TextMiddleY + (i * TextLineHeight);\n    }\n\n    Label* message = new Label(Messages[i], MessageWidths[i], TextFontSize,\n                               RendererOutlineMode::Full);\n\n    MessageItems->Add(message, FDIR_DOWN);\n  }\n\n  float totalChoiceWidth = 0.0f;\n  for (int i = 0; i < ChoiceCount; i++) {\n    totalChoiceWidth += ChoiceWidths[i] + ChoicePadding;\n  }\n  maxWidth = std::max(maxWidth, totalChoiceWidth);\n  maxWidth = std::max(maxWidth, MinMaxMesWidth);\n\n  ChoiceX = (maxWidth / 2.0f) - totalChoiceWidth + ChoiceXBase;\n\n  float tempChoiceX = ChoiceX;\n\n  for (int i = 0; i < ChoiceCount; i++) {\n    diff = Choices[i][0].DestRect.X - tempChoiceX;\n    for (ProcessedTextGlyph& choice : Choices[i]) {\n      choice.DestRect.X -= diff;\n      choice.DestRect.Y = ChoiceY;\n    }\n\n    Button* choice = new SystemMessageButton(\n        i, nullSprite, nullSprite, SelectionLeftPart, SelectionMiddlePart,\n        SelectionRightPart,\n        glm::vec2(Choices[i][0].DestRect.X, Choices[i][0].DestRect.Y));\n    choice->HighlightOffset = glm::vec2(HighlightXOffset, HighlightYOffset);\n\n    choice->SetText(Choices[i], ChoiceWidths[i],\n                    Profile::Dialogue::DefaultFontSize,\n                    RendererOutlineMode::Full);\n    choice->OnClickHandler = onClick;\n\n    ChoiceItems->Add(choice, FDIR_LEFT);\n\n    tempChoiceX += ChoiceWidths[i] + ChoicePadding;\n  }\n\n  FadeAnimation.StartIn();\n  MessageItems->Show();\n  MessageItems->HasFocus = false;\n  if (ChoiceCount != 0) ChoiceItems->Show();\n  State = Showing;\n\n  if (UI::FocusedMenu != 0) {\n    LastFocusedMenu = UI::FocusedMenu;\n    LastFocusedMenu->IsFocused = false;\n  }\n  IsFocused = true;\n  UI::FocusedMenu = this;\n\n  LoadingStarsFadeAnimation.StartIn(true);\n  for (SysMesBoxStar& star : LoadingStars) {\n    star.Angle = ScrWorkAngleToRad(CALCrnd(8192) << 3);\n    star.RotationSpeed = ScrWorkAngleToRad(CALCrnd(4096) - 2048);\n  }\n}\n\nvoid SysMesBox::Hide() {\n  FadeAnimation.StartOut();\n  State = Hiding;\n}\n\nvoid SysMesBox::Update(float dt) {\n  UpdateInput(dt);\n\n  FadeAnimation.Update(dt);\n  if (State != Hidden) {\n    if (State == Hiding && FadeAnimation.IsOut()) {\n      if (LastFocusedMenu != 0) {\n        UI::FocusedMenu = LastFocusedMenu;\n        LastFocusedMenu->IsFocused = true;\n      } else {\n        UI::FocusedMenu = 0;\n      }\n      IsFocused = false;\n    }\n\n    if (FadeAnimation.IsIn()) State = Shown;\n    if (FadeAnimation.IsOut()) State = Hidden;\n\n    if (IsFocused) {\n      MessageItems->Update(dt);\n      MessageItems->UpdateInput(dt);\n      ChoiceItems->Update(dt);\n      ChoiceItems->UpdateInput(dt);\n    }\n\n    if (GetFlag(SF_SAVEICON)) {\n      LoadingStarsFadeAnimation.Update(dt);\n      for (SysMesBoxStar& star : LoadingStars) {\n        star.Angle += star.RotationSpeed;\n      }\n    }\n  }\n}\n\nvoid SysMesBox::Render() {\n  if (State == Hidden) return;\n\n  glm::vec4 col(1.0f, 1.0f, 1.0f, FadeAnimation.Progress);\n\n  float maxWidth = FLT_MIN;\n  for (int i = 0; i < MessageCount; i++) {\n    if (maxWidth < MessageWidths[i]) maxWidth = MessageWidths[i];\n  }\n  if (maxWidth < MinMaxMesWidth) maxWidth = MinMaxMesWidth;\n\n  Renderer->DrawSprite(Box, glm::vec2(BoxX, BoxY), col);\n\n  MessageItems->Tint.a = FadeAnimation.Progress;\n  MessageItems->Render();\n  ChoiceItems->Tint.a = FadeAnimation.Progress;\n  ChoiceItems->Render();\n\n  if (GetFlag(SF_SAVEICON)) {\n    for (size_t i = 0; i < LoadingStars.size(); i++) {\n      const SysMesBoxStar& star = LoadingStars[i];\n      glm::vec2 position =\n          LoadingStarsPosition + glm::vec2(LoadingStar.ScaledWidth() * i, 0.0f);\n\n      CornersQuad dest = LoadingStar.ScaledBounds()\n                             .RotateAroundCenter(star.Angle)\n                             .Translate(position);\n\n      float alpha = LoadingStarsFadeAnimation.Progress;\n      Renderer->DrawSprite(LoadingStar, dest, {1.0f, 1.0f, 1.0f, alpha});\n    }\n  }\n}\n\nvoid SysMesBox::Init() {\n  ChoiceMade = false;\n  MessageCount = 0;\n  ChoiceCount = 0;\n\n  FadeAnimation.DurationIn = FadeInDuration;\n  FadeAnimation.DurationOut = FadeOutDuration;\n  FadeAnimation.Direction = AnimationDirection::In;\n  FadeAnimation.LoopMode = AnimationLoopMode::Stop;\n}\n\nvoid SysMesBox::AddMessage(Vm::BufferOffsetContext ctx) {\n  Impacto::Vm::Sc3VmThread dummy;\n  dummy.IpOffset = ctx.IpOffset;\n  dummy.ScriptBufferId = ctx.ScriptBufferId;\n  Messages[MessageCount] =\n      TextLayoutPlainLine(&dummy, 255, Profile::Dialogue::DialogueFont,\n                          TextFontSize, Profile::Dialogue::ColorTable[0], 1.0f,\n                          glm::vec2(TextX, 0.0f), TextAlignment::Left);\n  float mesLen = 0.0f;\n  for (size_t i = 0; i < Messages[MessageCount].size(); i++) {\n    mesLen += Messages[MessageCount][i].DestRect.Width;\n  }\n  MessageWidths[MessageCount] = mesLen;\n  MessageCount++;\n}\n\nvoid SysMesBox::AddChoice(Vm::BufferOffsetContext ctx) {\n  Impacto::Vm::Sc3VmThread dummy;\n  dummy.IpOffset = ctx.IpOffset;\n  dummy.ScriptBufferId = ctx.ScriptBufferId;\n  Choices[ChoiceCount] =\n      TextLayoutPlainLine(&dummy, 255, Profile::Dialogue::DialogueFont,\n                          TextFontSize, Profile::Dialogue::ColorTable[0], 1.0f,\n                          glm::vec2(TextX, 0.0f), TextAlignment::Left);\n  float mesLen = 0.0f;\n  if (Choices[ChoiceCount].size() != 0) {\n    const RectF firstGlyph = Choices[ChoiceCount][0].DestRect;\n    const size_t lastIndex = Choices[ChoiceCount].size() - 1;\n    const RectF lastGlyph = Choices[ChoiceCount][lastIndex].DestRect;\n    mesLen = lastGlyph.X + lastGlyph.Width - firstGlyph.X;\n  }\n  ChoiceWidths[ChoiceCount] = mesLen;\n  ChoiceCount++;\n}\n\n}  // namespace CHLCC\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/chlcc/sysmesbox.h",
    "content": "#pragma once\n\n#include \"../../ui/sysmesbox.h\"\n#include \"../../ui/widgets/button.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace CHLCC {\n\nstruct SysMesBoxStar {\n  float Angle;\n  float RotationSpeed;\n};\n\nclass SysMesBox : public UI::SysMesBox {\n public:\n  SysMesBox();\n\n  virtual void Show() override;\n  virtual void Hide() override;\n  virtual void Update(float dt) override;\n  virtual void Render() override;\n\n  virtual void Init() override;\n  virtual void AddMessage(Vm::BufferOffsetContext ctx) override;\n  virtual void AddChoice(Vm::BufferOffsetContext ctx) override;\n\n private:\n  std::array<SysMesBoxStar, 14> LoadingStars;\n  Animation LoadingStarsFadeAnimation;\n\n  void ChoiceItemOnClick(UI::Widgets::Button* target);\n};\n\n}  // namespace CHLCC\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/chlcc/systemmenu.cpp",
    "content": "#include \"systemmenu.h\"\n\n#include \"../../profile/game.h\"\n#include \"../../renderer/renderer.h\"\n#include \"../../ui/ui.h\"\n#include \"../../vm/interface/input.h\"\n#include \"../../inputsystem.h\"\n#include \"../../profile/ui/systemmenu.h\"\n#include \"../../ui/widgets/chlcc/systemmenuentrybutton.h\"\n\n#include \"../../profile/games/chlcc/systemmenu.h\"\n#include \"../../profile/games/chlcc/commonmenu.h\"\n#include \"../../profile/games/chlcc/backlogmenu.h\"\n#include \"../../profile/games/chlcc/savemenu.h\"\n#include \"../../profile/games/chlcc/optionsmenu.h\"\n#include \"../../profile/games/chlcc/tipsmenu.h\"\n#include \"../../profile/games/chlcc/trophymenu.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace CHLCC {\n\nusing namespace Impacto::Profile::CHLCC::CommonMenu;\nusing namespace Impacto::Profile::CHLCC::SystemMenu;\nusing namespace Impacto::Profile::ScriptVars;\nusing namespace Impacto::Vm::Interface;\nusing namespace Impacto::Profile::SystemMenu;\nusing namespace Impacto::UI::Widgets::CHLCC;\nusing namespace Impacto::Input;\n\nvoid SystemMenu::MenuButtonOnClick(Widgets::Button* target) {\n  target->Hovered = false;\n  if (static_cast<SystemMenuEntryButton*>(target)->IsLocked) {\n    // Yep, that's similar to how it's done in the binary\n    // Binary checks for button state and if it's locked, PADone is modified\n    // like button press never happened, and then script reads inputs\n    PADinputButtonWentDown = PADinputButtonWentDown & ~PAD1A;\n    PADinputMouseWentDown = PADinputMouseWentDown & ~PAD1A;\n    return;\n  }\n\n  ScrWork[SW_SYSMENUCNO] = target->Id;\n  // Make the Id match the save menu mode (5th button would be Quick Load which\n  // is case 0)\n  UI::SaveMenuPtr->ActiveMenuType =\n      magic_enum::enum_cast<SaveMenuPageType>(target->Id % 4);\n  ChoiceMade = true;\n}\n\nSystemMenu::SystemMenu() : CommonMenu(false) {\n  CurrentColor = Profile::CHLCC::SystemMenu::BackgroundColor;\n  SubItemsTransition = MenuTransition;\n  auto onClick = [this](auto* btn) { return MenuButtonOnClick(btn); };\n\n  MainItems = new Widgets::Group(this);\n\n  for (int i = 0; i < MenuEntriesNum; i++) {\n    SystemMenuEntryButton* menuButton = new SystemMenuEntryButton(\n        i, MenuEntriesSprites[i], MenuEntriesSprites[i],\n        RgbIntToFloat(FocusTint), Sprite(), MenuEntriesPositions[i],\n        RectF(\n            MenuEntriesPositions[i].x - 30,\n            (i + 1) * MenuSelectionDotMultiplier + MenuSelectionPosition.y - 14,\n            300, 40));\n\n    menuButton->OnClickHandler = onClick;\n    MainItems->Add(menuButton, FDIR_DOWN);\n  }\n  MainItems->Children[0]->SetFocus(MainItems->Children[MenuEntriesNum - 1],\n                                   FDIR_UP);\n  MainItems->Children[MenuEntriesNum - 1]->SetFocus(MainItems->Children[0],\n                                                    FDIR_DOWN);\n}\n\nvoid SystemMenu::Show() {\n  if (State != Shown) {\n    State = Showing;\n    MenuTransition.StartIn();\n    if (UI::FocusedMenu != 0) {\n      LastFocusedMenu = UI::FocusedMenu;\n      LastFocusedMenu->IsFocused = false;\n    }\n    IsFocused = true;\n    UI::FocusedMenu = this;\n\n    SubItemShow();\n  }\n\n  bool noFreeSlots = SaveSystem::MaxSaveEntries ==\n                     SaveSystem::Implementation->GetLockedQuickSaveCount();\n  SetFlag(SF_SAVEALLPROTECTED, noFreeSlots);\n}\n\nvoid SystemMenu::Hide() {\n  if (State != Hidden) {\n    const bool isLoading = GetFlag(SF_RESTARTMASK);\n    if (isLoading) {\n      State = Hidden;\n      MenuTransition.Finish(AnimationDirection::Out);\n    } else {\n      State = Hiding;\n      MenuTransition.StartOut();\n    }\n    SubItemsHide(isLoading);\n    if (LastFocusedMenu != 0) {\n      UI::FocusedMenu = LastFocusedMenu;\n      LastFocusedMenu->IsFocused = true;\n    } else {\n      UI::FocusedMenu = 0;\n    }\n    IsFocused = false;\n  }\n}\n\nvoid SystemMenu::SubItemsHide(bool instantHide) {\n  if (SubItemsState != Hidden) {\n    if (instantHide) {\n      SubItemsState = Hidden;\n      SubItemsTransition.Finish(AnimationDirection::Out);\n    } else {\n      SubItemsState = Hiding;\n      SubItemsTransition.StartOut(true);\n    }\n  }\n  if (CurrentlyFocusedElement) {\n    auto* btn = static_cast<Widgets::Button*>(CurrentlyFocusedElement);\n    if (btn) {\n      LastFocusedButtonId = btn->Id;\n    }\n  }\n}\nvoid SystemMenu::SubItemShow() {\n  if (SubItemsState != Shown) {\n    SubItemsState = Showing;\n    SubItemsTransition.StartIn(true);\n    MainItems->Show();\n    SelectAnimation.StartIn(true);\n  }\n  if (LastFocusedButtonId && *LastFocusedButtonId < MenuEntriesNum) {\n    CurrentlyFocusedElement = MainItems->Children[*LastFocusedButtonId];\n    CurrentlyFocusedElement->HasFocus = true;\n  } else if (!CurrentlyFocusedElement) {\n    AdvanceFocus(FDIR_DOWN);\n  }\n}\n\nvoid SystemMenu::Update(float dt) {\n  UpdateInput(dt);\n  const bool isSysMenuOpen = GetFlag(SF_SYSTEMMENU);\n  const int sysMenuCt = ScrWork[SW_SYSMENUCT];\n  if ((!isSysMenuOpen || sysMenuCt < 10000) && State == Shown) {\n    Hide();\n  } else if (isSysMenuOpen && sysMenuCt > 0 && State == Hidden) {\n    Show();\n  }\n\n  // this branch is only for hiding sub items when menu is open\n  if (isSysMenuOpen && State == Shown) {\n    if (UI::FocusedMenu != this && SubItemsState == Shown &&\n        UI::SysMesBoxPtr->State == UI::MenuState::Hidden) {\n      const bool isLoading = GetFlag(SF_RESTARTMASK);\n      SubItemsHide(isLoading);\n    } else if (UI::FocusedMenu == this && SubItemsState == Hidden) {\n      SubItemShow();\n    }\n  }\n\n  if (MenuTransition.IsOut() && sysMenuCt == 0 && State == Hiding) {\n    MainItems->Hide();\n    State = Hidden;\n    if (CurrentlyFocusedElement) {\n      CurrentlyFocusedElement->HasFocus = false;\n      CurrentlyFocusedElement = nullptr;\n    }\n  } else if (MenuTransition.IsIn() && sysMenuCt == 10000 && State == Showing) {\n    State = Shown;\n  }\n\n  if (SubItemsTransition.IsOut() && SubItemsState == Hiding) {\n    MainItems->Hide();\n    if (CurrentlyFocusedElement) {\n      CurrentlyFocusedElement->HasFocus = false;\n      CurrentlyFocusedElement = nullptr;\n    }\n    SubItemsState = Hidden;\n  } else if (SubItemsTransition.IsIn() && SubItemsState == Showing) {\n    SubItemsState = Shown;\n  }\n\n  if (SubItemsState != Hidden) {\n    SubItemsTransition.Update(dt);\n    UpdateRightTitle();\n  }\n\n  if (State != Hidden) {\n    MenuTransition.Update(dt);\n    if (MenuTransition.Direction == AnimationDirection::Out &&\n        MenuTransition.Progress <= 0.72f) {\n      TitleFade.StartOut();\n    } else if (MenuTransition.IsIn() &&\n               (TitleFade.Direction == AnimationDirection::In ||\n                TitleFade.IsOut())) {\n      TitleFade.StartIn();\n    }\n    TitleFade.Update(dt);\n\n    UpdateTitles();\n\n    bool savesDisabled = GetFlag(SF_SAVEDISABLE);\n    bool noFreeSlots = SaveSystem::MaxSaveEntries ==\n                       SaveSystem::Implementation->GetLockedQuickSaveCount();\n    bool quickSaveLockState =\n        savesDisabled || SaveSystem::HasQSavedOnCurrentLine() || noFreeSlots;\n    static_cast<SystemMenuEntryButton*>(\n        MainItems->Children[static_cast<size_t>(MenuItems::QuickSave)])\n        ->IsLocked = quickSaveLockState;\n    static_cast<SystemMenuEntryButton*>(\n        MainItems->Children[static_cast<size_t>(MenuItems::Save)])\n        ->IsLocked = savesDisabled;\n  }\n\n  auto* btn = static_cast<Widgets::Button*>(CurrentlyFocusedElement);\n  if (btn) {\n    IndexOfActiveButton = btn->Id;\n  }\n  if (State != Hidden) {\n    // These animations should update when sysmenu is unfocused (fading to\n    // submenu/showing sysmesbox)\n    UpdateSmoothSelection(dt);\n    SelectAnimation.Update(dt);\n    UpdateRunningSelectedLabel(dt);\n    if (IsFocused) {\n      MainItems->UpdateInput(dt);\n      MainItems->Update(dt);\n\n      if ((CurrentInputDevice == Device::Mouse ||\n           CurrentInputDevice == Device::Touch) &&\n          ((PADinputMouseWentDown & PAD1A))) {\n        bool noButtonsHovered = true;\n        for (auto child : MainItems->Children) {\n          auto button = static_cast<SystemMenuEntryButton*>(child);\n          if (button->Hovered) {\n            noButtonsHovered = false;\n            break;\n          }\n        }\n\n        if (noButtonsHovered) {\n          PADinputMouseWentDown = PADinputMouseWentDown & ~PAD1A;\n          PADinputButtonWentDown = PADinputButtonWentDown & ~PAD1A;\n        }\n      }\n    }\n  }\n}\n\nvoid SystemMenu::Render() {\n  if (State == Hidden) return;\n  CommonMenu::DrawSysMenu(GetCurrentBgColor(), CircleSprite, MainMenuTitleText,\n                          MenuTitleTextAngle);\n  if (SubItemsState == Hidden) return;\n\n  if (MenuTransition.Progress < 0.22f) return;\n\n  CommonMenu::DrawButtonPrompt(MenuButtonPrompt, MenuButtonPromptPosition,\n                               SubItemsTransition);\n\n  glm::vec2 offset = SubItemsTransition.GetPageOffset();\n  if (IndexOfActiveButton >= 0 && State != Hidden) {\n    DrawRunningSelectedLabel(SelectionOffsetY +\n                             MenuRunningSelectedLabelPosition.y + offset.y);\n  }\n\n  Renderer->DrawSprite(Background, glm::vec2(BackgroundPosition.x, offset.y));\n  SelectAnimation.Draw(SelectMenuHeader, SelectMenuHeaderPositions, offset);\n\n  glm::vec3 tint = {1.0f, 1.0f, 1.0f};\n  if (SubItemsState == Shown) {\n    DrawLeftTitle(MainMenuTitleText, glm::vec4(tint, TitleFade.Progress));\n  }\n  Renderer->DrawSprite(MenuItemsLine,\n                       glm::vec2(MenuItemsLinePosition.x, offset.y));\n  if (IndexOfActiveButton >= 0 && State != Hidden) {\n    Renderer->DrawSprite(\n        MenuSelectionDot,\n        glm::vec2(MenuSelectionDotPosition.x,\n                  MenuSelectionDotPosition.y +\n                      IndexOfActiveButton * MenuSelectionDotMultiplier +\n                      +offset.y));\n    Renderer->DrawSprite(\n        MenuSelection,\n        glm::vec2(MenuSelectionPosition.x,\n                  MenuSelectionPosition.y + SelectionOffsetY + offset.y));\n  }\n\n  MainItems->MoveTo(offset);\n  MainItems->Tint = glm::vec4(tint, 1.0f);\n  MainItems->Render();\n}\n\ninline void SystemMenu::UpdateSmoothSelection(float dt) {\n  float target = MenuSelectionDotMultiplier * IndexOfActiveButton;\n  SelectionOffsetY += (target - SelectionOffsetY) * HoverLerpSpeed * dt;\n}\n\ninline void SystemMenu::UpdateRunningSelectedLabel(float dt) {\n  CurrentRunningPosition += SelectedLabelSpeed * dt;\n  if (glm::abs(CurrentRunningPosition) >=\n      MenuRunningSelectedLabel.Bounds.Width) {\n    CurrentRunningPosition = MenuRunningSelectedLabel.Bounds.Width -\n                             glm::abs(CurrentRunningPosition);\n  }\n}\n\ninline void SystemMenu::DrawRunningSelectedLabel(float offsetY) {\n  float x = 0;\n  for (int i = -1; i < (1280 / MenuRunningSelectedLabel.Bounds.Width) + 1;\n       i++) {\n    x = (i * (MenuRunningSelectedLabel.Bounds.Width - 3) +\n         CurrentRunningPosition);\n    Renderer->DrawSprite(\n        MenuRunningSelectedLabel,\n        glm::vec2(x, x * MenuRunningSelectedLabelAngle + offsetY));\n  }\n}\n\nvoid SystemMenu::UpdateTitles() {\n  // it also uses SelectAnimation for leftTitle\n  if (SelectAnimation.Progress < 0.362f) {\n    LeftTitlePos = glm::vec2(\n        MenuTitleTextPosition.x,\n        glm::mix(1.0f, Profile::DesignHeight + 1.0f,\n                 1.01011f * std::sin(1.62223f * (SelectAnimation.Progress *\n                                                 2.7604561455f) +\n                                     3.152f) +\n                     1.01012f));\n  } else if (SelectAnimation.Progress > 0.637f) {\n    LeftTitlePos = glm::vec2(\n        MenuTitleTextPosition.x,\n        glm::mix(-MainMenuTitleText.Bounds.Height, 1.0f,\n                 1.01011f * std::sin(1.62223f * ((SelectAnimation.Progress *\n                                                  2.7604559169f) -\n                                                 1.774f) +\n                                     3.152f) +\n                     1.01012f));\n  }\n\n  if (MenuTransition.Progress <= 0.34f) return;\n\n  RedTitleLabelPos = RedBarLabelPosition;\n\n  if (MenuTransition.Progress >= 0.73f) return;\n\n  RedTitleLabelPos +=\n      glm::mix(DiagonalTitlesOffsetStart, DiagonalTitlesOffsetEnd,\n               MenuTransition.Progress);\n}\n\nvoid SystemMenu::UpdateRightTitle() {\n  if (SubItemsTransition.Progress <= 0.34f) return;\n\n  RightTitlePos = MainMenuLabelRightPosition;\n\n  if (SubItemsTransition.Progress >= 0.73f) return;\n  RightTitlePos += glm::mix(DiagonalTitlesOffsetStart, DiagonalTitlesOffsetEnd,\n                            SubItemsTransition.Progress);\n}\n\nglm::vec4 SystemMenu::GetCurrentBgColor() {\n  const glm::vec4 sourceColor = RgbIntToFloat(BackgroundColor);\n  if (UI::FocusedMenu != this) {\n    if (GetFlag(SF_BACKLOGMENU)) {\n      CurrentColor = Profile::CHLCC::BacklogMenu::BackgroundColor;\n    } else if (GetFlag(SF_SAVEMENU)) {\n      CurrentColor = Profile::CHLCC::SaveMenu::BackgroundColor;\n    } else if (GetFlag(SF_OPTIONMENU)) {\n      CurrentColor = Profile::CHLCC::OptionsMenu::BackgroundColor;\n    } else if (GetFlag(SF_TIPSMENU)) {\n      CurrentColor = Profile::CHLCC::TipsMenu::BackgroundColor;\n    } else if (GetFlag(SF_ACHIEVEMENTMENU)) {\n      CurrentColor = Profile::CHLCC::TrophyMenu::BackgroundColor;\n    } else {\n      CurrentColor = BackgroundColor;\n    }\n  }\n  glm::vec4 targetColor = RgbIntToFloat(CurrentColor);\n  // crossfading from one color to another\n  return glm::mix(targetColor, sourceColor, SubItemsTransition.Progress);\n}\n\n}  // namespace CHLCC\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/chlcc/systemmenu.h",
    "content": "#pragma once\n\n#include <optional>\n#include \"commonmenu.h\"\n#include \"animations/menutransition.h\"\n#include \"../../ui/menu.h\"\n#include \"../../ui/widgets/group.h\"\n#include \"../../ui/widgets/button.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace CHLCC {\n\nclass SystemMenu : public Menu, public CommonMenu {\n public:\n  SystemMenu();\n\n  void Show() override;\n  void Hide() override;\n  void SubItemShow();\n  void SubItemsHide(bool instantHide);\n  void Update(float dt) override;\n  void Render() override;\n\n  void MenuButtonOnClick(Widgets::Button* target);\n\n private:\n  glm::vec4 GetCurrentBgColor();\n\n  using CommonMenu::UpdateTitles;\n  void UpdateTitles();\n  void UpdateRightTitle();\n  void DrawRunningSelectedLabel(float offsetY);\n  void UpdateRunningSelectedLabel(float dt);\n  void UpdateSmoothSelection(float dt);\n\n  enum class MenuItems : size_t {\n    Backlog,\n    Save,\n    Load,\n    QuickSave,\n    QuickLoad,\n    Config,\n    TipsList,\n    Trophy,\n    ReturnTitle\n  };\n\n  MenuTransitionAnimation SubItemsTransition;\n  Widgets::Group* MainItems;\n\n  float CurrentRunningPosition = 0.0f;\n  float SelectionOffsetY = 0.0f;\n  int IndexOfActiveButton = 0;\n  std::optional<int> LastFocusedButtonId;\n  uint32_t CurrentColor;\n  MenuState SubItemsState = Hidden;\n};\n\n}  // namespace CHLCC\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/chlcc/tipsmenu.cpp",
    "content": "#include \"tipsmenu.h\"\n\n#include \"../../renderer/renderer.h\"\n#include \"../../mem.h\"\n#include \"../../profile/scriptvars.h\"\n#include \"../../profile/dialogue.h\"\n#include \"../../profile/ui/tipsmenu.h\"\n#include \"../../profile/games/chlcc/tipsmenu.h\"\n#include \"../../profile/games/chlcc/commonmenu.h\"\n\n#include \"../../data/tipssystem.h\"\n#include \"../../vm/interface/input.h\"\n#include \"../../ui/widgets/chlcc/tipsentrybutton.h\"\n#include \"../../inputsystem.h\"\n\n#include <numeric>\n\nnamespace Impacto {\nnamespace UI {\nnamespace CHLCC {\n\nusing namespace Impacto::Profile::CHLCC::CommonMenu;\n\nusing namespace Impacto::Profile::TipsMenu;\nusing namespace Impacto::Profile::CHLCC::TipsMenu;\nusing namespace Impacto::Profile::ScriptVars;\n\nusing namespace Impacto::Vm::Interface;\n\nusing namespace Impacto::UI::Widgets;\nusing namespace Impacto::UI::Widgets::CHLCC;\n\nstatic float GetEndScroll(Group* tipsItemsGroup) {\n  if (tipsItemsGroup->Children.empty() || tipsItemsGroup->Children.size() == 1)\n    return 0.0f;\n  const Widget* lastItem = tipsItemsGroup->Children.back();\n  const float lastItemEndPos = lastItem->Bounds.Height + lastItem->Bounds.Y;\n  const float pagePos =\n      lastItemEndPos - TipsListBounds.Height - TipsListBounds.Y;\n  if (pagePos < 0.0f) return 0.0f;\n  return round(pagePos / TipListYPadding) * TipListYPadding;\n}\n\nvoid TipsMenu::HandlePageChange(Widget* cur, Widget* next) {\n  if (cur != next) {\n    static_cast<Group*>(cur)->MoveTo(TipsListBounds.GetPos());\n    cur->Hide();\n    next->Show();\n    CurrentlyFocusedElement =\n        static_cast<Group*>(next)->GetFirstFocusableChild();\n    CurrentlyFocusedElement->HasFocus = true;\n    TipsEntryScrollPos = 0.0f;\n    TipsEntriesScrollbar = Scrollbar(\n        0, {TipsListBounds.X + TipsListBounds.Width - 3.0f, TipsListBounds.Y},\n        0.0f, GetEndScroll(static_cast<Group*>(next)), &TipsEntryScrollPos,\n        SBDIR_VERTICAL, TipsScrollTrack, TipsScrollThumb, {0.0f, -4.0f},\n        TipsScrollThumb.ScaledHeight(), TipsListBounds);\n    TipsEntriesScrollbar->Step = TipListYPadding;\n  }\n}\n\nTipsMenu::TipsMenu()\n    : CommonMenu(true),\n      ItemsList(\n          Widgets::CarouselDirection::CDIR_HORIZONTAL,\n          [this](Widget* cur, Widget* next) { HandlePageChange(cur, next); },\n          [this](Widget* cur, Widget* next) { HandlePageChange(cur, next); }),\n      TipViewItems(this) {\n  TipViewItems.FocusLock = false;\n\n  TextPage.Glyphs.reserve(Profile::Dialogue::MaxPageSize);\n  TextPage.Clear();\n  TextPage.Mode = DPM_TIPS;\n  TextPage.FadeAnimation.Progress = 1.0f;\n}\n\nvoid TipsMenu::Show() {\n  if (State != Shown) {\n    if (State != Showing) {\n      MenuTransition.StartIn();\n      FromSystemMenuTransition->StartIn();\n      SelectAnimation.StartIn(true);\n    }\n    State = Showing;\n    if (UI::FocusedMenu != 0) {\n      LastFocusedMenu = UI::FocusedMenu;\n      LastFocusedMenu->IsFocused = false;\n    }\n    UI::FocusedMenu = this;\n    TipsEntryScrollPos = 0.0f;\n    (*ItemsList.GetCurrent())->Show();\n    (*ItemsList.GetCurrent())->HasFocus = false;\n    TipViewItems.Bounds = RectF{};\n    TipViewItems.Show();\n    if (TipsEntriesScrollbar) {\n      TipsEntriesScrollbar->MoveTo(\n          {TipsListBounds.X + TipsListBounds.Width - 3.0f, TipsListBounds.Y});\n    }\n  }\n}\nvoid TipsMenu::Hide() {\n  if (State != Hidden) {\n    if (State != Hiding) {\n      MenuTransition.StartOut();\n      FromSystemMenuTransition->StartOut();\n    }\n    State = Hiding;\n    if (LastFocusedMenu != 0) {\n      UI::FocusedMenu = LastFocusedMenu;\n    } else {\n      UI::FocusedMenu = 0;\n    }\n    IsFocused = false;\n  }\n}\n\nvoid TipsMenu::Render() {\n  if (State == Hidden) return;\n  CommonMenu::DrawSubmenu(BackgroundColor, CircleSprite, MenuTitleText,\n                          MenuTitleTextAngle);\n  const glm::vec2 animationOffset = MenuTransition.GetPageOffset();\n\n  if (MenuTransition.State != AnimationState::Stopped) {\n    Group* currentPage = static_cast<Group*>(*ItemsList.GetCurrent());\n    const glm::vec2 pgOffset = [&] {\n      glm::vec2 fOffset = TipsListBounds.GetPos() + animationOffset;\n      return glm::vec2{std::round(fOffset.x),\n                       std::round(fOffset.y - TipsEntryScrollPos)};\n    }();\n    const glm::vec2 renderBoundsOffset =\n        TipsListRenderBounds.GetPos() + animationOffset;\n\n    currentPage->MoveTo(pgOffset);\n    currentPage->RenderingBounds.X = std::round(renderBoundsOffset.x);\n    currentPage->RenderingBounds.Y = std::round(renderBoundsOffset.y);\n    TipViewItems.MoveTo(animationOffset);\n    TextPage.MoveTo(Profile::Dialogue::TipsBounds.GetPos() + animationOffset);\n    if (TipsEntriesScrollbar) {\n      const glm::vec2 scrollbarOffset =\n          animationOffset +\n          glm::vec2{TipsListBounds.X + TipsListBounds.Width, TipsListBounds.Y};\n      TipsEntriesScrollbar->MoveTo(scrollbarOffset);\n    }\n  }\n\n  DrawTipsTree();\n  SelectAnimation.Draw(SelectWordSprites, SelectWordPos, animationOffset);\n  DrawLeftTitle(MenuTitleText);\n\n  if (MenuTransition.Progress > 0.34) {\n    // Alpha goes from 0 to 1 in half the time\n    const float alpha = std::clamp(MenuTransition.Progress * 2.0f, 0.0f, 1.0f);\n    ItemsList.Tint.a = alpha;\n    ItemsList.Render();\n    if (CurrentlyDisplayedTipId != -1) {\n      TipViewItems.Tint.a = alpha;\n      TipViewItems.Render();\n      Renderer->DrawProcessedText(TextPage.Glyphs,\n                                  Profile::Dialogue::DialogueFont, alpha,\n                                  RendererOutlineMode::Full, true);\n    }\n    if (TipsEntriesScrollbar) {\n      TipsEntriesScrollbar->Tint.a = alpha;\n      TipsEntriesScrollbar->Render();\n    }\n  }\n\n  DrawButtonPrompt(ButtonPromptSprite, ButtonPromptPosition);\n}\n\nvoid TipsMenu::UpdateInput(float dt) {\n  if (State == Shown) {\n    ItemsList.UpdateInput(dt);\n    TipViewItems.UpdateInput(dt);\n    UpdatePageInput(dt);\n    if (CurrentlyDisplayedTipId != -1) {\n      if (PADinputButtonWentDown & PAD1X) {\n        AdvanceTipPage(TipAdvanceMode::NextLooped);\n      }\n\n      if (Input::CurrentInputDevice == Input::Device::Mouse) {\n        const RectF tipBox(CurrentTipBackgroundPosition.x,\n                           CurrentTipBackgroundPosition.y,\n                           CurrentTipBackgroundSprite.Bounds.Width,\n                           CurrentTipBackgroundSprite.Bounds.Height);\n\n        if (tipBox.ContainsPoint(Input::CurMousePos)) {\n          if (Input::MouseWheelDeltaY > 0.0f) {\n            AdvanceTipPage(TipAdvanceMode::PrevClamped);\n          } else if (Input::MouseWheelDeltaY < 0.0f) {\n            AdvanceTipPage(TipAdvanceMode::NextClamped);\n          }\n        }\n      }\n\n      PrevPageTipClickArea.UpdateInput(dt);\n      NextPageTipClickArea.UpdateInput(dt);\n    }\n  }\n}\n\nvoid TipsMenu::Update(float dt) {\n  const int sysMenuCt = ScrWork[SW_SYSMENUCT];\n  const int systemMenuCHG = ScrWork[SW_SYSTEMMENUCHG];\n\n  if ((!GetFlag(SF_TIPSMENU) || sysMenuCt < 10000 ||\n       (sysMenuCt == 10000 && systemMenuCHG != 0 && systemMenuCHG != 64)) &&\n      State == Shown) {\n    Hide();\n  } else if (GetFlag(SF_TIPSMENU) && sysMenuCt > 0 && State == Hidden) {\n    Show();\n  }\n\n  if (MenuTransition.IsOut() && !GetFlag(SF_TIPSMENU) && systemMenuCHG == 0 &&\n      (sysMenuCt == 0 || GetFlag(SF_SYSTEMMENU)) && State == Hiding) {\n    State = Hidden;\n\n    (*ItemsList.GetCurrent())->Hide();\n    TipViewItems.Hide();\n\n    for (const auto tipNewId : TipsSystem::GetNewTipsIndices()) {\n      TipsSystem::SetTipNewState(tipNewId, false);\n    }\n    TipsSystem::GetNewTipsIndices().resize(0);\n    CurrentlyDisplayedTipId = -1;\n    if (LastFocusedMenu) LastFocusedMenu->IsFocused = true;\n  } else if (MenuTransition.IsIn() && sysMenuCt == 10000 &&\n             (systemMenuCHG == 0 || systemMenuCHG == 64) &&\n             GetFlag(SF_TIPSMENU) && State == Showing) {\n    State = Shown;\n    IsFocused = true;\n    (*ItemsList.GetCurrent())->HasFocus = true;\n    AdvanceFocus(FDIR_DOWN);\n  }\n\n  if (State != Hidden) {\n    MenuTransition.Update(dt);\n    FromSystemMenuTransition->Update(dt);\n    SelectAnimation.Update(dt);\n    if (MenuTransition.Direction == AnimationDirection::Out &&\n        MenuTransition.Progress <= 0.72f) {\n      TitleFade.StartOut();\n    } else if (MenuTransition.IsIn() &&\n               (TitleFade.Direction == AnimationDirection::In ||\n                TitleFade.IsOut())) {\n      TitleFade.StartIn();\n    }\n    TitleFade.Update(dt);\n    UpdateTitles(MenuTitleTextRightPosition, MenuTitleTextLeftPosition);\n    if (TipsEntriesScrollbar) {\n      TipsEntriesScrollbar->Update(dt);\n    }\n\n    if (CurrentlyDisplayedTipId != -1) {\n      PrevPageTipClickArea.Update(dt);\n      NextPageTipClickArea.Update(dt);\n    }\n  }\n\n  if (State == Shown) {\n    UpdateInput(dt);\n    ItemsList.Update(dt);\n    TipViewItems.Update(dt);\n  }\n}\n\nvoid TipsMenu::TipOnClick(Button* target) {\n  auto tipEntry = static_cast<TipsEntryButton*>(target);\n  if (!tipEntry->TipEntryRecord->IsLocked) {\n    CurrentlyFocusedElement = target;\n    SwitchToTipId(tipEntry->TipEntryRecord->Id);\n  }\n}\n\nvoid TipsMenu::Init() {\n  auto onClick = [this](auto* btn) { return TipOnClick(btn); };\n  int currentCategoryId = -1;\n\n  // String of characters by which tips are sorted, taken from _system script\n  auto [scriptBufId, catStrAddr] =\n      Vm::ScriptGetTextTableStrAddress(TipsStringTable, CategoryStringIndex);\n  uint8_t* categoryString = &Vm::ScriptBuffers[scriptBufId][catStrAddr];\n  ItemsList.Clear();\n  TipViewItems.Clear();\n  Group* allTipsGroup = new Group(this);\n  allTipsGroup->Bounds = TipsListBounds;\n  allTipsGroup->RenderingBounds = TipsListRenderBounds;\n  // Add a bit of margins\n\n  ItemsList.Add(allTipsGroup);\n  TipsEntryScrollPos = 0;\n  // Sorting tip records\n  auto& records = *TipsSystem::GetTipRecords();\n  const auto recordCount = TipsSystem::GetTipCount();\n  auto indexes = [recordCount] {\n    std::vector<int> result(recordCount);\n    std::iota(result.begin(), result.end(), 0);\n    std::ranges::sort(result, TipsSystem::TipsComparator(TipsStringTable,\n                                                         SortStringIndex, 2));\n    return result;\n  }();\n  auto sortedView = std::ranges::views::transform(\n      indexes, [&records](size_t i) -> TipsSystem::TipsDataRecord& {\n        return records[i];\n      });\n  std::vector<int> sortedIndicesMap(recordCount);\n  for (int i = 0; i < std::ssize(sortedView); i++) {\n    sortedIndicesMap[sortedView[i].Id] = i;\n  }\n\n  auto createCategory = [&](auto labelText, float yPos) {\n    Label* categoryLabel = new Label();\n    categoryLabel->Bounds.X = TipListEntryBounds.X;\n    categoryLabel->Bounds.Y = yPos;\n    categoryLabel->Bounds.X += 5;\n    categoryLabel->SetText(labelText, TipListEntryFontSize,\n                           RendererOutlineMode::Full, 0);\n    categoryLabel->Bounds.X -= 5;\n    return categoryLabel;\n  };\n  {\n    float currentY = TipListEntryBounds.Y;\n    for (auto& record : sortedView) {\n      //  Each category is a character from the sort string and contains all\n      //  tips the names of which begin with that character\n\n      // Start new category\n      // We take a character from the sort string and use that as the\n      // category name inside a predefined template\n      if (record.CategoryLetterIndex != currentCategoryId) {\n        currentCategoryId = record.CategoryLetterIndex;\n        std::ranges::copy(CategoryString,\n                          std::back_inserter(CategoryStringBuffer));\n        if (currentCategoryId != std::numeric_limits<uint16_t>::max()) {\n          CategoryStringBuffer[1] = UnalignedRead<uint16_t>(\n              &categoryString[currentCategoryId * sizeof(uint16_t)]);\n        }\n        Vm::Sc3Stream categoryStrStream(\n            reinterpret_cast<uint8_t*>(CategoryStringBuffer.data()));\n        allTipsGroup->Add(createCategory(categoryStrStream, currentY));\n        currentY += TipListYPadding;\n      }\n\n      // Actual tip entry button\n      RectF bounds = TipListEntryBounds;\n      bounds.Y = currentY;\n      TipsEntryButton* button = new TipsEntryButton(\n          sortedIndicesMap[record.Id], &record, bounds, TipsEntryHighlightBar);\n      button->OnClickHandler = onClick;\n\n      allTipsGroup->Add(button, FDIR_DOWN);\n      currentY += TipListYPadding;\n    }\n  }\n  {\n    float currentY = TipListEntryBounds.Y;\n    auto* newTipsGroup = new Group(this);\n    newTipsGroup->Add(createCategory(\n        Vm::ScriptGetTextTableStrAddress(TipsStringTable, NewLabelStrIndex),\n        currentY));\n    currentY += TipListYPadding;\n\n    auto newRecordsView =\n        std::ranges::views::transform(\n            TipsSystem::GetNewTipsIndices(),\n            [&records](size_t i) -> TipsSystem::TipsDataRecord& {\n              return records[i];\n            }) |\n        std::views::reverse;\n    for (auto& record : newRecordsView) {\n      // assert(!record.IsLocked && record.IsNew);\n      RectF bounds = TipListEntryBounds;\n      bounds.Y = currentY;\n      TipsEntryButton* button = new TipsEntryButton(\n          sortedIndicesMap[record.Id], &record, bounds, TipsEntryHighlightBar);\n      button->OnClickHandler = onClick;\n\n      newTipsGroup->Add(button, FDIR_DOWN);\n      currentY += TipListYPadding;\n    }\n    if (newTipsGroup->Children.size() > 1) {\n      ItemsList.Add(newTipsGroup);\n      newTipsGroup->Bounds = TipsListBounds;\n      newTipsGroup->RenderingBounds = TipsListRenderBounds;\n    } else {\n      delete newTipsGroup;\n      newTipsGroup = nullptr;\n    }\n  }\n\n  {\n    float currentY = TipListEntryBounds.Y;\n\n    Group* unreadTipsGroup = new Group(this);\n    unreadTipsGroup->Add(createCategory(\n        Vm::ScriptGetTextTableStrAddress(TipsStringTable, UnreadLabelStrIndex),\n        currentY));\n    currentY += TipListYPadding;\n    for (auto& record : sortedView) {\n      if (!record.IsUnread || record.IsLocked) {\n        continue;\n      }\n      RectF bounds = TipListEntryBounds;\n      bounds.Y = currentY;\n      TipsEntryButton* button = new TipsEntryButton(\n          sortedIndicesMap[record.Id], &record, bounds, TipsEntryHighlightBar);\n      button->OnClickHandler = onClick;\n\n      unreadTipsGroup->Add(button, FDIR_DOWN);\n      currentY += TipListYPadding;\n    }\n    if (unreadTipsGroup->Children.size() > 1) {\n      ItemsList.Add(unreadTipsGroup);\n      unreadTipsGroup->Bounds = TipsListBounds;\n      unreadTipsGroup->RenderingBounds = TipsListRenderBounds;\n    } else {\n      delete unreadTipsGroup;\n    }\n  }\n\n  TipsEntriesScrollbar =\n      Scrollbar(0, {TipsListBounds.X + TipsListBounds.Width, TipsListBounds.Y},\n                0.0f, GetEndScroll(allTipsGroup), &TipsEntryScrollPos,\n                SBDIR_VERTICAL, TipsScrollTrack, TipsScrollThumb, {0.0f, 0.0f},\n                TipsScrollThumb.ScaledHeight(), TipsListBounds);\n  TipsEntriesScrollbar->Step = TipListYPadding;\n  Name = new Label();\n  Name->Bounds = NameInitialBounds;\n  TipViewItems.Add(Name);\n\n  Pronounciation = new Label();\n  Pronounciation->Bounds = PronounciationInitialBounds;\n  TipViewItems.Add(Pronounciation);\n\n  // Number label\n  NumberText = new Label(\n      Vm::ScriptGetTextTableStrAddress(TipsStringTable, NumberLabelStrIndex),\n      NumberLabelPosition, NumberLabelFontSize, RendererOutlineMode::Full,\n      DefaultColorIndex);\n  TipViewItems.Add(NumberText);\n  // Tip number\n  Number = new Label();\n  Number->Bounds = NumberBounds;\n  TipViewItems.Add(Number);\n  // Tip page separator\n  auto* const pageSeparator =\n      new Label(PageSeparatorSprite, PageSeparatorPosition);\n  TipViewItems.Add(pageSeparator);\n  // Current tip page\n  CurrentPage = new Label();\n  CurrentPage->Bounds.SetPos(CurrentPagePosition);\n  TipViewItems.Add(CurrentPage);\n  // Total tip pages\n  TotalPages = new Label();\n  TotalPages->Bounds.SetPos(TotalPagesPosition);\n  TipViewItems.Add(TotalPages);\n\n  // use size from CurrentPageSprite and use Y coord from CurrentPagePos\n  const glm::vec2 pageNumberSize = CurrentPageSprites[0].Bounds.GetSize();\n  RectF pagerBounds = RectF(CurrentPagePosition.x, CurrentPagePosition.y,\n                            pageNumberSize.x, pageNumberSize.y);\n  const std::function<void(ClickArea*)> onClickPrev = [this](auto* btn) {\n    return AdvanceTipPage(TipAdvanceMode::PrevClamped);\n  };\n  PrevPageTipClickArea = ClickArea(0, pagerBounds, onClickPrev);\n\n  pagerBounds.X = TotalPagesPosition.x;\n  const std::function<void(ClickArea*)> onClickNext = [this](auto* btn) {\n    return AdvanceTipPage(TipAdvanceMode::NextClamped);\n  };\n  NextPageTipClickArea = ClickArea(1, pagerBounds, onClickNext);\n}\n\nvoid TipsMenu::UpdatePageInput(float dt) {\n  using namespace Vm::Interface;\n  if (!IsFocused) return;\n  auto prevEntry = CurrentlyFocusedElement;\n  const float oldScrollPos = TipsEntryScrollPos;\n\n  auto checkScrollBounds = [&]() {\n    return !TipsListBounds.Contains(CurrentlyFocusedElement->Bounds);\n  };\n\n  UI::TipsMenu::UpdateInput(dt);\n\n  auto* curPage = static_cast<Group*>(*ItemsList.GetCurrent());\n\n  if (CurrentlyFocusedElement != prevEntry && checkScrollBounds()) {\n    if (CurrentlyFocusedElement == curPage->GetFirstFocusableChild()) {\n      TipsEntryScrollPos = 0;\n    } else if (CurrentlyFocusedElement == curPage->Children.back()) {\n      TipsEntryScrollPos = TipsEntriesScrollbar->EndValue;\n    } else {\n      TipsEntryScrollPos +=\n          CurrentlyFocusedElement->Bounds.Y - prevEntry->Bounds.Y;\n    }\n  }\n\n  if (TipsEntriesScrollbar) {\n    TipsEntriesScrollbar->UpdateInput(dt);\n  }\n\n  if (oldScrollPos != TipsEntryScrollPos) {\n    float delta = oldScrollPos - TipsEntryScrollPos;\n    if (std::fmod(std::abs(delta), TipListYPadding) >\n        std::numeric_limits<float>::epsilon()) {\n      const float newDelta =\n          std::round(delta / TipListYPadding) * TipListYPadding;\n      TipsEntryScrollPos = oldScrollPos - newDelta;\n      delta = newDelta;\n    }\n    curPage->Move({0, delta});\n\n    if (TipsEntriesScrollbar && TipsEntriesScrollbar->IsScrollHeld() &&\n        CurrentlyFocusedElement) {\n      // advance focus during drag\n      FocusDirection dir = (delta < 0) ? FDIR_DOWN : FDIR_UP;\n      Widget* startElement = CurrentlyFocusedElement;\n      while (!TipsListBounds.Contains(CurrentlyFocusedElement->Bounds)) {\n        AdvanceFocus(dir);\n        if (CurrentlyFocusedElement == startElement) break;\n      }\n    }\n  }\n\n  if (!TipsEntriesScrollbar || !TipsEntriesScrollbar->IsScrollHeld())\n    curPage->UpdateInput(dt);\n}\n\nvoid TipsMenu::DrawTipsTree() {\n  const glm::vec2 animationOffset = MenuTransition.GetPageOffset();\n  glm::vec2 currentTipBackgroundPosition(\n      CurrentTipBackgroundPosition.x,\n      CurrentTipBackgroundPosition.y + animationOffset.y);\n  Renderer->DrawSprite(CurrentTipBackgroundSprite,\n                       currentTipBackgroundPosition);\n  glm::vec2 gradientPosition(GradientPosition.x,\n                             GradientPosition.y + animationOffset.y);\n  Renderer->DrawSprite(TipsGradient, gradientPosition);\n  const float gradientEndY =\n      TipsListBounds.Y + TipsListBounds.Height + animationOffset.y - 5.0f;\n  Renderer->DrawQuad(\n      RectF(GradientPosition.x, gradientEndY, TipsGradient.Bounds.Width, 91.0f),\n      RgbIntToFloat(EndOfGradientColor));\n  glm::vec2 treePosition(TreePosition.x, TreePosition.y + animationOffset.y);\n  Renderer->DrawSprite(TipsTree, treePosition);\n\n  const auto* const currentPage =\n      static_cast<Widgets::Group*>(*ItemsList.GetCurrent());\n  size_t i = 0;\n  for (const auto* const widget : currentPage->Children) {\n    const glm::vec2 pos = widget->Bounds.GetPos();\n    const glm::vec2 linePos = pos + glm::vec2{-22, 0};\n    const float bottomEdge =\n        animationOffset.y + TipsListBounds.Y + TipsListBounds.Height;\n    const bool lastViewable = bottomEdge - pos.y < TipListYPadding + 1.0f &&\n                              bottomEdge - pos.y > 0.0f;\n\n    std::reference_wrapper<const Sprite> barSprite = TipsListBgBar;\n    std::reference_wrapper<const Sprite> lineSprite = TipsLeftLine;\n    if (const auto* const btn = dynamic_cast<const TipsEntryButton*>(widget)) {\n      barSprite = TipsListBgBarHole;\n      lineSprite = TipsLeftLineHole;\n      if (btn == currentPage->Children.back()) {\n        lineSprite = TipsLeftLineHoleEnd;\n      }\n    }\n\n    Renderer->EnableScissor();\n    Renderer->SetScissorRect(currentPage->RenderingBounds);\n    Renderer->DrawSprite(barSprite, pos);\n    Renderer->DrawSprite(lineSprite, linePos);\n    Renderer->DisableScissor();\n    if (lastViewable && widget != currentPage->Children.back())\n      Renderer->DrawSprite(TipsLeftLineEnd,\n                           linePos + glm::vec2{0, TipListYPadding});\n    ++i;\n  }\n  // Fill in the rest of the bg if tips is less than full page\n  if (i * TipListYPadding < TipsListBounds.Height) {\n    const float remainder = TipsListBounds.Height - (i * TipListYPadding);\n    const float start =\n        TipsListBounds.Y + animationOffset.y + (i * TipListYPadding);\n    RectF dest{\n        GradientPosition.x + animationOffset.x,\n        start,\n        TipsListBounds.Width,\n        remainder,\n    };\n    Renderer->DrawQuad(dest, RgbIntToFloat(EndOfGradientColor));\n  }\n}\n\nvoid TipsMenu::SwitchToTipId(int id) {\n  CurrentlyDisplayedTipId = id;\n  CurrentTipPage = 1;\n  TipsSystem::SetTipUnreadState(id, false);\n  TipsSystem::SetTipNewState(id, false);\n  auto tipsScriptBufferId = TipsSystem::GetTipsScriptBufferId();\n\n  auto tipRecord = TipsSystem::GetTipRecord(id);\n  Name->SetText({.ScriptBufferId = tipsScriptBufferId,\n                 .IpOffset = tipRecord->StringAdr[0]},\n                NameFontSize, RendererOutlineMode::Full, DefaultColorIndex);\n  Pronounciation->SetText({.ScriptBufferId = tipsScriptBufferId,\n                           .IpOffset = tipRecord->StringAdr[1]},\n                          PronounciationFontSize, RendererOutlineMode::Full,\n                          DefaultColorIndex);\n  // Right alignment\n  Name->MoveTo(NameInitialBounds.GetPos() -\n               glm::vec2{Name->Bounds.Width, 0.0f});\n  Pronounciation->MoveTo(PronounciationInitialBounds.GetPos() -\n                         glm::vec2{Pronounciation->Bounds.Width, 0.0f});\n\n  const int sortedTipId =\n      static_cast<TipsEntryButton*>(CurrentlyFocusedElement)->Id;\n  Number->SetText(fmt::format(\"{:4d}\", sortedTipId + 1), NumberFontSize,\n                  RendererOutlineMode::Full, DefaultColorIndex);\n\n  CurrentPage->SetSprite(CurrentPageSprites[CurrentTipPage]);\n  TotalPages->SetSprite(TotalPageSprites[tipRecord->NumberOfContentStrings]);\n\n  TextPage.Clear();\n  Vm::Sc3VmThread dummy;\n  dummy.IpOffset = tipRecord->StringAdr[3];\n  dummy.ScriptBufferId = tipsScriptBufferId;\n  TextPage.AddString(&dummy);\n}\n\nvoid TipsMenu::AdvanceTipPage(TipAdvanceMode mode) {\n  auto currentRecord = TipsSystem::GetTipRecord(CurrentlyDisplayedTipId);\n  const int numberOfContentStrings =\n      static_cast<int>(currentRecord->NumberOfContentStrings);\n  if (numberOfContentStrings == 1) return;\n  CurrentTipPage += mode == TipAdvanceMode::PrevClamped ? -1 : 1;\n  switch (mode) {\n    case TipAdvanceMode::PrevClamped: {\n      CurrentTipPage = std::max(CurrentTipPage, 1);\n      break;\n    }\n    case TipAdvanceMode::NextClamped: {\n      CurrentTipPage = std::min(CurrentTipPage, numberOfContentStrings);\n      break;\n    }\n    case TipAdvanceMode::NextLooped: {\n      if (CurrentTipPage > numberOfContentStrings) CurrentTipPage = 1;\n      break;\n    }\n  }\n\n  TextPage.Clear();\n  Vm::Sc3VmThread dummy;\n  dummy.IpOffset = currentRecord->StringAdr[3 + CurrentTipPage - 1];\n  dummy.ScriptBufferId = TipsSystem::GetTipsScriptBufferId();\n  TextPage.AddString(&dummy);\n  CurrentPage->SetSprite(CurrentPageSprites[CurrentTipPage]);\n}\n\n}  // namespace CHLCC\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/chlcc/tipsmenu.h",
    "content": "#pragma once\n\n#include \"commonmenu.h\"\n#include \"../../ui/tipsmenu.h\"\n#include \"../../ui/widgets/group.h\"\n#include \"../../ui/widgets/button.h\"\n#include \"../../ui/widgets/clickarea.h\"\n#include \"../../ui/widgets/carousel.h\"\n#include \"../../ui/widgets/label.h\"\n#include \"../../ui/widgets/scrollbar.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace CHLCC {\n\nclass TipsMenu : public UI::TipsMenu, public CommonMenu {\n public:\n  TipsMenu();\n\n  void Init() override;\n\n  void Show() override;\n  void Hide() override;\n  void UpdateInput(float dt) override;\n  void Update(float dt) override;\n  void Render() override;\n  void UpdatePageInput(float dt);\n\n  void TipOnClick(Widgets::Button* target);\n\n protected:\n  void SwitchToTipId(int id) override;\n  void AdvanceTipPage(TipAdvanceMode mode) override;\n\n private:\n  void DrawTipsTree();\n  void HandlePageChange(Widget* cur, Widget* next);\n\n  Widgets::ClickArea PrevPageTipClickArea;\n  Widgets::ClickArea NextPageTipClickArea;\n\n  int CurrentTipPage = 1;\n  float TipsEntryScrollPos = 0;\n\n  Widgets::Carousel ItemsList;\n  Widgets::Group TipViewItems;\n  Widgets::Label* CurrentPage;\n  Widgets::Label* TotalPages;\n  std::vector<uint16_t> CategoryStringBuffer;\n  std::optional<Widgets::Scrollbar> TipsEntriesScrollbar;\n};\n\n}  // namespace CHLCC\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/chlcc/tipssystem.cpp",
    "content": "#include \"tipssystem.h\"\n\n#include \"../../data/savesystem.h\"\n#include \"../../vm/vm.h\"\n#include \"../../io/memorystream.h\"\n#include \"../../profile/data/tipssystem.h\"\n#include \"../../profile/games/chlcc/tipsmenu.h\"\n#include \"../../profile/charset.h\"\n#include \"../../ui/ui.h\"\n\nnamespace Impacto {\nnamespace CHLCC {\n\nusing namespace Impacto::Vm;\nusing namespace Impacto::Profile::TipsSystem;\nusing namespace Impacto::Profile::CHLCC::TipsMenu;\nusing namespace Impacto::Io;\n\nvoid TipsSystem::DataInit(uint32_t scriptBufferId, uint32_t tipsDataAdr,\n                          uint32_t tipsDataSize) {\n  ScriptBufferId = (uint8_t)scriptBufferId;\n  const auto scriptBuffer = ScriptBuffers[scriptBufferId];\n\n  const std::unordered_map<uint16_t, int> strIndicesMap = [&] {\n    std::unordered_map<uint16_t, int> sc3Map;\n    auto [scrBufId, offset] =\n        ScriptGetTextTableStrAddress(TipsStringTable, SortStringIndex);\n\n    auto sortStr = &ScriptBuffers[scrBufId][offset];\n    size_t i = 0;\n    int distance = 0;\n    while (sortStr[i] != 0xFF) {\n      if (sortStr[i] & 0x80) {\n        uint16_t currentSc3Char =\n            SDL_SwapBE16(UnalignedRead<uint16_t>(sortStr + i));\n        i += 2;\n        sc3Map.try_emplace(currentSc3Char, distance++);\n      } else {\n        ImpLogSlow(LogLevel::Error, LogChannel::VM,\n                   \"TipsSorter: SC3 Tag Found in Sort String\\n\", sortStr[i]);\n        i++;\n      }\n    }\n    return sc3Map;\n  }();\n\n  // Read tips data from the script and create UI elements for each tip\n  MemoryStream stream =\n      MemoryStream(&scriptBuffer[tipsDataAdr], tipsDataSize, false);\n  uint16_t numberOfContentStrings = ReadLE<uint16_t>(&stream);\n  while (numberOfContentStrings != 255) {\n    if (TipEntryCount >= MaxTipsCount) {\n      ImpLog(LogLevel::Error, LogChannel::VM, \"Too many tips in tips data\\n\");\n      break;\n    }\n    // Read tip entry from the data array\n    TipsDataRecord record{\n        .Id = static_cast<uint16_t>(TipEntryCount),\n        .NumberOfContentStrings = numberOfContentStrings,\n        .IsLocked = true,\n        .IsUnread = true,\n        .IsNew = true,\n    };\n    ReadLE<uint16_t>(&stream);  // Reads in a padding space string\n    for (uint16_t i = 0; i < record.NumberOfContentStrings + 3; i++) {\n      record.StringAdr[i] =\n          ScriptGetStrAddress(scriptBufferId, ReadLE<uint16_t>(&stream));\n    }\n    auto sortStrIndex = record.StringAdr[2];\n    auto firstChar = SDL_SwapBE16(\n        UnalignedRead<uint16_t>(&ScriptBuffers[scriptBufferId][sortStrIndex]));\n    auto sortIndexItr = strIndicesMap.find(firstChar);\n    assert(sortIndexItr != strIndicesMap.end());\n    record.CategoryLetterIndex =\n        static_cast<uint16_t>(SortCategoryMapping->at(sortIndexItr->second));\n    Records[TipEntryCount] = std::move(record);\n\n    // Next tip entry from the data array\n    numberOfContentStrings = Io::ReadLE<uint16_t>(&stream);\n    TipEntryCount++;\n  }\n\n  Records.resize(TipEntryCount);\n}\n\nvoid TipsSystem::UpdateTipRecords() {\n  if (TipEntryCount != 0) {\n    for (size_t i = 0; i < TipEntryCount; i++) {\n      TipsDataRecord& record = Records[i];\n      const uint32_t tipStatus = SaveSystem::GetTipStatus(record.Id);\n      record.IsLocked = (tipStatus & 1) == 0;\n      record.IsUnread = (tipStatus & 2) == 0;\n      record.IsNew = (tipStatus & 4) == 0;\n    }\n  }\n}\n\nvoid TipsSystem::SetTipLockedState(size_t id, bool state) {\n  Records[id].IsLocked = state;\n  SaveSystem::SetTipStatus(id, Records[id].IsLocked, Records[id].IsUnread,\n                           Records[id].IsNew);\n}\n\nvoid TipsSystem::SetTipUnreadState(size_t id, bool state) {\n  Records[id].IsUnread = state;\n  SaveSystem::SetTipStatus(id, Records[id].IsLocked, Records[id].IsUnread,\n                           Records[id].IsNew);\n}\n\nvoid TipsSystem::SetTipNewState(size_t id, bool state) {\n  Records[id].IsNew = state;\n  SaveSystem::SetTipStatus(id, Records[id].IsLocked, Records[id].IsUnread,\n                           Records[id].IsNew);\n}\n\nbool TipsSystem::GetTipLockedState(size_t id) { return Records[id].IsLocked; }\n\n}  // namespace CHLCC\n}  // namespace Impacto"
  },
  {
    "path": "src/games/chlcc/tipssystem.h",
    "content": "#pragma once\n\n#include \"../../data/tipssystem.h\"\n\nnamespace Impacto {\nnamespace CHLCC {\n\nusing namespace Impacto::TipsSystem;\n\nclass TipsSystem : public TipsSystemBase {\n public:\n  TipsSystem(size_t maxTipsCount) : TipsSystemBase(maxTipsCount) {};\n\n  void DataInit(uint32_t scriptBufferId, uint32_t tipsDataAdr,\n                uint32_t tipsDataSize);\n  void UpdateTipRecords();\n  void SetTipLockedState(size_t id, bool state);\n  void SetTipUnreadState(size_t id, bool state);\n  void SetTipNewState(size_t id, bool state);\n\n  bool GetTipLockedState(size_t id);\n};\n\n}  // namespace CHLCC\n}  // namespace Impacto"
  },
  {
    "path": "src/games/chlcc/titlemenu.cpp",
    "content": "#include \"titlemenu.h\"\n\n#include \"../../spritesheet.h\"\n\n#include \"../../renderer/renderer.h\"\n#include \"../../mem.h\"\n#include \"../../inputsystem.h\"\n#include \"../../vm/interface/input.h\"\n#include \"../../ui/widgets/label.h\"\n#include \"../../profile/ui/titlemenu.h\"\n#include \"../../profile/games/chlcc/titlemenu.h\"\n#include \"../../profile/scriptvars.h\"\n#include \"../../background2d.h\"\n#include \"../../profile/game.h\"\n#include <vector>\n\nnamespace Impacto {\nnamespace UI {\nnamespace CHLCC {\n\nusing namespace Impacto::Profile::TitleMenu;\nusing namespace Impacto::Profile::CHLCC::TitleMenu;\nusing namespace Impacto::Profile::ScriptVars;\nusing namespace Impacto::Profile;\n\nusing namespace Impacto::Vm::Interface;\n\nusing namespace Impacto::UI::Widgets::CHLCC;\n\nvoid TitleMenu::MenuButtonOnClick(Widgets::Button* target) {\n  target->Hovered = false;\n  ScrWork[SW_TITLECUR1] = target->Id;\n  ChoiceMade = true;\n  // disable focus immediately, so MainItems couldn't get stuck being hovered\n  MainItems->HasFocus = false;\n}\n\nvoid TitleMenu::SecondaryButtonOnClick(Widgets::Button* target) {\n  ScrWork[SW_TITLECUR2] = target->Id;\n  ChoiceMade = true;\n}\n\nvoid TitleMenu::ExitButtonOnClick(Widgets::Button* target) {\n  PADinputButtonWentDown = PADinputButtonWentDown & ~PAD1A;\n  PADinputMouseWentDown = PADinputMouseWentDown & ~PAD1A;\n  Input::KeyboardButtonWentDown[SDL_SCANCODE_ESCAPE] = true;\n}\n\nTitleMenu::TitleMenu() {\n  MainItems = new Widgets::Group(this);\n  LoadItems = new Widgets::Group(this);\n  LockedExtraItems = new Widgets::Group(this);\n  UnlockedExtraItems = new Widgets::Group(this);\n  SystemItems = new Widgets::Group(this);\n\n  auto onClick = [this](auto* btn) { return MenuButtonOnClick(btn); };\n  auto secondaryOnClick = [this](auto* btn) {\n    return SecondaryButtonOnClick(btn);\n  };\n\n  // Start menu button\n  Start = new TitleButton(0, MenuEntriesSprites[0], MenuEntriesHSprites[0],\n                          ItemHighlightSprite,\n                          glm::vec2(ItemHighlightOffset.x - 1.0f,\n                                    ItemYBase - 1.0f + 0 * ItemPadding));\n  Start->OnClickHandler = onClick;\n  MainItems->Add(Start, FDIR_DOWN);\n\n  // Load menu button\n  Load = new TitleButton(1, MenuEntriesSprites[1], MenuEntriesHSprites[1],\n                         ItemHighlightSprite,\n                         glm::vec2(ItemHighlightOffset.x - 1.0f,\n                                   ItemYBase - 1.0f + 1 * ItemPadding));\n  Load->OnClickHandler = onClick;\n  MainItems->Add(Load, FDIR_DOWN);\n\n  // Extra menu button\n  Extra = new TitleButton(2, MenuEntriesSprites[2], MenuEntriesHSprites[2],\n                          ItemHighlightSprite,\n                          glm::vec2(ItemHighlightOffset.x - 1.0f,\n                                    ItemYBase - 1.0f + 2 * ItemPadding));\n  Extra->OnClickHandler = onClick;\n  MainItems->Add(Extra, FDIR_DOWN);\n\n  // System menu button\n  System = new TitleButton(3, MenuEntriesSprites[3], MenuEntriesHSprites[3],\n                           ItemHighlightSprite,\n                           glm::vec2(ItemHighlightOffset.x - 1.0f,\n                                     ItemYBase - 1.0f + 3 * ItemPadding));\n  System->OnClickHandler = onClick;\n  MainItems->Add(System, FDIR_DOWN);\n\n  // Exit menu button (Configuration/Patch driven)\n  if (HasScriptedExitLogic) {\n    auto* const exitPtr =\n        new TitleButton(4, ExitSprite, ExitHighlightSprite, ItemHighlightSprite,\n                        glm::vec2(ItemHighlightOffset.x - 1.0f,\n                                  ItemYBase - 1.0f + 4 * ItemPadding));\n    exitPtr->OnClickHandler = [this](auto* btn) {\n      return ExitButtonOnClick(btn);\n    };\n\n    Exit.emplace(*exitPtr);\n    MainItems->Add(exitPtr, FDIR_DOWN);\n  }\n\n  // Quick Load secondary Load menu button\n  QuickLoad = new TitleButton(0, MenuEntriesSprites[4], MenuEntriesHSprites[4],\n                              SecondaryItemHighlightSprite,\n                              glm::vec2(SecondaryItemX, ItemLoadQuickY));\n  QuickLoad->OnClickHandler = secondaryOnClick;\n  QuickLoad->IsSubButton = true;\n  QuickLoad->LineDecoration = LineSprites[2];\n  QuickLoad->LineY = SecondaryMenuLoadLineY;\n  LoadItems->Add(QuickLoad, FDIR_DOWN);\n\n  // Sub Load secondary Load menu button\n  SubLoad = new TitleButton(1, MenuEntriesSprites[5], MenuEntriesHSprites[5],\n                            SecondaryItemHighlightSprite,\n                            glm::vec2(SecondaryItemX, ItemLoadY));\n  SubLoad->OnClickHandler = secondaryOnClick;\n  SubLoad->IsSubButton = true;\n  SubLoad->LineDecoration = LineSprites[3];\n  SubLoad->LineY = SecondaryMenuLoadQuickLineY;\n  LoadItems->Add(SubLoad, FDIR_DOWN);\n\n  // Locked Extra Menus\n\n  // Clear List secondary Extra menu button\n  ClearList = new TitleButton(0, MenuEntriesSprites[6], MenuEntriesHSprites[6],\n                              SecondaryItemHighlightSprite,\n                              glm::vec2(SecondaryItemX, ItemSoundLibraryY));\n  ClearList->OnClickHandler = secondaryOnClick;\n  ClearList->IsSubButton = true;\n  ClearList->LineDecoration = LineSprites[2];\n  ClearList->LineY = SecondaryMenuExtraSoundY;\n  LockedExtraItems->Add(ClearList, FDIR_DOWN);\n\n  // Tips secondary Extra menu button\n  Tips = new TitleButton(1, MenuEntriesSprites[10], MenuEntriesHSprites[10],\n                         SecondaryItemHighlightSprite,\n                         glm::vec2(SecondaryItemX, ItemMovieLibraryY));\n  Tips->OnClickHandler = secondaryOnClick;\n  Tips->IsSubButton = true;\n  Tips->LineDecoration = LineSprites[3];\n  Tips->LineY = SecondaryMenuExtraMovieY;\n  LockedExtraItems->Add(Tips, FDIR_DOWN);\n\n  // Trophy secondary Extra menu button\n  Trophy = new TitleButton(2, MenuEntriesSprites[11], MenuEntriesHSprites[11],\n                           SecondaryItemHighlightSprite,\n                           glm::vec2(SecondaryItemX, ItemTipsY));\n  Trophy->OnClickHandler = secondaryOnClick;\n  Trophy->IsSubButton = true;\n  Trophy->LineDecoration = LineSprites[4];\n  Trophy->LineY = SecondaryMenuExtraTipsY;\n  LockedExtraItems->Add(Trophy, FDIR_DOWN);\n\n  // Unlocked Extra Menus\n\n  // Clear List secondary Extra menu button\n  ClearList = new TitleButton(0, MenuEntriesSprites[6], MenuEntriesHSprites[6],\n                              SecondaryItemHighlightSprite,\n                              glm::vec2(SecondaryItemX, ItemClearListY));\n  ClearList->OnClickHandler = secondaryOnClick;\n  ClearList->IsSubButton = true;\n  ClearList->LineDecoration = LineSprites[0];\n  ClearList->LineY = SecondaryMenuExtraClearY;\n  UnlockedExtraItems->Add(ClearList, FDIR_DOWN);\n\n  // CG Library secondary Extra menu button\n  CGLibrary = new TitleButton(1, MenuEntriesSprites[7], MenuEntriesHSprites[7],\n                              SecondaryItemHighlightSprite,\n                              glm::vec2(SecondaryItemX, ItemCGLibraryY));\n  CGLibrary->OnClickHandler = secondaryOnClick;\n  CGLibrary->IsSubButton = true;\n  CGLibrary->LineDecoration = LineSprites[1];\n  CGLibrary->LineY = SecondaryMenuExtraCGY;\n  UnlockedExtraItems->Add(CGLibrary, FDIR_DOWN);\n\n  // Sound Library secondary Extra menu button\n  SoundLibrary =\n      new TitleButton(2, MenuEntriesSprites[8], MenuEntriesHSprites[8],\n                      SecondaryItemHighlightSprite,\n                      glm::vec2(SecondaryItemX, ItemSoundLibraryY));\n  SoundLibrary->OnClickHandler = secondaryOnClick;\n  SoundLibrary->IsSubButton = true;\n  SoundLibrary->LineDecoration = LineSprites[2];\n  SoundLibrary->LineY = SecondaryMenuExtraSoundY;\n  UnlockedExtraItems->Add(SoundLibrary, FDIR_DOWN);\n\n  // Movie Library secondary Extra menu button\n  MovieLibrary =\n      new TitleButton(3, MenuEntriesSprites[9], MenuEntriesHSprites[9],\n                      SecondaryItemHighlightSprite,\n                      glm::vec2(SecondaryItemX, ItemMovieLibraryY));\n  MovieLibrary->OnClickHandler = secondaryOnClick;\n  MovieLibrary->IsSubButton = true;\n  MovieLibrary->LineDecoration = LineSprites[3];\n  MovieLibrary->LineY = SecondaryMenuExtraMovieY;\n  UnlockedExtraItems->Add(MovieLibrary, FDIR_DOWN);\n\n  // Tips secondary Extra menu button\n  Tips = new TitleButton(4, MenuEntriesSprites[10], MenuEntriesHSprites[10],\n                         SecondaryItemHighlightSprite,\n                         glm::vec2(SecondaryItemX, ItemTipsY));\n  Tips->OnClickHandler = secondaryOnClick;\n  Tips->IsSubButton = true;\n  Tips->LineDecoration = LineSprites[4];\n  Tips->LineY = SecondaryMenuExtraTipsY;\n  UnlockedExtraItems->Add(Tips, FDIR_DOWN);\n\n  // Trophy secondary Extra menu button\n  Trophy = new TitleButton(5, MenuEntriesSprites[11], MenuEntriesHSprites[11],\n                           SecondaryItemHighlightSprite,\n                           glm::vec2(SecondaryItemX, ItemTrophyY));\n  Trophy->OnClickHandler = secondaryOnClick;\n  Trophy->IsSubButton = true;\n  Trophy->LineDecoration = LineSprites[5];\n  Trophy->LineY = SecondaryMenuExtraTrophyY;\n  UnlockedExtraItems->Add(Trophy, FDIR_DOWN);\n\n  // Option secondary System menu button\n  Config = new TitleButton(0, MenuEntriesSprites[12], MenuEntriesHSprites[12],\n                           SecondaryItemHighlightSprite,\n                           glm::vec2(SecondaryItemX, ItemConfigY));\n  Config->OnClickHandler = secondaryOnClick;\n  Config->IsSubButton = true;\n  Config->LineDecoration = LineSprites[2];\n  Config->LineY = SecondaryMenuSystemConfigY;\n  SystemItems->Add(Config, FDIR_DOWN);\n\n  // System Save secondary System menu button\n  SystemSave = new TitleButton(\n      1, MenuEntriesSprites[13], MenuEntriesHSprites[13],\n      SecondaryItemHighlightSprite, glm::vec2(SecondaryItemX, ItemSystemSaveY));\n  SystemSave->OnClickHandler = secondaryOnClick;\n  SystemSave->IsSubButton = true;\n  SystemSave->LineDecoration = LineSprites[3];\n  SystemSave->LineY = SecondaryMenuSystemSaveY;\n  SystemItems->Add(SystemSave, FDIR_DOWN);\n\n  CurrentExtraItems = LockedExtraItems;\n\n  SpinningCircleAnimation.LoopMode = AnimationLoopMode::Loop;\n  SpinningCircleAnimation.SetDuration(SpinningCircleAnimationDuration);\n  SpinningCircleFlashingAnimation.LoopMode =\n      AnimationLoopMode::ReverseDirection;\n  SpinningCircleFlashingAnimation.SetDuration(\n      SpinningCircleFlashingAnimationDuration);\n}\n\nvoid TitleMenu::Show() {\n  if (State != Shown) {\n    CurrentExtraItems =\n        GetFlag(SF_EXTRA_ENA) ? UnlockedExtraItems : LockedExtraItems;\n    ClearList = static_cast<TitleButton*>(CurrentExtraItems->Children[0]);\n    State = Shown;\n    if (UI::FocusedMenu != 0) {\n      LastFocusedMenu = UI::FocusedMenu;\n      LastFocusedMenu->IsFocused = false;\n    }\n    IsFocused = true;\n    UI::FocusedMenu = this;\n    if (PressToStartAnimation.State == AnimationState::Stopped) {\n      PressToStartAnimation.StartIn();\n    }\n\n    if (SpinningCircleAnimation.State == AnimationState::Stopped) {\n      SpinningCircleAnimation.StartIn();\n      SpinningCircleFlashingAnimation.StartIn();\n    }\n  }\n}\n\nvoid TitleMenu::Hide() {\n  if (State != Hidden) {\n    State = Hidden;\n    if (LastFocusedMenu != 0) {\n      UI::FocusedMenu = LastFocusedMenu;\n      LastFocusedMenu->IsFocused = true;\n    } else {\n      UI::FocusedMenu = 0;\n    }\n    IsFocused = false;\n\n    MainItems->Hide();\n    LoadItems->Hide();\n    SystemItems->Hide();\n    UnlockedExtraItems->Hide();\n  }\n}\n\nvoid TitleMenu::ResetIntroSequence() {\n  IntroSequence.Reset();\n  SpinningCircleAnimation.Reset();\n  SpinningCircleFlashingAnimation.Reset();\n}\n\nvoid TitleMenu::Update(float dt) {\n  UpdateInput(dt);\n  PressToStartAnimation.Update(dt);\n  SpinningCircleAnimation.Update(dt);\n  SpinningCircleFlashingAnimation.Update(dt);\n  PrimaryFadeAnimation.Update(dt);\n  SecondaryFadeAnimation.Update(dt);\n\n  if (GetFlag(SF_TITLEMODE)) {\n    Show();\n  } else if (State == Shown &&\n             static_cast<TitleDispCtState>(ScrWork[SW_TITLEDISPCT]) !=\n                 TitleDispCtState::ExtraSubEntriesControl) {\n    // when loading/starting a game from a submenu\n    Hide();\n  }\n\n  // CHLCC has \"congrats\" screen that is rendered as a BG on top of the title\n  // menu while it's still focused, and it's possible to hover extraSubItems\n  const bool hasVisibleBGs =\n      std::ranges::any_of(Impacto::Backgrounds2D,\n                          [](const auto& pair) { return pair.second->Show; });\n  if (State == Shown && IsFocused && !hasVisibleBGs) {\n    MainItems->Tint.a =\n        glm::smoothstep(0.0f, 1.0f, PrimaryFadeAnimation.Progress);\n    MainItems->UpdateInput(dt);\n    MainItems->Update(dt);\n    const float secondarySmoothProgress =\n        glm::smoothstep(0.0f, 1.0f, SecondaryFadeAnimation.Progress);\n    LoadItems->Tint.a = secondarySmoothProgress;\n    LoadItems->UpdateInput(dt);\n    LoadItems->Update(dt);\n    CurrentExtraItems->Tint.a = secondarySmoothProgress;\n    CurrentExtraItems->UpdateInput(dt);\n    CurrentExtraItems->Update(dt);\n    SystemItems->Tint.a = secondarySmoothProgress;\n    SystemItems->UpdateInput(dt);\n    SystemItems->Update(dt);\n\n    switch (static_cast<TitleDispCtState>(ScrWork[SW_TITLEDISPCT])) {\n      case TitleDispCtState::IntroAnimation: {\n        if (IntroSequence.IntroAnimation.IsIn() && ScrWork[SW_TITLECT] == 0) {\n          ResetIntroSequence();\n        }\n        if (!IntroSequence.IntroAnimation.IsPlaying()) {\n          IntroSequence.IntroAnimation.StartIn();\n        }\n\n        // Skip the animation if requested\n        if (ScrWork[SW_TITLECT] >= 934 &&\n            IntroSequence.IntroAnimation.State == AnimationState::Playing) {\n          IntroSequence.IntroAnimation.Finish();\n        }\n\n        if (!IntroSequence.SeiraAnimation.IsOut() &&\n            SpinningCircleAnimation.State == AnimationState::Stopped) {\n          SpinningCircleAnimation.StartIn();\n          SpinningCircleFlashingAnimation.StartIn();\n        }\n\n        IntroSequence.Update(dt);\n\n        MainItems->Hide();\n        // When returning to title menu from loading a game we need to hide the\n        // load sub-menu\n        if (LoadItems->VisibilityState != Hidden) {\n          SecondaryFadeAnimation.StartOut();\n          MainItems->HasFocus = true;\n          LoadItems->Hide();\n        }\n      } break;\n      case TitleDispCtState::PressStart: {\n        MainItems->MoveTo({-ItemHighlightOffset.x, 0});\n        if (!GetFlag(SF_TITLEMODE)) {\n          Hide();\n          break;\n        }\n        if (PressToStartAnimation.State == AnimationState::Stopped) {\n          PressToStartAnimation.StartIn();\n        }\n      } break;\n      case TitleDispCtState::EmptyBackground: {\n        MainItems->MoveTo({-ItemHighlightOffset.x, 0});\n        PrimaryFadeAnimation.Reset(AnimationDirection::In);\n        SecondaryFadeAnimation.Reset(AnimationDirection::In);\n      } break;\n      case TitleDispCtState::MainEntriesFading: {\n        if (MainItems->VisibilityState == Hidden && ScrWork[SW_TITLECT] == 0) {\n          MainItems->Show();\n          MainItems->Tint.a = 0.0f;\n          CurrentlyFocusedElement = Start;\n          Start->HasFocus = true;\n          PrimaryFadeAnimation.StartIn();\n          const glm::vec2 mainItemsEndPos = {0, 0};\n          MainItems->MoveTo(mainItemsEndPos, PrimaryFadeAnimation.DurationIn);\n        } else if (MainItems->VisibilityState != Hidden &&\n                   ScrWork[SW_TITLECT] == 32) {\n          PrimaryFadeAnimation.StartOut();\n          const glm::vec2 mainItemsEndPos = {-ItemHighlightOffset.x, 0};\n          MainItems->MoveTo(mainItemsEndPos, PrimaryFadeAnimation.DurationOut);\n\n        } else if (ScrWork[SW_TITLECT] == 0) {\n          MainItems->Hide();\n        }\n\n      } break;\n      case TitleDispCtState::MainEntriesControl: {\n        MainItems->MoveTo({0, 0});\n        if (!GetFlag(SF_TITLEMODE)) {\n          Hide();\n        }\n      } break;\n      case TitleDispCtState::LoadSubEntriesFading: {\n        if (LoadItems->VisibilityState == Hidden && ScrWork[SW_TITLECT] == 0) {\n          LoadItems->Show();\n          LoadItems->Tint.a = 0.0f;\n          MainItems->HasFocus = false;\n          CurrentlyFocusedElement = QuickLoad;\n          QuickLoad->HasFocus = true;\n          SecondaryFadeAnimation.StartIn();\n\n        } else if (LoadItems->VisibilityState != Hidden &&\n                   ScrWork[SW_TITLECT] == 32) {\n          SecondaryFadeAnimation.StartOut();\n\n        } else if (ScrWork[SW_TITLECT] == 0) {\n          LoadItems->Hide();\n          MainItems->HasFocus = true;\n        }\n      } break;\n      case TitleDispCtState::ExtraSubEntriesFading: {\n        if (CurrentExtraItems->VisibilityState == Hidden &&\n            ScrWork[SW_TITLECT] == 0) {\n          CurrentExtraItems->Show();\n          CurrentExtraItems->Tint.a = 0.0f;\n          MainItems->HasFocus = false;\n          CurrentlyFocusedElement = ClearList;\n          ClearList->HasFocus = true;\n          SecondaryFadeAnimation.StartIn();\n\n        } else if (CurrentExtraItems->VisibilityState != Hidden &&\n                   ScrWork[SW_TITLECT] == 32) {\n          SecondaryFadeAnimation.StartOut();\n\n        } else if (ScrWork[SW_TITLECT] == 0) {\n          CurrentExtraItems->Hide();\n          MainItems->HasFocus = true;\n        }\n      } break;\n      case TitleDispCtState::SystemSubEntriesFading: {\n        if (SystemItems->VisibilityState == Hidden &&\n            ScrWork[SW_TITLECT] == 0) {\n          SystemItems->Show();\n          SystemItems->Tint.a = 0.0f;\n          MainItems->HasFocus = false;\n          CurrentlyFocusedElement = Config;\n          Config->HasFocus = true;\n          SecondaryFadeAnimation.StartIn();\n\n        } else if (SystemItems->VisibilityState != Hidden &&\n                   ScrWork[SW_TITLECT] == 32) {\n          SecondaryFadeAnimation.StartOut();\n\n        } else if (ScrWork[SW_TITLECT] == 0) {\n          SystemItems->Hide();\n          MainItems->HasFocus = true;\n        }\n      } break;\n      default:\n        break;\n    }\n  }\n}\n\nvoid TitleMenu::Render() {\n  if (State != Hidden && GetFlag(SF_TITLEMODE)) {\n    if (ScrWork[SW_MENUCT] < 64) {\n      switch (static_cast<TitleDispCtState>(ScrWork[SW_TITLEDISPCT])) {\n        case TitleDispCtState::IntroAnimation: {\n          if (IntroSequence.FallingStarsAnimation.IsIn()) {\n            Renderer->DrawSprite(BackgroundSprite, glm::vec2(0.0f));\n          }\n\n          if (SpinningCircleAnimation.State == AnimationState::Playing) {\n            DrawSpinningCircle(IntroSequence.SeiraAnimation.Progress);\n          }\n\n          IntroSequence.Render();\n        } break;\n        case TitleDispCtState::PressStart: {\n          DrawTitleMenuBackGraphics();\n          glm::vec4 col = glm::vec4(1.0f);\n          col.a = glm::smoothstep(0.0f, 1.0f, PressToStartAnimation.Progress);\n          Renderer->DrawSprite(PressToStartSprite,\n                               glm::vec2(PressToStartX, PressToStartY), col);\n        } break;\n        case TitleDispCtState::EmptyBackground: {\n          DrawTitleMenuBackGraphics();\n        } break;\n        case TitleDispCtState::MainEntriesFading: {\n          DrawTitleMenuBackGraphics();\n          MainItems->Render();\n        } break;\n        case TitleDispCtState::MainEntriesControl: {\n          DrawTitleMenuBackGraphics();\n          MainItems->Render();\n        } break;\n        case TitleDispCtState::LoadSubEntriesFading:\n        case TitleDispCtState::LoadSubEntriesControl: {\n          DrawTitleMenuBackGraphics();\n          LoadItems->Render();\n          MainItems->Render();\n        } break;\n        case TitleDispCtState::ExtraSubEntriesFading:\n          [[fallthrough]];\n        case TitleDispCtState::ExtraSubEntriesControl: {\n          DrawTitleMenuBackGraphics();\n          CurrentExtraItems->Render();\n          MainItems->Render();\n        } break;\n        case TitleDispCtState::SystemSubEntriesFading:\n          [[fallthrough]];\n        case TitleDispCtState::SystemSubEntriesControl: {\n          DrawTitleMenuBackGraphics();\n          SystemItems->Render();\n          MainItems->Render();\n        } break;\n        default:\n          break;\n      }\n    }\n\n    int maskAlpha = ScrWork[SW_TITLEMASKALPHA];\n    glm::vec4 col = ScrWorkGetColor(SW_TITLEMASKCOLOR);\n    col.a = glm::min(maskAlpha / 255.0f, 1.0f);\n    Renderer->DrawQuad(RectF{0.0f, 0.0f, DesignWidth, DesignHeight}, col);\n  }\n}\n\nvoid TitleMenu::DrawSpinningCircle(float alpha) const {\n  const CornersQuad dest =\n      SpinningCircleSprite.ScaledBounds()\n          .Scale({2.0f, 2.0f}, {0.0f, 0.0f})\n          .RotateAroundCenter(-SpinningCircleAnimation.Progress * 2.0f *\n                              std::numbers::pi_v<float>)\n          .Translate(SpinningCirclePosition);\n\n  glm::vec4 tint = {1.0f, 1.0f, 1.0f, alpha};\n  float intensity = SpinningCircleFlashingAnimation.Progress;\n  glm::vec3 colorShift(intensity);\n\n  Renderer->DrawSprite(SpinningCircleSprite, dest, tint, colorShift);\n}\n\nvoid TitleMenu::DrawTitleMenuBackGraphics() const {\n  Renderer->DrawSprite(BackgroundSprite, glm::vec2(0.0f));\n  DrawSpinningCircle(1.0f);\n  Renderer->DrawSprite(DelusionADVUnderSprite,\n                       DelusionADVPosition - DelusionADVPopoutOffset);\n  Renderer->DrawSprite(DelusionADVSprite, DelusionADVPosition);\n  Renderer->DrawSprite(SeiraUnderSprite, SeiraUnderPosition);\n  Renderer->DrawSprite(SeiraSprite, SeiraPosition);\n  Renderer->DrawSprite(CHLogoSprite, CHLogoPosition);\n  Renderer->DrawSprite(LCCLogoUnderSprite, LCCLogoUnderPosition);\n  Renderer->DrawSprite(CopyrightTextSprite, CopyrightTextPosition);\n\n  for (size_t i : LCCLogoDrawOrder) {\n    Renderer->DrawSprite(LCCLogoSprites[i], LCCLogoPositions[i]);\n  }\n\n  Renderer->DrawSprite(StarLogoSprite, StarLogoPosition);\n}\n\n}  // namespace CHLCC\n}  // namespace UI\n}  // namespace Impacto\n"
  },
  {
    "path": "src/games/chlcc/titlemenu.h",
    "content": "#pragma once\n\n#include \"../../animation.h\"\n#include \"../../ui/menu.h\"\n#include \"../../ui/widgets/group.h\"\n#include \"../../ui/widgets/button.h\"\n#include \"../../ui/widgets/chlcc/titlebutton.h\"\n#include \"introsequence.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace CHLCC {\n\nenum class TitleDispCtState : uint8_t {\n  IntroAnimation = 0,\n  PressStart = 1,\n  EmptyBackground = 2,\n  MainEntriesFading = 3,\n  MainEntriesControl = 4,\n  // 5 and 6 go unused\n  LoadSubEntriesFading = 7,\n  LoadSubEntriesControl = 8,\n  ExtraSubEntriesFading = 9,\n  ExtraSubEntriesControl = 10,\n  SystemSubEntriesFading = 11,\n  SystemSubEntriesControl = 12,\n};\n\nclass TitleMenu : public Menu {\n public:\n  TitleMenu();\n\n  void Show() override;\n  void Hide() override;\n  void Update(float dt) override;\n  void Render() override;\n\n  Animation PressToStartAnimation;\n  Animation PrimaryFadeAnimation;\n  Animation SecondaryFadeAnimation;\n  Animation SpinningCircleAnimation;\n  Animation SpinningCircleFlashingAnimation;\n\n  void MenuButtonOnClick(Widgets::Button* target);\n  void SecondaryButtonOnClick(Widgets::Button* target);\n  void ExitButtonOnClick(Widgets::Button* target);\n\n  void DrawTitleMenuBackGraphics() const;\n  void DrawSpinningCircle(float alpha) const;\n\n private:\n  Widgets::Group* MainItems;\n  Widgets::CHLCC::TitleButton* Start;\n  Widgets::CHLCC::TitleButton* Load;\n  Widgets::CHLCC::TitleButton* Extra;\n  Widgets::CHLCC::TitleButton* System;\n  std::optional<std::reference_wrapper<Widgets::CHLCC::TitleButton>> Exit;\n\n  Widgets::Group* LoadItems;\n  Widgets::CHLCC::TitleButton* SubLoad;\n  Widgets::CHLCC::TitleButton* QuickLoad;\n\n  Widgets::Group* CurrentExtraItems;\n  Widgets::Group* LockedExtraItems;\n  Widgets::Group* UnlockedExtraItems;\n  Widgets::CHLCC::TitleButton* ClearList;\n  Widgets::CHLCC::TitleButton* CGLibrary;\n  Widgets::CHLCC::TitleButton* SoundLibrary;\n  Widgets::CHLCC::TitleButton* MovieLibrary;\n  Widgets::CHLCC::TitleButton* Tips;\n  Widgets::CHLCC::TitleButton* Trophy;\n\n  Widgets::Group* SystemItems;\n  Widgets::CHLCC::TitleButton* Config;\n  Widgets::CHLCC::TitleButton* SystemSave;\n\n  Impacto::CHLCC::IntroSequence IntroSequence;\n\n  void ResetIntroSequence();\n};\n\n}  // namespace CHLCC\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/chlcc/trophymenu.cpp",
    "content": "#include \"trophymenu.h\"\n\n#include \"../../renderer/renderer.h\"\n#include \"../../mem.h\"\n#include \"../../vm/vm.h\"\n#include \"../../profile/scriptvars.h\"\n#include \"../../profile/ui/trophymenu.h\"\n#include \"../../profile/games/chlcc/trophymenu.h\"\n#include \"../../profile/games/chlcc/commonmenu.h\"\n\n#include \"../../inputsystem.h\"\n#include \"../../vm/interface/input.h\"\n#include \"../../data/achievementsystem.h\"\n#include \"../../ui/widgets/chlcc/trophymenuentry.h\"\n\n#include <numbers>\n\nnamespace Impacto {\nnamespace UI {\nnamespace CHLCC {\n\nusing namespace Impacto::Profile::CHLCC::CommonMenu;\n\nusing namespace Impacto::Profile::TrophyMenu;\nusing namespace Impacto::Profile::CHLCC::TrophyMenu;\nusing namespace Impacto::Profile::ScriptVars;\nusing namespace Impacto::Profile::GameSpecific;\n\nusing namespace Impacto::Vm::Interface;\n\nusing namespace Impacto::UI::Widgets;\nusing namespace Impacto::UI::Widgets::CHLCC;\n\nTrophyMenu::TrophyMenu() : CommonMenu(true) {\n  TrophyCountHintLabel.Enabled = false;\n  TrophyCountHintLabel.MoveTo(TrophyCountHintLabelPos);\n}\n\nvoid TrophyMenu::Show() {\n  if (State != Shown) {\n    if (State != Showing) {\n      MenuTransition.StartIn();\n      FromSystemMenuTransition->StartIn();\n    }\n    State = Showing;\n    if (UI::FocusedMenu != 0) {\n      LastFocusedMenu = UI::FocusedMenu;\n      LastFocusedMenu->IsFocused = false;\n    }\n    IsFocused = true;\n    UI::FocusedMenu = this;\n\n    for (size_t i = 0; i < MaxTrophyPages; i++) {\n      for (size_t j = 0; j < EntriesPerPage; j++) {\n        const size_t index = i * EntriesPerPage + j;\n        if (index >= AchievementSystem::GetAchievementCount()) break;\n        TrophyMenuEntry* entry = new TrophyMenuEntry(static_cast<int>(index));\n        MainItems[i].Add(entry);\n      }\n    }\n    MainItems[CurrentPage].Show();\n    if (!TrophyCountHintLabel.Enabled) {\n      TrophyCountHintLabel.Enabled = true;\n      TrophyCountHintLabel.SetText(\n          Vm::ScriptGetTextTableStrAddress(TrophyCountHintTextTableId,\n                                           TrophyCountHintStringNum),\n          TrophyCountFontSize, RendererOutlineMode::Full, 0);\n    }\n  }\n}\nvoid TrophyMenu::Hide() {\n  if (State != Hidden) {\n    if (State != Hiding) {\n      MenuTransition.StartOut();\n      FromSystemMenuTransition->StartOut();\n    }\n    State = Hiding;\n    if (LastFocusedMenu != 0) {\n      UI::FocusedMenu = LastFocusedMenu;\n      LastFocusedMenu->IsFocused = true;\n    } else {\n      UI::FocusedMenu = 0;\n    }\n    IsFocused = false;\n  }\n}\n\nvoid TrophyMenu::Render() {\n  if (State == Hidden) return;\n  CommonMenu::DrawSubmenu(BackgroundColor, CircleSprite, MenuTitleText,\n                          MenuTitleTextAngle, true);\n\n  if (MenuTransition.Progress < 0.22f) return;\n\n  glm::vec2 offset = MenuTransition.GetPageOffset();\n\n  DrawButtonPrompt(ButtonPromptSprite, ButtonPromptPosition);\n\n  TrophyCountHintLabel.Move(offset);\n  TrophyCountHintLabel.Render();\n  TrophyCountHintLabel.Move(-offset);\n\n  Renderer->DrawSprite(PlatinumTrophySprite, offset + PlatinumTrophyPos);\n  Renderer->DrawSprite(GoldTrophySprite, offset + GoldTrophyPos);\n  Renderer->DrawSprite(SilverTrophySprite, offset + SilverTrophyPos);\n  Renderer->DrawSprite(BronzeTrophySprite, offset + BronzeTrophyPos);\n\n  Renderer->DrawSprite(TrophyPageCtBoxSprite, offset + TrophyPageCtPos);\n  Renderer->DrawSprite(PageNums[CurrentPage + 1], offset + CurrentPageNumPos);\n  Renderer->DrawSprite(PageNumSeparatorSlash, offset + PageNumSeparatorPos);\n  Renderer->DrawSprite(ReachablePageNums[MaxTrophyPages],\n                       offset + MaxPageNumPos);\n\n  MainItems[CurrentPage].Move(offset);\n  MainItems[CurrentPage].Render();\n  MainItems[CurrentPage].Move(-offset);\n\n  Renderer->DrawSprite(TrophyEntriesBorderSprite, offset);\n}\n\nvoid TrophyMenu::UpdateInput(float dt) {\n  if (IsFocused) {\n    if (PADinputButtonWentDown & PAD1DOWN || Input::MouseWheelDeltaY < 0 ||\n        PADinputButtonWentDown & PADcustom[8]) {\n      if (CurrentPage < 8) {\n        MainItems[CurrentPage++].Hide();\n        MainItems[CurrentPage].Show();\n      }\n    } else if (PADinputButtonWentDown & PAD1UP || Input::MouseWheelDeltaY > 0 ||\n               PADinputButtonWentDown & PADcustom[7]) {\n      if (CurrentPage > 0) {\n        MainItems[CurrentPage--].Hide();\n        MainItems[CurrentPage].Show();\n      }\n    }\n  }\n}\n\nvoid TrophyMenu::Update(float dt) {\n  UpdateInput(dt);\n\n  const int sysMenuCt = ScrWork[SW_SYSMENUCT];\n  const int systemMenuCHG = ScrWork[SW_SYSTEMMENUCHG];\n\n  if ((!GetFlag(SF_ACHIEVEMENTMENU) || sysMenuCt < 10000 ||\n       (sysMenuCt == 10000 && systemMenuCHG != 0 && systemMenuCHG != 64)) &&\n      State == Shown) {\n    Hide();\n  } else if (GetFlag(SF_ACHIEVEMENTMENU) && sysMenuCt > 0 && State == Hidden) {\n    Show();\n  }\n\n  if (MenuTransition.IsOut() && !GetFlag(SF_ACHIEVEMENTMENU) &&\n      systemMenuCHG == 0 && (sysMenuCt == 0 || GetFlag(SF_SYSTEMMENU)) &&\n      State == Hiding) {\n    State = Hidden;\n    for (int i = 0; i < 9; i++) {\n      MainItems[i].Clear();\n    }\n  } else if (MenuTransition.IsIn() && sysMenuCt == 10000 &&\n             (systemMenuCHG == 0 || systemMenuCHG == 64) &&\n             GetFlag(SF_ACHIEVEMENTMENU) && State == Showing) {\n    State = Shown;\n  }\n\n  if (State != Hidden) {\n    MenuTransition.Update(dt);\n    FromSystemMenuTransition->Update(dt);\n    if (MenuTransition.Direction == AnimationDirection::Out &&\n        MenuTransition.Progress <= 0.72f) {\n      TitleFade.StartOut();\n    } else if (MenuTransition.IsIn() &&\n               (TitleFade.Direction == AnimationDirection::In ||\n                TitleFade.IsOut())) {\n      TitleFade.StartIn();\n    }\n    TitleFade.Update(dt);\n    UpdateTitles(MenuTitleTextRightPosition, MenuTitleTextLeftPosition);\n  }\n}\n\n}  // namespace CHLCC\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/chlcc/trophymenu.h",
    "content": "#pragma once\n\n#include \"commonmenu.h\"\n#include \"animations/menutransition.h\"\n#include \"../../ui/menu.h\"\n#include \"../../ui/widgets/group.h\"\n#include \"../../ui/widgets/button.h\"\n#include \"../../ui/widgets/carousel.h\"\n#include \"../../ui/widgets/label.h\"\n\n#include \"../../profile/games/chlcc/trophymenu.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace CHLCC {\n\nclass TrophyMenu : public Menu, public CommonMenu {\n public:\n  TrophyMenu();\n\n  void Show();\n  void Hide();\n  void UpdateInput(float dt);\n  void Update(float dt);\n  void Render();\n\n private:\n  Impacto::UI::Widgets::Group\n      MainItems[Impacto::Profile::CHLCC::TrophyMenu::MaxTrophyPages] = {\n          this, this, this, this, this, this, this, this, this};\n  int CurrentPage = 0;\n\n  Impacto::UI::Widgets::Label TrophyCountHintLabel;\n};\n\n}  // namespace CHLCC\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/darling/sysmesbox.cpp",
    "content": "#include \"sysmesbox.h\"\n\n#include \"../../profile/ui/sysmesbox.h\"\n#include \"../../profile/games/darling/sysmesbox.h\"\n#include \"../../profile/dialogue.h\"\n#include \"../../profile/scriptvars.h\"\n#include \"../../mem.h\"\n#include \"../../renderer/renderer.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace Darling {\n\nusing namespace Impacto::UI::Widgets;\nusing namespace Impacto::Profile::ScriptVars;\nusing namespace Impacto::Profile::SysMesBox;\nusing namespace Impacto::Profile::Darling::SysMesBox;\n\nvoid SysMesBox::ChoiceItemOnClick(Button* target) {\n  ScrWork[SW_SYSSEL] = target->Id;\n  ChoiceMade = true;\n}\n\nvoid SysMesBox::Show() {\n  MessageItems = new Widgets::Group(this);\n  ChoiceItems = new Widgets::Group(this);\n\n  Sprite nullSprite = Sprite();\n  nullSprite.Bounds = RectF(0.0f, 0.0f, 0.0f, 0.0f);\n\n  auto onClick = [this](auto* btn) { return ChoiceItemOnClick(btn); };\n\n  float diff = 0.0f;\n  float maxWidth = FLT_MIN;\n  for (int i = 0; i < MessageCount; i++) {\n    if (maxWidth < MessageWidths[i]) maxWidth = MessageWidths[i];\n  }\n  if (maxWidth < BoxMinimumWidth) maxWidth = BoxMinimumWidth;\n\n  for (int i = 0; i < MessageCount; i++) {\n    if (Messages[i].empty()) continue;\n\n    diff = Messages[i][0].DestRect.X - (TextX - (maxWidth / 2.0f));\n    for (ProcessedTextGlyph& glyph : Messages[i]) {\n      glyph.DestRect.X -= diff;\n      glyph.DestRect.Y = TextMiddleY + (i * TextLineHeight);\n    }\n\n    Label* message = new Label(Messages[i], MessageWidths[i], TextFontSize,\n                               RendererOutlineMode::Full);\n\n    MessageItems->Add(message, FDIR_DOWN);\n  }\n\n  float totalChoiceWidth = 0.0f;\n  for (int i = 0; i < ChoiceCount; i++) {\n    totalChoiceWidth += ChoiceWidths[i] + ChoicePadding;\n  }\n  if (maxWidth < totalChoiceWidth) maxWidth = totalChoiceWidth;\n  if (maxWidth < MinMaxMesWidth) maxWidth = MinMaxMesWidth;\n\n  ChoiceX = (maxWidth / 2.0f) - totalChoiceWidth + ChoiceXBase;\n\n  float tempChoiceX = ChoiceX;\n\n  for (int i = 0; i < ChoiceCount; i++) {\n    diff = Choices[i][0].DestRect.X - tempChoiceX;\n    for (ProcessedTextGlyph& glyph : Choices[i]) {\n      glyph.DestRect.X -= diff;\n      glyph.DestRect.Y = ChoiceY;\n    }\n\n    Button* choice = new Button(\n        i, nullSprite, nullSprite, SelectionHighlight,\n        glm::vec2(Choices[i][0].DestRect.X, Choices[i][0].DestRect.Y));\n\n    choice->SetText(Choices[i], ChoiceWidths[i],\n                    Profile::Dialogue::DefaultFontSize,\n                    RendererOutlineMode::Full);\n    choice->OnClickHandler = onClick;\n\n    ChoiceItems->Add(choice, FDIR_LEFT);\n\n    tempChoiceX += ChoiceWidths[i] + ChoicePadding;\n  }\n\n  FadeAnimation.StartIn();\n  MessageItems->Show();\n  MessageItems->HasFocus = false;\n  if (ChoiceCount != 0) ChoiceItems->Show();\n  State = Showing;\n\n  if (UI::FocusedMenu != 0) {\n    LastFocusedMenu = UI::FocusedMenu;\n    LastFocusedMenu->IsFocused = false;\n  }\n  IsFocused = true;\n  UI::FocusedMenu = this;\n}\n\nvoid SysMesBox::Hide() {\n  FadeAnimation.StartOut();\n  State = Hiding;\n  if (LastFocusedMenu != 0) {\n    UI::FocusedMenu = LastFocusedMenu;\n    LastFocusedMenu->IsFocused = true;\n  } else {\n    UI::FocusedMenu = 0;\n  }\n  IsFocused = false;\n}\n\nvoid SysMesBox::Update(float dt) {\n  UpdateInput(dt);\n\n  FadeAnimation.Update(dt);\n  if (State != Hidden) {\n    if (FadeAnimation.IsIn()) State = Shown;\n    if (FadeAnimation.IsOut()) State = Hidden;\n\n    if (IsFocused) {\n      MessageItems->Update(dt);\n      ChoiceItems->Update(dt);\n      ChoiceItems->UpdateInput(dt);\n    }\n  }\n}\n\nvoid SysMesBox::Render() {\n  if (State == Hidden) return;\n\n  glm::vec4 col(1.0f, 1.0f, 1.0f, FadeAnimation.Progress);\n\n  float maxWidth = FLT_MIN;\n  for (int i = 0; i < MessageCount; i++) {\n    if (maxWidth < MessageWidths[i]) maxWidth = MessageWidths[i];\n  }\n  if (maxWidth < BoxMinimumWidth) maxWidth = BoxMinimumWidth;\n\n  float leftStartX = BoxMiddleBaseX - (maxWidth / 2.0f);\n  Renderer->DrawSprite(BoxPartLeft, glm::vec2(leftStartX, BoxY), col);\n\n  BoxPartMiddle.BaseScale =\n      glm::vec2((maxWidth - BoxRightBaseWidth) / BoxMiddleBaseWidth, 1.0f);\n  Renderer->DrawSprite(BoxPartMiddle,\n                       glm::vec2(leftStartX + BoxRightRemainPad, BoxY), col);\n\n  Renderer->DrawSprite(\n      BoxPartRight, glm::vec2(leftStartX + maxWidth + BoxRightBaseWidth, BoxY),\n      col);\n\n  MessageItems->Tint.a = FadeAnimation.Progress;\n  MessageItems->Render();\n  ChoiceItems->Tint.a = FadeAnimation.Progress;\n  ChoiceItems->Render();\n}\n\nvoid SysMesBox::Init() {\n  ChoiceMade = false;\n  MessageCount = 0;\n  ChoiceCount = 0;\n\n  FadeAnimation.DurationIn = FadeInDuration;\n  FadeAnimation.DurationOut = FadeOutDuration;\n  FadeAnimation.Direction = AnimationDirection::In;\n  FadeAnimation.LoopMode = AnimationLoopMode::Stop;\n}\n\nvoid SysMesBox::AddMessage(Vm::BufferOffsetContext ctx) {\n  Impacto::Vm::Sc3VmThread dummy;\n  dummy.IpOffset = ctx.IpOffset;\n  dummy.ScriptBufferId = ctx.ScriptBufferId;\n  Messages[MessageCount] =\n      TextLayoutPlainLine(&dummy, 255, Profile::Dialogue::DialogueFont,\n                          TextFontSize, Profile::Dialogue::ColorTable[0], 1.0f,\n                          glm::vec2(TextX, 0.0f), TextAlignment::Left);\n  float mesLen = 0.0f;\n  for (const ProcessedTextGlyph& glyph : Messages[MessageCount]) {\n    mesLen += glyph.DestRect.Width;\n  }\n  MessageWidths[MessageCount] = mesLen;\n  MessageCount++;\n}\n\nvoid SysMesBox::AddChoice(Vm::BufferOffsetContext ctx) {\n  Impacto::Vm::Sc3VmThread dummy;\n  dummy.IpOffset = ctx.IpOffset;\n  dummy.ScriptBufferId = ctx.ScriptBufferId;\n  Choices[ChoiceCount] =\n      TextLayoutPlainLine(&dummy, 255, Profile::Dialogue::DialogueFont,\n                          TextFontSize, Profile::Dialogue::ColorTable[0], 1.0f,\n                          glm::vec2(TextX, 0.0f), TextAlignment::Left);\n  float mesLen = 0.0f;\n  for (const ProcessedTextGlyph& choice : Choices[ChoiceCount]) {\n    mesLen += choice.DestRect.Width;\n  }\n  ChoiceWidths[ChoiceCount] = mesLen;\n  ChoiceCount++;\n}\n\n}  // namespace Darling\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/darling/sysmesbox.h",
    "content": "#pragma once\n\n#include \"../../ui/sysmesbox.h\"\n#include \"../../ui/widgets/button.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace Darling {\n\nclass SysMesBox : public UI::SysMesBox {\n public:\n  virtual void Show() override;\n  virtual void Hide() override;\n  virtual void Update(float dt) override;\n  virtual void Render() override;\n\n  virtual void Init() override;\n  virtual void AddMessage(Vm::BufferOffsetContext ctx) override;\n  virtual void AddChoice(Vm::BufferOffsetContext ctx) override;\n\n private:\n  void ChoiceItemOnClick(UI::Widgets::Button* target);\n};\n\n}  // namespace Darling\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/dash/titlemenu.cpp",
    "content": "#include \"titlemenu.h\"\n\n#include \"../../profile/ui/titlemenu.h\"\n#include \"../../profile/games/dash/titlemenu.h\"\n#include \"../../renderer/renderer.h\"\n#include \"../../texture/texture.h\"\n#include \"../../spritesheet.h\"\n#include \"../../audio/audiosystem.h\"\n#include \"../../audio/audiostream.h\"\n#include \"../../audio/audiochannel.h\"\n#include \"../../io/vfs.h\"\n#include \"../../mem.h\"\n#include \"../../profile/scriptvars.h\"\n#include \"../../background2d.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace Dash {\n\nusing namespace Impacto::Profile::TitleMenu;\nusing namespace Impacto::Profile::Dash::TitleMenu;\nusing namespace Impacto::Profile::ScriptVars;\n\nTitleMenu::TitleMenu() {}\n\nvoid TitleMenu::Show() {\n  if (State == Hidden) {\n    State = Shown;\n    if (UI::FocusedMenu != 0) {\n      LastFocusedMenu = UI::FocusedMenu;\n      LastFocusedMenu->IsFocused = false;\n    }\n    IsFocused = true;\n    UI::FocusedMenu = this;\n    if (PressToStartAnimation.State == AnimationState::Stopped) {\n      PressToStartAnimation.StartIn();\n    }\n  }\n}\nvoid TitleMenu::Hide() {\n  if (State == Shown) {\n    State = Hidden;\n    if (LastFocusedMenu != 0) {\n      UI::FocusedMenu = LastFocusedMenu;\n      LastFocusedMenu->IsFocused = true;\n    } else {\n      UI::FocusedMenu = 0;\n    }\n    IsFocused = false;\n  }\n}\n\nvoid TitleMenu::Update(float dt) {\n  UpdateInput(dt);\n\n  PressToStartAnimation.Update(dt);\n  if (GetFlag(SF_TITLEMODE) && ScrWork[SW_TITLEDISPCT] == 2) {\n    Show();\n  } else {\n    Hide();\n  }\n}\nvoid TitleMenu::Render() {\n  if (State != Hidden && GetFlag(SF_TITLEMODE)) {\n    glm::vec4 col = glm::vec4(1.0f);\n    col.a = glm::smoothstep(0.0f, 1.0f, PressToStartAnimation.Progress);\n    Renderer->DrawSprite(BackgroundSprite, glm::vec2(0.0f));\n    Renderer->DrawSprite(PressToStartSprite,\n                         glm::vec2(PressToStartX, PressToStartY), col);\n  }\n}\n\n}  // namespace Dash\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/dash/titlemenu.h",
    "content": "#pragma once\n\n#include \"../../ui/menu.h\"\n#include \"../../ui/widgets/button.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace Dash {\n\nclass TitleMenu : public Menu {\n public:\n  TitleMenu();\n\n  void Show() override;\n  void Hide() override;\n  void Update(float dt) override;\n  void Render() override;\n};\n\n}  // namespace Dash\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/mo6tw/actorsvoicemenu.cpp",
    "content": "#include \"actorsvoicemenu.h\"\n\n#include \"../../profile/ui/extramenus.h\"\n#include \"../../profile/games/mo6tw/actorsvoicemenu.h\"\n#include \"../../renderer/renderer.h\"\n#include \"../../mem.h\"\n#include \"../../profile/scriptvars.h\"\n#include \"../../data/savesystem.h\"\n#include \"../../background2d.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace MO6TW {\n\nusing namespace Impacto::Profile::MO6TW::ActorsVoiceMenu;\nusing namespace Impacto::Profile::ScriptVars;\n\nusing namespace Impacto::UI::Widgets;\n\nvoid ActorsVoiceMenu::VoiceButtonOnClick(Button* target) {\n  if (!target->IsLocked) {\n    ScrWork[SW_ACTORVOICE_CUR] = target->Id;\n  }\n}\n\nActorsVoiceMenu::ActorsVoiceMenu() {\n  FadeAnimation.Direction = AnimationDirection::In;\n  FadeAnimation.LoopMode = AnimationLoopMode::Stop;\n  FadeAnimation.DurationIn = FadeInDuration;\n  FadeAnimation.DurationOut = FadeOutDuration;\n\n  MainItems = new Group(this);\n\n  auto onClick = [this](auto* btn) { return VoiceButtonOnClick(btn); };\n\n  auto pos = InitialItemPosition;\n\n  for (int i = 0; i < ActorsVoiceCount; i++) {\n    auto button = new Widgets::MO6TW::ActorsVoiceButton(\n        i, UnlockedSprites[i], LockedSprites[i], UnlockedHighlightedSprites[i],\n        LockedHighlightedSprites[i], pos);\n    button->OnClickHandler = onClick;\n\n    MainItems->Add(button, FDIR_DOWN);\n    pos += ItemOffset;\n  }\n}\n\nvoid ActorsVoiceMenu::Show() {\n  if (State != Shown) {\n    State = Showing;\n    FadeAnimation.StartIn();\n    MainItems->Show();\n    UpdateActorsVoiceEntries();\n    ScrWork[SW_ACTORVOICE_CUR] = 255;\n    if (UI::FocusedMenu != 0) {\n      LastFocusedMenu = UI::FocusedMenu;\n      LastFocusedMenu->IsFocused = false;\n    }\n    IsFocused = true;\n    UI::FocusedMenu = this;\n  }\n}\nvoid ActorsVoiceMenu::Hide() {\n  if (State != Hidden) {\n    State = Hiding;\n    FadeAnimation.StartOut();\n    MainItems->Hide();\n    if (LastFocusedMenu != 0) {\n      UI::FocusedMenu = LastFocusedMenu;\n      LastFocusedMenu->IsFocused = true;\n    } else {\n      UI::FocusedMenu = 0;\n    }\n    IsFocused = false;\n  }\n}\n\nvoid ActorsVoiceMenu::UpdateInput(float dt) {\n  Menu::UpdateInput(dt);\n  if (State == Shown) {\n    if (ScrWork[SW_ACTORVOICE_CUR] == 255) {\n      MainItems->UpdateInput(dt);\n    }\n  }\n}\n\nvoid ActorsVoiceMenu::Update(float dt) {\n  UpdateInput(dt);\n\n  FadeAnimation.Update(dt);\n  if (ScrWork[SW_ACTORVOICE_ALPHA] < 256 && State == Shown) {\n    Hide();\n  } else if (ScrWork[SW_ACTORVOICE_ALPHA] == 256 && State == Hidden) {\n    Show();\n  }\n\n  if (ScrWork[SW_ACTORVOICE_ALPHA] == 256 && FadeAnimation.IsIn())\n    State = Shown;\n  else if (FadeAnimation.IsOut()) {\n    State = Hidden;\n  }\n\n  if (State != Hidden) {\n    MainItems->Update(dt);\n  }\n}\n\nvoid ActorsVoiceMenu::Render() {\n  if (State == Hidden) return;\n\n  glm::vec4 col(1.0f, 1.0f, 1.0f, FadeAnimation.Progress);\n  Renderer->DrawSprite(BackgroundSprite, glm::vec2(0.0f, 0.0f), col);\n  MainItems->Tint = col;\n  MainItems->Render();\n  if (ScrWork[SW_ACTORVOICE_CUR] != 255) {\n    Renderer->DrawSprite(Backgrounds2D[CharacterBackgroundBufferId]->BgSprite,\n                         glm::vec2(0.0f, 0.0f), col);\n  }\n}\n\nvoid ActorsVoiceMenu::UpdateActorsVoiceEntries() {\n  for (auto el : MainItems->Children) {\n    auto actorsVoiceButton = static_cast<Button*>(el);\n    actorsVoiceButton->IsLocked =\n        !GetFlag(SF_ACTORSVOICE_UNLOCK1 + actorsVoiceButton->Id);\n  }\n}\n\n}  // namespace MO6TW\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/mo6tw/actorsvoicemenu.h",
    "content": "#pragma once\n\n#include \"../../ui/menu.h\"\n#include \"../../ui/widgets/group.h\"\n#include \"../../ui/widgets/button.h\"\n#include \"../../ui/widgets/mo6tw/actorsvoicebutton.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace MO6TW {\n\nclass ActorsVoiceMenu : public Menu {\n public:\n  ActorsVoiceMenu();\n\n  void Show() override;\n  void Hide() override;\n  void UpdateInput(float dt) override;\n  void Update(float dt) override;\n  void Render() override;\n\n  void VoiceButtonOnClick(Widgets::Button* target);\n\n private:\n  Widgets::Group* MainItems;\n\n  void UpdateActorsVoiceEntries();\n\n  Animation FadeAnimation;\n};\n\n}  // namespace MO6TW\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/mo6tw/albummenu.cpp",
    "content": "#include \"albummenu.h\"\n\n#include \"../../profile/ui/extramenus.h\"\n#include \"../../profile/games/mo6tw/albummenu.h\"\n#include \"../../renderer/renderer.h\"\n#include \"../../mem.h\"\n#include \"../../profile/scriptvars.h\"\n#include \"../../data/savesystem.h\"\n#include \"../../vm/interface/input.h\"\n#include \"../../ui/widgets/mo6tw/albumcharacterbutton.h\"\n#include \"../../ui/widgets/mo6tw/albumthumbnailbutton.h\"\n#include \"../../profile/data/savesystem.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace MO6TW {\n\nusing namespace Impacto::Profile::MO6TW::AlbumMenu;\nusing namespace Impacto::Profile::ScriptVars;\n\nusing namespace Impacto::Vm::Interface;\n\nusing namespace Impacto::UI::Widgets;\n\nvoid AlbumMenu::CharacterButtonOnClick(Button* target) {\n  if (!(target->Id == YunoButtonIdx && target->IsLocked))\n    SwitchToCharacter(target->Id);\n}\n\nvoid AlbumMenu::ArrowUpOnClick(Widgets::Button* target) { MoveImageGrid(); }\n\nvoid AlbumMenu::ArrowDownOnClick(Widgets::Button* target) { MoveImageGrid(); }\n\nvoid AlbumMenu::CgOnClick(Widgets::Button* target) {\n  ShowCgViewer = true;\n  CgViewerWidget->LoadCgSprites((size_t)target->Id, \"bg\",\n                                Profile::SaveSystem::AlbumData[target->Id]);\n}\n\nvoid AlbumMenu::OnCgVariationEnd(Widgets::CgViewer* target) {\n  CgViewerGroup->Hide();\n  ShowCgViewer = false;\n}\n\nAlbumMenu::AlbumMenu() {\n  FadeAnimation.Direction = AnimationDirection::In;\n  FadeAnimation.LoopMode = AnimationLoopMode::Stop;\n  FadeAnimation.DurationIn = FadeInDuration;\n  FadeAnimation.DurationOut = FadeOutDuration;\n\n  ArrowsAnimation.Direction = AnimationDirection::In;\n  ArrowsAnimation.LoopMode = AnimationLoopMode::ReverseDirection;\n  ArrowsAnimation.SetDuration(ArrowsAnimationDuration);\n  ArrowsAnimation.StartIn();\n\n  CgViewerWidget = new CgViewer();\n  CgViewerWidget->OnVariationEndHandler = [this](auto* btn) {\n    return OnCgVariationEnd(btn);\n  };\n  CgViewerGroup = new Group(this);\n  CgViewerGroup->Add(CgViewerWidget, FDIR_DOWN);\n\n  MainItems = new Group(this);\n  SecondaryItems = new Group(this);\n  SecondaryItems->FocusLock = false;\n  ImageGrid = new Group(this);\n  ImageGrid->RenderingBounds = ThumbnailGridBounds;\n  ImageGrid->HoverBounds = ThumbnailGridBounds;\n  ImageGrid->WrapFocus = false;\n  auto pos = InitialButtonPosition;\n  int idx = 0;\n\n  auto characterOnClick = [this](auto* btn) {\n    return CharacterButtonOnClick(btn);\n  };\n\n  for (int i = 0; i < CharacterButtonCount; i++) {\n    if (idx % 2 == 0)\n      pos.x = ButtonEvenX;\n    else\n      pos.x = ButtonOddX;\n\n    Sprite *sprite, *lockedSprite, *highlightedSprite, *highlightedLockedSprite;\n    if (idx == YunoButtonIdx || idx == SuzuButtonIdx) {\n      sprite = &CharacterButtonSprites[i + 1];\n      lockedSprite = &CharacterButtonSprites[i];\n      highlightedSprite = &HighlightedCharacterButtonSprites[i + 1];\n      highlightedLockedSprite = &HighlightedCharacterButtonSprites[i];\n      i += 1;\n    } else {\n      sprite = &CharacterButtonSprites[i];\n      lockedSprite = sprite;\n      highlightedSprite = &HighlightedCharacterButtonSprites[i];\n      highlightedLockedSprite = highlightedSprite;\n    }\n    auto button = new Widgets::MO6TW::AlbumCharacterButton(\n        idx, *sprite, *lockedSprite, *highlightedSprite,\n        *highlightedLockedSprite, pos, HighlightAnimationDuration);\n    button->OnClickHandler = characterOnClick;\n    MainItems->Add(button, FDIR_DOWN);\n    pos += ButtonMargin;\n    idx += 1;\n  }\n\n  Arrows = new Group(this);\n  Arrows->FocusLock = false;\n  Sprite nullSprite = Sprite();\n  nullSprite.Bounds = RectF(0.0f, 0.0f, 0.0f, 0.0f);\n  ArrowUpButton = new Button(0, ArrowUp, ArrowUp, nullSprite, ArrowUpPosition);\n  ArrowUpButton->DisabledSprite = nullSprite;\n  ArrowUpButton->OnClickHandler = [this](auto* btn) {\n    return ArrowUpOnClick(btn);\n  };\n  ArrowDownButton =\n      new Button(1, ArrowDown, ArrowDown, nullSprite, ArrowDownPosition);\n  ArrowDownButton->DisabledSprite = nullSprite;\n  ArrowDownButton->OnClickHandler = [this](auto* btn) {\n    return ArrowDownOnClick(btn);\n  };\n  Arrows->Add(ArrowUpButton);\n  Arrows->Add(ArrowDownButton);\n}\n\nvoid AlbumMenu::Show() {\n  if (State != Shown) {\n    State = Showing;\n    FadeAnimation.StartIn();\n    MainItems->Show();\n\n    auto suzuButton =\n        static_cast<Button*>(MainItems->Children.at(SuzuButtonIdx));\n    suzuButton->IsLocked = !GetFlag(830);\n\n    int endIdx = YunoButtonIdx == CharacterPortraitCount\n                     ? EventCgCount\n                     : ThumbnailOffsets[YunoButtonIdx + 1];\n    int totalEvVariations = 0, viewedEvVariations = 0;\n    for (int i = ThumbnailOffsets[YunoButtonIdx]; i < endIdx; i++) {\n      int total, viewed;\n      SaveSystem::GetEVStatus(i, &total, &viewed);\n      totalEvVariations += total;\n      viewedEvVariations += viewed;\n    }\n    // Not used yet\n    (void)totalEvVariations;\n    auto yunoButton =\n        static_cast<Button*>(MainItems->Children.at(YunoButtonIdx));\n    yunoButton->IsLocked = viewedEvVariations == 0;\n\n    if (UI::FocusedMenu != 0) {\n      LastFocusedMenu = UI::FocusedMenu;\n      LastFocusedMenu->IsFocused = false;\n    }\n    IsFocused = true;\n    UI::FocusedMenu = this;\n  }\n}\nvoid AlbumMenu::Hide() {\n  if (State != Hidden) {\n    State = Hiding;\n    FadeAnimation.StartOut();\n    MainItems->Hide();\n    if (LastFocusedMenu != 0) {\n      UI::FocusedMenu = LastFocusedMenu;\n      LastFocusedMenu->IsFocused = true;\n    } else {\n      UI::FocusedMenu = 0;\n    }\n    IsFocused = false;\n  }\n}\n\nvoid AlbumMenu::UpdateInput(float dt) {\n  if (State == Shown) {\n    Menu::UpdateInput(dt);\n    MainItems->UpdateInput(dt);\n    if (ShowCgViewer) {\n      CgViewerGroup->UpdateInput(dt);\n    } else {\n      ImageGrid->UpdateInput(dt);\n    }\n    if (SelectedCharacterId != -1 && !ShowCgViewer) {\n      if ((PADinputButtonWentDown & PAD1DOWN ||\n           PADinputButtonWentDown & PAD1UP) &&\n          ImageGrid->HasFocus) {\n        MoveImageGrid();\n      }\n    }\n    if (PADinputButtonWentDown & PAD1B || PADinputMouseWentDown & PAD1B) {\n      if (CgViewerGroup->VisibilityState != Hidden) {\n        CgViewerGroup->Hide();\n        ShowCgViewer = false;\n      } else if (SelectedCharacterId == -1) {\n        SetFlag(SF_ALBUMEND, true);\n      } else {\n        SwitchToCharacter(-1);\n      }\n    }\n  }\n}\n\nvoid AlbumMenu::Update(float dt) {\n  UpdateInput(dt);\n\n  FadeAnimation.Update(dt);\n  if (ScrWork[SW_ALBUM_ALPHA] < 256 && State == Shown) {\n    Hide();\n  } else if (ScrWork[SW_ALBUM_ALPHA] == 256 && State == Hidden) {\n    Show();\n  }\n\n  if (ScrWork[SW_ALBUM_ALPHA] == 256 && FadeAnimation.IsIn())\n    State = Shown;\n  else if (FadeAnimation.IsOut()) {\n    State = Hidden;\n  }\n\n  if (State != Hidden) {\n    MainItems->Update(dt);\n    if (SelectedCharacterId != -1) {\n      if (ShowCgViewer) {\n        if (CgViewerGroup->VisibilityState == Hidden) CgViewerGroup->Show();\n        CgViewerGroup->Update(dt);\n      } else {\n        ArrowsAnimation.Update(dt);\n        ImageGrid->Update(dt);\n        if (ImageGrid->Bounds.Y == MaximumImageGridY) {\n          ArrowUpButton->Enabled = false;\n        } else if (ImageGrid->Bounds.Y == MinimumImageGridY) {\n          ArrowDownButton->Enabled = false;\n        } else {\n          ArrowUpButton->Enabled = true;\n          ArrowDownButton->Enabled = true;\n        }\n      }\n    }\n  }\n}\n\nvoid AlbumMenu::Render() {\n  if (State == Hidden) return;\n\n  glm::vec4 col(1.0f, 1.0f, 1.0f, FadeAnimation.Progress);\n  Renderer->DrawSprite(BackgroundSprite, glm::vec2(0.0f, 0.0f), col);\n  MainItems->Tint = col;\n  MainItems->Render();\n  SecondaryItems->Tint = col;\n  SecondaryItems->Render();\n  ImageGrid->Tint = col;\n  ImageGrid->Render();\n  Arrows->Tint =\n      glm::vec4(1.0f, 1.0f, 1.0f, glm::step(0.5f, ArrowsAnimation.Progress));\n  Arrows->Render();\n  CgViewerGroup->Render();\n}\n\nvoid AlbumMenu::SwitchToCharacter(int id) {\n  SelectedCharacterId = id;\n  if (id == -1) {\n    SecondaryItems->Hide();\n    ImageGrid->Hide();\n    Arrows->Hide();\n    MainItems->Show();\n  } else {\n    LoadCharacter(id);\n    MainItems->Hide();\n    SecondaryItems->Show();\n    Arrows->Show();\n    ImageGrid->Show();\n    FocusStart[FDIR_DOWN] = ImageGrid;\n  }\n}\n\nvoid AlbumMenu::LoadCharacter(int id) {\n  ImageGrid->Clear();\n  SecondaryItems->Clear();\n  ImageGrid->Bounds = ThumbnailGridBounds;\n  // Others category portrait is split into two sprites\n  if (id < CharacterPortraitCount) {\n    SecondaryItems->Add(new Label(\n        CharacterPortraits[id],\n        glm::vec2(PortraitPosition.x - CharacterPortraits[id].ScaledWidth(),\n                  PortraitPosition.y)));\n  } else {\n    SecondaryItems->Add(new Label(\n        OthersPortraitTopPart,\n        glm::vec2(\n            OthersPortraitPosition.x - OthersPortraitTopPart.ScaledWidth(),\n            OthersPortraitPosition.y)));\n    SecondaryItems->Add(new Label(\n        OthersPortraitBottomPart,\n        glm::vec2(\n            OthersPortraitPosition.x - OthersPortraitBottomPart.ScaledWidth(),\n            OthersPortraitPosition.y + OthersPortraitTopPart.ScaledHeight())));\n  }\n\n  auto cgOnClick = [this](auto* btn) { return CgOnClick(btn); };\n\n  int row = 1;\n  int endIdx =\n      id == CharacterPortraitCount ? EventCgCount : ThumbnailOffsets[id + 1];\n  int idx = 0;\n  auto pos = ThumbnailGridFirstPosition;\n  for (int i = ThumbnailOffsets[id]; i < endIdx; i++) {\n    if (idx && idx % ThumbnailsPerRow == 0) {\n      row += 1;\n      pos.x = ThumbnailGridFirstPosition.x;\n      pos.y += ThumbnailGridMargin.y;\n    }\n    int totalEvVariations, viewedEvVariations;\n    SaveSystem::GetEVStatus(i, &totalEvVariations, &viewedEvVariations);\n    auto button = new Widgets::MO6TW::AlbumThumbnailButton(\n        i, Thumbnails[i], LockedThumbnail, ThumbnailHighlightTopLeft,\n        ThumbnailHighlightTopRight, ThumbnailHighlightBottomLeft,\n        ThumbnailHighlightBottomRight, ThumbnailBorder, viewedEvVariations,\n        totalEvVariations, pos);\n    pos.x += ThumbnailGridMargin.x;\n    button->IsLocked = viewedEvVariations == 0;\n    button->OnClickHandler = cgOnClick;\n    ImageGrid->Add(button, FDIR_RIGHT);\n    if (row != 1) {\n      button->SetFocus(ImageGrid->Children.at(idx - ThumbnailsPerRow), FDIR_UP);\n    }\n    idx += 1;\n  }\n\n  int totalRows = row;\n  idx = 0;\n  row = 1;\n  for (const auto& el : ImageGrid->Children) {\n    if (row != totalRows) {\n      Widget* focusTarget;\n      if ((idx + ThumbnailsPerRow) >\n          static_cast<int>(ImageGrid->Children.size() - 1))\n        focusTarget = ImageGrid->Children.back();\n      else\n        focusTarget = ImageGrid->Children.at(idx + ThumbnailsPerRow);\n      el->SetFocus(focusTarget, FDIR_DOWN);\n    }\n\n    idx += 1;\n    if (idx % ThumbnailsPerRow == 0) {\n      row += 1;\n    }\n  }\n\n  MaximumImageGridY = ImageGrid->Bounds.Y;\n  MinimumImageGridY =\n      ImageGrid->Bounds.Y - (row - ThumbnailsPerColumn) * ThumbnailGridMargin.y;\n}\n\nvoid AlbumMenu::MoveImageGrid() {\n  auto focusedEl = CurrentlyFocusedElement;\n  if (focusedEl) {\n    if (focusedEl->Bounds.Y < ImageGrid->RenderingBounds.Y) {\n      ImageGrid->Move(glm::vec2(0.0f, ThumbnailGridMargin.y));\n    } else if (focusedEl->Bounds.Y + focusedEl->Bounds.Height >\n               ImageGrid->RenderingBounds.Y +\n                   ImageGrid->RenderingBounds.Height) {\n      ImageGrid->Move(glm::vec2(0.0f, -ThumbnailGridMargin.y));\n    }\n  }\n}\n\n}  // namespace MO6TW\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/mo6tw/albummenu.h",
    "content": "#pragma once\n\n#include \"../../ui/menu.h\"\n#include \"../../ui/widgets/group.h\"\n#include \"../../ui/widgets/button.h\"\n#include \"../../ui/widgets/cgviewer.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace MO6TW {\n\nclass AlbumMenu : public Menu {\n public:\n  AlbumMenu();\n\n  void Show() override;\n  void Hide() override;\n  void UpdateInput(float dt) override;\n  void Update(float dt) override;\n  void Render() override;\n\n  void CharacterButtonOnClick(Widgets::Button* target);\n  void ArrowUpOnClick(Widgets::Button* target);\n  void ArrowDownOnClick(Widgets::Button* target);\n  void CgOnClick(Widgets::Button* target);\n  void OnCgVariationEnd(Widgets::CgViewer* target);\n\n private:\n  Widgets::Group* MainItems;\n  Widgets::Group* ImageGrid;\n  Widgets::Group* SecondaryItems;\n  Widgets::Group* Arrows;\n  Widgets::Button* ArrowUpButton;\n  Widgets::Button* ArrowDownButton;\n\n  Widgets::Group* CgViewerGroup;\n  Widgets::CgViewer* CgViewerWidget;\n\n  float MinimumImageGridY;\n  float MaximumImageGridY;\n\n  int SelectedCharacterId = -1;\n  bool ShowCgViewer = false;\n\n  Animation FadeAnimation;\n  Animation ArrowsAnimation;\n\n  void SwitchToCharacter(int id);\n  void LoadCharacter(int id);\n  void MoveImageGrid();\n};\n\n}  // namespace MO6TW\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/mo6tw/backlogmenu.cpp",
    "content": "#include \"backlogmenu.h\"\n\n#include \"../../ui/backlogmenu.h\"\n#include \"../../profile/ui/backlogmenu.h\"\n#include \"../../profile/games/mo6tw/backlogmenu.h\"\n#include \"../../profile/games/mo6tw/systemmenu.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace MO6TW {\n\nusing namespace Impacto::Profile::BacklogMenu;\nusing namespace Impacto::Profile::MO6TW::BacklogMenu;\n\nvoid BacklogMenu::Render() {\n  if (State == Hidden) return;\n\n  float opacity = glm::smoothstep(0.0f, 1.0f, FadeAnimation.Progress);\n  glm::vec4 col(1.0f, 1.0f, 1.0f, opacity);\n  MainItems->Tint = col;\n  MainScrollbar->Tint = col;\n\n  Renderer->DrawSprite(BacklogBackground, glm::vec2(0.0f), col);\n  RenderHighlight();\n  MainItems->Render();\n  MainScrollbar->Render();\n}\n\nvoid BacklogMenu::UpdateVisibility() {\n  if (FadeAnimation.IsIn()) {\n    State = Shown;\n    IsFocused = true;\n  } else if (FadeAnimation.IsOut()) {\n    State = Hidden;\n    IsFocused = false;\n    if (UI::FocusedMenu) UI::FocusedMenu->IsFocused = true;\n\n    MainItems->Hide();\n  }\n}\n\n}  // namespace MO6TW\n}  // namespace UI\n}  // namespace Impacto\n"
  },
  {
    "path": "src/games/mo6tw/backlogmenu.h",
    "content": "#pragma once\n\n#include \"../../ui/backlogmenu.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace MO6TW {\n\nclass BacklogMenu : public UI::BacklogMenu {\n public:\n  void Render() override;\n\n private:\n  void UpdateVisibility() override;\n};\n\n}  // namespace MO6TW\n}  // namespace UI\n}  // namespace Impacto\n"
  },
  {
    "path": "src/games/mo6tw/clearlistmenu.cpp",
    "content": "#include \"clearlistmenu.h\"\n#include <fmt/format.h>\n#include <string>\n\n#include \"../../profile/ui/extramenus.h\"\n#include \"../../profile/games/mo6tw/clearlistmenu.h\"\n#include \"../../profile/ui/backlogmenu.h\"\n#include \"../../profile/dialogue.h\"\n#include \"../../renderer/renderer.h\"\n#include \"../../mem.h\"\n#include \"../../profile/scriptvars.h\"\n#include \"../../vm/vm.h\"\n#include \"../../vm/interface/input.h\"\n#include \"../../data/savesystem.h\"\n#include \"../../ui/widgets/mo6tw/scenelistentry.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace MO6TW {\n\nusing namespace Impacto::Profile::MO6TW::ClearListMenu;\nusing namespace Impacto::Profile::ScriptVars;\n\nusing namespace Impacto::Vm::Interface;\n\nusing namespace Impacto::UI::Widgets;\n\nvoid ClearListMenu::ArrowLeftOnClick(Button* target) { MainItems->Previous(); }\n\nvoid ClearListMenu::ArrowRightOnClick(Button* target) { MainItems->Next(); }\n\nClearListMenu::ClearListMenu() {\n  FadeAnimation.Direction = AnimationDirection::In;\n  FadeAnimation.LoopMode = AnimationLoopMode::Stop;\n  FadeAnimation.DurationIn = FadeInDuration;\n  FadeAnimation.DurationOut = FadeOutDuration;\n\n  ArrowsAnimation.Direction = AnimationDirection::In;\n  ArrowsAnimation.LoopMode = AnimationLoopMode::ReverseDirection;\n  ArrowsAnimation.SetDuration(ArrowsAnimationDuration);\n  ArrowsAnimation.StartIn();\n\n  MainItems = new Carousel(\n      CDIR_HORIZONTAL,\n      [this](auto* curPage, auto* nextPage) {\n        return OnAdvancePage(curPage, nextPage);\n      },\n      [this](auto* curPage, auto* nextPage) {\n        return OnGoBackPage(curPage, nextPage);\n      });\n\n  Arrows = new Group(this);\n  Arrows->FocusLock = false;\n  Sprite nullSprite = Sprite();\n  nullSprite.Bounds = RectF(0.0f, 0.0f, 0.0f, 0.0f);\n  auto arrowLeft =\n      new Button(0, ArrowLeft, ArrowLeft, nullSprite, ArrowLeftPosition);\n  arrowLeft->OnClickHandler = [this](auto* btn) {\n    return ArrowLeftOnClick(btn);\n  };\n  auto arrowRight =\n      new Button(1, ArrowRight, ArrowRight, nullSprite, ArrowRightPosition);\n  arrowRight->OnClickHandler = [this](auto* btn) {\n    return ArrowRightOnClick(btn);\n  };\n\n  Arrows->Add(arrowLeft);\n  Arrows->Add(arrowRight);\n  Arrows->VisibilityState = Shown;\n}\n\nvoid ClearListMenu::Show() {\n  if (!IsInit) {\n    InitMainPage();\n    InitSceneTitlePage();\n    InitEndingListPage();\n    IsInit = true;\n  }\n\n  if (State != Shown) {\n    State = Showing;\n    FadeAnimation.StartIn();\n    MainItems->Show();\n    UpdateEndingCount();\n    UpdateSceneCount();\n    UpdateAlbumCount();\n    UpdateCompletionPercentage();\n    UpdateEndingList();\n    UpdateSceneList();\n    if (UI::FocusedMenu != 0) {\n      LastFocusedMenu = UI::FocusedMenu;\n      LastFocusedMenu->IsFocused = false;\n    }\n    IsFocused = true;\n    UI::FocusedMenu = this;\n  }\n}\nvoid ClearListMenu::Hide() {\n  if (State != Hidden) {\n    State = Hiding;\n    FadeAnimation.StartOut();\n    MainItems->Hide();\n    if (LastFocusedMenu != 0) {\n      UI::FocusedMenu = LastFocusedMenu;\n      LastFocusedMenu->IsFocused = true;\n    } else {\n      UI::FocusedMenu = 0;\n    }\n    IsFocused = false;\n  }\n}\n\nvoid ClearListMenu::UpdateInput(float dt) {\n  Menu::UpdateInput(dt);\n  if (State == Shown) {\n    MainItems->UpdateInput(dt);\n    Arrows->UpdateInput(dt);\n    if ((PADinputButtonWentDown & PAD1DOWN ||\n         PADinputButtonWentDown & PAD1UP) &&\n        SceneTitleItems->HasFocus) {\n      auto focusedEl = CurrentlyFocusedElement;\n      if (focusedEl->Bounds.Y < SceneTitleItems->RenderingBounds.Y) {\n        SceneListY += SceneListTextMargin.y;\n      } else if (focusedEl->Bounds.Y + focusedEl->Bounds.Height >\n                 SceneTitleItems->RenderingBounds.Y +\n                     SceneTitleItems->RenderingBounds.Height) {\n        SceneListY -= SceneListTextMargin.y;\n      }\n    }\n  }\n}\n\nvoid ClearListMenu::Update(float dt) {\n  UpdateInput(dt);\n\n  FadeAnimation.Update(dt);\n  if (ScrWork[SW_PLAYDATA_ALPHA] < 256 && State == Shown) {\n    Hide();\n  } else if (ScrWork[SW_PLAYDATA_ALPHA] == 256 && State == Hidden) {\n    Show();\n  }\n\n  if (ScrWork[SW_PLAYDATA_ALPHA] == 256 && FadeAnimation.IsIn())\n    State = Shown;\n  else if (FadeAnimation.IsOut()) {\n    State = Hidden;\n  }\n\n  if (State != Hidden) {\n    ArrowsAnimation.Update(dt);\n    UpdatePlayTime();\n    MainItems->Update(dt);\n    Arrows->Update(dt);\n    SceneTitleItems->MoveTo(glm::vec2(SceneTitleItems->Bounds.X, SceneListY));\n\n    if (PreviousPage && PreviousPage->MoveAnimation.IsIn()) {\n      PreviousPage->Hide();\n    }\n  }\n}\n\nvoid ClearListMenu::Render() {\n  if (State == Hidden) return;\n\n  glm::vec4 col(1.0f, 1.0f, 1.0f, FadeAnimation.Progress);\n  Renderer->DrawSprite(BackgroundSprite, glm::vec2(0.0f, 0.0f), col);\n  MainItems->Tint = col;\n  MainItems->Render();\n  Arrows->Tint =\n      glm::vec4(1.0f, 1.0f, 1.0f, glm::step(0.5f, ArrowsAnimation.Progress));\n  Arrows->Render();\n}\n\nvoid ClearListMenu::InitMainPage() {\n  MainPage = new Group(this);\n  MainPage->FocusLock = false;\n\n  MainPage->Add(new Label(ClearListLabel, LabelPosition));\n  MainPage->Add(new Label(EndingsLabel, EndingsLabelPosition));\n  MainPage->Add(new Label(ClearListTextBackground,\n                          EndingsLabelPosition + ClearListTextBGOffset));\n  MainPage->Add(new Label(ScenesLabel, ScenesLabelPosition));\n  MainPage->Add(new Label(ClearListTextBackground,\n                          ScenesLabelPosition + ClearListTextBGOffset));\n  MainPage->Add(new Label(CompletionLabel, CompletionLabelPosition));\n  MainPage->Add(new Label(ClearListTextBackground,\n                          CompletionLabelPosition + ClearListTextBGOffset));\n  MainPage->Add(new Label(AlbumLabel, AlbumLabelPosition));\n  MainPage->Add(new Label(ClearListTextBackground,\n                          AlbumLabelPosition + ClearListTextBGOffset));\n  MainPage->Add(new Label(PlayTimeLabel, PlayTimeLabelPosition));\n  MainPage->Add(new Label(ClearListTextBackground,\n                          PlayTimeLabelPosition + ClearListTextBGOffset));\n\n  Vm::Sc3VmThread dummy;\n  uint16_t sc3StringBuffer[10];\n  Vm::Sc3Stream sc3StrStream(sc3StringBuffer);\n\n  auto separator =\n      Vm::ScriptGetTextTableStrAddress(SeparatorTable, SeparatorEntry);\n  dummy.IpOffset = separator.IpOffset;\n  dummy.ScriptBufferId = separator.ScriptBufferId;\n  SeparatorWidth =\n      TextGetPlainLineWidth(&dummy, Profile::Dialogue::DialogueFont, FontSize);\n\n  // Ending count\n  TextGetSc3String(fmt::to_string(EndingCount), sc3StringBuffer);\n  EndingCountWidth = TextGetPlainLineWidth(\n      sc3StrStream, Profile::Dialogue::DialogueFont, FontSize);\n  sc3StrStream = Vm::Sc3Stream(sc3StringBuffer);\n  MainPage->Add(new Label(\n      sc3StrStream,\n      glm::vec2(\n          (EndingsLabelPosition.x - EndingCountWidth) + EndingCountPosition.x,\n          EndingCountPosition.y),\n      FontSize, RendererOutlineMode::None, ClearListColorIndex));\n\n  MainPage->Add(new Label(\n      separator,\n      glm::vec2((EndingsLabelPosition.x - (EndingCountWidth + SeparatorWidth)) +\n                    EndingCountPosition.x,\n                EndingCountPosition.y),\n      FontSize, RendererOutlineMode::None, ClearListColorIndex));\n\n  UnlockedEndingCount = new Label();\n  MainPage->Add(UnlockedEndingCount);\n\n  // Scene count\n  TextGetSc3String(fmt::to_string(SceneCount), sc3StringBuffer);\n  SceneCountWidth = TextGetPlainLineWidth(\n      sc3StrStream, Profile::Dialogue::DialogueFont, FontSize);\n  sc3StrStream = Vm::Sc3Stream(sc3StringBuffer);\n  MainPage->Add(new Label(sc3StrStream,\n                          glm::vec2((ScenesLabelPosition.x - SceneCountWidth) +\n                                        SceneCountPosition.x,\n                                    SceneCountPosition.y),\n                          FontSize, RendererOutlineMode::None,\n                          ClearListColorIndex));\n  MainPage->Add(new Label(\n      separator,\n      glm::vec2((ScenesLabelPosition.x - (SceneCountWidth + SeparatorWidth)) +\n                    SceneCountPosition.x,\n                SceneCountPosition.y),\n      FontSize, RendererOutlineMode::None, ClearListColorIndex));\n\n  UnlockedSceneCount = new Label();\n  MainPage->Add(UnlockedSceneCount);\n\n  // Completion percentage\n  CompletionPercentage = new Label();\n  MainPage->Add(CompletionPercentage);\n\n  // Album count\n  int totalCount = 0, unlockedCount = 0;\n  SaveSystem::GetViewedEVsCount(&totalCount, &unlockedCount);\n  TextGetSc3String(fmt::to_string(totalCount), sc3StringBuffer);\n  sc3StrStream = Vm::Sc3Stream(sc3StringBuffer);\n  AlbumCountWidth = TextGetPlainLineWidth(\n      sc3StrStream, Profile::Dialogue::DialogueFont, FontSize);\n  sc3StrStream = Vm::Sc3Stream(sc3StringBuffer);\n  MainPage->Add(new Label(\n      sc3StrStream,\n      glm::vec2((AlbumLabelPosition.x - AlbumCountWidth) + AlbumCountPosition.x,\n                AlbumCountPosition.y),\n      FontSize, RendererOutlineMode::None, ClearListColorIndex));\n\n  MainPage->Add(new Label(\n      separator,\n      glm::vec2((AlbumLabelPosition.x - (AlbumCountWidth + SeparatorWidth)) +\n                    AlbumCountPosition.x,\n                AlbumCountPosition.y),\n      FontSize, RendererOutlineMode::None, ClearListColorIndex));\n\n  UnlockedAlbumCount = new Label();\n  MainPage->Add(UnlockedAlbumCount);\n\n  // Play time\n  auto secondsText = Vm::ScriptGetTextTableStrAddress(PlayTimeTextTable,\n                                                      PlayTimeSecondsTextEntry);\n  dummy.SetIp(secondsText);\n  SecondsTextWidth =\n      TextGetPlainLineWidth(&dummy, Profile::Dialogue::DialogueFont, FontSize);\n  MainPage->Add(new Label(secondsText,\n                          glm::vec2(PlayTimeLabelPosition.x - SecondsTextWidth +\n                                        PlayTimeSecondsTextPosition.x,\n                                    PlayTimeSecondsTextPosition.y),\n                          FontSize, RendererOutlineMode::None,\n                          ClearListColorIndex));\n\n  auto minutesText = Vm::ScriptGetTextTableStrAddress(PlayTimeTextTable,\n                                                      PlayTimeMinutesTextEntry);\n  dummy.SetIp(minutesText);\n  MinutesTextWidth =\n      TextGetPlainLineWidth(&dummy, Profile::Dialogue::DialogueFont, FontSize);\n  MainPage->Add(\n      new Label(minutesText,\n                glm::vec2(PlayTimeLabelPosition.x - SecondsTextWidth -\n                              MinutesTextWidth + PlayTimeMinutesTextPosition.x,\n                          PlayTimeMinutesTextPosition.y),\n                FontSize, RendererOutlineMode::None, ClearListColorIndex));\n\n  auto hoursText = Vm::ScriptGetTextTableStrAddress(PlayTimeTextTable,\n                                                    PlayTimeHoursTextEntry);\n  dummy.SetIp(hoursText);\n  HoursTextWidth =\n      TextGetPlainLineWidth(&dummy, Profile::Dialogue::DialogueFont, FontSize);\n  HoursText = new Label(\n      hoursText,\n      glm::vec2(PlayTimeLabelPosition.x - SecondsTextWidth - MinutesTextWidth -\n                    HoursTextWidth + PlayTimeHoursTextPosition.x,\n                PlayTimeHoursTextPosition.y),\n      FontSize, RendererOutlineMode::None, ClearListColorIndex);\n  HoursText->Tint.a = 0.0f;\n  MainPage->Add(HoursText);\n  PlaySeconds = new Label();\n  MainPage->Add(PlaySeconds);\n  PlayMinutes = new Label();\n  MainPage->Add(PlayMinutes);\n  PlayHours = new Label();\n  PlayHours->Tint.a = 0.0f;\n  MainPage->Add(PlayHours);\n\n  UpdatePlayTime();\n\n  MainPage->Show();\n\n  MainItems->Add(MainPage);\n}\n\nvoid ClearListMenu::UpdateEndingCount() {\n  uint16_t sc3StringBuffer[10];\n  Vm::Sc3Stream stream(sc3StringBuffer);\n\n  int unlockedEndingCount = 0;\n  for (int i = 0; i < EndingCount; i++) {\n    unlockedEndingCount += GetFlag(SF_CLR_END1 + i);\n  }\n  TextGetSc3String(fmt::format(\"{:2}\", unlockedEndingCount), sc3StringBuffer);\n  float unlockedEndingCountWidth =\n      TextGetPlainLineWidth(stream, Profile::Dialogue::DialogueFont, FontSize);\n  stream = Vm::Sc3Stream(sc3StringBuffer);\n  UnlockedEndingCount->Bounds.X =\n      (EndingsLabelPosition.x -\n       (EndingCountWidth + SeparatorWidth + unlockedEndingCountWidth)) +\n      EndingCountPosition.x;\n  UnlockedEndingCount->Bounds.Y = EndingCountPosition.y;\n  UnlockedEndingCount->SetText(stream, FontSize, RendererOutlineMode::None,\n                               ClearListColorIndex);\n}\n\nvoid ClearListMenu::UpdateSceneCount() {\n  uint16_t sc3StringBuffer[10];\n  Vm::Sc3Stream stream(sc3StringBuffer);\n  int unlockedSceneCount = 0;\n  for (int i = 0; i < SceneCount; ++i) {\n    unlockedSceneCount += GetFlag(SF_SCN_CLR1 + i);\n  }\n  TextGetSc3String(fmt::to_string(unlockedSceneCount), sc3StringBuffer);\n  float unlockedSceneCountWidth =\n      TextGetPlainLineWidth(stream, Profile::Dialogue::DialogueFont, FontSize);\n  stream = Vm::Sc3Stream(sc3StringBuffer);\n  UnlockedSceneCount->Bounds.X =\n      (ScenesLabelPosition.x -\n       (SceneCountWidth + SeparatorWidth + unlockedSceneCountWidth)) +\n      SceneCountPosition.x;\n  UnlockedSceneCount->Bounds.Y = SceneCountPosition.y;\n  UnlockedSceneCount->SetText(stream, FontSize, RendererOutlineMode::None,\n                              ClearListColorIndex);\n}\n\nvoid ClearListMenu::UpdateAlbumCount() {\n  uint16_t sc3StringBuffer[10];\n  Vm::Sc3Stream stream(sc3StringBuffer);\n\n  int totalCount = 0, unlockedCount = 0;\n  SaveSystem::GetViewedEVsCount(&totalCount, &unlockedCount);\n  TextGetSc3String(fmt::to_string(unlockedCount), sc3StringBuffer);\n  float unlockedAlbumCountWidth =\n      TextGetPlainLineWidth(stream, Profile::Dialogue::DialogueFont, FontSize);\n  stream = Vm::Sc3Stream(sc3StringBuffer);\n  UnlockedAlbumCount->Bounds.X =\n      (AlbumLabelPosition.x -\n       (AlbumCountWidth + SeparatorWidth + unlockedAlbumCountWidth)) +\n      AlbumCountPosition.x;\n  UnlockedAlbumCount->Bounds.Y = AlbumCountPosition.y;\n  UnlockedAlbumCount->SetText(stream, FontSize, RendererOutlineMode::None,\n                              ClearListColorIndex);\n}\n\nvoid ClearListMenu::UpdateCompletionPercentage() {\n  uint16_t sc3StringBuffer[10];\n  Vm::Sc3Stream stream(sc3StringBuffer);\n\n  int totalMessageCount = 0, readMessageCount = 0;\n\n  SaveSystem::GetReadMessagesCount(&totalMessageCount, &readMessageCount);\n  float readPercentage = readMessageCount / (float)totalMessageCount * 100.0f;\n  TextGetSc3String(fmt::format(\"{:.2f}%\", readPercentage), sc3StringBuffer);\n  float percentageWidth =\n      TextGetPlainLineWidth(stream, Profile::Dialogue::DialogueFont, FontSize);\n  stream = Vm::Sc3Stream(sc3StringBuffer);\n\n  CompletionPercentage->Bounds.X =\n      CompletionLabelPosition.x - percentageWidth + CompletionPosition.x;\n  CompletionPercentage->Bounds.Y = CompletionPosition.y;\n  CompletionPercentage->SetText(stream, FontSize, RendererOutlineMode::None,\n                                ClearListColorIndex);\n}\n\nvoid ClearListMenu::UpdatePlayTime() {\n  auto seconds = ScrWork[SW_TOTALPLAYTIME] % 3600 % 60;\n  auto minutes = ScrWork[SW_TOTALPLAYTIME] % 3600 / 60;\n  auto hours = ScrWork[SW_TOTALPLAYTIME] / 3600;\n\n  uint16_t sc3StringBuffer[10];\n  Vm::Sc3Stream stream(sc3StringBuffer);\n\n  TextGetSc3String(fmt::format(\"{:2d}\", seconds), sc3StringBuffer);\n  float secondsWidth =\n      TextGetPlainLineWidth(stream, Profile::Dialogue::DialogueFont, FontSize);\n  stream = Vm::Sc3Stream(sc3StringBuffer);\n  PlaySeconds->Bounds.X = PlayTimeLabelPosition.x -\n                          (SecondsTextWidth + secondsWidth) +\n                          PlayTimeSecondsPosition.x;\n  PlaySeconds->Bounds.Y = PlayTimeSecondsPosition.y;\n  PlaySeconds->SetText(stream, FontSize, RendererOutlineMode::None,\n                       ClearListColorIndex);\n  TextGetSc3String(fmt::format(\"{:2d}\", minutes), sc3StringBuffer);\n  stream = Vm::Sc3Stream(sc3StringBuffer);\n  float minutesWidth =\n      TextGetPlainLineWidth(stream, Profile::Dialogue::DialogueFont, FontSize);\n  stream = Vm::Sc3Stream(sc3StringBuffer);\n  PlayMinutes->Bounds.X = PlayTimeLabelPosition.x -\n                          (SecondsTextWidth + MinutesTextWidth + minutesWidth) +\n                          PlayTimeMinutesPosition.x;\n  PlayMinutes->Bounds.Y = PlayTimeMinutesPosition.y;\n  PlayMinutes->SetText(stream, FontSize, RendererOutlineMode::None,\n                       ClearListColorIndex);\n\n  if (hours != 0) {\n    HoursText->Tint.a = FadeAnimation.Progress;\n    PlayHours->Tint.a = FadeAnimation.Progress;\n    TextGetSc3String(fmt::format(\"{:2d}\", hours), sc3StringBuffer);\n    stream = Vm::Sc3Stream(sc3StringBuffer);\n    float hoursWidth = TextGetPlainLineWidth(\n        stream, Profile::Dialogue::DialogueFont, FontSize);\n    stream = Vm::Sc3Stream(sc3StringBuffer);\n    PlayHours->Bounds.X = PlayTimeLabelPosition.x - SecondsTextWidth -\n                          MinutesTextWidth - (HoursTextWidth + hoursWidth) +\n                          PlayTimeHoursPosition.x;\n    PlayHours->Bounds.Y = PlayTimeHoursPosition.y;\n    PlayHours->SetText(stream, FontSize, RendererOutlineMode::None,\n                       ClearListColorIndex);\n  } else {\n    PlayHours->Tint.a = 0.0f;\n    HoursText->Tint.a = 0.0f;\n  }\n}\n\nvoid ClearListMenu::InitEndingListPage() {\n  EndingListPage = new Group(this);\n  EndingListPage->FocusLock = false;\n  EndingNames = new Widget*[EndingCount * 2];\n\n  EndingListPage->Add(new Label(EndingListLabel, LabelPosition));\n  EndingListPage->Add(new Label(WindowSprite, WindowPosition));\n\n  auto numberLabelPos = EndingsListNumberInitialPosition;\n  auto textLabelPos = EndingsListTextInitialPosition;\n  int idx = 0;\n  auto lockedText = Vm::ScriptGetTextTableStrAddress(\n      EndingsListTextLockedTable, EndingsListTextLockedEntry);\n  for (int i = 0; i < EndingCount; i++) {\n    auto numberLabel = new Label(\n        fmt::format(\"{:2d}\", i + 1), numberLabelPos, EndingsListTextFontSize,\n        RendererOutlineMode::None, EndingsListTextColorIndex);\n    numberLabelPos += EndingsListTextMargin;\n\n    auto lockedLabel =\n        new Label(lockedText, textLabelPos, EndingsListTextFontSize,\n                  RendererOutlineMode::None, EndingsListTextColorIndex);\n    auto unlockedLabel =\n        new Label(Vm::ScriptGetTextTableStrAddress(EndingsListTextTable, i),\n                  textLabelPos, EndingsListTextFontSize,\n                  RendererOutlineMode::None, EndingsListTextColorIndex);\n    textLabelPos += EndingsListTextMargin;\n\n    EndingNames[idx] = unlockedLabel;\n    idx += 1;\n    EndingNames[idx] = lockedLabel;\n    idx += 1;\n\n    EndingListPage->Add(numberLabel);\n    EndingListPage->Add(lockedLabel);\n    EndingListPage->Add(unlockedLabel);\n  }\n\n  MainItems->Add(EndingListPage);\n}\n\nvoid ClearListMenu::UpdateEndingList() {\n  int idx = 0;\n  for (int i = 0; i < EndingCount * 2; i += 2) {\n    if (GetFlag(SF_CLR_END1 + idx)) {\n      EndingNames[i]->Tint.a = 1.0f;\n      EndingNames[i + 1]->Tint.a = 0.0f;\n    } else {\n      EndingNames[i + 1]->Tint.a = 1.0f;\n      EndingNames[i]->Tint.a = 0.0f;\n    }\n    idx += 1;\n  }\n}\n\nvoid ClearListMenu::InitSceneTitlePage() {\n  SceneTitlePage = new Group(this);\n  SceneTitlePage->FocusLock = false;\n  SceneNames = new Widget*[SceneCount * 2];\n\n  SceneTitlePage->Add(new Label(SceneTitleLabel, LabelPosition));\n  SceneTitlePage->Add(new Label(WindowSpritePartLeft, WindowPosition));\n  SceneTitlePage->Add(new Label(\n      WindowSpritePartRight,\n      WindowPosition + glm::vec2(WindowSpritePartLeft.ScaledWidth(), 0.0f)));\n\n  SceneTitleItems = new Group(this);\n  SceneTitleItems->FocusLock = false;\n\n  auto numberLabelPos = SceneListNumberInitialPosition;\n  auto textLabelPos = SceneListTextInitialPosition;\n\n  auto lockedText = Vm::ScriptGetTextTableStrAddress(SceneTitleLockedTable,\n                                                     SceneTitleLockedEntry);\n  for (int i = 0; i < SceneCount; i++) {\n    auto numberLabel = new Label(fmt::format(\"{:2d}\", i + 1), numberLabelPos,\n                                 SceneListFontSize, RendererOutlineMode::None,\n                                 SceneListColorIndex);\n    numberLabelPos += SceneListTextMargin;\n\n    auto lockedLabel =\n        new Label(lockedText, textLabelPos, SceneListFontSize,\n                  RendererOutlineMode::None, SceneListColorIndex);\n    auto unlockedLabel = new Label(\n        Vm::ScriptGetTextTableStrAddress(SceneListTextTable, i), textLabelPos,\n        SceneListFontSize, RendererOutlineMode::None, SceneListColorIndex);\n    textLabelPos += SceneListTextMargin;\n    SceneTitleItems->Add(new Widgets::MO6TW::SceneListEntry(\n                             i, numberLabel, lockedLabel, unlockedLabel,\n                             Profile::BacklogMenu::EntryHighlight, true),\n                         FDIR_DOWN);\n  }\n\n  float totalHeight =\n      (SceneListFontSize * SceneCount) +\n      ((SceneListTextMargin.y - SceneListFontSize) * (SceneCount - 2));\n  SceneTitleItems->RenderingBounds = SceneTitleItemsRenderingBounds;\n  SceneTitleItems->HoverBounds = SceneTitleItemsRenderingBounds;\n  SceneTitleItems->Bounds =\n      RectF(SceneListNumberInitialPosition.x, SceneListNumberInitialPosition.y,\n            SceneTitleItemsWidth, totalHeight);\n  SceneTitlePage->Add(SceneTitleItems);\n\n  SceneListY = ScrollbarStart;\n  auto scrollbar = new Scrollbar(\n      0, ScrollbarPosition, ScrollbarStart,\n      ScrollbarStart - totalHeight + ScrollAreaHeight, &SceneListY,\n      SBDIR_VERTICAL, ScrollbarTrack, ScrollbarThumb);\n  SceneTitlePage->Add(scrollbar);\n\n  MainItems->Add(SceneTitlePage);\n}\n\nvoid ClearListMenu::UpdateSceneList() {\n  for (int i = 0; i < SceneCount; i++) {\n    auto entry = static_cast<Widgets::MO6TW::SceneListEntry*>(\n        SceneTitleItems->Children.at(i));\n    entry->IsLocked = !GetFlag(SF_SCN_CLR1 + i);\n  }\n}\n\n// TEST\nvoid ClearListMenu::OnAdvancePage(Widget* currentPage, Widget* nextPage) {\n  currentPage->Move(glm::vec2(750.0f, 0.0f), 0.4f);\n  nextPage->MoveTo(glm::vec2(-750.0f, 0.0f));\n  nextPage->Move(glm::vec2(750.0f, 0.0f), 0.4f);\n  nextPage->Show();\n  CurrentPage = nextPage;\n  PreviousPage = currentPage;\n}\n\n// TEST\nvoid ClearListMenu::OnGoBackPage(Widget* currentPage, Widget* nextPage) {\n  currentPage->Move(glm::vec2(-750.0f, 0.0f), 0.4f);\n  nextPage->MoveTo(glm::vec2(750.0f, 0.0f));\n  nextPage->Move(glm::vec2(-750.0f, 0.0f), 0.4f);\n  nextPage->Show();\n  CurrentPage = nextPage;\n  PreviousPage = currentPage;\n}\n\n}  // namespace MO6TW\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/mo6tw/clearlistmenu.h",
    "content": "#pragma once\n\n#include \"../../ui/menu.h\"\n#include \"../../ui/widgets/group.h\"\n#include \"../../ui/widgets/carousel.h\"\n#include \"../../ui/widgets/label.h\"\n#include \"../../ui/widgets/button.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace MO6TW {\n\nclass ClearListMenu : public Menu {\n public:\n  ClearListMenu();\n\n  void Show() override;\n  void Hide() override;\n  void UpdateInput(float dt) override;\n  void Update(float dt) override;\n  void Render() override;\n\n  void OnAdvancePage(Widget* currentPage, Widget* nextPage);\n  void OnGoBackPage(Widget* currentPage, Widget* nextPage);\n  void ArrowLeftOnClick(Widgets::Button* target);\n  void ArrowRightOnClick(Widgets::Button* target);\n\n private:\n  void InitMainPage();\n  void UpdatePlayTime();\n  void UpdateEndingCount();\n  void UpdateSceneCount();\n  void UpdateAlbumCount();\n  void UpdateCompletionPercentage();\n  void InitEndingListPage();\n  void UpdateEndingList();\n  void InitSceneTitlePage();\n  void UpdateSceneList();\n\n  bool IsInit = false;\n\n  Widgets::Carousel* MainItems;\n  Widgets::Group* Arrows;\n  Widgets::Group* MainPage;\n  Widgets::Group* SceneTitlePage;\n  Widgets::Group* EndingListPage;\n\n  Widget* CurrentPage = 0;\n  Widget* PreviousPage = 0;\n\n  float SeparatorWidth;\n  Widgets::Label* UnlockedEndingCount;\n  float EndingCountWidth;\n  Widgets::Label* UnlockedSceneCount;\n  float SceneCountWidth;\n  Widgets::Label* UnlockedAlbumCount;\n  float AlbumCountWidth;\n  Widgets::Label* CompletionPercentage;\n  Widgets::Label* PlaySeconds;\n  Widgets::Label* PlayMinutes;\n  Widgets::Label* PlayHours;\n  Widgets::Label* HoursText;\n  float SecondsTextWidth;\n  float MinutesTextWidth;\n  float HoursTextWidth;\n\n  Widget** EndingNames;\n  Widget** SceneNames;\n\n  float SceneListY;\n  Widgets::Group* SceneTitleItems;\n\n  Animation FadeAnimation;\n  Animation ArrowsAnimation;\n};\n\n}  // namespace MO6TW\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/mo6tw/moviemenu.cpp",
    "content": "#include \"moviemenu.h\"\n\n#include \"../../profile/ui/extramenus.h\"\n#include \"../../profile/games/mo6tw/moviemenu.h\"\n#include \"../../renderer/renderer.h\"\n#include \"../../mem.h\"\n#include \"../../profile/scriptvars.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace MO6TW {\n\nusing namespace Impacto::Profile::MO6TW::MovieMenu;\nusing namespace Impacto::Profile::ScriptVars;\n\nusing namespace Impacto::UI::Widgets;\n\nvoid MovieMenu::MovieButtonOnClick(Button* target) {\n  auto movieButton = static_cast<Widgets::MO6TW::ImageThumbnailButton*>(target);\n  if (!movieButton->IsLocked) {\n    ScrWork[SW_MOVIEMODE_CUR] = movieButton->Id;\n  }\n}\n\nMovieMenu::MovieMenu() {\n  FadeAnimation.Direction = AnimationDirection::In;\n  FadeAnimation.LoopMode = AnimationLoopMode::Stop;\n  FadeAnimation.DurationIn = FadeInDuration;\n  FadeAnimation.DurationOut = FadeOutDuration;\n\n  MainItems = new Group(this);\n  MainItems->WrapFocus = false;\n\n  auto onClick = [this](auto* btn) { return MovieButtonOnClick(btn); };\n\n  auto pos = InitialItemPosition;\n\n  Sprite nullSprite = Sprite();\n  nullSprite.Bounds = RectF(0.0f, 0.0f, 0.0f, 0.0f);\n  auto firstOp = new Widgets::MO6TW::ImageThumbnailButton(\n      0, FirstOPTopPartSprite, FirstOPBottomPartSprite, nullSprite,\n      SelectionHighlightTopLeft, SelectionHighlightTopRight,\n      SelectionHighlightBottomLeft, SelectionHighlightBottomRight, pos);\n  firstOp->OnClickHandler = onClick;\n  MainItems->Add(firstOp, FDIR_RIGHT);\n\n  pos.x += ItemOffset.x;\n\n  auto secondOp = new Widgets::MO6TW::ImageThumbnailButton(\n      1, SecondOPTopPartSprite, SecondOPBottomPartSprite, nullSprite,\n      SelectionHighlightTopLeft, SelectionHighlightTopRight,\n      SelectionHighlightBottomLeft, SelectionHighlightBottomRight, pos);\n  secondOp->OnClickHandler = onClick;\n  MainItems->Add(secondOp, FDIR_RIGHT);\n\n  pos.x += ItemOffset.x;\n\n  int row = 1;\n  int totalRows = ItemCount / ItemsPerRow + (ItemCount % ItemsPerRow != 0);\n  for (int i = 2; i < ItemCount; i++) {\n    auto item = new Widgets::MO6TW::ImageThumbnailButton(\n        i, UnlockedMovieThumbnailSprites[i - 2],\n        LockedMovieThumbnailSprites[i - 2], SelectionHighlightTopLeft,\n        SelectionHighlightTopRight, SelectionHighlightBottomLeft,\n        SelectionHighlightBottomRight, pos);\n    item->OnClickHandler = onClick;\n    MainItems->Add(item, FDIR_RIGHT);\n    if (row != 1) {\n      item->SetFocus(MainItems->Children.at(i - ItemsPerRow), FDIR_UP);\n    }\n\n    if ((i + 1) % ItemsPerRow == 0) {\n      row += 1;\n      pos.x = InitialItemPosition.x;\n      pos.y += ItemOffset.y;\n    } else {\n      pos.x += ItemOffset.x;\n    }\n  }\n\n  row = 1;\n  size_t idx = 0;\n  for (const auto& el : MainItems->Children) {\n    if (row != totalRows) {\n      Widget* focusTarget;\n      if ((idx + ItemsPerRow) > MainItems->Children.size() - 1)\n        focusTarget = MainItems->Children.back();\n      else\n        focusTarget = MainItems->Children.at(idx + ItemsPerRow);\n      el->SetFocus(focusTarget, FDIR_DOWN);\n    }\n\n    idx += 1;\n    if (idx % ItemsPerRow == 0) {\n      row += 1;\n    }\n  }\n}\n\nvoid MovieMenu::Show() {\n  if (State != Shown) {\n    State = Showing;\n    FadeAnimation.StartIn();\n    MainItems->Show();\n    UpdateMovieEntries();\n    ScrWork[SW_MOVIEMODE_CUR] = 255;\n    if (UI::FocusedMenu != 0) {\n      LastFocusedMenu = UI::FocusedMenu;\n      LastFocusedMenu->IsFocused = false;\n    }\n    IsFocused = true;\n    UI::FocusedMenu = this;\n  }\n}\nvoid MovieMenu::Hide() {\n  if (State != Hidden) {\n    State = Hiding;\n    FadeAnimation.StartOut();\n    MainItems->Hide();\n    if (LastFocusedMenu != 0) {\n      UI::FocusedMenu = LastFocusedMenu;\n      LastFocusedMenu->IsFocused = true;\n    } else {\n      UI::FocusedMenu = 0;\n    }\n    IsFocused = false;\n  }\n}\n\nvoid MovieMenu::UpdateInput(float dt) {\n  Menu::UpdateInput(dt);\n  if (State == Shown) {\n    MainItems->UpdateInput(dt);\n  }\n}\n\nvoid MovieMenu::Update(float dt) {\n  UpdateInput(dt);\n\n  FadeAnimation.Update(dt);\n  if (ScrWork[SW_MOVIEMODE_ALPHA] < 256 && State == Shown) {\n    Hide();\n  } else if (ScrWork[SW_MOVIEMODE_ALPHA] == 256 && State == Hidden) {\n    Show();\n  }\n\n  if (ScrWork[SW_MOVIEMODE_ALPHA] == 256 && FadeAnimation.IsIn())\n    State = Shown;\n  else if (FadeAnimation.IsOut()) {\n    State = Hidden;\n  }\n\n  if (State != Hidden) {\n    MainItems->Update(dt);\n  }\n}\n\nvoid MovieMenu::Render() {\n  if (State == Hidden) return;\n\n  glm::vec4 col(1.0f, 1.0f, 1.0f, FadeAnimation.Progress);\n  Renderer->DrawSprite(BackgroundSprite, glm::vec2(0.0f, 0.0f), col);\n  MainItems->Tint = col;\n  MainItems->Render();\n}\n\nvoid MovieMenu::UpdateMovieEntries() {\n  for (auto el : MainItems->Children) {\n    auto movieButton = static_cast<Widgets::MO6TW::ImageThumbnailButton*>(el);\n    if (movieButton->Id == 0 || movieButton->Id == 1)\n      movieButton->IsLocked = false;\n    else\n      movieButton->IsLocked = !GetFlag(SF_MOVIE_UNLOCK1 + movieButton->Id);\n  }\n}\n\n}  // namespace MO6TW\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/mo6tw/moviemenu.h",
    "content": "#pragma once\n\n#include \"../../ui/menu.h\"\n#include \"../../ui/widgets/group.h\"\n#include \"../../ui/widgets/button.h\"\n#include \"../../ui/widgets/mo6tw/imagethumbnailbutton.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace MO6TW {\n\nclass MovieMenu : public Menu {\n public:\n  MovieMenu();\n\n  void Show() override;\n  void Hide() override;\n  void UpdateInput(float dt) override;\n  void Update(float dt) override;\n  void Render() override;\n\n  void MovieButtonOnClick(Widgets::Button* target);\n\n private:\n  Widgets::Group* MainItems;\n\n  void UpdateMovieEntries();\n\n  Animation FadeAnimation;\n};\n\n}  // namespace MO6TW\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/mo6tw/musicmenu.cpp",
    "content": "#include \"musicmenu.h\"\n\n#include \"../../profile/ui/extramenus.h\"\n#include \"../../profile/games/mo6tw/musicmenu.h\"\n#include \"../../profile/ui/backlogmenu.h\"\n#include \"../../renderer/renderer.h\"\n#include \"../../mem.h\"\n#include \"../../profile/scriptvars.h\"\n#include \"../../data/savesystem.h\"\n#include \"../../audio/audiochannel.h\"\n#include \"../../audio/audiosystem.h\"\n#include \"../../vm/interface/input.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace MO6TW {\n\nusing namespace Impacto::Profile::MO6TW::MusicMenu;\nusing namespace Impacto::Profile::ScriptVars;\n\nusing namespace Impacto::UI::Widgets;\n\nusing namespace Impacto::Vm::Interface;\n\nvoid MusicMenu::MusicButtonOnClick(Button* target) {\n  if (target->IsLocked) return;\n\n  SwitchToTrack(target->Id);\n}\n\nMusicMenu::MusicMenu() {\n  FadeAnimation.Direction = AnimationDirection::In;\n  FadeAnimation.LoopMode = AnimationLoopMode::Stop;\n  FadeAnimation.DurationIn = FadeInDuration;\n  FadeAnimation.DurationOut = FadeOutDuration;\n\n  MainItems = new Group(this);\n  MainItems->WrapFocus = false;\n  BackgroundItems = new Group(this);\n  BackgroundItems->FocusLock = false;\n  Timer = new Group(this);\n  Timer->FocusLock = false;\n\n  BackgroundItems->Add(new Label(ItemsWindow, ItemsWindowPosition));\n  BackgroundItems->Add(new Label(PlaybackWindow, PlaybackWindowPosition));\n  NullSprite = Sprite();\n  NullSprite.Bounds = RectF(0.0f, 0.0f, 0.0f, 0.0f);\n  Thumbnail = new Label(NullSprite, ThumbnailPosition);\n  BackgroundItems->Add(Thumbnail);\n  CurrentlyPlaying = new Label(NullSprite, CurrentlyPlayingLabelPosition);\n  BackgroundItems->Add(CurrentlyPlaying);\n  PlaybackModeLabel =\n      new Label(PlaybackModeLabels[PlaybackMode], PlaybackModeLabelPosition);\n  BackgroundItems->Add(PlaybackModeLabel);\n\n  auto onClick = [this](auto* btn) { return MusicButtonOnClick(btn); };\n\n  MainItems->RenderingBounds = ItemsWindowRenderingBounds;\n  MainItems->HoverBounds = ItemsWindowRenderingBounds;\n  auto pos = MusicListInitialPosition;\n  for (int i = 0; i < MusicTrackCount; i++) {\n    auto button = new Button(i, ItemNames[i], ItemNames[i],\n                             Profile::BacklogMenu::EntryHighlight, pos);\n    button->HighlightOffset = ItemNameHighlightOffset;\n    button->LockedSprite = LockedItem;\n    button->OnClickHandler = onClick;\n    MainItems->Add(button, FDIR_DOWN);\n\n    pos += MusicListMargin;\n  }\n\n  MusicListY = ScrollbarStart;\n  float totalHeight = MusicListMargin.y * (MusicTrackCount + 1);\n  MainItems->Bounds =\n      RectF(MusicListInitialPosition.x, MusicListInitialPosition.y,\n            ItemNames[0].ScaledWidth(), totalHeight);\n  auto scrollbar = new Scrollbar(\n      0, ScrollbarPosition, ScrollbarStart,\n      ScrollbarStart - totalHeight + ItemsWindowRenderingBounds.Height,\n      &MusicListY, SBDIR_VERTICAL, ScrollbarTrack, ScrollbarThumb);\n  BackgroundItems->Add(scrollbar);\n\n  pos = TimerInitialPosition;\n  for (int i = 0; i < 8; i++) {\n    Timer->Add(new Label(NullSprite, pos));\n    pos += TimerMargin;\n  }\n}\n\nvoid MusicMenu::Show() {\n  if (State != Shown) {\n    State = Showing;\n    UpdateMusicEntries();\n    FadeAnimation.StartIn();\n    MainItems->Show();\n    BackgroundItems->Show();\n    if (UI::FocusedMenu != 0) {\n      LastFocusedMenu = UI::FocusedMenu;\n      LastFocusedMenu->IsFocused = false;\n    }\n    IsFocused = true;\n    UI::FocusedMenu = this;\n  }\n}\nvoid MusicMenu::Hide() {\n  if (State != Hidden) {\n    State = Hiding;\n    FadeAnimation.StartOut();\n    MainItems->Hide();\n    BackgroundItems->Hide();\n    Timer->Hide();\n    if (LastFocusedMenu != 0) {\n      UI::FocusedMenu = LastFocusedMenu;\n      LastFocusedMenu->IsFocused = true;\n    } else {\n      UI::FocusedMenu = 0;\n    }\n    IsFocused = false;\n  }\n}\n\nvoid MusicMenu::UpdateInput(float dt) {\n  Menu::UpdateInput(dt);\n  if (State == Shown) {\n    MainItems->UpdateInput(dt);\n    BackgroundItems->UpdateInput(dt);\n    if ((PADinputButtonWentDown & PAD1DOWN ||\n         PADinputButtonWentDown & PAD1UP) &&\n        MainItems->HasFocus) {\n      auto focusedEl = CurrentlyFocusedElement;\n      if (focusedEl->Bounds.Y < MainItems->RenderingBounds.Y) {\n        MusicListY += MusicListMargin.y;\n      } else if (focusedEl->Bounds.Y + focusedEl->Bounds.Height >\n                 MainItems->RenderingBounds.Y +\n                     MainItems->RenderingBounds.Height) {\n        MusicListY -= MusicListMargin.y;\n      }\n    }\n\n    if (PADinputButtonWentDown & PAD1Y) {\n      auto mode = (int)PlaybackMode + 1;\n      if (mode > 3) mode = 0;\n      PlaybackMode = (MusicPlaybackMode)mode;\n      if (PlaybackMode == MPM_RepeatOne) {\n        Audio::Channels[Audio::AC_BGM0]->SetLooping(true);\n      } else {\n        Audio::Channels[Audio::AC_BGM0]->SetLooping(false);\n      }\n      PlaybackModeLabel->SetSprite(PlaybackModeLabels[PlaybackMode]);\n    }\n\n    if (PADinputButtonWentDown & PAD1X) {\n      SwitchToTrack(-1);\n    }\n  }\n}\n\nvoid MusicMenu::Update(float dt) {\n  UpdateInput(dt);\n\n  FadeAnimation.Update(dt);\n  if (ScrWork[SW_MUSICMODE_ALPHA] < 256 && State == Shown) {\n    Hide();\n  } else if (ScrWork[SW_MUSICMODE_ALPHA] == 256 && State == Hidden) {\n    Show();\n  }\n\n  if (ScrWork[SW_MUSICMODE_ALPHA] == 256 && FadeAnimation.IsIn())\n    State = Shown;\n  else if (FadeAnimation.IsOut()) {\n    State = Hidden;\n  }\n\n  if (State != Hidden) {\n    BackgroundItems->Update(dt);\n    MainItems->Update(dt);\n    MainItems->MoveTo(glm::vec2(MainItems->Bounds.X, MusicListY));\n\n    UpdateMusicTimer();\n    if (CurrentlyPlayingTrackId != -1 &&\n        Audio::Channels[Audio::AC_BGM0]->GetState() == Audio::ACS_Stopped) {\n      int trackId;\n      if (PlaybackMode == MPM_One) {\n        trackId = -1;\n      } else {\n        trackId = GetNextTrackId(CurrentlyPlayingTrackId + 1);\n        if (trackId == MusicTrackCount) {\n          if (PlaybackMode == MPM_RepeatPlaylist) {\n            trackId = GetNextTrackId(0);\n          } else if (PlaybackMode == MPM_Playlist) {\n            trackId = -1;\n          }\n        }\n      }\n      SwitchToTrack(trackId);\n    }\n  }\n}\n\nvoid MusicMenu::Render() {\n  if (State == Hidden) return;\n\n  glm::vec4 col(1.0f, 1.0f, 1.0f, FadeAnimation.Progress);\n  Renderer->DrawSprite(BackgroundSprite, glm::vec2(0.0f, 0.0f), col);\n  BackgroundItems->Tint = col;\n  BackgroundItems->Render();\n  MainItems->Tint = col;\n  MainItems->Render();\n  Timer->Tint.a *= col.a;\n  Timer->Render();\n}\n\nvoid MusicMenu::UpdateMusicEntries() {\n  for (auto el : MainItems->Children) {\n    auto button = static_cast<Button*>(el);\n    button->IsLocked = !SaveSystem::GetBgmFlag(Playlist[button->Id]);\n  }\n}\n\nvoid MusicMenu::UpdateMusicTimer() {\n  if (Audio::Channels[Audio::AC_BGM0]->GetState() == Audio::ACS_Stopped) {\n    Timer->Hide();\n  } else {\n    int position = (int)Audio::Channels[Audio::AC_BGM0]->PositionInSeconds();\n    auto seconds = position % 3600 % 60;\n    auto minutes = position % 3600 / 60;\n    auto hours = position / 3600;\n    std::string time =\n        fmt::format(\"{:02d}:{:02d}:{:02d}\", hours, minutes, seconds);\n    for (int i = 0; i < 8; i++) {\n      auto label = static_cast<Label*>(Timer->Children.at(i));\n      label->SetSprite(time[i] == ':' ? TimerChars[TimerCharCount - 1]\n                                      : TimerChars[time[i] - '0']);\n    }\n  }\n}\n\nvoid MusicMenu::SwitchToTrack(int id) {\n  CurrentlyPlayingTrackId = id;\n  if (id == -1) {\n    Audio::Channels[Audio::AC_BGM0]->Stop(0.5f);\n    Thumbnail->SetSprite(NullSprite);\n    CurrentlyPlaying->SetSprite(NullSprite);\n    Timer->Hide();\n    return;\n  }\n\n  Audio::Channels[Audio::AC_BGM0]->Play(\"bgm\", Playlist[id],\n                                        PlaybackMode == MPM_RepeatOne, 0.5f);\n  Thumbnail->SetSprite(Thumbnails[id]);\n  CurrentlyPlaying->SetSprite(ItemNames[id]);\n  Timer->Show();\n}\n\ninline int MusicMenu::GetNextTrackId(int id) {\n  while (!SaveSystem::GetBgmFlag(Playlist[id])) {\n    id += 1;\n    if (id == MusicTrackCount) {\n      if (PlaybackMode == MPM_RepeatPlaylist) {\n        id = 0;\n      } else if (PlaybackMode == MPM_Playlist) {\n        id = -1;\n        break;\n      }\n    }\n  }\n  return id;\n}\n\n}  // namespace MO6TW\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/mo6tw/musicmenu.h",
    "content": "#pragma once\n\n#include \"../../ui/menu.h\"\n#include \"../../ui/widgets/group.h\"\n#include \"../../ui/widgets/button.h\"\n#include \"../../ui/widgets/label.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace MO6TW {\n\nenum MusicPlaybackMode {\n  MPM_One,\n  MPM_Playlist,\n  MPM_RepeatOne,\n  MPM_RepeatPlaylist\n};\n\nclass MusicMenu : public Menu {\n public:\n  MusicMenu();\n\n  void Show() override;\n  void Hide() override;\n  void UpdateInput(float dt) override;\n  void Update(float dt) override;\n  void Render() override;\n\n  void MusicButtonOnClick(Widgets::Button* target);\n\n private:\n  void UpdateMusicEntries();\n  void UpdateMusicTimer();\n  void SwitchToTrack(int id);\n  int GetNextTrackId(int id);\n\n  Widgets::Group* MainItems;\n  Widgets::Group* BackgroundItems;\n  Widgets::Group* Timer;\n  Widgets::Label* Thumbnail;\n  Widgets::Label* CurrentlyPlaying;\n  Widgets::Label* PlaybackModeLabel;\n  Animation FadeAnimation;\n  Sprite NullSprite;\n\n  float MusicListY;\n  MusicPlaybackMode PlaybackMode = MPM_One;\n  int CurrentlyPlayingTrackId = -1;\n};\n\n}  // namespace MO6TW\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/mo6tw/optionsmenu.cpp",
    "content": "#include \"optionsmenu.h\"\n\n#include \"../../profile/ui/optionsmenu.h\"\n#include \"../../profile/games/mo6tw/optionsmenu.h\"\n#include \"../../renderer/renderer.h\"\n#include \"../../mem.h\"\n#include \"../../profile/scriptvars.h\"\n#include \"../../vm/interface/input.h\"\n#include \"../../audio/audiosystem.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace MO6TW {\n\nusing namespace Impacto::Profile::OptionsMenu;\nusing namespace Impacto::Profile::MO6TW::OptionsMenu;\nusing namespace Impacto::Profile::ScriptVars;\nusing namespace Impacto::Vm::Interface;\n\nvoid OptionsMenu::MessageSpeedToggleOnClick(Widgets::Toggle* target) {\n  if (*target->Value) {\n    memset(MessageSpeedValues, false, sizeof(MessageSpeedValues));\n    MessageSpeedValues[target->Id] = true;\n  } else {\n    *target->Value = true;\n  }\n}\n\nvoid OptionsMenu::AutoModeWaitTimeOnClick(Widgets::Toggle* target) {\n  if (*target->Value) {\n    memset(AutoModeWaitTimeValues, false, sizeof(AutoModeWaitTimeValues));\n    AutoModeWaitTimeValues[target->Id] = true;\n  } else {\n    *target->Value = true;\n  }\n}\n\nvoid OptionsMenu::SyncTextSpeedToVoiceOnClick(Widgets::Toggle* target) {\n  if (*target->Value) {\n    memset(SyncTextSpeedToVoiceValues, false,\n           sizeof(SyncTextSpeedToVoiceValues));\n    SyncTextSpeedToVoiceValues[target->Id] = true;\n  } else {\n    *target->Value = true;\n  }\n}\n\nvoid OptionsMenu::SkipVoiceAtNextLineOnClick(Widgets::Toggle* target) {\n  if (*target->Value) {\n    memset(SkipVoiceAtNextLineValues, false, sizeof(SkipVoiceAtNextLineValues));\n    SkipVoiceAtNextLineValues[target->Id] = true;\n  } else {\n    *target->Value = true;\n  }\n}\n\nvoid OptionsMenu::SkipModeOnClick(Widgets::Toggle* target) {\n  if (*target->Value) {\n    memset(SkipModeValues, false, sizeof(SkipModeValues));\n    SkipModeValues[target->Id] = true;\n  } else {\n    *target->Value = true;\n  }\n}\n\nvoid OptionsMenu::AutoSaveTriggerOnClick(Widgets::Toggle* target) {\n  if (*target->Value) {\n    memset(AutoSaveTriggerValues, false, sizeof(AutoSaveTriggerValues));\n    AutoSaveTriggerValues[target->Id] = true;\n  } else {\n    *target->Value = true;\n  }\n}\n\nvoid OptionsMenu::TipsNotificationsOnClick(Widgets::Toggle* target) {\n  if (*target->Value) {\n    memset(TipsNotificationsValues, false, sizeof(TipsNotificationsValues));\n    TipsNotificationsValues[target->Id] = true;\n  } else {\n    *target->Value = true;\n  }\n}\n\nOptionsMenu::OptionsMenu() : UI::OptionsMenu() {\n  // First page\n  auto firstPage = std::make_unique<Widgets::Group>(this);\n  CharacterVoiceToggles = new Widgets::Group(this);\n  CharacterVoiceToggles->FocusLock = false;\n  CharacterVoiceToggles->WrapFocus = false;\n\n  VoiceVolumeSlider = new Widgets::Scrollbar(\n      0, FirstPageSliderPos, 0.0f, 1.0f, &Audio::GroupVolumes[Audio::ACG_Voice],\n      Widgets::SBDIR_HORIZONTAL, SliderTrackSprite, SliderThumbSprite,\n      SliderFillSprite, SliderThumbOffset);\n  firstPage->Add(VoiceVolumeSlider, FDIR_DOWN);\n  FirstPageSliderPos.y += FirstPageSliderMargin;\n\n  BGMVolumeSlider = new Widgets::Scrollbar(\n      0, FirstPageSliderPos, 0.0f, 1.0f, &Audio::GroupVolumes[Audio::ACG_BGM],\n      Widgets::SBDIR_HORIZONTAL, SliderTrackSprite, SliderThumbSprite,\n      SliderFillSprite, SliderThumbOffset);\n  firstPage->Add(BGMVolumeSlider, FDIR_DOWN);\n  FirstPageSliderPos.y += FirstPageSliderMargin;\n\n  SEVolumeSlider = new Widgets::Scrollbar(\n      0, FirstPageSliderPos, 0.0f, 1.0f, &Audio::GroupVolumes[Audio::ACG_SE],\n      Widgets::SBDIR_HORIZONTAL, SliderTrackSprite, SliderThumbSprite,\n      SliderFillSprite, SliderThumbOffset);\n  firstPage->Add(SEVolumeSlider, FDIR_DOWN);\n  FirstPageSliderPos.y += FirstPageSliderMargin;\n\n  MovieVolumeSlider = new Widgets::Scrollbar(\n      0, FirstPageSliderPos, 0.0f, 1.0f, &Audio::GroupVolumes[Audio::ACG_Movie],\n      Widgets::SBDIR_HORIZONTAL, SliderTrackSprite, SliderThumbSprite,\n      SliderFillSprite, SliderThumbOffset);\n  firstPage->Add(MovieVolumeSlider, FDIR_DOWN);\n\n  auto pos = VoiceToggleStart;\n  int row = 1;\n  int totalRows = VoiceToggleCount / VoiceTogglePerLine +\n                  (VoiceToggleCount % VoiceTogglePerLine != 0);\n  for (int i = 0; i < VoiceToggleCount - 1; i++) {\n    auto toggle = new Widgets::Toggle(\n        1, &VoiceTest[i], VoiceToggleEnabledSprites[i],\n        VoiceToggleDisabledSprites[i], VoiceToggleHighlightSprite, pos, false);\n    toggle->Enabled = true;\n    CharacterVoiceToggles->Add(toggle, FDIR_RIGHT);\n    if (row == 1) {\n      toggle->SetFocus(MovieVolumeSlider, FDIR_UP);\n    } else {\n      toggle->SetFocus(\n          CharacterVoiceToggles->Children.at(i - VoiceTogglePerLine), FDIR_UP);\n      if (row == totalRows) {\n        toggle->SetFocus(VoiceVolumeSlider, FDIR_DOWN);\n      }\n    }\n\n    if ((i + 1) % VoiceTogglePerLine == 0) {\n      pos.x = VoiceToggleStart.x;\n      pos.y += VoiceTogglePadding.y;\n      row += 1;\n    } else {\n      pos.x += VoiceTogglePadding.x;\n    }\n  }\n  row = 1;\n  size_t idx = 0;\n  for (const auto& el : CharacterVoiceToggles->Children) {\n    if (row != totalRows) {\n      Widget* focusTarget;\n      if ((idx + VoiceTogglePerLine) >\n          CharacterVoiceToggles->Children.size() - 1)\n        focusTarget = CharacterVoiceToggles->Children.back();\n      else\n        focusTarget =\n            CharacterVoiceToggles->Children.at(idx + VoiceTogglePerLine);\n      el->SetFocus(focusTarget, FDIR_DOWN);\n    }\n\n    idx += 1;\n    if (idx % VoiceTogglePerLine == 0) {\n      row += 1;\n    }\n  }\n  firstPage->Add(CharacterVoiceToggles, FDIR_DOWN);\n\n  Pages.push_back(std::move(firstPage));\n\n  // Second page\n  auto secondPage = std::make_unique<Widgets::Group>(this);\n  int checkboxLabelIdx = 0;\n\n  MessageSpeedToggles = new Widgets::Group(this);\n  MessageSpeedToggles->FocusLock = false;\n  AutoModeWaitTimeToggles = new Widgets::Group(this);\n  AutoModeWaitTimeToggles->FocusLock = false;\n  SyncTextSpeedToVoiceToggles = new Widgets::Group(this);\n  SyncTextSpeedToVoiceToggles->FocusLock = false;\n  SkipVoiceAtNextLineToggles = new Widgets::Group(this);\n  SkipVoiceAtNextLineToggles->FocusLock = false;\n  SkipModeToggles = new Widgets::Group(this);\n  SkipModeToggles->FocusLock = false;\n  AutoSaveTriggerToggles = new Widgets::Group(this);\n  AutoSaveTriggerToggles->FocusLock = false;\n  TipsNotificationsToggles = new Widgets::Group(this);\n  TipsNotificationsToggles->FocusLock = false;\n\n  auto messageSpeedOnClick = [this](auto* btn) {\n    return MessageSpeedToggleOnClick(btn);\n  };\n  for (int i = 0; i < 4; i++) {\n    auto toggle = new Widgets::Toggle(\n        i, &MessageSpeedValues[i], CheckboxTickSprite, CheckboxBoxSprite,\n        CheckboxTickSprite,\n        glm::vec2(CheckboxFirstPos.x + (i * CheckboxFirstSectionPaddingX),\n                  CheckboxFirstPos.y),\n        true, CheckboxLabelSprites[checkboxLabelIdx++], CheckboxLabelOffset);\n    toggle->Enabled = true;\n    toggle->OnClickHandler = messageSpeedOnClick;\n    toggle->SetFocus(AutoModeWaitTimeToggles, FDIR_DOWN);\n    toggle->SetFocus(TipsNotificationsToggles, FDIR_UP);\n    MessageSpeedToggles->Add(toggle, FDIR_RIGHT);\n  }\n  secondPage->Add(MessageSpeedToggles, FDIR_DOWN);\n\n  CheckboxFirstPos += CheckboxMargin;\n  auto autoModeWaitTimeOnClick = [this](auto* btn) {\n    return AutoModeWaitTimeOnClick(btn);\n  };\n  for (int i = 0; i < 3; i++) {\n    auto toggle = new Widgets::Toggle(\n        i, &AutoModeWaitTimeValues[i], CheckboxTickSprite, CheckboxBoxSprite,\n        CheckboxTickSprite,\n        glm::vec2(CheckboxFirstPos.x + (i * CheckboxFirstSectionPaddingX),\n                  CheckboxFirstPos.y),\n        true, CheckboxLabelSprites[checkboxLabelIdx++], CheckboxLabelOffset);\n    toggle->Enabled = true;\n    toggle->OnClickHandler = autoModeWaitTimeOnClick;\n    toggle->SetFocus(SyncTextSpeedToVoiceToggles, FDIR_DOWN);\n    toggle->SetFocus(MessageSpeedToggles, FDIR_UP);\n    AutoModeWaitTimeToggles->Add(toggle, FDIR_RIGHT);\n  }\n  secondPage->Add(AutoModeWaitTimeToggles, FDIR_DOWN);\n\n  CheckboxFirstPos += CheckboxMargin;\n  auto syncTextSpeedToVoiceOnClick = [this](auto* btn) {\n    return SyncTextSpeedToVoiceOnClick(btn);\n  };\n  for (int i = 0; i < 2; i++) {\n    auto toggle = new Widgets::Toggle(\n        i, &SyncTextSpeedToVoiceValues[i], CheckboxTickSprite,\n        CheckboxBoxSprite, CheckboxTickSprite,\n        glm::vec2(CheckboxFirstPos.x + (i * CheckboxFirstSectionPaddingX),\n                  CheckboxFirstPos.y),\n        true, CheckboxLabelSprites[checkboxLabelIdx++], CheckboxLabelOffset);\n    toggle->Enabled = true;\n    toggle->OnClickHandler = syncTextSpeedToVoiceOnClick;\n    toggle->SetFocus(SkipVoiceAtNextLineToggles, FDIR_DOWN);\n    toggle->SetFocus(AutoModeWaitTimeToggles, FDIR_UP);\n    SyncTextSpeedToVoiceToggles->Add(toggle, FDIR_RIGHT);\n  }\n  checkboxLabelIdx -= 2;\n  secondPage->Add(SyncTextSpeedToVoiceToggles, FDIR_DOWN);\n\n  CheckboxFirstPos += CheckboxMargin;\n  auto skipVoiceAtNextLineOnClick = [this](auto* btn) {\n    return SkipVoiceAtNextLineOnClick(btn);\n  };\n  for (int i = 0; i < 2; i++) {\n    auto toggle = new Widgets::Toggle(\n        i, &SkipVoiceAtNextLineValues[i], CheckboxTickSprite, CheckboxBoxSprite,\n        CheckboxTickSprite,\n        glm::vec2(CheckboxFirstPos.x + (i * CheckboxFirstSectionPaddingX),\n                  CheckboxFirstPos.y),\n        true, CheckboxLabelSprites[checkboxLabelIdx++], CheckboxLabelOffset);\n    toggle->Enabled = true;\n    toggle->OnClickHandler = skipVoiceAtNextLineOnClick;\n    toggle->SetFocus(SkipModeToggles, FDIR_DOWN);\n    toggle->SetFocus(SyncTextSpeedToVoiceToggles, FDIR_UP);\n    SkipVoiceAtNextLineToggles->Add(toggle, FDIR_RIGHT);\n  }\n  secondPage->Add(SkipVoiceAtNextLineToggles, FDIR_DOWN);\n\n  auto skipModeOnClick = [this](auto* btn) { return SkipModeOnClick(btn); };\n  for (int i = 0; i < 2; i++) {\n    auto toggle = new Widgets::Toggle(\n        i, &SkipModeValues[i], CheckboxTickSprite, CheckboxBoxSprite,\n        CheckboxTickSprite,\n        glm::vec2(\n            CheckboxSecondPos.x + (i * CheckboxSecondSectionFirstPaddingX),\n            CheckboxSecondPos.y),\n        true, CheckboxLabelSprites[checkboxLabelIdx++], CheckboxLabelOffset);\n    toggle->Enabled = true;\n    toggle->OnClickHandler = skipModeOnClick;\n    toggle->SetFocus(AutoSaveTriggerToggles, FDIR_DOWN);\n    toggle->SetFocus(SkipVoiceAtNextLineToggles, FDIR_UP);\n    SkipModeToggles->Add(toggle, FDIR_RIGHT);\n  }\n  secondPage->Add(SkipModeToggles, FDIR_DOWN);\n\n  float dummy = 0.0f;\n  ScreenSizeSlider = new Widgets::Scrollbar(\n      0, ScreenSizeSliderPos, 0.0f, 1.0f, &dummy, Widgets::SBDIR_HORIZONTAL,\n      SliderTrackSprite, SliderThumbSprite, SliderFillSprite,\n      SliderThumbOffset);\n\n  CheckboxSecondPos += CheckboxMargin;\n  auto autoSaveTriggerOnClick = [this](auto* btn) {\n    return AutoSaveTriggerOnClick(btn);\n  };\n  for (int i = 0; i < 4; i++) {\n    auto toggle = new Widgets::Toggle(\n        i, &AutoSaveTriggerValues[i], CheckboxTickSprite, CheckboxBoxSprite,\n        CheckboxTickSprite,\n        glm::vec2(AutoSaveTriggerXPos[i], CheckboxSecondPos.y), true,\n        CheckboxLabelSprites[i == 3 ? 8 : checkboxLabelIdx++],\n        CheckboxLabelOffset);\n    toggle->Enabled = true;\n    toggle->OnClickHandler = autoSaveTriggerOnClick;\n    toggle->SetFocus(ScreenSizeSlider, FDIR_DOWN);\n    toggle->SetFocus(SkipModeToggles, FDIR_UP);\n    AutoSaveTriggerToggles->Add(toggle, FDIR_RIGHT);\n  }\n  secondPage->Add(AutoSaveTriggerToggles, FDIR_DOWN);\n\n  ScreenSizeSlider->SetFocus(TipsNotificationsToggles, FDIR_DOWN);\n  secondPage->Add(ScreenSizeSlider, FDIR_DOWN);\n\n  auto tipsNotificationsOnClick = [this](auto* btn) {\n    return TipsNotificationsOnClick(btn);\n  };\n  checkboxLabelIdx = 7;\n  for (int i = 0; i < 2; i++) {\n    auto toggle = new Widgets::Toggle(\n        i, &TipsNotificationsValues[i], CheckboxTickSprite, CheckboxBoxSprite,\n        CheckboxTickSprite,\n        glm::vec2(TipsPos.x + (i * CheckboxFirstSectionPaddingX), TipsPos.y),\n        true, CheckboxLabelSprites[checkboxLabelIdx++], CheckboxLabelOffset);\n    toggle->Enabled = true;\n    toggle->OnClickHandler = tipsNotificationsOnClick;\n    toggle->SetFocus(MessageSpeedToggles, FDIR_DOWN);\n    toggle->SetFocus(ScreenSizeSlider, FDIR_UP);\n    TipsNotificationsToggles->Add(toggle, FDIR_RIGHT);\n  }\n  secondPage->Add(TipsNotificationsToggles, FDIR_DOWN);\n\n  Pages.push_back(std::move(secondPage));\n}\n\nvoid OptionsMenu::UpdateVisibility() {\n  if (ScrWork[SW_OPTIONALPHA] < 256 && State == Shown) {\n    Hide();\n  } else if (ScrWork[SW_OPTIONALPHA] == 256 && State == Hidden) {\n    Show();\n  }\n\n  if (ScrWork[SW_OPTIONALPHA] == 256 && FadeAnimation.IsIn())\n    State = Shown;\n  else if (ScrWork[SW_OPTIONALPHA] == 0 && FadeAnimation.IsOut())\n    State = Hidden;\n}\n\nvoid OptionsMenu::Update(float dt) {\n  UI::OptionsMenu::Update(dt);\n\n  if (State == Shown) {\n    if (PADinputButtonWentDown & PAD1X)\n      GoToPage((CurrentPage + 1) % Pages.size());\n  }\n\n  if (GetControlState(CT_Back)) {\n    SetFlag(SF_SUBMENUEXIT, true);\n  }\n}\n\nvoid OptionsMenu::Render() {\n  if (State == Hidden) return;\n\n  glm::vec4 col(1.0f, 1.0f, 1.0f,\n                glm::smoothstep(0.0f, 1.0f, FadeAnimation.Progress));\n  Renderer->DrawSprite(BackgroundSprite, glm::vec2(0.0f, 0.0f), col);\n\n  switch (CurrentPage) {\n    case 0: {\n      auto pos = FirstPageSectionHeaderPos;\n      Renderer->DrawSprite(\n          SectionHeaderSprites[0 + VoiceVolumeSlider->HasFocus], pos, col);\n      pos.y += FirstPageSliderMargin;\n      Renderer->DrawSprite(SectionHeaderSprites[2 + BGMVolumeSlider->HasFocus],\n                           pos, col);\n      pos.y += FirstPageSliderMargin;\n      Renderer->DrawSprite(SectionHeaderSprites[4 + SEVolumeSlider->HasFocus],\n                           pos, col);\n      pos.y += FirstPageSliderMargin;\n      Renderer->DrawSprite(\n          SectionHeaderSprites[6 + MovieVolumeSlider->HasFocus], pos, col);\n      pos.y += FirstPageSliderMargin;\n      Renderer->DrawSprite(\n          SectionHeaderSprites[8 + CharacterVoiceToggles->HasFocus], pos, col);\n\n      break;\n    }\n    case 1: {\n      auto pos = SecondPageSectionHeaderPos;\n      Renderer->DrawSprite(\n          SectionHeaderSprites[10 + MessageSpeedToggles->HasFocus], pos, col);\n      pos += CheckboxMargin;\n      Renderer->DrawSprite(\n          SectionHeaderSprites[12 + AutoModeWaitTimeToggles->HasFocus], pos,\n          col);\n      pos += CheckboxMargin;\n      Renderer->DrawSprite(\n          SectionHeaderSprites[14 + SyncTextSpeedToVoiceToggles->HasFocus], pos,\n          col);\n      pos += CheckboxMargin;\n      Renderer->DrawSprite(\n          SectionHeaderSprites[16 + SkipVoiceAtNextLineToggles->HasFocus], pos,\n          col);\n      pos += CheckboxMargin;\n      pos.x = SecondPageSectionHeaderPos.x;\n      Renderer->DrawSprite(SectionHeaderSprites[18 + SkipModeToggles->HasFocus],\n                           pos, col);\n      pos += CheckboxMargin;\n      Renderer->DrawSprite(\n          SectionHeaderSprites[20 + AutoSaveTriggerToggles->HasFocus], pos,\n          col);\n      pos += CheckboxMargin;\n      Renderer->DrawSprite(\n          SectionHeaderSprites[22 + ScreenSizeSlider->HasFocus], pos, col);\n      pos += CheckboxMargin;\n      Renderer->DrawSprite(\n          SectionHeaderSprites[24 + TipsNotificationsToggles->HasFocus], pos,\n          col);\n\n      break;\n    }\n  }\n\n  Pages[CurrentPage]->Tint = col;\n  Pages[CurrentPage]->Render();\n}\n\n}  // namespace MO6TW\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/mo6tw/optionsmenu.h",
    "content": "#pragma once\n\n#include \"../../ui/optionsmenu.h\"\n#include \"../../ui/widgets/group.h\"\n#include \"../../ui/widgets/button.h\"\n#include \"../../ui/widgets/scrollbar.h\"\n#include \"../../ui/widgets/toggle.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace MO6TW {\n\nclass OptionsMenu : public UI::OptionsMenu {\n public:\n  OptionsMenu();\n\n  void Update(float dt) override;\n  void Render() override;\n\n  void MessageSpeedToggleOnClick(Widgets::Toggle* target);\n  void AutoModeWaitTimeOnClick(Widgets::Toggle* target);\n  void SyncTextSpeedToVoiceOnClick(Widgets::Toggle* target);\n  void SkipVoiceAtNextLineOnClick(Widgets::Toggle* target);\n  void SkipModeOnClick(Widgets::Toggle* target);\n  void AutoSaveTriggerOnClick(Widgets::Toggle* target);\n  void TipsNotificationsOnClick(Widgets::Toggle* target);\n\n private:\n  void UpdateVisibility() override;\n\n  Widgets::Scrollbar* VoiceVolumeSlider;\n  Widgets::Scrollbar* BGMVolumeSlider;\n  Widgets::Scrollbar* SEVolumeSlider;\n  Widgets::Scrollbar* MovieVolumeSlider;\n  Widgets::Group* CharacterVoiceToggles;\n  bool VoiceTest[13] = {0};\n\n  Widgets::Scrollbar* ScreenSizeSlider;\n  Widgets::Group* MessageSpeedToggles;\n  bool MessageSpeedValues[4] = {false};\n  Widgets::Group* AutoModeWaitTimeToggles;\n  bool AutoModeWaitTimeValues[3] = {false};\n  Widgets::Group* SyncTextSpeedToVoiceToggles;\n  bool SyncTextSpeedToVoiceValues[2] = {false};\n  Widgets::Group* SkipVoiceAtNextLineToggles;\n  bool SkipVoiceAtNextLineValues[2] = {false};\n  Widgets::Group* SkipModeToggles;\n  bool SkipModeValues[2] = {false};\n  Widgets::Group* AutoSaveTriggerToggles;\n  bool AutoSaveTriggerValues[4] = {false};\n  Widgets::Group* TipsNotificationsToggles;\n  bool TipsNotificationsValues[2] = {false};\n};\n\n}  // namespace MO6TW\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/mo6tw/savemenu.cpp",
    "content": "#include \"savemenu.h\"\n\n#include \"../../profile/ui/savemenu.h\"\n#include \"../../profile/games/mo6tw/savemenu.h\"\n#include \"../../renderer/renderer.h\"\n#include \"../../mem.h\"\n#include \"../../profile/scriptvars.h\"\n#include \"../../inputsystem.h\"\n#include \"../../ui/widgets/button.h\"\n#include \"../../ui/widgets/mo6tw/saveentrybutton.h\"\n#include \"../../data/savesystem.h\"\n#include \"../../vm/vm.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace MO6TW {\n\nusing namespace Impacto::Profile::SaveMenu;\nusing namespace Impacto::Profile::MO6TW::SaveMenu;\nusing namespace Impacto::Profile::ScriptVars;\n\nusing namespace Impacto::UI::Widgets::MO6TW;\n\nWidget* EntryGrid[RowsPerPage][EntriesPerRow];\n\nvoid SaveMenu::MenuButtonOnClick(Widgets::Button* target) {\n  target->Hovered = false;\n  const SaveSystem::SaveType saveType =\n      *ActiveMenuType == SaveMenuPageType::QuickLoad\n          ? SaveSystem::SaveType::Quick\n          : SaveSystem::SaveType::Full;\n\n  if ((SaveSystem::GetSaveStatus(saveType, target->Id) != 0) ||\n      *ActiveMenuType == SaveMenuPageType::Save) {\n    ScrWork[SW_SAVEFILENO] = target->Id;\n    ScrWork[SW_SAVEFILESTATUS] =\n        SaveSystem::GetSaveStatus(saveType, ScrWork[SW_SAVEFILENO]);\n    ChoiceMade = true;\n  }\n}\n\nSaveMenu::SaveMenu() : UI::SaveMenu() {\n  FadeAnimation.Direction = AnimationDirection::In;\n  FadeAnimation.LoopMode = AnimationLoopMode::Stop;\n  FadeAnimation.DurationIn = FadeInDuration;\n  FadeAnimation.DurationOut = FadeOutDuration;\n}\n\nvoid SaveMenu::Show() {\n  if (State != Shown) {\n    MainItems = new Widgets::Group(this);\n    MainItems->WrapFocus = false;\n\n    auto onClick = [this](auto* btn) { return MenuButtonOnClick(btn); };\n\n    Sprite entrySprite;\n    Sprite entryHSprite;\n    Sprite nullSprite = Sprite();\n    nullSprite.Bounds = RectF(0.0f, 0.0f, 0.0f, 0.0f);\n\n    switch (*ActiveMenuType) {\n      case SaveMenuPageType::QuickLoad:\n        entrySprite = QuickLoadEntrySprite;\n        entryHSprite = QuickLoadEntryHighlightedSprite;\n        break;\n      case SaveMenuPageType::Save:\n        entrySprite = SaveEntrySprite;\n        entryHSprite = SaveEntryHighlightedSprite;\n        break;\n      case SaveMenuPageType::Load:\n        entrySprite = LoadEntrySprite;\n        entryHSprite = LoadEntryHighlightedSprite;\n        break;\n    }\n    int id = 0;\n    for (int i = 0; i < RowsPerPage; i++) {\n      for (int j = 0; j < EntriesPerRow; j++) {\n        SaveEntryButton* saveEntryButton =\n            new SaveEntryButton(id, entrySprite, entryHSprite, nullSprite,\n                                glm::vec2(EntryStartX + (j * EntryXPadding),\n                                          EntryStartY + (i * EntryYPadding)));\n\n        saveEntryButton->OnClickHandler = onClick;\n        saveEntryButton->DisabledSprite = entrySprite;\n        if (SaveSystem::GetSaveStatus(SaveSystem::SaveType::Full, id) != 0) {\n          saveEntryButton->EntryActive = true;\n          saveEntryButton->AddSceneTitleText(\n              Vm::ScriptGetTextTableStrAddress(\n                  1, SaveSystem::GetSaveTitle(SaveSystem::SaveType::Full, id)),\n              20, true);\n          saveEntryButton->AddPlayTimeHintText(\n              Vm::ScriptGetTextTableStrAddress(0, 2), 16, true);\n          saveEntryButton->AddPlayTimeText(\n              Vm::ScriptGetTextTableStrAddress(0, 15), 16, true);\n          saveEntryButton->AddSaveDateHintText(\n              Vm::ScriptGetTextTableStrAddress(0, 3), 16, true);\n          saveEntryButton->AddSaveDateText(\n              Vm::ScriptGetTextTableStrAddress(0, 14), 16, true);\n        } else {\n          saveEntryButton->AddSceneTitleText(\n              Vm::ScriptGetTextTableStrAddress(0, 1), 24, true);\n        }\n        saveEntryButton->Thumbnail = EmptyThumbnailSprite;\n        id++;\n        if (j == EntriesPerRow - 1) {\n          MainItems->Add(saveEntryButton, FDIR_RIGHT);\n        } else {\n          MainItems->Add(saveEntryButton);\n        }\n        EntryGrid[i][j] = saveEntryButton;\n      }\n    }\n    for (int j = 0; j < EntriesPerRow; j++) {\n      for (int i = 0; i < RowsPerPage; i++) {\n        if (i != RowsPerPage - 1) {\n          EntryGrid[i][j]->SetFocus(EntryGrid[i + 1][j], FDIR_DOWN);\n          EntryGrid[i + 1][j]->SetFocus(EntryGrid[i][j], FDIR_UP);\n        }\n      }\n    }\n\n    State = Showing;\n    FadeAnimation.StartIn();\n    MainItems->Show();\n    CurrentlyFocusedElement = EntryGrid[0][0];\n    EntryGrid[0][0]->HasFocus = true;\n    if (UI::FocusedMenu != 0) {\n      LastFocusedMenu = UI::FocusedMenu;\n      LastFocusedMenu->IsFocused = false;\n    }\n    IsFocused = true;\n    UI::FocusedMenu = this;\n  }\n}\nvoid SaveMenu::Hide() {\n  if (State != Hidden) {\n    State = Hiding;\n    FadeAnimation.StartOut();\n    MainItems->Hide();\n    if (LastFocusedMenu != 0) {\n      UI::FocusedMenu = LastFocusedMenu;\n      LastFocusedMenu->IsFocused = true;\n    } else {\n      UI::FocusedMenu = 0;\n    }\n    IsFocused = false;\n  }\n}\n\nvoid SaveMenu::Update(float dt) {\n  UpdateInput(dt);\n\n  FadeAnimation.Update(dt);\n  if (ScrWork[SW_FILEALPHA] < 256 && State == Shown) {\n    Hide();\n  } else if (ScrWork[SW_FILEALPHA] == 256 && State == Hidden) {\n    Show();\n  }\n\n  if (ScrWork[SW_FILEALPHA] == 256 && FadeAnimation.IsIn())\n    State = Shown;\n  else if (ScrWork[SW_FILEALPHA] == 0 && FadeAnimation.IsOut())\n    State = Hidden;\n\n  if (State == Shown && IsFocused) {\n    MainItems->Update(dt);\n    MainItems->UpdateInput(dt);\n  }\n}\n\nvoid SaveMenu::Render() {\n  if (State != Hidden && ActiveMenuType) {\n    glm::vec4 col(1.0f, 1.0f, 1.0f, FadeAnimation.Progress);\n    Renderer->DrawSprite(SaveMenuBackgroundSprite, glm::vec2(0.0f), col);\n    switch (*ActiveMenuType) {\n      case SaveMenuPageType::QuickLoad:\n        Renderer->DrawSprite(QuickLoadTextSprite, MenuTitleTextPos, col);\n        break;\n      case SaveMenuPageType::Save:\n        Renderer->DrawSprite(SaveTextSprite, MenuTitleTextPos, col);\n        break;\n      case SaveMenuPageType::Load:\n        Renderer->DrawSprite(LoadTextSprite, MenuTitleTextPos, col);\n        break;\n    }\n    MainItems->Tint = col;\n    MainItems->Render();\n  }\n}\n\n}  // namespace MO6TW\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/mo6tw/savemenu.h",
    "content": "#pragma once\n\n#include \"../../ui/menu.h\"\n#include \"../../ui/savemenu.h\"\n#include \"../../ui/widgets/group.h\"\n#include \"../../ui/widgets/button.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace MO6TW {\n\nclass SaveMenu : public UI::SaveMenu {\n public:\n  SaveMenu();\n\n  void Show();\n  void Hide();\n  void Update(float dt);\n  void Render();\n\n  void RefreshCurrentEntryInfo() {}  // Todo\n\n  void MenuButtonOnClick(Widgets::Button* target);\n\n private:\n  Widgets::Group* MainItems;\n  Animation FadeAnimation;\n};\n\n}  // namespace MO6TW\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/mo6tw/savesystem.cpp",
    "content": "#include \"savesystem.h\"\n\n#include <time.h>\n#include \"../../io/memorystream.h\"\n#include \"../../io/physicalfilestream.h\"\n#include \"../../mem.h\"\n#include \"../../vm/vm.h\"\n#include \"../../profile/data/savesystem.h\"\n#include \"../../profile/scriptvars.h\"\n#include \"../../renderer/renderer.h\"\n\nnamespace Impacto {\nnamespace MO6TW {\n\nusing namespace Impacto::Vm;\nusing namespace Impacto::Profile::SaveSystem;\nusing namespace Impacto::Profile::ScriptVars;\n\nSaveFileEntry* WorkingSaveEntry = 0;\n\nSaveError SaveSystem::LoadSystemData() {\n  Io::MemoryStream stream =\n      Io::MemoryStream(SystemData.data(), SystemData.size(), false);\n\n  stream.Seek(0x14, SEEK_SET);\n\n  Io::ReadArrayLE<uint8_t>(&FlagWork[100], &stream, 50);\n  Io::ReadArrayLE<uint8_t>(&FlagWork[460], &stream, 40);\n  Io::ReadArrayLE<uint8_t>((uint8_t*)&ScrWork[600], &stream, 1600);\n\n  stream.Seek(0x7DA, SEEK_SET);\n  for (int i = 0; i < 150; i++) {\n    auto val = Io::ReadU8(&stream);\n    EVFlags[8 * i] = val & 1;\n    EVFlags[8 * i + 1] = (val & 2) != 0;\n    EVFlags[8 * i + 2] = (val & 4) != 0;\n    EVFlags[8 * i + 3] = (val & 8) != 0;\n    EVFlags[8 * i + 4] = (val & 0x10) != 0;\n    EVFlags[8 * i + 5] = (val & 0x20) != 0;\n    EVFlags[8 * i + 6] = (val & 0x40) != 0;\n    EVFlags[8 * i + 7] = val >> 7;\n  }\n\n  stream.Seek(0xbc2, SEEK_SET);\n  Io::ReadArrayLE<uint8_t>(BGMFlags, &stream, 100);\n\n  stream.Seek(0xc26, SEEK_SET);\n  Io::ReadArrayLE<uint8_t>(MessageFlags, &stream, 10000);\n\n  stream.Seek(0x3336, SEEK_SET);\n  Io::ReadArrayLE<uint8_t>(GameExtraData, &stream, 1024);\n\n  stream.Seek(0x3b06, SEEK_SET);  // TODO: Actually load system data\n\n  return SaveError::OK;\n}\n\nvoid SaveSystem::SaveSystemData() {\n  Io::MemoryStream stream =\n      Io::MemoryStream(SystemData.data(), SystemData.size(), false);\n\n  stream.Seek(0xbc2, SEEK_SET);\n  stream.Write(&BGMFlags, sizeof(uint8_t), 100);\n\n  stream.Seek(0xc26, SEEK_SET);\n  stream.Write(&MessageFlags, sizeof(uint8_t), 10000);\n\n  stream.Seek(0x3336, SEEK_SET);\n  stream.Write(&GameExtraData, sizeof(uint8_t), 1024);\n\n  stream.Seek(0x3b06, SEEK_SET);  // TODO: Actually save system data\n}\n\nSaveError SaveSystem::MountSaveFile(std::vector<QueuedTexture>& textures) {\n  Io::PhysicalFileStream* stream;\n  Io::Stream* instream;\n  IoError err = Io::PhysicalFileStream::Create(SaveFilePath, &instream);\n  switch (err) {\n    case IoError_NotFound:\n      return SaveError::NotFound;\n    case IoError_Fail:\n    case IoError_Eof:\n      return SaveError::Corrupted;\n    case IoError_OK:\n      break;\n  };\n  stream = (Io::PhysicalFileStream*)instream;\n\n  WorkingSaveEntry = new SaveFileEntry();\n  WorkingSaveThumbnail.Sheet =\n      SpriteSheet((float)Window->WindowWidth, (float)Window->WindowHeight);\n  WorkingSaveThumbnail.Bounds = RectF(0.0f, 0.0f, (float)Window->WindowWidth,\n                                      (float)Window->WindowHeight);\n\n  QueuedTexture txt = {\n      .Id = std::ref(WorkingSaveThumbnail.Sheet.Texture),\n  };\n  txt.Tex.LoadSolidColor((int)WorkingSaveThumbnail.Bounds.Width,\n                         (int)WorkingSaveThumbnail.Bounds.Height, 0x000000);\n  textures.push_back(txt);\n\n  Io::ReadArrayLE<uint8_t>(SystemData.data(), stream, SystemData.size());\n\n  for (int i = 0; i < MaxSaveEntries; i++) {\n    QuickSaveEntries[i] = new SaveFileEntry();\n\n    QuickSaveEntries[i]->Status = Io::ReadLE<uint8_t>(stream);\n    QuickSaveEntries[i]->Checksum = Io::ReadLE<uint16_t>(stream);\n    Io::ReadLE<uint8_t>(stream);\n    uint8_t saveMonth = Io::ReadLE<uint8_t>(stream);\n    uint8_t saveDay = Io::ReadLE<uint8_t>(stream);\n    uint8_t saveHour = Io::ReadLE<uint8_t>(stream);\n    uint8_t saveMinute = Io::ReadLE<uint8_t>(stream);\n    uint8_t saveYear = Io::ReadLE<uint8_t>(stream);\n    uint8_t saveSecond = Io::ReadLE<uint8_t>(stream);\n    std::tm t{};\n    t.tm_sec = saveSecond;\n    t.tm_min = saveMinute;\n    t.tm_hour = saveHour;\n    t.tm_mday = saveDay;\n    t.tm_mon = saveMonth - 1;\n    t.tm_year = saveYear + 100;\n    QuickSaveEntries[i]->SaveDate = t;\n\n    Io::ReadLE<uint16_t>(stream);\n    QuickSaveEntries[i]->PlayTime = Io::ReadLE<uint32_t>(stream);\n    QuickSaveEntries[i]->SwTitle = Io::ReadLE<uint16_t>(stream);\n    Io::ReadLE<uint8_t>(stream);\n    stream->Seek(31, SEEK_CUR);\n    Io::ReadArrayLE<uint8_t>(\n        ((SaveFileEntry*)QuickSaveEntries[i])->FlagWorkScript1, stream, 50);\n    Io::ReadArrayLE<uint8_t>(\n        ((SaveFileEntry*)QuickSaveEntries[i])->FlagWorkScript2, stream, 100);\n    Io::ReadArrayLE<int>(((SaveFileEntry*)QuickSaveEntries[i])->ScrWorkScript1,\n                         stream, 300);\n    Io::ReadArrayLE<int>(((SaveFileEntry*)QuickSaveEntries[i])->ScrWorkScript2,\n                         stream, 1100);\n    QuickSaveEntries[i]->MainThreadExecPriority = Io::ReadLE<uint32_t>(stream);\n    QuickSaveEntries[i]->MainThreadGroupId = Io::ReadLE<uint32_t>(stream);\n    QuickSaveEntries[i]->MainThreadWaitCounter = Io::ReadLE<uint32_t>(stream);\n    QuickSaveEntries[i]->MainThreadScriptParam = Io::ReadLE<uint32_t>(stream);\n    QuickSaveEntries[i]->MainThreadIp = Io::ReadLE<uint32_t>(stream);\n    QuickSaveEntries[i]->MainThreadLoopCounter = Io::ReadLE<uint32_t>(stream);\n    QuickSaveEntries[i]->MainThreadLoopAdr = Io::ReadLE<uint32_t>(stream);\n    QuickSaveEntries[i]->MainThreadCallStackDepth =\n        Io::ReadLE<uint32_t>(stream);\n    for (int j = 0; j < 8; j++) {\n      QuickSaveEntries[i]->MainThreadReturnIds[j] =\n          Io::ReadLE<uint32_t>(stream);\n      QuickSaveEntries[i]->MainThreadReturnBufIds[j] =\n          Io::ReadLE<uint32_t>(stream);\n    }\n    Io::ReadArrayLE<int>(QuickSaveEntries[i]->MainThreadVariables.data(),\n                         stream, 16);\n    // QuickSaveEntries[i]->MainThreadDialoguePageId =\n    //    Io::ReadLE<uint32_t>(stream);\n    stream->Seek(204, SEEK_CUR);\n  }\n\n  stream->Seek(0x3028, SEEK_CUR);\n\n  for (int i = 0; i < MaxSaveEntries; i++) {\n    FullSaveEntries[i] = new SaveFileEntry();\n\n    FullSaveEntries[i]->Status = Io::ReadLE<uint8_t>(stream);\n    FullSaveEntries[i]->Checksum = Io::ReadLE<uint16_t>(stream);\n    Io::ReadLE<uint8_t>(stream);\n    uint8_t saveMonth = Io::ReadLE<uint8_t>(stream);\n    uint8_t saveDay = Io::ReadLE<uint8_t>(stream);\n    uint8_t saveHour = Io::ReadLE<uint8_t>(stream);\n    uint8_t saveMinute = Io::ReadLE<uint8_t>(stream);\n    uint8_t saveYear = Io::ReadLE<uint8_t>(stream);\n    uint8_t saveSecond = Io::ReadLE<uint8_t>(stream);\n    std::tm t{};\n    t.tm_sec = saveSecond;\n    t.tm_min = saveMinute;\n    t.tm_hour = saveHour;\n    t.tm_mday = saveDay;\n    t.tm_mon = saveMonth - 1;\n    t.tm_year = saveYear + 100;\n    FullSaveEntries[i]->SaveDate = t;\n    Io::ReadLE<uint16_t>(stream);\n    FullSaveEntries[i]->PlayTime = Io::ReadLE<uint32_t>(stream);\n    FullSaveEntries[i]->SwTitle = Io::ReadLE<uint16_t>(stream);\n    Io::ReadLE<uint8_t>(stream);\n    stream->Seek(31, SEEK_CUR);\n    Io::ReadArrayLE<uint8_t>(\n        ((SaveFileEntry*)FullSaveEntries[i])->FlagWorkScript1, stream, 50);\n    Io::ReadArrayLE<uint8_t>(\n        ((SaveFileEntry*)FullSaveEntries[i])->FlagWorkScript2, stream, 100);\n    Io::ReadArrayLE<int>(((SaveFileEntry*)FullSaveEntries[i])->ScrWorkScript1,\n                         stream, 300);\n    Io::ReadArrayLE<int>(((SaveFileEntry*)FullSaveEntries[i])->ScrWorkScript2,\n                         stream, 1100);\n    FullSaveEntries[i]->MainThreadExecPriority = Io::ReadLE<uint32_t>(stream);\n    FullSaveEntries[i]->MainThreadGroupId = Io::ReadLE<uint32_t>(stream);\n    FullSaveEntries[i]->MainThreadWaitCounter = Io::ReadLE<uint32_t>(stream);\n    FullSaveEntries[i]->MainThreadScriptParam = Io::ReadLE<uint32_t>(stream);\n    FullSaveEntries[i]->MainThreadIp = Io::ReadLE<uint32_t>(stream);\n    FullSaveEntries[i]->MainThreadLoopCounter = Io::ReadLE<uint32_t>(stream);\n    FullSaveEntries[i]->MainThreadLoopAdr = Io::ReadLE<uint32_t>(stream);\n    FullSaveEntries[i]->MainThreadCallStackDepth = Io::ReadLE<uint32_t>(stream);\n    for (int j = 0; j < 8; j++) {\n      FullSaveEntries[i]->MainThreadReturnIds[j] = Io::ReadLE<uint32_t>(stream);\n      FullSaveEntries[i]->MainThreadReturnBufIds[j] =\n          Io::ReadLE<uint32_t>(stream);\n    }\n    Io::ReadArrayLE<int>(FullSaveEntries[i]->MainThreadVariables.data(), stream,\n                         16);\n    // FullSaveEntries[i]->MainThreadDialoguePageId =\n    // Io::ReadLE<uint32_t>(stream);\n    stream->Seek(204, SEEK_CUR);\n  }\n\n  stream->~PhysicalFileStream();\n\n  return SaveError::OK;\n}\n\n// uint16_t CalculateChecksum(int id) {\n//  return 0;\n//}\n\nvoid SaveSystem::FlushWorkingSaveEntry(SaveType type, int id,\n                                       int autoSaveType) {\n  auto* entry = GetSaveEntry<SaveFileEntry>(type, id);\n\n  if (WorkingSaveEntry != 0) {\n    if (entry != 0 && !(entry->Flags & WriteProtect)) {\n      Renderer->FreeTexture(entry->SaveThumbnail.Sheet.Texture);\n      if (type == SaveType::Quick) {\n        entry->SaveType = autoSaveType;\n        UpdateQuickSaveRecentSortedId(id);\n      }\n      entry->Status = 1;\n\n      entry->SaveDate = CurrentDateTime();\n      entry->PlayTime = WorkingSaveEntry->PlayTime;\n      entry->SwTitle = WorkingSaveEntry->SwTitle;\n\n      memcpy(entry->FlagWorkScript1, WorkingSaveEntry->FlagWorkScript1, 50);\n      memcpy(entry->FlagWorkScript2, WorkingSaveEntry->FlagWorkScript2, 100);\n      memcpy(entry->ScrWorkScript1, WorkingSaveEntry->ScrWorkScript1, 1200);\n      memcpy(entry->ScrWorkScript2, WorkingSaveEntry->ScrWorkScript2, 5200);\n\n      entry->MainThreadExecPriority = WorkingSaveEntry->MainThreadExecPriority;\n      entry->MainThreadWaitCounter = WorkingSaveEntry->MainThreadWaitCounter;\n      entry->MainThreadScriptParam = WorkingSaveEntry->MainThreadScriptParam;\n      entry->MainThreadGroupId = WorkingSaveEntry->MainThreadGroupId;\n      entry->MainThreadGroupId = WorkingSaveEntry->MainThreadGroupId;\n      entry->MainThreadIp = WorkingSaveEntry->MainThreadIp;\n      entry->MainThreadCallStackDepth =\n          WorkingSaveEntry->MainThreadCallStackDepth;\n\n      for (int j = 0; j < 8; j++) {\n        entry->MainThreadReturnIds[j] =\n            WorkingSaveEntry->MainThreadReturnIds[j];\n        entry->MainThreadReturnBufIds[j] =\n            WorkingSaveEntry->MainThreadReturnBufIds[j];\n      }\n\n      std::ranges::copy(WorkingSaveEntry->MainThreadVariables,\n                        entry->MainThreadVariables.begin());\n      entry->MainThreadDialoguePageId =\n          WorkingSaveEntry->MainThreadDialoguePageId;\n    }\n  }\n}\n\nSaveError SaveSystem::WriteSaveFile() {\n  using CF = Io::PhysicalFileStream::CreateFlagsMode;\n  Io::PhysicalFileStream* stream;\n  Io::Stream* instream;\n  {\n    IoError err = Io::PhysicalFileStream::Create(\n        SaveFilePath, &instream, CF::CREATE | CF::CREATE_DIRS | CF::WRITE);\n    if (err != IoError_OK) {\n      ImpLog(LogLevel::Error, LogChannel::IO, \"Failed to create save file\\n\");\n      return SaveError::Failed;\n    }\n  }\n  stream = (Io::PhysicalFileStream*)instream;\n\n  Io::WriteArrayLE<uint8_t>(SystemData.data(), stream, SystemData.size());\n  auto writeEntry = [&](SaveFileEntry* entry) {\n    if (entry == nullptr || entry->Status == 0) {\n      stream->Seek(0x1814, SEEK_CUR);\n      return SaveError::OK;\n    }\n    auto writeErr = stream->Write(&entry->Status, sizeof(uint8_t), 1);\n    if (writeErr != IoError_OK) {\n      ImpLog(LogLevel::Error, LogChannel::IO,\n             \"Failed to write save entry to file\\n\");\n      return SaveError::Failed;\n    }\n    // TODO: Add error checking\n    stream->Write(&entry->Checksum, sizeof(uint16_t), 1);\n    stream->Seek(1, SEEK_CUR);\n    uint8_t mon = (uint8_t)(entry->SaveDate.tm_mon + 1);\n    stream->Write(&mon, sizeof(uint8_t), 1);\n    stream->Write(&entry->SaveDate.tm_mday, sizeof(uint8_t), 1);\n    stream->Write(&entry->SaveDate.tm_hour, sizeof(uint8_t), 1);\n    stream->Write(&entry->SaveDate.tm_min, sizeof(uint8_t), 1);\n    uint8_t year = (uint8_t)(entry->SaveDate.tm_year - 100);\n    stream->Write(&year, sizeof(uint8_t), 1);\n    stream->Write(&entry->SaveDate.tm_sec, sizeof(uint8_t), 1);\n    stream->Seek(2, SEEK_CUR);\n    stream->Write(&entry->PlayTime, sizeof(uint32_t), 1);\n    stream->Write(&entry->SwTitle, sizeof(uint16_t), 1);\n    stream->Seek(32, SEEK_CUR);\n    stream->Write(((SaveFileEntry*)entry)->FlagWorkScript1, sizeof(uint8_t),\n                  50);\n    stream->Write(((SaveFileEntry*)entry)->FlagWorkScript2, sizeof(uint8_t),\n                  100);\n    stream->Write(((SaveFileEntry*)entry)->ScrWorkScript1, sizeof(int), 300);\n    stream->Write(((SaveFileEntry*)entry)->ScrWorkScript2, sizeof(int), 1100);\n    stream->Write(&entry->MainThreadExecPriority, sizeof(uint32_t), 1);\n    stream->Write(&entry->MainThreadGroupId, sizeof(uint32_t), 1);\n    stream->Write(&entry->MainThreadWaitCounter, sizeof(uint32_t), 1);\n    stream->Write(&entry->MainThreadScriptParam, sizeof(uint32_t), 1);\n    stream->Write(&entry->MainThreadIp, sizeof(uint32_t), 1);\n    stream->Write(&entry->MainThreadLoopCounter, sizeof(uint32_t), 1);\n    stream->Write(&entry->MainThreadLoopAdr, sizeof(uint32_t), 1);\n    stream->Write(&entry->MainThreadCallStackDepth, sizeof(uint32_t), 1);\n    for (int j = 0; j < 8; j++) {\n      stream->Write(&entry->MainThreadReturnIds[j], sizeof(uint32_t), 1);\n      stream->Write(&entry->MainThreadReturnBufIds[j], sizeof(uint32_t), 1);\n    }\n    stream->Write(&entry->MainThreadVariables, sizeof(int), 16);\n    stream->Seek(204, SEEK_CUR);\n    return SaveError::OK;\n  };\n\n  for (int i = 0; i < MaxSaveEntries; i++) {\n    int reverseI = MaxSaveEntries - i - 1;\n    auto err = writeEntry(static_cast<SaveFileEntry*>(\n        QuickSaveEntries[QuickSaveRecentSortedId[reverseI]]));\n    if (err != SaveError::OK) return err;\n  }\n\n  stream->Seek(0x3028, SEEK_CUR);\n\n  for (int i = 0; i < MaxSaveEntries; i++) {\n    auto err = writeEntry(static_cast<SaveFileEntry*>(FullSaveEntries[i]));\n    if (err != SaveError::OK) return err;\n  }\n\n  delete stream;\n  return SaveError::OK;\n}\n\nvoid SaveSystem::SaveMemory() {\n  if (WorkingSaveEntry != 0) {\n    WorkingSaveEntry->Status = 1;\n\n    WorkingSaveEntry->PlayTime = ScrWork[2304];\n    WorkingSaveEntry->SwTitle = ScrWork[2300];\n\n    memcpy(WorkingSaveEntry->FlagWorkScript1, &FlagWork[50], 50);\n    memcpy(WorkingSaveEntry->FlagWorkScript2, &FlagWork[300], 100);\n    memcpy(WorkingSaveEntry->ScrWorkScript1, &ScrWork[300], 1200);\n    memcpy(WorkingSaveEntry->ScrWorkScript2, &ScrWork[2300], 5200);\n\n    int threadId = ScrWork[SW_MAINTHDP];\n    Sc3VmThread* thd = &ThreadPool[threadId & 0x7FFFFFFF];\n    if (thd != 0 &&\n        (thd->GroupId == 4 || thd->GroupId == 5 || thd->GroupId == 6)) {\n      WorkingSaveEntry->MainThreadExecPriority = thd->ExecPriority;\n      WorkingSaveEntry->MainThreadWaitCounter = thd->WaitCounter;\n      WorkingSaveEntry->MainThreadScriptParam = thd->ScriptParam;\n      WorkingSaveEntry->MainThreadGroupId = thd->GroupId << 16;\n      WorkingSaveEntry->MainThreadGroupId |= thd->ScriptBufferId;\n      WorkingSaveEntry->MainThreadIp = thd->IpOffset;\n      WorkingSaveEntry->MainThreadCallStackDepth = thd->CallStackDepth;\n\n      for (size_t j = 0; j < thd->CallStackDepth; j++) {\n        WorkingSaveEntry->MainThreadReturnIds[j] = thd->ReturnAddresses[j];\n        WorkingSaveEntry->MainThreadReturnBufIds[j] =\n            thd->ReturnScriptBufferIds[j];\n      }\n\n      memcpy(WorkingSaveEntry->MainThreadVariables.data(), thd->Variables,\n             16 * sizeof(int));\n      WorkingSaveEntry->MainThreadDialoguePageId = thd->DialoguePageId;\n    }\n  }\n}\n\nvoid SaveSystem::LoadEntry(SaveType type, int id) {\n  auto* entry = GetSaveEntry<SaveFileEntry>(type, id);\n\n  if (entry != 0)\n    if (entry->Status) {\n      ScrWork[SW_PLAYTIME] = entry->PlayTime;\n      ScrWork[SW_TITLE] = entry->SwTitle;\n\n      memcpy(&FlagWork[50], entry->FlagWorkScript1, 50);\n      memcpy(&FlagWork[300], entry->FlagWorkScript2, 100);\n      memcpy(&ScrWork[300], entry->ScrWorkScript1, 1200);\n      memcpy(&ScrWork[2300], entry->ScrWorkScript2, 5200);\n\n      // TODO: What to do about this mess I wonder...\n      ScrWork[SW_SVSENO] = ScrWork[SW_SEREQNO];\n      ScrWork[SW_SVSENO + 1] = ScrWork[SW_SEREQNO + 1];\n      ScrWork[SW_SVSENO + 2] = ScrWork[SW_SEREQNO + 2];\n      ScrWork[SW_SVBGMNO] = ScrWork[SW_BGMREQNO];\n      ScrWork[SW_SVSCRNO1] = ScrWork[SW_SCRIPTNO2];\n      ScrWork[SW_SVSCRNO2] = ScrWork[SW_SCRIPTNO3];\n      ScrWork[SW_SVSCRNO3] = ScrWork[SW_SCRIPTNO4];\n      ScrWork[SW_SVSCRNO4] = ScrWork[SW_SCRIPTNO5];\n      ScrWork[SW_SVBGNO1] = ScrWork[SW_BG1NO];\n      ScrWork[SW_SVBGNO1 + 1] = ScrWork[2427];\n      ScrWork[SW_SVBGNO1 + 2] = ScrWork[2447];\n      ScrWork[SW_SVBGNO1 + 3] = ScrWork[2467];\n      ScrWork[SW_SVBGNO1 + 4] = ScrWork[2487];\n      ScrWork[SW_SVBGNO1 + 5] = ScrWork[2507];\n      ScrWork[SW_SVBGNO1 + 6] = ScrWork[2527];\n      ScrWork[SW_SVBGNO1 + 7] = ScrWork[2547];\n      ScrWork[SW_SVCHANO1] = ScrWork[SW_CHA1NO];\n      ScrWork[SW_SVCHANO1 + 1] = ScrWork[2629];\n      ScrWork[SW_SVCHANO1 + 2] = ScrWork[2649];\n      ScrWork[SW_SVCHANO1 + 3] = ScrWork[2669];\n      ScrWork[SW_SVCHANO1 + 4] = ScrWork[2689];\n      ScrWork[SW_SVCHANO1 + 5] = ScrWork[2709];\n      ScrWork[SW_SVCHANO1 + 6] = ScrWork[2729];\n      ScrWork[SW_SVCHANO1 + 7] = ScrWork[2749];\n      ScrWork[SW_SVCHANO1 + 8] = ScrWork[2769];\n      ScrWork[SW_SVCHANO1 + 9] = ScrWork[2789];\n      ScrWork[SW_SVCHANO1 + 10] = ScrWork[2809];\n      ScrWork[SW_SVCHANO1 + 11] = ScrWork[2829];\n      ScrWork[SW_SVCHANO1 + 12] = ScrWork[2849];\n      ScrWork[SW_SVCHANO1 + 13] = ScrWork[2869];\n      ScrWork[SW_SVCHANO1 + 14] = ScrWork[2889];\n      ScrWork[SW_SVCHANO1 + 15] = ScrWork[2909];\n      ScrWork[2034] = ScrWork[3200];\n      ScrWork[2035] = ScrWork[3201];\n      ScrWork[2036] = ScrWork[3202];\n      ScrWork[2037] = ScrWork[3203];\n      ScrWork[2038] = ScrWork[3204];\n      ScrWork[2039] = ScrWork[3205];\n      ScrWork[2040] = ScrWork[3206];\n      ScrWork[2041] = ScrWork[3207];\n      ScrWork[2042] = ScrWork[3208];\n      ScrWork[2043] = ScrWork[3209];\n      ScrWork[2044] = ScrWork[3210];\n      ScrWork[2045] = ScrWork[3211];\n      ScrWork[2046] = ScrWork[3212];\n      ScrWork[2047] = ScrWork[3213];\n      ScrWork[2048] = ScrWork[3214];\n      ScrWork[2049] = ScrWork[3215];\n      ScrWork[2050] = ScrWork[3216];\n      ScrWork[2051] = ScrWork[3220];\n      ScrWork[2052] = ScrWork[3221];\n      ScrWork[2053] = ScrWork[3222];\n      ScrWork[2054] = ScrWork[3223];\n      ScrWork[2055] = ScrWork[3224];\n      ScrWork[2056] = ScrWork[3225];\n      ScrWork[2057] = ScrWork[3226];\n      ScrWork[2058] = ScrWork[3227];\n      ScrWork[2059] = ScrWork[3228];\n      ScrWork[2060] = ScrWork[3229];\n      ScrWork[2061] = ScrWork[3230];\n      ScrWork[2062] = ScrWork[3231];\n      ScrWork[2063] = ScrWork[3232];\n      ScrWork[2064] = ScrWork[3233];\n      ScrWork[2065] = ScrWork[3234];\n      ScrWork[2066] = ScrWork[3235];\n      ScrWork[2067] = ScrWork[3236];\n\n      int threadId = ScrWork[SW_MAINTHDP];\n      Sc3VmThread* thd = &ThreadPool[threadId & 0x7FFFFFFF];\n      if (thd != 0 &&\n          (thd->GroupId == 4 || thd->GroupId == 5 || thd->GroupId == 6)) {\n        thd->ExecPriority = entry->MainThreadExecPriority;\n        thd->WaitCounter = entry->MainThreadWaitCounter;\n        thd->ScriptParam = entry->MainThreadScriptParam;\n        thd->GroupId = entry->MainThreadGroupId >> 16;\n        thd->ScriptBufferId = entry->MainThreadGroupId & 0xFFFF;\n        thd->IpOffset = entry->MainThreadIp;\n        // thd->CallStackDepth = entry->MainThreadCallStackDepth;\n\n        thd->CallStackDepth++;\n        thd->ReturnScriptBufferIds[0] = entry->MainThreadReturnBufIds[0];\n        thd->ReturnAddresses[0] = entry->MainThreadReturnIds[0];\n\n        memcpy(thd->Variables, entry->MainThreadVariables.data(),\n               16 * sizeof(int));\n        thd->DialoguePageId = entry->MainThreadDialoguePageId;\n      }\n    }\n}\n\nuint32_t SaveSystem::GetSavePlayTime(SaveType type, int id) const {\n  return GetSaveEntry<SaveFileEntry>(type, id)->PlayTime;\n}\n\nuint8_t SaveSystem::GetSaveFlags(SaveType type, int id) const {\n  return GetSaveEntry<SaveFileEntry>(type, id)->Flags;\n}\n\nvoid SaveSystem::SetSaveFlags(SaveType type, int id, uint8_t flags) {\n  auto* entry = GetSaveEntry<SaveFileEntry>(type, id);\n  entry->Flags = flags;\n}\n\ntm const& SaveSystem::GetSaveDate(SaveType type, int id) const {\n  return GetSaveEntry<SaveFileEntry>(type, id)->SaveDate;\n}\n\nuint8_t SaveSystem::GetSaveStatus(SaveType type, int id) const {\n  return GetSaveEntry<SaveFileEntry>(type, id)->Status;\n}\n\nint SaveSystem::GetSaveTitle(SaveType type, int id) const {\n  return GetSaveEntry<SaveFileEntry>(type, id)->SwTitle;\n}\n\nuint32_t SaveSystem::GetTipStatus(size_t tipId) const {\n  tipId *= 3;\n  uint8_t lockStatus = (GameExtraData[tipId >> 3] & Flbit[tipId & 7]) != 0;\n  uint8_t newStatus =\n      (GameExtraData[(tipId + 1) >> 3] & Flbit[(tipId + 1) & 7]) != 0;\n  uint8_t unreadStatus =\n      (GameExtraData[(tipId + 2) >> 3] & Flbit[(tipId + 2) & 7]) != 0;\n  return (lockStatus | (unreadStatus << 1)) | (newStatus << 2);\n}\n\nvoid SaveSystem::SetTipStatus(size_t tipId, bool isLocked, bool isUnread,\n                              bool isNew) {\n  tipId *= 3;\n  if (isLocked) {\n    GameExtraData[tipId >> 3] &= ~(Flbit[tipId & 7]);\n  } else {\n    GameExtraData[tipId >> 3] |= Flbit[tipId & 7];\n  }\n  if (isUnread) {\n    GameExtraData[(tipId + 2) >> 3] &= ~(Flbit[(tipId + 2) & 7]);\n  } else {\n    GameExtraData[(tipId + 2) >> 3] |= Flbit[(tipId + 2) & 7];\n  }\n  if (isNew) {\n    GameExtraData[(tipId + 1) >> 3] &= ~(Flbit[(tipId + 1) & 7]);\n  } else {\n    GameExtraData[(tipId + 1) >> 3] |= Flbit[(tipId + 1) & 7];\n  }\n}\n\nvoid SaveSystem::SetLineRead(int scriptId, int lineId) {\n  if (scriptId >= StoryScriptCount.value()) return;\n\n  uint32_t offset =\n      ScriptMessageData[StoryScriptIDs[scriptId]].SaveDataOffset + lineId;\n  if (offset == 0xFFFFFFFF) return;\n\n  // TODO: update some ScrWorks (2003, 2005 & 2006)\n\n  MessageFlags[offset >> 3] |= Flbit[offset & 0b111];\n}\n\nbool SaveSystem::IsLineRead(int scriptId, int lineId) const {\n  if (scriptId >= StoryScriptCount.value()) return false;\n\n  uint32_t offset =\n      ScriptMessageData[StoryScriptIDs[scriptId]].SaveDataOffset + lineId;\n  uint8_t flbit = Flbit[offset & 0b111];\n  uint8_t viewed = MessageFlags[offset >> 3];\n\n  return (bool)(flbit & viewed);\n}\n\nvoid SaveSystem::GetReadMessagesCount(int* totalMessageCount,\n                                      int* readMessageCount) const {\n  *totalMessageCount = 0;\n  *readMessageCount = 0;\n\n  for (int scriptId = 0; scriptId < StoryScriptCount; scriptId++) {\n    ScriptMessageDataPair script = ScriptMessageData[StoryScriptIDs[scriptId]];\n    *totalMessageCount += script.LineCount;\n\n    for (size_t lineId = 0; lineId < script.LineCount; lineId++) {\n      *readMessageCount += IsLineRead(scriptId, (int)lineId);\n    }\n  }\n}\n\nvoid SaveSystem::GetViewedEVsCount(int* totalEVCount,\n                                   int* viewedEVCount) const {\n  for (int i = 0; i < MaxAlbumEntries; i++) {\n    if (AlbumEvData[i][0] == 0xFFFF) break;\n    for (int j = 0; j < MaxAlbumSubEntries; j++) {\n      if (AlbumEvData[i][j] == 0xFFFF) break;\n      *totalEVCount += 1;\n      *viewedEVCount += EVFlags[AlbumEvData[i][j]];\n    }\n  }\n}\n\nvoid SaveSystem::GetEVStatus(int evId, int* totalVariations,\n                             int* viewedVariations) const {\n  *totalVariations = 0;\n  *viewedVariations = 0;\n  for (int i = 0; i < MaxAlbumSubEntries; i++) {\n    if (AlbumEvData[evId][i] == 0xFFFF) break;\n    *totalVariations += 1;\n    *viewedVariations += EVFlags[AlbumEvData[evId][i]];\n  }\n}\n\nvoid SaveSystem::SetEVStatus(int id) { EVFlags[id] = true; }\n\nbool SaveSystem::GetEVVariationIsUnlocked(size_t evId,\n                                          size_t variationIdx) const {\n  if (AlbumEvData[evId][variationIdx] == 0xFFFF) return false;\n  return EVFlags[AlbumEvData[evId][variationIdx]];\n}\n\nbool SaveSystem::GetBgmFlag(int id) const { return BGMFlags[id]; }\nvoid SaveSystem::SetBgmFlag(int id, bool flag) { BGMFlags[id] = flag; }\n\nSprite& SaveSystem::GetSaveThumbnail(SaveType type, int id) {\n  return GetSaveEntry<SaveFileEntry>(type, id)->SaveThumbnail;\n}\n\n}  // namespace MO6TW\n}  // namespace Impacto"
  },
  {
    "path": "src/games/mo6tw/savesystem.h",
    "content": "#pragma once\n\n#include \"../../data/savesystem.h\"\n\nnamespace Impacto {\nnamespace MO6TW {\n\nusing namespace Impacto::SaveSystem;\n\nclass SaveFileEntry : public SaveFileEntryBase {\n public:\n  uint8_t FlagWorkScript1[50];   // 50 bytes from &FlagWork[50]\n  uint8_t FlagWorkScript2[100];  // 100 bytes from &FlagWork[300]\n  int ScrWorkScript1[300];       // 1200 bytes from &ScrWork[300]\n  int ScrWorkScript2[1300];      // 5200 bytes from &ScrWork[2300]\n};\n\nclass SaveSystem : public SaveSystemBase {\n public:\n  SaveError CheckSaveFile() const override { return SaveError::OK; }  // Todo\n  SaveError MountSaveFile(std::vector<QueuedTexture>& textures) override;\n\n  SaveError LoadSystemData() override;\n  void SaveSystemData() override;\n  void InitializeSystemData() override {}  // Todo\n\n  void SaveThumbnailData() override {};  // Todo\n  void SaveMemory() override;\n  void LoadEntry(SaveType type, int id) override;\n  void FlushWorkingSaveEntry(SaveType type, int id, int autoSaveType) override;\n  SaveError WriteSaveFile() override;\n  uint32_t GetSavePlayTime(SaveType type, int id) const override;\n  uint8_t GetSaveFlags(SaveType type, int id) const override;\n  void SetSaveFlags(SaveType type, int id, uint8_t flags) override;\n  tm const& GetSaveDate(SaveType type, int id) const override;\n  uint8_t GetSaveStatus(SaveType type, int id) const override;\n  int GetSaveTitle(SaveType type, int id) const override;\n  uint32_t GetTipStatus(size_t tipId) const override;\n  void SetTipStatus(size_t tipId, bool isLocked, bool isUnread,\n                    bool isNew) override;\n  void SetLineRead(int scriptId, int lineId) override;\n  bool IsLineRead(int scriptId, int lineId) const override;\n  void GetReadMessagesCount(int* totalMessageCount,\n                            int* readMessageCount) const override;\n  void GetViewedEVsCount(int* totalEVCount, int* viewedEVCount) const override;\n  void GetEVStatus(int evId, int* totalVariations,\n                   int* viewedVariations) const override;\n  void SetEVStatus(int evId) override;\n  bool GetEVVariationIsUnlocked(size_t evId,\n                                size_t variationIdx) const override;\n  bool GetBgmFlag(int id) const override;\n  void SetBgmFlag(int id, bool flag) override;\n  void SetCheckpointId(int id) override {}\n  Sprite& GetSaveThumbnail(SaveType type, int id) override;\n\n private:\n  uint8_t GameExtraData[1024];\n  uint8_t MessageFlags[10000];\n  std::array<uint8_t, 0x3b06> SystemData;\n  bool EVFlags[1200];\n  uint8_t BGMFlags[100];\n};\n\n}  // namespace MO6TW\n}  // namespace Impacto"
  },
  {
    "path": "src/games/mo6tw/sysmesbox.cpp",
    "content": "#include \"sysmesbox.h\"\n\n#include \"../../profile/ui/sysmesbox.h\"\n#include \"../../profile/games/mo6tw/sysmesbox.h\"\n#include \"../../profile/dialogue.h\"\n#include \"../../profile/scriptvars.h\"\n#include \"../../mem.h\"\n#include \"../../renderer/renderer.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace MO6TW {\n\nusing namespace Impacto::UI::Widgets;\nusing namespace Impacto::Profile::ScriptVars;\nusing namespace Impacto::Profile::SysMesBox;\nusing namespace Impacto::Profile::MO6TW::SysMesBox;\n\nvoid SysMesBox::ChoiceItemOnClick(Button* target) {\n  ScrWork[SW_SYSSEL] = target->Id;\n  ChoiceMade = true;\n}\n\nvoid SysMesBox::Show() {\n  MessageItems = new Widgets::Group(this);\n  ChoiceItems = new Widgets::Group(this);\n\n  Sprite nullSprite = Sprite();\n  nullSprite.Bounds = RectF(0.0f, 0.0f, 0.0f, 0.0f);\n\n  auto onClick = [this](auto* btn) { return ChoiceItemOnClick(btn); };\n\n  float diff = 0.0f;\n  float maxWidth = FLT_MIN;\n  for (int i = 0; i < MessageCount; i++) {\n    if (maxWidth < MessageWidths[i]) maxWidth = MessageWidths[i];\n  }\n  if (maxWidth < BoxMinimumWidth) maxWidth = BoxMinimumWidth;\n\n  for (int i = 0; i < MessageCount; i++) {\n    if (Messages[i].empty()) continue;\n\n    diff = Messages[i][0].DestRect.X - (TextX - (maxWidth / 2.0f));\n    for (ProcessedTextGlyph& glyph : Messages[i]) {\n      glyph.DestRect.X -= diff;\n      glyph.DestRect.Y = TextMiddleY + (i * TextLineHeight);\n    }\n\n    Label* message = new Label(Messages[i], MessageWidths[i], TextFontSize,\n                               RendererOutlineMode::Full);\n\n    MessageItems->Add(message, FDIR_DOWN);\n  }\n\n  float totalChoiceWidth = 0.0f;\n  for (int i = 0; i < ChoiceCount; i++) {\n    totalChoiceWidth += ChoiceWidths[i] + ChoicePadding;\n  }\n  if (maxWidth < totalChoiceWidth) maxWidth = totalChoiceWidth;\n  if (maxWidth < MinMaxMesWidth) maxWidth = MinMaxMesWidth;\n\n  ChoiceX = (maxWidth / 2.0f) - totalChoiceWidth + ChoiceXBase;\n\n  float tempChoiceX = ChoiceX;\n\n  for (int i = 0; i < ChoiceCount; i++) {\n    diff = Choices[i][0].DestRect.X - tempChoiceX;\n    for (ProcessedTextGlyph& glyph : Choices[i]) {\n      glyph.DestRect.X -= diff;\n      glyph.DestRect.Y = ChoiceY;\n    }\n\n    Button* choice = new Button(\n        i, nullSprite, nullSprite, SelectionHighlight,\n        glm::vec2(Choices[i][0].DestRect.X, Choices[i][0].DestRect.Y));\n\n    choice->SetText(Choices[i], ChoiceWidths[i],\n                    Profile::Dialogue::DefaultFontSize,\n                    RendererOutlineMode::Full);\n    choice->OnClickHandler = onClick;\n\n    ChoiceItems->Add(choice, FDIR_LEFT);\n\n    tempChoiceX += ChoiceWidths[i] + ChoicePadding;\n  }\n\n  FadeAnimation.StartIn();\n  MessageItems->Show();\n  MessageItems->HasFocus = false;\n  if (ChoiceCount != 0) ChoiceItems->Show();\n  State = Showing;\n\n  if (UI::FocusedMenu != 0) {\n    LastFocusedMenu = UI::FocusedMenu;\n    LastFocusedMenu->IsFocused = false;\n  }\n  IsFocused = true;\n  UI::FocusedMenu = this;\n}\n\nvoid SysMesBox::Hide() {\n  FadeAnimation.StartOut();\n  State = Hiding;\n  if (LastFocusedMenu != 0) {\n    UI::FocusedMenu = LastFocusedMenu;\n    LastFocusedMenu->IsFocused = true;\n  } else {\n    UI::FocusedMenu = 0;\n  }\n  IsFocused = false;\n}\n\nvoid SysMesBox::Update(float dt) {\n  UpdateInput(dt);\n\n  FadeAnimation.Update(dt);\n  if (State != Hidden) {\n    if (FadeAnimation.IsIn()) State = Shown;\n    if (FadeAnimation.IsOut()) State = Hidden;\n\n    if (IsFocused) {\n      MessageItems->Update(dt);\n      ChoiceItems->Update(dt);\n      ChoiceItems->UpdateInput(dt);\n    }\n  }\n}\n\nvoid SysMesBox::Render() {\n  if (State == Hidden) return;\n\n  glm::vec4 col(1.0f, 1.0f, 1.0f, FadeAnimation.Progress);\n\n  float maxWidth = FLT_MIN;\n  for (int i = 0; i < MessageCount; i++) {\n    if (maxWidth < MessageWidths[i]) maxWidth = MessageWidths[i];\n  }\n  if (maxWidth < BoxMinimumWidth) maxWidth = BoxMinimumWidth;\n\n  Renderer->DrawSprite(BoxPartLeft, glm::vec2(BoxX - (maxWidth / 2.0f), BoxY),\n                       col);\n\n  float remainWidth = maxWidth - BoxMiddleRemainBase;\n  float currentX = BoxMiddleBaseX - (maxWidth / 2.0f);\n  while (remainWidth >= BoxMiddleBaseWidth) {\n    Renderer->DrawSprite(BoxPartMiddle, glm::vec2(currentX, BoxY), col);\n    currentX += BoxMiddleBaseWidth;\n    remainWidth -= BoxMiddleBaseWidth;\n  }\n\n  BoxPartRight.Bounds.X = BoxRightBaseX - (remainWidth + BoxRightRemainPad);\n  BoxPartRight.Bounds.Width = (remainWidth + BoxRightRemainPad) - 1.0f;\n  Renderer->DrawSprite(BoxPartRight, glm::vec2(currentX, BoxY), col);\n\n  MessageItems->Tint.a = FadeAnimation.Progress;\n  MessageItems->Render();\n  ChoiceItems->Tint.a = FadeAnimation.Progress;\n  ChoiceItems->Render();\n}\n\nvoid SysMesBox::Init() {\n  ChoiceMade = false;\n  MessageCount = 0;\n  ChoiceCount = 0;\n\n  FadeAnimation.DurationIn = FadeInDuration;\n  FadeAnimation.DurationOut = FadeOutDuration;\n  FadeAnimation.Direction = AnimationDirection::In;\n  FadeAnimation.LoopMode = AnimationLoopMode::Stop;\n}\n\nvoid SysMesBox::AddMessage(Vm::BufferOffsetContext ctx) {\n  Impacto::Vm::Sc3VmThread dummy;\n  dummy.IpOffset = ctx.IpOffset;\n  dummy.ScriptBufferId = ctx.ScriptBufferId;\n  Messages[MessageCount] =\n      TextLayoutPlainLine(&dummy, 255, Profile::Dialogue::DialogueFont,\n                          TextFontSize, Profile::Dialogue::ColorTable[0], 1.0f,\n                          glm::vec2(TextX, 0.0f), TextAlignment::Left);\n  float mesLen = 0.0f;\n  for (const ProcessedTextGlyph& glyph : Messages[MessageCount]) {\n    mesLen += glyph.DestRect.Width;\n  }\n  MessageWidths[MessageCount] = mesLen;\n  MessageCount++;\n}\n\nvoid SysMesBox::AddChoice(Vm::BufferOffsetContext ctx) {\n  Impacto::Vm::Sc3VmThread dummy;\n  dummy.IpOffset = ctx.IpOffset;\n  dummy.ScriptBufferId = ctx.ScriptBufferId;\n  Choices[ChoiceCount] =\n      TextLayoutPlainLine(&dummy, 255, Profile::Dialogue::DialogueFont,\n                          TextFontSize, Profile::Dialogue::ColorTable[0], 1.0f,\n                          glm::vec2(TextX, 0.0f), TextAlignment::Left);\n  float mesLen = 0.0f;\n  for (const ProcessedTextGlyph& glyph : Choices[ChoiceCount]) {\n    mesLen += glyph.DestRect.Width;\n  }\n  ChoiceWidths[ChoiceCount] = mesLen;\n  ChoiceCount++;\n}\n\n}  // namespace MO6TW\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/mo6tw/sysmesbox.h",
    "content": "#pragma once\n\n#include \"../../ui/sysmesbox.h\"\n#include \"../../ui/widgets/button.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace MO6TW {\n\nclass SysMesBox : public UI::SysMesBox {\n public:\n  virtual void Show() override;\n  virtual void Hide() override;\n  virtual void Update(float dt) override;\n  virtual void Render() override;\n\n  virtual void Init() override;\n  virtual void AddMessage(Vm::BufferOffsetContext ctx) override;\n  virtual void AddChoice(Vm::BufferOffsetContext ctx) override;\n\n private:\n  void ChoiceItemOnClick(UI::Widgets::Button* target);\n};\n\n}  // namespace MO6TW\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/mo6tw/systemmenu.cpp",
    "content": "#include \"systemmenu.h\"\n\n#include \"../../profile/ui/systemmenu.h\"\n#include \"../../profile/games/mo6tw/systemmenu.h\"\n#include \"../../renderer/renderer.h\"\n#include \"../../mem.h\"\n#include \"../../profile/scriptvars.h\"\n#include \"../../inputsystem.h\"\n#include \"../../ui/widgets/button.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace MO6TW {\n\nusing namespace Impacto::Profile::SystemMenu;\nusing namespace Impacto::Profile::MO6TW::SystemMenu;\nusing namespace Impacto::Profile::ScriptVars;\n\nusing namespace Impacto::UI::Widgets;\n\nvoid SystemMenu::MenuButtonOnClick(Widgets::Button* target) {\n  target->Hovered = false;\n  ScrWork[SW_SYSMENUCNO] = target->Id;\n  ChoiceMade = true;\n}\n\nSystemMenu::SystemMenu() {\n  MainItems = new Widgets::Group(this);\n\n  auto onClick = [this](auto* btn) { return MenuButtonOnClick(btn); };\n\n  for (int i = 0; i < MenuEntriesNum; i++) {\n    Button* menuButton =\n        new Button(i, MenuEntriesSprites[i], MenuEntriesSprites[i],\n                   MenuEntriesHighlightedSprite,\n                   glm::vec2(*MenuEntriesX,\n                             *MenuEntriesFirstY + (*MenuEntriesYPadding * i)));\n\n    menuButton->OnClickHandler = onClick;\n    MainItems->Add(menuButton, FDIR_DOWN);\n  }\n\n  FadeAnimation.Direction = AnimationDirection::In;\n  FadeAnimation.LoopMode = AnimationLoopMode::Stop;\n  FadeAnimation.DurationIn = FadeInDuration;\n  FadeAnimation.DurationOut = FadeOutDuration;\n}\n\nvoid SystemMenu::Show() {\n  if (State != Shown) {\n    State = Showing;\n    FadeAnimation.StartIn();\n    MainItems->Show();\n    if (UI::FocusedMenu != 0) {\n      LastFocusedMenu = UI::FocusedMenu;\n      LastFocusedMenu->IsFocused = false;\n    }\n    IsFocused = true;\n    UI::FocusedMenu = this;\n    if (LastFocusedButtonId && *LastFocusedButtonId < MenuEntriesNum) {\n      CurrentlyFocusedElement = MainItems->Children[*LastFocusedButtonId];\n      CurrentlyFocusedElement->HasFocus = true;\n    }\n  }\n}\nvoid SystemMenu::Hide() {\n  if (State != Hidden) {\n    if (CurrentlyFocusedElement) {\n      auto* btn = static_cast<Widgets::Button*>(CurrentlyFocusedElement);\n      if (btn) {\n        LastFocusedButtonId = btn->Id;\n      }\n    }\n    State = Hiding;\n    FadeAnimation.StartOut();\n    MainItems->Hide();\n    if (LastFocusedMenu != 0) {\n      UI::FocusedMenu = LastFocusedMenu;\n      LastFocusedMenu->IsFocused = true;\n    } else {\n      UI::FocusedMenu = 0;\n    }\n    IsFocused = false;\n  }\n}\n\nvoid SystemMenu::Update(float dt) {\n  UpdateInput(dt);\n\n  FadeAnimation.Update(dt);\n  if (ScrWork[SW_SYSMENUALPHA] > 0 && State == Hidden) {\n    Show();\n  }\n  if (ScrWork[SW_SYSMENUALPHA] < 256 && State == Shown) {\n    Hide();\n  }\n\n  if (ScrWork[SW_SYSMENUALPHA] == 256 && FadeAnimation.IsIn())\n    State = Shown;\n  else if (ScrWork[SW_SYSMENUALPHA] == 0 && FadeAnimation.IsOut())\n    State = Hidden;\n\n  if (State == Shown && IsFocused) {\n    MainItems->Update(dt);\n    MainItems->UpdateInput(dt);\n  }\n}\n\nvoid SystemMenu::Render() {\n  if (State != Hidden && ScrWork[SW_SYSMENUALPHA] > 0) {\n    glm::vec4 col(1.0f, 1.0f, 1.0f, FadeAnimation.Progress);\n    Renderer->DrawSprite(SystemMenuBackgroundSprite,\n                         glm::vec2(SystemMenuX, SystemMenuY), col);\n    MainItems->Tint = col;\n    MainItems->Render();\n  }\n}\n\n}  // namespace MO6TW\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/mo6tw/systemmenu.h",
    "content": "#pragma once\n\n#include <optional>\n#include \"../../ui/menu.h\"\n#include \"../../ui/widgets/group.h\"\n#include \"../../ui/widgets/button.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace MO6TW {\n\nclass SystemMenu : public Menu {\n public:\n  SystemMenu();\n\n  void Show() override;\n  void Hide() override;\n  void Update(float dt) override;\n  void Render() override;\n\n  void MenuButtonOnClick(Widgets::Button* target);\n\n private:\n  Widgets::Group* MainItems;\n  Animation FadeAnimation;\n  std::optional<int> LastFocusedButtonId;\n};\n\n}  // namespace MO6TW\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/mo6tw/tipsmenu.cpp",
    "content": "#include \"tipsmenu.h\"\n#include <fmt/format.h>\n\n#include \"../../renderer/renderer.h\"\n#include \"../../mem.h\"\n#include \"../../vm/vm.h\"\n#include \"../../profile/scriptvars.h\"\n#include \"../../profile/dialogue.h\"\n#include \"../../profile/ui/backlogmenu.h\"\n#include \"../../profile/ui/tipsmenu.h\"\n#include \"../../profile/games/mo6tw/tipsmenu.h\"\n#include \"../../ui/widgets/mo6tw/tipsentrybutton.h\"\n#include \"../../data/tipssystem.h\"\n#include \"../../vm/interface/input.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace MO6TW {\n\nusing namespace Impacto::Profile::TipsMenu;\nusing namespace Impacto::Profile::MO6TW::TipsMenu;\nusing namespace Impacto::Profile::ScriptVars;\n\nusing namespace Impacto::Vm::Interface;\n\nusing namespace Impacto::UI::Widgets;\nusing namespace Impacto::UI::Widgets::MO6TW;\n\nvoid TipsMenu::TipOnClick(Button* target) {\n  auto tipEntry = static_cast<TipsEntryButton*>(target);\n  if (!tipEntry->TipEntryRecord->IsLocked) SwitchToTipId(target->Id);\n}\n\nTipsMenu::TipsMenu() : ItemsList(CDIR_HORIZONTAL), TipViewItems(this) {\n  FadeAnimation.Direction = AnimationDirection::In;\n  FadeAnimation.LoopMode = AnimationLoopMode::Stop;\n  FadeAnimation.DurationIn = FadeInDuration;\n  FadeAnimation.DurationOut = FadeOutDuration;\n\n  TipViewItems.FocusLock = false;\n  TipViewItems.VisibilityState = Shown;\n\n  Name = new Label();\n  Name->Bounds = NameInitialBounds;\n  TipViewItems.Add(Name);\n\n  Pronounciation = new Label();\n  Pronounciation->Bounds = PronounciationInitialBounds;\n  TipViewItems.Add(Pronounciation);\n\n  Category = new Label();\n  Category->Bounds = CategoryInitialBounds;\n  TipViewItems.Add(Category);\n\n  TextPage.Glyphs.reserve(Profile::Dialogue::MaxPageSize);\n  TextPage.Clear();\n  TextPage.Mode = DPM_TIPS;\n  TextPage.FadeAnimation.Progress = 1.0f;\n}\n\nvoid TipsMenu::Show() {\n  if (State != Shown) {\n    State = Showing;\n    FadeAnimation.StartIn();\n    if (UI::FocusedMenu != 0) {\n      LastFocusedMenu = UI::FocusedMenu;\n      LastFocusedMenu->IsFocused = false;\n    }\n    IsFocused = true;\n    UI::FocusedMenu = this;\n  }\n}\nvoid TipsMenu::Hide() {\n  if (State != Hidden) {\n    State = Hiding;\n    FadeAnimation.StartOut();\n    if (LastFocusedMenu != 0) {\n      UI::FocusedMenu = LastFocusedMenu;\n      LastFocusedMenu->IsFocused = true;\n    } else {\n      UI::FocusedMenu = 0;\n    }\n    IsFocused = false;\n    CurrentlyDisplayedTipId = -1;\n  }\n}\n\nvoid TipsMenu::UpdateInput(float dt) {\n  if (State == Shown) {\n    Menu::UpdateInput(dt);\n    ItemsList.UpdateInput(dt);\n    if (CurrentlyDisplayedTipId != -1) {\n      if (PADinputButtonWentDown & PAD1X) {\n        AdvanceTipPage(TipAdvanceMode::NextLooped);\n      }\n    }\n  }\n}\n\nvoid TipsMenu::Update(float dt) {\n  UpdateInput(dt);\n\n  FadeAnimation.Update(dt);\n  if (ScrWork[SW_TIPSALPHA] < 256 && State == Shown) {\n    Hide();\n  } else if (ScrWork[SW_TIPSALPHA] == 256 && State == Hidden) {\n    Show();\n  }\n\n  if (ScrWork[SW_TIPSALPHA] == 256 && FadeAnimation.IsIn())\n    State = Shown;\n  else if (ScrWork[SW_TIPSALPHA] == 0 && FadeAnimation.IsOut())\n    State = Hidden;\n\n  if (State != Hidden) {\n    ItemsList.Tint.a = FadeAnimation.Progress;\n    TipViewItems.Tint.a = FadeAnimation.Progress;\n  }\n\n  if (State == Shown) {\n    ItemsList.Update(dt);\n    TipViewItems.Update(dt);\n  }\n}\n\nvoid TipsMenu::Render() {\n  if (State != Hidden && ScrWork[SW_TIPSALPHA] > 0) {\n    glm::vec4 col(1.0f, 1.0f, 1.0f, FadeAnimation.Progress);\n    Renderer->DrawSprite(BackgroundSprite, glm::vec2(0.0f), col);\n    ItemsList.Render();\n\n    if (CurrentlyDisplayedTipId != -1) {\n      TipViewItems.Render();\n      Renderer->DrawProcessedText(TextPage.Glyphs,\n                                  Profile::Dialogue::DialogueFont, col.a,\n                                  RendererOutlineMode::Full, true);\n      if (ThumbnailSprite) {\n        Renderer->DrawSprite(*ThumbnailSprite, ThumbnailPosition, col);\n      }\n    }\n  }\n}\n\nvoid TipsMenu::Init() {\n  auto onClick = [this](auto* btn) { return TipOnClick(btn); };\n  int currentPage = 0, currentCategoryId = -1;\n\n  // String of characters by which tips are sorted, taken from _system script\n  auto [scriptBufId, sortStrAddr] =\n      Vm::ScriptGetTextTableStrAddress(SortStringTable, SortStringIndex);\n  uint8_t* sortString = &Vm::ScriptBuffers[scriptBufId][sortStrAddr];\n  auto recordCount = TipsSystem::GetTipCount();\n\n  float currentY = TipListInitialY;\n\n  ItemsList.Clear();\n  TipViewItems.Clear();\n\n  Group* pageItems = new Group(this);\n  for (size_t i = 0; i < recordCount; i++) {\n    auto record = TipsSystem::GetTipRecord(i);\n\n    // In this case the tips are split into pages in UI with maximum of 5\n    // categories per page, with each category being a character from\n    // the sort string and containing all tips the names of which begin with\n    // that character\n    auto page = record->CategoryLetterIndex / TipListCategoriesPerPage;\n    if (page != currentPage) {\n      if (currentPage == 0) pageItems->Show();\n      currentPage = page;\n      currentY = TipListInitialY;\n      ItemsList.Add(pageItems);\n      pageItems = new Group(this);\n    }\n\n    // Start new category\n    // We take a character from the sort string and use that as the category\n    // name inside a predefined template\n    if (record->CategoryLetterIndex != currentCategoryId) {\n      currentCategoryId = record->CategoryLetterIndex;\n      CategoryString[1] = UnalignedRead<uint16_t>(\n          &sortString[currentCategoryId * sizeof(uint16_t)]);\n\n      Label* categoryLabel = new Label();\n      categoryLabel->Bounds.X = TipListEntryBounds.X;\n      categoryLabel->Bounds.Y = currentY;\n      Vm::Sc3Stream categoryStrStream(\n          reinterpret_cast<uint8_t*>(CategoryString));\n      categoryLabel->SetText(categoryStrStream, TipListEntryFontSize,\n                             RendererOutlineMode::Full, 0);\n      pageItems->Add(categoryLabel);\n      currentY += TipListYPadding;\n    }\n\n    // Actual tip entry button\n    TipListEntryBounds.Y = currentY;\n    TipsEntryButton* button =\n        new TipsEntryButton(record->Id, record, TipListEntryBounds,\n                            Profile::BacklogMenu::EntryHighlight);\n    button->OnClickHandler = onClick;\n\n    pageItems->Add(button, FDIR_DOWN);\n    currentY += TipListYPadding;\n  }\n\n  // Add last category\n  ItemsList.Add(pageItems);\n\n  // Number label\n  NumberText = new Label(Vm::ScriptGetTextTableStrAddress(NumberLabelStrTable,\n                                                          NumberLabelStrIndex),\n                         NumberLabelPosition, NumberLabelFontSize,\n                         RendererOutlineMode::Full, DefaultColorIndex);\n  TipViewItems.Add(NumberText);\n  // Tip number\n  Number = new Label();\n  Number->Bounds = NumberBounds;\n  TipViewItems.Add(Number);\n  // Tip page separator\n  PageSeparator = new Label(\n      Vm::ScriptGetTextTableStrAddress(PageSeparatorTable, PageSeparatorIndex),\n      PageSeparatorPosition, PageSeparatorFontSize, RendererOutlineMode::Full,\n      DefaultColorIndex);\n  TipViewItems.Add(PageSeparator);\n  // Current tip page\n  CurrentPage = new Label();\n  CurrentPage->Bounds = CurrentPageBounds;\n  TipViewItems.Add(CurrentPage);\n  // Total tip pages\n  TotalPages = new Label();\n  TotalPages->Bounds = TotalPagesBounds;\n  TipViewItems.Add(TotalPages);\n}\n\nvoid TipsMenu::SwitchToTipId(int id) {\n  CurrentlyDisplayedTipId = id;\n  CurrentTipPage = 1;\n  TipsSystem::SetTipUnreadState(id, false);\n  TipsSystem::SetTipNewState(id, false);\n  auto tipsScriptBufferId = TipsSystem::GetTipsScriptBufferId();\n\n  auto tipRecord = TipsSystem::GetTipRecord(id);\n  Name->SetText({.ScriptBufferId = tipsScriptBufferId,\n                 .IpOffset = tipRecord->StringAdr[0]},\n                NameFontSize, RendererOutlineMode::Full, DefaultColorIndex);\n  Pronounciation->SetText({.ScriptBufferId = tipsScriptBufferId,\n                           .IpOffset = tipRecord->StringAdr[1]},\n                          PronounciationFontSize, RendererOutlineMode::Full,\n                          DefaultColorIndex);\n\n  Vm::Sc3VmThread dummy;\n  dummy.IpOffset = tipRecord->StringAdr[2];\n  dummy.ScriptBufferId = tipsScriptBufferId;\n  float categoryWidth = TextGetPlainLineWidth(\n      &dummy, Profile::Dialogue::DialogueFont, CategoryFontSize);\n  Category->Bounds.X = CategoryEndX - categoryWidth;\n  Category->SetText({.ScriptBufferId = tipsScriptBufferId,\n                     .IpOffset = tipRecord->StringAdr[2]},\n                    CategoryFontSize, RendererOutlineMode::Full,\n                    DefaultColorIndex);\n  if (tipRecord->ThumbnailIndex != 0xFFFF)\n    ThumbnailSprite = &TipThumbnails[tipRecord->ThumbnailIndex];\n  else\n    ThumbnailSprite = &TipTextOnlyThumbnail;\n\n  Number->SetText(fmt::format(\"{:4d}\", tipRecord->Id + 1), NumberFontSize,\n                  RendererOutlineMode::Full, DefaultColorIndex);\n\n  CurrentPage->SetText(fmt::to_string(CurrentTipPage), PageSeparatorFontSize,\n                       RendererOutlineMode::Full, DefaultColorIndex);\n\n  TotalPages->SetText(fmt::to_string(tipRecord->NumberOfContentStrings),\n                      PageSeparatorFontSize, RendererOutlineMode::Full,\n                      DefaultColorIndex);\n\n  TextPage.Clear();\n  dummy.IpOffset = tipRecord->StringAdr[3];\n  dummy.ScriptBufferId = tipsScriptBufferId;\n  TextPage.AddString(&dummy);\n}\n\nvoid TipsMenu::AdvanceTipPage(TipAdvanceMode mode) {\n  auto currentRecord = TipsSystem::GetTipRecord(CurrentlyDisplayedTipId);\n  const int numberOfContentStrings =\n      static_cast<int>(currentRecord->NumberOfContentStrings);\n  if (numberOfContentStrings == 1) return;\n  CurrentTipPage += mode == TipAdvanceMode::PrevClamped ? -1 : 1;\n  switch (mode) {\n    case TipAdvanceMode::PrevClamped: {\n      CurrentTipPage = std::max(CurrentTipPage, 1);\n      break;\n    }\n    case TipAdvanceMode::NextClamped: {\n      CurrentTipPage = std::min(CurrentTipPage, numberOfContentStrings);\n      break;\n    }\n    case TipAdvanceMode::NextLooped: {\n      if (CurrentTipPage > numberOfContentStrings) CurrentTipPage = 1;\n      break;\n    }\n  }\n\n  TextPage.Clear();\n  Vm::Sc3VmThread dummy;\n  dummy.IpOffset = currentRecord->StringAdr[2 + CurrentTipPage];\n  dummy.ScriptBufferId = TipsSystem::GetTipsScriptBufferId();\n  TextPage.AddString(&dummy);\n  CurrentPage->SetText(fmt::to_string(CurrentTipPage), PageSeparatorFontSize,\n                       RendererOutlineMode::Full, DefaultColorIndex);\n}\n\n}  // namespace MO6TW\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/mo6tw/tipsmenu.h",
    "content": "#pragma once\n\n#include \"../../ui/tipsmenu.h\"\n#include \"../../ui/widgets/group.h\"\n#include \"../../ui/widgets/button.h\"\n#include \"../../ui/widgets/carousel.h\"\n#include \"../../ui/widgets/label.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace MO6TW {\n\nclass TipsMenu : public UI::TipsMenu {\n public:\n  TipsMenu();\n\n  void Init();\n\n  void Show();\n  void Hide();\n  void UpdateInput(float dt);\n  void Update(float dt);\n  void Render();\n\n  void TipOnClick(Widgets::Button* target);\n\n protected:\n  void SwitchToTipId(int id);\n  void AdvanceTipPage(TipAdvanceMode mode);\n\n private:\n  int CurrentTipPage = 1;\n\n  Widgets::Carousel ItemsList;\n  Widgets::Group TipViewItems;\n  Sprite* ThumbnailSprite;\n  Widgets::Label* PageSeparator;\n  Widgets::Label* CurrentPage;\n  Widgets::Label* TotalPages;\n};\n\n}  // namespace MO6TW\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/mo6tw/tipssystem.cpp",
    "content": "#include \"tipssystem.h\"\n\n#include \"../../data/savesystem.h\"\n#include \"../../vm/vm.h\"\n#include \"../../io/memorystream.h\"\n#include \"../../profile/data/tipssystem.h\"\n#include \"../../ui/ui.h\"\n\nnamespace Impacto {\nnamespace MO6TW {\n\nusing namespace Impacto::Vm;\nusing namespace Impacto::Profile::TipsSystem;\nusing namespace Impacto::Io;\n\nvoid TipsSystem::DataInit(uint32_t scriptBufferId, uint32_t tipsDataAdr,\n                          uint32_t tipsDataSize) {\n  auto scriptBuffer = ScriptBuffers[scriptBufferId];\n  ScriptBufferId = static_cast<uint8_t>(scriptBufferId);\n  // String of characters by which tips are sorted, taken from _system script\n  // auto sortString = (uint16_t *)ScriptGetTextTableStrAddress(2, 5);\n\n  int idx = 0;\n\n  // Read tips data from the script and create UI elements for each tip\n  MemoryStream* stream =\n      new MemoryStream(&scriptBuffer[tipsDataAdr], tipsDataSize);\n  auto unk01 = ReadLE<uint16_t>(stream);\n  while (unk01 != 255) {\n    // Read tip entry from the data array\n    TipsDataRecord record{.IsLocked = false, .IsUnread = false, .IsNew = false};\n    record.Id = (uint16_t)idx;\n    // I don't know, I don't care, this is not my magic\n    record.CategoryLetterIndex = (unk01 - 5 * ((unk01 + 1) / 10) - 6);\n    record.ThumbnailIndex = ReadLE<uint16_t>(stream);\n    record.NumberOfContentStrings = ReadLE<uint16_t>(stream);\n    for (int i = 0; i < record.NumberOfContentStrings + 3; i++) {\n      record.StringAdr[i] =\n          ScriptGetStrAddress(scriptBufferId, ReadLE<uint16_t>(stream));\n    }\n    Records[idx] = std::move(record);\n\n    // Next tip entry from the data array\n    unk01 = Io::ReadLE<uint16_t>(stream);\n    idx += 1;\n    TipEntryCount = idx;\n  }\n  Records.resize(TipEntryCount);\n\n  delete stream;\n}\n\nvoid TipsSystem::UpdateTipRecords() {\n  if (TipEntryCount != 0) {\n    for (size_t i = 0; i < TipEntryCount; i++) {\n      auto record = &Records[i];\n      auto tipStatus = SaveSystem::GetTipStatus(record->Id);\n      record->IsLocked = (tipStatus & 1) == 0;\n      record->IsUnread = (tipStatus & 2) == 0;\n      record->IsNew = (tipStatus & 4) == 0;\n    }\n  }\n}\n\nvoid TipsSystem::SetTipLockedState(size_t id, bool state) {\n  Records[id].IsLocked = state;\n  SaveSystem::SetTipStatus(id, Records[id].IsLocked, Records[id].IsUnread,\n                           Records[id].IsNew);\n}\n\nvoid TipsSystem::SetTipUnreadState(size_t id, bool state) {\n  Records[id].IsUnread = state;\n  SaveSystem::SetTipStatus(id, Records[id].IsLocked, Records[id].IsUnread,\n                           Records[id].IsNew);\n}\n\nvoid TipsSystem::SetTipNewState(size_t id, bool state) {\n  Records[id].IsNew = state;\n  SaveSystem::SetTipStatus(id, Records[id].IsLocked, Records[id].IsUnread,\n                           Records[id].IsNew);\n}\n\nbool TipsSystem::GetTipLockedState(size_t id) { return Records[id].IsLocked; }\n\n}  // namespace MO6TW\n}  // namespace Impacto"
  },
  {
    "path": "src/games/mo6tw/tipssystem.h",
    "content": "#pragma once\n\n#include \"../../data/tipssystem.h\"\n\nnamespace Impacto {\nnamespace MO6TW {\n\nusing namespace Impacto::TipsSystem;\n\nclass TipsSystem : public TipsSystemBase {\n public:\n  TipsSystem(size_t maxTipsCount) : TipsSystemBase(maxTipsCount) {};\n\n  void DataInit(uint32_t scriptBufferId, uint32_t tipsDataAdr,\n                uint32_t tipsDataSize) override;\n  void UpdateTipRecords() override;\n  void SetTipLockedState(size_t id, bool state) override;\n  void SetTipUnreadState(size_t id, bool state) override;\n  void SetTipNewState(size_t id, bool state) override;\n\n  bool GetTipLockedState(size_t id) override;\n};\n\n}  // namespace MO6TW\n}  // namespace Impacto"
  },
  {
    "path": "src/games/mo6tw/titlemenu.cpp",
    "content": "#include \"titlemenu.h\"\n\n#include \"../../renderer/renderer.h\"\n#include \"../../mem.h\"\n#include \"../../inputsystem.h\"\n#include \"../../ui/widgets/label.h\"\n#include \"../../profile/ui/titlemenu.h\"\n#include \"../../profile/games/mo6tw/titlemenu.h\"\n#include \"../../profile/scriptvars.h\"\n#include \"../../profile/game.h\"\n#include \"../../mask2d.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace MO6TW {\n\nusing namespace Impacto::Profile::TitleMenu;\nusing namespace Impacto::Profile::MO6TW::TitleMenu;\nusing namespace Impacto::Profile::ScriptVars;\n\nusing namespace Impacto::UI::Widgets::MO6TW;\n\nvoid TitleMenu::MenuButtonOnClick(Widgets::Button* target) {\n  target->Hovered = false;\n  ScrWork[SW_TITLECUR1] = target->Id;\n  ChoiceMade = true;\n}\n\nvoid TitleMenu::SecondaryButtonOnClick(Widgets::Button* target) {\n  target->Hovered = false;\n  ScrWork[SW_TITLECUR2] = target->Id;\n  ChoiceMade = true;\n}\n\nTitleMenu::TitleMenu() {\n  MainItems = new Widgets::Group(this);\n  ContinueItems = new Widgets::Group(this);\n  SystemItems = new Widgets::Group(this);\n  ExtraStoryItems = new Widgets::Group(this);\n  MemoriesItems = new Widgets::Group(this);\n\n  auto onClick = [this](auto* btn) { return MenuButtonOnClick(btn); };\n  auto secondaryOnClick = [this](auto* btn) {\n    return SecondaryButtonOnClick(btn);\n  };\n\n  Sprite nullSprite = Sprite();\n  nullSprite.Bounds = RectF(0.0f, 0.0f, 0.0f, 0.0f);\n\n  // Initial Start menu button\n  InitialStart =\n      new TitleButton(0, MenuEntriesSprites[0], MenuEntriesHSprites[0],\n                      nullSprite, glm::vec2(MenuEntriesX, MenuEntriesFirstY));\n  InitialStart->OnClickHandler = onClick;\n  MainItems->Add(InitialStart, FDIR_DOWN);\n\n  // Lockable Extra Story menu button\n  ExtraStory = new TitleButton(\n      1, MenuEntriesSprites[1], MenuEntriesHSprites[1], nullSprite,\n      glm::vec2(MenuEntriesX, MenuEntriesFirstY + (1 * MenuEntriesYPadding)));\n  ExtraStory->Enabled = false;\n  ExtraStory->OnClickHandler = onClick;\n  ExtraStory->DisabledSprite = MenuItemLockedSprite;\n  MainItems->Add(ExtraStory, FDIR_DOWN);\n\n  // Continue menu button\n  Continue = new TitleButton(\n      2, MenuEntriesSprites[2], MenuEntriesHSprites[2], nullSprite,\n      glm::vec2(MenuEntriesX, MenuEntriesFirstY + (2 * MenuEntriesYPadding)));\n  Continue->OnClickHandler = onClick;\n  MainItems->Add(Continue, FDIR_DOWN);\n\n  // Lockable Memories menu button\n  Memories = new TitleButton(\n      3, MenuEntriesSprites[3], MenuEntriesHSprites[3], nullSprite,\n      glm::vec2(MenuEntriesX, MenuEntriesFirstY + (3 * MenuEntriesYPadding)));\n  Memories->Enabled = false;\n  Memories->OnClickHandler = onClick;\n  Memories->DisabledSprite = MenuItemLockedSprite;\n  MainItems->Add(Memories, FDIR_DOWN);\n\n  // Encyclopedia menu button\n  Encyclopedia = new TitleButton(\n      4, MenuEntriesSprites[4], MenuEntriesHSprites[4], nullSprite,\n      glm::vec2(MenuEntriesX, MenuEntriesFirstY + (4 * MenuEntriesYPadding)));\n  Encyclopedia->OnClickHandler = onClick;\n  MainItems->Add(Encyclopedia, FDIR_DOWN);\n\n  // System menu button\n  System = new TitleButton(\n      5, MenuEntriesSprites[5], MenuEntriesHSprites[5], nullSprite,\n      glm::vec2(MenuEntriesX, MenuEntriesFirstY + (5 * MenuEntriesYPadding)));\n  System->OnClickHandler = onClick;\n  MainItems->Add(System, FDIR_DOWN);\n\n  // Exit menu button\n  Exit = new TitleButton(\n      6, MenuEntriesSprites[6], MenuEntriesHSprites[6], nullSprite,\n      glm::vec2(MenuEntriesX, MenuEntriesFirstY + (6 * MenuEntriesYPadding)));\n  Exit->OnClickHandler = onClick;\n  MainItems->Add(Exit, FDIR_DOWN);\n\n  // Load secondary Continue menu button\n  Load = new TitleButton(\n      0, MenuEntriesSprites[10], MenuEntriesHSprites[10], nullSprite,\n      glm::vec2(MenuEntriesX, MenuEntriesFirstY + (3 * MenuEntriesYPadding)));\n  Load->OnClickHandler = secondaryOnClick;\n  ContinueItems->Add(Load, FDIR_DOWN);\n\n  // Quick Load secondary Continue menu button\n  QuickLoad = new TitleButton(\n      1, MenuEntriesSprites[11], MenuEntriesHSprites[11], nullSprite,\n      glm::vec2(MenuEntriesX - 20.0f,\n                MenuEntriesFirstY + (4 * MenuEntriesYPadding)));\n  QuickLoad->OnClickHandler = secondaryOnClick;\n  ContinueItems->Add(QuickLoad, FDIR_DOWN);\n\n  // Prologue secondary Extra Story menu button\n  Prologue = new TitleButton(\n      0, MenuEntriesSprites[7], MenuEntriesHSprites[7], nullSprite,\n      glm::vec2(MenuEntriesX, MenuEntriesFirstY + (2 * MenuEntriesYPadding)));\n  Prologue->OnClickHandler = secondaryOnClick;\n  ExtraStoryItems->Add(Prologue, FDIR_DOWN);\n\n  // Otome Judge feat.Shin secondary Extra Story menu button\n  OtomeJudgeShin = new TitleButton(\n      1, MenuEntriesSprites[8], MenuEntriesHSprites[8], nullSprite,\n      glm::vec2(MenuEntriesX - 20.0f,\n                MenuEntriesFirstY + (3 * MenuEntriesYPadding)));\n  OtomeJudgeShin->OnClickHandler = secondaryOnClick;\n  ExtraStoryItems->Add(OtomeJudgeShin, FDIR_DOWN);\n\n  // Warning secondary Extra Story menu button\n  Warning = new TitleButton(\n      2, MenuEntriesSprites[9], MenuEntriesHSprites[9], nullSprite,\n      glm::vec2(MenuEntriesX - 40.0f,\n                MenuEntriesFirstY + (4 * MenuEntriesYPadding)));\n  Warning->OnClickHandler = secondaryOnClick;\n  ExtraStoryItems->Add(Warning, FDIR_DOWN);\n\n  // Warning secondary Extra Story menu button\n  ClearList = new TitleButton(\n      0, MenuEntriesSprites[13], MenuEntriesHSprites[13], nullSprite,\n      glm::vec2(MenuEntriesX, MenuEntriesFirstY + (4 * MenuEntriesYPadding)));\n  ClearList->OnClickHandler = secondaryOnClick;\n  MemoriesItems->Add(ClearList, FDIR_DOWN);\n\n  // Warning secondary Extra Story menu button\n  Album = new TitleButton(\n      1, MenuEntriesSprites[14], MenuEntriesHSprites[14], nullSprite,\n      glm::vec2(MenuEntriesX - 20.0f,\n                MenuEntriesFirstY + (5 * MenuEntriesYPadding)));\n  Album->OnClickHandler = secondaryOnClick;\n  MemoriesItems->Add(Album, FDIR_DOWN);\n\n  // Warning secondary Extra Story menu button\n  Music = new TitleButton(\n      2, MenuEntriesSprites[15], MenuEntriesHSprites[15], nullSprite,\n      glm::vec2(MenuEntriesX - 40.0f,\n                MenuEntriesFirstY + (6 * MenuEntriesYPadding)));\n  Music->OnClickHandler = secondaryOnClick;\n  MemoriesItems->Add(Music, FDIR_DOWN);\n\n  // Warning secondary Extra Story menu button\n  Movie = new TitleButton(\n      3, MenuEntriesSprites[16], MenuEntriesHSprites[16], nullSprite,\n      glm::vec2(MenuEntriesX - 60.0f,\n                MenuEntriesFirstY + (7 * MenuEntriesYPadding)));\n  Movie->OnClickHandler = secondaryOnClick;\n  MemoriesItems->Add(Movie, FDIR_DOWN);\n\n  // Warning secondary Extra Story menu button\n  ActorsVoice = new TitleButton(\n      4, MenuEntriesSprites[17], MenuEntriesHSprites[17], nullSprite,\n      glm::vec2(MenuEntriesX - 80.0f,\n                MenuEntriesFirstY + (8 * MenuEntriesYPadding)));\n  ActorsVoice->OnClickHandler = secondaryOnClick;\n  MemoriesItems->Add(ActorsVoice, FDIR_DOWN);\n\n  // Option secondary System menu button\n  Option = new TitleButton(\n      0, MenuEntriesSprites[18], MenuEntriesHSprites[18], nullSprite,\n      glm::vec2(MenuEntriesX, MenuEntriesFirstY + (6 * MenuEntriesYPadding)));\n  Option->OnClickHandler = secondaryOnClick;\n  SystemItems->Add(Option, FDIR_DOWN);\n\n  // System Save secondary System menu button\n  SystemSave = new TitleButton(\n      1, MenuEntriesSprites[20], MenuEntriesHSprites[20], nullSprite,\n      glm::vec2(MenuEntriesX - 20.0f,\n                MenuEntriesFirstY + (7 * MenuEntriesYPadding)));\n  SystemSave->OnClickHandler = secondaryOnClick;\n  SystemItems->Add(SystemSave, FDIR_DOWN);\n}\n\nvoid TitleMenu::Show() {\n  if (State == Hidden) {\n    State = Shown;\n    if (UI::FocusedMenu != 0) {\n      LastFocusedMenu = UI::FocusedMenu;\n      LastFocusedMenu->IsFocused = false;\n    }\n    IsFocused = true;\n    UI::FocusedMenu = this;\n    if (PressToStartAnimation.State == AnimationState::Stopped)\n      PressToStartAnimation.StartIn();\n  }\n}\nvoid TitleMenu::Hide() {\n  if (State == Shown) {\n    State = Hidden;\n    MainItems->Hide();\n    if (LastFocusedMenu != 0) {\n      UI::FocusedMenu = LastFocusedMenu;\n      LastFocusedMenu->IsFocused = true;\n    } else {\n      UI::FocusedMenu = 0;\n    }\n    IsFocused = false;\n  }\n}\n\nvoid TitleMenu::Update(float dt) {\n  UpdateInput(dt);\n\n  PressToStartAnimation.Update(dt);\n  PrimaryFadeAnimation.Update(dt);\n  SecondaryFadeAnimation.Update(dt);\n  if (GetFlag(SF_TITLEMODE)) {\n    Show();\n  } else if (ScrWork[SW_TITLECT] < 32) {\n    Hide();\n  }\n\n  if (State == Shown && IsFocused) {\n    ExtraStory->Enabled = GetFlag(867);\n    Memories->Enabled = (GetFlag(860) || GetFlag(861) || GetFlag(862) ||\n                         GetFlag(863) || GetFlag(864));\n\n    // Do not update these when it's not the time to update them (press to\n    // start portion)\n    if (ScrWork[SW_TITLEDISPCT] != 1 && ScrWork[SW_TITLEDISPCT] != 20 &&\n        ScrWork[SW_TITLEDISPCT] != 21) {\n      MainItems->Tint.a =\n          glm::smoothstep(0.0f, 1.0f, PrimaryFadeAnimation.Progress);\n      MainItems->Update(dt);\n      MainItems->UpdateInput(dt);\n      ContinueItems->Tint.a =\n          glm::smoothstep(0.0f, 1.0f, SecondaryFadeAnimation.Progress);\n      ContinueItems->Update(dt);\n      ContinueItems->UpdateInput(dt);\n      SystemItems->Tint.a =\n          glm::smoothstep(0.0f, 1.0f, SecondaryFadeAnimation.Progress);\n      SystemItems->Update(dt);\n      SystemItems->UpdateInput(dt);\n      ExtraStoryItems->Tint.a =\n          glm::smoothstep(0.0f, 1.0f, SecondaryFadeAnimation.Progress);\n      ExtraStoryItems->Update(dt);\n      ExtraStoryItems->UpdateInput(dt);\n      MemoriesItems->Tint.a =\n          glm::smoothstep(0.0f, 1.0f, SecondaryFadeAnimation.Progress);\n      MemoriesItems->Update(dt);\n      MemoriesItems->UpdateInput(dt);\n    }\n\n    switch (ScrWork[SW_TITLEDISPCT]) {\n      case 0: {\n        // When returning to title menu from loading a game we need to hide the\n        // continue sub-menu and extra story sub-menu\n        if (ContinueItems->VisibilityState != Hidden) {\n          MainItems->HasFocus = true;\n          SecondaryFadeAnimation.StartOut();\n          Load->Move(-SecondaryMenuAnimTarget);\n          QuickLoad->Move(\n              glm::vec2(-(SecondaryMenuAnimTarget.x + SecondaryMenuPadding),\n                        SecondaryMenuAnimTarget.y));\n\n          Memories->Move(glm::vec2(SecondaryMenuAnimUnderX,\n                                   -ContinueItemCount * MenuEntriesYPadding));\n          Encyclopedia->Move(\n              glm::vec2(SecondaryMenuAnimUnderX,\n                        -ContinueItemCount * MenuEntriesYPadding));\n          System->Move(glm::vec2(SecondaryMenuAnimUnderX,\n                                 -ContinueItemCount * MenuEntriesYPadding));\n          Exit->Move(glm::vec2(SecondaryMenuAnimUnderX,\n                               -ContinueItemCount * MenuEntriesYPadding));\n          ContinueItems->Hide();\n        }\n        if (ExtraStoryItems->VisibilityState != Hidden) {\n          MainItems->HasFocus = true;\n          SecondaryFadeAnimation.StartOut();\n          Prologue->Move(-SecondaryMenuAnimTarget);\n          OtomeJudgeShin->Move(\n              glm::vec2(-(SecondaryMenuAnimTarget.x + SecondaryMenuPadding),\n                        SecondaryMenuAnimTarget.y));\n          Warning->Move(glm::vec2(\n              -(SecondaryMenuAnimTarget.x + (2 * SecondaryMenuPadding)),\n              SecondaryMenuAnimTarget.y));\n          Continue->Move(glm::vec2(SecondaryMenuAnimUnderX,\n                                   -ExtraStoryItemCount * MenuEntriesYPadding));\n          Memories->Move(glm::vec2(SecondaryMenuAnimUnderX,\n                                   -ExtraStoryItemCount * MenuEntriesYPadding));\n          Encyclopedia->Move(\n              glm::vec2(SecondaryMenuAnimUnderX,\n                        -ExtraStoryItemCount * MenuEntriesYPadding));\n          System->Move(glm::vec2(SecondaryMenuAnimUnderX,\n                                 -ExtraStoryItemCount * MenuEntriesYPadding));\n          Exit->Move(glm::vec2(SecondaryMenuAnimUnderX,\n                               -ExtraStoryItemCount * MenuEntriesYPadding));\n          ExtraStoryItems->Hide();\n        }\n      } break;\n      case 3: {  // Main Menu Fade In\n        if (MainItems->VisibilityState == Hidden && ScrWork[SW_TITLECT] == 0) {\n          MainItems->Show();\n          MainItems->Tint.a = 0.0f;\n          PrimaryFadeAnimation.StartIn();\n        }\n      } break;\n      case 5: {  // Secondary menu Extra Story Fade In\n        if (ExtraStoryItems->VisibilityState != Shown &&\n            ScrWork[SW_TITLECT] == 0) {\n          ShowExtraStoryItems();\n        } else if (ExtraStoryItems->VisibilityState != Hidden &&\n                   ScrWork[SW_TITLECT] == 32) {\n          HideExtraStoryItems();\n        } else if (ScrWork[SW_TITLECT] == 0) {\n          ExtraStoryItems->Hide();\n          MainItems->HasFocus = true;\n        }\n      } break;\n      case 7: {  // Secondary menu Continue Fade In\n        if (ContinueItems->VisibilityState != Shown &&\n            ScrWork[SW_TITLECT] == 0) {\n          ShowContinueItems();\n        } else if (ContinueItems->VisibilityState != Hidden &&\n                   ScrWork[SW_TITLECT] == 32) {\n          HideContinueItems();\n        } else if (ScrWork[SW_TITLECT] == 0) {\n          ContinueItems->Hide();\n          MainItems->HasFocus = true;\n        }\n      } break;\n      case 9: {  // Secondary menu Memories Fade In\n        if (MemoriesItems->VisibilityState != Shown &&\n            ScrWork[SW_TITLECT] == 0) {\n          ShowMemoriesItems();\n        } else if (MemoriesItems->VisibilityState != Hidden &&\n                   ScrWork[SW_TITLECT] == 32) {\n          HideMemoriesItems();\n        } else if (ScrWork[SW_TITLECT] == 0) {\n          MemoriesItems->Hide();\n          MainItems->HasFocus = true;\n        }\n      } break;\n      case 10: {\n        if (MemoriesItems->VisibilityState != Hidden &&\n            MainItems->VisibilityState == Hidden) {\n          MainItems->VisibilityState = Shown;\n        }\n      } break;\n      case 11: {  // Secondary menu System Fade In\n        if (SystemItems->VisibilityState != Shown && ScrWork[SW_TITLECT] == 0) {\n          ShowSystemItems();\n        } else if (SystemItems->VisibilityState != Hidden &&\n                   ScrWork[SW_TITLECT] == 32) {\n          HideSystemItems();\n        } else if (ScrWork[SW_TITLECT] == 0) {\n          SystemItems->Hide();\n          MainItems->HasFocus = true;\n        }\n      } break;\n    }\n  }\n}\n\nvoid TitleMenu::Render() {\n  if (State != Hidden && GetFlag(SF_TITLEMODE)) {\n    if (ScrWork[SW_MENUCT] != 100) {\n      switch (ScrWork[SW_TITLEDISPCT]) {\n        case 0:  // Initial animation\n          Renderer->DrawMaskedSprite(\n              BackgroundSprite, BackgroundSprite,\n              RectF(0.0f, 0.0f, BackgroundSprite.ScaledWidth(),\n                    BackgroundSprite.ScaledHeight()),\n              (ScrWork[SW_TITLECT] * 287) / 48, 32, glm::mat4(1.0f),\n              glm::vec4(1.0f), false, true);\n          if (ScrWork[SW_TITLECT] > 48) {\n            Renderer->DrawMaskedSprite(\n                LogoSprite, Masks2D[17].MaskSprite,\n                RectF(LogoX, LogoY, LogoSprite.ScaledWidth(),\n                      LogoSprite.ScaledHeight()),\n                (ScrWork[SW_TITLECT] * 271 - 13008) / 32, 16);\n          }\n          break;\n        case 1: {  // Press to start\n          Renderer->DrawMaskedSprite(\n              BackgroundSprite, BackgroundSprite,\n              RectF(0.0f, 0.0f, BackgroundSprite.ScaledWidth(),\n                    BackgroundSprite.ScaledHeight()),\n              287, 32, glm::mat4(1.0f), glm::vec4(1.0f), false, true);\n          Renderer->DrawSprite(LogoSprite, glm::vec2(LogoX, LogoY));\n          Renderer->DrawSprite(CopyrightSprite,\n                               glm::vec2(CopyrightX, CopyrightY));\n          glm::vec4 col = glm::vec4(1.0f);\n          col.a = glm::smoothstep(0.0f, 1.0f, PressToStartAnimation.Progress);\n          Renderer->DrawSprite(PressToStartSprite,\n                               glm::vec2(PressToStartX, PressToStartY), col);\n        } break;\n        case 2: {\n          Renderer->DrawMaskedSprite(\n              BackgroundSprite, BackgroundSprite,\n              RectF(0.0f, 0.0f, BackgroundSprite.ScaledWidth(),\n                    BackgroundSprite.ScaledHeight()),\n              287, 32, glm::mat4(1.0f), glm::vec4(1.0f), false, true);\n          Renderer->DrawSprite(LogoSprite, glm::vec2(LogoX, LogoY));\n          Renderer->DrawSprite(CopyrightSprite,\n                               glm::vec2(CopyrightX, CopyrightY));\n        } break;\n        case 3:    // Main Menu Fade In\n        case 4: {  // Main Menu\n          Renderer->DrawMaskedSprite(\n              MenuBackgroundSprite, MenuBackgroundSprite,\n              RectF(0.0f, 0.0f, MenuBackgroundSprite.ScaledWidth(),\n                    MenuBackgroundSprite.ScaledHeight()),\n              287, 32, glm::mat4(1.0f), glm::vec4(1.0f), false, true);\n          MainItems->Render();\n        } break;\n        case 5:    // Secondary menu Extra story Fade In\n        case 6: {  // Secondary menu Extra story\n          Renderer->DrawMaskedSprite(\n              MenuBackgroundSprite, MenuBackgroundSprite,\n              RectF(0.0f, 0.0f, MenuBackgroundSprite.ScaledWidth(),\n                    MenuBackgroundSprite.ScaledHeight()),\n              287, 32, glm::mat4(1.0f), glm::vec4(1.0f), false, true);\n          ExtraStoryItems->Render();\n          MainItems->Render();\n        } break;\n        case 7:    // Secondary menu Continue Fade In\n        case 8: {  // Secondary menu Continue\n          Renderer->DrawMaskedSprite(\n              MenuBackgroundSprite, MenuBackgroundSprite,\n              RectF(0.0f, 0.0f, MenuBackgroundSprite.ScaledWidth(),\n                    MenuBackgroundSprite.ScaledHeight()),\n              287, 32, glm::mat4(1.0f), glm::vec4(1.0f), false, true);\n          ContinueItems->Render();\n          MainItems->Render();\n        } break;\n        case 9:     // Secondary menu Memories Fade In\n        case 10: {  // Secondary menu Memories\n          Renderer->DrawMaskedSprite(\n              MenuBackgroundSprite, MenuBackgroundSprite,\n              RectF(0.0f, 0.0f, MenuBackgroundSprite.ScaledWidth(),\n                    MenuBackgroundSprite.ScaledHeight()),\n              287, 32, glm::mat4(1.0f), glm::vec4(1.0f), false, true);\n          MemoriesItems->Render();\n          MainItems->Render();\n        } break;\n        case 11:    // Secondary menu System Fade In\n        case 12: {  // Secondary menu System\n          Renderer->DrawMaskedSprite(\n              MenuBackgroundSprite, MenuBackgroundSprite,\n              RectF(0.0f, 0.0f, MenuBackgroundSprite.ScaledWidth(),\n                    MenuBackgroundSprite.ScaledHeight()),\n              287, 32, glm::mat4(1.0f), glm::vec4(1.0f), false, true);\n          SystemItems->Render();\n          MainItems->Render();\n        } break;\n        case 20:\n        case 21: {  // Transition between Press to start and menus\n          Renderer->DrawMaskedSprite(\n              BackgroundSprite, BackgroundSprite,\n              RectF(0.0f, 0.0f, BackgroundSprite.ScaledWidth(),\n                    BackgroundSprite.ScaledHeight()),\n              287, 32, glm::mat4(1.0f), glm::vec4(1.0f), false, true);\n          Renderer->DrawMaskedSprite(\n              LogoSprite, Masks2D[17].MaskSprite,\n              RectF(LogoX, LogoY, LogoSprite.ScaledWidth(),\n                    LogoSprite.ScaledHeight()),\n              287, 16);\n          Renderer->DrawSprite(CopyrightSprite,\n                               glm::vec2(CopyrightX, CopyrightY));\n          Renderer->DrawMaskedSprite(\n              MenuBackgroundSprite, MenuBackgroundSprite,\n              RectF(0.0f, 0.0f, MenuBackgroundSprite.ScaledWidth(),\n                    MenuBackgroundSprite.ScaledHeight()),\n              (ScrWork[SW_TITLECT] * 287) / 48, 32, glm::mat4(1.0f),\n              glm::vec4(1.0f), false, true);\n        } break;\n      }\n    }\n\n    int maskAlpha = ScrWork[SW_TITLEMASKALPHA];\n    glm::vec4 col = ScrWorkGetColor(SW_TITLEMASKCOLOR);\n    col.a = glm::min(maskAlpha / 255.0f, 1.0f);\n    Renderer->DrawQuad(\n        RectF(0.0f, 0.0f, Profile::DesignWidth, Profile::DesignHeight), col);\n  }\n}\n\nvoid TitleMenu::ShowExtraStoryItems() {\n  ExtraStoryItems->Show();\n  ExtraStoryItems->Tint.a = 0.0f;\n  MainItems->HasFocus = false;\n\n  SecondaryFadeAnimation.StartIn();\n\n  Prologue->Move(SecondaryMenuAnimTarget, SecondaryMenuAnimDuration);\n  OtomeJudgeShin->Move(\n      glm::vec2(SecondaryMenuAnimTarget.x + SecondaryMenuPadding,\n                SecondaryMenuAnimTarget.y),\n      SecondaryMenuAnimDuration);\n  Warning->Move(\n      glm::vec2(SecondaryMenuAnimTarget.x + (2 * SecondaryMenuPadding),\n                SecondaryMenuAnimTarget.y),\n      SecondaryMenuAnimDuration);\n\n  Continue->Move(glm::vec2(SecondaryMenuAnimUnderX,\n                           ExtraStoryItemCount * MenuEntriesYPadding),\n                 SecondaryMenuAnimDuration);\n  Memories->Move(glm::vec2(SecondaryMenuAnimUnderX,\n                           ExtraStoryItemCount * MenuEntriesYPadding),\n                 SecondaryMenuAnimDuration);\n  Encyclopedia->Move(glm::vec2(SecondaryMenuAnimUnderX,\n                               ExtraStoryItemCount * MenuEntriesYPadding),\n                     SecondaryMenuAnimDuration);\n  System->Move(glm::vec2(SecondaryMenuAnimUnderX,\n                         ExtraStoryItemCount * MenuEntriesYPadding),\n               SecondaryMenuAnimDuration);\n  Exit->Move(glm::vec2(SecondaryMenuAnimUnderX,\n                       ExtraStoryItemCount * MenuEntriesYPadding),\n             SecondaryMenuAnimDuration);\n}\n\nvoid TitleMenu::HideExtraStoryItems() {\n  SecondaryFadeAnimation.StartOut();\n\n  Prologue->Move(-SecondaryMenuAnimTarget, SecondaryMenuAnimDuration);\n  OtomeJudgeShin->Move(\n      glm::vec2(-(SecondaryMenuAnimTarget.x + SecondaryMenuPadding),\n                SecondaryMenuAnimTarget.y),\n      SecondaryMenuAnimDuration);\n  Warning->Move(\n      glm::vec2(-(SecondaryMenuAnimTarget.x + (2 * SecondaryMenuPadding)),\n                SecondaryMenuAnimTarget.y),\n      SecondaryMenuAnimDuration);\n\n  Continue->Move(glm::vec2(SecondaryMenuAnimUnderX,\n                           -ExtraStoryItemCount * MenuEntriesYPadding),\n                 SecondaryMenuAnimDuration);\n  Memories->Move(glm::vec2(SecondaryMenuAnimUnderX,\n                           -ExtraStoryItemCount * MenuEntriesYPadding),\n                 SecondaryMenuAnimDuration);\n  Encyclopedia->Move(glm::vec2(SecondaryMenuAnimUnderX,\n                               -ExtraStoryItemCount * MenuEntriesYPadding),\n                     SecondaryMenuAnimDuration);\n  System->Move(glm::vec2(SecondaryMenuAnimUnderX,\n                         -ExtraStoryItemCount * MenuEntriesYPadding),\n               SecondaryMenuAnimDuration);\n  Exit->Move(glm::vec2(SecondaryMenuAnimUnderX,\n                       -ExtraStoryItemCount * MenuEntriesYPadding),\n             SecondaryMenuAnimDuration);\n}\n\nvoid TitleMenu::ShowContinueItems() {\n  ContinueItems->Show();\n  ContinueItems->Tint.a = 0.0f;\n  MainItems->HasFocus = false;\n\n  SecondaryFadeAnimation.StartIn();\n\n  Load->Move(SecondaryMenuAnimTarget, SecondaryMenuAnimDuration);\n  QuickLoad->Move(glm::vec2(SecondaryMenuAnimTarget.x + SecondaryMenuPadding,\n                            SecondaryMenuAnimTarget.y),\n                  SecondaryMenuAnimDuration);\n\n  Memories->Move(glm::vec2(SecondaryMenuAnimUnderX,\n                           ContinueItemCount * MenuEntriesYPadding),\n                 SecondaryMenuAnimDuration);\n  Encyclopedia->Move(glm::vec2(SecondaryMenuAnimUnderX,\n                               ContinueItemCount * MenuEntriesYPadding),\n                     SecondaryMenuAnimDuration);\n  System->Move(glm::vec2(SecondaryMenuAnimUnderX,\n                         ContinueItemCount * MenuEntriesYPadding),\n               SecondaryMenuAnimDuration);\n  Exit->Move(glm::vec2(SecondaryMenuAnimUnderX,\n                       ContinueItemCount * MenuEntriesYPadding),\n             SecondaryMenuAnimDuration);\n}\n\nvoid TitleMenu::HideContinueItems() {\n  SecondaryFadeAnimation.StartOut();\n\n  Load->Move(-SecondaryMenuAnimTarget, SecondaryMenuAnimDuration);\n  QuickLoad->Move(glm::vec2(-(SecondaryMenuAnimTarget.x + SecondaryMenuPadding),\n                            SecondaryMenuAnimTarget.y),\n                  SecondaryMenuAnimDuration);\n\n  Memories->Move(glm::vec2(SecondaryMenuAnimUnderX,\n                           -ContinueItemCount * MenuEntriesYPadding),\n                 SecondaryMenuAnimDuration);\n  Encyclopedia->Move(glm::vec2(SecondaryMenuAnimUnderX,\n                               -ContinueItemCount * MenuEntriesYPadding),\n                     SecondaryMenuAnimDuration);\n  System->Move(glm::vec2(SecondaryMenuAnimUnderX,\n                         -ContinueItemCount * MenuEntriesYPadding),\n               SecondaryMenuAnimDuration);\n  Exit->Move(glm::vec2(SecondaryMenuAnimUnderX,\n                       -ContinueItemCount * MenuEntriesYPadding),\n             SecondaryMenuAnimDuration);\n}\n\nvoid TitleMenu::ShowMemoriesItems() {\n  MemoriesItems->Show();\n  MemoriesItems->Tint.a = 0.0f;\n  MainItems->HasFocus = false;\n\n  SecondaryFadeAnimation.StartIn();\n\n  ClearList->Move(SecondaryMenuAnimTarget, SecondaryMenuAnimDuration);\n  Album->Move(glm::vec2(SecondaryMenuAnimTarget.x + SecondaryMenuPadding,\n                        SecondaryMenuAnimTarget.y),\n              SecondaryMenuAnimDuration);\n  Music->Move(glm::vec2(SecondaryMenuAnimTarget.x + (2 * SecondaryMenuPadding),\n                        SecondaryMenuAnimTarget.y),\n              SecondaryMenuAnimDuration);\n  Movie->Move(glm::vec2(SecondaryMenuAnimTarget.x + (3 * SecondaryMenuPadding),\n                        SecondaryMenuAnimTarget.y),\n              SecondaryMenuAnimDuration);\n  ActorsVoice->Move(\n      glm::vec2(SecondaryMenuAnimTarget.x + (4 * SecondaryMenuPadding),\n                SecondaryMenuAnimTarget.y),\n      SecondaryMenuAnimDuration);\n\n  Encyclopedia->Move(glm::vec2(SecondaryMenuAnimUnderX,\n                               MemoriesItemCount * MenuEntriesYPadding),\n                     SecondaryMenuAnimDuration);\n  System->Move(glm::vec2(SecondaryMenuAnimUnderX,\n                         MemoriesItemCount * MenuEntriesYPadding),\n               SecondaryMenuAnimDuration);\n  Exit->Move(glm::vec2(SecondaryMenuAnimUnderX,\n                       MemoriesItemCount * MenuEntriesYPadding),\n             SecondaryMenuAnimDuration);\n}\n\nvoid TitleMenu::HideMemoriesItems() {\n  SecondaryFadeAnimation.StartOut();\n\n  ClearList->Move(-SecondaryMenuAnimTarget, SecondaryMenuAnimDuration);\n  Album->Move(glm::vec2(-(SecondaryMenuAnimTarget.x + SecondaryMenuPadding),\n                        SecondaryMenuAnimTarget.y),\n              SecondaryMenuAnimDuration);\n  Music->Move(\n      glm::vec2(-(SecondaryMenuAnimTarget.x + (2 * SecondaryMenuPadding)),\n                SecondaryMenuAnimTarget.y),\n      SecondaryMenuAnimDuration);\n  Movie->Move(\n      glm::vec2(-(SecondaryMenuAnimTarget.x + (3 * SecondaryMenuPadding)),\n                SecondaryMenuAnimTarget.y),\n      SecondaryMenuAnimDuration);\n  ActorsVoice->Move(\n      glm::vec2(-(SecondaryMenuAnimTarget.x + (4 * SecondaryMenuPadding)),\n                SecondaryMenuAnimTarget.y),\n      SecondaryMenuAnimDuration);\n\n  Encyclopedia->Move(glm::vec2(SecondaryMenuAnimUnderX,\n                               -MemoriesItemCount * MenuEntriesYPadding),\n                     SecondaryMenuAnimDuration);\n  System->Move(glm::vec2(SecondaryMenuAnimUnderX,\n                         -MemoriesItemCount * MenuEntriesYPadding),\n               SecondaryMenuAnimDuration);\n  Exit->Move(glm::vec2(SecondaryMenuAnimUnderX,\n                       -MemoriesItemCount * MenuEntriesYPadding),\n             SecondaryMenuAnimDuration);\n}\n\nvoid TitleMenu::ShowSystemItems() {\n  SystemItems->Show();\n  SystemItems->Tint.a = 0.0f;\n  MainItems->HasFocus = false;\n\n  SecondaryFadeAnimation.StartIn();\n\n  Option->Move(SecondaryMenuAnimTarget, SecondaryMenuAnimDuration);\n  SystemSave->Move(glm::vec2(SecondaryMenuAnimTarget.x + SecondaryMenuPadding,\n                             SecondaryMenuAnimTarget.y),\n                   SecondaryMenuAnimDuration);\n\n  Exit->Move(\n      glm::vec2(SecondaryMenuAnimUnderX, SystemItemCount * MenuEntriesYPadding),\n      SecondaryMenuAnimDuration);\n}\n\nvoid TitleMenu::HideSystemItems() {\n  SecondaryFadeAnimation.StartOut();\n\n  Option->Move(-SecondaryMenuAnimTarget, SecondaryMenuAnimDuration);\n  SystemSave->Move(\n      glm::vec2(-(SecondaryMenuAnimTarget.x + SecondaryMenuPadding),\n                SecondaryMenuAnimTarget.y),\n      SecondaryMenuAnimDuration);\n\n  Exit->Move(glm::vec2(SecondaryMenuAnimUnderX,\n                       -SystemItemCount * MenuEntriesYPadding),\n             SecondaryMenuAnimDuration);\n}\n\n}  // namespace MO6TW\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/mo6tw/titlemenu.h",
    "content": "#pragma once\n\n#include \"../../animation.h\"\n#include \"../../ui/menu.h\"\n#include \"../../ui/widgets/button.h\"\n#include \"../../ui/widgets/group.h\"\n#include \"../../ui/widgets/mo6tw/titlebutton.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace MO6TW {\n\nclass TitleMenu : public Menu {\n public:\n  TitleMenu();\n\n  void Show() override;\n  void Hide() override;\n  void Update(float dt) override;\n  void Render() override;\n\n  Animation PressToStartAnimation;\n  Animation PrimaryFadeAnimation;\n  Animation SecondaryFadeAnimation;\n\n  void MenuButtonOnClick(Widgets::Button* target);\n  void SecondaryButtonOnClick(Widgets::Button* target);\n\n private:\n  Widgets::Group* MainItems;\n  Widgets::MO6TW::TitleButton* InitialStart;\n  Widgets::MO6TW::TitleButton* ExtraStory;\n  Widgets::MO6TW::TitleButton* Continue;\n  Widgets::MO6TW::TitleButton* Memories;\n  Widgets::MO6TW::TitleButton* Encyclopedia;\n  Widgets::MO6TW::TitleButton* System;\n  Widgets::MO6TW::TitleButton* Exit;\n\n  Widgets::Group* ExtraStoryItems;\n  Widgets::MO6TW::TitleButton* Prologue;\n  Widgets::MO6TW::TitleButton* OtomeJudgeShin;\n  Widgets::MO6TW::TitleButton* Warning;\n  void ShowExtraStoryItems();\n  void HideExtraStoryItems();\n\n  Widgets::Group* ContinueItems;\n  Widgets::MO6TW::TitleButton* Load;\n  Widgets::MO6TW::TitleButton* QuickLoad;\n  void ShowContinueItems();\n  void HideContinueItems();\n\n  Widgets::Group* MemoriesItems;\n  Widgets::MO6TW::TitleButton* ClearList;\n  Widgets::MO6TW::TitleButton* Album;\n  Widgets::MO6TW::TitleButton* Music;\n  Widgets::MO6TW::TitleButton* Movie;\n  Widgets::MO6TW::TitleButton* ActorsVoice;\n  void ShowMemoriesItems();\n  void HideMemoriesItems();\n\n  Widgets::Group* SystemItems;\n  Widgets::MO6TW::TitleButton* Option;\n  Widgets::MO6TW::TitleButton* SystemSave;\n  void ShowSystemItems();\n  void HideSystemItems();\n};\n\n}  // namespace MO6TW\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/mo8/optionsmenu.cpp",
    "content": "#include \"optionsmenu.h\"\n\n#include \"../../profile/ui/optionsmenu.h\"\n#include \"../../profile/games/mo8/optionsmenu.h\"\n#include \"../../renderer/renderer.h\"\n#include \"../../mem.h\"\n#include \"../../profile/scriptvars.h\"\n#include \"../../vm/interface/input.h\"\n#include \"../../audio/audiosystem.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace MO8 {\n\nusing namespace Impacto::Profile::OptionsMenu;\nusing namespace Impacto::Profile::MO8::OptionsMenu;\nusing namespace Impacto::Profile::ScriptVars;\nusing namespace Impacto::Vm::Interface;\n\nusing namespace Impacto::UI::Widgets;\n\nstatic bool FalseValue = false;\n\nvoid OptionsMenu::NextPageOnClick(Widgets::Button* target) {\n  GoToPage((CurrentPage + 1) % Pages.size());\n}\n\nvoid OptionsMenu::PreviousPageOnClick(Widgets::Button* target) {\n  GoToPage(static_cast<size_t>(\n      (static_cast<int>(CurrentPage) - 1 + std::ssize(Pages)) %\n      std::ssize(Pages)));\n}\n\nvoid OptionsMenu::MessageSpeedToggleOnClick(Widgets::Toggle* target) {\n  if (*target->Value) {\n    memset(MessageSpeedValues, false, sizeof(MessageSpeedValues));\n    MessageSpeedValues[target->Id] = true;\n  } else {\n    *target->Value = true;\n  }\n}\n\nvoid OptionsMenu::AutoModeWaitTimeOnClick(Widgets::Toggle* target) {\n  if (*target->Value) {\n    memset(AutoModeWaitTimeValues, false, sizeof(AutoModeWaitTimeValues));\n    AutoModeWaitTimeValues[target->Id] = true;\n  } else {\n    *target->Value = true;\n  }\n}\n\nvoid OptionsMenu::SkipModeOnClick(Widgets::Toggle* target) {\n  if (*target->Value) {\n    memset(SkipModeValues, false, sizeof(SkipModeValues));\n    SkipModeValues[target->Id] = true;\n  } else {\n    *target->Value = true;\n  }\n}\n\nOptionsMenu::OptionsMenu() : UI::OptionsMenu() {\n  PageFadeAnimation.Direction = AnimationDirection::In;\n  PageFadeAnimation.LoopMode = AnimationLoopMode::Stop;\n  PageFadeAnimation.DurationIn = FadeInDuration;\n  PageFadeAnimation.DurationOut = FadeOutDuration;\n\n  auto nextPageOnClick = [this](auto* btn) { return NextPageOnClick(btn); };\n  auto previousPageOnClick = [this](auto* btn) {\n    return PreviousPageOnClick(btn);\n  };\n\n  PageControls = new Group(this);\n  PageControls->FocusLock = false;\n  Sprite nullSprite = Sprite();\n  nullSprite.Bounds = RectF(0.0f, 0.0f, 0.0f, 0.0f);\n  auto nextPage = new Button(0, NextButtonSprite, NextButtonHighlightedSprite,\n                             nullSprite, NextButtonPosition);\n  nextPage->OnClickHandler = nextPageOnClick;\n  auto previousPage =\n      new Button(0, BackButtonSprite, BackButtonHighlightedSprite, nullSprite,\n                 BackButtonPosition);\n  previousPage->OnClickHandler = previousPageOnClick;\n  PageControls->Add(nextPage);\n  PageControls->Add(previousPage);\n  PageControls->VisibilityState = Shown;\n\n  auto currentPos = ListStartingPosition;\n\n  // --- Text page ---\n  auto textPage = std::make_unique<Group>(this);\n  textPage->FocusLock = false;\n  textPage->Add(new Label(TextPageLabel, PageLabelPosition));\n\n  // Text speed\n  auto textSpeedOnClick = [this](auto* btn) {\n    return MessageSpeedToggleOnClick(btn);\n  };\n  auto textSpeedOptions =\n      new OptionGroup(this, TextSpeedOptionsLabel, TextSpeedOptionsLabelH,\n                      ButtonHighlight, currentPos, OptionGroupItemsOffset);\n  for (int i = 0; i < TextSpeedOptionsNum; i++) {\n    auto toggle = new Toggle(\n        i, &MessageSpeedValues[i], TextSpeedOptionsHSprites[i],\n        TextSpeedOptionsSprites[i], nullSprite, glm::vec2(0.0f), false);\n    toggle->OnClickHandler = textSpeedOnClick;\n    textSpeedOptions->AddOption(toggle);\n  }\n  textPage->Add(textSpeedOptions, FDIR_DOWN);\n  currentPos += ListPadding;\n\n  // Auto mode wait time\n  auto autoModeWaitTimeOnClick = [this](auto* btn) {\n    return AutoModeWaitTimeOnClick(btn);\n  };\n  auto autoModeOptions =\n      new OptionGroup(this, AutoModeOptionsLabel, AutoModeOptionsLabelH,\n                      ButtonHighlight, currentPos, OptionGroupItemsOffset);\n  for (int i = 0; i < AutoModeOptionsNum; i++) {\n    auto toggle = new Toggle(\n        i, &AutoModeWaitTimeValues[i], AutoModeOptionsHSprites[i],\n        AutoModeOptionsSprites[i], nullSprite, glm::vec2(0.0f), false);\n    toggle->OnClickHandler = autoModeWaitTimeOnClick;\n    autoModeOptions->AddOption(toggle);\n  }\n  textPage->Add(autoModeOptions, FDIR_DOWN);\n  currentPos += ListPadding;\n\n  // Skip mode\n  auto skipModeOnClick = [this](auto* btn) { return SkipModeOnClick(btn); };\n  auto skipModeOptions =\n      new OptionGroup(this, SkipModeOptionsLabel, SkipModeOptionsLabelH,\n                      ButtonHighlight, currentPos, OptionGroupItemsOffset);\n  for (int i = 0; i < SkipModeOptionsNum; i++) {\n    auto toggle = new Toggle(i, &SkipModeValues[i], SkipModeOptionsHSprites[i],\n                             SkipModeOptionsSprites[i], nullSprite,\n                             glm::vec2(0.0f), false);\n    toggle->OnClickHandler = skipModeOnClick;\n    skipModeOptions->AddOption(toggle);\n  }\n  textPage->Add(skipModeOptions, FDIR_DOWN);\n  Pages.push_back(std::move(textPage));\n  currentPos = ListStartingPosition;\n\n  // --- Sound page 1 ---\n  auto soundPage1 = std::make_unique<Group>(this);\n  soundPage1->FocusLock = false;\n  soundPage1->Add(new Label(SoundPageLabel, PageLabelPosition));\n\n  // Voice sync options\n  auto voiceSyncOptions =\n      new OptionGroup(this, VoiceSyncOptionsLabel, VoiceSyncOptionsLabelH,\n                      ButtonHighlight, currentPos, OptionGroupItemsOffset);\n  for (int i = 0; i < SoundModeOptionsNum; i++) {\n    voiceSyncOptions->AddOption(new Toggle(\n        i, &FalseValue, SoundModeOptionsHSprites[i], SoundModeOptionsSprites[i],\n        nullSprite, glm::vec2(0.0f), false));\n  }\n  soundPage1->Add(voiceSyncOptions, FDIR_DOWN);\n  currentPos += ListPadding;\n\n  // Voice skip options\n  auto voiceSkipOptions =\n      new OptionGroup(this, VoiceSkipOptionsLabel, VoiceSkipOptionsLabelH,\n                      ButtonHighlight, currentPos, OptionGroupItemsOffset);\n  for (int i = 0; i < SoundModeOptionsNum; i++) {\n    voiceSkipOptions->AddOption(new Toggle(\n        i, &FalseValue, SoundModeOptionsHSprites[i], SoundModeOptionsSprites[i],\n        nullSprite, glm::vec2(0.0f), false));\n  }\n  soundPage1->Add(voiceSkipOptions, FDIR_DOWN);\n  currentPos += ListPadding;\n\n  // Voice highlight options\n  auto voiceHighlightOptions = new OptionGroup(\n      this, VoiceHighlightOptionsLabel, VoiceHighlightOptionsLabelH,\n      ButtonHighlight, currentPos, OptionGroupItemsOffset);\n  for (int i = 0; i < SoundModeOptionsNum; i++) {\n    voiceHighlightOptions->AddOption(new Toggle(\n        i, &FalseValue, SoundModeOptionsHSprites[i], SoundModeOptionsSprites[i],\n        nullSprite, glm::vec2(0.0f), false));\n  }\n  soundPage1->Add(voiceHighlightOptions, FDIR_DOWN);\n  Pages.push_back(std::move(soundPage1));\n  currentPos = ListStartingPosition;\n\n  // --- Sound page 2 ---\n  auto soundPage2 = std::make_unique<Group>(this);\n  soundPage2->FocusLock = false;\n  soundPage2->Add(new Label(SoundPageLabel, PageLabelPosition));\n\n  // BGM volume options\n  auto bgmVolumeOptions =\n      new OptionGroup(this, BgmVolumeLabel, BgmVolumeLabelH, nullSprite,\n                      currentPos, OptionGroupSliderOffset);\n  auto bgmVolumeSlider = new Scrollbar(\n      0, glm::vec2(0.0f), 0.0f, 1.0f, &Audio::GroupVolumes[Audio::ACG_BGM],\n      SBDIR_HORIZONTAL, SliderTrackSprite, nullSprite, SliderFillSprite);\n  bgmVolumeOptions->AddOption(bgmVolumeSlider);\n  bgmVolumeSlider->FillBeforeTrack = true;\n  soundPage2->Add(bgmVolumeOptions, FDIR_DOWN);\n  currentPos += ListPadding;\n\n  // Voice volume options\n  auto voiceVolumeOptions =\n      new OptionGroup(this, VoiceVolumeLabel, VoiceVolumeLabelH, nullSprite,\n                      currentPos, OptionGroupSliderOffset);\n  auto voiceVolumeSlider = new Scrollbar(\n      0, glm::vec2(0.0f), 0.0f, 1.0f, &Audio::GroupVolumes[Audio::ACG_Voice],\n      SBDIR_HORIZONTAL, SliderTrackSprite, nullSprite, SliderFillSprite);\n  voiceVolumeOptions->AddOption(voiceVolumeSlider);\n  voiceVolumeSlider->FillBeforeTrack = true;\n  soundPage2->Add(voiceVolumeOptions, FDIR_DOWN);\n  currentPos += ListPadding;\n\n  // SE volume options\n  auto seVolumeOptions =\n      new OptionGroup(this, SeVolumeLabel, SeVolumeLabelH, nullSprite,\n                      currentPos, OptionGroupSliderOffset);\n  auto seVolumeSlider = new Scrollbar(\n      0, glm::vec2(0.0f), 0.0f, 1.0f, &Audio::GroupVolumes[Audio::ACG_SE],\n      SBDIR_HORIZONTAL, SliderTrackSprite, nullSprite, SliderFillSprite);\n  seVolumeOptions->AddOption(seVolumeSlider);\n  seVolumeSlider->FillBeforeTrack = true;\n  soundPage2->Add(seVolumeOptions, FDIR_DOWN);\n  currentPos += ListPadding;\n\n  // SYSSE volume options\n  auto systemSeVolumeOptions =\n      new OptionGroup(this, SystemSeVolumeLabel, SystemSeVolumeLabelH,\n                      nullSprite, currentPos, OptionGroupSliderOffset);\n  auto systemSeVolumeSlider = new Scrollbar(\n      0, glm::vec2(0.0f), 0.0f, 1.0f, &Audio::GroupVolumes[Audio::ACG_SE],\n      SBDIR_HORIZONTAL, SliderTrackSprite, nullSprite, SliderFillSprite);\n  systemSeVolumeOptions->AddOption(systemSeVolumeSlider);\n  systemSeVolumeSlider->FillBeforeTrack = true;\n  soundPage2->Add(systemSeVolumeOptions, FDIR_DOWN);\n  currentPos += ListPadding;\n  auto characterVoiceButton =\n      new Button(0, CharacterVoiceVolumeLabel, CharacterVoiceVolumeLabelH,\n                 nullSprite, currentPos);\n  soundPage2->Add(characterVoiceButton, FDIR_DOWN);\n  Pages.push_back(std::move(soundPage2));\n  currentPos = ListStartingPosition;\n\n  // --- Other page ---\n  auto otherPage = std::make_unique<Group>(this);\n  otherPage->FocusLock = false;\n  otherPage->Add(new Label(OtherPageLabel, PageLabelPosition));\n\n  // Quick save options\n  auto quickSaveOptions =\n      new OptionGroup(this, QuickSaveOptionsLabel, QuickSaveOptionsLabelH,\n                      ButtonHighlight, currentPos, OptionGroupItemsOffset);\n  for (int i = 0; i < QuickSaveOptionsNum; i++) {\n    quickSaveOptions->AddOption(new Toggle(\n        i, &FalseValue, QuickSaveOptionsHSprites[i], QuickSaveOptionsSprites[i],\n        nullSprite, glm::vec2(0.0f), false));\n  }\n  otherPage->Add(quickSaveOptions, FDIR_DOWN);\n  Pages.push_back(std::move(otherPage));\n}\n\nvoid OptionsMenu::UpdatePageInput(float dt) {\n  // Mouse controls\n  PageControls->Update(dt);\n  PageControls->UpdateInput(dt);\n\n  UI::OptionsMenu::UpdatePageInput(dt);\n}\n\nvoid OptionsMenu::Show() {\n  if (State != Showing) {\n    PreviousPage.reset();\n  }\n\n  UI::OptionsMenu::Show();\n}\n\nvoid OptionsMenu::UpdateVisibility() {\n  if (ScrWork[SW_SYSSUBMENUCT] < 16 && ScrWork[SW_SYSSUBMENUNO] == 5 &&\n      State == Shown) {\n    Hide();\n  } else if (ScrWork[SW_SYSSUBMENUCT] > 0 && ScrWork[SW_SYSSUBMENUNO] == 5 &&\n             State == Hidden) {\n    Show();\n  }\n\n  if (FadeAnimation.IsIn()) {\n    State = Shown;\n  } else if (ScrWork[SW_SYSSUBMENUCT] == 0 && ScrWork[SW_SYSSUBMENUNO] == 5 &&\n             FadeAnimation.IsOut()) {\n    State = Hidden;\n  }\n}\n\nvoid OptionsMenu::UpdateInput(float dt) {\n  UI::OptionsMenu::UpdateInput(dt);\n\n  if (GetControlState(CT_Back)) SetFlag(SF_SUBMENUEXIT, true);\n}\n\nvoid OptionsMenu::Update(float dt) {\n  PageFadeAnimation.Update(dt);\n\n  UI::OptionsMenu::Update(dt);\n\n  if (State != Hidden) {\n    if (PageFadeAnimation.State == AnimationState::Playing) {\n      Pages[*PreviousPage]->Update(dt);\n    } else if (PreviousPage.has_value() &&\n               Pages[*PreviousPage]->VisibilityState != Hidden) {\n      Pages[*PreviousPage]->Hide();\n      Pages[CurrentPage]->Show();\n    }\n  }\n}\n\nvoid OptionsMenu::Render() {\n  if (State == Hidden) return;\n\n  glm::vec4 col(1.0f, 1.0f, 1.0f,\n                glm::smoothstep(0.0f, 1.0f, FadeAnimation.Progress));\n  Renderer->DrawSprite(BackgroundSprite, glm::vec2(0.0f, 0.0f), col);\n\n  std::unique_ptr<Group>& currentPage = Pages[CurrentPage];\n  if (PageFadeAnimation.State == AnimationState::Playing) {\n    std::unique_ptr<Group>& previousPage = Pages[*PreviousPage];\n\n    currentPage->Tint = col;\n    previousPage->Tint = col;\n    currentPage->Tint.a *=\n        glm::smoothstep(0.0f, 1.0f, PageFadeAnimation.Progress);\n    previousPage->Tint.a *=\n        glm::smoothstep(1.0f, 0.0f, PageFadeAnimation.Progress);\n\n    previousPage->Render();\n    currentPage->Render();\n  } else {\n    currentPage->Tint = col;\n    currentPage->Render();\n  }\n\n  PageControls->Tint = col;\n  PageControls->Render();\n}\n\nvoid OptionsMenu::GoToPage(size_t pageNumber) {\n  if (CurrentPage == pageNumber) return;\n\n  PreviousPage = CurrentPage;\n  UI::OptionsMenu::GoToPage(pageNumber);\n\n  PageFadeAnimation.StartIn(true);\n}\n\n}  // namespace MO8\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/mo8/optionsmenu.h",
    "content": "#pragma once\n\n#include \"../../ui/optionsmenu.h\"\n#include \"../../ui/widgets/group.h\"\n#include \"../../ui/widgets/button.h\"\n#include \"../../ui/widgets/scrollbar.h\"\n#include \"../../ui/widgets/toggle.h\"\n#include \"../../ui/widgets/optiongroup.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace MO8 {\n\nclass OptionsMenu : public UI::OptionsMenu {\n public:\n  OptionsMenu();\n\n  void Show() override;\n  void Update(float dt) override;\n  void UpdateInput(float dt) override;\n  void Render() override;\n\n  void NextPageOnClick(Widgets::Button* target);\n  void PreviousPageOnClick(Widgets::Button* target);\n  void MessageSpeedToggleOnClick(Widgets::Toggle* target);\n  void AutoModeWaitTimeOnClick(Widgets::Toggle* target);\n  void SkipModeOnClick(Widgets::Toggle* target);\n\n private:\n  void UpdatePageInput(float dt) override;\n  void UpdateVisibility() override;\n\n  void GoToPage(size_t pageNumber) override;\n  Animation PageFadeAnimation;\n  std::optional<size_t> PreviousPage;\n\n  Widgets::Group* PageControls;\n\n  bool MessageSpeedValues[4] = {false};\n  bool AutoModeWaitTimeValues[3] = {false};\n  bool SkipModeValues[2] = {false};\n};\n\n}  // namespace MO8\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/mo8/savemenu.cpp",
    "content": "#include \"savemenu.h\"\n\n#include \"../../profile/ui/savemenu.h\"\n#include \"../../profile/games/mo8/savemenu.h\"\n#include \"../../renderer/renderer.h\"\n#include \"../../mem.h\"\n#include \"../../profile/scriptvars.h\"\n#include \"../../inputsystem.h\"\n#include \"../../ui/widgets/button.h\"\n#include \"../../data/savesystem.h\"\n#include \"../../vm/interface/input.h\"\n#include \"../../vm/vm.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace MO8 {\n\nusing namespace Impacto::Profile::SaveMenu;\nusing namespace Impacto::Profile::MO8::SaveMenu;\nusing namespace Impacto::Profile::ScriptVars;\nusing namespace Impacto::Vm::Interface;\n\nusing namespace Impacto::UI::Widgets;\n\nWidget* EntryGrid[RowsPerPage][EntriesPerRow];\n\nvoid SaveMenu::MenuButtonOnClick(Widgets::Button* target) {\n  target->Hovered = false;\n  if ((SaveSystem::GetSaveStatus(SaveSystem::SaveType::Full, target->Id) !=\n       0) ||\n      ScrWork[SW_SYSSUBMENUNO] == 4) {\n    ScrWork[SW_SAVEFILENO] = target->Id;\n  }\n}\n\nSaveMenu::SaveMenu() : UI::SaveMenu() {\n  FadeAnimation.Direction = AnimationDirection::In;\n  FadeAnimation.LoopMode = AnimationLoopMode::Stop;\n  FadeAnimation.DurationIn = FadeInDuration;\n  FadeAnimation.DurationOut = FadeOutDuration;\n\n  PageControls = new Group(this);\n  PageControls->FocusLock = false;\n  Sprite nullSprite = Sprite();\n  nullSprite.Bounds = RectF(0.0f, 0.0f, 0.0f, 0.0f);\n  auto nextPage = new Button(0, NextButtonSprite, NextButtonHighlightedSprite,\n                             nullSprite, NextButtonPosition);\n  auto previousPage =\n      new Button(0, BackButtonSprite, BackButtonHighlightedSprite, nullSprite,\n                 BackButtonPosition);\n  PageControls->Add(nextPage);\n  PageControls->Add(previousPage);\n  PageControls->VisibilityState = Shown;\n}\n\nvoid SaveMenu::Show() {\n  if (State != Shown) {\n    State = Showing;\n    FadeAnimation.StartIn();\n    if (UI::FocusedMenu != 0) {\n      LastFocusedMenu = UI::FocusedMenu;\n      LastFocusedMenu->IsFocused = false;\n    }\n    IsFocused = true;\n    UI::FocusedMenu = this;\n  }\n}\n\nvoid SaveMenu::Hide() {\n  if (State != Hidden) {\n    State = Hiding;\n    FadeAnimation.StartOut();\n    if (LastFocusedMenu != 0) {\n      UI::FocusedMenu = LastFocusedMenu;\n      LastFocusedMenu->IsFocused = true;\n    } else {\n      UI::FocusedMenu = 0;\n    }\n    IsFocused = false;\n  }\n}\n\nvoid SaveMenu::Update(float dt) {\n  UpdateInput(dt);\n\n  FadeAnimation.Update(dt);\n  bool subMenuSave = ScrWork[SW_SYSSUBMENUNO] == 0 ||\n                     ScrWork[SW_SYSSUBMENUNO] == 3 ||\n                     ScrWork[SW_SYSSUBMENUNO] == 4;\n  if (ScrWork[SW_SYSSUBMENUCT] < 16 && subMenuSave && State == Shown) {\n    Hide();\n  } else if (ScrWork[SW_SYSSUBMENUCT] > 0 && subMenuSave && State == Hidden) {\n    Show();\n  }\n\n  if (ScrWork[SW_SYSSUBMENUCT] == 16 && subMenuSave && FadeAnimation.IsIn())\n    State = Shown;\n  else if (ScrWork[SW_SYSSUBMENUCT] == 0 && subMenuSave &&\n           FadeAnimation.IsOut())\n    State = Hidden;\n\n  if (State == Shown) {\n    PageControls->Update(dt);\n    PageControls->UpdateInput(dt);\n    if (GetControlState(CT_Back)) {\n      SetFlag(SF_SUBMENUEXIT, true);\n    }\n  }\n}\n\nvoid SaveMenu::Render() {\n  if (State == Hidden) return;\n\n  glm::vec4 col(1.0f, 1.0f, 1.0f, FadeAnimation.Progress);\n  Renderer->DrawSprite(SaveMenuBackgroundSprite, glm::vec2(0.0f), col);\n  switch (ScrWork[SW_SYSSUBMENUNO]) {\n    case 0:\n      Renderer->DrawSprite(QuickLoadTextSprite, MenuTitleTextPos, col);\n      break;\n    case 3:\n      Renderer->DrawSprite(SaveTextSprite, MenuTitleTextPos, col);\n      break;\n    case 4:\n      Renderer->DrawSprite(LoadTextSprite, MenuTitleTextPos, col);\n      break;\n  }\n  PageControls->Tint = col;\n  PageControls->Render();\n}\n\n}  // namespace MO8\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/mo8/savemenu.h",
    "content": "#pragma once\n\n#include \"../../ui/menu.h\"\n#include \"../../ui/savemenu.h\"\n#include \"../../ui/widgets/group.h\"\n#include \"../../ui/widgets/button.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace MO8 {\n\nclass SaveMenu : public UI::SaveMenu {\n public:\n  SaveMenu();\n\n  void Show();\n  void Hide();\n  void Update(float dt);\n  void Render();\n\n  void RefreshCurrentEntryInfo() {}  // Todo\n\n  void MenuButtonOnClick(Widgets::Button* target);\n\n private:\n  Widgets::Group* PageControls;\n  [[maybe_unused]] Widgets::Group* MainItems;\n  Animation FadeAnimation;\n};\n\n}  // namespace MO8\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/mo8/systemmenu.cpp",
    "content": "#include \"systemmenu.h\"\n\n#include \"../../profile/ui/systemmenu.h\"\n#include \"../../profile/games/mo8/systemmenu.h\"\n#include \"../../renderer/renderer.h\"\n#include \"../../mem.h\"\n#include \"../../profile/scriptvars.h\"\n#include \"../../inputsystem.h\"\n#include \"../../ui/widgets/button.h\"\n#include \"../../audio/audiosystem.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace MO8 {\n\nusing namespace Impacto::Profile::SystemMenu;\nusing namespace Impacto::Profile::MO8::SystemMenu;\nusing namespace Impacto::Profile::ScriptVars;\n\nusing namespace Impacto::UI::Widgets;\n\nvoid SystemMenu::MenuButtonOnClick(Widgets::Button* target) {\n  target->Hovered = false;\n  ScrWork[SW_SYSMENUCNO] = target->Id;\n  ChoiceMade = true;\n  // When exiting through a menu button remove focus from the menu to avoid\n  // triggering the onClick handler again\n  if (target->Id == ExitMenuButtonId)\n    IsFocused = false;\n  else\n    Audio::PlayInGroup(Audio::ACG_SE, \"sysse\", 5, false, 0.0f);\n}\n\nSystemMenu::SystemMenu() {\n  MainItems = new Widgets::Group(this);\n\n  auto onClick = [this](auto* btn) { return MenuButtonOnClick(btn); };\n\n  Sprite nullSprite = Sprite();\n  nullSprite.Bounds = RectF(0.0f, 0.0f, 0.0f, 0.0f);\n\n  for (int i = 0; i < MenuEntriesNum; i++) {\n    Button* menuButton =\n        new Button(i, MenuEntriesSprites[i], MenuEntriesHSprites[i], nullSprite,\n                   glm::vec2(*MenuEntriesX,\n                             *MenuEntriesFirstY + (*MenuEntriesYPadding * i)));\n    menuButton->LockedSprite = MenuEntriesLSprites[i];\n\n    menuButton->OnClickHandler = onClick;\n    MainItems->Add(menuButton, FDIR_DOWN);\n  }\n\n  FadeAnimation.Direction = AnimationDirection::In;\n  FadeAnimation.LoopMode = AnimationLoopMode::Stop;\n  FadeAnimation.DurationIn = FadeInDuration;\n  FadeAnimation.DurationOut = FadeOutDuration;\n}\n\nvoid SystemMenu::Show() {\n  if (State != Shown) {\n    State = Showing;\n    FadeAnimation.StartIn();\n    MainItems->Show();\n    if (UI::FocusedMenu != 0) {\n      LastFocusedMenu = UI::FocusedMenu;\n      LastFocusedMenu->IsFocused = false;\n    }\n    IsFocused = true;\n    UI::FocusedMenu = this;\n\n    if (LastFocusedButtonId && *LastFocusedButtonId < MenuEntriesNum) {\n      CurrentlyFocusedElement = MainItems->Children[*LastFocusedButtonId];\n      CurrentlyFocusedElement->HasFocus = true;\n    }\n  }\n}\n\nvoid SystemMenu::Hide() {\n  if (State != Hidden) {\n    if (CurrentlyFocusedElement) {\n      auto* btn = static_cast<Widgets::Button*>(CurrentlyFocusedElement);\n      if (btn) {\n        LastFocusedButtonId = btn->Id;\n      }\n    }\n\n    State = Hiding;\n    FadeAnimation.StartOut();\n    MainItems->Hide();\n    if (LastFocusedMenu != 0) {\n      UI::FocusedMenu = LastFocusedMenu;\n      LastFocusedMenu->IsFocused = true;\n    } else {\n      UI::FocusedMenu = 0;\n    }\n    IsFocused = false;\n  }\n}\n\nvoid SystemMenu::Update(float dt) {\n  UpdateInput(dt);\n\n  FadeAnimation.Update(dt);\n  if (ScrWork[SW_SYSMENUCT] < 32 && State == Shown) {\n    Hide();\n  }\n\n  if (ScrWork[SW_SYSMENUCT] == 32 && FadeAnimation.IsIn())\n    State = Shown;\n  else if (ScrWork[SW_SYSMENUCT] == 0 && FadeAnimation.IsOut())\n    State = Hidden;\n\n  if (State == Shown && IsFocused) {\n    MainItems->Update(dt);\n    MainItems->UpdateInput(dt);\n  }\n}\n\nvoid SystemMenu::Render() {\n  if (State != Hidden && ScrWork[SW_SYSMENUALPHA] > 0) {\n    glm::vec4 col(1.0f, 1.0f, 1.0f, FadeAnimation.Progress);\n    Renderer->DrawSprite(SystemMenuBackgroundSprite,\n                         glm::vec2(SystemMenuX, SystemMenuY), col);\n    MainItems->Tint = col;\n    MainItems->Render();\n  }\n}\n\n}  // namespace MO8\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/mo8/systemmenu.h",
    "content": "#pragma once\n\n#include <optional>\n#include \"../../ui/menu.h\"\n#include \"../../ui/widgets/group.h\"\n#include \"../../ui/widgets/button.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace MO8 {\n\nclass SystemMenu : public Menu {\n public:\n  SystemMenu();\n\n  void Show() override;\n  void Hide() override;\n  void Update(float dt) override;\n  void Render() override;\n\n  void MenuButtonOnClick(Widgets::Button* target);\n\n private:\n  Widgets::Group* MainItems;\n  Animation FadeAnimation;\n  std::optional<int> LastFocusedButtonId;\n};\n\n}  // namespace MO8\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/mo8/titlemenu.cpp",
    "content": "#include \"titlemenu.h\"\n\n#include \"../../profile/ui/titlemenu.h\"\n#include \"../../profile/games/mo8/titlemenu.h\"\n#include \"../../renderer/renderer.h\"\n#include \"../../texture/texture.h\"\n#include \"../../spritesheet.h\"\n#include \"../../audio/audiosystem.h\"\n#include \"../../audio/audiostream.h\"\n#include \"../../audio/audiochannel.h\"\n#include \"../../io/vfs.h\"\n#include \"../../mem.h\"\n#include \"../../profile/scriptvars.h\"\n#include \"../../profile/game.h\"\n#include \"../../background2d.h\"\n#include \"../../vm/interface/input.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace MO8 {\n\nusing namespace Impacto::Profile::TitleMenu;\nusing namespace Impacto::Profile::MO8::TitleMenu;\nusing namespace Impacto::Profile::ScriptVars;\n\nusing namespace Impacto::UI::Widgets;\n\nvoid TitleMenu::MenuButtonOnClick(Widgets::Button* target) {\n  target->Hovered = false;\n  AllowsScriptInput = true;\n  ScrWork[SW_TITLECUR] = target->Id;\n  SetFlag(SF_TITLEEND, 1);\n  Audio::PlayInGroup(Audio::ACG_SE, \"sysse\", 5, false, 0.0f);\n}\n\nvoid TitleMenu::ContinueButtonOnClick(Widgets::Button* target) {\n  target->Hovered = false;\n  ContinueSelected = true;\n  AllowsScriptInput = false;\n  Audio::PlayInGroup(Audio::ACG_SE, \"sysse\", 5, false, 0.0f);\n}\n\nvoid TitleMenu::GalleryButtonOnClick(Widgets::Button* target) {\n  target->Hovered = false;\n  GallerySelected = true;\n  AllowsScriptInput = false;\n  Audio::PlayInGroup(Audio::ACG_SE, \"sysse\", 5, false, 0.0f);\n}\n\nTitleMenu::TitleMenu() {\n  MainItems = new Widgets::Group(this);\n  ContinueItems = new Widgets::Group(this);\n  GalleryItems = new Widgets::Group(this);\n\n  auto onClick = [this](auto* btn) { return MenuButtonOnClick(btn); };\n  auto continueOnClick = [this](auto* btn) {\n    return ContinueButtonOnClick(btn);\n  };\n  auto galleryOnClick = [this](auto* btn) { return GalleryButtonOnClick(btn); };\n\n  Sprite nullSprite = Sprite();\n  nullSprite.Bounds = RectF(0.0f, 0.0f, 0.0f, 0.0f);\n\n  // New Game menu button\n  NewGame = new Button(0, MenuEntriesSprites[NewGameSpriteIndex],\n                       MenuEntriesHSprites[NewGameSpriteIndex],\n                       MenuEntriesHSprites[NewGameSpriteIndex + 1],\n                       glm::vec2(MenuEntriesX, MenuEntriesFirstY));\n  NewGame->OnClickHandler = onClick;\n  NewGame->HighlightOffset = glm::vec2(0.0f);\n  MainItems->Add(NewGame, FDIR_DOWN);\n  // Continue menu button\n  Continue = new Button(\n      0, MenuEntriesSprites[ContinueSpriteIndex],\n      MenuEntriesHSprites[ContinueSpriteIndex],\n      MenuEntriesHSprites[NewGameSpriteIndex + 1],\n      glm::vec2(MenuEntriesX, MenuEntriesFirstY + (1 * MenuEntriesYPadding)));\n  Continue->OnClickHandler = continueOnClick;\n  Continue->HighlightOffset = glm::vec2(0.0f);\n  MainItems->Add(Continue, FDIR_DOWN);\n  // Options menu button\n  Options = new Button(\n      20, MenuEntriesSprites[OptionSpriteIndex],\n      MenuEntriesHSprites[OptionSpriteIndex],\n      MenuEntriesHSprites[NewGameSpriteIndex + 1],\n      glm::vec2(MenuEntriesX, MenuEntriesFirstY + (2 * MenuEntriesYPadding)));\n  Options->OnClickHandler = onClick;\n  Options->HighlightOffset = glm::vec2(0.0f);\n  MainItems->Add(Options, FDIR_DOWN);\n  //  Gallery menu button\n  Gallery = new Button(\n      0, MenuEntriesSprites[GallerySpriteIndex],\n      MenuEntriesHSprites[GallerySpriteIndex],\n      MenuEntriesHSprites[NewGameSpriteIndex + 1],\n      glm::vec2(MenuEntriesX, MenuEntriesFirstY + (3 * MenuEntriesYPadding)));\n  Gallery->OnClickHandler = galleryOnClick;\n  Gallery->HighlightOffset = glm::vec2(0.0f);\n  Gallery->Enabled = false;\n  MainItems->Add(Gallery, FDIR_DOWN);\n\n  // Continue buttons\n  // Load button\n  Load = new Button(10, MenuEntriesSprites[LoadSpriteIndex],\n                    MenuEntriesHSprites[LoadSpriteIndex],\n                    MenuEntriesHSprites[NewGameSpriteIndex + 1],\n                    glm::vec2(MenuEntriesX, MenuEntriesFirstY));\n  Load->OnClickHandler = onClick;\n  Load->HighlightOffset = glm::vec2(0.0f);\n  ContinueItems->Add(Load, FDIR_DOWN);\n  // Quick load button\n  QuickLoad = new Button(\n      11, MenuEntriesSprites[QuickLoadSpriteIndex],\n      MenuEntriesHSprites[QuickLoadSpriteIndex],\n      MenuEntriesHSprites[NewGameSpriteIndex + 1],\n      glm::vec2(MenuEntriesX, MenuEntriesFirstY + (1 * MenuEntriesYPadding)));\n  QuickLoad->OnClickHandler = onClick;\n  QuickLoad->HighlightOffset = glm::vec2(0.0f);\n  ContinueItems->Add(QuickLoad, FDIR_DOWN);\n\n  int index = 0;\n  // Gallery buttons\n  // Album button\n  AlbumButton =\n      new Button(30, MenuEntriesSprites[AlbumSpriteIndex],\n                 MenuEntriesHSprites[AlbumSpriteIndex],\n                 MenuEntriesHSprites[NewGameSpriteIndex + 1],\n                 glm::vec2(MenuEntriesX, MenuEntriesGalleryFirstY +\n                                             (index * MenuEntriesYPadding)));\n  AlbumButton->OnClickHandler = onClick;\n  AlbumButton->HighlightOffset = glm::vec2(0.0f);\n  GalleryItems->Add(AlbumButton, FDIR_DOWN);\n  index++;\n  // Music button\n  MusicButton =\n      new Button(31, MenuEntriesSprites[MusicSpriteIndex],\n                 MenuEntriesHSprites[MusicSpriteIndex],\n                 MenuEntriesHSprites[NewGameSpriteIndex + 1],\n                 glm::vec2(MenuEntriesX, MenuEntriesGalleryFirstY +\n                                             (index * MenuEntriesYPadding)));\n  MusicButton->OnClickHandler = onClick;\n  MusicButton->HighlightOffset = glm::vec2(0.0f);\n  GalleryItems->Add(MusicButton, FDIR_DOWN);\n  index++;\n  // Clear List button\n  ClearListButton =\n      new Button(32, MenuEntriesSprites[ClearListSpriteIndex],\n                 MenuEntriesHSprites[ClearListSpriteIndex],\n                 MenuEntriesHSprites[NewGameSpriteIndex + 1],\n                 glm::vec2(MenuEntriesX, MenuEntriesGalleryFirstY +\n                                             (index * MenuEntriesYPadding)));\n  ClearListButton->OnClickHandler = onClick;\n  ClearListButton->HighlightOffset = glm::vec2(0.0f);\n  GalleryItems->Add(ClearListButton, FDIR_DOWN);\n  index++;\n  // Warning button\n  WarningButton =\n      new Button(33, MenuEntriesSprites[WarningSpriteIndex],\n                 MenuEntriesHSprites[WarningSpriteIndex],\n                 MenuEntriesHSprites[NewGameSpriteIndex + 1],\n                 glm::vec2(MenuEntriesX, MenuEntriesGalleryFirstY +\n                                             (index * MenuEntriesYPadding)));\n  WarningButton->OnClickHandler = onClick;\n  WarningButton->HighlightOffset = glm::vec2(0.0f);\n  GalleryItems->Add(WarningButton, FDIR_DOWN);\n  index++;\n  if (HasAdditional) {\n    // Additional button\n    AdditionalButton =\n        new Button(34, MenuEntriesSprites[AdditionalSpriteIndex],\n                   MenuEntriesHSprites[AdditionalSpriteIndex],\n                   MenuEntriesHSprites[NewGameSpriteIndex + 1],\n                   glm::vec2(MenuEntriesX, MenuEntriesGalleryFirstY +\n                                               (index * MenuEntriesYPadding)));\n    AdditionalButton->OnClickHandler = onClick;\n    AdditionalButton->HighlightOffset = glm::vec2(0.0f);\n    GalleryItems->Add(AdditionalButton, FDIR_DOWN);\n    index++;\n  }\n  // DLC button\n  DLCButton =\n      new Button(35 - (!HasAdditional), MenuEntriesSprites[DLCSpriteIndex],\n                 MenuEntriesHSprites[DLCSpriteIndex],\n                 MenuEntriesHSprites[NewGameSpriteIndex + 1],\n                 glm::vec2(MenuEntriesX, MenuEntriesGalleryFirstY +\n                                             (index * MenuEntriesYPadding)));\n  DLCButton->OnClickHandler = onClick;\n  DLCButton->HighlightOffset = glm::vec2(0.0f);\n  GalleryItems->Add(DLCButton, FDIR_DOWN);\n}\n\nvoid TitleMenu::Show() {\n  if (State == Hidden) {\n    State = Shown;\n    if (UI::FocusedMenu != 0) {\n      LastFocusedMenu = UI::FocusedMenu;\n      LastFocusedMenu->IsFocused = false;\n    }\n    IsFocused = true;\n    UI::FocusedMenu = this;\n    if (PressToStartAnimated &&\n        PressToStartAnimation.State == AnimationState::Stopped) {\n      PressToStartAnimation.StartIn();\n    }\n  }\n}\nvoid TitleMenu::Hide() {\n  if (State == Shown) {\n    State = Hidden;\n    if (LastFocusedMenu != 0) {\n      UI::FocusedMenu = LastFocusedMenu;\n      LastFocusedMenu->IsFocused = true;\n    } else {\n      UI::FocusedMenu = 0;\n    }\n    IsFocused = false;\n  }\n}\n\nvoid TitleMenu::Update(float dt) {\n  UpdateInput(dt);\n\n  PrimaryFadeAnimation.Update(dt);\n  MainItemsHideAnimation.Update(dt);\n  ContinueItemsShowAnimation.Update(dt);\n  GalleryItemsShowAnimation.Update(dt);\n  if (PressToStartAnimated) {\n    PressToStartAnimation.Update(dt);\n  }\n  if (GetFlag(SF_TITLEMODE)) {\n    Show();\n  } else {\n    Hide();\n  }\n\n  if (State != Hidden && IsFocused && GetFlag(SF_TITLEMODE) &&\n      ScrWork[SW_TITLEMODE] != 1) {\n    Gallery->Tint = glm::vec4(1.0f, 1.0f, 1.0f, GetFlag(803) ? 1.0f : 0.0f);\n    Gallery->Enabled = GetFlag(803);\n    MainItems->Update(dt);\n    MainItems->UpdateInput(dt);\n    ContinueItems->Update(dt);\n    GalleryItems->Update(dt);\n    if (ContinueSelected) {\n      ContinueItems->UpdateInput(dt);\n    } else if (GallerySelected) {\n      GalleryItems->UpdateInput(dt);\n    }\n\n    switch (ScrWork[SW_TITLEMODE]) {\n      case 0:\n      case 2: {\n        if (PrimaryFadeAnimation.State == AnimationState::Stopped &&\n            ScrWork[SW_TITLEDISPCT] == 0)\n          PrimaryFadeAnimation.StartOut(true);\n      } break;\n      case 5: {\n        if (ContinueSelected) {\n          UpdateSubMenu(&ContinueItemsShowAnimation, ContinueItems,\n                        &ContinueSelected);\n        } else if (GallerySelected) {\n          UpdateSubMenu(&GalleryItemsShowAnimation, GalleryItems,\n                        &GallerySelected);\n        } else {\n          if (MainItems->VisibilityState == Hidden) MainItems->Show();\n        }\n      } break;\n      case 6: {\n        if (PrimaryFadeAnimation.State == AnimationState::Stopped &&\n            ScrWork[SW_TITLEDISPCT] == 0)\n          PrimaryFadeAnimation.StartIn(true);\n      } break;\n      case 11: {\n        if (PrimaryFadeAnimation.IsIn()) {\n          PrimaryFadeAnimation.StartOut(true);\n        }\n      } break;\n    }\n  }\n}\n\nvoid TitleMenu::Render() {\n  if (State != Hidden && GetFlag(SF_TITLEMODE)) {\n    Renderer->DrawSprite(BackgroundSprite, glm::vec2(0.0f));\n    Renderer->DrawSprite(LogoSprite, glm::vec2(LogoPositionX, LogoPositionY));\n    switch (ScrWork[SW_TITLEMODE]) {\n      case 0:\n      case 1: {  // Press to start\n        glm::vec4 col = glm::vec4(1.0f);\n        if (PressToStartAnimated) {\n          col.a = glm::smoothstep(0.0f, 1.0f, PressToStartAnimation.Progress);\n        }\n        Renderer->DrawSprite(PressToStartSprite,\n                             glm::vec2(PressToStartX, PressToStartY), col);\n        glm::vec4 black = glm::vec4(0.0f);\n        black.a = glm::smoothstep(0.0f, 1.0f, PrimaryFadeAnimation.Progress);\n        Renderer->DrawQuad(\n            RectF(0.0f, 0.0f, Profile::DesignWidth, Profile::DesignHeight),\n            black);\n      } break;\n      case 2: {  // Press to start fade\n        glm::vec4 col = glm::vec4(1.0f);\n        col.a = glm::smoothstep(\n            0.0f, 1.0f,\n            ScrWork[SW_TITLEDISPCT] > 0 ? PrimaryFadeAnimation.Progress : 1.0f);\n        Renderer->DrawSprite(PressToStartSprite,\n                             glm::vec2(PressToStartX, PressToStartY), col);\n      } break;\n      case 5:\n      case 6:\n      case 7:\n      case 11: {  // Main and main fade out\n        MainItems->Tint = glm::vec4(\n            1.0f, 1.0f, 1.0f,\n            glm::smoothstep(1.0f, 0.0f, MainItemsHideAnimation.Progress));\n        MainItems->Render();\n        ContinueItems->Tint = glm::vec4(\n            1.0f, 1.0f, 1.0f,\n            glm::smoothstep(0.0f, 1.0f, ContinueItemsShowAnimation.Progress));\n        ContinueItems->Render();\n        GalleryItems->Tint = glm::vec4(\n            1.0f, 1.0f, 1.0f,\n            glm::smoothstep(0.0f, 1.0f, GalleryItemsShowAnimation.Progress));\n        GalleryItems->Render();\n        glm::vec4 black = glm::vec4(0.0f);\n        black.a = glm::smoothstep(0.0f, 1.0f, PrimaryFadeAnimation.Progress);\n        Renderer->DrawQuad(\n            RectF(0.0f, 0.0f, Profile::DesignWidth, Profile::DesignHeight),\n            black);\n      } break;\n    }\n  }\n}\n\nvoid TitleMenu::UpdateSubMenu(Animation* showAnimation,\n                              Widgets::Group* subMenuGroup,\n                              bool* subMenuSelected) {\n  if (MainItemsHideAnimation.IsOut()) {\n    MainItemsHideAnimation.StartIn();\n  } else if (MainItemsHideAnimation.IsIn() && showAnimation->IsOut()) {\n    if (subMenuGroup->VisibilityState != Hidden) {\n      MainItems->Show();\n      subMenuGroup->Hide();\n      MainItemsHideAnimation.StartOut();\n      *subMenuSelected = false;\n      AllowsScriptInput = true;\n    } else {\n      MainItems->Hide();\n      subMenuGroup->Show();\n      showAnimation->StartIn();\n    }\n  } else if (showAnimation->IsIn() &&\n             Vm::Interface::GetControlState(Vm::Interface::CT_Back)) {\n    Audio::PlayInGroup(Audio::ACG_SE, \"sysse\", 3, false, 0.0f);\n    showAnimation->StartOut();\n  }\n  Vm::Interface::PADinputButtonWentDown &= ~Vm::Interface::PADcustom[6];\n  Vm::Interface::PADinputMouseWentDown &= ~Vm::Interface::PADcustom[6];\n}\n\n}  // namespace MO8\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/mo8/titlemenu.h",
    "content": "#pragma once\n\n#include \"../../ui/menu.h\"\n#include \"../../ui/widgets/button.h\"\n#include \"../../ui/widgets/group.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace MO8 {\n\nclass TitleMenu : public Menu {\n public:\n  TitleMenu();\n\n  void Show() override;\n  void Hide() override;\n  void Update(float dt) override;\n  void Render() override;\n\n  void MenuButtonOnClick(Widgets::Button* target);\n  void ContinueButtonOnClick(Widgets::Button* target);\n  void GalleryButtonOnClick(Widgets::Button* target);\n\n  Animation PrimaryFadeAnimation;\n\n  Animation MainItemsHideAnimation;\n  Animation ContinueItemsShowAnimation;\n  Animation GalleryItemsShowAnimation;\n\n private:\n  Widgets::Group* MainItems;\n\n  Widgets::Button* NewGame;\n  Widgets::Button* Continue;\n  Widgets::Button* Options;\n  Widgets::Button* Gallery;\n\n  Widgets::Group* ContinueItems;\n\n  Widgets::Button* Load;\n  Widgets::Button* QuickLoad;\n\n  Widgets::Group* GalleryItems;\n\n  Widgets::Button* AlbumButton;\n  Widgets::Button* MusicButton;\n  Widgets::Button* ClearListButton;\n  Widgets::Button* WarningButton;\n  Widgets::Button* AdditionalButton;\n  Widgets::Button* DLCButton;\n\n  bool ContinueSelected = false;\n  bool GallerySelected = false;\n\n  void UpdateSubMenu(Animation* showAnimation, Widgets::Group* subMenuGroup,\n                     bool* subMenuSelected);\n};\n\n}  // namespace MO8\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/rne/sysmesbox.cpp",
    "content": "#include \"sysmesbox.h\"\n\n#include \"../../profile/ui/sysmesbox.h\"\n#include \"../../profile/games/rne/sysmesbox.h\"\n#include \"../../profile/dialogue.h\"\n#include \"../../profile/vm.h\"\n#include \"../../profile/scriptvars.h\"\n#include \"../../mem.h\"\n#include \"../../inputsystem.h\"\n#include \"../../renderer/renderer.h\"\n#include \"../../audio/audiochannel.h\"\n#include \"../../audio/audiosystem.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace RNE {\n\nusing namespace Impacto::UI::Widgets;\nusing namespace Impacto::Profile::ScriptVars;\nusing namespace Impacto::Profile::SysMesBox;\nusing namespace Impacto::Profile::RNE::SysMesBox;\n\nstatic float BoxAnimCount = 0.0f;\nstatic float BoxTopY = 0.0f;\nstatic float BoxBottomY = 0.0f;\nstatic float LineLength = 0.0f;\nstatic float BoxHeight = 0.0f;\nstatic float BoxProgressCount = 0.0f;\nstatic float ButtonYesX = 0.0f;\nstatic float ButtonRightX = 0.0f;\nstatic int TextStartCount = 0;\n\nvoid SysMesBox::ChoiceItemOnClick(Button* target) {\n  ScrWork[SW_SYSSEL] = target->Id;\n  ChoiceMade = true;\n}\n\nvoid SysMesBox::Show() {\n  MessageItems = new Widgets::Group(this);\n  ChoiceItems = new Widgets::Group(this);\n\n  Sprite nullSprite = Sprite();\n  nullSprite.Bounds = RectF(0.0f, 0.0f, 0.0f, 0.0f);\n\n  auto onClick = [this](auto* btn) { return ChoiceItemOnClick(btn); };\n\n  float textBeginY = TextMiddleY - (TextMarginY * (4 + MessageCount));\n  for (int i = 0; i < MessageCount; i++) {\n    for (ProcessedTextGlyph& glyph : Messages[i]) {\n      if (glyph.CharId == 0) break;\n      glyph.DestRect.Y = textBeginY + (i * TextLineHeight);\n    }\n\n    Label* message = new Label(Messages[i], MessageWidths[i], TextFontSize,\n                               RendererOutlineMode::None);\n\n    MessageItems->Add(message, FDIR_DOWN);\n  }\n\n  if (ChoiceCount == 1) {\n    WidgetOK = new Button(0, ButtonOK, ButtonOKHighlighted, nullSprite,\n                          glm::vec2(ButtonRightX, 0.0f));\n    WidgetOK->OnClickHandler = onClick;\n    ChoiceItems->Add(WidgetOK, FDIR_RIGHT);\n\n  } else if (ChoiceCount == 2) {\n    WidgetYes = new Button(0, ButtonYes, ButtonYesHighlighted, nullSprite,\n                           glm::vec2(ButtonYesX, 0.0f));\n    WidgetYes->OnClickHandler = onClick;\n    ChoiceItems->Add(WidgetYes, FDIR_RIGHT);\n    WidgetNo = new Button(1, ButtonNo, ButtonNoHighlighted, nullSprite,\n                          glm::vec2(ButtonRightX, 0.0f));\n    WidgetNo->OnClickHandler = onClick;\n    ChoiceItems->Add(WidgetNo, FDIR_LEFT);\n  }\n\n  MessageItems->Show();\n  MessageItems->HasFocus = false;\n  if (ChoiceCount != 0) ChoiceItems->Show();\n  State = Showing;\n\n  if (UI::FocusedMenu != 0) {\n    LastFocusedMenu = UI::FocusedMenu;\n    LastFocusedMenu->IsFocused = false;\n  }\n  IsFocused = true;\n  UI::FocusedMenu = this;\n\n  if (Profile::Vm::GameInstructionSet == Vm::InstructionSet::RNE) {\n    if (!ScrWork[SW_SYSMESANIMCTCUR]) {\n      Audio::PlayInGroup(Audio::ACG_SE, \"sysse\", 16, false, 0.0f);\n    }\n  }\n}\n\nvoid SysMesBox::Hide() {\n  if (Profile::Vm::GameInstructionSet == Vm::InstructionSet::RNE) {\n    Audio::PlayInGroup(Audio::ACG_SE, \"sysse\", 29, false, 0.0f);\n  }\n  State = Hiding;\n  if (LastFocusedMenu != 0) {\n    UI::FocusedMenu = LastFocusedMenu;\n    LastFocusedMenu->IsFocused = true;\n  } else {\n    UI::FocusedMenu = 0;\n  }\n  IsFocused = false;\n}\n\nvoid SysMesBox::Update(float dt) {\n  UpdateInput(dt);\n\n  FadeAnimation.Update(dt);\n\n  if (State == Hiding) {\n    BoxAnimCount -= AnimationSpeed * dt;\n    if (BoxAnimCount <= 0.0f) {\n      BoxAnimCount = 0.0f;\n      State = Hidden;\n    }\n  } else if (State == Showing) {\n    BoxAnimCount += AnimationSpeed * dt;\n    if (BoxAnimCount >= ScrWork[SW_SYSMESANIMCTF]) {\n      BoxAnimCount = (float)ScrWork[SW_SYSMESANIMCTF];\n      State = Shown;\n    }\n  }\n\n  ScrWork[SW_SYSMESANIMCTCUR] = (int)std::floor(BoxAnimCount);\n\n  if (State != Hidden) {\n    float linePosX = LinePositionXFirst;\n    LineLength = LineWidthFirst;\n    if (ScrWork[SW_SYSMESANIMCTCUR] > 1) {\n      LineLength = LineWidthMultiplier * BoxAnimCount + LineWidthBase;\n      linePosX = LinePositionX - LinePositionMultiplier * BoxAnimCount;\n    }\n    Line1.Bounds = RectF(linePosX + 1.0f, Line1SpriteY, LineLength + 2.0f,\n                         LineSpriteHeight);\n    Line2.Bounds = RectF(linePosX + 1.0f, Line2SpriteY, LineLength + 2.0f,\n                         LineSpriteHeight);\n\n    BoxProgressCount = BoxAnimCount - BoxDisplayStartCount;\n    TextStartCount = 2 * MessageCount + 8;\n    if (BoxProgressCount >= TextStartCount) {\n      BoxHeight = BoxTextFontSize * MessageCount + BoxHeightBase;\n    } else {\n      BoxHeight = BoxHeightMultiplier * BoxProgressCount;\n    }\n\n    BoxTopY = BoxTopYBase - (BoxHeight / 2.0f);\n    BoxBottomY = BoxTopY + BoxHeight;\n\n    float labelButtonSpriteOffsetX =\n        (MessageLabelSpriteMultiplier *\n         (BoxProgressCount - 2 * MessageCount - 8));\n\n    MessageLabel.Bounds =\n        RectF(MessageLabelSpriteXBase - labelButtonSpriteOffsetX + 1.0f,\n              MessageLabelSpriteY, labelButtonSpriteOffsetX - 2.0f,\n              MessageLabelSpriteHeight);\n\n    if (BoxProgressCount > TextStartCount) {\n      if (State == Showing && FadeAnimation.IsOut())\n        FadeAnimation.StartIn();\n      else if (State == Hiding && FadeAnimation.IsIn())\n        FadeAnimation.StartOut();\n    }\n\n    if (ChoiceCount == 2) {\n      float buttonYesSpriteWidth = ButtonYWidthBase + labelButtonSpriteOffsetX;\n      if (buttonYesSpriteWidth > ButtonWidth)\n        buttonYesSpriteWidth = ButtonWidth;\n      WidgetYes->NormalSprite.Bounds = RectF(\n          WidgetYes->NormalSprite.Bounds.X, WidgetYes->NormalSprite.Bounds.Y,\n          buttonYesSpriteWidth, WidgetYes->NormalSprite.Bounds.Height);\n      WidgetYes->FocusedSprite.Bounds = RectF(\n          WidgetYes->FocusedSprite.Bounds.X, WidgetYes->FocusedSprite.Bounds.Y,\n          buttonYesSpriteWidth, WidgetYes->FocusedSprite.Bounds.Height);\n      ButtonYesX = ButtonYesDisplayXBase - labelButtonSpriteOffsetX;\n      WidgetYes->MoveTo(glm::vec2(ButtonYesX, BoxBottomY - ButtonYOffset));\n    }\n    if (ChoiceCount >= 1) {\n      float buttonRightSpriteWidth =\n          ButtonRightWidthBase + labelButtonSpriteOffsetX;\n      ButtonRightX = ButtonRightDisplayXBase - buttonRightSpriteWidth;\n      if (buttonRightSpriteWidth > ButtonWidth)\n        buttonRightSpriteWidth = ButtonWidth;\n      if (WidgetNo) {\n        WidgetNo->NormalSprite.Bounds = RectF(\n            WidgetNo->NormalSprite.Bounds.X, WidgetNo->NormalSprite.Bounds.Y,\n            buttonRightSpriteWidth, WidgetNo->NormalSprite.Bounds.Height);\n        WidgetNo->FocusedSprite.Bounds = RectF(\n            WidgetNo->FocusedSprite.Bounds.X, WidgetNo->FocusedSprite.Bounds.Y,\n            buttonRightSpriteWidth, WidgetNo->FocusedSprite.Bounds.Height);\n        WidgetNo->MoveTo(glm::vec2(ButtonRightX, BoxBottomY - ButtonYOffset));\n      }\n      if (WidgetOK) {\n        WidgetOK->NormalSprite.Bounds = RectF(\n            WidgetOK->NormalSprite.Bounds.X, WidgetOK->NormalSprite.Bounds.Y,\n            buttonRightSpriteWidth, WidgetOK->NormalSprite.Bounds.Height);\n        WidgetOK->FocusedSprite.Bounds = RectF(\n            WidgetOK->FocusedSprite.Bounds.X, WidgetOK->FocusedSprite.Bounds.Y,\n            buttonRightSpriteWidth, WidgetOK->FocusedSprite.Bounds.Height);\n        WidgetOK->MoveTo(glm::vec2(ButtonRightX, BoxBottomY - ButtonYOffset));\n      }\n    }\n\n    if (Input::KeyboardButtonWentDown[SDL_SCANCODE_RIGHT] ||\n        Input::KeyboardButtonWentDown[SDL_SCANCODE_LEFT]) {\n      Audio::PlayInGroup(Audio::ACG_SE, \"sysse\", 1, false, 0.0f);\n    }\n\n    if (IsFocused) {\n      MessageItems->Update(dt);\n      ChoiceItems->Update(dt);\n      ChoiceItems->UpdateInput(dt);\n    }\n  }\n}\n\nvoid SysMesBox::Render() {\n  if (State == Hidden) return;\n\n  glm::vec4 col(1.0f, 1.0f, 1.0f, 1.0f);\n\n  if (BoxAnimCount > BoxDisplayStartCount) {\n    Renderer->DrawQuad(\n        RectF(BoxDisplayX, BoxTopY - 1.0f, BoxWidth, BoxHeight + 2.0f),\n        glm::vec4(1.0f, 1.0f, 1.0f, 0.75f));\n    Renderer->DrawSprite(BoxDecorationTop,\n                         glm::vec2(BoxDisplayX, BoxTopY - 3.0f), col);\n    if (Type == SysMesBoxType::Dash) {\n      Renderer->DrawSprite(\n          BoxDecorationBottom,\n          glm::vec2(BoxDisplayX,\n                    BoxBottomY - BoxDecorationBottom.Bounds.Height + 3.0f),\n          col);\n    } else {\n      Renderer->DrawSprite(BoxDecorationBottom,\n                           glm::vec2(BoxDisplayX, BoxBottomY - 3.0f), col);\n    }\n\n    if (BoxProgressCount > TextStartCount) {\n      glm::vec4 texCol(1.0f, 1.0f, 1.0f, FadeAnimation.Progress);\n      if (BoxHeight > TextDecorationStart) {\n        Renderer->DrawSprite(\n            TextDecoration,\n            glm::vec2(BoxDisplayX, BoxTopY + TextDecorationTopYOffset), texCol);\n        Renderer->DrawSprite(\n            TextDecoration,\n            glm::vec2(BoxDisplayX, BoxBottomY - TextDecorationBottomYOffset),\n            texCol);\n      }\n      Renderer->DrawSprite(MessageLabel, glm::vec2(BoxDisplayX, BoxTopY + 3.0f),\n                           texCol);\n\n      MessageItems->Tint.a = texCol.a;\n      MessageItems->Render();\n      ChoiceItems->Tint.a = texCol.a;\n      ChoiceItems->Render();\n    }\n\n  } else {\n    Renderer->DrawSprite(\n        Line1, glm::vec2(LineDisplayXBase - (LineLength / 2.0f), Line1DisplayY),\n        col);\n    Renderer->DrawSprite(\n        Line2, glm::vec2(LineDisplayXBase - (LineLength / 2.0f), Line2DisplayY),\n        col);\n  }\n}\n\nvoid SysMesBox::Init() {\n  ChoiceMade = false;\n  MessageCount = 0;\n  ChoiceCount = 0;\n\n  FadeAnimation.DurationIn = FadeInDuration;\n  FadeAnimation.DurationOut = FadeOutDuration;\n  FadeAnimation.Direction = AnimationDirection::In;\n  FadeAnimation.LoopMode = AnimationLoopMode::Stop;\n}\n\nvoid SysMesBox::AddMessage(Vm::BufferOffsetContext ctx) {\n  Impacto::Vm::Sc3VmThread dummy;\n  dummy.IpOffset = ctx.IpOffset;\n  dummy.ScriptBufferId = ctx.ScriptBufferId;\n  Messages[MessageCount] =\n      TextLayoutPlainLine(&dummy, 255, Profile::Dialogue::DialogueFont,\n                          TextFontSize, Profile::Dialogue::ColorTable[10], 1.0f,\n                          glm::vec2(TextX, 0.0f), TextAlignment::Left);\n  float mesLen = 0.0f;\n  for (const ProcessedTextGlyph& glyph : Messages[MessageCount]) {\n    mesLen += glyph.DestRect.Width;\n  }\n  MessageWidths[MessageCount] = mesLen;\n  MessageCount++;\n}\n\nvoid SysMesBox::AddChoice(Vm::BufferOffsetContext ctx) {\n  // No text based choices in R;NE\n  ChoiceCount++;\n}\n\n}  // namespace RNE\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/rne/sysmesbox.h",
    "content": "#pragma once\n\n#include \"../../ui/sysmesbox.h\"\n#include \"../../ui/widgets/button.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace RNE {\n\nclass SysMesBox : public UI::SysMesBox {\n public:\n  virtual void Show() override;\n  virtual void Hide() override;\n  virtual void Update(float dt) override;\n  virtual void Render() override;\n\n  virtual void Init() override;\n  virtual void AddMessage(Vm::BufferOffsetContext ctx) override;\n  virtual void AddChoice(Vm::BufferOffsetContext ctx) override;\n\n private:\n  void ChoiceItemOnClick(UI::Widgets::Button* target);\n  UI::Widgets::Button* WidgetOK;\n  UI::Widgets::Button* WidgetYes;\n  UI::Widgets::Button* WidgetNo;\n};\n\n}  // namespace RNE\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/rne/systemmenu.cpp",
    "content": "#include \"systemmenu.h\"\n\n#include \"../../profile/ui/systemmenu.h\"\n#include \"../../profile/games/rne/systemmenu.h\"\n#include \"../../games/rne/tilebackground.h\"\n#include \"../../renderer/renderer.h\"\n#include \"../../mem.h\"\n#include \"../../profile/scriptvars.h\"\n#include \"../../inputsystem.h\"\n#include \"../../ui/widgets/button.h\"\n#include \"../../ui/widgets/rne/sysmenubutton.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace RNE {\n\nusing namespace Impacto::Profile::SystemMenu;\nusing namespace Impacto::Profile::RNE::SystemMenu;\nusing namespace Impacto::Profile::ScriptVars;\n\nusing namespace Impacto::UI::Widgets;\nusing namespace Impacto::UI::Widgets::RNE;\n\nvoid SystemMenu::MenuButtonOnClick(Widgets::Button* target) {\n  target->Hovered = false;\n  ScrWork[SW_SYSMENUCNO] = target->Id;\n  ChoiceMade = true;\n}\n\nSystemMenu::SystemMenu() {\n  MainItems = new Widgets::Group(this);\n\n  auto onClick = [this](auto* btn) { return MenuButtonOnClick(btn); };\n\n  Sprite nullSprite = Sprite();\n  nullSprite.Bounds = RectF(0.0f, 0.0f, 0.0f, 0.0f);\n\n  for (int i = 0; i < MenuEntriesNum; i++) {\n    SysMenuButton* menuButton = new SysMenuButton(\n        i, MenuEntriesSprites[i], nullSprite, MenuEntriesHSprites[i],\n        glm::vec2(*MenuEntriesX,\n                  *MenuEntriesFirstY + (*MenuEntriesYPadding * i)));\n\n    menuButton->OnClickHandler = onClick;\n    MainItems->Add(menuButton, FDIR_DOWN);\n  }\n}\n\nvoid SystemMenu::Show() {\n  if (State != Shown) {\n    State = Showing;\n    if (BackgroundAnimation) BackgroundAnimation->StartIn();\n\n    MainItems->Show();\n    if (UI::FocusedMenu != 0) {\n      LastFocusedMenu = UI::FocusedMenu;\n      LastFocusedMenu->IsFocused = false;\n    }\n    IsFocused = true;\n    UI::FocusedMenu = this;\n\n    if (LastFocusedButtonId && *LastFocusedButtonId < MenuEntriesNum) {\n      CurrentlyFocusedElement = MainItems->Children[*LastFocusedButtonId];\n      CurrentlyFocusedElement->HasFocus = true;\n    }\n  }\n}\nvoid SystemMenu::Hide() {\n  if (State != Hidden) {\n    if (CurrentlyFocusedElement) {\n      auto* btn = static_cast<Widgets::Button*>(CurrentlyFocusedElement);\n      if (btn) {\n        LastFocusedButtonId = btn->Id;\n      }\n    }\n\n    State = Hiding;\n    HighlightAnimation.StartOut();\n    for (auto& item : MainItems->Children) {\n      item->HasFocus = false;\n    }\n    if (LastFocusedMenu != 0) {\n      UI::FocusedMenu = LastFocusedMenu;\n      LastFocusedMenu->IsFocused = true;\n    } else {\n      UI::FocusedMenu = 0;\n    }\n    IsFocused = false;\n  }\n}\n\nvoid SystemMenu::Update(float dt) {\n  UpdateInput(dt);\n\n  if (ScrWork[SW_SYSMENUALPHA] < 256 && State == Shown) {\n    Hide();\n  }\n\n  if (ScrWork[SW_SYSMENUCT] == 100 && State == Shown) {\n    Hide();\n  }\n\n  SkyMoveAnimation.Update(dt);\n  EntriesMoveAnimation.Update(dt);\n  HighlightAnimation.Update(dt);\n  if (BackgroundAnimation) BackgroundAnimation->Update(dt);\n\n  if (State == Showing) {\n    if (BackgroundAnimation->Progress >= SkyInStartProgress) {\n      if (SkyMoveAnimation.IsOut()) SkyMoveAnimation.StartIn();\n      if (EntriesMoveAnimation.IsOut()) EntriesMoveAnimation.StartIn();\n    }\n    if (SkyMoveAnimation.IsIn()) {\n      if (HighlightAnimation.IsOut()) HighlightAnimation.StartIn();\n    }\n    if (HighlightAnimation.IsIn()) State = Shown;\n  } else if (State == Hiding) {\n    if (HighlightAnimation.IsOut()) {\n      if (BackgroundAnimation && BackgroundAnimation->IsIn())\n        BackgroundAnimation->StartOut();\n    }\n    if (BackgroundAnimation->Progress <= SkyOutStartProgress) {\n      if (SkyMoveAnimation.IsIn()) SkyMoveAnimation.StartOut();\n      if (EntriesMoveAnimation.IsIn()) EntriesMoveAnimation.StartOut();\n    }\n    if (BackgroundAnimation->IsOut()) State = Hidden;\n  }\n  ButtonBackgroundSprite.Bounds.X =\n      ButtonBackgroundSprStartX -\n      (ButtonBackgroundTargetWidth * SkyMoveAnimation.Progress);\n  ButtonBackgroundSprite.Bounds.Width =\n      ButtonBackgroundTargetWidth * SkyMoveAnimation.Progress;\n\n  int idx = 0;\n  for (auto& item : MainItems->Children) {\n    item->MoveTo(glm::vec2(((idx * MenuEntriesXSkew) + *MenuEntriesXOffset) *\n                               (1.0f - EntriesMoveAnimation.Progress),\n                           *MenuEntriesFirstY + (*MenuEntriesYPadding * idx)));\n    item->Tint.a = glm::smoothstep(\n        0.0f, 1.0f, 1.0f - (idx + 1) * (1.0f - EntriesMoveAnimation.Progress));\n    Button* button = (Button*)item;\n    button->HighlightSprite.Bounds.Width =\n        MenuEntriesTargetWidth * HighlightAnimation.Progress;\n    idx++;\n  }\n\n  if (ScrWork[SW_SYSMENUALPHA] == 0) {\n    MainItems->Hide();\n  }\n\n  if (State == Shown && IsFocused) {\n    MainItems->Update(dt);\n    MainItems->UpdateInput(dt);\n  }\n}\n\nvoid SystemMenu::Render() {\n  if (State != Hidden && ScrWork[SW_SYSMENUALPHA] > 0) {\n    if (BackgroundAnimation) BackgroundAnimation->Render();\n    glm::vec4 colSky(1.0f);\n    colSky.a = glm::smoothstep(0.0f, 1.0f, SkyMoveAnimation.Progress);\n\n    glm::vec2 destSky = glm::vec2(\n        SkyBackgroundBeginX +\n            (SkyMoveAnimation.Progress * std::abs(SkyBackgroundBeginX)),\n        SkyBackgroundY);\n    Renderer->DrawSprite(SkyBackgroundSprite, destSky, colSky);\n    Renderer->DrawSprite(SkyArrowSprite, destSky, colSky);\n\n    Renderer->DrawSprite(\n        SkyTextSprite,\n        glm::vec2(SkyTextBeginX + (SkyMoveAnimation.Progress *\n                                   std::abs(SkyBackgroundBeginX)),\n                  SkyTextY),\n        colSky);\n\n    Renderer->DrawSprite(\n        ButtonBackgroundSprite,\n        glm::vec2(ButtonBackgroundStartX -\n                      (ButtonBackgroundStartX * SkyMoveAnimation.Progress),\n                  ButtonBackgroundY),\n        glm::vec4(1.0f));\n\n    if (SkyMoveAnimation.IsIn()) {\n      Renderer->DrawSprite(ButtonPromptsSprite,\n                           glm::vec2(ButtonBackgroundX, ButtonBackgroundY),\n                           glm::vec4(1.0f));\n    }\n    MainItems->Render();\n  }\n}\n\n}  // namespace RNE\n}  // namespace UI\n}  // namespace Impacto\n"
  },
  {
    "path": "src/games/rne/systemmenu.h",
    "content": "#pragma once\n\n#include <optional>\n#include \"../../ui/menu.h\"\n#include \"../../ui/widgets/group.h\"\n#include \"../../ui/widgets/button.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace RNE {\n\nclass SystemMenu : public Menu {\n public:\n  SystemMenu();\n\n  void Show() override;\n  void Hide() override;\n  void Update(float dt) override;\n  void Render() override;\n\n  void MenuButtonOnClick(Widgets::Button* target);\n\n private:\n  Widgets::Group* MainItems;\n  Animation FadeAnimation;\n  std::optional<int> LastFocusedButtonId;\n};\n\n}  // namespace RNE\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/rne/tilebackground.cpp",
    "content": "#include \"tilebackground.h\"\n#include \"../../renderer/renderer.h\"\n#include \"../../rng.h\"\n#include \"../../profile/game.h\"\n#include \"../../spritesheet.h\"\n\nnamespace Impacto {\nnamespace RNE {\n\nvoid TileBackground::Init() {\n  ColumnWidth = Profile::DesignWidth / (float)Columns;\n  RowHeight = Profile::DesignHeight / (float)Rows;\n}\n\nvoid TileBackground::Render() {\n  if (Progress == 0) return;\n  if (Progress == 1) {\n    Renderer->DrawSprite(BackgroundSprite, glm::vec2(0, 0));\n    return;\n  }\n\n  Sprite tileSprite = BackgroundSprite;\n  tileSprite.Bounds.Width = (BackgroundSprite.Bounds.Width / (float)Columns);\n  tileSprite.Bounds.Height = (BackgroundSprite.Bounds.Height / (float)Rows);\n\n  for (int x = 0; x < Columns; x++) {\n    for (int y = 0; y < Rows; y++) {\n      int i = y * Columns + x;\n\n      float angle = MaxAngle;\n      // the first five rows rotate up, the last two rotate down\n      // this is how the original behaves too\n      if ((float)y / (float)Rows >= CenterY) {\n        angle = -angle;\n      }\n\n      RNG rng(i, Seed);\n      // FloatBetween(x, y) is in seconds here, we divide by DurationIn to get\n      // the corresponding progress value for those seconds in the in direction\n      float tileDuration = rng.FloatBetween(0.22f, 0.28f) / DurationIn;\n      float tileStartProgress;\n      uint32_t startTimeLevel = rng.UintBetween(0, 2);\n      if (startTimeLevel == 0) {\n        tileStartProgress = rng.FloatBetween(0.0f, 0.20f) / DurationIn;\n      } else {\n        tileStartProgress = rng.FloatBetween(0.25f, 0.45f) / DurationIn;\n      }\n      float tileEndProgress = tileStartProgress + tileDuration;\n\n      float smoothedTileProgress =\n          glm::smoothstep(tileStartProgress, tileEndProgress, Progress);\n\n      angle = angle - smoothedTileProgress * angle;\n      glm::vec3 euler(angle, 0, 0);\n      glm::quat quat;\n      eulerZYXToQuat(&euler, &quat);\n\n      // all rows go from right-angled to screen to parallel, hence the\n      // different perspective per row\n      glm::vec2 vanishingPoint(VanishingPointX * Profile::DesignWidth,\n                               ((float)y + 0.5f) * RowHeight);\n\n      tileSprite.Bounds.X = (float)x * tileSprite.Bounds.Width;\n      tileSprite.Bounds.Y = (float)y * tileSprite.Bounds.Height;\n\n      CornersQuad dest = RectF((float)x * ColumnWidth, (float)y * RowHeight,\n                               ColumnWidth, RowHeight);\n      dest.Rotate(quat, {dest.Center(), 0.0f}, 2.0f, vanishingPoint);\n      Renderer->DrawSprite(tileSprite, dest,\n                           {1.0f, 1.0f, 1.0f, smoothedTileProgress});\n    }\n  }\n}\n\n}  // namespace RNE\n}  // namespace Impacto"
  },
  {
    "path": "src/games/rne/tilebackground.h",
    "content": "#pragma once\n\n#include \"../../animation.h\"\n#include \"../../spritesheet.h\"\n\nnamespace Impacto {\nnamespace RNE {\nclass TileBackground : public Animation {\n public:\n  void Init();\n  void Render() override;\n\n  uint32_t Seed;\n\n  int Columns;\n  int Rows;\n\n  float VanishingPointX;\n  float CenterY;\n  float Depth;\n\n  float MaxAngle;\n\n  Sprite BackgroundSprite;\n\n private:\n  float ColumnWidth;\n  float RowHeight;\n};\n}  // namespace RNE\n}  // namespace Impacto"
  },
  {
    "path": "src/games/rne/titlemenu.cpp",
    "content": "#include \"titlemenu.h\"\n\n#include \"../../profile/ui/titlemenu.h\"\n#include \"../../profile/games/rne/titlemenu.h\"\n#include \"../../renderer/renderer.h\"\n#include \"../../texture/texture.h\"\n#include \"../../spritesheet.h\"\n#include \"../../games/rne/tilebackground.h\"\n#include \"../../audio/audiosystem.h\"\n#include \"../../audio/audiostream.h\"\n#include \"../../audio/audiochannel.h\"\n#include \"../../io/vfs.h\"\n#include \"../../mem.h\"\n#include \"../../profile/scriptvars.h\"\n#include \"../../background2d.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace RNE {\n\nusing namespace Impacto::Profile::TitleMenu;\nusing namespace Impacto::Profile::RNE::TitleMenu;\nusing namespace Impacto::Profile::ScriptVars;\n\nstatic int constexpr TitleBgBufferId = 3;\n\nTitleMenu::TitleMenu() {}\n\nvoid TitleMenu::Show() {\n  if (State == Hidden) {\n    if (BackgroundAnimation) {\n      Impacto::RNE::TileBackground* background =\n          (Impacto::RNE::TileBackground*)BackgroundAnimation;\n      Backgrounds2D[TitleBgBufferId]->BgSprite.BaseScale =\n          glm::vec2(1280.0f / 960.0f, 720.0f / 544.0f);\n      background->BackgroundSprite = Backgrounds2D[TitleBgBufferId]->BgSprite;\n      BackgroundAnimation->StartIn();\n    }\n    State = Showing;\n  }\n}\nvoid TitleMenu::Hide() {\n  if (State == Shown) {\n    if (BackgroundAnimation) BackgroundAnimation->StartOut();\n    State = Hiding;\n  }\n}\nvoid TitleMenu::Update(float dt) {\n  UpdateInput(dt);\n\n  if (BackgroundAnimation) BackgroundAnimation->Update(dt);\n  PreTitleItemsAnimation.Update(dt);\n  PressToStartAnimation.Update(dt);\n  if (GetFlag(SF_TITLEMODE)) {\n    Show();\n  } else {\n    Hide();\n  }\n\n  if (State == Showing) {\n    if (BackgroundAnimation->IsIn()) {\n      if (PreTitleItemsAnimation.IsOut()) PreTitleItemsAnimation.StartIn();\n      if (PreTitleItemsAnimation.IsIn() &&\n          PressToStartAnimation.State == AnimationState::Stopped)\n        PressToStartAnimation.StartIn();\n    }\n    if (PreTitleItemsAnimation.IsIn()) State = Shown;\n\n    LineSprite.Bounds.Width = (LineWidth * PreTitleItemsAnimation.Progress);\n    CopyrightSprite.Bounds.Width =\n        (CopyrightWidth * PreTitleItemsAnimation.Progress);\n    LogoSprite.Bounds.Width = (LogoWidth * PreTitleItemsAnimation.Progress);\n    EliteSprite.Bounds.Height = (EliteHeight * PreTitleItemsAnimation.Progress);\n\n  } else if (State == Hiding) {\n    if (BackgroundAnimation->IsOut()) State = Hidden;\n  }\n}\nvoid TitleMenu::Render() {\n  if (State != Hidden && GetFlag(SF_TITLEMODE)) {\n    if (BackgroundAnimation) BackgroundAnimation->Render();\n    if (BackgroundAnimation->IsIn()) {\n      glm::vec4 col = glm::vec4(1.0f);\n      col.a = glm::smoothstep(0.0f, 1.0f, PressToStartAnimation.Progress);\n      Renderer->DrawSprite(PressToStartSprite,\n                           glm::vec2(PressToStartX, PressToStartY), col);\n\n      Renderer->DrawSprite(LineSprite, glm::vec2(LineX, LineY),\n                           glm::vec4(1.0f));\n      Renderer->DrawSprite(CopyrightSprite, glm::vec2(CopyrightX, CopyrightY),\n                           glm::vec4(1.0f));\n      Renderer->DrawSprite(EliteSprite, glm::vec2(EliteX, EliteY),\n                           glm::vec4(1.0f));\n      Renderer->DrawSprite(LogoSprite, {LogoX, LogoY}, glm::vec4(1.0f),\n                           ScrWork[SW_TITLECGNO] == 542);\n    }\n  }\n}\n\n}  // namespace RNE\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/games/rne/titlemenu.h",
    "content": "#pragma once\n\n#include \"../../ui/menu.h\"\n#include \"../../ui/widgets/button.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace RNE {\n\nclass TitleMenu : public Menu {\n public:\n  TitleMenu();\n\n  void Show() override;\n  void Hide() override;\n  void Update(float dt) override;\n  void Render() override;\n};\n\n}  // namespace RNE\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/hud/autoicondisplay.cpp",
    "content": "#include \"autoicondisplay.h\"\n\n#include \"../profile/dialogue.h\"\n#include \"../text/dialoguepage.h\"\n#include \"../renderer/renderer.h\"\n\nnamespace Impacto {\nnamespace AutoIconDisplay {\n\nstatic SpriteAnimation SpriteAnim;\nstatic FixedSpriteAnimation FixedSpriteAnim;\nstatic float Progress = 0.0f;\n\nusing namespace Impacto::Profile::Dialogue;\n\nvoid Init() {\n  switch (AutoIconCurrentType) {\n    case AutoIconType::SpriteAnim:\n      SpriteAnim = AutoIconSpriteAnim.Instantiate();\n      SpriteAnim.LoopMode = AnimationLoopMode::Stop;\n      SpriteAnim.StartIn();\n      break;\n\n    case AutoIconType::SpriteAnimFixed:\n      SpriteAnim = AutoIconSpriteAnim.Instantiate();\n      SpriteAnim.LoopMode = AnimationLoopMode::Stop;\n      FixedSpriteAnim = static_cast<FixedSpriteAnimation&>(SpriteAnim);\n      FixedSpriteAnim.Def->FixSpriteId = AutoIconFixedSpriteId;\n      FixedSpriteAnim.StartIn();\n      break;\n\n    case AutoIconType::None:\n    case AutoIconType::Fixed:\n    case AutoIconType::CHLCC:\n      break;\n  }\n}\n\nvoid Update(float dt) {\n  switch (AutoIconCurrentType) {\n    case AutoIconType::SpriteAnim:\n      if (AutoModeEnabled && SpriteAnim.Direction == AnimationDirection::Out)\n        SpriteAnim.StartIn();\n      if (!AutoModeEnabled && SpriteAnim.Direction == AnimationDirection::In)\n        SpriteAnim.StartOut();\n\n      SpriteAnim.Update(dt);\n      break;\n\n    case AutoIconType::SpriteAnimFixed:\n      if (AutoModeEnabled &&\n          FixedSpriteAnim.Direction == AnimationDirection::Out)\n        FixedSpriteAnim.StartIn();\n      if (!AutoModeEnabled &&\n          FixedSpriteAnim.Direction == AnimationDirection::In)\n        FixedSpriteAnim.StartOut();\n\n      FixedSpriteAnim.Update(dt);\n      break;\n\n    case AutoIconType::CHLCC:\n      Progress += dt * AutoIconRotationSpeed;\n      Progress -= (int)Progress;  // Progress %= 1.0f\n      break;\n\n    case AutoIconType::None:\n    case AutoIconType::Fixed:\n      break;\n  }\n}\n\nvoid Render(glm::vec4 opacityTint) {\n  switch (AutoIconCurrentType) {\n    case AutoIconType::SpriteAnim: {\n      if (!SpriteAnim.IsOut()) {\n        glm::vec4 col = glm::vec4(1.0f, 1.0f, 1.0f, opacityTint.a);\n        Renderer->DrawSprite(SpriteAnim.CurrentSprite(),\n                             glm::vec2(AutoIconOffset.x, AutoIconOffset.y),\n                             col);\n      }\n      break;\n    }\n\n    case AutoIconType::SpriteAnimFixed:\n      if (!FixedSpriteAnim.IsOut() &&\n          !(FixedSpriteAnim.Direction == AnimationDirection::Out &&\n            FixedSpriteAnim.Progress ==\n                FixedSpriteAnim.GetFixedSpriteProgress())) {\n        Renderer->DrawSprite(FixedSpriteAnim.CurrentSprite(),\n                             glm::vec2(AutoIconOffset.x, AutoIconOffset.y),\n                             opacityTint);\n      }\n      break;\n\n    case AutoIconType::CHLCC:\n      if (AutoModeEnabled) {\n        const CornersQuad arrowsDest =\n            AutoSkipArrowsSprite.ScaledBounds()\n                .RotateAroundCenter(Progress * 2.0f * std::numbers::pi_v<float>)\n                .Translate(AutoIconOffset);\n        Renderer->DrawSprite(AutoSkipArrowsSprite, arrowsDest, opacityTint);\n\n        Renderer->DrawSprite(AutoIconSprite, AutoIconOffset, opacityTint);\n      }\n      break;\n\n    case AutoIconType::None:\n    case AutoIconType::Fixed:\n      break;\n  }\n}\n\n}  // namespace AutoIconDisplay\n}  // namespace Impacto"
  },
  {
    "path": "src/hud/autoicondisplay.h",
    "content": "#pragma once\n\n#include <magic_enum/magic_enum.hpp>\n#include <glm/glm.hpp>\n\n#include \"dialoguebox.h\"\n\nnamespace Impacto {\nnamespace AutoIconDisplay {\n\nenum class AutoIconType : int {\n  None,\n  SpriteAnim,\n  SpriteAnimFixed,\n  Fixed,\n  CHLCC,\n};\nvoid Init();\nvoid Update(float dt);\nvoid Render(glm::vec4 opacityTint);\n\n}  // namespace AutoIconDisplay\n}  // namespace Impacto"
  },
  {
    "path": "src/hud/cc/dialoguebox.cpp",
    "content": "#include \"dialoguebox.h\"\n\n#include \"../nametagdisplay.h\"\n#include \"../../text/dialoguepage.h\"\n#include \"../../renderer/renderer.h\"\n#include \"../../profile/dialogue.h\"\n#include \"../../profile/game.h\"\n#include \"../../profile/games/cc/dialoguebox.h\"\n#include \"../../mem.h\"\n#include \"../../profile/scriptvars.h\"\n#include \"../../log.h\"\n\nnamespace Impacto {\nnamespace CC {\n\nusing namespace Impacto::Profile::ScriptVars;\nusing namespace Impacto::Profile::Dialogue;\nusing namespace Impacto::Profile::CC::DialogueBox;\n\nDialogueBox::DialogueBox() {\n  TextBoxEffect.DurationIn = ADVBoxEffectDuration;\n  TextBoxEffect.LoopMode = AnimationLoopMode::Loop;\n  TextBoxEffect.StartIn();\n}\n\nvoid DialogueBox::Update(float dt) {\n  Impacto::DialogueBox::Update(dt);\n  TextBoxEffect.Update(dt);\n}\n\nvoid DialogueBox::Render(const DialoguePageMode mode, const NameInfo& nameInfo,\n                         const glm::vec4 tint) {\n  switch (mode) {\n    case DPM_ADV: {\n      Renderer->DrawCCMessageBox(ADVBoxSprite, ADVBoxMask, ADVBoxPos,\n                                 glm::vec4(1.0f), (int)(tint.a * 272.0f), 16,\n                                 TextBoxEffect.Progress);\n\n      NametagDisplayInst->Render(nameInfo, tint);\n    } break;\n\n    case DPM_NVL: {\n      glm::vec4 nvlBoxTint(0.0f, 0.0f, 0.0f, tint.a * NVLBoxMaxOpacity);\n      Renderer->DrawQuad(\n          RectF(0, 0, Profile::DesignWidth, Profile::DesignHeight), nvlBoxTint);\n    } break;\n\n    default:\n      ImpLogSlow(LogLevel::Error, LogChannel::General,\n                 \"Unexpected dialogue page mode {:d} in Chaos;Child textbox\",\n                 static_cast<int>(mode));\n      break;\n  }\n}\n\n}  // namespace CC\n}  // namespace Impacto"
  },
  {
    "path": "src/hud/cc/dialoguebox.h",
    "content": "#pragma once\n\n#include \"../dialoguebox.h\"\n#include \"../../animation.h\"\n#include \"../../spriteanimation.h\"\n\nnamespace Impacto {\nnamespace CC {\n\nclass DialogueBox : public Impacto::DialogueBox {\n public:\n  DialogueBox();\n\n  void Update(float dt) override;\n  void Render(DialoguePageMode mode, const NameInfo& nameInfo,\n              glm::vec4 tint) override;\n\n private:\n  Animation TextBoxEffect;\n};\n\n}  // namespace CC\n}  // namespace Impacto"
  },
  {
    "path": "src/hud/cc/nametagdisplay.cpp",
    "content": "#include \"nametagdisplay.h\"\n\n#include \"../../profile/games/cc/dialoguebox.h\"\n#include \"../../renderer/renderer.h\"\n\nnamespace Impacto {\nnamespace CC {\n\nusing namespace Impacto::Profile::CC::DialogueBox;\n\nNametagDisplay::NametagDisplay() {\n  ShowAnimation.SetDuration(NametagShowDuration);\n  ShowAnimation.SkipOnSkipMode = true;\n  ShowAnimation.Reset(AnimationDirection::In);\n}\n\nvoid NametagDisplay::Render(const NameInfo& nameInfo, const glm::vec4 tint) {\n  if (ShowAnimation.IsOut() || !nameInfo.RenderWindow ||\n      !nameInfo.NameId.has_value() ||\n      nameInfo.NameId.value() >= std::ssize(NametagMainSprites)) {\n    return;\n  }\n\n  const Sprite& mainSprite = NametagMainSprites[*nameInfo.NameId];\n  const Sprite& labelSprite = NametagLabelSprites[*nameInfo.NameId];\n\n  const glm::vec2 mainStartPos = {-mainSprite.ScaledWidth(), NametagMainPos.y};\n  const glm::vec2 mainPos =\n      glm::mix(mainStartPos, NametagMainPos, ShowAnimation.Progress);\n  Renderer->DrawSprite(mainSprite, mainPos, tint);\n\n  const RectF labelDest =\n      labelSprite.ScaledBounds()\n          .Translate(NametagLabelPos)\n          .ScaleAroundCenter({ShowAnimation.Progress, 1.0f});\n  Renderer->DrawSprite(labelSprite, labelDest, tint);\n}\n\nvoid NametagDisplay::Update(const float dt) { ShowAnimation.Update(dt); }\n\nvoid NametagDisplay::Reset() { ShowAnimation.Reset(AnimationDirection::In); }\n\nvoid NametagDisplay::Show() { ShowAnimation.StartIn(); }\n\nvoid NametagDisplay::Hide() { ShowAnimation.StartOut(); }\n\n}  // namespace CC\n}  // namespace Impacto\n"
  },
  {
    "path": "src/hud/cc/nametagdisplay.h",
    "content": "#pragma once\n\n#include \"../nametagdisplay.h\"\n\nnamespace Impacto {\nnamespace CC {\n\nclass NametagDisplay : public Impacto::NametagDisplay {\n public:\n  NametagDisplay();\n\n  void Render(const NameInfo& nameInfo, glm::vec4 tint) override;\n  void Update(float dt) override;\n  void Reset() override;\n\n  void Show() override;\n  void Hide() override;\n\n private:\n  Animation ShowAnimation;\n};\n\n}  // namespace CC\n}  // namespace Impacto\n"
  },
  {
    "path": "src/hud/cclcc/tipsnotification.cpp",
    "content": "#include \"tipsnotification.h\"\n\n#include \"../../profile/scriptvars.h\"\n#include \"../../impacto.h\"\n#include \"../../mem.h\"\n#include \"../../renderer/renderer.h\"\n#include \"../../game.h\"\n#include \"../../profile/hud/tipsnotification.h\"\n#include \"../../profile/games/cclcc/tipsnotification.h\"\n#include \"../../data/tipssystem.h\"\n\nnamespace Impacto {\nnamespace CCLCC {\n\nusing namespace Impacto::Profile::TipsNotification;\nusing namespace Impacto::Profile::CCLCC::TipsNotification;\nusing namespace Impacto::Profile::ScriptVars;\nusing namespace Impacto::UI::Widgets;\n\nTipsNotification::TipsNotification() {\n  FadeAnimation.DurationIn = FadeInDuration;\n  FadeAnimation.DurationOut = FadeOutDuration;\n\n  Timer.DurationIn = TimerDuration;\n  Timer.LoopMode = AnimationLoopMode::Stop;\n\n  auto textBefore = Vm::ScriptGetTextTableStrAddress(\n      TextTableId, NotificationTextPart1MessageId);\n  TextPartBefore =\n      Label(textBefore, {NotificationPositionX, 0}, FontSize,\n            RendererOutlineMode::BottomRight, NotificationTextTableColorIndex);\n  auto textAfter = Vm::ScriptGetTextTableStrAddress(\n      TextTableId, NotificationTextPart2MessageId);\n  TextPartAfter =\n      Label(textAfter, {NotificationPositionX + TextPartBefore.Bounds.Width, 0},\n            FontSize, RendererOutlineMode::BottomRight,\n            NotificationTextTableColorIndex);\n  TipName = Label(\"\", {NotificationPositionX, 0}, FontSize,\n                  RendererOutlineMode::BottomRight, TipNameColor);\n\n  TextPartBefore.OutlineAlphaEnabled = true;\n  TextPartAfter.OutlineAlphaEnabled = true;\n  TipName.OutlineAlphaEnabled = true;\n  TextPartBefore.OutlineAlpha = 0.0f;\n  TextPartAfter.OutlineAlpha = 0.0f;\n  TipName.OutlineAlpha = 0.0f;\n}\n\nvoid TipsNotification::Update(float dt) {\n  FadeAnimation.Update(dt);\n  Timer.Update(dt);\n  PositionY = (float)(static_cast<int>(FadeAnimation.Progress * 255 * 96) >> 8);\n\n  auto UpdateNotificationDisplay = [&]() {\n    auto tipNameAdr = NotificationQueue.front();\n    auto tipsScrBufId = TipsSystem::GetTipsScriptBufferId();\n    TipName.SetText({.ScriptBufferId = tipsScrBufId, .IpOffset = tipNameAdr},\n                    FontSize, RendererOutlineMode::BottomRight, TipNameColor);\n    Timer.DurationIn = TimerDuration + TipName.GetTextLength() * 0.1f;\n    NotificationQueue.pop();\n  };\n  if (!NotificationQueue.empty()) {\n    if (Timer.IsOut()) {\n      UpdateNotificationDisplay();\n      Timer.StartIn();\n    }\n    if (FadeAnimation.IsOut()) {\n      FadeAnimation.StartIn();\n    }\n  } else if (FadeAnimation.IsIn() && Timer.IsIn()) {\n    FadeAnimation.StartOut();\n  }\n  if (Timer.IsIn()) Timer.Progress = 0.0f;\n}\n\nvoid TipsNotification::Render() {\n  if (FadeAnimation.IsOut()) return;\n  if (FadeAnimation.Progress > 0.0f) {\n    float smoothedFade = glm::smoothstep(0.0f, 1.0f, FadeAnimation.Progress);\n    float bgYPos = PositionY - BackgroundPositionYOffset;\n    Renderer->DrawSprite(NotificationBackground, {BackgroundPositionX, bgYPos},\n                         glm::vec4(1.0f, 1.0f, 1.0f, smoothedFade));\n    TipName.Tint.a = smoothedFade;\n    TextPartBefore.Tint.a = smoothedFade;\n    TextPartAfter.Tint.a = smoothedFade;\n    TipName.OutlineAlpha = smoothedFade / 3.0f;\n    TextPartBefore.OutlineAlpha = smoothedFade / 3.0f;\n    TextPartAfter.OutlineAlpha = smoothedFade / 3.0f;\n\n    float textYPos = (PositionY + NotificationPositionYOffset);\n    TextPartBefore.MoveTo({NotificationPositionX, textYPos});\n    float tipNameXPos = NotificationPositionX + TextPartBefore.Bounds.Width;\n    TipName.MoveTo({tipNameXPos, textYPos});\n    TextPartAfter.MoveTo({tipNameXPos + TipName.Bounds.Width, textYPos});\n\n    TipName.Render();\n    TextPartBefore.Render();\n    TextPartAfter.Render();\n  }\n}\n\nvoid TipsNotification::AddTip(int tipId) {\n  const auto* const record = TipsSystem::GetTipRecord(tipId);\n  NotificationQueue.push(record->StringAdr[1]);\n}\n\n}  // namespace CCLCC\n}  // namespace Impacto"
  },
  {
    "path": "src/hud/cclcc/tipsnotification.h",
    "content": "#pragma once\n\n#include \"../tipsnotification.h\"\n#include \"../../ui/widgets/label.h\"\n#include \"../../ui/widgets/group.h\"\n\nnamespace Impacto {\nnamespace CCLCC {\n\nusing namespace Impacto::TipsNotification;\n\nclass TipsNotification : public TipsNotificationBase {\n public:\n  TipsNotification();\n\n  void Update(float dt) override;\n  void Render() override;\n  void AddTip(int tipId) override;\n\n protected:\n  Animation Timer;\n\n  UI::Widgets::Label TextPartBefore;\n  UI::Widgets::Label TextPartAfter;\n  UI::Widgets::Label TipName;\n\n  float PositionY;\n};\n\n}  // namespace CCLCC\n}  // namespace Impacto"
  },
  {
    "path": "src/hud/chlcc/dialoguebox.cpp",
    "content": "#include \"dialoguebox.h\"\n\n#include \"../../renderer/renderer.h\"\n#include \"../../text/dialoguepage.h\"\n#include \"../../profile/dialogue.h\"\n#include \"../../profile/game.h\"\n#include \"../../profile/games/chlcc/dialoguebox.h\"\n#include \"../../mem.h\"\n#include \"../../profile/scriptvars.h\"\n#include \"../../log.h\"\n\nnamespace Impacto {\nnamespace CHLCC {\n\nusing namespace Impacto::Profile::ScriptVars;\nusing namespace Impacto::Profile::Dialogue;\nusing namespace Impacto::Profile::CHLCC::DialogueBox;\n\nvoid DialogueBox::Render(const DialoguePageMode mode, const NameInfo& nameInfo,\n                         glm::vec4 tint) {\n  switch (mode) {\n    case DPM_ADV: {\n      tint = {1.0f, 1.0f, 1.0f, tint.a};\n      const Sprite advBoxSprite = ScrWork[SW_MESWINDOW_COLOR] == 1\n                                      ? SecondaryADVBoxSprite\n                                      : ADVBoxSprite;\n      Renderer->DrawSprite(advBoxSprite, ADVBoxPos, tint);\n\n      NametagDisplayInst->Render(nameInfo, tint);\n    } break;\n\n    case DPM_REV: {\n      if (ScrWork[SW_MESWIN0TYPE] != 1) break;\n\n      Renderer->DrawSprite(ErinBoxSprite, ErinBoxPos, glm::vec4(1.0f));\n    } break;\n\n    case DPM_NVL: {\n      glm::vec4 nvlBoxTint(0.0f, 0.0f, 0.0f, tint.a * NVLBoxMaxOpacity);\n      Renderer->DrawQuad(\n          RectF(0, 0, Profile::DesignWidth, Profile::DesignHeight), nvlBoxTint);\n    } break;\n\n    default:\n      ImpLogSlow(LogLevel::Warning, LogChannel::General,\n                 \"Unexpected dialogue page mode {:d} when rendering Chaos;Head \"\n                 \"LCC dialogue box\",\n                 static_cast<int>(mode));\n      break;\n  }\n}\n\n}  // namespace CHLCC\n}  // namespace Impacto"
  },
  {
    "path": "src/hud/chlcc/dialoguebox.h",
    "content": "#pragma once\n\n#include \"../dialoguebox.h\"\n#include \"../../animation.h\"\n\nnamespace Impacto {\nnamespace CHLCC {\n\nclass DialogueBox : public Impacto::DialogueBox {\n public:\n  void Render(DialoguePageMode mode, const NameInfo& nameInfo,\n              glm::vec4 tint) override;\n};\n\n}  // namespace CHLCC\n}  // namespace Impacto"
  },
  {
    "path": "src/hud/chlcc/nametagdisplay.cpp",
    "content": "#include \"nametagdisplay.h\"\n\n#include \"../../profile/games/chlcc/dialoguebox.h\"\n#include \"../../profile/dialogue.h\"\n#include \"../../profile/scriptvars.h\"\n#include \"../../renderer/renderer.h\"\n\nnamespace Impacto {\nnamespace CHLCC {\n\nusing namespace Impacto::Profile::CHLCC::DialogueBox;\nusing namespace Impacto::Profile::Dialogue;\nusing namespace Impacto::Profile::ScriptVars;\n\nvoid NametagDisplay::Render(const NameInfo& nameInfo, glm::vec4 tint) {\n  if (!nameInfo.RenderWindow) return;\n\n  Renderer->DrawSprite(\n      ScrWork[SW_MESWINDOW_COLOR] == 1 ? SecondaryNametagSprite : NametagSprite,\n      NametagPosition, tint);\n\n  Renderer->DrawProcessedText(nameInfo.Name, DialogueFont, tint.a,\n                              RendererOutlineMode::Full);\n}\n\n}  // namespace CHLCC\n}  // namespace Impacto\n"
  },
  {
    "path": "src/hud/chlcc/nametagdisplay.h",
    "content": "#pragma once\n\n#include \"../nametagdisplay.h\"\n\nnamespace Impacto {\nnamespace CHLCC {\n\nclass NametagDisplay : public Impacto::NametagDisplay {\n public:\n  void Render(const NameInfo& nameInfo, glm::vec4 tint) override;\n  void Update(float dt) override {}\n  void Reset() override {}\n};\n\n}  // namespace CHLCC\n}  // namespace Impacto\n"
  },
  {
    "path": "src/hud/chlcc/tipsnotification.cpp",
    "content": "#include \"tipsnotification.h\"\n\n#include \"../../profile/hud/tipsnotification.h\"\n#include \"../../profile/games/chlcc/tipsnotification.h\"\n#include \"../../data/tipssystem.h\"\n#include \"../../profile/game.h\"\n\nnamespace Impacto {\nnamespace CHLCC {\n\nusing namespace Impacto::Profile::TipsNotification;\nusing namespace Impacto::Profile::CHLCC::TipsNotification;\nusing namespace Impacto::UI::Widgets;\n\nTipsNotification::TipsNotification() {\n  const auto headerText =\n      Vm::ScriptGetTextTableStrAddress(TextTableId, HeaderMessageId);\n  Header = Label(headerText, HeaderPosition, HeaderFontSize,\n                 RendererOutlineMode::BottomRight, HeaderColor);\n  Header.OutlineAlphaEnabled = true;\n\n  const auto beforeText = Vm::ScriptGetTextTableStrAddress(\n      TextTableId, NotificationTextPart1MessageId);\n  TextPartBefore = Label(beforeText, TextStartPosition, TextFontSize,\n                         RendererOutlineMode::BottomRight, 0);\n  TextPartBefore.OutlineAlphaEnabled = true;\n\n  const auto afterText = Vm::ScriptGetTextTableStrAddress(\n      TextTableId, NotificationTextPart2MessageId);\n  TextPartAfter =\n      Label(afterText,\n            TextStartPosition + glm::vec2(TextPartBefore.Bounds.Width, 0.0f),\n            TextFontSize, RendererOutlineMode::BottomRight, 0);\n  TextPartAfter.OutlineAlphaEnabled = true;\n\n  TipName = Label(\n      \"\", TextStartPosition + glm::vec2(TextPartBefore.Bounds.Width, 0.0f),\n      TextFontSize, RendererOutlineMode::BottomRight,\n      static_cast<int>(TipNameColorIndex));\n  TipName.OutlineAlphaEnabled = true;\n\n  FadeAnimation.DurationIn = FadeInDuration;\n  FadeAnimation.DurationOut = FadeOutDuration;\n\n  FadeOutAnimation = FadeAnimation;\n\n  SlideAnimation.DurationIn = SlideTime;\n\n  HoldAnimation.DurationIn = HoldTime;\n\n  TipsAnimation.AddAnimation(FadeAnimation, 0.0f);\n  TipsAnimation.AddAnimation(SlideAnimation, 0.0f);\n  TipsAnimation.AddAnimation(HoldAnimation);\n  TipsAnimation.AddAnimation(FadeOutAnimation, AnimationDirection::Out);\n}\n\nvoid TipsNotification::Update(const float dt) {\n  TipsAnimation.Update(dt);\n\n  if (TipsAnimation.State == AnimationState::Stopped &&\n      !NotificationQueue.empty()) {\n    // Start display animation\n    const auto tipNameAdr = NotificationQueue.front();\n    const auto tipsScrBufId = TipsSystem::GetTipsScriptBufferId();\n    TipName.SetText({.ScriptBufferId = tipsScrBufId, .IpOffset = tipNameAdr},\n                    TextFontSize, RendererOutlineMode::BottomRight,\n                    static_cast<int>(TipNameColorIndex));\n    NotificationQueue.pop();\n\n    TipsAnimation.StartIn(true);\n\n    FirstIsRendering = FirstInQueue;\n    FirstInQueue = false;\n  }\n\n  if (TipsAnimation.State != AnimationState::Stopped) {\n    // Update labels\n    const float slideProgress =\n        std::sin(SlideAnimation.Progress * std::numbers::pi_v<float> / 2.0f);\n    TextPartBefore.MoveTo(TextTargetPosition * slideProgress +\n                          TextStartPosition * (1.0f - slideProgress));\n    TipName.MoveTo(TextPartBefore.Bounds.GetPos() +\n                   glm::vec2(TextPartBefore.Bounds.Width, 0.0f));\n    TextPartAfter.MoveTo(TipName.Bounds.GetPos() +\n                         glm::vec2(TipName.Bounds.Width, 0.0f));\n\n    // Don't fade out if not the last entry in the queue,\n    // and don't fade in if not the first\n    float alpha = 1.0f;\n    if (FadeOutAnimation.State == AnimationState::Playing) {  // Fading out\n      if (NotificationQueue.empty()) alpha = FadeOutAnimation.Progress;\n    } else if (FirstIsRendering) {  // Fading in\n      alpha = FadeAnimation.Progress;\n    }\n\n    Header.Tint.a = alpha;\n    TextPartBefore.Tint.a = alpha;\n    TipName.Tint.a = alpha;\n    TextPartAfter.Tint.a = alpha;\n\n    Header.OutlineAlpha = alpha / 2.0f;\n    TextPartBefore.OutlineAlpha = alpha / 2.0f;\n    TipName.OutlineAlpha = alpha / 2.0f;\n    TextPartAfter.OutlineAlpha = alpha / 2.0f;\n  }\n}\n\nvoid TipsNotification::Render() {\n  if (TipsAnimation.State == AnimationState::Stopped) return;\n\n  Renderer->EnableScissor();\n  Renderer->SetScissorRect(RenderBounds);\n\n  Header.Render();\n  TextPartBefore.Render();\n  TipName.Render();\n  TextPartAfter.Render();\n\n  Renderer->DisableScissor();\n}\n\nvoid TipsNotification::AddTip(const int tipId) {\n  FirstInQueue |= TipsAnimation.State == AnimationState::Stopped &&\n                  NotificationQueue.empty();\n\n  const auto* const record = TipsSystem::GetTipRecord(tipId);\n  NotificationQueue.push(record->StringAdr[0]);\n}\n\n}  // namespace CHLCC\n}  // namespace Impacto\n"
  },
  {
    "path": "src/hud/chlcc/tipsnotification.h",
    "content": "#pragma once\n\n#include \"../tipsnotification.h\"\n#include \"../../ui/widgets/label.h\"\n#include \"../../sequencedanimation.h\"\n\nnamespace Impacto {\nnamespace CHLCC {\n\nusing namespace Impacto::TipsNotification;\n\nclass TipsNotification : public TipsNotificationBase {\n public:\n  TipsNotification();\n\n  void Update(float dt) override;\n  void Render() override;\n  void AddTip(int tipId) override;\n\n private:\n  UI::Widgets::Label Header;\n\n  UI::Widgets::Label TextPartBefore;\n  UI::Widgets::Label TextPartAfter;\n  UI::Widgets::Label TipName;\n\n  SequencedAnimation TipsAnimation;\n\n  Animation FadeOutAnimation;\n  Animation SlideAnimation;\n  Animation HoldAnimation;\n\n  bool FirstInQueue = true;\n  bool FirstIsRendering = false;\n};\n\n}  // namespace CHLCC\n}  // namespace Impacto\n"
  },
  {
    "path": "src/hud/datedisplay.cpp",
    "content": "#include \"datedisplay.h\"\n\n#include \"../profile/hud/datedisplay.h\"\n\nnamespace Impacto {\nnamespace DateDisplay {\n\nvoid Init() {\n  Profile::DateDisplay::Configure();\n  if (Implementation) {\n    Implementation->FadeAnimation.DurationIn =\n        Profile::DateDisplay::FadeInDuration;\n    Implementation->FadeAnimation.DurationOut =\n        Profile::DateDisplay::FadeOutDuration;\n  }\n}\n\nvoid Update(float dt) {\n  if (Implementation) Implementation->Update(dt);\n}\nvoid Render() {\n  if (Implementation) Implementation->Render();\n}\n\n}  // namespace DateDisplay\n}  // namespace Impacto"
  },
  {
    "path": "src/hud/datedisplay.h",
    "content": "#pragma once\n\n#include <magic_enum/magic_enum.hpp>\n#include \"../animation.h\"\n\nnamespace Impacto {\nnamespace DateDisplay {\n\nenum class DateDisplayType : int {\n  None,\n  RNE,\n  Darling,\n};\nclass DateDisplayBase {\n public:\n  virtual void Update(float dt) = 0;\n  virtual void Render() = 0;\n\n  Animation FadeAnimation;\n\n protected:\n  int Day = 0;\n  int Month = 0;\n  int Year = 0;\n  int Week = 0;\n};\n\ninline DateDisplayBase* Implementation = nullptr;\n\nvoid Init();\nvoid Update(float dt);\nvoid Render();\n\n}  // namespace DateDisplay\n}  // namespace Impacto"
  },
  {
    "path": "src/hud/defaultsaveiconanimation.cpp",
    "content": "#include \"defaultsaveiconanimation.h\"\n#include \"../renderer/renderer.h\"\n\n#include \"../profile/hud/saveicon.h\"\n\nnamespace Impacto {\nnamespace UI {\n\nDefaultSaveIconAnimation::DefaultSaveIconAnimation(\n    SpriteAnimationDef& foregroundAnimDef, Sprite backgroundSprite,\n    glm::vec2 backgroundOffset, float backgroundMaxAlpha, float fadeInDuration,\n    float fadeOutDuration)\n    : BackgroundSprite(backgroundSprite),\n      BackgroundOffset(backgroundOffset),\n      BackgroundMaxAlpha(backgroundMaxAlpha) {\n  ForegroundAnimation = foregroundAnimDef.Instantiate();\n  ForegroundAnimation.LoopMode = AnimationLoopMode::Loop;\n\n  FadeAnimation.DurationIn = fadeInDuration;\n  FadeAnimation.DurationOut = fadeOutDuration;\n}\n\nvoid DefaultSaveIconAnimation::Update(float dt) {\n  FadeAnimation.Update(dt);\n  // Images is fading-in and then ForegroundAnimation is playing, and in reverse\n  // for fade out\n  if (FadeAnimation.IsStopped()) {\n    ForegroundAnimation.Update(dt);\n  }\n}\n\nvoid DefaultSaveIconAnimation::Render(glm::vec2 position) {\n  if (FadeAnimation.IsOut()) return;\n\n  glm::vec4 col(1.0f);\n  col.a = glm::smoothstep(0.0f, 1.0f, FadeAnimation.Progress);\n\n  const Sprite bgSprite = GetBackgroundSprite();\n  const glm::vec2 bgSize = bgSprite.Bounds.GetSize();\n  // run less logic if bg sprite is a stub sprite with {0,0} dimensions\n  if (bgSize.x > 0 && bgSize.y > 0) {\n    glm::vec4 tint(col);\n    tint.a *= GetBackgroundMaxAlpha();\n    Renderer->DrawSprite(bgSprite, position + GetBackgroundOffset(), tint);\n  }\n\n  // if FadeAnimation is playing, should show full visible sprite\n  Renderer->DrawSprite(\n      FadeAnimation.IsPlaying()\n          ? ForegroundAnimation.Def\n                ->Frames[Profile::SaveIcon::FullyVisibleSpriteIndex]\n          : ForegroundAnimation.CurrentSprite(),\n      position, col);\n}\n\nvoid DefaultSaveIconAnimation::Show() {\n  FadeAnimation.StartIn(true);\n  ForegroundAnimation.StartIn(true);\n}\n\nvoid DefaultSaveIconAnimation::Hide() {\n  FadeAnimation.StartOut();\n  ForegroundAnimation.StartOut();\n}\n\n}  // namespace UI\n}  // namespace Impacto\n"
  },
  {
    "path": "src/hud/defaultsaveiconanimation.h",
    "content": "#pragma once\n\n#include \"../animation.h\"\n#include \"../spriteanimation.h\"\n\nnamespace Impacto {\nnamespace UI {\n\nclass DefaultSaveIconAnimation {\n public:\n  SpriteAnimation ForegroundAnimation;\n  Animation FadeAnimation;\n  Sprite BackgroundSprite;\n  glm::vec2 BackgroundOffset;\n  float BackgroundMaxAlpha;\n  DefaultSaveIconAnimation() = default;\n  DefaultSaveIconAnimation(SpriteAnimationDef& foregroundAnimDef,\n                           Sprite backgroundSprite, glm::vec2 backgroundOffset,\n                           float backgroundMaxAlpha, float fadeInDuration,\n                           float fadeOutDuration);\n\n  void Update(float dt);\n  void Render(glm::vec2 position);\n  Sprite GetBackgroundSprite() const { return BackgroundSprite; }\n  glm::vec2 GetBackgroundOffset() const { return BackgroundOffset; }\n  float GetBackgroundMaxAlpha() const { return BackgroundMaxAlpha; }\n\n  void Show();\n  void Hide();\n};\n\n}  // namespace UI\n}  // namespace Impacto\n"
  },
  {
    "path": "src/hud/dialoguebox.cpp",
    "content": "#include \"dialoguebox.h\"\n\n#include \"mo6tw/dialoguebox.h\"\n#include \"chlcc/dialoguebox.h\"\n#include \"cc/dialoguebox.h\"\n\n#include \"../text/dialoguepage.h\"\n#include \"../renderer/renderer.h\"\n#include \"../profile/dialogue.h\"\n#include \"../profile/game.h\"\n#include \"../mem.h\"\n#include \"../log.h\"\n#include \"../profile/scriptvars.h\"\n#include \"../profile/vm.h\"\n#include \"../vm/vm.h\"\n#include \"../character2d.h\"\n\nnamespace Impacto {\n\nusing namespace Impacto::Profile::ScriptVars;\nusing namespace Impacto::Profile::Dialogue;\n\nstd::unique_ptr<DialogueBox> DialogueBox::Create() {\n  switch (DialogueBoxCurrentType) {\n    default:\n      ImpLog(LogLevel::Warning, LogChannel::General,\n             \"Attempting to create unexpected DialogueBox type {:d}; \"\n             \"defaulting to Void\",\n             static_cast<int>(DialogueBoxCurrentType));\n      [[fallthrough]];\n    case DialogueBoxType::None:\n      return std::make_unique<VoidDialogueBox>();\n\n    case DialogueBoxType::MO6TW:\n      return std::make_unique<MO6TW::DialogueBox>();\n\n    case DialogueBoxType::CHLCC:\n      return std::make_unique<CHLCC::DialogueBox>();\n\n    case DialogueBoxType::CC:\n      return std::make_unique<CC::DialogueBox>();\n\n    case DialogueBoxType::Plain:\n      return std::make_unique<PlainDialogueBox>();\n  }\n}\n\nPlainDialogueBox::PlainDialogueBox() {\n  using namespace UI::Widgets;\n\n  Sprite nullSprite = Sprite();\n  nullSprite.Bounds = RectF(0.0f, 0.0f, 0.0f, 0.0f);\n\n  if (HasAutoButton) {\n    ControlButtons.emplace_back(std::make_unique<Button>(\n        0, AutoButtonSprite, AutoButtonSprite, nullSprite, AutoButtonPosition));\n  }\n\n  if (HasSkipButton) {\n    ControlButtons.emplace_back(std::make_unique<Button>(\n        0, SkipButtonSprite, SkipButtonSprite, nullSprite, SkipButtonPosition));\n  }\n\n  if (HasBacklogButton) {\n    ControlButtons.emplace_back(\n        std::make_unique<Button>(0, BacklogButtonSprite, BacklogButtonSprite,\n                                 nullSprite, BacklogButtonPosition));\n  }\n\n  if (HasMenuButton) {\n    ControlButtons.emplace_back(std::make_unique<Button>(\n        0, MenuButtonSprite, MenuButtonSprite, nullSprite, MenuButtonPosition));\n  }\n}\n\nvoid PlainDialogueBox::Show() {\n  DialogueBox::Show();\n  VisibilityState = Shown;\n}\n\nvoid PlainDialogueBox::Hide() {\n  DialogueBox::Hide();\n  VisibilityState = Hidden;\n}\n\nvoid PlainDialogueBox::UpdateInput(float dt) {\n  DialogueBox::UpdateInput(dt);\n  UpdateControlButtons(dt);\n}\n\nvoid PlainDialogueBox::Render(const DialoguePageMode mode,\n                              const NameInfo& nameInfo, glm::vec4 tint) {\n  tint = glm::vec4(glm::vec3(ScrWorkGetColor(SW_MESWINDOW_COLOR)), tint.a);\n\n  if (mode == DPM_ADV) {\n    Renderer->DrawSprite(ADVBoxSprite, ADVBoxPos, tint);\n    RenderControlButtons(tint);\n\n    if (!nameInfo.Name.empty()) {\n      if (HasSpeakerPortraits) {\n        // Draw Face\n        for (int i = 0; i < std::ssize(SpeakerPortraits); i++) {\n          int bufId = ScrWork[SW_FACE1SURF + i];\n          SpeakerPortraits[bufId].UpdateState(i);\n\n          SpeakerPortraits[bufId].Tint = tint;\n          SpeakerPortraits[bufId].Position +=\n              glm::vec2(SpeakerPortraitBaseOffsetX, SpeakerPortraitBaseOffsetY);\n\n          SpeakerPortraits[bufId].Render(-1);\n        }\n      }\n    }\n\n    NametagDisplayInst->Render(nameInfo, tint);\n\n  } else {\n    assert(mode == DPM_REV);\n\n    glm::vec4 nvlBoxTint(0.0f, 0.0f, 0.0f, tint.a * NVLBoxMaxOpacity);\n    Renderer->DrawQuad(RectF(0, 0, Profile::DesignWidth, Profile::DesignHeight),\n                       nvlBoxTint);\n  }\n}\n\nvoid PlainDialogueBox::UpdateControlButtons(float dt) {\n  for (auto& button : ControlButtons) {\n    button->UpdateInput(dt);\n    button->Update(dt);\n  }\n}\n\nvoid PlainDialogueBox::RenderControlButtons(glm::vec4 col) {\n  for (auto& button : ControlButtons) {\n    button->Tint = col;\n    button->Render();\n  }\n}\n\nvoid VoidDialogueBox::Show() {\n  DialogueBox::Show();\n  VisibilityState = Shown;\n}\n\nvoid VoidDialogueBox::Hide() {\n  DialogueBox::Hide();\n  VisibilityState = Hidden;\n}\n\n}  // namespace Impacto"
  },
  {
    "path": "src/hud/dialoguebox.h",
    "content": "#pragma once\n\n#include <magic_enum/magic_enum.hpp>\n#include <optional>\n#include <ankerl/unordered_dense.h>\n#include \"../text/text.h\"\n#include \"../ui/widgets/button.h\"\n#include \"nametagdisplay.h\"\n\nnamespace Impacto {\n\nenum DialoguePageMode : uint8_t;\n\nenum class DialogueBoxType : int {\n  None,\n  Plain,\n  MO6TW,\n  CHLCC,\n  CC,\n};\nclass DialogueBox {\n public:\n  static std::unique_ptr<DialogueBox> Create();\n  virtual ~DialogueBox() = default;\n\n  virtual void Show() { ShowName(); }\n  virtual void Hide() { HideName(); }\n  void ShowName() { NametagDisplayInst->Show(); }\n  void HideName() { NametagDisplayInst->Hide(); }\n\n  virtual void Update(float dt) { NametagDisplayInst->Update(dt); }\n  virtual void UpdateInput(float dt) {}\n\n  virtual void Render(DialoguePageMode mode, const NameInfo& nameInfo,\n                      glm::vec4 tint = glm::vec4(1.0f)) = 0;\n\n  enum class VisibilityStateType { Hidden, Hiding, Showing, Shown };\n  using enum VisibilityStateType;\n  VisibilityStateType VisibilityState = Hidden;\n\n protected:\n  std::unique_ptr<NametagDisplay> NametagDisplayInst = NametagDisplay::Create();\n};\n\nclass PlainDialogueBox : public DialogueBox {\n public:\n  PlainDialogueBox();\n\n  void Show() override;\n  void Hide() override;\n  void UpdateInput(float dt) override;\n  void Render(DialoguePageMode mode, const NameInfo& nameInfo,\n              glm::vec4 tint) override;\n\n private:\n  std::vector<std::unique_ptr<UI::Widgets::Button>> ControlButtons;\n\n  void UpdateControlButtons(float dt);\n  void RenderControlButtons(glm::vec4 col);\n};\n\nclass VoidDialogueBox : public DialogueBox {\n public:\n  void Show() override;\n  void Hide() override;\n  void Render(DialoguePageMode mode, const NameInfo& nameInfo,\n              glm::vec4 tint) override {}\n};\n\n}  // namespace Impacto\n"
  },
  {
    "path": "src/hud/loadingdisplay.cpp",
    "content": "#include \"loadingdisplay.h\"\n\n#include \"../impacto.h\"\n#include \"../renderer/renderer.h\"\n#include \"../game.h\"\n#include \"../mem.h\"\n#include \"../profile/scriptvars.h\"\n#include \"../spriteanimation.h\"\n#include \"../profile/hud/loadingdisplay.h\"\n\nnamespace Impacto {\nnamespace LoadingDisplay {\n\nusing namespace Impacto::Profile::ScriptVars;\n\nstatic bool IsResourceLoad = false;\n\nstatic SpriteAnimation ResourceLoadBg;\nstatic SpriteAnimation SaveLoadBg;\nstatic SpriteAnimation LoadingIcon;\nstatic SpriteAnimation LoadingText;\nstatic Animation FadeAnimation;\n\nvoid Init() {\n  Profile::LoadingDisplay::Configure();\n  FadeAnimation.DurationIn = Profile::LoadingDisplay::FadeInDuration;\n  FadeAnimation.DurationOut = Profile::LoadingDisplay::FadeOutDuration;\n\n  SaveLoadBg = Profile::LoadingDisplay::SaveLoadBgAnim.Instantiate();\n  SaveLoadBg.LoopMode = AnimationLoopMode::Loop;\n  ResourceLoadBg = Profile::LoadingDisplay::ResourceLoadBgAnim.Instantiate();\n  ResourceLoadBg.LoopMode = AnimationLoopMode::Loop;\n  LoadingIcon = Profile::LoadingDisplay::LoadingIconAnim.Instantiate();\n  LoadingIcon.LoopMode = AnimationLoopMode::Loop;\n  LoadingText = Profile::LoadingDisplay::LoadingTextAnim.Instantiate();\n  LoadingText.LoopMode = AnimationLoopMode::Loop;\n}\n\nvoid Update(float dt) {\n  FadeAnimation.Update(dt);\n\n  if (FadeAnimation.IsOut()) {\n    if (GetFlag(SF_LOADINGFROMSAVE)) {\n      FadeAnimation.StartIn();\n      IsResourceLoad = false;\n\n      SaveLoadBg.StartIn(true);\n      LoadingIcon.StartIn(true);\n      LoadingText.StartIn(true);\n    } else if (GetFlag(SF_LOADING)) {\n      FadeAnimation.StartIn();\n      IsResourceLoad = true;\n\n      ResourceLoadBg.StartIn(true);\n      LoadingIcon.StartIn(true);\n      LoadingText.StartIn(true);\n    }\n  } else if (FadeAnimation.IsIn() && !GetFlag(SF_LOADINGFROMSAVE) &&\n             !GetFlag(SF_LOADING)) {\n    FadeAnimation.StartOut();\n  }\n\n  if (!FadeAnimation.IsOut()) {\n    LoadingIcon.Update(dt);\n    LoadingText.Update(dt);\n    if (IsResourceLoad) {\n      ResourceLoadBg.Update(dt);\n    } else {\n      SaveLoadBg.Update(dt);\n    }\n  }\n}\n\nvoid Render() {\n  if (FadeAnimation.IsOut()) return;\n\n  glm::vec4 col(1.0f);\n  col.a = glm::smoothstep(0.0f, 1.0f, FadeAnimation.Progress);\n\n  if (IsResourceLoad) {\n    Renderer->DrawSprite(ResourceLoadBg.CurrentSprite(),\n                         Profile::LoadingDisplay::ResourceBgPos, col);\n  } else {\n    Renderer->DrawSprite(SaveLoadBg.CurrentSprite(),\n                         Profile::LoadingDisplay::SaveBgPos, col);\n  }\n\n  Renderer->DrawSprite(LoadingIcon.CurrentSprite(),\n                       Profile::LoadingDisplay::IconPos, col);\n  Renderer->DrawSprite(LoadingText.CurrentSprite(),\n                       Profile::LoadingDisplay::TextPos, col);\n}\n\n}  // namespace LoadingDisplay\n}  // namespace Impacto"
  },
  {
    "path": "src/hud/loadingdisplay.h",
    "content": "#pragma once\n\nnamespace Impacto {\nnamespace LoadingDisplay {\n\nvoid Init();\nvoid Update(float dt);\nvoid Render();\n}  // namespace LoadingDisplay\n}  // namespace Impacto"
  },
  {
    "path": "src/hud/mo6tw/dialoguebox.cpp",
    "content": "#include \"dialoguebox.h\"\n\n#include \"../../renderer/renderer.h\"\n#include \"../../text/dialoguepage.h\"\n#include \"../../profile/dialogue.h\"\n#include \"../../profile/game.h\"\n#include \"../../profile/games/mo6tw/dialoguebox.h\"\n#include \"../../mem.h\"\n#include \"../../profile/scriptvars.h\"\n\nnamespace Impacto {\nnamespace MO6TW {\n\nusing namespace Impacto::Profile::ScriptVars;\nusing namespace Impacto::Profile::Dialogue;\nusing namespace Impacto::Profile::MO6TW::DialogueBox;\n\nvoid DialogueBox::Render(const DialoguePageMode mode, const NameInfo& nameInfo,\n                         glm::vec4 tint) {\n  tint = glm::vec4(glm::vec3(ScrWorkGetColor(SW_MESWINDOW_COLOR)), tint.a);\n\n  if (mode == DPM_ADV) {\n    Renderer->DrawSprite(ADVBoxPartLeft, ADVBoxPartLeftPos, tint);\n    Renderer->DrawSprite(ADVBoxSprite, ADVBoxPos, tint);\n    Renderer->DrawSprite(ADVBoxPartRight, ADVBoxPartRightPos, tint);\n    Renderer->DrawSprite(ADVBoxDecoration, ADVBoxDecorationPos, tint);\n\n    NametagDisplayInst->Render(nameInfo, tint);\n\n  } else if (mode == DPM_NVL) {\n    glm::vec4 nvlBoxTint(0.0f, 0.0f, 0.0f, tint.a * NVLBoxMaxOpacity);\n    Renderer->DrawQuad(RectF(0, 0, Profile::DesignWidth, Profile::DesignHeight),\n                       nvlBoxTint);\n  }\n}\n\n}  // namespace MO6TW\n}  // namespace Impacto"
  },
  {
    "path": "src/hud/mo6tw/dialoguebox.h",
    "content": "#pragma once\n\n#include \"../dialoguebox.h\"\n\nnamespace Impacto {\nnamespace MO6TW {\n\nclass DialogueBox : public Impacto::DialogueBox {\n public:\n  void Render(DialoguePageMode mode, const NameInfo& nameInfo,\n              glm::vec4 tint) override;\n};\n\n}  // namespace MO6TW\n}  // namespace Impacto"
  },
  {
    "path": "src/hud/mo6tw/tipsnotification.cpp",
    "content": "#include \"tipsnotification.h\"\n\n#include \"../../profile/scriptvars.h\"\n#include \"../../impacto.h\"\n#include \"../../mem.h\"\n#include \"../../renderer/renderer.h\"\n#include \"../../game.h\"\n#include \"../../profile/hud/tipsnotification.h\"\n#include \"../../profile/games/mo6tw/tipsnotification.h\"\n#include \"../../data/tipssystem.h\"\n#include \"../../ui/menu.h\"\n\nnamespace Impacto {\nnamespace MO6TW {\n\nusing namespace Impacto::Profile::TipsNotification;\nusing namespace Impacto::Profile::MO6TW::TipsNotification;\nusing namespace Impacto::Profile::ScriptVars;\nusing namespace Impacto::UI;\nusing namespace Impacto::UI::Widgets;\n\nTipsNotification::TipsNotification() {\n  FadeAnimation.DurationIn = FadeInDuration;\n  FadeAnimation.DurationOut = FadeOutDuration;\n\n  Timer.DurationIn = TimerDuration;\n  Timer.LoopMode = AnimationLoopMode::Stop;\n\n  AlertTitle = new Group(NULL);\n  AlertTitle->FocusLock = true;\n  AlertTitle->VisibilityState = Shown;\n  AlertTitle->HasFocus = false;\n\n  Notification = new Group(NULL);\n  Notification->FocusLock = true;\n  Notification->VisibilityState = Shown;\n  Notification->HasFocus = false;\n\n  auto textAlert =\n      Vm::ScriptGetTextTableStrAddress(TextTableId, NotificationAlertMessageId);\n  TextAlert = new Label(textAlert, AlertPosition, FontSize,\n                        RendererOutlineMode::Full, AlertTextColorIndex);\n  AlertTitle->Add(TextAlert);\n\n  auto textBefore = Vm::ScriptGetTextTableStrAddress(\n      TextTableId, NotificationTextPart1MessageId);\n  TextPartBefore = new Label(textBefore, FinalNotificationPosition, FontSize,\n                             RendererOutlineMode::Full, 0);\n  Notification->Add(TextPartBefore);\n  auto textAfter = Vm::ScriptGetTextTableStrAddress(\n      TextTableId, NotificationTextPart2MessageId);\n  TextPartAfter = new Label(\n      textAfter,\n      glm::vec2(FinalNotificationPosition.x + TextPartBefore->Bounds.Width,\n                FinalNotificationPosition.y),\n      FontSize, RendererOutlineMode::Full, 0);\n  Notification->Add(TextPartAfter);\n  TipName = new Label();\n  Notification->Add(TipName);\n  Notification->Bounds.X = FinalNotificationPosition.x;\n  Notification->Bounds.Y = FinalNotificationPosition.y;\n  Notification->RenderingBounds = NotificationRenderingBounds;\n}\n\nvoid TipsNotification::Update(float dt) {\n  FadeAnimation.Update(dt);\n  Timer.Update(dt);\n  Notification->Update(dt);\n  if (!NotificationQueue.empty() && !FadeAnimation.IsIn()) {\n    FadeAnimation.StartIn();\n  }\n  if (FadeAnimation.IsIn() && Timer.IsOut()) {\n    Timer.StartIn();\n    auto tipNameAdr = NotificationQueue.front();\n    auto tipsScrBufId = TipsSystem::GetTipsScriptBufferId();\n    TipName->SetText({.ScriptBufferId = tipsScrBufId, .IpOffset = tipNameAdr},\n                     FontSize, RendererOutlineMode::Full, TipNameColorIndex);\n    TipName->MoveTo(\n        glm::vec2(FinalNotificationPosition.x + TextPartBefore->Bounds.Width,\n                  FinalNotificationPosition.y));\n    TextPartAfter->MoveTo(glm::vec2(FinalNotificationPosition.x +\n                                        TextPartBefore->Bounds.Width +\n                                        TipName->Bounds.Width,\n                                    FinalNotificationPosition.y));\n\n    NotificationQueue.pop();\n\n    Notification->MoveTo(InitialNotificationPosition);\n    Notification->MoveTo(FinalNotificationPosition, MoveAnimationDuration);\n  }\n  if (NotificationQueue.empty() && FadeAnimation.IsIn() && Timer.IsIn()) {\n    FadeAnimation.StartOut();\n  }\n  if (Timer.IsIn()) Timer.Progress = 0.0f;\n}\n\nvoid TipsNotification::Render() {\n  if (FadeAnimation.IsOut()) return;\n  if (FadeAnimation.Progress > 0.0f) {\n    float smoothedFade = glm::smoothstep(0.0f, 1.0f, FadeAnimation.Progress);\n    AlertTitle->Tint.a = smoothedFade;\n    AlertTitle->Render();\n    if (Timer.State == AnimationState::Playing ||\n        FadeAnimation.Direction == AnimationDirection::Out) {\n      Notification->Tint.a = smoothedFade;\n      Notification->Render();\n    }\n  }\n}\n\nvoid TipsNotification::AddTip(int tipId) {\n  const auto* const record = TipsSystem::GetTipRecord(tipId);\n  NotificationQueue.push(record->StringAdr[0]);\n}\n\n}  // namespace MO6TW\n}  // namespace Impacto"
  },
  {
    "path": "src/hud/mo6tw/tipsnotification.h",
    "content": "#pragma once\n\n#include \"../tipsnotification.h\"\n#include \"../../ui/widgets/label.h\"\n#include \"../../ui/widgets/group.h\"\n\nnamespace Impacto {\nnamespace MO6TW {\n\nusing namespace Impacto::TipsNotification;\n\nclass TipsNotification : public TipsNotificationBase {\n public:\n  TipsNotification();\n\n  void Update(float dt);\n  void Render();\n  void AddTip(int tipId);\n\n protected:\n  Animation Timer;\n\n  UI::Widgets::Group* AlertTitle;\n  UI::Widgets::Group* Notification;\n\n  UI::Widgets::Label* TextAlert;\n  UI::Widgets::Label* TextPartBefore;\n  UI::Widgets::Label* TextPartAfter;\n  UI::Widgets::Label* TipName;\n};\n\n}  // namespace MO6TW\n}  // namespace Impacto"
  },
  {
    "path": "src/hud/nametagdisplay.cpp",
    "content": "#include \"nametagdisplay.h\"\n\n#include \"../profile/dialogue.h\"\n#include \"chlcc/nametagdisplay.h\"\n#include \"cc/nametagdisplay.h\"\n#include \"../log.h\"\n\n#include <numeric>\n\nnamespace Impacto {\n\nusing namespace Impacto::Profile::Dialogue;\n\nstd::unique_ptr<NametagDisplay> NametagDisplay::Create() {\n  switch (NametagCurrentType) {\n    default:\n      ImpLog(LogLevel::Warning, LogChannel::General,\n             \"Attempted to create unexpected nametag type {:d}; defaulting to \"\n             \"Void\",\n             static_cast<int>(NametagCurrentType));\n      [[fallthrough]];\n    case NametagType::None:\n      return std::make_unique<VoidNametagDisplay>();\n\n    case NametagType::Sprite:\n      return std::make_unique<SpriteNametagDisplay>();\n\n    case NametagType::TwoPiece:\n    case NametagType::ThreePiece:\n      return std::make_unique<ThreePieceNametagDisplay>();\n\n    case NametagType::CHLCC:\n      return std::make_unique<CHLCC::NametagDisplay>();\n\n    case NametagType::CC:\n      return std::make_unique<CC::NametagDisplay>();\n  }\n}\n\nvoid VoidNametagDisplay::Render(const NameInfo& nameInfo,\n                                const glm::vec4 tint) {\n  if (Hidden || !nameInfo.RenderWindow) return;\n\n  Renderer->DrawProcessedText(nameInfo.Name, DialogueFont, tint.a,\n                              RendererOutlineMode::Full);\n}\n\nvoid SpriteNametagDisplay::Render(const NameInfo& nameInfo,\n                                  const glm::vec4 tint) {\n  if (Hidden || !nameInfo.RenderWindow) return;\n\n  Renderer->DrawSprite(NametagSprite, NametagPosition, tint);\n\n  Renderer->DrawProcessedText(nameInfo.Name, DialogueFont, tint.a,\n                              RendererOutlineMode::Full);\n}\n\nvoid ThreePieceNametagDisplay::Render(const NameInfo& nameInfo,\n                                      const glm::vec4 tint) {\n  if (Hidden || !nameInfo.RenderWindow) return;\n\n  Renderer->DrawSprite(NametagLeftSprite, NametagPosition, tint);\n\n  const RectF nameBounds = std::accumulate(\n      nameInfo.Name.begin() + 1, nameInfo.Name.end(), nameInfo.Name[0].DestRect,\n      [](const RectF rect, const ProcessedTextGlyph& glyph) {\n        return RectF::Coalesce(rect, glyph.DestRect);\n      });\n  float rightX =\n      NametagPosition.x + NametagLeftSprite.ScaledWidth() + nameBounds.Width;\n\n  if (NametagCurrentType == NametagType::ThreePiece) {\n    rightX -= NametagMiddleBaseWidth;\n\n    // Draw middle sprites\n    const float middleBegin =\n        NametagPosition.x + NametagLeftSprite.ScaledWidth();\n\n    Sprite middleSprite = NametagMiddleSprite;\n    for (float x = middleBegin; x < rightX;\n         x += NametagMiddleSprite.ScaledWidth()) {\n      middleSprite.SetScaledWidth(\n          std::min(rightX - x, NametagMiddleSprite.ScaledWidth()));\n      Renderer->DrawSprite(middleSprite, {x, NametagPosition.y}, tint);\n    }\n  }\n\n  Renderer->DrawSprite(NametagRightSprite, {rightX, NametagPosition.y}, tint);\n\n  Renderer->DrawProcessedText(nameInfo.Name, DialogueFont, tint.a,\n                              RendererOutlineMode::Full);\n}\n\n}  // namespace Impacto\n"
  },
  {
    "path": "src/hud/nametagdisplay.h",
    "content": "#pragma once\n\n#include \"../src/text/text.h\"\n\nnamespace Impacto {\n\nstruct NameInfo {\n  bool RenderWindow;\n  std::optional<int> NameId;\n  std::span<ProcessedTextGlyph> Name;\n};\n\nclass NametagDisplay {\n public:\n  static std::unique_ptr<NametagDisplay> Create();\n  virtual ~NametagDisplay() = default;\n\n  virtual void Render(const NameInfo& nameInfo, glm::vec4 tint) = 0;\n  virtual void Update(float dt) = 0;\n  virtual void Reset() = 0;\n\n  virtual void Show() { Hidden = false; }\n  virtual void Hide() { Hidden = true; }\n\n protected:\n  bool Hidden = true;\n};\n\nclass VoidNametagDisplay : public NametagDisplay {\n public:\n  void Render(const NameInfo& nameInfo, glm::vec4 tint) override;\n  void Update(float dt) override {}\n  void Reset() override {}\n};\n\nclass SpriteNametagDisplay : public NametagDisplay {\n public:\n  void Render(const NameInfo& nameInfo, glm::vec4 tint) override;\n  void Update(float dt) override {}\n  void Reset() override {}\n};\n\nclass ThreePieceNametagDisplay : public NametagDisplay {\n public:\n  void Render(const NameInfo& nameInfo, glm::vec4 tint) override;\n  void Update(float dt) override {}\n  void Reset() override {}\n};\n\n}  // namespace Impacto\n"
  },
  {
    "path": "src/hud/rne/datedisplay.cpp",
    "content": "#include \"datedisplay.h\"\n\n#include \"../../profile/scriptvars.h\"\n#include \"../../mem.h\"\n#include \"../../renderer/renderer.h\"\n#include \"../../profile/hud/datedisplay.h\"\n\n#include <ctime>\n\nnamespace Impacto {\nnamespace RNE {\n\nusing namespace Impacto::Profile::DateDisplay;\nusing namespace Impacto::Profile::ScriptVars;\n\nvoid DateDisplay::Update(float dt) {\n  FadeAnimation.Update(dt);\n  if (FadeAnimation.IsOut()) {\n    if (ScrWork[LR_DATE] != 0 && GetFlag(SF_DATEDISPLAY)) {\n      FadeAnimation.StartIn();\n      Year = (ScrWork[LR_DATE] / 10000);\n      Month = (ScrWork[LR_DATE] - 10000 * Year) / 100;\n      Day = (ScrWork[LR_DATE] - 10000 * Year) % 100;\n      Week = CurrentDateTime().tm_wday;\n    }\n  } else if (FadeAnimation.IsIn() && !GetFlag(SF_DATEDISPLAY)) {\n    FadeAnimation.StartOut();\n  }\n}\n\nvoid DateDisplay::Render() {\n  if (FadeAnimation.IsOut()) return;\n  if (FadeAnimation.Progress > 0.0f) {\n    float smoothedFade = glm::smoothstep(0.0f, 1.0f, FadeAnimation.Progress);\n\n    glm::vec4 col(1.0f);\n    col.a = smoothedFade;\n\n    Renderer->DrawSprite(\n        BackgroundSprite,\n        glm::mix(BackgroundStartPos, BackgroundEndPos, smoothedFade), col);\n\n    glm::vec2 pos(DateStartX, YearWeekY);\n    Renderer->DrawSprite(CloseBracketSprite, pos, col);\n\n    pos.x -= WeekSprites[Week].ScaledWidth() + Spacing;\n    Renderer->DrawSprite(WeekSprites[Week], pos, col);\n\n    pos.x -= OpenBracketSprite.ScaledWidth() + Spacing;\n    Renderer->DrawSprite(OpenBracketSprite, pos, col);\n\n    pos.x -= YearNumSprites[Year % 10].ScaledWidth();\n    Renderer->DrawSprite(YearNumSprites[Year % 10], pos, col);\n    pos.x -= YearNumSprites[Year / 10].ScaledWidth();\n    Renderer->DrawSprite(YearNumSprites[Year / 10], pos, col);\n    pos.x -= YearNumSprites[0].ScaledWidth();\n    Renderer->DrawSprite(YearNumSprites[0], pos, col);\n    pos.x -= YearNumSprites[2].ScaledWidth();\n    Renderer->DrawSprite(YearNumSprites[2], pos, col);\n\n    pos.x -= DYSeparatorSprite.ScaledWidth();\n    Renderer->DrawSprite(DYSeparatorSprite, pos, col);\n\n    pos.y = MonthDayY;\n    pos.x -= DayNumSprites[Day % 10].ScaledWidth() + Spacing;\n    Renderer->DrawSprite(DayNumSprites[Day % 10], pos, col);\n    pos.x -= DayNumSprites[Day / 10].ScaledWidth();\n    Renderer->DrawSprite(DayNumSprites[Day / 10], pos, col);\n\n    pos.x -= MDSeparatorSprite.ScaledWidth();\n    Renderer->DrawSprite(MDSeparatorSprite, pos, col);\n\n    pos.x -= MonthNumSprites[Month % 10].ScaledWidth() + Spacing;\n    Renderer->DrawSprite(MonthNumSprites[Month % 10], pos, col);\n    if (Month / 10 != 0) {\n      pos.x -= MonthNumSprites[Month / 10].ScaledWidth();\n      Renderer->DrawSprite(MonthNumSprites[Month / 10], pos, col);\n    }\n  }\n}\n\n}  // namespace RNE\n}  // namespace Impacto"
  },
  {
    "path": "src/hud/rne/datedisplay.h",
    "content": "#pragma once\n\n#include \"../datedisplay.h\"\n\nnamespace Impacto {\nnamespace RNE {\n\nusing namespace Impacto::DateDisplay;\n\nclass DateDisplay : public DateDisplayBase {\n public:\n  void Update(float dt);\n  void Render();\n};\n\n}  // namespace RNE\n}  // namespace Impacto"
  },
  {
    "path": "src/hud/saveicondisplay.cpp",
    "content": "#include \"saveicondisplay.h\"\n\n#include \"defaultsaveiconanimation.h\"\n#include \"../games/chlcc/animations/saveicon.h\"\n\n#include \"../profile/hud/saveicon.h\"\n\nnamespace Impacto {\nnamespace SaveIconDisplay {\nusing namespace Impacto::Profile::SaveIcon;\n\nstatic glm::vec2 Position;\n\nstatic std::variant<UI::DefaultSaveIconAnimation, UI::CHLCC::SaveIconAnimation>\n    IconAnimation;\nstatic Animation Timer;\n\nstatic bool IsTimed = false;\n\nvoid Init() {\n  Configure();\n  switch (SaveIconCurrentType) {\n    default:\n    case (SaveIconType::Default): {\n      IconAnimation = UI::DefaultSaveIconAnimation(\n          ForegroundAnimation, BackgroundSprite, BackgroundOffset,\n          BackgroundMaxAlpha, FadeInDuration, FadeOutDuration);\n      break;\n    }\n    case (SaveIconType::CHLCC): {\n      IconAnimation = UI::CHLCC::SaveIconAnimation(\n          ActiveAnimationDuration, FadeInDuration, FadeOutDuration,\n          std::span<Sprite, CHLCC_SAVE_ICON_SPRITES>(SaveIconSprites));\n      break;\n    }\n  }\n}\n\nvoid Hide() {\n  std::visit([](auto& anim) { anim.Hide(); }, IconAnimation);\n  IsTimed = false;\n}\n\nvoid Show() { ShowAt(DefaultPosition); }\nvoid ShowAt(glm::vec2 pos) {\n  Position = pos;\n  std::visit([](auto& anim) { anim.Show(); }, IconAnimation);\n}\nvoid ShowFor(float seconds) {\n  Timer.LoopMode = AnimationLoopMode::Stop;\n  Timer.SetDuration(seconds);\n  IsTimed = true;\n  Timer.StartIn(true);\n  Show();\n}\n\nvoid Update(float dt) {\n  Timer.Update(dt);\n  if (IsTimed && Timer.IsIn()) Hide();\n  std::visit([dt](auto& anim) { anim.Update(dt); }, IconAnimation);\n}\n\nvoid Render() {\n  std::visit([](auto& anim) { anim.Render(Position); }, IconAnimation);\n}\n\n}  // namespace SaveIconDisplay\n}  // namespace Impacto"
  },
  {
    "path": "src/hud/saveicondisplay.h",
    "content": "#pragma once\n\n#include <glm/glm.hpp>\n\nnamespace Impacto {\nnamespace SaveIconDisplay {\n\nvoid Init();\nvoid Hide();\nvoid Show();\nvoid ShowAt(glm::vec2 pos);\nvoid ShowFor(float seconds);\nvoid Update(float dt);\nvoid Render();\n\n}  // namespace SaveIconDisplay\n}  // namespace Impacto"
  },
  {
    "path": "src/hud/skipicondisplay.cpp",
    "content": "#include \"skipicondisplay.h\"\n\n#include \"../profile/dialogue.h\"\n#include \"../renderer/renderer.h\"\n#include \"../text/dialoguepage.h\"\n\nnamespace Impacto {\nnamespace SkipIconDisplay {\n\nstatic SpriteAnimation SpriteAnim;\nstatic FixedSpriteAnimation FixedSpriteAnim;\nstatic float Progress = 0.0f;\n\nusing namespace Impacto::Profile::Dialogue;\n\nvoid Init() {\n  switch (SkipIconCurrentType) {\n    case SkipIconType::SpriteAnim:\n      SpriteAnim = SkipIconSpriteAnim.Instantiate();\n      SpriteAnim.LoopMode = AnimationLoopMode::Stop;\n      SpriteAnim.StartIn();\n      break;\n\n    case SkipIconType::SpriteAnimFixed:\n      SpriteAnim = SkipIconSpriteAnim.Instantiate();\n      SpriteAnim.LoopMode = AnimationLoopMode::Stop;\n      FixedSpriteAnim = static_cast<FixedSpriteAnimation&>(SpriteAnim);\n      FixedSpriteAnim.Def->FixSpriteId = SkipIconFixedSpriteId;\n      FixedSpriteAnim.StartIn();\n      break;\n\n    case SkipIconType::None:\n    case SkipIconType::Fixed:\n    case SkipIconType::CHLCC:\n      break;\n  }\n}\n\nvoid Update(float dt) {\n  switch (SkipIconCurrentType) {\n    case SkipIconType::SpriteAnim:\n      if (SkipModeEnabled && SpriteAnim.Direction == AnimationDirection::Out)\n        SpriteAnim.StartIn();\n      if (!SkipModeEnabled && SpriteAnim.Direction == AnimationDirection::In)\n        SpriteAnim.StartOut();\n\n      SpriteAnim.Update(dt);\n      break;\n\n    case SkipIconType::SpriteAnimFixed:\n      if (SkipModeEnabled &&\n          FixedSpriteAnim.Direction == AnimationDirection::Out)\n        FixedSpriteAnim.StartIn();\n      if (!SkipModeEnabled &&\n          FixedSpriteAnim.Direction == AnimationDirection::In)\n        FixedSpriteAnim.StartOut();\n\n      FixedSpriteAnim.Update(dt);\n      break;\n\n    case SkipIconType::CHLCC:\n      Progress += dt * SkipIconRotationSpeed;\n      Progress -= (int)Progress;  // Progress %= 1.0f\n      break;\n\n    case SkipIconType::None:\n    case SkipIconType::Fixed:\n      break;\n  }\n}\n\nvoid Render(glm::vec4 opacityTint) {\n  switch (SkipIconCurrentType) {\n    case SkipIconType::SpriteAnim: {\n      if (!SpriteAnim.IsOut()) {\n        glm::vec4 col = glm::vec4(1.0f, 1.0f, 1.0f, opacityTint.a);\n        Renderer->DrawSprite(SpriteAnim.CurrentSprite(),\n                             glm::vec2(SkipIconOffset.x, SkipIconOffset.y),\n                             col);\n      }\n      break;\n    }\n\n    case SkipIconType::SpriteAnimFixed:\n      if (!FixedSpriteAnim.IsOut() &&\n          !(FixedSpriteAnim.Direction == AnimationDirection::Out &&\n            FixedSpriteAnim.Progress ==\n                FixedSpriteAnim.GetFixedSpriteProgress())) {\n        Renderer->DrawSprite(FixedSpriteAnim.CurrentSprite(),\n                             glm::vec2(SkipIconOffset.x, SkipIconOffset.y),\n                             opacityTint);\n      }\n      break;\n\n    case SkipIconType::CHLCC:\n      if (SkipModeEnabled) {\n        const CornersQuad arrowsDest =\n            AutoSkipArrowsSprite.ScaledBounds()\n                .RotateAroundCenter(Progress * 2.0f * std::numbers::pi_v<float>)\n                .Translate(SkipIconOffset);\n        Renderer->DrawSprite(AutoSkipArrowsSprite, arrowsDest, opacityTint);\n\n        Renderer->DrawSprite(SkipIconSprite,\n                             glm::vec2(SkipIconOffset.x, SkipIconOffset.y),\n                             opacityTint);\n      }\n      break;\n\n    case SkipIconType::None:\n    case SkipIconType::Fixed:\n      break;\n  }\n}\n\n}  // namespace SkipIconDisplay\n}  // namespace Impacto"
  },
  {
    "path": "src/hud/skipicondisplay.h",
    "content": "#pragma once\n\n#include <magic_enum/magic_enum.hpp>\n#include <glm/glm.hpp>\n\n#include \"dialoguebox.h\"\n\nnamespace Impacto {\nnamespace SkipIconDisplay {\n\nenum class SkipIconType : int {\n  None,\n  SpriteAnim,\n  SpriteAnimFixed,\n  Fixed,\n  CHLCC,\n};\nvoid Init();\nvoid Update(float dt);\nvoid Render(glm::vec4 opacityTint);\n\n}  // namespace SkipIconDisplay\n}  // namespace Impacto"
  },
  {
    "path": "src/hud/tipsnotification.cpp",
    "content": "#include \"datedisplay.h\"\n\n#include \"../profile/configsystem.h\"\n#include \"../profile/hud/tipsnotification.h\"\n\nnamespace Impacto {\nnamespace TipsNotification {\n\nvoid Init() { Profile::TipsNotification::Configure(); }\n\nvoid Update(float dt) {\n  if (Implementation) Implementation->Update(dt);\n}\nvoid Render() {\n  if (Implementation) Implementation->Render();\n}\n\nvoid AddTip(int tipId) {\n  if (Implementation && Profile::ConfigSystem::ShowTipsNotification)\n    Implementation->AddTip(tipId);\n}\n\n}  // namespace TipsNotification\n}  // namespace Impacto"
  },
  {
    "path": "src/hud/tipsnotification.h",
    "content": "#pragma once\n\n#include <queue>\n\n#include <magic_enum/magic_enum.hpp>\n#include \"../animation.h\"\n\nnamespace Impacto {\nnamespace TipsNotification {\n\nenum class TipsNotificationType : int {\n  None,\n  MO6TW,\n  CCLCC,\n  CHLCC,\n};\nclass TipsNotificationBase {\n public:\n  virtual ~TipsNotificationBase() = default;\n\n  virtual void Update(float dt) = 0;\n  virtual void Render() = 0;\n  virtual void AddTip(int tipId) = 0;\n\n  Animation FadeAnimation;\n\n protected:\n  std::queue<uint32_t> NotificationQueue;\n};\n\ninline std::unique_ptr<TipsNotificationBase> Implementation;\n\nvoid Init();\nvoid Update(float dt);\nvoid Render();\nvoid AddTip(int tipId);\n\n}  // namespace TipsNotification\n}  // namespace Impacto"
  },
  {
    "path": "src/hud/waiticondisplay.cpp",
    "content": "#include \"waiticondisplay.h\"\n\n#include \"../renderer/renderer.h\"\n#include \"../profile/dialogue.h\"\n#include \"../profile/scriptvars.h\"\n#include \"../profile/games/chlcc/dialoguebox.h\"\n#include \"../text/dialoguepage.h\"\n\n#include \"../animation.h\"\n\n#include <ranges>\n#include <numeric>\n\nnamespace Impacto {\nnamespace WaitIconDisplay {\n\nstatic Animation SimpleAnim;\nstatic SpriteAnimation SpriteAnim;\nstatic FixedSpriteAnimation FixedSpriteAnim;\n\nusing namespace Impacto::Profile::Dialogue;\nusing namespace Impacto::Profile::ScriptVars;\nusing namespace Impacto::Profile::CHLCC;\n\nvoid Init() {\n  if (WaitIconCurrentType == WaitIconType::None) return;\n\n  switch (WaitIconCurrentType) {\n    case WaitIconType::SpriteAnim:\n      SpriteAnim = WaitIconSpriteAnim.Instantiate();\n      SpriteAnim.LoopMode = AnimationLoopMode::Loop;\n      SpriteAnim.StartIn();\n      break;\n\n    case WaitIconType::SpriteAnimFixed:\n      SpriteAnim = WaitIconSpriteAnim.Instantiate();\n      SpriteAnim.LoopMode = AnimationLoopMode::Stop;\n      FixedSpriteAnim = static_cast<FixedSpriteAnimation&>(SpriteAnim);\n      FixedSpriteAnim.Def->FixSpriteId = WaitIconFixedSpriteId;\n      break;\n\n    default:\n      SimpleAnim.DurationIn = WaitIconAnimationDuration;\n      SimpleAnim.LoopMode = AnimationLoopMode::Loop;\n      SimpleAnim.StartIn();\n      break;\n  }\n}\n\nstatic bool AnyPageWaiting() {\n  return std::ranges::any_of(std::views::iota(0, std::ssize(DialoguePages)),\n                             [](int dialoguePageId) {\n                               return GetFlag(SF_SHOWWAITICON + dialoguePageId);\n                             });\n}\n\nvoid Update(float dt) {\n  switch (WaitIconCurrentType) {\n    case WaitIconType::None:\n      return;\n\n    case WaitIconType::SpriteAnim: {\n      const bool showWaitIcon = AnyPageWaiting();\n      if (showWaitIcon && SpriteAnim.IsOut())\n        SpriteAnim.StartIn();\n      else if (!showWaitIcon && SpriteAnim.IsIn())\n        SpriteAnim.StartOut();\n\n      SpriteAnim.Update(dt);\n      break;\n    }\n\n    case WaitIconType::SpriteAnimFixed: {\n      const bool showWaitIcon = AnyPageWaiting();\n      if (showWaitIcon && FixedSpriteAnim.IsOut())\n        FixedSpriteAnim.StartIn();\n      else if (!showWaitIcon && FixedSpriteAnim.IsIn())\n        FixedSpriteAnim.StartOut();\n\n      FixedSpriteAnim.Update(dt);\n      break;\n    }\n\n    default:\n      SimpleAnim.Update(dt);\n      break;\n  }\n}\n\nstatic void RenderSpriteAnim(glm::vec2 pos, glm::vec4 opacityTint,\n                             DialoguePageMode mode, int dialoguePageId) {\n  if (!GetFlag(SF_SHOWWAITICON + dialoguePageId)) return;\n\n  glm::vec2 offset = WaitIconOffset;\n\n  if (DialogueBoxCurrentType == DialogueBoxType::CHLCC) {\n    // To deal with multiple DialogueBox\n    opacityTint = glm::vec4(1.0f, 1.0f, 1.0f, opacityTint.a);\n\n    // Erin DialogueBox\n    if (mode == DPM_REV && ScrWork[SW_MESWIN0TYPE] == 1)\n      offset = Impacto::Profile::CHLCC::DialogueBox::REVWaitIconOffset;\n  }\n\n  Renderer->DrawSprite(SpriteAnim.CurrentSprite(), pos + offset, opacityTint);\n}\n\nstatic void RenderSpriteAnimFixed(glm::vec4 opacityTint) {\n  if (FixedSpriteAnim.Progress == 0.0f) return;\n\n  Renderer->DrawSprite(FixedSpriteAnim.CurrentSprite(),\n                       glm::vec2(WaitIconOffset.x, WaitIconOffset.y),\n                       opacityTint);\n}\n\nstatic void RenderRotateZ(glm::vec2 pos, glm::vec4 opacityTint,\n                          int dialoguePageId) {\n  if (!GetFlag(SF_SHOWWAITICON + dialoguePageId)) return;\n\n  // TODO: MO6TW only for now\n  glm::vec3 euler(SimpleAnim.Progress * 2.0f * std::numbers::pi_v<float>, 0,\n                  0.6f);\n  glm::quat quat;\n  eulerZYXToQuat(&euler, &quat);\n\n  glm::vec2 vanishingPoint = pos + WaitIconOffset + WaitIconSprite.Center();\n\n  CornersQuad dest =\n      WaitIconSprite.ScaledBounds().Translate(pos + WaitIconOffset);\n  dest.Rotate(quat, {dest.Center(), 0.0f}, 1.0f, vanishingPoint, true);\n  Renderer->DrawSprite(WaitIconSprite, dest, opacityTint);\n}\n\nstatic void RenderFixed(glm::vec4 opacityTint, int dialoguePageId) {\n  if (!GetFlag(SF_SHOWWAITICON + dialoguePageId)) return;\n\n  Renderer->DrawSprite(WaitIconSprite, WaitIconOffset, opacityTint);\n}\n\nvoid Render(glm::vec2 pos, glm::vec4 opacityTint, DialoguePageMode mode,\n            int dialoguePageId) {\n  switch (WaitIconCurrentType) {\n    case WaitIconType::None:\n      return;\n\n    case WaitIconType::SpriteAnim:\n      RenderSpriteAnim(pos, opacityTint, mode, dialoguePageId);\n      return;\n\n    case WaitIconType::SpriteAnimFixed:\n      RenderSpriteAnimFixed(opacityTint);\n      return;\n\n    case WaitIconType::RotateZ:\n      RenderRotateZ(pos, opacityTint, dialoguePageId);\n      return;\n\n    case WaitIconType::Fixed:\n      RenderFixed(opacityTint, dialoguePageId);\n      return;\n\n    default: {\n      if (!GetFlag(SF_SHOWWAITICON + dialoguePageId)) return;\n\n      const CornersQuad dest =\n          WaitIconSprite.ScaledBounds()\n              .RotateAroundCenter(SimpleAnim.Progress * 2.0f *\n                                  std::numbers::pi_v<float>)\n              .Translate(pos + WaitIconOffset);\n      Renderer->DrawSprite(WaitIconSprite, dest, opacityTint);\n\n      return;\n    }\n  }\n}\n\n}  // namespace WaitIconDisplay\n}  // namespace Impacto"
  },
  {
    "path": "src/hud/waiticondisplay.h",
    "content": "#pragma once\n\n#include <magic_enum/magic_enum.hpp>\n#include <glm/glm.hpp>\n\n#include \"dialoguebox.h\"\n\nnamespace Impacto {\nnamespace WaitIconDisplay {\n\nenum class WaitIconType : int {\n  None,\n  SpriteAnim,\n  SpriteAnimFixed,\n  Rotate,\n  Fixed,\n  RotateZ,\n};\nvoid Init();\nvoid Update(float dt);\nvoid Render(glm::vec2 pos, glm::vec4 opacityTint, DialoguePageMode mode,\n            int dialoguePageId);\n\n}  // namespace WaitIconDisplay\n}  // namespace Impacto"
  },
  {
    "path": "src/impacto.h",
    "content": "#pragma once\n\n#include <cstdint>\n#include <cstdlib>\n#include <cassert>\n#define _USE_MATH_DEFINES\n#include <cmath>\n\n#include \"config.h\"\n\n#include <SDL.h>\n\n#ifndef IMPACTO_DISABLE_OPENGL\n#include <glad/glad.h>\n#endif\n\n#ifndef IMPACTO_DISABLE_IMGUI\n#include <imgui.h>\n#include <imgui_impl_sdl2.h>\n#endif\n\n#include <fmt/base.h>\n#include <fmt/std.h>\n#include <fmt/compile.h>\n#include <fmt/format.h>\n#include <fmt/printf.h>\n#include <fmt/chrono.h>\n#include <ankerl/unordered_dense.h>\n#include <magic_enum/magic_enum.hpp>"
  },
  {
    "path": "src/inputsystem.cpp",
    "content": "#include \"inputsystem.h\"\n#include <SDL_timer.h>\n// #include \"window.h\"\n#include <ankerl/unordered_dense.h>\n#include \"renderer/renderer.h\"\n\n#include \"profile/game.h\"\n\nnamespace Impacto {\nnamespace Input {\n\nstatic std::array<SDL_FingerID, 2> CurrentFingers{};\n\nvoid BeginFrame() {\n  memset(ControllerButtonWentDown, false, sizeof(ControllerButtonWentDown));\n  memset(ControllerAxisWentDownLight, false,\n         sizeof(ControllerAxisWentDownLight));\n  memset(ControllerAxisWentDownHeavy, false,\n         sizeof(ControllerAxisWentDownHeavy));\n  memset(MouseButtonWentDown, false, sizeof(MouseButtonWentDown));\n  memset(KeyboardButtonWentDown, false, sizeof(KeyboardButtonWentDown));\n  TouchWentDown[0] = false;\n  TouchWentDown[1] = false;\n\n  PrevMousePos = CurMousePos;\n  PrevTouchPos = CurTouchPos;\n\n  MouseWheelDeltaX = MouseWheelDeltaY = 0;\n}\n\nstatic glm::vec2 SDLMouseCoordsToDesign(int x, int y) {\n  RectF viewport = Window->GetViewport();\n  glm::vec2 result;\n  result.x = ((float)x - viewport.X) *\n             (Profile::DesignWidth / (viewport.Width * Window->DpiScaleX));\n  result.y = ((float)y - viewport.Y) *\n             (Profile::DesignHeight / (viewport.Height * Window->DpiScaleY));\n  return result;\n}\n\nbool HandleEvent(SDL_Event const* ev) {\n  switch (ev->type) {\n    case SDL_CONTROLLERDEVICEADDED: {\n      SDL_ControllerDeviceEvent const* evt = &ev->cdevice;\n      CurrentInputDevice = Device::Controller;\n      SDL_GameControllerOpen(evt->which);\n      return true;\n      break;\n    }\n    case SDL_MOUSEMOTION: {\n      SDL_MouseMotionEvent const* evt = &ev->motion;\n      CurMousePos = SDLMouseCoordsToDesign(evt->x, evt->y);\n      CurrentInputDevice = Device::Mouse;\n      return true;\n      break;\n    }\n    case SDL_MOUSEBUTTONDOWN:\n    case SDL_MOUSEBUTTONUP: {\n      SDL_MouseButtonEvent const* evt = &ev->button;\n      CurMousePos = SDLMouseCoordsToDesign(evt->x, evt->y);\n      CurrentInputDevice = Device::Mouse;\n      MouseButtonWentDown[evt->button] =\n          (evt->state == SDL_PRESSED && !MouseButtonIsDown[evt->button]);\n      MouseButtonIsDown[evt->button] = evt->state == SDL_PRESSED;\n      return true;\n      break;\n    }\n    // TODO respect direction?\n    case SDL_MOUSEWHEEL: {\n      SDL_MouseWheelEvent const* evt = &ev->wheel;\n      CurrentInputDevice = Device::Mouse;\n      MouseWheelDeltaX += evt->x;\n      MouseWheelDeltaY += evt->y;\n      return true;\n      break;\n    }\n    case SDL_KEYDOWN:\n    case SDL_KEYUP: {\n      SDL_KeyboardEvent const* evt = &ev->key;\n      CurrentInputDevice = Device::Keyboard;\n      KeyboardButtonWentDown[evt->keysym.scancode] =\n          (evt->state == SDL_PRESSED &&\n           !KeyboardButtonIsDown[evt->keysym.scancode]);\n      KeyboardButtonIsDown[evt->keysym.scancode] = evt->state == SDL_PRESSED;\n      return true;\n      break;\n    }\n    case SDL_CONTROLLERBUTTONDOWN:\n    case SDL_CONTROLLERBUTTONUP: {\n      SDL_ControllerButtonEvent const* evt = &ev->cbutton;\n      CurrentInputDevice = Device::Controller;\n      ControllerButtonWentDown[evt->button] =\n          (evt->state == SDL_PRESSED && !ControllerButtonIsDown[evt->button]);\n      ControllerButtonIsDown[evt->button] = evt->state == SDL_PRESSED;\n      return true;\n      break;\n    }\n    case SDL_CONTROLLERAXISMOTION: {\n      SDL_ControllerAxisEvent const* evt = &ev->caxis;\n      float newVal = (float)evt->value / (float)INT16_MAX;\n      float newWeight = fabsf(newVal);\n      float oldWeight = fabsf(ControllerAxisValue[evt->axis]);\n      const bool axisIsDownLight = newWeight >= ControllerAxisLightThreshold;\n      const bool axisIsDownHeavy = newWeight >= ControllerAxisHeavyThreshold;\n      if (oldWeight < ControllerAxisLightThreshold && axisIsDownLight) {\n        ControllerAxisWentDownLight[evt->axis] = true;\n      }\n\n      if (oldWeight < ControllerAxisHeavyThreshold && axisIsDownHeavy) {\n        ControllerAxisWentDownHeavy[evt->axis] = true;\n      }\n\n      if (axisIsDownLight) {\n        CurrentInputDevice = Device::Controller;\n      }\n\n      ControllerAxisIsDownLight[evt->axis] = axisIsDownLight;\n      ControllerAxisIsDownHeavy[evt->axis] = axisIsDownHeavy;\n      ControllerAxisValue[evt->axis] = newVal;\n      return true;\n      break;\n    }\n    case SDL_FINGERMOTION: {\n      SDL_TouchFingerEvent const* evt = &ev->tfinger;\n      CurrentInputDevice = Device::Touch;\n      if (CurrentFingers[0] == evt->fingerId &&\n          TouchIsDown[0 && TouchIsDown[1]]) {\n        CurTouchPos =\n            SDLMouseCoordsToDesign((int)(evt->x * (float)Window->WindowWidth),\n                                   (int)(evt->y * (float)Window->WindowHeight));\n      }\n      return true;\n      break;\n    }\n    case SDL_FINGERDOWN: {\n      SDL_TouchFingerEvent const* evt = &ev->tfinger;\n      CurrentInputDevice = Device::Touch;\n      for (int8_t i = 0; i < FingerTapMax; ++i) {\n        if (!TouchIsDown[i]) {\n          CurTouchPos = SDLMouseCoordsToDesign(\n              (int)(evt->x * (float)Window->WindowWidth),\n              (int)(evt->y * (float)Window->WindowHeight));\n          CurrentFingers[i] = evt->fingerId;\n          TouchIsDown[i] = true;\n          TouchWentDown[i] = true;\n          break;\n        }\n      }\n      return true;\n      break;\n    }\n    case SDL_FINGERUP: {\n      SDL_TouchFingerEvent const* evt = &ev->tfinger;\n      CurrentInputDevice = Device::Touch;\n      for (int8_t i = 0; i < FingerTapMax; ++i) {\n        if (CurrentFingers[i] == evt->fingerId && TouchIsDown[i]) {\n          CurTouchPos = SDLMouseCoordsToDesign(\n              (int)(evt->x * (float)Window->WindowWidth),\n              (int)(evt->y * (float)Window->WindowHeight));\n          TouchIsDown[i] = false;\n        }\n      }\n      return true;\n      break;\n    }\n\n    default:\n      return false;\n  }\n}\n\n}  // namespace Input\n}  // namespace Impacto"
  },
  {
    "path": "src/inputsystem.h",
    "content": "#pragma once\n\n#include \"impacto.h\"\n#include <glm/glm.hpp>\n\nnamespace Impacto {\nnamespace Input {\nenum class Device { Mouse, Keyboard, Touch, Controller };\n\nfloat constexpr ControllerAxisLightThreshold = 0.3f;\nfloat constexpr ControllerAxisHeavyThreshold = 0.8f;\n\nint constexpr MouseButtonsMax = SDL_BUTTON_X2 + 1;\nint constexpr FingerTapMax = 2;\n\nvoid BeginFrame();\nbool HandleEvent(SDL_Event const* ev);\n\ninline Device CurrentInputDevice = Device::Mouse;\n\ninline glm::vec2 PrevMousePos = glm::vec2(0.0f);\ninline glm::vec2 CurMousePos = glm::vec2(0.0f);\n\ninline int MouseWheelDeltaX = 0;\ninline int MouseWheelDeltaY = 0;\n\n// TODO multitouch\ninline glm::vec2 PrevTouchPos = glm::vec2(0.0f);\ninline glm::vec2 CurTouchPos = glm::vec2(0.0f);\n\ninline float ControllerAxisValue[SDL_CONTROLLER_AXIS_MAX];\n\ninline bool MouseButtonWentDown[MouseButtonsMax] = {false};\ninline bool MouseButtonIsDown[MouseButtonsMax] = {false};\ninline bool ControllerButtonWentDown[SDL_CONTROLLER_BUTTON_MAX] = {false};\ninline bool ControllerButtonIsDown[SDL_CONTROLLER_BUTTON_MAX] = {false};\ninline bool ControllerAxisIsDownLight[SDL_CONTROLLER_AXIS_MAX] = {false};\ninline bool ControllerAxisWentDownLight[SDL_CONTROLLER_AXIS_MAX] = {false};\ninline bool ControllerAxisIsDownHeavy[SDL_CONTROLLER_AXIS_MAX] = {false};\ninline bool ControllerAxisWentDownHeavy[SDL_CONTROLLER_AXIS_MAX] = {false};\ninline bool KeyboardButtonWentDown[SDL_NUM_SCANCODES] = {false};\ninline bool KeyboardButtonIsDown[SDL_NUM_SCANCODES] = {false};\n\n// TODO multitouch\ninline bool TouchIsDown[FingerTapMax]{};\ninline bool TouchWentDown[FingerTapMax]{};\n\n// Using statements to ensure that types are coming from this header (for\n// consistent magic enum range specialization).\nusing KeyboardScanCode = SDL_Scancode;\nusing ControllerButton = SDL_GameControllerButton;\nusing ControllerAxis = SDL_GameControllerAxis;\n\n}  // namespace Input\n}  // namespace Impacto\n\nnamespace magic_enum::customize {\ntemplate <>\nstruct enum_range<Impacto::Input::KeyboardScanCode> {\n  static constexpr int min = 0;\n  static constexpr int max = 512;\n  // keep underscore because numbers can't be at the start of an identifier\n  static constexpr size_t prefix_length =\n      std::string_view(\"SDL_SCANCODE\").size();\n};\n\ntemplate <>\nconstexpr customize_t\nenum_type_name<Impacto::Input::KeyboardScanCode>() noexcept {\n  return \"KeyboardScanCode\";\n};\n\ntemplate <>\nstruct enum_range<Impacto::Input::ControllerButton> {\n  static constexpr size_t prefix_length =\n      std::string_view(\"SDL_CONTROLLER_BUTTON_\").size();\n};\ntemplate <>\nconstexpr customize_t\nenum_type_name<Impacto::Input::ControllerButton>() noexcept {\n  return \"ControllerButton\";\n};\n\ntemplate <>\nstruct enum_range<Impacto::Input::ControllerAxis> {\n  static constexpr size_t prefix_length =\n      std::string_view(\"SDL_CONTROLLER_AXIS_\").size();\n};\ntemplate <>\nconstexpr customize_t\nenum_type_name<Impacto::Input::ControllerAxis>() noexcept {\n  return \"ControllerAxis\";\n};\n}  // namespace magic_enum::customize"
  },
  {
    "path": "src/io/afsarchive.cpp",
    "content": "#include \"afsarchive.h\"\n\n#include \"../log.h\"\n#include \"uncompressedstream.h\"\n#include \"vfs.h\"\n#include \"../util.h\"\n#include <SDL_endian.h>\n\nnamespace Impacto {\nnamespace Io {\n\nstruct AfsMetaEntry : FileMeta {\n  int64_t Offset;\n};\n\nAfsArchive::~AfsArchive() {\n  if (TOC) delete[] TOC;\n}\n\nIoError AfsArchive::Open(FileMeta* file, Stream** outStream) {\n  AfsMetaEntry* entry = (AfsMetaEntry*)file;\n  IoError err = UncompressedStream::Create(BaseStream, entry->Offset,\n                                           entry->Size, outStream);\n  if (err != IoError_OK) {\n    ImpLog(LogLevel::Error, LogChannel::IO,\n           \"AFS file open failed for file \\\"{:s}\\\" in archive \\\"{:s}\\\"\\n\",\n           entry->FileName, BaseStream->Meta.FileName);\n  }\n  return err;\n}\n\nIoError AfsArchive::Create(Stream* stream, VfsArchive** outArchive) {\n  ImpLog(LogLevel::Trace, LogChannel::IO, \"Trying to mount \\\"{:s}\\\" as AFS\\n\",\n         stream->Meta.FileName);\n\n  AfsArchive* result = 0;\n  uint32_t* rawToc = 0;\n\n  uint32_t const magic = 0x41465300;\n\n  uint32_t const tocEntrySize = 8;\n\n  uint32_t fileCount;\n  char fileName[6];\n\n  if (ReadBE<uint32_t>(stream) != magic) {\n    ImpLog(LogLevel::Trace, LogChannel::IO, \"Not AFS\\n\");\n    goto fail;\n  }\n\n  fileCount = ReadLE<uint32_t>(stream);\n\n  rawToc = (uint32_t*)ImpStackAlloc(fileCount * tocEntrySize);\n  if (stream->Read(rawToc, fileCount * tocEntrySize) !=\n      fileCount * tocEntrySize)\n    goto fail;\n\n  result = new AfsArchive;\n  result->BaseStream = stream;\n  result->NamesToIds.reserve(fileCount);\n  result->IdsToFiles.reserve(fileCount);\n  result->TOC = new AfsMetaEntry[fileCount];\n\n  for (uint32_t i = 0; i < fileCount; i++) {\n    snprintf(fileName, 6, \"%05i\", i);\n    result->TOC[i].FileName = fileName;\n    result->TOC[i].Id = i;\n    result->TOC[i].Offset = SDL_SwapLE32(rawToc[i * 2]);\n    result->TOC[i].Size = SDL_SwapLE32(rawToc[i * 2 + 1]);\n    result->IdsToFiles[i] = &result->TOC[i];\n    result->NamesToIds[result->TOC[i].FileName] = i;\n  }\n\n  ImpStackFree(rawToc);\n\n  result->IsInit = true;\n  *outArchive = result;\n  return IoError_OK;\n\nfail:\n  stream->Seek(0, RW_SEEK_SET);\n  if (result) delete result;\n  if (rawToc) ImpStackFree(rawToc);\n  return IoError_Fail;\n}\n\n}  // namespace Io\n}  // namespace Impacto"
  },
  {
    "path": "src/io/afsarchive.h",
    "content": "#pragma once\n\n#include \"vfsarchive.h\"\n\nnamespace Impacto {\nnamespace Io {\n\nstruct AfsMetaEntry;\n\nclass AfsArchive : public VfsArchive {\n public:\n  ~AfsArchive();\n  IoError Open(FileMeta* file, Stream** outStream) override;\n\n  static IoError Create(Stream* stream, VfsArchive** outArchive);\n\n private:\n  AfsMetaEntry* TOC = 0;\n};\n\n}  // namespace Io\n}  // namespace Impacto"
  },
  {
    "path": "src/io/assetpath.cpp",
    "content": "#include \"assetpath.h\"\n#include \"physicalfilestream.h\"\n#include \"vfs.h\"\n\nnamespace Impacto {\nnamespace Io {\n\nIoError AssetPath::Open(Stream** outStream) {\n  if (Mount.empty()) {\n    return PhysicalFileStream::Create(FileName, outStream);\n  } else {\n    if (FileName.empty()) {\n      return VfsOpen(Mount, Id, outStream);\n    } else {\n      return VfsOpen(Mount, FileName, outStream);\n    }\n  }\n}\n\nIoError AssetPath::Slurp(void*& outMemory, int64_t& outSize) {\n  if (Mount.empty()) {\n    Stream* stream;\n    IoError err = PhysicalFileStream::Create(FileName, &stream);\n    if (err != IoError_OK) return err;\n    outMemory = malloc(stream->Meta.Size);\n    outSize = stream->Meta.Size;\n    int64_t readErr = stream->Read(outMemory, outSize);\n    // TODO should size output by Read() go into outSize, even though it may be\n    // less than the allocated size?\n    delete stream;\n    if (readErr < 0) {\n      free(outMemory);\n      err = IoError_Fail;\n    } else {\n      err = IoError_OK;\n    }\n    return err;\n  } else {\n    if (FileName.empty()) {\n      return VfsSlurp(Mount, Id, outMemory, outSize);\n    } else {\n      return VfsSlurp(Mount, FileName, outMemory, outSize);\n    }\n  }\n}\n\n}  // namespace Io\n}  // namespace Impacto"
  },
  {
    "path": "src/io/assetpath.h",
    "content": "#pragma once\n\n#include \"stream.h\"\n\nnamespace Impacto {\nnamespace Io {\n\nclass AssetPath {\n public:\n  std::string Mount = \"\";\n  std::string FileName = \"\";\n  uint32_t Id = 0;\n\n  IoError Open(Stream** outStream);\n  IoError Slurp(void*& outMemory, int64_t& outSize);\n};\n\n}  // namespace Io\n}  // namespace Impacto"
  },
  {
    "path": "src/io/buffering.h",
    "content": "#pragma once\n\n#include \"stream.h\"\n#include <algorithm>\n#include <string.h>\n\nnamespace Impacto {\nnamespace Io {\n\ntemplate <class T>\nclass Buffering {\n protected:\n  IoError FillBuffer();\n\n  uint8_t* Buffer;\n  int64_t BufferSize;\n  int64_t BufferFill = 0;\n  int64_t BufferConsumed = 0;\n\n  Buffering(int64_t bufferSize) {\n    BufferSize = bufferSize;\n    Buffer = (uint8_t*)malloc(bufferSize);\n  }\n  Buffering(const Buffering& other)\n      : BufferSize(other.BufferSize),\n        BufferFill(other.BufferFill),\n        BufferConsumed(other.BufferConsumed) {\n    Buffer = (uint8_t*)malloc(BufferSize);\n    memcpy(Buffer, other.Buffer, BufferSize);\n  }\n  ~Buffering() { free(Buffer); }\n\n  int64_t ReadBuffered(void* buffer, int64_t sz) {\n    T* stream = static_cast<T*>(this);\n    if (sz < 0) return IoError_Fail;\n    int64_t read = 0;\n    if (stream->Position == stream->Meta.Size) return IoError_Eof;\n    sz = std::min(stream->Meta.Size - stream->Position, sz);\n\n    while (sz) {\n      if (BufferConsumed < BufferFill) {\n        int64_t toCopy = std::min(sz, BufferFill - BufferConsumed);\n        if (buffer) {\n          memcpy((uint8_t*)buffer + read, Buffer + BufferConsumed, toCopy);\n        }\n        sz -= toCopy;\n        stream->Position += toCopy;\n        BufferConsumed += toCopy;\n        read += toCopy;\n        if (stream->Position == stream->Meta.Size) return read;\n      } else {\n        BufferConsumed = 0;\n        BufferFill = 0;\n        IoError err = stream->FillBuffer();\n        if (err != IoError_OK) return err;\n      }\n    }\n    return read;\n  }\n\n  int64_t SeekBuffered(int64_t pos) {\n    T* stream = static_cast<T*>(this);\n    if (stream->Position == pos) return pos;\n    int64_t bufferStart = stream->Position - BufferConsumed;\n    if (pos >= bufferStart && pos <= bufferStart + BufferFill) {\n      stream->Position = pos;\n      BufferConsumed = pos - bufferStart;\n      return pos;\n    } else {\n      // TODO support streams that do have random access on their own\n      return IoError_Fail;\n    }\n  }\n\n  int64_t DiscardSeekBuffered(int64_t pos) {\n    T* stream = static_cast<T*>(this);\n    while (stream->Position < pos) {\n      int64_t read = ReadBuffered(0, BufferSize);\n      if (read < IoError_OK) return read;\n    }\n    return stream->SeekBuffered(pos);\n  }\n};\n\n}  // namespace Io\n}  // namespace Impacto"
  },
  {
    "path": "src/io/cpkarchive.cpp",
    "content": "#include \"cpkarchive.h\"\n\n#include <variant>\n\n#include \"../log.h\"\n#include \"../util.h\"\n#include \"uncompressedstream.h\"\n#include <SDL_endian.h>\n\nnamespace Impacto {\nnamespace Io {\nusing CpkCell = std::variant<std::monostate, uint8_t, int8_t, uint16_t, int16_t,\n                             uint32_t, int32_t, uint64_t, int64_t, float,\n                             double, std::vector<uint8_t>, std::string>;\n\nstruct CpkColumn {\n  enum Kind : uint8_t {\n    U8 = 0,\n    I8,\n    U16,\n    I16,\n    U32,\n    I32,\n    U64,\n    I64,\n    Float,\n    Double,\n    String,\n    Data\n  };\n\n  enum Storage : uint8_t {\n    NONE = 0,\n    DEFAULT = 1,\n    CONSTANT = 3,\n    NORMAL = 5,\n  };\n  uint32_t Flags;\n  Kind GetType() const { return static_cast<Kind>(Flags & 0xF); }\n  Storage GetStorage() const {\n    return static_cast<Storage>((Flags >> 4) & 0xF);\n  }\n\n  std::string Name;\n  std::vector<CpkCell> Cells;\n  CpkCell Constant;\n};\n\nstruct CpkMetaEntry : public FileMeta {\n  int64_t Offset;\n  int64_t CompressedSize;\n  bool Compressed;\n};\n\nCpkArchive::~CpkArchive() {\n  if (FileList) delete[] FileList;\n}\n\nvoid DecryptUtfBlock(uint8_t* utfBlock, uint64_t size) {\n  uint32_t utfDecryptVal = 0x655f;\n  for (uint64_t i = 0; i < size; i++) {\n    utfBlock[i] ^= utfDecryptVal & 0xFF;\n    utfDecryptVal *= 0x4115;\n  }\n}\n\nstatic std::string ReadString(int64_t stringsOffset, Stream* utfStream) {\n  assert(utfStream);\n  int64_t stringAddr = stringsOffset + ReadBE<uint32_t>(utfStream);\n\n  int64_t retAddr = utfStream->Position;\n\n  utfStream->Seek(stringAddr, RW_SEEK_SET);\n\n  char ch;\n  std::string output;\n  while ((ch = ReadU8(utfStream)) != 0x00) {\n    output.push_back(ch);\n  }\n  utfStream->Seek(retAddr, RW_SEEK_SET);\n  return output;\n}\n\nstatic bool ReadUtfBlock(\n    std::vector<uint8_t>& utfBlock,\n    std::vector<ankerl::unordered_dense::map<std::string, CpkCell, string_hash,\n                                             std::equal_to<>>>& rows) {\n  const uint32_t utfMagic = 0x40555446;\n  auto utfStream =\n      std::make_unique<MemoryStream>(utfBlock.data(), utfBlock.size(), false);\n  if (ReadBE<uint32_t>(utfStream.get()) != utfMagic) {\n    DecryptUtfBlock(utfBlock.data(), utfBlock.size());\n    utfStream->Seek(0, RW_SEEK_SET);\n    if (ReadBE<uint32_t>(utfStream.get()) != utfMagic) {\n      ImpLog(LogLevel::Trace, LogChannel::IO, \"Error reading CPK UTF table\\n\");\n      return false;\n    }\n  }\n\n  // uint32_t tableSize = ReadBE<uint32_t>(utfStream);\n  utfStream->Seek(4, RW_SEEK_CUR);\n  int64_t rowsOffset = ReadBE<uint32_t>(utfStream.get());\n  int64_t stringsOffset = ReadBE<uint32_t>(utfStream.get());\n  int64_t dataOffset = ReadBE<uint32_t>(utfStream.get());\n\n  rowsOffset += 8;\n  stringsOffset += 8;\n  dataOffset += 8;\n\n  // uint32_t tableNameOffset = ReadBE<uint32_t>(UtfStream);\n  utfStream->Seek(4, RW_SEEK_CUR);\n  uint16_t numColumns = ReadBE<uint16_t>(utfStream.get());\n  uint16_t rowLength = ReadBE<uint16_t>(utfStream.get());\n  uint32_t numRows = ReadBE<uint32_t>(utfStream.get());\n\n  std::vector<CpkColumn> columns;\n\n  const auto readCell = [&utfStream, &stringsOffset,\n                         &dataOffset](CpkColumn const& column) {\n    const auto readOrDefaultT = []<typename T>(CpkColumn::Storage storeType,\n                                               Stream* s) {\n      if (storeType == CpkColumn::Storage::DEFAULT) return T{};\n      return Io::ReadBE<T>(s);\n    };\n\n    CpkCell cell{};\n    auto storeType = column.GetStorage();\n    switch (column.GetType()) {\n      case CpkColumn::Kind::U8:\n        cell = readOrDefaultT.operator()<uint8_t>(storeType, utfStream.get());\n        break;\n      case CpkColumn::Kind::I8:\n        cell = readOrDefaultT.operator()<int8_t>(storeType, utfStream.get());\n        break;\n      case CpkColumn::Kind::U16:\n        cell = readOrDefaultT.operator()<uint16_t>(storeType, utfStream.get());\n        break;\n      case CpkColumn::Kind::I16:\n        cell = readOrDefaultT.operator()<int16_t>(storeType, utfStream.get());\n        break;\n      case CpkColumn::Kind::U32:\n        cell = readOrDefaultT.operator()<uint32_t>(storeType, utfStream.get());\n        break;\n      case CpkColumn::Kind::I32:\n        cell = readOrDefaultT.operator()<int32_t>(storeType, utfStream.get());\n        break;\n      case CpkColumn::Kind::U64:\n        cell = readOrDefaultT.operator()<uint64_t>(storeType, utfStream.get());\n        break;\n      case CpkColumn::Kind::I64:\n        cell = readOrDefaultT.operator()<int64_t>(storeType, utfStream.get());\n        break;\n      case CpkColumn::Kind::Float:\n        cell = readOrDefaultT.operator()<float>(storeType, utfStream.get());\n        break;\n      case CpkColumn::Kind::Double:\n        cell = readOrDefaultT.operator()<double>(storeType, utfStream.get());\n        break;\n      case CpkColumn::Kind::String:\n        if (storeType == CpkColumn::Storage::DEFAULT) {\n          cell = std::string();\n        } else {\n          cell = ReadString(stringsOffset, utfStream.get());\n        }\n        break;\n      case CpkColumn::Kind::Data: {\n        if (storeType == CpkColumn::Storage::DEFAULT) {\n          cell = std::vector<uint8_t>();\n        } else {\n          int64_t dataPos = ReadBE<uint32_t>(utfStream.get()) + dataOffset;\n          uint64_t dataSize = ReadBE<uint32_t>(utfStream.get());\n          uint64_t retAddr = utfStream->Position;\n          std::vector<uint8_t> dataBuf(dataSize + sizeof(uint64_t));\n          utfStream->Seek(dataPos, RW_SEEK_SET);\n          utfStream->Read(dataBuf.data(), dataSize);\n          utfStream->Seek(retAddr, RW_SEEK_SET);\n          cell = std::move(dataBuf);\n        }\n      } break;\n    }\n    return cell;\n  };\n\n  for (int i = 0; i < numColumns; i++) {\n    CpkColumn column;\n    column.Flags = ReadU8(utfStream.get());\n    if (column.Flags == 0) column.Flags = ReadBE<uint32_t>(utfStream.get());\n\n    column.Name = ReadString(stringsOffset, utfStream.get());\n    if (column.GetStorage() == CpkColumn::Storage::CONSTANT) {\n      column.Constant = readCell(column);\n    }\n\n    columns.push_back(column);\n  }\n\n  for (uint32_t i = 0; i < numRows; i++) {\n    utfStream->Seek(rowsOffset + (i * rowLength), RW_SEEK_SET);\n    ankerl::unordered_dense::map<std::string, CpkCell, string_hash,\n                                 std::equal_to<>>\n        row;\n    for (auto& column : columns) {\n      row[column.Name] = (column.GetStorage() == CpkColumn::Storage::CONSTANT)\n                             ? column.Constant\n                             : readCell(column);\n    }\n    rows.push_back(row);\n  }\n\n  return true;\n}\n\nCpkMetaEntry* CpkArchive::GetFileListEntry(uint32_t id) {\n  CpkMetaEntry* entry;\n  auto it = IdsToFiles.find(id);\n  if (it == IdsToFiles.end()) {\n    entry = &FileList[NextFile];\n    IdsToFiles[id] = &FileList[NextFile];\n    NextFile++;\n  } else {\n    entry = (CpkMetaEntry*)it->second;\n  }\n  return entry;\n}\n\nIoError CpkArchive::ReadItoc(int64_t itocOffset, int64_t contentOffset,\n                             uint16_t align) {\n  BaseStream->Seek(itocOffset, RW_SEEK_SET);\n  const uint32_t itocMagic = 0x49544F43;\n  if (ReadBE<uint32_t>(BaseStream) != itocMagic) {\n    ImpLog(LogLevel::Trace, LogChannel::IO, \"Error reading CPK ITOC\\n\");\n    return IoError_Fail;\n  }\n  BaseStream->Seek(4, RW_SEEK_CUR);\n  uint64_t utfSize = ReadLE<uint64_t>(BaseStream);\n  std::vector<uint8_t> utfBlock(utfSize);\n  BaseStream->Read(utfBlock.data(), utfSize);\n  std::vector<ankerl::unordered_dense::map<std::string, CpkCell, string_hash,\n                                           std::equal_to<>>>\n      itocUtfTable;\n  if (!ReadUtfBlock(utfBlock, itocUtfTable)) return IoError_Fail;\n  auto lowDataItr = itocUtfTable[0].find(\"DataL\");\n  if (lowDataItr != itocUtfTable[0].end()) {\n    std::vector<ankerl::unordered_dense::map<std::string, CpkCell, string_hash,\n                                             std::equal_to<>>>\n        dataLUtfTable;\n    if (!ReadUtfBlock(std::get<std::vector<uint8_t>>(itocUtfTable[0][\"DataL\"]),\n                      dataLUtfTable))\n      return IoError_Fail;\n    std::vector<ankerl::unordered_dense::map<std::string, CpkCell, string_hash,\n                                             std::equal_to<>>>\n        dataHUtfTable;\n    if (!ReadUtfBlock(std::get<std::vector<uint8_t>>(itocUtfTable[0][\"DataH\"]),\n                      dataHUtfTable))\n      return IoError_Fail;\n\n    for (auto& row : dataLUtfTable) {\n      int id = std::get<uint16_t>(row[\"ID\"]);\n      CpkMetaEntry* entry = GetFileListEntry(id);\n      entry->Id = id;\n\n      uint16_t extractedSize = std::get<uint16_t>(row[\"ExtractSize\"]);\n      uint16_t fileSize = std::get<uint16_t>(row[\"FileSize\"]);\n      if (extractedSize && (extractedSize != fileSize)) {\n        entry->Size = extractedSize;\n        entry->CompressedSize = fileSize;\n        entry->Compressed = true;\n      } else {\n        entry->Size = fileSize;\n        entry->CompressedSize = fileSize;\n        entry->Compressed = false;\n      }\n      if (entry->FileName.empty()) {\n        char path[6];\n        snprintf(path, 6, \"%05i\", id);\n        entry->FileName = path;\n      }\n    }\n\n    for (auto& row : dataHUtfTable) {\n      int id = std::get<uint16_t>(row[\"ID\"]);\n      CpkMetaEntry* entry = GetFileListEntry(id);\n      entry->Id = id;\n\n      int extractedSize = std::get<uint32_t>(row[\"ExtractSize\"]);\n      int fileSize = std::get<uint32_t>(row[\"FileSize\"]);\n      if (extractedSize && (extractedSize != fileSize)) {\n        entry->Size = extractedSize;\n        entry->CompressedSize = fileSize;\n        entry->Compressed = true;\n      } else {\n        entry->Size = fileSize;\n        entry->CompressedSize = fileSize;\n        entry->Compressed = false;\n      }\n      if (entry->FileName.empty()) {\n        char path[6];\n        snprintf(path, 6, \"%05i\", id);\n        entry->FileName = path;\n      }\n    }\n  }\n\n  int64_t offset = contentOffset;\n  std::vector<std::pair<uint32_t, FileMeta*>> fileVec(IdsToFiles.begin(),\n                                                      IdsToFiles.end());\n  std::sort(fileVec.begin(), fileVec.end());\n  for (const auto& kv : fileVec) {\n    int64_t size;\n    CpkMetaEntry* entry = (CpkMetaEntry*)kv.second;\n    if (entry->CompressedSize > 0) {\n      size = entry->CompressedSize;\n    } else {\n      size = entry->Size;\n    }\n    entry->Offset = offset;\n    offset += size;\n    if (size % align) offset += align - (size % align);\n  }\n\n  return IoError_OK;\n}\n\nIoError CpkArchive::ReadToc(int64_t tocOffset, int64_t contentOffset) {\n  BaseStream->Seek(tocOffset, RW_SEEK_SET);\n  const uint32_t tocMagic = 0x544F4320;\n  if (ReadBE<uint32_t>(BaseStream) != tocMagic) {\n    ImpLog(LogLevel::Trace, LogChannel::IO, \"Error reading CPK TOC\\n\");\n    return IoError_Fail;\n  }\n  BaseStream->Seek(4, RW_SEEK_CUR);\n  uint64_t utfSize = ReadLE<uint64_t>(BaseStream);\n  std::vector<uint8_t> utfBlock(utfSize);\n  BaseStream->Read(utfBlock.data(), utfSize);\n  std::vector<ankerl::unordered_dense::map<std::string, CpkCell, string_hash,\n                                           std::equal_to<>>>\n      tocUtfTable;\n  if (!ReadUtfBlock(utfBlock, tocUtfTable)) return IoError_Fail;\n\n  for (auto& row : tocUtfTable) {\n    uint32_t id = std::get<uint32_t>(row[\"ID\"]);\n    CpkMetaEntry* entry = GetFileListEntry(id);\n\n    entry->Id = id;\n    entry->Offset = std::get<uint64_t>(row[\"FileOffset\"]);\n    constexpr static int CpkMaxPath = 244;\n    char path[CpkMaxPath * 2] = {0};\n\n    if (auto* dir = std::get_if<std::string>(&row[\"DirName\"]);\n        dir && !dir->empty()) {\n      fmt::format_to_n(path, CpkMaxPath * 2, \"{}/{}\", *dir,\n                       std::get<std::string>(row[\"FileName\"]));\n    } else {\n      fmt::format_to_n(path, CpkMaxPath, \"{}\",\n                       std::get<std::string>(row[\"FileName\"]));\n    }\n    if (strlen(path) == 0) {\n      snprintf(path, CpkMaxPath, \"%05i\", id);\n    }\n    entry->FileName = path;\n\n    int extractedSize = std::get<uint32_t>(row[\"ExtractSize\"]);\n    int fileSize = std::get<uint32_t>(row[\"FileSize\"]);\n    if (extractedSize && (extractedSize != fileSize)) {\n      entry->Size = extractedSize;\n      entry->CompressedSize = fileSize;\n      entry->Compressed = true;\n    } else {\n      entry->Size = fileSize;\n      entry->CompressedSize = fileSize;\n      entry->Compressed = false;\n    }\n  }\n\n  return IoError_OK;\n}\n\nIoError CpkArchive::ReadEtoc(int64_t etocOffset) {\n  BaseStream->Seek(etocOffset, RW_SEEK_SET);\n  const uint32_t etocMagic = 0x45544F43;\n  if (ReadBE<uint32_t>(BaseStream) != etocMagic) {\n    ImpLog(LogLevel::Trace, LogChannel::IO, \"Error reading CPK ETOC\\n\");\n    return IoError_Fail;\n  }\n  BaseStream->Seek(4, RW_SEEK_CUR);\n  uint64_t utfSize = ReadLE<uint64_t>(BaseStream);\n  std::vector<uint8_t> utfBlock(utfSize);\n  BaseStream->Read(utfBlock.data(), utfSize);\n  std::vector<ankerl::unordered_dense::map<std::string, CpkCell, string_hash,\n                                           std::equal_to<>>>\n      etocUtfTable;\n  if (!ReadUtfBlock(utfBlock, etocUtfTable)) return IoError_Fail;\n\n  // for (auto& row : etocUtfTable) {\n  // TODO: This contains the LocalDir and UpdateDateTime params. Do we actually\n  // need this?...\n  //}\n\n  return IoError_OK;\n}\n\nIoError CpkArchive::Create(Stream* stream, VfsArchive** outArchive) {\n  ImpLog(LogLevel::Trace, LogChannel::IO, \"Trying to mount \\\"{:s}\\\" as CPK\\n\",\n         stream->Meta.FileName);\n\n  CpkArchive* result = 0;\n\n  std::vector<ankerl::unordered_dense::map<std::string, CpkCell, string_hash,\n                                           std::equal_to<>>>\n      headerUtfTable;\n\n  uint16_t alignVal;\n\n  auto errorHandler = [&] {\n    stream->Seek(0, RW_SEEK_SET);\n    if (result) delete result;\n    return IoError_Fail;\n  };\n\n  uint32_t const magic = 0x43504B20;\n  if (ReadBE<uint32_t>(stream) != magic) {\n    ImpLog(LogLevel::Trace, LogChannel::IO, \"Not a CPK\\n\");\n    return errorHandler();\n  }\n\n  result = new CpkArchive;\n  result->BaseStream = stream;\n\n  bool encrypted = ReadLE<uint32_t>(stream) == 0;\n  {\n    uint64_t utfSize = ReadLE<uint64_t>(stream);\n    std::vector<uint8_t> utfBlock(utfSize);\n    if (encrypted) {\n      uint8_t key = 0x5F;\n      for (auto& elem : utfBlock) {\n        elem = elem ^ key;\n        key = (key * 0x15) & 0xFF;\n      }\n    }\n    stream->Read(utfBlock.data(), utfSize);\n    if (!ReadUtfBlock(utfBlock, headerUtfTable)) {\n      return errorHandler();\n    }\n  }\n\n  result->FileCount = std::get<uint32_t>(headerUtfTable[0][\"Files\"]);\n  result->Version = std::get<uint16_t>(headerUtfTable[0][\"Version\"]);\n  result->Revision = std::get<uint16_t>(headerUtfTable[0][\"Revision\"]);\n\n  alignVal = std::get<uint16_t>(headerUtfTable[0][\"Align\"]);\n\n  result->FileList = new CpkMetaEntry[result->FileCount];\n\n  if (std::get<uint64_t>(headerUtfTable[0][\"TocOffset\"]) != 0) {\n    result->ReadToc(std::get<uint64_t>(headerUtfTable[0][\"TocOffset\"]),\n                    std::get<uint64_t>(headerUtfTable[0][\"ContentOffset\"]));\n  }\n\n  if (std::get<uint64_t>(headerUtfTable[0][\"EtocOffset\"]) != 0) {\n    result->ReadEtoc(std::get<uint64_t>(headerUtfTable[0][\"EtocOffset\"]));\n  }\n\n  if (std::get<uint64_t>(headerUtfTable[0][\"ItocOffset\"]) != 0) {\n    result->ReadItoc(std::get<uint64_t>(headerUtfTable[0][\"ItocOffset\"]),\n                     std::get<uint64_t>(headerUtfTable[0][\"ContentOffset\"]),\n                     alignVal);\n  }\n\n  for (uint32_t i = 0; i < result->FileCount; i++) {\n    result->NamesToIds[result->FileList[i].FileName] = result->FileList[i].Id;\n  }\n\n  result->IsInit = true;\n  *outArchive = result;\n  return IoError_OK;\n}\n\nIoError CpkArchive::Open(FileMeta* file, Stream** outStream) {\n  CpkMetaEntry* entry = (CpkMetaEntry*)file;\n\n  IoError err;\n  BaseStream->Seek(entry->Offset, RW_SEEK_SET);\n  uint32_t const laylaMagic1 = 0x4352494C;\n  uint32_t const laylaMagic2 = 0x41594C41;\n\n  if (!entry->Compressed && ReadBE<uint32_t>(BaseStream) == laylaMagic1 &&\n      ReadBE<uint32_t>(BaseStream) == laylaMagic2) {\n    // Marking file as compressed because apparently some times it's just not\n    // marked in toc\n    entry->Size = ReadLE<uint32_t>(BaseStream) + 0x100;\n    entry->Compressed = true;\n  }\n\n  if (entry->Compressed) {\n    ImpLog(LogLevel::Debug, LogChannel::IO,\n           \"CPK cannot stream LAYLA compressed file \\\"{:s}\\\" in archive \"\n           \"\\\"{:s}\\\"\\n\",\n           entry->FileName, BaseStream->Meta.FileName);\n    return IoError_Fail;\n  } else {\n    err = UncompressedStream::Create(BaseStream, entry->Offset, entry->Size,\n                                     outStream);\n  }\n  if (err != IoError_OK) {\n    ImpLog(LogLevel::Error, LogChannel::IO,\n           \"CPK file open failed for file \\\"{:s}\\\" in archive \\\"{:s}\\\"\\n\",\n           entry->FileName, BaseStream->Meta.FileName);\n  }\n  return err;\n}\n\n// Based on https://forum.xentax.com/viewtopic.php?f=21&t=5137&hilit=CRILAYLA\n\nstatic uint32_t get_next_bits(char* input, int* offset_p, uint32_t* bit_pool_p,\n                              int* bits_left_p, int bit_count) {\n  uint32_t out_bits = 0;\n\n  if (*bits_left_p < bit_count) {\n    int count = ((24 - *bits_left_p) >> 3) + 1;\n    while (count) {\n      uint8_t source = input[*offset_p];\n      *offset_p = *offset_p - 1;\n      *bit_pool_p = (*bit_pool_p << 8) | source;\n      *bits_left_p += 8;\n      count--;\n    }\n  }\n\n  *bits_left_p -= bit_count;\n  out_bits = *bit_pool_p >> *bits_left_p;\n  uint32_t mask = (1 << bit_count) - 1;\n  out_bits &= mask;\n\n  return out_bits;\n}\n\n// Based on https://github.com/hcs64/vgm_ripping/tree/master/multi/utf_tab\n\nstatic IoError DecompressLayla(char* input, int64_t compressedSize,\n                               char* output, int64_t uncompressedSize) {\n  int uncompressed_size = SDL_SwapLE32(*(uint32_t*)(input + 8));\n  uint32_t compressedStreamLength = SDL_SwapLE32(*(uint32_t*)(input + 12));\n  uint32_t compressedOffset = 16;\n  int64_t prefixOffset = compressedOffset + compressedStreamLength;\n  if (compressedSize < prefixOffset || compressedSize - prefixOffset != 0x100) {\n    ImpLog(LogLevel::Debug, LogChannel::IO,\n           \"CPK unexpected end of LAYLA stream\\n\");\n    return IoError_Fail;\n  }\n  memcpy(output, input + compressedOffset + compressedStreamLength, 0x100);\n\n  int input_end = ((int)compressedSize - 0x100 - 1);\n  int input_offset = input_end;\n  int output_end = 0x100 + uncompressed_size - 1;\n  uint32_t bit_pool = 0;\n  int bits_left = 0, bytes_output = 0;\n  int vle_lens[4] = {2, 3, 5, 8};\n\n  while (bytes_output < uncompressed_size) {\n    if (get_next_bits(input, &input_offset, &bit_pool, &bits_left, 1) > 0) {\n      int backreference_offset =\n          output_end - bytes_output +\n          get_next_bits(input, &input_offset, &bit_pool, &bits_left, 13) + 3;\n      int backreference_length = 3;\n      int vle_level;\n\n      for (vle_level = 0; vle_level < 4; vle_level++) {\n        int this_level = get_next_bits(input, &input_offset, &bit_pool,\n                                       &bits_left, vle_lens[vle_level]);\n        backreference_length += this_level;\n        if (this_level != ((1 << vle_lens[vle_level]) - 1)) break;\n      }\n\n      if (vle_level == 4) {\n        int this_level;\n        do {\n          this_level =\n              get_next_bits(input, &input_offset, &bit_pool, &bits_left, 8);\n          backreference_length += this_level;\n        } while (this_level == 255);\n      }\n\n      for (int i = 0; i < backreference_length; i++) {\n        output[output_end - bytes_output] = output[backreference_offset--];\n        bytes_output++;\n      }\n    } else {\n      // verbatim byte\n      output[output_end - bytes_output] = (uint8_t)get_next_bits(\n          input, &input_offset, &bit_pool, &bits_left, 8);\n      bytes_output++;\n    }\n  }\n  return IoError_OK;\n}\n\nIoError CpkArchive::Slurp(FileMeta* file, void*& outBuffer, int64_t& outSize) {\n  CpkMetaEntry* entry = (CpkMetaEntry*)file;\n  if (!entry->Compressed) {\n    return IoError_Fail;\n  }\n\n  int64_t pos = BaseStream->Seek(entry->Offset, RW_SEEK_SET);\n  if (pos != entry->Offset) {\n    ImpLog(LogLevel::Error, LogChannel::IO,\n           \"CPK failed to seek when slurping compressed file \\\"{:s}\\\" in \"\n           \"archive \\\"{:s}\\\"\\n\",\n           entry->FileName, BaseStream->Meta.FileName);\n    return IoError_Fail;\n  }\n  char* compressedData = (char*)malloc(entry->CompressedSize);\n  int64_t read = BaseStream->Read(compressedData, entry->CompressedSize);\n  if (read != entry->CompressedSize) {\n    ImpLog(LogLevel::Error, LogChannel::IO,\n           \"CPK failed to read compressed data when slurping compressed file \"\n           \"\\\"{:s}\\\" in \"\n           \"archive \\\"{:s}\\\"\\n\",\n           entry->FileName, BaseStream->Meta.FileName);\n    free(compressedData);\n    return IoError_Fail;\n  }\n\n  outSize = entry->Size;\n  outBuffer = malloc(outSize);\n\n  IoError err = DecompressLayla(compressedData, entry->CompressedSize,\n                                (char*)outBuffer, outSize);\n  if (err != IoError_OK) free(outBuffer);\n  free(compressedData);\n  return err;\n}\n\n}  // namespace Io\n}  // namespace Impacto"
  },
  {
    "path": "src/io/cpkarchive.h",
    "content": "#pragma once\n\n#include \"vfsarchive.h\"\n#include \"memorystream.h\"\n#include <vector>\n#include <ankerl/unordered_dense.h>\n\nnamespace Impacto {\nnamespace Io {\n\nstruct CpkMetaEntry;\n\nclass CpkArchive : public VfsArchive {\n public:\n  ~CpkArchive();\n\n  IoError Open(FileMeta* file, Stream** outStream) override;\n  IoError Slurp(FileMeta* file, void*& outBuffer, int64_t& outSize) override;\n\n  static IoError Create(Stream* stream, VfsArchive** outArchive);\n\n private:\n  IoError ReadToc(int64_t tocOffset, int64_t contentOffset);\n  IoError ReadEtoc(int64_t etocOffset);\n  IoError ReadItoc(int64_t itocOffset, int64_t contentOffset, uint16_t align);\n\n  CpkMetaEntry* GetFileListEntry(uint32_t id);\n\n  uint16_t Version;\n  uint16_t Revision;\n\n  CpkMetaEntry* FileList = 0;\n  uint32_t FileCount = 0;\n  uint32_t NextFile = 0;\n};\n\n}  // namespace Io\n}  // namespace Impacto"
  },
  {
    "path": "src/io/filemeta.cpp",
    "content": "#include \"filemeta.h\"\n#include \"../log.h\"\n#include <system_error>\n\nnamespace Impacto {\nnamespace Io {\n\nint64_t GetFileSize(std::string const& path) {\n  const std::string& filePath = GetSystemDependentPath(path);\n  std::error_code ec;\n  uintmax_t result = std::filesystem::file_size(filePath, ec);\n  if (ec) {\n    ImpLog(LogLevel::Error, LogChannel::IO,\n           \"Error getting file size of file \\\"{:s}\\\", error: \\\"{:s}\\\"\\n\", path,\n           ec.message());\n    return IoError_Fail;\n  }\n  // Hopefully no one has a file of size between int64_t max and uint64_t max\n  return static_cast<int64_t>(result);\n}\n\nIoError PathExists(std::string const& path) {\n  const std::string& filePath = GetSystemDependentPath(path);\n  std::error_code ec;\n  bool result = std::filesystem::exists(filePath, ec);\n  if (ec) {\n    ImpLog(LogLevel::Error, LogChannel::IO,\n           \"Error checking for file existence for file \\\"{:s}\\\", error: \"\n           \"\\\"{:s}\\\"\\n\",\n           filePath, ec.message());\n    return IoError_Fail;\n  }\n  return result == false ? IoError_NotFound : IoError_OK;\n}\n\nint8_t CreateDirectories(std::string const& path, bool createParent) {\n  using Path = std::filesystem::path;\n  std::error_code ec;\n  const std::string& filePath = GetSystemDependentPath(path);\n  const auto parentPath = Path(filePath).parent_path();\n  if (createParent && parentPath.empty()) return IoError_OK;\n  const auto dirPath = createParent ? parentPath : Path(filePath);\n  bool result = std::filesystem::create_directories(dirPath, ec);\n  if (ec) {\n    ImpLog(LogLevel::Error, LogChannel::IO,\n           \"Error creating directories for file \\\"{:s}\\\", error: \\\"{:s}\\\"\\n\",\n           filePath, ec.message());\n    return IoError_Fail;\n  }\n  return result;\n}\n\nIoError GetFilePermissions(std::string const& path,\n                           FilePermissionsFlags& flags) {\n  std::error_code ec;\n  const std::string& filePath = GetSystemDependentPath(path);\n  flags = std::filesystem::status(filePath, ec).permissions();\n  if (ec) {\n    ImpLog(LogLevel::Error, LogChannel::IO,\n           \"Error retrieving permissions for file \\\"{:s}\\\", error: \\\"{:s}\\\"\\n\",\n           filePath, ec.message());\n    return IoError_Fail;\n  }\n  return IoError_OK;\n}\n\n}  // namespace Io\n}  // namespace Impacto"
  },
  {
    "path": "src/io/filemeta.h",
    "content": "#pragma once\n\n#include <string>\n#include <cstdint>\n#include <filesystem>\n#include \"io.h\"\n\n#ifdef __ANDROID__\n#include <SDL2/SDL_system.h>\n// Safe to bind to const ref, since temporaries have their lifetimes extended\n#define GetSystemDependentPath(filePath)                                   \\\n  ((filePath.rfind(SDL_AndroidGetExternalStoragePath(), 0) ==              \\\n    std::string::npos)                                                     \\\n       ? SDL_AndroidGetExternalStoragePath() + std::string(\"/\") + filePath \\\n       : filePath)\n#else\n#define GetSystemDependentPath(filePath) filePath\n#endif\n\nnamespace Impacto {\nnamespace Io {\n\nstruct FileMeta {\n  std::string ArchiveFileName = \"\";\n  std::string ArchiveMountPoint = \"\";\n  std::string FileName = \"\";\n  uint32_t Id = 0;\n  int64_t Size = 0;\n};\n\n// TODO: use our own perms enum\nusing FilePermissionsFlags = std::filesystem::perms;\n\nint64_t GetFileSize(std::string const& path);\nIoError PathExists(std::string const& path);\nint8_t CreateDirectories(std::string const& path, bool createParent = false);\nIoError GetFilePermissions(std::string const& path,\n                           FilePermissionsFlags& flags);\n\n}  // namespace Io\n}  // namespace Impacto"
  },
  {
    "path": "src/io/io.h",
    "content": "#pragma once\n\n#include <cstdint>\n\nnamespace Impacto {\n\nenum IoError : int64_t {\n  IoError_OK = 0,\n  IoError_Fail = -1,\n  IoError_Eof = -2,\n  IoError_NotFound = -3\n};\n\n}  // namespace Impacto"
  },
  {
    "path": "src/io/lnk4archive.cpp",
    "content": "#include \"lnk4archive.h\"\n\n#include \"../log.h\"\n#include \"uncompressedstream.h\"\n#ifndef IMPACTO_DISABLE_MSPACK\n#include \"lzxstream.h\"\n#endif\n#include \"vfs.h\"\n#include \"../util.h\"\n#include <SDL_endian.h>\n\nnamespace Impacto {\nnamespace Io {\n\nstruct Lnk4MetaEntry : FileMeta {\n  int64_t Offset;\n};\n\nLnk4Archive::~Lnk4Archive() {\n  if (TOC) delete[] TOC;\n}\n\nIoError Lnk4Archive::Open(FileMeta* file, Stream** outStream) {\n  Lnk4MetaEntry* entry = (Lnk4MetaEntry*)file;\n#ifndef IMPACTO_DISABLE_MSPACK\n  IoError err =\n      LzxStream::Create(BaseStream, entry->Offset, entry->Size, outStream);\n  if (err != IoError_OK) {\n    err = UncompressedStream::Create(BaseStream, entry->Offset, entry->Size,\n                                     outStream);\n  }\n#else\n  IoError err = UncompressedStream::Create(BaseStream, entry->Offset,\n                                           entry->Size, outStream);\n#endif\n  if (err != IoError_OK) {\n    ImpLog(LogLevel::Error, LogChannel::IO,\n           \"LNK4 file open failed for file \\\"{:s}\\\" in archive \\\"{:s}\\\"\\n\",\n           entry->FileName, BaseStream->Meta.FileName);\n  }\n  return err;\n}\n\nIoError Lnk4Archive::Create(Stream* stream, VfsArchive** outArchive) {\n  ImpLog(LogLevel::Trace, LogChannel::IO, \"Trying to mount \\\"{:s}\\\" as LNK4\\n\",\n         stream->Meta.FileName);\n\n  Lnk4Archive* result = 0;\n  uint32_t* rawToc = 0;\n\n  uint32_t offsetBlockSize = 2048;\n  uint32_t blockSize = 1024;\n\n  uint32_t const magic = 0x4C4E4B34;\n\n  uint32_t const tocOffset = 8;\n\n  uint32_t maxFileCount;\n  uint32_t fileCount;\n  uint32_t dataOffset;\n  char fileName[6];\n\n  if (ReadBE<uint32_t>(stream) != magic) {\n    ImpLog(LogLevel::Trace, LogChannel::IO, \"Not LNK4\\n\");\n    goto fail;\n  }\n\n  dataOffset = ReadLE<uint32_t>(stream);\n  if (dataOffset < tocOffset) {\n    ImpLog(LogLevel::Error, LogChannel::IO, \"LNK4 header too short\\n\");\n    goto fail;\n  }\n\n  maxFileCount = (dataOffset - tocOffset) / 8;\n\n  rawToc = (uint32_t*)ImpStackAlloc(dataOffset - tocOffset);\n  if (stream->Read(rawToc, dataOffset - tocOffset) != dataOffset - tocOffset)\n    goto fail;\n\n  fileCount = 0;\n  for (uint32_t* it = rawToc; it < rawToc + (maxFileCount * 2); it += 2) {\n    // first file starts at 0\n    if (SDL_SwapLE32(*it) == 0 && it != rawToc) break;\n    fileCount++;\n  }\n\n  result = new Lnk4Archive;\n  result->BaseStream = stream;\n  result->NamesToIds.reserve(fileCount);\n  result->IdsToFiles.reserve(fileCount);\n  result->TOC = new Lnk4MetaEntry[fileCount];\n\n  for (uint32_t i = 0; i < fileCount; i++) {\n    snprintf(fileName, 6, \"%05i\", i);\n    result->TOC[i].FileName = fileName;\n    result->TOC[i].Id = i;\n    result->TOC[i].Offset =\n        SDL_SwapLE32(rawToc[i * 2]) * offsetBlockSize + dataOffset;\n    result->TOC[i].Size = SDL_SwapLE32(rawToc[i * 2 + 1]) * blockSize;\n    result->IdsToFiles[i] = &result->TOC[i];\n    result->NamesToIds[result->TOC[i].FileName] = i;\n  }\n\n  ImpStackFree(rawToc);\n\n  result->IsInit = true;\n  *outArchive = result;\n  return IoError_OK;\n\nfail:\n  stream->Seek(0, RW_SEEK_SET);\n  if (result) delete result;\n  if (rawToc) ImpStackFree(rawToc);\n  return IoError_Fail;\n}\n\n}  // namespace Io\n}  // namespace Impacto"
  },
  {
    "path": "src/io/lnk4archive.h",
    "content": "#pragma once\n\n#include \"vfsarchive.h\"\n\nnamespace Impacto {\nnamespace Io {\n\nstruct Lnk4MetaEntry;\n\nclass Lnk4Archive : public VfsArchive {\n public:\n  ~Lnk4Archive();\n  IoError Open(FileMeta* file, Stream** outStream) override;\n\n  static IoError Create(Stream* stream, VfsArchive** outArchive);\n\n private:\n  Lnk4MetaEntry* TOC = 0;\n};\n\n}  // namespace Io\n}  // namespace Impacto"
  },
  {
    "path": "src/io/lzxstream.cpp",
    "content": "#include \"lzxstream.h\"\n\n#include \"system.h\"\n#include \"lzx.h\"\n\nnamespace Impacto {\nnamespace Io {\n\nLzxStream::~LzxStream() { free(CompressedBuffer); }\n\nIoError LzxStream::Create(Stream* baseStream, int64_t offset, int64_t size,\n                          Stream** out) {\n  if (offset + size > baseStream->Meta.Size) return IoError_Fail;\n  Stream* dup;\n  int64_t err = baseStream->Duplicate(&dup);\n  if (err != IoError_OK) return (IoError)err;\n  err = dup->Seek(offset, RW_SEEK_SET);\n  if (err != offset) {\n    delete dup;\n    return IoError_Fail;\n  }\n\n  uint32_t const magic = 0x0FF512EE;\n\n  if (ReadBE<uint32_t>(dup) != magic) {\n    ImpLog(LogLevel::Trace, LogChannel::IO, \"Not LZX\\n\");\n    delete dup;\n    return IoError_Fail;\n  }\n\n  err = dup->Seek(12, RW_SEEK_CUR);\n  if (err < IoError_OK) {\n    delete dup;\n    return (IoError)err;\n  }\n\n  int32_t windowSize = ReadBE<int32_t>(dup);\n  int32_t compressionPartitionSize = ReadBE<int32_t>(dup);\n\n  int64_t uncompressedSize = ReadBE<int64_t>(dup);\n  int64_t compressedSize = ReadBE<int64_t>(dup);\n\n  int32_t uncompressedBlockSize = ReadBE<int32_t>(dup);\n  int32_t compressedBlockSize = ReadBE<int32_t>(dup);\n\n  int64_t compressedOffset = offset + 48;\n\n  if (compressedSize + 48 > size) {\n    delete dup;\n    return IoError_Fail;\n  }\n\n  LzxStream* result = new LzxStream(uncompressedBlockSize);\n  result->BaseStream = dup;\n  result->CompressedOffset = compressedOffset;\n  result->CompressedPosition = 0;\n  result->CompressedSize = compressedSize;\n  result->UncompressedPosition = 0;\n  result->UncompressedSize = uncompressedSize;\n  result->Meta.Size = uncompressedSize;\n\n  result->WindowSize = windowSize;\n  result->CompressionPartitionSize = compressionPartitionSize;\n  result->CompressedBuffer = (uint8_t*)malloc(compressedBlockSize);\n  result->CompressedBufferSize = compressedBlockSize;\n  *out = (Stream*)result;\n  return IoError_OK;\n}\n\nint64_t LzxStream::Read(void* buffer, int64_t sz) {\n  return ReadBuffered(buffer, sz);\n}\n\nint64_t LzxStream::Seek(int64_t offset, int origin) {\n  const int64_t absPos = [&]() {\n    switch (origin) {\n      case RW_SEEK_SET:\n        return offset;\n\n      case RW_SEEK_CUR:\n        return Position + offset;\n\n      case RW_SEEK_END:\n        return Meta.Size - offset;\n\n      default:\n        throw std::invalid_argument(fmt::format(\"Unknown origin {}\", origin));\n    }\n  }();\n  if (absPos < 0 || absPos > Meta.Size) return IoError_Fail;\n\n  int64_t err = SeekBuffered(absPos);\n  if (err < IoError_OK) {\n    if (absPos < Position) {\n      Position = 0;\n      CompressedPosition = 0;\n      UncompressedPosition = 0;\n      BaseStream->Seek(CompressedOffset, RW_SEEK_SET);\n    }\n    err = DiscardSeekBuffered(absPos);\n  }\n  if (err < IoError_OK) {\n    return err;\n  }\n  return Position;\n}\n\nIoError LzxStream::Duplicate(Stream** outStream) {\n  Stream* dup;\n  int64_t err = BaseStream->Duplicate(&dup);\n  if (err != IoError_OK) return (IoError)err;\n  LzxStream* result = new LzxStream(*this);\n  result->CompressedBuffer = (uint8_t*)malloc(CompressedBufferSize);\n  result->BaseStream = dup;\n  *outStream = (Stream*)result;\n  return IoError_OK;\n}\n\nIoError LzxStream::FillBuffer() {\n  uint32_t compressedBytes = ReadBE<uint32_t>(BaseStream);\n  if (compressedBytes > static_cast<uint32_t>(CompressedBufferSize)) {\n    // error: LZX block size larger than advertised\n    return IoError_Fail;\n  }\n  if (CompressedPosition + compressedBytes > CompressedSize) {\n    // error: LZX block out of file bounds\n    return IoError_Fail;\n  }\n  CompressedPosition += compressedBytes;\n\n  int64_t bufPos = 0;\n  while (bufPos < compressedBytes) {\n    int64_t read = BaseStream->Read(CompressedBuffer, compressedBytes - bufPos);\n    if (read <= 0) return IoError_Fail;\n    bufPos += read;\n  }\n\n  int64_t remaining_bytes = UncompressedSize - UncompressedPosition;\n  int64_t expected_bytes = BufferSize;\n  if (BufferSize > remaining_bytes) expected_bytes = remaining_bytes;\n  int32_t result =\n      LZXDecompress(CompressedBuffer, compressedBytes, Buffer,\n                    (int)expected_bytes, WindowSize, CompressionPartitionSize);\n  if (result < 0) return IoError_Fail;\n  UncompressedPosition += result;\n\n  BufferFill = result;\n  return IoError_OK;\n}\n\nstruct LZXFile {\n  uint8_t* buf;\n  int bufSize;\n  int pos;\n  int rest;\n};\n\n// Based on\n// https://github.com/gildor2/UEViewer/blob/master/Unreal/UnCoreCompression.cpp\n\nstatic int LZXread(struct LZXFile* file, void* buffer, int bytes) {\n  if (!file->rest) {\n    // read block header\n    if (file->buf[file->pos] == 0xFF) {\n      // [0]   = FF\n      // [1,2] = uncompressed block size\n      // [3,4] = compressed block size\n      file->rest = (file->buf[file->pos + 3] << 8) | file->buf[file->pos + 4];\n      file->pos += 5;\n    } else {\n      // [0,1] = compressed size\n      file->rest = (file->buf[file->pos + 0] << 8) | file->buf[file->pos + 1];\n      file->pos += 2;\n    }\n    if (file->rest > file->bufSize - file->pos)\n      file->rest = file->bufSize - file->pos;\n  }\n  if (bytes > file->rest) bytes = file->rest;\n  if (!bytes) return 0;\n\n  // copy block data\n  memcpy(buffer, file->buf + file->pos, bytes);\n  file->pos += bytes;\n  file->rest -= bytes;\n\n  return bytes;\n}\n\nstatic int LZXwrite(struct LZXFile* file, void* buffer, int bytes) {\n  memcpy(file->buf + file->pos, buffer, bytes);\n  file->pos += bytes;\n  return bytes;\n}\n\nstatic void* LZXalloc(struct mspack_system* self, size_t bytes) {\n  return malloc(bytes);\n}\n\nstatic void LZXfree(void* ptr) { free(ptr); }\n\nstatic void LZXCopy(void* src, void* dst, size_t bytes) {\n  memcpy(dst, src, bytes);\n}\n\nstatic struct mspack_system LZXSys = {\n    NULL,  // open\n    NULL,  // close\n    (auto (*)(mspack_file*, void*, int)->int)&LZXread,\n    (auto (*)(mspack_file*, void*, int)->int)&LZXwrite,\n    NULL,  // seek\n    NULL,  // tell\n    NULL,  // message\n    &LZXalloc,\n    &LZXfree,\n    &LZXCopy,\n    NULL};\n\nint32_t LZXDecompress(uint8_t* CompressedBuffer, int CompressedSize,\n                      uint8_t* UncompressedBuffer, int UncompressedSize,\n                      int WindowSize, int CompressionPartitionSize) {\n  // setup streams\n  struct LZXFile src, dst;\n  src.buf = CompressedBuffer;\n  src.bufSize = CompressedSize;\n  src.pos = 0;\n  src.rest = 0;\n  dst.buf = UncompressedBuffer;\n  dst.bufSize = UncompressedSize;\n  dst.pos = 0;\n  // prepare decompressor\n  if (WindowSize >= 32) {  // max is 21 or 25 (delta)\n    int t = WindowSize;\n    for (WindowSize = 0; t >>= 1; WindowSize++);\n  }\n  if (WindowSize <= 0) WindowSize = 17;\n  if (CompressionPartitionSize <= 0) CompressionPartitionSize = 256 * 1024;\n  struct lzxd_stream* lzxd =\n      lzxd_init(&LZXSys, (mspack_file*)&src, (mspack_file*)&dst, WindowSize, 0,\n                CompressionPartitionSize, UncompressedSize, 0);\n  // decompress\n  int r = lzxd_decompress(lzxd, UncompressedSize);\n  if (r != MSPACK_ERR_OK) return -1;\n  int32_t ret = dst.pos;\n  // free resources\n  lzxd_free(lzxd);\n\n  return ret;\n}\n\n}  // namespace Io\n}  // namespace Impacto"
  },
  {
    "path": "src/io/lzxstream.h",
    "content": "#pragma once\n\n#include \"stream.h\"\n#include \"buffering.h\"\n#include \"../log.h\"\n\nnamespace Impacto {\nnamespace Io {\n\nint32_t LZXDecompress(uint8_t* CompressedBuffer, int CompressedSize,\n                      uint8_t* UncompressedBuffer, int UncompressedSize,\n                      int WindowSize, int CompressionPartitionSize);\n\nclass LzxStream : public Stream, public Buffering<LzxStream> {\n  friend class Buffering<LzxStream>;\n\n public:\n  ~LzxStream();\n\n  bool IsSeekSlow = true;\n\n  static IoError Create(Stream* baseStream, int64_t offset, int64_t size,\n                        Stream** out);\n  int64_t Read(void* buffer, int64_t sz) override;\n  int64_t Seek(int64_t offset, int origin) override;\n  IoError Duplicate(Stream** outStream) override;\n\n protected:\n  LzxStream(int64_t uncompressedBufferSize)\n      : Buffering(uncompressedBufferSize) {}\n  LzxStream(LzxStream const& other) = default;\n\n  IoError FillBuffer();\n\n  Stream* BaseStream;\n  int64_t CompressedOffset;\n  int64_t CompressedPosition;\n  int64_t CompressedSize;\n  int64_t UncompressedPosition;\n  int64_t UncompressedSize;\n\n  int32_t WindowSize;\n  int32_t CompressionPartitionSize;\n\n  int32_t CompressedBufferSize;\n  uint8_t* CompressedBuffer;\n};\n\n}  // namespace Io\n}  // namespace Impacto"
  },
  {
    "path": "src/io/memorymappedfilestream.cpp",
    "content": "#include \"memorymappedfilestream.h\"\n#include \"../log.h\"\n\n#include <cstring>\n#include <algorithm>\n#include <system_error>\n\nnamespace Impacto {\nnamespace Io {\ntemplate <AccessMode M>\nIoError MemoryMappedFileStream<M>::Create(std::string const& fileName,\n                                          Stream** out) {\n  MemoryMappedFileStream* result =\n      new MemoryMappedFileStream(GetSystemDependentPath(fileName));\n  result->Meta.Size = GetFileSize(result->SourceFileName);\n  if (result->Meta.Size == IoError_Fail) {\n    delete result;\n    return IoError_Fail;\n  }\n\n  std::error_code ec;\n  result->mmapFile.map(result->SourceFileName, 0, result->Meta.Size, ec);\n  if (ec) {\n    ImpLog(LogLevel::Error, LogChannel::IO,\n           \"Failed to open file \\\"{:s}\\\", error:\\\"{:s}\\\"\\n\",\n           result->SourceFileName, ec.message());\n    delete result;\n    return ec == std::errc::no_such_file_or_directory ? IoError_Eof\n                                                      : IoError_Fail;\n  }\n  *out = (Stream*)result;\n  return IoError_OK;\n}\n\ntemplate <AccessMode M>\nint64_t MemoryMappedFileStream<M>::Read(void* buffer, int64_t sz) {\n  if (sz < 0) return IoError_Fail;\n  if (Position >= Meta.Size) return IoError_Eof;\n\n  size_t bytesToRead = std::min(sz, Meta.Size - Position);\n  assert(mmapFile.data());\n  // Lock only if we are in read/write mode\n  if constexpr (M == AccessMode::write) {\n    std::shared_lock lock(*mutexLock);  // Lock for shared read access\n    memcpy(buffer, mmapFile.data() + Position, bytesToRead);\n  } else {\n    memcpy(buffer, mmapFile.data() + Position, bytesToRead);\n  }\n\n  Position += bytesToRead;\n  return bytesToRead;\n}\n\ntemplate <AccessMode M>\nint64_t MemoryMappedFileStream<M>::Seek(int64_t offset, int origin) {\n  const int64_t absPos = [&]() {\n    switch (origin) {\n      case RW_SEEK_SET:\n        return offset;\n\n      case RW_SEEK_CUR:\n        return Position + offset;\n\n      case RW_SEEK_END:\n        return Meta.Size - offset;\n\n      default:\n        throw std::invalid_argument(fmt::format(\"Unknown origin {}\", origin));\n    }\n  }();\n\n  if (absPos < 0 || absPos > Meta.Size) return IoError_Fail;\n  Position = absPos;\n  return Position;\n}\n\ntemplate <AccessMode M>\nIoError MemoryMappedFileStream<M>::Duplicate(Stream** outStream) {\n  MemoryMappedFileStream* result = new MemoryMappedFileStream(*this);\n  if (result->Seek(Position, RW_SEEK_SET) < 0) {\n    ImpLog(LogLevel::Error, LogChannel::IO, \"Seek failed for file \\\"{:s}\\\"\\n\",\n           SourceFileName);\n    delete result;\n    return IoError_Fail;\n  }\n  *outStream = (Stream*)result;\n  return IoError_OK;\n}\n\ntemplate <AccessMode M>\nint64_t MemoryMappedFileStream<M>::Write(const void* buffer, int64_t sz,\n                                         size_t cnt) {\n  if constexpr (M == AccessMode::read) {\n    assert(false);\n    return 0;\n  } else {\n    std::lock_guard<std::shared_mutex> lock(*mutexLock);\n    sz = std::min(Meta.Size - Position, sz);\n    memcpy(mmapFile.data() + Position, buffer, sz);\n    Position += sz;\n    return sz;\n  }\n}\n\ntemplate class MemoryMappedFileStream<AccessMode::write>;\ntemplate class MemoryMappedFileStream<AccessMode::read>;\n\n}  // namespace Io\n}  // namespace Impacto"
  },
  {
    "path": "src/io/memorymappedfilestream.h",
    "content": "#pragma once\n\n#include \"stream.h\"\n#include <fstream>\n#include <cstddef>\n#include <shared_mutex>\n#include <variant>\n#include <mio/mio.hpp>\n\nnamespace Impacto {\nnamespace Io {\n\nusing AccessMode = mio::access_mode;\n\ntemplate <AccessMode Access>\nclass MemoryMappedFileStream : public Stream {\n public:\n  static constexpr AccessMode Mode = Access;\n\n  static IoError Create(std::string const& fileName, Stream** out);\n  int64_t Read(void* buffer, int64_t sz) override;\n  int64_t Seek(int64_t offset, int origin) override;\n  IoError Duplicate(Stream** outStream) override;\n  int64_t Write(const void* buffer, int64_t sz, size_t cnt = 1) override;\n\n protected:\n  MemoryMappedFileStream(std::string filePath)\n      : SourceFileName(std::move(filePath)) {\n    Meta.FileName = SourceFileName;\n    if constexpr (Access == AccessMode::write) {\n      mutexLock = std::make_shared<std::shared_mutex>();\n    }\n  }\n\n  MemoryMappedFileStream(MemoryMappedFileStream const& other)\n      : SourceFileName(other.SourceFileName), mmapFile(other.mmapFile) {\n    Meta.FileName = SourceFileName;\n    Meta.Size = other.Meta.Size;\n    if constexpr (Access == AccessMode::write) {\n      mutexLock = other.mutexLock;\n    }\n  }\n  std::string SourceFileName;\n  mio::basic_shared_mmap<Access, std::byte> mmapFile;\n\n  using WriteLockMember =\n      std::conditional_t<Access == AccessMode::write,\n                         std::shared_ptr<std::shared_mutex>, std::monostate>;\n  WriteLockMember mutexLock;\n};\n}  // namespace Io\n}  // namespace Impacto"
  },
  {
    "path": "src/io/memorystream.cpp",
    "content": "#include \"memorystream.h\"\n\n#include <algorithm>\n\nnamespace Impacto {\nnamespace Io {\n\nMemoryStream::~MemoryStream() {\n  if (FreeOnClose) free(Memory);\n}\n\nMemoryStream::MemoryStream(void* mem, int64_t size, bool freeOnClose)\n    : Memory(mem), FreeOnClose(freeOnClose) {\n  Meta.Size = size;\n  IsMemory = true;\n}\n\nint64_t MemoryStream::Read(void* buffer, int64_t sz) {\n  if (sz < 0) return IoError_Fail;\n  if (Position == Meta.Size) return IoError_Eof;\n  sz = std::min(Meta.Size - Position, sz);\n  memcpy(buffer, (uint8_t*)Memory + Position, sz);\n  Position += sz;\n  return sz;\n}\n\nint64_t MemoryStream::Write(const void* buffer, int64_t sz, size_t cnt) {\n  if (sz < 0) return IoError_Fail;\n  if (Position == Meta.Size) return IoError_Eof;\n  int64_t bytesToWrite = std::min(Meta.Size - Position, sz * (int64_t)cnt);\n  memcpy((uint8_t*)Memory + Position, buffer, bytesToWrite);\n  Position += bytesToWrite;\n  return bytesToWrite;\n}\n\nint64_t MemoryStream::Seek(int64_t offset, int origin) {\n  int64_t newPos = Position;\n  if (origin == RW_SEEK_SET) {\n    newPos = offset;\n  } else if (origin == RW_SEEK_CUR) {\n    newPos += offset;\n  } else if (origin == RW_SEEK_END) {\n    newPos = Meta.Size - offset;\n  } else {\n    return IoError_Fail;\n  }\n  if (newPos < 0 || newPos > Meta.Size) return IoError_Fail;\n  Position = newPos;\n  return newPos;\n}\n\nIoError MemoryStream::Duplicate(Stream** outStream) {\n  MemoryStream* result = new MemoryStream(*this);\n  result->FreeOnClose = false;\n  *outStream = (Stream*)result;\n  return IoError_OK;\n}\n\n}  // namespace Io\n}  // namespace Impacto"
  },
  {
    "path": "src/io/memorystream.h",
    "content": "#pragma once\n\n#include \"stream.h\"\n\nnamespace Impacto {\nnamespace Io {\n\nclass MemoryStream : public Stream {\n public:\n  ~MemoryStream();\n  MemoryStream(void* mem, int64_t size, bool freeOnClose = false);\n\n  int64_t Read(void* buffer, int64_t sz) override;\n  int64_t Seek(int64_t offset, int origin) override;\n  int64_t Write(const void* buffer, int64_t sz, size_t cnt = 1) override;\n  IoError Duplicate(Stream** outStream) override;\n\n protected:\n  MemoryStream() {}\n  MemoryStream(MemoryStream const& other) = default;\n\n  void* Memory;\n  bool FreeOnClose;\n};\n\n}  // namespace Io\n}  // namespace Impacto"
  },
  {
    "path": "src/io/mpkarchive.cpp",
    "content": "#include \"mpkarchive.h\"\n\n#include \"../log.h\"\n#include <ankerl/unordered_dense.h>\n#include \"zlibstream.h\"\n#include \"uncompressedstream.h\"\n#include \"vfs.h\"\n\nnamespace Impacto {\nnamespace Io {\n\nstatic int constexpr MpkMaxPath = 224;\n\nstruct MpkMetaEntry : FileMeta {\n  int64_t Offset;\n  int64_t CompressedSize;\n  bool Compressed;\n};\n\nMpkArchive::~MpkArchive() {\n  if (TOC) delete[] TOC;\n}\n\nIoError MpkArchive::Open(FileMeta* file, Stream** outStream) {\n  MpkMetaEntry* entry = (MpkMetaEntry*)file;\n  IoError err;\n  if (entry->Compressed) {\n    err = ZlibStream::Create(BaseStream, entry->Offset, entry->CompressedSize,\n                             entry->Size, outStream);\n\n  } else {\n    err = UncompressedStream::Create(BaseStream, entry->Offset, entry->Size,\n                                     outStream);\n  }\n  if (err != IoError_OK) {\n    ImpLog(LogLevel::Error, LogChannel::IO,\n           \"MPK file open failed for file \\\"{:s}\\\" in archive \\\"{:s}\\\" \"\n           \"(compression {:d})\\n\",\n           entry->FileName, BaseStream->Meta.FileName, entry->Compressed);\n  }\n  return err;\n}\n\nIoError MpkArchive::Create(Stream* stream, VfsArchive** outArchive) {\n  ImpLog(LogLevel::Trace, LogChannel::IO, \"Trying to mount \\\"{:s}\\\" as MPK\\n\",\n         stream->Meta.FileName);\n\n  MpkArchive* result = 0;\n\n  uint32_t const magic = 0x4D504B00;\n\n  uint32_t FileCount;\n  uint16_t MinorVersion;\n  uint16_t MajorVersion;\n  char name[MpkMaxPath];\n\n  if (ReadBE<uint32_t>(stream) != magic) {\n    ImpLog(LogLevel::Trace, LogChannel::IO, \"Not an MPK\\n\");\n    goto fail;\n  }\n\n  MinorVersion = ReadLE<uint16_t>(stream);\n  MajorVersion = ReadLE<uint16_t>(stream);\n  // TODO support v1\n  if (MinorVersion != 0 || MajorVersion != 2) {\n    ImpLog(LogLevel::Trace, LogChannel::IO,\n           \"Unsupported MPK version {:d}.{:d}\\n\", MajorVersion, MinorVersion);\n    goto fail;\n  }\n\n  result = new MpkArchive;\n  result->BaseStream = stream;\n  FileCount = ReadLE<uint32_t>(stream);\n  result->NamesToIds.reserve(FileCount);\n  result->IdsToFiles.reserve(FileCount);\n  result->TOC = new MpkMetaEntry[FileCount];\n\n  stream->Seek(0x40, RW_SEEK_SET);\n  for (uint32_t i = 0; i < FileCount; i++) {\n    uint32_t Compression = ReadLE<uint32_t>(stream);\n    uint32_t Id = ReadLE<uint32_t>(stream);\n\n    if (Compression != 1 && Compression != 0) {\n      ImpLog(LogLevel::Error, LogChannel::IO,\n             \"Unknown MPK compression type {:d} on file {:d}\\n\", Id,\n             Compression);\n      stream->Seek(0x100 - 8, RW_SEEK_SET);\n      continue;\n    }\n\n    MpkMetaEntry* entry = &result->TOC[i];\n    entry->Compressed = Compression;\n    entry->Id = Id;\n    entry->Offset = ReadLE<uint64_t>(stream);\n    entry->CompressedSize = ReadLE<uint64_t>(stream);\n    entry->Size = ReadLE<uint64_t>(stream);\n    stream->Read(name, MpkMaxPath);\n    name[MpkMaxPath - 1] = '\\0';\n    entry->FileName = std::string(name);\n\n    if (result->IdsToFiles.find(Id) != result->IdsToFiles.end()) {\n      ImpLog(LogLevel::Error, LogChannel::IO, \"Duplicate MPK file ID {:d}\\n\",\n             Id);\n      continue;\n    }\n    if (!entry->Offset) {\n      ImpLog(LogLevel::Error, LogChannel::IO, \"Reached end of ToC\\n\");\n      break;\n    }\n\n    result->IdsToFiles[Id] = entry;\n    result->NamesToIds[entry->FileName] = Id;\n  }\n\n  result->IsInit = true;\n  *outArchive = result;\n  return IoError_OK;\n\nfail:\n  stream->Seek(0, RW_SEEK_SET);\n  if (result) delete result;\n  return IoError_Fail;\n}\n\n}  // namespace Io\n}  // namespace Impacto"
  },
  {
    "path": "src/io/mpkarchive.h",
    "content": "#pragma once\n\n#include \"vfsarchive.h\"\n\nnamespace Impacto {\nnamespace Io {\n\nstruct MpkMetaEntry;\n\nclass MpkArchive : public VfsArchive {\n public:\n  ~MpkArchive();\n  IoError Open(FileMeta* file, Stream** outStream) override;\n\n  static IoError Create(Stream* stream, VfsArchive** outArchive);\n\n private:\n  MpkMetaEntry* TOC = 0;\n};\n\n}  // namespace Io\n}  // namespace Impacto"
  },
  {
    "path": "src/io/physicalfilestream.cpp",
    "content": "#include \"physicalfilestream.h\"\n#include \"../log.h\"\n#include \"io.h\"\n\n#include <cstring>\n#include <algorithm>\n#include <system_error>\n\nnamespace Impacto {\nnamespace Io {\nstd::ios_base::openmode PhysicalFileStream::PrepareFileOpenMode(\n    CreateFlags flags) {\n  const bool createFlag = (flags & CREATE);\n  const bool truncFlag = (flags & TRUNCATE);\n  const bool appendFlag = (flags & APPEND);\n\n  std::ios_base::openmode mode = std::ios::binary;\n  ErrorCode = PathExists(SourceFileName);\n  if (ErrorCode == IoError_Fail) {\n    return {};\n  }\n  const bool fileExists = ErrorCode != IoError_NotFound;\n\n  const bool notFoundNoCreate = !fileExists && !createFlag;\n  if (notFoundNoCreate) {\n    std::string errMsg =\n        std::make_error_code(std::errc::no_such_file_or_directory).message();\n    ImpLog(LogLevel::Error, LogChannel::IO,\n           \"Failed to open stream \\\"{:s}\\\", error: \\\"{:s}\\\"\\n\", SourceFileName,\n           errMsg);\n    return {};\n  }\n\n  const bool writeExistNoOverwrite =\n      (flags & WRITE) && fileExists && !truncFlag && !appendFlag;\n  if (writeExistNoOverwrite) {\n    // avoid truncating when in write only mode without truncate flag to\n    // preserve file size\n    // I think this is more intuitive than making it an error\n    // or letting the truncate happen\n    flags |= CreateFlagsMode::READ;\n  }\n\n  // require write for create/overwrite flags\n  if (!fileExists && createFlag) {\n    flags |= WRITE;\n  }\n\n  // Don't truncate if readonly\n  assert((flags & WRITE) || !truncFlag);\n\n  // trunc and append are mutually exclusive\n  assert(!truncFlag || !appendFlag);\n\n  if (flags & READ) mode |= std::ios::in;\n  if (flags & WRITE) mode |= std::ios::out;\n  if (appendFlag) mode |= std::ios::app;\n  if (truncFlag || (!fileExists && !appendFlag)) mode |= std::ios::trunc;\n  if (flags & CREATE_DIRS) {\n    if (Io::CreateDirectories(SourceFileName, true) == IoError_Fail) {\n      ErrorCode = IoError_Fail;\n      return {};\n    }\n  }\n\n  return mode;\n}\n\nIoError PhysicalFileStream::Create(std::string const& fileName, Stream** out,\n                                   CreateFlags flags) {\n  PhysicalFileStream* result =\n      new PhysicalFileStream(GetSystemDependentPath(fileName), flags);\n  if (auto err = result->ErrorCode; err != IoError_OK) {\n    ImpLog(LogLevel::Error, LogChannel::IO, \"Failed to open file \\\"{:s}\\\"\\n\",\n           result->SourceFileName);\n    delete result;\n    return err;\n  }\n  if (!result->FileStream) {\n    ImpLog(LogLevel::Error, LogChannel::IO,\n           \"Failed to open file \\\"{:s}\\\", error: \\\"{:s}\\\"\\n\",\n           result->SourceFileName, std::generic_category().message(errno));\n    delete result;\n    return IoError_Fail;\n  }\n  *out = (Stream*)result;\n  return IoError_OK;\n}\n\nint64_t PhysicalFileStream::Read(void* buffer, int64_t sz) {\n  if (sz < 0 || !(Flags & READ)) return IoError_Fail;\n  if (Position >= Meta.Size) return IoError_Eof;\n\n  size_t bytesToRead = std::min(sz, Meta.Size - Position);\n  FileStream.read((char*)buffer, bytesToRead);\n  auto read = FileStream.gcount();\n\n  if (read == 0) {  // Check if no data was read\n    if (FileStream.eof()) return IoError_Eof;\n    ImpLog(LogLevel::Error, LogChannel::IO,\n           \"Read failed for file \\\"{:s}\\\" with error: \\\"{:s}\\\"\\n\",\n           SourceFileName, std::generic_category().message(errno));\n    FileStream.clear(FileStream.rdstate() & ~std::ios::failbit &\n                     ~std::ios::eofbit);\n    return IoError_Fail;\n  }\n\n  Position += read;\n  return read;\n}\n\nint64_t PhysicalFileStream::Seek(int64_t offset, int origin) {\n  if (!(Flags & READ) && (Flags & APPEND)) {\n    // seeking is useless in readless append mode\n    return IoError_Fail;\n  }\n  const int64_t absPos = [&]() {\n    switch (origin) {\n      case RW_SEEK_SET:\n        return offset;\n\n      case RW_SEEK_CUR:\n        return Position + offset;\n\n      case RW_SEEK_END:\n        return Meta.Size - offset;\n\n      default:\n        throw std::invalid_argument(fmt::format(\"Unknown origin {}\", origin));\n    }\n  }();\n\n  // seeking past EOF is a legal operation, after write past EOF, the gap\n  // between prev file size and write pos is zero padded\n  if (absPos < 0) return IoError_Fail;\n  FileStream.seekg(absPos, std::ios::beg);\n  if (!FileStream && !FileStream.eof()) {\n    ImpLog(LogLevel::Error, LogChannel::IO,\n           \"Seek failed for file \\\"{:s}\\\" with error: \\\"{:s}\\\"\\n\",\n           SourceFileName, std::generic_category().message(errno));\n    FileStream.clear(FileStream.rdstate() & ~std::ios::failbit &\n                     ~std::ios::eofbit);  // Clear only failbit and eofbit\n    return IoError_Fail;\n  }\n  Position = FileStream.tellg();\n  return Position;\n}\n\nIoError PhysicalFileStream::Duplicate(Stream** outStream) {\n  PhysicalFileStream* result = new PhysicalFileStream(*this);\n  if (IoError err = result->ErrorCode; err != IoError_OK) {\n    ImpLog(LogLevel::Error, LogChannel::IO, \"Failed to open file \\\"{:s}\\\"\\n\",\n           SourceFileName);\n    delete result;\n    return err;\n  }\n  if (!result->FileStream) {\n    ImpLog(LogLevel::Error, LogChannel::IO,\n           \"Failed to open file \\\"{:s}\\\", error: \\\"{:s}\\\"\\n\", SourceFileName,\n           std::generic_category().message(errno));\n    delete result;\n    return IoError_Fail;\n  }\n  if (result->Seek(Position, RW_SEEK_SET) < 0) {\n    ImpLog(LogLevel::Error, LogChannel::IO,\n           \"Seek failed for file \\\"{:s}\\\" with error: \\\"{:s}\\\"\\n\",\n           SourceFileName, std::generic_category().message(errno));\n    delete result;\n    return IoError_Fail;\n  }\n  *outStream = (Stream*)result;\n  return IoError_OK;\n}\n\nint64_t PhysicalFileStream::Write(const void* buffer, int64_t sz, size_t cnt) {\n  if (!(Flags & (WRITE | APPEND))) {\n    ImpLog(LogLevel::Error, LogChannel::IO,\n           \"Write failed for file \\\"{:s}\\\" with error: \\\"{:s}\\\"\\n\",\n           SourceFileName, std::generic_category().message(errno));\n    return IoError_Fail;\n  }\n  FileStream.write((char*)buffer, sz * cnt);\n  if (!FileStream) {\n    ImpLog(LogLevel::Error, LogChannel::IO,\n           \"Write failed for file \\\"{:s}\\\" with error: \\\"{:s}\\\"\\n\",\n           SourceFileName, std::generic_category().message(errno));\n    FileStream.clear(FileStream.rdstate() & ~std::ios::failbit &\n                     ~std::ios::eofbit);  // Clear only failbit and eofbit\n    return IoError_Fail;\n  }\n  int64_t written = sz * (int64_t)cnt;\n  Position += written;\n  Meta.Size = std::max(Position, Meta.Size);\n  return written;\n}\n\n}  // namespace Io\n}  // namespace Impacto"
  },
  {
    "path": "src/io/physicalfilestream.h",
    "content": "#pragma once\n\n#include \"stream.h\"\n#include <fstream>\n\nnamespace Impacto {\nnamespace Io {\n\nclass PhysicalFileStream : public Stream {\n public:\n  enum CreateFlagsMode {\n    READ = 1,\n    WRITE = READ << 1,\n    CREATE = WRITE << 1,\n    TRUNCATE = CREATE << 1,\n    APPEND = TRUNCATE << 1,\n    CREATE_DIRS = APPEND << 1,\n    UNBUFFERED = CREATE_DIRS << 1\n  };\n  using CreateFlags = int;\n  static IoError Create(std::string const& fileName, Stream** out,\n                        CreateFlags flags = CreateFlagsMode::READ);\n  int64_t Read(void* buffer, int64_t sz) override;\n  int64_t Seek(int64_t offset, int origin) override;\n  IoError Duplicate(Stream** outStream) override;\n  int64_t Write(const void* buffer, int64_t sz, size_t cnt = 1) override;\n\n protected:\n  std::ios_base::openmode PrepareFileOpenMode(CreateFlags flags);\n\n  PhysicalFileStream(std::string filePath, CreateFlags flags)\n      : Flags(flags), SourceFileName(std::move(filePath)) {\n    Init();\n  }\n\n  PhysicalFileStream(std::string filePath)\n      : PhysicalFileStream(std::move(filePath), CreateFlagsMode::READ) {}\n\n  PhysicalFileStream(PhysicalFileStream const& other)\n      : Flags(other.Flags), SourceFileName(other.SourceFileName) {\n    Init();\n  }\n  IoError ErrorCode = IoError_OK;\n  CreateFlags Flags;\n  std::string SourceFileName;\n  std::fstream FileStream;\n\n private:\n  void Init() {\n    Meta.FileName = SourceFileName;\n    if (Flags & UNBUFFERED) {\n      FileStream.rdbuf()->pubsetbuf(nullptr, 0);\n    }\n    auto fstreamFlags = PrepareFileOpenMode(Flags);\n    if (ErrorCode == IoError_NotFound) {\n      Meta.Size = 0;\n      ErrorCode = IoError_OK;\n    } else if (ErrorCode != IoError_Fail) {\n      Meta.Size = GetFileSize(SourceFileName);\n      if (Meta.Size == IoError_Fail) {\n        ErrorCode = IoError_Fail;\n        return;\n      }\n    } else {\n      return;\n    }\n    FileStream.open(SourceFileName, fstreamFlags);\n  }\n};\n\n}  // namespace Io\n}  // namespace Impacto"
  },
  {
    "path": "src/io/stream.h",
    "content": "#pragma once\n\n#include \"filemeta.h\"\n#include \"io.h\"\n#include <SDL_rwops.h>\n#include <SDL_endian.h>\n#include <glm/glm.hpp>\n#include <vector>\n#include <array>\n\nnamespace Impacto {\nnamespace Io {\n\nclass Stream {\n public:\n  virtual ~Stream() {}\n  FileMeta Meta;\n  int64_t Position = 0;\n\n  bool IsSeekSlow = false;\n  bool IsMemory = false;\n\n  virtual int64_t Read(void* buffer, int64_t sz) = 0;\n  virtual int64_t Seek(int64_t offset, int origin) = 0;\n  virtual int64_t Write(const void* buffer, int64_t sz, size_t cnt = 1) {\n    assert(false && \"Write not implemented for this stream\");\n    return IoError_Fail;\n  }\n  virtual IoError Duplicate(Stream** outStream) = 0;\n};\n\ninline uint8_t ReadU8(Stream* stream) {\n  uint8_t result;\n  stream->Read(&result, 1);\n  return result;\n}\n\ntemplate <typename T>\ninline T ReadWithoutSwap(Stream* stream) {\n  T result;\n  stream->Read(&result, sizeof(T));\n  return result;\n}\n\ntemplate <size_t count, typename T>\ninline void ReadArrayWithoutSwap(T* dest, Stream* stream) {\n  stream->Read(dest, count * sizeof(T));\n}\ntemplate <typename T>\ninline void ReadArrayWithoutSwap(T* dest, Stream* stream, size_t count) {\n  stream->Read(dest, count * sizeof(T));\n}\n\ntemplate <typename T>\ninline T SwapHelper(T value) {\n  if constexpr (std::is_same_v<uint16_t, T> || std::is_same_v<int16_t, T>) {\n    return SDL_Swap16(value);\n  } else if constexpr (std::is_same_v<uint32_t, T> ||\n                       std::is_same_v<int32_t, T>) {\n    return SDL_Swap32(value);\n  } else if constexpr (std::is_same_v<uint64_t, T> ||\n                       std::is_same_v<int64_t, T>) {\n    return SDL_Swap64(value);\n  } else if constexpr (std::is_same_v<float, T>) {\n    return SDL_SwapFloat(value);\n  } else if constexpr (std::is_same_v<double, T>) {\n    union {\n      double result;\n      uint64_t result_int;\n    } u;\n    u.result = value;\n    u.result_int = SDL_Swap64(u.result_int);\n    return u.result;\n  } else if constexpr (std::is_same_v<int8_t, T> ||\n                       std::is_same_v<uint8_t, T>) {\n    return value;\n  } else {\n    static_assert(!sizeof(T*), \"Unsupported type for SwapHelper\");\n  }\n}\n\ntemplate <typename T>\ninline T ReadSwap(Stream* stream) {\n  T result;\n  stream->Read(&result, sizeof(T));\n  return SwapHelper(result);\n}\n\ntemplate <size_t count, typename T>\ninline void ReadArraySwap(T* dest, Stream* stream) {\n  ReadArrayWithoutSwap<count, T>(dest, stream);\n  for (size_t i = 0; i < count; i++) dest[i] = SwapHelper<T>(dest[i]);\n}\n\ntemplate <typename T>\ninline void ReadArraySwap(T* dest, Stream* stream, size_t count) {\n  ReadArrayWithoutSwap<T>(dest, stream, count);\n  for (size_t i = 0; i < count; i++) dest[i] = SwapHelper<T>(dest[i]);\n}\n\n#if SDL_BYTEORDER == SDL_BIG_ENDIAN\ntemplate <typename T>\ninline T ReadLE(InputStream* stream) {\n  return ReadSwap<T>(stream);\n}\ntemplate <size_t count, typename T>\ninline void ReadArrayLE(T* dest, InputStream* stream) {\n  ReadArraySwap<count, T>(dest, stream);\n}\ntemplate <typename T>\ninline void ReadArrayLE(T* dest, InputStream* stream, size_t count) {\n  ReadArraySwap<T>(dest, stream, count);\n}\ntemplate <typename T>\ninline T ReadBE(InputStream* stream) {\n  return ReadWithoutSwap<T>(stream);\n}\ntemplate <size_t count, typename T>\ninline void ReadArrayBE(T* dest, InputStream* stream) {\n  ReadArrayWithoutSwap<count, T>(dest, stream);\n}\ntemplate <typename T>\ninline void ReadArrayBE(T* dest, InputStream* stream, size_t count) {\n  ReadArrayWithoutSwap<T>(dest, stream, count);\n}\n#else\ntemplate <typename T>\ninline T ReadLE(Stream* stream) {\n  return ReadWithoutSwap<T>(stream);\n}\ntemplate <size_t count, typename T>\ninline void ReadArrayLE(T* dest, Stream* stream) {\n  ReadArrayWithoutSwap<count, T>(dest, stream);\n}\ntemplate <typename T>\ninline void ReadArrayLE(T* dest, Stream* stream, size_t count) {\n  ReadArrayWithoutSwap<T>(dest, stream, count);\n}\ntemplate <typename T>\ninline T ReadBE(Stream* stream) {\n  return ReadSwap<T>(stream);\n}\ntemplate <size_t count, typename T>\ninline void ReadArrayBE(T* dest, Stream* stream) {\n  ReadArraySwap<count, T>(dest, stream);\n}\ntemplate <typename T>\ninline void ReadArrayBE(T* dest, Stream* stream, size_t count) {\n  ReadArraySwap<T>(dest, stream, count);\n}\n#endif\n\ninline void ReadVec2LE(float* dest, Stream* stream) {\n  ReadArrayLE<2>(dest, stream);\n}\ninline void ReadVec2LE(glm::vec2* dest, Stream* stream) {\n  ReadArrayLE<2>((float*)dest, stream);\n}\ninline void ReadVec3LE(float* dest, Stream* stream) {\n  ReadArrayLE<3>(dest, stream);\n}\ninline void ReadVec3LE(glm::vec3* dest, Stream* stream) {\n  ReadArrayLE<3>((float*)dest, stream);\n}\ninline void ReadVec4LE(float* dest, Stream* stream) {\n  ReadArrayLE<4>(dest, stream);\n}\ninline void ReadVec4LE(glm::vec4* dest, Stream* stream) {\n  ReadArrayLE<4>((float*)dest, stream);\n}\ninline void ReadMat4LE(float* dest, Stream* stream) {\n  ReadArrayLE<16>(dest, stream);\n}\ninline void ReadMat4LE(glm::mat4* dest, Stream* stream) {\n  ReadArrayLE<16>((float*)dest, stream);\n}\n\ninline void ReadVec2BE(float* dest, Stream* stream) {\n  ReadArrayBE<2>(dest, stream);\n}\ninline void ReadVec2BE(glm::vec2* dest, Stream* stream) {\n  ReadArrayBE<2>((float*)dest, stream);\n}\ninline void ReadVec3BE(float* dest, Stream* stream) {\n  ReadArrayBE<3>(dest, stream);\n}\ninline void ReadVec3BE(glm::vec3* dest, Stream* stream) {\n  ReadArrayBE<3>((float*)dest, stream);\n}\ninline void ReadVec4BE(float* dest, Stream* stream) {\n  ReadArrayBE<4>(dest, stream);\n}\ninline void ReadVec4BE(glm::vec4* dest, Stream* stream) {\n  ReadArrayBE<4>((float*)dest, stream);\n}\ninline void ReadMat4BE(float* dest, Stream* stream) {\n  ReadArrayBE<16>(dest, stream);\n}\ninline void ReadMat4BE(glm::mat4* dest, Stream* stream) {\n  ReadArrayBE<16>((float*)dest, stream);\n}\n\ninline void WriteU8(Stream* stream, uint8_t value) { stream->Write(&value, 1); }\n\ntemplate <typename T>\ninline void WriteWithoutSwap(Stream* stream, T value, size_t count = 1) {\n  for (size_t i = 0; i < count; i++) {\n    stream->Write(&value, sizeof(T));\n  }\n}\n\ntemplate <size_t count, typename T>\ninline void WriteArrayWithoutSwap(const T* src, Stream* stream) {\n  stream->Write(src, sizeof(T), count);\n}\ntemplate <typename T>\ninline void WriteArrayWithoutSwap(const T* src, Stream* stream, size_t count) {\n  stream->Write(src, sizeof(T), count);\n}\n\ntemplate <typename T>\ninline void WriteSwap(Stream* stream, T value, size_t count = 1) {\n  T result = SwapHelper<T>(value);\n\n  for (size_t i = 0; i < count; i++) {\n    stream->Write(&result, sizeof(T));\n  }\n}\n\ntemplate <size_t count, typename T>\ninline void WriteArraySwap(T* src, Stream* stream) {\n  std::array<T, count> swapped;\n  for (size_t i = 0; i < count; i++) swapped[i] = SwapHelper<T>(src[i]);\n  WriteArrayWithoutSwap<count, T>(swapped.data(), stream);\n}\n\ntemplate <typename T>\ninline void WriteArraySwap(T* src, Stream* stream, size_t count) {\n  std::vector<T> swapped(src, src + count);\n  for (size_t i = 0; i < count; i++) swapped[i] = SwapHelper(src[i]);\n  WriteArrayWithoutSwap<T>(swapped.data(), stream, count);\n}\n\n#if SDL_BYTEORDER == SDL_BIG_ENDIAN\ntemplate <typename T>\ninline T WriteLE(InputStream* stream, T value, size_t count = 1) {\n  return WriteSwap<T>(stream, value, count);\n}\ntemplate <size_t count, typename T>\ninline void WriteArrayLE(T* src, InputStream* stream) {\n  WriteArraySwap<count, t>(src, stream);\n}\ntemplate <typename T>\ninline void WriteArrayLE(T* src, InputStream* stream, size_t count) {\n  WriteArraySwap<T>(src, stream, count);\n}\ntemplate <typename T>\ninline T WriteBE(InputStream* stream, T value, size_t count = 1) {\n  return WriteWithoutSwap<T>(stream, value, count);\n}\ntemplate <size_t count, typename T>\ninline void WriteArrayBE(T* src, InputStream* stream) {\n  WriteArrayWithoutSwap<count, T>(src, stream);\n}\ntemplate <typename T>\ninline void WriteArrayBE(T* src, InputStream* stream, size_t count) {\n  WriteArrayWithoutSwap<T>(src, stream, count);\n}\n#else\ntemplate <typename T>\ninline void WriteLE(Stream* stream, T value, size_t count = 1) {\n  return WriteWithoutSwap<T>(stream, value, count);\n}\ntemplate <size_t count, typename T>\ninline void WriteArrayLE(const T* src, Stream* stream) {\n  WriteArrayWithoutSwap<count, T>(src, stream);\n}\ntemplate <typename T>\ninline void WriteArrayLE(const T* src, Stream* stream, size_t count) {\n  WriteArrayWithoutSwap<T>(src, stream, count);\n}\ntemplate <typename T>\ninline void WriteBE(Stream* stream, T value, size_t count = 1) {\n  return WriteSwap<T>(stream, value, count);\n}\ntemplate <size_t count, typename T>\ninline void WriteArrayBE(T* src, Stream* stream) {\n  WriteArraySwap<count, T>(src, stream);\n}\ntemplate <typename T>\ninline void WriteArrayBE(T* src, Stream* stream, size_t count) {\n  WriteArraySwap<T>(src, stream, count);\n}\n#endif\n\ninline void WriteVec2LE(float* src, Stream* stream) {\n  WriteArrayLE<2>(src, stream);\n}\ninline void WriteVec2LE(glm::vec2* src, Stream* stream) {\n  WriteArrayLE<2>((float*)src, stream);\n}\ninline void WriteVec3LE(float* src, Stream* stream) {\n  WriteArrayLE<3>(src, stream);\n}\ninline void WriteVec3LE(glm::vec3* src, Stream* stream) {\n  WriteArrayLE<3>((float*)src, stream);\n}\ninline void WriteVec4LE(float* src, Stream* stream) {\n  WriteArrayLE<4>(src, stream);\n}\ninline void WriteVec4LE(glm::vec4* src, Stream* stream) {\n  WriteArrayLE<4>((float*)src, stream);\n}\ninline void WriteMat4LE(float* src, Stream* stream) {\n  WriteArrayLE<16>(src, stream);\n}\ninline void WriteMat4LE(glm::mat4* src, Stream* stream) {\n  WriteArrayLE<16>((float*)src, stream);\n}\n\ninline void WriteVec2BE(float* src, Stream* stream) {\n  WriteArrayBE<2>(src, stream);\n}\ninline void WriteVec2BE(glm::vec2* src, Stream* stream) {\n  WriteArrayBE<2>((float*)src, stream);\n}\ninline void WriteVec3BE(float* src, Stream* stream) {\n  WriteArrayBE<3>(src, stream);\n}\ninline void WriteVec3BE(glm::vec3* src, Stream* stream) {\n  WriteArrayBE<3>((float*)src, stream);\n}\ninline void WriteVec4BE(float* src, Stream* stream) {\n  WriteArrayBE<4>(src, stream);\n}\ninline void WriteVec4BE(glm::vec4* src, Stream* stream) {\n  WriteArrayBE<4>((float*)src, stream);\n}\ninline void WriteMat4BE(float* src, Stream* stream) {\n  WriteArrayBE<16>(src, stream);\n}\ninline void WriteMat4BE(glm::mat4* src, Stream* stream) {\n  WriteArrayBE<16>((float*)src, stream);\n}\n\n}  // namespace Io\n}  // namespace Impacto"
  },
  {
    "path": "src/io/textarchive.cpp",
    "content": "#include \"textarchive.h\"\n\n#include \"../log.h\"\n#include \"io.h\"\n#include \"physicalfilestream.h\"\n#include \"vfs.h\"\n#include \"../util.h\"\n#include <sstream>\n#include <filesystem>\n\nnamespace Impacto {\nnamespace Io {\n\nenum TextArchiveType { CLS, MLP, TextMPK };\n\nstruct TextMetaEntry : FileMeta {\n  std::string FullPath;\n};\n\nTextArchive::~TextArchive() {\n  if (TOC) delete[] TOC;\n}\n\nIoError TextArchive::Open(FileMeta* file, Stream** outStream) {\n  TextMetaEntry* entry = (TextMetaEntry*)file;\n  IoError err = PhysicalFileStream::Create(entry->FullPath, outStream);\n  if (err != IoError_OK) {\n    ImpLog(\n        LogLevel::Error, LogChannel::IO,\n        \"TextArchive file open failed for file \\\"{:s}\\\" in archive \\\"{:s}\\\"\\n\",\n        entry->FullPath, BaseStream->Meta.FileName);\n  }\n  return err;\n}\n\nIoError TextArchive::GetCurrentSize(FileMeta* file, int64_t& outSize) {\n  TextMetaEntry* entry = (TextMetaEntry*)file;\n  std::error_code ec;\n  outSize = std::filesystem::file_size(entry->FullPath, ec);\n  if (ec) {\n    ImpLog(LogLevel::Error, LogChannel::IO,\n           \"TextArchive getting size failed for file \\\"{:s}\\\" in archive \"\n           \"\\\"{:s}\\\"\\nerror: {:s}\\n\",\n           entry->FullPath, BaseStream->Meta.FileName, ec.message());\n    return IoError_Fail;\n  }\n  return IoError_OK;\n}\n\nIoError TextArchive::Create(Stream* stream, VfsArchive** outArchive) {\n  ImpLog(LogLevel::Trace, LogChannel::IO,\n         \"Trying to mount \\\"{:s}\\\" as text archive\\n\", stream->Meta.FileName);\n\n  std::istringstream ss;\n  std::string content;\n  std::string basePath;\n\n  TextArchiveType type;\n\n  int64_t size;\n  int64_t maxFileCount;\n  TextArchive* result;\n  uint32_t lineId;\n\n  result = 0;\n\n  if (StringEndsWithCi(stream->Meta.FileName, \".cls\")) {\n    type = CLS;\n  } else if (StringEndsWithCi(stream->Meta.FileName, \".mlp\")) {\n    type = MLP;\n  } else if (StringEndsWithCi(stream->Meta.FileName, \".mpk\")) {\n    type = TextMPK;\n    if (ReadBE<uint32_t>(stream) == 0x4D504B00u) {\n      ImpLog(LogLevel::Trace, LogChannel::IO, \"Actually a binary MPK\\n\");\n      goto fail;\n    }\n    stream->Seek(0, RW_SEEK_SET);\n  } else {\n    ImpLog(LogLevel::Trace, LogChannel::IO, \"Not a text archive\\n\");\n    goto fail;\n  }\n\n  content.resize(stream->Meta.Size);\n  size = stream->Read(&content[0], stream->Meta.Size);\n  content.resize(size);\n  maxFileCount = std::count(content.begin(), content.end(), '\\n') + 1;\n\n  result = new TextArchive;\n  result->BaseStream = stream;\n  result->NamesToIds.reserve(maxFileCount);\n  result->IdsToFiles.reserve(maxFileCount);\n  result->TOC = new TextMetaEntry[maxFileCount];\n\n  switch (type) {\n    case CLS: {\n      basePath =\n          stream->Meta.FileName.substr(0, stream->Meta.FileName.length() -\n                                              std::string(\".cls\").length()) +\n          \"/\";\n      break;\n    }\n    case MLP:\n    case TextMPK: {\n      basePath = \"\";\n      break;\n    }\n  }\n\n  lineId = 0;\n\n  ss = std::istringstream(content, std::ios::in);\n  for (std::string line; std::getline(ss, line); lineId++) {\n    if (line.empty()) continue;\n\n    if (line[line.size() - 1] == '\\r') line.pop_back();\n    uint32_t id;\n\n    if (type == CLS) {\n      // fullPath\n      id = lineId;\n      result->TOC[lineId].FileName = line;\n      result->TOC[lineId].FullPath = basePath + line;\n\n    } else if (type == MLP) {\n      // fullPath,id\n      size_t firstColLength = line.find(',');\n      if (firstColLength == std::string::npos ||\n          firstColLength == line.length() - 1)\n        continue;\n      std::string fullPath = line.substr(0, firstColLength);\n      id = std::atoi(&line[firstColLength + 1]);\n      size_t fileNameStart = fullPath.rfind('/') + 1;\n      size_t fileNameStart2 = fullPath.rfind('\\\\') + 1;\n      if (fileNameStart2 > fileNameStart) fileNameStart = fileNameStart2;\n      result->TOC[lineId].FullPath = basePath + fullPath;\n      result->TOC[lineId].FileName = fullPath.substr(fileNameStart);\n\n    } else if (type == TextMPK) {\n      // fullPath,name,id\n      size_t firstColLength = line.find(',');\n      if (firstColLength == std::string::npos ||\n          firstColLength == line.length() - 1)\n        continue;\n      size_t secondColLength = line.find(',', firstColLength + 1);\n      if (secondColLength == std::string::npos ||\n          secondColLength == line.length() - 1)\n        continue;\n\n      std::string fullPath = line.substr(0, firstColLength);\n      id = std::atoi(&line[secondColLength + 1]);\n      result->TOC[lineId].FullPath = basePath + fullPath;\n      result->TOC[lineId].FileName =\n          line.substr(firstColLength + 1, secondColLength);\n    } else {\n      ImpLog(LogLevel::Error, LogChannel::IO,\n             \"Archive {:s} could not be mounted as type {:d} is unknown\\n\",\n             stream->Meta.FileName, to_underlying(type));\n      return IoError_Fail;\n    }\n\n    result->TOC[lineId].Size = -1;\n    result->IdsToFiles[id] = &result->TOC[lineId];\n    result->TOC[lineId].Id = id;\n    result->NamesToIds[result->TOC[lineId].FileName] = id;\n  }\n\n  result->IsInit = true;\n  *outArchive = result;\n  return IoError_OK;\n\nfail:\n  stream->Seek(0, RW_SEEK_SET);\n  if (result) delete result;\n  return IoError_Fail;\n}\n\n}  // namespace Io\n}  // namespace Impacto"
  },
  {
    "path": "src/io/textarchive.h",
    "content": "#pragma once\n\n#include \"vfsarchive.h\"\n#include <string>\n\nnamespace Impacto {\nnamespace Io {\n\nstruct TextMetaEntry;\n\nclass TextArchive : public VfsArchive {\n public:\n  ~TextArchive();\n  IoError Open(FileMeta* file, Stream** outStream) override;\n  IoError GetCurrentSize(FileMeta* file, int64_t& outSize) override;\n\n  static IoError Create(Stream* stream, VfsArchive** outArchive);\n\n private:\n  TextMetaEntry* TOC = 0;\n  std::string BasePath;\n};\n\n}  // namespace Io\n}  // namespace Impacto"
  },
  {
    "path": "src/io/uncompressedstream.cpp",
    "content": "#include \"uncompressedstream.h\"\n\n#include <algorithm>\n\nnamespace Impacto {\nnamespace Io {\n\nUncompressedStream::~UncompressedStream() { delete BaseStream; }\n\nIoError UncompressedStream::Create(Stream* baseStream, int64_t baseStreamOffset,\n                                   int64_t size, Stream** out) {\n  if (baseStreamOffset + size > baseStream->Meta.Size) return IoError_Fail;\n  Stream* dup;\n  int64_t err = baseStream->Duplicate(&dup);\n  if (err != IoError_OK) return (IoError)err;\n  err = dup->Seek(baseStreamOffset, RW_SEEK_SET);\n  if (err < 0) {\n    delete dup;\n    return (IoError)err;\n  }\n  UncompressedStream* result = new UncompressedStream;\n  result->BaseStream = dup;\n  result->BaseStreamOffset = baseStreamOffset;\n  result->Meta.Size = size;\n  *out = (Stream*)result;\n  return IoError_OK;\n}\n\nint64_t UncompressedStream::Read(void* buffer, int64_t sz) {\n  if (sz < 0) return IoError_Fail;\n  if (Position == Meta.Size) return IoError_Eof;\n  sz = std::min(Meta.Size - Position, sz);\n  int64_t read = BaseStream->Read(buffer, sz);\n  if (read < 0) return read;\n  Position += read;\n  return read;\n}\n\nint64_t UncompressedStream::Seek(int64_t offset, int origin) {\n  int64_t newPosInBase = IoError_Fail;\n  if (origin == RW_SEEK_SET && (offset >= 0 && offset <= Meta.Size)) {\n    newPosInBase = BaseStream->Seek(BaseStreamOffset + offset, RW_SEEK_SET);\n  } else if (origin == RW_SEEK_CUR &&\n             (Position + offset >= 0 && Position + offset <= Meta.Size)) {\n    newPosInBase = BaseStream->Seek(offset, RW_SEEK_CUR);\n  } else if (origin == RW_SEEK_END && (offset >= 0 && offset <= Meta.Size)) {\n    int64_t end = BaseStreamOffset + Meta.Size;\n    int64_t ourEndFromBaseEnd = BaseStream->Meta.Size - end;\n    newPosInBase = BaseStream->Seek(ourEndFromBaseEnd + offset, RW_SEEK_END);\n  }\n  if (newPosInBase < 0) return newPosInBase;\n  Position = newPosInBase - BaseStreamOffset;\n  return Position;\n}\n\nIoError UncompressedStream::Duplicate(Stream** outStream) {\n  Stream* baseDup;\n  IoError err = BaseStream->Duplicate(&baseDup);\n  if (err != IoError_OK) return err;\n  UncompressedStream* result = new UncompressedStream(*this);\n  result->BaseStream = baseDup;\n  *outStream = (Stream*)result;\n  return IoError_OK;\n}\n\n}  // namespace Io\n}  // namespace Impacto"
  },
  {
    "path": "src/io/uncompressedstream.h",
    "content": "#pragma once\n\n#include \"stream.h\"\n\nnamespace Impacto {\nnamespace Io {\n\nclass UncompressedStream : public Stream {\n public:\n  ~UncompressedStream();\n\n  static IoError Create(Stream* baseStream, int64_t baseStreamOffset,\n                        int64_t size, Stream** out);\n  int64_t Read(void* buffer, int64_t sz) override;\n  int64_t Seek(int64_t offset, int origin) override;\n  IoError Duplicate(Stream** outStream) override;\n\n protected:\n  UncompressedStream() {}\n  UncompressedStream(UncompressedStream const& other) = default;\n\n  Stream* BaseStream;\n  int64_t BaseStreamOffset;\n};\n\n}  // namespace Io\n}  // namespace Impacto"
  },
  {
    "path": "src/io/vfs.cpp",
    "content": "#include \"vfs.h\"\n#include \"../impacto.h\"\n#include \"../util.h\"\n#include <vector>\n#include <mutex>\n#include \"vfsarchive.h\"\n#include \"memorystream.h\"\n#include \"../log.h\"\n#include \"../profile/vfs.h\"\n#ifndef IMPACTO_DISABLE_MMAP\n#include \"memorymappedfilestream.h\"\n#else\n#include \"physicalfilestream.h\"\n#endif\n\n#include \"afsarchive.h\"\n#include \"cpkarchive.h\"\n#include \"lnk4archive.h\"\n#include \"mpkarchive.h\"\n#include \"textarchive.h\"\n\nnamespace Impacto {\nnamespace Io {\n\ntemplate <typename T>\nconcept FileId = std::convertible_to<T, uint32_t> ||\n                 std::convertible_to<T, std::string const&>;\n\nusing VfsArchiveFactory = auto (*)(Stream* stream, VfsArchive** outArchive)\n    -> IoError;\n\nstatic std::vector<VfsArchiveFactory> Archivers;\nstatic ankerl::unordered_dense::map<std::string,\n                                    std::vector<std::unique_ptr<VfsArchive>>,\n                                    string_hash, std::equal_to<>>\n    Mounts;\nstatic std::shared_mutex Lock;\n\nstatic IoError MountInternal(std::string const& mountpoint, Stream* stream) {\n  VfsArchive* archive = nullptr;\n  IoError err = IoError_Fail;\n  for (auto archiver : Archivers) {\n    err = archiver(stream, &archive);\n    if (err == IoError_OK) break;\n  }\n  if (err == IoError_OK) {\n    archive->MountPoint = mountpoint;\n    Mounts[mountpoint].emplace_back(archive);\n  } else {\n    ImpLog(LogLevel::Error, LogChannel::IO, \"No archiver supports file {:s}\\n\",\n           stream->Meta.FileName);\n  }\n  return err;\n}\n\nstatic VfsArchive* FindArchive(std::string const& mountpoint,\n                               std::string const& fileName) {\n  auto it = Mounts.find(mountpoint);\n  if (it == Mounts.end()) return 0;\n  for (auto& archive : it->second) {\n    if (archive->BaseStream->Meta.FileName == fileName) return archive.get();\n  }\n  return 0;\n}\n\nvoid VfsInit() {\n  Archivers.push_back(AfsArchive::Create);\n  Archivers.push_back(CpkArchive::Create);\n  Archivers.push_back(Lnk4Archive::Create);\n  Archivers.push_back(MpkArchive::Create);\n  Archivers.push_back(TextArchive::Create);\n\n  Profile::Vfs::Configure();\n}\n\nIoError VfsMount(std::string const& mountpoint,\n                 std::string const& archiveFileName) {\n  ImpLog(LogLevel::Debug, LogChannel::IO,\n         \"Trying to mount \\\"{:s}\\\" on mountpoint \\\"{:s}\\\"\\n\", archiveFileName,\n         mountpoint);\n\n  std::unique_lock mountLock{Lock};\n  if (FindArchive(mountpoint, archiveFileName) != 0) {\n    ImpLog(LogLevel::Error, LogChannel::IO,\n           \"File with this name already mounted!\\n\");\n    return IoError_Fail;\n  }\n\n  Stream* archiveFile;\n  IoError err;\n#ifndef IMPACTO_DISABLE_MMAP\n  err = MemoryMappedFileStream<AccessMode::read>::Create(archiveFileName,\n                                                         &archiveFile);\n#else\n  err = PhysicalFileStream::Create(archiveFileName, &archiveFile);\n#endif\n  if (err != IoError_OK) {\n    ImpLog(LogLevel::Debug, LogChannel::IO,\n           \"Could not open physical file \\\"{:s}\\\"\\n\", archiveFileName);\n    return err;\n  }\n  err = MountInternal(mountpoint, archiveFile);\n  if (err != IoError_OK) {\n    delete archiveFile;\n  }\n  return err;\n}\n\nIoError VfsMountMemory(std::string const& mountpoint,\n                       std::string const& archiveFileName, void* memory,\n                       int64_t size, bool freeOnClose) {\n  IoError err;\n  Stream* archiveFile;\n\n  ImpLog(LogLevel::Debug, LogChannel::IO,\n         \"Trying to mount in-memory archive named \\\"{:s}\\\" on mountpoint \"\n         \"\\\"{:s}\\\"\\n\",\n         archiveFileName, mountpoint);\n\n  std::unique_lock mountLock{Lock};\n  if (FindArchive(mountpoint, archiveFileName) != 0) {\n    err = IoError_Fail;\n    if (freeOnClose) free(memory);\n    return err;\n  }\n\n  archiveFile = new MemoryStream(memory, size, freeOnClose);\n  archiveFile->Meta.FileName = archiveFileName;\n  err = MountInternal(mountpoint, archiveFile);\n  if (err != IoError_OK) {\n    delete archiveFile;\n  }\n  return err;\n}\n\nIoError VfsUnmount(std::string const& mountpoint,\n                   std::string const& archiveFileName) {\n  ImpLog(LogLevel::Debug, LogChannel::IO,\n         \"Trying to unmount archive named \\\"{:s}\\\" on mountpoint \\\"{:s}\\\"\\n\",\n         archiveFileName, mountpoint);\n  std::unique_lock unmountLock{Lock};\n  auto it = Mounts.find(mountpoint);\n  if (it == Mounts.end()) return IoError_NotFound;\n  for (auto arcIt = it->second.begin(); arcIt != it->second.end(); arcIt++) {\n    if ((*arcIt)->BaseStream->Meta.FileName == archiveFileName) {\n      it->second.erase(arcIt);\n      return IoError_OK;\n    }\n  }\n  ImpLog(LogLevel::Debug, LogChannel::IO,\n         \"Unmounting failed (file not mounted?)\\n\");\n  return IoError_NotFound;\n}\n\nstatic IoError GetOrigMetaInternal(std::string const& mountpoint,\n                                   std::string const& fileName,\n                                   FileMeta*& outMeta,\n                                   VfsArchive*& outArchive) {\n  auto it = Mounts.find(mountpoint);\n  if (it == Mounts.end()) return IoError_NotFound;\n\n  for (auto& archive : it->second) {\n    auto nameToId = archive->NamesToIds.find(fileName);\n    if (nameToId != archive->NamesToIds.end()) {\n      outMeta = archive->IdsToFiles[nameToId->second];\n      outArchive = archive.get();\n      return IoError_OK;\n    }\n  }\n  return IoError_NotFound;\n}\n\nstatic IoError GetOrigMetaInternal(std::string const& mountpoint, uint32_t id,\n                                   FileMeta*& outMeta,\n                                   VfsArchive*& outArchive) {\n  auto it = Mounts.find(mountpoint);\n  if (it == Mounts.end()) return IoError_NotFound;\n\n  for (auto& archive : it->second) {\n    auto idToFile = archive->IdsToFiles.find(id);\n    if (idToFile != archive->IdsToFiles.end()) {\n      outMeta = idToFile->second;\n      outArchive = archive.get();\n      return IoError_OK;\n    }\n  }\n  return IoError_NotFound;\n}\n\ntemplate <FileId T>\nIoError VfsGetMetaImpl(std::string const& mountpoint, T file,\n                       FileMeta* outMeta) {\n  IoError err;\n\n  ImpLogSlow(LogLevel::Debug, LogChannel::IO,\n             \"Trying to get metadata for file \\\"{}\\\" on mountpoint \\\"{:s}\\\"\\n\",\n             file, mountpoint);\n\n  FileMeta* origMeta;\n  VfsArchive* archive;\n  std::shared_lock lock{Lock};\n  err = GetOrigMetaInternal(mountpoint, file, origMeta, archive);\n  if (err != IoError_OK) {\n    ImpLog(LogLevel::Error, LogChannel::IO, \"Could not get metadata\\n\");\n    return err;\n  }\n\n  *outMeta = *origMeta;\n  outMeta->ArchiveMountPoint = mountpoint;\n  outMeta->ArchiveFileName = archive->BaseStream->Meta.FileName;\n  if (outMeta->Size < 0) {\n    err = archive->GetCurrentSize(origMeta, outMeta->Size);\n    if (err != IoError_OK) {\n      ImpLog(LogLevel::Error, LogChannel::IO, \"Getting current size failed!\\n\");\n      return err;\n    }\n  }\n  return err;\n}\n\nstatic IoError OpenInternal(std::string const& mountpoint, VfsArchive* archive,\n                            FileMeta* origMeta, Stream** outStream) {\n  IoError err;\n\n  ImpLogSlow(LogLevel::Debug, LogChannel::IO,\n             \"Opening \\\"{:s}\\\" ({:d}) from mountpoint \\\"{:s}\\\" (archive file \"\n             \"\\\"{:s}\\\")\\n\",\n             origMeta->FileName, origMeta->Id, mountpoint,\n             archive->BaseStream->Meta.FileName);\n\n  err = archive->Open(origMeta, outStream);\n  if (err != IoError_OK) {\n    ImpLogSlow(LogLevel::Debug, LogChannel::IO,\n               \"VfsArchive->Open() failed, trying slurp\\n\");\n    // maybe streaming is not supported\n    void* memory;\n    int64_t size;\n    err = archive->Slurp(origMeta, memory, size);\n    // TODO lazy slurp stream\n    *outStream = new MemoryStream(memory, size, true);\n  }\n  if (err == IoError_OK) {\n    (*outStream)->Meta.ArchiveFileName = archive->BaseStream->Meta.FileName;\n    (*outStream)->Meta.ArchiveMountPoint = mountpoint;\n    (*outStream)->Meta.FileName = origMeta->FileName;\n    (*outStream)->Meta.Id = origMeta->Id;\n  } else {\n    ImpLog(LogLevel::Error, LogChannel::IO,\n           \"Opening \\\"{:s}\\\" ({:d}) from mountpoint \\\"{:s}\\\" (archive file \"\n           \"\\\"{:s}\\\") \"\n           \"failed\\n\",\n           origMeta->FileName, origMeta->Id, mountpoint,\n           archive->BaseStream->Meta.FileName);\n  }\n  return err;\n}\n\ntemplate <FileId T>\nIoError VfsOpenImpl(std::string const& mountpoint, T file, Stream** outStream) {\n  IoError err;\n\n  ImpLogSlow(LogLevel::Debug, LogChannel::IO,\n             \"Trying to open file \\\"{}\\\" on mountpoint \\\"{:s}\\\"\\n\", file,\n             mountpoint);\n  FileMeta* origMeta;\n  VfsArchive* archive;\n  std::unique_lock lock{Lock};\n  err = GetOrigMetaInternal(mountpoint, file, origMeta, archive);\n  if (err != IoError_OK) {\n    ImpLog(LogLevel::Error, LogChannel::IO, \"Could not get metadata\\n\");\n    return err;\n  }\n\n  return OpenInternal(mountpoint, archive, origMeta, outStream);\n}\n\nIoError SlurpInternal(VfsArchive* archive, FileMeta* origMeta, void*& outMemory,\n                      int64_t& outSize) {\n  IoError err;\n\n  ImpLogSlow(LogLevel::Debug, LogChannel::IO,\n             \"Slurping \\\"{:s}\\\" ({:d}) from mountpoint \\\"{:s}\\\" (archive file \"\n             \"\\\"{:s}\\\")\\n\",\n             origMeta->FileName, origMeta->Id, archive->MountPoint,\n             archive->BaseStream->Meta.FileName);\n\n  err = archive->Slurp(origMeta, outMemory, outSize);\n  if (err != IoError_OK) {\n    ImpLogSlow(LogLevel::Debug, LogChannel::IO,\n               \"VfsArchive->Slurp() failed, trying open\\n\");\n\n    Stream* stream;\n    err = archive->Open(origMeta, &stream);\n    if (err != IoError_OK) return err;\n    outMemory = malloc(stream->Meta.Size);\n    outSize = stream->Meta.Size;\n    int64_t readErr = stream->Read(outMemory, outSize);\n    // TODO should size output by Read() go into outSize, even though it may\n    // be less than the allocated size?\n    delete stream;\n    if (readErr < 0) {\n      free(outMemory);\n      ImpLog(\n          LogLevel::Error, LogChannel::IO,\n          \"Opening/reading \\\"{:s}\\\" ({:d}) from mountpoint \\\"{:s}\\\" (archive \"\n          \"file \"\n          \"\\\"{:s}\\\") failed\\n\",\n          origMeta->FileName, origMeta->Id, archive->MountPoint,\n          archive->BaseStream->Meta.FileName);\n      err = IoError_Fail;\n    } else {\n      err = IoError_OK;\n    }\n  }\n  return err;\n}\n\ntemplate <FileId T>\nIoError VfsSlurpImpl(std::string const& mountpoint, T fileName,\n                     void*& outMemory, int64_t& outSize) {\n  IoError err;\n  std::unique_lock lock{Lock};\n  FileMeta* origMeta;\n  VfsArchive* archive;\n  err = GetOrigMetaInternal(mountpoint, fileName, origMeta, archive);\n  if (err != IoError_OK) return err;\n\n  return SlurpInternal(archive, origMeta, outMemory, outSize);\n}\n\nIoError VfsListFiles(std::string const& mountpoint,\n                     std::map<uint32_t, std::string>& outListing) {\n  IoError err;\n  std::shared_lock lock{Lock};\n\n  auto it = Mounts.find(mountpoint);\n  if (it == Mounts.end()) {\n    err = IoError_NotFound;\n    return err;\n  }\n\n  outListing.clear();\n\n  // Reverse order so things first in the search path get overwritten last\n  for (auto arcIt = it->second.rbegin(); arcIt != it->second.rend(); arcIt++) {\n    for (auto nameToId : (*arcIt)->NamesToIds) {\n      outListing[nameToId.second] = nameToId.first;\n    }\n  }\n\n  return IoError_OK;\n}\n\nIoError VfsGetMeta(std::string const& mountpoint, std::string const& fileName,\n                   FileMeta* outMeta) {\n  return VfsGetMetaImpl(mountpoint, fileName, outMeta);\n}\nIoError VfsGetMeta(std::string const& mountpoint, uint32_t id,\n                   FileMeta* outMeta) {\n  return VfsGetMetaImpl(mountpoint, id, outMeta);\n}\nIoError VfsOpen(std::string const& mountpoint, std::string const& fileName,\n                Stream** outStream) {\n  return VfsOpenImpl(mountpoint, fileName, outStream);\n}\nIoError VfsOpen(std::string const& mountpoint, uint32_t id,\n                Stream** outStream) {\n  return VfsOpenImpl(mountpoint, id, outStream);\n}\nIoError VfsSlurp(std::string const& mountpoint, std::string const& fileName,\n                 void*& outMemory, int64_t& outSize) {\n  return VfsSlurpImpl(mountpoint, fileName, outMemory, outSize);\n}\nIoError VfsSlurp(std::string const& mountpoint, uint32_t id, void*& outMemory,\n                 int64_t& outSize) {\n  return VfsSlurpImpl(mountpoint, id, outMemory, outSize);\n}\n\n}  // namespace Io\n}  // namespace Impacto"
  },
  {
    "path": "src/io/vfs.h",
    "content": "#pragma once\n\n#include <vector>\n\n#include \"../impacto.h\"\n#include \"io.h\"\n\n// only these for new vfs\n#include \"stream.h\"\n#include <ankerl/unordered_dense.h>\n#include <map>\n\nnamespace Impacto {\nnamespace Io {\n\n// For transitional purposes, I'm keeping *both* VFS implementations here for\n// now.\n\n// TODO:\n// - c0data style redirection (multiple source mountpoints -> one target)\n// - Make the rest of the engine use the new VFS\n// - Configurable physical file search paths\n// - Search path reordering: For models, first mounted is good, we can configure\n// static overrides, the model CPKs get mounted later. For other things, we\n// might want to mount a patch archive when a user changes a setting in-game.\n\n// The public interface of vfs.h is threadsafe. Individual Streams are not.\n// Duplicate() them if you need to use them on multiple threads.\n\nvoid VfsInit();\n// Mount an archive from a physical file.\n// Files will always be loaded from the earliest-mounted archive they're found\n// in\nIoError VfsMount(std::string const& mountpoint,\n                 std::string const& archiveFileName);\n// Mount an archive from memory. A unique filename must be specified to identify\n// files coming from this archive and to unmount it.\nIoError VfsMountMemory(std::string const& mountpoint,\n                       std::string const& archiveFileName, void* memory,\n                       int64_t size, bool freeOnClose);\n// archiveFileName must match the filename an archive was mounted with\nIoError VfsUnmount(std::string const& mountpoint,\n                   std::string const& archiveFileName);\nIoError VfsGetMeta(std::string const& mountpoint, std::string const& fileName,\n                   FileMeta* outMeta);\nIoError VfsGetMeta(std::string const& mountpoint, uint32_t id,\n                   FileMeta* outMeta);\nIoError VfsOpen(std::string const& mountpoint, std::string const& fileName,\n                Stream** outStream);\nIoError VfsOpen(std::string const& mountpoint, uint32_t id, Stream** outStream);\nIoError VfsSlurp(std::string const& mountpoint, std::string const& fileName,\n                 void*& outMemory, int64_t& outSize);\nIoError VfsSlurp(std::string const& mountpoint, uint32_t id, void*& outMemory,\n                 int64_t& outSize);\n// You can provide a filled outListing, we'll clear it\nIoError VfsListFiles(std::string const& mountpoint,\n                     std::map<uint32_t, std::string>& outListing);\n\n}  // namespace Io\n}  // namespace Impacto"
  },
  {
    "path": "src/io/vfsarchive.cpp",
    "content": "#include \"vfsarchive.h\"\n\nnamespace Impacto {\nnamespace Io {\n\nVfsArchive::~VfsArchive() {\n  if (IsInit && BaseStream) delete BaseStream;\n}\n\nIoError VfsArchive::Slurp(FileMeta* file, void*& outBuffer, int64_t& outSize) {\n  return IoError_Fail;\n}\n\nIoError VfsArchive::GetCurrentSize(FileMeta* file, int64_t& outSize) {\n  outSize = file->Size;\n  return IoError_OK;\n}\n\n}  // namespace Io\n}  // namespace Impacto"
  },
  {
    "path": "src/io/vfsarchive.h",
    "content": "#pragma once\n\n#include \"stream.h\"\n#include \"../util.h\"\n#include <ankerl/unordered_dense.h>\n\nnamespace Impacto {\nnamespace Io {\n\nclass VfsArchive {\n public:\n  virtual ~VfsArchive();\n\n  // Meta.ArchiveFileName, Meta.ArchiveMountPoint, Meta.FileName are set by VFS,\n  // not by the archiver.\n  // These methods are only ever called with FileMeta* found in IdsToFiles.\n  virtual IoError Open(FileMeta* file, Stream** outStream) = 0;\n\n  virtual IoError Slurp(FileMeta* file, void*& outBuffer, int64_t& outSize);\n  // If the size of a file is uncertain when the archive is first opened (e.g.\n  // directory-listing archives), the file's size in IdsToFiles must be negative\n  // and this must be overridden\n  virtual IoError GetCurrentSize(FileMeta* file, int64_t& outSize);\n\n  ankerl::unordered_dense::map<std::string, uint32_t, string_hash,\n                               std::equal_to<>>\n      NamesToIds;\n  ankerl::unordered_dense::map<uint32_t, FileMeta*> IdsToFiles;\n\n  std::string MountPoint;\n\n  bool IsInit = false;\n  Stream* BaseStream = 0;\n};\n\n}  // namespace Io\n}  // namespace Impacto"
  },
  {
    "path": "src/io/zlibstream.cpp",
    "content": "#include \"zlibstream.h\"\n\n#include \"../log.h\"\n\nnamespace Impacto {\nnamespace Io {\n\nZlibStream::~ZlibStream() {\n  free(InputBuffer);\n  inflateEnd(&ZlibState);\n}\n\nIoError ZlibStream::Create(Stream* baseStream, int64_t compressedOffset,\n                           int64_t compressedSize, int64_t uncompressedSize,\n                           Stream** out) {\n  if (compressedOffset + compressedSize > baseStream->Meta.Size)\n    return IoError_Fail;\n  Stream* dup;\n  int64_t err = baseStream->Duplicate(&dup);\n  if (err != IoError_OK) return (IoError)err;\n  err = dup->Seek(compressedOffset, RW_SEEK_SET);\n  if (err != compressedOffset) {\n    delete dup;\n    return IoError_Fail;\n  }\n  ZlibStream* result = new ZlibStream;\n  result->BaseStream = dup;\n  result->CompressedOffset = compressedOffset;\n  result->CompressedSize = compressedSize;\n  result->Meta.Size = uncompressedSize;\n  memset(&result->ZlibState, 0, sizeof(z_stream));\n  result->InputBuffer = (uint8_t*)malloc(ZlibStreamInputBufferSize);\n  if (!result->Init()) {\n    delete result;\n    return IoError_Fail;\n  }\n  *out = (Stream*)result;\n  return IoError_OK;\n}\n\nbool ZlibStream::Init() {\n  int64_t read = BaseStream->Read(InputBuffer, ZlibStreamInputBufferSize);\n  if (read < 0) return false;\n  ZlibState.avail_in = (uint32_t)read;\n  ZlibState.next_in = InputBuffer;\n  int zErr;\n  zErr = inflateInit(&ZlibState);\n  return zErr == Z_OK;\n}\n\nint64_t ZlibStream::Read(void* buffer, int64_t sz) {\n  return ReadBuffered(buffer, sz);\n}\n\nint64_t ZlibStream::Seek(int64_t offset, int origin) {\n  const int64_t absPos = [&]() {\n    switch (origin) {\n      case RW_SEEK_SET:\n        return offset;\n\n      case RW_SEEK_CUR:\n        return Position + offset;\n\n      case RW_SEEK_END:\n        return Meta.Size - offset;\n\n      default:\n        throw std::invalid_argument(fmt::format(\"Unknown origin {}\", origin));\n    }\n  }();\n  if (absPos < 0 || absPos > Meta.Size) return IoError_Fail;\n\n  int64_t err = SeekBuffered(absPos);\n  if (err < IoError_OK) {\n    if (absPos < Position) {\n      Position = 0;\n      inflateReset(&ZlibState);\n      Init();\n      BaseStream->Seek(CompressedOffset, RW_SEEK_SET);\n    }\n    err = DiscardSeekBuffered(absPos);\n  }\n  if (err < IoError_OK) {\n    return err;\n  }\n  return Position;\n}\n\nIoError ZlibStream::Duplicate(Stream** outStream) {\n  Stream* dup;\n  int64_t err = BaseStream->Duplicate(&dup);\n  if (err != IoError_OK) return (IoError)err;\n  err = dup->Seek(CompressedOffset, RW_SEEK_SET);\n  if (err != CompressedOffset) {\n    delete dup;\n    return IoError_Fail;\n  }\n  ZlibStream* result = new ZlibStream(*this);\n  result->InputBuffer = (uint8_t*)malloc(ZlibStreamInputBufferSize);\n  result->BaseStream = dup;\n  memset(&result->ZlibState, 0, sizeof(z_stream));\n  result->Position = 0;\n  result->BufferFill = 0;\n  result->BufferConsumed = 0;\n  if (!result->Init()) {\n    delete result;\n    return IoError_Fail;\n  }\n  err = result->Seek(Position, RW_SEEK_SET);\n  if (err != Position) {\n    delete result;\n    return IoError_Fail;\n  }\n  *outStream = (Stream*)result;\n  return IoError_OK;\n}\n\nIoError ZlibStream::FillBuffer() {\n  int zErr;\n\n  ZlibState.avail_out = (uint32_t)BufferSize;\n  ZlibState.next_out = Buffer;\n  int64_t lastTotal = ZlibState.total_out;\n\n  do {\n    if (ZlibState.avail_in == 0) {\n      int64_t read = BaseStream->Read(InputBuffer, ZlibStreamInputBufferSize);\n      if (read < 0) return (IoError)read;\n      if (read == 0) return IoError_Eof;\n      ZlibState.next_in = InputBuffer;\n      ZlibState.avail_in = (uint32_t)read;\n    }\n\n    zErr = inflate(&ZlibState, Z_SYNC_FLUSH);\n  } while (zErr == Z_OK && ZlibState.avail_out > 0);\n\n  if (zErr != Z_OK && zErr != Z_STREAM_END) {\n    return IoError_Fail;\n  }\n\n  BufferFill = ZlibState.total_out - lastTotal;\n  return IoError_OK;\n}\n\n}  // namespace Io\n}  // namespace Impacto"
  },
  {
    "path": "src/io/zlibstream.h",
    "content": "#pragma once\n\n#include \"stream.h\"\n#include \"buffering.h\"\n#include <zlib.h>\n\nnamespace Impacto {\nnamespace Io {\n\nclass ZlibStream : public Stream, public Buffering<ZlibStream> {\n  friend class Buffering<ZlibStream>;\n\n public:\n  ~ZlibStream();\n\n  bool IsSeekSlow = true;\n\n  static IoError Create(Stream* baseStream, int64_t compressedOffset,\n                        int64_t compressedSize, int64_t uncompressedSize,\n                        Stream** out);\n  int64_t Read(void* buffer, int64_t sz) override;\n  int64_t Seek(int64_t offset, int origin) override;\n  IoError Duplicate(Stream** outStream) override;\n\n protected:\n  static int64_t const ZlibStreamBufferSize = 128 * 1024;\n  static int64_t const ZlibStreamInputBufferSize = 64 * 1024;\n\n  ZlibStream() : Buffering(ZlibStreamBufferSize) {}\n  ZlibStream(ZlibStream const& other) = default;\n\n  IoError FillBuffer();\n\n  bool Init();\n\n  Stream* BaseStream;\n  int64_t CompressedOffset;\n  int64_t CompressedSize;\n\n  uint8_t* InputBuffer;\n  z_stream ZlibState;\n};\n\n}  // namespace Io\n}  // namespace Impacto"
  },
  {
    "path": "src/loadable.h",
    "content": "#pragma once\n\n#include \"workqueue.h\"\n\nnamespace Impacto {\n\n// If you're looking for IRenderable3D loading, it's inside that class itself...\n// I'm so sorry\n\nenum class LoadStatus { Unloaded, Loading, Loaded };\n\ntemplate <typename T, typename LoadResult, typename... LoadArgs>\nclass Loadable {\n public:\n  LoadStatus Status = LoadStatus::Unloaded;\n\n  bool LoadAsync(LoadArgs... args) {\n    if (Status == LoadStatus::Loading) {\n      // cannot currently cancel a load\n      return false;\n    }\n    Unload();\n    NextLoadArgs = std::make_tuple(args...);\n    Status = LoadStatus::Loading;\n    WorkQueue::Push(static_cast<T*>(this), &LoadWorker, &OnLoaded);\n    return true;\n  }\n\n  void Unload() {\n    if (Status == LoadStatus::Loaded) {\n      static_cast<T*>(this)->UnloadSync();\n      Status = LoadStatus::Unloaded;\n    }\n  }\n\n protected:\n  LoadResult LoadSync(LoadArgs... args);\n  void UnloadSync();\n  void MainThreadOnLoad(LoadResult result);\n\n private:\n  std::tuple<LoadArgs...> NextLoadArgs;\n  LoadResult LastLoadResult;\n\n  static void LoadWorker(void* ptr) {\n    T* loadable = (T*)ptr;\n    loadable->LastLoadResult = std::apply(\n        &T::LoadSync,\n        std::tuple_cat(std::make_tuple(loadable), loadable->NextLoadArgs));\n  }\n\n  static void OnLoaded(void* ptr) {\n    T* loadable = (T*)ptr;\n    loadable->MainThreadOnLoad(loadable->LastLoadResult);\n    loadable->Status = LoadStatus::Loaded;\n  }\n};\n\n}  // namespace Impacto"
  },
  {
    "path": "src/log.cpp",
    "content": "#include <time.h>\n#include <cstdarg>\n\n#include <fmt/chrono.h>\n#include <SDL_log.h>\n#include \"log.h\"\n#include \"util.h\"\n#include \"io/physicalfilestream.h\"\nnamespace Impacto {\n\n// TODO Color output in console?\n\nstatic SDL_LogOutputFunction DefaultLoggingFunction = nullptr;\nstatic std::unique_ptr<Io::Stream> FileLogStream = nullptr;\n\nbool CheckLogConfig(LogLevel level, LogChannel channel) {\n  bool any = false;\n  if ((LoggingToConsole || LoggingToFile) && (level <= g_LogLevel) &&\n      (g_LogChannels & channel) != LogChannel::None) {\n    any = true;\n  }\n  return any;\n}\n\nvoid ConsoleWrite(LogLevel level, std::string_view str) {\n  assert(level > LogLevel::Off && level < LogLevel::Max);\n  switch (level) {\n    case LogLevel::Fatal:\n      SDL_LogCritical(SDL_LOG_CATEGORY_APPLICATION, \"%s\", str.data());\n      break;\n    case LogLevel::Error:\n      SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, \"%s\", str.data());\n      break;\n    case LogLevel::Warning:\n      SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, \"%s\", str.data());\n      break;\n    case LogLevel::Info:\n      SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, \"%s\", str.data());\n      break;\n    case LogLevel::Debug:\n      SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, \"%s\", str.data());\n      break;\n    case LogLevel::Trace:\n      SDL_LogVerbose(SDL_LOG_CATEGORY_APPLICATION, \"%s\", str.data());\n      break;\n    default:\n      assert(false);\n  }\n}\n\nvoid ImpLogImpl(LogLevel level, LogChannel channel, fmt::string_view format,\n                fmt::format_args args, size_t tailSize) {\n  constexpr size_t maxChannelSize = 16;\n  constexpr size_t maxTimestampSize = 21;\n  assert(level > LogLevel::Off && channel > LogChannel::None);\n  const size_t lineBufferSize =\n      maxChannelSize + maxTimestampSize + tailSize + 1;\n  auto* line = static_cast<char*>(ImpStackAlloc(lineBufferSize));\n\n  std::string_view channelStr = ChannelToString(channel);\n  time_t timestamp = time(nullptr);\n  auto tsFormat = fmt::format_to_n(\n      line, maxTimestampSize, \"[{:%Y-%m-%d %H:%M:%S}]\", fmt::gmtime(timestamp));\n  auto channelFormat =\n      fmt::format_to_n(tsFormat.out, maxChannelSize, \"[{}] \", channelStr);\n  auto tailFormat =\n      fmt::vformat_to_n(channelFormat.out, tailSize + 1, format, args);\n  *tailFormat.out = '\\0';\n\n  ConsoleWrite(level, line);\n}\n\nvoid LogToFile(void* userdata, [[maybe_unused]] int category,\n               SDL_LogPriority priority, const char* message) {\n  if (!FileLogStream) return;\n  static constexpr const char* SDL_priority_prefixes[] = {\n      NULL, \"VERBOSE\", \"DEBUG\", \"INFO\", \"WARN\", \"ERROR\", \"CRITICAL\"};\n\n  std::string logBuf =\n      fmt::format(\"{:s}: {:s}\\n\", SDL_priority_prefixes[priority], message);\n  FileLogStream->Write(logBuf.data(), logBuf.size());\n}\n\nvoid SDLLogger(void* userdata, [[maybe_unused]] int category,\n               SDL_LogPriority priority, const char* message) {\n  if (LoggingToFile) {\n    LogToFile(userdata, category, priority, message);\n  }\n  if (LoggingToConsole) {\n    DefaultLoggingFunction(userdata, category, priority, message);\n  }\n}\n\nvoid SetSDLLogger(SDL_LogOutputFunction loggingFunction) {\n  [[maybe_unused]] static const bool fetchDefaultLogger = [] {\n    if (!DefaultLoggingFunction) {\n      SDL_LogGetOutputFunction(&DefaultLoggingFunction, nullptr);\n    }\n    return true;\n  }();\n\n  if (loggingFunction) {\n    SDL_LogSetOutputFunction(loggingFunction, nullptr);\n  } else {\n    SDL_LogSetOutputFunction(DefaultLoggingFunction, nullptr);\n  }\n}\n\nvoid LogSetFile(std::string const& path) {\n  using CF = Io::PhysicalFileStream::CreateFlagsMode;\n  Io::Stream* stream;\n  IoError err = Io::PhysicalFileStream::Create(\n      path, &stream, CF::CREATE | CF::CREATE_DIRS | CF::WRITE | CF::UNBUFFERED);\n  if (err != IoError_OK) {\n    ImpLog(LogLevel::Error, LogChannel::IO,\n           \"Failed to open save file for writing\\n\");\n    return;\n  }\n  FileLogStream.reset(stream);\n  LoggingToFile = true;\n}\n\nvoid LogSetConsole(bool enabled) { LoggingToConsole = enabled; }\n\nvoid LogInit() {\n  SetSDLLogger(SDLLogger);\n  SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_VERBOSE);\n}\n\n#ifndef IMPACTO_DISABLE_OPENGL\nvoid GLAPIENTRY LogGLMessageCallback(GLenum source, GLenum type, GLuint id,\n                                     GLenum severity, GLsizei length,\n                                     const GLchar* message,\n                                     const void* userParam) {\n  LogLevel level;\n  switch (severity) {\n    case GL_DEBUG_SEVERITY_HIGH_ARB:\n      level = LogLevel::Error;\n      break;\n    case GL_DEBUG_SEVERITY_MEDIUM_ARB:\n      level = LogLevel::Warning;\n      break;\n    case GL_DEBUG_SEVERITY_LOW_ARB:\n    default:\n      level = LogLevel::Info;\n      break;\n  }\n\n  const char typeError[] = \"Error\";\n  const char typeDeprecated[] = \"DeprecatedBehaviour\";\n  const char typeUB[] = \"UndefinedBehaviour\";\n  const char typePerf[] = \"Performance\";\n  const char typePortability[] = \"Portability\";\n  const char typeOther[] = \"Other\";\n\n  const char* typeStr;\n  switch (type) {\n    case GL_DEBUG_TYPE_ERROR_ARB:\n      typeStr = typeError;\n      break;\n    case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_ARB:\n      typeStr = typeDeprecated;\n      break;\n    case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_ARB:\n      typeStr = typeUB;\n      break;\n    case GL_DEBUG_TYPE_PERFORMANCE_ARB:\n      typeStr = typePerf;\n      break;\n    case GL_DEBUG_TYPE_PORTABILITY_ARB:\n      typeStr = typePortability;\n      break;\n    case GL_DEBUG_TYPE_OTHER_ARB:\n    default:\n      typeStr = typeOther;\n      break;\n  }\n\n  const char sourceApi[] = \"API\";\n  const char sourceSc[] = \"ShaderCompiler\";\n  const char sourceWin[] = \"WindowSystem\";\n  const char source3rdParty[] = \"ThirdParty\";\n  const char sourceApplication[] = \"Application\";\n  const char sourceOther[] = \"Other\";\n\n  const char* sourceStr;\n  switch (source) {\n    case GL_DEBUG_SOURCE_API_ARB:\n      sourceStr = sourceApi;\n      break;\n    case GL_DEBUG_SOURCE_SHADER_COMPILER_ARB:\n      sourceStr = sourceSc;\n      break;\n    case GL_DEBUG_SOURCE_WINDOW_SYSTEM_ARB:\n      sourceStr = sourceWin;\n      break;\n    case GL_DEBUG_SOURCE_THIRD_PARTY_ARB:\n      sourceStr = source3rdParty;\n      break;\n    case GL_DEBUG_SOURCE_APPLICATION_ARB:\n      sourceStr = sourceApplication;\n      break;\n    case GL_DEBUG_SOURCE_OTHER_ARB:\n    default:\n      sourceStr = sourceOther;\n      break;\n  }\n\n  ImpLog(level, LogChannel::GL,\n         \"type={:s}, source={:s}, id={:d}, message: {:s}\\n\", typeStr, sourceStr,\n         id, message);\n}\n#endif\n\n}  // namespace Impacto"
  },
  {
    "path": "src/log.h",
    "content": "#pragma once\n\n#include \"impacto.h\"\n#include \"util.h\"\n\n#ifndef IMPACTO_DISABLE_OPENGL\n#include <glad/glad.h>\n#endif\n\n#include <magic_enum/magic_enum_format.hpp>\n\nnamespace Impacto {\nenum class LogLevel {\n  Off = 0,\n  Fatal = 1,\n  Error = 2,\n  Warning = 3,\n  Info = 4,\n  Debug = 5,\n  Trace = 6,\n  Max\n};\n\nenum class LogChannel : uint32_t {\n  None = 0,\n  General = (1 << 0),\n  IO = (1 << 1),\n  Render = (1 << 2),\n  ModelLoad = (1 << 3),\n  GL = (1 << 4),\n  Renderable3D = (1 << 5),\n  TextureLoad = (1 << 6),\n  Scene = (1 << 7),\n  VM = (1 << 8),\n  Expr = (1 << 9),\n  VMStub = (1 << 10),\n  Audio = (1 << 11),\n  Profile = (1 << 12),\n  Video = (1 << 13),\n  Subtitle = (1 << 14),\n  All = 0xFFFFFFFF\n};\n\nconstexpr LogChannel operator~(LogChannel channel) {\n  return static_cast<LogChannel>(~to_underlying(channel));\n}\nconstexpr LogChannel operator|(LogChannel channel, LogChannel other) {\n  return static_cast<LogChannel>(to_underlying(channel) | to_underlying(other));\n}\nconstexpr LogChannel operator&(LogChannel channel, LogChannel other) {\n  return static_cast<LogChannel>(to_underlying(channel) & to_underlying(other));\n}\nconstexpr LogChannel operator^(LogChannel channel, LogChannel other) {\n  return static_cast<LogChannel>(to_underlying(channel) ^ to_underlying(other));\n}\nconstexpr LogChannel& operator|=(LogChannel& channel, LogChannel other) {\n  channel = channel | other;\n  return channel;\n}\nconstexpr LogChannel& operator&=(LogChannel& channel, LogChannel other) {\n  channel = channel & other;\n  return channel;\n}\nconstexpr LogChannel& operator^=(LogChannel& channel, LogChannel other) {\n  channel = channel ^ other;\n  return channel;\n}\n\nconstexpr auto ChannelToString(LogChannel channel) {\n  using enum LogChannel;\n  switch (channel) {\n    case None:\n      return \"None\";\n    case General:\n      return \"General\";\n    case IO:\n      return \"IO\";\n    case Render:\n      return \"Render\";\n    case ModelLoad:\n      return \"ModelLoad\";\n    case GL:\n      return \"GL\";\n    case Renderable3D:\n      return \"Renderable3D\";\n    case TextureLoad:\n      return \"TextureLoad\";\n    case Scene:\n      return \"Scene\";\n    case VM:\n      return \"VM\";\n    case VMStub:\n      return \"VMStub\";\n    case Expr:\n      return \"Expr\";\n    case Audio:\n      return \"Audio\";\n    case Profile:\n      return \"Profile\";\n    case Video:\n      return \"Video\";\n    default:\n      return \"\";\n  }\n}\n\nconstexpr auto LevelToString(LogLevel level) {\n  using enum LogLevel;\n  switch (level) {\n    case Off:\n      return \"Off\";\n    case Fatal:\n      return \"Fatal\";\n    case Error:\n      return \"Error\";\n    case Warning:\n      return \"Warning\";\n    case Info:\n      return \"Info\";\n    case Debug:\n      return \"Debug\";\n    case Trace:\n      return \"Trace\";\n    default:\n      return \"\";\n  }\n}\n\nconstexpr auto StringToChannel(std::string_view channel) {\n  using enum LogChannel;\n  if (channel == \"None\") return None;\n  if (channel == \"General\") return General;\n  if (channel == \"IO\") return IO;\n  if (channel == \"Render\") return Render;\n  if (channel == \"ModelLoad\") return ModelLoad;\n  if (channel == \"GL\") return GL;\n  if (channel == \"Renderable3D\") return Renderable3D;\n  if (channel == \"TextureLoad\") return TextureLoad;\n  if (channel == \"Scene\") return Scene;\n  if (channel == \"VM\") return VM;\n  if (channel == \"Expr\") return Expr;\n  if (channel == \"VMStub\") return VMStub;\n  if (channel == \"Audio\") return Audio;\n  if (channel == \"Profile\") return Profile;\n  if (channel == \"Video\") return Video;\n  if (channel == \"All\")\n    return All;\n  else\n    return None;\n}\n\nconstexpr auto StringToLevel(std::string_view level) {\n  using enum LogLevel;\n  if (level == \"Off\") return Off;\n  if (level == \"Fatal\") return Fatal;\n  if (level == \"Error\") return Error;\n  if (level == \"Warning\") return Warning;\n  if (level == \"Info\") return Info;\n  if (level == \"Debug\") return Debug;\n  if (level == \"Trace\") return Trace;\n  if (level == \"Max\")\n    return Max;\n  else\n    return Off;\n}\n\ninline LogLevel g_LogLevel = LogLevel::Off;\ninline LogChannel g_LogChannels = LogChannel::None;\ninline bool LoggingToConsole = false;\ninline bool LoggingToFile = false;\n\nvoid LogSetFile(std::string const& path);\nvoid LogSetConsole(bool enabled);\nvoid LogInit();\nbool CheckLogConfig(LogLevel level, LogChannel channel);\n\nvoid ImpLogImpl(LogLevel level, LogChannel channel, fmt::string_view format,\n                fmt::format_args args, size_t tailSize);\n\ntemplate <typename... T>\nvoid ImpLog(LogLevel level, LogChannel channel, fmt::format_string<T...> format,\n            T&&... args) {\n  if (!CheckLogConfig(level, channel)) return;\n  size_t tailSize = fmt::formatted_size(format, std::forward<T>(args)...);\n  ImpLogImpl(level, channel, format.get(), fmt::make_format_args(args...),\n             tailSize);\n}\n#if IMPACTO_ENABLE_SLOW_LOG\n#define ImpLogSlow ImpLog\n#else\n#define ImpLogSlow(...) (void)0\n#endif\n\n#ifndef IMPACTO_DISABLE_OPENGL\nvoid GLAPIENTRY LogGLMessageCallback(GLenum source, GLenum type, GLuint id,\n                                     GLenum severity, GLsizei length,\n                                     const GLchar* message,\n                                     const void* userParam);\n#endif\n\n}  // namespace Impacto"
  },
  {
    "path": "src/main.cpp",
    "content": "#include \"impacto.h\"\n\n#include <ranges>\n\n#ifdef EMSCRIPTEN\n#include <emscripten.h>\n#endif\n\n#include \"log.h\"\n#include \"game.h\"\n#include \"util.h\"\n\n#include \"io/physicalfilestream.h\"\n\nusing namespace Impacto;\n\nstatic uint64_t t;\n\nvoid GameLoop() {\n  // TODO: Better FPS lock\n  uint64_t t2;\n  float dt;\n  t2 = SDL_GetPerformanceCounter();\n  dt = ((float)(t2 - t) / (float)SDL_GetPerformanceFrequency());\n  t = t2;\n  dt = std::min(dt, 1.0f);\n\n  Game::Update(dt);\n  Game::Render();\n}\n\n#ifdef EMSCRIPTEN\nextern \"C\" void EMSCRIPTEN_KEEPALIVE StartGame() {\n  t = SDL_GetPerformanceCounter();\n  emscripten_set_main_loop(GameLoop, -1, 0);\n}\n#endif\n\nstatic std::string handleArguments(std::vector<std::string_view> args) {\n  std::string profileName;\n  bool hasSetChannel = false;\n  for (size_t i = 0; i < args.size(); ++i) {\n    std::string_view arg = args[i];\n    auto handleArgInput = [&](std::ranges::range auto supportedArgs,\n                              std::invocable<std::string_view> auto action) {\n      if (std::find(std::begin(supportedArgs), std::end(supportedArgs), arg) !=\n          std::end(supportedArgs)) {\n        if (i++ < args.size()) {\n          std::string_view input = args[i];\n          action(input);\n          return true;\n        } else {\n          ImpLog(LogLevel::Fatal, LogChannel::General,\n                 \"Invalid number of arguments\");\n          exit(1);\n        }\n      }\n      return false;\n    };\n    using std::literals::string_view_literals::operator\"\"sv;\n    constexpr auto make_handler =\n        [](std::invocable<std::string_view> auto fn,\n           std::convertible_to<std::string_view> auto... strs) {\n          return std::pair{std::to_array<std::string_view>({strs...}), fn};\n        };\n\n    const auto argHandlers = std::tuple{\n        make_handler(\n            [&](std::string_view input) { LogSetFile(std::string(input)); },\n            \"-lf\", \"--logfile\"),\n        make_handler(\n            [&](std::string_view input) {\n              auto inputChannel = StringToChannel(input);\n              if (!hasSetChannel) {\n                g_LogChannels = {};\n                hasSetChannel = true;\n              }\n              if (inputChannel == LogChannel::None) {\n                g_LogChannels = inputChannel;\n              } else {\n                g_LogChannels |= inputChannel;\n              }\n            },\n            \"-lc\", \"--logchannel\"),\n        make_handler(\n            [&](std::string_view input) { g_LogLevel = StringToLevel(input); },\n            \"-ll\", \"--loglevel\"),\n    };\n\n    const bool matched = std::apply(\n        [&](auto&&... h) {\n          return ((handleArgInput(h.first, h.second)) || ...);\n        },\n        argHandlers);\n    if (!matched) profileName = arg;\n  }\n  return profileName;\n};\n\nint main(int argc, char* argv[]) {\n#ifdef EMSCRIPTEN\n  EM_ASM(OnGameLoadStart(););\n#endif\n#ifdef _WIN32\n  if (AttachConsole(ATTACH_PARENT_PROCESS)) {\n    FILE* fDummy;\n    freopen_s(&fDummy, \"CONOUT$\", \"w\", stdout);\n    freopen_s(&fDummy, \"CONOUT$\", \"w\", stderr);\n    freopen_s(&fDummy, \"CONIN$\", \"r\", stdin);\n  }\n#endif\n\n  std::string profileName;\n  LogInit();\n  g_LogChannels = LogChannel::All;\n  g_LogLevel = LogLevel::Fatal;\n#if __SWITCH__\n  LogSetFile(\"Impacto_Log.txt\");\n#else\n  LogSetConsole(true);\n#endif\n\n  std::vector<std::string_view> arguments;\n  for (int i = 1; i < argc; ++i) {\n    arguments.push_back(argv[i]);\n  }\n\n  profileName = handleArguments(arguments);\n  if (profileName.empty()) {\n    Io::Stream* stream;\n    IoError err = Io::PhysicalFileStream::Create(\"profile.txt\", &stream);\n    if (err != IoError_OK) {\n      ImpLog(LogLevel::Fatal, LogChannel::General,\n             \"Couldn't open profile.txt\\n\");\n      exit(1);\n    }\n    std::string fileContents;\n    fileContents.resize(stream->Meta.Size, '\\0');\n    stream->Read(&fileContents[0], stream->Meta.Size);\n    arguments.clear();\n    for (auto&& part : std::views::split(fileContents, ' ')) {\n      arguments.emplace_back(\n          std::string_view(&*part.begin(), std::ranges::distance(part)));\n    }\n    profileName = handleArguments(arguments);\n  }\n\n  TrimString(profileName);\n  MakeLowerCase(profileName);\n\n#ifdef EMSCRIPTEN\n  // Emscripten's EGL requests a window framebuffer with antialiasing by default\n  // (as WebGL does)\n  // Emscripten's SDL2 port fails to change this even with MSAA set to 0 in the\n  // context parameters\n  EM_ASM(EGL.antialias = false;);\n#endif\n  try {\n    Game::InitFromProfile(profileName);\n\n#ifdef EMSCRIPTEN\n    EM_ASM(OnGameLoaded(););\n#else\n    t = SDL_GetPerformanceCounter();\n\n    while (!Game::ShouldQuit) {\n      GameLoop();\n    }\n\n    ImpLog(LogLevel::Info, LogChannel::General, \"Bye!\\n\");\n\n    Game::Shutdown();\n#endif\n  } catch (std::exception const& e) {\n    ImpLog(LogLevel::Fatal, LogChannel::General,\n           \"Fatal error occured: {}, exiting!\\n\", e.what());\n    exit(1);\n  }\n  return 0;\n}\n"
  },
  {
    "path": "src/manifest_windows.manifest",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<assembly xmlns=\"urn:schemas-microsoft-com:asm.v1\" manifestVersion=\"1.0\" xmlns:asmv3=\"urn:schemas-microsoft-com:asm.v3\">\n  <trustInfo xmlns=\"urn:schemas-microsoft-com:asm.v3\">\n    <security>\n      <requestedPrivileges>\n        <requestedExecutionLevel level=\"asInvoker\" uiAccess=\"false\"></requestedExecutionLevel>\n      </requestedPrivileges>\n    </security>\n  </trustInfo>\n  <asmv3:application>\n    <asmv3:windowsSettings xmlns=\"http://schemas.microsoft.com/SMI/2005/WindowsSettings\">\n      <dpiAware>true/pm</dpiAware>\n    </asmv3:windowsSettings>\n  </asmv3:application>\n</assembly>"
  },
  {
    "path": "src/mask2d.cpp",
    "content": "#include \"mask2d.h\"\n\n#include \"io/memorystream.h\"\n#include \"io/vfs.h\"\n#include \"util.h\"\n#include \"profile/game.h\"\n#include \"renderer/renderer.h\"\n\nnamespace Impacto {\n\nvoid Mask2D::Init() {\n  std::map<uint32_t, std::string> maskFiles;\n  Io::VfsListFiles(\"mask\", maskFiles);\n  for (auto const& mask : maskFiles) {\n    Masks2D[mask.first].LoadSync(mask.first);\n  }\n}\n\nbool Mask2D::LoadSync(uint32_t maskId) {\n  Io::Stream* stream;\n  int64_t err = Io::VfsOpen(\"mask\", maskId, &stream);\n  if (err != IoError_OK) return false;\n  MaskTexture.Load(stream);\n  delete stream;\n  MaskSpriteSheet.Texture = MaskTexture.Submit();\n  if ((MaskTexture.Width == 1) && (MaskTexture.Height == 1)) {\n    MaskSpriteSheet.DesignWidth = Profile::DesignWidth;\n    MaskSpriteSheet.DesignHeight = Profile::DesignHeight;\n  } else {\n    MaskSpriteSheet.DesignWidth = (float)MaskTexture.Width;\n    MaskSpriteSheet.DesignHeight = (float)MaskTexture.Height;\n  }\n  MaskSprite.Sheet = MaskSpriteSheet;\n  MaskSprite.BaseScale = glm::vec2(1.0f);\n  MaskSprite.Bounds = RectF(0.0f, 0.0f, MaskSpriteSheet.DesignWidth,\n                            MaskSpriteSheet.DesignHeight);\n  return true;\n}\n\nvoid Mask2D::UnloadSync() {\n  Renderer->FreeTexture(MaskSpriteSheet.Texture);\n  MaskSpriteSheet.DesignHeight = 0.0f;\n  MaskSpriteSheet.DesignWidth = 0.0f;\n  MaskSpriteSheet.Texture = 0;\n}\n\n}  // namespace Impacto"
  },
  {
    "path": "src/mask2d.h",
    "content": "#pragma once\n\n#include \"texture/texture.h\"\n#include \"spritesheet.h\"\n\nnamespace Impacto {\n\nclass Mask2D {\n public:\n  static void Init();\n\n  Sprite MaskSprite;\n  bool LoadSync(uint32_t maskId);\n  void UnloadSync();\n\n private:\n  Texture MaskTexture;\n  SpriteSheet MaskSpriteSheet;\n};\n\nint constexpr MaxMasks2D = 32;\n\ninline Mask2D Masks2D[MaxMasks2D];\n\n}  // namespace Impacto"
  },
  {
    "path": "src/mem.cpp",
    "content": "#include \"mem.h\"\n\nnamespace Impacto {\n\nvoid SetFlag(uint32_t flagId, uint32_t value) {\n  if (flagId & 0x80000000) return;\n\n  uint32_t flagIndex = flagId >> 3;\n  int flagValue = 1 << (flagId - 8 * (flagId >> 3));\n  FlagWork[flagIndex] |= flagValue;\n  if (!value) {\n    FlagWork[flagIndex] ^= flagValue;\n  }\n}\n\nbool GetFlag(uint32_t flagId) {\n  return ((uint8_t)(1 << (flagId - 8 * (flagId >> 3))) &\n          FlagWork[flagId >> 3]) != 0;\n}\n\n}  // namespace Impacto"
  },
  {
    "path": "src/mem.h",
    "content": "#pragma once\n\n#include \"impacto.h\"\n#include <glm/glm.hpp>\n#include \"util.h\"\n\nnamespace Impacto {\n\nint constexpr ScrWorkSize = 8000;\nint constexpr FlagWorkSize = 1000;\n\ninline std::array<int, ScrWorkSize> ScrWork;\ninline std::array<uint8_t, FlagWorkSize> FlagWork;\n\ninline float ScrRealToFloat(int scrReal) { return (float)scrReal / 1000.0f; }\ninline int FloatToScrReal(float f) { return (int)(f * 1000.0f); }\n\ninline void ScrWorkSetFloat(int id, float f) {\n  ScrWork[id] = FloatToScrReal(f);\n}\ninline float ScrWorkGetFloat(int id) { return ScrRealToFloat(ScrWork[id]); }\ninline void ScrWorkSetVec3(int idX, int idY, int idZ, glm::vec3 vec) {\n  ScrWorkSetFloat(idX, vec.x);\n  ScrWorkSetFloat(idY, vec.y);\n  ScrWorkSetFloat(idZ, vec.z);\n}\ninline glm::vec3 ScrWorkGetVec3(int idX, int idY, int idZ) {\n  return glm::vec3(ScrWorkGetFloat(idX), ScrWorkGetFloat(idY),\n                   ScrWorkGetFloat(idZ));\n}\ninline void ScrWorkSetAngle(int id, float radians) {\n  ScrWorkSetFloat(id, RadToDeg(NormalizeRad(radians)));\n}\ninline float ScrWorkGetAngle(int id) {\n  return DegToRad(NormalizeDeg(ScrWorkGetFloat(id)));\n}\ninline void ScrWorkSetAngleVec2(int idX, int idY, glm::vec2 vecRadians) {\n  ScrWorkSetAngle(idX, vecRadians.x);\n  ScrWorkSetAngle(idY, vecRadians.y);\n}\ninline glm::vec2 ScrWorkGetAngleVec2(int idX, int idY) {\n  return glm::vec2(ScrWorkGetAngle(idX), ScrWorkGetAngle(idY));\n}\ninline void ScrWorkSetAngleVec3(int idX, int idY, int idZ,\n                                glm::vec3 vecRadians) {\n  ScrWorkSetAngle(idX, vecRadians.x);\n  ScrWorkSetAngle(idY, vecRadians.y);\n  ScrWorkSetAngle(idZ, vecRadians.z);\n}\ninline glm::vec3 ScrWorkGetAngleVec3(int idX, int idY, int idZ) {\n  return glm::vec3(ScrWorkGetAngle(idX), ScrWorkGetAngle(idY),\n                   ScrWorkGetAngle(idZ));\n}\ninline glm::vec4 ScrWorkGetColor(size_t id) {\n  return RgbIntToFloat(ScrWork[id]);\n}\n\nvoid SetFlag(uint32_t flagId, uint32_t value);\nbool GetFlag(uint32_t flagId);\n\n}  // namespace Impacto"
  },
  {
    "path": "src/modelviewer.cpp",
    "content": "#include \"modelviewer.h\"\n#include \"game.h\"\n\n// #include \"window.h\"\n#include \"io/io.h\"\n#include \"log.h\"\n#include \"renderer/renderer.h\"\n#include \"audio/audiosystem.h\"\n#include \"audio/audiochannel.h\"\n#include \"renderer/3d/scene.h\"\n#include \"renderer/3d/model.h\"\n\n#include \"profile/scene3d.h\"\n\nnamespace Impacto {\nnamespace ModelViewer {\n\nstatic void EnumerateBgm();\n\nstatic glm::vec3 CameraPosition;\nstatic glm::vec3 CameraTarget;\n[[maybe_unused]] static bool TrackCamera;\nstatic glm::vec4 UiTintColor;\nstatic uint32_t CurrentModel;\nstatic uint32_t CurrentAnim;\nstatic uint32_t CurrentBackground;\nstatic uint32_t CurrentBgm;\nstatic int UiWindowWidth;\nstatic int UiWindowHeight;\nstatic int UiMsaaCount;\n\nstatic std::vector<std::string> BgmNames;\nstatic std::vector<uint32_t> BgmIds;\nstatic bool BgmChangeQueued;\n\nstatic float BgmFadeOut;\nstatic float BgmFadeIn;\nstatic int BgmLoop;\n\nvoid Init() {\n  Model::EnumerateModels();\n  EnumerateBgm();\n\n  CameraPosition = Profile::Scene3D::DefaultCameraPosition;\n  CameraTarget = Profile::Scene3D::DefaultCameraTarget;\n\n  CurrentModel = 0;\n  CurrentAnim = 0;\n  CurrentBackground = 0;\n  CurrentBgm = 0;\n  BgmChangeQueued = false;\n\n  BgmFadeOut = 0.0f;\n  BgmFadeIn = 0.0f;\n  BgmLoop = true;\n\n  UiTintColor = glm::vec4(0.784f, 0.671f, 0.6f, 0.9f);\n\n  Renderer->Scene->Renderables[0]->LoadAsync(g_BackgroundModelIds[0]);\n  Renderer->Scene->Renderables[1]->LoadAsync(g_ModelIds[0]);\n\n  Renderer->Scene->Tint = glm::vec4(0.784f, 0.671f, 0.6f, 0.9f);\n  Renderer->Scene->LightPosition = glm::vec3(-2.85f, 16.68f, 6.30f);\n  Renderer->Scene->DarkMode = false;\n\n  UiWindowWidth = Window->WindowWidth;\n  UiWindowHeight = Window->WindowHeight;\n  UiMsaaCount = Window->MsaaCount;\n}\n\nvoid Update(float dt) {\n  if (Window->WindowDimensionsChanged) {\n    UiWindowWidth = Window->WindowWidth;\n    UiWindowHeight = Window->WindowHeight;\n    UiMsaaCount = Window->MsaaCount;\n  }\n\n  if (Renderer->Scene->Renderables[0]->Status == LoadStatus::Loaded) {\n    if (g_BackgroundModelIds[CurrentBackground] !=\n        Renderer->Scene->Renderables[0]->StaticModel->Id) {\n      Renderer->Scene->Renderables[0]->LoadAsync(\n          g_BackgroundModelIds[CurrentBackground]);\n    }\n  }\n\n#ifndef IMPACTO_DISABLE_IMGUI\n  if (ImGui::Begin(\"Scene\", nullptr,\n                   ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove |\n                       ImGuiWindowFlags_NoResize)) {\n    ImGui::SetWindowSize(ImVec2(300.0f, Window->WindowHeight - 40.0f),\n                         ImGuiCond_Once);\n    ImGui::SetWindowPos(ImVec2(20.0f, 20.0f), ImGuiCond_Once);\n    ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x);\n\n    ImGui::Text(\"%.3f ms/frame (%.1f FPS)\", 1000.0f / ImGui::GetIO().Framerate,\n                ImGui::GetIO().Framerate);\n    ImGui::Dummy(ImVec2(0.0f, 20.0f));\n\n    if (ImGui::CollapsingHeader(\"Window\")) {\n      ImGui::Spacing();\n      ImGui::Text(\"Width\");\n      ImGui::DragInt(\"##Width\", &UiWindowWidth, 0.1f, 0, 8192, \"%d\",\n                     ImGuiSliderFlags_AlwaysClamp);\n      ImGui::Spacing();\n      ImGui::Text(\"Height\");\n      ImGui::DragInt(\"##Height\", &UiWindowHeight, 0.1f, 0, 8192, \"%d\",\n                     ImGuiSliderFlags_AlwaysClamp);\n      ImGui::Spacing();\n      ImGui::Text(\"MSAA\");\n      ImGui::DragInt(\"##MSAA\", &UiMsaaCount, 0.01f, 0, 16, \"%d\",\n                     ImGuiSliderFlags_AlwaysClamp);\n      ImGui::Spacing();\n      if (ImGui::Button(\"Resize\",\n                        ImVec2(ImGui::GetContentRegionAvail().x, 0.0f))) {\n        Window->SetDimensions(UiWindowWidth, UiWindowHeight, UiMsaaCount,\n                              Window->RenderScale);\n      }\n    }\n\n    ImGui::Spacing();\n\n    if (ImGui::CollapsingHeader(\"Camera\")) {\n      ImGui::Spacing();\n      if (ImGui::Button(\"Reset\")) {\n        CameraPosition = Profile::Scene3D::DefaultCameraPosition;\n        CameraTarget = Profile::Scene3D::DefaultCameraTarget;\n      }\n      ImGui::Spacing();\n      ImGui::Text(\"Camera X\");\n      ImGui::SliderFloat(\"##cameraX\", &CameraPosition.x, -1500.0f, 1500.0f);\n      ImGui::Spacing();\n      ImGui::Text(\"Camera Y\");\n      ImGui::SliderFloat(\"##cameraY\", &CameraPosition.y, -1500.0f, 1500.0f);\n      ImGui::Spacing();\n      ImGui::Text(\"Camera Z\");\n      ImGui::SliderFloat(\"##cameraZ\", &CameraPosition.z, -1500.0f, 1500.0f);\n\n      ImGui::Spacing();\n      ImGui::Text(\"Camera target X\");\n      ImGui::SliderFloat(\"##cameraTargetX\", &CameraTarget.x, -1500.0f, 1500.0f);\n      ImGui::Spacing();\n      ImGui::Text(\"Camera target Y\");\n      ImGui::SliderFloat(\"##cameraTargetY\", &CameraTarget.y, -1500.0f, 1500.0f);\n      ImGui::Spacing();\n      ImGui::Text(\"Camera target Z\");\n      ImGui::SliderFloat(\"##cameraTargetZ\", &CameraTarget.z, -1500.0f, 1500.0f);\n    }\n\n    ImGui::Spacing();\n\n    if (ImGui::CollapsingHeader(\"Light\")) {\n      ImGui::Spacing();\n      ImGui::Checkbox(\"DarkMode\", &Renderer->Scene->DarkMode);\n      ImGui::Spacing();\n      ImGui::Text(\"LightPosition.x\");\n      ImGui::SliderFloat(\"##lightPosX\", &Renderer->Scene->LightPosition.x,\n                         -40.0f, 40.0f);\n      ImGui::Spacing();\n      ImGui::Text(\"LightPosition.y\");\n      ImGui::SliderFloat(\"##lightPosY\", &Renderer->Scene->LightPosition.y,\n                         -40.0f, 40.0f);\n      ImGui::Spacing();\n      ImGui::Text(\"LightPosition.z\");\n      ImGui::SliderFloat(\"##lightPosZ\", &Renderer->Scene->LightPosition.z,\n                         -40.0f, 40.0f);\n\n      ImGui::Spacing();\n      float lightTint[] = {UiTintColor.r, UiTintColor.g, UiTintColor.b,\n                           UiTintColor.a};\n      ImGui::ColorEdit4(\"Tint\", lightTint);\n      UiTintColor.r = lightTint[0];\n      UiTintColor.g = lightTint[1];\n      UiTintColor.b = lightTint[2];\n      UiTintColor.a = lightTint[3];\n\n      ImGui::Spacing();\n      ImGui::Text(\"Tint.R\");\n      ImGui::SliderFloat(\"##tintR\", &UiTintColor.r, 0.0f, 1.0f);\n      ImGui::Spacing();\n      ImGui::Text(\"Tint.G\");\n      ImGui::SliderFloat(\"##tintG\", &UiTintColor.g, -0.0f, 1.0f);\n      ImGui::Spacing();\n      ImGui::Text(\"Tint.B\");\n      ImGui::SliderFloat(\"##tintB\", &UiTintColor.b, -0.0f, 1.0f);\n      ImGui::Spacing();\n      ImGui::Text(\"Tint.A\");\n      ImGui::SliderFloat(\"##tintA\", &UiTintColor.a, -0.0f, 1.0f);\n      Renderer->Scene->Tint.r = UiTintColor.r;\n      Renderer->Scene->Tint.g = UiTintColor.g;\n      Renderer->Scene->Tint.b = UiTintColor.b;\n      Renderer->Scene->Tint.a = UiTintColor.a;\n    }\n\n    if (Renderer->Scene->Renderables[0]->Status == LoadStatus::Loaded) {\n      Renderer->Scene->Renderables[0]->IsVisible = true;\n\n      if (ImGui::CollapsingHeader(\"Background\",\n                                  ImGuiTreeNodeFlags_DefaultOpen)) {\n        ImGui::Spacing();\n\n        const char* comboPreviewValue =\n            g_BackgroundModelNames[CurrentBackground].data();\n        if (ImGui::BeginCombo(\"##backgroundCombo\", comboPreviewValue)) {\n          for (uint32_t i = 0; i < g_BackgroundModelCount; i++) {\n            ImGui::PushID(i);\n            const bool isSelected = (CurrentBackground == i);\n            if (ImGui::Selectable(g_BackgroundModelNames[i].data(), isSelected))\n              CurrentBackground = i;\n            if (isSelected) ImGui::SetItemDefaultFocus();\n            ImGui::PopID();\n          }\n          ImGui::EndCombo();\n        }\n\n        if (g_BackgroundModelIds[CurrentBackground] !=\n            Renderer->Scene->Renderables[0]->StaticModel->Id) {\n          Renderer->Scene->Renderables[0]->LoadAsync(\n              g_BackgroundModelIds[CurrentBackground]);\n        }\n      }\n    }\n\n    ImGui::Spacing();\n\n    if (Renderer->Scene->Renderables[1]->Status == LoadStatus::Loaded) {\n      Renderer->Scene->Renderables[1]->IsVisible = true;\n\n      if (ImGui::CollapsingHeader(\"Model\", ImGuiTreeNodeFlags_DefaultOpen)) {\n        ImGui::Spacing();\n        ImGui::Text(\"Model X\");\n        ImGui::SliderFloat(\n            \"##modelX\",\n            &Renderer->Scene->Renderables[1]->ModelTransform.Position.x, -40.0f,\n            40.0f);\n        ImGui::Spacing();\n        ImGui::Text(\"Model Y\");\n        ImGui::SliderFloat(\n            \"##modelY\",\n            &Renderer->Scene->Renderables[1]->ModelTransform.Position.y, -40.0f,\n            40.0f);\n        ImGui::Spacing();\n        ImGui::Text(\"Model Z\");\n        ImGui::SliderFloat(\n            \"##modelZ\",\n            &Renderer->Scene->Renderables[1]->ModelTransform.Position.z, -40.0f,\n            40.0f);\n\n        ImGui::Spacing();\n        ImGui::Checkbox(\"Track camera\", &TrackCamera);\n        if (TrackCamera) {\n          Renderer->Scene->Renderables[1]->ModelTransform.SetRotationFromEuler(\n              LookAtEulerZYX(\n                  Renderer->Scene->Renderables[1]->ModelTransform.Position +\n                      Profile::Scene3D::DefaultCameraTarget,\n                  CameraPosition));\n        } else {\n          Renderer->Scene->Renderables[1]->ModelTransform.Rotation =\n              glm::quat();\n        }\n\n        ImGui::Spacing();\n\n        const char* comboPreviewValue = g_ModelNames[CurrentModel].data();\n        if (ImGui::BeginCombo(\"##modelCombo\", comboPreviewValue)) {\n          for (uint32_t i = 0; i < g_ModelCount; i++) {\n            ImGui::PushID(i);\n            const bool isSelected = (CurrentModel == i);\n            if (ImGui::Selectable(g_ModelNames[i].data(), isSelected))\n              CurrentModel = i;\n            if (isSelected) ImGui::SetItemDefaultFocus();\n            ImGui::PopID();\n          }\n          ImGui::EndCombo();\n        }\n\n        if (g_ModelIds[CurrentModel] !=\n            Renderer->Scene->Renderables[1]->StaticModel->Id) {\n          CurrentAnim = 0;\n          Renderer->Scene->Renderables[1]->LoadAsync(g_ModelIds[CurrentModel]);\n        }\n      }\n\n      if (ImGui::CollapsingHeader(\"Animation\",\n                                  ImGuiTreeNodeFlags_DefaultOpen)) {\n        ImGui::Spacing();\n        ImGui::Checkbox(\"Playing\",\n                        &Renderer->Scene->Renderables[1]->Animator.IsPlaying);\n\n        ImGui::Spacing();\n        ImGui::Checkbox(\"Tweening\",\n                        &Renderer->Scene->Renderables[1]->Animator.Tweening);\n\n        if (Renderer->Scene->Renderables[1]->Animator.CurrentAnimation) {\n          ImGui::Spacing();\n          ImGui::Text(\"Loop start\");\n          ImGui::SliderFloat(\n              \"##loopStart\",\n              &Renderer->Scene->Renderables[1]->Animator.LoopStart, 0.0f,\n              Renderer->Scene->Renderables[1]->Animator.LoopEnd);\n          ImGui::Spacing();\n          ImGui::Text(\"Loop end\");\n          ImGui::SliderFloat(\"##loopEnd\",\n                             &Renderer->Scene->Renderables[1]->Animator.LoopEnd,\n                             0.0f,\n                             Renderer->Scene->Renderables[1]\n                                 ->Animator.CurrentAnimation->Duration);\n\n          // Nice hack\n          float backup = Renderer->Scene->Renderables[1]->Animator.CurrentTime;\n          ImGui::Spacing();\n          ImGui::Text(\"Current time\");\n          ImGui::SliderFloat(\n              \"##currentTime\",\n              &Renderer->Scene->Renderables[1]->Animator.CurrentTime, 0.0f,\n              Renderer->Scene->Renderables[1]\n                  ->Animator.CurrentAnimation->Duration);\n          if (backup != Renderer->Scene->Renderables[1]->Animator.CurrentTime) {\n            Renderer->Scene->Renderables[1]->Animator.Seek(\n                Renderer->Scene->Renderables[1]->Animator.CurrentTime);\n          }\n\n          ImGui::Spacing();\n          ImGui::Checkbox(\"OneShot\",\n                          &Renderer->Scene->Renderables[1]->Animator.OneShot);\n\n          ImGui::Spacing();\n          const char* comboPreviewValue =\n              Renderer->Scene->Renderables[1]\n                  ->StaticModel->AnimationNames[CurrentAnim]\n                  .data();\n          if (ImGui::BeginCombo(\"##animationCombo\", comboPreviewValue)) {\n            for (uint32_t i = 0;\n                 i <\n                 Renderer->Scene->Renderables[1]->StaticModel->AnimationCount;\n                 i++) {\n              ImGui::PushID(i);\n              const bool isSelected = (CurrentAnim == i);\n              if (ImGui::Selectable(Renderer->Scene->Renderables[1]\n                                        ->StaticModel->AnimationNames[i]\n                                        .data(),\n                                    isSelected))\n                CurrentAnim = i;\n              if (isSelected) ImGui::SetItemDefaultFocus();\n              ImGui::PopID();\n            }\n            ImGui::EndCombo();\n          }\n          ImGui::Spacing();\n          if (ImGui::Button(\"Switch\")) {\n            Renderer->Scene->Renderables[1]->SwitchAnimation(\n                (int16_t)Renderer->Scene->Renderables[1]\n                    ->StaticModel->AnimationIds[CurrentAnim],\n                0.66f);\n          }\n        }\n      }\n    }\n\n    ImGui::Spacing();\n\n    if (ImGui::CollapsingHeader(\"BGM\", ImGuiTreeNodeFlags_DefaultOpen)) {\n      std::string const& comboPreviewValue = BgmNames[CurrentBgm];\n\n      ImGui::Spacing();\n      if (ImGui::BeginCombo(\"##bgmCombo\", comboPreviewValue.c_str())) {\n        size_t i = 0;\n        for (const auto& name : BgmNames) {\n          ImGui::PushID((int)i);\n          const bool isSelected = (CurrentBgm == i);\n          if (ImGui::Selectable(name.c_str(), isSelected)) {\n            CurrentBgm = (uint32_t)i;\n          }\n          if (isSelected) ImGui::SetItemDefaultFocus();\n          ImGui::PopID();\n          i++;\n        }\n        ImGui::EndCombo();\n      }\n      ImGui::Spacing();\n      bool bgmLoop = (bool)BgmLoop;\n      ImGui::Checkbox(\"Loop (takes effect on switch)\", &bgmLoop);\n      BgmLoop = (int)bgmLoop;\n      ImGui::Spacing();\n      if (ImGui::Button(\"Switch##switchBgm\")) {\n        BgmChangeQueued = true;\n        Audio::Channels[Audio::AC_BGM0]->Stop(BgmFadeOut);\n      }\n      ImGui::Spacing();\n      ImGui::Text(\"Master volume\");\n      ImGui::SliderFloat(\"##masterVolume\", &Audio::MasterVolume, 0.0f, 1.0f);\n      ImGui::Spacing();\n      ImGui::Text(\"BGM volume\");\n      ImGui::SliderFloat(\"##bgmVolume\", &Audio::GroupVolumes[Audio::ACG_BGM],\n                         0.0f, 1.0f);\n      ImGui::Spacing();\n      ImGui::Text(\"Fade out duration\");\n      ImGui::SliderFloat(\"##fadeOutDur\", &BgmFadeOut, 0.0f, 5.0f);\n      ImGui::Spacing();\n      ImGui::Text(\"Fade in duration\");\n      ImGui::SliderFloat(\"##fadeInDur\", &BgmFadeIn, 0.0f, 5.0f);\n    }\n\n    ImGui::Spacing();\n  }\n\n  ImGui::End();\n#else\n  if (Renderer->Scene->Renderables[0]->Status == LoadStatus::Loaded) {\n    Renderer->Scene->Renderables[0]->IsVisible = true;\n  }\n\n  if (Renderer->Scene->Renderables[1]->Status == LoadStatus::Loaded) {\n    Renderer->Scene->Renderables[1]->IsVisible = true;\n  }\n#endif\n\n  if (BgmChangeQueued &&\n      Audio::Channels[Audio::AC_BGM0]->GetState() == Audio::ACS_Stopped) {\n    Audio::Channels[Audio::AC_BGM0]->Play(\"bgm\", BgmIds[CurrentBgm], BgmLoop,\n                                          BgmFadeIn);\n    BgmChangeQueued = false;\n  }\n\n  Renderer->Scene->MainCamera.CameraTransform.Position = CameraPosition;\n  Renderer->Scene->MainCamera.LookAt(CameraTarget);\n}\n\nstatic void EnumerateBgm() {\n  std::map<uint32_t, std::string> listing;\n  IoError err = Io::VfsListFiles(\"bgm\", listing);\n  if (err != IoError_OK) {\n    ImpLog(LogLevel::Warning, LogChannel::General,\n           \"Failed to list BGM archive files, stopping enumeration!\\n\");\n    return;\n  }\n\n  size_t bgmCount = listing.size();\n\n  BgmNames.reserve(bgmCount);\n  BgmIds.reserve(bgmCount);\n\n  for (auto const& file : listing) {\n    BgmIds.push_back(file.first);\n    BgmNames.push_back(std::move(file.second));\n  }\n}\n\n}  // namespace ModelViewer\n}  // namespace Impacto"
  },
  {
    "path": "src/modelviewer.h",
    "content": "#pragma once\n\n#include \"impacto.h\"\n\n#include <glm/glm.hpp>\n\nnamespace Impacto {\nnamespace ModelViewer {\n\nvoid Init();\nvoid Update(float dt);\n\n}  // namespace ModelViewer\n}  // namespace Impacto"
  },
  {
    "path": "src/pch.h",
    "content": "#pragma once\n\n#define NOMINMAX\n\n#include <algorithm>\n#include <chrono>\n#include <map>\n#include <memory>\n#include <optional>\n#include <string>\n#include <utility>\n#include <vector>\n#include <random>\n#include <ranges>\n\n#include <cstdio>\n#include <cassert>\n#include <ctime>\n#include <cstring>\n#include <cstdlib>\n#include <cstdint>\n\n#include <SDL.h>\n#include <ankerl/unordered_dense.h>\n#include <span>\n#include <glm/glm.hpp>\n#include <magic_enum/magic_enum.hpp>"
  },
  {
    "path": "src/profile/animations.cpp",
    "content": "#include \"animations.h\"\n#include \"profile_internal.h\"\n#include \"../log.h\"\n\nnamespace Impacto {\nnamespace Profile {\n\nvoid LoadAnimations() {\n  EnsurePushMemberOfType(\"Animations\", LUA_TTABLE);\n\n  PushInitialIndex();\n  while (PushNextTableElement() != 0) {\n    std::string name(EnsureGetKey<std::string>());\n\n    SpriteAnimationDef& animation = Animations[name];\n    animation.Duration = EnsureGetMember<float>(\"Duration\");\n\n    {\n      EnsurePushMemberOfType(\"Frames\", LUA_TTABLE);\n\n      animation.FrameCount = (uint32_t)lua_rawlen(LuaState, -1);\n      animation.Frames = (Sprite*)malloc(animation.FrameCount * sizeof(Sprite));\n      PushInitialIndex();\n      while (PushNextTableElement() != 0) {\n        animation.Frames[EnsureGetKey<int32_t>() - 1] =\n            EnsureGetArrayElement<Sprite>();\n        Pop();\n      }\n\n      Pop();\n    }\n\n    Pop();\n  }\n\n  Pop();\n}\n\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/animations.h",
    "content": "#pragma once\n\n#include <ankerl/unordered_dense.h>\n#include \"../spriteanimation.h\"\n#include \"../util.h\"\n\nnamespace Impacto {\nnamespace Profile {\n\ninline ankerl::unordered_dense::map<std::string, SpriteAnimationDef,\n                                    string_hash, std::equal_to<>>\n    Animations;\n\nvoid LoadAnimations();\n\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/charset.cpp",
    "content": "#include \"charset.h\"\n#include \"profile_internal.h\"\n#include \"../text/text.h\"\n\n#include <utf8.h>\n\nnamespace Impacto {\nnamespace Profile {\nnamespace Charset {\n\nvoid Load() {\n  EnsurePushMemberOfType(\"Charset\", LUA_TTABLE);\n\n  if (TryPushMember(\"Flags\")) {\n    AssertIs(LUA_TTABLE);\n    PushInitialIndex();\n    while (PushNextTableElement() != 0) {\n      uint16_t glyphId = EnsureGetKey<uint16_t>();\n      const uint8_t flags = EnsureGetArrayElement<uint8_t>();\n\n      StringToken::AddFlags(glyphId, flags);\n\n      Pop();\n    }\n\n    Pop();\n  }\n\n  if (TryPushMember(\"CharacterToSc3\")) {\n    AssertIs(LUA_TTABLE);\n\n    PushInitialIndex();\n    while (PushNextTableElement() != 0) {\n      std::string key(EnsureGetKey<std::string>());\n\n      std::string::iterator strIt = key.begin();\n      std::string::iterator strEnd = key.end();\n      auto codePoint = utf8::next(strIt, strEnd);\n\n      CharacterToSc3[codePoint] = EnsureGetArrayElement<uint16_t>();\n\n      Pop();\n    }\n\n    Pop();\n  }\n\n  Pop();\n}\n\n}  // namespace Charset\n}  // namespace Profile\n}  // namespace Impacto\n"
  },
  {
    "path": "src/profile/charset.h",
    "content": "#pragma once\n\n#include \"../impacto.h\"\n#include <ankerl/unordered_dense.h>\n\nnamespace Impacto {\nnamespace Profile {\nnamespace Charset {\n\ninline ankerl::unordered_dense::map<uint32_t, uint16_t> CharacterToSc3;\n\nvoid Load();\n\n}  // namespace Charset\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/configsystem.cpp",
    "content": "#include \"configsystem.h\"\n#include \"profile_internal.h\"\n#include \"../audio/audiosystem.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace ConfigSystem {\n\nvoid Configure() {\n  std::fill(Default::VoiceMuted.begin(), Default::VoiceMuted.end(), false);\n  std::fill(Default::VoiceVolume.begin(), Default::VoiceVolume.end(), 1.0f);\n  std::fill(Default::GroupVolumes.begin(), Default::GroupVolumes.end(), 0.5f);\n\n  Default::GroupVolumes[Audio::ACG_BGM] = 0.25f;\n\n  if (TryPushMember(\"ConfigSystem\")) {\n    AssertIs(LUA_TTABLE);\n\n    Default::ShowTipsNotification =\n        TryGetMember<bool>(\"ShowTipsNotification\")\n            .value_or(Default::ShowTipsNotification);\n    Default::AutoQuickSave =\n        TryGetMember<uint8_t>(\"AutoQuickSave\").value_or(Default::AutoQuickSave);\n    Default::ControllerType = TryGetMember<uint8_t>(\"ControllerType\")\n                                  .value_or(Default::ControllerType);\n    Default::AdvanceTextOnDirectionalInput =\n        TryGetMember<bool>(\"AdvanceTextOnDirectionalInput\")\n            .value_or(Default::AdvanceTextOnDirectionalInput);\n    Default::DirectionalInputForTrigger =\n        TryGetMember<bool>(\"DirectionalInputForTrigger\")\n            .value_or(Default::DirectionalInputForTrigger);\n    Default::TriggerStopSkip = TryGetMember<bool>(\"TriggerStopSkip\")\n                                   .value_or(Default::TriggerStopSkip);\n\n    Default::TextSpeed =\n        TryGetMember<float>(\"TextSpeed\").value_or(Default::TextSpeed);\n    TextSpeedBounds =\n        TryGetMember<glm::vec2>(\"TextSpeedBounds\").value_or(TextSpeedBounds);\n    Default::AutoSpeed =\n        TryGetMember<float>(\"AutoSpeed\").value_or(Default::AutoSpeed);\n    AutoSpeedBounds =\n        TryGetMember<glm::vec2>(\"AutoSpeedBounds\").value_or(AutoSpeedBounds);\n\n    Default::SkipRead =\n        TryGetMember<bool>(\"SkipRead\").value_or(Default::SkipRead);\n    Default::SyncVoice =\n        TryGetMember<bool>(\"SyncVoice\").value_or(Default::SyncVoice);\n    Default::SkipVoice =\n        TryGetMember<bool>(\"SkipVoice\").value_or(Default::SkipVoice);\n\n    std::optional<uint32_t> optionalVoiceCount =\n        TryGetMember<uint32_t>(\"VoiceCount\");\n    if (optionalVoiceCount) {\n      uint32_t voiceCount = optionalVoiceCount.value();\n      assert(voiceCount <= VoiceCount);\n\n      GetMemberArray(std::span(Default::VoiceMuted.data(), voiceCount),\n                     \"VoiceMuted\");\n      GetMemberArray(std::span(Default::VoiceVolume.data(), voiceCount),\n                     \"VoiceVolume\");\n    }\n\n    Default::ImageSize =\n        TryGetMember<float>(\"ImageSize\").value_or(Default::ImageSize);\n\n    Pop();\n  }\n\n  ResetToDefault();\n}\n\nvoid ResetToDefault() {\n  ShowTipsNotification = Default::ShowTipsNotification;\n  AutoQuickSave = Default::AutoQuickSave;\n  ControllerType = Default::ControllerType;\n  AdvanceTextOnDirectionalInput = Default::AdvanceTextOnDirectionalInput;\n  DirectionalInputForTrigger = Default::DirectionalInputForTrigger;\n  TriggerStopSkip = Default::TriggerStopSkip;\n\n  TextSpeed = Default::TextSpeed;\n  AutoSpeed = Default::AutoSpeed;\n\n  SkipRead = Default::SkipRead;\n  SyncVoice = Default::SyncVoice;\n  SkipVoice = Default::SkipVoice;\n\n  std::ranges::copy(Default::VoiceMuted, VoiceMuted.begin());\n  std::ranges::copy(Default::VoiceVolume, VoiceVolume.begin());\n  std::ranges::copy(Default::GroupVolumes, Audio::GroupVolumes.begin());\n\n  ImageSize = Default::ImageSize;\n}\n\n}  // namespace ConfigSystem\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/configsystem.h",
    "content": "#pragma once\n\n#include \"../audio/audiocommon.h\"\n#include <glm/glm.hpp>\n\nnamespace Impacto {\nnamespace Profile {\nnamespace ConfigSystem {\n\nconstexpr int VoiceCount = 33;\n\nenum class AutoQuickSaveType : uint8_t {\n  Never = 0,\n  OnTrigger = (1 << 0),\n  OnScene = (1 << 1),\n};\n\nnamespace Default {\ninline bool ShowTipsNotification = true;\ninline uint8_t AutoQuickSave =\n    +AutoQuickSaveType::OnTrigger | +AutoQuickSaveType::OnScene;\ninline uint8_t ControllerType = 0;\ninline bool AdvanceTextOnDirectionalInput = false;\ninline bool DirectionalInputForTrigger = false;\ninline bool TriggerStopSkip = true;\n\ninline float TextSpeed = 768.0f / 60.0f;\ninline float AutoSpeed = 768.0f / 60.0f;\n\ninline bool SkipRead = true;\ninline bool SyncVoice = true;\ninline bool SkipVoice = false;\n\ninline std::array<bool, VoiceCount> VoiceMuted;\ninline std::array<float, VoiceCount> VoiceVolume;\ninline std::array<float, Audio::ACG_Count> GroupVolumes;\n\ninline float ImageSize = 1.0f;\n}  // namespace Default\n\n// Add new tips to the tips notification rendering queue\ninline bool ShowTipsNotification = Default::ShowTipsNotification;\n\n// When should the game automatically quick save\ninline uint8_t AutoQuickSave = Default::AutoQuickSave;\n\n// What controller scheme to use\ninline uint8_t ControllerType = Default::ControllerType;\n\n// Advance text on L/R stick, arrow keys, etc.\ninline bool AdvanceTextOnDirectionalInput =\n    Default::AdvanceTextOnDirectionalInput;\n\n// Interact with trigger using left and right input in addition\n// to their regular counterparts\ninline bool DirectionalInputForTrigger = Default::DirectionalInputForTrigger;\n\n// Stop skip mode when reaching a trigger\n// (e.g. delusion trigger, phone trigger, etc.)\ninline bool TriggerStopSkip = Default::TriggerStopSkip;\n\n// Typewriter animation speed\ninline float TextSpeed = Default::TextSpeed;\ninline glm::vec2 TextSpeedBounds = glm::vec2(256.0f, 4096.0f) / 60.0f;\n\n// Speed to skip in auto mode (MessWaitSpeed)\ninline float AutoSpeed = Default::AutoSpeed;\n// Menu is essentially auto *time* as opposed to auto *speed*\ninline glm::vec2 AutoSpeedBounds = glm::vec2(2048.0f, 256.0f) / 60.0f;\n\n// Only skip read text\ninline bool SkipRead = Default::SkipRead;\n\n// Sync text speed with voice line duration\ninline bool SyncVoice = Default::SyncVoice;\n\n// Stop voice line after dialogue progression\ninline bool SkipVoice = Default::SkipVoice;\n\n// Individual character mute/volume settings\ninline std::array<bool, VoiceCount> VoiceMuted;\ninline std::array<float, VoiceCount> VoiceVolume;\n\n// Scalar for the the viewport to render onto the window\ninline float ImageSize;\n\nvoid Configure();\nvoid ResetToDefault();\n\n}  // namespace ConfigSystem\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/data/achievementsystem.cpp",
    "content": "#include \"achievementsystem.h\"\n#include \"../profile_internal.h\"\n\n#include \"../../data/achievementsystemps3.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace AchievementSystem {\n\nusing namespace Impacto::AchievementSystem;\n\nvoid Configure() {\n  if (TryPushMember(\"AchievementData\")) {\n    AssertIs(LUA_TTABLE);\n    Type = EnsureGetMember<AchievementDataType>(\"Type\");\n\n    switch (Type) {\n      case AchievementDataType::PS3:\n        Implementation = new Impacto::AchievementSystem::AchievementSystemPS3();\n        break;\n\n      case AchievementDataType::None:\n        break;\n    }\n\n    AchievementDataPath = EnsureGetMember<std::string>(\"AchievementDataPath\");\n\n    Pop();\n  }\n}\n}  // namespace AchievementSystem\n}  // namespace Profile\n}  // namespace Impacto\n"
  },
  {
    "path": "src/profile/data/achievementsystem.h",
    "content": "#pragma once\n\n#include \"../../data/achievementsystem.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace AchievementSystem {\n\ninline Impacto::AchievementSystem::AchievementDataType Type =\n    Impacto::AchievementSystem::AchievementDataType::None;\n\ninline std::string AchievementDataPath;\n\nvoid Configure();\n\n}  // namespace AchievementSystem\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/data/bgeff.cpp",
    "content": "#include \"bgeff.h\"\n\n#include <map>\n#include \"../profile_internal.h\"\n#include \"../../background2d.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace BgEff {\n\nvoid Load() {\n  using magic_enum::enum_cast;\n  EnsurePushMemberOfType(\"BgEffData\", LUA_TTABLE);\n\n  {\n    EnsurePushMemberOfType(\"BgEffShaderData\", LUA_TTABLE);\n\n    PushInitialIndex();\n    while (PushNextTableElement() != 0) {\n      AssertIs(LUA_TTABLE);\n\n      auto arraySize = lua_rawlen(LuaState, -1);\n      if (arraySize != 5) {\n        ImpLog(LogLevel::Fatal, LogChannel::Profile,\n               \"Expected five entries in shader definition defined; got {:d}\\n\",\n               arraySize);\n        Window->Shutdown();\n      }\n\n      Background2D::BgEffShaderMap.emplace(\n          EnsureGetArrayElementByIndex<int>(0),\n          std::array<ShaderProgramType, 4>{\n              enum_cast<ShaderProgramType>(EnsureGetArrayElementByIndex<int>(1))\n                  .value(),\n              enum_cast<ShaderProgramType>(EnsureGetArrayElementByIndex<int>(2))\n                  .value(),\n              enum_cast<ShaderProgramType>(EnsureGetArrayElementByIndex<int>(3))\n                  .value(),\n              enum_cast<ShaderProgramType>(EnsureGetArrayElementByIndex<int>(4))\n                  .value(),\n          });\n      Pop();\n    }\n\n    Pop();\n  }\n\n  {\n    EnsurePushMemberOfType(\"BgEffTextureData\", LUA_TTABLE);\n\n    PushInitialIndex();\n    while (PushNextTableElement() != 0) {\n      AssertIs(LUA_TTABLE);\n\n      auto arraySize = lua_rawlen(LuaState, -1);\n      if (arraySize != 5) {\n        ImpLog(\n            LogLevel::Fatal, LogChannel::Profile,\n            \"Expected five entries in texture id mapping defined; got {:d}\\n\",\n            arraySize);\n        Window->Shutdown();\n      }\n\n      Background2D::BgEffTextureIdMap.emplace(\n          EnsureGetArrayElementByIndex<int>(0),\n          std::array<int, 4>{\n              EnsureGetArrayElementByIndex<int>(1),\n              EnsureGetArrayElementByIndex<int>(2),\n              EnsureGetArrayElementByIndex<int>(3),\n              EnsureGetArrayElementByIndex<int>(4),\n          });\n\n      Pop();\n    }\n\n    Pop();\n  }\n\n  Pop();\n}\n\n}  // namespace BgEff\n}  // namespace Profile\n}  // namespace Impacto\n"
  },
  {
    "path": "src/profile/data/bgeff.h",
    "content": "#pragma once\n\nnamespace Impacto {\nnamespace Profile {\nnamespace BgEff {\n\nvoid Load();\n\n}  // namespace BgEff\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/data/savesystem.cpp",
    "content": "#include \"savesystem.h\"\n#include \"../profile_internal.h\"\n#include \"../../renderer/renderer.h\"\n\n#include \"../../games/cclcc/savesystem.h\"\n#include \"../../games/chlcc/savesystem.h\"\n#include \"../../games/mo6tw/savesystem.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace SaveSystem {\n\nusing namespace Impacto::SaveSystem;\n\nvoid Configure() {\n  EnsurePushMemberOfType(\"SaveData\", LUA_TTABLE);\n\n  Type = EnsureGetMember<SaveDataType>(\"Type\");\n\n  switch (Type) {\n    case SaveDataType::CHLCC:\n      Implementation = new Impacto::CHLCC::SaveSystem();\n\n      ThumbnailFilePath = EnsureGetMember<std::string>(\"ThumbnailFilePath\");\n\n      break;\n    case SaveDataType::MO6TW:\n      Implementation = new Impacto::MO6TW::SaveSystem();\n      break;\n    case SaveDataType::CCLCC:\n      Implementation = new Impacto::CCLCC::SaveSystem();\n      break;\n    case SaveDataType::None:\n      ImpLog(LogLevel::Warning, LogChannel::Profile,\n             \"Save data type is none, not setting implementation\\n\");\n      break;\n  }\n\n  SaveFilePath = EnsureGetMember<std::string>(\"SaveFilePath\");\n\n  if (TryPushMember(\"StoryScriptIDs\")) {\n    AssertIs(LUA_TTABLE);\n\n    StoryScriptCount = (int)lua_rawlen(LuaState, -1);\n    StoryScriptIDs.resize(*StoryScriptCount);\n    PushInitialIndex();\n    while (PushNextTableElement() != 0) {\n      int i = EnsureGetKey<int32_t>() - 1;\n      StoryScriptIDs[i] = EnsureGetArrayElement<int>();\n      Pop();\n    }\n\n    Pop();\n  }\n\n  if (TryPushMember(\"ScriptMessageData\")) {\n    AssertIs(LUA_TTABLE);\n\n    auto dataCount = lua_rawlen(LuaState, -1);\n    ScriptMessageData.resize(dataCount);\n    PushInitialIndex();\n    while (PushNextTableElement() != 0) {\n      int i = EnsureGetKey<int32_t>() - 1;\n      AssertIs(LUA_TTABLE);\n      auto pairSize = lua_rawlen(LuaState, -1);\n      if (pairSize != 2) {\n        ImpLog(LogLevel::Fatal, LogChannel::Profile, \"Expected two values\\n\");\n        Window->Shutdown();\n      }\n      ScriptMessageData[i].LineCount =\n          EnsureGetArrayElementByIndex<uint32_t>(0);\n      ScriptMessageData[i].SaveDataOffset =\n          EnsureGetArrayElementByIndex<uint32_t>(1);\n\n      Pop();\n    }\n\n    Pop();\n  }\n\n  if (TryPushMember(\"AlbumEvData\")) {\n    AssertIs(LUA_TTABLE);\n\n    auto dataCount = lua_rawlen(LuaState, -1);\n    PushInitialIndex();\n    while (PushNextTableElement() != 0) {\n      int i = EnsureGetKey<int32_t>() - 1;\n      AssertIs(LUA_TTABLE);\n      auto subDataCount = lua_rawlen(LuaState, -1);\n      PushInitialIndex();\n      while (PushNextTableElement() != 0) {\n        int j = EnsureGetKey<int32_t>() - 1;\n        AlbumEvData[i][j] = EnsureGetArrayElement<uint16_t>();\n        Pop();\n      }\n      // End marker\n      AlbumEvData[i][subDataCount] = 0xFFFF;\n      Pop();\n    }\n    // End marker\n    AlbumEvData[dataCount][0] = 0xFFFF;\n\n    Pop();\n  }\n\n  if (TryPushMember(\"AlbumData\")) {\n    AssertIs(LUA_TTABLE);\n\n    // Unused for now\n    // auto dataCount = lua_rawlen(LuaState, -1);\n    PushInitialIndex();\n    while (PushNextTableElement() != 0) {\n      int i = EnsureGetKey<int32_t>() - 1;\n      AssertIs(LUA_TTABLE);\n      auto subDataCount = lua_rawlen(LuaState, -1);\n      PushInitialIndex();\n      while (PushNextTableElement() != 0) {\n        int j = EnsureGetKey<int32_t>() - 1;\n        AssertIs(LUA_TTABLE);\n        auto cgSpriteCount = lua_rawlen(LuaState, -1);\n        PushInitialIndex();\n        while (PushNextTableElement() != 0) {\n          int k = EnsureGetKey<int32_t>() - 1;\n          AlbumData[i][j][k] = EnsureGetArrayElement<uint16_t>();\n          Pop();\n        }\n        // End marker\n        AlbumData[i][j][cgSpriteCount] = 0xFFFF;\n        Pop();\n      }\n      // End marker\n      AlbumData[i][subDataCount][0] = 0xFFFF;\n      Pop();\n    }\n    Pop();\n  }\n\n  Pop();\n}\n\n}  // namespace SaveSystem\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/data/savesystem.h",
    "content": "#pragma once\n\n#include \"../../data/savesystem.h\"\n#include <string>\n#include <optional>\n\nnamespace Impacto {\nnamespace Profile {\nnamespace SaveSystem {\n\nint constexpr MaxAlbumEntries = 200;\nint constexpr MaxAlbumSubEntries = 15;\nint constexpr MaxCGSprites = 4;\n\ninline Impacto::SaveSystem::SaveDataType Type =\n    Impacto::SaveSystem::SaveDataType::None;\n\ninline std::string SaveFilePath;\ninline std::optional<std::string> ThumbnailFilePath;\ninline std::vector<uint32_t> StoryScriptIDs;\ninline std::optional<int> StoryScriptCount;\ninline std::vector<Impacto::SaveSystem::ScriptMessageDataPair>\n    ScriptMessageData;\ninline uint16_t AlbumEvData[MaxAlbumEntries][MaxAlbumSubEntries];\ninline uint16_t AlbumData[MaxAlbumEntries][MaxAlbumSubEntries][MaxCGSprites];\n\nvoid Configure();\n\n}  // namespace SaveSystem\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/data/tipssystem.cpp",
    "content": "#include \"tipssystem.h\"\n#include \"../profile_internal.h\"\n\n#include \"../../games/mo6tw/tipssystem.h\"\n#include \"../../games/chlcc/tipssystem.h\"\n#include \"../../games/cclcc/tipssystem.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace TipsSystem {\n\nusing namespace Impacto::TipsSystem;\n\nvoid Configure() {\n  EnsurePushMemberOfType(\"TipsSystem\", LUA_TTABLE);\n\n  Type = EnsureGetMember<TipsSystemType>(\"Type\");\n\n  if (Type != TipsSystemType::None) {\n    MaxTipsCount = EnsureGetMember<size_t>(\"MaxTipsCount\");\n\n    switch (Type) {\n      case TipsSystemType::MO6TW:\n        Implementation =\n            std::make_unique<Impacto::MO6TW::TipsSystem>(MaxTipsCount);\n        break;\n      case TipsSystemType::CHLCC:\n        SortCategoryMapping =\n            EnsureGetMember<std::vector<int>>(\"SortCategoryMapping\");\n        Implementation =\n            std::make_unique<Impacto::CHLCC::TipsSystem>(MaxTipsCount);\n        break;\n      case TipsSystemType::CCLCC:\n        Implementation =\n            std::make_unique<Impacto::CCLCC::TipsSystem>(MaxTipsCount);\n        break;\n      case TipsSystemType::None:\n        ImpLog(LogLevel::Warning, LogChannel::Profile,\n               \"Tips system type is None, not setting system implementation\\n\");\n    }\n  }\n\n  Pop();\n}\n\n}  // namespace TipsSystem\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/data/tipssystem.h",
    "content": "#pragma once\n\n#include \"../../data/tipssystem.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace TipsSystem {\n\ninline Impacto::TipsSystem::TipsSystemType Type =\n    Impacto::TipsSystem::TipsSystemType::None;\n\ninline size_t MaxTipsCount;\n\ninline std::optional<std::vector<int>> SortCategoryMapping;\n\nvoid Configure();\n\n}  // namespace TipsSystem\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/data/waveeffects.cpp",
    "content": "#include \"waveeffects.h\"\n\n#include \"../profile_internal.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace WaveEffects {\nvoid Load() {\n  EnsurePushMemberOfType(\"WaveEffects\", LUA_TTABLE);\n  BGWaveGridSize = EnsureGetMember<glm::u16vec2>(\"BGWaveGridSize\");\n  WaveMaxCount = EnsureGetMember<uint32_t>(\"WaveMaxCount\");\n  Pop();\n}\n}  // namespace WaveEffects\n}  // namespace Profile\n}  // namespace Impacto\n"
  },
  {
    "path": "src/profile/data/waveeffects.h",
    "content": "#pragma once\n\nnamespace Impacto {\nnamespace Profile {\nnamespace WaveEffects {\n\ninline uint32_t WaveMaxCount = 20;\ninline glm::u16vec2 BGWaveGridSize = {160, 90};\n\nvoid Load();\n\n}  // namespace WaveEffects\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/dialogue.cpp",
    "content": "#include \"dialogue.h\"\n#include \"profile_internal.h\"\n#include \"../log.h\"\n#include \"../ui/ui.h\"\n\n#include \"games/mo6tw/dialoguebox.h\"\n#include \"games/chlcc/dialoguebox.h\"\n#include \"games/cc/dialoguebox.h\"\n\n#include \"../hud/nametagdisplay.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace Dialogue {\n\nstatic void ConfigureNametag() {\n  NametagCurrentType = EnsureGetMember<NametagType>(\"NametagCurrentType\");\n\n  switch (NametagCurrentType) {\n    case NametagType::None:\n      break;\n\n    case NametagType::Sprite:\n      NametagPosition = EnsureGetMember<glm::vec2>(\"NametagPosition\");\n      NametagSprite = EnsureGetMember<Sprite>(\"NametagSprite\");\n      break;\n\n    case NametagType::TwoPiece:\n    case NametagType::ThreePiece:\n      NametagPosition = EnsureGetMember<glm::vec2>(\"NametagPosition\");\n\n      NametagLeftSprite = EnsureGetMember<Sprite>(\"NametagLeftSprite\");\n      NametagRightSprite = EnsureGetMember<Sprite>(\"NametagRightSprite\");\n\n      if (NametagCurrentType == NametagType::ThreePiece) {\n        NametagMiddleSprite = EnsureGetMember<Sprite>(\"NametagMiddleSprite\");\n        NametagMiddleBaseWidth =\n            EnsureGetMember<float>(\"NametagMiddleBaseWidth\");\n      }\n      break;\n\n    case NametagType::CHLCC:\n      CHLCC::DialogueBox::ConfigureNametag();\n      break;\n\n    case NametagType::CC:\n      CC::DialogueBox::ConfigureNametag();\n      break;\n\n    default:\n      throw std::runtime_error(\n          fmt::format(\"Tried to configure unknown nametag type {:d}\",\n                      static_cast<int>(NametagCurrentType)));\n  }\n}\n\nvoid Configure() {\n  EnsurePushMemberOfType(\"Dialogue\", LUA_TTABLE);\n\n  NVLBounds = EnsureGetMember<RectF>(\"NVLBounds\");\n  ADVBounds = EnsureGetMember<RectF>(\"ADVBounds\");\n  REVBounds = EnsureGetMember<RectF>(\"REVBounds\");\n  TryGetMember<RectF>(\"SecondaryREVBounds\", SecondaryREVBounds);\n  TryGetMember<RectF>(\"TipsBounds\", TipsBounds);\n\n  ADVBoxSprite = EnsureGetMember<Sprite>(\"ADVBoxSprite\");\n  ADVBoxPos = EnsureGetMember<glm::vec2>(\"ADVBoxPos\");\n\n  FadeOutDuration = EnsureGetMember<float>(\"FadeOutDuration\");\n  FadeInDuration = EnsureGetMember<float>(\"FadeInDuration\");\n\n  TextFadeInDuration = EnsureGetMember<float>(\"TextFadeInDuration\");\n  TextFadeOutDuration = EnsureGetMember<float>(\"TextFadeOutDuration\");\n\n  TryGetMember<bool>(\"HasAutoButton\", HasAutoButton);\n  if (HasAutoButton) {\n    AutoButtonSprite = EnsureGetMember<Sprite>(\"AutoButtonSprite\");\n    AutoButtonActiveSprite = EnsureGetMember<Sprite>(\"AutoButtonActiveSprite\");\n    AutoButtonPosition = EnsureGetMember<glm::vec2>(\"AutoButtonPosition\");\n  }\n\n  TryGetMember<bool>(\"HasSkipButton\", HasSkipButton);\n  if (HasSkipButton) {\n    SkipButtonSprite = EnsureGetMember<Sprite>(\"SkipButtonSprite\");\n    SkipButtonActiveSprite = EnsureGetMember<Sprite>(\"SkipButtonActiveSprite\");\n    SkipButtonPosition = EnsureGetMember<glm::vec2>(\"SkipButtonPosition\");\n  }\n\n  TryGetMember<bool>(\"HasBacklogButton\", HasBacklogButton);\n  if (HasBacklogButton) {\n    BacklogButtonSprite = EnsureGetMember<Sprite>(\"BacklogButtonSprite\");\n    BacklogButtonActiveSprite =\n        EnsureGetMember<Sprite>(\"BacklogButtonActiveSprite\");\n    BacklogButtonPosition = EnsureGetMember<glm::vec2>(\"BacklogButtonPosition\");\n  }\n\n  TryGetMember<bool>(\"HasMenuButton\", HasMenuButton);\n  if (HasMenuButton) {\n    MenuButtonSprite = EnsureGetMember<Sprite>(\"MenuButtonSprite\");\n    MenuButtonActiveSprite = EnsureGetMember<Sprite>(\"MenuButtonActiveSprite\");\n    MenuButtonPosition = EnsureGetMember<glm::vec2>(\"MenuButtonPosition\");\n  }\n\n  DialogueBoxCurrentType =\n      EnsureGetMember<DialogueBoxType>(\"DialogueBoxCurrentType\");\n\n  switch (DialogueBoxCurrentType) {\n    case DialogueBoxType::MO6TW:\n      Profile::MO6TW::DialogueBox::Configure();\n      break;\n    case DialogueBoxType::CHLCC:\n      Profile::CHLCC::DialogueBox::Configure();\n      break;\n    case DialogueBoxType::CC:\n      Profile::CC::DialogueBox::Configure();\n      break;\n    default:\n      ImpLog(LogLevel::Warning, LogChannel::General,\n             \"Dialogue box is not implemented for the current profile yet!\\n\");\n      break;\n  }\n\n  NVLBoxMaxOpacity = EnsureGetMember<float>(\"NVLBoxMaxOpacity\");\n\n  ADVNameAlignment = EnsureGetMember<TextAlignment>(\"ADVNameAlignment\");\n\n  ADVNameFontSize = EnsureGetMember<float>(\"ADVNameFontSize\");\n  ADVNamePos = EnsureGetMember<glm::vec2>(\"ADVNamePos\");\n\n  REVNameFontSize = EnsureGetMember<float>(\"REVNameFontSize\");\n  REVColor = EnsureGetMember<int>(\"REVColor\");\n  REVNameColor = EnsureGetMember<int>(\"REVNameColor\");\n  REVNameOffset = EnsureGetMember<float>(\"REVNameOffset\");\n  REVNameLocation = EnsureGetMember<REVNameLocationType>(\"REVNameLocation\");\n  REVOutlineMode = RendererOutlineMode(EnsureGetMember<int>(\"REVOutlineMode\"));\n  REVNameOutlineMode =\n      RendererOutlineMode(EnsureGetMember<int>(\"REVNameOutlineMode\"));\n\n  TryGetMember<float>(\"TipsLineSpacing\", TipsLineSpacing);\n  TryGetMember<int>(\"TipsColorIndex\", TipsColorIndex);\n\n  WaitIconCurrentType =\n      TryGetMember<WaitIconDisplay::WaitIconType>(\"WaitIconCurrentType\")\n          .value_or(WaitIconDisplay::WaitIconType::None);\n  if (WaitIconCurrentType != WaitIconDisplay::WaitIconType::None) {\n    switch (WaitIconCurrentType) {\n      case WaitIconDisplay::WaitIconType::SpriteAnim:\n        WaitIconSpriteAnim =\n            EnsureGetMember<SpriteAnimationDef>(\"WaitIconSpriteAnim\");\n        break;\n      case WaitIconDisplay::WaitIconType::SpriteAnimFixed:\n        WaitIconSpriteAnim =\n            EnsureGetMember<SpriteAnimationDef>(\"WaitIconSpriteAnim\");\n        WaitIconFixedSpriteId = EnsureGetMember<int>(\"WaitIconFixedSpriteId\");\n        break;\n      case WaitIconDisplay::WaitIconType::Fixed:\n        WaitIconSprite = EnsureGetMember<Sprite>(\"WaitIconSprite\");\n        break;\n      default:\n        WaitIconSprite = EnsureGetMember<Sprite>(\"WaitIconSprite\");\n        WaitIconAnimationDuration =\n            EnsureGetMember<float>(\"WaitIconAnimationDuration\");\n    }\n    WaitIconOffset = EnsureGetMember<glm::vec2>(\"WaitIconOffset\");\n  }\n\n  AutoIconCurrentType =\n      TryGetMember<AutoIconDisplay::AutoIconType>(\"AutoIconCurrentType\")\n          .value_or(AutoIconDisplay::AutoIconType::None);\n  switch (AutoIconCurrentType) {\n    case AutoIconDisplay::AutoIconType::SpriteAnim:\n      AutoIconSpriteAnim =\n          EnsureGetMember<SpriteAnimationDef>(\"AutoIconSpriteAnim\");\n      AutoIconOffset = EnsureGetMember<glm::vec2>(\"AutoIconOffset\");\n      break;\n\n    case AutoIconDisplay::AutoIconType::SpriteAnimFixed:\n      AutoIconSpriteAnim =\n          EnsureGetMember<SpriteAnimationDef>(\"AutoIconSpriteAnim\");\n      AutoIconFixedSpriteId = EnsureGetMember<int>(\"AutoIconFixedSpriteId\");\n      AutoIconOffset = EnsureGetMember<glm::vec2>(\"AutoIconOffset\");\n      break;\n\n    case AutoIconDisplay::AutoIconType::CHLCC:\n      AutoIconSprite = EnsureGetMember<Sprite>(\"AutoIconSprite\");\n      AutoIconRotationSpeed = EnsureGetMember<float>(\"AutoIconRotationSpeed\");\n      AutoSkipArrowsSprite = EnsureGetMember<Sprite>(\"AutoSkipArrowsSprite\");\n      AutoIconOffset = EnsureGetMember<glm::vec2>(\"AutoIconOffset\");\n      break;\n\n    case AutoIconDisplay::AutoIconType::Fixed:\n    case AutoIconDisplay::AutoIconType::None:\n      break;\n  }\n\n  SkipIconCurrentType =\n      TryGetMember<SkipIconDisplay::SkipIconType>(\"SkipIconCurrentType\")\n          .value_or(SkipIconDisplay::SkipIconType::None);\n  switch (SkipIconCurrentType) {\n    case SkipIconDisplay::SkipIconType::SpriteAnim:\n      SkipIconSpriteAnim =\n          EnsureGetMember<SpriteAnimationDef>(\"SkipIconSpriteAnim\");\n      SkipIconOffset = EnsureGetMember<glm::vec2>(\"SkipIconOffset\");\n      break;\n\n    case SkipIconDisplay::SkipIconType::SpriteAnimFixed:\n      SkipIconSpriteAnim =\n          EnsureGetMember<SpriteAnimationDef>(\"SkipIconSpriteAnim\");\n      SkipIconFixedSpriteId = EnsureGetMember<int>(\"SkipIconFixedSpriteId\");\n      SkipIconOffset = EnsureGetMember<glm::vec2>(\"SkipIconOffset\");\n      break;\n\n    case SkipIconDisplay::SkipIconType::CHLCC:\n      SkipIconSprite = EnsureGetMember<Sprite>(\"SkipIconSprite\");\n      SkipIconRotationSpeed = EnsureGetMember<float>(\"SkipIconRotationSpeed\");\n      SkipIconOffset = EnsureGetMember<glm::vec2>(\"SkipIconOffset\");\n      break;\n\n    case SkipIconDisplay::SkipIconType::Fixed:\n    case SkipIconDisplay::SkipIconType::None:\n      break;\n  }\n\n  DialogueFont = EnsureGetMember<Font*>(\"DialogueFont\");\n  SetFontSizeRatio = EnsureGetMember<float>(\"SetFontSizeRatio\");\n  DefaultFontSize = EnsureGetMember<float>(\"DefaultFontSize\");\n  RubyFontSize = EnsureGetMember<float>(\"RubyFontSize\");\n  RubyYOffset = EnsureGetMember<float>(\"RubyYOffset\");\n\n  MaxPageSize = EnsureGetMember<int>(\"MaxPageSize\");\n  PageCount = EnsureGetMember<int>(\"PageCount\");\n\n  {\n    EnsurePushMemberOfType(\"ColorTable\", LUA_TTABLE);\n\n    ColorCount = (int)lua_rawlen(LuaState, -1);\n    ColorTable = new DialogueColorPair[ColorCount];\n    PushInitialIndex();\n    while (PushNextTableElement() != 0) {\n      int i = EnsureGetKey<int32_t>() - 1;\n      AssertIs(LUA_TTABLE);\n\n      auto pairSize = lua_rawlen(LuaState, -1);\n      if (pairSize != 2) {\n        ImpLog(LogLevel::Fatal, LogChannel::Profile, \"Expected two colors\\n\");\n        Window->Shutdown();\n      }\n      ColorTable[i].TextColor = EnsureGetArrayElementByIndex<uint32_t>(0);\n      ColorTable[i].OutlineColor = EnsureGetArrayElementByIndex<uint32_t>(1);\n\n      Pop();\n    }\n\n    Pop();\n  }\n\n  ColorTagIsUint8 = EnsureGetMember<bool>(\"ColorTagIsUint8\");\n\n  ConfigureNametag();\n\n  TryGetMember<bool>(\"HasSpeakerPortraits\", HasSpeakerPortraits);\n\n  Pop();\n}\n\n}  // namespace Dialogue\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/dialogue.h",
    "content": "#pragma once\n\n#include \"../util.h\"\n#include \"../spritesheet.h\"\n#include \"../hud/waiticondisplay.h\"\n#include \"../hud/autoicondisplay.h\"\n#include \"../hud/skipicondisplay.h\"\n#include \"../hud/dialoguebox.h\"\n#include \"../spriteanimation.h\"\n\n#include \"../text/text.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace Dialogue {\n\ninline RectF NVLBounds;\ninline RectF ADVBounds;\ninline RectF REVBounds;\ninline RectF SecondaryREVBounds;\ninline RectF TipsBounds;\n\ninline Sprite ADVBoxSprite;\ninline glm::vec2 ADVBoxPos;\n\ninline float FadeOutDuration;\ninline float FadeInDuration;\n\ninline float TextFadeInDuration = 0.0f;\ninline float TextFadeOutDuration = 0.0f;\n\ninline DialogueBoxType DialogueBoxCurrentType = DialogueBoxType::None;\n\ninline float NVLBoxMaxOpacity;\n\ninline TextAlignment ADVNameAlignment = TextAlignment::Left;\ninline float ADVNameFontSize;\n// Unlike most positions, this position is relative to alignment\n// e.g. if ADVNameAlignment == TextAlignment::Right, name will *end* at\n// ADVNamePos.x\ninline glm::vec2 ADVNamePos;\n\nenum class NametagType : int {\n  None,\n  Sprite,\n  TwoPiece,\n  ThreePiece,\n  CHLCC,\n  CC,\n};\nenum class REVNameLocationType : int {\n  None,\n  TopLeft,\n  LeftTop,\n};\ninline float REVNameFontSize;\ninline int REVColor;\ninline int REVNameColor;\ninline float REVNameOffset;\ninline REVNameLocationType REVNameLocation = REVNameLocationType::None;\ninline RendererOutlineMode REVOutlineMode = RendererOutlineMode::Full;\ninline RendererOutlineMode REVNameOutlineMode = RendererOutlineMode::Full;\n\ninline float TipsLineSpacing;\ninline int TipsColorIndex = 0;\n\ninline Sprite WaitIconSprite;\ninline SpriteAnimationDef WaitIconSpriteAnim;\ninline glm::vec2 WaitIconOffset;\ninline float WaitIconAnimationDuration;\ninline int WaitIconFixedSpriteId;\ninline WaitIconDisplay::WaitIconType WaitIconCurrentType =\n    WaitIconDisplay::WaitIconType::None;\n\ninline Sprite AutoIconSprite;\ninline SpriteAnimationDef AutoIconSpriteAnim;\ninline glm::vec2 AutoIconOffset;\ninline int AutoIconFixedSpriteId;\ninline float AutoIconRotationSpeed;\ninline AutoIconDisplay::AutoIconType AutoIconCurrentType =\n    AutoIconDisplay::AutoIconType::None;\n\ninline Sprite SkipIconSprite;\ninline SpriteAnimationDef SkipIconSpriteAnim;\ninline glm::vec2 SkipIconOffset;\ninline int SkipIconFixedSpriteId;\ninline float SkipIconRotationSpeed;\ninline SkipIconDisplay::SkipIconType SkipIconCurrentType =\n    SkipIconDisplay::SkipIconType::None;\n\ninline Sprite AutoSkipArrowsSprite;\n\ninline Font* DialogueFont;\ninline float SetFontSizeRatio;\ninline float DefaultFontSize;\ninline float RubyFontSize;\ninline float RubyYOffset;\n\ninline int ColorCount;\ninline DialogueColorPair* ColorTable;\n\ninline int MaxPageSize;\ninline int PageCount;\n\ninline bool ColorTagIsUint8;\n\ninline NametagType NametagCurrentType = NametagType::None;\n\ninline glm::vec2 NametagPosition{};\ninline Sprite NametagSprite;\ninline Sprite NametagLeftSprite;\ninline Sprite NametagMiddleSprite;\ninline Sprite NametagRightSprite;\ninline float NametagMiddleBaseWidth = 0.0f;\n\ninline bool HasSpeakerPortraits = false;\ninline float SpeakerPortraitBaseOffsetX = 250.0f;\ninline float SpeakerPortraitBaseOffsetY = 600.0f;\n\ninline bool HasAutoButton = false;\ninline Sprite AutoButtonSprite;\ninline Sprite AutoButtonActiveSprite;\ninline glm::vec2 AutoButtonPosition;\n\ninline bool HasSkipButton = false;\ninline Sprite SkipButtonSprite;\ninline Sprite SkipButtonActiveSprite;\ninline glm::vec2 SkipButtonPosition;\n\ninline bool HasBacklogButton = false;\ninline Sprite BacklogButtonSprite;\ninline Sprite BacklogButtonActiveSprite;\ninline glm::vec2 BacklogButtonPosition;\n\ninline bool HasMenuButton = false;\ninline Sprite MenuButtonSprite;\ninline Sprite MenuButtonActiveSprite;\ninline glm::vec2 MenuButtonPosition;\n\nvoid Configure();\n\n}  // namespace Dialogue\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/fonts.cpp",
    "content": "#include \"fonts.h\"\n#include \"profile_internal.h\"\n#include \"../log.h\"\n#include \"../renderer/renderer.h\"\n\nnamespace Impacto {\nnamespace Profile {\n\nvoid LoadFonts() {\n  EnsurePushMemberOfType(\"Fonts\", LUA_TTABLE);\n\n  PushInitialIndex();\n  while (PushNextTableElement() != 0) {\n    std::string name(EnsureGetKey<std::string>());\n\n    FontType type = EnsureGetMember<FontType>(\"Type\");\n\n    Font* baseFont = nullptr;\n\n    switch (type) {\n      case FontType::Basic: {\n        BasicFont* font = new BasicFont();\n        Fonts[name] = font;\n        baseFont = font;\n\n        font->Sheet = EnsureGetMember<SpriteSheet>(\"Sheet\");\n\n        break;\n      }\n      case FontType::LB: {\n        LBFont* font = new LBFont();\n        Fonts[name] = font;\n        baseFont = font;\n\n        font->ForegroundSheet = EnsureGetMember<SpriteSheet>(\"ForegroundSheet\");\n        font->OutlineSheet = EnsureGetMember<SpriteSheet>(\"OutlineSheet\");\n\n        if (!TryGetMember<glm::vec2>(\"ForegroundOffset\",\n                                     font->ForegroundOffset)) {\n          font->ForegroundOffset = glm::vec2(0.0f);\n        }\n        font->OutlineOffset = EnsureGetMember<glm::vec2>(\"OutlineOffset\");\n\n        break;\n      }\n    }\n\n    baseFont->Rows = EnsureGetMember<uint8_t>(\"Rows\");\n    baseFont->Columns = EnsureGetMember<uint8_t>(\"Columns\");\n\n    baseFont->CalculateDefaultSizes();\n\n    if (!TryGetMember<float>(\"BitmapEmWidth\", baseFont->BitmapEmWidth)) {\n      baseFont->BitmapEmWidth = baseFont->CellWidth;\n    }\n\n    if (!TryGetMember<float>(\"BitmapEmHeight\", baseFont->BitmapEmHeight)) {\n      baseFont->BitmapEmHeight = baseFont->CellHeight;\n    }\n\n    baseFont->LineSpacing = EnsureGetMember<float>(\"LineSpacing\");\n\n    float advanceWidthsEmWidth = EnsureGetMember<float>(\"AdvanceWidthsEmWidth\");\n    float bitmapEmWidth = baseFont->BitmapEmWidth;\n\n    baseFont->AdvanceWidths.resize(baseFont->Columns * baseFont->Rows *\n                                   sizeof(float));\n\n    float extraLetterSpacing;\n    if (!TryGetMember<float>(\"ExtraLetterSpacing\", extraLetterSpacing)) {\n      extraLetterSpacing = 0;\n    }\n\n    {\n      EnsurePushMember(\"AdvanceWidths\");\n\n      auto widthTablePath = TryGet<Io::AssetPath>();\n      if (widthTablePath) {\n        uint8_t* widthBin;\n        int64_t widthSz;\n        if (widthTablePath->Slurp((void*&)widthBin, widthSz) != IoError_OK) {\n          ImpLog(LogLevel::Fatal, LogChannel::Profile,\n                 \"Failed to load width table file for font {:s}\\n\", name);\n          Window->Shutdown();\n        }\n        assert(widthSz == baseFont->Columns * baseFont->Rows);\n        for (uint16_t i = 0; i < widthSz; i++) {\n          baseFont->AdvanceWidths[i] =\n              ((float)widthBin[i] + extraLetterSpacing) * bitmapEmWidth /\n              advanceWidthsEmWidth;\n        }\n      } else {\n        AssertIs(LUA_TTABLE);\n\n        PushInitialIndex();\n        while (PushNextTableElement() != 0) {\n          int i = EnsureGetKey<int32_t>();\n          baseFont->AdvanceWidths[i] =\n              (EnsureGetArrayElement<float>() + extraLetterSpacing) *\n              bitmapEmWidth / advanceWidthsEmWidth;\n          Pop();\n        }\n      }\n\n      Pop();\n    }\n\n    Pop();\n  }\n\n  Pop();\n}\n\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/fonts.h",
    "content": "#pragma once\n\n#include \"../font.h\"\n#include \"../util.h\"\n#include <ankerl/unordered_dense.h>\n\nnamespace Impacto {\nnamespace Profile {\n\ninline ankerl::unordered_dense::map<std::string, Font*, string_hash,\n                                    std::equal_to<>>\n    Fonts;\n\nvoid LoadFonts();\n\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/game.cpp",
    "content": "#include \"game.h\"\n#include \"profile.h\"\n#include \"profile_internal.h\"\n\n#include <cstdint>\n\nnamespace Impacto {\nnamespace Profile {\n\nvoid LoadGameFromLua() {\n  AssertIs(LUA_TTABLE);\n\n  ActiveRenderer = EnsureGetMember<RendererType>(\"ActiveRenderer\");\n\n  LayerCount = EnsureGetMember<int>(\"LayerCount\");\n  GameFeatures = EnsureGetMember<GameFeature>(\"GameFeatures\");\n  WindowName = EnsureGetMember<char const*>(\"WindowName\");\n  WindowIconPath = TryGetMember<std::string>(\"WindowIconPath\");\n  CursorArrowPath = TryGetMember<std::string>(\"CursorArrowPath\");\n  CursorPointerPath = TryGetMember<std::string>(\"CursorPointerPath\");\n  DesignWidth = EnsureGetMember<float>(\"DesignWidth\");\n  DesignHeight = EnsureGetMember<float>(\"DesignHeight\");\n\n  Language = EnsureGetMember<char const*>(\"Language\");\n  ResolutionWidth = EnsureGetMember<int>(\"ResolutionWidth\");\n  ResolutionHeight = EnsureGetMember<int>(\"ResolutionHeight\");\n  Fullscreen = EnsureGetMember<bool>(\"Fullscreen\");\n  SubtitleConfig = TryGetMember<SubtitleConfigType>(\"SubtitleConfig\")\n                       .value_or(SubtitleConfigType::All);\n  CloseBacklogWhenReachedEnd =\n      TryGetMember<bool>(\"CloseBacklogWhenReachedEnd\").value_or(true);\n  DateFormat =\n      TryGetMember<DateFormatType>(\"DateFormat\").value_or(DateFormatType::YMD);\n  HasScriptedExitLogic =\n      TryGetMember<bool>(\"HasScriptedExitLogic\").value_or(false);\n\n  LayFileBigEndian = TryGetMember<bool>(\"LayFileBigEndian\").value_or(false);\n  CharaIsMvl = TryGetMember<bool>(\"CharaIsMvl\").value_or(false);\n  LayFileTexXMultiplier =\n      TryGetMember<float>(\"LayFileTexXMultiplier\").value_or(1.0f);\n  LayFileTexYMultiplier =\n      TryGetMember<float>(\"LayFileTexYMultiplier\").value_or(1.0f);\n\n  ScreenCaptureCount = TryGetMember<size_t>(\"ScreenCaptureCount\").value_or(0);\n  TryGetMember<bool>(\"UseMoviePriority\", UseMoviePriority);\n  TryGetMember<bool>(\"UseBgChaEffects\", UseBgChaEffects);\n  TryGetMember<bool>(\"UseBgFrameEffects\", UseBgFrameEffects);\n  TryGetMember<bool>(\"UseWaveEffects\", UseWaveEffects);\n\n  ActiveAudioBackend =\n      TryGetMember<AudioBackendType>(\"AudioBackendType\").value_or([] {\n#ifndef IMPACTO_DISABLE_OPENAL\n        return AudioBackendType::OpenAL;\n#else\n        return AudioBackendType::None;\n#endif\n      }());\n\n  VideoPlayer = TryGetMember<VideoPlayerType>(\"VideoPlayerType\").value_or([] {\n#ifndef IMPACTO_DISABLE_FFMPEG\n    return VideoPlayerType::FFmpeg;\n#else\n    return VideoPlayerType::None;\n#endif\n  }());\n\n  TryGetMember<int>(\"PlatformId\", PlatformId);\n}\n\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/game.h",
    "content": "#pragma once\n\n#include \"../game.h\"\n\n#include <cstdint>\n#include <optional>\n#include <string>\n\nnamespace Impacto {\nnamespace Profile {\n\nenum class DateFormatType : uint8_t {\n  DMY,\n  MDY,\n  YMD,\n};\n\nenum class SubtitleConfigType : uint8_t {\n  None = 0,\n  Karaoke = 1 << 0,\n  Translation = 1 << 1,\n  All = 0xFF,\n};\n\nstruct DateFormatDef {\n  DateFormatDef(DateFormatType sel) : Sel(sel) {}\n  std::string_view FormattedString() const {\n    switch (Sel) {\n      case DateFormatType::DMY:\n        return \"{:%d/%m/%y}\";\n      case DateFormatType::MDY:\n        return \"{:%m/%d/%y}\";\n      case DateFormatType::YMD:\n      default:\n        return \"{:%y/%m/%d}\";\n    }\n  }\n  DateFormatType Sel;\n};\n\ninline RendererType ActiveRenderer = RendererType::OpenGL;\ninline VideoPlayerType VideoPlayer = VideoPlayerType::FFmpeg;\ninline AudioBackendType ActiveAudioBackend = AudioBackendType::OpenAL;\n\ninline SubtitleAssBackendType SubtitleAssBackend =\n    SubtitleAssBackendType::LibAss;\ninline SubtitleTextBackendType SubtitleTextBackend =\n    SubtitleTextBackendType::None;\ninline SubtitleBmpBackendType SubtitleBmpBackend = SubtitleBmpBackendType::None;\n\ninline uint32_t LayerCount;\ninline GameFeature GameFeatures;\n\ninline char const* WindowName;\ninline std::optional<std::string> WindowIconPath;\ninline std::optional<std::string> CursorArrowPath;\ninline std::optional<std::string> CursorPointerPath;\n\ninline bool LayFileBigEndian;\ninline bool CharaIsMvl;\n\ninline size_t ScreenCaptureCount = 0;\ninline bool UseMoviePriority = false;\ninline bool UseBgChaEffects = false;\ninline bool UseBgFrameEffects = false;\ninline bool UseWaveEffects = false;\n\ninline float LayFileTexXMultiplier;\ninline float LayFileTexYMultiplier;\n\n// The design coordinate system is: x,y from 0,0 to width,height,\n// origin is top left\ninline float DesignWidth;\ninline float DesignHeight;\n\n// This is for user configuration with realboot\ninline char const* Language;\ninline int ResolutionWidth;\ninline int ResolutionHeight;\ninline bool Fullscreen;\n// TODO Move to \"Patch\" logic\ninline SubtitleConfigType SubtitleConfig = SubtitleConfigType::None;\ninline bool CloseBacklogWhenReachedEnd = true;\ninline DateFormatDef DateFormat = DateFormatType::YMD;\ninline bool HasScriptedExitLogic = false;\n\ninline int PlatformId = 0;\n\nvoid LoadGameFromLua();\n\n}  // namespace Profile\n}  // namespace Impacto\n\ntemplate <>\nstruct magic_enum::customize::enum_range<Impacto::Profile::SubtitleConfigType> {\n  static constexpr bool is_flags = true;\n};"
  },
  {
    "path": "src/profile/games/cc/backlogmenu.cpp",
    "content": "#include \"backlogmenu.h\"\n#include \"../../profile_internal.h\"\n#include \"../../../ui/ui.h\"\n#include \"../../../games/cc/backlogmenu.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace CC {\nnamespace BacklogMenu {\n\nvoid Configure() {\n  BacklogBackgroundRepeatHeight =\n      EnsureGetMember<int>(\"BacklogBackgroundRepeatHeight\");\n\n  BacklogHeaderSprite = EnsureGetMember<Sprite>(\"BacklogHeaderSprite\");\n  BacklogHeaderPosition = EnsureGetMember<glm::vec2>(\"BacklogHeaderPosition\");\n\n  BacklogControlsSprite = EnsureGetMember<Sprite>(\"BacklogControlsSprite\");\n  BacklogControlsPosition =\n      EnsureGetMember<glm::vec2>(\"BacklogControlsPosition\");\n\n  MenuMaskSprite = EnsureGetMember<Sprite>(\"MenuMask\");\n  BacklogMaskSheet = EnsureGetMember<SpriteSheet>(\"BacklogMask\");\n\n  FadeInDirectDuration = EnsureGetMember<float>(\"FadeInDirectDuration\");\n  FadeOutDirectDuration = EnsureGetMember<float>(\"FadeOutDirectDuration\");\n\n  auto drawType = EnsureGetMember<Game::DrawComponentType>(\"DrawType\");\n\n  UI::BacklogMenuPtr = new UI::CC::BacklogMenu();\n  UI::Menus[drawType].push_back(UI::BacklogMenuPtr);\n}\n\n}  // namespace BacklogMenu\n}  // namespace CC\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/cc/backlogmenu.h",
    "content": "#pragma once\n\n#include \"../../../spritesheet.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace CC {\nnamespace BacklogMenu {\n\nvoid Configure();\n\ninline int BacklogBackgroundRepeatHeight;\n\ninline Sprite BacklogHeaderSprite;\ninline glm::vec2 BacklogHeaderPosition;\n\ninline Sprite BacklogControlsSprite;\ninline glm::vec2 BacklogControlsPosition;\n\ninline Sprite MenuMaskSprite;\ninline SpriteSheet BacklogMaskSheet;\n\ninline float FadeInDirectDuration;\ninline float FadeOutDirectDuration;\n\n}  // namespace BacklogMenu\n}  // namespace CC\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/cc/dialoguebox.cpp",
    "content": "#include \"dialoguebox.h\"\n\n#include \"../../profile_internal.h\"\n#include \"../../../hud/cc/nametagdisplay.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace CC {\nnamespace DialogueBox {\n\nvoid Configure() {\n  ADVBoxMask = EnsureGetMember<Sprite>(\"ADVBoxMask\");\n  ADVBoxEffectDuration = EnsureGetMember<float>(\"ADVBoxEffectDuration\");\n}\n\nvoid ConfigureNametag() {\n  NametagMainSprites =\n      EnsureGetMember<std::vector<Sprite>>(\"NametagMainSprites\");\n  NametagLabelSprites =\n      EnsureGetMember<std::vector<Sprite>>(\"NametagLabelSprites\");\n  if (NametagMainSprites.size() != NametagLabelSprites.size()) {\n    throw std::runtime_error(\n        \"Number of nametag main sprites not equal to number of \"\n        \"nametag label sprites\");\n  }\n\n  NametagMainPos = EnsureGetMember<glm::vec2>(\"NametagMainPos\");\n  NametagLabelPos = EnsureGetMember<glm::vec2>(\"NametagLabelPos\");\n\n  NametagShowDuration = EnsureGetMember<float>(\"NametagShowDuration\");\n}\n\n}  // namespace DialogueBox\n}  // namespace CC\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/cc/dialoguebox.h",
    "content": "#pragma once\n\n#include \"../../../spritesheet.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace CC {\nnamespace DialogueBox {\n\nvoid Configure();\nvoid ConfigureNametag();\n\ninline Sprite ADVBoxMask;\n\ninline float ADVBoxEffectDuration;\n\ninline std::vector<Sprite> NametagMainSprites;\ninline std::vector<Sprite> NametagLabelSprites;\n\ninline glm::vec2 NametagMainPos = glm::vec2(0.0f);\ninline glm::vec2 NametagLabelPos = glm::vec2(0.0f);\n\ninline float NametagShowDuration = 0.0f;\n\n}  // namespace DialogueBox\n}  // namespace CC\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/cc/sysmesbox.cpp",
    "content": "#include \"sysmesbox.h\"\n#include \"../../profile_internal.h\"\n\n#include \"../../../ui/ui.h\"\n#include \"../../../games/cc/sysmesbox.h\"\n#include \"../../../game.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace CC {\nnamespace SysMesBox {\n\nvoid Configure() {\n  GetMemberArray<Sprite>(std::span(SumoSealSprites, SealSpriteCount),\n                         \"SumoSealSprites\");\n\n  GetMemberArray<float>(std::span(SumoSealCenterPosX, SealSpriteCount),\n                        \"SumoSealCenterPosX\");\n  GetMemberArray<float>(std::span(SumoSealCenterPosY, SealSpriteCount),\n                        \"SumoSealCenterPosY\");\n  ButtonYes = EnsureGetMember<Sprite>(\"ButtonYes\");\n  ButtonNo = EnsureGetMember<Sprite>(\"ButtonNo\");\n  ButtonOK = EnsureGetMember<Sprite>(\"ButtonOK\");\n  ButtonYesHighlighted = EnsureGetMember<Sprite>(\"ButtonYesHighlighted\");\n  ButtonNoHighlighted = EnsureGetMember<Sprite>(\"ButtonNoHighlighted\");\n  ButtonOKHighlighted = EnsureGetMember<Sprite>(\"ButtonOKHighlighted\");\n  ButtonYesCenterPosX = EnsureGetMember<float>(\"ButtonYesCenterPosX\");\n  ButtonYesCenterPosY = EnsureGetMember<float>(\"ButtonYesCenterPosY\");\n  ButtonNoCenterPosX = EnsureGetMember<float>(\"ButtonNoCenterPosX\");\n  ButtonNoCenterPosY = EnsureGetMember<float>(\"ButtonNoCenterPosY\");\n  ButtonOKCenterPosX = EnsureGetMember<float>(\"ButtonOKCenterPosX\");\n  ButtonOKCenterPosY = EnsureGetMember<float>(\"ButtonOKCenterPosY\");\n  AnimationProgressWidgetsStartOffset =\n      EnsureGetMember<float>(\"AnimationProgressWidgetsStartOffset\");\n  ButtonNoDisplayStart = EnsureGetMember<float>(\"ButtonNoDisplayStart\");\n  ButtonNoAnimationProgressOffset =\n      EnsureGetMember<float>(\"ButtonNoAnimationProgressOffset\");\n  ButtonYesNoScaleMultiplier =\n      EnsureGetMember<float>(\"ButtonYesNoScaleMultiplier\");\n  ButtonOKScaleMultiplier = EnsureGetMember<float>(\"ButtonOKScaleMultiplier\");\n  ButtonScaleMax = EnsureGetMember<float>(\"ButtonScaleMax\");\n  ButtonYesAnimationProgressEnd =\n      EnsureGetMember<float>(\"ButtonYesAnimationProgressEnd\");\n  ButtonYesNoAlphaDivider = EnsureGetMember<float>(\"ButtonYesNoAlphaDivider\");\n  WidgetsAlphaMultiplier = EnsureGetMember<float>(\"WidgetsAlphaMultiplier\");\n\n  ButtonYesHoverBounds = EnsureGetMember<RectF>(\"ButtonYesHoverBounds\");\n  ButtonNoHoverBounds = EnsureGetMember<RectF>(\"ButtonNoHoverBounds\");\n  ButtonOkHoverBounds = EnsureGetMember<RectF>(\"ButtonOkHoverBounds\");\n\n  auto drawType = EnsureGetMember<Game::DrawComponentType>(\"DrawType\");\n\n  UI::SysMesBoxPtr = new UI::CC::SysMesBox();\n  UI::Menus[drawType].push_back(UI::SysMesBoxPtr);\n}\n\n}  // namespace SysMesBox\n}  // namespace CC\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/cc/sysmesbox.h",
    "content": "#pragma once\n\n#include \"../../../spritesheet.h\"\n#include \"../../../games/mo6tw/sysmesbox.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace CC {\nnamespace SysMesBox {\n\nint constexpr SealSpriteCount = 8;\ninline Sprite SumoSealSprites[SealSpriteCount];\n\ninline Sprite ButtonYes;\ninline Sprite ButtonNo;\ninline Sprite ButtonOK;\ninline Sprite ButtonYesHighlighted;\ninline Sprite ButtonNoHighlighted;\ninline Sprite ButtonOKHighlighted;\n\ninline float SumoSealCenterPosX[SealSpriteCount];\ninline float SumoSealCenterPosY[SealSpriteCount];\ninline float ButtonYesCenterPosX;\ninline float ButtonYesCenterPosY;\ninline float ButtonNoCenterPosX;\ninline float ButtonNoCenterPosY;\ninline float ButtonOKCenterPosX;\ninline float ButtonOKCenterPosY;\ninline float AnimationProgressWidgetsStartOffset;\ninline float ButtonNoDisplayStart;\ninline float ButtonNoAnimationProgressOffset;\ninline float ButtonYesNoScaleMultiplier;\ninline float ButtonOKScaleMultiplier;\ninline float ButtonScaleMax;\ninline float ButtonYesAnimationProgressEnd;\ninline float ButtonYesNoAlphaDivider;\ninline float WidgetsAlphaMultiplier;\n\ninline RectF ButtonYesHoverBounds;\ninline RectF ButtonNoHoverBounds;\ninline RectF ButtonOkHoverBounds;\n\nvoid Configure();\n\n}  // namespace SysMesBox\n}  // namespace CC\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/cc/titlemenu.cpp",
    "content": "#include \"titlemenu.h\"\n#include \"../../../log.h\"\n#include \"../../profile_internal.h\"\n\n#include \"../../ui/titlemenu.h\"\n#include \"../../../game.h\"\n#include \"../../../ui/ui.h\"\n#include \"../../../games/cc/titlemenu.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace CC {\nnamespace TitleMenu {\n\nvoid Configure() {\n  BackgroundSprite = EnsureGetMember<Sprite>(\"BackgroundSprite\");\n  FenceSprite = EnsureGetMember<Sprite>(\"FenceSprite\");\n  CopyrightSprite = EnsureGetMember<Sprite>(\"CopyrightSprite\");\n  OverlaySprite = EnsureGetMember<Sprite>(\"OverlaySprite\");\n  SmokeSprite = EnsureGetMember<Sprite>(\"SmokeSprite\");\n  ItemHighlightSprite = EnsureGetMember<Sprite>(\"ItemHighlightSprite\");\n  LoadSprite = EnsureGetMember<Sprite>(\"LoadSprite\");\n  LoadHighlightSprite = EnsureGetMember<Sprite>(\"LoadHighlightSprite\");\n  QuickLoadSprite = EnsureGetMember<Sprite>(\"QuickLoadSprite\");\n  QuickLoadHighlightSprite =\n      EnsureGetMember<Sprite>(\"QuickLoadHighlightSprite\");\n  TipsSprite = EnsureGetMember<Sprite>(\"TipsSprite\");\n  TipsHighlightSprite = EnsureGetMember<Sprite>(\"TipsHighlightSprite\");\n  LibrarySprite = EnsureGetMember<Sprite>(\"LibrarySprite\");\n  LibraryHighlightSprite = EnsureGetMember<Sprite>(\"LibraryHighlightSprite\");\n  EndingListSprite = EnsureGetMember<Sprite>(\"EndingListSprite\");\n  EndingListHighlightSprite =\n      EnsureGetMember<Sprite>(\"EndingListHighlightSprite\");\n  MenuSprite = EnsureGetMember<Sprite>(\"MenuSprite\");\n\n  PressToStartAnimFastDurationIn =\n      EnsureGetMember<float>(\"PressToStartAnimFastDurationIn\");\n  PressToStartAnimFastDurationOut =\n      EnsureGetMember<float>(\"PressToStartAnimFastDurationOut\");\n  BackgroundX = EnsureGetMember<float>(\"BackgroundX\");\n  BackgroundY = EnsureGetMember<float>(\"BackgroundY\");\n  BackgroundBoundsX = EnsureGetMember<float>(\"BackgroundBoundsX\");\n  BackgroundBoundsYNormal = EnsureGetMember<float>(\"BackgroundBoundsYNormal\");\n  BackgroundBoundsYTrue = EnsureGetMember<float>(\"BackgroundBoundsYTrue\");\n  BackgroundBoundsWidth = EnsureGetMember<float>(\"BackgroundBoundsWidth\");\n  BackgroundBoundsHeight = EnsureGetMember<float>(\"BackgroundBoundsHeight\");\n  FenceX = EnsureGetMember<float>(\"FenceX\");\n  FenceY = EnsureGetMember<float>(\"FenceY\");\n  FenceBoundsWidth = EnsureGetMember<float>(\"FenceBoundsWidth\");\n  FenceBoundsHeight = EnsureGetMember<float>(\"FenceBoundsHeight\");\n  FenceBoundsX = EnsureGetMember<float>(\"FenceBoundsX\");\n  FenceBoundsYNormal = EnsureGetMember<float>(\"FenceBoundsYNormal\");\n  FenceBoundsYTrue = EnsureGetMember<float>(\"FenceBoundsYTrue\");\n  CopyrightX = EnsureGetMember<float>(\"CopyrightX\");\n  CopyrightY = EnsureGetMember<float>(\"CopyrightY\");\n  CopyrightXMoveOffset = EnsureGetMember<float>(\"CopyrightXMoveOffset\");\n  SmokeOpacityNormal = EnsureGetMember<float>(\"SmokeOpacityNormal\");\n  SmokeX = EnsureGetMember<float>(\"SmokeX\");\n  SmokeY = EnsureGetMember<float>(\"SmokeY\");\n  SmokeBoundsX = EnsureGetMember<float>(\"SmokeBoundsX\");\n  SmokeBoundsY = EnsureGetMember<float>(\"SmokeBoundsY\");\n  SmokeBoundsWidth = EnsureGetMember<float>(\"SmokeBoundsWidth\");\n  SmokeBoundsHeight = EnsureGetMember<float>(\"SmokeBoundsHeight\");\n  SmokeAnimationBoundsXOffset =\n      EnsureGetMember<float>(\"SmokeAnimationBoundsXOffset\");\n  SmokeAnimationBoundsXMax = EnsureGetMember<float>(\"SmokeAnimationBoundsXMax\");\n  SmokeAnimationDurationIn = EnsureGetMember<float>(\"SmokeAnimationDurationIn\");\n  SmokeAnimationDurationOut =\n      EnsureGetMember<float>(\"SmokeAnimationDurationOut\");\n  MoveLeftAnimationDurationIn =\n      EnsureGetMember<float>(\"MoveLeftAnimationDurationIn\");\n  MoveLeftAnimationDurationOut =\n      EnsureGetMember<float>(\"MoveLeftAnimationDurationOut\");\n  ItemHighlightOffsetX = EnsureGetMember<float>(\"ItemHighlightOffsetX\");\n  ItemHighlightOffsetY = EnsureGetMember<float>(\"ItemHighlightOffsetY\");\n  ItemPadding = EnsureGetMember<float>(\"ItemPadding\");\n  ItemYBase = EnsureGetMember<float>(\"ItemYBase\");\n  SecondaryFirstItemHighlightOffsetX =\n      EnsureGetMember<float>(\"SecondaryFirstItemHighlightOffsetX\");\n  SecondarySecondItemHighlightOffsetX =\n      EnsureGetMember<float>(\"SecondarySecondItemHighlightOffsetX\");\n  SecondaryThirdItemHighlightOffsetX =\n      EnsureGetMember<float>(\"SecondaryThirdItemHighlightOffsetX\");\n  ItemHighlightPointerSprite =\n      EnsureGetMember<Sprite>(\"ItemHighlightPointerSprite\");\n  ItemHighlightPointerY = EnsureGetMember<float>(\"ItemHighlightPointerY\");\n  MenuX = EnsureGetMember<float>(\"MenuX\");\n  MenuY = EnsureGetMember<float>(\"MenuY\");\n\n  UI::CC::TitleMenu* menu = new UI::CC::TitleMenu();\n  menu->PressToStartAnimation.DurationIn =\n      Profile::TitleMenu::PressToStartAnimDurationIn;\n  menu->PressToStartAnimation.DurationOut =\n      Profile::TitleMenu::PressToStartAnimDurationOut;\n  menu->PressToStartAnimation.LoopMode = AnimationLoopMode::ReverseDirection;\n\n  menu->SmokeAnimation.LoopMode = AnimationLoopMode::Loop;\n  menu->SmokeAnimation.DurationIn = SmokeAnimationDurationIn;\n  menu->SmokeAnimation.DurationOut = SmokeAnimationDurationOut;\n  menu->MoveLeftAnimation.DurationIn = MoveLeftAnimationDurationIn;\n  menu->MoveLeftAnimation.DurationOut = MoveLeftAnimationDurationOut;\n  UI::TitleMenuPtr = menu;\n\n  auto drawType = EnsureGetMember<Game::DrawComponentType>(\"DrawType\");\n\n  UI::Menus[drawType].push_back(UI::TitleMenuPtr);\n}\n\n}  // namespace TitleMenu\n}  // namespace CC\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/cc/titlemenu.h",
    "content": "#pragma once\n\n#include \"../../../spritesheet.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace CC {\nnamespace TitleMenu {\n\nvoid Configure();\n\ninline Sprite BackgroundSprite;\ninline Sprite CopyrightSprite;\ninline Sprite FenceSprite;\ninline Sprite OverlaySprite;\ninline Sprite SmokeSprite;\ninline Sprite ItemHighlightSprite;\ninline Sprite ItemHighlightPointerSprite;\ninline Sprite LoadSprite;\ninline Sprite LoadHighlightSprite;\ninline Sprite QuickLoadSprite;\ninline Sprite QuickLoadHighlightSprite;\ninline Sprite TipsSprite;\ninline Sprite TipsHighlightSprite;\ninline Sprite LibrarySprite;\ninline Sprite LibraryHighlightSprite;\ninline Sprite EndingListSprite;\ninline Sprite EndingListHighlightSprite;\ninline Sprite MenuSprite;\n\ninline float PressToStartAnimFastDurationIn;\ninline float PressToStartAnimFastDurationOut;\ninline float BackgroundX;\ninline float BackgroundY;\ninline float BackgroundBoundsX;\ninline float BackgroundBoundsYNormal;\ninline float BackgroundBoundsYTrue;\ninline float BackgroundBoundsWidth;\ninline float BackgroundBoundsHeight;\ninline float FenceX;\ninline float FenceY;\ninline float FenceBoundsWidth;\ninline float FenceBoundsHeight;\ninline float FenceBoundsX;\ninline float FenceBoundsYNormal;\ninline float FenceBoundsYTrue;\ninline float CopyrightX;\ninline float CopyrightY;\ninline float CopyrightXMoveOffset;\ninline float SmokeOpacityNormal;\ninline float SmokeX;\ninline float SmokeY;\ninline float SmokeBoundsX;\ninline float SmokeBoundsY;\ninline float SmokeBoundsWidth;\ninline float SmokeBoundsHeight;\ninline float SmokeAnimationBoundsXOffset;\ninline float SmokeAnimationBoundsXMax;\ninline float SmokeAnimationDurationIn;\ninline float SmokeAnimationDurationOut;\ninline float MoveLeftAnimationDurationIn;\ninline float MoveLeftAnimationDurationOut;\ninline float ItemHighlightOffsetX;\ninline float ItemHighlightOffsetY;\ninline float ItemPadding;\ninline float ItemYBase;\ninline float SecondaryFirstItemHighlightOffsetX;\ninline float SecondarySecondItemHighlightOffsetX;\ninline float SecondaryThirdItemHighlightOffsetX;\ninline float ItemHighlightPointerY;\ninline float MenuX;\ninline float MenuY;\n\n}  // namespace TitleMenu\n}  // namespace CC\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/cclcc/clearlistmenu.cpp",
    "content": "#include \"clearlistmenu.h\"\n#include \"../../profile_internal.h\"\n#include \"../../../ui/ui.h\"\n#include \"../../../games/cclcc/clearlistmenu.h\"\n#include \"../../../game.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace CCLCC {\nnamespace ClearListMenu {\n\nvoid Configure() {\n  ClearListBookLayerSprite =\n      EnsureGetMember<Sprite>(\"ClearListBookLayerSprite\");\n  ClearListGuideSprite = EnsureGetMember<Sprite>(\"ClearListGuideSprite\");\n  ClearListMaskSprite = EnsureGetMember<Sprite>(\"ClearListMaskSprite\");\n\n  GetMemberArray<Sprite>(std::span(EndingSprites, Endings), \"EndingSprites\");\n  EndingSpriteOffsetY = EnsureGetMember<float>(\"EndingSpriteOffsetY\");\n  MenuOffsetY = EnsureGetMember<float>(\"MenuOffsetY\");\n\n  FadeInDuration = EnsureGetMember<float>(\"FadeInDuration\");\n  FadeOutDuration = EnsureGetMember<float>(\"FadeOutDuration\");\n\n  ClearListGuidePosition = EnsureGetMember<glm::vec2>(\"ClearListGuidePosition\");\n\n  auto drawType = EnsureGetMember<Game::DrawComponentType>(\"DrawType\");\n\n  auto clearList = new UI::CCLCC::ClearListMenu();\n  UI::Menus[drawType].push_back(clearList);\n}\n\n}  // namespace ClearListMenu\n}  // namespace CCLCC\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/cclcc/clearlistmenu.h",
    "content": "#pragma once\n\n#include \"../../../spritesheet.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace CCLCC {\nnamespace ClearListMenu {\n\nint constexpr Endings = 10;\n\ninline Sprite ClearListBookLayerSprite;\ninline Sprite ClearListCompleteBookLayerSprite;\ninline Sprite ClearListGuideSprite;\ninline Sprite ClearListMaskSprite;\n\ninline Sprite EndingSprites[Endings];\ninline float EndingSpriteOffsetY;\ninline float MenuOffsetY;\n\ninline float FadeInDuration;\ninline float FadeOutDuration;\ninline glm::vec2 ClearListGuidePosition;\n\nvoid Configure();\n\n}  // namespace ClearListMenu\n}  // namespace CCLCC\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/cclcc/delusiontrigger.cpp",
    "content": "#include \"delusiontrigger.h\"\n#include \"../../profile_internal.h\"\n\n#include \"../../../game.h\"\n#include \"../../../ui/ui.h\"\n#include \"../../../games/chlcc/delusiontrigger.h\"\nnamespace Impacto {\nnamespace Profile {\nnamespace CCLCC {\nnamespace DelusionTrigger {\n\nvoid Configure() {}\n\n}  // namespace DelusionTrigger\n}  // namespace CCLCC\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/cclcc/delusiontrigger.h",
    "content": "#pragma once\n\n#include \"../../../spritesheet.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace CCLCC {\nnamespace DelusionTrigger {\n\nvoid Configure();\n\n}  // namespace DelusionTrigger\n}  // namespace CCLCC\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/cclcc/helpmenu.cpp",
    "content": "#include \"helpmenu.h\"\n#include \"../../profile_internal.h\"\n#include \"../../../ui/ui.h\"\n#include \"../../../games/cclcc/helpmenu.h\"\n#include \"../../../game.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace CCLCC {\nnamespace HelpMenu {\n\nvoid Configure() {\n  FadeInDuration = EnsureGetMember<float>(\"FadeInDuration\");\n  FadeOutDuration = EnsureGetMember<float>(\"FadeOutDuration\");\n  NextPageInDuration = EnsureGetMember<float>(\"NextPageInDuration\");\n  NextPageOutDuration = EnsureGetMember<float>(\"NextPageOutDuration\");\n\n  ManualPages = EnsureGetMember<std::vector<Sprite>>(\"ManualPages\");\n  HelpMaskSprite = EnsureGetMember<Sprite>(\"HelpMaskSprite\");\n\n  auto drawType = EnsureGetMember<Game::DrawComponentType>(\"DrawType\");\n\n  UI::HelpMenuPtr = new UI::CCLCC::HelpMenu();\n  UI::Menus[drawType].push_back(UI::HelpMenuPtr);\n}\n\n}  // namespace HelpMenu\n}  // namespace CCLCC\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/cclcc/helpmenu.h",
    "content": "#pragma once\n\n#include \"../../../spritesheet.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace CCLCC {\nnamespace HelpMenu {\n\ninline std::vector<Sprite> ManualPages;\ninline Sprite HelpMaskSprite;\n\ninline float FadeInDuration;\ninline float FadeOutDuration;\ninline float NextPageInDuration;\ninline float NextPageOutDuration;\n\nvoid Configure();\n\n}  // namespace HelpMenu\n}  // namespace CCLCC\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/cclcc/librarymenu.cpp",
    "content": "#include \"librarymenu.h\"\n#include \"../../profile_internal.h\"\n#include \"../../../ui/ui.h\"\n#include \"../../../games/cclcc/librarymenu.h\"\n#include \"../../../text/text.h\"\n#include \"../../../game.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace CCLCC {\nnamespace LibraryMenu {\n\nstd::vector<AlbumDataEntry> GetAlbumTbl() {\n  std::vector<AlbumDataEntry> albumData;\n\n  EnsurePushMemberOfType(\"AlbumTbl\", LUA_TTABLE);\n  ForEachProfileArray([&](uint32_t rowIndex) {\n    AssertIs(LUA_TTABLE);\n    AlbumDataEntry entry;\n    ForEachProfileArray([&](uint32_t innerRowIndex) {\n      switch (innerRowIndex) {\n        case 0: {\n          entry.PageNumber = EnsureGetArrayElement<uint8_t>();\n        } break;\n        case 1: {\n          entry.IndexInPage = EnsureGetArrayElement<uint8_t>();\n        } break;\n        case 2: {\n          ForEachProfileArray([&](uint32_t variantIndex) {\n            Sprite thumbnail;\n            thumbnail.Sheet = SpriteSheets.at(\"Album\");\n            ForEachProfileArray([&](uint32_t variantSpriteDataIndex) {\n              switch (variantSpriteDataIndex) {\n                case 0:\n                  thumbnail.Bounds.X = EnsureGetArrayElement<float>() * 340 + 1;\n                  break;\n                case 1:\n                  thumbnail.Bounds.Y = EnsureGetArrayElement<float>() * 197 + 1;\n                  break;\n                case 2:\n                  thumbnail.Bounds.Width = EnsureGetArrayElement<float>();\n                  break;\n                case 3:\n                  thumbnail.Bounds.Height = EnsureGetArrayElement<float>();\n                  break;\n              }\n            });\n            entry.ThumbnailSprites.push_back(thumbnail);\n          });\n        } break;\n      }\n    });\n    albumData.push_back(entry);\n  });\n  Pop();\n  return albumData;\n}\n\nvoid Configure() {\n  FadeInDuration = EnsureGetMember<float>(\"FadeInDuration\");\n  FadeOutDuration = EnsureGetMember<float>(\"FadeOutDuration\");\n  ButtonBlinkDuration = EnsureGetMember<float>(\"ButtonBlinkDuration\");\n  ButtonBlinkTintMinimum = EnsureGetMember<float>(\"ButtonBlinkTintMinimum\");\n\n  LibraryTransitionPositionOffset =\n      EnsureGetMember<float>(\"LibraryTransitionPositionOffset\");\n  LibraryBackgroundSprite = EnsureGetMember<Sprite>(\"LibraryBackgroundSprite\");\n  LibraryBackgroundPosition =\n      EnsureGetMember<glm::vec2>(\"LibraryBackgroundPosition\");\n  LibraryIndexSprite = EnsureGetMember<Sprite>(\"LibraryIndexSprite\");\n  LibraryIndexPosition = EnsureGetMember<glm::vec2>(\"LibraryIndexPosition\");\n  LibraryButtonGuidePosition =\n      EnsureGetMember<glm::vec2>(\"LibraryButtonGuidePosition\");\n  LibraryMaskSprite = EnsureGetMember<Sprite>(\"LibraryMaskSprite\");\n  LibraryMaskAlpha = EnsureGetMember<float>(\"LibraryMaskAlpha\");\n\n  auto drawType = EnsureGetMember<Game::DrawComponentType>(\"DrawType\");\n\n  SnapPhotoSpriteHover = EnsureGetMember<Sprite>(\"SnapPhotoSpriteHover\");\n  SnapPhotoSpriteSelect = EnsureGetMember<Sprite>(\"SnapPhotoSpriteSelect\");\n  SnapPhotoPos = EnsureGetMember<glm::vec2>(\"SnapPhotoPos\");\n  HitSongsSpriteHover = EnsureGetMember<Sprite>(\"HitSongsSpriteHover\");\n  HitSongsSpriteSelect = EnsureGetMember<Sprite>(\"HitSongsSpriteSelect\");\n  HitSongsPos = EnsureGetMember<glm::vec2>(\"HitSongsPos\");\n  LoveMovieSpriteHover = EnsureGetMember<Sprite>(\"LoveMovieSpriteHover\");\n  LoveMovieSpriteSelect = EnsureGetMember<Sprite>(\"LoveMovieSpriteSelect\");\n  LoveMoviePos = EnsureGetMember<glm::vec2>(\"LoveMoviePos\");\n\n  AlbumThumbDispPos =\n      EnsureGetMember<std::vector<glm::vec2>>(\"AlbumThumbDispPos\");\n  AlbumData = GetAlbumTbl();\n  AlbumThumbnailPinSprites =\n      EnsureGetMember<std::vector<Sprite>>(\"AlbumThumbnailPinSprites\");\n  AlbumThumbnailThumbSprite =\n      EnsureGetMember<Sprite>(\"AlbumThumbnailThumbSprite\");\n  AlbumPgChangeDuration = EnsureGetMember<float>(\"AlbumPgChangeDuration\");\n  AlbumItemFadeDuration = EnsureGetMember<float>(\"AlbumItemFadeDuration\");\n  AlbumThumbnailPinRemoveOffset =\n      EnsureGetMember<glm::vec2>(\"AlbumThumbnailPinRemoveOffset\");\n  AlbumThumbnailThumbBlinkDuration =\n      EnsureGetMember<float>(\"AlbumThumbnailThumbBlinkDuration\");\n  AlbumModeChangeDuration = EnsureGetMember<float>(\"AlbumModeChangeDuration\");\n  AlbumPageNumberPositions =\n      EnsureGetMember<std::vector<glm::vec2>>(\"AlbumPageNumberPositions\");\n  AlbumPageNumberSprites =\n      EnsureGetMember<std::vector<Sprite>>(\"AlbumPageNumberSprites\");\n  AlbumCameraPageIconSprite =\n      EnsureGetMember<Sprite>(\"AlbumCameraPageIconSprite\");\n  AlbumCameraPageIconPosition =\n      EnsureGetMember<glm::vec2>(\"AlbumCameraPageIconPosition\");\n\n  MusicItemsBackgroundRepeatHeight =\n      EnsureGetMember<int>(\"MusicItemsBackgroundRepeatHeight\");\n  MusicItemsBackgroundSprite =\n      EnsureGetMember<Sprite>(\"MusicItemsBackgroundSprite\");\n  MusicItemsOverlaySprite = EnsureGetMember<Sprite>(\"MusicItemsOverlaySprite\");\n  MusicRenderingBounds = EnsureGetMember<RectF>(\"MusicRenderingBounds\");\n  MusicHoverBounds = EnsureGetMember<RectF>(\"MusicHoverBounds\");\n  MusicPlayIds = EnsureGetMember<std::vector<uint8_t>>(\"MusicPlayIds\");\n  MusicBGMFlagIds = EnsureGetMember<std::vector<uint8_t>>(\"MusicBGMFlagIds\");\n  MusicStringTableId = EnsureGetMember<uint8_t>(\"MusicStringTableId\");\n  MusicStringLockedIndex = EnsureGetMember<int>(\"MusicStringLockedIndex\");\n  MusicTrackNameSize = EnsureGetMember<float>(\"MusicTrackNameSize\");\n  MusicTrackNameOffsetX = EnsureGetMember<int>(\"MusicTrackNameOffsetX\");\n  MusicTrackArtistSize = EnsureGetMember<int>(\"MusicTrackArtistSize\");\n  MusicTrackArtistOffsetX = EnsureGetMember<int>(\"MusicTrackArtistOffsetX\");\n  MusicTrackNumberOffsetX = EnsureGetMember<int>(\"MusicTrackNumberOffsetX\");\n  MusicButtonTextYOffset = EnsureGetMember<int>(\"MusicButtonTextYOffset\");\n  MusicButtonTextColor = EnsureGetMember<uint32_t>(\"MusicButtonTextColor\");\n  MusicButtonTextOutlineColor =\n      EnsureGetMember<uint32_t>(\"MusicButtonTextOutlineColor\");\n  MusicButtonPlayingDispOffset =\n      EnsureGetMember<glm::vec2>(\"MusicButtonPlayingDispOffset\");\n  MusicButtonBounds = EnsureGetMember<RectF>(\"MusicButtonBounds\");\n  MusicDirectionalHoldTime = EnsureGetMember<float>(\"MusicDirectionalHoldTime\");\n  MusicDirectionalFocusTimeInterval =\n      EnsureGetMember<float>(\"MusicDirectionalFocusTimeInterval\");\n  MusicButtonHoverSprite = EnsureGetMember<Sprite>(\"MusicButtonHoverSprite\");\n  MusicButtonSelectSprite = EnsureGetMember<Sprite>(\"MusicButtonSelectSprite\");\n  MusicButtonPlayingSprite =\n      EnsureGetMember<Sprite>(\"MusicButtonPlayingSprite\");\n  MusicNowPlayingNotificationSprite =\n      EnsureGetMember<Sprite>(\"MusicNowPlayingNotificationSprite\");\n\n  MusicNowPlayingNotificationPos =\n      EnsureGetMember<glm::vec2>(\"MusicNowPlayingNotificationPos\");\n  MusicNowPlayingNotificationFadeIn =\n      EnsureGetMember<float>(\"MusicNowPlayingNotificationFadeIn\");\n  MusicNowPlayingNotificationFadeOut =\n      EnsureGetMember<float>(\"MusicNowPlayingNotificationFadeOut\");\n  MusicNowPlayingNotificationTrackOffset =\n      EnsureGetMember<glm::vec2>(\"MusicNowPlayingNotificationTrackOffset\");\n  MusicNowPlayingNotificationTrackFontSize =\n      EnsureGetMember<int>(\"MusicNowPlayingNotificationTrackFontSize\");\n  MusicNowPlayingTextColor =\n      EnsureGetMember<uint32_t>(\"MusicNowPlayingTextColor\");\n  MusicNowPlayingTextOutlineColor =\n      EnsureGetMember<uint32_t>(\"MusicNowPlayingTextOutlineColor\");\n  GetMemberArray<Sprite>(\n      std::span(MusicPlayingModeSprites.data(), MusicPlayingModeSprites.size()),\n      \"MusicPlayingModeSprites\");\n  GetMemberArray<RectF>(std::span(MusicPlayingModeDisplayBounds.data(),\n                                  MusicPlayingModeDisplayBounds.size()),\n                        \"MusicPlayingModeDisplayBounds\");\n\n  MovieDiskSprites = EnsureGetMember<std::vector<Sprite>>(\"MovieDiskSprites\");\n  MovieDiskHighlightSprites =\n      EnsureGetMember<std::vector<Sprite>>(\"MovieDiskHighlightSprites\");\n  MovieDiskDisplayPositions =\n      EnsureGetMember<std::vector<glm::vec2>>(\"MovieDiskDisplayPositions\");\n  SubMenuFadeInDuration = EnsureGetMember<float>(\"SubMenuFadeInDuration\");\n  SubMenuFadeOutDuration = EnsureGetMember<float>(\"SubMenuFadeOutDuration\");\n  MovieDiskPlayIds = EnsureGetMember<std::vector<int>>(\"MovieDiskPlayIds\");\n\n  MovieExtraVideosEnabled =\n      TryGetMember<bool>(\"MovieExtraVideosEnabled\").value_or(false);\n  if (MovieExtraVideosEnabled) {\n    MovieDiskExtraOp = EnsureGetMember<Sprite>(\"MovieDiskExtraOp\");\n    MovieDiskExtraOpHighlight =\n        EnsureGetMember<Sprite>(\"MovieDiskExtraOpHighlight\");\n    MovieDiskExtraOpPlayId = EnsureGetMember<int>(\"MovieDiskExtraOpPlayId\");\n  }\n\n  AlbumMenuGuideSprite = EnsureGetMember<Sprite>(\"AlbumMenuGuideSprite\");\n  AlbumMenuCGViewerGuideSprite =\n      EnsureGetMember<Sprite>(\"AlbumMenuCGViewerGuideSprite\");\n  MusicMenuGuideSprite = EnsureGetMember<Sprite>(\"MusicMenuGuideSprite\");\n  MovieMenuGuideSprite = EnsureGetMember<Sprite>(\"MovieMenuGuideSprite\");\n  GetAlbumTbl();\n\n  UI::LibraryMenuPtr = new UI::CCLCC::LibraryMenu();\n  UI::Menus[drawType].push_back(UI::LibraryMenuPtr);\n  // Don't push library submenus to the main menus list, let library menu\n  // handle it\n  UI::AlbumMenuPtr = new UI::CCLCC::AlbumMenu();\n  UI::MusicMenuPtr = new UI::CCLCC::MusicMenu();\n  UI::MovieMenuPtr = new UI::CCLCC::MovieMenu();\n}\n\n}  // namespace LibraryMenu\n}  // namespace CCLCC\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/cclcc/librarymenu.h",
    "content": "#pragma once\n\n#include \"../../../spritesheet.h\"\n#include <magic_enum/magic_enum.hpp>\n#include <magic_enum/magic_enum_containers.hpp>\n\nnamespace Impacto {\nnamespace Profile {\nnamespace CCLCC {\nnamespace LibraryMenu {\n\nstruct AlbumDataEntry {\n  std::vector<Sprite> ThumbnailSprites;\n  glm::vec2 Position{};\n  uint8_t IndexInPage;\n  uint8_t PageNumber;\n};\n\nenum class LibraryMenuPageType : int {\n  Album,\n  Sound,\n  Movie,\n};\nenum class MusicMenuPlayingMode : int {\n  RepeatOne,\n  PlayAll,\n  RepeatAll,\n  Shuffle,\n};\n\nconstexpr MusicMenuPlayingMode& operator++(MusicMenuPlayingMode& mode) {\n  mode = static_cast<MusicMenuPlayingMode>(\n      (+mode + 1) % magic_enum::enum_count<MusicMenuPlayingMode>());\n  return mode;\n}\n\ninline Sprite LibraryBackgroundSprite;\ninline glm::vec2 LibraryBackgroundPosition;\ninline Sprite LibraryIndexSprite;\ninline glm::vec2 LibraryIndexPosition;\ninline glm::vec2 LibraryButtonGuidePosition;\ninline Sprite LibraryMaskSprite;\ninline float LibraryTransitionPositionOffset;\ninline float LibraryMaskAlpha;\n\ninline Sprite SnapPhotoSpriteHover;\ninline Sprite SnapPhotoSpriteSelect;\ninline glm::vec2 SnapPhotoPos;\ninline Sprite HitSongsSpriteHover;\ninline Sprite HitSongsSpriteSelect;\ninline glm::vec2 HitSongsPos;\ninline Sprite LoveMovieSpriteHover;\ninline Sprite LoveMovieSpriteSelect;\ninline glm::vec2 LoveMoviePos;\n\ninline std::vector<glm::vec2> AlbumThumbDispPos;\ninline std::vector<AlbumDataEntry> AlbumData;\ninline std::vector<Sprite> AlbumThumbnailPinSprites;\ninline Sprite AlbumThumbnailThumbSprite;\ninline float AlbumItemFadeDuration;\ninline float AlbumPgChangeDuration;\ninline glm::vec2 AlbumThumbnailPinRemoveOffset;\ninline float AlbumThumbnailThumbBlinkDuration;\ninline float AlbumModeChangeDuration;\ninline std::vector<glm::vec2> AlbumPageNumberPositions;\ninline std::vector<Sprite> AlbumPageNumberSprites;\ninline Sprite AlbumCameraPageIconSprite;\ninline glm::vec2 AlbumCameraPageIconPosition;\n\ninline int MusicItemsBackgroundRepeatHeight;\ninline Sprite MusicItemsBackgroundSprite;\ninline Sprite MusicItemsOverlaySprite;\ninline glm::vec2 MusicItemsOverlayPosition;\ninline std::vector<uint8_t> MusicPlayIds;\ninline std::vector<uint8_t> MusicBGMFlagIds;\ninline RectF MusicRenderingBounds;\ninline RectF MusicHoverBounds;\ninline uint8_t MusicStringTableId;\ninline int MusicStringLockedIndex;\ninline float MusicTrackNameSize;\ninline int MusicTrackNameOffsetX;\ninline int MusicTrackArtistSize;\ninline int MusicTrackArtistOffsetX;\ninline int MusicTrackNumberOffsetX;\ninline uint32_t MusicButtonTextColor;\ninline uint32_t MusicButtonTextOutlineColor;\ninline int MusicButtonTextYOffset;\ninline glm::vec2 MusicButtonPlayingDispOffset;\ninline float MusicDirectionalHoldTime;\ninline float MusicDirectionalFocusTimeInterval;\ninline RectF MusicButtonBounds;\ninline Sprite MusicButtonHoverSprite;\ninline Sprite MusicButtonSelectSprite;\ninline Sprite MusicButtonPlayingSprite;\ninline Sprite MusicNowPlayingNotificationSprite;\ninline glm::vec2 MusicNowPlayingNotificationPos;\ninline float MusicNowPlayingNotificationFadeIn;\ninline float MusicNowPlayingNotificationFadeOut;\ninline glm::vec2 MusicNowPlayingNotificationTrackOffset;\ninline int MusicNowPlayingNotificationTrackFontSize;\ninline uint32_t MusicNowPlayingTextColor;\ninline uint32_t MusicNowPlayingTextOutlineColor;\ninline magic_enum::containers::array<MusicMenuPlayingMode, Sprite>\n    MusicPlayingModeSprites;\ninline magic_enum::containers::array<MusicMenuPlayingMode, RectF>\n    MusicPlayingModeDisplayBounds;\n\ninline std::vector<Sprite> MovieDiskSprites;\ninline std::vector<Sprite> MovieDiskHighlightSprites;\ninline std::vector<glm::vec2> MovieDiskDisplayPositions;\ninline std::vector<int> MovieDiskPlayIds;\n\ninline bool MovieExtraVideosEnabled;\ninline Sprite MovieDiskExtraOp;\ninline Sprite MovieDiskExtraOpHighlight;\ninline int MovieDiskExtraOpPlayId;\n\ninline Sprite AlbumMenuGuideSprite;\ninline Sprite AlbumMenuCGViewerGuideSprite;\ninline Sprite MusicMenuGuideSprite;\ninline Sprite MovieMenuGuideSprite;\n\ninline float FadeInDuration;\ninline float FadeOutDuration;\ninline float SubMenuFadeInDuration;\ninline float SubMenuFadeOutDuration;\ninline float ButtonBlinkDuration;\ninline float ButtonBlinkTintMinimum;\n\nvoid Configure();\n\n}  // namespace LibraryMenu\n}  // namespace CCLCC\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/cclcc/mapsystem.cpp",
    "content": "#include \"mapsystem.h\"\n#include \"../../profile_internal.h\"\n\n#include \"../../../game.h\"\n#include \"../../../ui/ui.h\"\n#include \"../../../games/cclcc/mapsystem.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace CCLCC {\nnamespace MapSystem {\n\nvoid Configure() {\n  if (!TryPushMember(\"MapSystem\")) return;\n  AssertIs(LUA_TTABLE);\n  MapBgSprite = EnsureGetMember<Sprite>(\"MapBackground\");\n  int MapPartsPhotosNum = EnsureGetMember<int>(\"MapPartsPhotosNum\");\n  GetMemberArray<Sprite>(std::span(MapPartsPhotoSprites, MapPartsPhotosNum),\n                         \"MapPartsPhotoSprites\");\n  SelectedMapPoolTagSprite = EnsureGetMember<Sprite>(\"SelectedMapPoolTag\");\n  int MapPartsArticlesNum = EnsureGetMember<int>(\"MapPartsArticlesNum\");\n  GetMemberArray<Sprite>(std::span(MapPartsArticleSprites, MapPartsArticlesNum),\n                         \"MapPartsArticleSprites\");\n  int MapPartsPinsNum = EnsureGetMember<int>(\"MapPartsPinsNum\");\n  GetMemberArray<Sprite>(std::span(MapPartsPinSprites, MapPartsPinsNum),\n                         \"MapPartsPinSprites\");\n  int MapPartsTagsNum = EnsureGetMember<int>(\"MapPartsTagsNum\");\n  GetMemberArray<Sprite>(std::span(MapPartsTagSprites, MapPartsTagsNum),\n                         \"MapPartsTagSprites\");\n  FadeAnimationDuration = EnsureGetMember<float>(\"FadeAnimationDuration\");\n  MapLine = EnsureGetMember<Sprite>(\"MapLine\");\n  MapLineRed = EnsureGetMember<Sprite>(\"MapLineRed\");\n\n  Pop();\n}\n\n}  // namespace MapSystem\n}  // namespace CCLCC\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/cclcc/mapsystem.h",
    "content": "#pragma once\n#include \"../../../spritesheet.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace CCLCC {\nnamespace MapSystem {\n\nconstexpr int MapPartsPhotoSpritesNumMax = 32;\nconstexpr int MapPartsArticleSpritesNumMax = 16;\nconstexpr int MapPartsPinSpritesNumMax = 12;\nconstexpr int MapPartsTagSpritesNumMax = 16;\n\ninline Sprite MapBgSprite;\ninline Sprite MapPartsPhotoSprites[MapPartsPhotoSpritesNumMax];\ninline Sprite MapPartsArticleSprites[MapPartsArticleSpritesNumMax];\ninline Sprite MapPartsPinSprites[MapPartsPinSpritesNumMax];\ninline Sprite MapPartsTagSprites[MapPartsTagSpritesNumMax];\ninline Sprite SelectedMapPoolTagSprite;\ninline Sprite MapLine;\ninline Sprite MapLineRed;\n\ninline float FadeAnimationDuration;\n\nvoid Configure();\n\n}  // namespace MapSystem\n}  // namespace CCLCC\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/cclcc/optionsmenu.cpp",
    "content": "#include \"optionsmenu.h\"\n#include \"../../profile_internal.h\"\n#include \"../../../log.h\"\n\n#include \"../../ui/optionsmenu.h\"\n#include \"../../../game.h\"\n#include \"../../../ui/ui.h\"\n#include \"../../../games/cclcc/optionsmenu.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace CCLCC {\nnamespace OptionsMenu {\n\nvoid Configure() {\n  BackgroundSprite = EnsureGetMember<Sprite>(\"BackgroundSprite\");\n  BackgroundPosition = EnsureGetMember<glm::vec2>(\"BackgroundPosition\");\n  BackgroundFadeStartPosition =\n      EnsureGetMember<glm::vec2>(\"BackgroundFadeStartPosition\");\n\n  HighlightColor = EnsureGetMember<glm::vec3>(\"HighlightColor\");\n\n  PointerSprite = EnsureGetMember<Sprite>(\"PointerSprite\");\n  PointerOffset = EnsureGetMember<glm::vec2>(\"PointerOffset\");\n\n  HeaderSprite = EnsureGetMember<Sprite>(\"HeaderSprite\");\n  HeaderPosition = EnsureGetMember<glm::vec2>(\"HeaderPosition\");\n  GetMemberArray<Sprite>(std::span(PageHeaderSprites, PageCount),\n                         \"PageHeaderSprites\");\n  PageHeaderPosition = EnsureGetMember<glm::vec2>(\"PageHeaderPosition\");\n\n  PagePanelSprite = EnsureGetMember<Sprite>(\"PagePanelSprite\");\n  PagePanelPosition = EnsureGetMember<glm::vec2>(\"PagePanelPosition\");\n  PagePanelFadeStartPosition =\n      EnsureGetMember<glm::vec2>(\"PagePanelFadeStartPosition\");\n  GetMemberArray<Sprite>(std::span(PagePanelSprites, PagePanelSpriteCount),\n                         \"PagePanelSprites\");\n  GetMemberArray<glm::vec2>(std::span(PagePanelIconOffsets, PageCount),\n                            \"PagePanelIconOffsets\");\n  GetMemberArray<RectF>(std::span(PagePanelHoverBounds, PageCount),\n                        \"PagePanelHoverBounds\");\n  PoleAnimation = EnsureGetMember<SpriteAnimationDef>(\"PoleAnimation\");\n\n  SliderTrackSprite = EnsureGetMember<Sprite>(\"SliderTrackSprite\");\n  SliderTrackOffset = EnsureGetMember<glm::vec2>(\"SliderTrackOffset\");\n  VoiceSliderTrackSprite = EnsureGetMember<Sprite>(\"VoiceSliderTrackSprite\");\n  VoiceSliderOffset = EnsureGetMember<glm::vec2>(\"VoiceSliderOffset\");\n  BinaryBoxSprite = EnsureGetMember<Sprite>(\"BinaryBoxSprite\");\n  BinaryBoxOffset = EnsureGetMember<glm::vec2>(\"BinaryBoxOffset\");\n  SliderSpeed = EnsureGetMember<float>(\"SliderSpeed\");\n\n  SkipReadSprite = EnsureGetMember<Sprite>(\"SkipReadSprite\");\n  SkipAllSprite = EnsureGetMember<Sprite>(\"SkipAllSprite\");\n  OnSprite = EnsureGetMember<Sprite>(\"OnSprite\");\n  OffSprite = EnsureGetMember<Sprite>(\"OffSprite\");\n  YesSprite = EnsureGetMember<Sprite>(\"YesSprite\");\n  NoSprite = EnsureGetMember<Sprite>(\"NoSprite\");\n\n  GuideSprite = EnsureGetMember<Sprite>(\"GuideSprite\");\n  VoiceGuideSprite = EnsureGetMember<Sprite>(\"VoiceGuideSprite\");\n  GuidePosition = EnsureGetMember<glm::vec2>(\"GuidePosition\");\n  GuideFadeStartPosition = EnsureGetMember<glm::vec2>(\"GuideFadeStartPosition\");\n\n  EntriesStartPosition = EnsureGetMember<glm::vec2>(\"EntriesStartPosition\");\n  EntriesVerticalOffset = EnsureGetMember<int>(\"EntriesVerticalOffset\");\n  SoundEntriesStartPosition =\n      EnsureGetMember<glm::vec2>(\"SoundEntriesStartPosition\");\n  SoundEntriesVerticalOffset =\n      EnsureGetMember<int>(\"SoundEntriesVerticalOffset\");\n  VoiceEntriesOffset = EnsureGetMember<glm::vec2>(\"VoiceEntriesOffset\");\n  EntryDimensions = EnsureGetMember<glm::vec2>(\"EntryDimensions\");\n  VoiceEntryDimensions = EnsureGetMember<glm::vec2>(\"VoiceEntryDimensions\");\n\n  GetMemberArray<Sprite>(std::span(LabelSprites, LabelCount), \"LabelSprites\");\n  LabelOffset = EnsureGetMember<glm::vec2>(\"LabelOffset\");\n  GetMemberArray<Sprite>(std::span(NametagSprites, NametagCount),\n                         \"NametagSprites\");\n  NametagOffset = EnsureGetMember<glm::vec2>(\"NametagOffset\");\n  GetMemberArray<Sprite>(std::span(PortraitSprites, PortraitCount),\n                         \"PortraitSprites\");\n  PortraitOffset = EnsureGetMember<glm::vec2>(\"PortraitOffset\");\n  VoicePosition = EnsureGetMember<glm::vec2>(\"VoicePosition\");\n\n  MenuMaskSprite = EnsureGetMember<Sprite>(\"MenuMask\");\n\n  auto drawType = EnsureGetMember<Game::DrawComponentType>(\"DrawType\");\n\n  UI::OptionsMenuPtr = new UI::CCLCC::OptionsMenu();\n  UI::Menus[drawType].push_back(UI::OptionsMenuPtr);\n}\n\n}  // namespace OptionsMenu\n}  // namespace CCLCC\n}  // namespace Profile\n}  // namespace Impacto\n"
  },
  {
    "path": "src/profile/games/cclcc/optionsmenu.h",
    "content": "#pragma once\n\n#include \"../../../spritesheet.h\"\n#include \"../../../spriteanimation.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace CCLCC {\nnamespace OptionsMenu {\n\nint constexpr PageCount = 4;\nint constexpr PagePanelSpriteCount = PageCount * 2;\nint constexpr LabelCount = 13;\nint constexpr NametagCount = 13;\nint constexpr PortraitCount = NametagCount * 2;\n\ninline Sprite BackgroundSprite;\ninline glm::vec2 BackgroundPosition;\ninline glm::vec2 BackgroundFadeStartPosition;\n\ninline glm::vec3 HighlightColor;\n\ninline Sprite PointerSprite;\ninline glm::vec2 PointerOffset;\n\ninline Sprite HeaderSprite;\ninline glm::vec2 HeaderPosition;\ninline Sprite PageHeaderSprites[PageCount];\ninline glm::vec2 PageHeaderPosition;\n\ninline Sprite PagePanelSprite;\ninline glm::vec2 PagePanelPosition;\ninline glm::vec2 PagePanelFadeStartPosition;\ninline Sprite PagePanelSprites[PagePanelSpriteCount];\ninline glm::vec2 PagePanelIconOffsets[PagePanelSpriteCount];\ninline SpriteAnimationDef PoleAnimation;\ninline RectF PagePanelHoverBounds[PageCount];\n\ninline Sprite SliderTrackSprite;\ninline glm::vec2 SliderTrackOffset;\ninline Sprite VoiceSliderTrackSprite;\ninline glm::vec2 VoiceSliderOffset;\ninline Sprite BinaryBoxSprite;\ninline glm::vec2 BinaryBoxOffset;\ninline float SliderSpeed;\n\ninline Sprite SkipReadSprite;\ninline Sprite SkipAllSprite;\ninline Sprite OnSprite;\ninline Sprite OffSprite;\ninline Sprite YesSprite;\ninline Sprite NoSprite;\n\ninline Sprite GuideSprite;\ninline Sprite VoiceGuideSprite;\ninline glm::vec2 GuidePosition;\ninline glm::vec2 GuideFadeStartPosition;\n\ninline glm::vec2 EntriesStartPosition;\ninline int EntriesVerticalOffset;\ninline glm::vec2 SoundEntriesStartPosition;\ninline int SoundEntriesVerticalOffset;\ninline glm::vec2 VoiceEntriesOffset;\ninline glm::vec2 EntryDimensions;\ninline glm::vec2 VoiceEntryDimensions;\n\ninline Sprite LabelSprites[LabelCount];\ninline glm::vec2 LabelOffset;\ninline Sprite NametagSprites[NametagCount];\ninline glm::vec2 NametagOffset;\ninline Sprite PortraitSprites[PortraitCount];\ninline glm::vec2 PortraitOffset;\ninline glm::vec2 VoicePosition;\n\ninline Sprite MenuMaskSprite;\n\nvoid Configure();\n\n}  // namespace OptionsMenu\n}  // namespace CCLCC\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/cclcc/savemenu.cpp",
    "content": "#include \"savemenu.h\"\n#include \"../../profile_internal.h\"\n#include \"../../../log.h\"\n\n#include \"../../ui/savemenu.h\"\n#include \"../../../game.h\"\n#include \"../../../ui/ui.h\"\n#include \"../../../games/cclcc/savemenu.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace CCLCC {\nnamespace SaveMenu {\n\nvoid Configure() {\n  auto drawType = EnsureGetMember<Game::DrawComponentType>(\"DrawType\");\n\n  EntryStartXL = EnsureGetMember<float>(\"EntryStartXL\");\n  EntryStartXR = EnsureGetMember<float>(\"EntryStartXR\");\n  EntryStartYL = EnsureGetMember<float>(\"EntryStartYL\");\n  EntryStartYR = EnsureGetMember<float>(\"EntryStartYR\");\n  EntryYPadding = EnsureGetMember<float>(\"EntryYPadding\");\n  PageSwapDuration = EnsureGetMember<float>(\"PageSwapDuration\");\n\n  GuidePosition = EnsureGetMember<glm::vec2>(\"GuidePosition\");\n  MenuTextPosition = EnsureGetMember<glm::vec2>(\"MenuTextPosition\");\n  SlotsBackgroundPosition =\n      EnsureGetMember<glm::vec2>(\"SlotsBackgroundPosition\");\n  PageNumberPosition = EnsureGetMember<glm::vec2>(\"PageNumberPosition\");\n  SlotLockedSpritePosition =\n      EnsureGetMember<glm::vec2>(\"SlotLockedSpritePosition\");\n  NoDataSpritePosition = EnsureGetMember<glm::vec2>(\"NoDataSpritePosition\");\n\n  SaveMenuMaskSprite = EnsureGetMember<Sprite>(\"SaveMenuMaskSprite\");\n  SaveEntryPrimaryColor = EnsureGetMember<uint32_t>(\"SaveEntryPrimaryColor\");\n  LoadEntryPrimaryColor = EnsureGetMember<uint32_t>(\"LoadEntryPrimaryColor\");\n  SaveEntrySecondaryColor =\n      EnsureGetMember<uint32_t>(\"SaveEntrySecondaryColor\");\n  for (auto [menuType, menuNameView] :\n       magic_enum::enum_entries<UI::SaveMenuPageType>()) {\n    auto menuName = std::string{menuNameView};\n    MenuTextSprite[menuType] =\n        EnsureGetMember<Sprite>((menuName + \"TextSprite\").c_str());\n    EntryHighlightedBoxSprite[menuType] = EnsureGetMember<Sprite>(\n        (menuName + \"EntryHighlightedBoxSprite\").c_str());\n    EntryHighlightedTextSprite[menuType] = EnsureGetMember<Sprite>(\n        (menuName + \"EntryHighlightedTextSprite\").c_str());\n    EntrySlotsSprite[menuType] =\n        EnsureGetMember<Sprite>((menuName + \"EntrySlotsSprite\").c_str());\n    ButtonGuideSprite[menuType] =\n        EnsureGetMember<Sprite>((menuName + \"ButtonGuideSprite\").c_str());\n    SeparationLineSprite[menuType] =\n        EnsureGetMember<Sprite>((menuName + \"SeparationLineSprite\").c_str());\n    for (int j = 0; j < 10; j++) {\n      NumberDigitSprite[menuType][j] = EnsureGetMember<Sprite>(\n          (menuName + \"NumberDigitSprite\" + std::to_string(j)).c_str());\n    }\n    NoDataSprite[menuType] =\n        EnsureGetMember<Sprite>((menuName + \"NoDataEntrySprite\").c_str());\n    BrokenDataSprite[menuType] =\n        EnsureGetMember<Sprite>((menuName + \"BrokenDataEntrySprite\").c_str());\n    SlotLockedSprite[menuType] =\n        EnsureGetMember<Sprite>((menuName + \"SlotLockedSprite\").c_str());\n    for (int j = 0; j < 6; j++) {\n      PageNumSprite[menuType][j] = EnsureGetMember<Sprite>(\n          (menuName + \"PageNumSprite\" + std::to_string(j)).c_str());\n    }\n    SaveTimeSprite[menuType] =\n        EnsureGetMember<Sprite>((menuName + \"SaveTimeSprite\").c_str());\n  }\n\n  UI::SaveMenuPtr = new UI::CCLCC::SaveMenu();\n  UI::Menus[drawType].push_back(UI::SaveMenuPtr);\n}\n}  // namespace SaveMenu\n}  // namespace CCLCC\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/cclcc/savemenu.h",
    "content": "#pragma once\n\n#include \"../../../spritesheet.h\"\n#include \"../../../ui/savemenu.h\"\n#include <magic_enum/magic_enum.hpp>\n#include <magic_enum/magic_enum_containers.hpp>\n\nnamespace Impacto {\nnamespace Profile {\nnamespace CCLCC {\nnamespace SaveMenu {\n\nint constexpr Pages = 6;\n\ninline float EntryStartXL;\ninline float EntryStartXR;\ninline float EntryStartYL;\ninline float EntryStartYR;\ninline float EntryYPadding;\ninline float PageSwapDuration;\ninline glm::vec2 GuidePosition;\ninline glm::vec2 MenuTextPosition;\ninline glm::vec2 SlotsBackgroundPosition;\ninline glm::vec2 PageNumberPosition;\ninline glm::vec2 SlotLockedSpritePosition;\ninline glm::vec2 NoDataSpritePosition;\n\nint constexpr EntriesPerRow = 2;\nint constexpr RowsPerPage = 4;\ninline Sprite SaveMenuMaskSprite;\ninline uint32_t SaveEntryPrimaryColor;\ninline uint32_t LoadEntryPrimaryColor;\ninline uint32_t SaveEntrySecondaryColor;\n\ninline magic_enum::containers::array<UI::SaveMenuPageType, Sprite>\n    MenuTextSprite;\ninline magic_enum::containers::array<UI::SaveMenuPageType, Sprite>\n    EntryHighlightedBoxSprite;\ninline magic_enum::containers::array<UI::SaveMenuPageType, Sprite>\n    EntryHighlightedTextSprite;\ninline magic_enum::containers::array<UI::SaveMenuPageType, Sprite>\n    EntrySlotsSprite;\ninline magic_enum::containers::array<UI::SaveMenuPageType, Sprite>\n    ButtonGuideSprite;\ninline magic_enum::containers::array<UI::SaveMenuPageType, Sprite>\n    SeparationLineSprite;\ninline magic_enum::containers::array<UI::SaveMenuPageType,\n                                     std::array<Sprite, 10>>\n    NumberDigitSprite;\ninline magic_enum::containers::array<UI::SaveMenuPageType, Sprite> NoDataSprite;\ninline magic_enum::containers::array<UI::SaveMenuPageType, Sprite>\n    BrokenDataSprite;\ninline magic_enum::containers::array<UI::SaveMenuPageType, Sprite>\n    SlotLockedSprite;\ninline magic_enum::containers::array<UI::SaveMenuPageType,\n                                     std::array<Sprite, 6>>\n    PageNumSprite;\ninline magic_enum::containers::array<UI::SaveMenuPageType, Sprite>\n    SaveTimeSprite;\n\nvoid Configure();\n\n}  // namespace SaveMenu\n}  // namespace CCLCC\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/cclcc/systemmenu.cpp",
    "content": "#include \"systemmenu.h\"\n#include \"../../profile_internal.h\"\n#include \"../../../renderer/renderer.h\"\n#include \"../../../log.h\"\n\n#include \"../../../games/cclcc/systemmenu.h\"\n#include \"../../ui/systemmenu.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace CCLCC {\nnamespace SystemMenu {\n\nvoid Configure() {\n  MoveInDuration = EnsureGetMember<float>(\"MoveInDuration\");\n  MoveOutDuration = EnsureGetMember<float>(\"MoveOutDuration\");\n  ItemsFadeInDuration = EnsureGetMember<float>(\"ItemsFadeInDuration\");\n  ItemsFadeOutDuration = EnsureGetMember<float>(\"ItemsFadeOutDuration\");\n\n  GetMemberArray<glm::vec2>(\n      std::span(MenuEntriesPositions, Profile::SystemMenu::MenuEntriesNum),\n      \"MenuEntriesPositions\");\n\n  GetMemberArray<RectF>(\n      std::span(MenuEntriesButtonBounds, Profile::SystemMenu::MenuEntriesNum),\n      \"MenuEntriesButtonBounds\");\n\n  SystemMenuBG = EnsureGetMember<Sprite>(\"SystemMenuBG\");\n  SystemMenuFrame = EnsureGetMember<Sprite>(\"SystemMenuFrame\");\n  MenuButtonGuide = EnsureGetMember<Sprite>(\"MenuButtonGuide\");\n  SystemMenuMask = EnsureGetMember<Sprite>(\"SystemMenuMask\");\n\n  BGDispOffsetTopLeft = EnsureGetMember<glm::vec2>(\"BGDispOffsetTopLeft\");\n  BGDispOffsetBottomLeft = EnsureGetMember<glm::vec2>(\"BGDispOffsetBottomLeft\");\n  BGDispOffsetTopRight = EnsureGetMember<glm::vec2>(\"BGDispOffsetTopRight\");\n  BGDispOffsetBottomRight =\n      EnsureGetMember<glm::vec2>(\"BGDispOffsetBottomRight\");\n  FrameOffsetTopLeft = EnsureGetMember<glm::vec2>(\"FrameOffsetTopLeft\");\n  FrameOffsetBottomLeft = EnsureGetMember<glm::vec2>(\"FrameOffsetBottomLeft\");\n  FrameOffsetTopRight = EnsureGetMember<glm::vec2>(\"FrameOffsetTopRight\");\n  FrameOffsetBottomRight = EnsureGetMember<glm::vec2>(\"FrameOffsetBottomRight\");\n\n  AngleMultiplier = EnsureGetMember<glm::vec3>(\"AngleMultiplier\");\n  BGRandPosRange = EnsureGetMember<glm::vec2>(\"BGRandPosRange\");\n\n  BGTranslationOffset = EnsureGetMember<glm::vec2>(\"BGTranslationOffset\");\n\n  auto drawType = EnsureGetMember<Game::DrawComponentType>(\"DrawType\");\n\n  UI::SystemMenuPtr = new UI::CCLCC::SystemMenu();\n  UI::Menus[drawType].push_back(UI::SystemMenuPtr);\n}\n\n}  // namespace SystemMenu\n}  // namespace CCLCC\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/cclcc/systemmenu.h",
    "content": "#pragma once\n\n#include \"../../../spritesheet.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace CCLCC {\nnamespace SystemMenu {\nconstexpr inline int MenuEntriesNumMax = 16;\nconstexpr inline int GridRowCount = 10;\nconstexpr inline int GridColCount = 20;\n\ninline Sprite SystemMenuBG;\ninline Sprite MenuButtonGuide;\ninline Sprite SystemMenuFrame;\ninline Sprite SystemMenuMask;\ninline float MoveInDuration;\ninline float MoveOutDuration;\ninline float ItemsFadeInDuration;\ninline float ItemsFadeOutDuration;\ninline glm::vec2 MenuEntriesPositions[MenuEntriesNumMax];\ninline RectF MenuEntriesButtonBounds[MenuEntriesNumMax];\n\ninline glm::vec2 BGDispOffsetTopLeft;\ninline glm::vec2 BGDispOffsetBottomLeft;\ninline glm::vec2 BGDispOffsetTopRight;\ninline glm::vec2 BGDispOffsetBottomRight;\n\ninline glm::vec2 FrameOffsetTopLeft;\ninline glm::vec2 FrameOffsetBottomLeft;\ninline glm::vec2 FrameOffsetTopRight;\ninline glm::vec2 FrameOffsetBottomRight;\n\ninline glm::vec3 AngleMultiplier;\ninline glm::vec2 BGRandPosRange;\n\ninline glm::vec2 BGTranslationOffset;\nvoid Configure();\n\n}  // namespace SystemMenu\n}  // namespace CCLCC\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/cclcc/tipsmenu.cpp",
    "content": "#include \"tipsmenu.h\"\n#include \"../../profile_internal.h\"\n#include \"../../../log.h\"\n#include \"../../../text/text.h\"\n\n#include \"../../ui/tipsmenu.h\"\n#include \"../../../game.h\"\n#include \"../../../ui/ui.h\"\n#include \"../../../games/cclcc/tipsmenu.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace CCLCC {\nnamespace TipsMenu {\n\nvoid Configure() {\n  TipsGuideSprite = EnsureGetMember<Sprite>(\"TipsGuideSprite\");\n  TipsMaskSprite = EnsureGetMember<Sprite>(\"TipsMaskSprite\");\n  TipsHighlightedSprite = EnsureGetMember<Sprite>(\"TipsHighlightedSprite\");\n  TipsHighlightedTabSprite =\n      EnsureGetMember<Sprite>(\"TipsHighlightedTabSprite\");\n  TipsNewSprite = EnsureGetMember<Sprite>(\"TipsNewSprite\");\n  TipsHighlightedTabAdder = EnsureGetMember<int>(\"TipsHighlightedTabAdder\");\n\n  TipsGuideX = EnsureGetMember<int>(\"TipsGuideX\");\n  TipsGuideY = EnsureGetMember<int>(\"TipsGuideY\");\n\n  TipsTextTableIndex = EnsureGetMember<int>(\"TipsTextTableIndex\");\n  TipsTextSortStringIndex = EnsureGetMember<int>(\"TipsTextSortStringIndex\");\n  TipsTextEntryLockedIndex = EnsureGetMember<int>(\"TipsTextEntryLockedIndex\");\n\n  TipsEntryBounds = EnsureGetMember<RectF>(\"TipsEntryBounds\");\n  TipEntryNewOffset = EnsureGetMember<glm::vec2>(\"TipEntryNewOffset\");\n  TipsEntryHighlightOffset =\n      EnsureGetMember<glm::vec2>(\"TipsEntryHighlightOffset\");\n  TipsEntryNumberOffset = EnsureGetMember<glm::vec2>(\"TipsEntryNumberOffset\");\n  TipsEntryNameOffset = EnsureGetMember<glm::vec2>(\"TipsEntryNameOffset\");\n  TipsEntryNumberFontSize = EnsureGetMember<int>(\"TipsEntryNumberFontSize\");\n  TipsEntryNameFontSize = EnsureGetMember<int>(\"TipsEntryNameFontSize\");\n\n  TipsTabBounds = EnsureGetMember<RectF>(\"TipsTabBounds\");\n  TipsTabNameDisplay = EnsureGetMember<glm::vec2>(\"TipsTabNameDisplay\");\n\n  CategoryPos = EnsureGetMember<glm::vec2>(\"CategoryPos\");\n  CategoryFontSize = EnsureGetMember<int>(\"CategoryFontSize\");\n  NamePos = EnsureGetMember<glm::vec2>(\"NamePos\");\n  NameFontSize = EnsureGetMember<int>(\"NameFontSize\");\n  PronounciationPos = EnsureGetMember<glm::vec2>(\"PronounciationPos\");\n  PronounciationFontSize = EnsureGetMember<int>(\"PronounciationFontSize\");\n  NumberPos = EnsureGetMember<glm::vec2>(\"NumberPos\");\n  NumberFontSize = EnsureGetMember<int>(\"NumberFontSize\");\n\n  TipsEntryNameUnreadColor =\n      EnsureGetMember<uint32_t>(\"TipsEntryNameUnreadColor\");\n  TipsMenuDarkTextColor = EnsureGetMember<uint32_t>(\"TipsMenuDarkTextColor\");\n\n  TipsScrollThumbSprite = EnsureGetMember<Sprite>(\"TipsScrollThumbSprite\");\n  TipsScrollThumbLength = EnsureGetMember<float>(\"TipsScrollThumbLength\");\n  TipsScrollYStart = EnsureGetMember<int>(\"TipsScrollYStart\");\n  TipsScrollYEnd = EnsureGetMember<int>(\"TipsScrollYEnd\");\n  TipsScrollEntriesX = EnsureGetMember<int>(\"TipsScrollEntriesX\");\n  TipsScrollDetailsX = EnsureGetMember<int>(\"TipsScrollDetailsX\");\n\n  TipsMaskSheet = EnsureGetMember<SpriteSheet>(\"TipsMask\");\n\n  TransitionInDuration = EnsureGetMember<float>(\"TransitionInDuration\");\n  TransitionOutDuration = EnsureGetMember<float>(\"TransitionOutDuration\");\n\n  auto drawType = EnsureGetMember<Game::DrawComponentType>(\"DrawType\");\n\n  UI::TipsMenuPtr = new UI::CCLCC::TipsMenu();\n  UI::Menus[drawType].push_back(UI::TipsMenuPtr);\n}\n\n}  // namespace TipsMenu\n}  // namespace CCLCC\n}  // namespace Profile\n}  // namespace Impacto\n"
  },
  {
    "path": "src/profile/games/cclcc/tipsmenu.h",
    "content": "#pragma once\n\n#include \"../../../spritesheet.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace CCLCC {\nnamespace TipsMenu {\n\ninline Sprite TipsGuideSprite;\ninline Sprite TipsMaskSprite;\ninline Sprite TipsHighlightedSprite;\ninline Sprite TipsNewSprite;\n\ninline int TipsGuideX;\ninline int TipsGuideY;\n\ninline int TipsTextTableIndex;\ninline int TipsTextSortStringIndex;\ninline int TipsTextEntryLockedIndex;\n\ninline RectF TipsEntryBounds;\ninline glm::vec2 TipEntryNewOffset;\ninline glm::vec2 TipsEntryNameOffset;\ninline glm::vec2 TipsEntryNumberOffset;\ninline glm::vec2 TipsEntryHighlightOffset;\ninline int TipsEntryNumberFontSize;\ninline int TipsEntryNameFontSize;\ninline RectF TipsTabBounds;\n\ninline Sprite TipsHighlightedTabSprite;\ninline glm::vec2 TipsTabNameDisplay;\ninline int TipsHighlightedTabAdder;\n\ninline glm::vec2 CategoryPos;\ninline int CategoryFontSize;\ninline glm::vec2 NamePos;\ninline int NameFontSize;\ninline glm::vec2 PronounciationPos;\ninline int PronounciationFontSize;\ninline glm::vec2 NumberPos;\ninline int NumberFontSize;\n\ninline uint32_t TipsEntryNameUnreadColor;\ninline uint32_t TipsMenuDarkTextColor;\n\ninline Sprite TipsScrollThumbSprite;\ninline float TipsScrollThumbLength;\ninline int TipsScrollYStart;\ninline int TipsScrollYEnd;\ninline int TipsScrollEntriesX;\ninline int TipsScrollDetailsX;\n\ninline SpriteSheet TipsMaskSheet;\n\ninline float TransitionInDuration;\ninline float TransitionOutDuration;\n\nvoid Configure();\n\n}  // namespace TipsMenu\n}  // namespace CCLCC\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/cclcc/tipsnotification.cpp",
    "content": "#include \"tipsnotification.h\"\n#include \"../../../log.h\"\n#include \"../../profile_internal.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace CCLCC {\nnamespace TipsNotification {\n\nvoid Configure() {\n  NotificationBackground = EnsureGetMember<Sprite>(\"NotificationBackground\");\n\n  BackgroundPositionX = EnsureGetMember<float>(\"BackgroundPositionX\");\n  BackgroundPositionYOffset =\n      EnsureGetMember<float>(\"BackgroundPositionYOffset\");\n  NotificationPositionX = EnsureGetMember<float>(\"NotificationPositionX\");\n  NotificationPositionYOffset =\n      EnsureGetMember<float>(\"NotificationPositionYOffset\");\n  TimerDuration = EnsureGetMember<float>(\"TimerDuration\");\n\n  FontSize = EnsureGetMember<float>(\"FontSize\");\n  TipNameColor =\n      DialogueColorPair{EnsureGetMember<uint32_t>(\"TipNameTextColor\"),\n                        EnsureGetMember<uint32_t>(\"TipNameOutlineColor\")};\n  NotificationTextTableColorIndex =\n      EnsureGetMember<int>(\"NotificationTextTableColorIndex\");\n}\n\n}  // namespace TipsNotification\n}  // namespace CCLCC\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/cclcc/tipsnotification.h",
    "content": "#pragma once\n\n#include \"../../../text/text.h\"\n#include \"../../../spritesheet.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace CCLCC {\nnamespace TipsNotification {\n\nvoid Configure();\n\ninline Sprite NotificationBackground;\n\ninline float BackgroundPositionX;\ninline float BackgroundPositionYOffset;\ninline float NotificationPositionX;\ninline float NotificationPositionYOffset;\ninline float TimerDuration;\ninline float MoveAnimationDuration;\n\ninline DialogueColorPair TipNameColor;\ninline int NotificationTextTableColorIndex;\ninline float FontSize;\n\n}  // namespace TipsNotification\n}  // namespace CCLCC\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/cclcc/titlemenu.cpp",
    "content": "#include \"titlemenu.h\"\n#include \"../../../log.h\"\n#include \"../../profile_internal.h\"\n\n#include \"../../ui/titlemenu.h\"\n#include \"../../../game.h\"\n#include \"../../../ui/ui.h\"\n#include \"../../../games/cclcc/titlemenu.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace CCLCC {\nnamespace TitleMenu {\n\nvoid Configure() {\n  BackgroundSprite = EnsureGetMember<Sprite>(\"BackgroundSprite\");\n  MainBackgroundSprite = EnsureGetMember<Sprite>(\"MainBackgroundSprite\");\n  CopyrightTextSprite = EnsureGetMember<Sprite>(\"CopyrightTextSprite\");\n  OverlaySprite = EnsureGetMember<Sprite>(\"OverlaySprite\");\n  SmokeSprite = EnsureGetMember<Sprite>(\"SmokeSprite\");\n  MenuSprite = EnsureGetMember<Sprite>(\"MenuSprite\");\n  ItemHighlightSprite = EnsureGetMember<Sprite>(\"ItemHighlightSprite\");\n  LoadSprite = EnsureGetMember<Sprite>(\"LoadSprite\");\n  LoadHighlightSprite = EnsureGetMember<Sprite>(\"LoadHighlightSprite\");\n  QuickLoadSprite = EnsureGetMember<Sprite>(\"QuickLoadSprite\");\n  QuickLoadHighlightSprite =\n      EnsureGetMember<Sprite>(\"QuickLoadHighlightSprite\");\n  TipsSprite = EnsureGetMember<Sprite>(\"TipsSprite\");\n  TipsHighlightSprite = EnsureGetMember<Sprite>(\"TipsHighlightSprite\");\n  LibrarySprite = EnsureGetMember<Sprite>(\"LibrarySprite\");\n  LibraryHighlightSprite = EnsureGetMember<Sprite>(\"LibraryHighlightSprite\");\n  EndingListSprite = EnsureGetMember<Sprite>(\"EndingListSprite\");\n  EndingListHighlightSprite =\n      EnsureGetMember<Sprite>(\"EndingListHighlightSprite\");\n  if (HasScriptedExitLogic) {\n    ExitSprite = EnsureGetMember<Sprite>(\"ExitSprite\");\n  }\n\n  PrimaryFadeInDuration = EnsureGetMember<float>(\"PrimaryFadeInDuration\");\n  PrimaryFadeOutDuration = EnsureGetMember<float>(\"PrimaryFadeOutDuration\");\n  SecondaryFadeInDuration = EnsureGetMember<float>(\"SecondaryFadeInDuration\");\n  SecondaryFadeOutDuration = EnsureGetMember<float>(\"SecondaryFadeOutDuration\");\n  CopyrightTextX = EnsureGetMember<float>(\"CopyrightTextX\");\n  CopyrightTextY = EnsureGetMember<float>(\"CopyrightTextY\");\n  SmokeOpacityNormal = EnsureGetMember<float>(\"SmokeOpacityNormal\");\n  SmokeX = EnsureGetMember<float>(\"SmokeX\");\n  SmokeY = EnsureGetMember<float>(\"SmokeY\");\n  SmokeBoundsX = EnsureGetMember<float>(\"SmokeBoundsX\");\n  SmokeBoundsY = EnsureGetMember<float>(\"SmokeBoundsY\");\n  SmokeBoundsWidth = EnsureGetMember<float>(\"SmokeBoundsWidth\");\n  SmokeBoundsHeight = EnsureGetMember<float>(\"SmokeBoundsHeight\");\n  SmokeAnimationBoundsXOffset =\n      EnsureGetMember<float>(\"SmokeAnimationBoundsXOffset\");\n  SmokeAnimationBoundsXMax = EnsureGetMember<float>(\"SmokeAnimationBoundsXMax\");\n  SmokeAnimationDurationIn = EnsureGetMember<float>(\"SmokeAnimationDurationIn\");\n  SmokeAnimationDurationOut =\n      EnsureGetMember<float>(\"SmokeAnimationDurationOut\");\n  MenuX = EnsureGetMember<float>(\"MenuX\");\n  MenuY = EnsureGetMember<float>(\"MenuY\");\n  ItemHighlightOffsetX = EnsureGetMember<float>(\"ItemHighlightOffsetX\");\n  ItemHighlightOffsetY = EnsureGetMember<float>(\"ItemHighlightOffsetY\");\n  ItemPadding = EnsureGetMember<float>(\"ItemPadding\");\n  ItemYBase = EnsureGetMember<float>(\"ItemYBase\");\n  SecondaryFirstItemHighlightOffsetX =\n      EnsureGetMember<float>(\"SecondaryFirstItemHighlightOffsetX\");\n  SecondarySecondItemHighlightOffsetX =\n      EnsureGetMember<float>(\"SecondarySecondItemHighlightOffsetX\");\n  SecondaryThirdItemHighlightOffsetX =\n      EnsureGetMember<float>(\"SecondaryThirdItemHighlightOffsetX\");\n  ItemHighlightPointerSprite =\n      EnsureGetMember<Sprite>(\"ItemHighlightPointerSprite\");\n  ItemHighlightPointerY = EnsureGetMember<float>(\"ItemHighlightPointerY\");\n\n  TitleAnimationDurationIn = EnsureGetMember<float>(\"TitleAnimationDurationIn\");\n  TitleAnimationDurationOut =\n      EnsureGetMember<float>(\"TitleAnimationDurationOut\");\n  TitleAnimationStartFrame = EnsureGetMember<int>(\"TitleAnimationStartFrame\");\n  TitleAnimationFrameCount = EnsureGetMember<int>(\"TitleAnimationFrameCount\");\n  TitleAnimationFileId = EnsureGetMember<int>(\"TitleAnimationFileId\");\n\n  ChoiceBlinkAnimationDurationIn =\n      EnsureGetMember<float>(\"ChoiceBlinkAnimationDurationIn\");\n  SlideItemsAnimationDurationIn =\n      EnsureGetMember<float>(\"SlideItemsAnimationDurationIn\");\n  SlideItemsAnimationDurationOut =\n      EnsureGetMember<float>(\"SlideItemsAnimationDurationOut\");\n  HighlightAnimationDurationIn =\n      EnsureGetMember<float>(\"HighlightAnimationDurationIn\");\n  HighlightAnimationDurationOut =\n      EnsureGetMember<float>(\"HighlightAnimationDurationOut\");\n  ExtraDisabledTint = EnsureGetMember<uint32_t>(\"ExtraDisabledTint\");\n\n  UI::CCLCC::TitleMenu* menu = new UI::CCLCC::TitleMenu();\n  menu->PressToStartAnimation.DurationIn =\n      Profile::TitleMenu::PressToStartAnimDurationIn;\n  menu->PressToStartAnimation.DurationOut =\n      Profile::TitleMenu::PressToStartAnimDurationOut;\n  menu->PressToStartAnimation.LoopMode = AnimationLoopMode::ReverseDirection;\n\n  menu->PrimaryFadeAnimation.DurationIn = PrimaryFadeInDuration;\n  menu->PrimaryFadeAnimation.DurationOut = PrimaryFadeOutDuration;\n  menu->SecondaryFadeAnimation.DurationIn = SecondaryFadeInDuration;\n  menu->SecondaryFadeAnimation.DurationOut = SecondaryFadeOutDuration;\n\n  menu->SmokeAnimation.LoopMode = AnimationLoopMode::Loop;\n  menu->SmokeAnimation.DurationIn = SmokeAnimationDurationIn;\n  menu->SmokeAnimation.DurationOut = SmokeAnimationDurationOut;\n\n  menu->TitleAnimation.DurationIn = TitleAnimationDurationIn;\n  menu->TitleAnimation.DurationOut = TitleAnimationDurationOut;\n\n  menu->TitleAnimationSprite.MountPoint = \"system\";\n  menu->TitleAnimationSprite.LoadAsync(TitleAnimationFileId);\n\n  UI::TitleMenuPtr = menu;\n\n  auto drawType = EnsureGetMember<Game::DrawComponentType>(\"DrawType\");\n\n  UI::Menus[drawType].push_back(UI::TitleMenuPtr);\n}\n\n}  // namespace TitleMenu\n}  // namespace CCLCC\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/cclcc/titlemenu.h",
    "content": "#pragma once\n\n#include \"../../../spritesheet.h\"\n#include \"../../../games/cclcc/titlemenu.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace CCLCC {\nnamespace TitleMenu {\n\ninline Sprite BackgroundSprite;\ninline Sprite MainBackgroundSprite;\ninline Sprite CopyrightTextSprite;\ninline Sprite OverlaySprite;\ninline Sprite SmokeSprite;\ninline Sprite MenuSprite;\ninline Sprite ItemHighlightSprite;\ninline Sprite ItemHighlightPointerSprite;\ninline Sprite LoadSprite;\ninline Sprite LoadHighlightSprite;\ninline Sprite QuickLoadSprite;\ninline Sprite QuickLoadHighlightSprite;\ninline Sprite TipsSprite;\ninline Sprite TipsHighlightSprite;\ninline Sprite LibrarySprite;\ninline Sprite LibraryHighlightSprite;\ninline Sprite EndingListSprite;\ninline Sprite EndingListHighlightSprite;\ninline Sprite ExitSprite;\n\ninline float PrimaryFadeInDuration;\ninline float PrimaryFadeOutDuration;\ninline float SecondaryFadeInDuration;\ninline float SecondaryFadeOutDuration;\ninline float CopyrightTextX;\ninline float CopyrightTextY;\ninline float SmokeOpacityNormal;\ninline float SmokeX;\ninline float SmokeY;\ninline float SmokeBoundsX;\ninline float SmokeBoundsY;\ninline float SmokeBoundsWidth;\ninline float SmokeBoundsHeight;\ninline float SmokeAnimationBoundsXOffset;\ninline float SmokeAnimationBoundsXMax;\ninline float SmokeAnimationDurationIn;\ninline float SmokeAnimationDurationOut;\ninline float MenuX;\ninline float MenuY;\ninline float ItemHighlightOffsetX;\ninline float ItemHighlightOffsetY;\ninline float ItemPadding;\ninline float ItemYBase;\ninline float SecondaryFirstItemHighlightOffsetX;\ninline float SecondarySecondItemHighlightOffsetX;\ninline float SecondaryThirdItemHighlightOffsetX;\ninline float ItemHighlightPointerY;\ninline float TitleAnimationDurationIn;\ninline float TitleAnimationDurationOut;\ninline float ChoiceBlinkAnimationDurationIn;\ninline float SlideItemsAnimationDurationIn;\ninline float SlideItemsAnimationDurationOut;\ninline float HighlightAnimationDurationIn;\ninline float HighlightAnimationDurationOut;\ninline uint32_t ExtraDisabledTint;\ninline int TitleAnimationStartFrame;\ninline int TitleAnimationFrameCount;\ninline int TitleAnimationFileId;\n\nvoid Configure();\n\n}  // namespace TitleMenu\n}  // namespace CCLCC\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/cclcc/yesnotrigger.cpp",
    "content": "#include \"yesnotrigger.h\"\n#include \"../../../log.h\"\n#include \"../../profile_internal.h\"\n#include \"../../../renderer/renderer.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace CCLCC {\nnamespace YesNoTrigger {\n\nbool Configure() {\n  if (!TryPushMember(\"YesNoTrigger\")) return false;\n  AssertIs(LUA_TTABLE);\n\n  YesNoBackground0 = EnsureGetMember<Sprite>(\"YesNoBackground0\");\n  YesNoBackground1 = EnsureGetMember<Sprite>(\"YesNoBackground1\");\n  YesNoBackground2 = EnsureGetMember<Sprite>(\"YesNoBackground2\");\n  YesNoBackground3 = EnsureGetMember<Sprite>(\"YesNoBackground3\");\n  YNChipBlurBg0 = EnsureGetMember<Sprite>(\"YNChipBlurBg0\");\n  YNChipBlurBg1 = EnsureGetMember<Sprite>(\"YNChipBlurBg1\");\n  YNChipBlurBg2 = EnsureGetMember<Sprite>(\"YNChipBlurBg2\");\n  YN1YesChipSmall = EnsureGetMember<Sprite>(\"YN1ChipYesS\");\n  YN1YesChipLarge = EnsureGetMember<Sprite>(\"YN1ChipYesL\");\n  YN1NoChipSmall = EnsureGetMember<Sprite>(\"YN1ChipNoS\");\n  YN1NoChipLarge = EnsureGetMember<Sprite>(\"YN1ChipNoL\");\n  YN2YesChipSmall = EnsureGetMember<Sprite>(\"YN2ChipYesS\");\n  YN2YesChipLarge = EnsureGetMember<Sprite>(\"YN2ChipYesL\");\n  YN2NoChipSmall = EnsureGetMember<Sprite>(\"YN2ChipNoS\");\n  YN2NoChipLarge = EnsureGetMember<Sprite>(\"YN2ChipNoL\");\n\n  StarChip = EnsureGetMember<Sprite>(\"ChipStar\");\n  YesNoBlurMask = EnsureGetMember<Sprite>(\"YNBlurMask\");\n  YesNoBgOverlay = EnsureGetMember<Sprite>(\"YNBgOverlay\");\n  StarRotationPeriod = EnsureGetMember<float>(\"StarRotationPeriod\");\n\n  {\n    EnsurePushMemberOfType(\"YesNoData1\", LUA_TTABLE);\n\n    PushInitialIndex();\n\n    while (PushNextTableElement()) {\n      AssertIs(LUA_TTABLE);\n      int i = EnsureGetKey<uint32_t>() - 1;\n      YesNoData1[i].BgPos = EnsureGetMember<glm::vec2>(\"bgPos\");\n      YesNoData1[i].NextYesIndex = EnsureGetMember<uint32_t>(\"index\");\n      YesNoData1[i].NextNoIndex = EnsureGetMember<uint32_t>(\"index2\");\n      YesNoData1[i].ChipYesPos = EnsureGetMember<glm::vec2>(\"yesChipPos\");\n      YesNoData1[i].ChipNoPos = EnsureGetMember<glm::vec2>(\"noChipPos\");\n      YesNoData1[i].BlurMaskPos = EnsureGetMember<glm::vec2>(\"bubblePos\");\n      Pop();\n    }\n\n    Pop();\n  }\n\n  {\n    EnsurePushMemberOfType(\"YesNoData2\", LUA_TTABLE);\n\n    PushInitialIndex();\n\n    while (PushNextTableElement()) {\n      AssertIs(LUA_TTABLE);\n      int i = EnsureGetKey<uint32_t>() - 1;\n      YesNoData2[i].BgPos = EnsureGetMember<glm::vec2>(\"bgPos\");\n      YesNoData2[i].NextYesIndex = EnsureGetMember<uint32_t>(\"index\");\n      YesNoData2[i].NextNoIndex = EnsureGetMember<uint32_t>(\"index2\");\n      YesNoData2[i].ChipYesPos = EnsureGetMember<glm::vec2>(\"yesChipPos\");\n      YesNoData2[i].ChipNoPos = EnsureGetMember<glm::vec2>(\"noChipPos\");\n      YesNoData2[i].BlurMaskPos = EnsureGetMember<glm::vec2>(\"bubblePos\");\n      Pop();\n    }\n\n    Pop();\n  }\n\n  Pop();\n  return true;\n}\n\n}  // namespace YesNoTrigger\n}  // namespace CCLCC\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/cclcc/yesnotrigger.h",
    "content": "#pragma once\n#include \"../../../games/cclcc/yesnotrigger.h\"\n#include \"../../../spritesheet.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace CCLCC {\nnamespace YesNoTrigger {\n\nbool Configure();\n\nconstexpr int BackgroundPositionsNum = 8;\nconstexpr int YesNoDataSize = 20;\n\ninline Sprite YesNoBackground0;\ninline Sprite YesNoBackground1;\ninline Sprite YesNoBackground2;\ninline Sprite YesNoBackground3;\ninline Sprite YNChipBlurBg0;\ninline Sprite YNChipBlurBg1;\ninline Sprite YNChipBlurBg2;\ninline Sprite YN1YesChipSmall;\ninline Sprite YN1YesChipLarge;\ninline Sprite YN1NoChipSmall;\ninline Sprite YN1NoChipLarge;\ninline Sprite YN2YesChipSmall;\ninline Sprite YN2YesChipLarge;\ninline Sprite YN2NoChipSmall;\ninline Sprite YN2NoChipLarge;\ninline Sprite StarChip;\ninline Sprite YesNoBlurMask;\ninline Sprite YesNoBgOverlay;\ninline float StarRotationPeriod;\n\ninline glm::vec2 BackgroundPositions[BackgroundPositionsNum];\ninline UI::CCLCC::YesNoTrigger::YesNoPositions YesNoData1[YesNoDataSize];\ninline UI::CCLCC::YesNoTrigger::YesNoPositions YesNoData2[YesNoDataSize];\n\n}  // namespace YesNoTrigger\n}  // namespace CCLCC\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/chlcc/albummenu.cpp",
    "content": "#include \"albummenu.h\"\n#include \"../../profile_internal.h\"\n#include \"../../../ui/ui.h\"\n\n#include \"../../../games/chlcc/albummenu.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace CHLCC {\nnamespace AlbumMenu {\n\nvoid Configure() {\n  BackgroundColor = EnsureGetMember<uint32_t>(\"BackgroundColor\");\n  CircleSprite = EnsureGetMember<Sprite>(\"CircleSprite\");\n  CGList = EnsureGetMember<Sprite>(\"CGList\");\n  CGListPosition = EnsureGetMember<glm::vec2>(\"CGListPosition\");\n  PageCountLabel = EnsureGetMember<Sprite>(\"PageCountLabel\");\n  PageLabelPosition = EnsureGetMember<glm::vec2>(\"PageLabelPosition\");\n  CGBox = EnsureGetMember<Sprite>(\"CGBox\");\n  CGBoxTemplatePosition = EnsureGetMember<glm::vec2>(\"CGBoxTemplatePosition\");\n  AlbumThumbnails =\n      EnsureGetMember<decltype(AlbumThumbnails)>(\"AlbumThumbnails\");\n  ThumbnailTemplatePosition =\n      EnsureGetMember<glm::vec2>(\"ThumbnailTemplatePosition\");\n  VariationUnlocked = EnsureGetMember<Sprite>(\"VariationUnlocked\");\n  VariationLocked = EnsureGetMember<Sprite>(\"VariationLocked\");\n  VariationTemplateOffset =\n      EnsureGetMember<glm::vec2>(\"VariationTemplateOffset\");\n  LockedCG = EnsureGetMember<Sprite>(\"LockedCG\");\n  ThumbnailOffset = EnsureGetMember<glm::vec2>(\"ThumbnailOffset\");\n  ThumbnailHighlight = EnsureGetMember<Sprite>(\"ThumbnailHighlight\");\n  AlbumPages = EnsureGetMember<int>(\"AlbumPages\");\n  EntriesPerPage = EnsureGetMember<int>(\"EntriesPerPage\");\n  GetMemberArray<Sprite>(std::span(PageNums, 10), \"PageNums\");\n  CurrentPageNumPos = EnsureGetMember<glm::vec2>(\"CurrentPageNumPos\");\n  MaxPageNumPos = EnsureGetMember<glm::vec2>(\"MaxPageNumPos\");\n  PageNumSeparatorSlash = EnsureGetMember<Sprite>(\"PageNumSeparatorSlash\");\n  PageNumSeparatorSlashPos =\n      EnsureGetMember<glm::vec2>(\"PageNumSeparatorSlashPos\");\n  GetMemberArray<Sprite>(std::span(ReachablePageNums, 10), \"ReachablePageNums\");\n  ButtonGuide = EnsureGetMember<Sprite>(\"ButtonGuide\");\n  ButtonGuidePos = EnsureGetMember<glm::vec2>(\"ButtonGuidePos\");\n  SelectDataSprites = EnsureGetMember<std::vector<Sprite>>(\"SelectDataSprites\");\n  SelectDataPos = EnsureGetMember<std::vector<glm::vec2>>(\"SelectDataPos\");\n  if (SelectDataSprites.size() != SelectDataPos.size()) {\n    throw std::runtime_error(\"Related arrays have mismatching sizes\");\n  }\n  AlbumMenuTitle = EnsureGetMember<Sprite>(\"AlbumMenuTitle\");\n  AlbumMenuTitleRightPos = EnsureGetMember<glm::vec2>(\"AlbumMenuTitleRightPos\");\n  AlbumMenuTitleLeftPos = EnsureGetMember<glm::vec2>(\"AlbumMenuTitleLeftPos\");\n  AlbumMenuTitleAngle = EnsureGetMember<float>(\"AlbumMenuTitleAngle\");\n  CgViewerButtonGuideVariation =\n      EnsureGetMember<Sprite>(\"CgViewerButtonGuideVariation\");\n  CgViewerButtonGuideNoVariation =\n      EnsureGetMember<Sprite>(\"CgViewerButtonGuideNoVariation\");\n  CgViewerButtonGuidePos = EnsureGetMember<glm::vec2>(\"CgViewerButtonGuidePos\");\n  SelectionMarkerSprite = EnsureGetMember<Sprite>(\"SelectionMarkerSprite\");\n  SelectionMarkerRelativePos =\n      EnsureGetMember<glm::vec2>(\"SelectionMarkerRelativePos\");\n  CgFadeDuration = EnsureGetMember<float>(\"CgFadeDuration\");\n\n  auto drawType = EnsureGetMember<Game::DrawComponentType>(\"DrawType\");\n\n  UI::AlbumMenuPtr = new UI::CHLCC::AlbumMenu();\n  UI::Menus[drawType].push_back(UI::AlbumMenuPtr);\n}\n\n}  // namespace AlbumMenu\n}  // namespace CHLCC\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/chlcc/albummenu.h",
    "content": "#pragma once\n\n#include \"../../../spritesheet.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace CHLCC {\nnamespace AlbumMenu {\n\ninline uint32_t BackgroundColor;\ninline Sprite CircleSprite;\ninline Sprite CGList;\ninline glm::vec2 CGListPosition;\ninline Sprite PageCountLabel;\ninline glm::vec2 PageLabelPosition;\ninline Sprite CGBox;\ninline glm::vec2 CGBoxTemplatePosition;\ninline std::array<Sprite, 63> AlbumThumbnails;\ninline glm::vec2 ThumbnailTemplatePosition;\ninline Sprite VariationUnlocked;\ninline Sprite VariationLocked;\ninline glm::vec2 VariationTemplateOffset;\ninline Sprite LockedCG;\ninline glm::vec2 ThumbnailOffset;\ninline Sprite ThumbnailHighlight;\ninline int32_t AlbumPages;\ninline int32_t EntriesPerPage;\ninline Sprite PageNums[10];\ninline glm::vec2 CurrentPageNumPos;\ninline glm::vec2 MaxPageNumPos;\ninline Sprite PageNumSeparatorSlash;\ninline glm::vec2 PageNumSeparatorSlashPos;\ninline Sprite ReachablePageNums[10];\ninline Sprite ButtonGuide;\ninline glm::vec2 ButtonGuidePos;\ninline std::vector<Sprite> SelectDataSprites;\ninline std::vector<glm::vec2> SelectDataPos;\ninline Sprite AlbumMenuTitle;\ninline glm::vec2 AlbumMenuTitleRightPos;\ninline glm::vec2 AlbumMenuTitleLeftPos;\ninline float AlbumMenuTitleAngle;\ninline Sprite CgViewerButtonGuideVariation;\ninline Sprite CgViewerButtonGuideNoVariation;\ninline glm::vec2 CgViewerButtonGuidePos;\ninline Sprite SelectionMarkerSprite;\ninline glm::vec2 SelectionMarkerRelativePos;\ninline float CgFadeDuration;\n\nvoid Configure();\n\n}  // namespace AlbumMenu\n}  // namespace CHLCC\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/chlcc/backlogmenu.cpp",
    "content": "#include \"backlogmenu.h\"\n#include \"../../profile_internal.h\"\n#include \"../../../ui/ui.h\"\n#include \"../../../games/chlcc/backlogmenu.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace CHLCC {\nnamespace BacklogMenu {\n\nvoid Configure() {\n  BackgroundColor = EnsureGetMember<uint32_t>(\"BackgroundColor\");\n  CircleSprite = EnsureGetMember<Sprite>(\"CircleSprite\");\n  MenuTitleTextRightPosition =\n      EnsureGetMember<glm::vec2>(\"MenuTitleTextRightPosition\");\n  MenuTitleTextLeftPosition =\n      EnsureGetMember<glm::vec2>(\"MenuTitleTextLeftPosition\");\n  MenuTitleTextAngle = EnsureGetMember<float>(\"MenuTitleTextAngle\");\n  MenuTitleText = EnsureGetMember<Sprite>(\"MenuTitleText\");\n\n  ButtonPromptPosition = EnsureGetMember<glm::vec2>(\"ButtonPromptPosition\");\n  ButtonPromptSprite = EnsureGetMember<Sprite>(\"BacklogButtonPromptSprite\");\n  BacklogBackgroundSprite = EnsureGetMember<Sprite>(\"BacklogBackgroundSprite\");\n  EntryHighlightSprite = EnsureGetMember<Sprite>(\"EntryHighlightSprite\");\n  VoiceIconSprite = EnsureGetMember<Sprite>(\"VoiceIconSprite\");\n  ScrollbarThumbSprite = EnsureGetMember<Sprite>(\"ScrollbarThumbSprite\");\n  ScrollbarPosition = EnsureGetMember<glm::vec2>(\"ScrollbarPosition\");\n  EntriesStart = EnsureGetMember<glm::vec2>(\"EntriesStart\");\n  RenderingBounds = EnsureGetMember<RectF>(\"RenderingBounds\");\n  EntryYPadding = EnsureGetMember<float>(\"EntryYPadding\");\n\n  auto drawType = EnsureGetMember<Game::DrawComponentType>(\"DrawType\");\n\n  UI::BacklogMenuPtr = new UI::CHLCC::BacklogMenu();\n  UI::Menus[drawType].push_back(UI::BacklogMenuPtr);\n}\n\n}  // namespace BacklogMenu\n}  // namespace CHLCC\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/chlcc/backlogmenu.h",
    "content": "#pragma once\n\n#include \"../../../spritesheet.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace CHLCC {\nnamespace BacklogMenu {\n\nvoid Configure();\n\ninline uint32_t BackgroundColor;\ninline Sprite CircleSprite;\ninline glm::vec2 MenuTitleTextRightPosition;\ninline glm::vec2 MenuTitleTextLeftPosition;\ninline float MenuTitleTextAngle;\ninline Sprite MenuTitleText;\n\ninline glm::vec2 ButtonPromptPosition;\ninline Sprite ButtonPromptSprite;\ninline Sprite BacklogBackgroundSprite;\ninline Sprite EntryHighlightSprite;\ninline Sprite VoiceIconSprite;\ninline Sprite ScrollbarThumbSprite;\ninline glm::vec2 ScrollbarPosition;\ninline glm::vec2 EntriesStart;\ninline RectF RenderingBounds;\ninline float EntryYPadding;\n\n}  // namespace BacklogMenu\n}  // namespace CHLCC\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/chlcc/clearlistmenu.cpp",
    "content": "#include \"clearlistmenu.h\"\n#include \"../../profile_internal.h\"\n#include \"../../../ui/ui.h\"\n\n#include \"../../../games/chlcc/clearlistmenu.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace CHLCC {\nnamespace ClearListMenu {\n\nvoid Configure() {\n  BackgroundColor = EnsureGetMember<uint32_t>(\"BackgroundColor\");\n  CircleSprite = EnsureGetMember<Sprite>(\"CircleSprite\");\n  MenuTitleTextRightPosition =\n      EnsureGetMember<glm::vec2>(\"MenuTitleTextRightPos\");\n  MenuTitleTextLeftPosition =\n      EnsureGetMember<glm::vec2>(\"MenuTitleTextLeftPos\");\n  MenuTitleTextAngle = EnsureGetMember<float>(\"MenuTitleTextAngle\");\n  MenuTitleText = EnsureGetMember<Sprite>(\"MenuTitleText\");\n\n  LabelPosition = EnsureGetMember<glm::vec2>(\"LabelPosition\");\n  ClearListLabel = EnsureGetMember<Sprite>(\"ClearListLabel\");\n  GetMemberArray<glm::vec2>(std::span(TimePositions, 6), \"TimePositions\");\n  EndingCountPosition = EnsureGetMember<glm::vec2>(\"EndingCountPosition\");\n  GetMemberArray<glm::vec2>(std::span(TIPSCountPositions, 3),\n                            \"TIPSCountPositions\");\n  GetMemberArray<Sprite>(std::span(Digits, 10), \"Digits\");\n  GetMemberArray<glm::vec2>(std::span(AlbumPositions, 3), \"AlbumPositions\");\n  ListPosition = EnsureGetMember<glm::vec2>(\"ListPosition\");\n  EndingList = EnsureGetMember<Sprite>(\"EndingList\");\n  GetMemberArray<glm::vec2>(std::span(BoxPositions, Endings), \"BoxPositions\");\n  EndingBox = EnsureGetMember<Sprite>(\"EndingBox\");\n  GetMemberArray<glm::vec2>(std::span(ThumbnailPositions, Endings),\n                            \"ThumbnailPositions\");\n  GetMemberArray<Sprite>(std::span(EndingThumbnails, Endings),\n                         \"EndingThumbnails\");\n  LockedThumbnail = EnsureGetMember<Sprite>(\"LockedThumbnail\");\n  ButtonPromptPosition = EnsureGetMember<glm::vec2>(\"ButtonPromptPosition\");\n  ButtonPromptSprite = EnsureGetMember<Sprite>(\"ButtonPromptSprite\");\n\n  auto drawType = EnsureGetMember<Game::DrawComponentType>(\"DrawType\");\n\n  auto clearList = new UI::CHLCC::ClearListMenu();\n  UI::Menus[drawType].push_back(clearList);\n}\n\n}  // namespace ClearListMenu\n}  // namespace CHLCC\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/chlcc/clearlistmenu.h",
    "content": "#pragma once\n\n#include \"../../../spritesheet.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace CHLCC {\nnamespace ClearListMenu {\n\nint constexpr Endings = 8;\n\ninline uint32_t BackgroundColor;\ninline Sprite CircleSprite;\ninline glm::vec2 MenuTitleTextRightPosition;\ninline glm::vec2 MenuTitleTextLeftPosition;\ninline float MenuTitleTextAngle;\ninline Sprite MenuTitleText;\n\ninline glm::vec2 LabelPosition;\ninline Sprite ClearListLabel;\ninline glm::vec2 TimePositions[6];\ninline glm::vec2 EndingCountPosition;\ninline glm::vec2 TIPSCountPositions[3];\ninline glm::vec2 AlbumPositions[3];\ninline Sprite Digits[10];\ninline glm::vec2 ListPosition;\ninline Sprite EndingList;\ninline glm::vec2 BoxPositions[Endings];\ninline Sprite EndingBox;\ninline glm::vec2 ThumbnailPositions[Endings];\ninline Sprite EndingThumbnails[Endings];\ninline Sprite LockedThumbnail;\ninline glm::vec2 ButtonPromptPosition;\ninline Sprite ButtonPromptSprite;\n\nvoid Configure();\n\n}  // namespace ClearListMenu\n}  // namespace CHLCC\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/chlcc/commonmenu.cpp",
    "content": "#include \"commonmenu.h\"\n#include \"../../profile_internal.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace CHLCC {\nnamespace CommonMenu {\n\nvoid Configure() {\n  TitleFadeInDuration = EnsureGetMember<float>(\"TitleFadeInDuration\");\n  TitleFadeOutDuration = EnsureGetMember<float>(\"TitleFadeOutDuration\");\n\n  CircleStartPosition = EnsureGetMember<glm::vec2>(\"CircleStartPosition\");\n  CircleOffset = EnsureGetMember<float>(\"CircleOffset\");\n\n  ErinPosition = EnsureGetMember<glm::vec2>(\"ErinPosition\");\n  ErinSprite = EnsureGetMember<Sprite>(\"ErinSprite\");\n\n  BackgroundFilter = EnsureGetMember<Sprite>(\"BackgroundFilter\");\n\n  InitialRedBarPosition = EnsureGetMember<glm::vec2>(\"InitialRedBarPosition\");\n  RightRedBarPosition = EnsureGetMember<glm::vec2>(\"RightRedBarPosition\");\n  InitialRedBarSprite = EnsureGetMember<Sprite>(\"RedBarSprite\");\n  RedBarDivision = EnsureGetMember<float>(\"RedBarDivision\");\n  RedBarBaseX = EnsureGetMember<float>(\"RedBarBaseX\");\n  RedBarLabelPosition = EnsureGetMember<glm::vec2>(\"RedBarLabelPosition\");\n  RedBarLabel = EnsureGetMember<Sprite>(\"RedBarLabel\");\n\n  MenuSelectPromptDuration = EnsureGetMember<float>(\"MenuSelectPromptDuration\");\n  MenuSelectPromptInterval = EnsureGetMember<float>(\"MenuSelectPromptInterval\");\n  MenuTransitionDuration = EnsureGetMember<float>(\"MenuTransitionDuration\");\n  ShowPageAnimationStartTime =\n      EnsureGetMember<float>(\"ShowPageAnimationStartTime\");\n  ShowPageAnimationDuration =\n      EnsureGetMember<float>(\"ShowPageAnimationDuration\");\n  ButtonPromptAnimationStartTime =\n      EnsureGetMember<float>(\"ButtonPromptAnimationStartTime\");\n  ButtonPromptAnimationDuration =\n      EnsureGetMember<float>(\"ButtonPromptAnimationDuration\");\n  ButtonPromptStartPosition =\n      EnsureGetMember<glm::vec2>(\"ButtonPromptStartPosition\");\n  DiagonalTitlesOffsetStart =\n      EnsureGetMember<glm::vec2>(\"DiagonalTitlesOffsetStart\");\n  DiagonalTitlesOffsetEnd =\n      EnsureGetMember<glm::vec2>(\"DiagonalTitlesOffsetEnd\");\n}\n\n}  // namespace CommonMenu\n}  // namespace CHLCC\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/chlcc/commonmenu.h",
    "content": "#pragma once\n\n#include \"../../../spritesheet.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace CHLCC {\nnamespace CommonMenu {\ninline glm::vec2 CircleStartPosition;\ninline float CircleOffset;\n\ninline glm::vec2 ErinPosition;\ninline Sprite ErinSprite;\n\ninline Sprite BackgroundFilter;\n\ninline float TitleFadeInDuration;\ninline float TitleFadeOutDuration;\n\ninline Sprite InitialRedBarSprite;\ninline Sprite RedBarSprite;\n\ninline glm::vec2 InitialRedBarPosition;\ninline glm::vec2 RedBarPosition;\n\ninline glm::vec2 RightRedBarPosition;\ninline float RedBarDivision;\ninline float RedBarBaseX;\ninline glm::vec2 RedBarLabelPosition;\ninline Sprite RedBarLabel;\ninline glm::vec2 DiagonalTitlesOffsetStart;\ninline glm::vec2 DiagonalTitlesOffsetEnd;\n\ninline float MenuSelectPromptDuration;\ninline float MenuSelectPromptInterval;\ninline float MenuTransitionDuration;\ninline float ShowPageAnimationStartTime;\ninline float ShowPageAnimationDuration;\ninline float ButtonPromptAnimationStartTime;\ninline float ButtonPromptAnimationDuration;\ninline glm::vec2 ButtonPromptStartPosition;\n\nvoid Configure();\n\n}  // namespace CommonMenu\n}  // namespace CHLCC\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/chlcc/delusiontrigger.cpp",
    "content": "#include \"delusiontrigger.h\"\n#include \"../../profile_internal.h\"\n#include \"../../../log.h\"\n#include \"../../../text/text.h\"\n\n#include \"../../../game.h\"\n#include \"../../../ui/ui.h\"\n#include \"../../../games/chlcc/delusiontrigger.h\"\nnamespace Impacto {\nnamespace Profile {\nnamespace CHLCC {\nnamespace DelusionTrigger {\n\nvoid Configure() {\n  if (!TryPushMember(\"DelusionTrigger\")) return;\n  AssertIs(LUA_TTABLE);\n  BackgroundSprite = EnsureGetMember<Sprite>(\"BackgroundSprite\");\n  BackgroundSpriteMask = EnsureGetMember<Sprite>(\"BackgroundSpriteMask\");\n  ScreenMask = EnsureGetMember<Sprite>(\"ScreenMask\");\n\n  BackgroundSpriteMask.Bounds.Y = BackgroundSprite.Bounds.Center().y -\n                                  BackgroundSpriteMask.Bounds.Center().y;\n\n  {\n    EnsurePushMemberOfType(\"DelusionTextGlyphs\", LUA_TTABLE);\n    DelusionTextGlyphs = EnsureGet<std::array<std::vector<Sprite>, 21>>();\n\n    Pop();\n  }\n\n  DelusionTextXVelocity = EnsureGetMember<float>(\"DelusionTextXVelocity\");\n  DelusionTextFadeDuration = EnsureGetMember<float>(\"DelusionTextFadeDuration\");\n  DelusionScaledGlyphWidth = EnsureGetMember<float>(\"DelusionScaledGlyphWidth\");\n  DelusionScaledGlyphHeight =\n      EnsureGetMember<float>(\"DelusionScaledGlyphHeight\");\n  DelusionTextLineSpacing = EnsureGetMember<float>(\"DelusionTextLineSpacing\");\n  Pop();\n}\n\n}  // namespace DelusionTrigger\n}  // namespace CHLCC\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/chlcc/delusiontrigger.h",
    "content": "#pragma once\n\n#include \"../../../spritesheet.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace CHLCC {\nnamespace DelusionTrigger {\n\ninline Sprite BackgroundSprite;\ninline Sprite BackgroundSpriteMask;\ninline Sprite ScreenMask;\ninline std::array<std::vector<Sprite>, 21> DelusionTextGlyphs;\ninline float DelusionTextXVelocity;\ninline float DelusionTextFadeDuration;\ninline float DelusionScaledGlyphWidth;\ninline float DelusionScaledGlyphHeight;\ninline float DelusionTextLineSpacing;\n\nvoid Configure();\n\n}  // namespace DelusionTrigger\n}  // namespace CHLCC\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/chlcc/dialoguebox.cpp",
    "content": "#include \"dialoguebox.h\"\n\n#include \"../../profile_internal.h\"\n#include \"../../dialogue.h\"\n#include \"../../../hud/chlcc/nametagdisplay.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace CHLCC {\nnamespace DialogueBox {\n\nvoid Configure() {\n  SecondaryADVBoxSprite = EnsureGetMember<Sprite>(\"SecondaryADVBoxSprite\");\n\n  ErinBoxSprite = EnsureGetMember<Sprite>(\"ErinBoxSprite\");\n  ErinBoxPos = EnsureGetMember<glm::vec2>(\"ErinBoxPos\");\n  REVWaitIconOffset = EnsureGetMember<glm::vec2>(\"REVWaitIconOffset\");\n  REVLineHeight = EnsureGetMember<uint8_t>(\"REVLineHeight\");\n  REVLineSpacing = EnsureGetMember<uint8_t>(\"REVLineSpacing\");\n  REVFontSize = EnsureGetMember<uint8_t>(\"REVFontSize\");\n}\n\nvoid ConfigureNametag() {\n  Dialogue::NametagPosition = EnsureGetMember<glm::vec2>(\"NametagPosition\");\n  Dialogue::NametagSprite = EnsureGetMember<Sprite>(\"NametagSprite\");\n  SecondaryNametagSprite = EnsureGetMember<Sprite>(\"SecondaryNametagSprite\");\n}\n\n}  // namespace DialogueBox\n}  // namespace CHLCC\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/chlcc/dialoguebox.h",
    "content": "#pragma once\n\n#include \"../../../spritesheet.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace CHLCC {\nnamespace DialogueBox {\n\nvoid Configure();\nvoid ConfigureNametag();\n\ninline Sprite SecondaryADVBoxSprite;\ninline Sprite SecondaryNametagSprite;\n\ninline Sprite ErinBoxSprite;\ninline glm::vec2 ErinBoxPos;\ninline glm::vec2 REVWaitIconOffset;\ninline uint8_t REVLineSpacing;\ninline uint8_t REVLineHeight;\ninline uint8_t REVFontSize;\n\n}  // namespace DialogueBox\n}  // namespace CHLCC\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/chlcc/moviemenu.cpp",
    "content": "#include \"moviemenu.h\"\n#include \"../../profile_internal.h\"\n#include \"../../../ui/ui.h\"\n\n#include \"../../../games/chlcc/moviemenu.h\"\n#include \"../../../renderer/renderer.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace CHLCC {\nnamespace MovieMenu {\n\nvoid Configure() {\n  BackgroundColor = EnsureGetMember<uint32_t>(\"BackgroundColor\");\n  CircleSprite = EnsureGetMember<Sprite>(\"CircleSprite\");\n  MenuTitleTextRightPosition =\n      EnsureGetMember<glm::vec2>(\"MenuTitleTextRightPos\");\n  MenuTitleTextLeftPosition =\n      EnsureGetMember<glm::vec2>(\"MenuTitleTextLeftPos\");\n  MenuTitleTextAngle = EnsureGetMember<float>(\"MenuTitleTextAngle\");\n  MenuTitleText = EnsureGetMember<Sprite>(\"MenuTitleText\");\n  SelectedMovieAnimation =\n      EnsureGetMember<SpriteAnimationDef>(\"SelectedMovieAnimation\");\n  SelectedMovieYellowDot = EnsureGetMember<Sprite>(\"SelectedMovieYellowDot\");\n\n  SelectMovie = EnsureGetMember<std::vector<Sprite>>(\"SelectMovie\");\n  SelectMoviePos = EnsureGetMember<std::vector<glm::vec2>>(\"SelectMoviePos\");\n  if (SelectMovie.size() != SelectMoviePos.size()) {\n    throw std::runtime_error(\"Related arrays have mismatching sizes\");\n  }\n  LabelPosition = EnsureGetMember<glm::vec2>(\"LabelPosition\");\n  MovieLabel = EnsureGetMember<Sprite>(\"MovieLabel\");\n  ListPosition = EnsureGetMember<glm::vec2>(\"ListPosition\");\n  MovieList = EnsureGetMember<Sprite>(\"MovieList\");\n  GetMemberArray<glm::vec2>(std::span(BoxPositions, Movies), \"BoxPositions\");\n  MovieBox = EnsureGetMember<Sprite>(\"MovieBox\");\n  GetMemberArray<glm::vec2>(std::span(ThumbnailPositions, Movies),\n                            \"ThumbnailPositions\");\n  GetMemberArray<Sprite>(std::span(MoviesThumbnails, Movies),\n                         \"MoviesThumbnails\");\n  LockedThumbnail = EnsureGetMember<Sprite>(\"LockedThumbnail\");\n  ButtonPromptPosition = EnsureGetMember<glm::vec2>(\"ButtonPromptPosition\");\n  ButtonPromptSprite = EnsureGetMember<Sprite>(\"ButtonPromptSprite\");\n\n  MovieExtraVideosEnabled =\n      TryGetMember<bool>(\"MovieExtraVideosEnabled\").value_or(false);\n  if (MovieExtraVideosEnabled) {\n    SelectedMovieExtraAnimation =\n        EnsureGetMember<SpriteAnimationDef>(\"SelectedMovieExtraAnimation\");\n    MovieBoxExtra = EnsureGetMember<Sprite>(\"MovieBoxExtra\");\n    MovieThumbnailExtraOp = EnsureGetMember<Sprite>(\"MovieThumbnailExtraOp\");\n    MovieThumbnailExtraOp2 = EnsureGetMember<Sprite>(\"MovieThumbnailExtraOp2\");\n    MovieButtonExtraPromptPosition =\n        EnsureGetMember<glm::vec2>(\"MovieButtonExtraPromptPosition\");\n    MovieButtonExtraPrompt = EnsureGetMember<Sprite>(\"MovieButtonExtraPrompt\");\n  }\n\n  auto drawType = EnsureGetMember<Game::DrawComponentType>(\"DrawType\");\n\n  UI::Menus[drawType].push_back(new UI::CHLCC::MovieMenu());\n}\n\n}  // namespace MovieMenu\n}  // namespace CHLCC\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/chlcc/moviemenu.h",
    "content": "#pragma once\n\n#include \"../../../spritesheet.h\"\n#include \"../../../spriteanimation.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace CHLCC {\nnamespace MovieMenu {\n\nint constexpr Movies = 10;\n\ninline uint32_t BackgroundColor;\ninline Sprite CircleSprite;\ninline glm::vec2 MenuTitleTextRightPosition;\ninline glm::vec2 MenuTitleTextLeftPosition;\ninline float MenuTitleTextAngle;\ninline Sprite MenuTitleText;\ninline SpriteAnimationDef SelectedMovieAnimation;\ninline Sprite SelectedMovieYellowDot;\n\ninline float SelectMovieFadeDuration;\ninline std::vector<Sprite> SelectMovie;\ninline std::vector<glm::vec2> SelectMoviePos;\ninline glm::vec2 LabelPosition;\ninline Sprite MovieLabel;\ninline Sprite MovieList;\ninline glm::vec2 BoxPositions[Movies];\ninline glm::vec2 ListPosition;\ninline Sprite MovieBox;\ninline glm::vec2 ThumbnailPositions[Movies];\ninline Sprite MoviesThumbnails[Movies];\ninline Sprite LockedThumbnail;\ninline glm::vec2 ButtonPromptPosition;\ninline Sprite ButtonPromptSprite;\n\ninline bool MovieExtraVideosEnabled;\ninline SpriteAnimationDef SelectedMovieExtraAnimation;\ninline Sprite MovieBoxExtra;\ninline Sprite MovieThumbnailExtraOp;\ninline Sprite MovieThumbnailExtraOp2;\ninline glm::vec2 MovieButtonExtraPromptPosition;\ninline Sprite MovieButtonExtraPrompt;\n\nvoid Configure();\n\n}  // namespace MovieMenu\n}  // namespace CHLCC\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/chlcc/musicmenu.cpp",
    "content": "#include \"musicmenu.h\"\n#include \"../../profile_internal.h\"\n#include \"../../../ui/ui.h\"\n\n#include \"../../../games/chlcc/musicmenu.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace CHLCC {\nnamespace MusicMenu {\n\nvoid Configure() {\n  BackgroundColor = EnsureGetMember<uint32_t>(\"BackgroundColor\");\n  CircleSprite = EnsureGetMember<Sprite>(\"CircleSprite\");\n  TrackTree = EnsureGetMember<Sprite>(\"TrackTreeSprite\");\n  TrackTreePos = EnsureGetMember<glm::vec2>(\"TrackTreePos\");\n  TrackButtonPosTemplate = EnsureGetMember<glm::vec2>(\"TrackButtonPosTemplate\");\n  TrackNameOffset = EnsureGetMember<glm::vec2>(\"TrackNameOffset\");\n  ArtistOffset = EnsureGetMember<glm::vec2>(\"ArtistOffset\");\n  TrackOffset = EnsureGetMember<glm::vec2>(\"TrackOffset\");\n  TrackHighlight = EnsureGetMember<Sprite>(\"TrackHighlight\");\n  TrackNumRelativePos = EnsureGetMember<glm::vec2>(\"TrackNumRelativePos\");\n  ButtonPromptPosition = EnsureGetMember<glm::vec2>(\"ButtonPromptPosition\");\n  ButtonPromptSprite = EnsureGetMember<Sprite>(\"ButtonPromptSprite\");\n  PlaymodeRepeatPos = EnsureGetMember<glm::vec2>(\"PlaymodeRepeatPos\");\n  PlaymodeAllPos = EnsureGetMember<glm::vec2>(\"PlaymodeAllPos\");\n  PlaymodeRepeat = EnsureGetMember<Sprite>(\"PlaymodeRepeat\");\n  PlaymodeAll = EnsureGetMember<Sprite>(\"PlaymodeAll\");\n  PlaymodeRepeatHighlight = EnsureGetMember<Sprite>(\"PlaymodeRepeatHighlight\");\n  PlaymodeAllHighlight = EnsureGetMember<Sprite>(\"PlaymodeAllHighlight\");\n  NowPlaying = EnsureGetMember<Sprite>(\"NowPlaying\");\n  NowPlayingPos = EnsureGetMember<glm::vec2>(\"NowPlayingPos\");\n  NowPlayingAnimationDuration =\n      EnsureGetMember<float>(\"NowPlayingAnimationDuration\");\n  PlayingTrackOffset = EnsureGetMember<glm::vec2>(\"PlayingTrackOffset\");\n  PlayingTrackArtistOffset =\n      EnsureGetMember<glm::vec2>(\"PlayingTrackArtistOffset\");\n  SoundLibraryTitle = EnsureGetMember<Sprite>(\"SoundLibraryTitle\");\n  SoundLibraryTitleLeftPos =\n      EnsureGetMember<glm::vec2>(\"SoundLibraryTitleLeftPos\");\n  SoundLibraryTitleRightPos =\n      EnsureGetMember<glm::vec2>(\"SoundLibraryTitleRightPos\");\n  SoundLibraryTitleAngle = EnsureGetMember<float>(\"SoundLibraryTitleAngle\");\n  HighlightStar = EnsureGetMember<Sprite>(\"HighlightStar\");\n  HighlightStarRelativePos =\n      EnsureGetMember<glm::vec2>(\"HighlightStarRelativePos\");\n  Playlist = EnsureGetMember<decltype(Playlist)>(\"Playlist\");\n  SelectSoundSprites =\n      EnsureGetMember<std::vector<Sprite>>(\"SelectSoundSprites\");\n  SelectSoundPos = EnsureGetMember<std::vector<glm::vec2>>(\"SelectSoundPos\");\n  if (SelectSoundSprites.size() != SelectSoundPos.size()) {\n    throw std::runtime_error(\"Related arrays have mismatching sizes\");\n  }\n\n  auto drawType = EnsureGetMember<Game::DrawComponentType>(\"DrawType\");\n  ScrollThumbSprite = EnsureGetMember<Sprite>(\"ScrollThumb\");\n  ScrollbarPosition = EnsureGetMember<glm::vec2>(\"ScrollbarPosition\");\n  TrackListBounds = EnsureGetMember<RectF>(\"TrackListBounds\");\n  ScrollTrackBounds = EnsureGetMember<glm::vec2>(\"ScrollTrackBounds\");\n\n  AyaseEndingBgmId = EnsureGetMember<int>(\"AyaseEndingBgmId\");\n  NormalEndingBgmId = EnsureGetMember<int>(\"NormalEndingBgmId\");\n\n  PresetBgmFlags = EnsureGetMember<std::vector<int>>(\"PresetBgmFlags\");\n  DstBgmPairedFlag = EnsureGetMember<std::vector<int>>(\"DstBgmPairedFlag\");\n  SrcBgmPairedFlag = EnsureGetMember<std::vector<int>>(\"SrcBgmPairedFlag\");\n\n  if (DstBgmPairedFlag.size() != SrcBgmPairedFlag.size()) {\n    throw std::runtime_error(\"Related arrays have mismatching sizes\");\n  }\n\n  UI::MusicMenuPtr = new UI::CHLCC::MusicMenu();\n  UI::Menus[drawType].push_back(UI::MusicMenuPtr);\n}\n\n}  // namespace MusicMenu\n}  // namespace CHLCC\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/chlcc/musicmenu.h",
    "content": "#pragma once\n\n#include \"../../../spritesheet.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace CHLCC {\nnamespace MusicMenu {\n\nint inline constexpr Endings = 8;\nint inline constexpr MusicTrackCount = 45;\nint inline constexpr VisibleItemsPerPage = 16;\nint inline constexpr SelectableItemsPerPage = 15;\n\ninline uint32_t BackgroundColor;\ninline Sprite CircleSprite;\ninline Sprite TrackTree;\ninline glm::vec2 TrackTreePos;\ninline glm::vec2 TrackButtonPosTemplate;\ninline glm::vec2 TrackNameOffset;\ninline glm::vec2 ArtistOffset;\ninline glm::vec2 TrackOffset;\ninline Sprite TrackHighlight;\ninline glm::vec2 TrackNumRelativePos;\ninline glm::vec2 PlaymodeRepeatPos;\ninline glm::vec2 PlaymodeAllPos;\ninline Sprite PlaymodeRepeat;\ninline Sprite PlaymodeAll;\ninline Sprite PlaymodeRepeatHighlight;\ninline Sprite PlaymodeAllHighlight;\ninline Sprite NowPlaying;\ninline glm::vec2 NowPlayingPos;\ninline float NowPlayingAnimationDuration;\ninline glm::vec2 PlayingTrackOffset;\ninline glm::vec2 PlayingTrackArtistOffset;\ninline Sprite SoundLibraryTitle;\ninline glm::vec2 SoundLibraryTitleLeftPos;\ninline glm::vec2 SoundLibraryTitleRightPos;\ninline float SoundLibraryTitleAngle;\ninline Sprite HighlightStar;\ninline glm::vec2 HighlightStarRelativePos;\ninline std::array<int, MusicTrackCount> Playlist;\ninline std::vector<Sprite> SelectSoundSprites;\ninline std::vector<glm::vec2> SelectSoundPos;\ninline glm::vec2 ButtonPromptPosition;\ninline Sprite ButtonPromptSprite;\ninline Sprite ScrollThumbSprite;\ninline glm::vec2 ScrollbarPosition;\ninline glm::vec2 ScrollTrackBounds;\ninline RectF TrackListBounds;\ninline int AyaseEndingBgmId;\ninline int NormalEndingBgmId;\ninline std::vector<int> PresetBgmFlags;\ninline std::vector<int> DstBgmPairedFlag;\ninline std::vector<int> SrcBgmPairedFlag;\n\nvoid Configure();\n\n}  // namespace MusicMenu\n}  // namespace CHLCC\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/chlcc/optionsmenu.cpp",
    "content": "#include \"optionsmenu.h\"\n#include \"../../profile_internal.h\"\n#include \"../../../log.h\"\n#include \"../../../text/text.h\"\n\n#include \"../../ui/optionsmenu.h\"\n#include \"../../../game.h\"\n#include \"../../../ui/ui.h\"\n#include \"../../../games/chlcc/optionsmenu.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace CHLCC {\nnamespace OptionsMenu {\n\nvoid Configure() {\n  BackgroundColor = EnsureGetMember<uint32_t>(\"BackgroundColor\");\n\n  CircleSprite = EnsureGetMember<Sprite>(\"CircleSprite\");\n\n  ShowPageAnimationStartTime =\n      EnsureGetMember<float>(\"ShowPageAnimationStartTime\");\n  ShowPageAnimationDuration =\n      EnsureGetMember<float>(\"ShowPageAnimationDuration\");\n\n  MenuTitleTextRightPosition =\n      EnsureGetMember<glm::vec2>(\"MenuTitleTextRightPos\");\n  MenuTitleTextAngle = EnsureGetMember<float>(\"MenuTitleTextAngle\");\n  MenuTitleText = EnsureGetMember<Sprite>(\"MenuTitleText\");\n\n  PageRotationAngle = EnsureGetMember<float>(\"PageRotationAngle\");\n  PageTransitionDuration = EnsureGetMember<float>(\"PageTransitionDuration\");\n\n  ButtonPromptSprite = EnsureGetMember<Sprite>(\"ButtonPromptSprite\");\n  ButtonPromptPosition = EnsureGetMember<glm::vec2>(\"ButtonPromptPosition\");\n\n  SelectedSprite = EnsureGetMember<Sprite>(\"SelectedSprite\");\n  SelectedSlideDuration = EnsureGetMember<float>(\"SelectedSlideDuration\");\n  SelectedLabelSprite = EnsureGetMember<Sprite>(\"SelectedLabelSprite\");\n  SelectedLabelOffset = EnsureGetMember<glm::vec2>(\"SelectedLabelOffset\");\n  SelectedDotSprite = EnsureGetMember<Sprite>(\"SelectedDotSprite\");\n  SelectedDotOffset = EnsureGetMember<glm::vec2>(\"SelectedDotOffset\");\n  SelectedDotVoicesOffset =\n      EnsureGetMember<glm::vec2>(\"SelectedDotVoicesOffset\");\n  VoiceMutedSprite = EnsureGetMember<Sprite>(\"VoiceMutedSprite\");\n  VoiceMutedOffset = EnsureGetMember<glm::vec2>(\"VoiceMutedOffset\");\n\n  SelectedLabelBaseSpeed = EnsureGetMember<float>(\"SelectedLabelBaseSpeed\");\n  SelectedLabelModalDistancePerEntry =\n      EnsureGetMember<float>(\"SelectedLabelModalDistancePerEntry\");\n\n  BasicSettingsSprite = EnsureGetMember<Sprite>(\"BasicSettingsSprite\");\n  BasicSettingsPos = EnsureGetMember<glm::vec2>(\"BasicSettingsPos\");\n  TextSettingsSprite = EnsureGetMember<Sprite>(\"TextSettingsSprite\");\n  TextSettingsPos = EnsureGetMember<glm::vec2>(\"TextSettingsPos\");\n  SoundSettingsSprite = EnsureGetMember<Sprite>(\"SoundSettingsSprite\");\n  SoundSettingsPos = EnsureGetMember<glm::vec2>(\"SoundSettingsPos\");\n  VoiceSettingsSprite = EnsureGetMember<Sprite>(\"VoiceSettingsSprite\");\n  VoiceSettingsPos = EnsureGetMember<glm::vec2>(\"VoiceSettingsPos\");\n\n  SliderBarBaseSprite = EnsureGetMember<Sprite>(\"SliderBarBaseSprite\");\n  SliderBarFillSprite = EnsureGetMember<Sprite>(\"SliderBarFillSprite\");\n  SliderBarFadeDuration = EnsureGetMember<float>(\"SliderBarFadeDuration\");\n  SliderBarTopRightOffset =\n      EnsureGetMember<glm::vec2>(\"SliderBarTopRightOffset\");\n  SliderBarFillOffset = EnsureGetMember<glm::vec2>(\"SliderBarFillOffset\");\n\n  SettingInstantSprite = EnsureGetMember<Sprite>(\"SettingInstantSprite\");\n  SettingFastSprite = EnsureGetMember<Sprite>(\"SettingFastSprite\");\n  SettingNormalSprite = EnsureGetMember<Sprite>(\"SettingNormalSprite\");\n  SettingSlowSprite = EnsureGetMember<Sprite>(\"SettingSlowSprite\");\n  SettingShortSprite = EnsureGetMember<Sprite>(\"SettingShortSprite\");\n  SettingLongSprite = EnsureGetMember<Sprite>(\"SettingLongSprite\");\n  SettingDoSprite = EnsureGetMember<Sprite>(\"SettingDoSprite\");\n  SettingDontSprite = EnsureGetMember<Sprite>(\"SettingDontSprite\");\n  SettingYesSprite = EnsureGetMember<Sprite>(\"SettingYesSprite\");\n  SettingNoSprite = EnsureGetMember<Sprite>(\"SettingNoSprite\");\n  SettingReadSprite = EnsureGetMember<Sprite>(\"SettingReadSprite\");\n  SettingAllSprite = EnsureGetMember<Sprite>(\"SettingAllSprite\");\n  SettingOnTriggerSprite = EnsureGetMember<Sprite>(\"SettingOnTriggerSprite\");\n  SettingOnSceneSprite = EnsureGetMember<Sprite>(\"SettingOnSceneSprite\");\n  SettingOnTriggerAndSceneSprite =\n      EnsureGetMember<Sprite>(\"SettingOnTriggerAndSceneSprite\");\n  SettingTypeASprite = EnsureGetMember<Sprite>(\"SettingTypeASprite\");\n  SettingTypeBSprite = EnsureGetMember<Sprite>(\"SettingTypeBSprite\");\n  SettingButtonTopRightOffset =\n      EnsureGetMember<glm::vec2>(\"SettingButtonTopRightOffset\");\n\n  GetMemberArray<glm::vec2>(\n      std::span(TextPageEntryPositions.data(), TextPageEntryPositions.size()),\n      \"TextPageEntryPositions\");\n  GetMemberArray<glm::vec2>(\n      std::span(SoundPageEntryPositions.data(), SoundPageEntryPositions.size()),\n      \"SoundPageEntryPositions\");\n  GetMemberArray<glm::vec2>(\n      std::span(VoicePageEntryPositions.data(), VoicePageEntryPositions.size()),\n      \"VoicePageEntryPositions\");\n\n  GetMemberArray<bool>(\n      std::span(TriggerStopSkipValues.data(), TriggerStopSkipValues.size()),\n      \"TriggerStopSkipValues\");\n  GetMemberArray<bool>(std::span(ShowTipsNotificationValues.data(),\n                                 ShowTipsNotificationValues.size()),\n                       \"ShowTipsNotificationValues\");\n  GetMemberArray<uint8_t>(\n      std::span(AutoQuickSaveValues.data(), AutoQuickSaveValues.size()),\n      \"AutoQuickSaveValues\");\n  GetMemberArray<uint8_t>(\n      std::span(ControllerTypeValues.data(), ControllerTypeValues.size()),\n      \"ControllerTypeValues\");\n  GetMemberArray<float>(\n      std::span(TextSpeedValues.data(), TextSpeedValues.size()),\n      \"TextSpeedValues\");\n  GetMemberArray<float>(\n      std::span(AutoSpeedValues.data(), AutoSpeedValues.size()),\n      \"AutoSpeedValues\");\n  GetMemberArray<bool>(std::span(SkipReadValues.data(), SkipReadValues.size()),\n                       \"SkipReadValues\");\n  GetMemberArray<bool>(\n      std::span(SyncVoiceValues.data(), SyncVoiceValues.size()),\n      \"SyncVoiceValues\");\n  GetMemberArray<bool>(\n      std::span(SkipVoiceValues.data(), SkipVoiceValues.size()),\n      \"SkipVoiceValues\");\n\n  auto drawType = EnsureGetMember<Game::DrawComponentType>(\"DrawType\");\n\n  UI::OptionsMenuPtr = new UI::CHLCC::OptionsMenu();\n  UI::Menus[drawType].push_back(UI::OptionsMenuPtr);\n}\n\n}  // namespace OptionsMenu\n}  // namespace CHLCC\n}  // namespace Profile\n}  // namespace Impacto\n"
  },
  {
    "path": "src/profile/games/chlcc/optionsmenu.h",
    "content": "#pragma once\n\n#include \"../../../spritesheet.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace CHLCC {\nnamespace OptionsMenu {\n\ninline uint32_t BackgroundColor;\n\ninline Sprite CircleSprite;\n\ninline float ShowPageAnimationStartTime;\ninline float ShowPageAnimationDuration;\n\ninline glm::vec2 MenuTitleTextRightPosition;\ninline float MenuTitleTextAngle;\ninline Sprite MenuTitleText;\n\ninline float PageRotationAngle;\ninline float PageTransitionDuration;\n\ninline Sprite ButtonPromptSprite;\ninline glm::vec2 ButtonPromptPosition;\n\ninline Sprite SelectedSprite;\ninline float SelectedSlideDuration;\ninline Sprite SelectedLabelSprite;\ninline glm::vec2 SelectedLabelOffset;\ninline Sprite SelectedDotSprite;\ninline glm::vec2 SelectedDotOffset;\ninline glm::vec2 SelectedDotVoicesOffset;\ninline Sprite VoiceMutedSprite;\ninline glm::vec2 VoiceMutedOffset;\n\ninline float SelectedLabelBaseSpeed;\ninline float SelectedLabelModalDistancePerEntry;\n\ninline Sprite BasicSettingsSprite;\ninline glm::vec2 BasicSettingsPos;\ninline Sprite TextSettingsSprite;\ninline glm::vec2 TextSettingsPos;\ninline Sprite SoundSettingsSprite;\ninline glm::vec2 SoundSettingsPos;\ninline Sprite VoiceSettingsSprite;\ninline glm::vec2 VoiceSettingsPos;\n\ninline Sprite SliderBarBaseSprite;\ninline Sprite SliderBarFillSprite;\ninline float SliderBarFadeDuration;\ninline glm::vec2 SliderBarTopRightOffset;\ninline glm::vec2 SliderBarFillOffset;\n\ninline Sprite SettingInstantSprite;\ninline Sprite SettingFastSprite;\ninline Sprite SettingNormalSprite;\ninline Sprite SettingSlowSprite;\ninline Sprite SettingShortSprite;\ninline Sprite SettingLongSprite;\ninline Sprite SettingDoSprite;\ninline Sprite SettingDontSprite;\ninline Sprite SettingYesSprite;\ninline Sprite SettingNoSprite;\ninline Sprite SettingReadSprite;\ninline Sprite SettingAllSprite;\ninline Sprite SettingOnTriggerSprite;\ninline Sprite SettingOnSceneSprite;\ninline Sprite SettingOnTriggerAndSceneSprite;\ninline Sprite SettingTypeASprite;\ninline Sprite SettingTypeBSprite;\ninline glm::vec2 SettingButtonTopRightOffset;\n\ninline std::array<glm::vec2, 8> TextPageEntryPositions;\ninline std::array<glm::vec2, 6> SoundPageEntryPositions;\ninline std::array<glm::vec2, 11> VoicePageEntryPositions;\n\ninline std::array<bool, 2> TriggerStopSkipValues;\ninline std::array<bool, 2> ShowTipsNotificationValues;\ninline std::array<uint8_t, 4> AutoQuickSaveValues;\ninline std::array<uint8_t, 2> ControllerTypeValues;\ninline std::array<float, 4> TextSpeedValues;\ninline std::array<float, 3> AutoSpeedValues;\ninline std::array<bool, 2> SkipReadValues;\ninline std::array<bool, 2> SyncVoiceValues;\ninline std::array<bool, 2> SkipVoiceValues;\n\nvoid Configure();\n\n}  // namespace OptionsMenu\n}  // namespace CHLCC\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/chlcc/savemenu.cpp",
    "content": "#include \"savemenu.h\"\n#include \"../../profile_internal.h\"\n#include \"../../../log.h\"\n\n#include \"../../ui/savemenu.h\"\n#include \"../../../game.h\"\n#include \"../../../ui/ui.h\"\n#include \"../../../games/chlcc/savemenu.h\"\n\n#include \"albummenu.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace CHLCC {\nnamespace SaveMenu {\n\nvoid Configure() {\n  EntryStartX = EnsureGetMember<float>(\"EntryStartX\");\n  EntryXPadding = EnsureGetMember<float>(\"EntryXPadding\");\n  EntryStartY = EnsureGetMember<float>(\"EntryStartY\");\n  EntryYPadding = EnsureGetMember<float>(\"EntryYPadding\");\n  SaveBackgroundColor = EnsureGetMember<uint32_t>(\"SaveBackgroundColor\");\n  LoadBackgroundColor = EnsureGetMember<uint32_t>(\"LoadBackgroundColor\");\n  QuickLoadBackgroundColor =\n      EnsureGetMember<uint32_t>(\"QuickLoadBackgroundColor\");\n  SaveCircle = EnsureGetMember<Sprite>(\"SaveCircle\");\n  LoadCircle = EnsureGetMember<Sprite>(\"LoadCircle\");\n  QuickLoadCircle = EnsureGetMember<Sprite>(\"QuickLoadCircle\");\n  QuickLoadTextSprite = EnsureGetMember<Sprite>(\"QuickLoadTextSprite\");\n  LoadTextSprite = EnsureGetMember<Sprite>(\"LoadTextSprite\");\n  SaveTextSprite = EnsureGetMember<Sprite>(\"SaveTextSprite\");\n  MenuTitleTextRightPos = EnsureGetMember<glm::vec2>(\"MenuTitleTextRightPos\");\n  MenuTitleTextLeftPos = EnsureGetMember<glm::vec2>(\"MenuTitleTextLeftPos\");\n  MenuTitleTextAngle = EnsureGetMember<float>(\"MenuTitleTextAngle\");\n\n  SaveListPosition = EnsureGetMember<glm::vec2>(\"SaveListPosition\");\n  SaveListSprite = EnsureGetMember<Sprite>(\"SaveListSprite\");\n  GetMemberArray<glm::vec2>(std::span(EntryPositions, EntriesPerPage),\n                            \"EntryPositions\");\n  QuickLoadEntrySprite = EnsureGetMember<Sprite>(\"QuickLoadEntrySprite\");\n  SaveEntrySprite = EnsureGetMember<Sprite>(\"SaveEntrySprite\");\n  LoadEntrySprite = EnsureGetMember<Sprite>(\"LoadEntrySprite\");\n  SelectionMarkerSprite = EnsureGetMember<Sprite>(\"SelectionMarkerSprite\");\n  SelectionMarkerOffset = EnsureGetMember<glm::vec2>(\"SelectionMarkerOffset\");\n  EntryHighlightedSprite = EnsureGetMember<Sprite>(\"EntryHighlightedSprite\");\n  LockedSymbolSprite = EnsureGetMember<Sprite>(\"LockedSymbolSprite\");\n  LockedSymbolRelativePos =\n      EnsureGetMember<glm::vec2>(\"LockedSymbolRelativePos\");\n  ThumbnailRelativePos = EnsureGetMember<glm::vec2>(\"ThumbnailRelativePos\");\n  PageNumBackgroundPos = EnsureGetMember<glm::vec2>(\"PageNumBackgroundPos\");\n  PageNumBackgroundSprite = EnsureGetMember<Sprite>(\"PageNumBackground\");\n  CurrentPageNumPos = EnsureGetMember<glm::vec2>(\"CurrentPageNumPos\");\n  GetMemberArray<Sprite>(std::span(BigDigits, 10), \"BigDigits\");\n  PageNumSeparatorSlashPos =\n      EnsureGetMember<glm::vec2>(\"PageNumSeparatorSlashPos\");\n  PageNumSeparatorSlashSprite =\n      EnsureGetMember<Sprite>(\"PageNumSeparatorSlash\");\n  MaxPageNumPos = EnsureGetMember<glm::vec2>(\"MaxPageNumPos\");\n  MaxPageNumSprite = EnsureGetMember<Sprite>(\"MaxPageNum\");\n  ButtonPromptPosition = EnsureGetMember<glm::vec2>(\"ButtonPromptPosition\");\n  ButtonPromptSprite = EnsureGetMember<Sprite>(\"ButtonPrompt\");\n  SelectDataTextPositions =\n      EnsureGetMember<std::vector<glm::vec2>>(\"SelectDataTextPositions\");\n  SelectDataTextSprites =\n      EnsureGetMember<std::vector<Sprite>>(\"SelectDataText\");\n  if (SelectDataTextPositions.size() != SelectDataTextSprites.size()) {\n    throw std::runtime_error(\"Related arrays have mismatching sizes\");\n  }\n\n  EntryNumberHintTextRelativePos =\n      EnsureGetMember<glm::vec2>(\"EntryNumberHintTextRelativePos\");\n  EntryNumberTextRelativePos =\n      EnsureGetMember<glm::vec2>(\"EntryNumberTextRelativePos\");\n  SceneTitleTextRelativePos =\n      EnsureGetMember<glm::vec2>(\"SceneTitleTextRelativePos\");\n  NoDataTextRelativePos = EnsureGetMember<glm::vec2>(\"NoDataTextRelativePos\");\n  PlayTimeHintTextRelativePos =\n      EnsureGetMember<glm::vec2>(\"PlayTimeHintTextRelativePos\");\n  PlayTimeTextRelativePos =\n      EnsureGetMember<glm::vec2>(\"PlayTimeTextRelativePos\");\n  SaveDateHintTextRelativePos =\n      EnsureGetMember<glm::vec2>(\"SaveDateHintTextRelativePos\");\n  SaveDateTextRelativePos =\n      EnsureGetMember<glm::vec2>(\"SaveDateTextRelativePos\");\n  SaveHourTextRelativePos =\n      EnsureGetMember<glm::vec2>(\"SaveHourTextRelativePos\");\n  MaxTitleWidth = EnsureGetMember<float>(\"MaxTitleWidth\");\n\n  auto drawType = EnsureGetMember<Game::DrawComponentType>(\"DrawType\");\n\n  UI::SaveMenuPtr = new UI::CHLCC::SaveMenu();\n  UI::Menus[drawType].push_back(UI::SaveMenuPtr);\n}\n\n}  // namespace SaveMenu\n}  // namespace CHLCC\n}  // namespace Profile\n}  // namespace Impacto\n"
  },
  {
    "path": "src/profile/games/chlcc/savemenu.h",
    "content": "#pragma once\n\n#include \"../../../spritesheet.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace CHLCC {\nnamespace SaveMenu {\n\nint constexpr Pages = 8;\nint constexpr EntriesPerPage = 6;\n\ninline float EntryStartX;\ninline float EntryXPadding;\ninline float EntryStartY;\ninline float EntryYPadding;\n\ninline uint32_t BackgroundColor;\ninline uint32_t SaveBackgroundColor;\ninline uint32_t LoadBackgroundColor;\ninline uint32_t QuickLoadBackgroundColor;\ninline Sprite CircleSprite;\ninline Sprite SaveCircle;\ninline Sprite LoadCircle;\ninline Sprite QuickLoadCircle;\ninline Sprite MenuTitleTextSprite;\ninline Sprite QuickLoadTextSprite;\ninline Sprite LoadTextSprite;\ninline Sprite SaveTextSprite;\ninline glm::vec2 MenuTitleTextLeftPos;\ninline glm::vec2 MenuTitleTextRightPos;\ninline float MenuTitleTextAngle;\n\ninline glm::vec2 SaveListPosition;\ninline Sprite SaveListSprite;\ninline glm::vec2 EntryPositions[EntriesPerPage];\ninline Sprite QuickLoadEntrySprite;\ninline Sprite SaveEntrySprite;\ninline Sprite LoadEntrySprite;\ninline Sprite SelectionMarkerSprite;\ninline glm::vec2 SelectionMarkerOffset;\ninline Sprite EntryHighlightedSprite;\ninline Sprite LockedSymbolSprite;\ninline glm::vec2 LockedSymbolRelativePos;\ninline glm::vec2 ThumbnailRelativePos;\ninline glm::vec2 PageNumBackgroundPos;\ninline Sprite PageNumBackgroundSprite;\ninline glm::vec2 CurrentPageNumPos;\ninline Sprite BigDigits[10];\ninline glm::vec2 PageNumSeparatorSlashPos;\ninline Sprite PageNumSeparatorSlashSprite;\ninline glm::vec2 MaxPageNumPos;\ninline Sprite MaxPageNumSprite;\ninline glm::vec2 ButtonPromptPosition;\ninline Sprite ButtonPromptSprite;\ninline std::vector<glm::vec2> SelectDataTextPositions;\ninline std::vector<Sprite> SelectDataTextSprites;\n\ninline glm::vec2 EntryNumberHintTextRelativePos;\ninline glm::vec2 EntryNumberTextRelativePos;\ninline glm::vec2 SceneTitleTextRelativePos;\ninline glm::vec2 NoDataTextRelativePos;\ninline glm::vec2 PlayTimeHintTextRelativePos;\ninline glm::vec2 PlayTimeTextRelativePos;\ninline glm::vec2 SaveDateHintTextRelativePos;\ninline glm::vec2 SaveDateTextRelativePos;\ninline glm::vec2 SaveHourTextRelativePos;\ninline float MaxTitleWidth;\n\nvoid Configure();\n\n}  // namespace SaveMenu\n}  // namespace CHLCC\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/chlcc/sysmesbox.cpp",
    "content": "#include \"sysmesbox.h\"\n#include \"../../profile_internal.h\"\n\n#include \"../../../ui/ui.h\"\n#include \"../../../games/chlcc/sysmesbox.h\"\n#include \"../../../game.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace CHLCC {\nnamespace SysMesBox {\n\nvoid Configure() {\n  Box = EnsureGetMember<Sprite>(\"Box\");\n  BoxDecoration = EnsureGetMember<Sprite>(\"BoxDecoration\");\n  SelectionLeftPart = EnsureGetMember<Sprite>(\"SelectionLeftPart\");\n  SelectionRightPart = EnsureGetMember<Sprite>(\"SelectionRightPart\");\n  SelectionMiddlePart = EnsureGetMember<Sprite>(\"SelectionMiddlePart\");\n  LoadingStar = EnsureGetMember<Sprite>(\"LoadingStar\");\n\n  BoxX = EnsureGetMember<float>(\"BoxX\");\n  BoxY = EnsureGetMember<float>(\"BoxY\");\n  ChoicePadding = EnsureGetMember<float>(\"ChoicePadding\");\n  ChoiceY = EnsureGetMember<float>(\"ChoiceY\");\n  ChoiceXBase = EnsureGetMember<float>(\"ChoiceXBase\");\n  MinMaxMesWidth = EnsureGetMember<float>(\"MinMaxMesWidth\");\n  MinHighlightWidth = EnsureGetMember<float>(\"MinHighlightWidth\");\n  HighlightBaseWidth = EnsureGetMember<float>(\"HighlightBaseWidth\");\n  HighlightRightPartSpriteWidth =\n      EnsureGetMember<float>(\"HighlightRightPartSpriteWidth\");\n  HighlightYOffset = EnsureGetMember<float>(\"HighlightYOffset\");\n  HighlightXOffset = EnsureGetMember<float>(\"HighlightXOffset\");\n  HighlightXBase = EnsureGetMember<float>(\"HighlightXBase\");\n  HighlightXStep = EnsureGetMember<float>(\"HighlightXStep\");\n  LoadingStarsFadeDuration = EnsureGetMember<float>(\"LoadingStarsFadeDuration\");\n  LoadingStarsPosition = EnsureGetMember<glm::vec2>(\"LoadingStarsPosition\");\n\n  auto drawType = EnsureGetMember<Game::DrawComponentType>(\"DrawType\");\n\n  UI::SysMesBoxPtr = new UI::CHLCC::SysMesBox();\n  UI::Menus[drawType].push_back(UI::SysMesBoxPtr);\n}\n\n}  // namespace SysMesBox\n}  // namespace CHLCC\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/chlcc/sysmesbox.h",
    "content": "#pragma once\n\n#include \"../../../spritesheet.h\"\n#include \"../../../games/chlcc/sysmesbox.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace CHLCC {\nnamespace SysMesBox {\n\nvoid Configure();\n\ninline Sprite Box;\ninline Sprite BoxDecoration;\ninline Sprite SelectionLeftPart;\ninline Sprite SelectionRightPart;\ninline Sprite SelectionMiddlePart;\ninline Sprite LoadingStar;\n\ninline float BoxX;\ninline float BoxY;\ninline float ChoicePadding;\ninline float ChoiceY;\ninline float ChoiceXBase;\ninline float MinMaxMesWidth;\ninline float MinHighlightWidth;\ninline float HighlightBaseWidth;\ninline float HighlightRightPartSpriteWidth;\ninline float HighlightYOffset;\ninline float HighlightXOffset;\ninline float HighlightXBase;\ninline float HighlightXStep;\ninline float LoadingStarsFadeDuration;\ninline glm::vec2 LoadingStarsPosition;\n\n}  // namespace SysMesBox\n}  // namespace CHLCC\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/chlcc/systemmenu.cpp",
    "content": "#include \"systemmenu.h\"\n#include \"../../profile_internal.h\"\n#include \"../../../renderer/renderer.h\"\n#include \"../../../log.h\"\n\n#include \"../../../games/chlcc/systemmenu.h\"\n#include \"../../ui/systemmenu.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace CHLCC {\nnamespace SystemMenu {\n\nvoid Configure() {\n  BackgroundColor = EnsureGetMember<uint32_t>(\"BackgroundColor\");\n  CircleSprite = EnsureGetMember<Sprite>(\"CircleSprite\");\n  FocusTint = EnsureGetMember<uint32_t>(\"FocusTint\");\n  GetMemberArray<glm::vec2>(\n      std::span(MenuEntriesPositions, Profile::SystemMenu::MenuEntriesNum),\n      \"MenuEntriesPositions\");\n  Background = EnsureGetMember<Sprite>(\"SystemMenuBackground\");\n  BackgroundPosition =\n      EnsureGetMember<glm::vec2>(\"SystemMenuBackgroundPosition\");\n\n  MenuItemsLine = EnsureGetMember<Sprite>(\"SystemMenuItemsLine\");\n  MenuItemsLinePosition =\n      EnsureGetMember<glm::vec2>(\"SystemMenuItemsLinePosition\");\n  MainMenuTitleText = EnsureGetMember<Sprite>(\"MainMenuTitleText\");\n  MainMenuLabelRightPosition =\n      EnsureGetMember<glm::vec2>(\"MainMenuLabelRightPosition\");\n  MenuTitleTextAngle = EnsureGetMember<float>(\"MenuTitleTextAngle\");\n  MenuTitleTextPosition = EnsureGetMember<glm::vec2>(\"MenuTitleTextPosition\");\n  MenuSelectionDot = EnsureGetMember<Sprite>(\"SystemMenuSelectionDot\");\n  MenuSelectionDotPosition =\n      EnsureGetMember<glm::vec2>(\"SystemMenuSelectionDotPosition\");\n  MenuSelectionDotMultiplier =\n      EnsureGetMember<float>(\"SystemMenuSelectionDotMultiplier\");\n  MenuSelection = EnsureGetMember<Sprite>(\"SystemMenuSelection\");\n  MenuSelectionPosition =\n      EnsureGetMember<glm::vec2>(\"SystemMenuSelectionPosition\");\n\n  SelectMenuHeader = EnsureGetMember<std::vector<Sprite>>(\"SelectMenuSprites\");\n  SelectMenuHeaderPositions =\n      EnsureGetMember<std::vector<glm::vec2>>(\"SelectMenuTextPositions\");\n  if (SelectMenuHeader.size() != SelectMenuHeaderPositions.size()) {\n    throw std::runtime_error(\"Related arrays have mismatching sizes\");\n  }\n\n  MenuLoopDuration = EnsureGetMember<float>(\"MenuLoopDuration\");\n  HoverLerpSpeed = EnsureGetMember<float>(\"MenuHoverLerpSpeed\");\n  MenuRunningSelectedLabel =\n      EnsureGetMember<Sprite>(\"SystemMenuRunningSelectedLabel\");\n  MenuRunningSelectedLabelPosition =\n      EnsureGetMember<glm::vec2>(\"SystemMenuRunningSelectedLabelPosition\");\n  MenuRunningSelectedLabelAngle =\n      EnsureGetMember<float>(\"SystemMenuRunningSelectedLabelAngle\");\n  MenuButtonPrompt = EnsureGetMember<Sprite>(\"SystemMenuButtonPrompt\");\n  MenuButtonPromptPosition =\n      EnsureGetMember<glm::vec2>(\"SystemMenuButtonPromptPosition\");\n  SelectedLabelSpeed = EnsureGetMember<float>(\"MenuSelectedLabelSpeed\");\n  StarAnimationDuration = EnsureGetMember<float>(\"StarAnimationDuration\");\n  StarRotationSpeed = EnsureGetMember<float>(\"StarRotationSpeed\");\n  LeftAngle = EnsureGetMember<float>(\"LeftAngle\");\n  GetMemberArray<glm::vec2>(std::span(StarsOffsetsStart.data(), STAR_COUNT),\n                            \"StarsOffsetsStart\");\n  GetMemberArray<glm::vec2>(std::span(StarsOffsetsEnd.data(), STAR_COUNT),\n                            \"StarsOffsetsEnd\");\n  StarSprite = EnsureGetMember<Sprite>(\"StarSprite\");\n\n  auto drawType = EnsureGetMember<Game::DrawComponentType>(\"DrawType\");\n\n  UI::SystemMenuPtr = new UI::CHLCC::SystemMenu();\n  UI::Menus[drawType].push_back(UI::SystemMenuPtr);\n}\n\n}  // namespace SystemMenu\n}  // namespace CHLCC\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/chlcc/systemmenu.h",
    "content": "#pragma once\n\n#include \"../../../spritesheet.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace CHLCC {\nnamespace SystemMenu {\nconstexpr int MenuEntriesNumMax = 9;\nconstexpr int STAR_COUNT = 6;\n\ninline uint32_t BackgroundColor;\ninline Sprite CircleSprite;\ninline uint32_t FocusTint;\ninline glm::vec2 MenuEntriesPositions[MenuEntriesNumMax];\ninline Sprite Background;\ninline glm::vec2 BackgroundPosition;\n\ninline float MenuLoopDuration;\ninline Sprite MenuBackground;\ninline glm::vec2 MenuBackgroundPosition;\ninline Sprite MenuItemsLine;\ninline glm::vec2 MenuItemsLinePosition;\ninline Sprite MainMenuTitleText;\ninline glm::vec2 MenuTitleTextPosition;\ninline glm::vec2 MainMenuLabelRightPosition;\ninline float MenuTitleTextAngle;\ninline Sprite MenuSelectionDot;\ninline glm::vec2 MenuSelectionDotPosition;\ninline float MenuSelectionDotMultiplier;\ninline Sprite MenuSelection;\ninline glm::vec2 MenuSelectionPosition;\ninline std::vector<Sprite> SelectMenuHeader;\ninline std::vector<glm::vec2> SelectMenuHeaderPositions;\ninline int SelectMenuHeaderCount;\ninline Sprite MenuRunningSelectedLabel;\ninline glm::vec2 MenuRunningSelectedLabelPosition;\ninline float MenuRunningSelectedLabelAngle;\ninline Sprite MenuButtonPrompt;\ninline glm::vec2 MenuButtonPromptPosition;\ninline glm::vec2 MenuTitleTextRightPosition;\ninline glm::vec2 MenuCHLCCLabelPosition;\ninline float SelectedLabelSpeed;\ninline float HoverLerpSpeed;\n\ninline float StarAnimationDuration;\ninline float StarRotationSpeed;\ninline float LeftAngle;\ninline std::array<glm::vec2, STAR_COUNT> StarsOffsetsStart;\ninline std::array<glm::vec2, STAR_COUNT> StarsOffsetsEnd;\ninline Sprite StarSprite;\n\nvoid Configure();\n\n}  // namespace SystemMenu\n}  // namespace CHLCC\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/chlcc/tipsmenu.cpp",
    "content": "#include \"tipsmenu.h\"\n#include \"../../profile_internal.h\"\n#include \"../../../log.h\"\n#include \"../../../text/text.h\"\n\n#include \"../../ui/tipsmenu.h\"\n#include \"../../../game.h\"\n#include \"../../../ui/ui.h\"\n#include \"../../../games/chlcc/tipsmenu.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace CHLCC {\nnamespace TipsMenu {\n\nvoid Configure() {\n  BackgroundColor = EnsureGetMember<uint32_t>(\"BackgroundColor\");\n  CircleSprite = EnsureGetMember<Sprite>(\"CircleSprite\");\n  MenuTitleTextRightPosition =\n      EnsureGetMember<glm::vec2>(\"MenuTitleTextRightPos\");\n  MenuTitleTextLeftPosition =\n      EnsureGetMember<glm::vec2>(\"MenuTitleTextLeftPos\");\n  MenuTitleTextAngle = EnsureGetMember<float>(\"MenuTitleTextAngle\");\n  MenuTitleText = EnsureGetMember<Sprite>(\"MenuTitleText\");\n  TreePosition = EnsureGetMember<glm::vec2>(\"TreePosition\");\n  TipsTree = EnsureGetMember<Sprite>(\"TipsTree\");\n  GradientPosition = EnsureGetMember<glm::vec2>(\"GradientPosition\");\n  TipsGradient = EnsureGetMember<Sprite>(\"TipsGradient\");\n  EndOfGradientColor = EnsureGetMember<uint32_t>(\"EndOfGradientColor\");\n  CurrentTipBackgroundPosition =\n      EnsureGetMember<glm::vec2>(\"CurrentTipBackgroundPosition\");\n  CurrentTipBackgroundSprite =\n      EnsureGetMember<Sprite>(\"CurrentTipBackgroundSprite\");\n  ButtonPromptPosition = EnsureGetMember<glm::vec2>(\"ButtonPromptPosition\");\n  ButtonPromptSprite = EnsureGetMember<Sprite>(\"ButtonPromptSprite\");\n\n  TipsStringTable = EnsureGetMember<int>(\"TipsStringTable\");\n  CategoryStringIndex = EnsureGetMember<int>(\"CategoryStringIndex\");\n  SortStringIndex = EnsureGetMember<int>(\"SortStringIndex\");\n  PageSeparatorIndex = EnsureGetMember<int>(\"PageSeparatorIndex\");\n  LockedTipsIndex = EnsureGetMember<int>(\"LockedTipsIndex\");\n  NumberLabelStrIndex = EnsureGetMember<int>(\"NumberLabelStrIndex\");\n  NewLabelStrIndex = EnsureGetMember<int>(\"NewLabelStrIndex\");\n  UnreadLabelStrIndex = EnsureGetMember<int>(\"UnreadLabelStrIndex\");\n  {\n    EnsurePushMember(\"CategoryString\");\n    auto str = EnsureGet<char const*>();\n    TextGetSc3String(str, CategoryString);\n    Pop();\n  }\n  NumberLabelPosition = EnsureGetMember<glm::vec2>(\"NumberLabelPosition\");\n  NumberLabelFontSize = EnsureGetMember<float>(\"NumberLabelFontSize\");\n  NumberBounds = EnsureGetMember<RectF>(\"NumberBounds\");\n  NumberFontSize = EnsureGetMember<float>(\"NumberFontSize\");\n\n  DefaultColorIndex = EnsureGetMember<int>(\"DefaultColorIndex\");\n  UnreadColorIndex = EnsureGetMember<int>(\"UnreadColorIndex\");\n\n  TipListEntryBounds = EnsureGetMember<RectF>(\"TipListEntryBounds\");\n  TipListEntryFontSize = EnsureGetMember<float>(\"TipListEntryFontSize\");\n  TipListYPadding = EnsureGetMember<float>(\"TipListYPadding\");\n  TipListEntryTextOffsetX = EnsureGetMember<float>(\"TipListEntryTextOffsetX\");\n\n  PronounciationFontSize = EnsureGetMember<float>(\"PronounciationFontSize\");\n  NameFontSize = EnsureGetMember<float>(\"NameFontSize\");\n\n  TipListEntryNameXOffset = EnsureGetMember<float>(\"TipListEntryNameXOffset\");\n\n  NameInitialBounds = EnsureGetMember<RectF>(\"NameInitialBounds\");\n  PronounciationInitialBounds =\n      EnsureGetMember<RectF>(\"PronounciationInitialBounds\");\n\n  TipsListBounds = EnsureGetMember<RectF>(\"TipsListBounds\");\n  TipScrollbarPos = EnsureGetMember<glm::vec2>(\"TipScrollbarPos\");\n  TipsListRenderBounds = EnsureGetMember<RectF>(\"TipsListRenderBounds\");\n\n  TipsEntryHighlightBar =\n      EnsureGetMember<Sprite>(\"TipsEntryHighlightBarSprite\");\n  TipsEntryHighlightDot =\n      EnsureGetMember<Sprite>(\"TipsEntryHighlightDotSprite\");\n  TipsEntryNewDot = EnsureGetMember<Sprite>(\"TipsEntryNewDotSprite\");\n  TipsLeftLine = EnsureGetMember<Sprite>(\"TipsLeftLineSprite\");\n  TipsLeftLineHole = EnsureGetMember<Sprite>(\"TipsLeftLineHoleSprite\");\n  TipsLeftLineEnd = EnsureGetMember<Sprite>(\"TipsLeftLineEndSprite\");\n  TipsLeftLineHoleEnd = EnsureGetMember<Sprite>(\"TipsLeftLineHoleEndSprite\");\n  TipsListBgBar = EnsureGetMember<Sprite>(\"TipsListBgBarSprite\");\n  TipsListBgBarHole = EnsureGetMember<Sprite>(\"TipsListBgBarHoleSprite\");\n\n  TipsListEntryDotOffset = EnsureGetMember<glm::vec2>(\"TipsListEntryDotOffset\");\n  TipsListNewDotOffset = EnsureGetMember<glm::vec2>(\"TipsListNewDotOffset\");\n  TipsScrollThumb = EnsureGetMember<Sprite>(\"TipsScrollThumbSprite\");\n  TipsScrollTrack = EnsureGetMember<Sprite>(\"TipsScrollTrackSprite\");\n\n  CurrentPagePosition = EnsureGetMember<glm::vec2>(\"CurrentPagePosition\");\n  TotalPagesPosition = EnsureGetMember<glm::vec2>(\"TotalPagesPosition\");\n  PageSeparatorPosition = EnsureGetMember<glm::vec2>(\"PageSeparatorPosition\");\n  CurrentPageSprites =\n      EnsureGetMember<std::vector<Sprite>>(\"CurrentPageSprites\");\n  TotalPageSprites = EnsureGetMember<std::vector<Sprite>>(\"TotalPageSprites\");\n  PageSeparatorSprite = EnsureGetMember<Sprite>(\"PageSeparatorSprite\");\n\n  SelectWordSprites = EnsureGetMember<std::vector<Sprite>>(\"SelectWordSprites\");\n  SelectWordPos = EnsureGetMember<std::vector<glm::vec2>>(\"SelectWordPos\");\n  if (SelectWordSprites.size() != SelectWordPos.size()) {\n    throw std::runtime_error(\"Related arrays have mismatching sizes\");\n  }\n\n  auto drawType = EnsureGetMember<Game::DrawComponentType>(\"DrawType\");\n\n  UI::TipsMenuPtr = new UI::CHLCC::TipsMenu();\n  UI::Menus[drawType].push_back(UI::TipsMenuPtr);\n}\n\n}  // namespace TipsMenu\n}  // namespace CHLCC\n}  // namespace Profile\n}  // namespace Impacto\n"
  },
  {
    "path": "src/profile/games/chlcc/tipsmenu.h",
    "content": "#pragma once\n\n#include \"../../../spritesheet.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace CHLCC {\nnamespace TipsMenu {\n\nint constexpr inline MaxCategoryString = 5;\n\ninline uint32_t BackgroundColor;\ninline Sprite CircleSprite;\ninline glm::vec2 MenuTitleTextRightPosition;\ninline glm::vec2 MenuTitleTextLeftPosition;\ninline float MenuTitleTextAngle;\ninline Sprite MenuTitleText;\ninline glm::vec2 TreePosition;\ninline Sprite TipsTree;\ninline glm::vec2 GradientPosition;\ninline Sprite TipsGradient;\ninline uint32_t EndOfGradientColor;\ninline glm::vec2 CurrentTipBackgroundPosition;\ninline Sprite CurrentTipBackgroundSprite;\ninline glm::vec2 ButtonPromptPosition;\ninline Sprite ButtonPromptSprite;\ninline int TipsStringTable;\ninline int CategoryStringIndex;\ninline int SortStringIndex;\ninline int PageSeparatorIndex;\ninline int LockedTipsIndex;\ninline int NumberLabelStrIndex;\ninline int NewLabelStrIndex;\ninline int UnreadLabelStrIndex;\ninline uint16_t CategoryString[MaxCategoryString];\ninline glm::vec2 NumberLabelPosition;\ninline float NumberLabelFontSize;\ninline RectF NumberBounds;\ninline float NumberFontSize;\ninline int DefaultColorIndex;\ninline int UnreadColorIndex;\ninline RectF TipListEntryBounds;\ninline float TipListEntryFontSize;\ninline float TipListYPadding;\ninline float PronounciationFontSize;\ninline float NameFontSize;\ninline float TipListEntryNameXOffset;\ninline float TipListEntryTextOffsetX;\ninline glm::vec2 TipsListEntryDotOffset;\ninline glm::vec2 TipsListNewDotOffset;\ninline RectF NameInitialBounds;\ninline RectF PronounciationInitialBounds;\ninline RectF TipsListBounds;\ninline glm::vec2 TipScrollbarPos;\ninline RectF TipsListRenderBounds;\ninline Sprite TipsEntryHighlightBar;\ninline Sprite TipsEntryHighlightDot;\ninline Sprite TipsEntryNewDot;\n\ninline Sprite TipsLeftLine;\ninline Sprite TipsLeftLineHole;\ninline Sprite TipsLeftLineEnd;\ninline Sprite TipsLeftLineHoleEnd;\ninline Sprite TipsListBgBar;\ninline Sprite TipsListBgBarHole;\ninline Sprite TipsScrollThumb;\ninline Sprite TipsScrollTrack;\n\ninline glm::vec2 CurrentPagePosition;\ninline glm::vec2 TotalPagesPosition;\ninline glm::vec2 PageSeparatorPosition;\ninline std::vector<Sprite> CurrentPageSprites;\ninline std::vector<Sprite> TotalPageSprites;\ninline Sprite PageSeparatorSprite;\n\ninline std::vector<Sprite> SelectWordSprites;\ninline std::vector<glm::vec2> SelectWordPos;\n\nvoid Configure();\n\n}  // namespace TipsMenu\n}  // namespace CHLCC\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/chlcc/tipsnotification.cpp",
    "content": "#include \"tipsnotification.h\"\n\n#include \"../../profile_internal.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace CHLCC {\nnamespace TipsNotification {\n\nvoid Configure() {\n  HeaderMessageId = EnsureGetMember<int>(\"HeaderMessageId\");\n  HeaderPosition = EnsureGetMember<glm::vec2>(\"HeaderPosition\");\n  HeaderFontSize = EnsureGetMember<float>(\"HeaderFontSize\");\n  HeaderColor = EnsureGetMember<DialogueColorPair>(\"HeaderColor\");\n\n  TextStartPosition = EnsureGetMember<glm::vec2>(\"TextStartPosition\");\n  TextTargetPosition = EnsureGetMember<glm::vec2>(\"TextTargetPosition\");\n  TextFontSize = EnsureGetMember<float>(\"TextFontSize\");\n  TipNameColorIndex = EnsureGetMember<size_t>(\"TipNameColorIndex\");\n\n  SlideTime = EnsureGetMember<float>(\"SlideTime\");\n  HoldTime = EnsureGetMember<float>(\"HoldTime\");\n\n  RenderBounds = EnsureGetMember<RectF>(\"RenderBounds\");\n}\n\n}  // namespace TipsNotification\n}  // namespace CHLCC\n}  // namespace Profile\n}  // namespace Impacto\n"
  },
  {
    "path": "src/profile/games/chlcc/tipsnotification.h",
    "content": "#pragma once\n\n#include \"../../../text/text.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace CHLCC {\nnamespace TipsNotification {\n\ninline int HeaderMessageId;\ninline glm::vec2 HeaderPosition;\ninline float HeaderFontSize;\ninline DialogueColorPair HeaderColor;\n\ninline glm::vec2 TextStartPosition;\ninline glm::vec2 TextTargetPosition;\ninline float TextFontSize;\ninline size_t TipNameColorIndex;\n\ninline float SlideTime;\ninline float HoldTime;\n\ninline RectF RenderBounds;\n\nvoid Configure();\n\n}  // namespace TipsNotification\n}  // namespace CHLCC\n}  // namespace Profile\n}  // namespace Impacto\n"
  },
  {
    "path": "src/profile/games/chlcc/titlemenu.cpp",
    "content": "#include \"titlemenu.h\"\n#include \"../../../renderer/renderer.h\"\n#include \"../../profile_internal.h\"\n\n#include \"../../ui/titlemenu.h\"\n#include \"../../game.h\"\n#include \"../../../ui/ui.h\"\n#include \"../../../games/chlcc/titlemenu.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace CHLCC {\nnamespace TitleMenu {\n\nvoid Configure() {\n  if (HasScriptedExitLogic) {\n    ExitSprite = EnsureGetMember<Sprite>(\"ExitSprite\");\n    ExitHighlightSprite = EnsureGetMember<Sprite>(\"ExitHighlightSprite\");\n  }\n\n  BackgroundSprite = EnsureGetMember<Sprite>(\"BackgroundSprite\");\n  DelusionADVUnderSprite = EnsureGetMember<Sprite>(\"DelusionADVUnderSprite\");\n  DelusionADVSprite = EnsureGetMember<Sprite>(\"DelusionADVSprite\");\n  DelusionADVPosition = EnsureGetMember<glm::vec2>(\"DelusionADVPosition\");\n  DelusionADVPopoutOffset =\n      EnsureGetMember<glm::vec2>(\"DelusionADVPopoutOffset\");\n  SeiraUnderSprite = EnsureGetMember<Sprite>(\"SeiraUnderSprite\");\n  SeiraUnderPosition = EnsureGetMember<glm::vec2>(\"SeiraUnderPosition\");\n  SeiraSprite = EnsureGetMember<Sprite>(\"SeiraSprite\");\n  SeiraPosition = EnsureGetMember<glm::vec2>(\"SeiraPosition\");\n  CHLogoSprite = EnsureGetMember<Sprite>(\"CHLogoSprite\");\n  CHLogoPosition = EnsureGetMember<glm::vec2>(\"CHLogoPosition\");\n  LCCLogoUnderSprite = EnsureGetMember<Sprite>(\"LCCLogoUnderSprite\");\n  LCCLogoUnderPosition = EnsureGetMember<glm::vec2>(\"LCCLogoUnderPosition\");\n  StarLogoSprite = EnsureGetMember<Sprite>(\"StarLogoSprite\");\n  StarLogoPosition = EnsureGetMember<glm::vec2>(\"StarLogoPosition\");\n  CopyrightTextSprite = EnsureGetMember<Sprite>(\"CopyrightTextSprite\");\n  CopyrightTextPosition = EnsureGetMember<glm::vec2>(\"CopyrightTextPosition\");\n  SpinningCircleSprite = EnsureGetMember<Sprite>(\"SpinningCircleSprite\");\n  SpinningCirclePosition = EnsureGetMember<glm::vec2>(\"SpinningCirclePosition\");\n  SpinningCircleAnimationDuration =\n      EnsureGetMember<float>(\"SpinningCircleAnimationDuration\");\n  SpinningCircleFlashingAnimationDuration =\n      EnsureGetMember<float>(\"SpinningCircleFlashingAnimationDuration\");\n  ItemHighlightSprite = EnsureGetMember<Sprite>(\"ItemHighlightSprite\");\n  ItemHighlightOffset = EnsureGetMember<glm::vec2>(\"ItemHighlightOffset\");\n  ItemPadding = EnsureGetMember<float>(\"ItemPadding\");\n  ItemYBase = EnsureGetMember<float>(\"ItemYBase\");\n  ItemFadeInDuration = EnsureGetMember<float>(\"ItemFadeInDuration\");\n  ItemFadeOutDuration = EnsureGetMember<float>(\"ItemFadeOutDuration\");\n  SecondaryItemFadeInDuration =\n      EnsureGetMember<float>(\"SecondaryItemFadeInDuration\");\n  SecondaryItemFadeOutDuration =\n      EnsureGetMember<float>(\"SecondaryItemFadeOutDuration\");\n  PrimaryFadeInDuration = EnsureGetMember<float>(\"PrimaryFadeInDuration\");\n  PrimaryFadeOutDuration = EnsureGetMember<float>(\"PrimaryFadeOutDuration\");\n  SecondaryFadeInDuration = EnsureGetMember<float>(\"SecondaryFadeInDuration\");\n  SecondaryFadeOutDuration = EnsureGetMember<float>(\"SecondaryFadeOutDuration\");\n  LineNum = EnsureGetMember<int>(\"LineNum\");\n  GetMemberArray<Sprite>(std::span(LineSprites, LineNum), \"LineEntriesSprites\");\n  ItemLoadQuickSprite = EnsureGetMember<Sprite>(\"ItemLoadQuickSprite\");\n  SecondaryItemX = EnsureGetMember<float>(\"SecondaryItemX\");\n  ItemLoadY = EnsureGetMember<float>(\"ItemLoadY\");\n  ItemLoadQuickY = EnsureGetMember<float>(\"ItemLoadQuickY\");\n  ItemLoadSprite = EnsureGetMember<Sprite>(\"ItemLoadSprite\");\n  ItemLoadQuickHighlightedSprite =\n      EnsureGetMember<Sprite>(\"ItemLoadQuickHighlightedSprite\");\n  ItemLoadHighlightedSprite =\n      EnsureGetMember<Sprite>(\"ItemLoadHighlightedSprite\");\n  ItemClearListY = EnsureGetMember<float>(\"ItemClearListY\");\n  ItemCGLibraryY = EnsureGetMember<float>(\"ItemCGLibraryY\");\n  ItemSoundLibraryY = EnsureGetMember<float>(\"ItemSoundLibraryY\");\n  ItemMovieLibraryY = EnsureGetMember<float>(\"ItemMovieLibraryY\");\n  ItemTipsY = EnsureGetMember<float>(\"ItemTipsY\");\n  ItemTrophyY = EnsureGetMember<float>(\"ItemTrophyY\");\n  ItemConfigY = EnsureGetMember<float>(\"ItemConfigY\");\n  ItemSystemSaveY = EnsureGetMember<float>(\"ItemSystemSaveY\");\n  SecondaryItemHighlightSprite =\n      EnsureGetMember<Sprite>(\"SecondaryItemHighlightSprite\");\n  SecondaryItemHighlightX = EnsureGetMember<float>(\"SecondaryItemHighlightX\");\n  SecondaryMenuPaddingY = EnsureGetMember<float>(\"SecondaryMenuPaddingY\");\n  SecondaryMenuLoadOffsetY = EnsureGetMember<float>(\"SecondaryMenuLoadOffsetY\");\n  SecondaryMenuLineX = EnsureGetMember<float>(\"SecondaryMenuLineX\");\n  SecondaryMenuLoadLineY = EnsureGetMember<float>(\"SecondaryMenuLoadLineY\");\n  SecondaryMenuLoadQuickLineY =\n      EnsureGetMember<float>(\"SecondaryMenuLoadQuickLineY\");\n  SecondaryMenuExtraClearY = EnsureGetMember<float>(\"SecondaryMenuExtraClearY\");\n  SecondaryMenuExtraCGY = EnsureGetMember<float>(\"SecondaryMenuExtraCGY\");\n  SecondaryMenuExtraSoundY = EnsureGetMember<float>(\"SecondaryMenuExtraSoundY\");\n  SecondaryMenuExtraMovieY = EnsureGetMember<float>(\"SecondaryMenuExtraMovieY\");\n  SecondaryMenuExtraTipsY = EnsureGetMember<float>(\"SecondaryMenuExtraTipsY\");\n  SecondaryMenuExtraTrophyY =\n      EnsureGetMember<float>(\"SecondaryMenuExtraTrophyY\");\n  SecondaryMenuSystemConfigY =\n      EnsureGetMember<float>(\"SecondaryMenuSystemConfigY\");\n  SecondaryMenuSystemSaveY = EnsureGetMember<float>(\"SecondaryMenuSystemSaveY\");\n\n  GetMemberArray<Sprite>(\n      std::span(IntroHighlightSprites.data(), IntroHighlightCount),\n      \"IntroHighlightSprites\");\n  GetMemberArray<float>(\n      std::span(IntroHighlightPositions.data(), IntroHighlightCount),\n      \"IntroHighlightPositions\");\n\n  GetMemberArray<Sprite>(std::span(LCCLogoSprites.data(), LCCLogoSpriteCount),\n                         \"LCCLogoSprites\");\n  GetMemberArray<glm::vec2>(\n      std::span(LCCLogoPositions.data(), LCCLogoSpriteCount),\n      \"LCCLogoPositions\");\n\n  IntroBackgroundSprite = EnsureGetMember<Sprite>(\"IntroBackgroundSprite\");\n  IntroPanningAnimationDuration =\n      EnsureGetMember<float>(\"IntroPanningAnimationDuration\");\n\n  IntroBouncingStarSprite = EnsureGetMember<Sprite>(\"IntroBouncingStarSprite\");\n  IntroStarBounceAnimationDuration =\n      EnsureGetMember<float>(\"IntroBouncingStarAnimationDuration\");\n\n  IntroExplodingStarSprite =\n      EnsureGetMember<Sprite>(\"IntroExplodingStarSprite\");\n  IntroExplodingStarAnimationDistance =\n      EnsureGetMember<float>(\"IntroExplodingStarAnimationDistance\");\n\n  IntroExplodingStarRotationAnimationDuration =\n      EnsureGetMember<float>(\"IntroExplodingStarAnimationRotationDuration\");\n\n  IntroExplodingStarAnimationDuration =\n      EnsureGetMember<float>(\"IntroExplodingStarAnimationDuration\");\n\n  IntroFallingStarSprite = EnsureGetMember<Sprite>(\"IntroFallingStarSprite\");\n  IntroFallingStarsAnimationDistance =\n      EnsureGetMember<float>(\"IntroFallingStarsAnimationDistance\");\n  IntroFallingStarsAnimationDirection = glm::normalize(\n      EnsureGetMember<glm::vec2>(\"IntroFallingStarsAnimationDirection\"));\n  IntroFallingStarsAnimationDuration =\n      EnsureGetMember<float>(\"IntroFallingStarsAnimationDuration\");\n\n  IntroFallingStarsRotationAnimationDuration =\n      EnsureGetMember<float>(\"IntroFallingStarsAnimationRotationDuration\");\n\n  IntroCHLogoFadeAnimationStartY =\n      EnsureGetMember<float>(\"IntroCHLogoFadeAnimationStartY\");\n  IntroLogoFadeAnimationDuration =\n      EnsureGetMember<float>(\"IntroCHLogoFadeAnimationDuration\");\n\n  IntroLCCLogoAnimationDuration =\n      EnsureGetMember<float>(\"IntroLCCLogoAnimationDuration\");\n\n  IntroLogoStarHighlightAnimationDuration =\n      EnsureGetMember<float>(\"IntroLogoStarHighlightAnimationDuration\");\n  IntroLogoStarHighlightSprite =\n      EnsureGetMember<Sprite>(\"IntroLogoStarHighlightSprite\");\n  IntroLogoStarHighlightPosition =\n      EnsureGetMember<glm::vec2>(\"IntroLogoStarHighlightPosition\");\n\n  IntroDelusionADVSpriteCount =\n      EnsureGetMember<int>(\"IntroDelusionADVSpriteCount\");\n  GetMemberArray<Sprite>(\n      std::span(IntroDelusionADVSprites.data(), IntroDelusionADVSpriteCount),\n      \"IntroDelusionADVSprites\");\n  GetMemberArray<glm::vec2>(\n      std::span(IntroDelusionADVPositions.data(), IntroDelusionADVSpriteCount),\n      \"IntroDelusionADVPositions\");\n  IntroDelusionADVAnimationDuration =\n      EnsureGetMember<float>(\"IntroDelusionADVAnimationDuration\");\n\n  SeiraPopoutOffset = EnsureGetMember<glm::vec2>(\"SeiraPopoutOffset\");\n  IntroSeiraAnimationDuration =\n      EnsureGetMember<float>(\"IntroSeiraAnimationDuration\");\n\n  IntroDelusionADVHighlightAnimationDuration =\n      EnsureGetMember<float>(\"IntroDelusionADVHighlightAnimationDuration\");\n\n  IntroLogoPopOutOffset = EnsureGetMember<glm::vec2>(\"IntroLogoPopOutOffset\");\n  IntroLogoPopOutAnimationDuration =\n      EnsureGetMember<float>(\"IntroLogoPopOutAnimationDuration\");\n  IntroLogoPopOutAnimationDelay =\n      EnsureGetMember<float>(\"IntroLogoPopOutAnimationDelay\");\n\n  IntroCopyrightAnimationDuration =\n      EnsureGetMember<float>(\"IntroCopyrightAnimationDuration\");\n\n  IntroAfterPanningWaitDuration =\n      EnsureGetMember<float>(\"IntroAfterPanningWaitDuration\");\n\n  UI::CHLCC::TitleMenu* menu = new UI::CHLCC::TitleMenu();\n  menu->PressToStartAnimation.DurationIn =\n      Profile::TitleMenu::PressToStartAnimDurationIn;\n  menu->PressToStartAnimation.DurationOut =\n      Profile::TitleMenu::PressToStartAnimDurationOut;\n  menu->PressToStartAnimation.LoopMode = AnimationLoopMode::ReverseDirection;\n\n  menu->PrimaryFadeAnimation.DurationIn = PrimaryFadeInDuration;\n  menu->PrimaryFadeAnimation.DurationOut = PrimaryFadeOutDuration;\n\n  menu->SecondaryFadeAnimation.DurationIn = SecondaryFadeInDuration;\n  menu->SecondaryFadeAnimation.DurationOut = SecondaryFadeOutDuration;\n\n  menu->SpinningCircleAnimation.LoopMode = AnimationLoopMode::Loop;\n  menu->SpinningCircleAnimation.SetDuration(SpinningCircleAnimationDuration);\n\n  menu->SpinningCircleFlashingAnimation.LoopMode =\n      AnimationLoopMode::ReverseDirection;\n\n  UI::TitleMenuPtr = menu;\n}\n\n}  // namespace TitleMenu\n}  // namespace CHLCC\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/chlcc/titlemenu.h",
    "content": "#pragma once\n\n#include \"../../../spritesheet.h\"\n#include \"../../../games/chlcc/titlemenu.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace CHLCC {\nnamespace TitleMenu {\n\nvoid Configure();\n\ninline Sprite ExitSprite;\ninline Sprite ExitHighlightSprite;\n\ninline int LineNum;\nint constexpr LineEntriesNumMax = 32;\ninline Sprite LineSprites[LineEntriesNumMax];\n\ninline Sprite BackgroundSprite;\n\ninline Sprite DelusionADVUnderSprite;\ninline Sprite DelusionADVSprite;\ninline glm::vec2 DelusionADVPosition;\ninline glm::vec2 DelusionADVPopoutOffset;\n\ninline Sprite SeiraUnderSprite;\ninline Sprite SeiraSprite;\ninline glm::vec2 SeiraUnderPosition;\ninline glm::vec2 SeiraPosition;\ninline glm::vec2 SeiraPopoutOffset;\n\ninline Sprite CHLogoSprite;\ninline glm::vec2 CHLogoPosition;\n\ninline Sprite LCCLogoUnderSprite;\ninline glm::vec2 LCCLogoUnderPosition;\n\ninline Sprite StarLogoSprite;\ninline glm::vec2 StarLogoPosition;\n\ninline Sprite CopyrightTextSprite;\ninline glm::vec2 CopyrightTextPosition;\n\ninline Sprite SpinningCircleSprite;\ninline glm::vec2 SpinningCirclePosition;\ninline float SpinningCircleAnimationDuration;\ninline float SpinningCircleFlashingAnimationDuration;\n\ninline Sprite ItemHighlightSprite;\ninline Sprite ItemLoadHighlightedSprite;\ninline Sprite SecondaryItemHighlightSprite;\ninline glm::vec2 ItemHighlightOffset;\ninline float ItemPadding;\ninline float ItemYBase;\ninline float ItemFadeInDuration;\ninline float ItemFadeOutDuration;\n\ninline float PrimaryFadeInDuration;\ninline float PrimaryFadeOutDuration;\n\ninline float SecondaryFadeInDuration;\ninline float SecondaryFadeOutDuration;\ninline float SecondaryItemX;\ninline float SecondaryItemFadeInDuration;\ninline float SecondaryItemFadeOutDuration;\n\ninline Sprite ItemLoadQuickSprite;\ninline Sprite ItemLoadSprite;\ninline Sprite ItemLoadQuickHighlightedSprite;\ninline float ItemLoadY;\ninline float ItemLoadQuickY;\ninline float ItemClearListY;\ninline float ItemCGLibraryY;\ninline float ItemSoundLibraryY;\ninline float ItemMovieLibraryY;\ninline float ItemTipsY;\ninline float ItemTrophyY;\ninline float ItemConfigY;\ninline float ItemSystemSaveY;\n\ninline float SecondaryItemHighlightX;\ninline float SecondaryMenuPaddingY;\ninline float SecondaryMenuLoadOffsetY;\ninline float SecondaryMenuLineX;\ninline float SecondaryMenuLoadLineY;\ninline float SecondaryMenuLoadQuickLineY;\ninline float SecondaryMenuExtraClearY;\ninline float SecondaryMenuExtraCGY;\ninline float SecondaryMenuExtraSoundY;\ninline float SecondaryMenuExtraMovieY;\ninline float SecondaryMenuExtraTipsY;\ninline float SecondaryMenuExtraTrophyY;\ninline float SecondaryMenuSystemConfigY;\ninline float SecondaryMenuSystemSaveY;\n\ninline Sprite IntroBackgroundSprite;\n\nconstexpr size_t IntroHighlightCount = 10;\ninline std::array<Sprite, IntroHighlightCount> IntroHighlightSprites;\ninline std::array<float, IntroHighlightCount> IntroHighlightPositions;\ninline float IntroPanningAnimationDuration;\ninline float IntroAfterPanningWaitDuration;\n\ninline Sprite IntroExplodingStarSprite;\ninline float IntroExplodingStarAnimationDistance;\ninline float IntroExplodingStarAnimationDuration;\ninline float IntroExplodingStarRotationAnimationDuration;\n\ninline Sprite IntroBouncingStarSprite;\ninline float IntroStarBounceAnimationDuration;\n\ninline Sprite IntroFallingStarSprite;\ninline glm::vec2 IntroFallingStarsAnimationDirection;\ninline float IntroFallingStarsAnimationDistance;\ninline float IntroFallingStarsAnimationDuration;\ninline float IntroFallingStarsRotationAnimationDuration;\n\ninline float IntroCHLogoFadeAnimationStartY;\ninline float IntroLogoFadeAnimationDuration;\n\nconstexpr size_t LCCLogoSpriteCount = 4;\nconstexpr std::array<size_t, 4> LCCLogoDrawOrder = {1, 2, 0, 3};\ninline std::array<glm::vec2, LCCLogoSpriteCount> LCCLogoPositions;\ninline std::array<Sprite, LCCLogoSpriteCount> LCCLogoSprites;\ninline float IntroLCCLogoAnimationDuration;\n\ninline Sprite IntroLogoStarHighlightSprite;\ninline glm::vec2 IntroLogoStarHighlightPosition;\ninline float IntroLogoStarHighlightAnimationDuration;\n\nconstexpr size_t IntroDelusionADVMaxSpriteCount = 7;\ninline int IntroDelusionADVSpriteCount;\ninline std::array<Sprite, IntroDelusionADVMaxSpriteCount>\n    IntroDelusionADVSprites;\ninline std::array<glm::vec2, IntroDelusionADVMaxSpriteCount>\n    IntroDelusionADVPositions;\ninline float IntroDelusionADVAnimationDuration;\ninline float IntroDelusionADVHighlightAnimationDuration;\n\ninline float IntroSeiraAnimationDuration;\n\ninline glm::vec2 IntroLogoPopOutOffset;\ninline float IntroLogoPopOutAnimationDuration;\ninline float IntroLogoPopOutAnimationDelay;\n\ninline float IntroCopyrightAnimationDuration;\n\n}  // namespace TitleMenu\n}  // namespace CHLCC\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/chlcc/trophymenu.cpp",
    "content": "#include \"trophymenu.h\"\n#include \"../../profile_internal.h\"\n#include \"../../../log.h\"\n#include \"../../../text/text.h\"\n\n#include \"../../ui/trophymenu.h\"\n#include \"../../../game.h\"\n#include \"../../../ui/ui.h\"\n#include \"../../../games/chlcc/trophymenu.h\"\n#include \"../../../data/achievementsystem.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace CHLCC {\nnamespace TrophyMenu {\n\nvoid Configure() {\n  CircleSprite = EnsureGetMember<Sprite>(\"CircleSprite\");\n  BackgroundColor = EnsureGetMember<uint32_t>(\"BackgroundColor\");\n  MenuTitleTextRightPosition =\n      EnsureGetMember<glm::vec2>(\"MenuTitleTextRightPos\");\n  MenuTitleTextLeftPosition =\n      EnsureGetMember<glm::vec2>(\"MenuTitleTextLeftPos\");\n  MenuTitleTextAngle = EnsureGetMember<float>(\"MenuTitleTextAngle\");\n  MenuTitleText = EnsureGetMember<Sprite>(\"MenuTitleText\");\n\n  ButtonPromptPosition = EnsureGetMember<glm::vec2>(\"ButtonPromptPosition\");\n  ButtonPromptSprite = EnsureGetMember<Sprite>(\"ButtonPromptSprite\");\n\n  PlatinumTrophySprite = EnsureGetMember<Sprite>(\"PlatinumTrophySprite\");\n  PlatinumTrophyPos = EnsureGetMember<glm::vec2>(\"PlatinumTrophyPos\");\n  GoldTrophySprite = EnsureGetMember<Sprite>(\"GoldTrophySprite\");\n  GoldTrophyPos = EnsureGetMember<glm::vec2>(\"GoldTrophyPos\");\n  SilverTrophySprite = EnsureGetMember<Sprite>(\"SilverTrophySprite\");\n  SilverTrophyPos = EnsureGetMember<glm::vec2>(\"SilverTrophyPos\");\n  BronzeTrophySprite = EnsureGetMember<Sprite>(\"BronzeTrophySprite\");\n  BronzeTrophyPos = EnsureGetMember<glm::vec2>(\"BronzeTrophyPos\");\n\n  DefaultTrophyIconSprite = EnsureGetMember<Sprite>(\"DefaultTrophyIconSprite\");\n  TrophyEntryCardSprite = EnsureGetMember<Sprite>(\"TrophyEntryCardSprite\");\n  TrophyEntriesBorderSprite =\n      EnsureGetMember<Sprite>(\"TrophyEntriesBorderSprite\");\n  TrophyPageCtBoxSprite = EnsureGetMember<Sprite>(\"TrophyPageCtBoxSprite\");\n  TrophyPageCtPos = EnsureGetMember<glm::vec2>(\"TrophyPageCtPos\");\n\n  EntriesPerPage = EnsureGetMember<size_t>(\"EntriesPerPage\");\n  FirstEntryPos = EnsureGetMember<glm::vec2>(\"FirstEntryPos\");\n  EntryHeight = EnsureGetMember<float>(\"EntryHeight\");\n  EntryCardOffset = EnsureGetMember<glm::vec2>(\"EntryCardOffset\");\n  EntryNameOffset = EnsureGetMember<glm::vec2>(\"EntryNameOffset\");\n  EntryNameFontSize = EnsureGetMember<float>(\"EntryNameFontSize\");\n  EntryDescriptionOffset = EnsureGetMember<glm::vec2>(\"EntryDescriptionOffset\");\n  EntryDescriptionFontSize = EnsureGetMember<float>(\"EntryDescriptionFontSize\");\n  EntryIconOffset = EnsureGetMember<glm::vec2>(\"EntryIconOffset\");\n  EntryDefaultNameTextTableId =\n      EnsureGetMember<uint32_t>(\"EntryDefaultNameTextTableId\");\n  EntryDefaultNameStringNum =\n      EnsureGetMember<uint32_t>(\"EntryDefaultNameStringNum\");\n\n  CurrentPageNumPos = EnsureGetMember<glm::vec2>(\"CurrentPageNumPos\");\n  PageNumSeparatorSlash =\n      EnsureGetMember<Sprite>(\"PageNumSeparatorSlashSprite\");\n  PageNumSeparatorPos = EnsureGetMember<glm::vec2>(\"PageNumSeparatorSlashPos\");\n  MaxPageNumPos = EnsureGetMember<glm::vec2>(\"MaxPageNumPos\");\n  GetMemberArray<Sprite>(std::span(PageNums.data(), std::ssize(PageNums)),\n                         \"PageNums\");\n  GetMemberArray<Sprite>(\n      std::span(ReachablePageNums.data(), std::ssize(ReachablePageNums)),\n      \"ReachablePageNums\");\n\n  TrophyCountHintTextTableId =\n      EnsureGetMember<uint32_t>(\"TrophyCountHintTextTableId\");\n  TrophyCountHintStringNum =\n      EnsureGetMember<uint32_t>(\"TrophyCountHintStringNum\");\n  TrophyCountHintLabelPos =\n      EnsureGetMember<glm::vec2>(\"TrophyCountHintLabelPos\");\n  TrophyCountFontSize = EnsureGetMember<float>(\"TrophyCountFontSize\");\n\n  auto drawType = EnsureGetMember<Game::DrawComponentType>(\"DrawType\");\n\n  assert(MaxTrophyPages * EntriesPerPage >=\n         AchievementSystem::GetAchievementCount());\n\n  UI::TrophyMenuPtr = new UI::CHLCC::TrophyMenu();\n  UI::Menus[drawType].push_back(UI::TrophyMenuPtr);\n}\n\n}  // namespace TrophyMenu\n}  // namespace CHLCC\n}  // namespace Profile\n}  // namespace Impacto\n"
  },
  {
    "path": "src/profile/games/chlcc/trophymenu.h",
    "content": "#pragma once\n\n#include \"../../../spritesheet.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace CHLCC {\nnamespace TrophyMenu {\n\nint constexpr inline MaxTrophyPages = 9;\n\ninline uint32_t BackgroundColor;\ninline Sprite CircleSprite;\n\ninline glm::vec2 MenuTitleTextRightPosition;\ninline glm::vec2 MenuTitleTextLeftPosition;\ninline float MenuTitleTextAngle;\ninline Sprite MenuTitleText;\n\ninline glm::vec2 ButtonPromptPosition;\ninline Sprite ButtonPromptSprite;\n\ninline Sprite PlatinumTrophySprite;\ninline glm::vec2 PlatinumTrophyPos;\ninline Sprite GoldTrophySprite;\ninline glm::vec2 GoldTrophyPos;\ninline Sprite SilverTrophySprite;\ninline glm::vec2 SilverTrophyPos;\ninline Sprite BronzeTrophySprite;\ninline glm::vec2 BronzeTrophyPos;\n\ninline Sprite DefaultTrophyIconSprite;\ninline Sprite TrophyEntryCardSprite;\ninline Sprite TrophyEntriesBorderSprite;\ninline Sprite TrophyPageCtBoxSprite;\ninline glm::vec2 TrophyPageCtPos;\n\ninline size_t EntriesPerPage;\ninline glm::vec2 FirstEntryPos;\ninline float EntryHeight;\ninline glm::vec2 EntryCardOffset;\ninline glm::vec2 EntryNameOffset;\ninline float EntryNameFontSize;\ninline glm::vec2 EntryDescriptionOffset;\ninline float EntryDescriptionFontSize;\ninline glm::vec2 EntryIconOffset;\ninline uint32_t EntryDefaultNameTextTableId;\ninline uint32_t EntryDefaultNameStringNum;\n\ninline glm::vec2 CurrentPageNumPos;\ninline Sprite PageNumSeparatorSlash;\ninline glm::vec2 PageNumSeparatorPos;\ninline glm::vec2 MaxPageNumPos;\ninline std::array<Sprite, 10> PageNums;\ninline std::array<Sprite, 10> ReachablePageNums;\n\ninline uint32_t TrophyCountHintTextTableId;\ninline uint32_t TrophyCountHintStringNum;\ninline glm::vec2 TrophyCountHintLabelPos;\ninline float TrophyCountFontSize;\n\nvoid Configure();\n\n}  // namespace TrophyMenu\n}  // namespace CHLCC\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/darling/sysmesbox.cpp",
    "content": "#include \"sysmesbox.h\"\n#include \"../../profile_internal.h\"\n\n#include \"../../../ui/ui.h\"\n#include \"../../../games/darling/sysmesbox.h\"\n#include \"../../../game.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace Darling {\nnamespace SysMesBox {\n\nvoid Configure() {\n  SelectionHighlight = EnsureGetMember<Sprite>(\"SelectionHighlight\");\n\n  BoxPartLeft = EnsureGetMember<Sprite>(\"BoxPartLeft\");\n  BoxPartRight = EnsureGetMember<Sprite>(\"BoxPartRight\");\n  BoxPartMiddle = EnsureGetMember<Sprite>(\"BoxPartMiddle\");\n  BoxDecoration = EnsureGetMember<Sprite>(\"BoxDecoration\");\n\n  BoxX = EnsureGetMember<float>(\"BoxX\");\n  BoxY = EnsureGetMember<float>(\"BoxY\");\n  ChoicePadding = EnsureGetMember<float>(\"ChoicePadding\");\n  ChoiceY = EnsureGetMember<float>(\"ChoiceY\");\n  ChoiceXBase = EnsureGetMember<float>(\"ChoiceXBase\");\n  MinMaxMesWidth = EnsureGetMember<float>(\"MinMaxMesWidth\");\n  BoxMinimumWidth = EnsureGetMember<float>(\"BoxMinimumWidth\");\n  BoxMiddleBaseX = EnsureGetMember<float>(\"BoxMiddleBaseX\");\n  BoxMiddleBaseWidth = EnsureGetMember<float>(\"BoxMiddleBaseWidth\");\n  BoxRightBaseWidth = EnsureGetMember<float>(\"BoxRightBaseWidth\");\n  BoxRightRemainPad = EnsureGetMember<float>(\"BoxRightRemainPad\");\n\n  auto drawType = EnsureGetMember<Game::DrawComponentType>(\"DrawType\");\n\n  UI::SysMesBoxPtr = new UI::Darling::SysMesBox();\n  UI::Menus[drawType].push_back(UI::SysMesBoxPtr);\n}\n\n}  // namespace SysMesBox\n}  // namespace Darling\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/darling/sysmesbox.h",
    "content": "#pragma once\n\n#include \"../../../spritesheet.h\"\n#include \"../../../games/darling/sysmesbox.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace Darling {\nnamespace SysMesBox {\n\nvoid Configure();\n\ninline Sprite BoxPartLeft;\ninline Sprite BoxPartRight;\ninline Sprite BoxPartMiddle;\ninline Sprite BoxDecoration;\ninline Sprite SelectionHighlight;\n\ninline float BoxX;\ninline float BoxY;\ninline float ChoicePadding;\ninline float ChoiceY;\ninline float ChoiceXBase;\ninline float MinMaxMesWidth;\ninline float BoxMinimumWidth;\ninline float BoxMiddleBaseX;\ninline float BoxMiddleBaseWidth;\ninline float BoxRightBaseWidth;\ninline float BoxRightRemainPad;\n\n}  // namespace SysMesBox\n}  // namespace Darling\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/dash/titlemenu.cpp",
    "content": "#include \"titlemenu.h\"\n#include \"../../profile_internal.h\"\n\n#include \"../../ui/titlemenu.h\"\n#include \"../../../game.h\"\n#include \"../../../ui/ui.h\"\n#include \"../../../games/dash/titlemenu.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace Dash {\nnamespace TitleMenu {\n\nvoid Configure() {\n  BackgroundSprite = EnsureGetMember<Sprite>(\"BackgroundSprite\");\n  PressToStartAnimation.DurationIn =\n      Profile::TitleMenu::PressToStartAnimDurationIn;\n  PressToStartAnimation.DurationOut =\n      Profile::TitleMenu::PressToStartAnimDurationOut;\n  PressToStartAnimation.LoopMode = AnimationLoopMode::ReverseDirection;\n\n  auto drawType = EnsureGetMember<Game::DrawComponentType>(\"DrawType\");\n\n  UI::TitleMenuPtr = new UI::Dash::TitleMenu();\n  UI::Menus[drawType].push_back(UI::TitleMenuPtr);\n}\n\n}  // namespace TitleMenu\n}  // namespace Dash\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/dash/titlemenu.h",
    "content": "#pragma once\n\n#include \"../../../spritesheet.h\"\n#include \"../../../games/dash/titlemenu.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace Dash {\nnamespace TitleMenu {\n\nvoid Configure();\n\ninline Sprite BackgroundSprite;\n\ninline Animation PressToStartAnimation;\n\n}  // namespace TitleMenu\n}  // namespace Dash\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/mo6tw/actorsvoicemenu.cpp",
    "content": "#include \"actorsvoicemenu.h\"\n#include \"../../profile_internal.h\"\n#include \"../../../ui/ui.h\"\n\n#include \"../../../games/mo6tw/actorsvoicemenu.h\"\n#include \"../../../text/text.h\"\n#include \"../../../game.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace MO6TW {\nnamespace ActorsVoiceMenu {\n\nvoid Configure() {\n  BackgroundSprite = EnsureGetMember<Sprite>(\"BackgroundSprite\");\n\n  GetMemberArray<Sprite>(std::span(UnlockedSprites, ActorsVoiceCount),\n                         \"UnlockedSprites\");\n  GetMemberArray<Sprite>(std::span(LockedSprites, ActorsVoiceCount),\n                         \"LockedSprites\");\n  GetMemberArray<Sprite>(\n      std::span(UnlockedHighlightedSprites, ActorsVoiceCount),\n      \"UnlockedHighlightedSprites\");\n  GetMemberArray<Sprite>(std::span(LockedHighlightedSprites, ActorsVoiceCount),\n                         \"LockedHighlightedSprites\");\n\n  InitialItemPosition = EnsureGetMember<glm::vec2>(\"InitialItemPosition\");\n  ItemOffset = EnsureGetMember<glm::vec2>(\"ItemOffset\");\n\n  CharacterBackgroundBufferId =\n      EnsureGetMember<int>(\"CharacterBackgroundBufferId\");\n\n  FadeInDuration = EnsureGetMember<float>(\"FadeInDuration\");\n  FadeOutDuration = EnsureGetMember<float>(\"FadeOutDuration\");\n\n  auto drawType = EnsureGetMember<Game::DrawComponentType>(\"DrawType\");\n\n  UI::Menus[drawType].push_back(new UI::MO6TW::ActorsVoiceMenu());\n}\n\n}  // namespace ActorsVoiceMenu\n}  // namespace MO6TW\n}  // namespace Profile\n}  // namespace Impacto\n"
  },
  {
    "path": "src/profile/games/mo6tw/actorsvoicemenu.h",
    "content": "#pragma once\n\n#include \"../../../spritesheet.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace MO6TW {\nnamespace ActorsVoiceMenu {\n\nint constexpr ActorsVoiceCount = 8;\n\nvoid Configure();\n\ninline Sprite BackgroundSprite;\n\ninline Sprite UnlockedSprites[ActorsVoiceCount];\ninline Sprite LockedSprites[ActorsVoiceCount];\ninline Sprite UnlockedHighlightedSprites[ActorsVoiceCount];\ninline Sprite LockedHighlightedSprites[ActorsVoiceCount];\ninline glm::vec2 InitialItemPosition;\ninline glm::vec2 ItemOffset;\ninline int CharacterBackgroundBufferId;\n\ninline float FadeInDuration;\ninline float FadeOutDuration;\n\n}  // namespace ActorsVoiceMenu\n}  // namespace MO6TW\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/mo6tw/albummenu.cpp",
    "content": "#include \"albummenu.h\"\n#include \"../../profile_internal.h\"\n#include \"../../../ui/ui.h\"\n\n#include \"../../../games/mo6tw/albummenu.h\"\n#include \"../../../text/text.h\"\n#include \"../../../game.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace MO6TW {\nnamespace AlbumMenu {\n\nvoid Configure() {\n  BackgroundSprite = EnsureGetMember<Sprite>(\"BackgroundSprite\");\n\n  GetMemberArray<Sprite>(\n      std::span(CharacterButtonSprites, CharacterButtonCount),\n      \"CharacterButtons\");\n  GetMemberArray<Sprite>(\n      std::span(HighlightedCharacterButtonSprites, CharacterButtonCount),\n      \"HighlightedCharacterButtons\");\n  InitialButtonPosition = EnsureGetMember<glm::vec2>(\"InitialButtonPosition\");\n  ButtonOddX = EnsureGetMember<float>(\"ButtonOddX\");\n  ButtonEvenX = EnsureGetMember<float>(\"ButtonEvenX\");\n  ButtonMargin = EnsureGetMember<glm::vec2>(\"ButtonMargin\");\n  HighlightAnimationDuration =\n      EnsureGetMember<float>(\"HighlightAnimationDuration\");\n  YunoButtonIdx = EnsureGetMember<int>(\"YunoButtonIdx\");\n  SuzuButtonIdx = EnsureGetMember<int>(\"SuzuButtonIdx\");\n  GetMemberArray<Sprite>(std::span(CharacterPortraits, CharacterPortraitCount),\n                         \"CharacterPortraits\");\n  OthersPortraitTopPart = EnsureGetMember<Sprite>(\"OthersPortraitTopPart\");\n  OthersPortraitBottomPart =\n      EnsureGetMember<Sprite>(\"OthersPortraitBottomPart\");\n  PortraitPosition = EnsureGetMember<glm::vec2>(\"PortraitPosition\");\n  OthersPortraitPosition = EnsureGetMember<glm::vec2>(\"OthersPortraitPosition\");\n  ThumbnailsPerRow = EnsureGetMember<int>(\"ThumbnailsPerRow\");\n  ThumbnailsPerColumn = EnsureGetMember<int>(\"ThumbnailsPerColumn\");\n  GetMemberArray<Sprite>(std::span(Thumbnails, EventCgCount), \"Thumbnails\");\n  GetMemberArray<int>(std::span(ThumbnailOffsets, CharacterCount),\n                      \"ThumbnailOffsets\");\n  LockedThumbnail = EnsureGetMember<Sprite>(\"LockedThumbnail\");\n  ThumbnailBorder = EnsureGetMember<Sprite>(\"ThumbnailBorder\");\n  ThumbnailHighlightTopLeft =\n      EnsureGetMember<Sprite>(\"ThumbnailHighlightTopLeft\");\n  ThumbnailHighlightTopRight =\n      EnsureGetMember<Sprite>(\"ThumbnailHighlightTopRight\");\n  ThumbnailHighlightBottomLeft =\n      EnsureGetMember<Sprite>(\"ThumbnailHighlightBottomLeft\");\n  ThumbnailHighlightBottomRight =\n      EnsureGetMember<Sprite>(\"ThumbnailHighlightBottomRight\");\n  ThumbnailGridFirstPosition =\n      EnsureGetMember<glm::vec2>(\"ThumbnailGridFirstPosition\");\n  ThumbnailGridMargin = EnsureGetMember<glm::vec2>(\"ThumbnailGridMargin\");\n  ThumbnailGridBounds = EnsureGetMember<RectF>(\"ThumbnailGridBounds\");\n  ArrowsAnimationDuration = EnsureGetMember<float>(\"ArrowsAnimationDuration\");\n  ArrowUp = EnsureGetMember<Sprite>(\"ArrowUp\");\n  ArrowUpPosition = EnsureGetMember<glm::vec2>(\"ArrowUpPosition\");\n  ArrowDown = EnsureGetMember<Sprite>(\"ArrowDown\");\n  ArrowDownPosition = EnsureGetMember<glm::vec2>(\"ArrowDownPosition\");\n  ThumbnailButtonBorderOffset =\n      EnsureGetMember<glm::vec2>(\"ThumbnailButtonBorderOffset\");\n  ThumbnailButtonTextFontSize =\n      EnsureGetMember<float>(\"ThumbnailButtonTextFontSize\");\n  ThumbnailButtonTextColorIndex =\n      EnsureGetMember<int>(\"ThumbnailButtonTextColorIndex\");\n  ThumbnailButtonTextOffset =\n      EnsureGetMember<glm::vec2>(\"ThumbnailButtonTextOffset\");\n\n  FadeInDuration = EnsureGetMember<float>(\"FadeInDuration\");\n  FadeOutDuration = EnsureGetMember<float>(\"FadeOutDuration\");\n\n  auto drawType = EnsureGetMember<Game::DrawComponentType>(\"DrawType\");\n\n  UI::Menus[drawType].push_back(new UI::MO6TW::AlbumMenu());\n}\n\n}  // namespace AlbumMenu\n}  // namespace MO6TW\n}  // namespace Profile\n}  // namespace Impacto\n"
  },
  {
    "path": "src/profile/games/mo6tw/albummenu.h",
    "content": "#pragma once\n\n#include \"../../../spritesheet.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace MO6TW {\nnamespace AlbumMenu {\n\nvoid Configure();\n\nint constexpr CharacterButtonCount = 8;\nint constexpr CharacterPortraitCount = 5;\nint constexpr CharacterCount = 6;\nint constexpr EventCgCount = 104;\n\ninline Sprite BackgroundSprite;\n\ninline Sprite CharacterButtonSprites[CharacterButtonCount];\ninline Sprite HighlightedCharacterButtonSprites[CharacterButtonCount];\ninline glm::vec2 InitialButtonPosition;\ninline float ButtonOddX;\ninline float ButtonEvenX;\ninline glm::vec2 ButtonMargin;\ninline float HighlightAnimationDuration;\ninline int YunoButtonIdx;\ninline int SuzuButtonIdx;\ninline Sprite CharacterPortraits[CharacterPortraitCount];\ninline Sprite OthersPortraitTopPart;\ninline Sprite OthersPortraitBottomPart;\ninline glm::vec2 PortraitPosition;\ninline glm::vec2 OthersPortraitPosition;\ninline int ThumbnailsPerRow;\ninline int ThumbnailsPerColumn;\ninline Sprite Thumbnails[EventCgCount];\ninline int ThumbnailOffsets[CharacterCount];\ninline Sprite LockedThumbnail;\ninline Sprite ThumbnailBorder;\ninline Sprite ThumbnailHighlightTopLeft;\ninline Sprite ThumbnailHighlightTopRight;\ninline Sprite ThumbnailHighlightBottomLeft;\ninline Sprite ThumbnailHighlightBottomRight;\ninline glm::vec2 ThumbnailGridFirstPosition;\ninline glm::vec2 ThumbnailGridMargin;\ninline RectF ThumbnailGridBounds;\ninline float ArrowsAnimationDuration;\ninline Sprite ArrowUp;\ninline glm::vec2 ArrowUpPosition;\ninline Sprite ArrowDown;\ninline glm::vec2 ArrowDownPosition;\ninline glm::vec2 ThumbnailButtonBorderOffset;\ninline float ThumbnailButtonTextFontSize;\ninline int ThumbnailButtonTextColorIndex;\ninline glm::vec2 ThumbnailButtonTextOffset;\n\ninline float FadeInDuration;\ninline float FadeOutDuration;\n\n}  // namespace AlbumMenu\n}  // namespace MO6TW\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/mo6tw/backlogmenu.cpp",
    "content": "#include \"backlogmenu.h\"\n#include \"../../profile_internal.h\"\n#include \"../../../ui/ui.h\"\n#include \"../../../games/mo6tw/backlogmenu.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace MO6TW {\nnamespace BacklogMenu {\n\nvoid Configure() {\n  auto drawType = EnsureGetMember<Game::DrawComponentType>(\"DrawType\");\n\n  UI::BacklogMenuPtr = new UI::MO6TW::BacklogMenu();\n  UI::Menus[drawType].push_back(UI::BacklogMenuPtr);\n}\n\n}  // namespace BacklogMenu\n}  // namespace MO6TW\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/mo6tw/backlogmenu.h",
    "content": "#pragma once\n\nnamespace Impacto {\nnamespace Profile {\nnamespace MO6TW {\nnamespace BacklogMenu {\n\nvoid Configure();\n\n}  // namespace BacklogMenu\n}  // namespace MO6TW\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/mo6tw/clearlistmenu.cpp",
    "content": "#include \"clearlistmenu.h\"\n#include \"../../profile_internal.h\"\n#include \"../../../ui/ui.h\"\n#include \"../../../games/mo6tw/clearlistmenu.h\"\n#include \"../../../text/text.h\"\n#include \"../../../game.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace MO6TW {\nnamespace ClearListMenu {\n\nvoid Configure() {\n  BackgroundSprite = EnsureGetMember<Sprite>(\"BackgroundSprite\");\n  WindowSprite = EnsureGetMember<Sprite>(\"WindowSprite\");\n  WindowPosition = EnsureGetMember<glm::vec2>(\"WindowPosition\");\n  WindowSpritePartLeft = EnsureGetMember<Sprite>(\"WindowSpritePartLeft\");\n  WindowSpritePartRight = EnsureGetMember<Sprite>(\"WindowSpritePartRight\");\n  FontSize = EnsureGetMember<float>(\"FontSize\");\n  SeparatorTable = EnsureGetMember<int>(\"SeparatorTable\");\n  SeparatorEntry = EnsureGetMember<int>(\"SeparatorEntry\");\n\n  LabelPosition = EnsureGetMember<glm::vec2>(\"LabelPosition\");\n\n  ClearListLabel = EnsureGetMember<Sprite>(\"ClearListLabel\");\n  EndingsLabelPosition = EnsureGetMember<glm::vec2>(\"EndingsLabelPosition\");\n  EndingsLabel = EnsureGetMember<Sprite>(\"EndingsLabel\");\n  EndingCountPosition = EnsureGetMember<glm::vec2>(\"EndingCountPosition\");\n  ScenesLabelPosition = EnsureGetMember<glm::vec2>(\"ScenesLabelPosition\");\n  ScenesLabel = EnsureGetMember<Sprite>(\"ScenesLabel\");\n  SceneCountPosition = EnsureGetMember<glm::vec2>(\"SceneCountPosition\");\n  CompletionLabelPosition =\n      EnsureGetMember<glm::vec2>(\"CompletionLabelPosition\");\n  CompletionLabel = EnsureGetMember<Sprite>(\"CompletionLabel\");\n  CompletionPosition = EnsureGetMember<glm::vec2>(\"CompletionPosition\");\n  AlbumLabelPosition = EnsureGetMember<glm::vec2>(\"AlbumLabelPosition\");\n  AlbumLabel = EnsureGetMember<Sprite>(\"AlbumLabel\");\n  AlbumCountPosition = EnsureGetMember<glm::vec2>(\"AlbumCountPosition\");\n  PlayTimeLabelPosition = EnsureGetMember<glm::vec2>(\"PlayTimeLabelPosition\");\n  PlayTimeLabel = EnsureGetMember<Sprite>(\"PlayTimeLabel\");\n  PlayTimeTextTable = EnsureGetMember<int>(\"PlayTimeTextTable\");\n  PlayTimeSecondsTextEntry = EnsureGetMember<int>(\"PlayTimeSecondsTextEntry\");\n  PlayTimeMinutesTextEntry = EnsureGetMember<int>(\"PlayTimeMinutesTextEntry\");\n  PlayTimeHoursTextEntry = EnsureGetMember<int>(\"PlayTimeHoursTextEntry\");\n  PlayTimeSecondsTextPosition =\n      EnsureGetMember<glm::vec2>(\"PlayTimeSecondsTextPosition\");\n  PlayTimeMinutesTextPosition =\n      EnsureGetMember<glm::vec2>(\"PlayTimeMinutesTextPosition\");\n  PlayTimeHoursTextPosition =\n      EnsureGetMember<glm::vec2>(\"PlayTimeHoursTextPosition\");\n  PlayTimeSecondsPosition =\n      EnsureGetMember<glm::vec2>(\"PlayTimeSecondsPosition\");\n  PlayTimeMinutesPosition =\n      EnsureGetMember<glm::vec2>(\"PlayTimeMinutesPosition\");\n  PlayTimeHoursPosition = EnsureGetMember<glm::vec2>(\"PlayTimeHoursPosition\");\n  ClearListColorIndex = EnsureGetMember<int>(\"ClearListColorIndex\");\n  ClearListTextBackground = EnsureGetMember<Sprite>(\"ClearListTextBackground\");\n  ClearListTextBGOffset = EnsureGetMember<glm::vec2>(\"ClearListTextBGOffset\");\n\n  EndingListLabel = EnsureGetMember<Sprite>(\"EndingListLabel\");\n  EndingCount = EnsureGetMember<int>(\"EndingCount\");\n  EndingsListNumberInitialPosition =\n      EnsureGetMember<glm::vec2>(\"EndingsListNumberInitialPosition\");\n  EndingsListTextInitialPosition =\n      EnsureGetMember<glm::vec2>(\"EndingsListTextInitialPosition\");\n  EndingsListTextMargin = EnsureGetMember<glm::vec2>(\"EndingsListTextMargin\");\n  EndingsListTextFontSize = EnsureGetMember<float>(\"EndingsListTextFontSize\");\n  EndingsListTextLockedTable =\n      EnsureGetMember<int>(\"EndingsListTextLockedTable\");\n  EndingsListTextLockedEntry =\n      EnsureGetMember<int>(\"EndingsListTextLockedEntry\");\n  EndingsListTextTable = EnsureGetMember<int>(\"EndingsListTextTable\");\n  EndingsListTextColorIndex = EnsureGetMember<int>(\"EndingsListTextColorIndex\");\n\n  SceneTitleLabel = EnsureGetMember<Sprite>(\"SceneTitleLabel\");\n  SceneCount = EnsureGetMember<int>(\"SceneCount\");\n  SceneListNumberInitialPosition =\n      EnsureGetMember<glm::vec2>(\"SceneListNumberInitialPosition\");\n  SceneListTextInitialPosition =\n      EnsureGetMember<glm::vec2>(\"SceneListTextInitialPosition\");\n  SceneListTextMargin = EnsureGetMember<glm::vec2>(\"SceneListTextMargin\");\n  SceneListFontSize = EnsureGetMember<float>(\"SceneListFontSize\");\n  SceneListTextTable = EnsureGetMember<int>(\"SceneListTextTable\");\n  SceneTitleItemsRenderingBounds =\n      EnsureGetMember<RectF>(\"SceneTitleItemsRenderingBounds\");\n  SceneListColorIndex = EnsureGetMember<int>(\"SceneListColorIndex\");\n  SceneTitleLockedTable = EnsureGetMember<int>(\"SceneTitleLockedTable\");\n  SceneTitleLockedEntry = EnsureGetMember<int>(\"SceneTitleLockedEntry\");\n  SceneTitleItemsWidth = EnsureGetMember<float>(\"SceneTitleItemsWidth\");\n  ScrollbarStart = EnsureGetMember<float>(\"ScrollbarStart\");\n  ScrollAreaHeight = EnsureGetMember<float>(\"ScrollAreaHeight\");\n  ScrollbarTrack = EnsureGetMember<Sprite>(\"ScrollbarTrackSprite\");\n  ScrollbarThumb = EnsureGetMember<Sprite>(\"ScrollbarThumbSprite\");\n  ScrollbarPosition = EnsureGetMember<glm::vec2>(\"ScrollbarPosition\");\n\n  ArrowLeft = EnsureGetMember<Sprite>(\"ArrowLeft\");\n  ArrowLeftPosition = EnsureGetMember<glm::vec2>(\"ArrowLeftPosition\");\n  ArrowRight = EnsureGetMember<Sprite>(\"ArrowRight\");\n  ArrowRightPosition = EnsureGetMember<glm::vec2>(\"ArrowRightPosition\");\n\n  ArrowsAnimationDuration = EnsureGetMember<float>(\"ArrowsAnimationDuration\");\n  FadeInDuration = EnsureGetMember<float>(\"FadeInDuration\");\n  FadeOutDuration = EnsureGetMember<float>(\"FadeOutDuration\");\n\n  auto drawType = EnsureGetMember<Game::DrawComponentType>(\"DrawType\");\n\n  auto clearList = new UI::MO6TW::ClearListMenu();\n  UI::Menus[drawType].push_back(clearList);\n}\n\n}  // namespace ClearListMenu\n}  // namespace MO6TW\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/mo6tw/clearlistmenu.h",
    "content": "#pragma once\n\n#include \"../../../spritesheet.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace MO6TW {\nnamespace ClearListMenu {\n\ninline Sprite BackgroundSprite;\ninline Sprite WindowSprite;\ninline glm::vec2 WindowPosition;\ninline Sprite WindowSpritePartLeft;\ninline Sprite WindowSpritePartRight;\ninline float FontSize;\ninline int SeparatorTable;\ninline int SeparatorEntry;\n\ninline glm::vec2 LabelPosition;\ninline Sprite ClearListLabel;\ninline glm::vec2 EndingsLabelPosition;\ninline Sprite EndingsLabel;\ninline glm::vec2 EndingCountPosition;\ninline glm::vec2 ScenesLabelPosition;\ninline Sprite ScenesLabel;\ninline glm::vec2 SceneCountPosition;\ninline glm::vec2 CompletionLabelPosition;\ninline Sprite CompletionLabel;\ninline glm::vec2 CompletionPosition;\ninline glm::vec2 AlbumLabelPosition;\ninline Sprite AlbumLabel;\ninline glm::vec2 AlbumCountPosition;\ninline glm::vec2 PlayTimeLabelPosition;\ninline Sprite PlayTimeLabel;\ninline int PlayTimeTextTable;\ninline int PlayTimeSecondsTextEntry;\ninline int PlayTimeMinutesTextEntry;\ninline int PlayTimeHoursTextEntry;\ninline glm::vec2 PlayTimeSecondsTextPosition;\ninline glm::vec2 PlayTimeMinutesTextPosition;\ninline glm::vec2 PlayTimeHoursTextPosition;\ninline glm::vec2 PlayTimeSecondsPosition;\ninline glm::vec2 PlayTimeMinutesPosition;\ninline glm::vec2 PlayTimeHoursPosition;\ninline int ClearListColorIndex;\ninline Sprite ClearListTextBackground;\ninline glm::vec2 ClearListTextBGOffset;\n\ninline Sprite EndingListLabel;\ninline int EndingCount;\ninline glm::vec2 EndingsListNumberInitialPosition;\ninline glm::vec2 EndingsListTextInitialPosition;\ninline glm::vec2 EndingsListTextMargin;\ninline float EndingsListTextFontSize;\ninline int EndingsListTextLockedTable;\ninline int EndingsListTextLockedEntry;\ninline int EndingsListTextTable;\ninline int EndingsListTextColorIndex;\n\ninline Sprite SceneTitleLabel;\ninline int SceneCount;\ninline glm::vec2 SceneListNumberInitialPosition;\ninline glm::vec2 SceneListTextInitialPosition;\ninline glm::vec2 SceneListTextMargin;\ninline float SceneListFontSize;\ninline int SceneListTextTable;\ninline RectF SceneTitleItemsRenderingBounds;\ninline int SceneListColorIndex;\ninline int SceneTitleLockedTable;\ninline int SceneTitleLockedEntry;\ninline float SceneTitleItemsWidth;\ninline float ScrollbarStart;\ninline float ScrollAreaHeight;\ninline Sprite ScrollbarTrack;\ninline Sprite ScrollbarThumb;\ninline glm::vec2 ScrollbarPosition;\n\ninline Sprite ArrowLeft;\ninline glm::vec2 ArrowLeftPosition;\ninline Sprite ArrowRight;\ninline glm::vec2 ArrowRightPosition;\n\ninline float ArrowsAnimationDuration;\ninline float FadeInDuration;\ninline float FadeOutDuration;\n\nvoid Configure();\n\n}  // namespace ClearListMenu\n}  // namespace MO6TW\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/mo6tw/dialoguebox.cpp",
    "content": "#include \"dialoguebox.h\"\n#include \"../../profile_internal.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace MO6TW {\nnamespace DialogueBox {\n\nvoid Configure() {\n  ADVBoxPartLeft = EnsureGetMember<Sprite>(\"ADVBoxPartLeft\");\n  ADVBoxPartRight = EnsureGetMember<Sprite>(\"ADVBoxPartRight\");\n  ADVBoxDecoration = EnsureGetMember<Sprite>(\"ADVBoxDecoration\");\n\n  ADVBoxPartLeftPos = EnsureGetMember<glm::vec2>(\"ADVBoxPartLeftPos\");\n  ADVBoxPartRightPos = EnsureGetMember<glm::vec2>(\"ADVBoxPartRightPos\");\n  ADVBoxDecorationPos = EnsureGetMember<glm::vec2>(\"ADVBoxDecorationPos\");\n}\n\n}  // namespace DialogueBox\n}  // namespace MO6TW\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/mo6tw/dialoguebox.h",
    "content": "#pragma once\n\n#include \"../../../spritesheet.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace MO6TW {\nnamespace DialogueBox {\n\nvoid Configure();\n\ninline Sprite ADVBoxPartLeft;\ninline Sprite ADVBoxPartRight;\ninline Sprite ADVBoxDecoration;\n\ninline glm::vec2 ADVBoxPartLeftPos;\ninline glm::vec2 ADVBoxPartRightPos;\ninline glm::vec2 ADVBoxDecorationPos;\n\n}  // namespace DialogueBox\n}  // namespace MO6TW\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/mo6tw/moviemenu.cpp",
    "content": "#include \"moviemenu.h\"\n#include \"../../profile_internal.h\"\n#include \"../../../ui/ui.h\"\n\n#include \"../../../games/mo6tw/moviemenu.h\"\n#include \"../../../text/text.h\"\n#include \"../../../game.h\"\n#include \"../../../renderer/renderer.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace MO6TW {\nnamespace MovieMenu {\n\nvoid Configure() {\n  BackgroundSprite = EnsureGetMember<Sprite>(\"BackgroundSprite\");\n\n  FirstOPTopPartSprite = EnsureGetMember<Sprite>(\"FirstOPTopPartSprite\");\n  FirstOPBottomPartSprite = EnsureGetMember<Sprite>(\"FirstOPBottomPartSprite\");\n  SecondOPTopPartSprite = EnsureGetMember<Sprite>(\"SecondOPTopPartSprite\");\n  SecondOPBottomPartSprite =\n      EnsureGetMember<Sprite>(\"SecondOPBottomPartSprite\");\n  GetMemberArray<Sprite>(\n      std::span(UnlockedMovieThumbnailSprites, MaxMovieThumbnails),\n      \"UnlockedMovieThumbnailSprites\");\n  GetMemberArray<Sprite>(\n      std::span(LockedMovieThumbnailSprites, MaxMovieThumbnails),\n      \"LockedMovieThumbnailSprites\");\n  SelectionHighlightTopLeft =\n      EnsureGetMember<Sprite>(\"SelectionHighlightTopLeft\");\n  SelectionHighlightTopRight =\n      EnsureGetMember<Sprite>(\"SelectionHighlightTopRight\");\n  SelectionHighlightBottomLeft =\n      EnsureGetMember<Sprite>(\"SelectionHighlightBottomLeft\");\n  SelectionHighlightBottomRight =\n      EnsureGetMember<Sprite>(\"SelectionHighlightBottomRight\");\n  ItemCount = EnsureGetMember<int>(\"ItemCount\");\n  ItemsPerRow = EnsureGetMember<int>(\"ItemsPerRow\");\n  InitialItemPosition = EnsureGetMember<glm::vec2>(\"InitialItemPosition\");\n  ItemOffset = EnsureGetMember<glm::vec2>(\"ItemOffset\");\n\n  HighlightAnimationDuration =\n      EnsureGetMember<float>(\"HighlightAnimationDuration\");\n  HighlightTopLeftOffset = EnsureGetMember<glm::vec2>(\"HighlightTopLeftOffset\");\n  HighlightTopRightOffset =\n      EnsureGetMember<glm::vec2>(\"HighlightTopRightOffset\");\n  HighlightBottomLeftOffset =\n      EnsureGetMember<glm::vec2>(\"HighlightBottomLeftOffset\");\n  HighlightBottomRightOffset =\n      EnsureGetMember<glm::vec2>(\"HighlightBottomRightOffset\");\n\n  FadeInDuration = EnsureGetMember<float>(\"FadeInDuration\");\n  FadeOutDuration = EnsureGetMember<float>(\"FadeOutDuration\");\n\n  auto drawType = EnsureGetMember<Game::DrawComponentType>(\"DrawType\");\n\n  UI::Menus[drawType].push_back(new UI::MO6TW::MovieMenu());\n}\n\n}  // namespace MovieMenu\n}  // namespace MO6TW\n}  // namespace Profile\n}  // namespace Impacto\n"
  },
  {
    "path": "src/profile/games/mo6tw/moviemenu.h",
    "content": "#pragma once\n\n#include \"../../../spritesheet.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace MO6TW {\nnamespace MovieMenu {\n\nvoid Configure();\n\nint constexpr MaxMovieThumbnails = 10;\n\ninline Sprite BackgroundSprite;\n\ninline Sprite FirstOPTopPartSprite;\ninline Sprite FirstOPBottomPartSprite;\ninline Sprite SecondOPTopPartSprite;\ninline Sprite SecondOPBottomPartSprite;\ninline Sprite UnlockedMovieThumbnailSprites[MaxMovieThumbnails];\ninline Sprite LockedMovieThumbnailSprites[MaxMovieThumbnails];\ninline Sprite SelectionHighlightTopLeft;\ninline Sprite SelectionHighlightTopRight;\ninline Sprite SelectionHighlightBottomLeft;\ninline Sprite SelectionHighlightBottomRight;\ninline int ItemCount;\ninline int ItemsPerRow;\ninline glm::vec2 InitialItemPosition;\ninline glm::vec2 ItemOffset;\n\ninline float HighlightAnimationDuration;\ninline glm::vec2 HighlightTopLeftOffset;\ninline glm::vec2 HighlightTopRightOffset;\ninline glm::vec2 HighlightBottomLeftOffset;\ninline glm::vec2 HighlightBottomRightOffset;\n\ninline float FadeInDuration;\ninline float FadeOutDuration;\n\n}  // namespace MovieMenu\n}  // namespace MO6TW\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/mo6tw/musicmenu.cpp",
    "content": "#include \"musicmenu.h\"\n#include \"../../profile_internal.h\"\n#include \"../../../ui/ui.h\"\n\n#include \"../../../games/mo6tw/musicmenu.h\"\n#include \"../../../text/text.h\"\n#include \"../../../game.h\"\n// #include \"../../../window.h\"\n#include \"../../../renderer/renderer.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace MO6TW {\nnamespace MusicMenu {\n\nvoid Configure() {\n  BackgroundSprite = EnsureGetMember<Sprite>(\"BackgroundSprite\");\n\n  GetMemberArray<Sprite>(std::span(Thumbnails, MusicTrackCount), \"Thumbnails\");\n  ThumbnailPosition = EnsureGetMember<glm::vec2>(\"ThumbnailPosition\");\n  ItemsWindow = EnsureGetMember<Sprite>(\"ItemsWindow\");\n  ItemsWindowPosition = EnsureGetMember<glm::vec2>(\"ItemsWindowPosition\");\n  ItemsWindowRenderingBounds =\n      EnsureGetMember<RectF>(\"ItemsWindowRenderingBounds\");\n  MusicListMargin = EnsureGetMember<glm::vec2>(\"MusicListMargin\");\n  MusicListInitialPosition =\n      EnsureGetMember<glm::vec2>(\"MusicListInitialPosition\");\n  PlaybackWindow = EnsureGetMember<Sprite>(\"PlaybackWindow\");\n  PlaybackWindowPosition = EnsureGetMember<glm::vec2>(\"PlaybackWindowPosition\");\n  GetMemberArray<Sprite>(\n      std::span(PlaybackModeLabels, MusicPlaybackModeLabelCount),\n      \"PlaybackModeLabels\");\n  PlaybackModeLabelPosition =\n      EnsureGetMember<glm::vec2>(\"PlaybackModeLabelPosition\");\n  CurrentlyPlayingLabelPosition =\n      EnsureGetMember<glm::vec2>(\"CurrentlyPlayingLabelPosition\");\n  GetMemberArray<Sprite>(std::span(ItemNames, MusicTrackCount), \"ItemNames\");\n  ItemNameHighlightOffset =\n      EnsureGetMember<glm::vec2>(\"ItemNameHighlightOffset\");\n  LockedItem = EnsureGetMember<Sprite>(\"LockedItem\");\n  ScrollbarThumb = EnsureGetMember<Sprite>(\"ScrollbarThumb\");\n  ScrollbarTrack = EnsureGetMember<Sprite>(\"ScrollbarTrack\");\n  ScrollbarPosition = EnsureGetMember<glm::vec2>(\"ScrollbarPosition\");\n  ScrollbarStart = EnsureGetMember<float>(\"ScrollbarStart\");\n\n  {\n    EnsurePushMemberOfType(\"Playlist\", LUA_TTABLE);\n\n    [[maybe_unused]] auto size = lua_rawlen(LuaState, -1);\n    assert(size == MusicTrackCount);\n    PushInitialIndex();\n    while (PushNextTableElement() != 0) {\n      int i = EnsureGetKey<int32_t>() - 1;\n      Playlist[i] = EnsureGetArrayElement<int>();\n      Pop();\n    }\n\n    Pop();\n  }\n\n  GetMemberArray<Sprite>(std::span(TimerChars, TimerCharCount), \"TimerChars\");\n  TimerInitialPosition = EnsureGetMember<glm::vec2>(\"TimerInitialPosition\");\n  TimerMargin = EnsureGetMember<glm::vec2>(\"TimerMargin\");\n\n  FadeInDuration = EnsureGetMember<float>(\"FadeInDuration\");\n  FadeOutDuration = EnsureGetMember<float>(\"FadeOutDuration\");\n\n  auto drawType = EnsureGetMember<Game::DrawComponentType>(\"DrawType\");\n\n  UI::Menus[drawType].push_back(new UI::MO6TW::MusicMenu());\n}\n\n}  // namespace MusicMenu\n}  // namespace MO6TW\n}  // namespace Profile\n}  // namespace Impacto\n"
  },
  {
    "path": "src/profile/games/mo6tw/musicmenu.h",
    "content": "#pragma once\n\n#include \"../../../spritesheet.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace MO6TW {\nnamespace MusicMenu {\n\nint constexpr MusicTrackCount = 39;\nint constexpr MusicPlaybackModeLabelCount = 4;\nint constexpr TimerCharCount = 11;\n\nvoid Configure();\n\ninline Sprite BackgroundSprite;\n\ninline Sprite Thumbnails[MusicTrackCount];\ninline glm::vec2 ThumbnailPosition;\ninline Sprite ItemsWindow;\ninline glm::vec2 ItemsWindowPosition;\ninline RectF ItemsWindowRenderingBounds;\ninline glm::vec2 MusicListMargin;\ninline glm::vec2 MusicListInitialPosition;\ninline Sprite PlaybackWindow;\ninline glm::vec2 PlaybackWindowPosition;\ninline Sprite PlaybackModeLabels[MusicPlaybackModeLabelCount];\ninline glm::vec2 PlaybackModeLabelPosition;\ninline glm::vec2 CurrentlyPlayingLabelPosition;\ninline Sprite ItemNames[MusicTrackCount];\ninline glm::vec2 ItemNameHighlightOffset;\ninline Sprite LockedItem;\ninline Sprite ScrollbarThumb;\ninline Sprite ScrollbarTrack;\ninline glm::vec2 ScrollbarPosition;\ninline float ScrollbarStart;\ninline int Playlist[MusicTrackCount];\ninline Sprite TimerChars[TimerCharCount];\ninline glm::vec2 TimerInitialPosition;\ninline glm::vec2 TimerMargin;\n\ninline float FadeInDuration;\ninline float FadeOutDuration;\n\n}  // namespace MusicMenu\n}  // namespace MO6TW\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/mo6tw/optionsmenu.cpp",
    "content": "#include \"optionsmenu.h\"\n#include \"../../profile_internal.h\"\n#include \"../../../log.h\"\n\n#include \"../../ui/optionsmenu.h\"\n#include \"../../../game.h\"\n#include \"../../../ui/ui.h\"\n#include \"../../../games/mo6tw/optionsmenu.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace MO6TW {\nnamespace OptionsMenu {\n\nvoid Configure() {\n  BackgroundSprite = EnsureGetMember<Sprite>(\"BackgroundSprite\");\n\n  GetMemberArray<Sprite>(std::span(VoiceToggleEnabledSprites, VoiceToggleCount),\n                         \"VoiceToggleEnabledSprites\");\n  GetMemberArray<Sprite>(\n      std::span(VoiceToggleDisabledSprites, VoiceToggleCount),\n      \"VoiceToggleDisabledSprites\");\n  VoiceToggleHighlightSprite =\n      EnsureGetMember<Sprite>(\"VoiceToggleHighlightSprite\");\n\n  VoiceToggleStart = EnsureGetMember<glm::vec2>(\"VoiceToggleStart\");\n  VoiceTogglePadding = EnsureGetMember<glm::vec2>(\"VoiceTogglePadding\");\n  VoiceTogglePerLine = EnsureGetMember<int>(\"VoiceTogglePerLine\");\n\n  GetMemberArray<Sprite>(\n      std::span(SectionHeaderSprites, SectionHeaderSpriteCount),\n      \"SectionHeaderSprites\");\n\n  SliderTrackSprite = EnsureGetMember<Sprite>(\"SliderTrackSprite\");\n  SliderFillSprite = EnsureGetMember<Sprite>(\"SliderFillSprite\");\n  SliderThumbSprite = EnsureGetMember<Sprite>(\"SliderThumbSprite\");\n\n  CheckboxBoxSprite = EnsureGetMember<Sprite>(\"CheckboxBoxSprite\");\n  CheckboxTickSprite = EnsureGetMember<Sprite>(\"CheckboxTickSprite\");\n  GetMemberArray<Sprite>(std::span(CheckboxLabelSprites, CheckboxLabelCount),\n                         \"CheckboxLabelSprites\");\n\n  FirstPageSliderPos = EnsureGetMember<glm::vec2>(\"FirstPageSliderPos\");\n  FirstPageSliderMargin = EnsureGetMember<float>(\"FirstPageSliderMargin\");\n  SliderThumbOffset = EnsureGetMember<glm::vec2>(\"SliderThumbOffset\");\n  CheckboxLabelOffset = EnsureGetMember<glm::vec2>(\"CheckboxLabelOffset\");\n  CheckboxFirstPos = EnsureGetMember<glm::vec2>(\"CheckboxFirstPos\");\n  CheckboxFirstSectionPaddingX =\n      EnsureGetMember<float>(\"CheckboxFirstSectionPaddingX\");\n  CheckboxMargin = EnsureGetMember<glm::vec2>(\"CheckboxMargin\");\n  CheckboxSecondPos = EnsureGetMember<glm::vec2>(\"CheckboxSecondPos\");\n  CheckboxSecondSectionFirstPaddingX =\n      EnsureGetMember<float>(\"CheckboxSecondSectionFirstPaddingX\");\n  GetMemberArray<float>(\n      std::span(AutoSaveTriggerXPos, AutoSaveTriggerXPosCount),\n      \"AutoSaveTriggerXPos\");\n  ScreenSizeSliderPos = EnsureGetMember<glm::vec2>(\"ScreenSizeSliderPos\");\n  TipsPos = EnsureGetMember<glm::vec2>(\"TipsPos\");\n  FirstPageSectionHeaderPos =\n      EnsureGetMember<glm::vec2>(\"FirstPageSectionHeaderPos\");\n  SecondPageSectionHeaderPos =\n      EnsureGetMember<glm::vec2>(\"SecondPageSectionHeaderPos\");\n\n  auto drawType = EnsureGetMember<Game::DrawComponentType>(\"DrawType\");\n\n  UI::OptionsMenuPtr = new UI::MO6TW::OptionsMenu();\n  UI::Menus[drawType].push_back(UI::OptionsMenuPtr);\n}\n\n}  // namespace OptionsMenu\n}  // namespace MO6TW\n}  // namespace Profile\n}  // namespace Impacto\n"
  },
  {
    "path": "src/profile/games/mo6tw/optionsmenu.h",
    "content": "#pragma once\n\n#include \"../../../spritesheet.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace MO6TW {\nnamespace OptionsMenu {\n\nint constexpr VoiceToggleCount = 14;\nint constexpr SectionHeaderSpriteCount = 13 * 2;\nint constexpr CheckboxLabelCount = 14;\nint constexpr AutoSaveTriggerXPosCount = 4;\n\ninline Sprite BackgroundSprite;\n\ninline Sprite VoiceToggleEnabledSprites[VoiceToggleCount];\ninline Sprite VoiceToggleDisabledSprites[VoiceToggleCount];\ninline Sprite VoiceToggleHighlightSprite;\n\ninline glm::vec2 VoiceToggleStart;\ninline glm::vec2 VoiceTogglePadding;\ninline int VoiceTogglePerLine;\n\ninline Sprite SectionHeaderSprites[SectionHeaderSpriteCount];\n\ninline Sprite SliderTrackSprite;\ninline Sprite SliderFillSprite;\ninline Sprite SliderThumbSprite;\n\ninline Sprite CheckboxBoxSprite;\ninline Sprite CheckboxTickSprite;\ninline Sprite CheckboxLabelSprites[CheckboxLabelCount];\n\ninline glm::vec2 FirstPageSliderPos;\ninline float FirstPageSliderMargin;\ninline glm::vec2 SliderThumbOffset;\ninline glm::vec2 CheckboxLabelOffset;\ninline glm::vec2 CheckboxFirstPos;\ninline float CheckboxFirstSectionPaddingX;\ninline glm::vec2 CheckboxMargin;\ninline glm::vec2 CheckboxSecondPos;\ninline float CheckboxSecondSectionFirstPaddingX;\ninline float AutoSaveTriggerXPos[AutoSaveTriggerXPosCount];\ninline glm::vec2 ScreenSizeSliderPos;\ninline glm::vec2 TipsPos;\ninline glm::vec2 FirstPageSectionHeaderPos;\ninline glm::vec2 SecondPageSectionHeaderPos;\n\nvoid Configure();\n\n}  // namespace OptionsMenu\n}  // namespace MO6TW\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/mo6tw/savemenu.cpp",
    "content": "#include \"savemenu.h\"\n#include \"../../profile_internal.h\"\n#include \"../../../log.h\"\n\n#include \"../../ui/savemenu.h\"\n#include \"../../../game.h\"\n#include \"../../../ui/ui.h\"\n#include \"../../../games/mo6tw/savemenu.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace MO6TW {\nnamespace SaveMenu {\n\nvoid Configure() {\n  SaveMenuBackgroundSprite =\n      EnsureGetMember<Sprite>(\"SaveMenuBackgroundSprite\");\n\n  EntryStartX = EnsureGetMember<float>(\"EntryStartX\");\n  EntryXPadding = EnsureGetMember<float>(\"EntryXPadding\");\n  EntryStartY = EnsureGetMember<float>(\"EntryStartY\");\n  EntryYPadding = EnsureGetMember<float>(\"EntryYPadding\");\n  QuickLoadTextSprite = EnsureGetMember<Sprite>(\"QuickLoadTextSprite\");\n  LoadTextSprite = EnsureGetMember<Sprite>(\"LoadTextSprite\");\n  SaveTextSprite = EnsureGetMember<Sprite>(\"SaveTextSprite\");\n  MenuTitleTextPos = EnsureGetMember<glm::vec2>(\"MenuTitleTextPos\");\n\n  QuickLoadEntrySprite = EnsureGetMember<Sprite>(\"QuickLoadEntrySprite\");\n  QuickLoadEntryHighlightedSprite =\n      EnsureGetMember<Sprite>(\"QuickLoadEntryHighlightedSprite\");\n  SaveEntrySprite = EnsureGetMember<Sprite>(\"SaveEntrySprite\");\n  SaveEntryHighlightedSprite =\n      EnsureGetMember<Sprite>(\"SaveEntryHighlightedSprite\");\n  LoadEntrySprite = EnsureGetMember<Sprite>(\"LoadEntrySprite\");\n  LoadEntryHighlightedSprite =\n      EnsureGetMember<Sprite>(\"LoadEntryHighlightedSprite\");\n\n  auto drawType = EnsureGetMember<Game::DrawComponentType>(\"DrawType\");\n\n  UI::SaveMenuPtr = new UI::MO6TW::SaveMenu();\n  UI::Menus[drawType].push_back(UI::SaveMenuPtr);\n}\n\n}  // namespace SaveMenu\n}  // namespace MO6TW\n}  // namespace Profile\n}  // namespace Impacto\n"
  },
  {
    "path": "src/profile/games/mo6tw/savemenu.h",
    "content": "#pragma once\n\n#include \"../../../spritesheet.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace MO6TW {\nnamespace SaveMenu {\n\nint constexpr EntriesPerRow = 2;\nint constexpr RowsPerPage = 4;\n\ninline float EntryStartX;\ninline float EntryXPadding;\ninline float EntryStartY;\ninline float EntryYPadding;\n\ninline Sprite QuickLoadTextSprite;\ninline Sprite LoadTextSprite;\ninline Sprite SaveTextSprite;\ninline glm::vec2 MenuTitleTextPos;\n\ninline Sprite QuickLoadEntrySprite;\ninline Sprite QuickLoadEntryHighlightedSprite;\ninline Sprite SaveEntrySprite;\ninline Sprite SaveEntryHighlightedSprite;\ninline Sprite LoadEntrySprite;\ninline Sprite LoadEntryHighlightedSprite;\n\ninline Sprite SaveMenuBackgroundSprite;\n\nvoid Configure();\n\n}  // namespace SaveMenu\n}  // namespace MO6TW\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/mo6tw/sysmesbox.cpp",
    "content": "#include \"sysmesbox.h\"\n#include \"../../profile_internal.h\"\n\n#include \"../../../ui/ui.h\"\n#include \"../../../games/mo6tw/sysmesbox.h\"\n#include \"../../../game.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace MO6TW {\nnamespace SysMesBox {\n\nvoid Configure() {\n  SelectionHighlight = EnsureGetMember<Sprite>(\"SelectionHighlight\");\n\n  BoxPartLeft = EnsureGetMember<Sprite>(\"BoxPartLeft\");\n  BoxPartRight = EnsureGetMember<Sprite>(\"BoxPartRight\");\n  BoxPartMiddle = EnsureGetMember<Sprite>(\"BoxPartMiddle\");\n  BoxDecoration = EnsureGetMember<Sprite>(\"BoxDecoration\");\n\n  BoxX = EnsureGetMember<float>(\"BoxX\");\n  BoxY = EnsureGetMember<float>(\"BoxY\");\n  ChoicePadding = EnsureGetMember<float>(\"ChoicePadding\");\n  ChoiceY = EnsureGetMember<float>(\"ChoiceY\");\n  ChoiceXBase = EnsureGetMember<float>(\"ChoiceXBase\");\n  MinMaxMesWidth = EnsureGetMember<float>(\"MinMaxMesWidth\");\n  BoxMinimumWidth = EnsureGetMember<float>(\"BoxMinimumWidth\");\n  BoxMiddleBaseX = EnsureGetMember<float>(\"BoxMiddleBaseX\");\n  BoxMiddleBaseWidth = EnsureGetMember<float>(\"BoxMiddleBaseWidth\");\n  BoxMiddleRemainBase = EnsureGetMember<float>(\"BoxMiddleRemainBase\");\n  BoxRightBaseX = EnsureGetMember<float>(\"BoxRightBaseX\");\n  BoxRightRemainPad = EnsureGetMember<float>(\"BoxRightRemainPad\");\n\n  auto drawType = EnsureGetMember<Game::DrawComponentType>(\"DrawType\");\n\n  UI::SysMesBoxPtr = new UI::MO6TW::SysMesBox();\n  UI::Menus[drawType].push_back(UI::SysMesBoxPtr);\n}\n\n}  // namespace SysMesBox\n}  // namespace MO6TW\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/mo6tw/sysmesbox.h",
    "content": "#pragma once\n\n#include \"../../../spritesheet.h\"\n#include \"../../../games/mo6tw/sysmesbox.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace MO6TW {\nnamespace SysMesBox {\n\nvoid Configure();\n\ninline Sprite BoxPartLeft;\ninline Sprite BoxPartRight;\ninline Sprite BoxPartMiddle;\ninline Sprite BoxDecoration;\ninline Sprite SelectionHighlight;\n\ninline float BoxX;\ninline float BoxY;\ninline float ChoicePadding;\ninline float ChoiceY;\ninline float ChoiceXBase;\ninline float MinMaxMesWidth;\ninline float BoxMinimumWidth;\ninline float BoxMiddleBaseX;\ninline float BoxMiddleBaseWidth;\ninline float BoxMiddleRemainBase;\ninline float BoxRightBaseX;\ninline float BoxRightRemainPad;\n\n}  // namespace SysMesBox\n}  // namespace MO6TW\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/mo6tw/systemmenu.cpp",
    "content": "#include \"systemmenu.h\"\n#include \"../../profile_internal.h\"\n#include \"../../../log.h\"\n\n#include \"../../ui/systemmenu.h\"\n#include \"../../../game.h\"\n#include \"../../../ui/ui.h\"\n#include \"../../../games/mo6tw/systemmenu.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace MO6TW {\nnamespace SystemMenu {\n\nvoid Configure() {\n  MenuEntriesHighlightedSprite =\n      EnsureGetMember<Sprite>(\"MenuEntriesHighlightedSprite\");\n  SystemMenuBackgroundSprite =\n      EnsureGetMember<Sprite>(\"SystemMenuBackgroundSprite\");\n  MenuEntriesTargetWidth = EnsureGetMember<float>(\"MenuEntriesTargetWidth\");\n  SystemMenuX = EnsureGetMember<float>(\"SystemMenuX\");\n  SystemMenuY = EnsureGetMember<float>(\"SystemMenuY\");\n\n  auto drawType = EnsureGetMember<Game::DrawComponentType>(\"DrawType\");\n\n  UI::SystemMenuPtr = new UI::MO6TW::SystemMenu();\n  UI::Menus[drawType].push_back(UI::SystemMenuPtr);\n}\n\n}  // namespace SystemMenu\n}  // namespace MO6TW\n}  // namespace Profile\n}  // namespace Impacto\n"
  },
  {
    "path": "src/profile/games/mo6tw/systemmenu.h",
    "content": "#pragma once\n\n#include \"../../../spritesheet.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace MO6TW {\nnamespace SystemMenu {\n\nvoid Configure();\n\ninline Sprite SystemMenuBackgroundSprite;\ninline Sprite MenuEntriesHighlightedSprite;\n\ninline float MenuEntriesTargetWidth;\ninline float SystemMenuX;\ninline float SystemMenuY;\n\n}  // namespace SystemMenu\n}  // namespace MO6TW\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/mo6tw/tipsmenu.cpp",
    "content": "#include \"tipsmenu.h\"\n#include \"../../profile_internal.h\"\n#include \"../../../renderer/renderer.h\"\n#include \"../../../log.h\"\n#include \"../../../text/text.h\"\n\n#include \"../../ui/tipsmenu.h\"\n#include \"../../../game.h\"\n#include \"../../../ui/ui.h\"\n#include \"../../../games/mo6tw/tipsmenu.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace MO6TW {\nnamespace TipsMenu {\n\nvoid Configure() {\n  GetMemberArray<Sprite>(std::span(TipThumbnails, 37), \"Thumbnails\");\n  TipTextOnlyThumbnail = EnsureGetMember<Sprite>(\"TextOnlyThumbnail\");\n  ThumbnailPosition = EnsureGetMember<glm::vec2>(\"ThumbnailPosition\");\n  auto str = EnsureGetMember<char const*>(\"CategoryString\");\n  TextGetSc3String(str, CategoryString);\n\n  DefaultColorIndex = EnsureGetMember<int>(\"DefaultColorIndex\");\n  UnreadColorIndex = EnsureGetMember<int>(\"UnreadColorIndex\");\n  NameInitialBounds = EnsureGetMember<RectF>(\"NameInitialBounds\");\n  NameFontSize = EnsureGetMember<float>(\"NameFontSize\");\n  PronounciationInitialBounds =\n      EnsureGetMember<RectF>(\"PronounciationInitialBounds\");\n  PronounciationFontSize = EnsureGetMember<float>(\"PronounciationFontSize\");\n  CategoryInitialBounds = EnsureGetMember<RectF>(\"CategoryInitialBounds\");\n  CategoryEndX = EnsureGetMember<float>(\"CategoryEndX\");\n  CategoryFontSize = EnsureGetMember<float>(\"CategoryFontSize\");\n  SortStringTable = EnsureGetMember<int>(\"SortStringTable\");\n  SortStringIndex = EnsureGetMember<int>(\"SortStringIndex\");\n  TipListInitialY = EnsureGetMember<float>(\"TipListInitialY\");\n  TipListCategoriesPerPage = EnsureGetMember<int>(\"TipListCategoriesPerPage\");\n  TipListMaxPages = EnsureGetMember<int>(\"TipListMaxPages\");\n  TipListEntryBounds = EnsureGetMember<RectF>(\"TipListEntryBounds\");\n  TipListEntryFontSize = EnsureGetMember<float>(\"TipListEntryFontSize\");\n  TipListYPadding = EnsureGetMember<float>(\"TipListYPadding\");\n  TipListEntryHighlightOffset =\n      EnsureGetMember<glm::vec2>(\"TipListEntryHighlightOffset\");\n  TipListEntryNameXOffset = EnsureGetMember<float>(\"TipListEntryNameXOffset\");\n  TipListEntryNewText = EnsureGetMember<std::string>(\"TipListEntryNewText\");\n  TipListEntryNewOffset = EnsureGetMember<float>(\"TipListEntryNewOffset\");\n  TipListEntryLockedTable = EnsureGetMember<int>(\"TipListEntryLockedTable\");\n  TipListEntryLockedIndex = EnsureGetMember<int>(\"TipListEntryLockedIndex\");\n  NumberLabelStrTable = EnsureGetMember<int>(\"NumberLabelStrTable\");\n  NumberLabelStrIndex = EnsureGetMember<int>(\"NumberLabelStrIndex\");\n  NumberLabelPosition = EnsureGetMember<glm::vec2>(\"NumberLabelPosition\");\n  NumberLabelFontSize = EnsureGetMember<float>(\"NumberLabelFontSize\");\n  NumberBounds = EnsureGetMember<RectF>(\"NumberBounds\");\n  NumberFontSize = EnsureGetMember<float>(\"NumberFontSize\");\n  PageSeparatorTable = EnsureGetMember<int>(\"PageSeparatorTable\");\n  PageSeparatorIndex = EnsureGetMember<int>(\"PageSeparatorIndex\");\n  PageSeparatorPosition = EnsureGetMember<glm::vec2>(\"PageSeparatorPosition\");\n  PageSeparatorFontSize = EnsureGetMember<float>(\"PageSeparatorFontSize\");\n  CurrentPageBounds = EnsureGetMember<RectF>(\"CurrentPageBounds\");\n  TotalPagesBounds = EnsureGetMember<RectF>(\"TotalPagesBounds\");\n\n  auto drawType = EnsureGetMember<Game::DrawComponentType>(\"DrawType\");\n\n  UI::TipsMenuPtr = new UI::MO6TW::TipsMenu();\n  UI::Menus[drawType].push_back(UI::TipsMenuPtr);\n}\n\n}  // namespace TipsMenu\n}  // namespace MO6TW\n}  // namespace Profile\n}  // namespace Impacto\n"
  },
  {
    "path": "src/profile/games/mo6tw/tipsmenu.h",
    "content": "#pragma once\n\n#include \"../../../spritesheet.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace MO6TW {\nnamespace TipsMenu {\n\nint constexpr ThumbnailCount = 37;\nint constexpr MaxCategoryString = 5;\n\ninline Sprite TipThumbnails[ThumbnailCount];\ninline Sprite TipTextOnlyThumbnail;\ninline glm::vec2 ThumbnailPosition;\ninline uint16_t CategoryString[MaxCategoryString];\n\ninline int DefaultColorIndex;\ninline int UnreadColorIndex;\ninline RectF NameInitialBounds;\ninline float NameFontSize;\ninline RectF PronounciationInitialBounds;\ninline float PronounciationFontSize;\ninline RectF CategoryInitialBounds;\ninline float CategoryEndX;\ninline float CategoryFontSize;\ninline int SortStringTable;\ninline int SortStringIndex;\ninline float TipListInitialY;\ninline int TipListCategoriesPerPage;\ninline int TipListMaxPages;\ninline RectF TipListEntryBounds;\ninline float TipListEntryFontSize;\ninline float TipListYPadding;\ninline glm::vec2 TipListEntryHighlightOffset;\ninline float TipListEntryNameXOffset;\ninline std::string TipListEntryNewText;\ninline float TipListEntryNewOffset;\ninline int TipListEntryLockedTable;\ninline int TipListEntryLockedIndex;\ninline int NumberLabelStrTable;\ninline int NumberLabelStrIndex;\ninline glm::vec2 NumberLabelPosition;\ninline float NumberLabelFontSize;\ninline RectF NumberBounds;\ninline float NumberFontSize;\ninline int PageSeparatorTable;\ninline int PageSeparatorIndex;\ninline glm::vec2 PageSeparatorPosition;\ninline float PageSeparatorFontSize;\ninline RectF CurrentPageBounds;\ninline RectF TotalPagesBounds;\n\nvoid Configure();\n\n}  // namespace TipsMenu\n}  // namespace MO6TW\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/mo6tw/tipsnotification.cpp",
    "content": "#include \"tipsnotification.h\"\n#include \"../../../log.h\"\n#include \"../../profile_internal.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace MO6TW {\nnamespace TipsNotification {\n\nvoid Configure() {\n  AlertPosition = EnsureGetMember<glm::vec2>(\"AlertPosition\");\n  NotificationAlertMessageId =\n      EnsureGetMember<int>(\"NotificationAlertMessageId\");\n  FinalNotificationPosition =\n      EnsureGetMember<glm::vec2>(\"FinalNotificationPosition\");\n  InitialNotificationPosition =\n      EnsureGetMember<glm::vec2>(\"InitialNotificationPosition\");\n  NotificationRenderingBounds =\n      EnsureGetMember<RectF>(\"NotificationRenderingBounds\");\n  TimerDuration = EnsureGetMember<float>(\"TimerDuration\");\n  MoveAnimationDuration = EnsureGetMember<float>(\"MoveAnimationDuration\");\n\n  AlertTextColorIndex = EnsureGetMember<int>(\"AlertTextColorIndex\");\n  TipNameColorIndex = EnsureGetMember<int>(\"TipNameColorIndex\");\n  FontSize = EnsureGetMember<float>(\"FontSize\");\n}\n\n}  // namespace TipsNotification\n}  // namespace MO6TW\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/mo6tw/tipsnotification.h",
    "content": "#pragma once\n\n#include \"../../../spritesheet.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace MO6TW {\nnamespace TipsNotification {\n\nvoid Configure();\n\ninline glm::vec2 AlertPosition;\ninline int NotificationAlertMessageId;\ninline glm::vec2 FinalNotificationPosition;\ninline glm::vec2 InitialNotificationPosition;\ninline RectF NotificationRenderingBounds;\ninline float TimerDuration;\ninline float MoveAnimationDuration;\n\ninline int AlertTextColorIndex;\ninline int TipNameColorIndex;\ninline float FontSize;\n\n}  // namespace TipsNotification\n}  // namespace MO6TW\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/mo6tw/titlemenu.cpp",
    "content": "#include \"titlemenu.h\"\n#include \"../../../log.h\"\n#include \"../../profile_internal.h\"\n\n#include \"../../ui/titlemenu.h\"\n#include \"../../../game.h\"\n#include \"../../../ui/ui.h\"\n#include \"../../../games/mo6tw/titlemenu.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace MO6TW {\nnamespace TitleMenu {\n\nvoid Configure() {\n  BackgroundSprite = EnsureGetMember<Sprite>(\"BackgroundSprite\");\n  CopyrightSprite = EnsureGetMember<Sprite>(\"CopyrightSprite\");\n  LogoSprite = EnsureGetMember<Sprite>(\"LogoSprite\");\n  LogoX = EnsureGetMember<float>(\"LogoX\");\n  LogoY = EnsureGetMember<float>(\"LogoY\");\n  CopyrightX = EnsureGetMember<float>(\"CopyrightX\");\n  CopyrightY = EnsureGetMember<float>(\"CopyrightY\");\n  MenuBackgroundSprite = EnsureGetMember<Sprite>(\"MenuBackgroundSprite\");\n  MenuItemLockedSprite = EnsureGetMember<Sprite>(\"MenuItemLockedSprite\");\n  MenuItemLockedSpriteH = EnsureGetMember<Sprite>(\"MenuItemLockedSpriteH\");\n  MenuEntriesTargetWidth = EnsureGetMember<float>(\"MenuEntriesTargetWidth\");\n  MenuEntriesX = EnsureGetMember<float>(\"MenuEntriesX\");\n  MenuEntriesFirstY = EnsureGetMember<float>(\"MenuEntriesFirstY\");\n  MenuEntriesYPadding = EnsureGetMember<float>(\"MenuEntriesYPadding\");\n  PrimaryFadeAnimDuration = EnsureGetMember<float>(\"PrimaryFadeAnimDuration\");\n  SecondaryMenuAnimTarget =\n      EnsureGetMember<glm::vec2>(\"SecondaryMenuAnimTarget\");\n  SecondaryMenuPadding = EnsureGetMember<float>(\"SecondaryMenuPadding\");\n  SecondaryMenuAnimDuration =\n      EnsureGetMember<float>(\"SecondaryMenuAnimDuration\");\n  SecondaryMenuAnimUnderX = EnsureGetMember<float>(\"SecondaryMenuAnimUnderX\");\n  ExtraStoryItemCount = EnsureGetMember<float>(\"ExtraStoryItemCount\");\n  ContinueItemCount = EnsureGetMember<float>(\"ContinueItemCount\");\n  MemoriesItemCount = EnsureGetMember<float>(\"MemoriesItemCount\");\n  SystemItemCount = EnsureGetMember<float>(\"SystemItemCount\");\n\n  UI::MO6TW::TitleMenu* menu = new UI::MO6TW::TitleMenu();\n  menu->PressToStartAnimation.DurationIn =\n      Profile::TitleMenu::PressToStartAnimDurationIn;\n  menu->PressToStartAnimation.DurationOut =\n      Profile::TitleMenu::PressToStartAnimDurationOut;\n  menu->PressToStartAnimation.LoopMode = AnimationLoopMode::ReverseDirection;\n  menu->PrimaryFadeAnimation.DurationIn = PrimaryFadeAnimDuration;\n  menu->PrimaryFadeAnimation.DurationOut = PrimaryFadeAnimDuration;\n  menu->SecondaryFadeAnimation.DurationIn = SecondaryMenuAnimDuration;\n  menu->SecondaryFadeAnimation.DurationOut = SecondaryMenuAnimDuration;\n  UI::TitleMenuPtr = menu;\n\n  auto drawType = EnsureGetMember<Game::DrawComponentType>(\"DrawType\");\n\n  UI::Menus[drawType].push_back(UI::TitleMenuPtr);\n}\n\n}  // namespace TitleMenu\n}  // namespace MO6TW\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/mo6tw/titlemenu.h",
    "content": "#pragma once\n\n#include \"../../../spritesheet.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace MO6TW {\nnamespace TitleMenu {\n\nvoid Configure();\n\ninline Sprite BackgroundSprite;\ninline Sprite CopyrightSprite;\ninline Sprite LogoSprite;\ninline Sprite MenuBackgroundSprite;\ninline Sprite MenuItemLockedSprite;\ninline Sprite MenuItemLockedSpriteH;\n\ninline float MenuEntriesX;\ninline float MenuEntriesFirstY;\ninline float MenuEntriesYPadding;\ninline float MenuEntriesTargetWidth;\ninline float LogoX;\ninline float LogoY;\ninline float CopyrightX;\ninline float CopyrightY;\ninline float PrimaryFadeAnimDuration;\ninline glm::vec2 SecondaryMenuAnimTarget;\ninline float SecondaryMenuPadding;\ninline float SecondaryMenuAnimDuration;\ninline float SecondaryMenuAnimUnderX;\ninline float ExtraStoryItemCount;\ninline float ContinueItemCount;\ninline float MemoriesItemCount;\ninline float SystemItemCount;\n\n}  // namespace TitleMenu\n}  // namespace MO6TW\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/mo8/optionsmenu.cpp",
    "content": "#include \"optionsmenu.h\"\n#include \"../../profile_internal.h\"\n#include \"../../../log.h\"\n\n#include \"../../ui/optionsmenu.h\"\n#include \"../../../game.h\"\n#include \"../../../ui/ui.h\"\n#include \"../../../games/mo8/optionsmenu.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace MO8 {\nnamespace OptionsMenu {\n\nvoid Configure() {\n  auto drawType = EnsureGetMember<Game::DrawComponentType>(\"DrawType\");\n\n  BackgroundSprite = EnsureGetMember<Sprite>(\"BackgroundSprite\");\n\n  NextButtonSprite = EnsureGetMember<Sprite>(\"NextButtonSprite\");\n  NextButtonHighlightedSprite =\n      EnsureGetMember<Sprite>(\"NextButtonHighlightedSprite\");\n  NextButtonPosition = EnsureGetMember<glm::vec2>(\"NextButtonPosition\");\n\n  BackButtonSprite = EnsureGetMember<Sprite>(\"BackButtonSprite\");\n  BackButtonHighlightedSprite =\n      EnsureGetMember<Sprite>(\"BackButtonHighlightedSprite\");\n  BackButtonPosition = EnsureGetMember<glm::vec2>(\"BackButtonPosition\");\n\n  SliderTrackSprite = EnsureGetMember<Sprite>(\"SliderTrackSprite\");\n  SliderFillSprite = EnsureGetMember<Sprite>(\"SliderFillSprite\");\n  SliderThumbSprite = EnsureGetMember<Sprite>(\"SliderThumbSprite\");\n\n  ButtonHighlight = EnsureGetMember<Sprite>(\"ButtonHighlight\");\n  PageLabelPosition = EnsureGetMember<glm::vec2>(\"PageLabelPosition\");\n  ListStartingPosition = EnsureGetMember<glm::vec2>(\"ListStartingPosition\");\n  ListPadding = EnsureGetMember<glm::vec2>(\"ListPadding\");\n  OptionGroupItemsOffset = EnsureGetMember<glm::vec2>(\"OptionGroupItemsOffset\");\n  OptionGroupSliderOffset =\n      EnsureGetMember<glm::vec2>(\"OptionGroupSliderOffset\");\n\n  TextPageLabel = EnsureGetMember<Sprite>(\"TextPageLabel\");\n\n  TextSpeedOptionsNum = EnsureGetMember<int>(\"TextSpeedOptionsNum\");\n  if (TextSpeedOptionsNum > 0) {\n    TextSpeedOptionsLabel = EnsureGetMember<Sprite>(\"TextSpeedOptionsLabel\");\n    TextSpeedOptionsLabelH = EnsureGetMember<Sprite>(\"TextSpeedOptionsLabelH\");\n\n    GetMemberArray<Sprite>(\n        std::span(TextSpeedOptionsSprites, TextSpeedOptionsNum),\n        \"TextSpeedOptionsSprites\");\n    GetMemberArray<Sprite>(\n        std::span(TextSpeedOptionsHSprites, TextSpeedOptionsNum),\n        \"TextSpeedOptionsHSprites\");\n  }\n\n  AutoModeOptionsNum = EnsureGetMember<int>(\"AutoModeOptionsNum\");\n  if (AutoModeOptionsNum > 0) {\n    AutoModeOptionsLabel = EnsureGetMember<Sprite>(\"AutoModeOptionsLabel\");\n    AutoModeOptionsLabelH = EnsureGetMember<Sprite>(\"AutoModeOptionsLabelH\");\n\n    GetMemberArray<Sprite>(\n        std::span(AutoModeOptionsSprites, AutoModeOptionsNum),\n        \"AutoModeOptionsSprites\");\n    GetMemberArray<Sprite>(\n        std::span(AutoModeOptionsHSprites, AutoModeOptionsNum),\n        \"AutoModeOptionsHSprites\");\n  }\n\n  SkipModeOptionsNum = EnsureGetMember<int>(\"SkipModeOptionsNum\");\n  if (SkipModeOptionsNum > 0) {\n    SkipModeOptionsLabel = EnsureGetMember<Sprite>(\"SkipModeOptionsLabel\");\n    SkipModeOptionsLabelH = EnsureGetMember<Sprite>(\"SkipModeOptionsLabelH\");\n\n    GetMemberArray<Sprite>(\n        std::span(SkipModeOptionsSprites, SkipModeOptionsNum),\n        \"SkipModeOptionsSprites\");\n    GetMemberArray<Sprite>(\n        std::span(SkipModeOptionsHSprites, SkipModeOptionsNum),\n        \"SkipModeOptionsHSprites\");\n  }\n\n  SoundPageLabel = EnsureGetMember<Sprite>(\"SoundPageLabel\");\n  SoundModeOptionsNum = EnsureGetMember<int>(\"SoundModeOptionsNum\");\n  if (SoundModeOptionsNum > 0) {\n    VoiceSyncOptionsLabel = EnsureGetMember<Sprite>(\"VoiceSyncOptionsLabel\");\n    VoiceSyncOptionsLabelH = EnsureGetMember<Sprite>(\"VoiceSyncOptionsLabelH\");\n    VoiceSkipOptionsLabel = EnsureGetMember<Sprite>(\"VoiceSkipOptionsLabel\");\n    VoiceSkipOptionsLabelH = EnsureGetMember<Sprite>(\"VoiceSkipOptionsLabelH\");\n    VoiceHighlightOptionsLabel =\n        EnsureGetMember<Sprite>(\"VoiceHighlightOptionsLabel\");\n    VoiceHighlightOptionsLabelH =\n        EnsureGetMember<Sprite>(\"VoiceHighlightOptionsLabelH\");\n\n    GetMemberArray<Sprite>(\n        std::span(SoundModeOptionsSprites, SoundModeOptionsNum),\n        \"SoundModeOptionsSprites\");\n    GetMemberArray<Sprite>(\n        std::span(SoundModeOptionsHSprites, SoundModeOptionsNum),\n        \"SoundModeOptionsHSprites\");\n  }\n\n  BgmVolumeLabel = EnsureGetMember<Sprite>(\"BgmVolumeLabel\");\n  BgmVolumeLabelH = EnsureGetMember<Sprite>(\"BgmVolumeLabelH\");\n  VoiceVolumeLabel = EnsureGetMember<Sprite>(\"VoiceVolumeLabel\");\n  VoiceVolumeLabelH = EnsureGetMember<Sprite>(\"VoiceVolumeLabelH\");\n  SeVolumeLabel = EnsureGetMember<Sprite>(\"SeVolumeLabel\");\n  SeVolumeLabelH = EnsureGetMember<Sprite>(\"SeVolumeLabelH\");\n  SystemSeVolumeLabel = EnsureGetMember<Sprite>(\"SystemSeVolumeLabel\");\n  SystemSeVolumeLabelH = EnsureGetMember<Sprite>(\"SystemSeVolumeLabelH\");\n  CharacterVoiceVolumeLabel =\n      EnsureGetMember<Sprite>(\"CharacterVoiceVolumeLabel\");\n  CharacterVoiceVolumeLabelH =\n      EnsureGetMember<Sprite>(\"CharacterVoiceVolumeLabelH\");\n\n  OtherPageLabel = EnsureGetMember<Sprite>(\"OtherPageLabel\");\n\n  QuickSaveOptionsNum = EnsureGetMember<int>(\"QuickSaveOptionsNum\");\n  if (QuickSaveOptionsNum > 0) {\n    QuickSaveOptionsLabel = EnsureGetMember<Sprite>(\"QuickSaveOptionsLabel\");\n    QuickSaveOptionsLabelH = EnsureGetMember<Sprite>(\"QuickSaveOptionsLabelH\");\n\n    GetMemberArray<Sprite>(\n        std::span(QuickSaveOptionsSprites, AutoModeOptionsNum),\n        \"QuickSaveOptionsSprites\");\n    GetMemberArray<Sprite>(\n        std::span(QuickSaveOptionsHSprites, AutoModeOptionsNum),\n        \"QuickSaveOptionsHSprites\");\n  }\n\n  UI::OptionsMenuPtr = new UI::MO8::OptionsMenu();\n  UI::Menus[drawType].push_back(UI::OptionsMenuPtr);\n}\n\n}  // namespace OptionsMenu\n}  // namespace MO8\n}  // namespace Profile\n}  // namespace Impacto\n"
  },
  {
    "path": "src/profile/games/mo8/optionsmenu.h",
    "content": "#pragma once\n\n#include \"../../../spritesheet.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace MO8 {\nnamespace OptionsMenu {\n\ninline Sprite BackgroundSprite;\n\ninline Sprite NextButtonSprite;\ninline Sprite NextButtonHighlightedSprite;\ninline glm::vec2 NextButtonPosition;\ninline Sprite BackButtonSprite;\ninline Sprite BackButtonHighlightedSprite;\ninline glm::vec2 BackButtonPosition;\n\ninline Sprite SliderTrackSprite;\ninline Sprite SliderFillSprite;\ninline Sprite SliderThumbSprite;\n\ninline Sprite ButtonHighlight;\ninline glm::vec2 PageLabelPosition;\ninline glm::vec2 ListStartingPosition;\ninline glm::vec2 ListPadding;\ninline glm::vec2 OptionGroupItemsOffset;\ninline glm::vec2 OptionGroupSliderOffset;\n\nint constexpr TextSpeedOptionsNumMax = 4;\n\ninline Sprite TextPageLabel;\ninline Sprite TextSpeedOptionsLabel;\ninline Sprite TextSpeedOptionsLabelH;\ninline int TextSpeedOptionsNum;\ninline Sprite TextSpeedOptionsSprites[TextSpeedOptionsNumMax];\ninline Sprite TextSpeedOptionsHSprites[TextSpeedOptionsNumMax];\n\nint constexpr AutoModeOptionsNumMax = 3;\n\ninline Sprite AutoModeOptionsLabel;\ninline Sprite AutoModeOptionsLabelH;\ninline int AutoModeOptionsNum;\ninline Sprite AutoModeOptionsSprites[AutoModeOptionsNumMax];\ninline Sprite AutoModeOptionsHSprites[AutoModeOptionsNumMax];\n\nint constexpr SkipModeOptionsNumMax = 3;\n\ninline Sprite SkipModeOptionsLabel;\ninline Sprite SkipModeOptionsLabelH;\ninline int SkipModeOptionsNum;\ninline Sprite SkipModeOptionsSprites[SkipModeOptionsNumMax];\ninline Sprite SkipModeOptionsHSprites[SkipModeOptionsNumMax];\n\nint constexpr SoundOptionsNumMax = 2;\n\ninline int SoundModeOptionsNum;\ninline Sprite SoundPageLabel;\ninline Sprite SoundModeOptionsSprites[SoundOptionsNumMax];\ninline Sprite SoundModeOptionsHSprites[SoundOptionsNumMax];\n\ninline Sprite VoiceSyncOptionsLabel;\ninline Sprite VoiceSyncOptionsLabelH;\n\ninline Sprite VoiceSkipOptionsLabel;\ninline Sprite VoiceSkipOptionsLabelH;\n\ninline Sprite VoiceHighlightOptionsLabel;\ninline Sprite VoiceHighlightOptionsLabelH;\n\ninline Sprite BgmVolumeLabel;\ninline Sprite BgmVolumeLabelH;\ninline Sprite VoiceVolumeLabel;\ninline Sprite VoiceVolumeLabelH;\ninline Sprite SeVolumeLabel;\ninline Sprite SeVolumeLabelH;\ninline Sprite SystemSeVolumeLabel;\ninline Sprite SystemSeVolumeLabelH;\ninline Sprite CharacterVoiceVolumeLabel;\ninline Sprite CharacterVoiceVolumeLabelH;\n\ninline Sprite OtherPageLabel;\n\nint constexpr QuickSaveOptionsNumMax = 3;\n\ninline Sprite QuickSaveOptionsLabel;\ninline Sprite QuickSaveOptionsLabelH;\ninline int QuickSaveOptionsNum;\ninline Sprite QuickSaveOptionsSprites[QuickSaveOptionsNumMax];\ninline Sprite QuickSaveOptionsHSprites[QuickSaveOptionsNumMax];\n\nvoid Configure();\n\n}  // namespace OptionsMenu\n}  // namespace MO8\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/mo8/savemenu.cpp",
    "content": "#include \"savemenu.h\"\n#include \"../../profile_internal.h\"\n#include \"../../../log.h\"\n\n#include \"../../ui/savemenu.h\"\n#include \"../../../game.h\"\n#include \"../../../ui/ui.h\"\n#include \"../../../games/mo8/savemenu.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace MO8 {\nnamespace SaveMenu {\n\nvoid Configure() {\n  SaveMenuBackgroundSprite =\n      EnsureGetMember<Sprite>(\"SaveMenuBackgroundSprite\");\n\n  EntryStartX = EnsureGetMember<float>(\"EntryStartX\");\n  EntryXPadding = EnsureGetMember<float>(\"EntryXPadding\");\n  EntryStartY = EnsureGetMember<float>(\"EntryStartY\");\n  EntryYPadding = EnsureGetMember<float>(\"EntryYPadding\");\n\n  QuickLoadTextSprite = EnsureGetMember<Sprite>(\"QuickLoadTextSprite\");\n  LoadTextSprite = EnsureGetMember<Sprite>(\"LoadTextSprite\");\n  SaveTextSprite = EnsureGetMember<Sprite>(\"SaveTextSprite\");\n  MenuTitleTextPos = EnsureGetMember<glm::vec2>(\"MenuTitleTextPos\");\n\n  NextButtonSprite = EnsureGetMember<Sprite>(\"NextButtonSprite\");\n  NextButtonHighlightedSprite =\n      EnsureGetMember<Sprite>(\"NextButtonHighlightedSprite\");\n  NextButtonPosition = EnsureGetMember<glm::vec2>(\"NextButtonPosition\");\n\n  BackButtonSprite = EnsureGetMember<Sprite>(\"BackButtonSprite\");\n  BackButtonHighlightedSprite =\n      EnsureGetMember<Sprite>(\"BackButtonHighlightedSprite\");\n  BackButtonPosition = EnsureGetMember<glm::vec2>(\"BackButtonPosition\");\n\n  auto drawType = EnsureGetMember<Game::DrawComponentType>(\"DrawType\");\n\n  UI::SaveMenuPtr = new UI::MO8::SaveMenu();\n  UI::Menus[drawType].push_back(UI::SaveMenuPtr);\n}\n\n}  // namespace SaveMenu\n}  // namespace MO8\n}  // namespace Profile\n}  // namespace Impacto\n"
  },
  {
    "path": "src/profile/games/mo8/savemenu.h",
    "content": "#pragma once\n\n#include \"../../../spritesheet.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace MO8 {\nnamespace SaveMenu {\n\nint constexpr EntriesPerRow = 2;\nint constexpr RowsPerPage = 4;\n\ninline float EntryStartX;\ninline float EntryXPadding;\ninline float EntryStartY;\ninline float EntryYPadding;\n\ninline Sprite QuickLoadTextSprite;\ninline Sprite LoadTextSprite;\ninline Sprite SaveTextSprite;\ninline glm::vec2 MenuTitleTextPos;\n\ninline Sprite NextButtonSprite;\ninline Sprite NextButtonHighlightedSprite;\ninline glm::vec2 NextButtonPosition;\ninline Sprite BackButtonSprite;\ninline Sprite BackButtonHighlightedSprite;\ninline glm::vec2 BackButtonPosition;\n\ninline Sprite SaveMenuBackgroundSprite;\n\nvoid Configure();\n\n}  // namespace SaveMenu\n}  // namespace MO8\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/mo8/systemmenu.cpp",
    "content": "#include \"systemmenu.h\"\n#include \"../../profile_internal.h\"\n#include \"../../../log.h\"\n\n#include \"../../ui/systemmenu.h\"\n#include \"../../../game.h\"\n#include \"../../../ui/ui.h\"\n#include \"../../../games/mo8/systemmenu.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace MO8 {\nnamespace SystemMenu {\n\nvoid Configure() {\n  MenuEntriesLNum = EnsureGetMember<int>(\"MenuEntriesLNum\");\n  if (MenuEntriesLNum > 0) {\n    GetMemberArray<Sprite>(std::span(MenuEntriesLSprites, MenuEntriesLNum),\n                           \"MenuEntriesLockedSprites\");\n  }\n  ExitMenuButtonId = EnsureGetMember<int>(\"ExitMenuButtonId\");\n  SystemMenuBackgroundSprite =\n      EnsureGetMember<Sprite>(\"SystemMenuBackgroundSprite\");\n  MenuEntriesTargetWidth = EnsureGetMember<float>(\"MenuEntriesTargetWidth\");\n  SystemMenuX = EnsureGetMember<float>(\"SystemMenuX\");\n  SystemMenuY = EnsureGetMember<float>(\"SystemMenuY\");\n\n  auto drawType = EnsureGetMember<Game::DrawComponentType>(\"DrawType\");\n\n  UI::SystemMenuPtr = new UI::MO8::SystemMenu();\n  UI::Menus[drawType].push_back(UI::SystemMenuPtr);\n}\n\n}  // namespace SystemMenu\n}  // namespace MO8\n}  // namespace Profile\n}  // namespace Impacto\n"
  },
  {
    "path": "src/profile/games/mo8/systemmenu.h",
    "content": "#pragma once\n\n#include \"../../../spritesheet.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace MO8 {\nnamespace SystemMenu {\n\nvoid Configure();\n\nint constexpr MenuEntriesNumMax = 16;\n\ninline Sprite SystemMenuBackgroundSprite;\ninline Sprite MenuEntriesLSprites[MenuEntriesNumMax];\ninline int MenuEntriesLNum;\n\ninline int ExitMenuButtonId;\n\ninline float MenuEntriesTargetWidth;\ninline float SystemMenuX;\ninline float SystemMenuY;\n\n}  // namespace SystemMenu\n}  // namespace MO8\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/mo8/titlemenu.cpp",
    "content": "#include \"titlemenu.h\"\n#include \"../../profile_internal.h\"\n\n#include \"../../ui/titlemenu.h\"\n#include \"../../../game.h\"\n#include \"../../../ui/ui.h\"\n#include \"../../../games/mo8/titlemenu.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace MO8 {\nnamespace TitleMenu {\n\nvoid Configure() {\n  BackgroundSprite = EnsureGetMember<Sprite>(\"BackgroundSprite\");\n  LogoSprite = EnsureGetMember<Sprite>(\"LogoSprite\");\n  LogoPositionX = EnsureGetMember<float>(\"LogoX\");\n  LogoPositionY = EnsureGetMember<float>(\"LogoY\");\n  NewGameSpriteIndex = EnsureGetMember<int>(\"NewGameSpriteIndex\");\n  ContinueSpriteIndex = EnsureGetMember<int>(\"ContinueSpriteIndex\");\n  OptionSpriteIndex = EnsureGetMember<int>(\"OptionSpriteIndex\");\n  GallerySpriteIndex = EnsureGetMember<int>(\"GallerySpriteIndex\");\n  AlbumSpriteIndex = EnsureGetMember<int>(\"AlbumSpriteIndex\");\n  MusicSpriteIndex = EnsureGetMember<int>(\"MusicSpriteIndex\");\n  ClearListSpriteIndex = EnsureGetMember<int>(\"ClearListSpriteIndex\");\n  WarningSpriteIndex = EnsureGetMember<int>(\"WarningSpriteIndex\");\n  AdditionalSpriteIndex = EnsureGetMember<int>(\"AdditionalSpriteIndex\");\n  DLCSpriteIndex = EnsureGetMember<int>(\"DLCSpriteIndex\");\n  LoadSpriteIndex = EnsureGetMember<int>(\"LoadSpriteIndex\");\n  QuickLoadSpriteIndex = EnsureGetMember<int>(\"QuickLoadSpriteIndex\");\n  MenuEntriesX = EnsureGetMember<float>(\"MenuEntriesX\");\n  MenuEntriesFirstY = EnsureGetMember<float>(\"MenuEntriesFirstY\");\n  MenuEntriesGalleryFirstY = EnsureGetMember<float>(\"MenuEntriesGalleryFirstY\");\n  MenuEntriesYPadding = EnsureGetMember<float>(\"MenuEntriesYPadding\");\n  HasAdditional = EnsureGetMember<bool>(\"HasAdditional\");\n  PressToStartAnimated = EnsureGetMember<bool>(\"PressToStartAnimated\");\n  PressToStartAnimation.DurationIn =\n      Profile::TitleMenu::PressToStartAnimDurationIn;\n  PressToStartAnimation.DurationOut =\n      Profile::TitleMenu::PressToStartAnimDurationOut;\n  PressToStartAnimation.LoopMode = AnimationLoopMode::ReverseDirection;\n  PrimaryFadeAnimDuration = EnsureGetMember<float>(\"PrimaryFadeAnimDuration\");\n  ItemFadeAnimDuration = EnsureGetMember<float>(\"ItemFadeAnimDuration\");\n\n  auto drawType = EnsureGetMember<Game::DrawComponentType>(\"DrawType\");\n\n  UI::MO8::TitleMenu* menu = new UI::MO8::TitleMenu();\n  menu->PrimaryFadeAnimation.DurationIn = PrimaryFadeAnimDuration;\n  menu->PrimaryFadeAnimation.DurationOut = PrimaryFadeAnimDuration;\n  menu->MainItemsHideAnimation.DurationIn = ItemFadeAnimDuration;\n  menu->MainItemsHideAnimation.DurationOut = ItemFadeAnimDuration;\n  menu->ContinueItemsShowAnimation.DurationIn = ItemFadeAnimDuration;\n  menu->ContinueItemsShowAnimation.DurationOut = ItemFadeAnimDuration;\n  menu->GalleryItemsShowAnimation.DurationIn = ItemFadeAnimDuration;\n  menu->GalleryItemsShowAnimation.DurationOut = ItemFadeAnimDuration;\n  UI::TitleMenuPtr = menu;\n  UI::Menus[drawType].push_back(UI::TitleMenuPtr);\n}\n\n}  // namespace TitleMenu\n}  // namespace MO8\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/mo8/titlemenu.h",
    "content": "#pragma once\n\n#include \"../../../spritesheet.h\"\n#include \"../../../games/mo8/titlemenu.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace MO8 {\nnamespace TitleMenu {\n\nvoid Configure();\n\ninline Sprite BackgroundSprite;\ninline Sprite LogoSprite;\n\ninline float LogoPositionX;\ninline float LogoPositionY;\n\ninline int NewGameSpriteIndex;\ninline int ContinueSpriteIndex;\ninline int OptionSpriteIndex;\ninline int GallerySpriteIndex;\ninline int AlbumSpriteIndex;\ninline int MusicSpriteIndex;\ninline int ClearListSpriteIndex;\ninline int WarningSpriteIndex;\ninline int AdditionalSpriteIndex;\ninline int DLCSpriteIndex;\ninline int LoadSpriteIndex;\ninline int QuickLoadSpriteIndex;\n\ninline float MenuEntriesX;\ninline float MenuEntriesFirstY;\ninline float MenuEntriesYPadding;\ninline float MenuEntriesGalleryFirstY;\n\ninline bool PressToStartAnimated;\ninline bool HasAdditional;\n\ninline Animation PressToStartAnimation;\ninline float PrimaryFadeAnimDuration;\ninline float ItemFadeAnimDuration;\n\n}  // namespace TitleMenu\n}  // namespace MO8\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/rne/sysmesbox.cpp",
    "content": "#include \"sysmesbox.h\"\n#include \"../../profile_internal.h\"\n\n#include \"../../../ui/ui.h\"\n#include \"../../../games/rne/sysmesbox.h\"\n#include \"../../../game.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace RNE {\nnamespace SysMesBox {\n\nvoid Configure() {\n  BoxDecorationTop = EnsureGetMember<Sprite>(\"BoxDecorationTop\");\n  BoxDecorationBottom = EnsureGetMember<Sprite>(\"BoxDecorationBottom\");\n  TextDecoration = EnsureGetMember<Sprite>(\"TextDecoration\");\n  MessageLabel = EnsureGetMember<Sprite>(\"MessageLabel\");\n  Line1 = EnsureGetMember<Sprite>(\"Line1\");\n  Line2 = EnsureGetMember<Sprite>(\"Line2\");\n  ButtonYes = EnsureGetMember<Sprite>(\"ButtonYes\");\n  ButtonNo = EnsureGetMember<Sprite>(\"ButtonNo\");\n  ButtonOK = EnsureGetMember<Sprite>(\"ButtonOK\");\n  ButtonYesHighlighted = EnsureGetMember<Sprite>(\"ButtonYesHighlighted\");\n  ButtonNoHighlighted = EnsureGetMember<Sprite>(\"ButtonNoHighlighted\");\n  ButtonOKHighlighted = EnsureGetMember<Sprite>(\"ButtonOKHighlighted\");\n\n  LinePositionXFirst = EnsureGetMember<float>(\"LinePositionXFirst\");\n  LinePositionX = EnsureGetMember<float>(\"LinePositionX\");\n  LinePositionMultiplier = EnsureGetMember<float>(\"LinePositionMultiplier\");\n  LineWidthFirst = EnsureGetMember<float>(\"LineWidthFirst\");\n  LineWidthBase = EnsureGetMember<float>(\"LineWidthBase\");\n  LineWidthMultiplier = EnsureGetMember<float>(\"LineWidthMultiplier\");\n  Line1SpriteY = EnsureGetMember<float>(\"Line1SpriteY\");\n  Line2SpriteY = EnsureGetMember<float>(\"Line2SpriteY\");\n  LineSpriteHeight = EnsureGetMember<float>(\"LineSpriteHeight\");\n  LineDisplayXBase = EnsureGetMember<float>(\"LineDisplayXBase\");\n  Line1DisplayY = EnsureGetMember<float>(\"Line1DisplayY\");\n  Line2DisplayY = EnsureGetMember<float>(\"Line2DisplayY\");\n  BoxDisplayStartCount = EnsureGetMember<float>(\"BoxDisplayStartCount\");\n  BoxHeightBase = EnsureGetMember<float>(\"BoxHeightBase\");\n  BoxHeightMultiplier = EnsureGetMember<float>(\"BoxHeightMultiplier\");\n  BoxWidth = EnsureGetMember<float>(\"BoxWidth\");\n  BoxTextFontSize = EnsureGetMember<float>(\"BoxTextFontSize\");\n  BoxTopYBase = EnsureGetMember<float>(\"BoxTopYBase\");\n  BoxDisplayX = EnsureGetMember<float>(\"BoxDisplayX\");\n  MessageLabelSpriteXBase = EnsureGetMember<float>(\"MessageLabelSpriteXBase\");\n  MessageLabelSpriteY = EnsureGetMember<float>(\"MessageLabelSpriteY\");\n  MessageLabelSpriteHeight = EnsureGetMember<float>(\"MessageLabelSpriteHeight\");\n  MessageLabelSpriteMultiplier =\n      EnsureGetMember<float>(\"MessageLabelSpriteMultiplier\");\n  ButtonYesDisplayXBase = EnsureGetMember<float>(\"ButtonYesDisplayXBase\");\n  ButtonRightDisplayXBase = EnsureGetMember<float>(\"ButtonRightDisplayXBase\");\n  ButtonWidth = EnsureGetMember<float>(\"ButtonWidth\");\n  ButtonYOffset = EnsureGetMember<float>(\"ButtonYOffset\");\n  ButtonYWidthBase = EnsureGetMember<float>(\"ButtonYWidthBase\");\n  ButtonRightWidthBase = EnsureGetMember<float>(\"ButtonRightWidthBase\");\n  TextDecorationStart = EnsureGetMember<float>(\"TextDecorationStart\");\n  TextDecorationTopYOffset = EnsureGetMember<float>(\"TextDecorationTopYOffset\");\n  TextDecorationBottomYOffset =\n      EnsureGetMember<float>(\"TextDecorationBottomYOffset\");\n\n  auto drawType = EnsureGetMember<Game::DrawComponentType>(\"DrawType\");\n\n  UI::SysMesBoxPtr = new UI::RNE::SysMesBox();\n  UI::Menus[drawType].push_back(UI::SysMesBoxPtr);\n}\n\n}  // namespace SysMesBox\n}  // namespace RNE\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/rne/sysmesbox.h",
    "content": "#pragma once\n\n#include \"../../../spritesheet.h\"\n#include \"../../../games/rne/sysmesbox.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace RNE {\nnamespace SysMesBox {\n\nvoid Configure();\n\ninline Sprite BoxDecorationTop;\ninline Sprite BoxDecorationBottom;\ninline Sprite TextDecoration;\ninline Sprite MessageLabel;\ninline Sprite Line1;\ninline Sprite Line2;\ninline Sprite ButtonYes;\ninline Sprite ButtonNo;\ninline Sprite ButtonOK;\ninline Sprite ButtonYesHighlighted;\ninline Sprite ButtonNoHighlighted;\ninline Sprite ButtonOKHighlighted;\n\ninline float LinePositionXFirst;\ninline float LinePositionX;\ninline float LinePositionMultiplier;\ninline float LineWidthFirst;\ninline float LineWidthBase;\ninline float LineWidthMultiplier;\ninline float Line1SpriteY;\ninline float Line2SpriteY;\ninline float LineSpriteHeight;\ninline float LineDisplayXBase;\ninline float Line1DisplayY;\ninline float Line2DisplayY;\ninline float BoxDisplayStartCount;\ninline float BoxHeightBase;\ninline float BoxHeightMultiplier;\ninline float BoxWidth;\ninline float BoxTextFontSize;\ninline float BoxTopYBase;\ninline float BoxDisplayX;\ninline float MessageLabelSpriteXBase;\ninline float MessageLabelSpriteY;\ninline float MessageLabelSpriteHeight;\ninline float MessageLabelSpriteMultiplier;\ninline float ButtonYesDisplayXBase;\ninline float ButtonRightDisplayXBase;\ninline float ButtonWidth;\ninline float ButtonSelectedSpriteY;\ninline float ButtonYOffset;\ninline float ButtonYWidthBase;\ninline float ButtonRightWidthBase;\ninline float TextDecorationStart;\ninline float TextDecorationTopYOffset;\ninline float TextDecorationBottomYOffset;\n\n}  // namespace SysMesBox\n}  // namespace RNE\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/rne/systemmenu.cpp",
    "content": "#include \"systemmenu.h\"\n#include \"../../profile_internal.h\"\n#include \"tilebackground.h\"\n\n#include \"../../ui/systemmenu.h\"\n#include \"../../../ui/ui.h\"\n#include \"../../../games/rne/systemmenu.h\"\n#include \"../../../game.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace RNE {\nnamespace SystemMenu {\n\nvoid Configure() {\n  EnsurePushMemberOfType(\"Background\", LUA_TTABLE);\n  BackgroundAnimation = RNE::ParseTileBackground();\n  RNE::ParseTileBackground();\n  Pop();\n\n  SkyBackgroundSprite = EnsureGetMember<Sprite>(\"SkyBackgroundSprite\");\n  SkyArrowSprite = EnsureGetMember<Sprite>(\"SkyArrowSprite\");\n  SkyTextSprite = EnsureGetMember<Sprite>(\"SkyTextSprite\");\n  SkyBackgroundBeginX = EnsureGetMember<float>(\"SkyBackgroundBeginX\");\n  SkyBackgroundY = EnsureGetMember<float>(\"SkyBackgroundY\");\n  SkyTextBeginX = EnsureGetMember<float>(\"SkyTextBeginX\");\n  SkyTextY = EnsureGetMember<float>(\"SkyTextY\");\n  MenuEntriesXSkew = EnsureGetMember<float>(\"MenuEntriesXSkew\");\n  MenuEntriesTargetWidth = EnsureGetMember<float>(\"MenuEntriesTargetWidth\");\n  SkyInStartProgress = EnsureGetMember<float>(\"SkyInStartProgress\");\n  SkyOutStartProgress = EnsureGetMember<float>(\"SkyOutStartProgress\");\n  SkyMoveDurationIn = EnsureGetMember<float>(\"SkyMoveDurationIn\");\n  SkyMoveDurationOut = EnsureGetMember<float>(\"SkyMoveDurationOut\");\n  EntriesMoveDurationIn = EnsureGetMember<float>(\"EntriesMoveDurationIn\");\n  EntriesMoveDurationOut = EnsureGetMember<float>(\"EntriesMoveDurationOut\");\n  HighlightDurationIn = EnsureGetMember<float>(\"HighlightDurationIn\");\n  HighlightDurationOut = EnsureGetMember<float>(\"HighlightDurationOut\");\n\n  ButtonBackgroundSprite = EnsureGetMember<Sprite>(\"ButtonBackgroundSprite\");\n  ButtonPromptsSprite = EnsureGetMember<Sprite>(\"ButtonPromptsSprite\");\n  ButtonBackgroundStartX = EnsureGetMember<float>(\"ButtonBackgroundStartX\");\n  ButtonBackgroundX = EnsureGetMember<float>(\"ButtonBackgroundX\");\n  ButtonBackgroundY = EnsureGetMember<float>(\"ButtonBackgroundY\");\n  ButtonBackgroundTargetWidth =\n      EnsureGetMember<float>(\"ButtonBackgroundTargetWidth\");\n  ButtonBackgroundSprStartX =\n      EnsureGetMember<float>(\"ButtonBackgroundSprStartX\");\n\n  SkyMoveAnimation.DurationIn = SkyMoveDurationIn;\n  SkyMoveAnimation.DurationOut = SkyMoveDurationOut;\n\n  EntriesMoveAnimation.DurationIn = EntriesMoveDurationIn;\n  EntriesMoveAnimation.DurationOut = EntriesMoveDurationOut;\n\n  HighlightAnimation.DurationIn = HighlightDurationIn;\n  HighlightAnimation.DurationOut = HighlightDurationOut;\n\n  auto drawType = EnsureGetMember<Game::DrawComponentType>(\"DrawType\");\n\n  UI::SystemMenuPtr = new UI::RNE::SystemMenu();\n  UI::Menus[drawType].push_back(UI::SystemMenuPtr);\n}\n\n}  // namespace SystemMenu\n}  // namespace RNE\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/rne/systemmenu.h",
    "content": "#pragma once\n\n#include \"../../../spritesheet.h\"\n#include \"../../../games/rne/systemmenu.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace RNE {\nnamespace SystemMenu {\n\ninline Sprite SkyBackgroundSprite;\ninline Sprite SkyArrowSprite;\ninline Sprite SkyTextSprite;\ninline Sprite ButtonBackgroundSprite;\ninline Sprite ButtonPromptsSprite;\ninline float ButtonBackgroundStartX;\ninline float ButtonBackgroundX;\ninline float ButtonBackgroundY;\ninline float ButtonBackgroundTargetWidth;\ninline float ButtonBackgroundSprStartX;\ninline float SkyBackgroundBeginX;\ninline float SkyBackgroundY;\ninline float SkyTextBeginX;\ninline float SkyTextY;\ninline float MenuEntriesXSkew;\ninline float MenuEntriesTargetWidth;\ninline float SkyInStartProgress;\ninline float SkyOutStartProgress;\ninline float SkyMoveDurationIn;\ninline float SkyMoveDurationOut;\ninline float EntriesMoveDurationIn;\ninline float EntriesMoveDurationOut;\ninline float HighlightDurationIn;\ninline float HighlightDurationOut;\ninline Animation* BackgroundAnimation = nullptr;\ninline Animation SkyMoveAnimation;\ninline Animation EntriesMoveAnimation;\ninline Animation HighlightAnimation;\n\nvoid Configure();\n\n}  // namespace SystemMenu\n}  // namespace RNE\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/rne/tilebackground.cpp",
    "content": "#include \"../../profile_internal.h\"\n#include \"tilebackground.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace RNE {\n\nImpacto::RNE::TileBackground* ParseTileBackground() {\n  AssertIs(LUA_TTABLE);\n\n  Impacto::RNE::TileBackground* result = new Impacto::RNE::TileBackground();\n\n  result->DurationIn = EnsureGetMember<float>(\"DurationIn\");\n  result->DurationOut = EnsureGetMember<float>(\"DurationOut\");\n\n  result->Seed = EnsureGetMember<uint32_t>(\"Seed\");\n\n  result->Rows = EnsureGetMember<int>(\"Rows\");\n  result->Columns = EnsureGetMember<int>(\"Columns\");\n\n  result->VanishingPointX = EnsureGetMember<float>(\"VanishingPointX\");\n  result->CenterY = EnsureGetMember<float>(\"CenterY\");\n  result->Depth = EnsureGetMember<float>(\"Depth\");\n\n  result->MaxAngle = EnsureGetMember<float>(\"MaxAngle\");\n\n  result->BackgroundSprite = EnsureGetMember<Sprite>(\"Sprite\");\n\n  result->Init();\n\n  return result;\n}\n\n}  // namespace RNE\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/rne/tilebackground.h",
    "content": "#pragma once\n\n#include \"../../../games/rne/tilebackground.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace RNE {\n\nImpacto::RNE::TileBackground* ParseTileBackground();\n}\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/rne/titlemenu.cpp",
    "content": "#include \"titlemenu.h\"\n#include \"../../profile_internal.h\"\n#include \"tilebackground.h\"\n\n#include \"../../ui/titlemenu.h\"\n#include \"../../../game.h\"\n#include \"../../../ui/ui.h\"\n#include \"../../../games/rne/titlemenu.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace RNE {\nnamespace TitleMenu {\n\nvoid Configure() {\n  EnsurePushMemberOfType(\"Background\", LUA_TTABLE);\n  BackgroundAnimation = RNE::ParseTileBackground();\n  Pop();\n\n  LineSprite = EnsureGetMember<Sprite>(\"LineSprite\");\n  CopyrightSprite = EnsureGetMember<Sprite>(\"CopyrightSprite\");\n  EliteSprite = EnsureGetMember<Sprite>(\"EliteSprite\");\n  LogoSprite = EnsureGetMember<Sprite>(\"LogoSprite\");\n\n  LineWidth = EnsureGetMember<float>(\"LineWidth\");\n  CopyrightWidth = EnsureGetMember<float>(\"CopyrightWidth\");\n  EliteHeight = EnsureGetMember<float>(\"EliteHeight\");\n  LogoWidth = EnsureGetMember<float>(\"LogoWidth\");\n  LineX = EnsureGetMember<float>(\"LineX\");\n  LineY = EnsureGetMember<float>(\"LineY\");\n  CopyrightX = EnsureGetMember<float>(\"CopyrightX\");\n  CopyrightY = EnsureGetMember<float>(\"CopyrightY\");\n  EliteX = EnsureGetMember<float>(\"EliteX\");\n  EliteY = EnsureGetMember<float>(\"EliteY\");\n  LogoX = EnsureGetMember<float>(\"LogoX\");\n  LogoY = EnsureGetMember<float>(\"LogoY\");\n\n  PreTitleAnimDurationIn = EnsureGetMember<float>(\"PreTitleAnimDurationIn\");\n  PreTitleAnimDurationOut = EnsureGetMember<float>(\"PreTitleAnimDurationOut\");\n\n  PressToStartAnimation.DurationIn =\n      Profile::TitleMenu::PressToStartAnimDurationIn;\n  PressToStartAnimation.DurationOut =\n      Profile::TitleMenu::PressToStartAnimDurationOut;\n  PressToStartAnimation.LoopMode = AnimationLoopMode::ReverseDirection;\n  PreTitleItemsAnimation.DurationIn = PreTitleAnimDurationIn;\n  PreTitleItemsAnimation.DurationOut = PreTitleAnimDurationOut;\n\n  auto drawType = EnsureGetMember<Game::DrawComponentType>(\"DrawType\");\n\n  UI::TitleMenuPtr = new UI::RNE::TitleMenu();\n  UI::Menus[drawType].push_back(UI::TitleMenuPtr);\n}\n\n}  // namespace TitleMenu\n}  // namespace RNE\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/games/rne/titlemenu.h",
    "content": "#pragma once\n\n#include \"../../../spritesheet.h\"\n#include \"../../../games/rne/titlemenu.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace RNE {\nnamespace TitleMenu {\n\nvoid Configure();\n\ninline Sprite LineSprite;\ninline Sprite CopyrightSprite;\ninline Sprite EliteSprite;\ninline Sprite LogoSprite;\n\ninline float PreTitleAnimDurationIn;\ninline float PreTitleAnimDurationOut;\n\ninline float LineWidth;\ninline float CopyrightWidth;\ninline float EliteHeight;\ninline float LogoWidth;\ninline float LineX;\ninline float LineY;\ninline float CopyrightX;\ninline float CopyrightY;\ninline float EliteX;\ninline float EliteY;\ninline float LogoX;\ninline float LogoY;\n\ninline Animation* BackgroundAnimation = nullptr;\ninline Animation PreTitleItemsAnimation;\ninline Animation PressToStartAnimation;\n\n}  // namespace TitleMenu\n}  // namespace RNE\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/hud/datedisplay.cpp",
    "content": "#include \"datedisplay.h\"\n#include \"../profile_internal.h\"\n#include \"../../log.h\"\n#include \"../../renderer/renderer.h\"\n#include \"../../hud/rne/datedisplay.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace DateDisplay {\n\nusing namespace Impacto::DateDisplay;\n\nDateDisplayType Type = DateDisplayType::None;\n\nvoid Configure() {\n  EnsurePushMemberOfType(\"DateDisplay\", LUA_TTABLE);\n\n  Type = EnsureGetMember<DateDisplayType>(\"Type\");\n\n  if (Type == DateDisplayType::RNE) {\n    Impacto::DateDisplay::Implementation = new Impacto::RNE::DateDisplay;\n  }\n\n  GetMemberArray<Sprite>(std::span(MonthNumSprites, NumSpriteCount),\n                         \"MonthNumSprites\");\n  GetMemberArray<Sprite>(std::span(DayNumSprites, NumSpriteCount),\n                         \"DayNumSprites\");\n  GetMemberArray<Sprite>(std::span(YearNumSprites, NumSpriteCount),\n                         \"YearNumSprites\");\n  GetMemberArray<Sprite>(std::span(WeekSprites, WeekSpriteCount),\n                         \"WeekSprites\");\n\n  MDSeparatorSprite = EnsureGetMember<Sprite>(\"MDSeparatorSprite\");\n  DYSeparatorSprite = EnsureGetMember<Sprite>(\"DYSeparatorSprite\");\n  OpenBracketSprite = EnsureGetMember<Sprite>(\"OpenBracketSprite\");\n  CloseBracketSprite = EnsureGetMember<Sprite>(\"CloseBracketSprite\");\n  BackgroundSprite = EnsureGetMember<Sprite>(\"BackgroundSprite\");\n\n  BackgroundStartPos = EnsureGetMember<glm::vec2>(\"BackgroundStartPos\");\n  BackgroundEndPos = EnsureGetMember<glm::vec2>(\"BackgroundEndPos\");\n  DateStartX = EnsureGetMember<float>(\"DateStartX\");\n  YearWeekY = EnsureGetMember<float>(\"YearWeekY\");\n  MonthDayY = EnsureGetMember<float>(\"MonthDayY\");\n  Spacing = EnsureGetMember<float>(\"Spacing\");\n  FadeInDuration = EnsureGetMember<float>(\"FadeInDuration\");\n  FadeOutDuration = EnsureGetMember<float>(\"FadeOutDuration\");\n\n  Pop();\n}\n\n}  // namespace DateDisplay\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/hud/datedisplay.h",
    "content": "#pragma once\n\n#include \"../sprites.h\"\n#include \"../../hud/datedisplay.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace DateDisplay {\n\nint constexpr NumSpriteCount = 10;\nint constexpr WeekSpriteCount = 7;\n\ninline Sprite MonthNumSprites[NumSpriteCount];\ninline Sprite DayNumSprites[NumSpriteCount];\ninline Sprite YearNumSprites[NumSpriteCount];\ninline Sprite WeekSprites[WeekSpriteCount];\n\ninline Sprite MDSeparatorSprite;\ninline Sprite DYSeparatorSprite;\ninline Sprite OpenBracketSprite;\ninline Sprite CloseBracketSprite;\ninline Sprite BackgroundSprite;\n\ninline glm::vec2 BackgroundStartPos;\ninline glm::vec2 BackgroundEndPos;\ninline float DateStartX;\ninline float YearWeekY;\ninline float MonthDayY;\ninline float Spacing;\ninline float FadeInDuration;\ninline float FadeOutDuration;\n\nvoid Configure();\n\n}  // namespace DateDisplay\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/hud/loadingdisplay.cpp",
    "content": "#include \"loadingdisplay.h\"\n#include \"../profile_internal.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace LoadingDisplay {\n\nvoid Configure() {\n  EnsurePushMemberOfType(\"LoadingDisplay\", LUA_TTABLE);\n\n  ResourceLoadBgAnim =\n      EnsureGetMember<SpriteAnimationDef>(\"ResourceLoadBgAnim\");\n  SaveLoadBgAnim = EnsureGetMember<SpriteAnimationDef>(\"SaveLoadBgAnim\");\n  LoadingIconAnim = EnsureGetMember<SpriteAnimationDef>(\"LoadingIconAnim\");\n  LoadingTextAnim = EnsureGetMember<SpriteAnimationDef>(\"LoadingTextAnim\");\n  ResourceBgPos = EnsureGetMember<glm::vec2>(\"ResourceBgPos\");\n  SaveBgPos = EnsureGetMember<glm::vec2>(\"SaveBgPos\");\n  IconPos = EnsureGetMember<glm::vec2>(\"IconPos\");\n  TextPos = EnsureGetMember<glm::vec2>(\"TextPos\");\n  FadeInDuration = EnsureGetMember<float>(\"FadeInDuration\");\n  FadeOutDuration = EnsureGetMember<float>(\"FadeOutDuration\");\n\n  Pop();\n}\n\n}  // namespace LoadingDisplay\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/hud/loadingdisplay.h",
    "content": "#pragma once\n\n#include \"../../spriteanimation.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace LoadingDisplay {\n\ninline SpriteAnimationDef ResourceLoadBgAnim;\ninline SpriteAnimationDef SaveLoadBgAnim;\ninline SpriteAnimationDef LoadingIconAnim;\ninline SpriteAnimationDef LoadingTextAnim;\n\ninline glm::vec2 ResourceBgPos;\ninline glm::vec2 SaveBgPos;\ninline glm::vec2 IconPos;\ninline glm::vec2 TextPos;\n\ninline float FadeInDuration;\ninline float FadeOutDuration;\n\nvoid Configure();\n\n}  // namespace LoadingDisplay\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/hud/saveicon.cpp",
    "content": "#include \"saveicon.h\"\n#include \"../profile_internal.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace SaveIcon {\n\nvoid Configure() {\n  EnsurePushMemberOfType(\"SaveIcon\", LUA_TTABLE);\n\n  SaveIconCurrentType = TryGetMember<SaveIconType>(\"SaveIconCurrentType\")\n                            .value_or(SaveIconType::Default);\n  SaveIconMenuOverlay =\n      TryGetMember<bool>(\"SaveIconMenuOverlay\").value_or(true);\n  DefaultPosition = EnsureGetMember<glm::vec2>(\"DefaultPosition\");\n  FadeInDuration = EnsureGetMember<float>(\"FadeInDuration\");\n  FadeOutDuration = EnsureGetMember<float>(\"FadeOutDuration\");\n\n  switch (SaveIconCurrentType) {\n    default:\n    case SaveIconType::Default: {\n      ForegroundAnimation =\n          EnsureGetMember<SpriteAnimationDef>(\"ForegroundAnimation\");\n      BackgroundSprite = EnsureGetMember<Sprite>(\"BackgroundSprite\");\n      BackgroundOffset = EnsureGetMember<glm::vec2>(\"BackgroundOffset\");\n      BackgroundMaxAlpha = EnsureGetMember<float>(\"BackgroundMaxAlpha\");\n      FullyVisibleSpriteIndex =\n          TryGetMember<uint8_t>(\"FullyVisibleSpriteIndex\").value_or(0);\n      break;\n    }\n    case SaveIconType::CHLCC: {\n      SaveIconSprites = EnsureGetMember<std::vector<Sprite>>(\"SaveIconSprites\");\n      if (SaveIconSprites.size() != CHLCC_SAVE_ICON_SPRITES) {\n        throw std::runtime_error(\"Wrong number of sprites for CHLCC SaveIcon\");\n      }\n      ActiveAnimationDuration =\n          EnsureGetMember<float>(\"ActiveAnimationDuration\");\n      break;\n    }\n  }\n\n  Pop();\n}\n\n}  // namespace SaveIcon\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/hud/saveicon.h",
    "content": "#pragma once\n\n#include \"../../spriteanimation.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace SaveIcon {\n\nconstexpr size_t CHLCC_SAVE_ICON_SPRITES = 3;\n\nenum class SaveIconType : int {\n  Default,\n  CHLCC,\n};\ninline SaveIconType SaveIconCurrentType = SaveIconType::Default;\n\ninline SpriteAnimationDef ForegroundAnimation;\ninline glm::vec2 DefaultPosition;\ninline Sprite BackgroundSprite;\ninline glm::vec2 BackgroundOffset;\ninline float BackgroundMaxAlpha;\ninline uint8_t FullyVisibleSpriteIndex;\ninline float FadeInDuration;\ninline float FadeOutDuration;\ninline bool SaveIconMenuOverlay = true;\n\ninline std::vector<Sprite> SaveIconSprites;\ninline float ActiveAnimationDuration;\n\nvoid Configure();\n\n}  // namespace SaveIcon\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/hud/tipsnotification.cpp",
    "content": "#include \"tipsnotification.h\"\n#include \"../profile_internal.h\"\n#include \"../games/mo6tw/tipsnotification.h\"\n#include \"../games/cclcc/tipsnotification.h\"\n#include \"../games/chlcc/tipsnotification.h\"\n#include \"../../hud/mo6tw/tipsnotification.h\"\n#include \"../../hud/cclcc/tipsnotification.h\"\n#include \"../../hud/chlcc/tipsnotification.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace TipsNotification {\n\nusing namespace Impacto::TipsNotification;\n\nTipsNotificationType Type = TipsNotificationType::None;\n\nvoid Configure() {\n  EnsurePushMemberOfType(\"TipsNotification\", LUA_TTABLE);\n\n  Type = EnsureGetMember<TipsNotificationType>(\"Type\");\n\n  switch (Type) {\n    case TipsNotificationType::MO6TW:\n      MO6TW::TipsNotification::Configure();\n      break;\n    case TipsNotificationType::CCLCC:\n      CCLCC::TipsNotification::Configure();\n      break;\n    case TipsNotificationType::CHLCC:\n      CHLCC::TipsNotification::Configure();\n      break;\n    default:\n      Pop();\n      return;\n  }\n\n  TextTableId = EnsureGetMember<int>(\"TextTableId\");\n  NotificationTextPart1MessageId =\n      EnsureGetMember<int>(\"NotificationTextPart1MessageId\");\n  NotificationTextPart2MessageId =\n      EnsureGetMember<int>(\"NotificationTextPart2MessageId\");\n\n  FadeInDuration = EnsureGetMember<float>(\"FadeInDuration\");\n  FadeOutDuration = EnsureGetMember<float>(\"FadeOutDuration\");\n\n  Pop();\n}\n\nvoid CreateInstance() {\n  switch (Type) {\n    case TipsNotificationType::MO6TW:\n      Impacto::TipsNotification::Implementation =\n          std::make_unique<Impacto::MO6TW::TipsNotification>();\n      break;\n    case TipsNotificationType::CCLCC:\n      Impacto::TipsNotification::Implementation =\n          std::make_unique<Impacto::CCLCC::TipsNotification>();\n      break;\n    case TipsNotificationType::CHLCC:\n      Impacto::TipsNotification::Implementation =\n          std::make_unique<Impacto::CHLCC::TipsNotification>();\n      break;\n    default:\n      return;\n  }\n}\n\n}  // namespace TipsNotification\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/hud/tipsnotification.h",
    "content": "#pragma once\n\n#include \"../sprites.h\"\n#include \"../../hud/tipsnotification.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace TipsNotification {\n\ninline int TextTableId;\ninline int NotificationTextPart1MessageId;\ninline int NotificationTextPart2MessageId;\n\ninline float FadeInDuration;\ninline float FadeOutDuration;\n\nvoid Configure();\n\nvoid CreateInstance();\n\n}  // namespace TipsNotification\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/profile.cpp",
    "content": "#include \"profile.h\"\n#include \"profile_internal.h\"\n#include \"../io/physicalfilestream.h\"\n#include \"../log.h\"\n#include <ankerl/unordered_dense.h>\n\n#include \"ui/backlogmenu.h\"\n#include \"dialogue.h\"\n#include \"configsystem.h\"\n#include \"subtitle.h\"\n#include \"game.h\"\n#include \"../text/text.h\"\n#include \"../game.h\"\n#include \"../ui/ui.h\"\n#include \"../data/savesystem.h\"\n#include \"../data/achievementsystem.h\"\n#include \"../data/tipssystem.h\"\n#include \"../hud/datedisplay.h\"\n#include \"../hud/waiticondisplay.h\"\n#include \"../hud/autoicondisplay.h\"\n#include \"../hud/skipicondisplay.h\"\n#include \"../hud/tipsnotification.h\"\n#include \"../profile/hud/saveicon.h\"\n#include \"../inputsystem.h\"\n\nnamespace Impacto {\nnamespace Profile {\n\nstatic ankerl::unordered_dense::set<std::string> IncludedFiles;\n\nstatic int LuaPrint(lua_State* ctx) {\n  ImpLog(LogLevel::Info, LogChannel::Profile, \"Lua: {:s}\\n\",\n         lua_tostring(ctx, 1));\n  return 0;\n}\n\nstatic int LuaInclude(lua_State* ctx) {\n  auto fileName = lua_tostring(ctx, 1);\n  std::string file = \"profiles/\" + std::string(fileName);\n  if (IncludedFiles.find(file) != IncludedFiles.end()) {\n    ImpLog(LogLevel::Debug, LogChannel::Profile,\n           \"File {:s} already included, skipping...\\n\", file);\n    return 0;\n  }\n\n  IncludedFiles.insert(file);\n  ImpLog(LogLevel::Debug, LogChannel::Profile, \"Including {:s}\\n\", file);\n\n  Io::Stream* stream;\n  IoError err = Io::PhysicalFileStream::Create(file, &stream);\n  if (err != IoError_OK) {\n    ImpLog(LogLevel::Error, LogChannel::Profile, \"Could not open {:s}\\n\", file);\n    return 0;\n  }\n\n  char const prefix[] = \"do\\n\";\n  char const suffix[] = \"\\nend\";\n\n  size_t script_len = strlen(prefix) + stream->Meta.Size + strlen(suffix) + 1;\n  char* script = (char*)malloc(script_len);\n\n  if (script == NULL) {\n    delete stream;\n\n    ImpLog(LogLevel::Error, LogChannel::Profile,\n           \"Could not allocate memory for script: {:s}\", file);\n    return 0;\n  }\n\n  script[script_len - 1] = '\\0';\n  memcpy(script, prefix, strlen(prefix));\n\n  int64_t len = stream->Read(script + strlen(prefix), stream->Meta.Size);\n\n  if (len < 0) {\n    free(script);\n    delete stream;\n\n    ImpLog(LogLevel::Error, LogChannel::Profile, \"Could not open {:s}\\n\", file);\n    return 0;\n  }\n\n  len += strlen(prefix);\n  memcpy(script + len, suffix, strlen(suffix));\n  len += strlen(suffix);\n\n  if (luaL_loadbuffer(LuaState, script, len, script)) {\n    ImpLog(LogLevel::Fatal, LogChannel::Profile,\n           \"Lua profile compile error: {:s}\\n\", lua_tostring(ctx, -1));\n    lua_close(LuaState);\n    exit(1);\n  }\n  if (lua_pcall(ctx, 0, 0, 0)) {\n    ImpLog(LogLevel::Fatal, LogChannel::Profile,\n           \"Lua profile execute error: {:s}\\n\", lua_tostring(ctx, -1));\n    lua_close(LuaState);\n    exit(1);\n  }\n\n  free(script);\n  delete stream;\n\n  lua_pop(ctx, -1);\n\n  return 0;\n}\n\ntemplate <typename Enum>\nstatic void DefineEnum(lua_State* ctx) {\n  lua_createtable(ctx, 0, 0);\n  const auto enumTypeName = std::string(magic_enum::enum_type_name<Enum>());\n  for (auto&& [value, name] : magic_enum::enum_entries<Enum>()) {\n    const auto fieldName = std::string(name);\n    if constexpr (std::is_floating_point_v<std::underlying_type_t<Enum>>) {\n      lua_pushnumber(ctx, to_underlying(value));\n    } else {\n      lua_pushinteger(ctx, to_underlying(value));\n    }\n    lua_setfield(ctx, -2, fieldName.c_str());\n  }\n  lua_setglobal(ctx, enumTypeName.c_str());\n}\n\nvoid MakeLuaProfile(std::string const& name) {\n  Io::Stream* stream;\n  IoError err =\n      Io::PhysicalFileStream::Create(\"profiles/\" + name + \"/game.lua\", &stream);\n  if (err != IoError_OK) {\n    ImpLog(LogLevel::Fatal, LogChannel::Profile,\n           \"Could not open profiles/{:s}/game.lua\\n\", name);\n    exit(1);\n  }\n\n  char* script = (char*)malloc(stream->Meta.Size + 1);\n  if (script == NULL) {\n    delete stream;\n\n    ImpLog(LogLevel::Error, LogChannel::Profile,\n           \"Could not allocate memory for script: profiles/{:s}/game.lua\",\n           name);\n    exit(1);\n  }\n\n  int64_t len = stream->Read(script, stream->Meta.Size);\n  if (len < 0) {\n    delete stream;\n    free(script);\n\n    ImpLog(LogLevel::Fatal, LogChannel::Profile,\n           \"Could not open profiles/{:s}/game.lua\\n\", name);\n    exit(1);\n  }\n  script[len] = '\\0';\n\n  LuaState = luaL_newstate();\n  // Set up API\n  luaL_openlibs(LuaState);\n  lua_pushcfunction(LuaState, LuaPrint);\n  lua_setglobal(LuaState, \"print\");\n  lua_pushcfunction(LuaState, LuaInclude);\n  lua_setglobal(LuaState, \"include\");\n\n  // Root profile object\n  lua_createtable(LuaState, 0, 0);\n  lua_setglobal(LuaState, \"root\");\n\n  // Enums /sigh\n  DefineEnum<RendererType>(LuaState);\n  DefineEnum<VideoPlayerType>(LuaState);\n  DefineEnum<AudioBackendType>(LuaState);\n  DefineEnum<TextAlignment>(LuaState);\n  DefineEnum<GameFeature>(LuaState);\n  DefineEnum<CharacterTypeFlags>(LuaState);\n  DefineEnum<Vm::InstructionSet>(LuaState);\n  DefineEnum<Game::DrawComponentType>(LuaState);\n  DefineEnum<SaveSystem::SaveDataType>(LuaState);\n  DefineEnum<AchievementSystem::AchievementDataType>(LuaState);\n  DefineEnum<TipsSystem::TipsSystemType>(LuaState);\n  DefineEnum<UI::CommonMenuType>(LuaState);\n  DefineEnum<UI::SystemMenuType>(LuaState);\n  DefineEnum<UI::TitleMenuType>(LuaState);\n  DefineEnum<UI::SaveMenuType>(LuaState);\n  DefineEnum<UI::OptionsMenuType>(LuaState);\n  DefineEnum<UI::TrophyMenuType>(LuaState);\n  DefineEnum<UI::HelpMenuType>(LuaState);\n  DefineEnum<BacklogMenu::BacklogMenuType>(LuaState);\n  DefineEnum<BacklogMenu::EntryHighlightLocationType>(LuaState);\n  DefineEnum<UI::TipsMenuType>(LuaState);\n  DefineEnum<UI::LibraryMenuType>(LuaState);\n  DefineEnum<UI::ClearListMenuType>(LuaState);\n  DefineEnum<UI::AlbumMenuType>(LuaState);\n  DefineEnum<UI::MusicMenuType>(LuaState);\n  DefineEnum<UI::MovieMenuType>(LuaState);\n  DefineEnum<UI::ActorsVoiceMenuType>(LuaState);\n  DefineEnum<DateDisplay::DateDisplayType>(LuaState);\n  DefineEnum<WaitIconDisplay::WaitIconType>(LuaState);\n  DefineEnum<AutoIconDisplay::AutoIconType>(LuaState);\n  DefineEnum<SkipIconDisplay::SkipIconType>(LuaState);\n  DefineEnum<SaveIcon::SaveIconType>(LuaState);\n  DefineEnum<Dialogue::NametagType>(LuaState);\n  DefineEnum<TipsNotification::TipsNotificationType>(LuaState);\n  DefineEnum<DialogueBoxType>(LuaState);\n  DefineEnum<UI::SysMesBoxType>(LuaState);\n  DefineEnum<FontType>(LuaState);\n  DefineEnum<LKMVersion>(LuaState);\n  DefineEnum<Dialogue::REVNameLocationType>(LuaState);\n  DefineEnum<ShaderProgramType>(LuaState);\n  DefineEnum<ConfigSystem::AutoQuickSaveType>(LuaState);\n  DefineEnum<UI::GameSpecificType>(LuaState);\n  DefineEnum<DateFormatType>(LuaState);\n  DefineEnum<SubtitleAssBackendType>(LuaState);\n  DefineEnum<SubtitleBmpBackendType>(LuaState);\n  DefineEnum<SubtitleTextBackendType>(LuaState);\n  DefineEnum<Subtitle::SubtitleType>(LuaState);\n  DefineEnum<SubtitleConfigType>(LuaState);\n  DefineEnum<Input::KeyboardScanCode>(LuaState);\n  DefineEnum<Input::ControllerButton>(LuaState);\n  DefineEnum<Input::ControllerAxis>(LuaState);\n\n  ImpLog(LogLevel::Info, LogChannel::Profile, \"Starting profile {:s}\\n\", name);\n\n  if (luaL_loadbuffer(LuaState, script, len, name.c_str())) {\n    ImpLog(LogLevel::Fatal, LogChannel::Profile,\n           \"Lua profile compile error: {:s}\\n\", lua_tostring(LuaState, -1));\n    lua_close(LuaState);\n    exit(1);\n  }\n  if (lua_pcall(LuaState, 0, 0, 0)) {\n    ImpLog(LogLevel::Fatal, LogChannel::Profile,\n           \"Lua profile execute error: {:s}\\n\", lua_tostring(LuaState, -1));\n    lua_close(LuaState);\n    exit(1);\n  }\n\n  delete stream;\n  free(script);\n\n  // Push the global onto the stack to load all the values later\n  lua_getglobal(LuaState, \"root\");\n\n  ImpLog(LogLevel::Info, LogChannel::Profile, \"Lua profile execute success\\n\");\n}\n\nvoid ClearProfile() { ClearProfileInternal(); }\n\n}  // namespace Profile\n}  // namespace Impacto\n"
  },
  {
    "path": "src/profile/profile.h",
    "content": "#pragma once\n\n#include <string>\n\nnamespace Impacto {\nnamespace Profile {\n\nvoid MakeLuaProfile(std::string const& name);\nvoid ClearProfile();\n\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/profile_internal.cpp",
    "content": "#include \"profile_internal.h\"\n#include \"../log.h\"\n#include \"../renderer/renderer.h\"\n\n#include \"sprites.h\"\n#include \"fonts.h\"\n#include \"animations.h\"\n\nnamespace Impacto {\nnamespace Profile {\n\nvoid LuaDumpStack() {\n  std::string callStack;\n  int top = lua_gettop(LuaState);\n  for (int i = 1; i <= top; i++) {\n    fmt::format_to(std::back_inserter(callStack), \"{:d}\\t{}\\t\", i,\n                   luaL_typename(LuaState, i));\n    switch (lua_type(LuaState, i)) {\n      case LUA_TNUMBER:\n        fmt::format_to(std::back_inserter(callStack), \"{:g}\",\n                       lua_tonumber(LuaState, i));\n        break;\n      case LUA_TSTRING:\n        fmt::format_to(std::back_inserter(callStack), \"{:s}\",\n                       lua_tostring(LuaState, i));\n        break;\n      case LUA_TBOOLEAN:\n        fmt::format_to(std::back_inserter(callStack), \"{}\",\n                       lua_toboolean(LuaState, i) ? \"true\" : \"false\");\n        break;\n      case LUA_TNIL:\n        fmt::format_to(std::back_inserter(callStack), \"nil\");\n        break;\n      default:\n        fmt::format_to(std::back_inserter(callStack), \"{}\",\n                       lua_topointer(LuaState, i));\n        break;\n    }\n  }\n  ImpLog(LogLevel::Debug, LogChannel::Profile, \"Current stack: \\n{}\",\n         callStack);\n}\n\nvoid Pop() { lua_pop(LuaState, 1); }\n\nbool TryPushMember(char const* name) {\n  if (lua_getfield(LuaState, -1, name) == LUA_TNIL) {\n    Pop();\n    return false;\n  }\n  return true;\n}\n\nvoid EnsurePushMember(char const* name) {\n  bool success = TryPushMember(name);\n  if (!success) {\n    std::string error = fmt::format(\"Expected member {:s}\", name);\n    ImpLog(LogLevel::Fatal, LogChannel::Profile, \"{:s}\\n\", error);\n    throw std::runtime_error(error);\n  }\n}\n\nvoid EnsurePushMemberOfType(char const* name, int type) {\n  EnsurePushMember(name);\n  AssertIs(type);\n}\n\nvoid AssertIs(int type) {\n  int actualType = lua_type(LuaState, -1);\n  if (actualType != type) {\n    std::string error =\n        fmt::format(\"Unexpected type {}, expected {}\", actualType, type);\n    ImpLog(LogLevel::Fatal, LogChannel::Profile, \"{:s}\\n\", error);\n    throw std::runtime_error(error);\n  }\n}\n\nvoid PushInitialIndex() { lua_pushnil(LuaState); }\n\nint PushNextTableElement() { return lua_next(LuaState, -2); }\n\nstd::optional<Io::AssetPath> TryGetImpl<Io::AssetPath>::Call() {\n  if (lua_isstring(LuaState, -1)) {\n    return Io::AssetPath{.Mount = \"\", .FileName = lua_tostring(LuaState, -1)};\n  }\n  if (!lua_istable(LuaState, -1)) return std::nullopt;\n  auto id = TryGetMember<uint32_t>(\"Id\");\n  auto mount = TryGetMember<std::string>(\"Mount\");\n  if (!id || !mount) return std::nullopt;\n  return Io::AssetPath{.Mount = *mount, .Id = *id};\n}\n\nstd::optional<RectF> TryGetImpl<RectF>::Call() {\n  if (!lua_istable(LuaState, -1)) return std::nullopt;\n  RectF outRectF;\n  if (TryGetMember(\"X\", outRectF.X) && TryGetMember(\"Y\", outRectF.Y) &&\n      TryGetMember(\"Width\", outRectF.Width) &&\n      TryGetMember(\"Height\", outRectF.Height)) {\n    return outRectF;\n  }\n  return std::nullopt;\n}\n\nstd::optional<DialogueColorPair> TryGetImpl<DialogueColorPair>::Call() {\n  if (!lua_istable(LuaState, -1)) return std::nullopt;\n  DialogueColorPair outColor;\n  if (TryGetMember(\"TextColor\", outColor.TextColor) &&\n      TryGetMember(\"OutlineColor\", outColor.OutlineColor)) {\n    return outColor;\n  }\n  return std::nullopt;\n}\n\nstd::optional<bool> TryGetImpl<bool>::Call() {\n  if (lua_isboolean(LuaState, -1)) {\n    return lua_toboolean(LuaState, -1);\n  }\n  // TODO conversion?\n\n  return std::nullopt;\n}\n\nvoid ClearProfileInternal() {\n  lua_close(LuaState);\n  LuaState = nullptr;\n}\n\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/profile_internal.h",
    "content": "#pragma once\n\n#include <minilua/minilua.h>\n#include <initializer_list>\n#include <optional>\n#include <charconv>\n#include <system_error>\n#include <glm/glm.hpp>\n#include \"../io/assetpath.h\"\n#include \"../util.h\"\n#include \"../log.h\"\n#include \"../game.h\"\n\n#include \"animations.h\"\n#include \"sprites.h\"\n#include \"fonts.h\"\n#include \"../spritesheet.h\"\n#include \"../font.h\"\n#include \"../spriteanimation.h\"\n\nnamespace Impacto {\nnamespace Profile {\n\ninline lua_State* LuaState;\n\nvoid LuaDumpStack();\nvoid Pop();\nbool TryPushMember(char const* name);\nvoid EnsurePushMember(char const* name);\nvoid EnsurePushMemberOfType(char const* name, int type);\nvoid AssertIs(int type);\nvoid PushInitialIndex();\nint PushNextTableElement();\nvoid ClearProfileInternal();\n\ntemplate <typename T>\nstruct TryGetImpl {\n  static std::optional<T> Call() {\n    static_assert(sizeof(T) == 0, \"TryGet not implemented for this type\");\n  }\n};\n\ntemplate <typename T>\nstd::optional<T> TryGet() {\n  return TryGetImpl<T>::Call();\n}\n\ntemplate <typename T>\nbool TryGet(T& out);\n\ntemplate <typename T>\nT EnsureGet();\n\ntemplate <typename T>\nT EnsureGetKey();\n\ntemplate <typename T>\n  requires requires(T x) {\n    { std::variant{x} } -> std::same_as<T>;\n  }\nT EnsureGetKey();\n\ntemplate <typename T>\nbool TryGetMember(char const* name, T& out);\n\ntemplate <typename T>\nstd::optional<T> TryGetMember(char const* name);\n\ntemplate <typename T>\nT EnsureGetMember(char const* name);\n\ntemplate <typename T>\nT EnsureGetArrayElement();\ntemplate <typename T>\nT EnsureGetArrayElementByIndex(uint32_t index);\n\ntemplate <typename T>\nvoid GetArray(std::span<T> out);\n\ntemplate <typename T>\nvoid GetMemberArray(std::span<T> out, char const* name);\n\nvoid ForEachProfileArray(std::invocable<uint32_t> auto func);\n\ntemplate <typename K>\nvoid ForEachProfileTable(std::invocable<K const&> auto func);\n\ntemplate <typename T>\n  requires is_any_of_v<std::decay_t<T>, char const*, std::string,\n                       std::string_view>\nstd::optional<T> TryGet();\n\ntemplate <typename T>\n  requires std::is_arithmetic_v<T> && (!is_any_of_v<T, bool, long double>)\nstd::optional<T> TryGet();\n\ntemplate <typename T>\nconcept HasEntityType =\n    is_any_of_v<T, Sprite, SpriteSheet, Font*, SpriteAnimationDef>;\n\ntemplate <typename T>\n  requires HasEntityType<T>\nstd::optional<T> TryGet();\n\n// generic glm::vec getter\ntemplate <typename T>\n  requires requires(T x) {\n    { glm::vec{x} } -> std::same_as<T>;\n  }\nstd::optional<T> TryGet();\n\n// generic map getter\ntemplate <typename T>\n  requires is_any_of_v<\n      typename T::value_type,\n      std::pair<const typename T::key_type, typename T::mapped_type>,\n      std::pair<typename T::key_type, typename T::mapped_type>>\nstd::optional<T> TryGet();\n\ntemplate <typename T>\n  requires std::is_same_v<\n      T, std::vector<typename T::value_type, typename T::allocator_type>>\nstd::optional<T> TryGet();\n\ntemplate <typename T>\n  requires is_std_array<T>::value\nstd::optional<T> TryGet();\n\ntemplate <typename T>\n  requires requires(T x) {\n    { std::tuple{x} } -> std::same_as<T>;\n  }\nstd::optional<T> TryGet();\n\ntemplate <typename T>\n  requires requires(T x) {\n    { std::pair{x} } -> std::same_as<T>;\n  }\nstd::optional<T> TryGet();\n\ntemplate <typename T>\n  requires requires(T x) {\n    { std::variant{x} } -> std::same_as<T>;\n  }\nstd::optional<T> TryGet();\n\ntemplate <typename E>\n  requires std::is_enum_v<E>\nstd::optional<E> TryGet();\n\n// Definitions:\ntemplate <typename T>\ninline T EnsureGet() {\n  std::optional<T> result = TryGet<T>();\n\n  if (!result.has_value()) {\n    ImpLog(Impacto::LogLevel::Fatal, Impacto::LogChannel::Profile,\n           \"Unexpected type\\n\");\n    Game::Shutdown();\n  }\n\n  return result.value();\n}\n\ntemplate <typename T>\ninline T EnsureGetKey() {\n  if constexpr (is_any_of_v<T, char const*, std::string_view, std::string>)\n    return lua_tostring(LuaState, -2);\n  else if constexpr (std::is_integral_v<T> && !std::is_same_v<T, bool>)\n    return (T)lua_tointeger(LuaState, -2);\n  else if constexpr (requires(T x) {\n                       {\n                         std::variant { x }\n                       } -> std::same_as<T>;\n                     }) {\n    lua_rotate(LuaState, -2, 1);\n    T result = EnsureGet<T>();\n    lua_rotate(LuaState, -2, -1);\n    return result;\n\n  } else\n    static_assert(sizeof(T*) == 0, \"Invalid Key Type\");\n}\n\ntemplate <typename T>\n  requires requires(T x) {\n    { std::variant{x} } -> std::same_as<T>;\n  }\ninline T EnsureGetKey() {\n  static constexpr auto isKeyType = []<typename K>() {\n    return is_any_of_v<K, char const*, std::string_view, std::string> ||\n           (std::is_integral_v<K> && !std::is_same_v<K, bool>);\n  };\n  static constexpr auto variantTypeChecker = []<std::size_t... Is>(\n                                                 std::index_sequence<Is...>) {\n    return (\n        isKeyType.template operator()<std::variant_alternative_t<Is, T>>() ||\n        ...);\n  };\n\n  if constexpr (variantTypeChecker.template operator()(\n                    std::make_index_sequence<std::variant_size_v<T>>{})) {\n    lua_rotate(LuaState, -2, 1);\n    T result = EnsureGet<T>();\n    lua_rotate(LuaState, -2, -1);\n    return result;\n  } else {\n    static_assert(sizeof(T*) == 0, \"Invalid Key Type\");\n  }\n}\n\ntemplate <typename T>\ninline bool TryGetMember(char const* name, T& out) {\n  if (!TryPushMember(name)) return false;\n  bool result = TryGet<T>(out);\n  Pop();\n  return result;\n}\n\ntemplate <typename T>\ninline std::optional<T> TryGetMember(char const* name) {\n  if (!TryPushMember(name)) return std::nullopt;\n  auto result = TryGet<T>();\n  Pop();\n  return result;\n}\n\ntemplate <typename T>\ninline T EnsureGetMember(char const* name) {\n  EnsurePushMember(name);\n  T result = EnsureGet<T>();\n  Pop();\n  return result;\n}\n\ntemplate <typename T>\ninline T EnsureGetArrayElement() {\n  return EnsureGet<T>();\n}\n\ntemplate <typename T>\ninline T EnsureGetArrayElementByIndex(uint32_t index) {\n  lua_rawgeti(LuaState, -1, index + 1);\n  T result = EnsureGet<T>();\n  Pop();\n  return result;\n}\n\ntemplate <typename T>\ninline bool TryGet(T& out) {\n  std::optional<T> opt = TryGet<T>();\n  if (opt) {\n    out = std::move(*opt);\n    return true;\n  }\n  return false;\n}\n\ntemplate <typename T>\n  requires is_any_of_v<std::decay_t<T>, char const*, std::string,\n                       std::string_view>\ninline std::optional<T> TryGet() {\n  if (!lua_isstring(LuaState, -1)) return std::nullopt;\n  return lua_tostring(LuaState, -1);\n}\n\ntemplate <typename T>\n  requires std::is_arithmetic_v<T> && (!is_any_of_v<T, bool, long double>)\ninline std::optional<T> TryGet() {\n  if (lua_isnumber(LuaState, -1)) {\n    if constexpr (std::is_integral_v<T>) {\n      return static_cast<T>(lua_tointeger(LuaState, -1));\n    } else {\n      return static_cast<T>(lua_tonumber(LuaState, -1));\n    }\n  }\n  if (lua_isstring(LuaState, -1)) {\n    std::string_view inputStr = {lua_tostring(LuaState, -1)};\n    T outNumber{};\n    if constexpr (std::is_integral_v<T>) {\n      auto [ptr, ec] = std::from_chars(\n          inputStr.data(), inputStr.data() + inputStr.size(), outNumber);\n      if (ec == std::errc{}) {\n        return outNumber;\n      }\n      ImpLog(LogLevel::Warning, LogChannel::Profile,\n             \"Error encountered converting {:s} to number: {:s}\\n\", inputStr,\n             std::make_error_code(ec).message());\n    } else {\n      char* endPtr = nullptr;\n      if constexpr (std::is_same_v<T, double>) {\n        outNumber = std::strtod(inputStr.data(), &endPtr);\n      } else if constexpr (std::is_same_v<T, float>) {\n        outNumber = std::strtof(inputStr.data(), &endPtr);\n      }\n      if (endPtr != inputStr.data()) {\n        return outNumber;\n      }\n      ImpLog(LogLevel::Warning, LogChannel::Profile,\n             \"Error encountered converting {:s} to number: {:s}\\n\", inputStr,\n             std::error_code{errno, std::generic_category()}.message());\n    }\n  }\n  return std::nullopt;\n}\n\ntemplate <typename T>\n  requires HasEntityType<T>\ninline const auto& GetEntityMap() {\n  using decayedT = std::decay_t<T>;\n  using namespace std::literals::string_view_literals;\n  if constexpr (std::is_same_v<decayedT, Sprite>) {\n    return Sprites;\n  } else if constexpr (std::is_same_v<decayedT, SpriteSheet>) {\n    return SpriteSheets;\n  } else if constexpr (std::is_same_v<decayedT, Font*>) {\n    return Fonts;\n  } else if constexpr (std::is_same_v<decayedT, SpriteAnimationDef>) {\n    return Animations;\n  } else {\n    static_assert(sizeof(T*) == 0, \"Not a valid Entity type\");\n  }\n}\n\ntemplate <typename T>\n  requires HasEntityType<T>\ninline std::optional<T> TryGet() {\n  auto const& map = GetEntityMap<T>();\n  std::optional<std::string_view> name = TryGet<std::string_view>();\n  if (!name) return std::nullopt;\n  const auto& ref = map.find(*name);\n  if (ref == map.end()) return std::nullopt;\n\n  return ref->second;\n}\n\n// generic glm::vec getter\ntemplate <typename T>\n  requires requires(T x) {\n    { glm::vec{x} } -> std::same_as<T>;\n  }\ninline std::optional<T> TryGet() {\n  if (!lua_istable(LuaState, -1)) return std::nullopt;\n  using value_type = typename T::value_type;\n  std::array<std::optional<value_type>, T::length()> out;\n  out[0] = TryGetMember<value_type>(\"X\");\n  if constexpr (T::length() > 1) out[1] = TryGetMember<value_type>(\"Y\");\n  if constexpr (T::length() > 2) out[2] = TryGetMember<value_type>(\"Z\");\n  if constexpr (T::length() > 3) out[3] = TryGetMember<value_type>(\"W\");\n  static_assert(T::length() <= 4, \"Invalid glm::vec dimensions, >4\");\n  if (!std::ranges::all_of(out, [](auto& v) { return v.has_value(); }))\n    return std::nullopt;\n\n  const auto makeOptVec = [&]<std::size_t... I>(std::index_sequence<I...>) {\n    return std::optional<T>{T{*out[I]...}};\n  };\n\n  return makeOptVec(std::make_index_sequence<T::length()>{});\n}\n\ntemplate <typename T>\n  requires requires(T x) {\n    { std::pair{x} } -> std::same_as<T>;\n  }\nstd::optional<T> TryGet() {\n  using TupleType = std::tuple<typename T::first_type, typename T::second_type>;\n  const auto toPair = [](std::optional<TupleType>&& opt) -> std::optional<T> {\n    if (!opt) return std::nullopt;\n    return std::optional<T>{std::in_place, std::move(std::get<0>(*opt)),\n                            std::move(std::get<1>(*opt))};\n  };\n  return toPair(TryGet<TupleType>());\n}\n\n// generic tuple getter\ntemplate <typename T>\n  requires requires(T x) {\n    { std::tuple{x} } -> std::same_as<T>;\n  }\ninline std::optional<T> TryGet() {\n  if (!lua_istable(LuaState, -1)) return std::nullopt;\n  constexpr static auto tupleSize = std::tuple_size_v<T>;\n\n  AssertIs(LUA_TTABLE);\n  PushInitialIndex();\n\n  const auto errorHandler = [&](std::string error) {\n    ImpLog(LogLevel::Fatal, LogChannel::Profile, \"{:s}\\n\", error);\n    throw std::runtime_error(error);\n  };\n\n  T out;\n  size_t curIndex = 0;\n  const auto tupleElemHandler = [&]<std::size_t I> {\n    using TupleType = std::tuple_element_t<I, T>;\n    if (!PushNextTableElement()) return false;\n\n    auto i = EnsureGetKey<uint32_t>() - 1;\n    if (curIndex++ != i)\n      errorHandler(fmt::format(\"Unexpected key {}, expected {}\", i, curIndex));\n    auto elemOpt = TryGet<TupleType>();\n    if (!elemOpt) {\n      errorHandler(fmt::format(\"Failed to get {}th element in tuple\", i));\n    }\n    std::get<I>(out) = *elemOpt;\n\n    Pop();\n    return true;\n  };\n\n  const auto tupleFiller = [&]<std::size_t... Is>(std::index_sequence<Is...>) {\n    if (!(tupleElemHandler.template operator()<Is>() && ...)) {\n      errorHandler(fmt::format(\"Tuple size too small, expected {}, got {}\",\n                               tupleSize, curIndex));\n    } else if (PushNextTableElement()) {\n      Pop();\n      errorHandler(fmt::format(\n          \"Tuple size too large for profile array, expected {}\", tupleSize));\n    }\n  };\n\n  tupleFiller(std::make_index_sequence<tupleSize>{});\n  return out;\n}\n\n// generic map getter\ntemplate <typename T>\n  requires is_any_of_v<\n      typename T::value_type,\n      std::pair<const typename T::key_type, typename T::mapped_type>,\n      std::pair<typename T::key_type, typename T::mapped_type>>\ninline std::optional<T> TryGet() {\n  if (!lua_istable(LuaState, -1)) return std::nullopt;\n\n  T result;\n  ForEachProfileTable<typename T::key_type>(\n      [&](typename T::key_type const& key) {\n        result.try_emplace(key, EnsureGet<typename T::mapped_type>());\n      });\n  return result;\n}\n\ntemplate <typename T>\n  requires std::is_same_v<\n      T, std::vector<typename T::value_type, typename T::allocator_type>>\ninline std::optional<T> TryGet() {\n  if (!lua_istable(LuaState, -1)) return std::nullopt;\n\n  T result;\n  ForEachProfileArray([&]([[maybe_unused]] size_t i) {\n    if (i != result.size()) {\n      std::string error = fmt::format(\n          \"Unexpected key {} in lua array, expected {}\", i, result.size());\n      ImpLog(LogLevel::Fatal, LogChannel::Profile, \"{:s}\\n\", error);\n      throw std::runtime_error(error);\n    }\n    result.push_back(EnsureGet<typename T::value_type>());\n  });\n  return result;\n}\n\ntemplate <typename T>\n  requires requires(T x) {\n    { std::variant{x} } -> std::same_as<T>;\n  }\ninline std::optional<T> TryGet() {\n  const auto variantElemHandler =\n      [&]<std::size_t I>(std::optional<T>& variantOpt) {\n        using ElemType = std::variant_alternative_t<I, T>;\n        auto elemOpt = TryGet<ElemType>();\n        if (elemOpt) variantOpt = std::move(*elemOpt);\n        return elemOpt.has_value();\n      };\n\n  const auto variantFetcher =\n      [&]<std::size_t... Is>(std::index_sequence<Is...>) {\n        std::optional<T> variantOpt;\n\n        (variantElemHandler.template operator()<Is>(variantOpt) || ...);\n        return variantOpt;\n      };\n\n  return variantFetcher(std::make_index_sequence<std::variant_size_v<T>>{});\n}\n\ntemplate <typename T>\n  requires is_std_array<T>::value\ninline std::optional<T> TryGet() {\n  if (!lua_istable(LuaState, -1)) return std::nullopt;\n  auto errorHandler = [](std::string error) {\n    ImpLog(LogLevel::Fatal, LogChannel::Profile, \"{:s}\\n\", error);\n    throw std::runtime_error(error);\n  };\n\n  T result;\n  size_t count = 0;\n  ForEachProfileArray([&]([[maybe_unused]] size_t i) {\n    if (count != i) {\n      errorHandler(fmt::format(\"Unexpected key {} in lua array, expected {}\", i,\n                               result.size()));\n    }\n    if (i >= result.size()) {\n      errorHandler(fmt::format(\"Array too small, expected max {} elements\",\n                               result.size()));\n    }\n    result[count++] = EnsureGet<typename T::value_type>();\n  });\n  if (count != result.size()) {\n    errorHandler(fmt::format(\"Expected {} elements in array, got {}\",\n                             result.size(), count));\n  }\n  return result;\n}\n\ntemplate <typename E>\n  requires std::is_enum_v<E>\ninline std::optional<E> TryGet() {\n  using MagicEnumRange = magic_enum::customize::enum_range<E>;\n  const auto optUnderlying = TryGet<std::underlying_type_t<E>>();\n  if (optUnderlying) {\n    if constexpr (requires { MagicEnumRange::is_flags; }) {\n      if constexpr (MagicEnumRange::is_flags)\n        return magic_enum::enum_flags_cast<E>(*optUnderlying);\n      else\n        return magic_enum::enum_cast<E>(*optUnderlying);\n    } else\n      return magic_enum::enum_cast<E>(*optUnderlying);\n  }\n  return std::nullopt;\n}\n\ntemplate <>\nstruct TryGetImpl<Io::AssetPath> {\n  static std::optional<Io::AssetPath> Call();\n};\n\ntemplate <>\nstruct TryGetImpl<RectF> {\n  static std::optional<RectF> Call();\n};\ntemplate <>\nstruct TryGetImpl<bool> {\n  static std::optional<bool> Call();\n};\n\ntemplate <>\nstruct TryGetImpl<DialogueColorPair> {\n  static std::optional<DialogueColorPair> Call();\n};\n\ntemplate <typename T>\ninline void GetArray(std::span<T> out) {\n  size_t actualCount = static_cast<size_t>(lua_rawlen(LuaState, -1));\n  if (actualCount != out.size()) {\n    std::string error =\n        fmt::format(\"Expected to have {:d} values for array, got {:d}\",\n                    out.size(), actualCount);\n    ImpLog(LogLevel::Fatal, LogChannel::Profile, \"{:s}\\n\", error);\n    throw std::runtime_error(error);\n  }\n\n  ForEachProfileArray([&](uint32_t i) { out[i] = EnsureGetArrayElement<T>(); });\n}\n\ntemplate <typename T>\ninline void GetMemberArray(std::span<T> out, char const* name) {\n  EnsurePushMemberOfType(name, LUA_TTABLE);\n  GetArray(out);\n  Pop();\n}\n\ninline void ForEachProfileArray(std::invocable<uint32_t> auto func) {\n  AssertIs(LUA_TTABLE);\n  PushInitialIndex();\n  while (PushNextTableElement() != 0) {\n    auto index = EnsureGetKey<uint32_t>() - 1;\n    func(index);\n    Pop();\n  }\n}\n\ntemplate <typename K>\ninline void ForEachProfileTable(std::invocable<K const&> auto func) {\n  AssertIs(LUA_TTABLE);\n  PushInitialIndex();\n  while (PushNextTableElement() != 0) {\n    auto key = EnsureGetKey<K>();\n    func(key);\n    Pop();\n  }\n}\n\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/scene3d.cpp",
    "content": "#include \"scene3d.h\"\n#include \"profile_internal.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace Scene3D {\n\nvoid Configure() {\n  EnsurePushMemberOfType(\"Scene3D\", LUA_TTABLE);\n\n  Version = EnsureGetMember<LKMVersion>(\"MaxRenderables\");\n\n  {\n    EnsurePushMemberOfType(\"DefaultCamera\", LUA_TTABLE);\n\n    DefaultCameraPosition = EnsureGetMember<glm::vec3>(\"Position\");\n    DefaultCameraTarget = EnsureGetMember<glm::vec3>(\"Target\");\n    DefaultFov = EnsureGetMember<float>(\"Fov\");\n\n    Pop();\n  }\n\n  if (TryPushMember(\"AnimationParseBlacklist\")) {\n    AssertIs(LUA_TTABLE);\n\n    PushInitialIndex();\n    while (PushNextTableElement() != 0) {\n      const uint32_t model = EnsureGetKey<uint32_t>();\n      AssertIs(LUA_TTABLE);\n      PushInitialIndex();\n      while (PushNextTableElement() != 0) {\n        AnimationParseBlacklist.emplace_back(model,\n                                             EnsureGetArrayElement<int>());\n        Pop();\n      }\n      Pop();\n    }\n\n    Pop();\n  }\n\n  // Characters\n\n  {\n    EnsurePushMemberOfType(\"Characters\", LUA_TTABLE);\n\n    PushInitialIndex();\n    while (PushNextTableElement() != 0) {\n      const uint32_t charId = EnsureGetKey<uint32_t>();\n\n      CharacterDef& character = Characters[charId];\n      character.CharacterId = charId;\n      character.IdleAnimation = EnsureGetMember<uint16_t>(\"IdleAnimation\");\n\n      {\n        EnsurePushMemberOfType(\"Models\", LUA_TTABLE);\n\n        PushInitialIndex();\n        while (PushNextTableElement() != 0) {\n          uint32_t modelId = EnsureGetArrayElement<uint32_t>();\n          character.Models.push_back(modelId);\n          ModelsToCharacters[modelId] = charId;\n          Pop();\n        }\n\n        Pop();\n      }\n\n      {\n        EnsurePushMemberOfType(\"Animations\", LUA_TTABLE);\n\n        PushInitialIndex();\n        while (PushNextTableElement() != 0) {\n          uint16_t animId = EnsureGetKey<uint16_t>();\n\n          AnimationDef& animDef = character.Animations[animId];\n          animDef.AnimId = animId;\n          animDef.CharacterId = charId;\n\n          if (!TryGetMember<float>(\"LoopStart\", animDef.LoopStart))\n            animDef.LoopStart = 0;\n          if (!TryGetMember<float>(\"LoopEnd\", animDef.LoopEnd))\n            animDef.LoopEnd = 0;\n          if (!TryGetMember<bool>(\"OneShot\", animDef.OneShot))\n            animDef.OneShot = false;\n\n          Pop();\n        }\n\n        Pop();\n      }\n\n      Pop();\n    }\n\n    Pop();\n  }\n\n  Pop();\n}\n\n}  // namespace Scene3D\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/scene3d.h",
    "content": "#pragma once\n\n#include <glm/glm.hpp>\n#include <vector>\n#include <ankerl/unordered_dense.h>\n#include \"../renderer/3d/model.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace Scene3D {\n\ninline LKMVersion Version = LKMVersion::RNE;\n\ninline int MaxRenderables;\n\ninline glm::vec3 DefaultCameraPosition;\ninline glm::vec3 DefaultCameraTarget;\ninline float DefaultFov;\n\ninline float AnimationDesignFrameRate;\n\ninline std::vector<std::pair<uint32_t, int16_t>> AnimationParseBlacklist;\n\nstruct AnimationDef {\n  uint32_t CharacterId;\n  int16_t AnimId;\n  bool OneShot = false;\n  float LoopStart = 0.0f;\n  float LoopEnd = 0.0f;\n};\n\nclass CharacterDef {\n public:\n  uint32_t CharacterId;\n  int16_t IdleAnimation;\n  std::vector<uint32_t> Models;\n  ankerl::unordered_dense::map<int16_t, AnimationDef> Animations;\n};\n\ninline ankerl::unordered_dense::map<uint32_t, CharacterDef> Characters;\ninline ankerl::unordered_dense::map<uint32_t, uint32_t> ModelsToCharacters;\n\nvoid Configure();\n\n}  // namespace Scene3D\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/scriptinput.cpp",
    "content": "#include \"scriptinput.h\"\n#include \"profile_internal.h\"\n\n#include \"../vm/interface/input.h\"\n\nnamespace Impacto {\nnamespace Profile {\n\ntemplate <>\nstruct TryGetImpl<ScriptInput::KeyboardPadMapping> {\n  static std::optional<ScriptInput::KeyboardPadMapping> Call() {\n    using ScriptInput::KeyboardPadMapping;\n    if (!lua_istable(LuaState, -1)) return std::nullopt;\n    KeyboardPadMapping kbPadMap;\n    uint8_t kbMode{};\n    if (TryGetMember(\"Id\", kbPadMap.Id) && TryGetMember(\"Mode\", kbMode)) {\n      kbPadMap.Mode = static_cast<KeyboardPadMapping::InputMode>(kbMode);\n      return kbPadMap;\n    }\n    return std::nullopt;\n  }\n};\n\ntemplate <>\nstruct TryGetImpl<ScriptInput::InputAxisDir> {\n  static std::optional<ScriptInput::InputAxisDir> Call() {\n    using UnderlyingT = std::underlying_type_t<ScriptInput::InputAxisDir>;\n    std::optional<UnderlyingT> opt = TryGet<UnderlyingT>();\n    if (opt) return static_cast<ScriptInput::InputAxisDir>(*opt);\n    return std::nullopt;\n  }\n};\n\nnamespace ScriptInput {\nusing namespace Impacto::Vm::Interface;\n\nvoid Configure() {\n  EnsurePushMemberOfType(\"Input\", LUA_TTABLE);\n\n  PADToKBcustom = EnsureGetMember<decltype(PADToKBcustom)>(\"PADtoKBcustom\");\n  PADToMouse = EnsureGetMember<decltype(PADToMouse)>(\"PADtoMS\");\n  PADToController = EnsureGetMember<decltype(PADToController)>(\"PADtoGP\");\n  PADToControllerAxis =\n      EnsureGetMember<decltype(PADToControllerAxis)>(\"PADtoGPA\");\n  PADcustomA = EnsureGetMember<std::vector<uint32_t>>(\"PADcustomA\");\n  PADcustomB = EnsureGetMember<std::vector<uint32_t>>(\"PADcustomB\");\n\n  KBcustom = EnsureGetMember<decltype(KBcustom)>(\"KBcustom\");\n\n  Pop();\n}\n\n}  // namespace ScriptInput\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/scriptinput.h",
    "content": "#pragma once\n\n#include <ankerl/unordered_dense.h>\n\n#include \"../util.h\"\n#include \"../inputsystem.h\"\nnamespace Impacto {\nnamespace Profile {\nnamespace ScriptInput {\n\nenum InputAxisDir {\n  AxisPos = 1,\n  AxisNeg = -1,\n};\n\nstruct KeyboardPadMapping {\n  enum InputMode : uint8_t {\n    Tap = 0b01,\n    Held = 0b10,\n    Any = Tap | Held,\n  };\n\n  uint8_t Id{};\n  InputMode Mode;\n};\n\ninline ankerl::unordered_dense::map<int, std::vector<KeyboardPadMapping>>\n    PADToKBcustom;\ninline ankerl::unordered_dense::map<int, uint32_t> PADToMouse;\ninline ankerl::unordered_dense::map<int, Input::ControllerButton>\n    PADToController;\ninline ankerl::unordered_dense::map<\n    int, std::pair<Input::ControllerAxis, InputAxisDir>>\n    PADToControllerAxis;\n\ninline std::vector<uint32_t> PADcustomA;\ninline std::vector<uint32_t> PADcustomB;\n\nvoid Configure();\n\n}  // namespace ScriptInput\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/scriptvars.cpp",
    "content": "#include \"scriptvars.h\"\n#include \"profile_internal.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace ScriptVars {\n\n// TODO: scope scriptvars for common and gamespecific\nvoid Configure() {\n  EnsurePushMemberOfType(\"ScriptVars\", LUA_TTABLE);\n\n#define V(var) var = TryGetMember<int>(#var).value_or(-1);\n#include \"../scriptvars.h\"\n#undef V\n\n  Pop();\n}\n\n}  // namespace ScriptVars\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/scriptvars.h",
    "content": "#pragma once\n\n#include \"../impacto.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace ScriptVars {\n\n#define V(var) inline int var;\n#include \"../scriptvars.h\"\n#undef V\n\nvoid Configure();\n\n}  // namespace ScriptVars\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/sprites.cpp",
    "content": "#include \"sprites.h\"\n#include \"profile_internal.h\"\n#include \"../io/assetpath.h\"\n#include \"../log.h\"\n#include \"../renderer/renderer.h\"\n#include \"../texture/texture.h\"\n#include <future>\n\nnamespace Impacto {\nnamespace Profile {\n\nstatic Texture LoadTexture(Io::Stream* stream, std::string name) {\n  Texture texture{};\n  if (!texture.Load(stream)) {\n    ImpLog(LogLevel::Error, LogChannel::Profile,\n           \"Spritesheet {:s} texture could not be imported, using fallback\\n\",\n           name);\n    texture.LoadCheckerboard();\n  }\n\n  delete stream;\n\n  return texture;\n}\n\nvoid LoadSpritesheets() {\n  EnsurePushMemberOfType(\"SpriteSheets\", LUA_TTABLE);\n\n  std::vector<std::tuple<std::string, std::future<Texture>>> futures;\n\n  PushInitialIndex();\n  while (PushNextTableElement() != 0) {\n    std::string name(EnsureGetKey<std::string>());\n\n    SpriteSheet& sheet = SpriteSheets[name];\n    sheet.DesignWidth = EnsureGetMember<float>(\"DesignWidth\");\n    sheet.DesignHeight = EnsureGetMember<float>(\"DesignHeight\");\n\n    Io::AssetPath asset = EnsureGetMember<Io::AssetPath>(\"Path\");\n\n    Io::Stream* stream;\n    IoError err = asset.Open(&stream);\n    if (err != IoError_OK) {\n      ImpLog(LogLevel::Fatal, LogChannel::Profile,\n             \"Could not open spritesheet {:s}\\n\", name);\n      Window->Shutdown();\n    }\n\n    futures.emplace_back(std::tuple(\n        name, std::async(std::launch::async, LoadTexture, stream, name)));\n\n    Pop();\n  }\n\n  for (auto& [name, future] : futures) {\n    SpriteSheet& sheet = SpriteSheets[name];\n    sheet.Texture = future.get().Submit();\n  }\n\n  Pop();\n\n  EnsurePushMemberOfType(\"Sprites\", LUA_TTABLE);\n\n  PushInitialIndex();\n  while (PushNextTableElement() != 0) {\n    std::string name(EnsureGetKey<std::string>());\n\n    Sprite& sprite = Sprites[name];\n    sprite.Sheet = EnsureGetMember<SpriteSheet>(\"Sheet\");\n    sprite.Bounds = EnsureGetMember<RectF>(\"Bounds\");\n    if (!TryGetMember<glm::vec2>(\"BaseScale\", sprite.BaseScale))\n      sprite.BaseScale = glm::vec2(1.0f);\n\n    Pop();\n  }\n\n  Pop();\n}\n\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/sprites.h",
    "content": "#pragma once\n\n#include \"../spritesheet.h\"\n#include \"../util.h\"\n#include <ankerl/unordered_dense.h>\n\nnamespace Impacto {\nnamespace Profile {\n\ninline ankerl::unordered_dense::map<std::string, SpriteSheet, string_hash,\n                                    std::equal_to<>>\n    SpriteSheets;\ninline ankerl::unordered_dense::map<std::string, Sprite, string_hash,\n                                    std::equal_to<>>\n    Sprites;\n\nvoid LoadSpritesheets();\n\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/subtitle.cpp",
    "content": "#include \"subtitle.h\"\n#include \"../io/filemeta.h\"\n\n#include \"profile_internal.h\"\n\nnamespace Impacto::Profile {\ntemplate <>\nstruct TryGetImpl<Subtitle::SubtitleTrackFile> {\n  static std::optional<Subtitle::SubtitleTrackFile> Call() {\n    if (!lua_istable(LuaState, -1)) return std::nullopt;\n    auto typeOpt = TryGetMember<Subtitle::SubtitleType>(\"Type\");\n    auto idOpt = TryGetMember<int>(\"Id\");\n    auto pathOpt = TryGetMember<std::string>(\"Path\");\n    auto configOpt = TryGetMember<SubtitleConfigType>(\"Config\");\n\n    if (!typeOpt || !configOpt || !(pathOpt.has_value() ^ idOpt.has_value()))\n      return std::nullopt;\n    return Subtitle::SubtitleTrackFile{\n        .Type = *typeOpt,\n        .Config = *configOpt,\n        .Id = std::move(idOpt),\n        .Path = std::move(pathOpt),\n    };\n  }\n};\n\n}  // namespace Impacto::Profile\n\nnamespace Impacto::Profile::Subtitle {\nvoid Configure() {\n  if (TryPushMember(\"Subtitle\")) {\n    AssertIs(LUA_TTABLE);\n    SubtitleMappings =\n        TryGetMember<decltype(SubtitleMappings)>(\"SubtitleMappings\")\n            .value_or(SubtitleMappings);\n\n    SubtitleFontsDir =\n        TryGetMember<decltype(SubtitleFontsDir)>(\"SubtitleFontsDir\")\n            .value_or(SubtitleFontsDir);\n    for (auto& dir : SubtitleFontsDir) {\n      dir = GetSystemDependentPath(dir);\n    }\n\n    Pop();\n  }\n}\n}  // namespace Impacto::Profile::Subtitle"
  },
  {
    "path": "src/profile/subtitle.h",
    "content": "#pragma once\n#include <ankerl/unordered_dense.h>\n#include <string>\n#include <magic_enum/magic_enum.hpp>\n#include \"game.h\"\n\nnamespace Impacto::Profile::Subtitle {\n\nenum class SubtitleType : uint8_t {\n  None = 0,\n  Bitmap,\n  Text,\n  Ass,\n};\n\nstruct SubtitleTrackFile {\n  SubtitleType Type;\n  SubtitleConfigType Config;\n  std::optional<int> Id;\n  std::optional<std::string> Path;\n};\n\nusing SubtitleTrackFiles = std::vector<SubtitleTrackFile>;\nusing SubtitleMountMapping =\n    ankerl::unordered_dense::map<std::variant<uint32_t, std::string>,\n                                 SubtitleTrackFiles>;\ninline ankerl::unordered_dense::map<std::string, SubtitleMountMapping>\n    SubtitleMappings;\ninline std::vector<std::string> SubtitleFontsDir;\n\nvoid Configure();\n}  // namespace Impacto::Profile::Subtitle"
  },
  {
    "path": "src/profile/ui/backlogmenu.cpp",
    "content": "#include \"backlogmenu.h\"\n#include \"../profile_internal.h\"\n#include \"../../ui/ui.h\"\n#include \"../../log.h\"\n#include \"../../game.h\"\n\n#include \"../../ui/widgets/backlogentry.h\"\n\n#include \"../games/mo6tw/backlogmenu.h\"\n#include \"../games/chlcc/backlogmenu.h\"\n#include \"../games/cc/backlogmenu.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace BacklogMenu {\n\nusing namespace Impacto::UI;\n\nvoid Configure() {\n  if (TryPushMember(\"BacklogMenu\")) {\n    AssertIs(LUA_TTABLE);\n\n    Type = EnsureGetMember<BacklogMenuType>(\"Type\");\n\n    if (Type == BacklogMenuType::None) {\n      UI::BacklogMenuPtr = new UI::BacklogMenu();\n      UI::Menus[Game::DrawComponentType::None].push_back(UI::BacklogMenuPtr);\n\n      Pop();\n      return;\n    }\n\n    EntryHighlightLocation =\n        EnsureGetMember<EntryHighlightLocationType>(\"EntryHighlightLocation\");\n\n    BacklogBackground = EnsureGetMember<Sprite>(\"BacklogBackgroundSprite\");\n    EntryHighlight = EnsureGetMember<Sprite>(\"EntryHighlightSprite\");\n    VoiceIcon = EnsureGetMember<Sprite>(\"VoiceIconSprite\");\n    ScrollbarThumb = EnsureGetMember<Sprite>(\"ScrollbarThumbSprite\");\n    ScrollbarTrack = EnsureGetMember<Sprite>(\"ScrollbarTrackSprite\");\n\n    EntryYPadding = EnsureGetMember<float>(\"EntryYPadding\");\n    EntriesStart = EnsureGetMember<glm::vec2>(\"EntriesStart\");\n    EntryHighlightOffset = EnsureGetMember<glm::vec2>(\"EntryHighlightOffset\");\n    EntryHighlightPadding = EnsureGetMember<float>(\"EntryHighlightPadding\");\n    VoiceIconOffset = EnsureGetMember<glm::vec2>(\"VoiceIconOffset\");\n    ScrollbarPosition = EnsureGetMember<glm::vec2>(\"ScrollbarPosition\");\n    ScrollbarThumbLength = EnsureGetMember<float>(\"ScrollbarThumbLength\");\n    RenderingBounds = EnsureGetMember<RectF>(\"RenderingBounds\");\n    HoverBounds = EnsureGetMember<RectF>(\"HoverBounds\");\n\n    FadeInDuration = EnsureGetMember<float>(\"FadeInDuration\");\n    FadeOutDuration = EnsureGetMember<float>(\"FadeOutDuration\");\n\n    ScrollingSpeed = EnsureGetMember<float>(\"ScrollingSpeed\");\n    PageUpDownHeight = EnsureGetMember<float>(\"PageUpDownHeight\");\n\n    switch (Type) {\n      case BacklogMenuType::MO6TW:\n        MO6TW::BacklogMenu::Configure();\n        break;\n\n      case BacklogMenuType::CHLCC:\n        CHLCC::BacklogMenu::Configure();\n        break;\n\n      case BacklogMenuType::CC:\n        CC::BacklogMenu::Configure();\n        break;\n\n      case BacklogMenuType::None:\n        break;\n    }\n\n    Pop();\n  }\n}\n\n}  // namespace BacklogMenu\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/ui/backlogmenu.h",
    "content": "#pragma once\n\n#include \"../../ui/ui.h\"\n#include \"../../spritesheet.h\"\n#include <magic_enum/magic_enum.hpp>\n\nnamespace Impacto {\nnamespace Profile {\nnamespace BacklogMenu {\n\nenum class BacklogMenuType : int {\n  None,\n  MO6TW,\n  CHLCC,\n  CC,\n};\nenum class EntryHighlightLocationType : int {\n  None,\n  BottomLeftOfEntry,\n  TopLineLeftOfScreen,\n  AllLinesLeftOfScreen,\n};\n\ninline BacklogMenuType Type = BacklogMenuType::None;\ninline EntryHighlightLocationType EntryHighlightLocation =\n    EntryHighlightLocationType::None;\n\ninline Sprite BacklogBackground;\ninline Sprite EntryHighlight;\ninline Sprite VoiceIcon;\ninline Sprite ScrollbarTrack;\ninline Sprite ScrollbarThumb;\n\ninline float EntryYPadding;\ninline glm::vec2 EntriesStart;\ninline glm::vec2 EntryHighlightOffset;\ninline float EntryHighlightPadding;\ninline glm::vec2 VoiceIconOffset;\ninline glm::vec2 ScrollbarPosition;\ninline float ScrollbarThumbLength;\ninline RectF RenderingBounds;\ninline RectF HoverBounds;\n\ninline float FadeInDuration;\ninline float FadeOutDuration;\n\ninline float ScrollingSpeed;\ninline float PageUpDownHeight;\n\nvoid Configure();\n\n}  // namespace BacklogMenu\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/ui/commonmenu.cpp",
    "content": "#include \"commonmenu.h\"\n#include \"../profile_internal.h\"\n#include \"../../ui/ui.h\"\n#include \"../games/chlcc/commonmenu.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace CommonMenu {\n\nvoid Configure() {\n  if (TryPushMember(\"CommonMenu\")) {\n    AssertIs(LUA_TTABLE);\n\n    Type = EnsureGetMember<CommonMenuType>(\"Type\");\n\n    if (Type == CommonMenuType::CHLCC) {\n      CHLCC::CommonMenu::Configure();\n    }\n\n    Pop();\n  }\n}\n\n}  // namespace CommonMenu\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/ui/commonmenu.h",
    "content": "#pragma once\n\n#include \"../../ui/ui.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace CommonMenu {\n\nusing namespace Impacto::UI;\ninline CommonMenuType Type = CommonMenuType::None;\n\nvoid Configure();\n\n}  // namespace CommonMenu\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/ui/extramenus.cpp",
    "content": "#include \"extramenus.h\"\n#include \"../profile_internal.h\"\n#include \"../../ui/ui.h\"\n#include \"../games/mo6tw/clearlistmenu.h\"\n#include \"../games/mo6tw/moviemenu.h\"\n#include \"../games/mo6tw/actorsvoicemenu.h\"\n#include \"../games/mo6tw/musicmenu.h\"\n#include \"../games/mo6tw/albummenu.h\"\n#include \"../games/cclcc/librarymenu.h\"\n#include \"../games/cclcc/clearlistmenu.h\"\n#include \"../games/chlcc/clearlistmenu.h\"\n#include \"../games/chlcc/moviemenu.h\"\n#include \"../games/chlcc/musicmenu.h\"\n#include \"../games/chlcc/albummenu.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace ExtraMenus {\n\nusing namespace Impacto::UI;\n\nvoid Configure() {\n  if (TryPushMember(\"ExtraMenus\")) {\n    AssertIs(LUA_TTABLE);\n\n    if (TryPushMember(\"LibraryMenu\")) {\n      LibraryMenuType = EnsureGetMember<UI::LibraryMenuType>(\"Type\");\n\n      if (LibraryMenuType == LibraryMenuType::CCLCC) {\n        CCLCC::LibraryMenu::Configure();\n      }\n\n      Pop();\n    }\n\n    if (TryPushMember(\"ClearListMenu\")) {\n      ClearListType = EnsureGetMember<ClearListMenuType>(\"Type\");\n\n      if (ClearListType == ClearListMenuType::MO6TW) {\n        MO6TW::ClearListMenu::Configure();\n      } else if (ClearListType == ClearListMenuType::CCLCC) {\n        CCLCC::ClearListMenu::Configure();\n      } else if (ClearListType == ClearListMenuType::CHLCC) {\n        CHLCC::ClearListMenu::Configure();\n      }\n\n      Pop();\n    }\n\n    if (TryPushMember(\"AlbumMenu\")) {\n      AlbumType = EnsureGetMember<AlbumMenuType>(\"Type\");\n\n      if (AlbumType == AlbumMenuType::MO6TW) {\n        MO6TW::AlbumMenu::Configure();\n      } else if (AlbumType == AlbumMenuType::CHLCC) {\n        CHLCC::AlbumMenu::Configure();\n      }\n\n      Pop();\n    }\n\n    if (TryPushMember(\"MusicMenu\")) {\n      MusicType = EnsureGetMember<MusicMenuType>(\"Type\");\n\n      if (MusicType == MusicMenuType::MO6TW) {\n        MO6TW::MusicMenu::Configure();\n      } else if (MusicType == MusicMenuType::CHLCC) {\n        CHLCC::MusicMenu::Configure();\n      }\n\n      Pop();\n    }\n\n    if (TryPushMember(\"MovieMenu\")) {\n      MovieType = EnsureGetMember<MovieMenuType>(\"Type\");\n\n      if (MovieType == MovieMenuType::MO6TW) {\n        MO6TW::MovieMenu::Configure();\n      } else if (MovieType == MovieMenuType::CHLCC) {\n        CHLCC::MovieMenu::Configure();\n      }\n\n      Pop();\n    }\n\n    if (TryPushMember(\"ActorsVoiceMenu\")) {\n      ActorsVoiceType = EnsureGetMember<ActorsVoiceMenuType>(\"Type\");\n\n      if (ActorsVoiceType == ActorsVoiceMenuType::MO6TW) {\n        MO6TW::ActorsVoiceMenu::Configure();\n      }\n\n      Pop();\n    }\n\n    Pop();\n  }\n}\n\n}  // namespace ExtraMenus\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/ui/extramenus.h",
    "content": "#pragma once\n\n#include \"../../ui/ui.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace ExtraMenus {\n\ninline Impacto::UI::LibraryMenuType LibraryMenuType =\n    Impacto::UI::LibraryMenuType::None;\ninline Impacto::UI::ClearListMenuType ClearListType =\n    Impacto::UI::ClearListMenuType::None;\ninline Impacto::UI::AlbumMenuType AlbumType = Impacto::UI::AlbumMenuType::None;\ninline Impacto::UI::MusicMenuType MusicType = Impacto::UI::MusicMenuType::None;\ninline Impacto::UI::MovieMenuType MovieType = Impacto::UI::MovieMenuType::None;\ninline Impacto::UI::ActorsVoiceMenuType ActorsVoiceType =\n    Impacto::UI::ActorsVoiceMenuType::None;\n\nvoid Configure();\n\n}  // namespace ExtraMenus\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/ui/gamespecific.cpp",
    "content": "#include \"gamespecific.h\"\n#include \"../profile_internal.h\"\n#include \"../../ui/ui.h\"\n\n#include \"../games/chlcc/delusiontrigger.h\"\n#include \"../games/cclcc/delusiontrigger.h\"\n#include \"../games/cclcc/yesnotrigger.h\"\n#include \"../games/cclcc/mapsystem.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace GameSpecific {\n\nusing namespace Impacto::UI;\n\nvoid Configure() {\n  if (!TryPushMember(\"GameSpecific\")) return;\n  AssertIs(LUA_TTABLE);\n  GameSpecificType = EnsureGetMember<UI::GameSpecificType>(\"Type\");\n\n  // Get common gamespecific UI elements\n  if (GameSpecificType == GameSpecificType::CCLCC) {\n    UIButtonGuideSprites =\n        EnsureGetMember<std::vector<Sprite>>(\"UIButtonGuideSprites\");\n    UIButtonGuideEndDisp = EnsureGetMember<RectF>(\"UIButtonGuideEndDisp\");\n  }\n  if (GameSpecificType == GameSpecificType::CHLCC) {\n    MonitorScanline = EnsureGetMember<Sprite>(\"MonitorScanline\");\n    EyecatchStar = EnsureGetMember<Sprite>(\"EyecatchStar\");\n    ButterflySprites = EnsureGetMember<std::vector<Sprite>>(\"ButterflySprites\");\n    ButterflyFrameCount = EnsureGetMember<uint8_t>(\"ButterflyFrameCount\");\n    ButterflyFlapFrameDuration =\n        EnsureGetMember<float>(\"ButterflyFlapFrameDuration\");\n    ButterflyFadeDuration = EnsureGetMember<float>(\"ButterflyFadeDuration\");\n\n    BubbleSpriteSmall = EnsureGetMember<Sprite>(\"BubbleSpriteSmall\");\n    BubbleSpriteBig = EnsureGetMember<Sprite>(\"BubbleSpriteBig\");\n    BubbleFadeDuration = EnsureGetMember<float>(\"BubbleFadeDuration\");\n  }\n\n  Pop();\n\n  switch (GameSpecificType) {\n    case GameSpecificType::CHLCC:\n      CHLCC::DelusionTrigger::Configure();\n      break;\n    case GameSpecificType::CC:\n      break;\n    case GameSpecificType::CCLCC:\n      CCLCC::MapSystem::Configure();\n      CCLCC::YesNoTrigger::Configure();\n      CCLCC::DelusionTrigger::Configure();\n      break;\n    case GameSpecificType::RNE:\n      break;\n    case GameSpecificType::Dash:\n      break;\n    case GameSpecificType::None:\n      break;\n  }\n}\n\n}  // namespace GameSpecific\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/ui/gamespecific.h",
    "content": "#pragma once\n\n#include \"../../ui/ui.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace GameSpecific {\n\ninline Impacto::UI::GameSpecificType GameSpecificType =\n    Impacto::UI::GameSpecificType::None;\n\ninline std::optional<std::vector<Sprite>> UIButtonGuideSprites;\ninline std::optional<RectF> UIButtonGuideEndDisp;\ninline Sprite MonitorScanline;\n\ninline std::vector<Sprite> ButterflySprites;\ninline uint8_t ButterflyFrameCount;\ninline float ButterflyFlapFrameDuration;\ninline float ButterflyFadeDuration;\n\ninline Sprite BubbleSpriteSmall;\ninline Sprite BubbleSpriteBig;\ninline float BubbleFadeDuration;\n\ninline Sprite EyecatchStar;\n\nvoid Configure();\n\n}  // namespace GameSpecific\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/ui/helpmenu.cpp",
    "content": "#include \"helpmenu.h\"\n#include \"../profile_internal.h\"\n#include \"../games/cclcc/helpmenu.h\"\n#include \"../../ui/ui.h\"\n#include \"../../log.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace HelpMenu {\n\nusing namespace Impacto::UI;\n\nvoid Configure() {\n  if (TryPushMember(\"HelpMenu\")) {\n    AssertIs(LUA_TTABLE);\n\n    Type = EnsureGetMember<HelpMenuType>(\"Type\");\n\n    if (Type == HelpMenuType::CCLCC) {\n      CCLCC::HelpMenu::Configure();\n    }\n\n    Pop();\n  }\n}\n\n}  // namespace HelpMenu\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/ui/helpmenu.h",
    "content": "#pragma once\n\n#include \"../../ui/ui.h\"\n#include \"../../spritesheet.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace HelpMenu {\n\ninline Impacto::UI::HelpMenuType Type = Impacto::UI::HelpMenuType::None;\n\nvoid Configure();\n\n}  // namespace HelpMenu\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/ui/mapsystem.cpp",
    "content": "#include \"mapsystem.h\"\n#include \"../profile_internal.h\"\n\n#include \"../games/cclcc/mapsystem.h\"\n#include \"../../games/cclcc/mapsystem.h\"\n#include \"../../profile/games/cclcc/mapsystem.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace MapSystem {\n\nvoid Configure() {\n  if (!TryPushMember(\"MapSystem\")) return;\n  AssertIs(LUA_TTABLE);\n  CCLCC::MapSystem::Configure();\n  Pop();\n}\nvoid CreateInstance() {\n  if (!Impacto::UI::MapSystem::MapSystemPtr) {\n    Impacto::UI::MapSystem::MapSystemPtr = new Impacto::UI::CCLCC::MapSystem;\n  }\n}\n}  // namespace MapSystem\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/ui/mapsystem.h",
    "content": "#pragma once\n\n#include \"../sprites.h\"\n#include \"../../ui/mapsystem.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace MapSystem {\n\nvoid Configure();\n\nvoid CreateInstance();\n\n}  // namespace MapSystem\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/ui/optionsmenu.cpp",
    "content": "#include \"optionsmenu.h\"\n#include \"../profile_internal.h\"\n#include \"../games/mo6tw/optionsmenu.h\"\n#include \"../games/mo8/optionsmenu.h\"\n#include \"../games/chlcc/optionsmenu.h\"\n#include \"../games/cclcc/optionsmenu.h\"\n#include \"../../ui/ui.h\"\n#include \"../../log.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace OptionsMenu {\n\nusing namespace Impacto::UI;\n\nvoid Configure() {\n  if (TryPushMember(\"OptionsMenu\")) {\n    AssertIs(LUA_TTABLE);\n\n    Type = EnsureGetMember<OptionsMenuType>(\"Type\");\n\n    FadeInDuration = EnsureGetMember<float>(\"FadeInDuration\");\n    FadeOutDuration = EnsureGetMember<float>(\"FadeOutDuration\");\n\n    switch (Type) {\n      case OptionsMenuType::MO6TW:\n        MO6TW::OptionsMenu::Configure();\n        break;\n\n      case OptionsMenuType::MO8:\n        MO8::OptionsMenu::Configure();\n        break;\n\n      case OptionsMenuType::CHLCC:\n        CHLCC::OptionsMenu::Configure();\n        break;\n\n      case OptionsMenuType::CCLCC:\n        CCLCC::OptionsMenu::Configure();\n        break;\n\n      case OptionsMenuType::None:\n        break;\n    }\n\n    Pop();\n  }\n}\n\n}  // namespace OptionsMenu\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/ui/optionsmenu.h",
    "content": "#pragma once\n\n#include \"../../ui/ui.h\"\n#include \"../../spritesheet.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace OptionsMenu {\n\ninline Impacto::UI::OptionsMenuType Type = Impacto::UI::OptionsMenuType::None;\n\ninline float FadeInDuration;\ninline float FadeOutDuration;\n\nvoid Configure();\n\n}  // namespace OptionsMenu\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/ui/savemenu.cpp",
    "content": "#include \"savemenu.h\"\n#include \"../profile_internal.h\"\n#include \"../games/mo6tw/savemenu.h\"\n#include \"../games/mo8/savemenu.h\"\n#include \"../games/chlcc/savemenu.h\"\n#include \"../games/cclcc/savemenu.h\"\n#include \"../../ui/ui.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace SaveMenu {\n\nusing namespace Impacto::UI;\n\nvoid Configure() {\n  if (TryPushMember(\"SaveMenu\")) {\n    AssertIs(LUA_TTABLE);\n\n    Type = EnsureGetMember<SaveMenuType>(\"Type\");\n\n    EmptyThumbnailSprite = EnsureGetMember<Sprite>(\"EmptyThumbnailSprite\");\n\n    FadeInDuration = EnsureGetMember<float>(\"FadeInDuration\");\n    FadeOutDuration = EnsureGetMember<float>(\"FadeOutDuration\");\n\n    if (Type == SaveMenuType::MO6TW) {\n      MO6TW::SaveMenu::Configure();\n    } else if (Type == SaveMenuType::CHLCC) {\n      CHLCC::SaveMenu::Configure();\n    } else if (Type == SaveMenuType::CCLCC) {\n      CCLCC::SaveMenu::Configure();\n    } else if (Type == SaveMenuType::MO8) {\n      MO8::SaveMenu::Configure();\n    }\n\n    Pop();\n  }\n}\n\n}  // namespace SaveMenu\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/ui/savemenu.h",
    "content": "#pragma once\n\n#include \"../../ui/ui.h\"\n#include \"../../spritesheet.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace SaveMenu {\n\ninline Impacto::UI::SaveMenuType Type = Impacto::UI::SaveMenuType::None;\n\ninline Sprite EmptyThumbnailSprite;\n\ninline float FadeInDuration;\ninline float FadeOutDuration;\n\nvoid Configure();\n\n}  // namespace SaveMenu\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/ui/selectionmenu.cpp",
    "content": "#include \"selectionmenu.h\"\n#include \"../profile_internal.h\"\n#include \"../../ui/ui.h\"\n// #include \"../../window.h\"\n#include \"../../renderer/renderer.h\"\n#include \"../../game.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace SelectionMenu {\n\nvoid Configure() {\n  EnsurePushMemberOfType(\"SelectionDisplay\", LUA_TTABLE);\n\n  SelectionBackground = EnsureGetMember<Sprite>(\"SelectionBackgroundSprite\");\n  PlainSelectionFrameTopLeft =\n      EnsureGetMember<Sprite>(\"PlainSelectionFrameTopLeftSprite\");\n  PlainSelectionFrameTopSide =\n      EnsureGetMember<Sprite>(\"PlainSelectionFrameTopSideSprite\");\n  PlainSelectionFrameTopRight =\n      EnsureGetMember<Sprite>(\"PlainSelectionFrameTopRightSprite\");\n  PlainSelectionFrameLeftSide =\n      EnsureGetMember<Sprite>(\"PlainSelectionFrameLeftSideSprite\");\n  PlainSelectionFrameBottomLeft =\n      EnsureGetMember<Sprite>(\"PlainSelectionFrameBottomLeftSprite\");\n  PlainSelectionFrameRightSide =\n      EnsureGetMember<Sprite>(\"PlainSelectionFrameRightSideSprite\");\n  PlainSelectionFrameBottomRight =\n      EnsureGetMember<Sprite>(\"PlainSelectionFrameBottomRightSprite\");\n  PlainSelectionFrameBottomSide =\n      EnsureGetMember<Sprite>(\"PlainSelectionFrameBottomSideSprite\");\n  PlainSelectionFrameMiddle =\n      EnsureGetMember<Sprite>(\"PlainSelectionFrameMiddleSprite\");\n\n  Sprite nullSprite = Sprite();\n  nullSprite.Bounds = RectF(0.0f, 0.0f, 0.0f, 0.0f);\n  SelectionHighlight = nullSprite;\n  TryGetMember<Sprite>(\"SelectionHighlightSprite\", SelectionHighlight);\n  SelectionFocused = nullSprite;\n  TryGetMember<Sprite>(\"SelectionFocusedSprite\", SelectionFocused);\n\n  TryGetMember<bool>(\"HighlightTextOnly\", HighlightTextOnly);\n\n  SelectionMaxCount = EnsureGetMember<int>(\"SelectionMaxCount\");\n  SelectionBackgroundX = EnsureGetMember<float>(\"SelectionBackgroundX\");\n  GetMemberArray<float>(std::span(SelectionBackgroundY, SelectionMaxCount),\n                        \"SelectionBackgroundY\");\n  SelectionYSpacing = EnsureGetMember<float>(\"SelectionYSpacing\");\n  PlainSelectionYSpacing = EnsureGetMember<float>(\"PlainSelectionYSpacing\");\n  FadeAnimationDurationInOut =\n      EnsureGetMember<float>(\"FadeAnimationDurationInOut\");\n\n  auto drawType = EnsureGetMember<Game::DrawComponentType>(\"DrawType\");\n\n  UI::SelectionMenuPtr = new UI::SelectionMenu();\n  UI::Menus[drawType].push_back(UI::SelectionMenuPtr);\n\n  Pop();\n}\n\n}  // namespace SelectionMenu\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/ui/selectionmenu.h",
    "content": "#pragma once\n\n#include \"../../spritesheet.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace SelectionMenu {\n\ninline Sprite SelectionBackground;\ninline Sprite PlainSelectionFrameTopLeft;\ninline Sprite PlainSelectionFrameTopSide;\ninline Sprite PlainSelectionFrameTopRight;\ninline Sprite PlainSelectionFrameLeftSide;\ninline Sprite PlainSelectionFrameBottomLeft;\ninline Sprite PlainSelectionFrameRightSide;\ninline Sprite PlainSelectionFrameBottomRight;\ninline Sprite PlainSelectionFrameBottomSide;\ninline Sprite PlainSelectionFrameMiddle;\ninline Sprite SelectionHighlight;\ninline Sprite SelectionFocused;\n\ninline int SelectionMaxCount;\ninline float SelectionBackgroundX;\ninline float SelectionBackgroundY[15];\ninline float SelectionBackgroundY1;\ninline float SelectionBackgroundY2;\ninline float SelectionBackgroundY3;\ninline float SelectionBackgroundY4;\ninline float SelectionBackgroundY5;\ninline float SelectionYSpacing;\ninline float PlainSelectionYSpacing;\ninline float FadeAnimationDurationInOut;\n\ninline bool HighlightTextOnly = true;\n\nvoid Configure();\n\n}  // namespace SelectionMenu\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/ui/sysmesbox.cpp",
    "content": "#include \"sysmesbox.h\"\n#include \"../games/rne/sysmesbox.h\"\n#include \"../games/chlcc/sysmesbox.h\"\n#include \"../games/mo6tw/sysmesbox.h\"\n#include \"../games/darling/sysmesbox.h\"\n#include \"../games/cc/sysmesbox.h\"\n\n#include \"../profile_internal.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace SysMesBox {\n\nusing namespace Impacto::UI;\n\nvoid Configure() {\n  if (TryPushMember(\"SysMesBoxDisplay\")) {\n    AssertIs(LUA_TTABLE);\n\n    Type = EnsureGetMember<SysMesBoxType>(\"Type\");\n\n    if (Type == SysMesBoxType::RNE || Type == SysMesBoxType::Dash) {\n      RNE::SysMesBox::Configure();\n    } else if (Type == SysMesBoxType::CHLCC) {\n      CHLCC::SysMesBox::Configure();\n    } else if (Type == SysMesBoxType::MO6TW) {\n      MO6TW::SysMesBox::Configure();\n    } else if (Type == SysMesBoxType::Darling) {\n      Darling::SysMesBox::Configure();\n    } else if (Type == SysMesBoxType::CC) {\n      CC::SysMesBox::Configure();\n    }\n\n    TextFontSize = EnsureGetMember<float>(\"TextFontSize\");\n    TextMiddleY = EnsureGetMember<float>(\"TextMiddleY\");\n    TextX = EnsureGetMember<float>(\"TextX\");\n    TextLineHeight = EnsureGetMember<float>(\"TextLineHeight\");\n    TextMarginY = EnsureGetMember<float>(\"TextMarginY\");\n    AnimationSpeed = EnsureGetMember<float>(\"AnimationSpeed\");\n    FadeInDuration = EnsureGetMember<float>(\"FadeInDuration\");\n    FadeOutDuration = EnsureGetMember<float>(\"FadeOutDuration\");\n\n    // Implementation->FadeAnimation.DurationIn = FadeInDuration;\n    // Implementation->FadeAnimation.DurationOut = FadeOutDuration;\n\n    Pop();\n  }\n}\n\n}  // namespace SysMesBox\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/ui/sysmesbox.h",
    "content": "#pragma once\n\n#include \"../../ui/ui.h\"\n#include \"../sprites.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace SysMesBox {\n\ninline Impacto::UI::SysMesBoxType Type = Impacto::UI::SysMesBoxType::None;\n\ninline float TextFontSize;\ninline float TextMiddleY;\ninline float TextX;\ninline float TextLineHeight;\ninline float TextMarginY;\ninline float AnimationSpeed;\ninline float FadeInDuration;\ninline float FadeOutDuration;\n\nvoid Configure();\n\n}  // namespace SysMesBox\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/ui/systemmenu.cpp",
    "content": "#include \"systemmenu.h\"\n#include \"../profile_internal.h\"\n#include \"../games/rne/tilebackground.h\"\n#include \"../games/rne/systemmenu.h\"\n#include \"../games/mo6tw/systemmenu.h\"\n#include \"../games/chlcc/systemmenu.h\"\n#include \"../games/mo8/systemmenu.h\"\n#include \"../games/cclcc/systemmenu.h\"\n#include \"../../ui/ui.h\"\n#include \"../../log.h\"\n#include \"../../renderer/renderer.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace SystemMenu {\n\nusing namespace Impacto::UI;\n\nvoid Configure() {\n  if (TryPushMember(\"SystemMenu\")) {\n    AssertIs(LUA_TTABLE);\n\n    Type = EnsureGetMember<SystemMenuType>(\"Type\");\n\n    MenuEntriesNum = EnsureGetMember<int>(\"MenuEntriesNum\");\n    MenuEntriesHNum = EnsureGetMember<int>(\"MenuEntriesHNum\");\n    MenuEntriesX = TryGetMember<float>(\"MenuEntriesX\");\n    MenuEntriesXOffset = TryGetMember<float>(\"MenuEntriesXOffset\");\n    MenuEntriesFirstY = TryGetMember<float>(\"MenuEntriesFirstY\");\n    MenuEntriesYPadding = TryGetMember<float>(\"MenuEntriesYPadding\");\n\n    if (MenuEntriesNum > 0) {\n      GetMemberArray<Sprite>(std::span(MenuEntriesSprites, MenuEntriesNum),\n                             \"MenuEntriesSprites\");\n    }\n    if (MenuEntriesHNum > 0) {\n      GetMemberArray<Sprite>(std::span(MenuEntriesHSprites, MenuEntriesHNum),\n                             \"MenuEntriesHighlightedSprites\");\n    }\n\n    TryGetMember<float>(\"FadeInDuration\", FadeInDuration);\n    TryGetMember<float>(\"FadeOutDuration\", FadeOutDuration);\n\n    if (Type == SystemMenuType::RNE) {\n      RNE::SystemMenu::Configure();\n    } else if (Type == SystemMenuType::MO6TW) {\n      MO6TW::SystemMenu::Configure();\n    } else if (Type == SystemMenuType::CHLCC) {\n      CHLCC::SystemMenu::Configure();\n    } else if (Type == SystemMenuType::MO8) {\n      MO8::SystemMenu::Configure();\n    } else if (Type == SystemMenuType::CCLCC) {\n      CCLCC::SystemMenu::Configure();\n    }\n\n    Pop();\n  }\n}\n\n}  // namespace SystemMenu\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/ui/systemmenu.h",
    "content": "#pragma once\n\n#include <optional>\n#include \"../../ui/ui.h\"\n#include \"../../spritesheet.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace SystemMenu {\n\ninline Impacto::UI::SystemMenuType Type = Impacto::UI::SystemMenuType::None;\n\nint constexpr MenuEntriesNumMax = 16;\n\ninline Sprite MenuEntriesSprites[MenuEntriesNumMax];\ninline Sprite MenuEntriesHSprites[MenuEntriesNumMax];\ninline int MenuEntriesNum;\ninline int MenuEntriesHNum;\ninline std::optional<float> MenuEntriesX;\ninline std::optional<float> MenuEntriesXOffset;\ninline std::optional<float> MenuEntriesFirstY;\ninline std::optional<float> MenuEntriesYPadding;\ninline float FadeInDuration = 0.5f;\ninline float FadeOutDuration = 0.5f;\n\nvoid Configure();\n\n}  // namespace SystemMenu\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/ui/tipsmenu.cpp",
    "content": "#include \"tipsmenu.h\"\n#include \"../profile_internal.h\"\n#include \"../games/mo6tw/tipsmenu.h\"\n#include \"../games/chlcc/tipsmenu.h\"\n#include \"../games/cclcc/tipsmenu.h\"\n#include \"../../ui/ui.h\"\n#include \"../../log.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace TipsMenu {\n\nusing namespace Impacto::UI;\n\nvoid Configure() {\n  if (TryPushMember(\"TipsMenu\")) {\n    AssertIs(LUA_TTABLE);\n\n    Type = EnsureGetMember<TipsMenuType>(\"Type\");\n\n    if (Type != TipsMenuType::None && Type != TipsMenuType::CHLCC) {\n      FadeInDuration = EnsureGetMember<float>(\"FadeInDuration\");\n      FadeOutDuration = EnsureGetMember<float>(\"FadeOutDuration\");\n\n      BackgroundSprite = EnsureGetMember<Sprite>(\"BackgroundSprite\");\n    }\n\n    if (Type == TipsMenuType::MO6TW) {\n      MO6TW::TipsMenu::Configure();\n    } else if (Type == TipsMenuType::CHLCC) {\n      CHLCC::TipsMenu::Configure();\n    } else if (Type == TipsMenuType::CCLCC) {\n      CCLCC::TipsMenu::Configure();\n    } else {\n      UI::TipsMenuPtr = new UI::TipsMenu();\n    }\n\n    Pop();\n  }\n}\n\n}  // namespace TipsMenu\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/ui/tipsmenu.h",
    "content": "#pragma once\n\n#include \"../../ui/ui.h\"\n#include \"../../spritesheet.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace TipsMenu {\n\ninline Impacto::UI::TipsMenuType Type = Impacto::UI::TipsMenuType::None;\n\ninline Sprite BackgroundSprite;\n\ninline float FadeInDuration;\ninline float FadeOutDuration;\n\nvoid Configure();\n\n}  // namespace TipsMenu\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/ui/titlemenu.cpp",
    "content": "#include \"titlemenu.h\"\n#include \"../profile_internal.h\"\n#include \"../games/rne/tilebackground.h\"\n#include \"../games/rne/titlemenu.h\"\n#include \"../games/dash/titlemenu.h\"\n#include \"../games/chlcc/titlemenu.h\"\n#include \"../games/mo6tw/titlemenu.h\"\n#include \"../games/mo8/titlemenu.h\"\n#include \"../games/cc/titlemenu.h\"\n#include \"../games/cclcc/titlemenu.h\"\n#include \"../../log.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace TitleMenu {\n\nusing namespace Impacto::UI;\n\nvoid Configure() {\n  if (TryPushMember(\"TitleMenu\")) {\n    AssertIs(LUA_TTABLE);\n\n    Type = EnsureGetMember<TitleMenuType>(\"Type\");\n\n    MenuEntriesNum = EnsureGetMember<int>(\"MenuEntriesNum\");\n    if (MenuEntriesNum > 0) {\n      GetMemberArray<Sprite>(std::span(MenuEntriesSprites, MenuEntriesNum),\n                             \"MenuEntriesSprites\");\n      GetMemberArray<Sprite>(std::span(MenuEntriesHSprites, MenuEntriesNum),\n                             \"MenuEntriesHighlightedSprites\");\n    }\n\n    PressToStartSprite = EnsureGetMember<Sprite>(\"PressToStartSprite\");\n\n    PressToStartAnimDurationIn =\n        EnsureGetMember<float>(\"PressToStartAnimDurationIn\");\n    PressToStartAnimDurationOut =\n        EnsureGetMember<float>(\"PressToStartAnimDurationOut\");\n\n    PressToStartX = EnsureGetMember<float>(\"PressToStartX\");\n    PressToStartY = EnsureGetMember<float>(\"PressToStartY\");\n\n    if (Type == TitleMenuType::RNE) {\n      RNE::TitleMenu::Configure();\n    } else if (Type == TitleMenuType::Dash) {\n      Dash::TitleMenu::Configure();\n    } else if (Type == TitleMenuType::CHLCC) {\n      CHLCC::TitleMenu::Configure();\n    } else if (Type == TitleMenuType::MO6TW) {\n      MO6TW::TitleMenu::Configure();\n    } else if (Type == TitleMenuType::MO8) {\n      MO8::TitleMenu::Configure();\n    } else if (Type == TitleMenuType::CC) {\n      CC::TitleMenu::Configure();\n    } else if (Type == TitleMenuType::CCLCC) {\n      CCLCC::TitleMenu::Configure();\n    }\n\n    // if (Implementation != 0) {\n    //  Implementation->PressToStartAnimation.DurationIn =\n    //      PressToStartAnimDurationIn;\n    //  Implementation->PressToStartAnimation.DurationOut =\n    //      PressToStartAnimDurationOut;\n    //  Implementation->PressToStartAnimation.LoopMode = ReverseDirection;\n    //}\n\n    Pop();\n  }\n}\n\n}  // namespace TitleMenu\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/ui/titlemenu.h",
    "content": "#pragma once\n\n#include \"../../ui/ui.h\"\n#include \"../../spritesheet.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace TitleMenu {\n\nint constexpr MenuEntriesNumMax = 32;\n\ninline Impacto::UI::TitleMenuType Type = Impacto::UI::TitleMenuType::None;\n\ninline Sprite PressToStartSprite;\ninline Sprite MenuEntriesSprites[MenuEntriesNumMax];\ninline Sprite MenuEntriesHSprites[MenuEntriesNumMax];\n\ninline int MenuEntriesNum;\n\ninline float PressToStartAnimDurationIn;\ninline float PressToStartAnimDurationOut;\n\ninline float PressToStartX;\ninline float PressToStartY;\n\nvoid Configure();\n\n}  // namespace TitleMenu\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/ui/trophymenu.cpp",
    "content": "#include \"trophymenu.h\"\n#include \"../profile_internal.h\"\n#include \"../games/chlcc/trophymenu.h\"\n#include \"../../ui/ui.h\"\n#include \"../../log.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace TrophyMenu {\n\nusing namespace Impacto::UI;\n\nvoid Configure() {\n  if (TryPushMember(\"TrophyMenu\")) {\n    AssertIs(LUA_TTABLE);\n\n    Type = EnsureGetMember<TrophyMenuType>(\"Type\");\n\n    if (Type == TrophyMenuType::CHLCC) {\n      CHLCC::TrophyMenu::Configure();\n    }\n\n    Pop();\n  }\n}\n\n}  // namespace TrophyMenu\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/ui/trophymenu.h",
    "content": "#pragma once\n\n#include \"../../ui/ui.h\"\n#include \"../../spritesheet.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace TrophyMenu {\n\ninline Impacto::UI::TrophyMenuType Type = Impacto::UI::TrophyMenuType::None;\n\nvoid Configure();\n\n}  // namespace TrophyMenu\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/vfs.cpp",
    "content": "#include \"vfs.h\"\n#include \"profile_internal.h\"\n#include \"../io/vfs.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace Vfs {\nvoid Configure() {\n  EnsurePushMemberOfType(\"Vfs\", LUA_TTABLE);\n\n  {\n    EnsurePushMemberOfType(\"Mounts\", LUA_TTABLE);\n\n    PushInitialIndex();\n    while (PushNextTableElement() != 0) {\n      std::string name(EnsureGetKey<std::string>());\n\n      PushInitialIndex();\n      while (PushNextTableElement() != 0) {\n        std::string file(EnsureGetArrayElement<std::string>());\n        Io::VfsMount(name, file);\n        Pop();\n      }\n\n      Pop();\n    }\n\n    Pop();\n  }\n\n  Pop();\n}\n}  // namespace Vfs\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/vfs.h",
    "content": "#pragma once\n\nnamespace Impacto {\nnamespace Profile {\nnamespace Vfs {\nvoid Configure();\n}\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/vm.cpp",
    "content": "#include \"vm.h\"\n#include \"profile_internal.h\"\n#include \"game.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace Vm {\n\nvoid Configure() {\n  EnsurePushMemberOfType(\"Vm\", LUA_TTABLE);\n\n  StartScript = EnsureGetMember<uint32_t>(\"StartScript\");\n  StartScriptBuffer = EnsureGetMember<uint32_t>(\"StartScriptBuffer\");\n\n  GameInstructionSet =\n      EnsureGetMember<Impacto::Vm::InstructionSet>(\"GameInstructionSet\");\n\n  UseReturnIds = EnsureGetMember<bool>(\"UseReturnIds\");\n  TryGetMember<bool>(\"UseMsbStrings\", UseMsbStrings);\n  TryGetMember<bool>(\"UseSeparateMsbArchive\", UseSeparateMsbArchive);\n  TryGetMember<bool>(\"RestartMaskUsesThreadAlpha\", RestartMaskUsesThreadAlpha);\n\n  ScrWorkChaStructSize = EnsureGetMember<int>(\"ScrWorkChaStructSize\");\n  ScrWorkChaOffsetStructSize =\n      EnsureGetMember<int>(\"ScrWorkChaOffsetStructSize\");\n  ScrWorkBgStructSize = EnsureGetMember<int>(\"ScrWorkBgStructSize\");\n  ScrWorkBgOffsetStructSize = EnsureGetMember<int>(\"ScrWorkBgOffsetStructSize\");\n\n  if (ScreenCaptureCount > 0) {\n    ScrWorkCaptureEffectInfoStructSize =\n        EnsureGetMember<int>(\"ScrWorkCaptureEffectInfoStructSize\");\n    TryGetMember<int>(\"ScrWorkCaptureStructSize\", ScrWorkCaptureStructSize);\n    TryGetMember<int>(\"ScrWorkCaptureOffsetStructSize\",\n                      ScrWorkCaptureOffsetStructSize);\n  }\n\n  TryGetMember<int>(\"ScrWorkBgEffStructSize\", ScrWorkBgEffStructSize);\n  TryGetMember<int>(\"ScrWorkBgEffOffsetStructSize\",\n                    ScrWorkBgEffOffsetStructSize);\n\n  TryGetMember<int>(\"MaxLinkedBgBuffers\", MaxLinkedBgBuffers);\n  TryGetMember<int>(\"SystemScriptBuffer\", SystemScriptBuffer);\n\n  TryGetMember<int>(\"SpeakerPortraitsScrWorkOffset\",\n                    SpeakerPortraitsScrWorkOffset);\n\n  Pop();\n}\n\n}  // namespace Vm\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/profile/vm.h",
    "content": "#pragma once\n\n#include \"../impacto.h\"\n#include \"../vm/vm.h\"\n\nnamespace Impacto {\nnamespace Profile {\nnamespace Vm {\n\ninline uint32_t StartScript;\ninline uint32_t StartScriptBuffer;\n\ninline Impacto::Vm::InstructionSet GameInstructionSet =\n    Impacto::Vm::InstructionSet::RNE;\n\ninline bool UseReturnIds = true;\ninline bool UseMsbStrings = false;\ninline bool UseSeparateMsbArchive = false;\ninline bool RestartMaskUsesThreadAlpha = false;\n\ninline int ScrWorkChaStructSize = 0;\ninline int ScrWorkChaOffsetStructSize = 0;\ninline int ScrWorkBgStructSize = 0;\ninline int ScrWorkBgOffsetStructSize = 0;\ninline int ScrWorkCaptureStructSize = 0;\ninline int ScrWorkCaptureOffsetStructSize = 0;\ninline int ScrWorkCaptureEffectInfoStructSize = 0;\ninline int ScrWorkBgEffStructSize = 0;\ninline int ScrWorkBgEffOffsetStructSize = 0;\n\ninline int MaxLinkedBgBuffers = 1;\ninline int SystemScriptBuffer = 1;\n\ninline int SpeakerPortraitsScrWorkOffset = 8;\n\nvoid Configure();\n\n}  // namespace Vm\n}  // namespace Profile\n}  // namespace Impacto"
  },
  {
    "path": "src/renderer/3d/animation.cpp",
    "content": "#include <vector>\n\n#include \"animation.h\"\n\n#include \"../../log.h\"\n#include \"../../util.h\"\n\n#include \"../../profile/scene3d.h\"\n\nnamespace Impacto {\n\nstatic int constexpr HeaderSize = 0x30;\nstatic int constexpr HeaderDurationOffset = 0x24;\nstatic int constexpr TrackSize = 0xE8;\nstatic int constexpr TrackSize_DaSH = 0x108;\nstatic int constexpr TrackCountsOffset = 6;\nstatic int constexpr TrackCountsOffset_DaSH = 0x26;\nstatic int constexpr TrackOffsetsOffset = 0x68;\nstatic int constexpr TrackOffsetsOffset_DaSH = 0x88;\n\nenum TargetType {\n  TargetType_Bone = 0,\n  TargetType_MeshGroup = 0x8000,\n  // Our invention\n  TargetType_NotFound = 0xFFFF\n};\nenum SubTrackType {\n  STT_Visibility = 0,\n  STT_TranslateX = 1,\n  STT_TranslateY = 2,\n  STT_TranslateZ = 3,\n  STT_RotateX = 4,\n  STT_RotateY = 5,\n  STT_RotateZ = 6,\n  STT_ScaleX = 7,\n  STT_ScaleY = 8,\n  STT_ScaleZ = 9,\n};\n\nusing namespace Impacto::Io;\n\nstruct Target {\n  uint8_t Name[32];\n  TargetType Type;\n  uint16_t Id;\n};\n\nTarget GetTarget(Stream* stream, Model* model) {\n  Target result;\n  result.Type = TargetType_NotFound;\n\n  if (Profile::Scene3D::Version == LKMVersion::DaSH) {\n    stream->Read(result.Name, 32);\n\n    stream->Seek(2 * sizeof(uint16_t), RW_SEEK_CUR);\n\n    std::string nameStr = std::string((char*)result.Name);\n    if (model->NamedBones.count(nameStr) != 0) {\n      result.Type = TargetType_Bone;\n      result.Id = model->NamedBones[nameStr];\n    } else if (model->NamedMeshGroups.count(nameStr) != 0) {\n      result.Type = TargetType_MeshGroup;\n      result.Id = (uint16_t)model->NamedMeshGroups[nameStr];\n    }\n  } else {\n    memset(result.Name, 0, sizeof(result.Name));\n\n    result.Id = ReadLE<uint16_t>(stream);\n    uint16_t targetType = ReadLE<uint16_t>(stream);\n    assert(targetType == TargetType_Bone || targetType == TargetType_MeshGroup);\n    result.Type = (TargetType)targetType;\n  }\n\n  return result;\n}\n\nModelAnimation* ModelAnimation::Load(Stream* stream, Model* model,\n                                     int16_t animId) {\n  int trackSize, trackCountsOffset, trackOffsetsOffset;\n  if (Profile::Scene3D::Version == LKMVersion::DaSH) {\n    trackSize = TrackSize_DaSH;\n    trackCountsOffset = TrackCountsOffset_DaSH;\n    trackOffsetsOffset = TrackOffsetsOffset_DaSH;\n  } else {\n    trackSize = TrackSize;\n    trackCountsOffset = TrackCountsOffset;\n    trackOffsetsOffset = TrackOffsetsOffset;\n  }\n\n  ModelAnimation* result = new ModelAnimation;\n  result->Id = animId;\n  result->Name = stream->Meta.FileName;\n\n  ImpLogSlow(LogLevel::Trace, LogChannel::ModelLoad,\n             \"Loading animation {:d} ({:s}) for model {:d}\\n\", animId,\n             result->Name, model->Id);\n\n  stream->Seek(HeaderDurationOffset, RW_SEEK_SET);\n  result->Duration =\n      ReadLE<float>(stream) / Profile::Scene3D::AnimationDesignFrameRate;\n  uint32_t trackCount = ReadLE<uint32_t>(stream);\n  uint32_t tracksOffset = ReadLE<uint32_t>(stream);\n\n  ImpLogSlow(LogLevel::Trace, LogChannel::ModelLoad,\n             \"Duration (s): {:f}, track count: {:d}, tracksOffset: 0x{:08x}\\n\",\n             result->Duration, trackCount, tracksOffset);\n\n  // Offset into result->CoordKeyframes[]\n  int currentCoordOffset = 0;\n\n  // Input data is bad and sometimes contains more than one track for the same\n  // ID (especially 0). The reference implementation ignores everything after\n  // the first track for an ID. We need to do that to, otherwise glitches e.g.\n  // on c002_030.\n  // TODO: Is this per-track (current implementation) or per-*subtrack*? E.g.\n  // can there be a track specifying only TranslateX for a bone and another\n  // specifying only TranslateY?\n  int32_t TrackForBone[ModelMaxBonesPerModel];\n  memset(TrackForBone, 0xFF, sizeof(TrackForBone));\n\n  // Only get coord track counts/offsets and other simple data\n  for (uint32_t i = 0; i < trackCount; i++) {\n    ImpLogSlow(LogLevel::Trace, LogChannel::ModelLoad,\n               \"Pass 1 for track {:d}\\n\", i);\n\n    stream->Seek(tracksOffset + trackSize * i, RW_SEEK_SET);\n    Target target = GetTarget(stream, model);\n    if (target.Type == TargetType_Bone) {\n      if (TrackForBone[target.Id] != -1) {\n        ImpLogSlow(LogLevel::Trace, LogChannel::ModelLoad,\n                   \"Skipping duplicate track {:d} for bone {:d}\\n\", i,\n                   target.Id);\n        continue;\n      }\n      ImpLogSlow(LogLevel::Trace, LogChannel::ModelLoad,\n                 \"Track {:d} is bone {:d}\\n\", i, target.Id);\n\n      BoneTrack* track = &result->BoneTracks[result->BoneTrackCount];\n      memcpy(track->Name, target.Name, sizeof(target.Name));\n\n      track->Bone = target.Id;\n      TrackForBone[target.Id] = i;\n\n      uint32_t seekPos = tracksOffset + trackSize * i;\n      // Skip name\n      if (Profile::Scene3D::Version == LKMVersion::DaSH) seekPos += 32;\n      // Skip id, targetType, unknown ushort and visibility\n      seekPos += (4 * sizeof(uint16_t));\n      stream->Seek(seekPos, RW_SEEK_SET);\n\n      // Read coord offsets/counts, so far, so normal...\n\n      ReadArrayLE<(BKT_Rotate - BKT_TranslateX)>(\n          track->KeyCounts + BKT_TranslateX, stream);\n      for (int j = BKT_TranslateX; j < BKT_Rotate; j++) {\n        track->KeyOffsets[j] = currentCoordOffset;\n        currentCoordOffset += track->KeyCounts[j];\n        ImpLogSlow(LogLevel::Trace, LogChannel::ModelLoad,\n                   \"Subtrack {:d}: count {:d} offset 0x{:08x}\\n\", j,\n                   track->KeyCounts[j], track->KeyOffsets[j]);\n      }\n\n      // Skip rotates\n      stream->Seek(3 * sizeof(uint16_t), RW_SEEK_CUR);\n\n      ReadArrayLE<(BKT_Count - BKT_ScaleX)>(track->KeyCounts + BKT_ScaleX,\n                                            stream);\n      for (int j = BKT_ScaleX; j < BKT_Count; j++) {\n        track->KeyOffsets[j] = currentCoordOffset;\n        currentCoordOffset += track->KeyCounts[j];\n        ImpLogSlow(LogLevel::Trace, LogChannel::ModelLoad,\n                   \"Subtrack {:d}: count {:d} offset 0x{:08x}\\n\", j,\n                   track->KeyCounts[j], track->KeyOffsets[j]);\n      }\n\n      result->BoneTrackCount++;\n    } else if (target.Type == TargetType_MeshGroup) {\n      ImpLogSlow(LogLevel::Trace, LogChannel::ModelLoad,\n                 \"Track {:d} is mesh group {:d}\\n\", i, target.Id);\n\n      // Mesh group track\n      std::vector<Mesh*> meshes;\n      for (uint32_t j = 0; j < model->MeshCount; j++) {\n        if (model->Meshes[j].GroupId == target.Id)\n          meshes.push_back(&model->Meshes[j]);\n      }\n\n      uint32_t seekPos = tracksOffset + trackSize * i;\n      // Skip name\n      if (Profile::Scene3D::Version == LKMVersion::DaSH) seekPos += 32;\n      // Skip id, targetType, unknown ushort\n      seekPos += 3 * sizeof(uint16_t);\n      stream->Seek(seekPos, RW_SEEK_SET);\n\n      uint16_t visibilityCount = ReadLE<uint16_t>(stream);\n      int visibilityOffset = currentCoordOffset;\n      currentCoordOffset += visibilityCount;\n      ImpLogSlow(LogLevel::Trace, LogChannel::ModelLoad,\n                 \"Visibility count {:d} offset 0x{:08x}\\n\", visibilityCount,\n                 visibilityOffset);\n\n      stream->Seek(0x1E, RW_SEEK_CUR);\n\n      uint16_t morphTargetCount = ReadLE<uint16_t>(stream);\n      ImpLogSlow(LogLevel::Trace, LogChannel::ModelLoad,\n                 \"Morph target count {:d}\\n\", morphTargetCount);\n      uint16_t morphTargetIds[AnimMaxMorphTargetsPerTrack];\n      ReadArrayLE(morphTargetIds, stream, morphTargetCount);\n\n      seekPos = tracksOffset + trackSize * i;\n      // Skip name\n      if (Profile::Scene3D::Version == LKMVersion::DaSH) seekPos += 32;\n      seekPos += 0x48;\n      stream->Seek(seekPos, RW_SEEK_SET);\n\n      uint16_t morphInfluenceCounts[AnimMaxMorphTargetsPerTrack];\n      int morphInfluenceOffsets[AnimMaxMorphTargetsPerTrack];\n      ReadArrayLE(morphInfluenceCounts, stream, morphTargetCount);\n      for (int j = 0; j < morphTargetCount; j++) {\n        morphInfluenceOffsets[j] = currentCoordOffset;\n        currentCoordOffset += morphInfluenceCounts[j];\n        ImpLogSlow(LogLevel::Trace, LogChannel::ModelLoad,\n                   \"Morph {:d}: influence count {:d} offset 0x{:08x}\\n\", j,\n                   morphInfluenceCounts[j], morphInfluenceOffsets[j]);\n      }\n\n      for (size_t j = 0; j < meshes.size(); j++) {\n        Mesh* mesh = meshes[j];\n        MeshTrack* track = &result->MeshTracks[result->MeshTrackCount + j];\n        memcpy(track->Name, target.Name, sizeof(target.Name));\n        track->Mesh = (uint16_t)mesh->Id;\n        ImpLogSlow(LogLevel::Trace, LogChannel::ModelLoad,\n                   \"Mesh group {:d} <= mesh {:d}\\n\", target.Id, mesh->Id);\n\n        track->KeyCounts[MKT_Visible] = visibilityCount;\n        track->KeyOffsets[MKT_Visible] = visibilityOffset;\n        track->MorphTargetCount = morphTargetCount;\n        memcpy(track->KeyCounts + MKT_MorphInfluenceStart, morphInfluenceCounts,\n               morphTargetCount * sizeof(uint16_t));\n        memcpy(track->KeyOffsets + MKT_MorphInfluenceStart,\n               morphInfluenceOffsets, morphTargetCount * sizeof(int));\n        memcpy(track->MorphTargetIds, morphTargetIds,\n               morphTargetCount * sizeof(uint16_t));\n      }\n      result->MeshTrackCount += (int)meshes.size();\n    }\n  }\n\n  result->CoordKeyframeCount = currentCoordOffset;\n  result->CoordKeyframes = (CoordKeyframe*)malloc(sizeof(CoordKeyframe) *\n                                                  result->CoordKeyframeCount);\n\n  ImpLogSlow(LogLevel::Trace, LogChannel::ModelLoad,\n             \"---\\nTotal coord keyframe count: {:d}\\n---\\n\",\n             result->CoordKeyframeCount);\n\n  //\n  // Interleave rotations. Fun!\n  //\n  // Background: We need rotations as quaternions to interpolate between them\n  // properly. We generate one quaternion keyframe for every time x, y or z\n  // rotation changes. This also means that if e.g. y changes after x, we\n  // interpolate across the x rotation first, then the y rotation (though if\n  // they change on the same frame, we interpolate them simultaneously). Not\n  // ideal, but best we can do without glitches, and for R;NE's animations it\n  // looks fairly smooth in practice.\n  //\n  std::vector<std::vector<QuatKeyframe>> rotationTracks;\n  rotationTracks.reserve(result->BoneTrackCount);\n\n  result->QuatKeyframeCount = 0;\n\n  int currentBoneTrack = 0;\n  for (uint32_t i = 0; i < trackCount; i++) {\n    uint32_t seekPos = tracksOffset + trackSize * i;\n    stream->Seek(seekPos, RW_SEEK_SET);\n    Target target = GetTarget(stream, model);\n    if (target.Type == TargetType_Bone) {\n      if (static_cast<int32_t>(i) != TrackForBone[target.Id]) continue;\n\n      ImpLogSlow(LogLevel::Trace, LogChannel::ModelLoad,\n                 \"Interleaving rotations track {:d} bone {:d}\\n\", i, target.Id);\n      BoneTrack* track = &result->BoneTracks[currentBoneTrack];\n\n      std::vector<QuatKeyframe> rotationTrack;\n\n      seekPos = tracksOffset + trackSize * i;\n      seekPos += (trackCountsOffset + STT_RotateX * sizeof(uint16_t));\n      stream->Seek(seekPos, RW_SEEK_SET);\n\n      uint16_t rawRotXCount = ReadLE<uint16_t>(stream);\n      uint16_t rawRotYCount = ReadLE<uint16_t>(stream);\n      uint16_t rawRotZCount = ReadLE<uint16_t>(stream);\n\n      ImpLogSlow(LogLevel::Trace, LogChannel::ModelLoad,\n                 \"rawRotXCount={:d}, rawRotYCount={:d}, rawRotZCount={:d}\\n\",\n                 rawRotXCount, rawRotYCount, rawRotZCount);\n      rotationTrack.reserve(rawRotXCount + rawRotYCount + rawRotZCount);\n\n      seekPos = tracksOffset + trackSize * i;\n      seekPos += (trackOffsetsOffset + STT_RotateX * sizeof(uint32_t));\n      stream->Seek(seekPos, RW_SEEK_SET);\n\n      int rawRotXOffset = ReadLE<int>(stream) + HeaderSize;\n      int rawRotYOffset = ReadLE<int>(stream) + HeaderSize;\n      int rawRotZOffset = ReadLE<int>(stream) + HeaderSize;\n\n      ImpLogSlow(LogLevel::Trace, LogChannel::ModelLoad,\n                 \"rawRotXOffset={:d}, rawRotYOffset={:d}, rawRotZOffset={:d}\\n\",\n                 rawRotXOffset, rawRotYOffset, rawRotZOffset);\n\n      CoordKeyframe* rotXBuffer =\n          (CoordKeyframe*)malloc(sizeof(CoordKeyframe) * rawRotXCount);\n      CoordKeyframe* rotYBuffer =\n          (CoordKeyframe*)malloc(sizeof(CoordKeyframe) * rawRotYCount);\n      CoordKeyframe* rotZBuffer =\n          (CoordKeyframe*)malloc(sizeof(CoordKeyframe) * rawRotZCount);\n      stream->Seek(rawRotXOffset, RW_SEEK_SET);\n      ReadArrayLE((float*)rotXBuffer, stream, 2 * rawRotXCount);\n      stream->Seek(rawRotYOffset, RW_SEEK_SET);\n      ReadArrayLE((float*)rotYBuffer, stream, 2 * rawRotYCount);\n      stream->Seek(rawRotZOffset, RW_SEEK_SET);\n      ReadArrayLE((float*)rotZBuffer, stream, 2 * rawRotZCount);\n\n      glm::vec3 currentEuler = glm::vec3(0.0f);\n      // The defaults are 0, not BaseRotation, yes, really...\n\n      uint16_t nextX = 0;\n      uint16_t nextY = 0;\n      uint16_t nextZ = 0;\n\n      float currentTime = -1;\n\n      while (nextX < rawRotXCount || nextY < rawRotYCount ||\n             nextZ < rawRotZCount) {\n        float nextTime = FLT_MAX;\n\n        // Find lowest next time\n        if (nextX < rawRotXCount) {\n          if (rotXBuffer[nextX].Time > currentTime &&\n              rotXBuffer[nextX].Time < nextTime) {\n            nextTime = rotXBuffer[nextX].Time;\n          }\n        }\n        if (nextY < rawRotYCount) {\n          if (rotYBuffer[nextY].Time > currentTime &&\n              rotYBuffer[nextY].Time < nextTime) {\n            nextTime = rotYBuffer[nextY].Time;\n          }\n        }\n        if (nextZ < rawRotZCount) {\n          if (rotZBuffer[nextZ].Time > currentTime &&\n              rotZBuffer[nextZ].Time < nextTime) {\n            nextTime = rotZBuffer[nextZ].Time;\n          }\n        }\n\n        // Advance each series to that time\n        if (nextX < rawRotXCount) {\n          if (rotXBuffer[nextX].Time == nextTime) {\n            currentEuler.x = rotXBuffer[nextX].Value;\n            nextX++;\n          }\n        }\n        if (nextY < rawRotYCount) {\n          if (rotYBuffer[nextY].Time == nextTime) {\n            currentEuler.y = rotYBuffer[nextY].Value;\n            nextY++;\n          }\n        }\n        if (nextZ < rawRotZCount) {\n          if (rotZBuffer[nextZ].Time == nextTime) {\n            currentEuler.z = rotZBuffer[nextZ].Value;\n            nextZ++;\n          }\n        }\n\n        currentTime = nextTime;\n        QuatKeyframe key;\n        key.Time = currentTime / Profile::Scene3D::AnimationDesignFrameRate;\n        eulerZYXToQuat(&currentEuler, &key.Value);\n        rotationTrack.push_back(key);\n\n        ImpLogSlow(LogLevel::Trace, LogChannel::ModelLoad,\n                   \"QuatKeyframe with z={:f}, y={:f}, x={:f} - nextX={:d}, \"\n                   \"nextY={:d}, \"\n                   \"nextZ={:d} - currentTime={:f}\\n\",\n                   currentEuler.z, currentEuler.y, currentEuler.x, nextX, nextY,\n                   nextZ, currentTime);\n      }\n\n      free(rotXBuffer);\n      free(rotYBuffer);\n      free(rotZBuffer);\n\n      rotationTrack.shrink_to_fit();\n      rotationTracks.push_back(rotationTrack);\n      track->KeyCounts[BKT_Rotate] = (uint16_t)rotationTrack.size();\n      track->KeyOffsets[BKT_Rotate] = result->QuatKeyframeCount;\n      result->QuatKeyframeCount += track->KeyCounts[BKT_Rotate];\n\n      ImpLogSlow(LogLevel::Trace, LogChannel::ModelLoad,\n                 \"QuatKeyframe count {:d} offset 0x{:08x}\\n\",\n                 track->KeyCounts[BKT_Rotate], track->KeyOffsets[BKT_Rotate]);\n\n      currentBoneTrack++;\n    }\n  }\n\n  result->QuatKeyframes =\n      (QuatKeyframe*)malloc(sizeof(QuatKeyframe) * result->QuatKeyframeCount);\n\n  ImpLogSlow(LogLevel::Trace, LogChannel::ModelLoad,\n             \"---\\nTotal quat keyframe count: {:d}\\n---\\n\",\n             result->QuatKeyframeCount);\n\n  // Now that we have the buffers, fill with data\n\n  currentBoneTrack = 0;\n  currentCoordOffset = 0;\n  for (uint32_t i = 0; i < trackCount; i++) {\n    uint32_t seekPos = tracksOffset + trackSize * i;\n    stream->Seek(seekPos, RW_SEEK_SET);\n    Target target = GetTarget(stream, model);\n    if (target.Type == TargetType_Bone) {\n      if (static_cast<int32_t>(i) != TrackForBone[target.Id]) continue;\n\n      BoneTrack* track = &result->BoneTracks[currentBoneTrack];\n\n      // Coords\n      for (int j = STT_Visibility; j <= STT_ScaleZ; j++) {\n        if (j == STT_Visibility || j == STT_RotateX || j == STT_RotateY ||\n            j == STT_RotateZ)\n          continue;\n\n        seekPos = tracksOffset + trackSize * i;\n        seekPos += trackCountsOffset + sizeof(uint16_t) * j;\n        stream->Seek(seekPos, RW_SEEK_SET);\n        uint16_t currentKeyframeCount = ReadLE<uint16_t>(stream);\n        seekPos = tracksOffset + trackSize * i;\n        seekPos += trackOffsetsOffset + sizeof(uint32_t) * j;\n        stream->Seek(seekPos, RW_SEEK_SET);\n        uint32_t currentKeyframeOffset = ReadLE<uint32_t>(stream);\n\n        stream->Seek(HeaderSize + currentKeyframeOffset, RW_SEEK_SET);\n\n        ReadArrayLE((float*)(result->CoordKeyframes + currentCoordOffset),\n                    stream, 2 * currentKeyframeCount);\n        for (float* time =\n                 (float*)(result->CoordKeyframes + currentCoordOffset);\n             time < (float*)(result->CoordKeyframes + currentCoordOffset +\n                             currentKeyframeCount);\n             time += 2) {\n          *time /= Profile::Scene3D::AnimationDesignFrameRate;\n        }\n        currentCoordOffset += currentKeyframeCount;\n      }\n\n      // Already have rotate keyframe data\n      QuatKeyframe* quatSrc = rotationTracks[currentBoneTrack].data();\n      memcpy(result->QuatKeyframes + track->KeyOffsets[BKT_Rotate], quatSrc,\n             track->KeyCounts[BKT_Rotate] * sizeof(QuatKeyframe));\n\n      // Animator currently needs this\n      for (int j = BKT_TranslateX; j < BKT_Count; j++) {\n        if (track->KeyCounts[j]) {\n          if (j == BKT_Rotate) {\n            // assert(result->QuatKeyframes[track->KeyOffsets[j]].Time == 0.0f);\n            // This is quite far off for some stuff (e.g. a CoordKeyframe in\n            // c001_000@10_out_pokecom.lka) - may need to fix this properly\n            result->QuatKeyframes[track->KeyOffsets[j]].Time = 0.0f;\n          } else {\n            // assert(result->CoordKeyframes[track->KeyOffsets[j]].Time ==\n            // 0.0f);\n            result->CoordKeyframes[track->KeyOffsets[j]].Time = 0.0f;\n          }\n        }\n      }\n\n      currentBoneTrack++;\n    } else if (target.Type == TargetType_MeshGroup) {\n      // Mesh group track\n\n      seekPos = tracksOffset + trackSize * i;\n      // Skip id, targetType, unknown ushort\n      seekPos += trackCountsOffset;\n      stream->Seek(seekPos, RW_SEEK_SET);\n\n      uint16_t visibilityCount = ReadLE<uint16_t>(stream);\n\n      stream->Seek(0x1E, RW_SEEK_CUR);\n      uint16_t morphTargetCount = ReadLE<uint16_t>(stream);\n\n      seekPos = tracksOffset + trackSize * i;\n      // Skip name\n      if (Profile::Scene3D::Version == LKMVersion::DaSH) seekPos += 32;\n      seekPos += 0x48;\n      stream->Seek(seekPos, RW_SEEK_SET);\n\n      uint16_t morphInfluenceCounts[AnimMaxMorphTargetsPerTrack];\n      ReadArrayLE(morphInfluenceCounts, stream, morphTargetCount);\n\n      // Visibility data\n      seekPos = tracksOffset + trackSize * i;\n      seekPos += trackOffsetsOffset;\n      stream->Seek(seekPos, RW_SEEK_SET);\n      int rawVisibilityOffset = ReadLE<int>(stream);\n      stream->Seek(HeaderSize + rawVisibilityOffset, RW_SEEK_SET);\n\n      ReadArrayLE((float*)(result->CoordKeyframes + currentCoordOffset), stream,\n                  2 * visibilityCount);\n      for (float* time = (float*)(result->CoordKeyframes + currentCoordOffset);\n           time < (float*)(result->CoordKeyframes + currentCoordOffset +\n                           visibilityCount);\n           time += 2) {\n        *time /= Profile::Scene3D::AnimationDesignFrameRate;\n      }\n      currentCoordOffset += visibilityCount;\n\n      // Morph influence data\n      seekPos = tracksOffset + trackSize * i;\n      // Skip name\n      if (Profile::Scene3D::Version == LKMVersion::DaSH) seekPos += 32;\n      seekPos += 0xA8;\n      stream->Seek(seekPos, RW_SEEK_SET);\n      int rawInfluenceOffsets[16];\n      ReadArrayLE(rawInfluenceOffsets, stream, morphTargetCount);\n      for (int j = 0; j < morphTargetCount; j++) {\n        stream->Seek(HeaderSize + rawInfluenceOffsets[j], RW_SEEK_SET);\n        ReadArrayLE((float*)(result->CoordKeyframes + currentCoordOffset),\n                    stream, morphInfluenceCounts[j] * 2);\n        for (CoordKeyframe* key = result->CoordKeyframes + currentCoordOffset;\n             key < (result->CoordKeyframes + currentCoordOffset +\n                    morphInfluenceCounts[j]);\n             key++) {\n          key->Time /= Profile::Scene3D::AnimationDesignFrameRate;\n          key->Value /= 100.0f;\n        }\n        currentCoordOffset += morphInfluenceCounts[j];\n      }\n    }\n  }\n\n  // Ahhh, now fetch metadata\n\n  result->LoopEnd = result->Duration;\n\n  auto charId = Profile::Scene3D::ModelsToCharacters.find(model->Id);\n  if (charId != Profile::Scene3D::ModelsToCharacters.end()) {\n    auto const& character = Profile::Scene3D::Characters[charId->second];\n    auto animDef = character.Animations.find(animId);\n    if (animDef != character.Animations.end()) {\n      if (animDef->second.OneShot) {\n        if (animId != character.IdleAnimation) {\n          // Idle animation always loops, even if the data says otherwise...\n          result->OneShot = true;\n        }\n      } else {\n        result->LoopStart = animDef->second.LoopStart /\n                            Profile::Scene3D::AnimationDesignFrameRate;\n        if (animDef->second.LoopEnd > 0) {\n          result->LoopEnd = animDef->second.LoopEnd /\n                            Profile::Scene3D::AnimationDesignFrameRate;\n        }\n      }\n    }\n  }\n\n  return result;\n}\n\nModelAnimation::~ModelAnimation() {\n  if (CoordKeyframes) free(CoordKeyframes);\n  if (QuatKeyframes) free(QuatKeyframes);\n}\n\n}  // namespace Impacto"
  },
  {
    "path": "src/renderer/3d/animation.h",
    "content": "#pragma once\n\n#include \"../../io/stream.h\"\n\n#include \"model.h\"\n\nnamespace Impacto {\n\nint constexpr AnimMaxMorphTargetsPerTrack = 16;\n\nstruct CoordKeyframe {\n  float Time;\n  float Value;\n};\n\nstruct QuatKeyframe {\n  float Time;\n  glm::quat Value;\n};\n\nenum BoneKeyType {\n  BKT_TranslateX = 0,\n  BKT_TranslateY = 1,\n  BKT_TranslateZ = 2,\n  BKT_Rotate = 3,\n  BKT_ScaleX = 4,\n  BKT_ScaleY = 5,\n  BKT_ScaleZ = 6,\n  BKT_Count = 7\n};\n\nenum MeshKeyType {\n  MKT_Visible = 0,\n  MKT_MorphInfluenceStart = 1,\n  MKT_Count = MKT_MorphInfluenceStart + AnimMaxMorphTargetsPerTrack\n};\n\nstruct BoneTrack {\n  // DaSH addition\n  uint8_t Name[32];\n\n  uint16_t Bone;\n\n  int KeyOffsets[BKT_Count];\n  uint16_t KeyCounts[BKT_Count];\n};\n\n// Note: Unlike R;NE animation files, we keep a track per mesh, not a track per\n// mesh group\nstruct MeshTrack {\n  // DaSH addition\n  uint8_t Name[32];\n\n  uint16_t Mesh;\n\n  int KeyOffsets[MKT_Count];\n  uint16_t KeyCounts[MKT_Count];\n\n  uint16_t MorphTargetCount;\n  uint16_t MorphTargetIds[AnimMaxMorphTargetsPerTrack];\n};\n\nclass ModelAnimation {\n public:\n  static ModelAnimation* Load(Io::Stream* stream, Model* model, int16_t animId);\n  ~ModelAnimation();\n\n  // Per-model ID\n  // or global in DaSH\n  int16_t Id = 0;\n\n  std::string Name;\n\n  // Whole animation duration in seconds\n  float Duration = 0.0f;\n\n  bool OneShot = false;\n  float LoopStart = 0.0f;\n  float LoopEnd = 0.0f;\n\n  int CoordKeyframeCount = 0;\n  CoordKeyframe* CoordKeyframes = 0;\n\n  int QuatKeyframeCount = 0;\n  QuatKeyframe* QuatKeyframes = 0;\n\n  int BoneTrackCount = 0;\n  BoneTrack BoneTracks[ModelMaxBonesPerModel];\n\n  int MeshTrackCount = 0;\n  MeshTrack MeshTracks[ModelMaxMeshesPerModel];\n};\n\n}  // namespace Impacto"
  },
  {
    "path": "src/renderer/3d/camera.cpp",
    "content": "#include \"camera.h\"\n\n#include <glm/ext/matrix_transform.hpp>\n#include <glm/ext/matrix_clip_space.hpp>\n\n#include \"../../util.h\"\n#include \"../../profile/scene3d.h\"\n#include \"../../profile/game.h\"\n\nnamespace Impacto {\n\nvoid Camera::LookAt(glm::vec3 target) {\n  // for camera, need to invert this\n  CameraTransform.SetRotationFromEuler(\n      LookAtEulerZYX(target, CameraTransform.Position));\n}\n\nvoid Camera::ResetTransform() {\n  CameraTransform.Position = Profile::Scene3D::DefaultCameraPosition;\n  Up = glm::vec3(0.0f, 1.0f, 0.0f);\n  LookAt(Profile::Scene3D::DefaultCameraTarget);\n}\n\nvoid Camera::ResetPerspective() {\n  Fov = Profile::Scene3D::DefaultFov;\n  Near = 0.1f;\n  Far = 1000.0f;\n  AspectRatio = 1.0f;\n}\n\nvoid Camera::Init() {\n  ResetTransform();\n  ResetPerspective();\n  Recalculate();\n}\n\nvoid Camera::Recalculate() {\n  // X axis is flipped in DaSH, for whatever reason\n  if (Profile::Scene3D::Version == LKMVersion::DaSH) {\n    Transform xFlippedTransform = CameraTransform;\n    xFlippedTransform.Scale.x = -xFlippedTransform.Scale.x;\n    View = glm::inverse(xFlippedTransform.Matrix());\n  } else {\n    View = glm::inverse(\n        CameraTransform.Matrix());  // move the world, not the camera\n  }\n\n  if (Profile::ActiveRenderer == RendererType::Vulkan) {\n    Projection = glm::perspectiveRH_ZO(Fov, AspectRatio, Near, Far);\n    Projection[1][1] *= -1.0f;\n  } else {\n    Projection = glm::perspective(Fov, AspectRatio, Near, Far);\n  }\n  ViewProjection = Projection * View;\n}\n\n}  // namespace Impacto"
  },
  {
    "path": "src/renderer/3d/camera.h",
    "content": "#pragma once\n\n#include <glm/glm.hpp>\n\n#include \"transform.h\"\n\nnamespace Impacto {\n\nclass Camera {\n public:\n  // Rotate camera to new target while keeping position\n  void LookAt(glm::vec3 target);\n  // Reset position, direction and up\n  void ResetTransform();\n  // Reset FoV, near and far plane\n  void ResetPerspective();\n  // Reset everything to defaults and recalculate matrices\n  void Init();\n  // Recalculate matrices\n  void Recalculate();\n\n  Transform CameraTransform;\n  glm::vec3 Up;\n\n  // vertical FoV in radians\n  float Fov;\n  float Near;\n  float Far;\n\n  float AspectRatio;\n\n  glm::mat4 View;\n  glm::mat4 Projection;\n  glm::mat4 ViewProjection;\n};\n\n}  // namespace Impacto"
  },
  {
    "path": "src/renderer/3d/model.cpp",
    "content": "#include \"model.h\"\n\n#include \"animation.h\"\n#include \"../../io/vfs.h\"\n#include \"../../io/io.h\"\n#include \"../../log.h\"\n\n#include <glm/gtc/matrix_transform.hpp>\n\n#include \"../../io/memorystream.h\"\n\n#include \"../../profile/scene3d.h\"\n\nusing namespace Impacto::Io;\n\nnamespace Impacto {\n\nuint32_t constexpr ModelFileCountsOffset = 0x24;\nuint32_t constexpr ModelFileHeaderSize = 0x54;\nuint32_t constexpr ModelFileMeshInfoSize = 0x18C;\nuint32_t constexpr ModelFileMeshInfoSize_DaSH = 0x1A8;\nuint32_t constexpr ModelFileBoneSize = 0x1D0;\nuint32_t constexpr ModelFileBoneSize_DaSH = 0x1FC;\nuint32_t constexpr MorphTargetInfoSize = 16;\nuint32_t constexpr BoneBaseTransformOffset = 0x11C;\nuint32_t constexpr BoneBaseTransformOffset_DaSH = 0x13C;\nuint32_t constexpr MeshInfoCountsOffset = 0xE0;\nuint32_t constexpr MeshInfoCountsOffset_DaSH = 0xF8;\n\nbool AnimationIsBlacklisted(uint32_t modelId, int16_t animId) {\n  for (auto p : Profile::Scene3D::AnimationParseBlacklist) {\n    if (p.first == modelId && p.second == animId) return true;\n  }\n  return false;\n}\n\nvoid Model::EnumerateModels() {\n  // List models\n  // TODO: We don't need this in the game - take it out when we remove the model\n  // viewer, it's a waste of time then\n\n  g_ModelCount = 0;\n  g_BackgroundModelCount = 0;\n\n  std::map<uint32_t, std::string> listing;\n  IoError err = VfsListFiles(\"model\", listing);\n  if (err != IoError_OK) {\n    ImpLog(LogLevel::Error, LogChannel::Render,\n           \"Failed to list models from VFS: IO error {:s}\\n\", err);\n    return;\n  }\n\n  for (auto const& file : listing) {\n    if (Profile::Scene3D::ModelsToCharacters.count(file.first) != 0) {\n      g_ModelCount++;\n    } else {\n      g_BackgroundModelCount++;\n    }\n  }\n\n  g_ModelIds.reserve(g_ModelCount);\n  g_ModelNames.reserve(g_ModelCount);\n  g_BackgroundModelIds.reserve(g_BackgroundModelCount);\n  g_BackgroundModelNames.reserve(g_BackgroundModelCount);\n\n  for (auto const& file : listing) {\n    if (Profile::Scene3D::ModelsToCharacters.count(file.first) != 0) {\n      g_ModelIds.push_back(file.first);\n      g_ModelNames.push_back(file.second);\n    } else {\n      g_BackgroundModelIds.push_back(file.first);\n      g_BackgroundModelNames.push_back(file.second);\n    }\n  }\n}\n\nModel::~Model() {\n  if (VertexBuffers) free(VertexBuffers);\n  if (MorphVertexBuffers) free(MorphVertexBuffers);\n  if (Indices) free(Indices);\n  for (auto animation : Animations) {\n    if (animation.second) delete animation.second;\n  }\n}\n\nstatic IoError GetModelMountpoint(uint32_t modelId,\n                                  std::string& outMountpoint) {\n  if (Profile::Scene3D::Version == LKMVersion::DaSH) {\n    outMountpoint = \"model\";\n    return IoError_OK;\n  } else {\n    FileMeta arcMeta;\n    IoError err = VfsGetMeta(\"model\", modelId, &arcMeta);\n    if (err != IoError_OK) {\n      ImpLog(LogLevel::Error, LogChannel::ModelLoad,\n             \"Could not open model archive for {:d}\\n\", modelId);\n      return err;\n    }\n    outMountpoint = \"model_\" + arcMeta.FileName;\n    return IoError_OK;\n  }\n}\n\nstatic IoError MountModel(uint32_t modelId, std::string& outMountpoint) {\n  // DaSH doesn't have model archives\n  if (Profile::Scene3D::Version == LKMVersion::DaSH) {\n    outMountpoint = \"model\";\n    return IoError_OK;\n  }\n\n  FileMeta arcMeta;\n  IoError err = VfsGetMeta(\"model\", modelId, &arcMeta);\n  if (err != IoError_OK) {\n    ImpLog(LogLevel::Error, LogChannel::ModelLoad,\n           \"Could not open model archive for {:d}\\n\", modelId);\n    return err;\n  }\n  void* arcMem;\n  int64_t arcSz;\n  err = VfsSlurp(\"model\", modelId, arcMem, arcSz);\n  if (err != IoError_OK) {\n    ImpLog(LogLevel::Error, LogChannel::ModelLoad,\n           \"Could not open model archive for {:d}\\n\", modelId);\n    return err;\n  }\n  std::string modelMountpoint = \"model_\" + arcMeta.FileName;\n  err = VfsMountMemory(modelMountpoint, arcMeta.FileName, arcMem, arcSz, true);\n  if (err != IoError_OK) {\n    ImpLog(LogLevel::Error, LogChannel::ModelLoad,\n           \"Could not open model archive for {:d}\\n\", modelId);\n    free(arcMem);\n    return err;\n  }\n\n  outMountpoint = modelMountpoint;\n  return IoError_OK;\n}\n\nstatic void UnmountModel(uint32_t modelId) {\n  if (Profile::Scene3D::Version == LKMVersion::DaSH) return;\n\n  FileMeta arcMeta;\n  IoError err = VfsGetMeta(\"model\", modelId, &arcMeta);\n  if (err != IoError_OK) {\n    ImpLog(\n        LogLevel::Error, LogChannel::ModelLoad,\n        \"Could not unmount model archive for {:d} (this should never happen)\\n\",\n        modelId);\n    return;\n  }\n  VfsUnmount(\"model_\" + arcMeta.FileName, arcMeta.FileName);\n}\n\nstatic IoError SlurpModel(uint32_t modelId, void*& outMemory,\n                          int64_t& outSize) {\n  if (Profile::Scene3D::Version == LKMVersion::DaSH) {\n    return VfsSlurp(\"model\", modelId, outMemory, outSize);\n  } else {\n    std::string mountpoint;\n    IoError err = GetModelMountpoint(modelId, mountpoint);\n    if (err != IoError_OK) return err;\n    return VfsSlurp(mountpoint, 0, outMemory, outSize);\n  }\n}\n\nstatic IoError SlurpAnim(uint32_t modelId, int16_t animId, void*& outMemory,\n                         int64_t& outSize, std::string& outName) {\n  std::string mountpoint;\n  IoError err;\n\n  if (Profile::Scene3D::Version == LKMVersion::DaSH) {\n    mountpoint = \"motion\";\n  } else {\n    err = GetModelMountpoint(modelId, mountpoint);\n    if (err != IoError_OK) return err;\n  }\n\n  FileMeta animMeta;\n  err = VfsGetMeta(mountpoint, animId, &animMeta);\n  if (err != IoError_OK) {\n    ImpLog(LogLevel::Error, LogChannel::ModelLoad,\n           \"Could not find animation file %h for model {:d}\\n\", animId,\n           modelId);\n    return err;\n  }\n  outName = animMeta.FileName;\n\n  return VfsSlurp(mountpoint, animId, outMemory, outSize);\n}\n\nModel* Model::Load(uint32_t modelId) {\n  uint32_t meshInfoSize, meshInfoCountsOffset, boneSize,\n      boneBaseTransformOffset;\n  if (Profile::Scene3D::Version == LKMVersion::DaSH) {\n    meshInfoSize = ModelFileMeshInfoSize_DaSH;\n    meshInfoCountsOffset = MeshInfoCountsOffset_DaSH;\n    boneSize = ModelFileBoneSize_DaSH;\n    boneBaseTransformOffset = BoneBaseTransformOffset_DaSH;\n  } else {\n    meshInfoSize = ModelFileMeshInfoSize;\n    meshInfoCountsOffset = MeshInfoCountsOffset;\n    boneSize = ModelFileBoneSize;\n    boneBaseTransformOffset = BoneBaseTransformOffset;\n  }\n\n  ImpLogSlow(LogLevel::Debug, LogChannel::ModelLoad, \"Loading model {:d}\\n\",\n             modelId);\n\n  std::string modelMountpoint;\n  IoError err = MountModel(modelId, modelMountpoint);\n  if (err != IoError_OK) {\n    return NULL;\n  }\n\n  void* file;\n  int64_t fileSize;\n  err = SlurpModel(modelId, file, fileSize);\n  if (err != IoError_OK) {\n    ImpLog(LogLevel::Error, LogChannel::ModelLoad,\n           \"Could not read model file for {:d}\\n\", modelId);\n    UnmountModel(modelId);\n    return NULL;\n  }\n\n  // We read the whole file at once for speed, but we still want a stream for\n  // convenience\n  Stream* stream = new MemoryStream(file, fileSize, true);\n\n  Model* result = new Model;\n  result->Id = modelId;\n  uint32_t type = ReadLE<uint32_t>(stream);\n  if (type == ModelType_DaSH_Unspecified) {\n    assert(Profile::Scene3D::Version == LKMVersion::DaSH);\n    if (Profile::Scene3D::ModelsToCharacters.count(modelId) != 0) {\n      type = ModelType_Character;\n    } else {\n      type = ModelType_Background;\n    }\n  }\n  assert(type == ModelType_Background || type == ModelType_Character);\n  result->Type = (ModelType)type;\n\n  // Read model resource counts and offsets\n  stream->Seek(ModelFileCountsOffset, RW_SEEK_SET);\n  result->MeshCount = ReadLE<uint32_t>(stream);\n  assert(result->MeshCount <= ModelMaxMeshesPerModel);\n  result->BoneCount = ReadLE<uint32_t>(stream);\n  if (result->Type == ModelType_Background) {\n    // Backgrounds have a skeleton, but it seems to be unused\n    result->BoneCount = 0;\n  }\n  assert(result->BoneCount <= ModelMaxBonesPerModel);\n  result->TextureCount = ReadLE<uint32_t>(stream);\n  assert(result->TextureCount <= ModelMaxTexturesPerModel);\n  result->MorphTargetCount = ReadLE<uint32_t>(stream);\n  assert(\n      result->MorphTargetCount <= ModelMaxMorphTargetsPerModel &&\n      (result->Type == ModelType_Character || result->MorphTargetCount == 0));\n\n  ImpLog(LogLevel::Debug, LogChannel::ModelLoad,\n         \"Model {:d} MeshCount={:d} BoneCount={:d} TextureCount={:d} \"\n         \"MorphTargetCount={:d}\\n\",\n         modelId, result->MeshCount, result->BoneCount, result->TextureCount,\n         result->MorphTargetCount);\n\n  uint32_t MeshInfosOffset = ReadLE<uint32_t>(stream);\n  uint32_t BonesOffset = ReadLE<uint32_t>(stream);\n  uint32_t TexturesOffset = ReadLE<uint32_t>(stream);\n  uint32_t MorphVertexOffset = ReadLE<uint32_t>(stream);\n  uint32_t MorphTargetOffset = ReadLE<uint32_t>(stream);\n\n  result->VertexCount = 0;\n  result->IndexCount = 0;\n\n  // Accumulate per-mesh vertex buffer / index counts\n  for (uint32_t i = 0; i < result->MeshCount; i++) {\n    int64_t seekPos = MeshInfosOffset + meshInfoSize * i;\n    seekPos += meshInfoCountsOffset;\n    stream->Seek(seekPos, RW_SEEK_SET);\n    result->VertexCount += ReadLE<int32_t>(stream);\n    result->IndexCount += ReadLE<int32_t>(stream);\n  }\n\n  if (result->Type == ModelType_Character) {\n    if (Profile::Scene3D::Version == LKMVersion::DaSH) {\n      result->VertexBuffers =\n          calloc(result->VertexCount, sizeof(VertexBufferDaSH));\n    } else {\n      result->VertexBuffers = calloc(result->VertexCount, sizeof(VertexBuffer));\n    }\n  } else if (result->Type == ModelType_Background) {\n    result->VertexBuffers = calloc(result->VertexCount, sizeof(BgVertexBuffer));\n  }\n  result->Indices = (uint16_t*)calloc(result->IndexCount, sizeof(uint16_t));\n\n  // Read mesh attributes\n  for (uint32_t i = 0; i < result->MeshCount; i++) {\n    stream->Seek(MeshInfosOffset + meshInfoSize * i, RW_SEEK_SET);\n    Mesh* mesh = &result->Meshes[i];\n\n    if (Profile::Scene3D::Version == LKMVersion::DaSH) {\n      stream->Read(mesh->Name, 32);\n    } else {\n      memset(mesh->Name, 0, sizeof(mesh->Name));\n    }\n\n    mesh->Id = i;\n    mesh->GroupId = ReadLE<int32_t>(stream);\n\n    if (Profile::Scene3D::Version == LKMVersion::DaSH) {\n      std::string nameStr = std::string((char*)mesh->Name);\n      assert(result->NamedMeshGroups.count(nameStr) == 0 ||\n             result->NamedMeshGroups[nameStr] == mesh->GroupId ||\n             nameStr == \"\");\n      result->NamedMeshGroups[nameStr] = mesh->GroupId;\n    }\n\n    mesh->MeshBone = ReadLE<int16_t>(stream);\n    stream->Seek(9, RW_SEEK_CUR);\n    mesh->MorphTargetCount = ReadU8(stream);\n    assert(mesh->MorphTargetCount <= ModelMaxMorphTargetsPerMesh);\n    stream->Read(mesh->MorphTargetIds, ModelMaxMorphTargetsPerMesh);\n\n    int64_t seekPos = (Profile::Scene3D::Version == LKMVersion::DaSH)\n                          ? ModelUnknownsAfterMorphTargets_DaSH\n                          : ModelUnknownsAfterMorphTargets;\n    // Skip translation, rotation and scale (these sometimes don't match the\n    // matrix below, which is authoritative)\n    seekPos += 3 * sizeof(glm::vec3);\n    stream->Seek(seekPos, RW_SEEK_CUR);\n\n    glm::mat4 modelMtx;\n    ReadMat4LE(&modelMtx, stream);\n    mesh->ModelTransform = Transform(modelMtx);\n    // Skip model matrix inverse\n    stream->Seek(sizeof(glm::mat4), RW_SEEK_CUR);\n\n    mesh->VertexCount = ReadLE<int32_t>(stream);\n    mesh->IndexCount = ReadLE<int32_t>(stream);\n\n    stream->Seek(0x68, RW_SEEK_CUR);\n    if (Profile::Scene3D::Version == LKMVersion::DaSH) {\n      // Don't ask me why there's a float for this\n      mesh->HasShadowColorMap = ReadLE<float>(stream) != 0.0f;\n    } else {\n      stream->Seek(4, RW_SEEK_CUR);\n      mesh->HasShadowColorMap = false;\n    }\n    mesh->Opacity = ReadLE<float>(stream);\n\n    stream->Seek(0x14, RW_SEEK_CUR);\n    ReadArrayLE<TT_Count>(mesh->Maps, stream);\n\n    mesh->Flags = ReadLE<uint32_t>(stream);\n\n    // Choose material\n    if (result->Type == ModelType_Background) {\n      mesh->Material = MT_Background;\n    } else if (Profile::Scene3D::Version == LKMVersion::DaSH) {\n      stream->Seek(3 * 4, RW_SEEK_CUR);\n      mesh->Material = (MaterialType)ReadLE<int>(stream);\n      assert(mesh->Material == MT_DaSH_Generic ||\n             mesh->Material == MT_DaSH_Eye || mesh->Material == MT_DaSH_Face ||\n             mesh->Material == MT_DaSH_Skin);\n    } else {\n      mesh->Material =\n          mesh->Maps[TT_Eye_WhiteColorMap] >= 0 ? MT_Eye : MT_Generic;\n    }\n  }\n\n  // Read vertex buffers and indices\n  int32_t CurrentVertexOffset = 0;\n  int32_t CurrentIndexOffset = 0;\n  for (uint32_t i = 0; i < result->MeshCount; i++) {\n    int64_t seekPos = MeshInfosOffset + meshInfoSize * i;\n    Mesh* mesh = &result->Meshes[i];\n    mesh->VertexOffset = CurrentVertexOffset;\n    mesh->IndexOffset = CurrentIndexOffset;\n\n    seekPos += meshInfoCountsOffset + 4 * sizeof(int);\n    stream->Seek(seekPos, RW_SEEK_SET);\n    int32_t RawVertexOffset = ModelFileHeaderSize + ReadLE<int32_t>(stream);\n    int32_t RawIndexOffset = ModelFileHeaderSize + ReadLE<int32_t>(stream);\n\n    mesh->UsedBones = ReadLE<uint32_t>(stream);\n    assert(mesh->UsedBones <= ModelMaxBonesPerMesh);\n    ReadArrayLE(mesh->BoneMap, stream, mesh->UsedBones);\n\n    // Read vertex buffers\n    stream->Seek(RawVertexOffset, RW_SEEK_SET);\n    if (result->Type == ModelType_Character) {\n      if (Profile::Scene3D::Version == LKMVersion::DaSH) {\n        for (uint32_t j = 0; j < mesh->VertexCount; j++) {\n          VertexBufferDaSH* vertex =\n              &((VertexBufferDaSH*)result->VertexBuffers)[CurrentVertexOffset];\n          CurrentVertexOffset++;\n          // Position, then Normal\n          ReadArrayLE<3 * 2>((float*)&vertex->Position, stream);\n          // vec3 unk1, vec4 unk2\n          stream->Seek(7 * sizeof(float), RW_SEEK_CUR);\n          // UV\n          ReadVec2LE(&vertex->UV, stream);\n\n          if (mesh->UsedBones > 0) {\n            stream->Read(vertex->BoneIndices, 4);\n\n            ReadVec4LE(&vertex->BoneWeights, stream);\n          } else {\n            // TODO skinned/unskinned mesh distinction?\n            *(int*)vertex->BoneIndices = 0;\n\n            vertex->BoneWeights = glm::vec4(1.0f, 0.0f, 0.0f, 0.0f);\n\n            stream->Seek(4 * sizeof(uint8_t) + 4 * sizeof(float), RW_SEEK_CUR);\n          }\n        }\n      } else {\n        for (uint32_t j = 0; j < mesh->VertexCount; j++) {\n          VertexBuffer* vertex =\n              &((VertexBuffer*)result->VertexBuffers)[CurrentVertexOffset];\n          CurrentVertexOffset++;\n          // Position, then Normal, then UV\n          ReadArrayLE<3 * 2 + 2 * 1>((float*)&vertex->Position, stream);\n\n          if (mesh->UsedBones > 0) {\n            stream->Read(vertex->BoneIndices, 4);\n\n            ReadVec4LE(&vertex->BoneWeights, stream);\n          } else {\n            // TODO skinned/unskinned mesh distinction?\n            *(int*)vertex->BoneIndices = 0;\n\n            vertex->BoneWeights = glm::vec4(1.0f, 0.0f, 0.0f, 0.0f);\n\n            stream->Seek(4 * sizeof(uint8_t) + 4 * sizeof(float), RW_SEEK_CUR);\n          }\n        }\n      }\n    } else if (result->Type == ModelType_Background) {\n      for (uint32_t j = 0; j < mesh->VertexCount; j++) {\n        BgVertexBuffer* vertex =\n            &((BgVertexBuffer*)result->VertexBuffers)[CurrentVertexOffset];\n        CurrentVertexOffset++;\n        // Position, then UV\n        ReadArrayLE<3 * 1 + 2 * 1>((float*)&vertex->Position, stream);\n      }\n    }\n\n    // Read indices\n    stream->Seek(RawIndexOffset, RW_SEEK_SET);\n    ReadArrayLE(result->Indices + CurrentIndexOffset, stream, mesh->IndexCount);\n    CurrentIndexOffset += mesh->IndexCount;\n  }\n\n  // Read skeleton\n  for (uint32_t i = 0; i < result->BoneCount; i++) {\n    uint32_t seekPos = BonesOffset + boneSize * i;\n    stream->Seek(seekPos, RW_SEEK_SET);\n    StaticBone* bone = &result->Bones[i];\n\n    if (Profile::Scene3D::Version == LKMVersion::DaSH) {\n      stream->Read(bone->Name, 32);\n    } else {\n      memset(bone->Name, 0, sizeof(bone->Name));\n    }\n\n    bone->Id = ReadLE<int16_t>(stream);\n\n    if (Profile::Scene3D::Version == LKMVersion::DaSH) {\n      std::string nameStr = std::string((char*)bone->Name);\n      assert(result->NamedBones.count(nameStr) == 0 ||\n             result->NamedBones[nameStr] == bone->Id || nameStr == \"\");\n      result->NamedBones[nameStr] = bone->Id;\n    }\n\n    stream->Seek(2, RW_SEEK_CUR);\n    bone->Parent = ReadLE<int16_t>(stream);\n    if (bone->Parent < 0) {\n      result->RootBones[result->RootBoneCount] = (int16_t)i;\n      result->RootBoneCount++;\n      assert(result->RootBoneCount < ModelMaxRootBones);\n    }\n    stream->Seek(8, RW_SEEK_CUR);\n    bone->ChildrenCount = ReadLE<int16_t>(stream);\n    assert(bone->ChildrenCount <= ModelMaxChildrenPerBone);\n\n    if (Profile::Scene3D::Version == LKMVersion::DaSH) {\n      stream->Seek(2, RW_SEEK_CUR);\n    }\n\n    ReadArrayLE(bone->Children, stream, bone->ChildrenCount);\n    stream->Seek(BonesOffset + boneSize * i + boneBaseTransformOffset,\n                 RW_SEEK_SET);\n\n    glm::vec3 position, euler, scale;\n    ReadVec3LE(&position, stream);\n    ReadVec3LE(&euler, stream);\n    ReadVec3LE(&scale, stream);\n    // More often than not these are actually not set...\n    if (glm::length(scale) < 0.001f) scale = glm::vec3(1.0f);\n    bone->BaseTransform = Transform(position, euler, scale);\n\n    // skip over bindpose\n    stream->Seek(sizeof(glm::mat4), RW_SEEK_CUR);\n\n    ReadMat4LE(&bone->BindInverse, stream);\n  }\n\n  result->MorphVertexCount = 0;\n  // Accumulate per-morph target vertex buffer counts\n  for (uint32_t i = 0; i < result->MorphTargetCount; i++) {\n    int64_t seekPos = MorphTargetOffset + MorphTargetInfoSize * i;\n    seekPos += 4;\n    stream->Seek(seekPos, RW_SEEK_SET);\n    result->MorphVertexCount += ReadLE<int32_t>(stream);\n  }\n\n  result->MorphVertexBuffers = (MorphVertexBuffer*)calloc(\n      result->MorphVertexCount, sizeof(MorphVertexBuffer));\n\n  uint32_t CurrentMorphVertexOffset = 0;\n  // Read morph targets\n  for (uint32_t i = 0; i < result->MorphTargetCount; i++) {\n    MorphTarget* target = &result->MorphTargets[i];\n    target->VertexOffset = CurrentMorphVertexOffset;\n    int64_t seekPos = MorphTargetOffset + MorphTargetInfoSize * i;\n    seekPos += 4;\n    stream->Seek(seekPos, RW_SEEK_SET);\n    target->VertexCount = ReadLE<int32_t>(stream);\n    stream->Seek(4, RW_SEEK_CUR);\n    uint32_t RawMorphVertexOffset =\n        MorphVertexOffset + ReadLE<uint32_t>(stream);\n\n    // Read vertex buffers\n    stream->Seek(RawMorphVertexOffset, RW_SEEK_SET);\n    // Per morph vertex buffer: Position, then Normal\n    ReadArrayLE((float*)&result->MorphVertexBuffers[CurrentMorphVertexOffset],\n                stream, 3 * 2 * target->VertexCount);\n    CurrentMorphVertexOffset += target->VertexCount;\n  }\n\n  // Read textures\n  stream->Seek(TexturesOffset, RW_SEEK_SET);\n  for (uint32_t i = 0; i < result->TextureCount; i++) {\n    uint32_t size = ReadLE<uint32_t>(stream);\n    ImpLogSlow(LogLevel::Debug, LogChannel::ModelLoad,\n               \"Loading texture {:d}, size=0x{:08x}\\n\", i, size);\n    void* gxt = malloc(size);\n    stream->Read(gxt, size);\n    Stream* gxtStream = new MemoryStream(gxt, size, true);\n    if (!result->Textures[i].Load(gxtStream)) {\n      ImpLog(LogLevel::Debug, LogChannel::ModelLoad,\n             \"Texture {:d} failed to load, falling back to 1x1 pixel\\n\", i);\n      result->Textures[i].LoadCheckerboard();\n    }\n    delete gxtStream;\n  }\n\n  if (stream) {\n    delete stream;\n  }\n\n  // Animations\n\n  // TODO remove listing, see above\n  // WOW this really shouldn't be here anyway\n  // TODO should we still remove this?\n\n  if (result->Type == ModelType_Character) {\n    Profile::Scene3D::CharacterDef& charDef = Profile::Scene3D::Characters\n        [Profile::Scene3D::ModelsToCharacters[modelId]];\n\n    result->IdleAnimation = charDef.IdleAnimation;\n    result->AnimationCount = 0;\n\n    for (auto const& anim : charDef.Animations) {\n      auto animId = anim.first;\n\n      if (AnimationIsBlacklisted(modelId, animId)) continue;\n\n      if (!AnimationIsBlacklisted(modelId, animId)) {\n        std::string animName;\n        int64_t animSize;\n        void* animData;\n\n        if (SlurpAnim(modelId, animId, animData, animSize, animName) !=\n            IoError_OK) {\n          ImpLog(LogLevel::Error, LogChannel::ModelLoad,\n                 \"Could not read animation %h for model {:d}\\n\", animId,\n                 modelId);\n          continue;\n        }\n\n        Stream* animStream = new MemoryStream(animData, animSize, true);\n        animStream->Meta.FileName = animName;\n        ModelAnimation* modelAnim =\n            ModelAnimation::Load(animStream, result, animId);\n        delete animStream;\n        if (modelAnim == nullptr) {\n          ImpLog(LogLevel::Error, LogChannel::ModelLoad,\n                 \"Could not parse animation %h for model {:d}\\n\", animId,\n                 modelId);\n          continue;\n        }\n        assert(result->Animations.count(animId) == 0);\n        result->Animations[animId] = modelAnim;\n        result->AnimationCount++;\n      }\n    }\n\n    result->AnimationIds.reserve(result->AnimationCount);\n    result->AnimationNames.reserve(result->AnimationCount);\n\n    for (auto const& anim : result->Animations) {\n      result->AnimationIds.push_back(anim.first);\n      result->AnimationNames.push_back(anim.second->Name);\n    }\n  }\n\n  UnmountModel(modelId);\n\n  return result;\n}\n\n// WARNING: Breaks with DaSH\nModel* Model::MakePlane() {\n  Model* result = new Model;\n  result->Id = std::numeric_limits<uint32_t>::max();\n  result->Type = ModelType_Character;\n  result->MeshCount = 1;\n  result->VertexCount = 4;\n  result->IndexCount = 6;\n  result->TextureCount = 1;\n  result->BoneCount = 1;\n  result->RootBoneCount = 1;\n  result->MorphTargetCount = 0;\n\n  result->VertexBuffers = (VertexBuffer*)malloc(4 * sizeof(VertexBuffer));\n  result->Indices = (uint16_t*)malloc(6 * sizeof(uint16_t));\n\n  VertexBuffer* vertexBuffers = (VertexBuffer*)result->VertexBuffers;\n\n  vertexBuffers[0].Position = glm::vec3(-20.0f, 0.0f, -20.0f);\n  vertexBuffers[1].Position = glm::vec3(20.0f, 0.0f, -20.0f);\n  vertexBuffers[2].Position = glm::vec3(-20.0f, 0.0f, 20.0f);\n  vertexBuffers[3].Position = glm::vec3(20.0f, 0.0f, 20.0f);\n\n  vertexBuffers[0].Normal = vertexBuffers[1].Normal = vertexBuffers[2].Normal =\n      vertexBuffers[3].Normal = glm::vec3(0.0f, 1.0f, 0.0f);\n\n  vertexBuffers[0].UV = glm::vec2(0.0f, 0.0f);\n  vertexBuffers[1].UV = glm::vec2(1.0f, 0.0f);\n  vertexBuffers[2].UV = glm::vec2(0.0f, 1.0f);\n  vertexBuffers[3].UV = glm::vec2(1.0f, 1.0f);\n\n  for (int i = 0; i < 4; i++) {\n    for (int j = 0; j < 4; j++) vertexBuffers[i].BoneIndices[j] = 0;\n    vertexBuffers[i].BoneWeights = glm::vec4(1.0f, 0.0f, 0.0f, 0.0f);\n  }\n\n  result->Indices[0] = 0;\n  result->Indices[1] = 2;\n  result->Indices[2] = 1;\n  result->Indices[3] = 1;\n  result->Indices[4] = 2;\n  result->Indices[5] = 3;\n\n  memset(result->Bones[0].Name, 0, sizeof(result->Bones[0].Name));\n  result->Bones[0].Id = 0;\n  result->Bones[0].Parent = -1;\n  result->Bones[0].ChildrenCount = 0;\n  result->Bones[0].BindInverse = glm::mat4(1.0f);\n  result->Bones[0].BaseTransform = Transform();\n\n  result->RootBones[0] = 0;\n\n  result->Textures[0].LoadPoliticalCompass();\n\n  memset(result->Meshes[0].Name, 0, sizeof(result->Meshes[0].Name));\n  result->Meshes[0].MorphTargetCount = 0;\n  result->Meshes[0].BoneMap[0] = 0;\n  result->Meshes[0].VertexCount = 4;\n  result->Meshes[0].IndexCount = 6;\n  result->Meshes[0].VertexOffset = 0;\n  result->Meshes[0].IndexOffset = 0;\n  result->Meshes[0].Opacity = 1.0f;\n  result->Meshes[0].UsedBones = 0;\n  result->Meshes[0].Maps[TT_ColorMap] = 0;\n  for (int i = TT_ColorMap + 1; i < TT_Count; i++) {\n    result->Meshes[0].Maps[i] = -1;\n  }\n  result->Meshes[0].MeshBone = 0;\n  result->Meshes[0].Material = MT_Generic;\n\n  return result;\n}\n\n}  // namespace Impacto"
  },
  {
    "path": "src/renderer/3d/model.h",
    "content": "#pragma once\n\n#include <magic_enum/magic_enum.hpp>\n#include \"../../impacto.h\"\n#include \"../../util.h\"\n#include \"../../texture/texture.h\"\n#include \"transform.h\"\n\n#include <glm/glm.hpp>\n\n#include <ankerl/unordered_dense.h>\n\n// BIG TODO: Use integer types consistently internally (not always possible for\n// file content, but eh)\n\nnamespace Impacto {\n\nenum class LKMVersion : int {\n  RNE = 0,\n  DaSH,\n};\nenum ModelType : uint32_t {\n  ModelType_Background = 1,\n  ModelType_Character = 2,\n  ModelType_DaSH_Unspecified = (2 << 16)\n};\n\nenum MeshFlag : uint32_t {\n  // MeshFlag_DoubleSided = (1 << 0),\n  MeshFlag_Later = (1 << 1)\n};\n\nint constexpr ModelMaxChildrenPerBone = 133;\nint constexpr ModelMaxMorphTargetsPerModel = 256;\nint constexpr ModelMaxMorphTargetsPerMesh = 32;\nint constexpr ModelUnknownsAfterMorphTargets = 12;\nint constexpr ModelUnknownsAfterMorphTargets_DaSH = 4;\n// TODO: How do we actually want to do this?\n// Character models generally have <300 bones. Some background models have >600\n// bones (what are these for? - some of them seem broken).\n// Dynamic array?\nint constexpr ModelMaxBonesPerModel = 768;\nint constexpr ModelMaxBonesPerMesh = 32;\nint constexpr ModelMaxMeshesPerModel = 64;\nint constexpr ModelMaxRootBones = 32;\nint constexpr ModelMaxTexturesPerModel = 64;\n\ninline std::vector<uint32_t> g_ModelIds;\ninline std::vector<std::string> g_ModelNames;\ninline uint32_t g_ModelCount;\n\ninline std::vector<uint32_t> g_BackgroundModelIds;\ninline std::vector<std::string> g_BackgroundModelNames;\ninline uint32_t g_BackgroundModelCount;\n\nstruct VertexBuffer {\n  glm::vec3 Position;\n  glm::vec3 Normal;\n  glm::vec2 UV;\n  uint8_t BoneIndices[4];  // indices into Mesh.BoneMap\n  glm::vec4 BoneWeights;\n};\n\nstruct VertexBufferDaSH {\n  glm::vec3 Position;\n  glm::vec3 Normal;\n\n  glm::vec3 unk1;  // tangent, only used in generic and skin\n  glm::vec4 unk2;  // all 1 in the first few vertices of c001_010, apparently\n                   // not used in rendering\n\n  glm::vec2 UV;\n  uint8_t BoneIndices[4];  // indices into Mesh.BoneMap\n  glm::vec4 BoneWeights;\n};\n\nstruct BgVertexBuffer {\n  glm::vec3 Position;\n  glm::vec2 UV;\n};\n\nstruct MorphVertexBuffer {\n  glm::vec3 Position;\n  glm::vec3 Normal;\n};\n\nstruct MorphTarget {\n  int32_t VertexCount;\n  // Offset (in sizeof(MorphVertexBuffer) units) into Model.MorphVertexBuffers\n  int32_t VertexOffset;\n};\n\nstruct StaticBone {\n  // DaSH addition\n  uint8_t Name[32];\n\n  int16_t Id;\n  int16_t Parent;\n  int16_t ChildrenCount;\n  int16_t Children[ModelMaxChildrenPerBone];  // TODO check\n\n  Transform BaseTransform;\n\n  glm::mat4 BindInverse;\n};\n\nenum TextureType {\n  TT_ColorMap = 0,\n  TT_GradientMaskMap = 2,\n  TT_SpecularColorMap = 4,\n  TT_Character3D_Max = 4,\n\n  TT_Eye_IrisColorMap = 0,\n  TT_Eye_WhiteColorMap = 1,\n  TT_Eye_IrisSpecularColorMap = 2,\n  TT_Eye_HighlightColorMap = 3,\n  TT_Eye_Max = 3,\n\n  TT_DaSH_ColorMap = 0,\n  TT_DaSH_Unk1 = 1,  // 1 and 3 are gradients\n  TT_DaSH_Unk2 = 2,  // 1 pixel\n  TT_DaSH_Unk3 = 3,\n  TT_DaSH_ShadowColorMap = 4,\n  TT_DaSH_GradientMaskMap = 5,\n  TT_DaSH_SpecularColorMap = 6,\n  TT_DaSH_NoiseMap = 7,  // Noise\n\n  TT_DaSH_Eye_WhiteColorMap = 0,\n  TT_DaSH_Eye_IrisColorMap = 1,\n  TT_DaSH_Eye_HighlightColorMap = 2,\n\n  TT_Count = 8\n};\n\nenum MaterialType {\n  // Ours\n  MT_None = 0,\n  MT_Outline = 1,\n  MT_Generic = 2,\n  MT_Eye = 3,\n  MT_Background = 4,\n  // DaSH\n  MT_DaSH_Generic = 200,\n  MT_DaSH_Eye = 300,\n  MT_DaSH_Face = 400,\n  MT_DaSH_Skin = 500\n};\n\nstruct Mesh {\n  // DaSH addition\n  uint8_t Name[32];\n\n  // Meshes in one group are animated together\n  int32_t GroupId;\n\n  int32_t Id;\n\n  uint32_t VertexCount;\n  // Offset (in sizeof(VertexBuffer) units) into Model.VertexBuffers\n  int32_t VertexOffset;\n  int32_t IndexCount;\n  // Offset (in sizeof(uint16_t) units) into Model.Indices\n  int32_t IndexOffset;\n\n  MaterialType Material;\n  uint32_t Flags;\n  float Opacity;\n\n  // This is only used with background models\n  Transform ModelTransform;\n\n  uint8_t MorphTargetIds[ModelMaxMorphTargetsPerMesh];\n\n  uint32_t UsedBones;                     // 0 => not skinned\n  int16_t BoneMap[ModelMaxBonesPerMesh];  // indices into Model.Bones\n\n  int16_t Maps[TT_Count];\n  bool HasShadowColorMap;\n\n  // Sets transform for meshes without individual vertex skinning\n  int16_t MeshBone;\n\n  uint8_t MorphTargetCount;\n};\n\nclass ModelAnimation;\n\nclass Model {\n public:\n  static void EnumerateModels();\n\n  // Parses a R;NE model file. No GPU submission happens in this class.\n  static Model* Load(uint32_t modelId);\n  // Ground plane\n  static Model* MakePlane();\n\n  ~Model();\n\n  uint32_t Id;\n\n  ModelType Type;\n\n  uint32_t MeshCount = 0;\n  Mesh Meshes[ModelMaxMeshesPerModel];\n\n  int32_t VertexCount = 0;\n  void* VertexBuffers = 0;\n\n  uint32_t MorphTargetCount = 0;\n  MorphTarget MorphTargets[ModelMaxMorphTargetsPerModel];\n\n  int32_t MorphVertexCount = 0;\n  MorphVertexBuffer* MorphVertexBuffers = 0;\n\n  int32_t IndexCount = 0;\n  uint16_t* Indices = 0;\n\n  uint32_t TextureCount = 0;\n  Texture Textures[ModelMaxTexturesPerModel];\n\n  uint32_t BoneCount = 0;\n  StaticBone Bones[ModelMaxBonesPerModel];\n\n  int32_t RootBoneCount = 0;\n  int16_t RootBones[ModelMaxRootBones];\n\n  ankerl::unordered_dense::map<int16_t, ModelAnimation*> Animations;\n\n  // These are only filled for DaSH\n  ankerl::unordered_dense::map<std::string, int32_t, string_hash,\n                               std::equal_to<>>\n      NamedMeshGroups;\n  ankerl::unordered_dense::map<std::string, uint16_t, string_hash,\n                               std::equal_to<>>\n      NamedBones;\n\n  int16_t IdleAnimation = -1;\n\n  std::vector<int32_t> AnimationIds;\n  std::vector<std::string_view> AnimationNames;\n  uint32_t AnimationCount = 0;\n};\n\n}  // namespace Impacto"
  },
  {
    "path": "src/renderer/3d/modelanimator.cpp",
    "content": "#include \"modelanimator.h\"\n\n#include \"renderable3d.h\"\n\nnamespace Impacto {\n\nvoid ModelAnimator::Start(int16_t animId) {\n  assert(Character != 0);\n\n  if (Character->StaticModel->Animations.find(animId) ==\n      Character->StaticModel->Animations.end()) {\n    CurrentAnimation = 0;\n    IsPlaying = false;\n    return;\n  }\n  CurrentAnimation = Character->StaticModel->Animations.at(animId);\n\n  CurrentTime = 0.0f;\n  LoopStart = CurrentAnimation->LoopStart;\n  LoopEnd = CurrentAnimation->LoopEnd;\n  IsPlaying = true;\n  OneShot = CurrentAnimation->OneShot;\n  Reset();\n  Update(0);\n}\nvoid ModelAnimator::Reset() {\n  assert(Character != 0);\n  assert(CurrentAnimation != 0);\n\n  Character->ReloadDefaultBoneTransforms();\n  Character->ReloadDefaultMeshAnimStatus();\n\n  if (CurrentAnimation != 0) {\n    for (int i = 0; i < CurrentAnimation->BoneTrackCount; i++) {\n      for (int j = 0; j < BKT_Count; j++) {\n        BoneKeys[i].CurrentKeys[j] = 0;\n        BoneKeys[i].NextKeys[j] = 1;\n      }\n    }\n\n    for (int i = 0; i < CurrentAnimation->MeshTrackCount; i++) {\n      for (int j = 0; j < MKT_Count; j++) {\n        MeshKeys[i].CurrentKeys[j] = 0;\n        MeshKeys[i].NextKeys[j] = 1;\n      }\n    }\n  }\n}\n\nvoid ModelAnimator::Unload() {\n  Character->ReloadDefaultBoneTransforms();\n  Character->ReloadDefaultMeshAnimStatus();\n  CurrentAnimation = 0;\n  CurrentTime = 0.0f;\n  LoopStart = 0.0f;\n  LoopEnd = 0.0f;\n  IsPlaying = false;\n}\n\n// For each sub-track, if we're not on the last keyframe already, make sure the\n// *next* keyframe is *after* the current time\n#define TrackIncrementLoop(targetType, dataType, type)            \\\n  while (targetType[i].NextKeys[type] < track->KeyCounts[type] && \\\n         CurrentAnimation                                         \\\n                 ->dataType[track->KeyOffsets[type] +             \\\n                            targetType[i].NextKeys[type]]         \\\n                 .Time <= CurrentTime) {                          \\\n    targetType[i].CurrentKeys[type]++;                            \\\n    targetType[i].NextKeys[type]++;                               \\\n  }                                                               \\\n  (void)0\n\n#define TrackInterpolate(targetType, dataType, type, dest, func)       \\\n  if (track->KeyCounts[type]) {                                        \\\n    if (targetType[i].NextKeys[type] < track->KeyCounts[type]) {       \\\n      float nextTime = CurrentAnimation                                \\\n                           ->dataType[track->KeyOffsets[type] +        \\\n                                      targetType[i].NextKeys[type]]    \\\n                           .Time;                                      \\\n      float prevTime = CurrentAnimation                                \\\n                           ->dataType[track->KeyOffsets[type] +        \\\n                                      targetType[i].CurrentKeys[type]] \\\n                           .Time;                                      \\\n      float duration = nextTime - prevTime;                            \\\n      float timeAlongDuration = CurrentTime - prevTime;                \\\n      float factor = timeAlongDuration / duration;                     \\\n      dest = glm::func(CurrentAnimation                                \\\n                           ->dataType[track->KeyOffsets[type] +        \\\n                                      targetType[i].CurrentKeys[type]] \\\n                           .Value,                                     \\\n                       CurrentAnimation                                \\\n                           ->dataType[track->KeyOffsets[type] +        \\\n                                      targetType[i].NextKeys[type]]    \\\n                           .Value,                                     \\\n                       factor);                                        \\\n    } else {                                                           \\\n      dest = CurrentAnimation                                          \\\n                 ->dataType[track->KeyOffsets[type] +                  \\\n                            targetType[i].CurrentKeys[type]]           \\\n                 .Value;                                               \\\n    }                                                                  \\\n  }                                                                    \\\n  void(0)\n\n#define TrackNoInterpolate(targetType, dataType, type, dest) \\\n  if (track->KeyCounts[type]) {                              \\\n    dest = CurrentAnimation                                  \\\n               ->dataType[track->KeyOffsets[type] +          \\\n                          targetType[i].CurrentKeys[type]]   \\\n               .Value;                                       \\\n  }                                                          \\\n  void(0)\n\nvoid ModelAnimator::Update(float dt) {\n  if (!IsPlaying || CurrentAnimation == 0 || Character == 0) return;\n\n  CurrentTime += dt;\n  if (CurrentTime > LoopEnd) {\n    if (OneShot) {\n      IsPlaying = false;\n      return;\n    }\n    float remainder = CurrentTime - LoopEnd;\n    Reset();\n    CurrentTime = LoopStart;\n    CurrentTime += remainder;\n  }\n\n  for (int i = 0; i < CurrentAnimation->BoneTrackCount; i++) {\n    BoneTrack* track = &CurrentAnimation->BoneTracks[i];\n    Transform* transform = &Character->CurrentPose[track->Bone].LocalTransform;\n\n    TrackIncrementLoop(BoneKeys, CoordKeyframes, BKT_TranslateX);\n    TrackIncrementLoop(BoneKeys, CoordKeyframes, BKT_TranslateY);\n    TrackIncrementLoop(BoneKeys, CoordKeyframes, BKT_TranslateZ);\n    TrackIncrementLoop(BoneKeys, CoordKeyframes, BKT_ScaleX);\n    TrackIncrementLoop(BoneKeys, CoordKeyframes, BKT_ScaleY);\n    TrackIncrementLoop(BoneKeys, CoordKeyframes, BKT_ScaleZ);\n    TrackIncrementLoop(BoneKeys, QuatKeyframes, BKT_Rotate);\n\n    if (Tweening) {\n      TrackInterpolate(BoneKeys, CoordKeyframes, BKT_TranslateX,\n                       transform->Position.x, mix);\n      TrackInterpolate(BoneKeys, CoordKeyframes, BKT_TranslateY,\n                       transform->Position.y, mix);\n      TrackInterpolate(BoneKeys, CoordKeyframes, BKT_TranslateZ,\n                       transform->Position.z, mix);\n      TrackInterpolate(BoneKeys, CoordKeyframes, BKT_ScaleX, transform->Scale.x,\n                       mix);\n      TrackInterpolate(BoneKeys, CoordKeyframes, BKT_ScaleY, transform->Scale.y,\n                       mix);\n      TrackInterpolate(BoneKeys, CoordKeyframes, BKT_ScaleZ, transform->Scale.z,\n                       mix);\n      TrackInterpolate(BoneKeys, QuatKeyframes, BKT_Rotate, transform->Rotation,\n                       slerp);\n    } else {\n      TrackNoInterpolate(BoneKeys, CoordKeyframes, BKT_TranslateX,\n                         transform->Position.x);\n      TrackNoInterpolate(BoneKeys, CoordKeyframes, BKT_TranslateY,\n                         transform->Position.y);\n      TrackNoInterpolate(BoneKeys, CoordKeyframes, BKT_TranslateZ,\n                         transform->Position.z);\n      TrackNoInterpolate(BoneKeys, CoordKeyframes, BKT_ScaleX,\n                         transform->Scale.x);\n      TrackNoInterpolate(BoneKeys, CoordKeyframes, BKT_ScaleY,\n                         transform->Scale.y);\n      TrackNoInterpolate(BoneKeys, CoordKeyframes, BKT_ScaleZ,\n                         transform->Scale.z);\n      TrackNoInterpolate(BoneKeys, QuatKeyframes, BKT_Rotate,\n                         transform->Rotation);\n    }\n  }\n\n  for (int i = 0; i < CurrentAnimation->MeshTrackCount; i++) {\n    MeshTrack* track = &CurrentAnimation->MeshTracks[i];\n    AnimatedMesh* animatedMesh = &Character->MeshAnimStatus[track->Mesh];\n\n    TrackIncrementLoop(MeshKeys, CoordKeyframes, MKT_Visible);\n    for (int j = MKT_MorphInfluenceStart;\n         j < track->MorphTargetCount + MKT_MorphInfluenceStart; j++) {\n      TrackIncrementLoop(MeshKeys, CoordKeyframes, j);\n    }\n\n    TrackNoInterpolate(MeshKeys, CoordKeyframes, MKT_Visible,\n                       animatedMesh->Visible);\n\n    if (Tweening) {\n      for (int j = MKT_MorphInfluenceStart;\n           j < (track->MorphTargetCount + MKT_MorphInfluenceStart); j++) {\n        TrackInterpolate(\n            MeshKeys, CoordKeyframes, j,\n            animatedMesh->MorphInfluences\n                [track->MorphTargetIds[j - MKT_MorphInfluenceStart]],\n            mix);\n      }\n    } else {\n      for (int j = MKT_MorphInfluenceStart;\n           j < (track->MorphTargetCount + MKT_MorphInfluenceStart); j++) {\n        TrackNoInterpolate(\n            MeshKeys, CoordKeyframes, j,\n            animatedMesh->MorphInfluences\n                [track->MorphTargetIds[j - MKT_MorphInfluenceStart]]);\n      }\n    }\n  }\n}\n\nvoid ModelAnimator::Seek(float t) {\n  assert(Character != 0);\n  assert(CurrentAnimation != 0);\n  assert(t <= CurrentAnimation->Duration);\n\n  bool isPlaying = IsPlaying;\n  float loopStart = LoopStart;\n  float loopEnd = LoopEnd;\n\n  IsPlaying = true;\n  LoopStart = 0.0f;\n  LoopEnd = CurrentAnimation->Duration;\n\n  Reset();\n  CurrentTime = 0.0f;\n  Update(t);\n\n  IsPlaying = isPlaying;\n  LoopStart = loopStart;\n  LoopEnd = loopEnd;\n}\n\n}  // namespace Impacto"
  },
  {
    "path": "src/renderer/3d/modelanimator.h",
    "content": "#pragma once\n\n#include \"animation.h\"\n\nnamespace Impacto {\n\nclass IRenderable3D;\n\nstruct BoneTrackStatus {\n  uint16_t CurrentKeys[BKT_Count];\n  uint16_t NextKeys[BKT_Count];\n};\n\nstruct MeshTrackStatus {\n  uint16_t CurrentKeys[MKT_Count];\n  uint16_t NextKeys[MKT_Count];\n};\n\nclass ModelAnimator {\n public:\n  // Note: These are has-a relationships - Character and CurrentAnimation should\n  // not be deleted with the animator\n  IRenderable3D* Character = 0;\n  ModelAnimation* CurrentAnimation = 0;\n\n  // Time in seconds\n  float CurrentTime = 0;\n  BoneTrackStatus BoneKeys[ModelMaxBonesPerModel];\n  MeshTrackStatus MeshKeys[ModelMaxMeshesPerModel];\n\n  float LoopStart = 0.0f;\n  float LoopEnd = 0.0f;\n  bool IsPlaying = false;\n  bool OneShot = false;\n  bool Tweening = true;\n\n  void Unload();\n  void Start(int16_t animId);\n  void Reset();\n  void Update(float dt);\n  void Seek(float dt);\n};\n}  // namespace Impacto"
  },
  {
    "path": "src/renderer/3d/renderable3d.h",
    "content": "#pragma once\n\n#include \"model.h\"\n#include \"modelanimator.h\"\n#include \"../../loadable.h\"\n#include \"../3d/scene.h\"\n\nnamespace Impacto {\n\nstruct PosedBone {\n  // Affine transform in relation to parent\n  Transform LocalTransform;\n  // Order: T * Rz * Ry * Rx * S\n\n  // Affine transform in relation to world\n  glm::mat4 World;\n  // Affine transform in relation to bindpose\n  glm::mat4 Offset;\n};\n\nstruct AnimatedMesh {\n  // TODO: Visibility not blended during animation transitions - is this okay?\n  float Visible;\n  float MorphInfluences[ModelMaxMorphTargetsPerMesh];\n  int MorphedVerticesOffset;\n};\n\nclass ModelAnimator;\nclass Camera;\n\nclass IRenderable3D {\n public:\n  virtual void MakePlane() = 0;\n\n  virtual void Update(float dt) = 0;\n  virtual void Render() = 0;\n\n  virtual void ReloadDefaultBoneTransforms() = 0;\n  virtual void InitMeshAnimStatus() = 0;\n  virtual void ReloadDefaultMeshAnimStatus() = 0;\n\n  virtual void SwitchAnimation(int16_t animId, float transitionTime) = 0;\n\n  LoadStatus Status = LoadStatus::Unloaded;\n\n  bool LoadAsync(uint32_t id) {\n    if (Status == LoadStatus::Loading) {\n      // cannot currently cancel a load\n      return false;\n    }\n    Unload();\n    NextLoadId = id;\n    Status = LoadStatus::Loading;\n    WorkQueue::Push(this, &LoadWorker, &OnLoaded);\n    return true;\n  }\n\n  void Unload() {\n    if (Status == LoadStatus::Loaded) {\n      UnloadSync();\n      Status = LoadStatus::Unloaded;\n    }\n  }\n\n  Model* StaticModel = 0;\n\n  // Per-frame results of vertex animation\n  MorphVertexBuffer* CurrentMorphedVertices = 0;\n  AnimatedMesh MeshAnimStatus[ModelMaxMeshesPerModel];\n  PosedBone CurrentPose[ModelMaxBonesPerModel];\n  Transform ModelTransform;\n  ModelAnimator Animator;\n\n  bool IsUsed = false;\n  bool IsSubmitted = false;\n  bool IsVisible = false;\n\n protected:\n  virtual bool LoadSync(uint32_t modelId) = 0;\n  virtual void UnloadSync() = 0;\n  virtual void MainThreadOnLoad() = 0;\n\n  uint32_t NextLoadId;\n\n  static void LoadWorker(void* ptr) {\n    auto renderable = static_cast<IRenderable3D*>(ptr);\n    renderable->LoadSync(renderable->NextLoadId);\n  }\n\n  static void OnLoaded(void* ptr) {\n    auto renderable = static_cast<IRenderable3D*>(ptr);\n    renderable->MainThreadOnLoad();\n    renderable->Status = LoadStatus::Loaded;\n  }\n};\n\n}  // namespace Impacto"
  },
  {
    "path": "src/renderer/3d/scene.h",
    "content": "#pragma once\n\n#include <glm/glm.hpp>\n\n#include \"renderable3d.h\"\n#include \"camera.h\"\n\nnamespace Impacto {\n\nclass IScene3D {\n public:\n  virtual void Init() = 0;\n  virtual void Shutdown() = 0;\n  virtual void Update(float dt) = 0;\n  virtual void Render() = 0;\n\n  Camera MainCamera;\n\n  IRenderable3D** Renderables;\n\n  glm::vec3 LightPosition;\n  glm::vec4 Tint;\n  bool DarkMode;\n};\n\n}  // namespace Impacto"
  },
  {
    "path": "src/renderer/3d/transform.cpp",
    "content": "#include \"transform.h\"\n\n#include \"../../util.h\"\n\n#define GLM_ENABLE_EXPERIMENTAL\n#include <glm/gtx/matrix_decompose.hpp>\n\nnamespace Impacto {\n\nTransform::Transform(glm::mat4 const& transformMatrix) {\n  glm::vec3 dummy1;\n  glm::vec4 dummy2;\n  glm::decompose(transformMatrix, Scale, Rotation, Position, dummy1, dummy2);\n}\n\nvoid Transform::SetRotationFromEuler(glm::vec3 eulerZYX) {\n  eulerZYXToQuat(&eulerZYX, &Rotation);\n}\n\nglm::mat4 Transform::Matrix() {\n  glm::mat4 result = glm::translate(glm::mat4(1.0), Position);\n  result = result * glm::mat4_cast(Rotation);\n  result = glm::scale(result, Scale);\n  return result;\n}\n\nTransform Transform::Interpolate(Transform next, float factor) {\n  Transform result;\n  result.Position = glm::mix(Position, next.Position, factor);\n  result.Rotation = glm::slerp(Rotation, next.Rotation, factor);\n  result.Scale = glm::mix(Scale, next.Scale, factor);\n  return result;\n}\n\n}  // namespace Impacto"
  },
  {
    "path": "src/renderer/3d/transform.h",
    "content": "#pragma once\n\n#include <glm/gtc/quaternion.hpp>\n\nnamespace Impacto {\n\nstruct Transform {\n  Transform() : Position(), Rotation(), Scale(1.0f) {}\n  Transform(glm::vec3 position, glm::quat rotation, glm::vec3 scale)\n      : Position(position), Rotation(rotation), Scale(scale) {}\n\n  Transform(glm::vec3 position, glm::vec3 eulerZYX, glm::vec3 scale)\n      : Position(position), Scale(scale) {\n    SetRotationFromEuler(eulerZYX);\n  }\n\n  // Decompose\n  Transform(glm::mat4 const& transformMatrix);\n\n  void SetRotationFromEuler(glm::vec3 eulerZYX);\n  glm::mat4 Matrix();\n\n  Transform Interpolate(Transform next, float factor);\n\n  glm::vec3 Position;\n  glm::quat Rotation;\n  glm::vec3 Scale;\n};\n\n}  // namespace Impacto"
  },
  {
    "path": "src/renderer/dx9/3d/renderable3d.cpp",
    "content": "#include <glm/glm.hpp>\n#include <glm/gtc/type_ptr.hpp>\n\n#include \"renderable3d.h\"\n\n#include \"../renderer.h\"\n#include \"../../3d/camera.h\"\n#include \"../../3d/modelanimator.h\"\n#include \"../../../log.h\"\n#include \"../../../profile/scene3d.h\"\n\nnamespace Impacto {\nnamespace DirectX9 {\n\nstatic uint32_t TextureDummy = 0;\n\n// character\nstatic Shader *ShaderMain = 0, *ShaderOutline = 0, *ShaderEye = 0;\n\n// background\nstatic Shader* ShaderBackground = 0;\n\nstatic glm::mat4 ViewProjection;\n\nstatic IScene3D* CurrentScene;\nstatic Camera* CurrentCamera;\n\nstatic bool IsInit = false;\n\nstatic MaterialType CurrentMaterial = MT_None;\nstatic bool CurrentMaterialIsDepthWrite = false;\nstatic bool CurrentMaterialIsBackfaceCull = false;\n\nstatic DirectX9Window* Window;\nstatic IDirect3DDevice9* Device;\n\nvoid Renderable3D::Init(DirectX9Window* window, IDirect3DDevice9* device) {\n  assert(IsInit == false);\n  ImpLog(LogLevel::Info, LogChannel::Renderable3D,\n         \"Initializing Renderable3D system\\n\");\n  IsInit = true;\n\n  Window = window;\n  Device = device;\n\n  IDirect3DVertexDeclaration9* modelVertexDeclaration;\n  D3D_SHADER_MACRO* shaderMacros = nullptr;\n  if (Profile::Scene3D::Version == LKMVersion::DaSH) {\n    D3D_SHADER_MACRO dashMacros[] = {\"IsDaSH\", \"1\", NULL, NULL};\n    shaderMacros = dashMacros;\n\n    std::vector<D3DVERTEXELEMENT9> vertexDeclDesc{\n        {0, offsetof(VertexBufferDaSH, Position), D3DDECLTYPE_FLOAT3,\n         D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0},\n        {0, offsetof(VertexBufferDaSH, Normal), D3DDECLTYPE_FLOAT3,\n         D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL, 0},\n        {0, offsetof(VertexBufferDaSH, UV), D3DDECLTYPE_FLOAT2,\n         D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0},\n        {0, offsetof(VertexBufferDaSH, BoneIndices), D3DDECLTYPE_UBYTE4,\n         D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_BLENDINDICES, 0},\n        {0, offsetof(VertexBufferDaSH, BoneWeights), D3DDECLTYPE_FLOAT4,\n         D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_BLENDWEIGHT, 0},\n        D3DDECL_END()};\n    Device->CreateVertexDeclaration(vertexDeclDesc.data(),\n                                    &modelVertexDeclaration);\n  } else {\n    std::vector<D3DVERTEXELEMENT9> vertexDeclDesc{\n        {0, offsetof(VertexBuffer, Position), D3DDECLTYPE_FLOAT3,\n         D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0},\n        {0, offsetof(VertexBuffer, Normal), D3DDECLTYPE_FLOAT3,\n         D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL, 0},\n        {0, offsetof(VertexBuffer, UV), D3DDECLTYPE_FLOAT2,\n         D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0},\n        {0, offsetof(VertexBuffer, BoneIndices), D3DDECLTYPE_UBYTE4,\n         D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_BLENDINDICES, 0},\n        {0, offsetof(VertexBuffer, BoneWeights), D3DDECLTYPE_FLOAT4,\n         D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_BLENDWEIGHT, 0},\n        D3DDECL_END()};\n    Device->CreateVertexDeclaration(vertexDeclDesc.data(),\n                                    &modelVertexDeclaration);\n  }\n\n  ShaderMain = new Shader();\n  ShaderMain->Compile(\"Renderable3D_Character\", Device, modelVertexDeclaration,\n                      shaderMacros);\n  ShaderOutline = new Shader();\n  ShaderOutline->Compile(\"Renderable3D_Outline\", Device, modelVertexDeclaration,\n                         shaderMacros);\n  ShaderEye = new Shader();\n  ShaderEye->Compile(\"Renderable3D_Eye\", Device, modelVertexDeclaration,\n                     shaderMacros);\n\n  IDirect3DVertexDeclaration9* bgVertexDeclaration;\n  std::vector<D3DVERTEXELEMENT9> vertexDeclDesc{\n      {0, offsetof(BgVertexBuffer, Position), D3DDECLTYPE_FLOAT3,\n       D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0},\n      {0, offsetof(BgVertexBuffer, UV), D3DDECLTYPE_FLOAT2,\n       D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0},\n      D3DDECL_END()};\n  Device->CreateVertexDeclaration(vertexDeclDesc.data(), &bgVertexDeclaration);\n  ShaderBackground = new Shader();\n  ShaderBackground->Compile(\"Renderable3D_Background\", Device,\n                            bgVertexDeclaration);\n\n  Texture texDummy;\n  texDummy.Load1x1();\n  TextureDummy = texDummy.Submit();\n}\n\nvoid Renderable3D::BeginFrame(IScene3D* scene, Camera* camera) {\n  CurrentMaterial = MT_None;\n\n  CurrentScene = scene;\n  CurrentCamera = camera;\n  /* SceneUniformBufferType entry{};\n  entry.ViewProjection = camera->ViewProjection;\n  entry.Tint = scene->Tint;\n  entry.WorldLightPosition = scene->LightPosition;\n  entry.WorldEyePosition = camera->CameraTransform.Position;\n  entry.DarkMode = scene->DarkMode;\n  memcpy(SceneUniformBuffersMapped[CurrentFrameIndex], &entry,\n         sizeof(SceneUniformBufferType));*/\n\n  ViewProjection = camera->ViewProjection;\n}\n\nvoid Renderable3D::SetSceneUniformValues() {\n  if (StaticModel->Type != ModelType_Background) {\n    SceneUniformBufferType entry{};\n    entry.ViewProjection = CurrentCamera->ViewProjection;\n    entry.Tint = CurrentScene->Tint;\n    entry.WorldLightPosition = CurrentScene->LightPosition;\n    entry.WorldEyePosition = CurrentCamera->CameraTransform.Position;\n    entry.DarkMode = CurrentScene->DarkMode;\n\n    Device->SetPixelShaderConstantF(0, glm::value_ptr(entry.ViewProjection), 4);\n    Device->SetPixelShaderConstantF(4, glm::value_ptr(entry.Tint), 1);\n    Device->SetPixelShaderConstantF(5, glm::value_ptr(entry.WorldLightPosition),\n                                    1);\n    Device->SetPixelShaderConstantF(6, glm::value_ptr(entry.WorldEyePosition),\n                                    1);\n    Device->SetPixelShaderConstantB(0, (BOOL*)&entry.DarkMode, 1);\n\n    Device->SetVertexShaderConstantF(0, glm::value_ptr(entry.ViewProjection),\n                                     4);\n    Device->SetVertexShaderConstantF(4, glm::value_ptr(entry.Tint), 1);\n    Device->SetVertexShaderConstantF(\n        5, glm::value_ptr(entry.WorldLightPosition), 1);\n    Device->SetVertexShaderConstantF(6, glm::value_ptr(entry.WorldEyePosition),\n                                     1);\n    Device->SetVertexShaderConstantB(0, (BOOL*)&entry.DarkMode, 1);\n  }\n}\n\nbool Renderable3D::LoadSync(uint32_t modelId) {\n  assert(IsUsed == false);\n\n  ImpLog(LogLevel::Info, LogChannel::Renderable3D,\n         \"Creating renderable (model ID {:d})\\n\", modelId);\n\n  StaticModel = Model::Load(modelId);\n  ModelTransform = Transform();\n  PrevPoseWeight = 0.0f;\n\n  if (!StaticModel) {\n    ImpLog(LogLevel::Error, LogChannel::Renderable3D,\n           \"Model loading failed for character with model ID {:d}\\n\", modelId);\n    return false;\n  }\n\n  InitMeshAnimStatus();\n  ReloadDefaultBoneTransforms();\n\n  Animator.Character = this;\n  SwitchAnimation(StaticModel->IdleAnimation, 0.0f);\n\n  IsUsed = true;\n\n  return true;\n}\n\nvoid Renderable3D::MakePlane() {\n  assert(IsUsed == false);\n\n  StaticModel = Model::MakePlane();\n  ModelTransform = Transform();\n  PrevPoseWeight = 0.0f;\n\n  Animator.Character = this;\n\n  InitMeshAnimStatus();\n  ReloadDefaultBoneTransforms();\n\n  IsUsed = true;\n}\n\nvoid Renderable3D::InitMeshAnimStatus() {\n  int totalMorphedVertices = 0;\n  for (uint32_t i = 0; i < StaticModel->MeshCount; i++) {\n    MeshAnimStatus[i].MorphedVerticesOffset = totalMorphedVertices;\n    if (StaticModel->Meshes[i].MorphTargetCount > 0) {\n      totalMorphedVertices += StaticModel->Meshes[i].VertexCount;\n    }\n  }\n  if (totalMorphedVertices > 0) {\n    if (Profile::Scene3D::Version == LKMVersion::DaSH) {\n      Device->CreateVertexBuffer(\n          totalMorphedVertices * sizeof(VertexBufferDaSH), 0, 0,\n          D3DPOOL_MANAGED, &MorphedVerticesDevice, NULL);\n    } else {\n      Device->CreateVertexBuffer(totalMorphedVertices * sizeof(VertexBuffer), 0,\n                                 0, D3DPOOL_MANAGED, &MorphedVerticesDevice,\n                                 NULL);\n    }\n  }\n  ReloadDefaultMeshAnimStatus();\n}\n\nvoid Renderable3D::ReloadDefaultMeshAnimStatus() {\n  for (uint32_t i = 0; i < StaticModel->MeshCount; i++) {\n    MeshAnimStatus[i].Visible = 1.0f;\n    if (StaticModel->Meshes[i].MorphTargetCount > 0) {\n      for (uint8_t j = 0; j < StaticModel->Meshes[i].MorphTargetCount; j++) {\n        MeshAnimStatus[i].MorphInfluences[j] = 0.0f;\n      }\n    }\n  }\n}\n\nvoid Renderable3D::SwitchAnimation(int16_t animId, float transitionTime) {\n  if (Animator.CurrentAnimation != 0 && transitionTime > 0.0f) {\n    PrevPoseWeight = 1.0f;\n    for (uint32_t i = 0; i < StaticModel->BoneCount; i++) {\n      PrevBoneTransforms[i] = CurrentPose[i].LocalTransform;\n    }\n    memcpy(PrevMeshAnimStatus, MeshAnimStatus, sizeof(MeshAnimStatus));\n    AnimationTransitionTime = transitionTime;\n  } else {\n    PrevPoseWeight = 0.0f;\n  }\n  Animator.Start(animId);\n}\n\nvoid Renderable3D::ReloadDefaultBoneTransforms() {\n  for (uint32_t i = 0; i < StaticModel->BoneCount; i++) {\n    CurrentPose[i].LocalTransform = StaticModel->Bones[i].BaseTransform;\n  }\n}\n\nvoid Renderable3D::CalculateMorphedVertices(int id) {\n  Mesh* mesh = &StaticModel->Meshes[id];\n  AnimatedMesh* animStatus = &MeshAnimStatus[id];\n  AnimatedMesh* prevAnimStatus = &PrevMeshAnimStatus[id];\n  if (mesh->MorphTargetCount == 0) return;\n\n  void* currentMorphedVertex;\n  auto res = MorphedVerticesDevice->Lock(0, 0, &CurrentMorphedVerticesDx, 0);\n  if (Profile::Scene3D::Version == LKMVersion::DaSH) {\n    currentMorphedVertex = ((VertexBufferDaSH*)CurrentMorphedVerticesDx) +\n                           animStatus->MorphedVerticesOffset;\n  } else {\n    currentMorphedVertex = ((VertexBuffer*)CurrentMorphedVerticesDx) +\n                           animStatus->MorphedVerticesOffset;\n  }\n  VertexBuffer* currentMorphedVertexRNE = (VertexBuffer*)currentMorphedVertex;\n  VertexBufferDaSH* currentMorphedVertexDaSH =\n      (VertexBufferDaSH*)currentMorphedVertex;\n\n  void* currentVertex;\n  if (Profile::Scene3D::Version == LKMVersion::DaSH) {\n    currentVertex =\n        ((VertexBufferDaSH*)StaticModel->VertexBuffers) + mesh->VertexOffset;\n  } else {\n    currentVertex =\n        ((VertexBuffer*)StaticModel->VertexBuffers) + mesh->VertexOffset;\n  }\n\n  VertexBuffer* currentVertexRNE = (VertexBuffer*)currentVertex;\n  VertexBufferDaSH* currentVertexDaSH = (VertexBufferDaSH*)currentVertex;\n\n  for (uint32_t j = 0; j < mesh->VertexCount; j++) {\n    if (Profile::Scene3D::Version == LKMVersion::DaSH) {\n      memcpy(currentMorphedVertexDaSH, currentVertexDaSH,\n             sizeof(VertexBufferDaSH));\n      currentMorphedVertexDaSH++;\n      currentVertexDaSH++;\n    } else {\n      memcpy(currentMorphedVertexRNE, currentVertexRNE, sizeof(VertexBuffer));\n      currentMorphedVertexRNE++;\n      currentVertexRNE++;\n    }\n  }\n\n  for (uint8_t k = 0; k < mesh->MorphTargetCount; k++) {\n    float influence;\n    if (PrevPoseWeight > 0.0f) {\n      influence = glm::mix(prevAnimStatus->MorphInfluences[k],\n                           animStatus->MorphInfluences[k],\n                           glm::smoothstep(0.0f, 1.0f, 1.0f - PrevPoseWeight));\n    } else {\n      influence = animStatus->MorphInfluences[k];\n    }\n\n    if (influence == 0.0f) continue;\n\n    if (Profile::Scene3D::Version == LKMVersion::DaSH) {\n      currentMorphedVertex = ((VertexBufferDaSH*)CurrentMorphedVerticesDx) +\n                             animStatus->MorphedVerticesOffset;\n    } else {\n      currentMorphedVertex = ((VertexBuffer*)CurrentMorphedVerticesDx) +\n                             animStatus->MorphedVerticesOffset;\n    }\n    currentMorphedVertexRNE = (VertexBuffer*)currentMorphedVertex;\n    currentMorphedVertexDaSH = (VertexBufferDaSH*)currentMorphedVertex;\n\n    if (Profile::Scene3D::Version == LKMVersion::DaSH) {\n      currentVertex =\n          ((VertexBufferDaSH*)StaticModel->VertexBuffers) + mesh->VertexOffset;\n    } else {\n      currentVertex =\n          ((VertexBuffer*)StaticModel->VertexBuffers) + mesh->VertexOffset;\n    }\n\n    currentVertexRNE = (VertexBuffer*)currentVertex;\n    currentVertexDaSH = (VertexBufferDaSH*)currentVertex;\n\n    MorphVertexBuffer* currentMorphTargetVbo =\n        StaticModel->MorphVertexBuffers +\n        StaticModel->MorphTargets[mesh->MorphTargetIds[k]].VertexOffset;\n\n    for (uint32_t j = 0; j < mesh->VertexCount; j++) {\n      if (Profile::Scene3D::Version == LKMVersion::DaSH) {\n        currentMorphedVertexDaSH->Position +=\n            (currentMorphTargetVbo->Position - currentVertexDaSH->Position) *\n            influence;\n        currentMorphedVertexDaSH->Normal +=\n            (currentMorphTargetVbo->Normal - currentVertexDaSH->Normal) *\n            influence;\n        currentVertexDaSH++;\n        currentMorphedVertexDaSH++;\n      } else {\n        currentMorphedVertexRNE->Position +=\n            (currentMorphTargetVbo->Position - currentVertexRNE->Position) *\n            influence;\n        currentMorphedVertexRNE->Normal +=\n            (currentMorphTargetVbo->Normal - currentVertexRNE->Normal) *\n            influence;\n        currentVertexRNE++;\n        currentMorphedVertexRNE++;\n      }\n      currentMorphTargetVbo++;\n    }\n  }\n\n  res = MorphedVerticesDevice->Unlock();\n}\n\nvoid Renderable3D::Pose() {\n  for (int i = 0; i < StaticModel->RootBoneCount; i++) {\n    PoseBone(StaticModel->RootBones[i]);\n  }\n}\n\nvoid Renderable3D::PoseBone(int16_t id) {\n  StaticBone* bone = &StaticModel->Bones[id];\n  PosedBone* transformed = &CurrentPose[id];\n\n  glm::mat4 parentWorld;\n  if (bone->Parent < 0) {\n    parentWorld = glm::mat4(1.0);\n  } else {\n    parentWorld = CurrentPose[bone->Parent].World;\n  }\n\n  Transform transform;\n  if (PrevPoseWeight > 0.0f) {\n    transform = PrevBoneTransforms[id].Interpolate(\n        transformed->LocalTransform,\n        glm::smoothstep(0.0f, 1.0f, 1.0f - PrevPoseWeight));\n  } else {\n    transform = transformed->LocalTransform;\n  }\n\n  glm::mat4 local = transform.Matrix();\n\n  transformed->World = parentWorld * local;\n\n  for (int i = 0; i < bone->ChildrenCount; i++) {\n    PoseBone(bone->Children[i]);\n  }\n\n  transformed->Offset = transformed->World * bone->BindInverse;\n}\n\nvoid Renderable3D::Update(float dt) {\n  if (!IsUsed) return;\n  if (Animator.CurrentAnimation) {\n    if (!Animator.IsPlaying) {\n      // oneshot ended\n      SwitchAnimation(StaticModel->IdleAnimation, AnimationTransitionTime);\n    } else {\n      Animator.Update(dt);\n    }\n  }\n  Pose();\n  if (PrevPoseWeight > 0.0f) {\n    PrevPoseWeight -= dt / AnimationTransitionTime;\n    if (PrevPoseWeight < 0.0f) PrevPoseWeight = 0.0f;\n  }\n\n  for (uint32_t i = 0; i < StaticModel->MeshCount; i++) {\n    CalculateMorphedVertices(i);\n  }\n}\n\nvoid Renderable3D::DrawMesh(int id, RenderPass pass) {\n  if (pass == RP_Outline && StaticModel->Type == ModelType_Background) return;\n\n  if (!MeshAnimStatus[id].Visible) return;\n\n  Mesh& mesh = StaticModel->Meshes[id];\n\n  if (pass == RP_First && mesh.Flags & MeshFlag_Later) return;\n  if (pass == RP_Second && !(mesh.Flags & MeshFlag_Later)) return;\n\n  if (pass == RP_Outline) {\n    UseMaterial(MT_Outline);\n  } else {\n    UseMaterial(mesh.Material);\n  }\n  // Because of the above, we use CurrentMaterial later, not mesh.Material\n\n  UseMesh(id);\n\n  switch (CurrentMaterial) {\n    case MT_Background: {\n      int constexpr backgroundTextureTypes[] = {TT_ColorMap};\n      SetTextures(id, backgroundTextureTypes, 1);\n      break;\n    }\n    case MT_Outline: {\n      if (Profile::Scene3D::Version == LKMVersion::DaSH) {\n        int constexpr outlineTextureTypes[] = {TT_DaSH_ColorMap,\n                                               TT_DaSH_NoiseMap};\n        SetTextures(id, outlineTextureTypes, 2);\n      } else {\n        int constexpr outlineTextureTypes[] = {TT_ColorMap};\n        SetTextures(id, outlineTextureTypes, 1);\n      }\n      break;\n    }\n    case MT_Generic: {\n      int constexpr genericTextureTypes[] = {TT_ColorMap, TT_GradientMaskMap,\n                                             TT_SpecularColorMap};\n      SetTextures(id, genericTextureTypes, 3);\n      break;\n    }\n    case MT_Eye: {\n      int constexpr eyeTextureTypes[] = {\n          TT_Eye_IrisColorMap, TT_Eye_WhiteColorMap, TT_Eye_HighlightColorMap,\n          TT_Eye_IrisSpecularColorMap};\n      SetTextures(id, eyeTextureTypes, 4);\n      break;\n    }\n    case MT_DaSH_Generic: {\n      int constexpr dashGenericTextureTypes[] = {\n          TT_DaSH_ColorMap, TT_DaSH_GradientMaskMap, TT_DaSH_SpecularColorMap,\n          TT_DaSH_ShadowColorMap};\n      SetTextures(id, dashGenericTextureTypes, 4);\n      break;\n    }\n    case MT_DaSH_Face:\n    case MT_DaSH_Skin: {\n      int constexpr dashGenericTextureTypes[] = {\n          TT_DaSH_ColorMap, TT_DaSH_GradientMaskMap, TT_DaSH_SpecularColorMap};\n      SetTextures(id, dashGenericTextureTypes, 3);\n      break;\n    }\n    case MT_DaSH_Eye: {\n      int constexpr dashEyeTextureTypes[] = {TT_DaSH_Eye_IrisColorMap,\n                                             TT_DaSH_Eye_WhiteColorMap,\n                                             TT_DaSH_Eye_HighlightColorMap};\n      SetTextures(id, dashEyeTextureTypes, 3);\n      break;\n    }\n    case MT_None:\n      break;\n  }\n\n  if (mesh.Opacity < 0.9 && CurrentMaterialIsDepthWrite) {\n    Device->SetRenderState(D3DRS_ZWRITEENABLE, 0);\n  }\n\n  size_t stride = Profile::Scene3D::Version == LKMVersion::DaSH\n                      ? sizeof(VertexBufferDaSH)\n                      : sizeof(VertexBuffer);\n  if (StaticModel->Type == ModelType_Background)\n    stride = sizeof(BgVertexBuffer);\n\n  if (StaticModel->Meshes[id].MorphTargetCount > 0) {\n    Device->SetStreamSource(\n        0, MorphedVerticesDevice,\n        (UINT)(MeshAnimStatus[id].MorphedVerticesOffset * stride),\n        (UINT)stride);\n\n  } else {\n    Device->SetStreamSource(0, MeshVertexBuffersDevice[id], 0, (UINT)stride);\n  }\n  Device->SetIndices(MeshIndexBuffersDevice[id]);\n\n  Device->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, 0, mesh.IndexCount, 0,\n                               mesh.IndexCount / 3);\n\n  if (mesh.Opacity < 0.9 && CurrentMaterialIsDepthWrite) {\n    Device->SetRenderState(D3DRS_ZWRITEENABLE, 1);\n  }\n\n  if (pass != RP_Outline) DrawMesh(id, RP_Outline);\n}\n\nvoid Renderable3D::Render() {\n  if (!IsUsed || !IsVisible) return;\n\n  memset(UniformsUpdated, 0, sizeof(UniformsUpdated));\n\n  for (int i = RP_First; i < RP_Count; i++) {\n    for (uint32_t j = 0; j < StaticModel->MeshCount; j++) {\n      DrawMesh(j, (RenderPass)i);\n    }\n  }\n\n  if (CurrentMaterial == MT_Outline) {\n    // Clean up some state\n    UseMaterial(MT_Background);\n  }\n}\n\nvoid Renderable3D::SetTextures(int id, int const* textureTypes, int count) {\n  for (int i = 0; i < count; i++) {\n    int t = textureTypes[i];\n    if (StaticModel->Meshes[id].Maps[t] >= 0) {\n      Device->SetTexture(i,\n                         Textures[TexBuffers[StaticModel->Meshes[id].Maps[t]]]);\n    } else {\n      Device->SetTexture(i, Textures[TextureDummy]);\n    }\n  }\n}\n\nvoid Renderable3D::LoadModelUniforms() {\n  if (StaticModel->Type == ModelType_Character) {\n    ModelUniformBufferType entry{};\n    entry.Model = ModelTransform.Matrix();\n    Device->SetVertexShaderConstantF(7, glm::value_ptr(entry.Model), 4);\n    Device->SetPixelShaderConstantF(7, glm::value_ptr(entry.Model), 4);\n  }\n}\n\nvoid Renderable3D::UseMaterial(MaterialType type) {\n  if (CurrentMaterial == type) return;\n\n  switch (type) {\n    case MT_Outline: {\n      ShaderOutline->UseShader(Device);\n      break;\n    }\n    case MT_Background: {\n      ShaderBackground->UseShader(Device);\n      break;\n    }\n    case MT_Generic:\n    case MT_DaSH_Generic:\n    case MT_DaSH_Face:\n    case MT_DaSH_Skin: {\n      ShaderMain->UseShader(Device);\n      break;\n    }\n    case MT_Eye:\n    case MT_DaSH_Eye: {\n      ShaderEye->UseShader(Device);\n      break;\n    }\n    case MT_None:\n      break;\n  }\n\n  SetSceneUniformValues();\n  LoadModelUniforms();\n\n  if (type == MT_Outline) {\n    Device->SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW);\n  } else {\n    Device->SetRenderState(D3DRS_CULLMODE, D3DCULL_CW);\n  }\n\n  if (type == MT_Outline) {\n    CurrentMaterialIsDepthWrite = false;\n  } else {\n    CurrentMaterialIsDepthWrite = true;\n  }\n\n  CurrentMaterial = type;\n}\n\nvoid Renderable3D::UseMesh(int id) { LoadMeshUniforms(id); }\n\nvoid Renderable3D::LoadMeshUniforms(int id) {\n  Mesh& mesh = StaticModel->Meshes[id];\n\n  if (StaticModel->Type == ModelType_Character) {\n    MeshUniformBufferType entry{};\n    if (mesh.UsedBones > 0) {\n      glm::mat4* outBone = entry.Bones;\n      for (uint32_t j = 0; j < mesh.UsedBones; j++) {\n        memcpy(outBone, glm::value_ptr(CurrentPose[mesh.BoneMap[j]].Offset),\n               sizeof(glm::mat4));\n        outBone++;\n      }\n    } else {\n      entry.Bones[0] = CurrentPose[mesh.MeshBone].Offset;\n    }\n\n    entry.ModelOpacity = mesh.Opacity;\n    entry.HasShadowColorMap =\n        mesh.Material == MT_DaSH_Generic && mesh.HasShadowColorMap;\n\n    Device->SetVertexShaderConstantB(1, (BOOL*)&entry.HasShadowColorMap, 1);\n    Device->SetVertexShaderConstantF(11, &entry.ModelOpacity, 1);\n    Device->SetVertexShaderConstantF(12, (float*)&entry.Bones,\n                                     4 * ModelMaxBonesPerMesh);\n\n    Device->SetPixelShaderConstantB(1, (BOOL*)&entry.HasShadowColorMap, 1);\n    Device->SetPixelShaderConstantF(11, &entry.ModelOpacity, 1);\n    Device->SetPixelShaderConstantF(12, (float*)&entry.Bones,\n                                    4 * ModelMaxBonesPerMesh);\n  } else if (StaticModel->Type == ModelType_Background) {\n    BgMVPUniformBufferType entry{};\n    entry.MVP = ViewProjection * mesh.ModelTransform.Matrix();\n    Device->SetVertexShaderConstantF(0, glm::value_ptr(entry.MVP), 4);\n  }\n}\n\nvoid Renderable3D::UnloadSync() {\n  Animator.CurrentAnimation = 0;\n  PrevPoseWeight = 0.0f;\n  if (StaticModel) {\n    ImpLog(LogLevel::Info, LogChannel::Renderable3D, \"Unloading model {:d}\\n\",\n           StaticModel->Id);\n    if (IsSubmitted) {\n      for (uint32_t i = 0; i < StaticModel->MeshCount; i++) {\n        MeshVertexBuffersDevice[i]->Release();\n        MeshIndexBuffersDevice[i]->Release();\n      }\n    }\n    delete StaticModel;\n    StaticModel = 0;\n  }\n  if (MorphedVerticesDevice) {\n    MorphedVerticesDevice->Release();\n  }\n  if (CurrentMorphedVertices) {\n    free(CurrentMorphedVertices);\n    CurrentMorphedVertices = 0;\n  }\n  ModelTransform = Transform();\n  IsSubmitted = false;\n  IsUsed = false;\n  IsVisible = false;\n}\n\nvoid Renderable3D::MainThreadOnLoad() {\n  assert(IsSubmitted == false);\n\n  ImpLog(LogLevel::Info, LogChannel::Renderable3D,\n         \"Submitting data to GPU for model ID {:d}\\n\", StaticModel->Id);\n\n  for (uint32_t i = 0; i < StaticModel->MeshCount; i++) {\n    if (StaticModel->Type == ModelType_Character) {\n      if (Profile::Scene3D::Version == LKMVersion::DaSH) {\n        Device->CreateVertexBuffer(\n            sizeof(VertexBufferDaSH) * StaticModel->Meshes[i].VertexCount, 0, 0,\n            D3DPOOL_MANAGED, &MeshVertexBuffersDevice[i], NULL);\n\n        VOID* pVoid;\n        auto res = MeshVertexBuffersDevice[i]->Lock(0, 0, (void**)&pVoid, 0);\n        memcpy(pVoid,\n               (VertexBufferDaSH*)StaticModel->VertexBuffers +\n                   StaticModel->Meshes[i].VertexOffset,\n               sizeof(VertexBufferDaSH) * StaticModel->Meshes[i].VertexCount);\n        res = MeshVertexBuffersDevice[i]->Unlock();\n      } else {\n        Device->CreateVertexBuffer(\n            sizeof(VertexBuffer) * StaticModel->Meshes[i].VertexCount, 0, 0,\n            D3DPOOL_MANAGED, &MeshVertexBuffersDevice[i], NULL);\n\n        VOID* pVoid;\n        auto res = MeshVertexBuffersDevice[i]->Lock(0, 0, (void**)&pVoid, 0);\n        memcpy(pVoid,\n               (VertexBuffer*)StaticModel->VertexBuffers +\n                   StaticModel->Meshes[i].VertexOffset,\n               sizeof(VertexBuffer) * StaticModel->Meshes[i].VertexCount);\n        res = MeshVertexBuffersDevice[i]->Unlock();\n      }\n    } else if (StaticModel->Type == ModelType_Background) {\n      Device->CreateVertexBuffer(\n          sizeof(BgVertexBuffer) * StaticModel->Meshes[i].VertexCount, 0, 0,\n          D3DPOOL_MANAGED, &MeshVertexBuffersDevice[i], NULL);\n\n      VOID* pVoid;\n      auto res = MeshVertexBuffersDevice[i]->Lock(0, 0, (void**)&pVoid, 0);\n      memcpy(pVoid,\n             (BgVertexBuffer*)StaticModel->VertexBuffers +\n                 StaticModel->Meshes[i].VertexOffset,\n             sizeof(BgVertexBuffer) * StaticModel->Meshes[i].VertexCount);\n      res = MeshVertexBuffersDevice[i]->Unlock();\n    }\n\n    Device->CreateIndexBuffer(\n        sizeof(uint16_t) * StaticModel->Meshes[i].IndexCount, 0, D3DFMT_INDEX16,\n        D3DPOOL_MANAGED, &MeshIndexBuffersDevice[i], NULL);\n    VOID* pVoid;\n    auto res = MeshIndexBuffersDevice[i]->Lock(0, 0, (void**)&pVoid, 0);\n    memcpy(pVoid, StaticModel->Indices + StaticModel->Meshes[i].IndexOffset,\n           sizeof(uint16_t) * StaticModel->Meshes[i].IndexCount);\n    res = MeshIndexBuffersDevice[i]->Unlock();\n  }\n\n  for (uint32_t i = 0; i < StaticModel->TextureCount; i++) {\n    TexBuffers[i] = StaticModel->Textures[i].Submit();\n    if (TexBuffers[i] == 0) {\n      ImpLog(LogLevel::Fatal, LogChannel::Renderable3D,\n             \"Submitting texture {:d} for model {:d} failed\\n\", i,\n             StaticModel->Id);\n    }\n  }\n\n  IsSubmitted = true;\n}\n\n}  // namespace DirectX9\n}  // namespace Impacto"
  },
  {
    "path": "src/renderer/dx9/3d/renderable3d.h",
    "content": "#pragma once\n\n#ifndef NOMINMAX\n#define NOMINMAX 1\n#endif\n\n#include \"../shader.h\"\n#include \"../window.h\"\n#include \"../../3d/renderable3d.h\"\n#include \"../utils.h\"\n\nnamespace Impacto {\nnamespace DirectX9 {\n\nenum RenderPass { RP_Outline = 0, RP_First = 1, RP_Second = 2, RP_Count };\n\nstruct SceneUniformBufferType {\n  glm::mat4 ViewProjection;\n  glm::vec4 Tint;\n  glm::vec3 WorldLightPosition;\n  glm::vec3 WorldEyePosition;\n  bool DarkMode;\n};\n\nstruct ModelUniformBufferType {\n  glm::mat4 Model;\n};\n\nstruct MeshUniformBufferType {\n  glm::mat4 Bones[ModelMaxBonesPerMesh];\n  float ModelOpacity;\n  bool HasShadowColorMap;\n};\n\nstruct BgMVPUniformBufferType {\n  glm::mat4 MVP;\n};\n\nclass Renderable3D : public IRenderable3D {\n public:\n  static void Init(DirectX9Window* window, IDirect3DDevice9* device);\n  static void BeginFrame(IScene3D* scene, Camera* camera);\n\n  void MakePlane() override;\n\n  void Update(float dt) override;\n  void Render() override;\n\n  void ReloadDefaultBoneTransforms() override;\n  void InitMeshAnimStatus() override;\n  void ReloadDefaultMeshAnimStatus() override;\n\n  void SwitchAnimation(int16_t animId, float transitionTime) override;\n\n protected:\n  bool LoadSync(uint32_t modelId) override;\n  void UnloadSync() override;\n  void MainThreadOnLoad() override;\n\n private:\n  void SetSceneUniformValues();\n\n  void Pose();\n  void PoseBone(int16_t id);\n\n  void CalculateMorphedVertices(int id);\n\n  void UseMaterial(MaterialType type);\n  void UseMesh(int id);\n  void LoadModelUniforms();\n  void LoadMeshUniforms(int id);\n  void SetTextures(int id, int const* textureUnits, int count);\n  void DrawMesh(int id, RenderPass pass);\n\n  IDirect3DVertexBuffer9* MeshVertexBuffersDevice[ModelMaxMeshesPerModel];\n  IDirect3DIndexBuffer9* MeshIndexBuffersDevice[ModelMaxMeshesPerModel];\n\n  void* CurrentMorphedVerticesDx;\n  IDirect3DVertexBuffer9* MorphedVerticesDevice;\n\n  uint32_t TexBuffers[ModelMaxTexturesPerModel];\n\n  bool UniformsUpdated[ModelMaxMeshesPerModel];\n\n  Transform PrevBoneTransforms[ModelMaxBonesPerModel];\n  AnimatedMesh PrevMeshAnimStatus[ModelMaxMeshesPerModel];\n  float PrevPoseWeight;\n  float AnimationTransitionTime;\n};\n\n}  // namespace DirectX9\n}  // namespace Impacto"
  },
  {
    "path": "src/renderer/dx9/3d/scene.cpp",
    "content": "#include \"scene.h\"\n\n#include \"../../3d/camera.h\"\n#include \"../../../log.h\"\n#include \"../../../workqueue.h\"\n#include \"renderable3d.h\"\n\n#include \"../../../profile/scene3d.h\"\n\nnamespace Impacto {\nnamespace DirectX9 {\n\nScene3D::Scene3D(DirectX9Window* window, IDirect3DDevice9* device) {\n  Window = window;\n  Device = device;\n}\n\nvoid Scene3D::Init() {\n  assert(IsInit == false);\n  ImpLog(LogLevel::Info, LogChannel::Scene, \"Initializing 3D scene system\\n\");\n  IsInit = true;\n\n  Profile::Scene3D::Configure();\n\n  Renderables = new IRenderable3D*[Profile::Scene3D::MaxRenderables];\n  for (int i = 0; i < Profile::Scene3D::MaxRenderables; i++) {\n    Renderables[i] = new Renderable3D();\n  }\n\n  Renderable3D::Init(Window, Device);\n\n  MainCamera.Init();\n\n  // clang-format off\n  float ScreenFillingTriangle[] = {\n      // Position       // UV\n      -1.0f, -1.0f,     0.0f, 0.0f,\n      3.0f, -1.0f,      2.0f, 0.0f,\n      -1.0f, 3.0f,      0.0f, 2.0f\n  };\n  // clang-format on\n}\n\nvoid Scene3D::Shutdown() {\n  if (!IsInit) return;\n  if (Renderables) delete[] Renderables;\n}\n\nvoid Scene3D::Update(float dt) {\n  for (int i = 0; i < Profile::Scene3D::MaxRenderables; i++) {\n    if (Renderables[i]->Status == LoadStatus::Loaded) {\n      Renderables[i]->Update(dt);\n    }\n  }\n}\nvoid Scene3D::Render() {\n  RectF viewport = Window->GetViewport();\n  MainCamera.AspectRatio = viewport.Width / viewport.Height;\n  MainCamera.Recalculate();\n\n  Renderable3D::BeginFrame(this, &MainCamera);\n\n  for (int i = 0; i < Profile::Scene3D::MaxRenderables; i++) {\n    if (Renderables[i]->Status == LoadStatus::Loaded &&\n        Renderables[i]->StaticModel->Type == ModelType_Background) {\n      Renderables[i]->Render();\n    }\n  }\n\n  Device->SetRenderState(D3DRS_ZENABLE, D3DZB_TRUE);\n\n  for (int i = 0; i < Profile::Scene3D::MaxRenderables; i++) {\n    if (Renderables[i]->Status == LoadStatus::Loaded &&\n        Renderables[i]->StaticModel->Type == ModelType_Character) {\n      Renderables[i]->Render();\n    }\n  }\n}\n\n}  // namespace DirectX9\n}  // namespace Impacto"
  },
  {
    "path": "src/renderer/dx9/3d/scene.h",
    "content": "#pragma once\n\n#include <glm/glm.hpp>\n\n#include \"../../3d/scene.h\"\n#include \"../window.h\"\n#include \"../renderer.h\"\n\nnamespace Impacto {\nnamespace DirectX9 {\n\nclass Scene3D : public IScene3D {\n public:\n  Scene3D(DirectX9Window* window, IDirect3DDevice9* device);\n\n  void Init();\n  void Shutdown();\n  void Update(float dt);\n  void Render();\n\n private:\n  bool IsInit = false;\n\n  DirectX9Window* Window;\n  IDirect3DDevice9* Device;\n};\n\n}  // namespace DirectX9\n}  // namespace Impacto"
  },
  {
    "path": "src/renderer/dx9/nv12frame.cpp",
    "content": "#include \"nv12frame.h\"\n\n#include \"../../log.h\"\n\nnamespace Impacto {\nnamespace DirectX9 {\n\nDX9NV12Frame::DX9NV12Frame(IDirect3DDevice9* device) { Device = device; }\n\nvoid DX9NV12Frame::Init(float width, float height) {\n  Width = width;\n  Height = height;\n  auto err = Device->CreateTexture((UINT)Width, (UINT)Height, 1, 0, D3DFMT_A8,\n                                   D3DPOOL_MANAGED, &Luma, nullptr);\n  err = Device->CreateTexture((UINT)(Width / 2), (UINT)(Height / 2), 1, 0,\n                              D3DFMT_A8L8, D3DPOOL_MANAGED, &CbCr, nullptr);\n}\n\nvoid Impacto::DirectX9::DX9NV12Frame::Submit(const void* luma, int lumaStride,\n                                             const void* cbcr, int cbcrStride) {\n  D3DLOCKED_RECT lockRect;\n  auto err = Luma->LockRect(0, &lockRect, NULL, 0);\n  const uint8_t* src = (const uint8_t*)luma;\n  uint8_t* dst = (uint8_t*)lockRect.pBits;\n  for (int y = 0; y < (int)Height; y++) {\n    memcpy(dst, src, (int)Width);\n    src += lumaStride;\n    dst += lockRect.Pitch;\n  }\n\n  err = Luma->UnlockRect(0);\n\n  err = CbCr->LockRect(0, &lockRect, NULL, 0);\n\n  src = (const uint8_t*)cbcr;\n  dst = (uint8_t*)lockRect.pBits;\n  for (int y = 0; y < (int)Height / 2; y++) {\n    memcpy(dst, src, (int)Width);\n    src += cbcrStride;\n    dst += lockRect.Pitch;\n  }\n\n  err = CbCr->UnlockRect(0);\n}\n\nvoid DX9NV12Frame::Release() {\n  Luma->Release();\n  CbCr->Release();\n}\n\n}  // namespace DirectX9\n}  // namespace Impacto"
  },
  {
    "path": "src/renderer/dx9/nv12frame.h",
    "content": "#pragma once\n\n#include \"../nv12frame.h\"\n\n#include <d3d9.h>\n\nnamespace Impacto {\nnamespace DirectX9 {\n\nclass DX9NV12Frame : public NV12Frame {\n  friend class Renderer;\n\n public:\n  DX9NV12Frame(IDirect3DDevice9* device);\n\n  void Init(float width, float height) override;\n\n  void Submit(const void* luma, int lumaStride, const void* cbcr,\n              int cbcrStride) override;\n  void Release() override;\n\n protected:\n  IDirect3DDevice9* Device;\n\n  IDirect3DTexture9* Luma;\n  IDirect3DTexture9* CbCr;\n};\n\n}  // namespace DirectX9\n}  // namespace Impacto"
  },
  {
    "path": "src/renderer/dx9/renderer.cpp",
    "content": "#include \"renderer.h\"\n\n#include <SDL_syswm.h>\n#include <array>\n\n#include \"../../profile/game.h\"\n#include \"../../game.h\"\n#include \"../../log.h\"\n#include \"3d/scene.h\"\n#include \"utils.h\"\n\n#ifndef IMPACTO_DISABLE_IMGUI\n#include <imgui_impl_dx9.h>\n#endif\n\nnamespace Impacto {\nnamespace DirectX9 {\n\nvoid Renderer::Init() {\n  if (IsInit) return;\n  ImpLog(LogLevel::Info, LogChannel::Render,\n         \"Initializing Renderer2D DirectX 9 system\\n\");\n  IsInit = true;\n\n  DXWindow = new DirectX9Window();\n  DXWindow->Init();\n  Window = (BaseWindow*)DXWindow;\n\n  Interface = Direct3DCreate9(D3D_SDK_VERSION);\n\n  SDL_SysWMinfo wmInfo;\n  SDL_VERSION(&wmInfo.version);\n  SDL_GetWindowWMInfo(Window->SDLWindow, &wmInfo);\n  HWND hWnd = wmInfo.info.win.window;\n\n  D3DPRESENT_PARAMETERS d3dpp{};\n  d3dpp.Windowed = TRUE;\n  d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;\n  d3dpp.hDeviceWindow = hWnd;\n  d3dpp.EnableAutoDepthStencil = TRUE;\n  d3dpp.AutoDepthStencilFormat = D3DFMT_D16;\n\n  Interface->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd,\n                          D3DCREATE_HARDWARE_VERTEXPROCESSING, &d3dpp, &Device);\n  Device->CreateVertexBuffer(VertexBufferSize, 0, 0, D3DPOOL_MANAGED,\n                             &VertexBufferDevice, NULL);\n  Device->CreateIndexBuffer(IndexBufferCount * sizeof(uint16_t), 0,\n                            D3DFMT_INDEX16, D3DPOOL_MANAGED, &IndexBufferDevice,\n                            NULL);\n\n  Device->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);\n  Device->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);\n  Device->SetRenderState(D3DRS_ALPHABLENDENABLE, true);\n  Device->SetRenderState(D3DRS_BLENDOPALPHA, D3DBLENDOP_ADD);\n  Device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);\n  Device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);\n  Device->SetRenderState(D3DRS_SRCBLENDALPHA, D3DBLEND_SRCALPHA);\n  Device->SetRenderState(D3DRS_DESTBLENDALPHA, D3DBLEND_INVSRCALPHA);\n  Device->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);\n\n  IDirect3DVertexDeclaration9* vertexDeclaration;\n  std::vector<D3DVERTEXELEMENT9> vertexDeclDesc{\n      {0, offsetof(VertexBufferSprites, Position), D3DDECLTYPE_FLOAT2,\n       D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0},\n      {0, offsetof(VertexBufferSprites, UV), D3DDECLTYPE_FLOAT2,\n       D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0},\n      {0, offsetof(VertexBufferSprites, Tint), D3DDECLTYPE_FLOAT4,\n       D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_COLOR, 0},\n      {0, offsetof(VertexBufferSprites, MaskUV), D3DDECLTYPE_FLOAT2,\n       D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 1},\n      D3DDECL_END()};\n  Device->CreateVertexDeclaration(vertexDeclDesc.data(), &vertexDeclaration);\n\n  ShaderSprite = new Shader();\n  ShaderSprite->Compile(\"Sprite\", Device, vertexDeclaration);\n  ShaderSpriteInverted = new Shader();\n  ShaderSpriteInverted->Compile(\"Sprite_inverted\", Device, vertexDeclaration);\n  ShaderMaskedSprite = new Shader();\n  ShaderMaskedSprite->Compile(\"MaskedSprite\", Device, vertexDeclaration);\n  ShaderMaskedSpriteNoAlpha = new Shader();\n  ShaderMaskedSpriteNoAlpha->Compile(\"MaskedSpriteNoAlpha\", Device,\n                                     vertexDeclaration);\n  ShaderYUVFrame = new Shader();\n  ShaderYUVFrame->Compile(\"YUVFrame\", Device, vertexDeclaration);\n  ShaderNV12Frame = new Shader();\n  ShaderNV12Frame->Compile(\"NV12Frame\", Device, vertexDeclaration);\n  ShaderCCMessageBox = new Shader();\n  ShaderCCMessageBox->Compile(\"CCMessageBoxSprite\", Device, vertexDeclaration);\n  ShaderCHLCCMenuBackground = new Shader();\n  ShaderCHLCCMenuBackground->Compile(\"CHLCCMenuBackground\", Device,\n                                     vertexDeclaration);\n\n  CurrentShader = ShaderSprite;\n  CurrentShader->UseShader(Device);\n\n  if (+Profile::GameFeatures & +GameFeature::Scene3D) {\n    Scene = new Scene3D(DXWindow, Device);\n    Scene->Init();\n  }\n\n  // Fill index buffer with quads\n  size_t index = 0;\n  uint16_t vertex = 0;\n  while (index + 6 <= IndexBufferCount) {\n    // bottom-left -> top-left -> top-right\n    IndexBuffer[index] = vertex + 0;\n    IndexBuffer[index + 1] = vertex + 1;\n    IndexBuffer[index + 2] = vertex + 2;\n    // bottom-left -> top-right -> bottom-right\n    IndexBuffer[index + 3] = vertex + 0;\n    IndexBuffer[index + 4] = vertex + 2;\n    IndexBuffer[index + 5] = vertex + 3;\n    index += 6;\n    vertex += 4;\n  }\n\n  // Make 1x1 white pixel for colored rectangles\n  Texture rectTexture;\n  rectTexture.Load1x1(0xFF, 0xFF, 0xFF, 0xFF);\n  SpriteSheet rectSheet(1.0f, 1.0f);\n  rectSheet.Texture = rectTexture.Submit();\n  RectSprite = Sprite(rectSheet, 0.0f, 0.0f, 1.0f, 1.0f);\n\n#ifndef IMPACTO_DISABLE_IMGUI\n  // Setup Platform/Renderer backends\n  ImGui_ImplSDL2_InitForD3D(DXWindow->SDLWindow);\n  ImGui_ImplDX9_Init(Device);\n#endif\n}\n\nvoid Renderer::Shutdown() {\n  if (!IsInit) return;\n  IsInit = false;\n\n  if (+Profile::GameFeatures & +GameFeature::Scene3D) {\n    Scene->Shutdown();\n  }\n\n  VertexBufferDevice->Release();\n  IndexBufferDevice->Release();\n  Device->Release();\n  Interface->Release();\n}\n\n#ifndef IMPACTO_DISABLE_IMGUI\nvoid Renderer::ImGuiBeginFrame() {\n  ImGui_ImplDX9_NewFrame();\n  ImGui_ImplSDL2_NewFrame();\n  ImGui::NewFrame();\n}\n#endif\n\nvoid Renderer::BeginFrame() {\n  if (Drawing) {\n    ImpLog(LogLevel::Error, LogChannel::Render,\n           \"Renderer->BeginFrame() called before EndFrame()\\n\");\n    return;\n  }\n\n  Drawing = true;\n  VertexBufferFill = 0;\n  IndexBufferFill = 0;\n\n  Device->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER,\n                D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);\n  Device->BeginScene();\n}\n\nvoid Renderer::BeginFrame2D() {\n  Device->SetRenderState(D3DRS_ZENABLE, D3DZB_FALSE);\n  Device->SetRenderState(D3DRS_ZWRITEENABLE, 0);\n  Device->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);\n}\n\nvoid Renderer::EndFrame() {\n  if (!Drawing) return;\n  Flush();\n  Drawing = false;\n\n#ifndef IMPACTO_DISABLE_IMGUI\n  ImGui::Render();\n  ImGui_ImplDX9_RenderDrawData(ImGui::GetDrawData());\n#endif\n\n  Device->EndScene();\n  Device->Present(NULL, NULL, NULL, NULL);\n  CurrentShader = 0;\n  CurrentTexture = std::numeric_limits<uint32_t>::max();\n}\n\nuint32_t Renderer::SubmitTexture(TexFmt format, uint8_t* buffer, int width,\n                                 int height) {\n  int imageSize = 0;\n  D3DFORMAT imageFormat = D3DFMT_A1;\n  uint8_t* newBuffer = nullptr;\n\n  switch (format) {\n    case TexFmt_RGBA: {\n      imageSize = width * height * 4;\n      imageFormat = D3DFMT_A8R8G8B8;\n      newBuffer = (uint8_t*)malloc(imageSize);\n      int num = width * height;\n      for (int i = 0; i < num; i++) {\n        newBuffer[4 * i] = buffer[4 * i + 2];\n        newBuffer[4 * i + 1] = buffer[4 * i + 1];\n        newBuffer[4 * i + 2] = buffer[4 * i];\n        newBuffer[4 * i + 3] = buffer[4 * i + 3];\n      }\n    } break;\n    case TexFmt_RGB: {\n      imageSize = width * height * 4;\n      imageFormat = D3DFMT_A8R8G8B8;\n      newBuffer = (uint8_t*)malloc(imageSize);\n      int num = width * height;\n      for (int i = 0; i < num; i++) {\n        newBuffer[4 * i] = buffer[3 * i + 2];\n        newBuffer[4 * i + 1] = buffer[3 * i + 1];\n        newBuffer[4 * i + 2] = buffer[3 * i];\n        newBuffer[4 * i + 3] = 0xff;\n      }\n    } break;\n    case TexFmt_U8:\n      imageSize = width * height;\n      imageFormat = D3DFMT_A8;\n  }\n\n  IDirect3DTexture9* texture;\n  auto err = Device->CreateTexture(width, height, 1, 0, imageFormat,\n                                   D3DPOOL_MANAGED, &texture, nullptr);\n  D3DLOCKED_RECT lockRect;\n  err = texture->LockRect(0, &lockRect, NULL, 0);\n\n  if (format == TexFmt_RGBA || format == TexFmt_RGB) {\n    memcpy(lockRect.pBits, newBuffer, imageSize);\n    free(newBuffer);\n  } else {\n    memcpy(lockRect.pBits, buffer, imageSize);\n  }\n\n  if (FAILED(texture->UnlockRect(0))) {\n    ImpLog(LogLevel::Error, LogChannel::Render, \"Failed to load texture!\\n\");\n    Window->Shutdown();\n  }\n\n  auto id = NextTextureId;\n  NextTextureId += 1;\n  Textures[id] = texture;\n\n  return id;\n}\n\nvoid Renderer::FreeTexture(uint32_t id) {\n  auto textureItr = Textures.find(id);\n  if (textureItr != Textures.end()) {\n    if (textureItr->second != nullptr) {\n      textureItr->second->Release();\n    }\n    Textures.erase(textureItr);\n  }\n}\n\nYUVFrame* Renderer::CreateYUVFrame(float width, float height) {\n  VideoFrameInternalYUV = new DX9YUVFrame(Device);\n  VideoFrameInternalYUV->Init(width, height);\n  return (YUVFrame*)VideoFrameInternalYUV;\n}\n\nNV12Frame* Renderer::CreateNV12Frame(float width, float height) {\n  VideoFrameInternalNV12 = new DX9NV12Frame(Device);\n  VideoFrameInternalNV12->Init(width, height);\n  return (NV12Frame*)VideoFrameInternalNV12;\n}\n\nvoid Renderer::DrawSprite(const Sprite& sprite, const CornersQuad& dest,\n                          const glm::mat4 transformation,\n                          const std::span<const glm::vec4, 4> tints,\n                          const glm::vec3 colorShift, const bool inverted,\n                          const bool disableBlend,\n                          const bool textureWrapRepeat) {\n  if (!Drawing) {\n    ImpLog(LogLevel::Error, LogChannel::Render,\n           \"Renderer->DrawSprite() called before BeginFrame()\\n\");\n    return;\n  }\n\n  // Do we have space for one more sprite quad?\n  EnsureSpaceAvailable(4, sizeof(VertexBufferSprites), 6);\n\n  // Are we in sprite mode?\n  if (inverted)\n    EnsureShader(ShaderSpriteInverted);\n  else\n    EnsureShader(ShaderSprite);\n\n  // Do we have the texture assigned?\n  EnsureTextureBound(sprite.Sheet.Texture);\n\n  // OK, all good, make quad\n\n  VertexBufferSprites* vertices =\n      (VertexBufferSprites*)(VertexBuffer + VertexBufferFill);\n  VertexBufferFill += 4 * sizeof(VertexBufferSprites);\n\n  IndexBufferFill += 6;\n\n  QuadSetUV(sprite.Bounds, sprite.Sheet.GetDimensions(), &vertices[0].UV,\n            sizeof(VertexBufferSprites));\n  QuadSetPosition(CornersQuad(dest).Transform(transformation),\n                  &vertices[0].Position, sizeof(VertexBufferSprites));\n\n  for (int i = 0; i < 4; i++) vertices[i].Tint = tints[i];\n}\n\nvoid Renderer::DrawMaskedSprite(\n    const Sprite& sprite, const Sprite& mask, const CornersQuad& spriteDest,\n    const CornersQuad& maskDest, int alpha, const int fadeRange,\n    const glm::mat4 spriteTransformation, const glm::mat4 maskTransformation,\n    const std::span<const glm::vec4, 4> tints, const bool isInverted,\n    const bool isSameTexture) {\n  if (!Drawing) {\n    ImpLog(LogLevel::Error, LogChannel::Render,\n           \"Renderer->DrawMaskedSpriteOverlay() called before BeginFrame()\\n\");\n    return;\n  }\n\n  if (sprite.Sheet.IsScreenCap) Flush();\n\n  alpha = std::clamp(alpha, 0, fadeRange + 256);\n  const float alphaRange = 256.0f / fadeRange;\n  const float constAlpha = ((255.0f - alpha) * alphaRange) / 255.0f;\n\n  // Do we have space for one more sprite quad?\n  EnsureSpaceAvailable(4, sizeof(VertexBufferSprites), 6);\n\n  std::array<float, 4> alphaRes = {alphaRange, constAlpha, 0.0f, 0.0f};\n  const BOOL isInvertedB = (BOOL)isInverted;\n  const BOOL isSameTextureB = (BOOL)isSameTexture;\n\n  Flush();\n\n  EnsureShader(ShaderMaskedSprite);\n  Device->SetTexture(0, Textures[sprite.Sheet.Texture]);\n  Device->SetTexture(1, Textures[mask.Sheet.Texture]);\n\n  Device->SetSamplerState(1, D3DSAMP_ADDRESSU, D3DTADDRESS_CLAMP);\n  Device->SetSamplerState(1, D3DSAMP_ADDRESSV, D3DTADDRESS_CLAMP);\n  Device->SetPixelShaderConstantF(0, alphaRes.data(), 1);\n  Device->SetPixelShaderConstantB(0, &isInvertedB, 1);\n  Device->SetPixelShaderConstantB(1, &isSameTextureB, 1);\n\n  // OK, all good, make quad\n\n  VertexBufferSprites* vertices =\n      (VertexBufferSprites*)(VertexBuffer + VertexBufferFill);\n  VertexBufferFill += 4 * sizeof(VertexBufferSprites);\n\n  IndexBufferFill += 6;\n\n  QuadSetUV(sprite.Bounds, sprite.Sheet.GetDimensions(), &vertices[0].UV,\n            sizeof(VertexBufferSprites));\n  QuadSetUV(CornersQuad(maskDest).Transform(maskTransformation),\n            sprite.Sheet.GetDimensions(), &vertices[0].MaskUV,\n            sizeof(VertexBufferSprites));\n\n  QuadSetPosition(CornersQuad(spriteDest).Transform(spriteTransformation),\n                  &vertices[0].Position, sizeof(VertexBufferSprites));\n\n  for (int i = 0; i < 4; i++) vertices[i].Tint = tints[i];\n}\n\nvoid Renderer::DrawMaskedSpriteOverlay(\n    const Sprite& sprite, const Sprite& mask, const CornersQuad& spriteDest,\n    const CornersQuad& maskDest, int alpha, const int fadeRange,\n    const glm::mat4 spriteTransformation, const glm::mat4 maskTransformation,\n    const std::span<const glm::vec4, 4> tints, const bool isInverted,\n    const bool useMaskAlpha) {\n  if (!Drawing) {\n    ImpLog(LogLevel::Error, LogChannel::Render,\n           \"Renderer->DrawMaskedSpriteOverlay() called before BeginFrame()\\n\");\n    return;\n  }\n\n  if (sprite.Sheet.IsScreenCap) Flush();\n\n  if (alpha < 0) alpha = 0;\n  if (alpha > fadeRange + 256) alpha = fadeRange + 256;\n\n  float alphaRange = 256.0f / fadeRange;\n  float constAlpha = ((255.0f - alpha) * alphaRange) / 255.0f;\n\n  // Do we have space for one more sprite quad?\n  EnsureSpaceAvailable(4, sizeof(VertexBufferSprites), 6);\n\n  std::array<float, 4> alphaRes = {alphaRange, constAlpha, 0.0f, 0.0f};\n  BOOL isInvertedB = (BOOL)isInverted;\n  BOOL isSameTextureB = (BOOL) false;\n\n  if (useMaskAlpha) {\n    EnsureShader(ShaderMaskedSprite);\n    Device->SetPixelShaderConstantB(1, &isSameTextureB, 1);\n  } else {\n    EnsureShader(ShaderMaskedSpriteNoAlpha);\n  }\n\n  Device->SetTexture(0, Textures[sprite.Sheet.Texture]);\n  Device->SetTexture(1, Textures[mask.Sheet.Texture]);\n\n  Device->SetSamplerState(1, D3DSAMP_ADDRESSU, D3DTADDRESS_CLAMP);\n  Device->SetSamplerState(1, D3DSAMP_ADDRESSV, D3DTADDRESS_CLAMP);\n  Device->SetPixelShaderConstantF(0, alphaRes.data(), 1);\n  Device->SetPixelShaderConstantB(0, &isInvertedB, 1);\n\n  // OK, all good, make quad\n\n  VertexBufferSprites* vertices =\n      (VertexBufferSprites*)(VertexBuffer + VertexBufferFill);\n  VertexBufferFill += 4 * sizeof(VertexBufferSprites);\n\n  IndexBufferFill += 6;\n\n  QuadSetUV(sprite.Bounds, sprite.Sheet.GetDimensions(), &vertices[0].UV,\n            sizeof(VertexBufferSprites));\n  QuadSetUV(CornersQuad(maskDest).Transform(maskTransformation),\n            mask.Sheet.GetDimensions(), &vertices[0].MaskUV,\n            sizeof(VertexBufferSprites));\n\n  QuadSetPosition(CornersQuad(spriteDest).Transform(spriteTransformation),\n                  &vertices[0].Position, sizeof(VertexBufferSprites));\n\n  for (int i = 0; i < 4; i++) vertices[i].Tint = tints[i];\n}\n\nvoid Renderer::DrawPrimitives(\n    const SpriteSheet& sheet, const SpriteSheet* const mask,\n    const ShaderProgramType shaderType,\n    const std::span<const VertexBufferSprites> vertices,\n    const std::span<const uint16_t> indices,\n    const glm::mat4 spriteTransformation, const glm::mat4 maskTransformation,\n    const bool inverted, TopologyMode topologyMode, bool textureWrapRepeat) {\n  if (!Drawing) {\n    ImpLog(LogLevel::Error, LogChannel::Render,\n           \"Renderer->DrawVertices() called before BeginFrame()\\n\");\n    return;\n  }\n\n  // The index buffer needs to be flushed\n  Flush();\n\n  if (mask != nullptr) {\n    std::array<float, 4> alphaRes = {1.0f, 0.0f, 0.0f, 0.0f};\n    BOOL isInvertedB = (BOOL)inverted;\n\n    EnsureShader(ShaderMaskedSpriteNoAlpha);\n\n    Device->SetTexture(0, Textures[sheet.Texture]);\n    Device->SetTexture(1, Textures[mask->Texture]);\n\n    Device->SetSamplerState(1, D3DSAMP_ADDRESSU, D3DTADDRESS_CLAMP);\n    Device->SetSamplerState(1, D3DSAMP_ADDRESSV, D3DTADDRESS_CLAMP);\n    Device->SetPixelShaderConstantF(0, alphaRes.data(), 1);\n    Device->SetPixelShaderConstantB(0, &isInvertedB, 1);\n\n  } else {\n    if (inverted)\n      EnsureShader(ShaderSpriteInverted);\n    else\n      EnsureShader(ShaderSprite);\n\n    EnsureTextureBound(sheet.Texture);\n  }\n\n  // Push vertices\n  VertexBufferSprites* vertexBuffer =\n      (VertexBufferSprites*)(VertexBuffer + VertexBufferFill);\n  VertexBufferFill += vertices.size_bytes();\n\n  const auto vertexInfoToNDC = [this, spriteTransformation,\n                                maskTransformation](VertexBufferSprites info) {\n    info.Position = DesignToNDC(\n        glm::vec2(spriteTransformation * glm::vec4(info.Position, 0.0f, 1.0f)));\n    info.MaskUV =\n        glm::vec2(maskTransformation * glm::vec4(info.MaskUV, 0.0f, 1.0f));\n    return info;\n  };\n  std::transform(vertices.begin(), vertices.end(), vertexBuffer,\n                 vertexInfoToNDC);\n\n  // Push indices\n  IndexBufferFill += indices.size();\n  std::ranges::copy(indices, IndexBuffer);\n\n  // Flush again and bind back our buffer\n  Flush();\n\n  // ReFill index buffer with quads\n  size_t index = 0;\n  uint16_t vertex = 0;\n  while (index + 6 <= IndexBufferCount) {\n    // bottom-left -> top-left -> top-right\n    IndexBuffer[index] = vertex + 0;\n    IndexBuffer[index + 1] = vertex + 1;\n    IndexBuffer[index + 2] = vertex + 2;\n    // bottom-left -> top-right -> bottom-right\n    IndexBuffer[index + 3] = vertex + 0;\n    IndexBuffer[index + 4] = vertex + 2;\n    IndexBuffer[index + 5] = vertex + 3;\n    index += 6;\n    vertex += 4;\n  }\n}\n\nvoid Renderer::DrawCCMessageBox(Sprite const& sprite, Sprite const& mask,\n                                RectF const& dest, glm::vec4 tint, int alpha,\n                                int fadeRange, float effectCt) {\n  if (!Drawing) {\n    ImpLog(LogLevel::Error, LogChannel::Render,\n           \"Renderer->DrawCCMessageBox() called before BeginFrame()\\n\");\n    return;\n  }\n\n  if (alpha < 0) alpha = 0;\n  if (alpha > fadeRange + 256) alpha = fadeRange + 256;\n\n  float alphaRange = 256.0f / fadeRange;\n  float constAlpha = ((255.0f - alpha) * alphaRange) / 255.0f;\n\n  // Do we have space for one more sprite quad?\n  EnsureSpaceAvailable(4, sizeof(VertexBufferSprites), 6);\n\n  EnsureShader(ShaderCCMessageBox);\n\n  Device->SetTexture(0, Textures[sprite.Sheet.Texture]);\n  CurrentTexture = sprite.Sheet.Texture;\n  Device->SetTexture(1, Textures[mask.Sheet.Texture]);\n\n  // This is cursed man, idk\n  float alphaRes[] = {alphaRange, constAlpha, effectCt, 0.0f};\n  Device->SetPixelShaderConstantF(0, alphaRes, 1);\n\n  // OK, all good, make quad\n\n  VertexBufferSprites* vertices =\n      (VertexBufferSprites*)(VertexBuffer + VertexBufferFill);\n  VertexBufferFill += 4 * sizeof(VertexBufferSprites);\n\n  IndexBufferFill += 6;\n\n  QuadSetUV(sprite.Bounds, sprite.Sheet.GetDimensions(), &vertices[0].UV,\n            sizeof(VertexBufferSprites));\n  QuadSetUV(mask.Bounds, mask.Sheet.GetDimensions(), &vertices[0].MaskUV,\n            sizeof(VertexBufferSprites));\n\n  QuadSetPosition(dest, &vertices[0].Position, sizeof(VertexBufferSprites));\n\n  for (int i = 0; i < 4; i++) vertices[i].Tint = tint;\n}\n\nvoid Renderer::DrawCHLCCMenuBackground(const Sprite& sprite, const Sprite& mask,\n                                       const RectF& dest, float alpha) {\n  if (!Drawing) {\n    ImpLog(LogLevel::Error, LogChannel::Render,\n           \"Renderer->DrawCHLCCMenuBackground() called before BeginFrame()\\n\");\n    return;\n  }\n\n  if (alpha < 0.0f)\n    alpha = 0;\n  else if (alpha > 1.0f)\n    alpha = 1.0f;\n\n  // Do we have space for one more sprite quad?\n  EnsureSpaceAvailable(4, sizeof(VertexBufferSprites), 6);\n\n  EnsureShader(ShaderCHLCCMenuBackground);\n\n  Device->SetTexture(0, Textures[sprite.Sheet.Texture]);\n  CurrentTexture = sprite.Sheet.Texture;\n  Device->SetTexture(1, Textures[mask.Sheet.Texture]);\n\n  // This is cursed man, idk\n  float alphaRes[] = {alpha, 0.0f, 0.0f, 0.0f};\n  Device->GetPixelShaderConstantF(0, alphaRes, 1);\n\n  // OK, all good, make quad\n\n  VertexBufferSprites* vertices =\n      (VertexBufferSprites*)(VertexBuffer + VertexBufferFill);\n  VertexBufferFill += 4 * sizeof(VertexBufferSprites);\n\n  IndexBufferFill += 6;\n\n  QuadSetUVFlipped(sprite.Bounds, sprite.Sheet.GetDimensions(), &vertices[0].UV,\n                   sizeof(VertexBufferSprites));\n\n  QuadSetUV(mask.Bounds, mask.Sheet.GetDimensions(), &vertices[0].MaskUV,\n            sizeof(VertexBufferSprites));\n\n  QuadSetPosition(dest, &vertices[0].Position, sizeof(VertexBufferSprites));\n\n  for (int i = 0; i < 4; i++)\n    vertices[i].Tint = glm::vec4(1.0f, 1.0f, 1.0f, 1.0f);\n}\n\nvoid Renderer::EnsureSpaceAvailable(int vertices, int vertexSize, int indices) {\n  if (VertexBufferFill + vertices * vertexSize > VertexBufferSize ||\n      IndexBufferFill + indices > IndexBufferCount) {\n    ImpLogSlow(\n        LogLevel::Trace, LogChannel::Render,\n        \"Renderer->EnsureSpaceAvailable flushing because buffers full at \"\n        \"VertexBufferFill=0x{:08x},IndexBufferFill=0x{:08x}\\n\",\n        VertexBufferFill, IndexBufferFill);\n    Flush();\n  }\n}\n\nvoid Renderer::EnsureTextureBound(uint32_t texture) {\n  if (CurrentTexture != texture) {\n    ImpLogSlow(LogLevel::Trace, LogChannel::Render,\n               \"Renderer->EnsureTextureBound flushing because texture {:d} is \"\n               \"not {:d}\\n\",\n               CurrentTexture, texture);\n    Flush();\n    Device->SetTexture(0, Textures[texture]);\n    CurrentTexture = texture;\n  }\n}\n\nvoid Renderer::EnsureShader(Shader* shader, bool flush) {\n  if (CurrentShader != shader) {\n    ImpLogSlow(LogLevel::Debug, LogChannel::Render,\n               \"Renderer2D changing mode\\n\");\n    if (flush) Flush();\n    shader->UseShader(Device);\n    CurrentShader = shader;\n  }\n}\n\nvoid Renderer::Flush() {\n  if (!Drawing) {\n    ImpLog(LogLevel::Error, LogChannel::Render,\n           \"Renderer->Flush() called before BeginFrame()\\n\");\n    return;\n  }\n\n  if (VertexBufferFill > 0 && IndexBufferFill > 0) {\n    VOID* pVoid;\n    auto res = VertexBufferDevice->Lock(0, 0, (void**)&pVoid, 0);\n    memcpy(pVoid, VertexBuffer, VertexBufferFill);\n    res = VertexBufferDevice->Unlock();\n\n    res = IndexBufferDevice->Lock(0, 0, (void**)&pVoid, 0);\n    memcpy(pVoid, IndexBuffer, IndexBufferFill * sizeof(uint16_t));\n    res = IndexBufferDevice->Unlock();\n\n    auto result = Device->SetStreamSource(0, VertexBufferDevice, 0,\n                                          sizeof(VertexBufferSprites));\n    if (FAILED(result)) {\n      ImpLog(LogLevel::Debug, LogChannel::Render,\n             \"Failed to set stream source!\\n\");\n      Window->Shutdown();\n    }\n\n    result = Device->SetIndices(IndexBufferDevice);\n    if (FAILED(result)) {\n      ImpLog(LogLevel::Debug, LogChannel::Render,\n             \"Failed to set stream indices!\\n\");\n      Window->Shutdown();\n    }\n\n    result = Device->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, 0,\n                                          (UINT)IndexBufferFill, 0,\n                                          (UINT)(IndexBufferFill / 3));\n    if (FAILED(result)) {\n      ImpLog(LogLevel::Debug, LogChannel::Render, \"Failed to draw!\\n\");\n      Window->Shutdown();\n    }\n  }\n  IndexBufferFill = 0;\n  VertexBufferFill = 0;\n  CurrentTexture = std::numeric_limits<uint32_t>::max();\n}\n\nvoid Renderer::DrawVideoTexture(const YUVFrame& frame, const RectF& dest,\n                                const glm::vec4 tint, const bool alphaVideo) {\n  if (!Drawing) {\n    ImpLog(LogLevel::Error, LogChannel::Render,\n           \"Renderer->DrawVideoTexture() called before BeginFrame()\\n\");\n    return;\n  }\n\n  EnsureShader(ShaderYUVFrame);\n  CurrentTexture = std::numeric_limits<uint32_t>::max();\n\n  // Do we have space for one more sprite quad?\n  EnsureSpaceAvailable(4, sizeof(VertexBufferSprites), 6);\n\n  auto err = Device->SetTexture(0, VideoFrameInternalYUV->Luma);\n  err = Device->SetTexture(1, VideoFrameInternalYUV->Cb);\n  err = Device->SetTexture(2, VideoFrameInternalYUV->Cr);\n\n  // This is cursed man, idk\n  BOOL alphaVideoB = (BOOL)alphaVideo;\n  Device->SetPixelShaderConstantB(0, &alphaVideoB, 1);\n\n  // OK, all good, make quad\n\n  VertexBufferSprites* vertices =\n      (VertexBufferSprites*)(VertexBuffer + VertexBufferFill);\n  VertexBufferFill += 4 * sizeof(VertexBufferSprites);\n\n  IndexBufferFill += 6;\n\n  QuadSetUV(RectF(0.0f, 0.0f, frame.Width, frame.Height),\n            {frame.Width, frame.Height}, &vertices[0].UV,\n            sizeof(VertexBufferSprites));\n  QuadSetPosition(dest, &vertices[0].Position, sizeof(VertexBufferSprites));\n\n  for (int i = 0; i < 4; i++) vertices[i].Tint = tint;\n}\n\nvoid Renderer::DrawVideoTexture(const NV12Frame& frame, const RectF& dest,\n                                const glm::vec4 tint, const bool alphaVideo) {\n  if (!Drawing) {\n    ImpLog(LogLevel::Error, LogChannel::Render,\n           \"Renderer->DrawVideoTexture() called before BeginFrame()\\n\");\n    return;\n  }\n\n  EnsureShader(ShaderNV12Frame);\n  CurrentTexture = std::numeric_limits<uint32_t>::max();\n\n  // Do we have space for one more sprite quad?\n  EnsureSpaceAvailable(4, sizeof(VertexBufferSprites), 6);\n\n  auto err = Device->SetTexture(0, VideoFrameInternalNV12->Luma);\n  err = Device->SetTexture(1, VideoFrameInternalNV12->CbCr);\n\n  // This is cursed man, idk\n  BOOL alphaVideoB = (BOOL)alphaVideo;\n  Device->SetPixelShaderConstantB(0, &alphaVideoB, 1);\n\n  // OK, all good, make quad\n\n  VertexBufferSprites* vertices =\n      (VertexBufferSprites*)(VertexBuffer + VertexBufferFill);\n  VertexBufferFill += 4 * sizeof(VertexBufferSprites);\n\n  IndexBufferFill += 6;\n\n  QuadSetUV(RectF(0.0f, 0.0f, frame.Width, frame.Height),\n            {frame.Width, frame.Height}, &vertices[0].UV,\n            sizeof(VertexBufferSprites));\n  QuadSetPosition(dest, &vertices[0].Position, sizeof(VertexBufferSprites));\n\n  for (int i = 0; i < 4; i++) vertices[i].Tint = tint;\n}\n\nvoid Renderer::CaptureScreencap(Sprite& sprite) {\n  Flush();\n  if (sprite.Sheet.Texture != 0) {\n    FreeTexture(sprite.Sheet.Texture);\n  }\n\n  sprite.Sheet.IsScreenCap = true;\n\n  IDirect3DSurface9* sourceSurface = nullptr;\n  Device->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &sourceSurface);\n\n  D3DSURFACE_DESC backBufferDesc;\n  sourceSurface->GetDesc(&backBufferDesc);\n\n  sprite.Sheet.DesignWidth = static_cast<float>(backBufferDesc.Width);\n  sprite.Sheet.DesignHeight = static_cast<float>(backBufferDesc.Height);\n  sprite.Bounds = RectF{0.0f, 0.0f, static_cast<float>(backBufferDesc.Width),\n                        static_cast<float>(backBufferDesc.Height)};\n  sprite.BaseScale = {Profile::DesignWidth / backBufferDesc.Width,\n                      Profile::DesignHeight / backBufferDesc.Height};\n\n  IDirect3DTexture9* texture;\n  Device->CreateTexture(backBufferDesc.Width, backBufferDesc.Height, 1,\n                        D3DUSAGE_RENDERTARGET, D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT,\n                        &texture, nullptr);\n  auto id = NextTextureId;\n  NextTextureId += 1;\n  Textures[id] = texture;\n  sprite.Sheet.Texture = id;\n\n  IDirect3DSurface9* destSurface = nullptr;\n  texture->GetSurfaceLevel(0, &destSurface);\n  Device->StretchRect(sourceSurface, nullptr, destSurface, nullptr,\n                      D3DTEXF_NONE);\n\n  sourceSurface->Release();\n  destSurface->Release();\n}\n\nvoid Renderer::EnableScissor() {\n  Flush();\n  Device->SetRenderState(D3DRS_SCISSORTESTENABLE, true);\n}\n\nvoid Renderer::SetScissorRect(RectF const& rect) {\n  Rect viewport = Window->GetViewport();\n  float scale = fmin((float)Window->WindowWidth / Profile::DesignWidth,\n                     (float)Window->WindowHeight / Profile::DesignHeight);\n  float rectX = rect.X * scale;\n  float rectY = rect.Y * scale;\n  float rectWidth = rect.Width * scale;\n  float rectHeight = rect.Height * scale;\n\n  RECT rectW{};\n  rectW.left = (LONG)rectX;\n  rectW.top = (LONG)rectY;\n  rectW.right = (LONG)(rect.X + rectWidth);\n  rectW.bottom = (LONG)(rect.Y + rectHeight);\n  Device->SetScissorRect(&rectW);\n}\n\nvoid Renderer::DisableScissor() {\n  Flush();\n  Device->SetRenderState(D3DRS_SCISSORTESTENABLE, false);\n}\n\nglm::vec2 Renderer::DesignToNDC(glm::vec2 designCoord) const {\n  glm::vec2 result;\n  result.x = (designCoord.x / (Profile::DesignWidth * 0.5f)) - 1.0f;\n  result.y = 1.0f - (designCoord.y / (Profile::DesignHeight * 0.5f));\n  return result;\n}\n\nvoid Renderer::SetBlendMode(RendererBlendMode blendMode) {\n  Flush();\n\n  switch (blendMode) {\n    case RendererBlendMode::Normal:\n      Device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);\n      Device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);\n      break;\n    case RendererBlendMode::Additive:\n      Device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);\n      Device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ONE);\n      break;\n    case RendererBlendMode::Premultiplied:\n      Device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ONE);\n      Device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);\n      break;\n  }\n}\n\nvoid Renderer::Clear(glm::vec4 color) {\n  Flush();\n\n  color *= 255;\n  D3DCOLOR clearColor =\n      D3DCOLOR_ARGB((BYTE)color.a, (BYTE)color.r, (BYTE)color.g, (BYTE)color.b);\n  Device->Clear(0, NULL, D3DCLEAR_TARGET, clearColor, 1.0f, 0);\n}\n\n}  // namespace DirectX9\n}  // namespace Impacto\n"
  },
  {
    "path": "src/renderer/dx9/renderer.h",
    "content": "#pragma once\n\n#include \"../renderer.h\"\n#include \"window.h\"\n#include \"shader.h\"\n#include \"yuvframe.h\"\n#include \"nv12frame.h\"\n\n#include <d3d9.h>\n\nnamespace Impacto {\nnamespace DirectX9 {\n\nclass Renderer : public BaseRenderer {\n public:\n  void Init() override;\n  void Shutdown() override;\n\n#ifndef IMPACTO_DISABLE_IMGUI\n  void ImGuiBeginFrame() override;\n#endif\n\n  void BeginFrame() override;\n  void BeginFrame2D() override;\n  void EndFrame() override;\n\n  uint32_t SubmitTexture(TexFmt format, uint8_t* buffer, int width,\n                         int height) override;\n\n  int GetSpriteSheetImage(SpriteSheet const& sheet,\n                          std::span<uint8_t> outBuffer) override {\n    // TODO implement\n    return 0;\n  }\n  void FreeTexture(uint32_t id) override;\n  YUVFrame* CreateYUVFrame(float width, float height) override;\n  NV12Frame* CreateNV12Frame(float width, float height) override;\n\n  void DrawSprite(const Sprite& sprite, const CornersQuad& dest,\n                  glm::mat4 transformation, std::span<const glm::vec4, 4> tints,\n                  glm::vec3 colorShift, bool inverted, bool disableBlend,\n                  bool textureWrapRepeat) override;\n\n  void DrawMaskedSprite(const Sprite& sprite, const Sprite& mask,\n                        const CornersQuad& spriteDest,\n                        const CornersQuad& maskDest, int alpha, int fadeRange,\n                        glm::mat4 spriteTransformation,\n                        glm::mat4 maskTransformation,\n                        std::span<const glm::vec4, 4> tints, bool isInverted,\n                        bool isSameTexture) override;\n\n  // TODO: implement\n  void DrawMaskedBinarySprite(const Sprite& sprite, const Sprite& mask,\n                              const CornersQuad& spriteDest,\n                              const CornersQuad& maskDest,\n                              glm::mat4 spriteTransformation,\n                              std::optional<glm::mat4> maskTransformation,\n                              std::span<const glm::vec4, 4> tints,\n                              bool isInverted) override {}\n\n  void DrawMaskedSpriteOverlay(const Sprite& sprite, const Sprite& mask,\n                               const CornersQuad& spriteDest,\n                               const CornersQuad& maskDest, int alpha,\n                               int fadeRange, glm::mat4 spriteTransformation,\n                               glm::mat4 maskTransformation,\n                               std::span<const glm::vec4, 4> tints,\n                               bool isInverted, bool useMaskAlpha) override;\n\n  void DrawPrimitives(const SpriteSheet& sheet, const SpriteSheet* mask,\n                      ShaderProgramType shaderType,\n                      std::span<const VertexBufferSprites> vertices,\n                      std::span<const uint16_t> indices,\n                      glm::mat4 spriteTransformation,\n                      glm::mat4 maskTransformation, bool inverted,\n                      TopologyMode topologyMode,\n                      bool textureWrapRepeat = false) override;\n\n  void DrawCCMessageBox(Sprite const& sprite, Sprite const& mask,\n                        RectF const& dest, glm::vec4 tint, int alpha,\n                        int fadeRange, float effectCt) override;\n\n  void DrawCHLCCMenuBackground(const Sprite& sprite, const Sprite& mask,\n                               const RectF& dest, float alpha) override;\n\n  void DrawBlurredSprite(const Sprite& sprite, const CornersQuad& dest,\n                         glm::mat4 transformation,\n                         RendererBlurDirection blurDirection,\n                         glm::vec4 tint) override {};  // TODO: Implement\n\n  void DrawMosaic(const Sprite& sprite, const CornersQuad dest, float tileSize,\n                  glm::mat4 transformation,\n                  glm::vec4 tint) override {};  // TODO: Implement\n\n  void DrawVideoTexture(const YUVFrame& frame, const RectF& dest,\n                        glm::vec4 tint, bool alphaVideo) override;\n  void DrawVideoTexture(const NV12Frame& frame, const RectF& dest,\n                        glm::vec4 tint, bool alphaVideo = false) override;\n  void DrawSubtitleGlyph(const Sprite& sprite, const CornersQuad& dest,\n                         const glm::mat4 transformation,\n                         const glm::vec4 tint) override {};  // TODO: Implement\n\n  void CaptureScreencap(Sprite& sprite) override;\n\n  void SetFramebuffer(size_t buffer) override {};  // TODO: Implement\n  int GetFramebufferTexture(size_t buffer) override {\n    return 0;\n  };  // TODO: Implement\n\n  void EnableScissor() override;\n  void SetScissorRect(RectF const& rect) override;\n  void DisableScissor() override;\n\n  void SetStencilMode(StencilBufferMode mode) override {};  // TODO: implement\n  void ClearStencilBuffer() override {};                    // TODO: implement\n\n  void SetBlendMode(RendererBlendMode blendMode) override;\n\n  void Clear(glm::vec4 color) override;\n\n private:\n  void EnsureSpaceAvailable(int vertices, int vertexSize, int indices);\n  void EnsureTextureBound(uint32_t texture);\n  void EnsureShader(Shader* shader, bool flush = true);\n  void Flush() override;\n\n  glm::vec2 DesignToNDC(glm::vec2 designCoord) const override;\n\n  DirectX9Window* DXWindow;\n\n  IDirect3D9* Interface;\n  IDirect3DDevice9* Device;\n\n  Shader* CurrentShader;\n\n  Shader* ShaderSprite;\n  Shader* ShaderSpriteInverted;\n  Shader* ShaderMaskedSprite;\n  Shader* ShaderMaskedSpriteNoAlpha;\n  Shader* ShaderYUVFrame;\n  Shader* ShaderNV12Frame;\n  Shader* ShaderCCMessageBox;\n  Shader* ShaderCHLCCMenuBackground;\n\n  uint32_t CurrentTexture = 0;\n  uint32_t NextTextureId = 1;\n\n  DX9YUVFrame* VideoFrameInternalYUV;\n  DX9NV12Frame* VideoFrameInternalNV12;\n\n  bool Drawing = false;\n\n  static int constexpr VertexBufferSize = 1024 * 1024;\n  static int constexpr IndexBufferCount =\n      VertexBufferSize / (4 * sizeof(VertexBufferSprites)) * 6;\n\n  uint8_t VertexBuffer[VertexBufferSize];\n  IDirect3DVertexBuffer9* VertexBufferDevice;\n  size_t VertexBufferFill = 0;\n  uint16_t IndexBuffer[IndexBufferCount];\n  IDirect3DIndexBuffer9* IndexBufferDevice;\n  IDirect3DIndexBuffer9* IndexBufferMvl;\n  size_t IndexBufferFill = 0;\n};\n\n}  // namespace DirectX9\n}  // namespace Impacto\n"
  },
  {
    "path": "src/renderer/dx9/shader.cpp",
    "content": "#include \"shader.h\"\n\n#include <d3dcompiler.h>\n\n#include \"renderer.h\"\n#include \"../../log.h\"\n\nnamespace Impacto {\nnamespace DirectX9 {\n\nstatic char const ShaderPath[] = \"./shaders/dx9\";\nstatic char const FragShaderExtension[] = \"_frag.hlsl\";\nstatic char const VertShaderExtension[] = \"_vert.hlsl\";\n\nvoid Shader::Compile(char const* name, IDirect3DDevice9* device,\n                     IDirect3DVertexDeclaration9* vertexDeclaration,\n                     D3D_SHADER_MACRO* macros) {\n  ImpLog(LogLevel::Debug, LogChannel::Render, \"Compiling shader \\\"{:s}\\\"\\n\",\n         name);\n\n  VertexDeclaration = vertexDeclaration;\n\n  ID3DBlob* vertexShaderBuffer{};\n  ID3DBlob* errorBlob{};\n  ID3DBlob* pixelShaderBuffer{};\n\n  // Vertex shader\n  std::string vertexShaderPath = fmt::format(FMT_COMPILE(\"{}/{}{}\"), ShaderPath,\n                                             name, VertShaderExtension);\n  size_t sourceRawSz;\n  char* source = (char*)SDL_LoadFile(vertexShaderPath.c_str(), &sourceRawSz);\n  if (!source) {\n    ImpLog(LogLevel::Debug, LogChannel::Render,\n           \"Failed to read shader source file\\n\");\n    return;\n  }\n  auto result =\n      D3DCompile(source, sourceRawSz, nullptr, macros, nullptr, \"main\",\n                 \"vs_3_0\", 0, 0, &vertexShaderBuffer, &errorBlob);\n  if (FAILED(result)) {\n    ImpLog(LogLevel::Debug, LogChannel::Render,\n           \"Failed to compile shader source file {:s}\\n\",\n           static_cast<const char*>(errorBlob->GetBufferPointer()));\n    return;\n  }\n\n  result = device->CreateVertexShader(\n      (DWORD*)vertexShaderBuffer->GetBufferPointer(), &VertexShader);\n  if (FAILED(result)) return;\n\n  // Pixel shader\n  std::string fragShaderPath =\n      fmt::format(\"{}/{}{}\", ShaderPath, name, FragShaderExtension);\n  source = (char*)SDL_LoadFile(fragShaderPath.c_str(), &sourceRawSz);\n  if (!source) {\n    ImpLog(LogLevel::Debug, LogChannel::Render,\n           \"Failed to read shader source file\\n\");\n    return;\n  }\n  result = D3DCompile(source, sourceRawSz, nullptr, macros, nullptr, \"main\",\n                      \"ps_3_0\", 0, 0, &pixelShaderBuffer, &errorBlob);\n  if (FAILED(result)) {\n    ImpLog(LogLevel::Debug, LogChannel::Render,\n           \"Failed to compile shader source file {:s}\\n\",\n           static_cast<const char*>(errorBlob->GetBufferPointer()));\n    return;\n  }\n\n  result = device->CreatePixelShader(\n      (DWORD*)pixelShaderBuffer->GetBufferPointer(), &PixelShader);\n  if (FAILED(result)) return;\n\n  vertexShaderBuffer->Release();\n  pixelShaderBuffer->Release();\n  if (errorBlob) errorBlob->Release();\n}\n\nvoid Shader::UseShader(IDirect3DDevice9* device) {\n  device->SetVertexDeclaration(VertexDeclaration);\n  device->SetVertexShader(VertexShader);\n  device->SetPixelShader(PixelShader);\n}\n\n}  // namespace DirectX9\n}  // namespace Impacto"
  },
  {
    "path": "src/renderer/dx9/shader.h",
    "content": "#pragma once\n\n#include <d3d9.h>\n#include <d3dcommon.h>\n\nnamespace Impacto {\nnamespace DirectX9 {\n\nclass Shader {\n public:\n  void Compile(char const* name, IDirect3DDevice9* device,\n               IDirect3DVertexDeclaration9* vertexDeclaration,\n               D3D_SHADER_MACRO* macros = nullptr);\n  void UseShader(IDirect3DDevice9* device);\n\n private:\n  IDirect3DVertexDeclaration9* VertexDeclaration;\n\n  IDirect3DVertexShader9* VertexShader;\n  IDirect3DPixelShader9* PixelShader;\n};\n\n}  // namespace DirectX9\n}  // namespace Impacto"
  },
  {
    "path": "src/renderer/dx9/utils.h",
    "content": "#pragma once\n\n#include <d3d9.h>\n#include <ankerl/unordered_dense.h>\n#include <cstdint>\n\nnamespace Impacto {\nnamespace DirectX9 {\n\ninline ankerl::unordered_dense::map<uint32_t, IDirect3DTexture9*> Textures;\n\n}  // namespace DirectX9\n}  // namespace Impacto"
  },
  {
    "path": "src/renderer/dx9/window.cpp",
    "content": "#include \"window.h\"\n\n#include \"../renderer.h\"\n\n#include \"../../log.h\"\n\n#include \"../../profile/game.h\"\n\n#include \"../../game.h\"\n\n#ifndef IMPACTO_DISABLE_IMGUI\n#include <imgui_impl_dx9.h>\n#endif\n\nnamespace Impacto {\nnamespace DirectX9 {\n\nvoid DirectX9Window::UpdateDimensions() {\n  WindowDimensionsChanged = false;\n  // SDL_Vulkan_GetDrawableSize(SDLWindow, &WindowWidth, &WindowHeight);\n  if (WindowWidth != lastWidth || WindowHeight != lastHeight ||\n      MsaaCount != lastMsaa || RenderScale != lastRenderScale) {\n    WindowDimensionsChanged = true;\n    ImpLog(LogLevel::Debug, LogChannel::General,\n           \"Drawable size (pixels): {:d} x {:d} ({:d}x MSAA requested, render \"\n           \"scale \"\n           \"{:f})\\n\",\n           WindowWidth, WindowHeight, MsaaCount, RenderScale);\n  }\n  lastWidth = WindowWidth;\n  lastHeight = WindowHeight;\n  lastMsaa = MsaaCount;\n  lastRenderScale = RenderScale;\n\n  int osWindowWidth, osWindowHeight;\n  SDL_GetWindowSize(SDLWindow, &osWindowWidth, &osWindowHeight);\n  DpiScaleX = (float)WindowWidth / (float)osWindowWidth;\n  DpiScaleY = (float)WindowHeight / (float)osWindowHeight;\n  // SDL_SetWindowInputFocus(SDLWindow);\n}\n\nRectF DirectX9Window::GetViewport() {\n  RectF viewport;\n  float scale = fmin((float)WindowWidth / Profile::DesignWidth,\n                     (float)WindowHeight / Profile::DesignHeight);\n  viewport.Width = Profile::DesignWidth * scale;\n  viewport.Height = Profile::DesignHeight * scale;\n  viewport.X = ((float)WindowWidth - viewport.Width) / 2.0f;\n  viewport.Y = ((float)WindowHeight - viewport.Height) / 2.0f;\n  return viewport;\n}\n\nRectF DirectX9Window::GetScaledViewport() {\n  RectF viewport = GetViewport();\n  viewport.Width *= RenderScale;\n  viewport.Height *= RenderScale;\n  viewport.X *= RenderScale;\n  viewport.Y *= RenderScale;\n  return viewport;\n}\n\nvoid DirectX9Window::Init() {\n  assert(IsInit == false);\n  ImpLog(LogLevel::Info, LogChannel::General, \"Creating window\\n\");\n  IsInit = true;\n\n#ifdef __ANDROID__\n  SDL_SetHint(SDL_HINT_ORIENTATIONS, \"LandscapeLeft LandscapeRight\");\n#endif\n  if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER) != 0) {\n    ImpLog(LogLevel::Fatal, LogChannel::General,\n           \"SDL initialisation failed: {:s}\\n\", SDL_GetError());\n    Shutdown();\n    return;\n  }\n\n  SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, \"0\");\n\n  uint32_t windowFlags = 0;\n#if IMPACTO_USE_SDL_HIGHDPI\n  windowFlags |= SDL_WINDOW_ALLOW_HIGHDPI;\n#endif\n  if (Profile::Fullscreen) {\n    windowFlags |= SDL_WINDOW_FULLSCREEN;\n  }\n\n  SDLWindow = SDL_CreateWindow(\n      Profile::WindowName, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,\n      Profile::ResolutionWidth, Profile::ResolutionHeight, windowFlags);\n\n  if (SDLWindow == NULL) {\n    ImpLog(LogLevel::Error, LogChannel::General,\n           \"Window creation failed: {:s}\\n\", SDL_GetError());\n    return;\n  }\n\n  SDL_GetWindowSize(SDLWindow, &WindowWidth, &WindowHeight);\n  ImpLog(LogLevel::Debug, LogChannel::General,\n         \"Window size (screen coords): {:d} x {:d}\\n\", WindowWidth,\n         WindowHeight);\n}\n\nvoid DirectX9Window::SetDimensions(int width, int height, int msaa,\n                                   float renderScale) {}\n\nvoid DirectX9Window::SwapRTs() {}\n\nvoid DirectX9Window::Update() {}\n\nvoid DirectX9Window::Draw() {\n#ifndef IMPACTO_DISABLE_IMGUI\n  if (ImGui::GetIO().ConfigFlags & ImGuiConfigFlags_ViewportsEnable) {\n    ImGui::UpdatePlatformWindows();\n    ImGui::RenderPlatformWindowsDefault();\n  }\n#endif\n}\n\nvoid DirectX9Window::Shutdown() {\n  SDL_DestroyWindow(SDLWindow);\n  SDL_Quit();\n  // TODO move exit to users\n  exit(0);\n}\n\n}  // namespace DirectX9\n}  // namespace Impacto"
  },
  {
    "path": "src/renderer/dx9/window.h",
    "content": "#pragma once\n\n#include \"../window.h\"\n\nnamespace Impacto {\nnamespace DirectX9 {\n\nclass DirectX9Window : public BaseWindow {\n public:\n  void Init() override;\n  void SetDimensions(int width, int height, int msaa,\n                     float renderScale) override;\n  RectF GetViewport() override;\n  RectF GetScaledViewport() override;\n  void SwapRTs() override;\n  void Update() override;\n  void Draw() override;\n  void Shutdown() override;\n\n private:\n  void UpdateDimensions() override;\n};\n\n}  // namespace DirectX9\n}  // namespace Impacto"
  },
  {
    "path": "src/renderer/dx9/yuvframe.cpp",
    "content": "#include \"yuvframe.h\"\n\n#include \"../../log.h\"\n\nnamespace Impacto {\nnamespace DirectX9 {\n\nDX9YUVFrame::DX9YUVFrame(IDirect3DDevice9* device) { Device = device; }\n\nvoid DX9YUVFrame::Init(float width, float height) {\n  Width = width;\n  Height = height;\n  D3DFORMAT imageFormat = D3DFMT_A8;\n  auto err = Device->CreateTexture((UINT)Width, (UINT)Height, 1, 0, imageFormat,\n                                   D3DPOOL_MANAGED, &Luma, nullptr);\n  err = Device->CreateTexture((UINT)(Width / 2), (UINT)(Height / 2), 1, 0,\n                              imageFormat, D3DPOOL_MANAGED, &Cb, nullptr);\n  err = Device->CreateTexture((UINT)(Width / 2), (UINT)(Height / 2), 1, 0,\n                              imageFormat, D3DPOOL_MANAGED, &Cr, nullptr);\n}\n\nvoid DX9YUVFrame::Submit(const void* luma, const void* cb, const void* cr) {\n  D3DLOCKED_RECT lockRect;\n  auto err = Luma->LockRect(0, &lockRect, NULL, 0);\n  memcpy(lockRect.pBits, luma, (size_t)(Width * Height));\n  err = Luma->UnlockRect(0);\n\n  err = Cb->LockRect(0, &lockRect, NULL, 0);\n  memcpy(lockRect.pBits, cb, (size_t)((Width / 2) * (Height / 2)));\n  err = Cb->UnlockRect(0);\n\n  err = Cr->LockRect(0, &lockRect, NULL, 0);\n  memcpy(lockRect.pBits, cr, (size_t)((Width / 2) * (Height / 2)));\n  err = Cr->UnlockRect(0);\n}\n\nvoid DX9YUVFrame::Release() {\n  Luma->Release();\n  Cb->Release();\n  Cr->Release();\n}\n\n}  // namespace DirectX9\n}  // namespace Impacto"
  },
  {
    "path": "src/renderer/dx9/yuvframe.h",
    "content": "#pragma once\n\n#include \"../yuvframe.h\"\n\n#include <d3d9.h>\n\nnamespace Impacto {\nnamespace DirectX9 {\n\nclass DX9YUVFrame : public YUVFrame {\n  friend class Renderer;\n\n public:\n  DX9YUVFrame(IDirect3DDevice9* device);\n\n  void Init(float width, float height) override;\n\n  void Submit(const void* luma, const void* cb, const void* cr) override;\n  void Release() override;\n\n protected:\n  IDirect3DDevice9* Device;\n\n  IDirect3DTexture9* Luma;\n  IDirect3DTexture9* Cb;\n  IDirect3DTexture9* Cr;\n};\n\n}  // namespace DirectX9\n}  // namespace Impacto"
  },
  {
    "path": "src/renderer/nv12frame.h",
    "content": "#pragma once\n\n#include \"../impacto.h\"\n\nnamespace Impacto {\n\nclass NV12Frame {\n public:\n  float Width;\n  float Height;\n  uint32_t LumaId;\n  uint32_t CbCrId;\n\n  virtual void Init(float width, float height) = 0;\n\n  virtual void Submit(const void* luma, int lumaStride, const void* cbcr,\n                      int cbcrStride) = 0;\n  virtual void Release() = 0;\n};\n\n}  // namespace Impacto"
  },
  {
    "path": "src/renderer/opengl/3d/renderable3d.cpp",
    "content": "#include <glm/glm.hpp>\n#include <glm/gtc/type_ptr.hpp>\n\n#include \"renderable3d.h\"\n\n#include \"../../3d/camera.h\"\n#include \"../../3d/modelanimator.h\"\n#include \"../../../log.h\"\n#include \"../../../profile/scene3d.h\"\n\nnamespace Impacto {\nnamespace OpenGL {\n\nenum SceneUniform {\n  SU_ViewProjection = 0,\n  SU_Tint = 1,\n  SU_WorldLightPosition = 2,\n  SU_WorldEyePosition = 3,\n  SU_DarkMode = 4,\n  SU_Count = 5\n};\nenum ModelUniform { MU_Model = 0, MU_Count = 1 };\nenum MeshUniform {\n  MSU_Bones = 0,\n  MSU_ModelOpacity = 1,\n  MSU_HasShadowColorMap = 2,\n  MSU_Count = 3\n};\n\nstatic char const* SceneUniformNames[SU_Count] = {\n    \"ViewProjection\", \"Tint\", \"WorldLightPosition\", \"WorldEyePosition\",\n    \"DarkMode\"};\nstatic GLint SceneUniformOffsets[SU_Count];\nstatic char const* ModelUniformNames[MU_Count] = {\"Model\"};\nstatic GLint ModelUniformOffsets[MU_Count];\nstatic char const* MeshUniformNames[MSU_Count] = {\"Bones\", \"ModelOpacity\",\n                                                  \"HasShadowColorMap\"};\nstatic GLint MeshUniformOffsets[MSU_Count];\n\nstatic GLuint TextureDummy = 0;\n// character\nstatic GLuint ShaderProgram = 0, ShaderProgramOutline = 0, ShaderProgramEye = 0,\n              UBOScene = 0;\n// background\nstatic GLuint ShaderProgramBackground = 0, UniformMVPBackground = 0;\n\nstatic uint8_t* SceneUniformBuffer;\nstatic GLint SceneUniformBlockSize;\nstatic uint8_t* ModelUniformBuffer;\nstatic GLint ModelUniformBlockSize;\nstatic uint8_t* MeshUniformBuffer;\nstatic GLint MeshUniformBlockSize;\n\nstatic glm::mat4 ViewProjection;\n\nstatic bool IsInit = false;\n\nstatic MaterialType CurrentMaterial = MT_None;\nstatic bool CurrentMaterialIsDepthWrite = false;\n// static bool CurrentMaterialIsBackfaceCull = false;\n\nstatic GLWindow* Window;\n\nvoid Renderable3D::Init(GLWindow* window, ShaderCompiler& shaderCompiler) {\n  assert(IsInit == false);\n  ImpLog(LogLevel::Info, LogChannel::Renderable3D,\n         \"Initializing Renderable3D system\\n\");\n  IsInit = true;\n\n  Window = window;\n\n  ShaderParamMap shaderParams;\n  shaderParams[\"ModelMaxBonesPerMesh\"] = ModelMaxBonesPerMesh;\n  int isDaSH = (int)(Profile::Scene3D::Version == LKMVersion::DaSH);\n  shaderParams[\"DASH\"] = ShaderParameter(isDaSH, true);\n\n  ShaderProgram =\n      shaderCompiler.Compile(\"Renderable3D_Character\", shaderParams);\n  ShaderProgramOutline =\n      shaderCompiler.Compile(\"Renderable3D_Outline\", shaderParams);\n  ShaderProgramEye = shaderCompiler.Compile(\"Renderable3D_Eye\", shaderParams);\n\n  ShaderProgramBackground =\n      shaderCompiler.Compile(\"Renderable3D_Background\", shaderParams);\n\n  GLuint sceneUniformIndices[SU_Count];\n  glGetUniformIndices(ShaderProgram, SU_Count, SceneUniformNames,\n                      sceneUniformIndices);\n  glGetActiveUniformsiv(ShaderProgram, SU_Count, sceneUniformIndices,\n                        GL_UNIFORM_OFFSET, SceneUniformOffsets);\n  GLuint modelUniformIndices[MU_Count];\n  glGetUniformIndices(ShaderProgram, MU_Count, ModelUniformNames,\n                      modelUniformIndices);\n  glGetActiveUniformsiv(ShaderProgram, MU_Count, modelUniformIndices,\n                        GL_UNIFORM_OFFSET, ModelUniformOffsets);\n  GLuint meshUniformIndices[MSU_Count];\n  glGetUniformIndices(ShaderProgram, MSU_Count, MeshUniformNames,\n                      meshUniformIndices);\n  glGetActiveUniformsiv(ShaderProgram, MSU_Count, meshUniformIndices,\n                        GL_UNIFORM_OFFSET, MeshUniformOffsets);\n\n  GLuint blockIndex = glGetUniformBlockIndex(ShaderProgram, \"Character3DScene\");\n  glUniformBlockBinding(ShaderProgram, blockIndex, 0);\n  glGetActiveUniformBlockiv(ShaderProgram, blockIndex,\n                            GL_UNIFORM_BLOCK_DATA_SIZE, &SceneUniformBlockSize);\n  blockIndex = glGetUniformBlockIndex(ShaderProgram, \"Character3DModel\");\n  glUniformBlockBinding(ShaderProgram, blockIndex, 1);\n  glGetActiveUniformBlockiv(ShaderProgram, blockIndex,\n                            GL_UNIFORM_BLOCK_DATA_SIZE, &ModelUniformBlockSize);\n  blockIndex = glGetUniformBlockIndex(ShaderProgram, \"Character3DMesh\");\n  glUniformBlockBinding(ShaderProgram, blockIndex, 2);\n  glGetActiveUniformBlockiv(ShaderProgram, blockIndex,\n                            GL_UNIFORM_BLOCK_DATA_SIZE, &MeshUniformBlockSize);\n\n  blockIndex = glGetUniformBlockIndex(ShaderProgramOutline, \"Character3DScene\");\n  glUniformBlockBinding(ShaderProgramOutline, blockIndex, 0);\n  blockIndex = glGetUniformBlockIndex(ShaderProgramOutline, \"Character3DModel\");\n  glUniformBlockBinding(ShaderProgramOutline, blockIndex, 1);\n  blockIndex = glGetUniformBlockIndex(ShaderProgramOutline, \"Character3DMesh\");\n  glUniformBlockBinding(ShaderProgramOutline, blockIndex, 2);\n\n  blockIndex = glGetUniformBlockIndex(ShaderProgramEye, \"Character3DScene\");\n  glUniformBlockBinding(ShaderProgramEye, blockIndex, 0);\n  blockIndex = glGetUniformBlockIndex(ShaderProgramEye, \"Character3DModel\");\n  glUniformBlockBinding(ShaderProgramEye, blockIndex, 1);\n  blockIndex = glGetUniformBlockIndex(ShaderProgramEye, \"Character3DMesh\");\n  glUniformBlockBinding(ShaderProgramEye, blockIndex, 2);\n\n  glGenBuffers(1, &UBOScene);\n  // Initialize storage\n  glBindBuffer(GL_UNIFORM_BUFFER, UBOScene);\n  glBufferData(GL_UNIFORM_BUFFER, SceneUniformBlockSize, NULL, GL_DYNAMIC_DRAW);\n\n  SceneUniformBuffer = (uint8_t*)malloc(SceneUniformBlockSize);\n  ModelUniformBuffer = (uint8_t*)malloc(ModelUniformBlockSize);\n  MeshUniformBuffer = (uint8_t*)malloc(MeshUniformBlockSize);\n\n  UniformMVPBackground = glGetUniformLocation(ShaderProgramBackground, \"MVP\");\n\n  glUseProgram(ShaderProgram);\n  if (Profile::Scene3D::Version == LKMVersion::DaSH) {\n    glUniform1i(glGetUniformLocation(ShaderProgram, \"ColorMap\"),\n                TT_DaSH_ColorMap);\n    glUniform1i(glGetUniformLocation(ShaderProgram, \"ShadowColorMap\"),\n                TT_DaSH_ShadowColorMap);\n    glUniform1i(glGetUniformLocation(ShaderProgram, \"GradientMaskMap\"),\n                TT_DaSH_GradientMaskMap);\n    glUniform1i(glGetUniformLocation(ShaderProgram, \"SpecularColorMap\"),\n                TT_DaSH_SpecularColorMap);\n  } else {\n    glUniform1i(glGetUniformLocation(ShaderProgram, \"ColorMap\"), TT_ColorMap);\n    glUniform1i(glGetUniformLocation(ShaderProgram, \"GradientMaskMap\"),\n                TT_GradientMaskMap);\n    glUniform1i(glGetUniformLocation(ShaderProgram, \"SpecularColorMap\"),\n                TT_SpecularColorMap);\n  }\n\n  glUseProgram(ShaderProgramOutline);\n  if (Profile::Scene3D::Version == LKMVersion::DaSH) {\n    glUniform1i(glGetUniformLocation(ShaderProgramOutline, \"ColorMap\"),\n                TT_DaSH_ColorMap);\n    glUniform1i(glGetUniformLocation(ShaderProgramOutline, \"NoiseMap\"),\n                TT_DaSH_NoiseMap);\n  } else {\n    glUniform1i(glGetUniformLocation(ShaderProgramOutline, \"ColorMap\"),\n                TT_ColorMap);\n  }\n\n  glUseProgram(ShaderProgramEye);\n  if (Profile::Scene3D::Version == LKMVersion::DaSH) {\n    glUniform1i(glGetUniformLocation(ShaderProgramEye, \"HighlightColorMap\"),\n                TT_DaSH_Eye_HighlightColorMap);\n    glUniform1i(glGetUniformLocation(ShaderProgramEye, \"WhiteColorMap\"),\n                TT_DaSH_Eye_WhiteColorMap);\n    glUniform1i(glGetUniformLocation(ShaderProgramEye, \"IrisColorMap\"),\n                TT_DaSH_Eye_IrisColorMap);\n  } else {\n    glUniform1i(glGetUniformLocation(ShaderProgramEye, \"HighlightColorMap\"),\n                TT_Eye_HighlightColorMap);\n    glUniform1i(glGetUniformLocation(ShaderProgramEye, \"WhiteColorMap\"),\n                TT_Eye_WhiteColorMap);\n    glUniform1i(glGetUniformLocation(ShaderProgramEye, \"IrisSpecularColorMap\"),\n                TT_Eye_IrisSpecularColorMap);\n    glUniform1i(glGetUniformLocation(ShaderProgramEye, \"IrisColorMap\"),\n                TT_Eye_IrisColorMap);\n  }\n\n  glUseProgram(ShaderProgramBackground);\n  glUniform1i(glGetUniformLocation(ShaderProgramBackground, \"ColorMap\"),\n              TT_ColorMap);\n\n  Texture texDummy;\n  texDummy.Load1x1();\n  TextureDummy = texDummy.Submit();\n}\n\nvoid Renderable3D::BeginFrame(IScene3D* scene, Camera* camera) {\n  CurrentMaterial = MT_None;\n\n  glBindBufferBase(GL_UNIFORM_BUFFER, 0, UBOScene);\n  memcpy(SceneUniformBuffer + SceneUniformOffsets[SU_ViewProjection],\n         glm::value_ptr(camera->ViewProjection),\n         sizeof(camera->ViewProjection));\n  memcpy(SceneUniformBuffer + SceneUniformOffsets[SU_Tint],\n         glm::value_ptr(scene->Tint), sizeof(scene->Tint));\n  memcpy(SceneUniformBuffer + SceneUniformOffsets[SU_WorldLightPosition],\n         glm::value_ptr(scene->LightPosition), sizeof(scene->LightPosition));\n  memcpy(SceneUniformBuffer + SceneUniformOffsets[SU_WorldEyePosition],\n         glm::value_ptr(camera->CameraTransform.Position),\n         sizeof(camera->CameraTransform.Position));\n  *(uint32_t*)(SceneUniformBuffer + SceneUniformOffsets[SU_DarkMode]) =\n      scene->DarkMode;\n  glBufferSubData(GL_UNIFORM_BUFFER, 0, SceneUniformBlockSize,\n                  SceneUniformBuffer);\n\n  ViewProjection = camera->ViewProjection;\n}\n\nbool Renderable3D::LoadSync(uint32_t modelId) {\n  assert(IsUsed == false);\n\n  ImpLog(LogLevel::Info, LogChannel::Renderable3D,\n         \"Creating renderable (model ID {:d})\\n\", modelId);\n\n  StaticModel = Model::Load(modelId);\n  ModelTransform = Transform();\n  PrevPoseWeight = 0.0f;\n\n  if (!StaticModel) {\n    ImpLog(LogLevel::Error, LogChannel::Renderable3D,\n           \"Model loading failed for character with model ID {:d}\\n\", modelId);\n    return false;\n  }\n\n  InitMeshAnimStatus();\n  ReloadDefaultBoneTransforms();\n\n  Animator.Character = this;\n  SwitchAnimation(StaticModel->IdleAnimation, 0.0f);\n\n  IsUsed = true;\n\n  return true;\n}\n\nvoid Renderable3D::MakePlane() {\n  assert(IsUsed == false);\n\n  StaticModel = Model::MakePlane();\n  ModelTransform = Transform();\n  PrevPoseWeight = 0.0f;\n\n  Animator.Character = this;\n\n  InitMeshAnimStatus();\n  ReloadDefaultBoneTransforms();\n\n  IsUsed = true;\n}\n\nvoid Renderable3D::InitMeshAnimStatus() {\n  int totalMorphedVertices = 0;\n  for (uint32_t i = 0; i < StaticModel->MeshCount; i++) {\n    MeshAnimStatus[i].MorphedVerticesOffset = totalMorphedVertices;\n    if (StaticModel->Meshes[i].MorphTargetCount > 0) {\n      totalMorphedVertices += StaticModel->Meshes[i].VertexCount;\n    }\n  }\n  CurrentMorphedVertices = (MorphVertexBuffer*)malloc(\n      totalMorphedVertices * sizeof(MorphVertexBuffer));\n  ReloadDefaultMeshAnimStatus();\n}\n\nvoid Renderable3D::ReloadDefaultMeshAnimStatus() {\n  for (uint32_t i = 0; i < StaticModel->MeshCount; i++) {\n    MeshAnimStatus[i].Visible = 1.0f;\n    if (StaticModel->Meshes[i].MorphTargetCount > 0) {\n      for (int j = 0; j < StaticModel->Meshes[i].MorphTargetCount; j++) {\n        MeshAnimStatus[i].MorphInfluences[j] = 0.0f;\n      }\n    }\n  }\n}\n\nvoid Renderable3D::SwitchAnimation(int16_t animId, float transitionTime) {\n  if (Animator.CurrentAnimation != 0 && transitionTime > 0.0f) {\n    PrevPoseWeight = 1.0f;\n    for (uint32_t i = 0; i < StaticModel->BoneCount; i++) {\n      PrevBoneTransforms[i] = CurrentPose[i].LocalTransform;\n    }\n    memcpy(PrevMeshAnimStatus, MeshAnimStatus, sizeof(MeshAnimStatus));\n    AnimationTransitionTime = transitionTime;\n  } else {\n    PrevPoseWeight = 0.0f;\n  }\n  Animator.Start(animId);\n}\n\nvoid Renderable3D::ReloadDefaultBoneTransforms() {\n  for (uint32_t i = 0; i < StaticModel->BoneCount; i++) {\n    CurrentPose[i].LocalTransform = StaticModel->Bones[i].BaseTransform;\n  }\n}\n\nvoid Renderable3D::CalculateMorphedVertices(int id) {\n  Mesh* mesh = &StaticModel->Meshes[id];\n  AnimatedMesh* animStatus = &MeshAnimStatus[id];\n  AnimatedMesh* prevAnimStatus = &PrevMeshAnimStatus[id];\n  if (mesh->MorphTargetCount == 0) return;\n\n  MorphVertexBuffer* currentMorphedVertex =\n      CurrentMorphedVertices + animStatus->MorphedVerticesOffset;\n\n  void* currentVertex;\n  if (Profile::Scene3D::Version == LKMVersion::DaSH) {\n    currentVertex =\n        ((VertexBufferDaSH*)StaticModel->VertexBuffers) + mesh->VertexOffset;\n  } else {\n    currentVertex =\n        ((VertexBuffer*)StaticModel->VertexBuffers) + mesh->VertexOffset;\n  }\n\n  VertexBuffer* currentVertexRNE = (VertexBuffer*)currentVertex;\n  VertexBufferDaSH* currentVertexDaSH = (VertexBufferDaSH*)currentVertex;\n\n  for (uint32_t j = 0; j < mesh->VertexCount; j++) {\n    if (Profile::Scene3D::Version == LKMVersion::DaSH) {\n      currentMorphedVertex->Position = currentVertexDaSH->Position;\n      currentMorphedVertex->Normal = currentVertexDaSH->Normal;\n      currentVertexDaSH++;\n    } else {\n      currentMorphedVertex->Position = currentVertexRNE->Position;\n      currentMorphedVertex->Normal = currentVertexRNE->Normal;\n      currentVertexRNE++;\n    }\n    currentMorphedVertex++;\n  }\n\n  for (int k = 0; k < mesh->MorphTargetCount; k++) {\n    float influence;\n    if (PrevPoseWeight > 0.0f) {\n      influence = glm::mix(prevAnimStatus->MorphInfluences[k],\n                           animStatus->MorphInfluences[k],\n                           glm::smoothstep(0.0f, 1.0f, 1.0f - PrevPoseWeight));\n    } else {\n      influence = animStatus->MorphInfluences[k];\n    }\n\n    if (influence == 0.0f) continue;\n\n    currentMorphedVertex =\n        CurrentMorphedVertices + animStatus->MorphedVerticesOffset;\n\n    if (Profile::Scene3D::Version == LKMVersion::DaSH) {\n      currentVertex =\n          ((VertexBufferDaSH*)StaticModel->VertexBuffers) + mesh->VertexOffset;\n    } else {\n      currentVertex =\n          ((VertexBuffer*)StaticModel->VertexBuffers) + mesh->VertexOffset;\n    }\n\n    currentVertexRNE = (VertexBuffer*)currentVertex;\n    currentVertexDaSH = (VertexBufferDaSH*)currentVertex;\n\n    MorphVertexBuffer* currentMorphTargetVbo =\n        StaticModel->MorphVertexBuffers +\n        StaticModel->MorphTargets[mesh->MorphTargetIds[k]].VertexOffset;\n\n    for (uint32_t j = 0; j < mesh->VertexCount; j++) {\n      if (Profile::Scene3D::Version == LKMVersion::DaSH) {\n        currentMorphedVertex->Position +=\n            (currentMorphTargetVbo->Position - currentVertexDaSH->Position) *\n            influence;\n        currentMorphedVertex->Normal +=\n            (currentMorphTargetVbo->Normal - currentVertexDaSH->Normal) *\n            influence;\n        currentVertexDaSH++;\n      } else {\n        currentMorphedVertex->Position +=\n            (currentMorphTargetVbo->Position - currentVertexRNE->Position) *\n            influence;\n        currentMorphedVertex->Normal +=\n            (currentMorphTargetVbo->Normal - currentVertexRNE->Normal) *\n            influence;\n        currentVertexRNE++;\n      }\n      currentMorphedVertex++;\n      currentMorphTargetVbo++;\n    }\n  }\n}\n\nvoid Renderable3D::Pose() {\n  for (int i = 0; i < StaticModel->RootBoneCount; i++) {\n    PoseBone(StaticModel->RootBones[i]);\n  }\n}\n\nvoid Renderable3D::PoseBone(int16_t id) {\n  StaticBone* bone = &StaticModel->Bones[id];\n  PosedBone* transformed = &CurrentPose[id];\n\n  glm::mat4 parentWorld;\n  if (bone->Parent < 0) {\n    parentWorld = glm::mat4(1.0);\n  } else {\n    parentWorld = CurrentPose[bone->Parent].World;\n  }\n\n  Transform transform;\n  if (PrevPoseWeight > 0.0f) {\n    transform = PrevBoneTransforms[id].Interpolate(\n        transformed->LocalTransform,\n        glm::smoothstep(0.0f, 1.0f, 1.0f - PrevPoseWeight));\n  } else {\n    transform = transformed->LocalTransform;\n  }\n\n  glm::mat4 local = transform.Matrix();\n\n  transformed->World = parentWorld * local;\n\n  for (int i = 0; i < bone->ChildrenCount; i++) {\n    PoseBone(bone->Children[i]);\n  }\n\n  transformed->Offset = transformed->World * bone->BindInverse;\n}\n\nvoid Renderable3D::Update(float dt) {\n  if (!IsUsed) return;\n  if (Animator.CurrentAnimation) {\n    if (!Animator.IsPlaying) {\n      // oneshot ended\n      SwitchAnimation(StaticModel->IdleAnimation, AnimationTransitionTime);\n    } else {\n      Animator.Update(dt);\n    }\n  }\n  Pose();\n  if (PrevPoseWeight > 0.0f) {\n    PrevPoseWeight -= dt / AnimationTransitionTime;\n    if (PrevPoseWeight < 0.0f) PrevPoseWeight = 0.0f;\n  }\n\n  for (uint32_t i = 0; i < StaticModel->MeshCount; i++) {\n    CalculateMorphedVertices(i);\n  }\n}\n\nvoid Renderable3D::DrawMesh(int id, RenderPass pass) {\n  if (pass == RP_Outline && StaticModel->Type == ModelType_Background) return;\n\n  if (!MeshAnimStatus[id].Visible) return;\n\n  Mesh& mesh = StaticModel->Meshes[id];\n\n  if (pass == RP_First && mesh.Flags & MeshFlag_Later) return;\n  if (pass == RP_Second && !(mesh.Flags & MeshFlag_Later)) return;\n\n  if (pass == RP_Outline) {\n    UseMaterial(MT_Outline);\n  } else {\n    UseMaterial(mesh.Material);\n  }\n  // Because of the above, we use CurrentMaterial later, not mesh.Material\n\n  UseMesh(id);\n\n  switch (CurrentMaterial) {\n    case MT_Background: {\n      int constexpr backgroundTextureTypes[] = {TT_ColorMap};\n      SetTextures(id, backgroundTextureTypes, 1);\n      break;\n    }\n    case MT_Outline: {\n      if (Profile::Scene3D::Version == LKMVersion::DaSH) {\n        int constexpr outlineTextureTypes[] = {TT_DaSH_ColorMap,\n                                               TT_DaSH_NoiseMap};\n        SetTextures(id, outlineTextureTypes, 2);\n      } else {\n        int constexpr outlineTextureTypes[] = {TT_ColorMap};\n        SetTextures(id, outlineTextureTypes, 1);\n      }\n      break;\n    }\n    case MT_Generic: {\n      int constexpr genericTextureTypes[] = {TT_ColorMap, TT_GradientMaskMap,\n                                             TT_SpecularColorMap};\n      SetTextures(id, genericTextureTypes, 3);\n      break;\n    }\n    case MT_Eye: {\n      int constexpr eyeTextureTypes[] = {\n          TT_Eye_HighlightColorMap, TT_Eye_IrisColorMap,\n          TT_Eye_IrisSpecularColorMap, TT_Eye_WhiteColorMap};\n      SetTextures(id, eyeTextureTypes, 4);\n      break;\n    }\n    case MT_DaSH_Generic: {\n      int constexpr dashGenericTextureTypes[] = {\n          TT_DaSH_ColorMap, TT_DaSH_ShadowColorMap, TT_DaSH_GradientMaskMap,\n          TT_DaSH_SpecularColorMap};\n      SetTextures(id, dashGenericTextureTypes, 4);\n      break;\n    }\n    case MT_DaSH_Face:\n    case MT_DaSH_Skin: {\n      int constexpr dashGenericTextureTypes[] = {\n          TT_DaSH_ColorMap, TT_DaSH_GradientMaskMap, TT_DaSH_SpecularColorMap};\n      SetTextures(id, dashGenericTextureTypes, 3);\n      break;\n    }\n    case MT_DaSH_Eye: {\n      int constexpr dashEyeTextureTypes[] = {TT_DaSH_Eye_HighlightColorMap,\n                                             TT_DaSH_Eye_IrisColorMap,\n                                             TT_DaSH_Eye_WhiteColorMap};\n      SetTextures(id, dashEyeTextureTypes, 3);\n      break;\n    }\n    default:\n      ImpLog(LogLevel::Warning, LogChannel::Renderable3D,\n             \"Unknown texture type!\\n\");\n      break;\n  }\n\n  if (mesh.Opacity < 0.9 && CurrentMaterialIsDepthWrite) {\n    glDepthMask(GL_FALSE);\n  }\n  // if (mesh.Flags & MeshFlag_DoubleSided && CurrentMaterialIsBackfaceCull) {\n  //  glDisable(GL_CULL_FACE);\n  //}\n\n  glDrawElements(GL_TRIANGLES, mesh.IndexCount, GL_UNSIGNED_SHORT, 0);\n\n  if (mesh.Opacity < 0.9 && CurrentMaterialIsDepthWrite) {\n    glDepthMask(GL_TRUE);\n  }\n  // if (mesh.Flags & MeshFlag_DoubleSided && CurrentMaterialIsBackfaceCull) {\n  //  glEnable(GL_CULL_FACE);\n  //}\n\n  if (pass != RP_Outline) DrawMesh(id, RP_Outline);\n}\n\nvoid Renderable3D::Render() {\n  if (!IsUsed || !IsVisible) return;\n\n  LoadModelUniforms();\n\n  memset(VAOsUpdated, 0, sizeof(VAOsUpdated));\n  memset(UniformsUpdated, 0, sizeof(UniformsUpdated));\n\n  for (uint32_t i = RP_First; i < RP_Count; i++) {\n    for (uint32_t j = 0; j < StaticModel->MeshCount; j++) {\n      DrawMesh(j, (RenderPass)i);\n    }\n  }\n\n  if (CurrentMaterial == MT_Outline) {\n    // Clean up some state\n    UseMaterial(MT_Background);\n  }\n}\n\nvoid Renderable3D::SetTextures(int id, int const* textureTypes, int count) {\n  for (int i = 0; i < count; i++) {\n    int t = textureTypes[i];\n    glActiveTexture(GL_TEXTURE0 + t);\n    if (StaticModel->Meshes[id].Maps[t] >= 0) {\n      glBindTexture(GL_TEXTURE_2D, TexBuffers[StaticModel->Meshes[id].Maps[t]]);\n    } else {\n      glBindTexture(GL_TEXTURE_2D, TextureDummy);\n    }\n  }\n}\n\nvoid Renderable3D::LoadModelUniforms() {\n  if (StaticModel->Type == ModelType_Character) {\n    glBindBufferBase(GL_UNIFORM_BUFFER, 1, UBOModel);\n    *(glm::mat4*)(ModelUniformBuffer + ModelUniformOffsets[MU_Model]) =\n        ModelTransform.Matrix();\n    glBufferSubData(GL_UNIFORM_BUFFER, 0, ModelUniformBlockSize,\n                    ModelUniformBuffer);\n  }\n}\n\nvoid Renderable3D::UseMaterial(MaterialType type) {\n  if (CurrentMaterial == type) return;\n\n  switch (type) {\n    case MT_Outline: {\n      glUseProgram(ShaderProgramOutline);\n      break;\n    }\n    case MT_Background: {\n      glUseProgram(ShaderProgramBackground);\n      break;\n    }\n    case MT_Generic:\n    case MT_DaSH_Generic:\n    case MT_DaSH_Face:\n    case MT_DaSH_Skin: {\n      glUseProgram(ShaderProgram);\n      break;\n    }\n    case MT_Eye:\n    case MT_DaSH_Eye: {\n      glUseProgram(ShaderProgramEye);\n      break;\n    }\n    default:\n      ImpLog(LogLevel::Warning, LogChannel::Renderable3D,\n             \"Unknown texture type!\\n\");\n      break;\n  }\n\n  glEnable(GL_CULL_FACE);\n  if (type == MT_Outline) {\n    glCullFace(GL_FRONT);\n  } else {\n    glCullFace(GL_BACK);\n  }\n\n  // if (type == MT_Outline || type == MT_Generic || type == MT_Eye) {\n  //  glEnable(GL_CULL_FACE);\n  //  GLenum side = type == MT_Outline ? GL_FRONT : GL_BACK;\n  //  glCullFace(side);\n  //  CurrentMaterialIsBackfaceCull = side == GL_BACK;\n  //} else {\n  //  glDisable(GL_CULL_FACE);\n  //  CurrentMaterialIsBackfaceCull = false;\n  //}\n\n  if (type == MT_Outline) {\n    CurrentMaterialIsDepthWrite = false;\n    glDepthMask(GL_FALSE);\n  } else {\n    CurrentMaterialIsDepthWrite = true;\n    glDepthMask(GL_TRUE);\n  }\n\n  CurrentMaterial = type;\n}\n\nvoid Renderable3D::UseMesh(int id) {\n  glBindVertexArray(VAOs[id]);\n\n  LoadMeshUniforms(id);\n\n  if (!VAOsUpdated[id]) {\n    if (StaticModel->Meshes[id].MorphTargetCount > 0) {\n      glBindBuffer(GL_ARRAY_BUFFER, MorphVBOs[id]);\n      glBufferData(\n          GL_ARRAY_BUFFER,\n          sizeof(MorphVertexBuffer) * StaticModel->Meshes[id].VertexCount,\n          CurrentMorphedVertices + MeshAnimStatus[id].MorphedVerticesOffset,\n          GL_DYNAMIC_DRAW);\n      glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(MorphVertexBuffer),\n                            (void*)offsetof(MorphVertexBuffer, Position));\n      glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(MorphVertexBuffer),\n                            (void*)offsetof(MorphVertexBuffer, Normal));\n    }\n\n    VAOsUpdated[id] = true;\n  }\n}\n\nvoid Renderable3D::LoadMeshUniforms(int id) {\n  Mesh& mesh = StaticModel->Meshes[id];\n\n  if (StaticModel->Type == ModelType_Character) {\n    glBindBufferBase(GL_UNIFORM_BUFFER, 2, UBOs[id]);\n\n    if (!UniformsUpdated[id]) {\n      if (mesh.UsedBones > 0) {\n        glm::mat4* outBone =\n            (glm::mat4*)(MeshUniformBuffer + MeshUniformOffsets[MSU_Bones]);\n        for (uint32_t j = 0; j < mesh.UsedBones; j++) {\n          *outBone = CurrentPose[mesh.BoneMap[j]].Offset;\n          outBone++;\n        }\n      } else {\n        memcpy(MeshUniformBuffer + MeshUniformOffsets[MSU_Bones],\n               glm::value_ptr(CurrentPose[mesh.MeshBone].Offset),\n               sizeof(glm::mat4));\n      }\n\n      memcpy(MeshUniformBuffer + MeshUniformOffsets[MSU_ModelOpacity],\n             &mesh.Opacity, sizeof(mesh.Opacity));\n\n      *(uint32_t*)(MeshUniformBuffer +\n                   MeshUniformOffsets[MSU_HasShadowColorMap]) =\n          mesh.Material == MT_DaSH_Generic && mesh.HasShadowColorMap;\n\n      glBufferSubData(GL_UNIFORM_BUFFER, 0, MeshUniformBlockSize,\n                      MeshUniformBuffer);\n\n      UniformsUpdated[id] = true;\n    }\n  } else if (StaticModel->Type == ModelType_Background) {\n    glm::mat4 mvp = ViewProjection * mesh.ModelTransform.Matrix();\n    glUniformMatrix4fv(UniformMVPBackground, 1, GL_FALSE, glm::value_ptr(mvp));\n  }\n}\n\nvoid Renderable3D::UnloadSync() {\n  Animator.CurrentAnimation = 0;\n  PrevPoseWeight = 0.0f;\n  if (StaticModel) {\n    ImpLog(LogLevel::Info, LogChannel::Renderable3D, \"Unloading model {:d}\\n\",\n           StaticModel->Id);\n    if (IsSubmitted) {\n      glDeleteBuffers(StaticModel->MeshCount, IBOs);\n      glDeleteBuffers(StaticModel->MeshCount, VBOs);\n      glDeleteBuffers(StaticModel->MeshCount, MorphVBOs);\n      glDeleteBuffers(StaticModel->MeshCount, UBOs);\n      glDeleteVertexArrays(StaticModel->MeshCount, VAOs);\n      glDeleteTextures(StaticModel->TextureCount, TexBuffers);\n      glDeleteBuffers(1, &UBOModel);\n    }\n    delete StaticModel;\n    StaticModel = 0;\n  }\n  if (CurrentMorphedVertices) {\n    free(CurrentMorphedVertices);\n    CurrentMorphedVertices = 0;\n  }\n  ModelTransform = Transform();\n  IsSubmitted = false;\n  IsUsed = false;\n  IsVisible = false;\n}\n\nvoid Renderable3D::MainThreadOnLoad() {\n  assert(IsSubmitted == false);\n\n  ImpLog(LogLevel::Info, LogChannel::Renderable3D,\n         \"Submitting data to GPU for model ID {:d}\\n\", StaticModel->Id);\n\n  glGenVertexArrays(StaticModel->MeshCount, VAOs);\n  glGenBuffers(StaticModel->MeshCount, VBOs);\n  glGenBuffers(StaticModel->MeshCount, MorphVBOs);\n  glGenBuffers(StaticModel->MeshCount, IBOs);\n  glGenBuffers(StaticModel->MeshCount, UBOs);\n  glGenBuffers(1, &UBOModel);\n\n  glBindBuffer(GL_UNIFORM_BUFFER, UBOModel);\n  glBufferData(GL_UNIFORM_BUFFER, ModelUniformBlockSize, NULL, GL_DYNAMIC_DRAW);\n\n  for (uint32_t i = 0; i < StaticModel->MeshCount; i++) {\n    glBindBuffer(GL_UNIFORM_BUFFER, UBOs[i]);\n    glBufferData(GL_UNIFORM_BUFFER, MeshUniformBlockSize, NULL,\n                 GL_DYNAMIC_DRAW);\n\n    glBindVertexArray(VAOs[i]);\n\n    glBindBuffer(GL_ARRAY_BUFFER, VBOs[i]);\n\n    if (StaticModel->Type == ModelType_Character) {\n      if (Profile::Scene3D::Version == LKMVersion::DaSH) {\n        glBufferData(\n            GL_ARRAY_BUFFER,\n            sizeof(VertexBufferDaSH) * StaticModel->Meshes[i].VertexCount,\n            (VertexBufferDaSH*)StaticModel->VertexBuffers +\n                StaticModel->Meshes[i].VertexOffset,\n            GL_STATIC_DRAW);\n        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE,\n                              sizeof(VertexBufferDaSH),\n                              (void*)offsetof(VertexBufferDaSH, Position));\n        glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE,\n                              sizeof(VertexBufferDaSH),\n                              (void*)offsetof(VertexBufferDaSH, Normal));\n        glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE,\n                              sizeof(VertexBufferDaSH),\n                              (void*)offsetof(VertexBufferDaSH, UV));\n        // WebGL doesn't like unsigned byte here, figures...\n        glVertexAttribIPointer(3, 4, GL_BYTE, sizeof(VertexBufferDaSH),\n                               (void*)offsetof(VertexBufferDaSH, BoneIndices));\n        glVertexAttribPointer(4, 4, GL_FLOAT, GL_FALSE,\n                              sizeof(VertexBufferDaSH),\n                              (void*)offsetof(VertexBufferDaSH, BoneWeights));\n        glEnableVertexAttribArray(0);\n        glEnableVertexAttribArray(1);\n        glEnableVertexAttribArray(2);\n        glEnableVertexAttribArray(3);\n        glEnableVertexAttribArray(4);\n      } else {\n        glBufferData(GL_ARRAY_BUFFER,\n                     sizeof(VertexBuffer) * StaticModel->Meshes[i].VertexCount,\n                     (VertexBuffer*)StaticModel->VertexBuffers +\n                         StaticModel->Meshes[i].VertexOffset,\n                     GL_STATIC_DRAW);\n        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(VertexBuffer),\n                              (void*)offsetof(VertexBuffer, Position));\n        glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(VertexBuffer),\n                              (void*)offsetof(VertexBuffer, Normal));\n        glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(VertexBuffer),\n                              (void*)offsetof(VertexBuffer, UV));\n        // WebGL doesn't like unsigned byte here, figures...\n        glVertexAttribIPointer(3, 4, GL_BYTE, sizeof(VertexBuffer),\n                               (void*)offsetof(VertexBuffer, BoneIndices));\n        glVertexAttribPointer(4, 4, GL_FLOAT, GL_FALSE, sizeof(VertexBuffer),\n                              (void*)offsetof(VertexBuffer, BoneWeights));\n        glEnableVertexAttribArray(0);\n        glEnableVertexAttribArray(1);\n        glEnableVertexAttribArray(2);\n        glEnableVertexAttribArray(3);\n        glEnableVertexAttribArray(4);\n      }\n    } else if (StaticModel->Type == ModelType_Background) {\n      glBufferData(GL_ARRAY_BUFFER,\n                   sizeof(BgVertexBuffer) * StaticModel->Meshes[i].VertexCount,\n                   (BgVertexBuffer*)StaticModel->VertexBuffers +\n                       StaticModel->Meshes[i].VertexOffset,\n                   GL_STATIC_DRAW);\n      glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(BgVertexBuffer),\n                            (void*)offsetof(BgVertexBuffer, Position));\n      glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(BgVertexBuffer),\n                            (void*)offsetof(BgVertexBuffer, UV));\n      glEnableVertexAttribArray(0);\n      glEnableVertexAttribArray(1);\n    }\n\n    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IBOs[i]);\n    glBufferData(GL_ELEMENT_ARRAY_BUFFER,\n                 sizeof(uint16_t) * StaticModel->Meshes[i].IndexCount,\n                 StaticModel->Indices + StaticModel->Meshes[i].IndexOffset,\n                 GL_STATIC_DRAW);\n  }\n\n  for (uint32_t i = 0; i < StaticModel->TextureCount; i++) {\n    TexBuffers[i] = StaticModel->Textures[i].Submit();\n    if (TexBuffers[i] == 0) {\n      ImpLog(LogLevel::Fatal, LogChannel::Renderable3D,\n             \"Submitting texture {:d} for model {:d} failed\\n\", i,\n             StaticModel->Id);\n    }\n  }\n\n  IsSubmitted = true;\n}\n\n}  // namespace OpenGL\n}  // namespace Impacto"
  },
  {
    "path": "src/renderer/opengl/3d/renderable3d.h",
    "content": "#pragma once\n\n#include <glad/glad.h>\n\n#include \"../../3d/renderable3d.h\"\n#include \"../shader.h\"\n\nnamespace Impacto {\nnamespace OpenGL {\n\nenum RenderPass { RP_Outline = 0, RP_First = 1, RP_Second = 2, RP_Count };\n\nclass Renderable3D : public IRenderable3D {\n public:\n  static void Init(GLWindow* window, ShaderCompiler& shaderCompiler);\n  static void BeginFrame(IScene3D* scene, Camera* camera);\n\n  void MakePlane() override;\n\n  void Update(float dt) override;\n  void Render() override;\n\n  void ReloadDefaultBoneTransforms() override;\n  void InitMeshAnimStatus() override;\n  void ReloadDefaultMeshAnimStatus() override;\n\n  void SwitchAnimation(int16_t animId, float transitionTime) override;\n\n protected:\n  bool LoadSync(uint32_t modelId) override;\n  void UnloadSync() override;\n  void MainThreadOnLoad() override;\n\n private:\n  void Pose();\n  void PoseBone(int16_t id);\n\n  void CalculateMorphedVertices(int id);\n\n  void UseMaterial(MaterialType type);\n  void UseMesh(int id);\n  void LoadModelUniforms();\n  void LoadMeshUniforms(int id);\n  void SetTextures(int id, int const* textureUnits, int count);\n  void DrawMesh(int id, RenderPass pass);\n\n  GLuint UBOModel;\n\n  GLuint VAOs[ModelMaxMeshesPerModel];\n  GLuint VBOs[ModelMaxMeshesPerModel];\n  GLuint MorphVBOs[ModelMaxMeshesPerModel];\n  GLuint IBOs[ModelMaxMeshesPerModel];\n  GLuint UBOs[ModelMaxMeshesPerModel];\n\n  GLuint TexBuffers[ModelMaxTexturesPerModel];\n\n  bool VAOsUpdated[ModelMaxMeshesPerModel];\n  bool UniformsUpdated[ModelMaxMeshesPerModel];\n\n  Transform PrevBoneTransforms[ModelMaxBonesPerModel];\n  AnimatedMesh PrevMeshAnimStatus[ModelMaxMeshesPerModel];\n  float PrevPoseWeight;\n  float AnimationTransitionTime;\n};\n\n}  // namespace OpenGL\n}  // namespace Impacto"
  },
  {
    "path": "src/renderer/opengl/3d/scene.cpp",
    "content": "#include \"scene.h\"\n\n#include \"../../3d/camera.h\"\n#include \"../../../log.h\"\n#include \"../../../workqueue.h\"\n#include \"../glc.h\"\n#include \"renderable3d.h\"\n\n#include \"../../../profile/scene3d.h\"\n\nnamespace Impacto {\nnamespace OpenGL {\n\nScene3D::Scene3D(GLWindow* window, ShaderCompiler& shaderCompiler)\n    : Window(window), Shaders(shaderCompiler) {}\n\nvoid Scene3D::Init() {\n  assert(IsInit == false);\n  ImpLog(LogLevel::Info, LogChannel::Scene, \"Initializing 3D scene system\\n\");\n  IsInit = true;\n\n  Profile::Scene3D::Configure();\n\n  Renderables = new IRenderable3D*[Profile::Scene3D::MaxRenderables];\n  for (int i = 0; i < Profile::Scene3D::MaxRenderables; i++) {\n    Renderables[i] = new Renderable3D();\n  }\n\n  Renderable3D::Init(Window, Shaders);\n\n  MainCamera.Init();\n\n  glGenVertexArrays(1, &VAOScreenFillingTriangle);\n  glBindVertexArray(VAOScreenFillingTriangle);\n  glGenBuffers(1, &VBOScreenFillingTriangle);\n  glBindBuffer(GL_ARRAY_BUFFER, VBOScreenFillingTriangle);\n\n  // clang-format off\n  float ScreenFillingTriangle[] = {\n      // Position       // UV\n      -1.0f, -1.0f,     0.0f, 0.0f,\n      3.0f, -1.0f,      2.0f, 0.0f,\n      -1.0f, 3.0f,      0.0f, 2.0f\n  };\n  // clang-format on\n\n  glBufferData(GL_ARRAY_BUFFER, sizeof(ScreenFillingTriangle),\n               ScreenFillingTriangle, GL_STATIC_DRAW);\n  // Position\n  glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), 0);\n  glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float),\n                        (void*)(2 * sizeof(float)));\n  glEnableVertexAttribArray(0);\n  glEnableVertexAttribArray(1);\n}\n\nvoid Scene3D::Shutdown() {\n  if (!IsInit) return;\n  if (VBOScreenFillingTriangle) glDeleteBuffers(1, &VBOScreenFillingTriangle);\n  if (VAOScreenFillingTriangle)\n    glDeleteVertexArrays(1, &VAOScreenFillingTriangle);\n  CleanFramebufferState();\n  if (Renderables) delete[] Renderables;\n}\n\nvoid Scene3D::Update(float dt) {\n  for (int i = 0; i < Profile::Scene3D::MaxRenderables; i++) {\n    if (Renderables[i]->Status == LoadStatus::Loaded) {\n      Renderables[i]->Update(dt);\n    }\n  }\n}\nvoid Scene3D::Render() {\n  RectF viewport = Window->GetViewport();\n  MainCamera.AspectRatio = viewport.Width / viewport.Height;\n  MainCamera.Recalculate();\n\n  Renderable3D::BeginFrame(this, &MainCamera);\n\n  for (int i = 0; i < Profile::Scene3D::MaxRenderables; i++) {\n    if (Renderables[i]->Status == LoadStatus::Loaded &&\n        Renderables[i]->StaticModel->Type == ModelType_Background) {\n      Renderables[i]->Render();\n    }\n  }\n\n  // Draw background without MSAA\n\n  SetupFramebufferState();\n\n  for (int i = 0; i < Profile::Scene3D::MaxRenderables; i++) {\n    if (Renderables[i]->Status == LoadStatus::Loaded &&\n        Renderables[i]->StaticModel->Type == ModelType_Character) {\n      Renderables[i]->Render();\n    }\n  }\n\n  DrawToScreen();\n}\n\nvoid Scene3D::SetupFramebufferState() {\n  Rect viewport = Window->GetViewport();\n  Rect scaledViewport = Window->GetScaledViewport();\n\n  MSResolveMode msaa = CheckMSResolveMode();\n\n  if (Window->WindowDimensionsChanged) {\n    CleanFramebufferState();\n\n    switch (msaa) {\n      case MS_None:\n        ImpLog(\n            LogLevel::Info, LogChannel::Render,\n            \"Creating 3D scene framebuffer {:d}x{:d} (=> {:d}x{:d}), no MSAA\\n\",\n            scaledViewport.Width, scaledViewport.Height, viewport.Width,\n            viewport.Height);\n        break;\n      case MS_MultisampleTexture:\n        ImpLog(LogLevel::Info, LogChannel::Render,\n               \"Creating 3D scene framebuffer {:d}x{:d} (=> {:d}x{:d}), {:d}x \"\n               \"MSAA using multisample texture\\n\",\n               scaledViewport.Width, scaledViewport.Height, viewport.Width,\n               viewport.Height, Window->MsaaCount);\n        break;\n      case MS_SinglesampleTextureExt:\n        ImpLog(LogLevel::Info, LogChannel::Render,\n               \"Creating 3D scene framebuffer {:d}x{:d} (=> {:d}x{:d}), {:d}x \"\n               \"MSAA using multisampled_render_to_texture\\n\",\n               scaledViewport.Width, scaledViewport.Height, viewport.Width,\n               viewport.Height, Window->MsaaCount);\n        break;\n      case MS_BlitFromRenderbuffer:\n        ImpLog(LogLevel::Info, LogChannel::Render,\n               \"Creating 3D scene framebuffer {:d}x{:d} (=> {:d}x{:d}), {:d}x \"\n               \"MSAA using blit from renderbuffer\\n\",\n               scaledViewport.Width, scaledViewport.Height, viewport.Width,\n               viewport.Height, Window->MsaaCount);\n        break;\n    }\n\n    glGenFramebuffers(1, &FBO);\n    GLC::BindFramebuffer(GL_DRAW_FRAMEBUFFER, FBO);\n    glGenTextures(1, &RenderTextureColor);\n\n    GLenum textureTarget;\n    if (msaa == MS_MultisampleTexture) {\n      textureTarget = GL_TEXTURE_2D_MULTISAMPLE;\n      glBindTexture(textureTarget, RenderTextureColor);\n      glTexImage2DMultisample(textureTarget, Window->MsaaCount, GL_RGBA,\n                              scaledViewport.Width, scaledViewport.Height,\n                              GL_FALSE);\n    } else {\n      textureTarget = GL_TEXTURE_2D;\n      glBindTexture(textureTarget, RenderTextureColor);\n      glTexImage2D(textureTarget, 0, GL_RGBA, scaledViewport.Width,\n                   scaledViewport.Height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);\n\n      glTexParameteri(textureTarget, GL_TEXTURE_MIN_FILTER, GL_NEAREST);\n      glTexParameteri(textureTarget, GL_TEXTURE_MAG_FILTER, GL_NEAREST);\n    }\n\n    if (msaa == MS_SinglesampleTextureExt) {\n      glFramebufferTexture2DMultisampleEXT(\n          GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, textureTarget,\n          RenderTextureColor, 0, Window->MsaaCount);\n    } else {\n      glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,\n                             textureTarget, RenderTextureColor, 0);\n    }\n\n    // If blitting from renderbuffer, our destination framebuffer does not need\n    // depth/stencil since we aren't rendering 3D content on it directly\n    if (msaa != MS_BlitFromRenderbuffer) {\n      // Combining multisampled renderbuffer + multisampled texture does not\n      // work on Nvidia for me, so only use DS renderbuffer for the other cases\n      if (msaa == MS_None || msaa == MS_SinglesampleTextureExt) {\n        glGenRenderbuffers(1, &RenderbufferDS);\n        glBindRenderbuffer(GL_RENDERBUFFER, RenderbufferDS);\n        if (msaa == MS_SinglesampleTextureExt) {\n          glRenderbufferStorageMultisampleEXT(\n              GL_RENDERBUFFER, Window->MsaaCount, GL_DEPTH24_STENCIL8,\n              scaledViewport.Width, scaledViewport.Height);\n        } else {\n          glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8,\n                                scaledViewport.Width, scaledViewport.Height);\n        }\n        glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER,\n                                  GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER,\n                                  RenderbufferDS);\n      } else {\n        // MS_MultisampleTexture only\n        textureTarget = GL_TEXTURE_2D_MULTISAMPLE;\n        glGenTextures(1, &RenderTextureDS);\n        glBindTexture(textureTarget, RenderTextureDS);\n        glTexImage2DMultisample(textureTarget, Window->MsaaCount,\n                                GL_DEPTH_STENCIL, scaledViewport.Width,\n                                scaledViewport.Height, GL_FALSE);\n        glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT,\n                               textureTarget, RenderTextureDS, 0);\n      }\n    }\n\n    ShaderParamMap shaderParams;\n    if (msaa == MS_MultisampleTexture) {\n      shaderParams[\"MultisampleCount\"] = Window->MsaaCount;\n      shaderParams[\"MSAA_MODE_MULTISAMPLE_TEXTURE\"] = ShaderParameter(1, true);\n    }\n    shaderParams[\"WindowDimensions\"] =\n        glm::vec2(viewport.Width, viewport.Height);\n    shaderParams[\"RenderScale\"] = Window->RenderScale;\n\n    ShaderProgram = Shaders.Compile(\"SceneToRT\", shaderParams);\n    glUseProgram(ShaderProgram);\n    glUniform1i(glGetUniformLocation(ShaderProgram, \"Framebuffer3D\"), 0);\n\n    ImpLog(LogLevel::Debug, LogChannel::Render, \"FBO status: 0x{:08X}\\n\",\n           glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER));\n\n    if (msaa == MS_BlitFromRenderbuffer) {\n      glGenFramebuffers(1, &FBOMultisample);\n      GLC::BindFramebuffer(GL_DRAW_FRAMEBUFFER, FBOMultisample);\n      glGenRenderbuffers(1, &RenderbufferColor);\n      glGenRenderbuffers(1, &RenderbufferDS);\n\n      glBindRenderbuffer(GL_RENDERBUFFER, RenderbufferColor);\n      glRenderbufferStorageMultisample(GL_RENDERBUFFER, Window->MsaaCount,\n                                       GL_RGBA8, scaledViewport.Width,\n                                       scaledViewport.Height);\n      glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,\n                                GL_RENDERBUFFER, RenderbufferColor);\n\n      glBindRenderbuffer(GL_RENDERBUFFER, RenderbufferDS);\n      glRenderbufferStorageMultisample(\n          GL_RENDERBUFFER, Window->MsaaCount, GL_DEPTH24_STENCIL8,\n          scaledViewport.Width, scaledViewport.Height);\n      glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER,\n                                GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER,\n                                RenderbufferDS);\n    }\n  } else {\n    if (msaa == MS_BlitFromRenderbuffer) {\n      GLC::BindFramebuffer(GL_DRAW_FRAMEBUFFER, FBOMultisample);\n    } else {\n      GLC::BindFramebuffer(GL_DRAW_FRAMEBUFFER, FBO);\n    }\n  }\n\n  glViewport(0, 0, scaledViewport.Width, scaledViewport.Height);\n  glClearColor(0.0f, 0.0f, 0.0f, 0.0f);\n  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);\n\n  glEnable(GL_DEPTH_TEST);\n  glEnable(GL_BLEND);\n  glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE,\n                      GL_ONE_MINUS_SRC_ALPHA);\n}\n\nvoid Scene3D::CleanFramebufferState() {\n  if (ShaderProgram) {\n    glDeleteProgram(ShaderProgram);\n    ShaderProgram = 0;\n  }\n  if (RenderTextureColor) {\n    glDeleteTextures(1, &RenderTextureColor);\n    RenderTextureColor = 0;\n  }\n  if (RenderTextureDS) {\n    glDeleteTextures(1, &RenderTextureDS);\n    RenderTextureDS = 0;\n  }\n  if (RenderbufferDS) {\n    glDeleteRenderbuffers(1, &RenderbufferDS);\n    RenderbufferDS = 0;\n  }\n  if (RenderbufferColor) {\n    glDeleteRenderbuffers(1, &RenderbufferColor);\n    RenderbufferColor = 0;\n  }\n  if (FBO) {\n    GLC::DeleteFramebuffers(1, &FBO);\n    FBO = 0;\n  }\n  if (FBOMultisample) {\n    GLC::DeleteFramebuffers(1, &FBOMultisample);\n    FBOMultisample = 0;\n  }\n}\n\nvoid Scene3D::DrawToScreen() {\n  Rect viewport = Window->GetViewport();\n  Rect scaledViewport = Window->GetScaledViewport();\n\n  MSResolveMode msaa = CheckMSResolveMode();\n\n  if (msaa == MS_BlitFromRenderbuffer) {\n    GLC::BindFramebuffer(GL_READ_FRAMEBUFFER, FBOMultisample);\n    GLC::BindFramebuffer(GL_DRAW_FRAMEBUFFER, FBO);\n\n    glBlitFramebuffer(0, 0, scaledViewport.Width, scaledViewport.Height, 0, 0,\n                      scaledViewport.Width, scaledViewport.Height,\n                      GL_COLOR_BUFFER_BIT, GL_NEAREST);\n\n    GLenum attachments[2] = {GL_COLOR_ATTACHMENT0, GL_DEPTH_STENCIL_ATTACHMENT};\n    if (glInvalidateFramebuffer)\n      glInvalidateFramebuffer(GL_READ_FRAMEBUFFER, 2, attachments);\n  }\n\n  GLC::BindFramebuffer(GL_DRAW_FRAMEBUFFER, Window->DrawRT);\n  glViewport(0, 0, viewport.Width, viewport.Height);\n\n  glEnable(GL_BLEND);\n  glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);\n\n  // TODO: Better than linear filtering for supersampling\n\n  glActiveTexture(GL_TEXTURE0);\n  if (msaa == MS_MultisampleTexture) {\n    glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, RenderTextureColor);\n  } else {\n    glBindTexture(GL_TEXTURE_2D, RenderTextureColor);\n  }\n\n  glBindVertexArray(VAOScreenFillingTriangle);\n  glUseProgram(ShaderProgram);\n  glDrawArrays(GL_TRIANGLES, 0, 3);\n\n  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);\n}\n\nMSResolveMode Scene3D::CheckMSResolveMode() {\n  if (Window->MsaaCount == 1) return MS_None;\n\n  if (ActualGraphicsApi != GfxApi_GL) {\n    if (GLAD_GL_EXT_multisampled_render_to_texture) {\n      return MS_SinglesampleTextureExt;\n    }\n    return MS_BlitFromRenderbuffer;\n  }\n\n  // return MS_MultisampleTexture;\n  return MS_BlitFromRenderbuffer;  // Apparently this is actually slightly\n                                   // faster, who'd've thunk..\n}\n\n}  // namespace OpenGL\n}  // namespace Impacto"
  },
  {
    "path": "src/renderer/opengl/3d/scene.h",
    "content": "#pragma once\n\n#include <glm/glm.hpp>\n\n#include \"../../3d/scene.h\"\n#include \"../shader.h\"\n#include \"../window.h\"\n#include \"../renderer.h\"\n\nnamespace Impacto {\nnamespace OpenGL {\n\nenum MSResolveMode {\n  MS_None,\n  // Use a framebuffer with multisample texture\n  MS_MultisampleTexture,\n  // Use a framebuffer with singlesample texture provided by\n  // EXT_multisampled_render_to_texture\n  MS_SinglesampleTextureExt,\n  // Use a framebuffer with multisample renderbuffer and blit to framebuffer\n  // with singlesample texture\n  MS_BlitFromRenderbuffer\n};\n\nclass Scene3D : public IScene3D {\n public:\n  Scene3D(GLWindow* window, ShaderCompiler& shaderCompiler);\n\n  void Init();\n  void Shutdown();\n  void Update(float dt);\n  void Render();\n\n private:\n  bool IsInit = false;\n\n  GLWindow* Window;\n  ShaderCompiler& Shaders;\n\n  void SetupFramebufferState();\n  void CleanFramebufferState();\n  void DrawToScreen();\n\n  MSResolveMode CheckMSResolveMode();\n\n  GLuint FBO = 0;\n  GLuint RenderTextureColor = 0;\n  GLuint RenderTextureDS = 0;\n\n  // Only for MS_BlitFromRenderbuffer\n  GLuint FBOMultisample = 0;\n  GLuint RenderbufferColor = 0;\n  GLuint RenderbufferDS = 0;\n\n  GLuint VAOScreenFillingTriangle = 0;\n  GLuint VBOScreenFillingTriangle = 0;\n\n  GLuint ShaderProgram = 0;\n};\n\n}  // namespace OpenGL\n}  // namespace Impacto"
  },
  {
    "path": "src/renderer/opengl/glc.cpp",
    "content": "#include \"glc.h\"\n\n#include \"../../profile/game.h\"\n\nnamespace Impacto {\nnamespace GLC {\n\nstatic GLuint CurrentDrawFramebuffer = 0, CurrentReadFramebuffer = 0,\n              CurrentFramebuffer = 0;\n\nvoid InitializeFramebuffers() {\n  glGenFramebuffers((GLsizei)Framebuffers.max_size(), Framebuffers.data());\n  glGenTextures((GLsizei)FramebufferTextures.max_size(),\n                FramebufferTextures.data());\n  glGenRenderbuffers((GLsizei)StencilBuffers.max_size(), StencilBuffers.data());\n\n  RectF viewport = Window->GetViewport();\n  for (size_t buffer = 0; buffer < FramebufferTextures.size(); buffer++) {\n    const GLuint framebufferId = Framebuffers[buffer];\n    const GLuint textureId = FramebufferTextures[buffer];\n    const GLuint stencilBufferId = StencilBuffers[buffer];\n\n    glBindFramebuffer(GL_FRAMEBUFFER, framebufferId);\n    glBindTexture(GL_TEXTURE_2D, textureId);\n    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, (GLsizei)viewport.Width,\n                 (GLsizei)viewport.Height, 0, GL_RGB, GL_UNSIGNED_BYTE,\n                 nullptr);\n\n    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);\n    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);\n\n    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,\n                           textureId, 0);\n\n    glBindRenderbuffer(GL_RENDERBUFFER, stencilBufferId);\n    glRenderbufferStorage(GL_RENDERBUFFER, GL_STENCIL_INDEX8,\n                          (GLsizei)viewport.Width, (GLsizei)viewport.Height);\n    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT,\n                              GL_RENDERBUFFER, stencilBufferId);\n  }\n\n  glBindFramebuffer(GL_FRAMEBUFFER, 0);\n}\n\nvoid BindFramebuffer(GLenum target, GLuint framebuffer) {\n  switch (target) {\n    case GL_DRAW_FRAMEBUFFER: {\n      if (CurrentDrawFramebuffer != framebuffer) {\n        CurrentDrawFramebuffer = framebuffer;\n        glBindFramebuffer(target, framebuffer);\n      }\n      break;\n    }\n    case GL_READ_FRAMEBUFFER: {\n      if (CurrentReadFramebuffer != framebuffer) {\n        CurrentReadFramebuffer = framebuffer;\n        glBindFramebuffer(target, framebuffer);\n      }\n      break;\n    }\n    case GL_FRAMEBUFFER: {\n      if (CurrentFramebuffer != framebuffer) {\n        CurrentFramebuffer = framebuffer;\n        glBindFramebuffer(target, framebuffer);\n      }\n      break;\n    }\n    default: {\n      glBindFramebuffer(target, framebuffer);\n    }\n  }\n}\n\nvoid DeleteFramebuffers(GLsizei n, GLuint const* framebuffers) {\n  glDeleteFramebuffers(n, framebuffers);\n  for (GLsizei i = 0; i < n; i++) {\n    if (CurrentDrawFramebuffer == framebuffers[i]) CurrentDrawFramebuffer = 0;\n    if (CurrentReadFramebuffer == framebuffers[i]) CurrentReadFramebuffer = 0;\n    if (CurrentFramebuffer == framebuffers[i]) CurrentFramebuffer = 0;\n  }\n}\n\n}  // namespace GLC\n}  // namespace Impacto"
  },
  {
    "path": "src/renderer/opengl/glc.h",
    "content": "#pragma once\n\n#include \"../../impacto.h\"\n#include \"../renderer.h\"\n\n#include <array>\n\nnamespace Impacto {\nnamespace GLC {\n\ninline std::array<GLuint, MaxFramebuffers> Framebuffers;\ninline std::array<GLuint, MaxFramebuffers> FramebufferTextures;\ninline std::array<GLuint, MaxFramebuffers> StencilBuffers;\n\nvoid InitializeFramebuffers();\n\nvoid BindFramebuffer(GLenum target, GLuint framebuffer);\n\nvoid DeleteFramebuffers(GLsizei n, GLuint const* framebuffers);\n\n}  // namespace GLC\n}  // namespace Impacto"
  },
  {
    "path": "src/renderer/opengl/nv12frame.cpp",
    "content": "#include \"nv12frame.h\"\n#include \"../../log.h\"\n\n#include <glad/glad.h>\n\nnamespace Impacto {\nnamespace OpenGL {\n\nvoid GLNV12Frame::Init(float width, float height) {\n  Width = width;\n  Height = height;\n  GLuint yuv[2];\n\n  glGenTextures(2, yuv);\n  LumaId = yuv[0];\n  CbCrId = yuv[1];\n}\n\nvoid Impacto::OpenGL::GLNV12Frame::Submit(const void* luma, int lumaStride,\n                                          const void* cbcr, int cbcrStride) {\n  glBindTexture(GL_TEXTURE_2D, LumaId);\n  glPixelStorei(GL_UNPACK_ROW_LENGTH, lumaStride);\n  glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, (GLsizei)Width, (GLsizei)Height, 0,\n               GL_RED, GL_UNSIGNED_BYTE, luma);\n  glGenerateMipmap(GL_TEXTURE_2D);\n\n  glBindTexture(GL_TEXTURE_2D, CbCrId);\n  glPixelStorei(GL_UNPACK_ROW_LENGTH, cbcrStride / 2);\n  glTexImage2D(GL_TEXTURE_2D, 0, GL_RG8, (GLsizei)(Width / 2),\n               (GLsizei)(Height / 2), 0, GL_RG, GL_UNSIGNED_BYTE, cbcr);\n  glGenerateMipmap(GL_TEXTURE_2D);\n  glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);\n}\n\nvoid GLNV12Frame::Release() {\n  GLuint yuv[2];\n  yuv[0] = LumaId;\n  yuv[1] = CbCrId;\n  glDeleteTextures(2, yuv);\n}\n\n}  // namespace OpenGL\n}  // namespace Impacto"
  },
  {
    "path": "src/renderer/opengl/nv12frame.h",
    "content": "#pragma once\n\n#include \"../nv12frame.h\"\n\nnamespace Impacto {\nnamespace OpenGL {\n\nclass GLNV12Frame : public NV12Frame {\n public:\n  void Init(float width, float height) override;\n\n  void Submit(const void* luma, int lumaStride, const void* cbcr,\n              int cbcrStride) override;\n  void Release() override;\n};\n\n}  // namespace OpenGL\n}  // namespace Impacto"
  },
  {
    "path": "src/renderer/opengl/renderer.cpp",
    "content": "#include \"renderer.h\"\n\n#include <array>\n#include <numeric>\n#include \"shader.h\"\n#include \"../../profile/game.h\"\n#include \"../../game.h\"\n#include \"../../log.h\"\n#include \"3d/scene.h\"\n#include \"yuvframe.h\"\n#include \"nv12frame.h\"\n\n#ifndef IMPACTO_DISABLE_IMGUI\n#include <imgui_custom/backends/imgui_impl_opengl3.h>\n#endif\n\nnamespace Impacto {\nnamespace OpenGL {\n\nstatic CornersQuad FlipUvVertical(CornersQuad quad) {\n  quad.BottomLeft.y = 1.0f - quad.BottomLeft.y;\n  quad.BottomRight.y = 1.0f - quad.BottomRight.y;\n  quad.TopRight.y = 1.0f - quad.TopRight.y;\n  quad.TopLeft.y = 1.0f - quad.TopLeft.y;\n\n  return quad;\n}\n\nvoid Renderer::Init() {\n  if (IsInit) return;\n  ImpLog(LogLevel::Info, LogChannel::Render,\n         \"Initializing Renderer2D system\\n\");\n  IsInit = true;\n\n  OpenGLWindow = new GLWindow();\n  OpenGLWindow->Init();\n  Window = (BaseWindow*)OpenGLWindow;\n\n  if (+Profile::GameFeatures & +GameFeature::Scene3D) {\n    Scene = new Scene3D(OpenGLWindow, Shaders);\n    Scene->Init();\n  }\n\n  // Generate buffers\n  glGenBuffers(1, &VBO);\n  glGenBuffers(1, &IBO);\n  glGenVertexArrays(1, &VAOSprites);\n\n  GLC::InitializeFramebuffers();\n\n  // Specify vertex layouts\n  glBindVertexArray(VAOSprites);\n  glBindBuffer(GL_ARRAY_BUFFER, VBO);\n  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IBO);\n  glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(VertexBufferSprites),\n                        (void*)offsetof(VertexBufferSprites, Position));\n  glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(VertexBufferSprites),\n                        (void*)offsetof(VertexBufferSprites, UV));\n  glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, sizeof(VertexBufferSprites),\n                        (void*)offsetof(VertexBufferSprites, Tint));\n  glVertexAttribPointer(3, 2, GL_FLOAT, GL_FALSE, sizeof(VertexBufferSprites),\n                        (void*)offsetof(VertexBufferSprites, MaskUV));\n  for (GLuint i = 0; i < 4; i++) glEnableVertexAttribArray(i);\n\n  // Make 1x1 white pixel for colored rectangles\n  Texture rectTexture;\n  rectTexture.Load1x1(0xFF, 0xFF, 0xFF, 0xFF);\n  SpriteSheet rectSheet(1.0f, 1.0f);\n  rectSheet.Texture = rectTexture.Submit();\n  RectSprite = Sprite(rectSheet, 0.0f, 0.0f, 1.0f, 1.0f);\n\n  // Set up shaders\n  SpriteShaderProgram.emplace(Shaders.Compile(\"Sprite\"));\n  SpriteInvertedShaderProgram.emplace(Shaders.Compile(\"SpriteInverted\"));\n  MaskedSpriteShaderProgram.emplace(Shaders.Compile(\"MaskedSprite\"));\n  MaskedSpriteBinaryShaderProgram.emplace(\n      Shaders.Compile(\"MaskedSpriteBinary\"));\n  MaskedSpriteNoAlphaShaderProgram.emplace(\n      Shaders.Compile(\"MaskedSpriteNoAlpha\"));\n  ColorMaskedSpriteShaderProgram.emplace(Shaders.Compile(\"ColorMaskedSprite\"));\n  AdditiveMaskedSpriteShaderProgram.emplace(\n      Shaders.Compile(\"AdditiveMaskedSprite\"));\n  ColorBurnMaskedSpriteShaderProgram.emplace(\n      Shaders.Compile(\"ColorBurnMaskedSprite\"));\n  ColorDodgeMaskedSpriteShaderProgram.emplace(\n      Shaders.Compile(\"ColorDodgeMaskedSprite\"));\n  HardLightMaskedSpriteShaderProgram.emplace(\n      Shaders.Compile(\"HardLightMaskedSprite\"));\n  LinearBurnMaskedSpriteShaderProgram.emplace(\n      Shaders.Compile(\"LinearBurnMaskedSprite\"));\n  OverlayMaskedSpriteShaderProgram.emplace(\n      Shaders.Compile(\"OverlayMaskedSprite\"));\n  ScreenMaskedSpriteShaderProgram.emplace(\n      Shaders.Compile(\"ScreenMaskedSprite\"));\n  SoftLightMaskedSpriteShaderProgram.emplace(\n      Shaders.Compile(\"SoftLightMaskedSprite\"));\n  YUVFrameShaderProgram.emplace(Shaders.Compile(\"YUVFrame\"));\n  CCMessageBoxShaderProgram.emplace(Shaders.Compile(\"CCMessageBoxSprite\"));\n  CHLCCMenuBackgroundShaderProgram.emplace(\n      Shaders.Compile(\"CHLCCMenuBackground\"));\n  GaussianBlurShaderProgram.emplace(Shaders.Compile(\"GaussianBlur\"));\n  MosaicShaderProgram.emplace(Shaders.Compile(\"Mosaic\"));\n  SubtitleGlyphShaderProgram.emplace(Shaders.Compile(\"SubtitleGlyph\"));\n  NV12FrameShaderProgram.emplace(Shaders.Compile(\"NV12Frame\"));\n\n  glGenSamplers((GLsizei)Samplers.size(), Samplers.data());\n  for (size_t i = 0; i < TextureUnitCount; i++) {\n    glBindSampler((GLuint)i, Samplers[i]);\n\n    glSamplerParameteri(Samplers[i], GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);\n    glSamplerParameteri(Samplers[i], GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);\n\n    glSamplerParameteri(Samplers[i], GL_TEXTURE_MIN_FILTER, GL_LINEAR);\n    glSamplerParameteri(Samplers[i], GL_TEXTURE_MAG_FILTER, GL_LINEAR);\n\n    glSamplerParameteri(Samplers[i], GL_TEXTURE_MAX_ANISOTROPY, 16);\n  }\n\n  glActiveTexture(GL_TEXTURE0);\n  glBindSampler(0, Samplers[0]);\n}\n\nvoid Renderer::Shutdown() {\n  if (!IsInit) return;\n  if (VBO) glDeleteBuffers(1, &VBO);\n  if (IBO) glDeleteBuffers(1, &IBO);\n  if (VAOSprites) glDeleteVertexArrays(1, &VAOSprites);\n  if (RectSprite.Sheet.Texture) glDeleteTextures(1, &RectSprite.Sheet.Texture);\n  IsInit = false;\n\n  GLC::DeleteFramebuffers((GLsizei)GLC::Framebuffers.size(),\n                          GLC::Framebuffers.data());\n  glDeleteTextures((GLsizei)GLC::FramebufferTextures.size(),\n                   GLC::FramebufferTextures.data());\n  glDeleteRenderbuffers((GLsizei)GLC::StencilBuffers.size(),\n                        GLC::StencilBuffers.data());\n\n  glDeleteSamplers((GLsizei)Samplers.size(), Samplers.data());\n\n  if (+Profile::GameFeatures & +GameFeature::Scene3D) {\n    Scene->Shutdown();\n  }\n}\n\n#ifndef IMPACTO_DISABLE_IMGUI\nvoid Renderer::ImGuiBeginFrame() {\n  ImGui_ImplOpenGL3_NewFrame();\n  ImGui_ImplSDL2_NewFrame();\n  ImGui::NewFrame();\n}\n#endif\n\nvoid Renderer::BeginFrame() {}\n\nvoid Renderer::BeginFrame2D() {\n  if (Drawing) {\n    ImpLog(LogLevel::Error, LogChannel::Render,\n           \"Renderer->BeginFrame() called before EndFrame()\\n\");\n    return;\n  }\n\n  Drawing = true;\n\n  VertexBuffer.clear();\n  IndexBuffer.clear();\n  NextFreeIndex = 0;\n\n  glDisable(GL_CULL_FACE);\n\n  glBindSampler(0, Samplers[0]);\n}\n\nvoid Renderer::EndFrame() {\n  if (!Drawing) return;\n  Flush();\n  Drawing = false;\n\n  glBindSampler(0, 0);\n}\n\nuint32_t Renderer::SubmitTexture(TexFmt format, uint8_t* buffer, int width,\n                                 int height) {\n  GLint prevBound;\n  uint32_t result;\n  glGetIntegerv(GL_TEXTURE_BINDING_2D, &prevBound);\n  glGenTextures(1, &result);\n  glBindTexture(GL_TEXTURE_2D, result);\n\n  // Anisotropic filtering\n  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,\n                  GL_LINEAR_MIPMAP_LINEAR);\n  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);\n  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 16);\n\n  // Load in data\n  const auto [internalFormat,\n              texFormat] = [format]() -> std::pair<GLint, GLenum> {\n    switch (format) {\n      case TexFmt_RGBA:\n        glPixelStorei(GL_UNPACK_ALIGNMENT, 4);\n        return {GL_RGBA, GL_RGBA};\n\n      case TexFmt_RGB:\n        glPixelStorei(GL_UNPACK_ALIGNMENT, 1);\n        return {GL_RGB, GL_RGB};\n\n      case TexFmt_U8:\n        glPixelStorei(GL_UNPACK_ALIGNMENT, 1);\n        return {GL_R8, GL_RED};\n\n      default:\n        throw std::invalid_argument(\n            fmt::format(\"Unimplemented texture format {}\", (int)format));\n    }\n  }();\n  glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, width, height, 0, texFormat,\n               GL_UNSIGNED_BYTE, buffer);\n\n  // Build mip chain\n  // TODO do this ourselves outside of Submit(), this can easily cause a\n  // framedrop\n  glGenerateMipmap(GL_TEXTURE_2D);\n  glBindTexture(GL_TEXTURE_2D, prevBound);\n  return result;\n}\n\nint Renderer::GetSpriteSheetImage(SpriteSheet const& sheet,\n                                  std::span<uint8_t> outBuffer) {\n  const size_t bufferSize =\n      static_cast<size_t>(sheet.DesignWidth * sheet.DesignHeight * 4);\n  assert(outBuffer.size() >= bufferSize);\n  glBindTexture(GL_TEXTURE_2D, sheet.Texture);\n\n  GLuint fbo;\n  glGenFramebuffers(1, &fbo);\n  glBindFramebuffer(GL_FRAMEBUFFER, fbo);\n  glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,\n                         sheet.Texture, 0);\n  glReadPixels(0, 0, (GLsizei)sheet.DesignWidth, (GLsizei)sheet.DesignHeight,\n               GL_RGBA, GL_UNSIGNED_BYTE, outBuffer.data());\n\n  glBindFramebuffer(GL_FRAMEBUFFER, 0);\n  glDeleteFramebuffers(1, &fbo);\n\n  if (sheet.IsScreenCap) {\n    auto itr = outBuffer.begin();\n    auto revItr = std::make_reverse_iterator(itr + bufferSize);\n    while (itr < revItr.base() - (size_t)(sheet.DesignWidth * 4)) {\n      std::swap_ranges(itr, itr + (size_t)sheet.DesignWidth * 4,\n                       revItr.base() - ((size_t)sheet.DesignWidth * 4));\n      itr += (size_t)sheet.DesignWidth * 4;\n      revItr += (size_t)sheet.DesignWidth * 4;\n    }\n  }\n\n  return static_cast<int>(bufferSize);\n}\n\nvoid Renderer::FreeTexture(uint32_t id) { glDeleteTextures(1, &id); }\n\nYUVFrame* Renderer::CreateYUVFrame(float width, float height) {\n  auto frame = new GLYUVFrame();\n  frame->Init(width, height);\n  return (YUVFrame*)frame;\n}\n\nNV12Frame* Renderer::CreateNV12Frame(float width, float height) {\n  auto frame = new GLNV12Frame();\n  frame->Init(width, height);\n  return (NV12Frame*)frame;\n}\n\nvoid Renderer::InsertVertices(\n    const std::span<const VertexBufferSprites> vertices,\n    const std::span<const uint16_t> indices) {\n  if (vertices.empty() || indices.empty()) return;\n  assert(indices.size() <= MaxIndexCount);\n\n  if (IndexBuffer.size() + indices.size() > MaxIndexCount) Flush();\n\n  VertexBuffer.insert(VertexBuffer.end(), vertices.begin(), vertices.end());\n\n  const size_t offset = IndexBuffer.size();\n  const size_t indicesCount = indices.size();\n  IndexBuffer.resize(offset + indicesCount);\n\n  uint16_t maxIndex = 0;\n  for (size_t i = 0; i < indicesCount; ++i) {\n    IndexBuffer[i + offset] = indices[i] + NextFreeIndex;\n    if (indices[i] > maxIndex) maxIndex = indices[i];\n  }\n\n  NextFreeIndex += maxIndex + 1;\n}\n\nvoid Renderer::InsertVerticesQuad(const CornersQuad pos, const CornersQuad uv,\n                                  const std::span<const glm::vec4, 4> tints,\n                                  const CornersQuad maskUV) {\n  const std::array<const VertexBufferSprites, 4> vertices = {\n      VertexBufferSprites{\n          .Position = pos.BottomLeft,\n          .UV = uv.BottomLeft,\n          .Tint = tints[0],\n          .MaskUV = maskUV.BottomLeft,\n      },\n      VertexBufferSprites{\n          .Position = pos.TopLeft,\n          .UV = uv.TopLeft,\n          .Tint = tints[1],\n          .MaskUV = maskUV.TopLeft,\n      },\n      VertexBufferSprites{\n          .Position = pos.TopRight,\n          .UV = uv.TopRight,\n          .Tint = tints[2],\n          .MaskUV = maskUV.TopRight,\n      },\n      VertexBufferSprites{\n          .Position = pos.BottomRight,\n          .UV = uv.BottomRight,\n          .Tint = tints[3],\n          .MaskUV = maskUV.BottomRight,\n      },\n  };\n\n  const std::array<const uint16_t, 6> indices = {0, 1, 2, 0, 2, 3};\n\n  InsertVertices(vertices, indices);\n}\n\nvoid Renderer::UseTextures(\n    const std::span<const std::pair<uint32_t, size_t>> textureUnitPairs) {\n  for (const auto& [textureId, unitIndex] : textureUnitPairs) {\n    if (TextureUnits[unitIndex].TextureId != textureId &&\n        TextureUnits[unitIndex].InUse) {\n      Flush();\n    }\n  }\n\n  for (const auto& [textureId, unitIndex] : textureUnitPairs) {\n    TextureUnit& textureUnit = TextureUnits[unitIndex];\n\n    // Always update the active texture in case the texture contents of the same\n    // index changes, like in videos\n    glActiveTexture((GLenum)(GL_TEXTURE0 + unitIndex));\n    glBindTexture(GL_TEXTURE_2D, (GLuint)textureId);\n\n    textureUnit.TextureId = textureId;\n    textureUnit.InUse = true;\n  }\n}\n\nvoid Renderer::DrawSprite(const Sprite& sprite, const CornersQuad& dest,\n                          const glm::mat4 transformation,\n                          const std::span<const glm::vec4, 4> tints,\n                          const glm::vec3 colorShift, const bool inverted,\n                          const bool disableBlend,\n                          const bool textureWrapRepeat) {\n  if (!Drawing) {\n    ImpLog(LogLevel::Error, LogChannel::Render,\n           \"Renderer->DrawSprite() called before BeginFrame()\\n\");\n    return;\n  }\n\n  EnsureTopologyMode(TopologyMode::Triangles);\n\n  if (sprite.Sheet.IsScreenCap) Flush();\n\n  if (textureWrapRepeat) {\n    Flush();\n    glBindSampler(0, Samplers[0]);\n    glSamplerParameteri(Samplers[0], GL_TEXTURE_WRAP_S, GL_REPEAT);\n    glSamplerParameteri(Samplers[0], GL_TEXTURE_WRAP_T, GL_REPEAT);\n  }\n\n  if (disableBlend) {\n    Flush();\n    glDisable(GL_BLEND);\n  }\n\n  // Set uniform variables\n  if (inverted) {\n    SpriteInvertedUniforms uniforms{\n        .Projection = Projection,\n        .Transformation = transformation,\n        .ColorMap = 0,\n    };\n\n    UseShader(*SpriteInvertedShaderProgram, uniforms);\n\n  } else {\n    SpriteUniforms uniforms{\n        .Projection = Projection,\n        .Transformation = transformation,\n        .ColorMap = 0,\n        .ColorShift = colorShift,\n    };\n\n    UseShader(*SpriteShaderProgram, uniforms);\n  }\n\n  UseTextures(std::array<std::pair<uint32_t, size_t>, 1>{\n      std::pair{sprite.Sheet.Texture, 0}});\n\n  // OK, all good, make quad\n\n  CornersQuad uvDest = sprite.NormalizedBounds();\n  if (sprite.Sheet.IsScreenCap) uvDest = FlipUvVertical(uvDest);\n  InsertVerticesQuad(dest, uvDest, tints);\n\n  if (disableBlend) {\n    Flush();\n    glEnable(GL_BLEND);\n  }\n\n  if (textureWrapRepeat) {\n    Flush();\n    glBindSampler(0, Samplers[0]);\n    glSamplerParameteri(Samplers[0], GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);\n    glSamplerParameteri(Samplers[0], GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);\n  }\n}\n\nvoid Renderer::DrawMaskedSprite(const Sprite& sprite, const Sprite& mask,\n                                const CornersQuad& spriteDest,\n                                const CornersQuad& maskDest, int alpha,\n                                int fadeRange, glm::mat4 spriteTransformation,\n                                glm::mat4 maskTransformation,\n                                const std::span<const glm::vec4, 4> tints,\n                                const bool isInverted,\n                                const bool isSameTexture) {\n  if (!Drawing) {\n    ImpLog(LogLevel::Error, LogChannel::Render,\n           \"Renderer->DrawMaskedSprite() called before BeginFrame()\\n\");\n    return;\n  }\n\n  EnsureTopologyMode(TopologyMode::Triangles);\n\n  if (fadeRange == 0) fadeRange = 16;\n  alpha = std::clamp(alpha, 0, fadeRange + 256);\n  const float alphaRange = 256.0f / fadeRange;\n  const float constAlpha = (1.0f - alpha / 255.0f) * alphaRange;\n\n  // Set uniform variables\n  MaskedSpriteUniforms uniforms{\n      .Projection = Projection,\n      .SpriteTransformation = spriteTransformation,\n      .MaskTransformation = maskTransformation,\n      .ColorMap = 0,\n      .Mask = 2,\n      .Alpha = {alphaRange, constAlpha},\n      .IsInverted = isInverted,\n      .IsSameTexture = isSameTexture,\n  };\n\n  UseShader(*MaskedSpriteShaderProgram, uniforms);\n\n  UseTextures(std::array<std::pair<uint32_t, size_t>, 2>{\n      std::pair{sprite.Sheet.Texture, 0},\n      std::pair{mask.Sheet.Texture, 2},\n  });\n\n  // OK, all good, make quad\n\n  CornersQuad uvDest = sprite.NormalizedBounds();\n  CornersQuad maskUVDest = RectF(0.0, 0.0, 1.0f, 1.0f);\n  if (sprite.Sheet.IsScreenCap) uvDest = FlipUvVertical(uvDest);\n  if (mask.Sheet.IsScreenCap) maskUVDest = FlipUvVertical(maskUVDest);\n  InsertVerticesQuad(spriteDest, uvDest, tints, maskUVDest);\n}\n\nvoid Renderer::DrawMaskedBinarySprite(\n    const Sprite& sprite, const Sprite& mask, const CornersQuad& spriteDest,\n    const CornersQuad& maskDest, glm::mat4 spriteTransformation,\n    std::optional<glm::mat4> maskTransformation,\n    std::span<const glm::vec4, 4> tints, bool isInverted) {\n  if (!Drawing) {\n    ImpLog(LogLevel::Error, LogChannel::Render,\n           \"Renderer->DrawMaskedSprite() called before BeginFrame()\\n\");\n    return;\n  }\n\n  EnsureTopologyMode(TopologyMode::Triangles);\n\n  // Set uniform variables\n  MaskedSpriteBinaryUniforms uniforms{\n      .Projection = Projection,\n      .SpriteTransformation = spriteTransformation,\n      .MaskTransformation = maskTransformation.value_or(glm::mat4(1.0f)),\n      .FullscreenMask = !maskTransformation.has_value(),\n      .ColorMap = 0,\n      .Mask = 2,\n      .IsInverted = isInverted,\n  };\n\n  UseShader(*MaskedSpriteBinaryShaderProgram, uniforms);\n\n  UseTextures(std::array<std::pair<uint32_t, size_t>, 2>{\n      std::pair{sprite.Sheet.Texture, 0},\n      std::pair{mask.Sheet.Texture, 2},\n  });\n\n  // OK, all good, make quad\n\n  CornersQuad uvDest = sprite.NormalizedBounds();\n  CornersQuad maskUVDest = CornersQuad(maskDest).Scale(\n      {1.0f / mask.Bounds.Width, 1.0f / mask.Bounds.Height}, {0.0f, 0.0f});\n  if (sprite.Sheet.IsScreenCap) uvDest = FlipUvVertical(uvDest);\n  if (mask.Sheet.IsScreenCap) maskUVDest = FlipUvVertical(maskUVDest);\n  InsertVerticesQuad(spriteDest, uvDest, tints, maskUVDest);\n}\n\nvoid Renderer::DrawMaskedSpriteOverlay(\n    const Sprite& sprite, const Sprite& mask, const CornersQuad& spriteDest,\n    const CornersQuad& maskDest, int alpha, const int fadeRange,\n    glm::mat4 spriteTransformation, glm::mat4 maskTransformation,\n    const std::span<const glm::vec4, 4> tints, const bool isInverted,\n    const bool useMaskAlpha) {\n  if (!Drawing) {\n    ImpLog(LogLevel::Error, LogChannel::Render,\n           \"Renderer->DrawMaskedSpriteOverlay() called before BeginFrame()\\n\");\n    return;\n  }\n\n  EnsureTopologyMode(TopologyMode::Triangles);\n\n  alpha = std::clamp(alpha, 0, fadeRange + 256);\n  const float alphaRange = 256.0f / fadeRange;\n  const float constAlpha = ((255.0f - alpha) * alphaRange) / 255.0f;\n\n  if (useMaskAlpha) {\n    MaskedSpriteUniforms uniforms{\n        .Projection = Projection,\n        .SpriteTransformation = spriteTransformation,\n        .MaskTransformation = maskTransformation,\n        .ColorMap = 0,\n        .Mask = 2,\n        .Alpha = {alphaRange, constAlpha},\n        .IsInverted = isInverted,\n        .IsSameTexture = false,\n    };\n\n    UseShader(*MaskedSpriteShaderProgram, uniforms);\n\n  } else {\n    MaskedSpriteNoAlphaUniforms uniforms{\n        .Projection = Projection,\n        .SpriteTransformation = spriteTransformation,\n        .MaskTransformation = maskTransformation,\n        .ColorMap = 0,\n        .Mask = 2,\n        .Alpha = {alphaRange, constAlpha},\n        .IsInverted = isInverted,\n    };\n\n    UseShader(*MaskedSpriteNoAlphaShaderProgram, uniforms);\n  }\n\n  UseTextures(std::array<std::pair<uint32_t, size_t>, 2>{\n      std::pair{sprite.Sheet.Texture, 0},\n      std::pair{mask.Sheet.Texture, 2},\n  });\n\n  // OK, all good, make quad\n\n  CornersQuad normalizedMaskDest = CornersQuad(maskDest).Scale(\n      {1.0f / mask.Sheet.DesignWidth, 1.0f / mask.Sheet.DesignHeight},\n      {0.0f, 0.0f});\n  InsertVerticesQuad(spriteDest, sprite.NormalizedBounds(), tints,\n                     normalizedMaskDest);\n}\n\nvoid Renderer::DrawPrimitives(\n    const SpriteSheet& sheet, const SpriteSheet* const mask,\n    const ShaderProgramType shaderType,\n    const std::span<const VertexBufferSprites> vertices,\n    const std::span<const uint16_t> indices,\n    const glm::mat4 spriteTransformation, const glm::mat4 maskTransformation,\n    const bool inverted, TopologyMode topologyMode,\n    const bool textureWrapRepeat) {\n  if (!Drawing) {\n    ImpLog(LogLevel::Error, LogChannel::Render,\n           \"Renderer->DrawVertices() called before BeginFrame()\\n\");\n    return;\n  }\n  EnsureTopologyMode(topologyMode);\n\n  if (textureWrapRepeat) {\n    Flush();\n    glBindSampler(0, Samplers[0]);\n    glSamplerParameteri(Samplers[0], GL_TEXTURE_WRAP_S, GL_REPEAT);\n    glSamplerParameteri(Samplers[0], GL_TEXTURE_WRAP_T, GL_REPEAT);\n  }\n\n  // Set uniform variables\n  // PringlesGang: I promise I'll clean all of this up with the bgfx refactor!!\n  switch (shaderType) {\n    case ShaderProgramType::AdditiveMaskedSprite: {\n      AdditiveMaskedSpriteUniforms uniforms{\n          .Projection = Projection,\n          .SpriteTransformation = spriteTransformation,\n          .MaskTransformation = maskTransformation,\n          .ColorMap = 0,\n          .Mask = 2,\n      };\n\n      UseShader(*AdditiveMaskedSpriteShaderProgram, uniforms);\n    } break;\n\n    case ShaderProgramType::ColorBurnMaskedSprite: {\n      ColorBurnMaskedSpriteUniforms uniforms{\n          .Projection = Projection,\n          .SpriteTransformation = spriteTransformation,\n          .MaskTransformation = maskTransformation,\n          .ColorMap = 0,\n          .Mask = 2,\n      };\n\n      UseShader(*ColorBurnMaskedSpriteShaderProgram, uniforms);\n    } break;\n\n    case ShaderProgramType::ColorDodgeMaskedSprite: {\n      ColorDodgeMaskedSpriteUniforms uniforms{\n          .Projection = Projection,\n          .SpriteTransformation = spriteTransformation,\n          .MaskTransformation = maskTransformation,\n          .ColorMap = 0,\n          .Mask = 2,\n      };\n\n      UseShader(*ColorDodgeMaskedSpriteShaderProgram, uniforms);\n    } break;\n\n    case ShaderProgramType::ColorMaskedSprite: {\n      ColorMaskedSpriteUniforms uniforms{\n          .Projection = Projection,\n          .SpriteTransformation = spriteTransformation,\n          .MaskTransformation = maskTransformation,\n          .ColorMap = 0,\n          .Mask = 2,\n      };\n\n      UseShader(*ColorMaskedSpriteShaderProgram, uniforms);\n    } break;\n\n    case ShaderProgramType::HardLightMaskedSprite: {\n      HardLightMaskedSpriteUniforms uniforms{\n          .Projection = Projection,\n          .SpriteTransformation = spriteTransformation,\n          .MaskTransformation = maskTransformation,\n          .ColorMap = 0,\n          .Mask = 2,\n      };\n\n      UseShader(*HardLightMaskedSpriteShaderProgram, uniforms);\n    } break;\n\n    case ShaderProgramType::LinearBurnMaskedSprite: {\n      LinearBurnMaskedSpriteUniforms uniforms{\n          .Projection = Projection,\n          .SpriteTransformation = spriteTransformation,\n          .MaskTransformation = maskTransformation,\n          .ColorMap = 0,\n          .Mask = 2,\n      };\n\n      UseShader(*LinearBurnMaskedSpriteShaderProgram, uniforms);\n    } break;\n\n    case ShaderProgramType::MaskedSprite: {\n      MaskedSpriteUniforms uniforms{\n          .Projection = Projection,\n          .SpriteTransformation = spriteTransformation,\n          .MaskTransformation = maskTransformation,\n          .ColorMap = 0,\n          .Mask = 2,\n          .Alpha = {1.0f, 0.0f},\n          .IsInverted = inverted,\n          .IsSameTexture = false,\n      };\n\n      UseShader(*MaskedSpriteShaderProgram, uniforms);\n    } break;\n\n    case ShaderProgramType::MaskedSpriteBinary: {\n      MaskedSpriteBinaryUniforms uniforms{\n          .Projection = Projection,\n          .SpriteTransformation = spriteTransformation,\n          .MaskTransformation = maskTransformation,\n          .FullscreenMask = false,\n          .ColorMap = 0,\n          .Mask = 2,\n          .IsInverted = inverted,\n      };\n\n      UseShader(*MaskedSpriteBinaryShaderProgram, uniforms);\n    } break;\n\n    case ShaderProgramType::MaskedSpriteNoAlpha: {\n      MaskedSpriteNoAlphaUniforms uniforms{\n          .Projection = Projection,\n          .SpriteTransformation = spriteTransformation,\n          .MaskTransformation = maskTransformation,\n          .ColorMap = 0,\n          .Mask = 2,\n          .Alpha = {1.0f, 0.0f},\n          .IsInverted = inverted,\n      };\n\n      UseShader(*MaskedSpriteNoAlphaShaderProgram, uniforms);\n    } break;\n\n    case ShaderProgramType::OverlayMaskedSprite: {\n      OverlayMaskedSpriteUniforms uniforms{\n          .Projection = Projection,\n          .SpriteTransformation = spriteTransformation,\n          .MaskTransformation = maskTransformation,\n          .ColorMap = 0,\n          .Mask = 2,\n      };\n\n      UseShader(*OverlayMaskedSpriteShaderProgram, uniforms);\n    } break;\n\n    case ShaderProgramType::ScreenMaskedSprite: {\n      ScreenMaskedSpriteUniforms uniforms{\n          .Projection = Projection,\n          .SpriteTransformation = spriteTransformation,\n          .MaskTransformation = maskTransformation,\n          .ColorMap = 0,\n          .Mask = 2,\n      };\n\n      UseShader(*ScreenMaskedSpriteShaderProgram, uniforms);\n    } break;\n\n    case ShaderProgramType::SoftLightMaskedSprite: {\n      SoftLightMaskedSpriteUniforms uniforms{\n          .Projection = Projection,\n          .SpriteTransformation = spriteTransformation,\n          .MaskTransformation = maskTransformation,\n          .ColorMap = 0,\n          .Mask = 2,\n      };\n\n      UseShader(*SoftLightMaskedSpriteShaderProgram, uniforms);\n    } break;\n\n    case ShaderProgramType::Sprite: {\n      SpriteUniforms uniforms{\n          .Projection = Projection,\n          .Transformation = spriteTransformation,\n          .ColorMap = 0,\n          .ColorShift = glm::vec3(0.0f),\n      };\n\n      UseShader(*SpriteShaderProgram, uniforms);\n    } break;\n\n    case ShaderProgramType::SpriteInverted: {\n      SpriteInvertedUniforms uniforms{\n          .Projection = Projection,\n          .Transformation = spriteTransformation,\n          .ColorMap = 0,\n      };\n\n      UseShader(*SpriteInvertedShaderProgram, uniforms);\n    } break;\n\n    default:\n      ImpLog(LogLevel::Error, LogChannel::Render,\n             \"Attempted to render vertices using unsupported shader type {}\",\n             (int)shaderType);\n      return;\n  }\n\n  if (mask == nullptr) {\n    UseTextures(std::array<std::pair<uint32_t, size_t>, 1>{\n        std::pair{sheet.Texture, 0},\n    });\n  } else {\n    UseTextures(std::array<std::pair<uint32_t, size_t>, 2>{\n        std::pair{sheet.Texture, 0},\n        std::pair{mask->Texture, 2},\n    });\n  }\n\n  std::span<const VertexBufferSprites> transformedVertices;\n  if (sheet.IsScreenCap) {\n    static std::vector<VertexBufferSprites> transformedVerticesBuffer(\n        MaxIndexCount);\n\n    const auto transformVertex = [](VertexBufferSprites info) {\n      info.UV.y = 1.0f - info.UV.y;\n      return info;\n    };\n    std::transform(vertices.begin(), vertices.end(),\n                   transformedVerticesBuffer.begin(), transformVertex);\n    transformedVertices =\n        std::span(transformedVerticesBuffer).subspan(0, vertices.size());\n  } else {\n    transformedVertices = vertices;\n  }\n\n  InsertVertices(transformedVertices, indices);\n\n  if (textureWrapRepeat) {\n    Flush();\n    glBindSampler(0, Samplers[0]);\n    glSamplerParameteri(Samplers[0], GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);\n    glSamplerParameteri(Samplers[0], GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);\n  }\n}\n\nvoid Renderer::DrawCCMessageBox(Sprite const& sprite, Sprite const& mask,\n                                RectF const& dest, glm::vec4 tint, int alpha,\n                                int fadeRange, float effectCt) {\n  if (!Drawing) {\n    ImpLog(LogLevel::Error, LogChannel::Render,\n           \"Renderer->DrawCCMessageBox() called before BeginFrame()\\n\");\n    return;\n  }\n  EnsureTopologyMode(TopologyMode::Triangles);\n\n  if (alpha < 0) alpha = 0;\n  if (alpha > fadeRange + 256) alpha = fadeRange + 256;\n\n  float alphaRange = 256.0f / fadeRange;\n  float constAlpha = ((255.0f - alpha) * alphaRange) / 255.0f;\n\n  CCMessageBoxUniforms uniforms{\n      .Projection = Projection,\n      .ColorMap = 0,\n      .Mask = 2,\n      .Alpha = {alphaRange, constAlpha, effectCt, 0.0f},\n  };\n\n  UseShader(*CCMessageBoxShaderProgram, uniforms);\n\n  UseTextures(std::array<std::pair<uint32_t, size_t>, 2>{\n      std::pair{sprite.Sheet.Texture, 0},\n      std::pair{mask.Sheet.Texture, 2},\n  });\n\n  // OK, all good, make quad\n\n  CornersQuad uvDest = sprite.NormalizedBounds();\n  if (sprite.Sheet.IsScreenCap) uvDest = FlipUvVertical(uvDest);\n  InsertVerticesQuad(dest, uvDest, tint, mask.NormalizedBounds());\n}\n\nvoid Renderer::DrawCHLCCMenuBackground(const Sprite& sprite, const Sprite& mask,\n                                       const RectF& dest, float alpha) {\n  if (!Drawing) {\n    ImpLog(LogLevel::Error, LogChannel::Render,\n           \"Renderer->DrawCHLCCMenuBackground() called before BeginFrame()\\n\");\n    return;\n  }\n  EnsureTopologyMode(TopologyMode::Triangles);\n\n  if (alpha < 0.0f)\n    alpha = 0;\n  else if (alpha > 1.0f)\n    alpha = 1.0f;\n\n  CHLCCMenuBackgroundUniforms uniforms{\n      .Projection = Projection,\n      .ColorMap = 0,\n      .Mask = 2,\n      .Alpha = alpha,\n  };\n\n  UseShader(*CHLCCMenuBackgroundShaderProgram, uniforms);\n\n  UseTextures(std::array<std::pair<uint32_t, size_t>, 2>{\n      std::pair{sprite.Sheet.Texture, 0},\n      std::pair{mask.Sheet.Texture, 2},\n  });\n\n  // OK, all good, make quad\n\n  CornersQuad uvDest = sprite.NormalizedBounds();\n  if (sprite.Sheet.IsScreenCap) uvDest = FlipUvVertical(uvDest);\n  InsertVerticesQuad(dest, uvDest, glm::vec4(1.0f), mask.NormalizedBounds());\n}\n\nvoid Renderer::DrawBlurredSprite(const Sprite& sprite, const CornersQuad& dest,\n                                 glm::mat4 transformation,\n                                 RendererBlurDirection blurDirection,\n                                 glm::vec4 tint) {\n  if (!Drawing) {\n    ImpLog(LogLevel::Error, LogChannel::Render,\n           \"Renderer->DrawBlurredSprite() called before BeginFrame()\\n\");\n    return;\n  }\n  EnsureTopologyMode(TopologyMode::Triangles);\n\n  GaussianBlurUniforms uniforms{\n      .Projection = Projection,\n      .Transformation = transformation,\n      .ColorMap = 0,\n      .TextureDimensions = sprite.Bounds.GetSize(),\n      .IsHorizontal = blurDirection == RendererBlurDirection::Horizontal,\n  };\n\n  UseShader(*GaussianBlurShaderProgram, uniforms);\n\n  UseTextures(std::array<std::pair<uint32_t, size_t>, 1>{\n      std::pair{sprite.Sheet.Texture, 0}});\n\n  // OK, all good, make quad\n\n  CornersQuad uvDest = sprite.NormalizedBounds();\n  if (sprite.Sheet.IsScreenCap) uvDest = FlipUvVertical(uvDest);\n  InsertVerticesQuad(dest, uvDest, tint);\n}\n\nvoid Renderer::DrawMosaic(const Sprite& sprite, const CornersQuad dest,\n                          float tileSize, glm::mat4 transformation,\n                          glm::vec4 tint) {\n  if (!Drawing) {\n    ImpLog(LogLevel::Error, LogChannel::Render,\n           \"Renderer->DrawMosaic() called before BeginFrame()\\n\");\n    return;\n  }\n  EnsureTopologyMode(TopologyMode::Triangles);\n\n  MosaicUniforms uniforms{\n      .Projection = Projection,\n      .Transformation = transformation,\n      .ColorMap = 0,\n      .TextureDimensions = sprite.Bounds.GetSize(),\n      .TileSize = tileSize / sprite.BaseScale.x,\n  };\n\n  UseShader(*MosaicShaderProgram, uniforms);\n\n  UseTextures(std::array<std::pair<uint32_t, size_t>, 1>{\n      std::pair{sprite.Sheet.Texture, 0}});\n\n  // OK, all good, make quad\n\n  CornersQuad uvDest = sprite.NormalizedBounds();\n  if (sprite.Sheet.IsScreenCap) uvDest = FlipUvVertical(uvDest);\n  InsertVerticesQuad(dest, uvDest, tint);\n}\n\nvoid Renderer::Flush() {\n  if (!Drawing) {\n    ImpLog(LogLevel::Error, LogChannel::Render,\n           \"Renderer->Flush() called before BeginFrame()\\n\");\n    return;\n  }\n  if (!VertexBuffer.empty() && !IndexBuffer.empty()) {\n    glBindBuffer(GL_ARRAY_BUFFER, VBO);\n    glBufferData(GL_ARRAY_BUFFER,\n                 VertexBuffer.size() * sizeof(VertexBufferSprites),\n                 VertexBuffer.data(), GL_DYNAMIC_DRAW);\n\n    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IBO);\n    glBufferData(GL_ELEMENT_ARRAY_BUFFER, IndexBuffer.size() * sizeof(uint16_t),\n                 IndexBuffer.data(), GL_DYNAMIC_DRAW);\n\n    GLenum mode;\n    switch (LastTopologyMode) {\n      default:  // falls through\n      case TopologyMode::Triangles: {\n        mode = GL_TRIANGLES;\n        break;\n      }\n      case TopologyMode::TriangleStrips: {\n        mode = GL_TRIANGLE_STRIP;\n        break;\n      }\n    }\n    glDrawElements(mode, (GLsizei)IndexBuffer.size(), GL_UNSIGNED_SHORT, 0);\n  }\n\n  VertexBuffer.clear();\n  IndexBuffer.clear();\n  NextFreeIndex = 0;\n\n  FlushTextures();\n}\n\nvoid Renderer::DrawVideoTexture(const YUVFrame& frame, const RectF& dest,\n                                const glm::vec4 tint, const bool alphaVideo) {\n  if (!Drawing) {\n    ImpLog(LogLevel::Error, LogChannel::Render,\n           \"Renderer->DrawVideoTexture() called before BeginFrame()\\n\");\n    return;\n  }\n  EnsureTopologyMode(TopologyMode::Triangles);\n\n  UseTextures(std::array<std::pair<uint32_t, size_t>, 3>{\n      std::pair{frame.LumaId, 0},\n      std::pair{frame.CbId, 1},\n      std::pair{frame.CrId, 2},\n  });\n\n  YUVFrameUniforms uniforms{\n      .Projection = Projection,\n      .Luma = 0,\n      .Cb = 1,\n      .Cr = 2,\n      .IsAlpha = alphaVideo,\n  };\n\n  UseShader(*YUVFrameShaderProgram, uniforms);\n\n  // OK, all good, make quad\n\n  InsertVerticesQuad(dest, RectF(0.0f, 0.0f, 1.0f, 1.0f), tint);\n}\n\nvoid Renderer::DrawVideoTexture(const NV12Frame& frame, const RectF& dest,\n                                const glm::vec4 tint, const bool alphaVideo) {\n  if (!Drawing) {\n    ImpLog(LogLevel::Error, LogChannel::Render,\n           \"Renderer->DrawVideoTexture() called before BeginFrame()\\n\");\n    return;\n  }\n  EnsureTopologyMode(TopologyMode::Triangles);\n\n  UseTextures(std::array<std::pair<uint32_t, size_t>, 2>{\n      std::pair{frame.LumaId, 0},\n      std::pair{frame.CbCrId, 1},\n  });\n\n  NV12FrameUniforms uniforms{\n      .Projection = Projection,\n      .Luma = 0,\n      .CbCr = 1,\n      .IsAlpha = alphaVideo,\n  };\n\n  UseShader(*NV12FrameShaderProgram, uniforms);\n\n  // OK, all good, make quad\n\n  InsertVerticesQuad(dest, RectF(0.0f, 0.0f, 1.0f, 1.0f), tint);\n}\n\nvoid Renderer::CaptureScreencap(Sprite& sprite) {\n  Flush();\n  sprite.Sheet.IsScreenCap = true;\n\n  int fbWidth, fbHeight;\n  glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH,\n                               &fbWidth);\n  glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT,\n                               &fbHeight);\n\n  sprite.Sheet.DesignWidth = static_cast<float>(fbWidth);\n  sprite.Sheet.DesignHeight = static_cast<float>(fbHeight);\n  sprite.Bounds = RectF{0.0f, 0.0f, static_cast<float>(fbWidth),\n                        static_cast<float>(fbHeight)};\n  sprite.BaseScale = {Profile::DesignWidth / fbWidth,\n                      Profile::DesignHeight / fbHeight};\n\n  int prevReadBuffer;\n  int drawBuffer;\n  int prevTextureBinding;\n\n  glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &prevReadBuffer);\n  glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &drawBuffer);\n  glGetIntegerv(GL_TEXTURE_BINDING_2D, &prevTextureBinding);\n\n  GLC::BindFramebuffer(GL_READ_FRAMEBUFFER, drawBuffer);\n  glBindTexture(GL_TEXTURE_2D, sprite.Sheet.Texture);\n  glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 0, 0, fbWidth, fbHeight, 0);\n\n  glBindTexture(GL_TEXTURE_2D, prevTextureBinding);\n  GLC::BindFramebuffer(GL_READ_FRAMEBUFFER, prevReadBuffer);\n}\n\nvoid Renderer::DrawSubtitleGlyph(const Sprite& sprite, const CornersQuad& dest,\n                                 const glm::mat4 transformation,\n                                 const glm::vec4 tint) {\n  if (!Drawing) {\n    ImpLog(LogLevel::Error, LogChannel::Render,\n           \"Renderer->DrawSubtitleGlyph() called before BeginFrame()\\n\");\n    return;\n  }\n\n  EnsureTopologyMode(TopologyMode::Triangles);\n\n  if (sprite.Sheet.IsScreenCap) Flush();\n\n  // Set uniform variables\n\n  SubtitleGlyphUniforms uniforms{\n      .Projection = Projection,\n      .Transformation = transformation,\n      .CoverageMap = 0,\n  };\n\n  UseShader(*SubtitleGlyphShaderProgram, uniforms);\n\n  UseTextures(std::array<std::pair<uint32_t, size_t>, 1>{\n      std::pair{sprite.Sheet.Texture, 0}});\n\n  // OK, all good, make quad\n\n  CornersQuad uvDest = sprite.NormalizedBounds();\n  if (sprite.Sheet.IsScreenCap) uvDest = FlipUvVertical(uvDest);\n  InsertVerticesQuad(dest, uvDest, tint);\n}\n\nvoid Renderer::EnableScissor() {\n  if (ScissorEnabled) return;\n\n  Flush();\n  glEnable(GL_SCISSOR_TEST);\n  ScissorEnabled = true;\n}\n\nvoid Renderer::SetScissorRect(RectF const& rect) {\n  if (!ScissorEnabled) return;\n\n  Rect viewport = Window->GetViewport();\n  float scale = fmin((float)Window->WindowWidth / Profile::DesignWidth,\n                     (float)Window->WindowHeight / Profile::DesignHeight);\n  float rectX = rect.X * scale;\n  float rectY = rect.Y * scale;\n  float rectWidth = rect.Width * scale;\n  float rectHeight = rect.Height * scale;\n\n  glScissor((GLint)(rectX),\n            (GLint)((viewport.Height - (GLint)(rectY + rectHeight))),\n            (GLint)(rectWidth), (GLint)(rectHeight));\n}\n\nvoid Renderer::DisableScissor() {\n  if (!ScissorEnabled) return;\n\n  Flush();\n  glDisable(GL_SCISSOR_TEST);\n  ScissorEnabled = false;\n}\n\nvoid Renderer::SetStencilMode(StencilBufferMode mode) {\n  Flush();\n\n  switch (mode) {\n    case StencilBufferMode::Off: {\n      glDisable(GL_STENCIL_TEST);\n      break;\n    }\n\n    case StencilBufferMode::Test: {\n      glEnable(GL_STENCIL_TEST);\n      glStencilMask(0x00);\n\n      glStencilFunc(GL_NOTEQUAL, 0x00, 0xFF);\n      glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);\n      break;\n    }\n\n    case StencilBufferMode::Write: {\n      glEnable(GL_STENCIL_TEST);\n      glStencilMask(0xFF);\n\n      glStencilFunc(GL_NEVER, 0x01, 0xFF);\n      glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);\n      break;\n    }\n\n    default: {\n      ImpLogSlow(LogLevel::Warning, LogChannel::Render,\n                 \"Unexpected stencil mode {:d}\\n\", (size_t)mode);\n      break;\n    }\n  }\n}\n\nvoid Renderer::ClearStencilBuffer() {\n  Flush();\n\n  GLint oldMask;\n  glGetIntegerv(GL_STENCIL_WRITEMASK, &oldMask);\n\n  glStencilMask(0xFF);\n  glClear(GL_STENCIL_BUFFER_BIT);\n  glStencilMask(oldMask);\n}\n\nvoid Renderer::SetBlendMode(RendererBlendMode blendMode) {\n  Flush();\n\n  switch (blendMode) {\n    case RendererBlendMode::Normal:\n      glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);\n      return;\n    case RendererBlendMode::Additive:\n      glBlendFunc(GL_SRC_ALPHA, GL_ONE);\n      return;\n    case RendererBlendMode::Premultiplied:\n      glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);\n      return;\n  }\n}\n\nvoid Renderer::Clear(glm::vec4 color) {\n  Flush();\n\n  glClearColor(color.r, color.g, color.b, color.a);\n  glClear(GL_COLOR_BUFFER_BIT);\n}\n\nvoid Renderer::EnsureTopologyMode(TopologyMode newMode) {\n  if (LastTopologyMode != newMode) {\n    Flush();\n    LastTopologyMode = newMode;\n  }\n}\n\n}  // namespace OpenGL\n}  // namespace Impacto\n"
  },
  {
    "path": "src/renderer/opengl/renderer.h",
    "content": "#pragma once\n\n#include \"../renderer.h\"\n#include \"window.h\"\n\n#include <map>\n\n#include \"shader.h\"\n#include \"glc.h\"\n\n#include \"../../profile/game.h\"\n\nnamespace Impacto {\nnamespace OpenGL {\n\nint constexpr NkMaxVertexMemory = 256 * 1024;\nint constexpr NkMaxElementMemory = 128 * 1024;\n\nclass Renderer : public BaseRenderer {\n public:\n  void Init() override;\n  void Shutdown() override;\n\n#ifndef IMPACTO_DISABLE_IMGUI\n  void ImGuiBeginFrame() override;\n#endif\n\n  void BeginFrame() override;\n  void BeginFrame2D() override;\n  void EndFrame() override;\n\n  uint32_t SubmitTexture(TexFmt format, uint8_t* buffer, int width,\n                         int height) override;\n  int GetSpriteSheetImage(SpriteSheet const& sheet,\n                          std::span<uint8_t> outBuffer) override;\n  void FreeTexture(uint32_t id) override;\n  YUVFrame* CreateYUVFrame(float width, float height) override;\n  NV12Frame* CreateNV12Frame(float width, float height) override;\n\n  void DrawSprite(const Sprite& sprite, const CornersQuad& dest,\n                  glm::mat4 transformation, std::span<const glm::vec4, 4> tints,\n                  glm::vec3 colorShift, bool inverted, bool disableBlend,\n                  bool textureWrapRepeat) override;\n\n  void DrawMaskedSprite(const Sprite& sprite, const Sprite& mask,\n                        const CornersQuad& spriteDest,\n                        const CornersQuad& maskDest, int alpha, int fadeRange,\n                        glm::mat4 spriteTransformation,\n                        glm::mat4 maskTransformation,\n                        std::span<const glm::vec4, 4> tints, bool isInverted,\n                        bool isSameTexture) override;\n\n  void DrawMaskedBinarySprite(const Sprite& sprite, const Sprite& mask,\n                              const CornersQuad& spriteDest,\n                              const CornersQuad& maskDest,\n                              glm::mat4 spriteTransformation,\n                              std::optional<glm::mat4> maskTransformation,\n                              std::span<const glm::vec4, 4> tints,\n                              bool isInverted = false) override;\n\n  void DrawMaskedSpriteOverlay(const Sprite& sprite, const Sprite& mask,\n                               const CornersQuad& spriteDest,\n                               const CornersQuad& maskDest, int alpha,\n                               int fadeRange, glm::mat4 spriteTransformation,\n                               glm::mat4 maskTransformation,\n                               std::span<const glm::vec4, 4> tints,\n                               bool isInverted, bool useMaskAlpha) override;\n\n  void DrawPrimitives(const SpriteSheet& sheet, const SpriteSheet* mask,\n                      ShaderProgramType shaderType,\n                      std::span<const VertexBufferSprites> vertices,\n                      std::span<const uint16_t> indices,\n                      glm::mat4 spriteTransformation,\n                      glm::mat4 maskTransformation, bool inverted,\n                      TopologyMode topologyMode,\n                      const bool textureWrapRepeat = false) override;\n\n  void DrawCCMessageBox(Sprite const& sprite, Sprite const& mask,\n                        RectF const& dest, glm::vec4 tint, int alpha,\n                        int fadeRange, float effectCt) override;\n\n  void DrawCHLCCMenuBackground(const Sprite& sprite, const Sprite& mask,\n                               const RectF& dest, float alpha) override;\n\n  void DrawBlurredSprite(const Sprite& sprite, const CornersQuad& dest,\n                         glm::mat4 transformation,\n                         RendererBlurDirection blurDirection,\n                         glm::vec4 tint) override;\n\n  void DrawMosaic(const Sprite& sprite, const CornersQuad dest, float tileSize,\n                  glm::mat4 transformation, glm::vec4 tint) override;\n\n  void DrawVideoTexture(const YUVFrame& frame, const RectF& dest,\n                        glm::vec4 tint, bool alphaVideo) override;\n  void DrawVideoTexture(const NV12Frame& frame, const RectF& dest,\n                        glm::vec4 tint, bool alphaVideo) override;\n  void DrawSubtitleGlyph(const Sprite& sprite, const CornersQuad& dest,\n                         const glm::mat4 transformation,\n                         const glm::vec4 tint) override;\n\n  void CaptureScreencap(Sprite& sprite) override;\n\n  void SetFramebuffer(size_t buffer) override {\n    Flush();\n    GLC::BindFramebuffer(GL_FRAMEBUFFER, buffer == 0\n                                             ? OpenGLWindow->DrawRT\n                                             : GLC::Framebuffers[buffer - 1]);\n  };\n\n  int GetFramebufferTexture(size_t buffer) override {\n    return buffer == 0 ? OpenGLWindow->ReadRenderTexture\n                       : GLC::FramebufferTextures[buffer - 1];\n  }\n\n  void EnableScissor() override;\n  void SetScissorRect(RectF const& rect) override;\n  void DisableScissor() override;\n\n  void SetStencilMode(StencilBufferMode mode) override;\n  void ClearStencilBuffer() override;\n\n  void SetBlendMode(RendererBlendMode blendMode) override;\n\n  void Clear(glm::vec4 color) override;\n\n private:\n  std::optional<SpriteShader> SpriteShaderProgram;\n  std::optional<SpriteInvertedShader> SpriteInvertedShaderProgram;\n  std::optional<MaskedSpriteShader> MaskedSpriteShaderProgram;\n  std::optional<MaskedSpriteBinaryShader> MaskedSpriteBinaryShaderProgram;\n  std::optional<MaskedSpriteNoAlphaShader> MaskedSpriteNoAlphaShaderProgram;\n  std::optional<ColorMaskedSpriteShader> ColorMaskedSpriteShaderProgram;\n  std::optional<AdditiveMaskedSpriteShader> AdditiveMaskedSpriteShaderProgram;\n  std::optional<ColorBurnMaskedSpriteShader> ColorBurnMaskedSpriteShaderProgram;\n  std::optional<ColorDodgeMaskedSpriteShader>\n      ColorDodgeMaskedSpriteShaderProgram;\n  std::optional<HardLightMaskedSpriteShader> HardLightMaskedSpriteShaderProgram;\n  std::optional<LinearBurnMaskedSpriteShader>\n      LinearBurnMaskedSpriteShaderProgram;\n  std::optional<OverlayMaskedSpriteShader> OverlayMaskedSpriteShaderProgram;\n  std::optional<ScreenMaskedSpriteShader> ScreenMaskedSpriteShaderProgram;\n  std::optional<SoftLightMaskedSpriteShader> SoftLightMaskedSpriteShaderProgram;\n  std::optional<YUVFrameShader> YUVFrameShaderProgram;\n  std::optional<CCMessageBoxShader> CCMessageBoxShaderProgram;\n  std::optional<CHLCCMenuBackgroundShader> CHLCCMenuBackgroundShaderProgram;\n  std::optional<GaussianBlurShader> GaussianBlurShaderProgram;\n  std::optional<MosaicShader> MosaicShaderProgram;\n  std::optional<SubtitleGlyphShader> SubtitleGlyphShaderProgram;\n  std::optional<NV12FrameShader> NV12FrameShaderProgram;\n\n  const void* CurrentShaderProgram = nullptr;\n\n  template <typename ShaderType, typename UniformsStruct>\n  void UseShader(ShaderType& shader, UniformsStruct uniforms) {\n    static_assert(std::is_base_of<Shader<UniformsStruct>, ShaderType>());\n\n    if (CurrentShaderProgram != &shader) {\n      Flush();\n      CurrentShaderProgram = &shader;\n      shader.Bind();\n    }\n\n    if (shader.GetUniforms() != uniforms) {\n      Flush();\n      shader.UploadUniforms(uniforms);\n    }\n  }\n\n  void Flush() override;\n\n  void InsertVertices(std::span<const VertexBufferSprites> vertices,\n                      std::span<const uint16_t> indices);\n  void InsertVerticesQuad(CornersQuad pos, CornersQuad uv,\n                          std::span<const glm::vec4, 4> tints,\n                          CornersQuad maskUV = RectF());\n  void InsertVerticesQuad(CornersQuad pos, CornersQuad uv,\n                          glm::vec4 tint = glm::vec4(1.0f),\n                          CornersQuad maskUV = RectF()) {\n    InsertVerticesQuad(pos, uv, std::array{tint, tint, tint, tint}, maskUV);\n  }\n  void EnsureTopologyMode(TopologyMode newMode);\n\n  GLWindow* OpenGLWindow;\n\n  GLuint VBO;\n  GLuint IBO;\n  GLuint VAOSprites;\n\n  bool Drawing = false;\n\n  TopologyMode LastTopologyMode = TopologyMode::Triangles;\n\n  struct TextureUnit {\n    uint32_t TextureId = 0;\n    bool InUse = false;\n  };\n  static constexpr size_t TextureUnitCount = 15;\n  std::array<TextureUnit, TextureUnitCount> TextureUnits;\n\n  void UseTextures(\n      std::span<const std::pair<uint32_t, size_t>> textureUnitPairs);\n  void FlushTextures() {\n    std::transform(TextureUnits.begin(), TextureUnits.end(),\n                   TextureUnits.begin(), [](TextureUnit unit) {\n                     unit.InUse = false;\n                     return unit;\n                   });\n  }\n  std::vector<VertexBufferSprites> VertexBuffer;\n  std::vector<uint16_t> IndexBuffer;\n\n  std::array<GLuint, TextureUnitCount> Samplers;\n  const glm::mat4 Projection =\n      glm::ortho(0.0f, Profile::DesignWidth, Profile::DesignHeight, 0.0f,\n                 -Profile::DesignWidth, Profile::DesignWidth);\n\n  static constexpr size_t MaxVertexCount =\n      1024 * 1024 / sizeof(VertexBufferSprites);\n  static constexpr uint16_t MaxIndexCount =\n      std::numeric_limits<uint16_t>::max();\n\n  uint16_t NextFreeIndex = 0;\n\n  // ShaderCompiler compiler\n  ShaderCompiler Shaders;\n\n  bool ScissorEnabled = false;\n};\n\n}  // namespace OpenGL\n}  // namespace Impacto\n"
  },
  {
    "path": "src/renderer/opengl/shader.cpp",
    "content": "#include <algorithm>\n\n#include \"shader.h\"\n#include \"renderer.h\"\n\n#include \"../../impacto.h\"\n#include \"../../io/io.h\"\n#include \"../../log.h\"\n#include \"../3d/model.h\"\n#include \"../../util.h\"\n\nnamespace Impacto {\nnamespace OpenGL {\n\nstatic char const ShaderPath[] = \"./shaders/opengl\";\nstatic char const FragShaderExtension[] = \"_frag.glsl\";\nstatic char const VertShaderExtension[] = \"_vert.glsl\";\n\n// We need highp in vertex shaders, but uniforms shared between vertex and\n// fragment shaders need to have the same precision in both\n\nstatic char const ShaderHeader[] =\n    \"#version 330\\n#define UNIFORM_PRECISION highp\\n\\n\";\nstatic GLint const ShaderHeaderLength =\n    sizeof(ShaderHeader) - 1;  // without null terminator, for glShaderSource()\nstatic char const ShaderHeaderES[] =\n    \"#version 300 es\\n#define UNIFORM_PRECISION mediump\\n\\n\";\nstatic GLint const ShaderHeaderESLength = sizeof(ShaderHeaderES) - 1;\nstatic char const ShaderHeaderVert[] =\n    \"#define VERTEX_SHADER\\n#ifdef GL_ES\\nprecision highp float;\\n#endif\\n\\n\";\nstatic GLint const ShaderHeaderVertLength = sizeof(ShaderHeaderVert) - 1;\nstatic char const ShaderHeaderFrag[] =\n    \"#define FRAGMENT_SHADER\\n#ifdef GL_ES\\nprecision mediump \"\n    \"float;\\n#endif\\n\\n\";\nstatic GLint const ShaderHeaderFragLength = sizeof(ShaderHeaderFrag) - 1;\n\nint ShaderCompiler::PrintParameter(char* dest, int destSz, char const* name,\n                                   ShaderParameter const& param) {\n  switch (param.Type) {\n    case SPT_Float:\n      return snprintf(dest, destSz, \"const float %s = %f;\\n\", name,\n                      param.Val_Float);\n      break;\n    case SPT_Int:\n      return snprintf(dest, destSz, \"const int %s = %d;\\n\", name,\n                      param.Val_Int);\n      break;\n\n    case SPT_MacroFloat:\n      return snprintf(dest, destSz, \"#define %s %f\\n\", name, param.Val_Float);\n      break;\n    case SPT_MacroInt:\n      return snprintf(dest, destSz, \"#define %s %d\\n\", name, param.Val_Int);\n      break;\n\n    case SPT_Vec2:\n      return snprintf(dest, destSz, \"const vec2 %s = vec2(%f, %f);\\n\", name,\n                      param.Val_Vec2.x, param.Val_Vec2.y);\n      break;\n    case SPT_Vec3:\n      return snprintf(dest, destSz, \"const vec3 %s = vec3(%f, %f, %f);\\n\", name,\n                      param.Val_Vec3.x, param.Val_Vec3.y, param.Val_Vec3.z);\n      break;\n    case SPT_Vec4:\n      return snprintf(dest, destSz, \"const vec4 %s = vec4(%f, %f, %f, %f);\\n\",\n                      name, param.Val_Vec4.x, param.Val_Vec4.y,\n                      param.Val_Vec4.z, param.Val_Vec4.w);\n      break;\n\n    case SPT_Ivec2:\n      return snprintf(dest, destSz, \"const ivec2 %s = ivec2(%d, %d);\\n\", name,\n                      param.Val_Ivec2.x, param.Val_Ivec2.y);\n      break;\n    case SPT_Ivec3:\n      return snprintf(dest, destSz, \"const ivec3 %s = ivec3(%d, %d, %d);\\n\",\n                      name, param.Val_Ivec3.x, param.Val_Ivec3.y,\n                      param.Val_Ivec3.z);\n      break;\n    case SPT_Ivec4:\n      return snprintf(dest, destSz, \"const ivec4 %s = ivec4(%d, %d, %d, %d);\\n\",\n                      name, param.Val_Ivec4.x, param.Val_Ivec4.y,\n                      param.Val_Ivec4.z, param.Val_Ivec4.w);\n      break;\n\n    default:\n      ImpLog(LogLevel::Error, LogChannel::Render,\n             \"Invalid shader parameter type {}\\n\", param.Type);\n      if (destSz > 0) *dest = '\\0';\n      return 0;\n  }\n}\n\nGLuint ShaderCompiler::Attach(GLuint program, GLenum shaderType,\n                              char const* path, char const* params) {\n  ImpLog(LogLevel::Debug, LogChannel::Render,\n         \"Loading shader object (type {:d}) \\\"{:s}\\\"\\n\", shaderType, path);\n\n  size_t sourceRawSz;\n  char* source = (char*)SDL_LoadFile(path, &sourceRawSz);\n  if (!source) {\n    ImpLog(LogLevel::Debug, LogChannel::Render,\n           \"Failed to read shader source file\\n\");\n    return 0;\n  }\n\n  GLuint shader = glCreateShader(shaderType);\n  if (!shader) {\n    ImpLog(LogLevel::Fatal, LogChannel::Render,\n           \"Failed to create shader object\\n\");\n    SDL_free(source);\n    return 0;\n  }\n\n  const GLchar* codeParts[4];\n  codeParts[0] =\n      (ActualGraphicsApi != GfxApi_GL) ? ShaderHeaderES : ShaderHeader;\n  codeParts[1] =\n      shaderType == GL_VERTEX_SHADER ? ShaderHeaderVert : ShaderHeaderFrag;\n  codeParts[2] = params;\n  codeParts[3] = source;\n\n  GLint codeLengths[4];\n  codeLengths[0] = (ActualGraphicsApi != GfxApi_GL) ? ShaderHeaderESLength\n                                                    : ShaderHeaderLength;\n  codeLengths[1] = shaderType == GL_VERTEX_SHADER ? ShaderHeaderVertLength\n                                                  : ShaderHeaderFragLength;\n  codeLengths[2] = (GLint)strlen(params);\n  codeLengths[3] = (GLint)strlen(source);\n\n  glShaderSource(shader, 4, codeParts, codeLengths);\n\n  GLint result = 0;\n  static GLchar errorLog[1024] = {0};\n\n  glCompileShader(shader);\n  glGetShaderiv(shader, GL_COMPILE_STATUS, &result);\n  if (!result) {\n    glGetShaderInfoLog(shader, sizeof(errorLog), NULL, errorLog);\n    ImpLog(LogLevel::Fatal, LogChannel::Render,\n           \"Error compiling shader: {:s}\\n\", errorLog);\n    SDL_free(source);\n    glDeleteShader(shader);\n    assert(0);\n    return 0;\n  }\n\n  glAttachShader(program, shader);\n\n  SDL_free(source);\n\n  return shader;\n}\n\nGLuint ShaderCompiler::Compile(char const* name, ShaderParamMap const& params) {\n  GLuint program = glCreateProgram();\n  if (!program) {\n    ImpLog(LogLevel::Fatal, LogChannel::Render,\n           \"Could not create shader program\\n\");\n    return program;\n  }\n\n  ImpLog(LogLevel::Debug, LogChannel::Render, \"Compiling shader \\\"{:s}\\\"\\n\",\n         name);\n\n  int paramSz = 1;\n  for (auto const& param : params) {\n    paramSz += PrintParameter(0, 0, param.first.c_str(), param.second);\n  }\n\n  char* paramStr = (char*)ImpStackAlloc(paramSz);\n  char* paramWrite = paramStr;\n  int bytesLeft = paramSz;\n  for (auto const& param : params) {\n    int thisParamSz = PrintParameter(paramWrite, bytesLeft, param.first.c_str(),\n                                     param.second);\n    bytesLeft -= thisParamSz;\n    paramWrite += thisParamSz;\n  }\n  paramStr[paramSz - 1] = '\\0';\n\n  std::string vertexShaderPath = fmt::format(FMT_COMPILE(\"{}/{}{}\"), ShaderPath,\n                                             name, VertShaderExtension);\n  GLuint vs =\n      Attach(program, GL_VERTEX_SHADER, vertexShaderPath.c_str(), paramStr);\n  if (!vs) {\n    glDeleteProgram(program);\n    ImpStackFree(paramStr);\n    return 0;\n  }\n  std::string fragShaderPath = fmt::format(FMT_COMPILE(\"{}/{}{}\"), ShaderPath,\n                                           name, FragShaderExtension);\n  GLuint fs =\n      Attach(program, GL_FRAGMENT_SHADER, fragShaderPath.c_str(), paramStr);\n  if (!fs) {\n    static GLchar errorLog[1024] = {};\n    glGetProgramInfoLog(program, sizeof(errorLog), NULL, errorLog);\n    ImpLog(LogLevel::Fatal, LogChannel::Render,\n           \"Error linking shader program: {:s}\\n\", errorLog);\n    glDeleteShader(vs);\n    glDeleteProgram(program);\n    ImpStackFree(paramStr);\n    return 0;\n  }\n\n  ImpStackFree(paramStr);\n\n  GLint result = 0;\n  static GLchar errorLog[1024] = {};\n\n  glLinkProgram(program);\n\n  glDetachShader(program, vs);\n  glDetachShader(program, fs);\n  glDeleteShader(vs);\n  glDeleteShader(fs);\n\n  glGetProgramiv(program, GL_LINK_STATUS, &result);\n  if (!result) {\n    glGetProgramInfoLog(program, sizeof(errorLog), NULL, errorLog);\n    ImpLog(LogLevel::Fatal, LogChannel::Render,\n           \"Error linking shader program: {:s}\\n\", errorLog);\n    glDeleteProgram(program);\n    return 0;\n  }\n\n  // TODO: Figure out why this actually doesn't work on macOS\n#ifndef __APPLE__\n  glValidateProgram(program);\n  glGetProgramiv(program, GL_VALIDATE_STATUS, &result);\n  if (!result) {\n    glGetProgramInfoLog(program, sizeof(errorLog), NULL, errorLog);\n    ImpLog(LogLevel::Fatal, LogChannel::Render,\n           \"ShaderCompiler program failed to validate: {:s}\\n\", errorLog);\n    glDeleteProgram(program);\n    return 0;\n  }\n#endif\n\n  return program;\n}\n\nSpriteShader::SpriteShader(GLint programId)\n    : Shader(programId),\n      ProjectionLocation(glGetUniformLocation(programId, \"Projection\")),\n      TransformationLocation(glGetUniformLocation(programId, \"Transformation\")),\n      ColorMapLocation(glGetUniformLocation(programId, \"ColorMap\")),\n      ColorShiftLocation(glGetUniformLocation(programId, \"ColorShift\")) {\n  UploadVar(Uniforms.Projection, ProjectionLocation);\n  UploadVar(Uniforms.Transformation, TransformationLocation);\n  UploadVar(Uniforms.ColorMap, ColorMapLocation);\n  UploadVar(Uniforms.ColorShift, ColorShiftLocation);\n}\n\nvoid SpriteShader::UploadUniforms(SpriteUniforms newUniforms) {\n  UpdateVar(newUniforms.Projection, Uniforms.Projection, ProjectionLocation);\n  UpdateVar(newUniforms.Transformation, Uniforms.Transformation,\n            TransformationLocation);\n  UpdateVar(newUniforms.ColorMap, Uniforms.ColorMap, ColorMapLocation);\n  UpdateVar(newUniforms.ColorShift, Uniforms.ColorShift, ColorShiftLocation);\n}\n\nSpriteInvertedShader::SpriteInvertedShader(GLint programId)\n    : Shader(programId),\n      ProjectionLocation(glGetUniformLocation(programId, \"Projection\")),\n      TransformationLocation(glGetUniformLocation(programId, \"Transformation\")),\n      ColorMapLocation(glGetUniformLocation(programId, \"ColorMap\")) {\n  UploadVar(Uniforms.Projection, ProjectionLocation);\n  UploadVar(Uniforms.Transformation, TransformationLocation);\n  UploadVar(Uniforms.ColorMap, ColorMapLocation);\n}\n\nvoid SpriteInvertedShader::UploadUniforms(SpriteInvertedUniforms newUniforms) {\n  UpdateVar(newUniforms.Projection, Uniforms.Projection, ProjectionLocation);\n  UpdateVar(newUniforms.Transformation, Uniforms.Transformation,\n            TransformationLocation);\n  UpdateVar(newUniforms.ColorMap, Uniforms.ColorMap, ColorMapLocation);\n}\n\nYUVFrameShader::YUVFrameShader(GLint programId)\n    : Shader(programId),\n      ProjectionLocation(glGetUniformLocation(programId, \"Projection\")),\n      LumaLocation(glGetUniformLocation(programId, \"Luma\")),\n      CbLocation(glGetUniformLocation(programId, \"Cb\")),\n      CrLocation(glGetUniformLocation(programId, \"Cr\")),\n      IsAlphaLocation(glGetUniformLocation(programId, \"IsAlpha\")) {\n  UploadVar(Uniforms.Projection, ProjectionLocation);\n  UploadVar(Uniforms.Luma, LumaLocation);\n  UploadVar(Uniforms.Cb, CbLocation);\n  UploadVar(Uniforms.Cr, CrLocation);\n  UploadVar(Uniforms.IsAlpha, IsAlphaLocation);\n}\n\nvoid YUVFrameShader::UploadUniforms(YUVFrameUniforms newUniforms) {\n  UpdateVar(newUniforms.Projection, Uniforms.Projection, ProjectionLocation);\n\n  UpdateVar(newUniforms.Luma, Uniforms.Luma, LumaLocation);\n  UpdateVar(newUniforms.Cb, Uniforms.Cb, CbLocation);\n  UpdateVar(newUniforms.Cr, Uniforms.Cr, CrLocation);\n  UpdateVar(newUniforms.IsAlpha, Uniforms.IsAlpha, IsAlphaLocation);\n}\n\nMaskedSpriteNoAlphaShader::MaskedSpriteNoAlphaShader(GLint programId)\n    : Shader(programId),\n      ProjectionLocation(glGetUniformLocation(programId, \"Projection\")),\n      SpriteTransformationLocation(\n          glGetUniformLocation(programId, \"SpriteTransformation\")),\n      MaskTransformationLocation(\n          glGetUniformLocation(programId, \"MaskTransformation\")),\n      ColorMapLocation(glGetUniformLocation(programId, \"ColorMap\")),\n      MaskLocation(glGetUniformLocation(programId, \"Mask\")),\n      AlphaLocation(glGetUniformLocation(programId, \"Alpha\")),\n      IsInvertedLocation(glGetUniformLocation(programId, \"IsInverted\")) {\n  UploadVar(Uniforms.Projection, ProjectionLocation);\n  UploadVar(Uniforms.SpriteTransformation, SpriteTransformationLocation);\n  UploadVar(Uniforms.MaskTransformation, MaskTransformationLocation);\n  UploadVar(Uniforms.ColorMap, ColorMapLocation);\n  UploadVar(Uniforms.Mask, MaskLocation);\n  UploadVar(Uniforms.Alpha, AlphaLocation);\n  UploadVar(Uniforms.IsInverted, IsInvertedLocation);\n}\n\nvoid MaskedSpriteNoAlphaShader::UploadUniforms(\n    MaskedSpriteNoAlphaUniforms newUniforms) {\n  UpdateVar(newUniforms.Projection, Uniforms.Projection, ProjectionLocation);\n  UpdateVar(newUniforms.SpriteTransformation, Uniforms.SpriteTransformation,\n            SpriteTransformationLocation);\n  UpdateVar(newUniforms.MaskTransformation, Uniforms.MaskTransformation,\n            MaskTransformationLocation);\n\n  UpdateVar(newUniforms.ColorMap, Uniforms.ColorMap, ColorMapLocation);\n  UpdateVar(newUniforms.Mask, Uniforms.Mask, MaskLocation);\n  UpdateVar(newUniforms.Alpha, Uniforms.Alpha, AlphaLocation);\n  UpdateVar(newUniforms.IsInverted, Uniforms.IsInverted, IsInvertedLocation);\n}\n\nMaskedSpriteShader::MaskedSpriteShader(GLint programId)\n    : Shader(programId),\n      ProjectionLocation(glGetUniformLocation(programId, \"Projection\")),\n      SpriteTransformationLocation(\n          glGetUniformLocation(programId, \"SpriteTransformation\")),\n      MaskTransformationLocation(\n          glGetUniformLocation(programId, \"MaskTransformation\")),\n      ColorMapLocation(glGetUniformLocation(programId, \"ColorMap\")),\n      MaskLocation(glGetUniformLocation(programId, \"Mask\")),\n      AlphaLocation(glGetUniformLocation(programId, \"Alpha\")),\n      IsInvertedLocation(glGetUniformLocation(programId, \"IsInverted\")),\n      IsSameTextureLocation(glGetUniformLocation(programId, \"IsSameTexture\")) {\n  UploadVar(Uniforms.Projection, ProjectionLocation);\n  UploadVar(Uniforms.SpriteTransformation, SpriteTransformationLocation);\n  UploadVar(Uniforms.MaskTransformation, MaskTransformationLocation);\n  UploadVar(Uniforms.ColorMap, ColorMapLocation);\n  UploadVar(Uniforms.Mask, MaskLocation);\n  UploadVar(Uniforms.Alpha, AlphaLocation);\n  UploadVar(Uniforms.IsInverted, IsInvertedLocation);\n  UploadVar(Uniforms.IsSameTexture, IsSameTextureLocation);\n}\n\nvoid MaskedSpriteBinaryShader::UploadUniforms(\n    MaskedSpriteBinaryUniforms newUniforms) {\n  UpdateVar(newUniforms.Projection, Uniforms.Projection, ProjectionLocation);\n  UpdateVar(newUniforms.SpriteTransformation, Uniforms.SpriteTransformation,\n            SpriteTransformationLocation);\n  UpdateVar(newUniforms.MaskTransformation, Uniforms.MaskTransformation,\n            MaskTransformationLocation);\n  UpdateVar(newUniforms.FullscreenMask, Uniforms.FullscreenMask,\n            FullscreenMaskLocation);\n\n  UpdateVar(newUniforms.ColorMap, Uniforms.ColorMap, ColorMapLocation);\n  UpdateVar(newUniforms.Mask, Uniforms.Mask, MaskLocation);\n  UpdateVar(newUniforms.IsInverted, Uniforms.IsInverted, IsInvertedLocation);\n}\n\nMaskedSpriteBinaryShader::MaskedSpriteBinaryShader(GLint programId)\n    : Shader(programId),\n      ProjectionLocation(glGetUniformLocation(programId, \"Projection\")),\n      SpriteTransformationLocation(\n          glGetUniformLocation(programId, \"SpriteTransformation\")),\n      MaskTransformationLocation(\n          glGetUniformLocation(programId, \"MaskTransformation\")),\n      FullscreenMaskLocation(glGetUniformLocation(programId, \"FullscreenMask\")),\n      ColorMapLocation(glGetUniformLocation(programId, \"ColorMap\")),\n      MaskLocation(glGetUniformLocation(programId, \"Mask\")),\n      IsInvertedLocation(glGetUniformLocation(programId, \"IsInverted\")) {\n  UploadVar(Uniforms.Projection, ProjectionLocation);\n  UploadVar(Uniforms.SpriteTransformation, SpriteTransformationLocation);\n  UploadVar(Uniforms.MaskTransformation, MaskTransformationLocation);\n  UploadVar(Uniforms.FullscreenMask, FullscreenMaskLocation);\n  UploadVar(Uniforms.ColorMap, ColorMapLocation);\n  UploadVar(Uniforms.Mask, MaskLocation);\n  UploadVar(Uniforms.IsInverted, IsInvertedLocation);\n}\n\nvoid MaskedSpriteShader::UploadUniforms(MaskedSpriteUniforms newUniforms) {\n  UpdateVar(newUniforms.Projection, Uniforms.Projection, ProjectionLocation);\n  UpdateVar(newUniforms.SpriteTransformation, Uniforms.SpriteTransformation,\n            SpriteTransformationLocation);\n  UpdateVar(newUniforms.MaskTransformation, Uniforms.MaskTransformation,\n            MaskTransformationLocation);\n\n  UpdateVar(newUniforms.ColorMap, Uniforms.ColorMap, ColorMapLocation);\n  UpdateVar(newUniforms.Mask, Uniforms.Mask, MaskLocation);\n  UpdateVar(newUniforms.Alpha, Uniforms.Alpha, AlphaLocation);\n  UpdateVar(newUniforms.IsInverted, Uniforms.IsInverted, IsInvertedLocation);\n  UpdateVar(newUniforms.IsSameTexture, Uniforms.IsSameTexture,\n            IsSameTextureLocation);\n}\n\nColorBurnMaskedSpriteShader::ColorBurnMaskedSpriteShader(GLint programId)\n    : Shader(programId),\n      ProjectionLocation(glGetUniformLocation(programId, \"Projection\")),\n      SpriteTransformationLocation(\n          glGetUniformLocation(programId, \"SpriteTransformation\")),\n      MaskTransformationLocation(\n          glGetUniformLocation(programId, \"MaskTransformation\")),\n      ColorMapLocation(glGetUniformLocation(programId, \"ColorMap\")),\n      MaskLocation(glGetUniformLocation(programId, \"Mask\")) {\n  UploadVar(Uniforms.Projection, ProjectionLocation);\n  UploadVar(Uniforms.SpriteTransformation, SpriteTransformationLocation);\n  UploadVar(Uniforms.MaskTransformation, MaskTransformationLocation);\n  UploadVar(Uniforms.ColorMap, ColorMapLocation);\n  UploadVar(Uniforms.Mask, MaskLocation);\n}\n\nvoid ColorBurnMaskedSpriteShader::UploadUniforms(\n    ColorBurnMaskedSpriteUniforms newUniforms) {\n  UpdateVar(newUniforms.Projection, Uniforms.Projection, ProjectionLocation);\n  UpdateVar(newUniforms.SpriteTransformation, Uniforms.SpriteTransformation,\n            SpriteTransformationLocation);\n  UpdateVar(newUniforms.MaskTransformation, Uniforms.MaskTransformation,\n            MaskTransformationLocation);\n\n  UpdateVar(newUniforms.ColorMap, Uniforms.ColorMap, ColorMapLocation);\n  UpdateVar(newUniforms.Mask, Uniforms.Mask, MaskLocation);\n}\n\nColorDodgeMaskedSpriteShader::ColorDodgeMaskedSpriteShader(GLint programId)\n    : Shader(programId),\n      ProjectionLocation(glGetUniformLocation(programId, \"Projection\")),\n      SpriteTransformationLocation(\n          glGetUniformLocation(programId, \"SpriteTransformation\")),\n      MaskTransformationLocation(\n          glGetUniformLocation(programId, \"MaskTransformation\")),\n      ColorMapLocation(glGetUniformLocation(programId, \"ColorMap\")),\n      MaskLocation(glGetUniformLocation(programId, \"Mask\")) {\n  UploadVar(Uniforms.Projection, ProjectionLocation);\n  UploadVar(Uniforms.SpriteTransformation, SpriteTransformationLocation);\n  UploadVar(Uniforms.MaskTransformation, MaskTransformationLocation);\n  UploadVar(Uniforms.ColorMap, ColorMapLocation);\n  UploadVar(Uniforms.Mask, MaskLocation);\n}\n\nvoid ColorDodgeMaskedSpriteShader::UploadUniforms(\n    ColorDodgeMaskedSpriteUniforms newUniforms) {\n  UpdateVar(newUniforms.Projection, Uniforms.Projection, ProjectionLocation);\n  UpdateVar(newUniforms.SpriteTransformation, Uniforms.SpriteTransformation,\n            SpriteTransformationLocation);\n  UpdateVar(newUniforms.MaskTransformation, Uniforms.MaskTransformation,\n            MaskTransformationLocation);\n\n  UpdateVar(newUniforms.ColorMap, Uniforms.ColorMap, ColorMapLocation);\n  UpdateVar(newUniforms.Mask, Uniforms.Mask, MaskLocation);\n}\n\nHardLightMaskedSpriteShader::HardLightMaskedSpriteShader(GLint programId)\n    : Shader(programId),\n      ProjectionLocation(glGetUniformLocation(programId, \"Projection\")),\n      SpriteTransformationLocation(\n          glGetUniformLocation(programId, \"SpriteTransformation\")),\n      MaskTransformationLocation(\n          glGetUniformLocation(programId, \"MaskTransformation\")),\n      ColorMapLocation(glGetUniformLocation(programId, \"ColorMap\")),\n      MaskLocation(glGetUniformLocation(programId, \"Mask\")) {\n  UploadVar(Uniforms.Projection, ProjectionLocation);\n  UploadVar(Uniforms.SpriteTransformation, SpriteTransformationLocation);\n  UploadVar(Uniforms.MaskTransformation, MaskTransformationLocation);\n  UploadVar(Uniforms.ColorMap, ColorMapLocation);\n  UploadVar(Uniforms.Mask, MaskLocation);\n}\n\nvoid HardLightMaskedSpriteShader::UploadUniforms(\n    HardLightMaskedSpriteUniforms newUniforms) {\n  UpdateVar(newUniforms.Projection, Uniforms.Projection, ProjectionLocation);\n  UpdateVar(newUniforms.SpriteTransformation, Uniforms.SpriteTransformation,\n            SpriteTransformationLocation);\n  UpdateVar(newUniforms.MaskTransformation, Uniforms.MaskTransformation,\n            MaskTransformationLocation);\n\n  UpdateVar(newUniforms.ColorMap, Uniforms.ColorMap, ColorMapLocation);\n  UpdateVar(newUniforms.Mask, Uniforms.Mask, MaskLocation);\n}\n\nLinearBurnMaskedSpriteShader::LinearBurnMaskedSpriteShader(GLint programId)\n    : Shader(programId),\n      ProjectionLocation(glGetUniformLocation(programId, \"Projection\")),\n      SpriteTransformationLocation(\n          glGetUniformLocation(programId, \"SpriteTransformation\")),\n      MaskTransformationLocation(\n          glGetUniformLocation(programId, \"MaskTransformation\")),\n      ColorMapLocation(glGetUniformLocation(programId, \"ColorMap\")),\n      MaskLocation(glGetUniformLocation(programId, \"Mask\")) {\n  UploadVar(Uniforms.Projection, ProjectionLocation);\n  UploadVar(Uniforms.SpriteTransformation, SpriteTransformationLocation);\n  UploadVar(Uniforms.MaskTransformation, MaskTransformationLocation);\n  UploadVar(Uniforms.ColorMap, ColorMapLocation);\n  UploadVar(Uniforms.Mask, MaskLocation);\n}\n\nvoid LinearBurnMaskedSpriteShader::UploadUniforms(\n    LinearBurnMaskedSpriteUniforms newUniforms) {\n  UpdateVar(newUniforms.Projection, Uniforms.Projection, ProjectionLocation);\n  UpdateVar(newUniforms.SpriteTransformation, Uniforms.SpriteTransformation,\n            SpriteTransformationLocation);\n  UpdateVar(newUniforms.MaskTransformation, Uniforms.MaskTransformation,\n            MaskTransformationLocation);\n\n  UpdateVar(newUniforms.ColorMap, Uniforms.ColorMap, ColorMapLocation);\n  UpdateVar(newUniforms.Mask, Uniforms.Mask, MaskLocation);\n}\n\nOverlayMaskedSpriteShader::OverlayMaskedSpriteShader(GLint programId)\n    : Shader(programId),\n      ProjectionLocation(glGetUniformLocation(programId, \"Projection\")),\n      SpriteTransformationLocation(\n          glGetUniformLocation(programId, \"SpriteTransformation\")),\n      MaskTransformationLocation(\n          glGetUniformLocation(programId, \"MaskTransformation\")),\n      ColorMapLocation(glGetUniformLocation(programId, \"ColorMap\")),\n      MaskLocation(glGetUniformLocation(programId, \"Mask\")) {\n  UploadVar(Uniforms.Projection, ProjectionLocation);\n  UploadVar(Uniforms.SpriteTransformation, SpriteTransformationLocation);\n  UploadVar(Uniforms.MaskTransformation, MaskTransformationLocation);\n  UploadVar(Uniforms.ColorMap, ColorMapLocation);\n  UploadVar(Uniforms.Mask, MaskLocation);\n}\n\nvoid OverlayMaskedSpriteShader::UploadUniforms(\n    OverlayMaskedSpriteUniforms newUniforms) {\n  UpdateVar(newUniforms.Projection, Uniforms.Projection, ProjectionLocation);\n  UpdateVar(newUniforms.SpriteTransformation, Uniforms.SpriteTransformation,\n            SpriteTransformationLocation);\n  UpdateVar(newUniforms.MaskTransformation, Uniforms.MaskTransformation,\n            MaskTransformationLocation);\n\n  UpdateVar(newUniforms.ColorMap, Uniforms.ColorMap, ColorMapLocation);\n  UpdateVar(newUniforms.Mask, Uniforms.Mask, MaskLocation);\n}\n\nScreenMaskedSpriteShader::ScreenMaskedSpriteShader(GLint programId)\n    : Shader(programId),\n      ProjectionLocation(glGetUniformLocation(programId, \"Projection\")),\n      SpriteTransformationLocation(\n          glGetUniformLocation(programId, \"SpriteTransformation\")),\n      MaskTransformationLocation(\n          glGetUniformLocation(programId, \"MaskTransformation\")),\n      ColorMapLocation(glGetUniformLocation(programId, \"ColorMap\")),\n      MaskLocation(glGetUniformLocation(programId, \"Mask\")) {\n  UploadVar(Uniforms.Projection, ProjectionLocation);\n  UploadVar(Uniforms.SpriteTransformation, SpriteTransformationLocation);\n  UploadVar(Uniforms.MaskTransformation, MaskTransformationLocation);\n  UploadVar(Uniforms.ColorMap, ColorMapLocation);\n  UploadVar(Uniforms.Mask, MaskLocation);\n}\n\nvoid ScreenMaskedSpriteShader::UploadUniforms(\n    ScreenMaskedSpriteUniforms newUniforms) {\n  UpdateVar(newUniforms.Projection, Uniforms.Projection, ProjectionLocation);\n  UpdateVar(newUniforms.SpriteTransformation, Uniforms.SpriteTransformation,\n            SpriteTransformationLocation);\n  UpdateVar(newUniforms.MaskTransformation, Uniforms.MaskTransformation,\n            MaskTransformationLocation);\n\n  UpdateVar(newUniforms.ColorMap, Uniforms.ColorMap, ColorMapLocation);\n  UpdateVar(newUniforms.Mask, Uniforms.Mask, MaskLocation);\n}\n\nSoftLightMaskedSpriteShader::SoftLightMaskedSpriteShader(GLint programId)\n    : Shader(programId),\n      ProjectionLocation(glGetUniformLocation(programId, \"Projection\")),\n      SpriteTransformationLocation(\n          glGetUniformLocation(programId, \"SpriteTransformation\")),\n      MaskTransformationLocation(\n          glGetUniformLocation(programId, \"MaskTransformation\")),\n      ColorMapLocation(glGetUniformLocation(programId, \"ColorMap\")),\n      MaskLocation(glGetUniformLocation(programId, \"Mask\")) {\n  UploadVar(Uniforms.Projection, ProjectionLocation);\n  UploadVar(Uniforms.SpriteTransformation, SpriteTransformationLocation);\n  UploadVar(Uniforms.MaskTransformation, MaskTransformationLocation);\n  UploadVar(Uniforms.ColorMap, ColorMapLocation);\n  UploadVar(Uniforms.Mask, MaskLocation);\n}\n\nvoid SoftLightMaskedSpriteShader::UploadUniforms(\n    SoftLightMaskedSpriteUniforms newUniforms) {\n  UpdateVar(newUniforms.Projection, Uniforms.Projection, ProjectionLocation);\n  UpdateVar(newUniforms.SpriteTransformation, Uniforms.SpriteTransformation,\n            SpriteTransformationLocation);\n  UpdateVar(newUniforms.MaskTransformation, Uniforms.MaskTransformation,\n            MaskTransformationLocation);\n\n  UpdateVar(newUniforms.ColorMap, Uniforms.ColorMap, ColorMapLocation);\n  UpdateVar(newUniforms.Mask, Uniforms.Mask, MaskLocation);\n}\n\nColorMaskedSpriteShader::ColorMaskedSpriteShader(GLint programId)\n    : Shader(programId),\n      ProjectionLocation(glGetUniformLocation(programId, \"Projection\")),\n      SpriteTransformationLocation(\n          glGetUniformLocation(programId, \"SpriteTransformation\")),\n      MaskTransformationLocation(\n          glGetUniformLocation(programId, \"MaskTransformation\")),\n      ColorMapLocation(glGetUniformLocation(programId, \"ColorMap\")),\n      MaskLocation(glGetUniformLocation(programId, \"Mask\")) {\n  UploadVar(Uniforms.Projection, ProjectionLocation);\n  UploadVar(Uniforms.SpriteTransformation, SpriteTransformationLocation);\n  UploadVar(Uniforms.MaskTransformation, MaskTransformationLocation);\n  UploadVar(Uniforms.ColorMap, ColorMapLocation);\n  UploadVar(Uniforms.Mask, MaskLocation);\n}\n\nvoid ColorMaskedSpriteShader::UploadUniforms(\n    ColorMaskedSpriteUniforms newUniforms) {\n  UpdateVar(newUniforms.Projection, Uniforms.Projection, ProjectionLocation);\n  UpdateVar(newUniforms.SpriteTransformation, Uniforms.SpriteTransformation,\n            SpriteTransformationLocation);\n  UpdateVar(newUniforms.MaskTransformation, Uniforms.MaskTransformation,\n            MaskTransformationLocation);\n\n  UpdateVar(newUniforms.ColorMap, Uniforms.ColorMap, ColorMapLocation);\n  UpdateVar(newUniforms.Mask, Uniforms.Mask, MaskLocation);\n}\n\nAdditiveMaskedSpriteShader::AdditiveMaskedSpriteShader(GLint programId)\n    : Shader(programId),\n      ProjectionLocation(glGetUniformLocation(programId, \"Projection\")),\n      SpriteTransformationLocation(\n          glGetUniformLocation(programId, \"SpriteTransformation\")),\n      MaskTransformationLocation(\n          glGetUniformLocation(programId, \"MaskTransformation\")),\n      ColorMapLocation(glGetUniformLocation(programId, \"ColorMap\")),\n      MaskLocation(glGetUniformLocation(programId, \"Mask\")) {\n  UploadVar(Uniforms.Projection, ProjectionLocation);\n  UploadVar(Uniforms.SpriteTransformation, SpriteTransformationLocation);\n  UploadVar(Uniforms.MaskTransformation, MaskTransformationLocation);\n  UploadVar(Uniforms.ColorMap, ColorMapLocation);\n  UploadVar(Uniforms.Mask, MaskLocation);\n}\n\nvoid AdditiveMaskedSpriteShader::UploadUniforms(\n    AdditiveMaskedSpriteUniforms newUniforms) {\n  UpdateVar(newUniforms.Projection, Uniforms.Projection, ProjectionLocation);\n  UpdateVar(newUniforms.SpriteTransformation, Uniforms.SpriteTransformation,\n            SpriteTransformationLocation);\n  UpdateVar(newUniforms.MaskTransformation, Uniforms.MaskTransformation,\n            MaskTransformationLocation);\n\n  UpdateVar(newUniforms.ColorMap, Uniforms.ColorMap, ColorMapLocation);\n  UpdateVar(newUniforms.Mask, Uniforms.Mask, MaskLocation);\n}\n\nCCMessageBoxShader::CCMessageBoxShader(GLint programId)\n    : Shader(programId),\n      ProjectionLocation(glGetUniformLocation(programId, \"Projection\")),\n      ColorMapLocation(glGetUniformLocation(programId, \"ColorMap\")),\n      MaskLocation(glGetUniformLocation(programId, \"Mask\")),\n      AlphaLocation(glGetUniformLocation(programId, \"Alpha\")) {\n  UploadVar(Uniforms.Projection, ProjectionLocation);\n  UploadVar(Uniforms.ColorMap, ColorMapLocation);\n  UploadVar(Uniforms.Mask, MaskLocation);\n  UploadVar(Uniforms.Alpha, AlphaLocation);\n}\n\nvoid CCMessageBoxShader::UploadUniforms(CCMessageBoxUniforms newUniforms) {\n  UpdateVar(newUniforms.Projection, Uniforms.Projection, ProjectionLocation);\n\n  UpdateVar(newUniforms.ColorMap, Uniforms.ColorMap, ColorMapLocation);\n  UpdateVar(newUniforms.Mask, Uniforms.Mask, MaskLocation);\n  UpdateVar(newUniforms.Alpha, Uniforms.Alpha, AlphaLocation);\n}\n\nCHLCCMenuBackgroundShader::CHLCCMenuBackgroundShader(GLint programId)\n    : Shader(programId),\n      ProjectionLocation(glGetUniformLocation(programId, \"Projection\")),\n      ColorMapLocation(glGetUniformLocation(programId, \"ColorMap\")),\n      MaskLocation(glGetUniformLocation(programId, \"Mask\")),\n      AlphaLocation(glGetUniformLocation(programId, \"Alpha\")) {\n  UploadVar(Uniforms.Projection, ProjectionLocation);\n  UploadVar(Uniforms.ColorMap, ColorMapLocation);\n  UploadVar(Uniforms.Mask, MaskLocation);\n  UploadVar(Uniforms.Alpha, AlphaLocation);\n}\n\nvoid CHLCCMenuBackgroundShader::UploadUniforms(\n    CHLCCMenuBackgroundUniforms newUniforms) {\n  UpdateVar(newUniforms.Projection, Uniforms.Projection, ProjectionLocation);\n\n  UpdateVar(newUniforms.ColorMap, Uniforms.ColorMap, ColorMapLocation);\n  UpdateVar(newUniforms.Mask, Uniforms.Mask, MaskLocation);\n  UpdateVar(newUniforms.Alpha, Uniforms.Alpha, AlphaLocation);\n}\n\nGaussianBlurShader::GaussianBlurShader(GLint programId)\n    : Shader(programId),\n      ProjectionLocation(glGetUniformLocation(programId, \"Projection\")),\n      TransformationLocation(glGetUniformLocation(programId, \"Transformation\")),\n      ColorMapLocation(glGetUniformLocation(programId, \"ColorMap\")),\n      TextureDimensionsLocation(\n          glGetUniformLocation(programId, \"TextureDimensions\")),\n      IsHorizontalLocation(glGetUniformLocation(programId, \"IsHorizontal\")) {\n  UploadVar(Uniforms.Projection, ProjectionLocation);\n  UploadVar(Uniforms.Transformation, TransformationLocation);\n  UploadVar(Uniforms.ColorMap, ColorMapLocation);\n  UploadVar(Uniforms.TextureDimensions, TextureDimensionsLocation);\n  UploadVar(Uniforms.IsHorizontal, IsHorizontalLocation);\n}\n\nvoid GaussianBlurShader::UploadUniforms(GaussianBlurUniforms newUniforms) {\n  UpdateVar(newUniforms.Projection, Uniforms.Projection, ProjectionLocation);\n  UpdateVar(newUniforms.Transformation, Uniforms.Transformation,\n            TransformationLocation);\n\n  UpdateVar(newUniforms.ColorMap, Uniforms.ColorMap, ColorMapLocation);\n  UpdateVar(newUniforms.TextureDimensions, Uniforms.TextureDimensions,\n            TextureDimensionsLocation);\n  UpdateVar(newUniforms.IsHorizontal, Uniforms.IsHorizontal,\n            IsHorizontalLocation);\n}\n\nMosaicShader::MosaicShader(GLint programId)\n    : Shader(programId),\n      ProjectionLocation(glGetUniformLocation(programId, \"Projection\")),\n      TransformationLocation(glGetUniformLocation(programId, \"Transformation\")),\n      ColorMapLocation(glGetUniformLocation(programId, \"ColorMap\")),\n      TextureDimensionsLocation(\n          glGetUniformLocation(programId, \"TextureDimensions\")),\n      TileSizeLocation(glGetUniformLocation(programId, \"TileSize\")) {\n  UploadVar(Uniforms.Projection, ProjectionLocation);\n  UploadVar(Uniforms.Transformation, TransformationLocation);\n  UploadVar(Uniforms.ColorMap, ColorMapLocation);\n  UploadVar(Uniforms.TextureDimensions, TextureDimensionsLocation);\n  UploadVar(Uniforms.TileSize, TileSizeLocation);\n}\n\nvoid MosaicShader::UploadUniforms(MosaicUniforms newUniforms) {\n  UpdateVar(newUniforms.Projection, Uniforms.Projection, ProjectionLocation);\n  UpdateVar(newUniforms.Transformation, Uniforms.Transformation,\n            TransformationLocation);\n\n  UpdateVar(newUniforms.ColorMap, Uniforms.ColorMap, ColorMapLocation);\n  UpdateVar(newUniforms.TextureDimensions, Uniforms.TextureDimensions,\n            TextureDimensionsLocation);\n  UpdateVar(newUniforms.TileSize, Uniforms.TileSize, TileSizeLocation);\n}\n\nSubtitleGlyphShader::SubtitleGlyphShader(GLint programId)\n    : Shader(programId),\n      ProjectionLocation(glGetUniformLocation(programId, \"Projection\")),\n      TransformationLocation(glGetUniformLocation(programId, \"Transformation\")),\n      CoverageMapLocation(glGetUniformLocation(programId, \"CoverageMap\")) {\n  UploadVar(Uniforms.Projection, ProjectionLocation);\n  UploadVar(Uniforms.Transformation, TransformationLocation);\n  UploadVar(Uniforms.CoverageMap, CoverageMapLocation);\n}\n\nvoid SubtitleGlyphShader::UploadUniforms(SubtitleGlyphUniforms newUniforms) {\n  UpdateVar(newUniforms.Projection, Uniforms.Projection, ProjectionLocation);\n  UpdateVar(newUniforms.Transformation, Uniforms.Transformation,\n            TransformationLocation);\n  UpdateVar(newUniforms.CoverageMap, Uniforms.CoverageMap, CoverageMapLocation);\n}\n\nNV12FrameShader::NV12FrameShader(GLint programId)\n    : Shader(programId),\n      ProjectionLocation(glGetUniformLocation(programId, \"Projection\")),\n      LumaLocation(glGetUniformLocation(programId, \"Luma\")),\n      CbCrLocation(glGetUniformLocation(programId, \"CbCr\")),\n      IsAlphaLocation(glGetUniformLocation(programId, \"IsAlpha\")) {\n  UploadVar(Uniforms.Projection, ProjectionLocation);\n  UploadVar(Uniforms.Luma, LumaLocation);\n  UploadVar(Uniforms.CbCr, CbCrLocation);\n  UploadVar(Uniforms.IsAlpha, IsAlphaLocation);\n}\n\nvoid NV12FrameShader::UploadUniforms(NV12FrameUniforms newUniforms) {\n  UpdateVar(newUniforms.Projection, Uniforms.Projection, ProjectionLocation);\n  UpdateVar(newUniforms.Luma, Uniforms.Luma, LumaLocation);\n  UpdateVar(newUniforms.CbCr, Uniforms.CbCr, CbCrLocation);\n  UpdateVar(newUniforms.IsAlpha, Uniforms.IsAlpha, IsAlphaLocation);\n}\n\n}  // namespace OpenGL\n}  // namespace Impacto\n"
  },
  {
    "path": "src/renderer/opengl/shader.h",
    "content": "#pragma once\n\n#include <glad/glad.h>\n#include <glm/glm.hpp>\n#include <ankerl/unordered_dense.h>\n#include <string>\n#include \"window.h\"\n\nnamespace Impacto {\nnamespace OpenGL {\n\nenum ShaderParameterType {\n  SPT_Int,\n  SPT_Float,\n\n  SPT_MacroInt,\n  SPT_MacroFloat,\n\n  SPT_Vec2,\n  SPT_Vec3,\n  SPT_Vec4,\n\n  SPT_Ivec2,\n  SPT_Ivec3,\n  SPT_Ivec4\n};\n\nstruct ShaderParameter {\n  ShaderParameterType Type;\n  union {\n    int Val_Int;\n    float Val_Float;\n\n    glm::vec2 Val_Vec2;\n    glm::vec3 Val_Vec3;\n    glm::vec4 Val_Vec4;\n\n    glm::ivec2 Val_Ivec2;\n    glm::ivec3 Val_Ivec3;\n    glm::ivec4 Val_Ivec4;\n  };\n\n  ShaderParameter() {}\n  ShaderParameter(int val) : Type(SPT_Int), Val_Int(val) {}\n  ShaderParameter(float val) : Type(SPT_Float), Val_Float(val) {}\n  ShaderParameter(int val, bool macro)\n      : Type(macro ? SPT_MacroInt : SPT_Int), Val_Int(val) {}\n  ShaderParameter(float val, bool macro)\n      : Type(macro ? SPT_MacroFloat : SPT_Float), Val_Float(val) {}\n  ShaderParameter(glm::vec2 val) : Type(SPT_Vec2), Val_Vec2(val) {}\n  ShaderParameter(glm::vec3 val) : Type(SPT_Vec3), Val_Vec3(val) {}\n  ShaderParameter(glm::vec4 val) : Type(SPT_Vec4), Val_Vec4(val) {}\n  ShaderParameter(glm::ivec2 val) : Type(SPT_Ivec2), Val_Ivec2(val) {}\n  ShaderParameter(glm::ivec3 val) : Type(SPT_Ivec3), Val_Ivec3(val) {}\n  ShaderParameter(glm::ivec4 val) : Type(SPT_Ivec4), Val_Ivec4(val) {}\n};\n\ntypedef ankerl::unordered_dense::map<std::string, ShaderParameter, string_hash,\n                                     std::equal_to<>>\n    ShaderParamMap;\n\nclass ShaderCompiler {\n public:\n  GLuint Compile(char const* name,\n                 ShaderParamMap const& params = ShaderParamMap());\n\n private:\n  int PrintParameter(char* dest, int destSz, char const* name,\n                     ShaderParameter const& param);\n  GLuint Attach(GLuint program, GLenum shaderType, char const* path,\n                char const* params);\n};\n\ntemplate <typename UniformsStruct>\nclass Shader {\n public:\n  Shader(GLuint programId) : ProgramId(programId) { Bind(); }\n\n  virtual ~Shader() { glDeleteProgram(ProgramId); }\n\n  void Bind() { glUseProgram(ProgramId); }\n\n  UniformsStruct GetUniforms() { return Uniforms; }\n  virtual void UploadUniforms(UniformsStruct uniforms) = 0;\n\n protected:\n  const GLuint ProgramId;\n\n  UniformsStruct Uniforms{};\n\n  void UploadVar(bool value, GLint location) { glUniform1i(location, value); }\n  void UploadVar(int value, GLint location) { glUniform1i(location, value); }\n  void UploadVar(GLuint value, GLint location) {\n    glUniform1ui(location, value);\n  }\n  void UploadVar(float value, GLint location) { glUniform1f(location, value); }\n  void UploadVar(glm::vec2 value, GLint location) {\n    glUniform2fv(location, 1, &value[0]);\n  }\n  void UploadVar(glm::vec3 value, GLint location) {\n    glUniform3fv(location, 1, &value[0]);\n  }\n  void UploadVar(glm::vec4 value, GLint location) {\n    glUniform4fv(location, 1, &value[0]);\n  }\n  void UploadVar(glm::mat4 value, GLint location) {\n    glUniformMatrix4fv(location, 1, GL_FALSE, &value[0][0]);\n  }\n\n  template <typename T>\n  void UpdateVar(T newValue, T& oldValue, GLint location) {\n    if (newValue != oldValue) {\n      oldValue = newValue;\n      UploadVar(newValue, location);\n    }\n  }\n};\n\nstruct AdditiveMaskedSpriteUniforms {\n  bool operator==(const AdditiveMaskedSpriteUniforms& other) const = default;\n\n  glm::mat4 Projection{};\n  glm::mat4 SpriteTransformation{};\n  glm::mat4 MaskTransformation{};\n\n  GLint ColorMap;\n  GLint Mask;\n};\n\nclass AdditiveMaskedSpriteShader : public Shader<AdditiveMaskedSpriteUniforms> {\n public:\n  AdditiveMaskedSpriteShader(GLint programId);\n\n  void UploadUniforms(AdditiveMaskedSpriteUniforms uniforms) override;\n\n private:\n  const GLint ProjectionLocation;\n  const GLint SpriteTransformationLocation;\n  const GLint MaskTransformationLocation;\n\n  const GLint ColorMapLocation;\n  const GLint MaskLocation;\n};\n\nstruct SpriteUniforms {\n  bool operator==(const SpriteUniforms& other) const = default;\n\n  glm::mat4 Projection{};\n  glm::mat4 Transformation{};\n\n  GLint ColorMap = 0;\n  glm::vec3 ColorShift{};\n};\n\nclass SpriteShader : public Shader<SpriteUniforms> {\n public:\n  SpriteShader(GLint programId);\n\n  void UploadUniforms(SpriteUniforms uniforms) override;\n\n private:\n  const GLint ProjectionLocation;\n  const GLint TransformationLocation;\n\n  const GLint ColorMapLocation;\n  const GLint ColorShiftLocation;\n};\n\nstruct SpriteInvertedUniforms {\n  bool operator==(const SpriteInvertedUniforms& other) const = default;\n\n  glm::mat4 Projection{};\n  glm::mat4 Transformation{};\n\n  GLint ColorMap = 0;\n};\n\nclass SpriteInvertedShader : public Shader<SpriteInvertedUniforms> {\n public:\n  SpriteInvertedShader(GLint programId);\n\n  void UploadUniforms(SpriteInvertedUniforms uniforms) override;\n\n private:\n  const GLint ProjectionLocation;\n  const GLint TransformationLocation;\n\n  const GLint ColorMapLocation;\n};\n\nstruct YUVFrameUniforms {\n  bool operator==(const YUVFrameUniforms& other) const = default;\n\n  glm::mat4 Projection;\n\n  GLint Luma = 0;\n  GLint Cb = 0;\n  GLint Cr = 0;\n  bool IsAlpha = false;\n};\n\nclass YUVFrameShader : public Shader<YUVFrameUniforms> {\n public:\n  YUVFrameShader(GLint programId);\n\n  void UploadUniforms(YUVFrameUniforms uniforms) override;\n\n private:\n  const GLint ProjectionLocation;\n\n  const GLint LumaLocation;\n  const GLint CbLocation;\n  const GLint CrLocation;\n  const GLint IsAlphaLocation;\n};\n\nstruct MaskedSpriteNoAlphaUniforms {\n  bool operator==(const MaskedSpriteNoAlphaUniforms& other) const = default;\n\n  glm::mat4 Projection{};\n  glm::mat4 SpriteTransformation{};\n  glm::mat4 MaskTransformation{};\n\n  GLint ColorMap = 0;\n  GLint Mask = 0;\n  glm::vec2 Alpha{};\n  bool IsInverted = false;\n};\n\nclass MaskedSpriteNoAlphaShader : public Shader<MaskedSpriteNoAlphaUniforms> {\n public:\n  MaskedSpriteNoAlphaShader(GLint programId);\n\n  void UploadUniforms(MaskedSpriteNoAlphaUniforms uniforms) override;\n\n private:\n  const GLint ProjectionLocation;\n  const GLint SpriteTransformationLocation;\n  const GLint MaskTransformationLocation;\n\n  const GLint ColorMapLocation;\n  const GLint MaskLocation;\n  const GLint AlphaLocation;\n  const GLint IsInvertedLocation;\n};\n\nstruct MaskedSpriteUniforms {\n  bool operator==(const MaskedSpriteUniforms& other) const = default;\n\n  glm::mat4 Projection{};\n  glm::mat4 SpriteTransformation{};\n  glm::mat4 MaskTransformation{};\n\n  GLint ColorMap;\n  GLint Mask;\n  glm::vec2 Alpha{};\n  bool IsInverted = false;\n  bool IsSameTexture = false;\n};\n\nclass MaskedSpriteShader : public Shader<MaskedSpriteUniforms> {\n public:\n  MaskedSpriteShader(GLint programId);\n\n  void UploadUniforms(MaskedSpriteUniforms uniforms) override;\n\n private:\n  const GLint ProjectionLocation;\n  const GLint SpriteTransformationLocation;\n  const GLint MaskTransformationLocation;\n\n  const GLint ColorMapLocation;\n  const GLint MaskLocation;\n  const GLint AlphaLocation;\n  const GLint IsInvertedLocation;\n  const GLint IsSameTextureLocation;\n};\n\nstruct MaskedSpriteBinaryUniforms {\n  bool operator==(const MaskedSpriteBinaryUniforms& other) const = default;\n\n  glm::mat4 Projection{};\n  glm::mat4 SpriteTransformation{};\n  glm::mat4 MaskTransformation{};\n  bool FullscreenMask = false;\n\n  GLint ColorMap;\n  GLint Mask;\n  bool IsInverted = false;\n};\n\nclass MaskedSpriteBinaryShader : public Shader<MaskedSpriteBinaryUniforms> {\n public:\n  MaskedSpriteBinaryShader(GLint programId);\n\n  void UploadUniforms(MaskedSpriteBinaryUniforms uniforms) override;\n\n private:\n  const GLint ProjectionLocation;\n  const GLint SpriteTransformationLocation;\n  const GLint MaskTransformationLocation;\n  const GLint FullscreenMaskLocation;\n\n  const GLint ColorMapLocation;\n  const GLint MaskLocation;\n  const GLint IsInvertedLocation;\n};\n\nstruct ColorBurnMaskedSpriteUniforms {\n  bool operator==(const ColorBurnMaskedSpriteUniforms& other) const = default;\n\n  glm::mat4 Projection{};\n  glm::mat4 SpriteTransformation{};\n  glm::mat4 MaskTransformation{};\n\n  GLint ColorMap;\n  GLint Mask;\n};\n\nclass ColorBurnMaskedSpriteShader\n    : public Shader<ColorBurnMaskedSpriteUniforms> {\n public:\n  ColorBurnMaskedSpriteShader(GLint programId);\n\n  void UploadUniforms(ColorBurnMaskedSpriteUniforms uniforms) override;\n\n private:\n  const GLint ProjectionLocation;\n  const GLint SpriteTransformationLocation;\n  const GLint MaskTransformationLocation;\n\n  const GLint ColorMapLocation;\n  const GLint MaskLocation;\n};\n\nstruct ColorDodgeMaskedSpriteUniforms {\n  bool operator==(const ColorDodgeMaskedSpriteUniforms& other) const = default;\n\n  glm::mat4 Projection{};\n  glm::mat4 SpriteTransformation{};\n  glm::mat4 MaskTransformation{};\n\n  GLint ColorMap;\n  GLint Mask;\n};\n\nclass ColorDodgeMaskedSpriteShader\n    : public Shader<ColorDodgeMaskedSpriteUniforms> {\n public:\n  ColorDodgeMaskedSpriteShader(GLint programId);\n\n  void UploadUniforms(ColorDodgeMaskedSpriteUniforms uniforms) override;\n\n private:\n  const GLint ProjectionLocation;\n  const GLint SpriteTransformationLocation;\n  const GLint MaskTransformationLocation;\n\n  const GLint ColorMapLocation;\n  const GLint MaskLocation;\n};\n\nstruct ColorMaskedSpriteUniforms {\n  bool operator==(const ColorMaskedSpriteUniforms& other) const = default;\n\n  glm::mat4 Projection{};\n  glm::mat4 SpriteTransformation{};\n  glm::mat4 MaskTransformation{};\n\n  GLint ColorMap;\n  GLint Mask;\n};\n\nclass ColorMaskedSpriteShader : public Shader<ColorMaskedSpriteUniforms> {\n public:\n  ColorMaskedSpriteShader(GLint programId);\n\n  void UploadUniforms(ColorMaskedSpriteUniforms uniforms) override;\n\n private:\n  const GLint ProjectionLocation;\n  const GLint SpriteTransformationLocation;\n  const GLint MaskTransformationLocation;\n\n  const GLint ColorMapLocation;\n  const GLint MaskLocation;\n};\n\nstruct HardLightMaskedSpriteUniforms {\n  bool operator==(const HardLightMaskedSpriteUniforms& other) const = default;\n\n  glm::mat4 Projection{};\n  glm::mat4 SpriteTransformation{};\n  glm::mat4 MaskTransformation{};\n\n  GLint ColorMap;\n  GLint Mask;\n};\n\nclass HardLightMaskedSpriteShader\n    : public Shader<HardLightMaskedSpriteUniforms> {\n public:\n  HardLightMaskedSpriteShader(GLint programId);\n\n  void UploadUniforms(HardLightMaskedSpriteUniforms uniforms) override;\n\n private:\n  const GLint ProjectionLocation;\n  const GLint SpriteTransformationLocation;\n  const GLint MaskTransformationLocation;\n\n  const GLint ColorMapLocation;\n  const GLint MaskLocation;\n};\n\nstruct LinearBurnMaskedSpriteUniforms {\n  bool operator==(const LinearBurnMaskedSpriteUniforms& other) const = default;\n\n  glm::mat4 Projection{};\n  glm::mat4 SpriteTransformation{};\n  glm::mat4 MaskTransformation{};\n\n  GLint ColorMap;\n  GLint Mask;\n};\n\nclass LinearBurnMaskedSpriteShader\n    : public Shader<LinearBurnMaskedSpriteUniforms> {\n public:\n  LinearBurnMaskedSpriteShader(GLint programId);\n\n  void UploadUniforms(LinearBurnMaskedSpriteUniforms uniforms) override;\n\n private:\n  const GLint ProjectionLocation;\n  const GLint SpriteTransformationLocation;\n  const GLint MaskTransformationLocation;\n\n  const GLint ColorMapLocation;\n  const GLint MaskLocation;\n};\n\nstruct OverlayMaskedSpriteUniforms {\n  bool operator==(const OverlayMaskedSpriteUniforms& other) const = default;\n\n  glm::mat4 Projection{};\n  glm::mat4 SpriteTransformation{};\n  glm::mat4 MaskTransformation{};\n\n  GLint ColorMap;\n  GLint Mask;\n};\n\nclass OverlayMaskedSpriteShader : public Shader<OverlayMaskedSpriteUniforms> {\n public:\n  OverlayMaskedSpriteShader(GLint programId);\n\n  void UploadUniforms(OverlayMaskedSpriteUniforms uniforms) override;\n\n private:\n  const GLint ProjectionLocation;\n  const GLint SpriteTransformationLocation;\n  const GLint MaskTransformationLocation;\n\n  const GLint ColorMapLocation;\n  const GLint MaskLocation;\n};\n\nstruct ScreenMaskedSpriteUniforms {\n  bool operator==(const ScreenMaskedSpriteUniforms& other) const = default;\n\n  glm::mat4 Projection{};\n  glm::mat4 SpriteTransformation{};\n  glm::mat4 MaskTransformation{};\n\n  GLint ColorMap;\n  GLint Mask;\n};\n\nclass ScreenMaskedSpriteShader : public Shader<ScreenMaskedSpriteUniforms> {\n public:\n  ScreenMaskedSpriteShader(GLint programId);\n\n  void UploadUniforms(ScreenMaskedSpriteUniforms uniforms) override;\n\n private:\n  const GLint ProjectionLocation;\n  const GLint SpriteTransformationLocation;\n  const GLint MaskTransformationLocation;\n\n  const GLint ColorMapLocation;\n  const GLint MaskLocation;\n};\n\nstruct SoftLightMaskedSpriteUniforms {\n  bool operator==(const SoftLightMaskedSpriteUniforms& other) const = default;\n\n  glm::mat4 Projection{};\n  glm::mat4 SpriteTransformation{};\n  glm::mat4 MaskTransformation{};\n\n  GLint ColorMap;\n  GLint Mask;\n};\n\nclass SoftLightMaskedSpriteShader\n    : public Shader<SoftLightMaskedSpriteUniforms> {\n public:\n  SoftLightMaskedSpriteShader(GLint programId);\n\n  void UploadUniforms(SoftLightMaskedSpriteUniforms uniforms) override;\n\n private:\n  const GLint ProjectionLocation;\n  const GLint SpriteTransformationLocation;\n  const GLint MaskTransformationLocation;\n\n  const GLint ColorMapLocation;\n  const GLint MaskLocation;\n};\n\nstruct CCMessageBoxUniforms {\n  bool operator==(const CCMessageBoxUniforms& other) const = default;\n\n  glm::mat4 Projection{};\n\n  GLint ColorMap;\n  GLint Mask;\n  glm::vec4 Alpha{};\n};\n\nclass CCMessageBoxShader : public Shader<CCMessageBoxUniforms> {\n public:\n  CCMessageBoxShader(GLint programId);\n\n  void UploadUniforms(CCMessageBoxUniforms uniforms) override;\n\n private:\n  const GLint ProjectionLocation;\n\n  const GLint ColorMapLocation;\n  const GLint MaskLocation;\n  const GLint AlphaLocation;\n};\n\nstruct CHLCCMenuBackgroundUniforms {\n  bool operator==(const CHLCCMenuBackgroundUniforms& other) const = default;\n\n  glm::mat4 Projection{};\n\n  GLint ColorMap;\n  GLint Mask;\n  float Alpha = 0.0f;\n};\n\nclass CHLCCMenuBackgroundShader : public Shader<CHLCCMenuBackgroundUniforms> {\n public:\n  CHLCCMenuBackgroundShader(GLint programId);\n\n  void UploadUniforms(CHLCCMenuBackgroundUniforms uniforms) override;\n\n private:\n  const GLint ProjectionLocation;\n\n  const GLint ColorMapLocation;\n  const GLint MaskLocation;\n  const GLint AlphaLocation;\n};\n\nstruct GaussianBlurUniforms {\n  bool operator==(const GaussianBlurUniforms& other) const = default;\n\n  glm::mat4 Projection{};\n  glm::mat4 Transformation{};\n\n  GLint ColorMap;\n  glm::vec2 TextureDimensions;\n  bool IsHorizontal;\n};\n\nclass GaussianBlurShader : public Shader<GaussianBlurUniforms> {\n public:\n  GaussianBlurShader(GLint programId);\n\n  void UploadUniforms(GaussianBlurUniforms uniforms) override;\n\n private:\n  const GLint ProjectionLocation;\n  const GLint TransformationLocation;\n\n  const GLint ColorMapLocation;\n  const GLint TextureDimensionsLocation;\n  const GLint IsHorizontalLocation;\n};\n\nstruct MosaicUniforms {\n  bool operator==(const MosaicUniforms& other) const = default;\n\n  glm::mat4 Projection{};\n  glm::mat4 Transformation{};\n\n  GLint ColorMap;\n  glm::vec2 TextureDimensions;\n  float TileSize = 1.0f;\n};\n\nclass MosaicShader : public Shader<MosaicUniforms> {\n public:\n  MosaicShader(GLint programId);\n\n  void UploadUniforms(MosaicUniforms uniforms) override;\n\n private:\n  const GLint ProjectionLocation;\n  const GLint TransformationLocation;\n\n  const GLint ColorMapLocation;\n  const GLint TextureDimensionsLocation;\n  const GLint TileSizeLocation;\n};\n\nstruct SubtitleGlyphUniforms {\n  bool operator==(const SubtitleGlyphUniforms& other) const = default;\n\n  glm::mat4 Projection{};\n  glm::mat4 Transformation{};\n\n  GLint CoverageMap = 0;\n};\n\nclass SubtitleGlyphShader : public Shader<SubtitleGlyphUniforms> {\n public:\n  SubtitleGlyphShader(GLint programId);\n\n  void UploadUniforms(SubtitleGlyphUniforms uniforms) override;\n\n private:\n  const GLint ProjectionLocation;\n  const GLint TransformationLocation;\n\n  const GLint CoverageMapLocation;\n};\n\nstruct NV12FrameUniforms {\n  bool operator==(const NV12FrameUniforms& other) const = default;\n\n  glm::mat4 Projection;\n\n  GLint Luma = 0;\n  GLint CbCr = 0;\n  bool IsAlpha = false;\n};\n\nclass NV12FrameShader : public Shader<NV12FrameUniforms> {\n public:\n  NV12FrameShader(GLint programId);\n\n  void UploadUniforms(NV12FrameUniforms uniforms) override;\n\n private:\n  const GLint ProjectionLocation;\n\n  const GLint LumaLocation;\n  const GLint CbCrLocation;\n  const GLint IsAlphaLocation;\n};\n\n}  // namespace OpenGL\n}  // namespace Impacto"
  },
  {
    "path": "src/renderer/opengl/window.cpp",
    "content": "#include \"window.h\"\n\n#include <glad/glad.h>\n#include <SDL_opengl.h>\n\n#include \"../renderer.h\"\n\n#include \"../../log.h\"\n#include \"glc.h\"\n\n#include \"../../profile/game.h\"\n\n#ifndef IMPACTO_DISABLE_IMGUI\n#include <imgui_custom/backends/imgui_impl_opengl3.h>\n#endif\n#include \"../../game.h\"\n#include \"../../profile/vm.h\"\n\nnamespace Impacto {\nnamespace OpenGL {\n\nvoid GLWindow::UpdateDimensions() {\n  WindowDimensionsChanged = false;\n  SDL_GL_GetDrawableSize(SDLWindow, &WindowWidth, &WindowHeight);\n  if (WindowWidth != lastWidth || WindowHeight != lastHeight ||\n      MsaaCount != lastMsaa || RenderScale != lastRenderScale) {\n    WindowDimensionsChanged = true;\n    ImpLog(LogLevel::Debug, LogChannel::General,\n           \"Drawable size (pixels): {:d} x {:d} ({:d}x MSAA requested, render \"\n           \"scale \"\n           \"{:f})\\n\",\n           WindowWidth, WindowHeight, MsaaCount, RenderScale);\n  }\n  lastWidth = WindowWidth;\n  lastHeight = WindowHeight;\n  lastMsaa = MsaaCount;\n  lastRenderScale = RenderScale;\n\n  int osWindowWidth, osWindowHeight;\n  SDL_GetWindowSize(SDLWindow, &osWindowWidth, &osWindowHeight);\n  DpiScaleX = (float)WindowWidth / (float)osWindowWidth;\n  DpiScaleY = (float)WindowHeight / (float)osWindowHeight;\n  // SDL_SetWindowInputFocus(SDLWindow);\n}\n\nRectF GLWindow::GetViewport() {\n  RectF viewport;\n  float scale = fmin((float)WindowWidth / Profile::DesignWidth,\n                     (float)WindowHeight / Profile::DesignHeight);\n  viewport.Width = Profile::DesignWidth * scale;\n  viewport.Height = Profile::DesignHeight * scale;\n  viewport.X = ((float)WindowWidth - viewport.Width) / 2.0f;\n  viewport.Y = ((float)WindowHeight - viewport.Height) / 2.0f;\n  return viewport;\n}\n\nRectF GLWindow::GetScaledViewport() {\n  RectF viewport = GetViewport();\n  viewport.Width *= RenderScale;\n  viewport.Height *= RenderScale;\n  viewport.X *= RenderScale;\n  viewport.Y *= RenderScale;\n  return viewport;\n}\n\nvoid GLWindow::TryCreateGL(GraphicsApi api) {\n  if (GLContext != NULL) return;\n\n  if (SDLWindow) {\n    SDL_DestroyWindow(SDLWindow);\n    SDLWindow = NULL;\n  }\n\n  SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);\n  SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);\n  SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);\n  // Required for blending to work right in Firefox\n  SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8);\n\n  SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 0);\n  SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);\n\n  SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 0);\n  SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 0);\n\n  int contextFlags = 0;\n\n  switch (api) {\n    case GfxApi_GL:\n      ImpLog(LogLevel::Info, LogChannel::General,\n             \"Trying to create desktop GL context\\n\");\n      SDL_SetHint(SDL_HINT_OPENGL_ES_DRIVER, \"0\");\n      SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);\n      SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);\n      SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK,\n                          SDL_GL_CONTEXT_PROFILE_CORE);\n\n      contextFlags |= SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG;\n      break;\n\n    case GfxApi_ForceDesktopGLES:\n      ImpLog(LogLevel::Info, LogChannel::General,\n             \"Trying to create GLES on desktop GL context\\n\");\n      SDL_SetHint(SDL_HINT_OPENGL_ES_DRIVER, \"0\");\n      SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);\n      SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);\n      SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK,\n                          SDL_GL_CONTEXT_PROFILE_ES);\n      break;\n\n    case GfxApi_ForceNativeGLES:\n      ImpLog(LogLevel::Info, LogChannel::General,\n             \"Trying to create native GLES context\\n\");\n\n#ifndef EMSCRIPTEN\n      // Emscripten's EGL implementation fails when you ask it for GLES 3.2,\n      // though that is what it gives you when building for WebGL 2...\n      SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);\n      SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);\n      SDL_SetHint(SDL_HINT_OPENGL_ES_DRIVER, \"1\");\n#endif\n      SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK,\n                          SDL_GL_CONTEXT_PROFILE_ES);\n      break;\n  }\n#if IMPACTO_GL_DEBUG\n  contextFlags |= SDL_GL_CONTEXT_DEBUG_FLAG;\n#endif\n  SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, contextFlags);\n\n  uint32_t windowFlags = SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE;\n#if IMPACTO_USE_SDL_HIGHDPI\n  windowFlags |= SDL_WINDOW_ALLOW_HIGHDPI;\n#endif\n  if (Profile::Fullscreen) {\n    windowFlags |= SDL_WINDOW_FULLSCREEN;\n  }\n\n  SDLWindow = SDL_CreateWindow(\n      Profile::WindowName, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,\n      Profile::ResolutionWidth, Profile::ResolutionHeight, windowFlags);\n\n  if (SDLWindow == NULL) {\n    ImpLog(LogLevel::Error, LogChannel::General,\n           \"Window creation failed: {:s}\\n\", SDL_GetError());\n    return;\n  }\n\n  GLContext = SDL_GL_CreateContext(SDLWindow);\n  if (GLContext == NULL) {\n    ImpLog(LogLevel::Error, LogChannel::General,\n           \"OpenGL context creation failed: {:s}\\n\", SDL_GetError());\n    return;\n  } else {\n    ActualGraphicsApi = api;\n  }\n\n#ifndef IMPACTO_DISABLE_IMGUI\n  ImGui_ImplSDL2_InitForOpenGL(SDLWindow, GLContext);\n  ImGui_ImplOpenGL3_Init();\n#endif\n}\n\nvoid GLWindow::Init() {\n  assert(IsInit == false);\n  ImpLog(LogLevel::Info, LogChannel::General, \"Creating window\\n\");\n  IsInit = true;\n\n#ifdef __ANDROID__\n  SDL_SetHint(SDL_HINT_ORIENTATIONS, \"LandscapeLeft LandscapeRight\");\n#endif\n  if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER) != 0) {\n    ImpLog(LogLevel::Fatal, LogChannel::General,\n           \"SDL initialisation failed: {:s}\\n\", SDL_GetError());\n    Shutdown();\n    return;\n  }\n\n  SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, \"0\");\n\n  switch (GraphicsApiHint) {\n    case GfxApi_GL:\n      TryCreateGL(GfxApi_GL);\n      TryCreateGL(GfxApi_ForceDesktopGLES);\n      TryCreateGL(GfxApi_ForceNativeGLES);\n      break;\n    case GfxApi_ForceDesktopGLES:\n      TryCreateGL(GfxApi_ForceDesktopGLES);\n      TryCreateGL(GfxApi_ForceNativeGLES);\n      TryCreateGL(GfxApi_GL);\n      break;\n    case GfxApi_ForceNativeGLES:\n      TryCreateGL(GfxApi_ForceNativeGLES);\n      TryCreateGL(GfxApi_ForceDesktopGLES);\n      TryCreateGL(GfxApi_GL);\n      break;\n  }\n\n  if (GLContext == NULL) {\n    ImpLog(LogLevel::Fatal, LogChannel::General,\n           \"All options for OpenGL context creation failed\\n\");\n    Shutdown();\n    return;\n  }\n\n  SDL_GetWindowSize(SDLWindow, &WindowWidth, &WindowHeight);\n  ImpLog(LogLevel::Debug, LogChannel::General,\n         \"Window size (screen coords): {:d} x {:d}\\n\", WindowWidth,\n         WindowHeight);\n\n  bool gladOk;\n  if (ActualGraphicsApi == GfxApi_GL) {\n    gladOk = gladLoadGLLoader(SDL_GL_GetProcAddress);\n  } else {\n    gladOk = gladLoadGLES2Loader(SDL_GL_GetProcAddress);\n  }\n  if (!gladOk) {\n    ImpLog(LogLevel::Fatal, LogChannel::General,\n           \"GLAD initialisation failed\\n\");\n    Shutdown();\n  }\n\n  // At the time of writing, this is on by default on Intel on Windows until we\n  // resize the window???\n  glDisable(GL_FRAMEBUFFER_SRGB);\n\n#if IMPACTO_GL_DEBUG\n  int glContextVersion[2]{};\n  SDL_GL_GetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, glContextVersion);\n  SDL_GL_GetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, glContextVersion + 1);\n  bool isDesktopGL = ActualGraphicsApi == GfxApi_GL;\n  bool isVer3_2 = glContextVersion[0] >= 3 && glContextVersion[1] >= 2;\n  if (!isDesktopGL && !isVer3_2) {\n    ImpLog(LogLevel::Error, LogChannel::GL,\n           \"IMPACTO_GL_DEBUG defined but no debug context requested because \"\n           \"we're using GLES <3.2\\n\");\n  } else {\n    GLint flags;\n    glGetIntegerv(GL_CONTEXT_FLAGS, &flags);\n    if (flags & GL_CONTEXT_FLAG_DEBUG_BIT) {\n      GLDebug = true;\n      glEnable(GL_DEBUG_OUTPUT);\n      glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);\n      if (isDesktopGL) {\n        glDebugMessageCallbackARB(&LogGLMessageCallback, NULL);\n        glDebugMessageControlARB(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0,\n                                 NULL, GL_TRUE);\n      } else if (isVer3_2) {\n        glDebugMessageCallback(&LogGLMessageCallback, NULL);\n        glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, NULL,\n                              GL_TRUE);\n      }\n    } else {\n      ImpLog(LogLevel::Error, LogChannel::GL, \"Could not get debug context\\n\");\n    }\n  }\n#endif\n\n  // Vsync\n  SDL_GL_SetSwapInterval(0);\n}\n\nvoid GLWindow::SetDimensions(int width, int height, int msaa,\n                             float renderScale) {\n  ImpLog(LogLevel::Info, LogChannel::General,\n         \"Attempting to change window dimensions to {:d} x {:d}, {:d}x MSAA, \"\n         \"render \"\n         \"scale {:f}\\n\",\n         width, height, msaa, renderScale);\n  assert(width > 0 && height > 0 && msaa >= 0 && renderScale > 0.0f);\n\n  SDL_SetWindowSize(SDLWindow, (int)((float)width / DpiScaleX),\n                    (int)((float)height / DpiScaleY));\n\n  MsaaCount = msaa;\n  RenderScale = renderScale;\n}\n\nvoid GLWindow::CleanFBOs() {\n  if (DrawRenderTexture) glDeleteTextures(1, &DrawRenderTexture);\n  if (ReadRenderTexture) glDeleteTextures(1, &ReadRenderTexture);\n  if (DrawRT) GLC::DeleteFramebuffers(1, &DrawRT);\n  if (ReadRT) GLC::DeleteFramebuffers(1, &ReadRT);\n  if (StencilBuffer) glDeleteRenderbuffers(1, &StencilBuffer);\n  glDeleteTextures((GLsizei)GLC::FramebufferTextures.size(),\n                   GLC::FramebufferTextures.data());\n  GLC::FramebufferTextures.fill(0);\n  GLC::DeleteFramebuffers((GLsizei)GLC::Framebuffers.size(),\n                          GLC::Framebuffers.data());\n  GLC::Framebuffers.fill(0);\n  glDeleteRenderbuffers((GLsizei)GLC::StencilBuffers.size(),\n                        GLC::StencilBuffers.data());\n  GLC::StencilBuffers.fill(0);\n  DrawRenderTexture = ReadRenderTexture = DrawRT = ReadRT = 0;\n}\n\nvoid GLWindow::SwapRTs() {\n  std::swap(DrawRT, ReadRT);\n  std::swap(DrawRenderTexture, ReadRenderTexture);\n\n  GLC::BindFramebuffer(GL_DRAW_FRAMEBUFFER, DrawRT);\n  GLC::BindFramebuffer(GL_READ_FRAMEBUFFER, ReadRT);\n}\n\nvoid GLWindow::Update() {\n  UpdateDimensions();\n\n  Rect viewport = GetViewport();\n\n  if (WindowDimensionsChanged) {\n    CleanFBOs();\n\n    auto createFBO = [&](GLuint& frameBufId, GLuint& texture, int bufferType) {\n      glGenFramebuffers(1, &frameBufId);\n      GLC::BindFramebuffer(bufferType, frameBufId);\n      glGenTextures(1, &texture);\n\n      glBindTexture(GL_TEXTURE_2D, texture);\n      glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, viewport.Width, viewport.Height,\n                   0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);\n\n      glFramebufferTexture2D(bufferType, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,\n                             texture, 0);\n      glFramebufferRenderbuffer(bufferType, GL_STENCIL_ATTACHMENT,\n                                GL_RENDERBUFFER, StencilBuffer);\n    };\n\n    glGenRenderbuffers(1, &StencilBuffer);\n    glBindRenderbuffer(GL_RENDERBUFFER, StencilBuffer);\n    glRenderbufferStorage(GL_RENDERBUFFER, GL_STENCIL_INDEX8, viewport.Width,\n                          viewport.Height);\n    createFBO(ReadRT, ReadRenderTexture, GL_READ_FRAMEBUFFER);\n    createFBO(DrawRT, DrawRenderTexture, GL_DRAW_FRAMEBUFFER);\n    GLC::InitializeFramebuffers();\n  }\n\n  glClearColor(0.0f, 0.0f, 0.0f, 1.0f);\n\n  if (viewport.X != 0 || viewport.Y != 0) {\n    // Clear outside letter-/pillarbox\n    // Unfortunately we seem to need to do this every frame - I get content\n    // flickering into the margins on Linux otherwise\n    GLC::BindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);\n    glViewport(0, 0, WindowWidth, WindowHeight);\n    glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);\n  }\n\n  SwapRTs();\n\n  glViewport(0, 0, viewport.Width, viewport.Height);\n  glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);\n\n  glEnable(GL_BLEND);\n\n  if (Profile::Vm::GameInstructionSet == Vm::InstructionSet::CHLCC) {\n    glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE);\n  } else {\n    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);\n  }\n}\n\nvoid GLWindow::Draw() {\n#ifndef IMPACTO_DISABLE_IMGUI\n  ImGui::Render();\n  ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());\n#endif\n\n  GLC::BindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);\n  GLC::BindFramebuffer(GL_READ_FRAMEBUFFER, DrawRT);\n\n  Rect viewport = GetViewport();\n\n  glBlitFramebuffer(0, 0, viewport.Width, viewport.Height, viewport.X,\n                    viewport.Y, viewport.Width + viewport.X,\n                    viewport.Height + viewport.Y, GL_COLOR_BUFFER_BIT,\n                    GL_NEAREST);\n\n#ifndef IMPACTO_DISABLE_IMGUI\n  if (ImGui::GetIO().ConfigFlags & ImGuiConfigFlags_ViewportsEnable) {\n    ImGui::UpdatePlatformWindows();\n    ImGui::RenderPlatformWindowsDefault();\n    SDL_GL_MakeCurrent(SDLWindow, GLContext);\n  }\n#endif\n\n  SDL_GL_SwapWindow(SDLWindow);\n}\n\nvoid GLWindow::Shutdown() {\n  CleanFBOs();\n  SDL_GL_DeleteContext(GLContext);\n  SDL_DestroyWindow(SDLWindow);\n  SDL_Quit();\n  // TODO move exit to users\n  exit(0);\n}\n\n}  // namespace OpenGL\n}  // namespace Impacto"
  },
  {
    "path": "src/renderer/opengl/window.h",
    "content": "#pragma once\n\n#include \"../window.h\"\n\nnamespace Impacto {\nnamespace OpenGL {\n\nclass GLWindow : public BaseWindow {\n public:\n  void Init() override;\n  void SetDimensions(int width, int height, int msaa,\n                     float renderScale) override;\n  RectF GetViewport() override;\n  RectF GetScaledViewport() override;\n  void SwapRTs() override;\n  void Update() override;\n  void Draw() override;\n  void Shutdown() override;\n\n  SDL_GLContext GLContext;\n\n  // FBO of current render target for drawing onto\n  GLuint DrawRT;\n  // FBO of current render target for reading from\n  GLuint ReadRT;\n  // Texture associated with ReadRT\n  GLuint ReadRenderTexture;\n  // Stencil buffer associated with both ReadRT and DrawRT\n  GLuint StencilBuffer;\n\n private:\n  void UpdateDimensions() override;\n  void TryCreateGL(GraphicsApi api);\n  void CleanFBOs();\n\n  [[maybe_unused]] bool GLDebug = false;\n  GLuint DrawRenderTexture = 0;\n};\n\n}  // namespace OpenGL\n}  // namespace Impacto"
  },
  {
    "path": "src/renderer/opengl/yuvframe.cpp",
    "content": "#include \"yuvframe.h\"\n#include \"../../log.h\"\n\n#include <glad/glad.h>\n\nnamespace Impacto {\nnamespace OpenGL {\n\nvoid GLYUVFrame::Init(float width, float height) {\n  Width = width;\n  Height = height;\n  GLuint yuv[3];\n\n  glGenTextures(3, yuv);\n  LumaId = yuv[0];\n  CbId = yuv[1];\n  CrId = yuv[2];\n}\n\nvoid GLYUVFrame::Submit(const void* luma, const void* cb, const void* cr) {\n  glBindTexture(GL_TEXTURE_2D, LumaId);\n  glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, (GLsizei)Width, (GLsizei)Height, 0,\n               GL_RED, GL_UNSIGNED_BYTE, luma);\n  glGenerateMipmap(GL_TEXTURE_2D);\n\n  glBindTexture(GL_TEXTURE_2D, CbId);\n  glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, (GLsizei)(Width / 2),\n               (GLsizei)(Height / 2), 0, GL_RED, GL_UNSIGNED_BYTE, cb);\n  glGenerateMipmap(GL_TEXTURE_2D);\n\n  glBindTexture(GL_TEXTURE_2D, CrId);\n  glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, (GLsizei)(Width / 2),\n               (GLsizei)(Height / 2), 0, GL_RED, GL_UNSIGNED_BYTE, cr);\n  glGenerateMipmap(GL_TEXTURE_2D);\n}\n\nvoid GLYUVFrame::Release() {\n  GLuint yuv[3];\n  yuv[0] = LumaId;\n  yuv[1] = CbId;\n  yuv[2] = CrId;\n  glDeleteTextures(3, yuv);\n}\n\n}  // namespace OpenGL\n}  // namespace Impacto"
  },
  {
    "path": "src/renderer/opengl/yuvframe.h",
    "content": "#pragma once\n\n#include \"../yuvframe.h\"\n\nnamespace Impacto {\nnamespace OpenGL {\n\nclass GLYUVFrame : public YUVFrame {\n public:\n  void Init(float width, float height) override;\n\n  void Submit(const void* luma, const void* cb, const void* cr) override;\n  void Release() override;\n};\n\n}  // namespace OpenGL\n}  // namespace Impacto"
  },
  {
    "path": "src/renderer/renderer.cpp",
    "content": "#include \"renderer.h\"\n\n#include \"../profile/game.h\"\n#include \"../log.h\"\n\n#ifndef IMPACTO_DISABLE_OPENGL\n#include \"opengl/renderer.h\"\n#endif\n#ifndef IMPACTO_DISABLE_VULKAN\n#include \"vulkan/renderer.h\"\n#endif\n#ifndef IMPACTO_DISABLE_DX9\n#include \"dx9/renderer.h\"\n#endif\n\n#include <numeric>\n\nnamespace Impacto {\n\nvoid InitRenderer() {\n  switch (Profile::ActiveRenderer) {\n#ifndef IMPACTO_DISABLE_OPENGL\n    case RendererType::OpenGL:\n      Renderer = new OpenGL::Renderer();\n      break;\n#endif\n#ifndef IMPACTO_DISABLE_VULKAN\n    case RendererType::Vulkan:\n      Renderer = new Vulkan::Renderer();\n      break;\n#endif\n#ifndef IMPACTO_DISABLE_DX9\n    case RendererType::DirectX9:\n      Renderer = new DirectX9::Renderer();\n      break;\n#endif\n    default:\n      ImpLog(LogLevel::Error, LogChannel::Render,\n             \"Unknown or unsupported renderer selected!\\n\");\n      exit(1);\n  }\n\n  Renderer->Init();\n}\n\nstatic void InsertQuad(const std::span<VertexBufferSprites, 4> vertices,\n                       const CornersQuad position, const CornersQuad uv,\n                       const glm::vec4 tint,\n                       const CornersQuad maskUV = RectF()) {\n  vertices[0] = {\n      .Position = position.BottomLeft,\n      .UV = uv.BottomLeft,\n      .Tint = tint,\n      .MaskUV = maskUV.BottomLeft,\n  };\n  vertices[1] = {\n      .Position = position.TopLeft,\n      .UV = uv.TopLeft,\n      .Tint = tint,\n      .MaskUV = maskUV.TopLeft,\n  };\n  vertices[2] = {\n      .Position = position.TopRight,\n      .UV = uv.TopRight,\n      .Tint = tint,\n      .MaskUV = maskUV.TopRight,\n  };\n  vertices[3] = {\n      .Position = position.BottomRight,\n      .UV = uv.BottomRight,\n      .Tint = tint,\n      .MaskUV = maskUV.BottomRight,\n  };\n}\n\nvoid BaseRenderer::DrawCCMessageBox(Sprite const& sprite, Sprite const& mask,\n                                    glm::vec2 topLeft, glm::vec4 tint,\n                                    int alpha, int fadeRange, float effectCt,\n                                    glm::vec2 scale) {\n  const RectF dest =\n      sprite.ScaledBounds().Scale(scale, {0.0f, 0.0f}).Translate(topLeft);\n  DrawCCMessageBox(sprite, mask, dest, tint, alpha, fadeRange, effectCt);\n}\n\nvoid BaseRenderer::DrawConvexShape(const std::span<const glm::vec2> vertices,\n                                   const glm::mat4 transformation,\n                                   const glm::vec4 color) {\n  assert(vertices.size() >= 3);\n\n  if (vertices.size() == 4) {\n    CornersQuad quad = {vertices[0], vertices[1], vertices[2], vertices[3]};\n    quad.Transform(transformation);\n    DrawQuad(quad, color);\n\n    return;\n  }\n\n  std::vector<VertexBufferSprites> vertexAttributes;\n  vertexAttributes.reserve(vertices.size());\n  std::transform(\n      vertices.begin(), vertices.end(), std::back_inserter(vertexAttributes),\n      [color, transformation](const glm::vec2 pos) {\n        return VertexBufferSprites{\n            .Position = glm::vec2(transformation * glm::vec4(pos, 0.0f, 1.0f)),\n            .Tint = color,\n        };\n      });\n\n  if (vertices.size() == 3) {\n    DrawPrimitives(RectSprite.Sheet, ShaderProgramType::Sprite,\n                   vertexAttributes, std::array<uint16_t, 3>{0, 1, 2});\n    return;\n  }\n\n  std::vector<uint16_t> indices;\n  indices.reserve(vertices.size() * 3);\n\n  const glm::vec2 centerOfMass =\n      std::reduce(vertices.begin(), vertices.end()) / (float)vertices.size();\n\n  const uint16_t centerOfMassIndex = (uint16_t)vertices.size();\n  vertexAttributes.emplace_back(VertexBufferSprites{\n      .Position = centerOfMass,\n      .Tint = color,\n  });\n\n  for (uint16_t i = 0; i < centerOfMassIndex; i++) {\n    const uint16_t nextIndex = (i != centerOfMassIndex - 1) ? i + 1 : 0;\n    indices.insert(indices.end(), {centerOfMassIndex, i, nextIndex});\n  }\n\n  DrawPrimitives(RectSprite.Sheet, ShaderProgramType::Sprite, vertexAttributes,\n                 indices);\n}\n\nvoid BaseRenderer::DrawQuad(const CornersQuad& dest, const glm::vec4 color) {\n  DrawSprite(RectSprite, dest, color);\n}\n\nvoid BaseRenderer::DrawProcessedText(std::span<const ProcessedTextGlyph> text,\n                                     Font* font, float opacity,\n                                     RendererOutlineMode outlineMode,\n                                     bool smoothstepGlyphOpacity,\n                                     SpriteSheet* maskedSheet) {\n  switch (font->Type) {\n    case FontType::Basic:\n      DrawProcessedText_BasicFont(text, (BasicFont*)font, opacity, outlineMode,\n                                  smoothstepGlyphOpacity, opacity, maskedSheet);\n      break;\n    case FontType::LB: {\n      DrawProcessedText_LBFont(text, (LBFont*)font, opacity, outlineMode,\n                               smoothstepGlyphOpacity, opacity, maskedSheet);\n    }\n  }\n}\n\nvoid BaseRenderer::DrawProcessedText(std::span<const ProcessedTextGlyph> text,\n                                     Font* font, float opacity,\n                                     float outlineOpacity,\n                                     RendererOutlineMode outlineMode,\n                                     bool smoothstepGlyphOpacity,\n                                     SpriteSheet* maskedSheet) {\n  switch (font->Type) {\n    case FontType::Basic:\n      DrawProcessedText_BasicFont(text, (BasicFont*)font, opacity, outlineMode,\n                                  smoothstepGlyphOpacity, outlineOpacity,\n                                  maskedSheet);\n      break;\n    case FontType::LB: {\n      DrawProcessedText_LBFont(text, (LBFont*)font, opacity, outlineMode,\n                               smoothstepGlyphOpacity, outlineOpacity,\n                               maskedSheet);\n    }\n  }\n}\n\nstatic std::vector<size_t> GetVisibleGlyphIds(\n    std::span<const ProcessedTextGlyph> text) {\n  const static RectF viewport{0.0f, 0.0f, Profile::DesignWidth,\n                              Profile::DesignHeight};\n\n  std::vector<size_t> visibleGlyphIds;\n  for (size_t i = 0; i < text.size(); i++) {\n    if (text[i].DestRect.Intersects(viewport)) visibleGlyphIds.push_back(i);\n  }\n  return visibleGlyphIds;\n}\n\nvoid BaseRenderer::DrawProcessedText_BasicFont(\n    std::span<const ProcessedTextGlyph> text, BasicFont* font, float opacity,\n    RendererOutlineMode outlineMode, bool smoothstepGlyphOpacity,\n    float outlineOpacity, SpriteSheet* maskedSheet) {\n  const std::vector<size_t> visibleGlyphIds = GetVisibleGlyphIds(text);\n  const size_t glyphCount = visibleGlyphIds.size();\n  if (glyphCount == 0) return;\n\n  const size_t vertexCount = glyphCount * 4;\n  const size_t indexCount = glyphCount * 6;\n  std::vector<VertexBufferSprites> vertices;\n  std::vector<uint16_t> indices;\n  vertices.resize(vertexCount);\n  indices.resize(indexCount);\n\n  for (uint16_t i = 0; i < glyphCount; i++) {\n    const uint16_t bl = i * 4;\n    const uint16_t tl = bl + 1;\n    const uint16_t tr = bl + 2;\n    const uint16_t br = bl + 3;\n\n    indices[i * 6 + 0] = bl;\n    indices[i * 6 + 1] = tl;\n    indices[i * 6 + 2] = tr;\n    indices[i * 6 + 3] = bl;\n    indices[i * 6 + 4] = tr;\n    indices[i * 6 + 5] = br;\n  }\n\n  for (size_t i = 0; i < glyphCount; i++) {\n    const ProcessedTextGlyph glyph = text[visibleGlyphIds[i]];\n\n    const CornersQuad dest = glyph.DestRect;\n    const CornersQuad destUV = font->Glyph(glyph.CharId).NormalizedBounds();\n    const CornersQuad maskUV = CornersQuad(dest).Scale(\n        {1.0f / Window->WindowWidth, 1.0f / Window->WindowHeight},\n        {0.0f, 0.0f});\n    glm::vec4 color = RgbIntToFloat(glyph.Colors.TextColor);\n    color.a = opacity * (smoothstepGlyphOpacity\n                             ? glm::smoothstep(0.0f, 1.0f, glyph.Opacity)\n                             : glyph.Opacity);\n\n    InsertQuad(std::span<VertexBufferSprites, 4>(vertices.begin() + i * 4, 4),\n               dest, destUV, color, maskUV);\n  }\n  uint16_t maxIndex = (uint16_t)vertexCount;\n\n  if (outlineMode != RendererOutlineMode::None) {\n    // Add outline to the front of the buffers\n    vertices.insert(vertices.end(), vertices.begin(), vertices.end());\n\n    indices.reserve(indexCount * 2);\n    std::transform(indices.begin(), indices.end(), std::back_inserter(indices),\n                   [maxIndex](uint16_t index) { return index + maxIndex; });\n    maxIndex += (uint16_t)vertexCount;\n\n    // Set the color of the outline\n    for (size_t i = 0; i < glyphCount; i++) {\n      const ProcessedTextGlyph glyph = text[visibleGlyphIds[i]];\n      glm::vec4 color = RgbIntToFloat(glyph.Colors.OutlineColor);\n      color.a =\n          outlineOpacity * (smoothstepGlyphOpacity\n                                ? glm::smoothstep(0.0f, 1.0f, glyph.Opacity)\n                                : glyph.Opacity);\n      const auto glyphStart = vertices.begin() + i * 4;\n      const auto glyphEnd = glyphStart + 4;\n      std::transform(glyphStart, glyphEnd, glyphStart, [color](auto vertex) {\n        vertex.Tint = color;\n        return vertex;\n      });\n    }\n\n    const auto translateVertex = [](VertexBufferSprites vertex,\n                                    const glm::vec2 offset) {\n      vertex.Position += offset;\n      return vertex;\n    };\n\n    switch (outlineMode) {\n      case RendererOutlineMode::Full: {\n        // Add bottom-right outline\n        vertices.insert(vertices.begin() + vertexCount, vertices.begin(),\n                        vertices.begin() + vertexCount);\n\n        indices.resize(indexCount * 3);\n        std::transform(indices.begin(), indices.begin() + indexCount,\n                       indices.begin() + indexCount * 2,\n                       [maxIndex](uint16_t index) { return index + maxIndex; });\n        maxIndex += (uint16_t)vertexCount;\n\n        // Translate outlines\n        const auto tlOutlineStart = vertices.begin();\n        const auto brOutlineStart = tlOutlineStart + vertexCount;\n        const auto foregroundStart = brOutlineStart + vertexCount;\n        std::transform(tlOutlineStart, brOutlineStart, tlOutlineStart,\n                       [translateVertex](auto vertex) {\n                         return translateVertex(vertex, {-1.0f, -1.0f});\n                       });\n        std::transform(brOutlineStart, foregroundStart, brOutlineStart,\n                       [translateVertex](auto vertex) {\n                         return translateVertex(vertex, {1.0f, 1.0f});\n                       });\n\n        break;\n      }\n\n      case RendererOutlineMode::BottomRight: {\n        const auto brOutlineStart = vertices.begin();\n        const auto foregroundStart = vertices.begin() + vertexCount;\n        std::transform(brOutlineStart, foregroundStart, brOutlineStart,\n                       [translateVertex](auto vertex) {\n                         return translateVertex(vertex, {1.0f, 1.0f});\n                       });\n\n        break;\n      }\n\n      default:\n        ImpLogSlow(LogLevel::Warning, LogChannel::Render,\n                   \"Unexpected outline mode!\");\n        break;\n    }\n  }\n\n  const ShaderProgramType shader = maskedSheet == nullptr\n                                       ? ShaderProgramType::Sprite\n                                       : ShaderProgramType::MaskedSpriteNoAlpha;\n  DrawPrimitives(font->Sheet, maskedSheet, shader, vertices, indices);\n}\n\nvoid BaseRenderer::DrawProcessedText_LBFont(\n    std::span<const ProcessedTextGlyph> text, LBFont* font, float opacity,\n    RendererOutlineMode outlineMode, bool smoothstepGlyphOpacity,\n    float outlineOpacity, SpriteSheet* maskedSheet) {\n  const std::vector<size_t> visibleGlyphIds = GetVisibleGlyphIds(text);\n  const size_t glyphCount = visibleGlyphIds.size();\n  if (glyphCount == 0) return;\n\n  const ShaderProgramType shader = maskedSheet == nullptr\n                                       ? ShaderProgramType::Sprite\n                                       : ShaderProgramType::MaskedSpriteNoAlpha;\n\n  const size_t vertexCount = glyphCount * 4;\n  const size_t indexCount = glyphCount * 6;\n  std::vector<VertexBufferSprites> vertices(vertexCount);\n  std::vector<uint16_t> indices(indexCount);\n\n  for (uint16_t i = 0; i < glyphCount; i++) {\n    const uint16_t bl = i * 4;\n    const uint16_t tl = bl + 1;\n    const uint16_t tr = bl + 2;\n    const uint16_t br = bl + 3;\n\n    indices[i * 6 + 0] = bl;\n    indices[i * 6 + 1] = tl;\n    indices[i * 6 + 2] = tr;\n    indices[i * 6 + 3] = bl;\n    indices[i * 6 + 4] = tr;\n    indices[i * 6 + 5] = br;\n  }\n\n  if (outlineMode != RendererOutlineMode::None) {\n    for (size_t i = 0; i < glyphCount; i++) {\n      const ProcessedTextGlyph glyph = text[visibleGlyphIds[i]];\n\n      glm::vec4 color = RgbIntToFloat(glyph.Colors.OutlineColor);\n      color.a =\n          outlineOpacity * (smoothstepGlyphOpacity\n                                ? glm::smoothstep(0.0f, 1.0f, glyph.Opacity)\n                                : glyph.Opacity);\n      const CornersQuad destUV =\n          font->OutlineGlyph(glyph.CharId).NormalizedBounds();\n\n      CornersQuad dest = RectF();\n      const glm::vec2 scale = {glyph.DestRect.Height / font->BitmapEmWidth,\n                               glyph.DestRect.Height / font->BitmapEmHeight};\n      switch (outlineMode) {\n        case RendererOutlineMode::Full: {\n          dest = RectF(font->OutlineOffset.x, font->OutlineOffset.y,\n                       font->OutlineCellWidth, font->OutlineCellHeight)\n                     .Scale(scale, {0.0f, 0.0f})\n                     .Translate(glyph.DestRect.GetPos());\n          break;\n        }\n\n        case RendererOutlineMode::BottomRight: {\n          dest = RectF(font->OutlineOffset.x * 3 / 4,\n                       font->OutlineOffset.y * 3 / 4,\n                       font->OutlineCellWidth + font->OutlineOffset.x / 2,\n                       font->OutlineCellHeight + font->OutlineOffset.y / 2)\n                     .Scale(scale, {0.0f, 0.0f})\n                     .Translate(glyph.DestRect.GetPos());\n          break;\n        }\n\n        default:\n          ImpLogSlow(LogLevel::Warning, LogChannel::Render,\n                     \"Unexpected outline mode!\");\n          break;\n      }\n\n      const CornersQuad maskUV = CornersQuad(dest).Scale(\n          {1.0f / Profile::DesignWidth, 1.0f / Profile::DesignHeight},\n          {0.0f, 0.0f});\n      InsertQuad(std::span<VertexBufferSprites, 4>(vertices.begin() + i * 4, 4),\n                 dest, destUV, color, maskUV);\n    }\n\n    DrawPrimitives(font->OutlineSheet, maskedSheet, shader, vertices, indices);\n  }\n\n  for (size_t i = 0; i < glyphCount; i++) {\n    const ProcessedTextGlyph glyph = text[visibleGlyphIds[i]];\n\n    glm::vec2 scale = {glyph.DestRect.Height / font->BitmapEmWidth,\n                       glyph.DestRect.Height / font->BitmapEmHeight};\n    CornersQuad dest = RectF(font->ForegroundOffset.x, font->ForegroundOffset.y,\n                             font->CellWidth, font->CellHeight)\n                           .Scale(scale, {0.0f, 0.0f})\n                           .Translate(glyph.DestRect.GetPos());\n\n    const CornersQuad destUV = font->Glyph(glyph.CharId).NormalizedBounds();\n\n    glm::vec4 color = RgbIntToFloat(glyph.Colors.TextColor);\n    color.a = opacity * (smoothstepGlyphOpacity\n                             ? glm::smoothstep(0.0f, 1.0f, glyph.Opacity)\n                             : glyph.Opacity);\n\n    const CornersQuad maskUV = CornersQuad(dest).Scale(\n        {1.0f / Profile::DesignWidth, 1.0f / Profile::DesignHeight},\n        {0.0f, 0.0f});\n    InsertQuad(std::span<VertexBufferSprites, 4>(vertices.begin() + i * 4, 4),\n               dest, destUV, color, maskUV);\n  }\n\n  DrawPrimitives(font->ForegroundSheet, maskedSheet, shader, vertices, indices);\n}\n\nvoid BaseRenderer::QuadSetPosition(CornersQuad quad, glm::vec2* const pos,\n                                   int stride) {\n  *(glm::vec2*)((uint8_t*)pos + 0 * stride) = DesignToNDC(quad.TopLeft);\n  *(glm::vec2*)((uint8_t*)pos + 1 * stride) = DesignToNDC(quad.BottomLeft);\n  *(glm::vec2*)((uint8_t*)pos + 2 * stride) = DesignToNDC(quad.BottomRight);\n  *(glm::vec2*)((uint8_t*)pos + 3 * stride) = DesignToNDC(quad.TopRight);\n}\n\nvoid BaseRenderer::QuadSetUV(const CornersQuad bounds,\n                             const glm::vec2 dimensions, glm::vec2* const uvs,\n                             const size_t stride) {\n  *(glm::vec2*)((uint8_t*)uvs + 0 * stride) = bounds.TopLeft / dimensions;\n  *(glm::vec2*)((uint8_t*)uvs + 1 * stride) = bounds.BottomLeft / dimensions;\n  *(glm::vec2*)((uint8_t*)uvs + 2 * stride) = bounds.BottomRight / dimensions;\n  *(glm::vec2*)((uint8_t*)uvs + 3 * stride) = bounds.TopRight / dimensions;\n}\n\n}  // namespace Impacto\n"
  },
  {
    "path": "src/renderer/renderer.h",
    "content": "#pragma once\n\n#include \"window.h\"\n\n#include \"3d/scene.h\"\n#include \"../spritesheet.h\"\n#include \"../text/text.h\"\n#include \"yuvframe.h\"\n#include \"nv12frame.h\"\n#include <span>\n\nnamespace Impacto {\n\ninline GraphicsApi GraphicsApiHint;\ninline GraphicsApi ActualGraphicsApi;\n\nenum class RendererOutlineMode { None, BottomRight, Full };\n\nenum class StencilBufferMode { Off, Test, Write };\n\nenum class TopologyMode : uint8_t { Triangles, TriangleStrips };\n\nconstexpr inline int MaxFramebuffers = 10;\n\nstruct VertexBufferSprites {\n  glm::vec2 Position = {0.0f, 0.0f};\n  glm::vec2 UV = {0.0f, 0.0f};\n  glm::vec4 Tint = glm::vec4(1.0f);\n  glm::vec2 MaskUV = {0.0f, 0.0f};\n};\n\nstruct PrimitiveData {\n  std::span<VertexBufferSprites> Vertices;\n  std::span<uint16_t> Indices;\n  template <typename T, typename U>\n  operator std::tuple<T, U>() {\n    return std::tuple<T, U>{Vertices, Indices};\n  }\n};\n\nenum class ShaderProgramType : int {\n  AdditiveMaskedSprite,\n  CCMessageBoxSprite,\n  CHLCCMenuBackground,\n  ColorBurnMaskedSprite,\n  ColorDodgeMaskedSprite,\n  ColorMaskedSprite,\n  HardLightMaskedSprite,\n  LinearBurnMaskedSprite,\n  MaskedSprite,\n  MaskedSpriteBinary,\n  MaskedSpriteNoAlpha,\n  OverlayMaskedSprite,\n  ScreenMaskedSprite,\n  SoftLightMaskedSprite,\n  Sprite,\n  SpriteInverted,\n  YUVFrame,\n  GaussianBlur,\n  Mosaic,\n};\n\nenum class RendererBlendMode { Normal, Additive, Premultiplied };\nenum class RendererBlurDirection { Horizontal, Vertical };\n\nclass BaseRenderer {\n public:\n  virtual void Init() = 0;\n  virtual void Shutdown() = 0;\n\n#ifndef IMPACTO_DISABLE_IMGUI\n  virtual void ImGuiBeginFrame() = 0;\n#endif\n\n  virtual void BeginFrame() = 0;\n  virtual void BeginFrame2D() = 0;\n  virtual void EndFrame() = 0;\n\n  virtual uint32_t SubmitTexture(TexFmt format, uint8_t* buffer, int width,\n                                 int height) = 0;\n\n  std::vector<uint8_t> GetSpriteSheetImage(SpriteSheet const& sheet) {\n    std::vector<uint8_t> result(\n        (size_t)(sheet.DesignWidth * sheet.DesignHeight * 4));\n    GetSpriteSheetImage(sheet, result);\n    return result;\n  };\n\n  virtual int GetSpriteSheetImage(SpriteSheet const& sheet,\n                                  std::span<uint8_t> outBuffer) = 0;\n  virtual void FreeTexture(uint32_t id) = 0;\n  virtual YUVFrame* CreateYUVFrame(float width, float height) = 0;\n  virtual NV12Frame* CreateNV12Frame(float width, float height) = 0;\n\n  virtual void DrawSprite(const Sprite& sprite, const CornersQuad& dest,\n                          glm::mat4 transformation,\n                          std::span<const glm::vec4, 4> tints,\n                          glm::vec3 colorShift = glm::vec3(0.0f),\n                          bool inverted = false, bool disableBlend = false,\n                          bool textureWrapRepeat = false) = 0;\n\n  void DrawSprite(const Sprite& sprite, const CornersQuad& dest,\n                  glm::mat4 transformation = glm::mat4(1.0f),\n                  glm::vec4 tint = glm::vec4(1.0f),\n                  glm::vec3 colorShift = glm::vec3(0.0f), bool inverted = false,\n                  bool disableBlend = false) {\n    DrawSprite(sprite, dest, transformation,\n               std::array<glm::vec4, 4>{tint, tint, tint, tint}, colorShift,\n               inverted, disableBlend);\n  }\n  void DrawSprite(const Sprite& sprite, const CornersQuad& dest, glm::vec4 tint,\n                  glm::vec3 colorShift = glm::vec3(0.0f), bool inverted = false,\n                  bool disableBlend = false) {\n    DrawSprite(sprite, dest, glm::mat4(1.0f), tint, colorShift, inverted,\n               disableBlend);\n  }\n  void DrawSprite(const Sprite& sprite, glm::mat4 transformation,\n                  glm::vec4 tint = glm::vec4(1.0f), bool inverted = false,\n                  bool disableBlend = false) {\n    DrawSprite(sprite, sprite.ScaledBounds(), transformation, tint,\n               glm::vec3(0.0f), inverted, disableBlend);\n  }\n  void DrawSprite(const Sprite& sprite, glm::vec2 topLeft,\n                  glm::vec4 tint = glm::vec4(1.0f), bool inverted = false,\n                  bool disableBlend = false) {\n    DrawSprite(sprite, sprite.ScaledBounds().Translate(topLeft),\n               glm::mat4(1.0f), tint, glm::vec3(0.0f), inverted, disableBlend);\n  }\n\n  virtual void DrawMaskedSprite(const Sprite& sprite, const Sprite& mask,\n                                const CornersQuad& spriteDest,\n                                const CornersQuad& maskDest, int alpha,\n                                int fadeRange, glm::mat4 spriteTransformation,\n                                glm::mat4 maskTransformation,\n                                std::span<const glm::vec4, 4> tints,\n                                bool isInverted = false,\n                                bool isSameTexture = false) = 0;\n\n  void DrawMaskedSprite(const Sprite& sprite, const Sprite& mask,\n                        const CornersQuad& spriteDest, int alpha, int fadeRange,\n                        glm::mat4 spriteTransformation = glm::mat4(1.0f),\n                        glm::vec4 tint = glm::vec4(1.0f),\n                        bool isInverted = false, bool isSameTexture = false) {\n    DrawMaskedSprite(sprite, mask, spriteDest, sprite.ScaledBounds(), alpha,\n                     fadeRange, spriteTransformation, glm::mat4(1.0f),\n                     std::array<glm::vec4, 4>{tint, tint, tint, tint},\n                     isInverted, isSameTexture);\n  }\n  void DrawMaskedSprite(const Sprite& sprite, const Sprite& mask, int alpha,\n                        int fadeRange, glm::mat4 spriteTransformation,\n                        glm::mat4 maskTransformation,\n                        glm::vec4 tint = glm::vec4(1.0f),\n                        bool isInverted = false, bool isSameTexture = false) {\n    DrawMaskedSprite(sprite, mask, sprite.ScaledBounds(), sprite.ScaledBounds(),\n                     alpha, fadeRange, spriteTransformation, maskTransformation,\n                     std::array<glm::vec4, 4>{tint, tint, tint, tint},\n                     isInverted, isSameTexture);\n  }\n  void DrawMaskedSprite(const Sprite& sprite, const Sprite& mask, int alpha,\n                        int fadeRange, glm::mat4 spriteTransformation,\n                        glm::vec4 tint = glm::vec4(1.0f),\n                        bool isInverted = false, bool isSameTexture = false) {\n    DrawMaskedSprite(sprite, mask, alpha, fadeRange, spriteTransformation,\n                     glm::mat4(1.0f), tint, isInverted, isSameTexture);\n  }\n  void DrawMaskedSprite(const Sprite& sprite, const Sprite& mask, int alpha,\n                        int fadeRange, glm::vec2 spriteTopLeft,\n                        glm::vec2 maskTopLeft, glm::vec4 tint = glm::vec4(1.0f),\n                        bool isInverted = false, bool isSameTexture = false) {\n    DrawMaskedSprite(sprite, mask,\n                     sprite.ScaledBounds().Translate(spriteTopLeft),\n                     sprite.ScaledBounds().Translate(maskTopLeft), alpha,\n                     fadeRange, glm::mat4(1.0f), glm::mat4(1.0f),\n                     std::array<glm::vec4, 4>{tint, tint, tint, tint},\n                     isInverted, isSameTexture);\n  }\n  void DrawMaskedSprite(const Sprite& sprite, const Sprite& mask, int alpha,\n                        int fadeRange, glm::vec2 spriteTopLeft,\n                        glm::vec4 tint = glm::vec4(1.0f),\n                        bool isInverted = false, bool isSameTexture = false) {\n    DrawMaskedSprite(sprite, mask, alpha, fadeRange, spriteTopLeft,\n                     {0.0f, 0.0f}, tint, isInverted, isSameTexture);\n  }\n\n  virtual void DrawMaskedBinarySprite(\n      const Sprite& sprite, const Sprite& mask, const CornersQuad& spriteDest,\n      const CornersQuad& maskDest, glm::mat4 spriteTransformation,\n      std::optional<glm::mat4> maskTransformation,\n      std::span<const glm::vec4, 4> tints, bool isInverted = false) = 0;\n\n  void DrawMaskedBinarySprite(const Sprite& sprite, const Sprite& mask,\n                              glm::mat4 spriteTransformation,\n                              std::optional<glm::mat4> maskTransformation,\n                              glm::vec4 tint = glm::vec4(1.0f),\n                              bool isInverted = false) {\n    DrawMaskedBinarySprite(\n        sprite, mask, sprite.ScaledBounds(), mask.ScaledBounds(),\n        spriteTransformation, maskTransformation,\n        std::array<glm::vec4, 4>{tint, tint, tint, tint}, isInverted);\n  }\n\n  virtual void DrawMaskedSpriteOverlay(\n      const Sprite& sprite, const Sprite& mask, const CornersQuad& spriteDest,\n      const CornersQuad& maskDest, int alpha, int fadeRange,\n      glm::mat4 spriteTransformation, glm::mat4 maskTransformation,\n      std::span<const glm::vec4, 4> tints, bool isInverted = false,\n      bool useMaskAlpha = true) = 0;\n\n  void DrawMaskedSpriteOverlay(const Sprite& sprite, const Sprite& mask,\n                               const CornersQuad& spriteDest,\n                               const CornersQuad& maskDest, int alpha,\n                               int fadeRange, glm::mat4 spriteTransformation,\n                               glm::mat4 maskTransformation,\n                               glm::vec4 tint = glm::vec4(1.0f),\n                               bool isInverted = false,\n                               bool useMaskAlpha = true) {\n    DrawMaskedSpriteOverlay(sprite, mask, spriteDest, maskDest, alpha,\n                            fadeRange, spriteTransformation, maskTransformation,\n                            std::array<glm::vec4, 4>{tint, tint, tint, tint},\n                            isInverted, useMaskAlpha);\n  }\n\n  void DrawMaskedSpriteOverlay(const Sprite& sprite, const Sprite& mask,\n                               const CornersQuad& spriteDest,\n                               const CornersQuad& maskDest, int alpha,\n                               int fadeRange, glm::mat4 spriteTransformation,\n                               glm::vec4 tint = glm::vec4(1.0f),\n                               bool isInverted = false,\n                               bool useMaskAlpha = true) {\n    DrawMaskedSpriteOverlay(sprite, mask, spriteDest, maskDest, alpha,\n                            fadeRange, spriteTransformation, glm::mat4(1.0f),\n                            std::array<glm::vec4, 4>{tint, tint, tint, tint},\n                            isInverted, useMaskAlpha);\n  }\n  void DrawMaskedSpriteOverlay(const Sprite& sprite, const Sprite& mask,\n                               const CornersQuad& spriteDest, int alpha,\n                               int fadeRange,\n                               glm::mat4 spriteTransformation = glm::mat4(1.0f),\n                               glm::vec4 tint = glm::vec4(1.0f),\n                               bool isInverted = false,\n                               bool useMaskAlpha = true) {\n    DrawMaskedSpriteOverlay(sprite, mask, spriteDest, spriteDest, alpha,\n                            fadeRange, spriteTransformation, glm::mat4(1.0f),\n                            std::array<glm::vec4, 4>{tint, tint, tint, tint},\n                            isInverted, useMaskAlpha);\n  }\n  void DrawMaskedSpriteOverlay(const Sprite& sprite, const Sprite& mask,\n                               int alpha, int fadeRange,\n                               glm::mat4 spriteTransformation,\n                               glm::mat4 maskTransformation,\n                               glm::vec4 tint = glm::vec4(1.0f),\n                               bool isInverted = false,\n                               bool useMaskAlpha = true) {\n    DrawMaskedSpriteOverlay(sprite, mask, sprite.ScaledBounds(),\n                            mask.ScaledBounds(), alpha, fadeRange,\n                            spriteTransformation, maskTransformation,\n                            std::array<glm::vec4, 4>{tint, tint, tint, tint},\n                            isInverted, useMaskAlpha);\n  }\n  void DrawMaskedSpriteOverlay(const Sprite& sprite, const Sprite& mask,\n                               int alpha, int fadeRange,\n                               glm::mat4 spriteTransformation,\n                               glm::vec4 tint = glm::vec4(1.0f),\n                               bool isInverted = false,\n                               bool useMaskAlpha = true) {\n    DrawMaskedSpriteOverlay(sprite, mask, alpha, fadeRange,\n                            spriteTransformation, glm::mat4(1.0f), tint,\n                            isInverted, useMaskAlpha);\n  }\n  void DrawMaskedSpriteOverlay(const Sprite& sprite, const Sprite& mask,\n                               int alpha, int fadeRange,\n                               glm::vec2 spriteTopLeft, glm::vec2 maskTopLeft,\n                               glm::vec4 tint = glm::vec4(1.0f),\n                               bool isInverted = false,\n                               bool useMaskAlpha = true) {\n    DrawMaskedSpriteOverlay(sprite, mask,\n                            sprite.ScaledBounds().Translate(spriteTopLeft),\n                            mask.ScaledBounds().Translate(maskTopLeft), alpha,\n                            fadeRange, glm::mat4(1.0f), glm::mat4(1.0f),\n                            std::array<glm::vec4, 4>{tint, tint, tint, tint},\n                            isInverted, useMaskAlpha);\n  }\n  void DrawMaskedSpriteOverlay(const Sprite& sprite, const Sprite& mask,\n                               int alpha, int fadeRange,\n                               glm::vec2 spriteTopLeft,\n                               glm::vec4 tint = glm::vec4(1.0f),\n                               bool isInverted = false,\n                               bool useMaskAlpha = true) {\n    DrawMaskedSpriteOverlay(sprite, mask, alpha, fadeRange, spriteTopLeft,\n                            {0.0f, 0.0f}, tint, isInverted, useMaskAlpha);\n  }\n\n  virtual void DrawPrimitives(const SpriteSheet& sheet, const SpriteSheet* mask,\n                              ShaderProgramType shaderType,\n                              std::span<const VertexBufferSprites> vertices,\n                              std::span<const uint16_t> indices,\n                              glm::mat4 spriteTransformation = glm::mat4(1.0f),\n                              glm::mat4 maskTransformation = glm::mat4(1.0f),\n                              bool inverted = false,\n                              TopologyMode topology = TopologyMode::Triangles,\n                              bool textureWrapRepeat = false) = 0;\n\n  virtual void DrawPrimitives(const SpriteSheet& sheet,\n                              ShaderProgramType shaderType,\n                              std::span<const VertexBufferSprites> vertices,\n                              std::span<const uint16_t> indices,\n                              glm::mat4 transformation = glm::mat4(1.0f),\n                              bool inverted = false,\n                              bool textureWrapRepeat = false) {\n    DrawPrimitives(sheet, nullptr, shaderType, vertices, indices,\n                   transformation, glm::mat4(1.0f), inverted,\n                   TopologyMode::Triangles, textureWrapRepeat);\n  }\n\n  void DrawPrimitives(const SpriteSheet& sheet, const SpriteSheet* mask,\n                      ShaderProgramType shaderType,\n                      std::span<const VertexBufferSprites> vertices,\n                      std::span<const uint16_t> indices, glm::vec2 offset,\n                      bool inverted = false, bool textureWrapRepeat = false) {\n    DrawPrimitives(sheet, mask, shaderType, vertices, indices,\n                   glm::translate(glm::mat4(1.0f), glm::vec3(offset, 0.0f)),\n                   glm::mat4(1.0f), inverted, TopologyMode::Triangles,\n                   textureWrapRepeat);\n  }\n\n  void DrawPrimitives(const SpriteSheet& sheet, ShaderProgramType shaderType,\n                      std::span<const VertexBufferSprites> vertices,\n                      std::span<const uint16_t> indices, glm::vec2 offset,\n                      bool inverted = false, bool textureWrapRepeat = false) {\n    DrawPrimitives(sheet, shaderType, vertices, indices,\n                   glm::translate(glm::mat4(1.0f), glm::vec3(offset, 0.0f)),\n                   inverted, textureWrapRepeat);\n  }\n\n  void DrawConvexShape(std::span<const glm::vec2> vertices,\n                       glm::mat4 transformation, glm::vec4 color);\n  void DrawConvexShape(std::span<const glm::vec2> vertices, glm::vec4 color) {\n    DrawConvexShape(vertices, glm::mat4(1.0f), color);\n  }\n\n  void DrawQuad(const CornersQuad& dest, glm::vec4 color);\n\n  void DrawCCMessageBox(Sprite const& sprite, Sprite const& mask,\n                        glm::vec2 topLeft, glm::vec4 tint, int alpha,\n                        int fadeRange, float effectCt,\n                        glm::vec2 scale = glm::vec2(1.0));\n  virtual void DrawCCMessageBox(Sprite const& sprite, Sprite const& mask,\n                                RectF const& dest, glm::vec4 tint, int alpha,\n                                int fadeRange, float effectCt) = 0;\n\n  virtual void DrawCHLCCMenuBackground(const Sprite& sprite, const Sprite& mask,\n                                       const RectF& dest, float alpha) = 0;\n\n  virtual void DrawBlurredSprite(const Sprite& sprite, const CornersQuad& dest,\n                                 glm::mat4 transformation,\n                                 RendererBlurDirection blurDirection,\n                                 glm::vec4 tint) = 0;\n\n  virtual void DrawMosaic(const Sprite& sprite, const CornersQuad dest,\n                          float tileSize, glm::mat4 transformation,\n                          glm::vec4 tint) = 0;\n\n  void DrawProcessedText_BasicFont(std::span<const ProcessedTextGlyph> text,\n                                   BasicFont* font, float opacity,\n                                   RendererOutlineMode outlineMode,\n                                   bool smoothstepGlyphOpacity,\n                                   float outlineOpacity,\n                                   SpriteSheet* maskedSheet);\n\n  void DrawProcessedText_LBFont(std::span<const ProcessedTextGlyph> text,\n                                LBFont* font, float opacity,\n                                RendererOutlineMode outlineMode,\n                                bool smoothstepGlyphOpacity,\n                                float outlineOpacity, SpriteSheet* maskedSheet);\n  void DrawProcessedText(\n      std::span<const ProcessedTextGlyph> text, Font* font,\n      float opacity = 1.0f,\n      RendererOutlineMode outlineMode = RendererOutlineMode::None,\n      bool smoothstepGlyphOpacity = true, SpriteSheet* maskedSheet = nullptr);\n\n  void DrawProcessedText(\n      std::span<const ProcessedTextGlyph> text, Font* font, float opacity,\n      float outlineOpacity,\n      RendererOutlineMode outlineMode = RendererOutlineMode::None,\n      bool smoothstepGlyphOpacity = true, SpriteSheet* maskedSheet = nullptr);\n\n  virtual void DrawVideoTexture(const YUVFrame& frame, const RectF& dest,\n                                glm::vec4 tint, bool alphaVideo = false) = 0;\n  virtual void DrawVideoTexture(const NV12Frame& frame, const RectF& dest,\n                                glm::vec4 tint, bool alphaVideo = false) = 0;\n\n  virtual void DrawSubtitleGlyph(const Sprite& sprite, const CornersQuad& dest,\n                                 glm::mat4 transformation, glm::vec4 tint) = 0;\n\n  void DrawSubtitleGlyph(const Sprite& sprite, glm::vec2 topLeft,\n                         glm::vec4 tint) {\n    DrawSubtitleGlyph(sprite, sprite.ScaledBounds().Translate(topLeft),\n                      glm::mat4(1.0f), tint);\n  }\n\n  virtual void CaptureScreencap(Sprite& sprite) = 0;\n\n  virtual void SetFramebuffer(size_t buffer) = 0;\n  virtual int GetFramebufferTexture(size_t buffer) = 0;\n\n  virtual void EnableScissor() = 0;\n  virtual void SetScissorRect(RectF const& rect) = 0;\n  virtual void DisableScissor() = 0;\n\n  virtual void SetStencilMode(StencilBufferMode mode) = 0;\n  virtual void ClearStencilBuffer() = 0;\n\n  virtual void SetBlendMode(RendererBlendMode blendMode) = 0;\n\n  virtual void Clear(glm::vec4 color) = 0;\n\n  IScene3D* Scene = 0;\n  bool IsInit = false;\n\n protected:\n  virtual void Flush() = 0;\n\n  static void QuadSetUV(CornersQuad spriteBounds, glm::vec2 designDimensions,\n                        glm::vec2* uvs, size_t stride);\n\n  static void QuadSetUVFlipped(CornersQuad spriteBounds,\n                               glm::vec2 designDimensions, glm::vec2* uvs,\n                               size_t stride) {\n    QuadSetUV({spriteBounds.BottomLeft, spriteBounds.TopLeft,\n               spriteBounds.BottomRight, spriteBounds.TopRight},\n              designDimensions, uvs, stride);\n  }\n\n  void QuadSetPosition(CornersQuad destQuad, glm::vec2* positions, int stride);\n\n  virtual glm::vec2 DesignToNDC(glm::vec2 designCoord) const {\n    return designCoord;\n  }\n\n  Sprite RectSprite;\n};\n\ninline BaseRenderer* Renderer;\ninline BaseWindow* Window;\n\nvoid InitRenderer();\n\n}  // namespace Impacto\n"
  },
  {
    "path": "src/renderer/vulkan/3d/renderable3d.cpp",
    "content": "#include <glm/glm.hpp>\n#include <glm/gtc/type_ptr.hpp>\n\n#include \"renderable3d.h\"\n\n#include \"../../3d/camera.h\"\n#include \"../../3d/modelanimator.h\"\n#include \"../../../log.h\"\n#include \"../../../profile/scene3d.h\"\n\nnamespace Impacto {\nnamespace Vulkan {\n\nstatic uint32_t TextureDummy = 0;\n\n// character\nstatic Pipeline *PipelineMain = 0, *PipelineOutline = 0, *PipelineEye = 0;\nstatic Pipeline *PipelineMainNoDepthWrite = 0, *PipelineEyeNoDepthWrite = 0;\n\n// background\nstatic Pipeline* PipelineBackground = 0;\n\nstatic AllocatedBuffer SceneUniformBuffers[MAX_FRAMES_IN_FLIGHT];\nstatic uint8_t* SceneUniformBuffersMapped[MAX_FRAMES_IN_FLIGHT];\n\nstatic glm::mat4 ViewProjection;\n\nstatic bool IsInit = false;\n\nstatic MaterialType CurrentMaterial = MT_None;\nstatic bool CurrentMaterialIsDepthWrite = false;\n\nstatic VulkanWindow* Window;\nstatic VkDevice Device;\nstatic VkCommandBuffer* CommandBuffers;\nstatic VkDescriptorSetLayout ModelDescriptorSetLayout;\nstatic VkDescriptorSetLayout BgTextureSetLayout;\n\nstatic uint32_t StagingBufferSize = 4096 * 4096;\n\nvoid Renderable3D::Init(VulkanWindow* window, VkDevice device,\n                        VkRenderPass renderPass,\n                        VkCommandBuffer* commandBuffers) {\n  assert(IsInit == false);\n  ImpLog(LogLevel::Info, LogChannel::Renderable3D,\n         \"Initializing Renderable3D system\\n\");\n  IsInit = true;\n\n  Window = window;\n  Device = device;\n  CommandBuffers = commandBuffers;\n\n  // Character shaders\n  VkDescriptorSetLayoutBinding uniformBinds[4] = {};\n  uniformBinds[0].binding = 0;\n  uniformBinds[0].descriptorCount = 1;\n  uniformBinds[0].descriptorType =\n      VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;  // Scene uniform\n  uniformBinds[0].pImmutableSamplers = nullptr;\n  uniformBinds[0].stageFlags = VK_SHADER_STAGE_ALL_GRAPHICS;\n  uniformBinds[1].binding = 1;\n  uniformBinds[1].descriptorCount = 1;\n  uniformBinds[1].descriptorType =\n      VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;  // Model uniform\n  uniformBinds[1].pImmutableSamplers = nullptr;\n  uniformBinds[1].stageFlags = VK_SHADER_STAGE_ALL_GRAPHICS;\n  uniformBinds[2].binding = 2;\n  uniformBinds[2].descriptorCount = 1;\n  uniformBinds[2].descriptorType =\n      VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;  // Mesh uniform\n  uniformBinds[2].pImmutableSamplers = nullptr;\n  uniformBinds[2].stageFlags = VK_SHADER_STAGE_ALL_GRAPHICS;\n  uniformBinds[3].binding = 3;\n  uniformBinds[3].descriptorCount = 4;\n  uniformBinds[3].descriptorType =\n      VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;  // Textures\n  uniformBinds[3].pImmutableSamplers = nullptr;\n  uniformBinds[3].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;\n\n  VkDescriptorSetLayoutCreateInfo modelSetLayoutInfo = {};\n  modelSetLayoutInfo.bindingCount = 4;\n  modelSetLayoutInfo.flags =\n      VK_DESCRIPTOR_SET_LAYOUT_CREATE_PUSH_DESCRIPTOR_BIT_KHR;\n  modelSetLayoutInfo.pNext = nullptr;\n  modelSetLayoutInfo.sType =\n      VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;\n  modelSetLayoutInfo.pBindings = uniformBinds;\n\n  vkCreateDescriptorSetLayout(Device, &modelSetLayoutInfo, nullptr,\n                              &ModelDescriptorSetLayout);\n\n  for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {\n    SceneUniformBuffers[i] = CreateBuffer(sizeof(SceneUniformBufferType),\n                                          VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,\n                                          VMA_MEMORY_USAGE_CPU_ONLY);\n    void* data;\n    vmaMapMemory(Allocator, SceneUniformBuffers[i].Allocation, &data);\n    SceneUniformBuffersMapped[i] = (uint8_t*)data;\n  }\n\n  VkVertexInputBindingDescription modelBindingDescription{};\n  modelBindingDescription.binding = 0;\n  modelBindingDescription.stride = Profile::Scene3D::Version == LKMVersion::DaSH\n                                       ? sizeof(VertexBufferDaSH)\n                                       : sizeof(VertexBuffer);\n  modelBindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;\n\n  VkVertexInputAttributeDescription modelAttributeDescriptions[5] = {};\n  if (Profile::Scene3D::Version == LKMVersion::DaSH) {\n    modelAttributeDescriptions[0].binding = 0;\n    modelAttributeDescriptions[0].location = 0;\n    modelAttributeDescriptions[0].format = VK_FORMAT_R32G32B32_SFLOAT;\n    modelAttributeDescriptions[0].offset = offsetof(VertexBufferDaSH, Position);\n    modelAttributeDescriptions[1].binding = 0;\n    modelAttributeDescriptions[1].location = 1;\n    modelAttributeDescriptions[1].format = VK_FORMAT_R32G32B32_SFLOAT;\n    modelAttributeDescriptions[1].offset = offsetof(VertexBufferDaSH, Normal);\n    modelAttributeDescriptions[2].binding = 0;\n    modelAttributeDescriptions[2].location = 2;\n    modelAttributeDescriptions[2].format = VK_FORMAT_R32G32_SFLOAT;\n    modelAttributeDescriptions[2].offset = offsetof(VertexBufferDaSH, UV);\n    modelAttributeDescriptions[3].binding = 0;\n    modelAttributeDescriptions[3].location = 3;\n    modelAttributeDescriptions[3].format = VK_FORMAT_R8G8B8A8_SINT;\n    modelAttributeDescriptions[3].offset =\n        offsetof(VertexBufferDaSH, BoneIndices);\n    modelAttributeDescriptions[4].binding = 0;\n    modelAttributeDescriptions[4].location = 4;\n    modelAttributeDescriptions[4].format = VK_FORMAT_R32G32B32A32_SFLOAT;\n    modelAttributeDescriptions[4].offset =\n        offsetof(VertexBufferDaSH, BoneWeights);\n\n  } else {\n    modelAttributeDescriptions[0].binding = 0;\n    modelAttributeDescriptions[0].location = 0;\n    modelAttributeDescriptions[0].format = VK_FORMAT_R32G32B32_SFLOAT;\n    modelAttributeDescriptions[0].offset = offsetof(VertexBuffer, Position);\n    modelAttributeDescriptions[1].binding = 0;\n    modelAttributeDescriptions[1].location = 1;\n    modelAttributeDescriptions[1].format = VK_FORMAT_R32G32B32_SFLOAT;\n    modelAttributeDescriptions[1].offset = offsetof(VertexBuffer, Normal);\n    modelAttributeDescriptions[2].binding = 0;\n    modelAttributeDescriptions[2].location = 2;\n    modelAttributeDescriptions[2].format = VK_FORMAT_R32G32_SFLOAT;\n    modelAttributeDescriptions[2].offset = offsetof(VertexBuffer, UV);\n    modelAttributeDescriptions[3].binding = 0;\n    modelAttributeDescriptions[3].location = 3;\n    modelAttributeDescriptions[3].format = VK_FORMAT_R8G8B8A8_SINT;\n    modelAttributeDescriptions[3].offset = offsetof(VertexBuffer, BoneIndices);\n    modelAttributeDescriptions[4].binding = 0;\n    modelAttributeDescriptions[4].location = 4;\n    modelAttributeDescriptions[4].format = VK_FORMAT_R32G32B32A32_SFLOAT;\n    modelAttributeDescriptions[4].offset = offsetof(VertexBuffer, BoneWeights);\n  }\n\n  VkPipelineDepthStencilStateCreateInfo depthStencil{};\n  depthStencil.sType =\n      VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;\n  depthStencil.depthTestEnable = VK_TRUE;\n  depthStencil.depthWriteEnable = VK_TRUE;\n  depthStencil.depthCompareOp = VK_COMPARE_OP_LESS;\n  depthStencil.depthBoundsTestEnable = VK_FALSE;\n  depthStencil.stencilTestEnable = VK_FALSE;\n\n  VkPipelineRasterizationStateCreateInfo rasterizer{};\n  rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;\n  rasterizer.depthClampEnable = VK_FALSE;\n  rasterizer.rasterizerDiscardEnable = VK_FALSE;\n  rasterizer.polygonMode = VK_POLYGON_MODE_FILL;\n  rasterizer.lineWidth = 1.0f;\n  rasterizer.cullMode = VK_CULL_MODE_FRONT_BIT;\n  rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;\n  rasterizer.depthBiasEnable = VK_FALSE;\n\n  VkPushConstantRange pushConstant;\n  pushConstant.offset = 0;\n  pushConstant.size = sizeof(PipelinePushConstants);\n  pushConstant.stageFlags =\n      VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_VERTEX_BIT;\n\n  PipelineMain = new Pipeline(Device, renderPass);\n  PipelineMain->SetPushConstants(&pushConstant, 1);\n  PipelineMain->SetDepthStencilInfo(depthStencil);\n  PipelineMain->CreateWithShader(\n      \"Renderable3D_Character\", modelBindingDescription,\n      modelAttributeDescriptions, 5, ModelDescriptorSetLayout);\n\n  PipelineEye = new Pipeline(Device, renderPass);\n  PipelineEye->SetPushConstants(&pushConstant, 1);\n  PipelineEye->SetDepthStencilInfo(depthStencil);\n  PipelineEye->CreateWithShader(\"Renderable3D_Eye\", modelBindingDescription,\n                                modelAttributeDescriptions, 5,\n                                ModelDescriptorSetLayout);\n\n  depthStencil.depthWriteEnable = VK_FALSE;\n  PipelineOutline = new Pipeline(Device, renderPass);\n  PipelineOutline->SetPushConstants(&pushConstant, 1);\n  PipelineOutline->SetDepthStencilInfo(depthStencil);\n  PipelineOutline->SetRasterizerInfo(rasterizer);\n  PipelineOutline->CreateWithShader(\n      \"Renderable3D_Outline\", modelBindingDescription,\n      modelAttributeDescriptions, 5, ModelDescriptorSetLayout);\n\n  PipelineMainNoDepthWrite = new Pipeline(Device, renderPass);\n  PipelineMainNoDepthWrite->SetPushConstants(&pushConstant, 1);\n  PipelineMainNoDepthWrite->SetDepthStencilInfo(depthStencil);\n  PipelineMainNoDepthWrite->CreateWithShader(\n      \"Renderable3D_Character\", modelBindingDescription,\n      modelAttributeDescriptions, 5, ModelDescriptorSetLayout);\n\n  PipelineEyeNoDepthWrite = new Pipeline(Device, renderPass);\n  PipelineEyeNoDepthWrite->SetPushConstants(&pushConstant, 1);\n  PipelineEyeNoDepthWrite->SetDepthStencilInfo(depthStencil);\n  PipelineEyeNoDepthWrite->CreateWithShader(\n      \"Renderable3D_Eye\", modelBindingDescription, modelAttributeDescriptions,\n      5, ModelDescriptorSetLayout);\n\n  // BG shaders\n  VkDescriptorSetLayoutBinding bgBinds[2] = {};\n  bgBinds[0].binding = 0;\n  bgBinds[0].descriptorCount = 1;\n  bgBinds[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;  // MVP uniform\n  bgBinds[0].pImmutableSamplers = nullptr;\n  bgBinds[0].stageFlags = VK_SHADER_STAGE_VERTEX_BIT;\n  bgBinds[1].binding = 3;\n  bgBinds[1].descriptorCount = 1;\n  bgBinds[1].descriptorType =\n      VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;  // texture\n  bgBinds[1].pImmutableSamplers = nullptr;\n  bgBinds[1].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;\n\n  VkDescriptorSetLayoutCreateInfo bgBindInfo = {};\n  bgBindInfo.bindingCount = 2;\n  bgBindInfo.flags = VK_DESCRIPTOR_SET_LAYOUT_CREATE_PUSH_DESCRIPTOR_BIT_KHR;\n  bgBindInfo.pNext = nullptr;\n  bgBindInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;\n  bgBindInfo.pBindings = bgBinds;\n\n  vkCreateDescriptorSetLayout(Device, &bgBindInfo, nullptr,\n                              &BgTextureSetLayout);\n\n  VkVertexInputBindingDescription backgroundBindingDescription{};\n  backgroundBindingDescription.binding = 0;\n  backgroundBindingDescription.stride = sizeof(BgVertexBuffer);\n  backgroundBindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;\n  VkVertexInputAttributeDescription backgroundAttributeDescriptions[2] = {};\n  backgroundAttributeDescriptions[0].binding = 0;\n  backgroundAttributeDescriptions[0].location = 0;\n  backgroundAttributeDescriptions[0].format = VK_FORMAT_R32G32B32_SFLOAT;\n  backgroundAttributeDescriptions[0].offset =\n      offsetof(BgVertexBuffer, Position);\n  backgroundAttributeDescriptions[1].binding = 0;\n  backgroundAttributeDescriptions[1].location = 1;\n  backgroundAttributeDescriptions[1].format = VK_FORMAT_R32G32_SFLOAT;\n  backgroundAttributeDescriptions[1].offset = offsetof(BgVertexBuffer, UV);\n  PipelineBackground = new Pipeline(Device, renderPass);\n  PipelineBackground->SetPushConstants(&pushConstant, 1);\n  PipelineBackground->CreateWithShader(\n      \"Renderable3D_Background\", backgroundBindingDescription,\n      backgroundAttributeDescriptions, 2, BgTextureSetLayout);\n\n  Texture texDummy;\n  texDummy.Load1x1();\n  TextureDummy = texDummy.Submit();\n}\n\nvoid Renderable3D::BeginFrame(IScene3D* scene, Camera* camera) {\n  CurrentMaterial = MT_None;\n\n  SceneUniformBufferType entry{};\n  entry.ViewProjection = camera->ViewProjection;\n  entry.Tint = scene->Tint;\n  entry.WorldLightPosition = scene->LightPosition;\n  entry.WorldEyePosition = camera->CameraTransform.Position;\n  entry.DarkMode = scene->DarkMode;\n  memcpy(SceneUniformBuffersMapped[CurrentFrameIndex], &entry,\n         sizeof(SceneUniformBufferType));\n\n  ViewProjection = camera->ViewProjection;\n}\n\nbool Renderable3D::LoadSync(uint32_t modelId) {\n  assert(IsUsed == false);\n\n  ImpLog(LogLevel::Info, LogChannel::Renderable3D,\n         \"Creating renderable (model ID {:d})\\n\", modelId);\n\n  StaticModel = Model::Load(modelId);\n  ModelTransform = Transform();\n  PrevPoseWeight = 0.0f;\n\n  if (!StaticModel) {\n    ImpLog(LogLevel::Error, LogChannel::Renderable3D,\n           \"Model loading failed for character with model ID {:d}\\n\", modelId);\n    return false;\n  }\n\n  InitMeshAnimStatus();\n  ReloadDefaultBoneTransforms();\n\n  Animator.Character = this;\n  SwitchAnimation(StaticModel->IdleAnimation, 0.0f);\n\n  IsUsed = true;\n\n  return true;\n}\n\nvoid Renderable3D::MakePlane() {\n  assert(IsUsed == false);\n\n  StaticModel = Model::MakePlane();\n  ModelTransform = Transform();\n  PrevPoseWeight = 0.0f;\n\n  Animator.Character = this;\n\n  InitMeshAnimStatus();\n  ReloadDefaultBoneTransforms();\n\n  IsUsed = true;\n}\n\nvoid Renderable3D::InitMeshAnimStatus() {\n  int totalMorphedVertices = 0;\n  for (uint32_t i = 0; i < StaticModel->MeshCount; i++) {\n    MeshAnimStatus[i].MorphedVerticesOffset = totalMorphedVertices;\n    if (StaticModel->Meshes[i].MorphTargetCount > 0) {\n      totalMorphedVertices += StaticModel->Meshes[i].VertexCount;\n    }\n  }\n  if (totalMorphedVertices > 0) {\n    for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {\n      if (Profile::Scene3D::Version == LKMVersion::DaSH) {\n        CurrentMorphedVerticesVk[i] = CreateBuffer(\n            totalMorphedVertices * sizeof(VertexBufferDaSH),\n            VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VMA_MEMORY_USAGE_CPU_ONLY);\n      } else {\n        CurrentMorphedVerticesVk[i] = CreateBuffer(\n            totalMorphedVertices * sizeof(VertexBuffer),\n            VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VMA_MEMORY_USAGE_CPU_ONLY);\n      }\n      vmaMapMemory(Allocator, CurrentMorphedVerticesVk[i].Allocation,\n                   &CurrentMorphedVerticesVkMapped[i]);\n    }\n  }\n  ReloadDefaultMeshAnimStatus();\n}\n\nvoid Renderable3D::ReloadDefaultMeshAnimStatus() {\n  /*\n  void* currentMorphedVertex;\n  if (Profile::Scene3D::Version == LKMVersion::DaSH) {\n    currentMorphedVertex =\n        ((VertexBufferDaSH*)CurrentMorphedVerticesVkMapped[CurrentFrameIndex]);\n  } else {\n    currentMorphedVertex =\n        ((VertexBuffer*)CurrentMorphedVerticesVkMapped[CurrentFrameIndex]);\n  }\n  VertexBuffer* currentMorphedVertexRNE =\n  (VertexBuffer*)currentMorphedVertex; VertexBufferDaSH*\n  currentMorphedVertexDaSH =\n      (VertexBufferDaSH*)currentMorphedVertex;\n  */\n\n  for (uint32_t i = 0; i < StaticModel->MeshCount; i++) {\n    MeshAnimStatus[i].Visible = 1.0f;\n    if (StaticModel->Meshes[i].MorphTargetCount > 0) {\n      for (int j = 0; j < StaticModel->Meshes[i].MorphTargetCount; j++) {\n        MeshAnimStatus[i].MorphInfluences[j] = 0.0f;\n      }\n    }\n  }\n}\n\nvoid Renderable3D::SwitchAnimation(int16_t animId, float transitionTime) {\n  if (Animator.CurrentAnimation != 0 && transitionTime > 0.0f) {\n    PrevPoseWeight = 1.0f;\n    for (uint32_t i = 0; i < StaticModel->BoneCount; i++) {\n      PrevBoneTransforms[i] = CurrentPose[i].LocalTransform;\n    }\n    memcpy(PrevMeshAnimStatus, MeshAnimStatus, sizeof(MeshAnimStatus));\n    AnimationTransitionTime = transitionTime;\n  } else {\n    PrevPoseWeight = 0.0f;\n  }\n  Animator.Start(animId);\n}\n\nvoid Renderable3D::ReloadDefaultBoneTransforms() {\n  for (uint32_t i = 0; i < StaticModel->BoneCount; i++) {\n    CurrentPose[i].LocalTransform = StaticModel->Bones[i].BaseTransform;\n  }\n}\n\nvoid Renderable3D::CalculateMorphedVertices(int id) {\n  Mesh* mesh = &StaticModel->Meshes[id];\n  AnimatedMesh* animStatus = &MeshAnimStatus[id];\n  AnimatedMesh* prevAnimStatus = &PrevMeshAnimStatus[id];\n  if (mesh->MorphTargetCount == 0) return;\n\n  void* currentMorphedVertex;\n  if (Profile::Scene3D::Version == LKMVersion::DaSH) {\n    currentMorphedVertex =\n        ((VertexBufferDaSH*)CurrentMorphedVerticesVkMapped[CurrentFrameIndex]) +\n        animStatus->MorphedVerticesOffset;\n  } else {\n    currentMorphedVertex =\n        ((VertexBuffer*)CurrentMorphedVerticesVkMapped[CurrentFrameIndex]) +\n        animStatus->MorphedVerticesOffset;\n  }\n  VertexBuffer* currentMorphedVertexRNE = (VertexBuffer*)currentMorphedVertex;\n  VertexBufferDaSH* currentMorphedVertexDaSH =\n      (VertexBufferDaSH*)currentMorphedVertex;\n\n  void* currentVertex;\n  if (Profile::Scene3D::Version == LKMVersion::DaSH) {\n    currentVertex =\n        ((VertexBufferDaSH*)StaticModel->VertexBuffers) + mesh->VertexOffset;\n  } else {\n    currentVertex =\n        ((VertexBuffer*)StaticModel->VertexBuffers) + mesh->VertexOffset;\n  }\n\n  VertexBuffer* currentVertexRNE = (VertexBuffer*)currentVertex;\n  VertexBufferDaSH* currentVertexDaSH = (VertexBufferDaSH*)currentVertex;\n\n  for (uint32_t j = 0; j < mesh->VertexCount; j++) {\n    if (Profile::Scene3D::Version == LKMVersion::DaSH) {\n      memcpy(currentMorphedVertexDaSH, currentVertexDaSH,\n             sizeof(VertexBufferDaSH));\n      currentMorphedVertexDaSH++;\n      currentVertexDaSH++;\n    } else {\n      memcpy(currentMorphedVertexRNE, currentVertexRNE, sizeof(VertexBuffer));\n      currentMorphedVertexRNE++;\n      currentVertexRNE++;\n    }\n  }\n\n  for (int k = 0; k < mesh->MorphTargetCount; k++) {\n    float influence;\n    if (PrevPoseWeight > 0.0f) {\n      influence = glm::mix(prevAnimStatus->MorphInfluences[k],\n                           animStatus->MorphInfluences[k],\n                           glm::smoothstep(0.0f, 1.0f, 1.0f - PrevPoseWeight));\n    } else {\n      influence = animStatus->MorphInfluences[k];\n    }\n\n    if (influence == 0.0f) continue;\n\n    if (Profile::Scene3D::Version == LKMVersion::DaSH) {\n      currentMorphedVertex =\n          ((VertexBufferDaSH*)\n               CurrentMorphedVerticesVkMapped[CurrentFrameIndex]) +\n          animStatus->MorphedVerticesOffset;\n    } else {\n      currentMorphedVertex =\n          ((VertexBuffer*)CurrentMorphedVerticesVkMapped[CurrentFrameIndex]) +\n          animStatus->MorphedVerticesOffset;\n    }\n    currentMorphedVertexRNE = (VertexBuffer*)currentMorphedVertex;\n    currentMorphedVertexDaSH = (VertexBufferDaSH*)currentMorphedVertex;\n\n    if (Profile::Scene3D::Version == LKMVersion::DaSH) {\n      currentVertex =\n          ((VertexBufferDaSH*)StaticModel->VertexBuffers) + mesh->VertexOffset;\n    } else {\n      currentVertex =\n          ((VertexBuffer*)StaticModel->VertexBuffers) + mesh->VertexOffset;\n    }\n\n    currentVertexRNE = (VertexBuffer*)currentVertex;\n    currentVertexDaSH = (VertexBufferDaSH*)currentVertex;\n\n    MorphVertexBuffer* currentMorphTargetVbo =\n        StaticModel->MorphVertexBuffers +\n        StaticModel->MorphTargets[mesh->MorphTargetIds[k]].VertexOffset;\n\n    for (uint32_t j = 0; j < mesh->VertexCount; j++) {\n      if (Profile::Scene3D::Version == LKMVersion::DaSH) {\n        currentMorphedVertexDaSH->Position +=\n            (currentMorphTargetVbo->Position - currentVertexDaSH->Position) *\n            influence;\n        currentMorphedVertexDaSH->Normal +=\n            (currentMorphTargetVbo->Normal - currentVertexDaSH->Normal) *\n            influence;\n        currentVertexDaSH++;\n        currentMorphedVertexDaSH++;\n      } else {\n        currentMorphedVertexRNE->Position +=\n            (currentMorphTargetVbo->Position - currentVertexRNE->Position) *\n            influence;\n        currentMorphedVertexRNE->Normal +=\n            (currentMorphTargetVbo->Normal - currentVertexRNE->Normal) *\n            influence;\n        currentVertexRNE++;\n        currentMorphedVertexRNE++;\n      }\n      currentMorphTargetVbo++;\n    }\n  }\n}\n\nvoid Renderable3D::Pose() {\n  for (int i = 0; i < StaticModel->RootBoneCount; i++) {\n    PoseBone(StaticModel->RootBones[i]);\n  }\n}\n\nvoid Renderable3D::PoseBone(int16_t id) {\n  StaticBone* bone = &StaticModel->Bones[id];\n  PosedBone* transformed = &CurrentPose[id];\n\n  glm::mat4 parentWorld;\n  if (bone->Parent < 0) {\n    parentWorld = glm::mat4(1.0);\n  } else {\n    parentWorld = CurrentPose[bone->Parent].World;\n  }\n\n  Transform transform;\n  if (PrevPoseWeight > 0.0f) {\n    transform = PrevBoneTransforms[id].Interpolate(\n        transformed->LocalTransform,\n        glm::smoothstep(0.0f, 1.0f, 1.0f - PrevPoseWeight));\n  } else {\n    transform = transformed->LocalTransform;\n  }\n\n  glm::mat4 local = transform.Matrix();\n\n  transformed->World = parentWorld * local;\n\n  for (int i = 0; i < bone->ChildrenCount; i++) {\n    PoseBone(bone->Children[i]);\n  }\n\n  transformed->Offset = transformed->World * bone->BindInverse;\n}\n\nvoid Renderable3D::Update(float dt) {\n  if (!IsUsed) return;\n  if (Animator.CurrentAnimation) {\n    if (!Animator.IsPlaying) {\n      // oneshot ended\n      SwitchAnimation(StaticModel->IdleAnimation, AnimationTransitionTime);\n    } else {\n      Animator.Update(dt);\n    }\n  }\n  Pose();\n  if (PrevPoseWeight > 0.0f) {\n    PrevPoseWeight -= dt / AnimationTransitionTime;\n    if (PrevPoseWeight < 0.0f) PrevPoseWeight = 0.0f;\n  }\n\n  for (uint32_t i = 0; i < StaticModel->MeshCount; i++) {\n    CalculateMorphedVertices(i);\n  }\n}\n\nvoid Renderable3D::DrawMesh(int id, RenderPass pass) {\n  if (pass == RP_Outline && StaticModel->Type == ModelType_Background) return;\n\n  if (!MeshAnimStatus[id].Visible) return;\n\n  Mesh& mesh = StaticModel->Meshes[id];\n\n  if (pass == RP_First && mesh.Flags & MeshFlag_Later) return;\n  if (pass == RP_Second && !(mesh.Flags & MeshFlag_Later)) return;\n\n  if (pass == RP_Outline) {\n    UseMaterial(MT_Outline);\n  } else {\n    UseMaterial(mesh.Material);\n  }\n  // Because of the above, we use CurrentMaterial later, not mesh.Material\n\n  UseMesh(id);\n\n  VkDescriptorBufferInfo bgMvpBufferInfo{};\n  VkDescriptorBufferInfo sceneBufferInfo{};\n  VkDescriptorBufferInfo modelBufferInfo{};\n  VkDescriptorBufferInfo meshBufferInfo{};\n  switch (CurrentMaterial) {\n    case MT_Background: {\n      bgMvpBufferInfo.buffer =\n          BackgroundMvpBuffers[id][CurrentFrameIndex].Buffer;\n      bgMvpBufferInfo.offset = 0;\n      bgMvpBufferInfo.range = sizeof(BgMVPUniformBufferType);\n\n      WriteDescriptorSet[CurrentWriteDescriptorSet].sType =\n          VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;\n      WriteDescriptorSet[CurrentWriteDescriptorSet].dstSet = 0;\n      WriteDescriptorSet[CurrentWriteDescriptorSet].dstBinding = 0;\n      WriteDescriptorSet[CurrentWriteDescriptorSet].descriptorCount = 1;\n      WriteDescriptorSet[CurrentWriteDescriptorSet].descriptorType =\n          VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;\n      WriteDescriptorSet[CurrentWriteDescriptorSet].pBufferInfo =\n          &bgMvpBufferInfo;\n      CurrentWriteDescriptorSet += 1;\n    } break;\n    case MT_Generic:\n    case MT_Eye:\n    case MT_Outline:\n    case MT_DaSH_Generic:\n    case MT_DaSH_Face:\n    case MT_DaSH_Skin:\n    case MT_DaSH_Eye: {\n      sceneBufferInfo.buffer = SceneUniformBuffers[CurrentFrameIndex].Buffer;\n      sceneBufferInfo.offset = 0;\n      sceneBufferInfo.range = sizeof(SceneUniformBufferType);\n\n      WriteDescriptorSet[CurrentWriteDescriptorSet].sType =\n          VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;\n      WriteDescriptorSet[CurrentWriteDescriptorSet].dstSet = 0;\n      WriteDescriptorSet[CurrentWriteDescriptorSet].dstBinding = 0;\n      WriteDescriptorSet[CurrentWriteDescriptorSet].descriptorCount = 1;\n      WriteDescriptorSet[CurrentWriteDescriptorSet].descriptorType =\n          VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;\n      WriteDescriptorSet[CurrentWriteDescriptorSet].pBufferInfo =\n          &sceneBufferInfo;\n      CurrentWriteDescriptorSet += 1;\n\n      modelBufferInfo.buffer = ModelUniformBuffers[CurrentFrameIndex].Buffer;\n      modelBufferInfo.offset = 0;\n      modelBufferInfo.range = sizeof(ModelUniformBufferType);\n\n      WriteDescriptorSet[CurrentWriteDescriptorSet].sType =\n          VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;\n      WriteDescriptorSet[CurrentWriteDescriptorSet].dstSet = 0;\n      WriteDescriptorSet[CurrentWriteDescriptorSet].dstBinding = 1;\n      WriteDescriptorSet[CurrentWriteDescriptorSet].descriptorCount = 1;\n      WriteDescriptorSet[CurrentWriteDescriptorSet].descriptorType =\n          VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;\n      WriteDescriptorSet[CurrentWriteDescriptorSet].pBufferInfo =\n          &modelBufferInfo;\n      CurrentWriteDescriptorSet += 1;\n\n      meshBufferInfo.buffer = MeshUniformBuffers[id][CurrentFrameIndex].Buffer;\n      meshBufferInfo.offset = 0;\n      meshBufferInfo.range = sizeof(MeshUniformBufferType);\n\n      WriteDescriptorSet[CurrentWriteDescriptorSet].sType =\n          VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;\n      WriteDescriptorSet[CurrentWriteDescriptorSet].dstSet = 0;\n      WriteDescriptorSet[CurrentWriteDescriptorSet].dstBinding = 2;\n      WriteDescriptorSet[CurrentWriteDescriptorSet].descriptorCount = 1;\n      WriteDescriptorSet[CurrentWriteDescriptorSet].descriptorType =\n          VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;\n      WriteDescriptorSet[CurrentWriteDescriptorSet].pBufferInfo =\n          &meshBufferInfo;\n      CurrentWriteDescriptorSet += 1;\n\n    } break;\n    default:\n      ImpLog(LogLevel::Warning, LogChannel::Renderable3D,\n             \"Unknown material!\\n\");\n      break;\n  }\n\n  int textureCount = 0;\n\n  switch (CurrentMaterial) {\n    case MT_Background: {\n      int constexpr backgroundTextureTypes[] = {TT_ColorMap};\n      textureCount = 1;\n      SetTextures(id, backgroundTextureTypes, 1);\n      break;\n    }\n    case MT_Outline: {\n      if (Profile::Scene3D::Version == LKMVersion::DaSH) {\n        int constexpr outlineTextureTypes[] = {TT_DaSH_ColorMap,\n                                               TT_DaSH_NoiseMap};\n        textureCount = 2;\n        SetTextures(id, outlineTextureTypes, 2);\n      } else {\n        int constexpr outlineTextureTypes[] = {TT_ColorMap};\n        textureCount = 1;\n        SetTextures(id, outlineTextureTypes, 1);\n      }\n      break;\n    }\n    case MT_Generic: {\n      int constexpr genericTextureTypes[] = {TT_ColorMap, TT_GradientMaskMap,\n                                             TT_SpecularColorMap};\n      textureCount = 3;\n      SetTextures(id, genericTextureTypes, 3);\n      break;\n    }\n    case MT_Eye: {\n      int constexpr eyeTextureTypes[] = {\n          TT_Eye_IrisColorMap, TT_Eye_WhiteColorMap, TT_Eye_HighlightColorMap,\n          TT_Eye_IrisSpecularColorMap};\n      textureCount = 4;\n      SetTextures(id, eyeTextureTypes, 4);\n      break;\n    }\n    case MT_DaSH_Generic: {\n      int constexpr dashGenericTextureTypes[] = {\n          TT_DaSH_ColorMap, TT_DaSH_GradientMaskMap, TT_DaSH_SpecularColorMap,\n          TT_DaSH_ShadowColorMap};\n      textureCount = 4;\n      SetTextures(id, dashGenericTextureTypes, 4);\n      break;\n    }\n    case MT_DaSH_Face:\n    case MT_DaSH_Skin: {\n      int constexpr dashGenericTextureTypes[] = {\n          TT_DaSH_ColorMap, TT_DaSH_GradientMaskMap, TT_DaSH_SpecularColorMap};\n      textureCount = 3;\n      SetTextures(id, dashGenericTextureTypes, 3);\n      break;\n    }\n    case MT_DaSH_Eye: {\n      int constexpr dashEyeTextureTypes[] = {TT_DaSH_Eye_IrisColorMap,\n                                             TT_DaSH_Eye_WhiteColorMap,\n                                             TT_DaSH_Eye_HighlightColorMap};\n      textureCount = 3;\n      SetTextures(id, dashEyeTextureTypes, 3);\n      break;\n    }\n    default:\n      ImpLog(LogLevel::Warning, LogChannel::Renderable3D,\n             \"Unknown material!\\n\");\n      break;\n  }\n\n  CurrentTextureBufferInfo = 0;\n\n  if (mesh.Opacity < 0.9 && CurrentMaterialIsDepthWrite) {\n    switch (CurrentMaterial) {\n      case MT_Generic:\n      case MT_DaSH_Generic:\n      case MT_DaSH_Face:\n      case MT_DaSH_Skin: {\n        vkCmdBindPipeline(CommandBuffers[CurrentFrameIndex],\n                          VK_PIPELINE_BIND_POINT_GRAPHICS,\n                          PipelineMainNoDepthWrite->GraphicsPipeline);\n        CurrentPipeline = PipelineMainNoDepthWrite;\n      } break;\n      case MT_Eye:\n      case MT_DaSH_Eye: {\n        vkCmdBindPipeline(CommandBuffers[CurrentFrameIndex],\n                          VK_PIPELINE_BIND_POINT_GRAPHICS,\n                          PipelineEyeNoDepthWrite->GraphicsPipeline);\n        CurrentPipeline = PipelineEyeNoDepthWrite;\n        break;\n      }\n      default:\n        ImpLog(LogLevel::Warning, LogChannel::Renderable3D,\n               \"Unknown material!\\n\");\n        break;\n    }\n  }\n\n  WriteDescriptorSet[CurrentWriteDescriptorSet].sType =\n      VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;\n  WriteDescriptorSet[CurrentWriteDescriptorSet].dstSet = 0;\n  WriteDescriptorSet[CurrentWriteDescriptorSet].dstBinding = 3;\n  WriteDescriptorSet[CurrentWriteDescriptorSet].descriptorCount = textureCount;\n  WriteDescriptorSet[CurrentWriteDescriptorSet].descriptorType =\n      VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;\n  WriteDescriptorSet[CurrentWriteDescriptorSet].pImageInfo = TextureBufferInfo;\n  CurrentWriteDescriptorSet += 1;\n\n  vkCmdPushDescriptorSetKHR(CommandBuffers[CurrentFrameIndex],\n                            VK_PIPELINE_BIND_POINT_GRAPHICS,\n                            CurrentPipeline->PipelineLayout, 0,\n                            CurrentWriteDescriptorSet, WriteDescriptorSet);\n  CurrentWriteDescriptorSet = 0;\n\n  PipelinePushConstants pushConstants{Profile::Scene3D::Version ==\n                                      LKMVersion::DaSH};\n  vkCmdPushConstants(CommandBuffers[CurrentFrameIndex],\n                     CurrentPipeline->PipelineLayout,\n                     VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_VERTEX_BIT,\n                     0, sizeof(PipelinePushConstants), &pushConstants);\n\n  if (StaticModel->Meshes[id].MorphTargetCount > 0) {\n    VkBuffer vertexBuffers[] = {\n        CurrentMorphedVerticesVk[CurrentFrameIndex].Buffer};\n    size_t stride = Profile::Scene3D::Version == LKMVersion::DaSH\n                        ? sizeof(VertexBufferDaSH)\n                        : sizeof(VertexBuffer);\n    VkDeviceSize offsets[] = {\n        (VkDeviceSize)(MeshAnimStatus[id].MorphedVerticesOffset * stride)};\n    vkCmdBindVertexBuffers(CommandBuffers[CurrentFrameIndex], 0, 1,\n                           vertexBuffers, offsets);\n  } else {\n    VkBuffer vertexBuffers[] = {\n        MeshVertexBuffers[id][CurrentFrameIndex].Buffer};\n    VkDeviceSize offsets[] = {(VkDeviceSize)0};\n    vkCmdBindVertexBuffers(CommandBuffers[CurrentFrameIndex], 0, 1,\n                           vertexBuffers, offsets);\n  }\n  vkCmdBindIndexBuffer(CommandBuffers[CurrentFrameIndex],\n                       MeshIndexBuffers[id][CurrentFrameIndex].Buffer, 0,\n                       VK_INDEX_TYPE_UINT16);\n  vkCmdDrawIndexed(CommandBuffers[CurrentFrameIndex], mesh.IndexCount, 1, 0, 0,\n                   0);\n\n  if (mesh.Opacity < 0.9 && CurrentMaterialIsDepthWrite) {\n    switch (CurrentMaterial) {\n      case MT_Generic:\n      case MT_DaSH_Generic:\n      case MT_DaSH_Face:\n      case MT_DaSH_Skin: {\n        vkCmdBindPipeline(CommandBuffers[CurrentFrameIndex],\n                          VK_PIPELINE_BIND_POINT_GRAPHICS,\n                          PipelineMain->GraphicsPipeline);\n        CurrentPipeline = PipelineMain;\n      } break;\n      case MT_Eye:\n      case MT_DaSH_Eye: {\n        vkCmdBindPipeline(CommandBuffers[CurrentFrameIndex],\n                          VK_PIPELINE_BIND_POINT_GRAPHICS,\n                          PipelineEye->GraphicsPipeline);\n        CurrentPipeline = PipelineEye;\n        break;\n      }\n      default:\n        ImpLog(LogLevel::Warning, LogChannel::Renderable3D,\n               \"Unknown material!\\n\");\n        break;\n    }\n  }\n\n  if (pass != RP_Outline) DrawMesh(id, RP_Outline);\n}\n\nvoid Renderable3D::Render() {\n  if (!IsUsed || !IsVisible) return;\n\n  LoadModelUniforms();\n\n  memset(UniformsUpdated, 0, sizeof(UniformsUpdated));\n  CurrentTextureBufferInfo = 0;\n  CurrentWriteDescriptorSet = 0;\n\n  for (int i = RP_First; i < RP_Count; i++) {\n    for (size_t j = 0; j < StaticModel->MeshCount; j++) {\n      DrawMesh((int)j, (RenderPass)i);\n    }\n  }\n\n  if (CurrentMaterial == MT_Outline) {\n    // Clean up some state\n    UseMaterial(MT_Background);\n  }\n}\n\nvoid Renderable3D::SetTextures(int id, int const* textureTypes, int count) {\n  for (int i = 0; i < count; i++) {\n    int t = textureTypes[i];\n    if (StaticModel->Meshes[id].Maps[t] >= 0) {\n      TextureBufferInfo[CurrentTextureBufferInfo].sampler = Sampler;\n      TextureBufferInfo[CurrentTextureBufferInfo].imageView =\n          Textures[TexBuffers[StaticModel->Meshes[id].Maps[t]]].ImageView;\n      TextureBufferInfo[CurrentTextureBufferInfo].imageLayout =\n          VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;\n    } else {\n      TextureBufferInfo[CurrentTextureBufferInfo].sampler = Sampler;\n      TextureBufferInfo[CurrentTextureBufferInfo].imageView =\n          Textures[TextureDummy].ImageView;\n      TextureBufferInfo[CurrentTextureBufferInfo].imageLayout =\n          VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;\n    }\n    CurrentTextureBufferInfo += 1;\n  }\n}\n\nvoid Renderable3D::LoadModelUniforms() {\n  if (StaticModel->Type == ModelType_Character) {\n    ModelUniformBufferType entry{};\n    entry.Model = ModelTransform.Matrix();\n    memcpy(ModelUniformBuffersMapped[CurrentFrameIndex], &entry,\n           sizeof(ModelUniformBufferType));\n  }\n}\n\nvoid Renderable3D::UseMaterial(MaterialType type) {\n  if (CurrentMaterial == type) return;\n\n  switch (type) {\n    case MT_Outline: {\n      vkCmdBindPipeline(CommandBuffers[CurrentFrameIndex],\n                        VK_PIPELINE_BIND_POINT_GRAPHICS,\n                        PipelineOutline->GraphicsPipeline);\n      CurrentPipeline = PipelineOutline;\n      break;\n    }\n    case MT_Background: {\n      vkCmdBindPipeline(CommandBuffers[CurrentFrameIndex],\n                        VK_PIPELINE_BIND_POINT_GRAPHICS,\n                        PipelineBackground->GraphicsPipeline);\n      CurrentPipeline = PipelineBackground;\n      break;\n    }\n    case MT_Generic:\n    case MT_DaSH_Generic:\n    case MT_DaSH_Face:\n    case MT_DaSH_Skin: {\n      vkCmdBindPipeline(CommandBuffers[CurrentFrameIndex],\n                        VK_PIPELINE_BIND_POINT_GRAPHICS,\n                        PipelineMain->GraphicsPipeline);\n      CurrentPipeline = PipelineMain;\n      break;\n    }\n    case MT_Eye:\n    case MT_DaSH_Eye: {\n      vkCmdBindPipeline(CommandBuffers[CurrentFrameIndex],\n                        VK_PIPELINE_BIND_POINT_GRAPHICS,\n                        PipelineEye->GraphicsPipeline);\n      CurrentPipeline = PipelineEye;\n      break;\n    }\n    default:\n      ImpLog(LogLevel::Warning, LogChannel::Renderable3D,\n             \"Unknown material!\\n\");\n      break;\n  }\n\n  if (type == MT_Outline) {\n    CurrentMaterialIsDepthWrite = false;\n  } else {\n    CurrentMaterialIsDepthWrite = true;\n  }\n\n  CurrentMaterial = type;\n}\n\nvoid Renderable3D::UseMesh(int id) { LoadMeshUniforms(id); }\n\nvoid Renderable3D::LoadMeshUniforms(int id) {\n  Mesh& mesh = StaticModel->Meshes[id];\n\n  if (StaticModel->Type == ModelType_Character) {\n    MeshUniformBufferType entry{};\n    if (!UniformsUpdated[id]) {\n      if (mesh.UsedBones > 0) {\n        glm::mat4* outBone = entry.Bones;\n        for (uint32_t j = 0; j < mesh.UsedBones; j++) {\n          *outBone = CurrentPose[mesh.BoneMap[j]].Offset;\n          outBone++;\n        }\n      } else {\n        entry.Bones[0] = CurrentPose[mesh.MeshBone].Offset;\n      }\n\n      entry.ModelOpacity = mesh.Opacity;\n      entry.HasShadowColorMap =\n          mesh.Material == MT_DaSH_Generic && mesh.HasShadowColorMap;\n\n      memcpy(MeshUniformBuffersMapped[id][CurrentFrameIndex], &entry,\n             sizeof(MeshUniformBufferType));\n\n      UniformsUpdated[id] = true;\n    }\n  } else if (StaticModel->Type == ModelType_Background) {\n    BgMVPUniformBufferType entry{};\n    entry.MVP = ViewProjection * mesh.ModelTransform.Matrix();\n    memcpy(BackgroundMvpMapped[id][CurrentFrameIndex], &entry,\n           sizeof(BgMVPUniformBufferType));\n  }\n}\n\nvoid Renderable3D::UnloadSync() {\n  Animator.CurrentAnimation = 0;\n  PrevPoseWeight = 0.0f;\n  if (StaticModel) {\n    ImpLog(LogLevel::Info, LogChannel::Renderable3D, \"Unloading model {:d}\\n\",\n           StaticModel->Id);\n    if (IsSubmitted) {\n      if (StaticModel->Type == ModelType_Background) {\n        for (uint32_t i = 0; i < StaticModel->MeshCount; i++) {\n          for (uint32_t j = 0; j < MAX_FRAMES_IN_FLIGHT; j++) {\n            vmaUnmapMemory(Allocator, BackgroundMvpBuffers[i][j].Allocation);\n            vmaDestroyBuffer(Allocator, BackgroundMvpBuffers[i][j].Buffer,\n                             BackgroundMvpBuffers[i][j].Allocation);\n          }\n        }\n      } else {\n        for (uint32_t i = 0; i < StaticModel->MeshCount; i++) {\n          for (uint32_t j = 0; j < MAX_FRAMES_IN_FLIGHT; j++) {\n            vmaUnmapMemory(Allocator, MeshUniformBuffers[i][j].Allocation);\n            vmaDestroyBuffer(Allocator, MeshUniformBuffers[i][j].Buffer,\n                             MeshUniformBuffers[i][j].Allocation);\n          }\n        }\n      }\n\n      for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {\n        vmaUnmapMemory(Allocator, ModelUniformBuffers[i].Allocation);\n        vmaDestroyBuffer(Allocator, ModelUniformBuffers[i].Buffer,\n                         ModelUniformBuffers[i].Allocation);\n      }\n    }\n    delete StaticModel;\n    StaticModel = 0;\n  }\n  for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {\n    if (CurrentMorphedVerticesVkMapped[i]) {\n      vmaUnmapMemory(Allocator, CurrentMorphedVerticesVk[i].Allocation);\n      CurrentMorphedVerticesVkMapped[i] = 0;\n      vmaDestroyBuffer(Allocator, CurrentMorphedVerticesVk[i].Buffer,\n                       CurrentMorphedVerticesVk[i].Allocation);\n    }\n  }\n  if (CurrentMorphedVertices) {\n    free(CurrentMorphedVertices);\n    CurrentMorphedVertices = 0;\n  }\n  ModelTransform = Transform();\n  IsSubmitted = false;\n  IsUsed = false;\n  IsVisible = false;\n}\n\nvoid Renderable3D::MainThreadOnLoad() {\n  assert(IsSubmitted == false);\n\n  ImpLog(LogLevel::Info, LogChannel::Renderable3D,\n         \"Submitting data to GPU for model ID {:d}\\n\", StaticModel->Id);\n\n  if (StaticModel->Type == ModelType_Background) {\n    for (uint32_t i = 0; i < StaticModel->MeshCount; i++) {\n      for (uint32_t j = 0; j < MAX_FRAMES_IN_FLIGHT; j++) {\n        BackgroundMvpBuffers[i][j] = CreateBuffer(\n            sizeof(BgMVPUniformBufferType), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,\n            VMA_MEMORY_USAGE_CPU_ONLY);\n        void* data;\n        vmaMapMemory(Allocator, BackgroundMvpBuffers[i][j].Allocation, &data);\n        BackgroundMvpMapped[i][j] = (uint8_t*)data;\n      }\n    }\n  }\n\n  for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {\n    ModelUniformBuffers[i] = CreateBuffer(sizeof(ModelUniformBufferType),\n                                          VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,\n                                          VMA_MEMORY_USAGE_CPU_ONLY);\n    void* data;\n    vmaMapMemory(Allocator, ModelUniformBuffers[i].Allocation, &data);\n    ModelUniformBuffersMapped[i] = (uint8_t*)data;\n  }\n\n  auto stagingBuffer =\n      CreateBuffer(StagingBufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT,\n                   VMA_MEMORY_USAGE_CPU_ONLY);\n\n  void* stagingBufferMapped;\n  vmaMapMemory(Allocator, stagingBuffer.Allocation, &stagingBufferMapped);\n\n  for (uint32_t i = 0; i < StaticModel->MeshCount; i++) {\n    for (uint32_t j = 0; j < MAX_FRAMES_IN_FLIGHT; j++) {\n      MeshUniformBuffers[i][j] = CreateBuffer(\n          sizeof(MeshUniformBufferType), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,\n          VMA_MEMORY_USAGE_CPU_ONLY);\n      void* data;\n      vmaMapMemory(Allocator, MeshUniformBuffers[i][j].Allocation, &data);\n      MeshUniformBuffersMapped[i][j] = (uint8_t*)data;\n\n      uint32_t vertexCopySize = 0;\n\n      if (StaticModel->Type == ModelType_Character) {\n        if (Profile::Scene3D::Version == LKMVersion::DaSH) {\n          MeshVertexBuffers[i][j] = CreateBuffer(\n              sizeof(VertexBufferDaSH) * StaticModel->Meshes[i].VertexCount,\n              VK_BUFFER_USAGE_TRANSFER_DST_BIT |\n                  VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,\n              VMA_MEMORY_USAGE_CPU_ONLY);\n          vertexCopySize =\n              sizeof(VertexBufferDaSH) * StaticModel->Meshes[i].VertexCount;\n\n          memcpy(stagingBufferMapped,\n                 (VertexBufferDaSH*)StaticModel->VertexBuffers +\n                     StaticModel->Meshes[i].VertexOffset,\n                 sizeof(VertexBufferDaSH) * StaticModel->Meshes[i].VertexCount);\n        } else {\n          MeshVertexBuffers[i][j] = CreateBuffer(\n              sizeof(VertexBuffer) * StaticModel->Meshes[i].VertexCount,\n              VK_BUFFER_USAGE_TRANSFER_DST_BIT |\n                  VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,\n              VMA_MEMORY_USAGE_GPU_ONLY);\n          vertexCopySize =\n              sizeof(VertexBuffer) * StaticModel->Meshes[i].VertexCount;\n\n          memcpy(stagingBufferMapped,\n                 (VertexBuffer*)StaticModel->VertexBuffers +\n                     StaticModel->Meshes[i].VertexOffset,\n                 sizeof(VertexBuffer) * StaticModel->Meshes[i].VertexCount);\n        }\n      } else if (StaticModel->Type == ModelType_Background) {\n        MeshVertexBuffers[i][j] = CreateBuffer(\n            sizeof(BgVertexBuffer) * StaticModel->Meshes[i].VertexCount,\n            VK_BUFFER_USAGE_TRANSFER_DST_BIT |\n                VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,\n            VMA_MEMORY_USAGE_GPU_ONLY);\n        vertexCopySize =\n            sizeof(BgVertexBuffer) * StaticModel->Meshes[i].VertexCount;\n\n        memcpy(stagingBufferMapped,\n               (BgVertexBuffer*)StaticModel->VertexBuffers +\n                   StaticModel->Meshes[i].VertexOffset,\n               sizeof(BgVertexBuffer) * StaticModel->Meshes[i].VertexCount);\n      }\n\n      MeshIndexBuffers[i][j] = CreateBuffer(\n          sizeof(uint16_t) * StaticModel->Meshes[i].IndexCount,\n          VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT,\n          VMA_MEMORY_USAGE_GPU_ONLY);\n\n      memcpy((uint8_t*)stagingBufferMapped + vertexCopySize,\n             StaticModel->Indices + StaticModel->Meshes[i].IndexOffset,\n             sizeof(uint16_t) * StaticModel->Meshes[i].IndexCount);\n\n      ImmediateSubmit(\n          [this, vertexCopySize, stagingBuffer, i, j](VkCommandBuffer cmd) {\n            VkBufferCopy copy;\n            copy.dstOffset = 0;\n            copy.srcOffset = 0;\n            copy.size = vertexCopySize;\n            vkCmdCopyBuffer(cmd, stagingBuffer.Buffer,\n                            MeshVertexBuffers[i][j].Buffer, 1, &copy);\n\n            copy.dstOffset = 0;\n            copy.srcOffset = vertexCopySize;\n            copy.size = sizeof(uint16_t) * StaticModel->Meshes[i].IndexCount;\n            vkCmdCopyBuffer(cmd, stagingBuffer.Buffer,\n                            MeshIndexBuffers[i][j].Buffer, 1, &copy);\n          });\n    }\n  }\n\n  for (uint32_t i = 0; i < StaticModel->TextureCount; i++) {\n    TexBuffers[i] = StaticModel->Textures[i].Submit();\n    if (TexBuffers[i] == 0) {\n      ImpLog(LogLevel::Fatal, LogChannel::Renderable3D,\n             \"Submitting texture {:d} for model {:d} failed\\n\", i,\n             StaticModel->Id);\n    }\n  }\n\n  vmaUnmapMemory(Allocator, stagingBuffer.Allocation);\n  vmaDestroyBuffer(Allocator, stagingBuffer.Buffer, stagingBuffer.Allocation);\n\n  IsSubmitted = true;\n}\n\n}  // namespace Vulkan\n}  // namespace Impacto"
  },
  {
    "path": "src/renderer/vulkan/3d/renderable3d.h",
    "content": "#pragma once\n\n#include \"../utils.h\"\n#include \"../pipeline.h\"\n#include \"../window.h\"\n#include \"../../3d/renderable3d.h\"\n\nnamespace Impacto {\nnamespace Vulkan {\n\nenum RenderPass { RP_Outline = 0, RP_First = 1, RP_Second = 2, RP_Count };\n\nstruct PipelinePushConstants {\n  VkBool32 IsDash;\n};\n\nstruct SceneUniformBufferType {\n  alignas(16) glm::mat4 ViewProjection;\n  alignas(16) glm::vec4 Tint;\n  alignas(16) glm::vec3 WorldLightPosition;\n  alignas(16) glm::vec3 WorldEyePosition;\n  alignas(16) VkBool32 DarkMode;\n};\n\nstruct ModelUniformBufferType {\n  alignas(16) glm::mat4 Model;\n};\n\nstruct MeshUniformBufferType {\n  alignas(16) glm::mat4 Bones[ModelMaxBonesPerMesh];\n  alignas(16) float ModelOpacity;\n  alignas(16) VkBool32 HasShadowColorMap;\n};\n\nstruct BgMVPUniformBufferType {\n  alignas(16) glm::mat4 MVP;\n};\n\nclass Renderable3D : public IRenderable3D {\n public:\n  static void Init(VulkanWindow* window, VkDevice device,\n                   VkRenderPass renderPass, VkCommandBuffer* commandBuffers);\n  static void BeginFrame(IScene3D* scene, Camera* camera);\n\n  void MakePlane() override;\n\n  void Update(float dt) override;\n  void Render() override;\n\n  void ReloadDefaultBoneTransforms() override;\n  void InitMeshAnimStatus() override;\n  void ReloadDefaultMeshAnimStatus() override;\n\n  void SwitchAnimation(int16_t animId, float transitionTime) override;\n\n protected:\n  bool LoadSync(uint32_t modelId) override;\n  void UnloadSync() override;\n  void MainThreadOnLoad() override;\n\n private:\n  void Pose();\n  void PoseBone(int16_t id);\n\n  void CalculateMorphedVertices(int id);\n\n  void UseMaterial(MaterialType type);\n  void UseMesh(int id);\n  void LoadModelUniforms();\n  void LoadMeshUniforms(int id);\n  void SetTextures(int id, int const* textureUnits, int count);\n  void DrawMesh(int id, RenderPass pass);\n\n  AllocatedBuffer ModelUniformBuffers[MAX_FRAMES_IN_FLIGHT];\n  uint8_t* ModelUniformBuffersMapped[MAX_FRAMES_IN_FLIGHT];\n  AllocatedBuffer MeshUniformBuffers[ModelMaxMeshesPerModel]\n                                    [MAX_FRAMES_IN_FLIGHT];\n  uint8_t* MeshUniformBuffersMapped[ModelMaxMeshesPerModel]\n                                   [MAX_FRAMES_IN_FLIGHT];\n  AllocatedBuffer MeshVertexBuffers[ModelMaxMeshesPerModel]\n                                   [MAX_FRAMES_IN_FLIGHT];\n  AllocatedBuffer MeshIndexBuffers[ModelMaxMeshesPerModel]\n                                  [MAX_FRAMES_IN_FLIGHT];\n\n  AllocatedBuffer BackgroundMvpBuffers[ModelMaxMeshesPerModel]\n                                      [MAX_FRAMES_IN_FLIGHT];\n  uint8_t* BackgroundMvpMapped[ModelMaxMeshesPerModel][MAX_FRAMES_IN_FLIGHT];\n\n  int CurrentTextureBufferInfo = 0;\n  VkDescriptorImageInfo TextureBufferInfo[4];\n\n  int CurrentWriteDescriptorSet = 0;\n  VkWriteDescriptorSet WriteDescriptorSet[4];\n\n  AllocatedBuffer CurrentMorphedVerticesVk[MAX_FRAMES_IN_FLIGHT];\n  void* CurrentMorphedVerticesVkMapped[MAX_FRAMES_IN_FLIGHT];\n\n  uint32_t TexBuffers[ModelMaxTexturesPerModel];\n\n  bool UniformsUpdated[ModelMaxMeshesPerModel];\n\n  Transform PrevBoneTransforms[ModelMaxBonesPerModel];\n  AnimatedMesh PrevMeshAnimStatus[ModelMaxMeshesPerModel];\n  float PrevPoseWeight;\n  float AnimationTransitionTime;\n};\n\n}  // namespace Vulkan\n}  // namespace Impacto"
  },
  {
    "path": "src/renderer/vulkan/3d/scene.cpp",
    "content": "#include \"scene.h\"\n\n#include \"../../3d/camera.h\"\n#include \"../../../log.h\"\n#include \"renderable3d.h\"\n\n#include \"../../../profile/scene3d.h\"\n\nnamespace Impacto {\nnamespace Vulkan {\n\nScene3D::Scene3D(VulkanWindow* window, VkDevice device, VkRenderPass renderPass,\n                 VkCommandBuffer* commandBuffers) {\n  Window = window;\n  Device = device;\n  RenderPass = renderPass;\n  CommandBuffers = commandBuffers;\n}\n\nvoid Scene3D::Init() {\n  assert(IsInit == false);\n  ImpLog(LogLevel::Info, LogChannel::Scene, \"Initializing 3D scene system\\n\");\n  IsInit = true;\n\n  Profile::Scene3D::Configure();\n\n  Renderables = new IRenderable3D*[Profile::Scene3D::MaxRenderables];\n  for (int i = 0; i < Profile::Scene3D::MaxRenderables; i++) {\n    Renderables[i] = new Renderable3D();\n  }\n\n  Renderable3D::Init(Window, Device, RenderPass, CommandBuffers);\n\n  MainCamera.Init();\n\n  // clang-format off\n  float ScreenFillingTriangle[] = {\n      // Position       // UV\n      -1.0f, -1.0f,     0.0f, 0.0f,\n      3.0f, -1.0f,      2.0f, 0.0f,\n      -1.0f, 3.0f,      0.0f, 2.0f\n  };\n  // clang-format on\n  (void)ScreenFillingTriangle;  // Currently unused\n}\n\nvoid Scene3D::Shutdown() {\n  if (!IsInit) return;\n  if (Renderables) delete[] Renderables;\n}\n\nvoid Scene3D::Update(float dt) {\n  for (int i = 0; i < Profile::Scene3D::MaxRenderables; i++) {\n    if (Renderables[i]->Status == LoadStatus::Loaded) {\n      Renderables[i]->Update(dt);\n    }\n  }\n}\nvoid Scene3D::Render() {\n  RectF viewport = Window->GetViewport();\n  MainCamera.AspectRatio = viewport.Width / viewport.Height;\n  MainCamera.Recalculate();\n\n  Renderable3D::BeginFrame(this, &MainCamera);\n\n  for (int i = 0; i < Profile::Scene3D::MaxRenderables; i++) {\n    if (Renderables[i]->Status == LoadStatus::Loaded &&\n        Renderables[i]->StaticModel->Type == ModelType_Background) {\n      Renderables[i]->Render();\n    }\n  }\n\n  for (int i = 0; i < Profile::Scene3D::MaxRenderables; i++) {\n    if (Renderables[i]->Status == LoadStatus::Loaded &&\n        Renderables[i]->StaticModel->Type == ModelType_Character) {\n      Renderables[i]->Render();\n    }\n  }\n}\n\n}  // namespace Vulkan\n}  // namespace Impacto"
  },
  {
    "path": "src/renderer/vulkan/3d/scene.h",
    "content": "#pragma once\n\n#include <glm/glm.hpp>\n\n#include \"../../3d/scene.h\"\n#include \"../window.h\"\n#include \"../renderer.h\"\n\nnamespace Impacto {\nnamespace Vulkan {\n\nclass Scene3D : public IScene3D {\n public:\n  Scene3D(VulkanWindow* window, VkDevice device, VkRenderPass renderPass,\n          VkCommandBuffer* commandBuffers);\n\n  void Init();\n  void Shutdown();\n  void Update(float dt);\n  void Render();\n\n private:\n  bool IsInit = false;\n\n  VulkanWindow* Window;\n  VkDevice Device;\n  VkRenderPass RenderPass;\n  VkCommandBuffer* CommandBuffers;\n};\n\n}  // namespace Vulkan\n}  // namespace Impacto"
  },
  {
    "path": "src/renderer/vulkan/nv12frame.cpp",
    "content": "#include \"nv12frame.h\"\n\nnamespace Impacto {\nnamespace Vulkan {\n\nvoid VkNV12Frame::Init(float width, float height) {\n  Width = width;\n  Height = height;\n\n  VkDeviceSize imageSize = (VkDeviceSize)(width * height);\n  VkDeviceSize bufferSize =\n      imageSize + 2 * (((VkDeviceSize)width / 2) * ((VkDeviceSize)height / 2));\n\n  StagingBuffer = CreateBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT,\n                               VMA_MEMORY_USAGE_CPU_ONLY);\n  vmaMapMemory(Allocator, StagingBuffer.Allocation, &MappedStagingBuffer);\n\n  VkExtent3D lumaImageExtent;\n  lumaImageExtent.width = static_cast<uint32_t>(width);\n  lumaImageExtent.height = static_cast<uint32_t>(height);\n  lumaImageExtent.depth = 1;\n\n  VkExtent3D cbCrImageExtent;\n  cbCrImageExtent.width = static_cast<uint32_t>(width / 2);\n  cbCrImageExtent.height = static_cast<uint32_t>(height / 2);\n  cbCrImageExtent.depth = 1;\n\n  auto lumaImageInfo =\n      GetImageCreateInfo(VK_FORMAT_R8_UNORM, lumaImageExtent,\n                         VkSampleCountFlagBits::VK_SAMPLE_COUNT_1_BIT);\n  VmaAllocationCreateInfo dimgAllocinfo = {};\n  dimgAllocinfo.usage = VMA_MEMORY_USAGE_GPU_ONLY;\n  vmaCreateImage(Allocator, &lumaImageInfo, &dimgAllocinfo,\n                 &LumaImage.Image.Image, &LumaImage.Image.Allocation, nullptr);\n  auto cbcrImageInfo =\n      GetImageCreateInfo(VK_FORMAT_R8G8_UNORM, cbCrImageExtent,\n                         VkSampleCountFlagBits::VK_SAMPLE_COUNT_1_BIT);\n  vmaCreateImage(Allocator, &cbcrImageInfo, &dimgAllocinfo,\n                 &CbCrImage.Image.Image, &CbCrImage.Image.Allocation, nullptr);\n\n  VkImageViewCreateInfo imageInfo = {};\n  imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;\n  imageInfo.pNext = nullptr;\n  imageInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;\n  imageInfo.image = LumaImage.Image.Image;\n  imageInfo.format = VK_FORMAT_R8_UNORM;\n  imageInfo.subresourceRange.baseMipLevel = 0;\n  imageInfo.subresourceRange.levelCount = 1;\n  imageInfo.subresourceRange.baseArrayLayer = 0;\n  imageInfo.subresourceRange.layerCount = 1;\n  imageInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;\n\n  vkCreateImageView(MainUploadContext.Device, &imageInfo, nullptr,\n                    &LumaImage.ImageView);\n\n  imageInfo.image = CbCrImage.Image.Image;\n  imageInfo.format = VK_FORMAT_R8G8_UNORM;\n  vkCreateImageView(MainUploadContext.Device, &imageInfo, nullptr,\n                    &CbCrImage.ImageView);\n}\n\nvoid Impacto::Vulkan::VkNV12Frame::Submit(const void* luma, int lumaStride,\n                                          const void* cbcr, int cbcrStride) {\n  const int cbcrOffset = (int)(Width * Height);\n\n  const uint8_t* src = (const uint8_t*)luma;\n  uint8_t* dst = (uint8_t*)MappedStagingBuffer;\n  for (int y = 0; y < Height; y++) {\n    memcpy(dst, src, (int)Width);\n    src += lumaStride;\n    dst += (int)Width;\n  }\n\n  src = (const uint8_t*)cbcr;\n  dst = (uint8_t*)MappedStagingBuffer + cbcrOffset;\n  for (int y = 0; y < (int)Height / 2; y++) {\n    memcpy(dst, src, (int)Width);\n    src += cbcrStride;\n    dst += (int)Width;\n  }\n\n  ImmediateSubmit([&](VkCommandBuffer cmd) {\n    VkExtent3D lumaImageExtent;\n    lumaImageExtent.width = static_cast<uint32_t>(Width);\n    lumaImageExtent.height = static_cast<uint32_t>(Height);\n    lumaImageExtent.depth = 1;\n    VkExtent3D cbCrImageExtent;\n    cbCrImageExtent.width = static_cast<uint32_t>(Width / 2);\n    cbCrImageExtent.height = static_cast<uint32_t>(Height / 2);\n    cbCrImageExtent.depth = 1;\n\n    VkImageSubresourceRange range;\n    range.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;\n    range.baseMipLevel = 0;\n    range.levelCount = 1;\n    range.baseArrayLayer = 0;\n    range.layerCount = 1;\n\n    VkImageMemoryBarrier imageBarrierToTransfer = {};\n    imageBarrierToTransfer.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;\n    imageBarrierToTransfer.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;\n    imageBarrierToTransfer.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;\n    imageBarrierToTransfer.image = LumaImage.Image.Image;\n    imageBarrierToTransfer.subresourceRange = range;\n    imageBarrierToTransfer.srcAccessMask = 0;\n    imageBarrierToTransfer.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;\n\n    vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,\n                         VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0,\n                         nullptr, 1, &imageBarrierToTransfer);\n\n    VkBufferImageCopy copyRegion = {};\n    copyRegion.bufferOffset = 0;\n    copyRegion.bufferRowLength = 0;\n    copyRegion.bufferImageHeight = 0;\n    copyRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;\n    copyRegion.imageSubresource.mipLevel = 0;\n    copyRegion.imageSubresource.baseArrayLayer = 0;\n    copyRegion.imageSubresource.layerCount = 1;\n    copyRegion.imageExtent = lumaImageExtent;\n    vkCmdCopyBufferToImage(cmd, StagingBuffer.Buffer, LumaImage.Image.Image,\n                           VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1,\n                           &copyRegion);\n\n    VkImageMemoryBarrier imageBarrierToReadable = imageBarrierToTransfer;\n    imageBarrierToReadable.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;\n    imageBarrierToReadable.newLayout = VK_IMAGE_LAYOUT_GENERAL;\n    imageBarrierToReadable.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;\n    imageBarrierToReadable.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;\n    vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TRANSFER_BIT,\n                         VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, nullptr,\n                         0, nullptr, 1, &imageBarrierToReadable);\n\n    imageBarrierToTransfer.image = CbCrImage.Image.Image;\n    vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,\n                         VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0,\n                         nullptr, 1, &imageBarrierToTransfer);\n\n    copyRegion.bufferOffset = (VkDeviceSize)(Width * Height);\n    copyRegion.imageExtent = cbCrImageExtent;\n    vkCmdCopyBufferToImage(cmd, StagingBuffer.Buffer, CbCrImage.Image.Image,\n                           VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1,\n                           &copyRegion);\n\n    imageBarrierToReadable.image = CbCrImage.Image.Image;\n    vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TRANSFER_BIT,\n                         VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, nullptr,\n                         0, nullptr, 1, &imageBarrierToReadable);\n  });\n}\n\nvoid VkNV12Frame::Release() {\n  vmaUnmapMemory(Allocator, StagingBuffer.Allocation);\n}\n\n}  // namespace Vulkan\n}  // namespace Impacto"
  },
  {
    "path": "src/renderer/vulkan/nv12frame.h",
    "content": "#pragma once\n\n#include \"../nv12frame.h\"\n#include \"utils.h\"\n\n#include <vulkan/vulkan.h>\n\nnamespace Impacto {\nnamespace Vulkan {\n\nclass VkNV12Frame : public NV12Frame {\n  friend class Renderer;\n\n public:\n  void Init(float width, float height) override;\n\n  void Submit(const void* luma, int lumaStride, const void* cbcr,\n              int cbcrStride) override;\n  void Release() override;\n\n protected:\n  VkTexture LumaImage{};\n  VkTexture CbCrImage{};\n\n private:\n  AllocatedBuffer StagingBuffer;\n  void* MappedStagingBuffer;\n};\n\n}  // namespace Vulkan\n}  // namespace Impacto"
  },
  {
    "path": "src/renderer/vulkan/pipeline.cpp",
    "content": "#include \"pipeline.h\"\n\n#include <vector>\n\n#include \"../../log.h\"\n#include \"../../util.h\"\n#include \"renderer.h\"\n\nnamespace Impacto {\nnamespace Vulkan {\n\nstatic char const ShaderPath[] = \"./shaders/vulkan\";\nstatic char const FragShaderExtension[] = \"_frag.spv\";\nstatic char const VertShaderExtension[] = \"_vert.spv\";\n\nPipeline::Pipeline(VkDevice device, VkRenderPass renderPass) {\n  Device = device;\n  RenderPass = renderPass;\n\n  VkPipelineRasterizationStateCreateInfo rasterizer{};\n  rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;\n  rasterizer.depthClampEnable = VK_FALSE;\n  rasterizer.rasterizerDiscardEnable = VK_FALSE;\n  rasterizer.polygonMode = VK_POLYGON_MODE_FILL;\n  rasterizer.lineWidth = 1.0f;\n  rasterizer.cullMode = VK_CULL_MODE_NONE;\n  rasterizer.depthBiasEnable = VK_FALSE;\n  RasterizerInfo = rasterizer;\n\n  VkPipelineDepthStencilStateCreateInfo depthStencil{};\n  depthStencil.sType =\n      VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;\n  depthStencil.depthTestEnable = VK_FALSE;\n  depthStencil.depthWriteEnable = VK_FALSE;\n  depthStencil.depthCompareOp = VK_COMPARE_OP_LESS;\n  depthStencil.depthBoundsTestEnable = VK_FALSE;\n  depthStencil.stencilTestEnable = VK_FALSE;\n  DepthStencilInfo = depthStencil;\n}\n\nvoid Pipeline::SetRasterizerInfo(\n    VkPipelineRasterizationStateCreateInfo rasterizerInfo) {\n  RasterizerInfo = rasterizerInfo;\n}\n\nvoid Pipeline::SetDepthStencilInfo(\n    VkPipelineDepthStencilStateCreateInfo depthStencilInfo) {\n  DepthStencilInfo = depthStencilInfo;\n}\n\nvoid Pipeline::SetPushConstants(VkPushConstantRange* pushConstantRange,\n                                int count) {\n  PushConstantRange = pushConstantRange;\n  PushConstantRangeCount = count;\n}\n\nvoid Pipeline::CreateWithShader(\n    char const* name, VkVertexInputBindingDescription bindingDescription,\n    VkVertexInputAttributeDescription* attributeDescriptions,\n    size_t attributeNum, VkDescriptorSetLayout setLayout, bool enableBlending) {\n  ImpLog(LogLevel::Debug, LogChannel::Render,\n         \"Creating pipeline with shader \\\"{:s}\\\"\\n\", name);\n\n  size_t vertShaderCodeSize;\n  std::string vertexShaderPath = fmt::format(FMT_COMPILE(\"{}/{}{}\"), ShaderPath,\n                                             name, VertShaderExtension);\n  char* vertShaderCode =\n      (char*)SDL_LoadFile(vertexShaderPath.c_str(), &vertShaderCodeSize);\n  if (!vertShaderCode) {\n    ImpLog(LogLevel::Debug, LogChannel::Render,\n           \"Failed to read shader source file\\n\");\n    Window->Shutdown();\n  }\n\n  size_t fragShaderCodeSize;\n  std::string fragShaderPath = fmt::format(FMT_COMPILE(\"{}/{}{}\"), ShaderPath,\n                                           name, FragShaderExtension);\n  char* fragShaderCode =\n      (char*)SDL_LoadFile(fragShaderPath.c_str(), &fragShaderCodeSize);\n  if (!fragShaderCode) {\n    ImpLog(LogLevel::Debug, LogChannel::Render,\n           \"Failed to read shader source file\\n\");\n    Window->Shutdown();\n  }\n\n  VkShaderModule vertShaderModule =\n      CreateShaderModule(vertShaderCode, vertShaderCodeSize);\n  VkShaderModule fragShaderModule =\n      CreateShaderModule(fragShaderCode, fragShaderCodeSize);\n\n  VkPipelineShaderStageCreateInfo vertShaderStageInfo{};\n  vertShaderStageInfo.sType =\n      VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;\n  vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT;\n  vertShaderStageInfo.module = vertShaderModule;\n  vertShaderStageInfo.pName = \"main\";\n\n  VkPipelineShaderStageCreateInfo fragShaderStageInfo{};\n  fragShaderStageInfo.sType =\n      VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;\n  fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT;\n  fragShaderStageInfo.module = fragShaderModule;\n  fragShaderStageInfo.pName = \"main\";\n\n  VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo,\n                                                    fragShaderStageInfo};\n\n  VkPipelineVertexInputStateCreateInfo vertexInputInfo{};\n  vertexInputInfo.sType =\n      VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;\n  vertexInputInfo.vertexBindingDescriptionCount = 1;\n  vertexInputInfo.vertexAttributeDescriptionCount =\n      static_cast<uint32_t>(attributeNum);\n  vertexInputInfo.pVertexBindingDescriptions = &bindingDescription;\n  vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions;\n\n  VkPipelineInputAssemblyStateCreateInfo inputAssembly{};\n  inputAssembly.sType =\n      VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;\n  inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;\n  inputAssembly.primitiveRestartEnable = VK_FALSE;\n\n  VkPipelineViewportStateCreateInfo viewportState{};\n  viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;\n  viewportState.viewportCount = 1;\n  viewportState.scissorCount = 1;\n\n  VkPipelineMultisampleStateCreateInfo multisampling{};\n  multisampling.sType =\n      VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;\n  multisampling.sampleShadingEnable = VK_FALSE;\n  multisampling.rasterizationSamples = (VkSampleCountFlagBits)Window->MsaaCount;\n\n  VkPipelineColorBlendAttachmentState colorBlendAttachment{};\n  if (enableBlending) {\n    colorBlendAttachment.colorWriteMask =\n        VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |\n        VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;\n    colorBlendAttachment.blendEnable = VK_TRUE;\n    colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;\n    colorBlendAttachment.dstColorBlendFactor =\n        VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;\n    colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD;\n    colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;\n    colorBlendAttachment.dstAlphaBlendFactor =\n        VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;\n    colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD;\n  } else {\n    colorBlendAttachment.colorWriteMask =\n        VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |\n        VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;\n    colorBlendAttachment.blendEnable = VK_FALSE;\n  }\n\n  VkPipelineColorBlendStateCreateInfo colorBlending{};\n  colorBlending.sType =\n      VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;\n  colorBlending.logicOpEnable = VK_FALSE;\n  colorBlending.logicOp = VK_LOGIC_OP_COPY;\n  colorBlending.attachmentCount = 1;\n  colorBlending.pAttachments = &colorBlendAttachment;\n  colorBlending.blendConstants[0] = 0.0f;\n  colorBlending.blendConstants[1] = 0.0f;\n  colorBlending.blendConstants[2] = 0.0f;\n  colorBlending.blendConstants[3] = 0.0f;\n\n  std::vector<VkDynamicState> dynamicStates = {VK_DYNAMIC_STATE_VIEWPORT,\n                                               VK_DYNAMIC_STATE_SCISSOR};\n  VkPipelineDynamicStateCreateInfo dynamicState{};\n  dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;\n  dynamicState.dynamicStateCount = static_cast<uint32_t>(dynamicStates.size());\n  dynamicState.pDynamicStates = dynamicStates.data();\n\n  VkDescriptorSetLayout texturedSetLayouts[] = {setLayout};\n\n  VkPipelineLayoutCreateInfo pipelineLayoutInfo{};\n  pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;\n  pipelineLayoutInfo.setLayoutCount = 1;\n  pipelineLayoutInfo.pSetLayouts = texturedSetLayouts;\n  pipelineLayoutInfo.pPushConstantRanges = PushConstantRange;\n  pipelineLayoutInfo.pushConstantRangeCount = PushConstantRangeCount;\n\n  if (vkCreatePipelineLayout(Device, &pipelineLayoutInfo, nullptr,\n                             &PipelineLayout) != VK_SUCCESS) {\n    ImpLog(LogLevel::Error, LogChannel::Render,\n           \"Failed to create pipeline layout!\");\n    Window->Shutdown();\n  }\n\n  VkGraphicsPipelineCreateInfo pipelineInfo{};\n  pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;\n  pipelineInfo.stageCount = 2;\n  pipelineInfo.pStages = shaderStages;\n  pipelineInfo.pVertexInputState = &vertexInputInfo;\n  pipelineInfo.pInputAssemblyState = &inputAssembly;\n  pipelineInfo.pViewportState = &viewportState;\n  pipelineInfo.pRasterizationState = &RasterizerInfo;\n  pipelineInfo.pMultisampleState = &multisampling;\n  pipelineInfo.pColorBlendState = &colorBlending;\n  pipelineInfo.pDynamicState = &dynamicState;\n  pipelineInfo.pDepthStencilState = &DepthStencilInfo;\n  pipelineInfo.layout = PipelineLayout;\n  pipelineInfo.renderPass = RenderPass;\n  pipelineInfo.subpass = 0;\n  pipelineInfo.basePipelineHandle = VK_NULL_HANDLE;\n\n  if (vkCreateGraphicsPipelines(Device, VK_NULL_HANDLE, 1, &pipelineInfo,\n                                nullptr, &GraphicsPipeline) != VK_SUCCESS) {\n    ImpLog(LogLevel::Error, LogChannel::Render,\n           \"Failed to create graphics pipeline!\");\n    Window->Shutdown();\n  }\n\n  vkDestroyShaderModule(Device, fragShaderModule, nullptr);\n  vkDestroyShaderModule(Device, vertShaderModule, nullptr);\n}\n\nVkShaderModule Pipeline::CreateShaderModule(char const* code, size_t codeSize) {\n  VkShaderModuleCreateInfo createInfo{};\n  createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;\n  createInfo.codeSize = codeSize;\n  createInfo.pCode = reinterpret_cast<const uint32_t*>(code);\n\n  VkShaderModule shaderModule;\n  if (vkCreateShaderModule(Device, &createInfo, nullptr, &shaderModule) !=\n      VK_SUCCESS) {\n    ImpLog(LogLevel::Error, LogChannel::Render,\n           \"Failed to create shader module!\");\n    Window->Shutdown();\n  }\n\n  return shaderModule;\n}\n\nPipeline::~Pipeline() {\n  vkDestroyPipeline(Device, GraphicsPipeline, nullptr);\n  vkDestroyPipelineLayout(Device, PipelineLayout, nullptr);\n}\n\n}  // namespace Vulkan\n}  // namespace Impacto"
  },
  {
    "path": "src/renderer/vulkan/pipeline.h",
    "content": "#pragma once\n\n#include <vulkan/vulkan.h>\n\nnamespace Impacto {\nnamespace Vulkan {\n\nclass Pipeline {\n public:\n  Pipeline(VkDevice device, VkRenderPass renderPass);\n  ~Pipeline();\n  void SetRasterizerInfo(VkPipelineRasterizationStateCreateInfo rasterizerInfo);\n  void SetDepthStencilInfo(\n      VkPipelineDepthStencilStateCreateInfo depthStencilInfo);\n  void SetPushConstants(VkPushConstantRange* pushConstantRange, int count);\n  void CreateWithShader(\n      char const* name, VkVertexInputBindingDescription bindingDescription,\n      VkVertexInputAttributeDescription* attributeDescriptions,\n      size_t attributeNum, VkDescriptorSetLayout setLayout,\n      bool enableBlending = true);\n\n  VkPipeline GraphicsPipeline;\n  VkPipelineLayout PipelineLayout;\n\n private:\n  void CreateRenderPass(VkFormat swapChainImageFormat);\n  VkShaderModule CreateShaderModule(char const* code, size_t codeSize);\n\n  VkDevice Device;\n  VkRenderPass RenderPass;\n  VkPipelineRasterizationStateCreateInfo RasterizerInfo;\n  VkPipelineDepthStencilStateCreateInfo DepthStencilInfo;\n  VkPushConstantRange* PushConstantRange = 0;\n  int PushConstantRangeCount = 0;\n};\n\n}  // namespace Vulkan\n}  // namespace Impacto"
  },
  {
    "path": "src/renderer/vulkan/renderer.cpp",
    "content": "#include \"renderer.h\"\n\n#include <SDL_vulkan.h>\n#include <set>\n\n#include \"../../profile/game.h\"\n#include \"../../game.h\"\n#include \"../../log.h\"\n#include \"3d/scene.h\"\n#include \"yuvframe.h\"\n#include \"nv12frame.h\"\n\n#ifndef IMPACTO_DISABLE_IMGUI\n#include <imgui_impl_vulkan.h>\n#endif\n\nnamespace Impacto {\nnamespace Vulkan {\n\nVkVertexInputBindingDescription Renderer::GetBindingDescription() {\n  VkVertexInputBindingDescription bindingDescription{};\n  bindingDescription.binding = 0;\n  bindingDescription.stride = sizeof(VertexBufferSprites);\n  bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;\n\n  return bindingDescription;\n}\n\nstd::array<VkVertexInputAttributeDescription, 4>\nRenderer::GetAttributeDescriptions() {\n  std::array<VkVertexInputAttributeDescription, 4> attributeDescriptions{};\n\n  attributeDescriptions[0].binding = 0;\n  attributeDescriptions[0].location = 0;\n  attributeDescriptions[0].format = VK_FORMAT_R32G32_SFLOAT;\n  attributeDescriptions[0].offset = offsetof(VertexBufferSprites, Position);\n\n  attributeDescriptions[1].binding = 0;\n  attributeDescriptions[1].location = 1;\n  attributeDescriptions[1].format = VK_FORMAT_R32G32_SFLOAT;\n  attributeDescriptions[1].offset = offsetof(VertexBufferSprites, UV);\n\n  attributeDescriptions[2].binding = 0;\n  attributeDescriptions[2].location = 2;\n  attributeDescriptions[2].format = VK_FORMAT_R32G32B32A32_SFLOAT;\n  attributeDescriptions[2].offset = offsetof(VertexBufferSprites, Tint);\n\n  attributeDescriptions[3].binding = 0;\n  attributeDescriptions[3].location = 3;\n  attributeDescriptions[3].format = VK_FORMAT_R32G32_SFLOAT;\n  attributeDescriptions[3].offset = offsetof(VertexBufferSprites, MaskUV);\n\n  return attributeDescriptions;\n}\n\nstatic VKAPI_ATTR VkBool32 VKAPI_CALL\nDebugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,\n              VkDebugUtilsMessageTypeFlagsEXT messageType,\n              const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData,\n              void* pUserData) {\n  Impacto::ImpLog(LogLevel::Debug, LogChannel::Render,\n                  \"validation layer: {:s}\\n\", pCallbackData->pMessage);\n  return VK_FALSE;\n}\n\nVkResult CreateDebugUtilsMessengerEXT(\n    VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo,\n    const VkAllocationCallbacks* pAllocator,\n    VkDebugUtilsMessengerEXT* pDebugMessenger) {\n  auto func = (PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr(\n      instance, \"vkCreateDebugUtilsMessengerEXT\");\n  if (func != nullptr) {\n    return func(instance, pCreateInfo, pAllocator, pDebugMessenger);\n  } else {\n    return VK_ERROR_EXTENSION_NOT_PRESENT;\n  }\n}\n\nvoid DestroyDebugUtilsMessengerEXT(VkInstance instance,\n                                   VkDebugUtilsMessengerEXT debugMessenger,\n                                   const VkAllocationCallbacks* pAllocator) {\n  auto func = (PFN_vkDestroyDebugUtilsMessengerEXT)vkGetInstanceProcAddr(\n      instance, \"vkDestroyDebugUtilsMessengerEXT\");\n  if (func != nullptr) {\n    func(instance, debugMessenger, pAllocator);\n  }\n}\n\nvoid Renderer::CreateInstance() {\n  unsigned int extensionCount = 0;\n  SDL_Vulkan_GetInstanceExtensions(Window->SDLWindow, &extensionCount, nullptr);\n  std::vector<const char*> extensionNames(extensionCount);\n  SDL_Vulkan_GetInstanceExtensions(Window->SDLWindow, &extensionCount,\n                                   extensionNames.data());\n\n  VkApplicationInfo appInfo = {};\n  appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;\n  appInfo.pApplicationName = Profile::WindowName;\n  appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0);\n  appInfo.pEngineName = \"impacto\";\n  appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0);\n  appInfo.apiVersion = VK_API_VERSION_1_1;\n\n  VkInstanceCreateInfo instanceCreateInfo = {};\n  instanceCreateInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;\n  instanceCreateInfo.pApplicationInfo = &appInfo;\n#ifdef __APPLE__\n  extensionNames.emplace_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME);\n  instanceCreateInfo.flags |= VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR;\n#endif\n  if (EnableValidationLayers) {\n    extensionNames.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);\n  }\n  instanceCreateInfo.enabledExtensionCount = (uint32_t)extensionNames.size();\n  instanceCreateInfo.ppEnabledExtensionNames = extensionNames.data();\n\n  VkResult result = vkCreateInstance(&instanceCreateInfo, nullptr, &Instance);\n\n  if (result != VK_SUCCESS) {\n    ImpLog(LogLevel::Error, LogChannel::Render,\n           \"Failed to create Vulkan instance! 0x{:04x}\\n\",\n           to_underlying(result));\n    Window->Shutdown();\n  }\n}\n\nvoid Renderer::SetupDebug() {\n  if (!EnableValidationLayers) return;\n\n  VkDebugUtilsMessengerCreateInfoEXT createInfo{};\n  createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;\n  createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT |\n                               VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT |\n                               VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;\n  createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT |\n                           VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT |\n                           VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT;\n  createInfo.pfnUserCallback = DebugCallback;\n  createInfo.pUserData = nullptr;\n  if (CreateDebugUtilsMessengerEXT(Instance, &createInfo, nullptr,\n                                   &DebugMessenger) != VK_SUCCESS) {\n    ImpLog(LogLevel::Error, LogChannel::Render,\n           \"Failed to create Vulkan debug!\");\n    Window->Shutdown();\n  }\n}\n\nvoid Renderer::PickPhysicalDevice() {\n  uint32_t deviceCount = 0;\n  vkEnumeratePhysicalDevices(Instance, &deviceCount, nullptr);\n\n  if (deviceCount == 0) {\n    ImpLog(LogLevel::Error, LogChannel::Render,\n           \"No suitable video adapter found!\");\n    Window->Shutdown();\n  }\n\n  std::vector<VkPhysicalDevice> devices(deviceCount);\n  vkEnumeratePhysicalDevices(Instance, &deviceCount, devices.data());\n\n  // TODO: Implement proper device selection\n  PhysicalDevice = devices[0];\n}\n\nvoid Renderer::FindQueues() {\n  uint32_t queueFamilyCount = 0;\n  vkGetPhysicalDeviceQueueFamilyProperties(PhysicalDevice, &queueFamilyCount,\n                                           nullptr);\n  std::vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount);\n  vkGetPhysicalDeviceQueueFamilyProperties(PhysicalDevice, &queueFamilyCount,\n                                           queueFamilies.data());\n\n  QueueIndices.GraphicsQueueIdx = 0xFFFFFFFF;\n  QueueIndices.PresentQueueIdx = 0xFFFFFFFF;\n  int i = 0;\n  for (const auto& queueFamily : queueFamilies) {\n    if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) {\n      QueueIndices.GraphicsQueueIdx = i;\n    }\n\n    VkBool32 presentSupport = false;\n    vkGetPhysicalDeviceSurfaceSupportKHR(PhysicalDevice, i, Surface,\n                                         &presentSupport);\n    if (presentSupport) QueueIndices.PresentQueueIdx = i;\n\n    if (QueueIndices.GraphicsQueueIdx != 0xFFFFFFFF &&\n        QueueIndices.PresentQueueIdx != 0xFFFFFFFF)\n      return;\n\n    i++;\n  }\n}\n\nvoid Renderer::CreateLogicalDevice() {\n  FindQueues();\n\n  if (QueueIndices.GraphicsQueueIdx == 0xFFFFFFFF) {\n    ImpLog(LogLevel::Error, LogChannel::Render, \"No graphics queue found!\");\n    Window->Shutdown();\n  }\n\n  const std::vector<const char*> deviceExtensions = {\n      VK_KHR_SWAPCHAIN_EXTENSION_NAME, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME};\n\n  std::vector<VkDeviceQueueCreateInfo> queueCreateInfos;\n  std::set<uint32_t> uniqueQueueFamilies = {QueueIndices.GraphicsQueueIdx,\n                                            QueueIndices.PresentQueueIdx};\n\n  float queuePriority = 1.0f;\n  for (uint32_t queueFamily : uniqueQueueFamilies) {\n    VkDeviceQueueCreateInfo queueCreateInfo{};\n    queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;\n    queueCreateInfo.queueFamilyIndex = queueFamily;\n    queueCreateInfo.queueCount = 1;\n    queueCreateInfo.pQueuePriorities = &queuePriority;\n    queueCreateInfos.push_back(queueCreateInfo);\n  }\n\n  VkPhysicalDeviceFeatures deviceFeatures{};\n  deviceFeatures.samplerAnisotropy = VK_TRUE;\n\n  VkDeviceCreateInfo createInfo{};\n  createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;\n  createInfo.queueCreateInfoCount =\n      static_cast<uint32_t>(queueCreateInfos.size());\n  createInfo.pQueueCreateInfos = queueCreateInfos.data();\n  createInfo.pEnabledFeatures = &deviceFeatures;\n  createInfo.enabledExtensionCount = (uint32_t)deviceExtensions.size();\n  createInfo.ppEnabledExtensionNames = deviceExtensions.data();\n\n  if (vkCreateDevice(PhysicalDevice, &createInfo, nullptr, &Device) !=\n      VK_SUCCESS) {\n    ImpLog(LogLevel::Error, LogChannel::Render,\n           \"Failed to create logical device!\");\n    Window->Shutdown();\n  }\n\n  vkGetDeviceQueue(Device, QueueIndices.GraphicsQueueIdx, 0, &GraphicsQueue);\n  vkGetDeviceQueue(Device, QueueIndices.PresentQueueIdx, 0, &PresentQueue);\n\n  CreateAllocator(PhysicalDevice, Device, Instance);\n\n  vkCmdPushDescriptorSetKHR =\n      (PFN_vkCmdPushDescriptorSetKHR)vkGetDeviceProcAddr(\n          Device, \"vkCmdPushDescriptorSetKHR\");\n\n  MainUploadContext.Device = Device;\n  MainUploadContext.GraphicsQueue = GraphicsQueue;\n}\n\nvoid Renderer::CreateSurface() {\n  if (SDL_Vulkan_CreateSurface(Window->SDLWindow, Instance, &Surface) !=\n      SDL_TRUE) {\n    ImpLog(LogLevel::Error, LogChannel::Render, \"Failed to create surface!\");\n    Window->Shutdown();\n  }\n}\n\nvoid Renderer::CreateSwapChain() {\n  VkSurfaceCapabilitiesKHR surfaceCapabilities;\n  vkGetPhysicalDeviceSurfaceCapabilitiesKHR(PhysicalDevice, Surface,\n                                            &surfaceCapabilities);\n\n  uint32_t surfaceFormatsCount;\n  vkGetPhysicalDeviceSurfaceFormatsKHR(PhysicalDevice, Surface,\n                                       &surfaceFormatsCount, nullptr);\n  std::vector<VkSurfaceFormatKHR> surfaceFormats(surfaceFormatsCount);\n  vkGetPhysicalDeviceSurfaceFormatsKHR(\n      PhysicalDevice, Surface, &surfaceFormatsCount, surfaceFormats.data());\n\n  int width, height = 0;\n  SDL_Vulkan_GetDrawableSize(Window->SDLWindow, &width, &height);\n\n  SwapChainExtent.width = width;\n  SwapChainExtent.height = height;\n  SwapChainImageFormat = surfaceFormats[0].format;\n\n  uint32_t imageCount = surfaceCapabilities.minImageCount + 1;\n  if (surfaceCapabilities.maxImageCount > 0 &&\n      imageCount > surfaceCapabilities.maxImageCount) {\n    imageCount = surfaceCapabilities.maxImageCount;\n  }\n\n  VkSwapchainCreateInfoKHR createInfo = {};\n  createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;\n  createInfo.surface = Surface;\n  createInfo.minImageCount = surfaceCapabilities.minImageCount;\n  createInfo.imageFormat = SwapChainImageFormat;\n  createInfo.imageColorSpace = surfaceFormats[0].colorSpace;\n  createInfo.imageExtent = SwapChainExtent;\n  createInfo.imageArrayLayers = 1;\n  createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;\n\n  uint32_t queueFamilyIndices[] = {QueueIndices.GraphicsQueueIdx,\n                                   QueueIndices.PresentQueueIdx};\n  if (QueueIndices.GraphicsQueueIdx != QueueIndices.PresentQueueIdx) {\n    createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT;\n    createInfo.queueFamilyIndexCount = 2;\n    createInfo.pQueueFamilyIndices = queueFamilyIndices;\n  } else {\n    createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;\n  }\n\n  createInfo.preTransform = surfaceCapabilities.currentTransform;\n  createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;\n  createInfo.presentMode = VK_PRESENT_MODE_FIFO_KHR;  // Vsync, kind of\n  createInfo.clipped = VK_TRUE;\n\n  vkCreateSwapchainKHR(Device, &createInfo, nullptr, &SwapChain);\n\n  uint32_t swapChainImageCount = 0;\n  vkGetSwapchainImagesKHR(Device, SwapChain, &swapChainImageCount, nullptr);\n  SwapChainImages.resize(swapChainImageCount);\n  vkGetSwapchainImagesKHR(Device, SwapChain, &swapChainImageCount,\n                          SwapChainImages.data());\n}\n\nvoid Renderer::CreateImageViews() {\n  SwapChainImageViews.resize(SwapChainImages.size());\n  for (size_t i = 0; i < SwapChainImages.size(); i++) {\n    VkImageViewCreateInfo createInfo{};\n    createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;\n    createInfo.image = SwapChainImages[i];\n    createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;\n    createInfo.format = SwapChainImageFormat;\n    createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY;\n    createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY;\n    createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY;\n    createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY;\n    createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;\n    createInfo.subresourceRange.baseMipLevel = 0;\n    createInfo.subresourceRange.levelCount = 1;\n    createInfo.subresourceRange.baseArrayLayer = 0;\n    createInfo.subresourceRange.layerCount = 1;\n    if (vkCreateImageView(Device, &createInfo, nullptr,\n                          &SwapChainImageViews[i]) != VK_SUCCESS) {\n      ImpLog(LogLevel::Error, LogChannel::Render,\n             \"Failed to create image view!\");\n      Window->Shutdown();\n    }\n  }\n}\n\nvoid Renderer::CreateRenderPass() {\n  VkAttachmentDescription colorAttachment{};\n  colorAttachment.format = SwapChainImageFormat;\n  colorAttachment.samples = (VkSampleCountFlagBits)Window->MsaaCount;\n  colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;\n  colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;\n  colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;\n  colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;\n  colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;\n  colorAttachment.finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;\n\n  VkAttachmentReference colorAttachmentRef{};\n  colorAttachmentRef.attachment = 0;\n  colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;\n\n  VkAttachmentDescription depthAttachment{};\n  depthAttachment.format = VK_FORMAT_D32_SFLOAT;\n  depthAttachment.samples = (VkSampleCountFlagBits)Window->MsaaCount;\n  depthAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;\n  depthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;\n  depthAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;\n  depthAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;\n  depthAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;\n  depthAttachment.finalLayout =\n      VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;\n\n  VkAttachmentReference depthAttachmentRef{};\n  depthAttachmentRef.attachment = 1;\n  depthAttachmentRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;\n\n  VkAttachmentDescription colorAttachmentResolve{};\n  colorAttachmentResolve.format = SwapChainImageFormat;\n  colorAttachmentResolve.samples = VK_SAMPLE_COUNT_1_BIT;\n  colorAttachmentResolve.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;\n  colorAttachmentResolve.storeOp = VK_ATTACHMENT_STORE_OP_STORE;\n  colorAttachmentResolve.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;\n  colorAttachmentResolve.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;\n  colorAttachmentResolve.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;\n  colorAttachmentResolve.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;\n\n  VkAttachmentReference colorAttachmentResolveRef{};\n  colorAttachmentResolveRef.attachment = 2;\n  colorAttachmentResolveRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;\n\n  VkSubpassDescription subpass{};\n  subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;\n  subpass.colorAttachmentCount = 1;\n  subpass.pColorAttachments = &colorAttachmentRef;\n  subpass.pDepthStencilAttachment = &depthAttachmentRef;\n  if (Window->MsaaCount > 1)\n    subpass.pResolveAttachments = &colorAttachmentResolveRef;\n\n  VkSubpassDependency dependency{};\n  dependency.srcSubpass = VK_SUBPASS_EXTERNAL;\n  dependency.dstSubpass = 0;\n  dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT |\n                            VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT;\n  dependency.srcAccessMask = 0;\n  dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT |\n                            VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT;\n  dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT |\n                             VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;\n\n  VkAttachmentDescription attachments[3] = {colorAttachment, depthAttachment,\n                                            colorAttachmentResolve};\n  VkRenderPassCreateInfo renderPassInfo{};\n  renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;\n  renderPassInfo.attachmentCount = Window->MsaaCount > 1 ? 3 : 2;\n  renderPassInfo.pAttachments = attachments;\n  renderPassInfo.subpassCount = 1;\n  renderPassInfo.pSubpasses = &subpass;\n  renderPassInfo.dependencyCount = 1;\n  renderPassInfo.pDependencies = &dependency;\n\n  if (vkCreateRenderPass(Device, &renderPassInfo, nullptr, &RenderPass) !=\n      VK_SUCCESS) {\n    ImpLog(LogLevel::Debug, LogChannel::Render,\n           \"Failed to read create render pass!\\n\");\n    Window->Shutdown();\n  }\n}\n\nvoid Renderer::CreateFramebuffers() {\n  SwapChainFramebuffers.resize(SwapChainImageViews.size());\n  for (size_t i = 0; i < SwapChainImageViews.size(); i++) {\n    VkImageView attachments[] = {ColorImageView, DepthImageView,\n                                 SwapChainImageViews[i]};\n    if (Window->MsaaCount == 1) attachments[0] = attachments[2];\n\n    VkFramebufferCreateInfo framebufferInfo{};\n    framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;\n    framebufferInfo.renderPass = RenderPass;\n    framebufferInfo.attachmentCount = Window->MsaaCount > 1 ? 3 : 2;\n    framebufferInfo.pAttachments = attachments;\n    framebufferInfo.width = SwapChainExtent.width;\n    framebufferInfo.height = SwapChainExtent.height;\n    framebufferInfo.layers = 1;\n\n    if (vkCreateFramebuffer(Device, &framebufferInfo, nullptr,\n                            &SwapChainFramebuffers[i]) != VK_SUCCESS) {\n      ImpLog(LogLevel::Debug, LogChannel::Render,\n             \"Failed to create framebuffer!\\n\");\n      Window->Shutdown();\n    }\n  }\n}\n\nvoid Renderer::CreateCommandPool() {\n  VkCommandPoolCreateInfo poolInfo{};\n  poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;\n  poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;\n  poolInfo.queueFamilyIndex = QueueIndices.GraphicsQueueIdx;\n  if ((vkCreateCommandPool(Device, &poolInfo, nullptr, &CommandPool) !=\n       VK_SUCCESS) ||\n      (vkCreateCommandPool(Device, &poolInfo, nullptr,\n                           &MainUploadContext.CommandPool) != VK_SUCCESS)) {\n    ImpLog(LogLevel::Debug, LogChannel::Render,\n           \"Failed to create command pool!\\n\");\n    Window->Shutdown();\n  }\n}\n\nvoid Renderer::CreateCommandBuffer() {\n  VkCommandBufferAllocateInfo allocInfo{};\n  allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;\n  allocInfo.commandPool = CommandPool;\n  allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;\n  allocInfo.commandBufferCount = MAX_FRAMES_IN_FLIGHT;\n\n  if ((vkAllocateCommandBuffers(Device, &allocInfo, CommandBuffers) !=\n       VK_SUCCESS) ||\n      (vkAllocateCommandBuffers(Device, &allocInfo,\n                                &MainUploadContext.CommandBuffer) !=\n       VK_SUCCESS)) {\n    ImpLog(LogLevel::Debug, LogChannel::Render,\n           \"Failed to allocate command buffers!\\n\");\n    Window->Shutdown();\n  }\n}\n\nvoid Renderer::CreateSyncObjects() {\n  VkSemaphoreCreateInfo semaphoreInfo{};\n  semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;\n\n  VkFenceCreateInfo fenceInfo{};\n  fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;\n  fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;\n\n  for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {\n    if (vkCreateSemaphore(Device, &semaphoreInfo, nullptr,\n                          &ImageAvailableSemaphores[i]) != VK_SUCCESS ||\n        vkCreateSemaphore(Device, &semaphoreInfo, nullptr,\n                          &RenderFinishedSemaphores[i]) != VK_SUCCESS ||\n        vkCreateFence(Device, &fenceInfo, nullptr, &InFlightFences[i]) !=\n            VK_SUCCESS ||\n        vkCreateFence(Device, &fenceInfo, nullptr,\n                      &MainUploadContext.UploadFence) != VK_SUCCESS) {\n      ImpLog(LogLevel::Debug, LogChannel::Render,\n             \"Failed to create synchronization objects for a frame!\\n\");\n      Window->Shutdown();\n    }\n  }\n}\n\nvoid Renderer::CreateVertexBuffer() {\n  VertexBufferAlloc =\n      CreateBuffer(VertexBufferSize, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,\n                   VMA_MEMORY_USAGE_CPU_ONLY);\n\n  void* data;\n  vmaMapMemory(Allocator, VertexBufferAlloc.Allocation, &data);\n  VertexBuffer = (uint8_t*)data;\n}\n\nvoid Renderer::CreateIndexBuffer() {\n  IndexBufferAlloc =\n      CreateBuffer(IndexBufferCount, VK_BUFFER_USAGE_INDEX_BUFFER_BIT,\n                   VMA_MEMORY_USAGE_CPU_ONLY);\n\n  void* data;\n  vmaMapMemory(Allocator, IndexBufferAlloc.Allocation, &data);\n  IndexBuffer = (uint16_t*)data;\n}\n\nvoid Renderer::CreateDescriptors() {\n  std::vector<VkDescriptorPoolSize> sizes = {\n      {VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 10},\n      {VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 10},\n      {VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 10},\n      {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 100}};\n\n  VkDescriptorPoolCreateInfo poolInfo = {};\n  poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;\n  poolInfo.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT;\n  poolInfo.maxSets = 100;\n  poolInfo.poolSizeCount = (uint32_t)sizes.size();\n  poolInfo.pPoolSizes = sizes.data();\n  vkCreateDescriptorPool(Device, &poolInfo, nullptr, &DescriptorPool);\n\n  VkDescriptorSetLayoutBinding textureBind = {};\n  textureBind.binding = 0;\n  textureBind.descriptorCount = 1;\n  textureBind.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;\n  textureBind.pImmutableSamplers = nullptr;\n  textureBind.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;\n\n  VkDescriptorSetLayoutCreateInfo textureBindInfo = {};\n  textureBindInfo.bindingCount = 1;\n  textureBindInfo.flags =\n      VK_DESCRIPTOR_SET_LAYOUT_CREATE_PUSH_DESCRIPTOR_BIT_KHR;\n  textureBindInfo.pNext = nullptr;\n  textureBindInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;\n  textureBindInfo.pBindings = &textureBind;\n\n  vkCreateDescriptorSetLayout(Device, &textureBindInfo, nullptr,\n                              &SingleTextureSetLayout);\n\n  VkDescriptorSetLayoutBinding textureBinds;\n  textureBinds.binding = 0;\n  textureBinds.descriptorCount = 2;\n  textureBinds.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;\n  textureBinds.pImmutableSamplers = nullptr;\n  textureBinds.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;\n\n  VkDescriptorSetLayoutCreateInfo texturesBindInfo = {};\n  texturesBindInfo.bindingCount = 1;\n  texturesBindInfo.flags =\n      VK_DESCRIPTOR_SET_LAYOUT_CREATE_PUSH_DESCRIPTOR_BIT_KHR;\n  texturesBindInfo.pNext = nullptr;\n  texturesBindInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;\n  texturesBindInfo.pBindings = &textureBinds;\n\n  vkCreateDescriptorSetLayout(Device, &texturesBindInfo, nullptr,\n                              &DoubleTextureSetLayout);\n\n  textureBinds.descriptorCount = 3;\n  vkCreateDescriptorSetLayout(Device, &texturesBindInfo, nullptr,\n                              &TripleTextureSetLayout);\n}\n\nvoid Renderer::CreateColorAndDepthImage() {\n  VkExtent3D imageExtent;\n  imageExtent.width = SwapChainExtent.width;\n  imageExtent.height = SwapChainExtent.height;\n  imageExtent.depth = 1;\n\n  // Color image\n  VkImageCreateInfo cimgInfo =\n      GetImageCreateInfo(SwapChainImageFormat, imageExtent,\n                         static_cast<VkSampleCountFlagBits>(Window->MsaaCount));\n  cimgInfo.tiling = VK_IMAGE_TILING_OPTIMAL;\n  cimgInfo.usage = VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT |\n                   VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;\n  VmaAllocationCreateInfo cimgAllocinfo = {};\n  cimgAllocinfo.usage = VMA_MEMORY_USAGE_GPU_ONLY;\n  vmaCreateImage(Allocator, &cimgInfo, &cimgAllocinfo, &ColorImage.Image,\n                 &ColorImage.Allocation, nullptr);\n\n  VkImageViewCreateInfo colorImageInfo = {};\n  colorImageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;\n  colorImageInfo.pNext = nullptr;\n  colorImageInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;\n  colorImageInfo.image = ColorImage.Image;\n  colorImageInfo.format = SwapChainImageFormat;\n  colorImageInfo.subresourceRange.baseMipLevel = 0;\n  colorImageInfo.subresourceRange.levelCount = 1;\n  colorImageInfo.subresourceRange.baseArrayLayer = 0;\n  colorImageInfo.subresourceRange.layerCount = 1;\n  colorImageInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;\n  vkCreateImageView(Device, &colorImageInfo, nullptr, &ColorImageView);\n\n  // Depth image\n  VkImageCreateInfo dimgInfo =\n      GetImageCreateInfo(VK_FORMAT_D32_SFLOAT, imageExtent,\n                         static_cast<VkSampleCountFlagBits>(Window->MsaaCount));\n  dimgInfo.tiling = VK_IMAGE_TILING_OPTIMAL;\n  dimgInfo.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;\n  VmaAllocationCreateInfo dimgAllocinfo = {};\n  dimgAllocinfo.usage = VMA_MEMORY_USAGE_GPU_ONLY;\n  vmaCreateImage(Allocator, &dimgInfo, &dimgAllocinfo, &DepthImage.Image,\n                 &DepthImage.Allocation, nullptr);\n\n  VkImageViewCreateInfo imageInfo = {};\n  imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;\n  imageInfo.pNext = nullptr;\n  imageInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;\n  imageInfo.image = DepthImage.Image;\n  imageInfo.format = VK_FORMAT_D32_SFLOAT;\n  imageInfo.subresourceRange.baseMipLevel = 0;\n  imageInfo.subresourceRange.levelCount = 1;\n  imageInfo.subresourceRange.baseArrayLayer = 0;\n  imageInfo.subresourceRange.layerCount = 1;\n  imageInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;\n  vkCreateImageView(Device, &imageInfo, nullptr, &DepthImageView);\n}\n\nvoid Renderer::CleanupSwapChain() {\n  for (size_t i = 0; i < SwapChainFramebuffers.size(); i++) {\n    vkDestroyFramebuffer(Device, SwapChainFramebuffers[i], nullptr);\n  }\n\n  for (size_t i = 0; i < SwapChainImageViews.size(); i++) {\n    vkDestroyImageView(Device, SwapChainImageViews[i], nullptr);\n  }\n\n  vkDestroySwapchainKHR(Device, SwapChain, nullptr);\n}\n\nvoid Renderer::RecreateSwapChain() {\n  vkDeviceWaitIdle(Device);\n\n  CleanupSwapChain();\n\n  CreateSwapChain();\n  CreateImageViews();\n  CreateFramebuffers();\n}\n\nvoid Renderer::Init() {\n  if (IsInit) return;\n  ImpLog(LogLevel::Info, LogChannel::Render,\n         \"Initializing Renderer2D Vulkan system\\n\");\n  IsInit = true;\n\n  CurrentFrameIndex = 0;\n  CurrentImageIndex = 0;\n\n  VkWindow = new VulkanWindow();\n  VkWindow->Init();\n  Window = (BaseWindow*)VkWindow;\n\n  CreateInstance();\n  SetupDebug();\n  PickPhysicalDevice();\n  CreateSurface();\n  CreateLogicalDevice();\n\n  CreateSwapChain();\n  CreateImageViews();\n  CreateColorAndDepthImage();\n  CreateRenderPass();\n  CreateFramebuffers();\n  CreateCommandPool();\n  CreateVertexBuffer();\n  CreateIndexBuffer();\n  CreateCommandBuffer();\n  CreateSyncObjects();\n  CreateDescriptors();\n\n  auto attributeDescriptions = GetAttributeDescriptions();\n  auto bindingDescription = GetBindingDescription();\n\n  PipelineSprite = new Pipeline(Device, RenderPass);\n  PipelineSprite->CreateWithShader(\n      \"Sprite\", bindingDescription, attributeDescriptions.data(),\n      attributeDescriptions.size(), SingleTextureSetLayout);\n\n  PipelineSpriteNoBlending = new Pipeline(Device, RenderPass);\n  PipelineSpriteNoBlending->CreateWithShader(\n      \"Sprite\", bindingDescription, attributeDescriptions.data(),\n      attributeDescriptions.size(), SingleTextureSetLayout, false);\n\n  PipelineSpriteInverted = new Pipeline(Device, RenderPass);\n  PipelineSpriteInverted->CreateWithShader(\n      \"Sprite_inverted\", bindingDescription, attributeDescriptions.data(),\n      attributeDescriptions.size(), SingleTextureSetLayout);\n\n  VkPushConstantRange maskedSpritePushConstant;\n  maskedSpritePushConstant.offset = 0;\n  maskedSpritePushConstant.size = sizeof(SpritePushConstants);\n  maskedSpritePushConstant.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;\n  PipelineMaskedSprite = new Pipeline(Device, RenderPass);\n  PipelineMaskedSprite->SetPushConstants(&maskedSpritePushConstant, 1);\n  PipelineMaskedSprite->CreateWithShader(\n      \"MaskedSprite\", bindingDescription, attributeDescriptions.data(),\n      attributeDescriptions.size(), DoubleTextureSetLayout);\n\n  VkPushConstantRange maskedSpriteNoAlphaPushConstant;\n  maskedSpriteNoAlphaPushConstant.offset = 0;\n  maskedSpriteNoAlphaPushConstant.size = sizeof(MaskedNoAlphaPushConstants);\n  maskedSpriteNoAlphaPushConstant.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;\n  PipelineMaskedSpriteNoAlpha = new Pipeline(Device, RenderPass);\n  PipelineMaskedSpriteNoAlpha->SetPushConstants(\n      &maskedSpriteNoAlphaPushConstant, 1);\n  PipelineMaskedSpriteNoAlpha->CreateWithShader(\n      \"MaskedSpriteNoAlpha\", bindingDescription, attributeDescriptions.data(),\n      attributeDescriptions.size(), DoubleTextureSetLayout);\n\n  VkPushConstantRange yuvFramePushConstants;\n  yuvFramePushConstants.offset = 0;\n  yuvFramePushConstants.size = sizeof(YUVFramePushConstants);\n  yuvFramePushConstants.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;\n  PipelineYUVFrame = new Pipeline(Device, RenderPass);\n  PipelineYUVFrame->SetPushConstants(&yuvFramePushConstants, 1);\n  PipelineYUVFrame->CreateWithShader(\n      \"YUVFrame\", bindingDescription, attributeDescriptions.data(),\n      attributeDescriptions.size(), TripleTextureSetLayout);\n  PipelineNV12Frame = new Pipeline(Device, RenderPass);\n  PipelineNV12Frame->SetPushConstants(&yuvFramePushConstants, 1);\n  PipelineNV12Frame->CreateWithShader(\n      \"NV12Frame\", bindingDescription, attributeDescriptions.data(),\n      attributeDescriptions.size(), TripleTextureSetLayout);\n\n  VkPushConstantRange ccBoxPushConstant;\n  ccBoxPushConstant.offset = 0;\n  ccBoxPushConstant.size = sizeof(CCBoxPushConstants);\n  ccBoxPushConstant.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;\n  PipelineCCMessageBox = new Pipeline(Device, RenderPass);\n  PipelineCCMessageBox->SetPushConstants(&ccBoxPushConstant, 1);\n  PipelineCCMessageBox->CreateWithShader(\n      \"CCMessageBoxSprite\", bindingDescription, attributeDescriptions.data(),\n      attributeDescriptions.size(), DoubleTextureSetLayout);\n\n  PipelineCHLCCMenuBackground = new Pipeline(Device, RenderPass);\n  PipelineCHLCCMenuBackground->SetPushConstants(&ccBoxPushConstant, 1);\n  PipelineCHLCCMenuBackground->CreateWithShader(\n      \"CHLCCMenuBackground\", bindingDescription, attributeDescriptions.data(),\n      attributeDescriptions.size(), DoubleTextureSetLayout);\n\n  CurrentPipeline = PipelineSprite;\n\n  if (+Profile::GameFeatures & +GameFeature::Scene3D) {\n    Scene = new Scene3D(VkWindow, Device, RenderPass, CommandBuffers);\n    Scene->Init();\n  }\n\n  // Make 1x1 white pixel for colored rectangles\n  Texture rectTexture;\n  rectTexture.Load1x1(0xFF, 0xFF, 0xFF, 0xFF);\n  SpriteSheet rectSheet(1.0f, 1.0f);\n  rectSheet.Texture = rectTexture.Submit();\n  RectSprite = Sprite(rectSheet, 0.0f, 0.0f, 1.0f, 1.0f);\n\n  VkSamplerCreateInfo samplerInfo = {};\n  samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;\n  samplerInfo.pNext = nullptr;\n  samplerInfo.magFilter = VK_FILTER_LINEAR;\n  samplerInfo.minFilter = VK_FILTER_LINEAR;\n  samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT;\n  samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT;\n  samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;\n  samplerInfo.anisotropyEnable = VK_TRUE;\n  samplerInfo.maxAnisotropy = 16;\n  vkCreateSampler(Device, &samplerInfo, nullptr, &Sampler);\n\n#ifndef IMPACTO_DISABLE_IMGUI\n  // Setup Platform/Renderer backends\n  ImGui_ImplSDL2_InitForVulkan(VkWindow->SDLWindow);\n\n  ImGui_ImplVulkan_InitInfo imguiInfo = {};\n  imguiInfo.Instance = Instance;\n  imguiInfo.PhysicalDevice = PhysicalDevice;\n  imguiInfo.Device = Device;\n  imguiInfo.QueueFamily = QueueIndices.PresentQueueIdx;\n  imguiInfo.Queue = PresentQueue;\n  imguiInfo.DescriptorPool = DescriptorPool;\n  imguiInfo.RenderPass = RenderPass;\n  imguiInfo.Subpass = 0;\n  imguiInfo.MinImageCount = 2;\n  imguiInfo.ImageCount = 2;\n  imguiInfo.MSAASamples = (VkSampleCountFlagBits)Window->MsaaCount;\n  ImGui_ImplVulkan_Init(&imguiInfo);\n#endif\n\n  MainRendererInstance = this;\n}\n\nvoid Renderer::Shutdown() {\n  if (!IsInit) return;\n  IsInit = false;\n\n  if (+Profile::GameFeatures & +GameFeature::Scene3D) {\n    Scene->Shutdown();\n  }\n\n  for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {\n    vkWaitForFences(Device, 1, &InFlightFences[i], VK_TRUE, UINT64_MAX);\n  }\n\n  // Textures are invalidated when freeing, so store keys first\n  std::vector<uint32_t> textureIds;\n  for (const auto& texture : Textures) {\n    textureIds.push_back(texture.first);\n  }\n  for (uint32_t id : textureIds) {\n    FreeTexture(id);\n  }\n  for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {\n    vkDestroySemaphore(Device, RenderFinishedSemaphores[i], nullptr);\n    vkDestroySemaphore(Device, ImageAvailableSemaphores[i], nullptr);\n    vkDestroyFence(Device, InFlightFences[i], nullptr);\n  }\n  vkDestroyCommandPool(Device, CommandPool, nullptr);\n  vkDestroyRenderPass(Device, RenderPass, nullptr);\n  vmaUnmapMemory(Allocator, VertexBufferAlloc.Allocation);\n  vmaDestroyBuffer(Allocator, VertexBufferAlloc.Buffer,\n                   VertexBufferAlloc.Allocation);\n  vmaUnmapMemory(Allocator, IndexBufferAlloc.Allocation);\n  vmaDestroyBuffer(Allocator, IndexBufferAlloc.Buffer,\n                   IndexBufferAlloc.Allocation);\n  vkDestroyImageView(Device, DepthImageView, nullptr);\n  vmaDestroyImage(Allocator, DepthImage.Image, DepthImage.Allocation);\n  vkDestroyImageView(Device, ColorImageView, nullptr);\n  vmaDestroyImage(Allocator, ColorImage.Image, ColorImage.Allocation);\n  CleanupSwapChain();\n  vkDestroySurfaceKHR(Instance, Surface, nullptr);\n  vkDestroyDevice(Device, nullptr);\n  if (EnableValidationLayers) {\n    DestroyDebugUtilsMessengerEXT(Instance, DebugMessenger, nullptr);\n  }\n  vkDestroyInstance(Instance, nullptr);\n}\n\n#ifndef IMPACTO_DISABLE_IMGUI\nvoid Renderer::ImGuiBeginFrame() {\n  ImGui_ImplVulkan_NewFrame();\n  ImGui_ImplSDL2_NewFrame();\n  ImGui::NewFrame();\n}\n#endif\n\nvoid Renderer::BeginFrame() {\n  if (Drawing) {\n    ImpLog(LogLevel::Error, LogChannel::Render,\n           \"Renderer->BeginFrame() called before EndFrame()\\n\");\n    return;\n  }\n\n  Drawing = true;\n\n  CurrentTexture = 0;\n\n  vkWaitForFences(Device, 1, &InFlightFences[CurrentFrameIndex], VK_TRUE,\n                  UINT64_MAX);\n\n  vkAcquireNextImageKHR(Device, SwapChain, UINT64_MAX,\n                        ImageAvailableSemaphores[CurrentFrameIndex],\n                        VK_NULL_HANDLE, &CurrentImageIndex);\n\n  vkResetFences(Device, 1, &InFlightFences[CurrentFrameIndex]);\n\n  VkCommandBufferBeginInfo beginInfo{};\n  beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;\n  beginInfo.flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT;\n\n  vkResetCommandBuffer(CommandBuffers[CurrentFrameIndex], 0);\n  if (vkBeginCommandBuffer(CommandBuffers[CurrentFrameIndex], &beginInfo) !=\n      VK_SUCCESS) {\n    ImpLog(LogLevel::Debug, LogChannel::Render,\n           \"Failed to begin recording command buffer!\\n\");\n    Window->Shutdown();\n  }\n\n  VkRenderPassBeginInfo renderPassInfo{};\n  renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;\n  renderPassInfo.renderPass = RenderPass;\n  renderPassInfo.framebuffer = SwapChainFramebuffers[CurrentImageIndex];\n  renderPassInfo.renderArea.offset = {0, 0};\n  renderPassInfo.renderArea.extent = SwapChainExtent;\n  renderPassInfo.clearValueCount = 0;\n\n  vkCmdBeginRenderPass(CommandBuffers[CurrentFrameIndex], &renderPassInfo,\n                       VK_SUBPASS_CONTENTS_INLINE);\n\n  VkClearValue clearColor = {{{0.0f, 0.0f, 0.0f, 1.0f}}};\n  VkClearAttachment clearAttachments[2] = {};\n  clearAttachments[0].aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;\n  clearAttachments[0].clearValue = clearColor;\n  clearAttachments[0].colorAttachment = 0;\n  VkClearValue clearDepth = {};\n  clearDepth.depthStencil = {1.0f, 0};\n  clearAttachments[1].aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;\n  clearAttachments[1].clearValue = clearDepth;\n  VkClearRect clearRect = {};\n  clearRect.layerCount = 1;\n  clearRect.rect.offset = {0, 0};\n  clearRect.rect.extent = SwapChainExtent;\n\n  vkCmdClearAttachments(CommandBuffers[CurrentFrameIndex], 2, clearAttachments,\n                        1, &clearRect);\n  vkCmdBindPipeline(CommandBuffers[CurrentFrameIndex],\n                    VK_PIPELINE_BIND_POINT_GRAPHICS,\n                    CurrentPipeline->GraphicsPipeline);\n  VkViewport viewport{};\n  viewport.x = 0.0f;\n  viewport.y = 0.0f;\n  viewport.width = static_cast<float>(SwapChainExtent.width);\n  viewport.height = static_cast<float>(SwapChainExtent.height);\n  viewport.minDepth = 0.0f;\n  viewport.maxDepth = 1.0f;\n  vkCmdSetViewport(CommandBuffers[CurrentFrameIndex], 0, 1, &viewport);\n\n  VkRect2D scissor{};\n  scissor.offset = {0, 0};\n  scissor.extent = SwapChainExtent;\n  vkCmdSetScissor(CommandBuffers[CurrentFrameIndex], 0, 1, &scissor);\n  PreviousScissorRect = RectF(0.0f, 0.0f, (float)SwapChainExtent.width,\n                              (float)SwapChainExtent.height);\n\n  VertexBufferOffset = 0;\n  IndexBufferOffset = 0;\n}\n\nvoid Renderer::BeginFrame2D() {}\n\nvoid Renderer::EndFrame() {\n  if (!Drawing) return;\n  Flush();\n\n#ifndef IMPACTO_DISABLE_IMGUI\n  ImGui::Render();\n  ImGui_ImplVulkan_RenderDrawData(ImGui::GetDrawData(),\n                                  CommandBuffers[CurrentFrameIndex]);\n#endif\n\n  vkCmdEndRenderPass(CommandBuffers[CurrentFrameIndex]);\n  if (vkEndCommandBuffer(CommandBuffers[CurrentFrameIndex]) != VK_SUCCESS) {\n    ImpLog(LogLevel::Debug, LogChannel::Render,\n           \"Failed to record command buffer!\\n\");\n    Window->Shutdown();\n  }\n\n  VkSubmitInfo submitInfo{};\n  VkSemaphore waitSemaphores[] = {ImageAvailableSemaphores[CurrentFrameIndex]};\n  VkPipelineStageFlags waitStages[] = {\n      VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT};\n  VkSemaphore signalSemaphores[] = {\n      RenderFinishedSemaphores[CurrentFrameIndex]};\n  submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;\n  submitInfo.waitSemaphoreCount = 1;\n  submitInfo.pWaitSemaphores = waitSemaphores;\n  submitInfo.pWaitDstStageMask = waitStages;\n  submitInfo.commandBufferCount = 1;\n  submitInfo.pCommandBuffers = &CommandBuffers[CurrentFrameIndex];\n  submitInfo.signalSemaphoreCount = 1;\n  submitInfo.pSignalSemaphores = signalSemaphores;\n  VkResult res = vkQueueSubmit(GraphicsQueue, 1, &submitInfo,\n                               InFlightFences[CurrentFrameIndex]);\n  if (res != VK_SUCCESS) {\n    ImpLog(LogLevel::Debug, LogChannel::Render,\n           \"Failed to submit draw command buffer! 0x{:04x}\\n\",\n           to_underlying(res));\n    Window->Shutdown();\n  }\n\n  VkPresentInfoKHR presentInfo{};\n  VkSwapchainKHR swapChains[] = {SwapChain};\n  presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;\n  presentInfo.waitSemaphoreCount = 1;\n  presentInfo.pWaitSemaphores = signalSemaphores;\n  presentInfo.swapchainCount = 1;\n  presentInfo.pSwapchains = swapChains;\n  presentInfo.pImageIndices = &CurrentImageIndex;\n  vkQueuePresentKHR(PresentQueue, &presentInfo);\n\n  CurrentFrameIndex = (CurrentFrameIndex + 1) % MAX_FRAMES_IN_FLIGHT;\n\n  Drawing = false;\n}\n\nuint32_t Renderer::SubmitTexture(TexFmt format, uint8_t* buffer, int width,\n                                 int height) {\n  VkDeviceSize imageSize = 0;\n  VkFormat imageFormat = VK_FORMAT_R8G8B8A8_SRGB;\n  uint8_t* newBuffer = nullptr;\n\n  switch (format) {\n    case TexFmt_RGBA:\n      imageSize = width * height * 4;\n      imageFormat = VK_FORMAT_R8G8B8A8_UNORM;\n      break;\n    case TexFmt_RGB: {\n      imageSize = width * height * 4;\n      imageFormat = VK_FORMAT_R8G8B8A8_UNORM;\n      newBuffer = (uint8_t*)malloc(imageSize);\n      int num = width * height;\n      for (int i = 0; i < num; i++) {\n        newBuffer[4 * i] = buffer[3 * i];\n        newBuffer[4 * i + 1] = buffer[3 * i + 1];\n        newBuffer[4 * i + 2] = buffer[3 * i + 2];\n        newBuffer[4 * i + 3] = 0xFF;\n      }\n    } break;\n    case TexFmt_U8:\n      imageSize = width * height;\n      imageFormat = VK_FORMAT_R8_UNORM;\n  }\n\n  AllocatedBuffer stagingBuffer = CreateBuffer(\n      imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VMA_MEMORY_USAGE_CPU_ONLY);\n  void* data;\n  vmaMapMemory(Allocator, stagingBuffer.Allocation, &data);\n  if (format == TexFmt_RGB) {\n    memcpy(data, newBuffer, static_cast<size_t>(imageSize));\n    free(newBuffer);\n  } else {\n    memcpy(data, buffer, static_cast<size_t>(imageSize));\n  }\n  vmaUnmapMemory(Allocator, stagingBuffer.Allocation);\n\n  VkExtent3D imageExtent;\n  imageExtent.width = static_cast<uint32_t>(width);\n  imageExtent.height = static_cast<uint32_t>(height);\n  imageExtent.depth = 1;\n\n  VkImageCreateInfo dimgInfo = GetImageCreateInfo(\n      imageFormat, imageExtent, VkSampleCountFlagBits::VK_SAMPLE_COUNT_1_BIT);\n  AllocatedImage newImage{};\n  VmaAllocationCreateInfo dimgAllocinfo = {};\n  dimgAllocinfo.usage = VMA_MEMORY_USAGE_GPU_ONLY;\n  vmaCreateImage(Allocator, &dimgInfo, &dimgAllocinfo, &newImage.Image,\n                 &newImage.Allocation, nullptr);\n\n  ImmediateSubmit([&](VkCommandBuffer cmd) {\n    VkImageSubresourceRange range;\n    range.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;\n    range.baseMipLevel = 0;\n    range.levelCount = 1;\n    range.baseArrayLayer = 0;\n    range.layerCount = 1;\n\n    VkImageMemoryBarrier imageBarrierToTransfer = {};\n    imageBarrierToTransfer.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;\n    imageBarrierToTransfer.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;\n    imageBarrierToTransfer.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;\n    imageBarrierToTransfer.image = newImage.Image;\n    imageBarrierToTransfer.subresourceRange = range;\n    imageBarrierToTransfer.srcAccessMask = 0;\n    imageBarrierToTransfer.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;\n\n    vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,\n                         VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0,\n                         nullptr, 1, &imageBarrierToTransfer);\n\n    VkBufferImageCopy copyRegion = {};\n    copyRegion.bufferOffset = 0;\n    copyRegion.bufferRowLength = 0;\n    copyRegion.bufferImageHeight = 0;\n    copyRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;\n    copyRegion.imageSubresource.mipLevel = 0;\n    copyRegion.imageSubresource.baseArrayLayer = 0;\n    copyRegion.imageSubresource.layerCount = 1;\n    copyRegion.imageExtent = imageExtent;\n    vkCmdCopyBufferToImage(cmd, stagingBuffer.Buffer, newImage.Image,\n                           VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1,\n                           &copyRegion);\n\n    VkImageMemoryBarrier imageBarrierToReadable = imageBarrierToTransfer;\n    imageBarrierToReadable.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;\n    imageBarrierToReadable.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;\n    imageBarrierToReadable.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;\n    imageBarrierToReadable.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;\n    vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TRANSFER_BIT,\n                         VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, nullptr,\n                         0, nullptr, 1, &imageBarrierToReadable);\n  });\n\n  vmaDestroyBuffer(Allocator, stagingBuffer.Buffer, stagingBuffer.Allocation);\n\n  VkTexture outTexture{};\n  outTexture.Image = newImage;\n  VkImageViewCreateInfo imageInfo = {};\n  imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;\n  imageInfo.pNext = nullptr;\n  imageInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;\n  imageInfo.image = outTexture.Image.Image;\n  imageInfo.format = imageFormat;\n  imageInfo.subresourceRange.baseMipLevel = 0;\n  imageInfo.subresourceRange.levelCount = 1;\n  imageInfo.subresourceRange.baseArrayLayer = 0;\n  imageInfo.subresourceRange.layerCount = 1;\n  imageInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;\n  vkCreateImageView(Device, &imageInfo, nullptr, &outTexture.ImageView);\n\n  auto id = NextTextureId;\n  NextTextureId += 1;\n  Textures[id] = outTexture;\n\n  return id;\n}\n\nvoid Renderer::FreeTexture(uint32_t id) {\n  // TODO: I need to figure this out... images are getting destroyed but are\n  // still used in draw somehow\n  // UPDATE: This... seems to just work now? Keep an eye on it I guess\n\n  vmaDestroyImage(Allocator, Textures[id].Image.Image,\n                  Textures[id].Image.Allocation);\n  vkDestroyImageView(Device, Textures[id].ImageView, nullptr);\n  Textures.erase(id);\n}\n\nYUVFrame* Renderer::CreateYUVFrame(float width, float height) {\n  VideoFrameInternalYUV = new VkYUVFrame();\n  VideoFrameInternalYUV->Init(width, height);\n  return (YUVFrame*)VideoFrameInternalYUV;\n}\n\nNV12Frame* Renderer::CreateNV12Frame(float width, float height) {\n  VideoFrameInternalNV12 = new VkNV12Frame();\n  VideoFrameInternalNV12->Init(width, height);\n  return (NV12Frame*)VideoFrameInternalNV12;\n}\n\nvoid Renderer::DrawSprite(const Sprite& sprite, const CornersQuad& dest,\n                          const glm::mat4 transformation,\n                          const std::span<const glm::vec4, 4> tints,\n                          const glm::vec3 colorShift, const bool inverted,\n                          const bool disableBlend,\n                          const bool textureWrapRepeat) {\n  if (!Drawing) {\n    ImpLog(LogLevel::Error, LogChannel::Render,\n           \"Renderer->DrawSprite() called before BeginFrame()\\n\");\n    return;\n  }\n\n  if (!sprite.Sheet.Texture) return;\n  if (Textures.count(sprite.Sheet.Texture) == 0) return;\n\n  // Are we in sprite mode?\n  if (inverted)\n    EnsureMode(PipelineSpriteInverted);\n  else if (disableBlend)\n    EnsureMode(PipelineSpriteNoBlending);\n  else\n    EnsureMode(PipelineSprite);\n\n  // Do we have the texture assigned?\n  EnsureTextureBound(sprite.Sheet.Texture);\n\n  // OK, all good, make quad\n  MakeQuad();\n\n  VertexBufferSprites* vertices =\n      (VertexBufferSprites*)(VertexBuffer + VertexBufferOffset +\n                             VertexBufferFill);\n  VertexBufferFill += 4 * sizeof(VertexBufferSprites);\n\n  QuadSetUV(sprite.Bounds, sprite.Sheet.GetDimensions(), &vertices[0].UV,\n            sizeof(VertexBufferSprites));\n  QuadSetPosition(CornersQuad(dest).Transform(transformation),\n                  &vertices[0].Position, sizeof(VertexBufferSprites));\n\n  for (int i = 0; i < 4; i++) vertices[i].Tint = tints[i];\n}\n\nvoid Renderer::DrawMaskedSprite(\n    const Sprite& sprite, const Sprite& mask, const CornersQuad& spriteDest,\n    const CornersQuad& maskDest, int alpha, const int fadeRange,\n    const glm::mat4 spriteTransformation, const glm::mat4 maskTransformation,\n    const std::span<const glm::vec4, 4> tints, const bool isInverted,\n    const bool isSameTexture) {\n  if (!Drawing) {\n    ImpLog(LogLevel::Error, LogChannel::Render,\n           \"Renderer->DrawMaskedSpriteOverlay() called before BeginFrame()\\n\");\n    return;\n  }\n\n  Flush();\n\n  if (Textures.count(sprite.Sheet.Texture) == 0 ||\n      Textures.count(mask.Sheet.Texture) == 0)\n    return;\n\n  alpha = std::clamp(alpha, 0, fadeRange + 256);\n  const float alphaRange = 256.0f / fadeRange;\n  const float constAlpha = ((255.0f - alpha) * alphaRange) / 255.0f;\n\n  VkSamplerCreateInfo samplerInfo = {};\n  samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;\n  samplerInfo.pNext = nullptr;\n  samplerInfo.magFilter = VK_FILTER_LINEAR;\n  samplerInfo.minFilter = VK_FILTER_LINEAR;\n  samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;\n  samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;\n  samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;\n  samplerInfo.anisotropyEnable = VK_TRUE;\n  samplerInfo.maxAnisotropy = 16;\n\n  VkDescriptorImageInfo imageBufferInfo[2];\n  imageBufferInfo[0].sampler = Sampler;\n  imageBufferInfo[0].imageView = Textures[sprite.Sheet.Texture].ImageView;\n  imageBufferInfo[0].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;\n\n  vkCreateSampler(Device, &samplerInfo, nullptr, &imageBufferInfo[1].sampler);\n  imageBufferInfo[1].imageView = Textures[mask.Sheet.Texture].ImageView;\n  imageBufferInfo[1].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;\n\n  VkWriteDescriptorSet writeDescriptorSet{};\n  writeDescriptorSet.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;\n  writeDescriptorSet.dstSet = 0;\n  writeDescriptorSet.dstBinding = 0;\n  writeDescriptorSet.descriptorCount = 2;\n  writeDescriptorSet.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;\n  writeDescriptorSet.pImageInfo = imageBufferInfo;\n\n  EnsureMode(PipelineMaskedSprite, true);\n  vkCmdPushDescriptorSetKHR(\n      CommandBuffers[CurrentFrameIndex], VK_PIPELINE_BIND_POINT_GRAPHICS,\n      CurrentPipeline->PipelineLayout, 0, 1, &writeDescriptorSet);\n  SpritePushConstants constants = {};\n  constants.Alpha = glm::vec2(alphaRange, constAlpha);\n  constants.IsInverted = isInverted;\n  constants.IsSameTexture = isSameTexture;\n  vkCmdPushConstants(\n      CommandBuffers[CurrentFrameIndex], CurrentPipeline->PipelineLayout,\n      VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(SpritePushConstants), &constants);\n\n  // OK, all good, make quad\n  MakeQuad();\n\n  VertexBufferSprites* vertices =\n      (VertexBufferSprites*)(VertexBuffer + VertexBufferOffset +\n                             VertexBufferFill);\n  VertexBufferFill += 4 * sizeof(VertexBufferSprites);\n\n  QuadSetUV(sprite.Bounds, sprite.Sheet.GetDimensions(), &vertices[0].UV,\n            sizeof(VertexBufferSprites));\n  QuadSetUV(CornersQuad(maskDest).Transform(maskTransformation),\n            sprite.Sheet.GetDimensions(), &vertices[0].MaskUV,\n            sizeof(VertexBufferSprites));\n\n  QuadSetPosition(CornersQuad(spriteDest).Transform(spriteTransformation),\n                  &vertices[0].Position, sizeof(VertexBufferSprites));\n\n  for (int i = 0; i < 4; i++) vertices[i].Tint = tints[i];\n}\n\nvoid Renderer::DrawMaskedSpriteOverlay(\n    const Sprite& sprite, const Sprite& mask, const CornersQuad& spriteDest,\n    const CornersQuad& maskDest, int alpha, const int fadeRange,\n    const glm::mat4 spriteTransformation, const glm::mat4 maskTransformation,\n    const std::span<const glm::vec4, 4> tints, const bool isInverted,\n    const bool useMaskAlpha) {\n  if (!Drawing) {\n    ImpLog(LogLevel::Error, LogChannel::Render,\n           \"Renderer->DrawMaskedSpriteOverlay() called before BeginFrame()\\n\");\n    return;\n  }\n\n  if (sprite.Sheet.IsScreenCap) Flush();\n\n  if (Textures.count(sprite.Sheet.Texture) == 0 ||\n      Textures.count(mask.Sheet.Texture) == 0)\n    return;\n\n  alpha = std::clamp(alpha, 0, fadeRange + 256);\n  const float alphaRange = 256.0f / fadeRange;\n  const float constAlpha = ((255.0f - alpha) * alphaRange) / 255.0f;\n\n  VkSamplerCreateInfo samplerInfo = {};\n  samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;\n  samplerInfo.pNext = nullptr;\n  samplerInfo.magFilter = VK_FILTER_LINEAR;\n  samplerInfo.minFilter = VK_FILTER_LINEAR;\n  samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;\n  samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;\n  samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;\n  samplerInfo.anisotropyEnable = VK_TRUE;\n  samplerInfo.maxAnisotropy = 16;\n\n  VkDescriptorImageInfo imageBufferInfo[2];\n  imageBufferInfo[0].sampler = Sampler;\n  imageBufferInfo[0].imageView = Textures[sprite.Sheet.Texture].ImageView;\n  imageBufferInfo[0].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;\n\n  vkCreateSampler(Device, &samplerInfo, nullptr, &imageBufferInfo[1].sampler);\n  imageBufferInfo[1].imageView = Textures[mask.Sheet.Texture].ImageView;\n  imageBufferInfo[1].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;\n\n  VkWriteDescriptorSet writeDescriptorSet{};\n  writeDescriptorSet.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;\n  writeDescriptorSet.dstSet = 0;\n  writeDescriptorSet.dstBinding = 0;\n  writeDescriptorSet.descriptorCount = 2;\n  writeDescriptorSet.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;\n  writeDescriptorSet.pImageInfo = imageBufferInfo;\n\n  if (useMaskAlpha) {\n    EnsureMode(PipelineMaskedSprite, true);\n    vkCmdPushDescriptorSetKHR(\n        CommandBuffers[CurrentFrameIndex], VK_PIPELINE_BIND_POINT_GRAPHICS,\n        CurrentPipeline->PipelineLayout, 0, 1, &writeDescriptorSet);\n    SpritePushConstants constants = {};\n    constants.Alpha = glm::vec2(alphaRange, constAlpha);\n    constants.IsInverted = isInverted;\n    constants.IsSameTexture = false;\n    vkCmdPushConstants(CommandBuffers[CurrentFrameIndex],\n                       CurrentPipeline->PipelineLayout,\n                       VK_SHADER_STAGE_FRAGMENT_BIT, 0,\n                       sizeof(SpritePushConstants), &constants);\n\n  } else {\n    EnsureMode(PipelineMaskedSpriteNoAlpha, true);\n    vkCmdPushDescriptorSetKHR(\n        CommandBuffers[CurrentFrameIndex], VK_PIPELINE_BIND_POINT_GRAPHICS,\n        CurrentPipeline->PipelineLayout, 0, 1, &writeDescriptorSet);\n    MaskedNoAlphaPushConstants constants = {};\n    constants.Alpha = glm::vec2(alphaRange, constAlpha);\n    constants.IsInverted = isInverted;\n    vkCmdPushConstants(CommandBuffers[CurrentFrameIndex],\n                       CurrentPipeline->PipelineLayout,\n                       VK_SHADER_STAGE_FRAGMENT_BIT, 0,\n                       sizeof(MaskedNoAlphaPushConstants), &constants);\n  }\n\n  // OK, all good, make quad\n  MakeQuad();\n\n  VertexBufferSprites* vertices =\n      (VertexBufferSprites*)(VertexBuffer + VertexBufferOffset +\n                             VertexBufferFill);\n  VertexBufferFill += 4 * sizeof(VertexBufferSprites);\n\n  QuadSetUV(sprite.Bounds, sprite.Sheet.GetDimensions(), &vertices[0].UV,\n            sizeof(VertexBufferSprites));\n  QuadSetUV(CornersQuad(maskDest).Transform(maskTransformation),\n            mask.Sheet.GetDimensions(), &vertices[0].MaskUV,\n            sizeof(VertexBufferSprites));\n\n  QuadSetPosition(CornersQuad(spriteDest).Transform(spriteTransformation),\n                  &vertices[0].Position, sizeof(VertexBufferSprites));\n\n  for (int i = 0; i < 4; i++) vertices[i].Tint = tints[i];\n}\n\nvoid Renderer::DrawPrimitives(\n    const SpriteSheet& sheet, const SpriteSheet* const mask,\n    const ShaderProgramType shaderType,\n    const std::span<const VertexBufferSprites> vertices,\n    const std::span<const uint16_t> indices,\n    const glm::mat4 spriteTransformation, const glm::mat4 maskTransformation,\n    const bool inverted, TopologyMode topologyMode, bool textureWrapRepeat) {\n  if (!Drawing) {\n    ImpLog(LogLevel::Error, LogChannel::Render,\n           \"Renderer->DrawVertices() called before BeginFrame()\\n\");\n    return;\n  }\n\n  if (Textures.count(sheet.Texture) == 0) return;\n\n  // The index buffer needs to be flushed\n  Flush();\n\n  if (mask != nullptr) {\n    VkSamplerCreateInfo samplerInfo = {};\n    samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;\n    samplerInfo.pNext = nullptr;\n    samplerInfo.magFilter = VK_FILTER_LINEAR;\n    samplerInfo.minFilter = VK_FILTER_LINEAR;\n    samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;\n    samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;\n    samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;\n    samplerInfo.anisotropyEnable = VK_TRUE;\n    samplerInfo.maxAnisotropy = 16;\n\n    VkDescriptorImageInfo imageBufferInfo[2];\n    imageBufferInfo[0].sampler = Sampler;\n    imageBufferInfo[0].imageView = Textures[sheet.Texture].ImageView;\n    imageBufferInfo[0].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;\n\n    vkCreateSampler(Device, &samplerInfo, nullptr, &imageBufferInfo[1].sampler);\n    imageBufferInfo[1].imageView = Textures[mask->Texture].ImageView;\n    imageBufferInfo[1].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;\n\n    VkWriteDescriptorSet writeDescriptorSet{};\n    writeDescriptorSet.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;\n    writeDescriptorSet.dstSet = 0;\n    writeDescriptorSet.dstBinding = 0;\n    writeDescriptorSet.descriptorCount = 2;\n    writeDescriptorSet.descriptorType =\n        VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;\n    writeDescriptorSet.pImageInfo = imageBufferInfo;\n\n    EnsureMode(PipelineMaskedSpriteNoAlpha);\n    vkCmdPushDescriptorSetKHR(\n        CommandBuffers[CurrentFrameIndex], VK_PIPELINE_BIND_POINT_GRAPHICS,\n        CurrentPipeline->PipelineLayout, 0, 1, &writeDescriptorSet);\n    MaskedNoAlphaPushConstants constants = {};\n    constants.Alpha = glm::vec2(1.0f, 0.0f);\n    constants.IsInverted = inverted;\n    vkCmdPushConstants(CommandBuffers[CurrentFrameIndex],\n                       CurrentPipeline->PipelineLayout,\n                       VK_SHADER_STAGE_FRAGMENT_BIT, 0,\n                       sizeof(MaskedNoAlphaPushConstants), &constants);\n  } else {\n    if (inverted)\n      EnsureMode(PipelineSpriteInverted);\n    else\n      EnsureMode(PipelineSprite);\n\n    EnsureTextureBound(sheet.Texture);\n  }\n\n  // Push vertices\n  VertexBufferSprites* vertexBuffer =\n      (VertexBufferSprites*)(VertexBuffer + VertexBufferOffset +\n                             VertexBufferFill);\n  VertexBufferFill += vertices.size_bytes();\n\n  const auto vertexInfoToNDC = [this, spriteTransformation,\n                                maskTransformation](VertexBufferSprites info) {\n    info.Position = DesignToNDC(\n        glm::vec2(spriteTransformation * glm::vec4(info.Position, 0.0f, 1.0f)));\n    info.MaskUV =\n        glm::vec2(maskTransformation * glm::vec4(info.MaskUV, 0.0f, 1.0f));\n    return info;\n  };\n  std::transform(vertices.begin(), vertices.end(), vertexBuffer,\n                 vertexInfoToNDC);\n\n  // Push indices\n  IndexBufferFill += indices.size();\n  size_t indexBufferOffset = IndexBufferOffset / sizeof(uint16_t);\n  std::ranges::copy(indices, IndexBuffer + indexBufferOffset);\n\n  // Flush again and bind back our buffer\n  Flush();\n}\n\nvoid Renderer::DrawCCMessageBox(Sprite const& sprite, Sprite const& mask,\n                                RectF const& dest, glm::vec4 tint, int alpha,\n                                int fadeRange, float effectCt) {\n  if (!Drawing) {\n    ImpLog(LogLevel::Error, LogChannel::Render,\n           \"Renderer->DrawCCMessageBox() called before BeginFrame()\\n\");\n    return;\n  }\n\n  if (Textures.count(sprite.Sheet.Texture) == 0 ||\n      Textures.count(mask.Sheet.Texture) == 0)\n    return;\n\n  if (alpha < 0) alpha = 0;\n  if (alpha > fadeRange + 256) alpha = fadeRange + 256;\n\n  float alphaRange = 256.0f / fadeRange;\n  float constAlpha = ((255.0f - alpha) * alphaRange) / 255.0f;\n\n  EnsureMode(PipelineCCMessageBox);\n\n  VkDescriptorImageInfo imageBufferInfo[2];\n  imageBufferInfo[0].sampler = Sampler;\n  imageBufferInfo[0].imageView = Textures[sprite.Sheet.Texture].ImageView;\n  imageBufferInfo[0].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;\n  imageBufferInfo[1].sampler = Sampler;\n  imageBufferInfo[1].imageView = Textures[mask.Sheet.Texture].ImageView;\n  imageBufferInfo[1].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;\n\n  VkWriteDescriptorSet writeDescriptorSet{};\n  writeDescriptorSet.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;\n  writeDescriptorSet.dstSet = 0;\n  writeDescriptorSet.dstBinding = 0;\n  writeDescriptorSet.descriptorCount = 2;\n  writeDescriptorSet.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;\n  writeDescriptorSet.pImageInfo = imageBufferInfo;\n\n  vkCmdPushDescriptorSetKHR(\n      CommandBuffers[CurrentFrameIndex], VK_PIPELINE_BIND_POINT_GRAPHICS,\n      PipelineMaskedSprite->PipelineLayout, 0, 1, &writeDescriptorSet);\n\n  CCBoxPushConstants constants = {};\n  constants.CCBoxAlpha = glm::vec4(alphaRange, constAlpha, effectCt, 0.0f);\n  vkCmdPushConstants(\n      CommandBuffers[CurrentFrameIndex], CurrentPipeline->PipelineLayout,\n      VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(CCBoxPushConstants), &constants);\n\n  // OK, all good, make quad\n  MakeQuad();\n\n  VertexBufferSprites* vertices =\n      (VertexBufferSprites*)(VertexBuffer + VertexBufferOffset +\n                             VertexBufferFill);\n  VertexBufferFill += 4 * sizeof(VertexBufferSprites);\n\n  QuadSetUV(sprite.Bounds, sprite.Sheet.GetDimensions(), &vertices[0].UV,\n            sizeof(VertexBufferSprites));\n  QuadSetUV(mask.Bounds, mask.Sheet.GetDimensions(), &vertices[0].MaskUV,\n            sizeof(VertexBufferSprites));\n\n  QuadSetPosition(dest, &vertices[0].Position, sizeof(VertexBufferSprites));\n\n  for (int i = 0; i < 4; i++) vertices[i].Tint = tint;\n}\n\nvoid Renderer::DrawCHLCCMenuBackground(const Sprite& sprite, const Sprite& mask,\n                                       const RectF& dest, float alpha) {\n  if (!Drawing) {\n    ImpLog(LogLevel::Error, LogChannel::Render,\n           \"Renderer->DrawCCMessageBox() called before BeginFrame()\\n\");\n    return;\n  }\n\n  if (Textures.count(sprite.Sheet.Texture) == 0 ||\n      Textures.count(mask.Sheet.Texture) == 0)\n    return;\n\n  if (alpha < 0.0f)\n    alpha = 0;\n  else if (alpha > 1.0f)\n    alpha = 1.0f;\n\n  EnsureMode(PipelineCHLCCMenuBackground);\n\n  VkDescriptorImageInfo imageBufferInfo[2];\n  imageBufferInfo[0].sampler = Sampler;\n  imageBufferInfo[0].imageView = Textures[sprite.Sheet.Texture].ImageView;\n  imageBufferInfo[0].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;\n  imageBufferInfo[1].sampler = Sampler;\n  imageBufferInfo[1].imageView = Textures[mask.Sheet.Texture].ImageView;\n  imageBufferInfo[1].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;\n\n  VkWriteDescriptorSet writeDescriptorSet{};\n  writeDescriptorSet.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;\n  writeDescriptorSet.dstSet = 0;\n  writeDescriptorSet.dstBinding = 0;\n  writeDescriptorSet.descriptorCount = 2;\n  writeDescriptorSet.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;\n  writeDescriptorSet.pImageInfo = imageBufferInfo;\n\n  vkCmdPushDescriptorSetKHR(\n      CommandBuffers[CurrentFrameIndex], VK_PIPELINE_BIND_POINT_GRAPHICS,\n      PipelineMaskedSprite->PipelineLayout, 0, 1, &writeDescriptorSet);\n\n  CCBoxPushConstants constants = {};\n  constants.CCBoxAlpha = glm::vec4(alpha, 0.0f, 0.0f, 0.0f);\n  vkCmdPushConstants(\n      CommandBuffers[CurrentFrameIndex], CurrentPipeline->PipelineLayout,\n      VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(CCBoxPushConstants), &constants);\n\n  // OK, all good, make quad\n  MakeQuad();\n\n  VertexBufferSprites* vertices =\n      (VertexBufferSprites*)(VertexBuffer + VertexBufferOffset +\n                             VertexBufferFill);\n  VertexBufferFill += 4 * sizeof(VertexBufferSprites);\n\n  QuadSetUV(sprite.Bounds, sprite.Sheet.GetDimensions(), &vertices[0].UV,\n            sizeof(VertexBufferSprites));\n  QuadSetUV(mask.Bounds, mask.Sheet.GetDimensions(), &vertices[0].MaskUV,\n            sizeof(VertexBufferSprites));\n\n  QuadSetPosition(dest, &vertices[0].Position, sizeof(VertexBufferSprites));\n}\n\ninline void Renderer::MakeQuad() {\n  size_t indexBufferOffset = IndexBufferOffset / sizeof(uint16_t);\n  if (IndexBufferFill + 6 <= IndexBufferCount) {\n    // bottom-left -> top-left -> top-right\n    IndexBuffer[indexBufferOffset + IndexBufferFill] =\n        (uint16_t)VertexBufferCount + 0;\n    IndexBuffer[indexBufferOffset + IndexBufferFill + 1] =\n        (uint16_t)VertexBufferCount + 1;\n    IndexBuffer[indexBufferOffset + IndexBufferFill + 2] =\n        (uint16_t)VertexBufferCount + 2;\n    // bottom-left -> top-right -> bottom-right\n    IndexBuffer[indexBufferOffset + IndexBufferFill + 3] =\n        (uint16_t)VertexBufferCount + 0;\n    IndexBuffer[indexBufferOffset + IndexBufferFill + 4] =\n        (uint16_t)VertexBufferCount + 2;\n    IndexBuffer[indexBufferOffset + IndexBufferFill + 5] =\n        (uint16_t)VertexBufferCount + 3;\n    IndexBufferFill += 6;\n    VertexBufferCount += 4;\n  }\n}\n\nvoid Renderer::EnsureTextureBound(unsigned int texture) {\n  if (CurrentTexture != texture) {\n    ImpLogSlow(LogLevel::Trace, LogChannel::Render,\n               \"Renderer->EnsureTextureBound flushing because texture {:d} is \"\n               \"not {:d}\\n\",\n               CurrentTexture, texture);\n    Flush();\n\n    VkDescriptorImageInfo imageBufferInfo;\n    imageBufferInfo.sampler = Sampler;\n    imageBufferInfo.imageView = Textures[texture].ImageView;\n    imageBufferInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;\n\n    VkWriteDescriptorSet writeDescriptorSet{};\n    writeDescriptorSet.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;\n    writeDescriptorSet.dstSet = 0;\n    writeDescriptorSet.dstBinding = 0;\n    writeDescriptorSet.descriptorCount = 1;\n    writeDescriptorSet.descriptorType =\n        VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;\n    writeDescriptorSet.pImageInfo = &imageBufferInfo;\n\n    vkCmdPushDescriptorSetKHR(\n        CommandBuffers[CurrentFrameIndex], VK_PIPELINE_BIND_POINT_GRAPHICS,\n        CurrentPipeline->PipelineLayout, 0, 1, &writeDescriptorSet);\n\n    CurrentTexture = texture;\n  }\n}\n\nvoid Renderer::EnsureMode(Pipeline* pipeline, bool flush) {\n  if (CurrentPipeline != pipeline) {\n    ImpLogSlow(LogLevel::Trace, LogChannel::Render,\n               \"Renderer2D changing mode\\n\");\n    if (flush) Flush();\n    vkCmdBindPipeline(CommandBuffers[CurrentFrameIndex],\n                      VK_PIPELINE_BIND_POINT_GRAPHICS,\n                      pipeline->GraphicsPipeline);\n    CurrentPipeline = pipeline;\n  }\n}\n\nvoid Renderer::Flush() {\n  if (!Drawing) {\n    ImpLog(LogLevel::Error, LogChannel::Render,\n           \"Renderer->Flush() called before BeginFrame()\\n\");\n    return;\n  }\n\n  if (VertexBufferFill > 0 && IndexBufferFill > 0) {\n    VkBuffer vertexBuffers[] = {VertexBufferAlloc.Buffer};\n    VkDeviceSize offsets[] = {(VkDeviceSize)VertexBufferOffset};\n    vkCmdBindVertexBuffers(CommandBuffers[CurrentFrameIndex], 0, 1,\n                           vertexBuffers, offsets);\n    vkCmdBindIndexBuffer(CommandBuffers[CurrentFrameIndex],\n                         IndexBufferAlloc.Buffer, IndexBufferOffset,\n                         VK_INDEX_TYPE_UINT16);\n\n    vkCmdDrawIndexed(CommandBuffers[CurrentFrameIndex],\n                     (uint32_t)IndexBufferFill, 1, 0, 0, 0);\n  }\n  IndexBufferOffset += IndexBufferFill * sizeof(uint16_t);\n  IndexBufferFill = 0;\n  VertexBufferOffset += VertexBufferFill;\n  VertexBufferFill = 0;\n  VertexBufferCount = 0;\n  CurrentTexture = 0;\n}\n\nvoid Renderer::DrawVideoTexture(const YUVFrame& frame, const RectF& dest,\n                                const glm::vec4 tint, const bool alphaVideo) {\n  if (!Drawing) {\n    ImpLog(LogLevel::Error, LogChannel::Render,\n           \"Renderer->DrawVideoTexture() called before BeginFrame()\\n\");\n    return;\n  }\n\n  EnsureMode(PipelineYUVFrame);\n\n  VkDescriptorImageInfo imageBufferInfo[3];\n  imageBufferInfo[0].sampler = Sampler;\n  imageBufferInfo[0].imageView = VideoFrameInternalYUV->LumaImage.ImageView;\n  imageBufferInfo[0].imageLayout = VK_IMAGE_LAYOUT_GENERAL;\n  imageBufferInfo[1].sampler = Sampler;\n  imageBufferInfo[1].imageView = VideoFrameInternalYUV->CbImage.ImageView;\n  imageBufferInfo[1].imageLayout = VK_IMAGE_LAYOUT_GENERAL;\n  imageBufferInfo[2].sampler = Sampler;\n  imageBufferInfo[2].imageView = VideoFrameInternalYUV->CrImage.ImageView;\n  imageBufferInfo[2].imageLayout = VK_IMAGE_LAYOUT_GENERAL;\n\n  VkWriteDescriptorSet writeDescriptorSet{};\n  writeDescriptorSet.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;\n  writeDescriptorSet.dstSet = 0;\n  writeDescriptorSet.dstBinding = 0;\n  writeDescriptorSet.descriptorCount = 3;\n  writeDescriptorSet.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;\n  writeDescriptorSet.pImageInfo = imageBufferInfo;\n\n  vkCmdPushDescriptorSetKHR(\n      CommandBuffers[CurrentFrameIndex], VK_PIPELINE_BIND_POINT_GRAPHICS,\n      PipelineYUVFrame->PipelineLayout, 0, 1, &writeDescriptorSet);\n\n  YUVFramePushConstants constants = {};\n  constants.IsAlpha = alphaVideo;\n  vkCmdPushConstants(CommandBuffers[CurrentFrameIndex],\n                     CurrentPipeline->PipelineLayout,\n                     VK_SHADER_STAGE_FRAGMENT_BIT, 0,\n                     sizeof(YUVFramePushConstants), &constants);\n\n  // OK, all good, make quad\n  MakeQuad();\n\n  VertexBufferSprites* vertices =\n      (VertexBufferSprites*)(VertexBuffer + VertexBufferOffset +\n                             VertexBufferFill);\n  VertexBufferFill += 4 * sizeof(VertexBufferSprites);\n\n  QuadSetUV(RectF(0.0f, 0.0f, frame.Width, frame.Height),\n            {frame.Width, frame.Height}, &vertices[0].UV,\n            sizeof(VertexBufferSprites));\n  QuadSetPosition(dest, &vertices[0].Position, sizeof(VertexBufferSprites));\n\n  for (int i = 0; i < 4; i++) vertices[i].Tint = tint;\n}\n\nvoid Renderer::DrawVideoTexture(const NV12Frame& frame, const RectF& dest,\n                                const glm::vec4 tint, const bool alphaVideo) {\n  if (!Drawing) {\n    ImpLog(LogLevel::Error, LogChannel::Render,\n           \"Renderer->DrawVideoTexture() called before BeginFrame()\\n\");\n    return;\n  }\n\n  EnsureMode(PipelineNV12Frame);\n\n  VkDescriptorImageInfo imageBufferInfo[2];\n  imageBufferInfo[0].sampler = Sampler;\n  imageBufferInfo[0].imageView = VideoFrameInternalNV12->LumaImage.ImageView;\n  imageBufferInfo[0].imageLayout = VK_IMAGE_LAYOUT_GENERAL;\n  imageBufferInfo[1].sampler = Sampler;\n  imageBufferInfo[1].imageView = VideoFrameInternalNV12->CbCrImage.ImageView;\n  imageBufferInfo[1].imageLayout = VK_IMAGE_LAYOUT_GENERAL;\n\n  VkWriteDescriptorSet writeDescriptorSet{};\n  writeDescriptorSet.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;\n  writeDescriptorSet.dstSet = 0;\n  writeDescriptorSet.dstBinding = 0;\n  writeDescriptorSet.descriptorCount = 2;\n  writeDescriptorSet.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;\n  writeDescriptorSet.pImageInfo = imageBufferInfo;\n\n  vkCmdPushDescriptorSetKHR(\n      CommandBuffers[CurrentFrameIndex], VK_PIPELINE_BIND_POINT_GRAPHICS,\n      PipelineNV12Frame->PipelineLayout, 0, 1, &writeDescriptorSet);\n\n  YUVFramePushConstants constants = {};\n  constants.IsAlpha = alphaVideo;\n  vkCmdPushConstants(CommandBuffers[CurrentFrameIndex],\n                     CurrentPipeline->PipelineLayout,\n                     VK_SHADER_STAGE_FRAGMENT_BIT, 0,\n                     sizeof(YUVFramePushConstants), &constants);\n\n  // OK, all good, make quad\n  MakeQuad();\n\n  VertexBufferSprites* vertices =\n      (VertexBufferSprites*)(VertexBuffer + VertexBufferOffset +\n                             VertexBufferFill);\n  VertexBufferFill += 4 * sizeof(VertexBufferSprites);\n\n  QuadSetUV(RectF(0.0f, 0.0f, frame.Width, frame.Height),\n            {frame.Width, frame.Height}, &vertices[0].UV,\n            sizeof(VertexBufferSprites));\n  QuadSetPosition(dest, &vertices[0].Position, sizeof(VertexBufferSprites));\n\n  for (int i = 0; i < 4; i++) vertices[i].Tint = tint;\n}\n\nvoid Renderer::CaptureScreencap(Sprite& sprite) {\n  if (Textures.count(sprite.Sheet.Texture) == 0) return;\n  sprite.Sheet.IsScreenCap = true;\n\n  sprite.Sheet.DesignWidth = static_cast<float>(SwapChainExtent.width);\n  sprite.Sheet.DesignHeight = static_cast<float>(SwapChainExtent.height);\n  sprite.Bounds.Width = sprite.Sheet.DesignWidth;\n  sprite.Bounds.Height = sprite.Sheet.DesignHeight;\n  sprite.BaseScale = {Profile::DesignWidth / SwapChainExtent.width,\n                      Profile::DesignHeight / SwapChainExtent.height};\n\n  // Here we go...\n  Flush();\n  vkCmdEndRenderPass(CommandBuffers[CurrentFrameIndex]);\n\n  // Capture here\n  VkImageSubresourceRange range = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};\n\n  VkImageMemoryBarrier imageBarrierToTransfer = {};\n  imageBarrierToTransfer.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;\n  imageBarrierToTransfer.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;\n  imageBarrierToTransfer.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;\n  imageBarrierToTransfer.image = Textures[sprite.Sheet.Texture].Image.Image;\n  imageBarrierToTransfer.subresourceRange = range;\n  imageBarrierToTransfer.srcAccessMask = 0;\n  imageBarrierToTransfer.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;\n  vkCmdPipelineBarrier(CommandBuffers[CurrentFrameIndex],\n                       VK_PIPELINE_STAGE_TRANSFER_BIT,\n                       VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0,\n                       nullptr, 1, &imageBarrierToTransfer);\n\n  imageBarrierToTransfer.oldLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;\n  imageBarrierToTransfer.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;\n  imageBarrierToTransfer.image = SwapChainImages[CurrentFrameIndex];\n  imageBarrierToTransfer.srcAccessMask = VK_ACCESS_MEMORY_READ_BIT;\n  imageBarrierToTransfer.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;\n  vkCmdPipelineBarrier(CommandBuffers[CurrentFrameIndex],\n                       VK_PIPELINE_STAGE_TRANSFER_BIT,\n                       VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0,\n                       nullptr, 1, &imageBarrierToTransfer);\n\n  VkOffset3D blitSize;\n  blitSize.x = SwapChainExtent.width;\n  blitSize.y = SwapChainExtent.height;\n  blitSize.z = 1;\n  VkImageBlit imageBlitRegion{};\n  imageBlitRegion.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;\n  imageBlitRegion.srcSubresource.layerCount = 1;\n  imageBlitRegion.srcOffsets[1] = blitSize;\n  imageBlitRegion.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;\n  imageBlitRegion.dstSubresource.layerCount = 1;\n  imageBlitRegion.dstOffsets[1] = blitSize;\n\n  vkCmdBlitImage(CommandBuffers[CurrentFrameIndex],\n                 SwapChainImages[CurrentFrameIndex],\n                 VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,\n                 Textures[sprite.Sheet.Texture].Image.Image,\n                 VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &imageBlitRegion,\n                 VK_FILTER_LINEAR);\n\n  imageBarrierToTransfer.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;\n  imageBarrierToTransfer.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;\n  imageBarrierToTransfer.image = Textures[sprite.Sheet.Texture].Image.Image;\n  imageBarrierToTransfer.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;\n  imageBarrierToTransfer.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT;\n  vkCmdPipelineBarrier(CommandBuffers[CurrentFrameIndex],\n                       VK_PIPELINE_STAGE_TRANSFER_BIT,\n                       VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0,\n                       nullptr, 1, &imageBarrierToTransfer);\n\n  imageBarrierToTransfer.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;\n  imageBarrierToTransfer.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;\n  imageBarrierToTransfer.image = SwapChainImages[CurrentFrameIndex];\n  imageBarrierToTransfer.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;\n  imageBarrierToTransfer.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT;\n  vkCmdPipelineBarrier(CommandBuffers[CurrentFrameIndex],\n                       VK_PIPELINE_STAGE_TRANSFER_BIT,\n                       VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0,\n                       nullptr, 1, &imageBarrierToTransfer);\n\n  VkRenderPassBeginInfo renderPassInfo{};\n  renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;\n  renderPassInfo.renderPass = RenderPass;\n  renderPassInfo.framebuffer = SwapChainFramebuffers[CurrentImageIndex];\n  renderPassInfo.renderArea.offset = {0, 0};\n  renderPassInfo.renderArea.extent = SwapChainExtent;\n  renderPassInfo.clearValueCount = 0;\n\n  vkCmdBeginRenderPass(CommandBuffers[CurrentFrameIndex], &renderPassInfo,\n                       VK_SUBPASS_CONTENTS_INLINE);\n}\n\nint Renderer::GetSpriteSheetImage(SpriteSheet const& sheet,\n                                  std::span<uint8_t> outBuffer) {\n  const size_t bufferSize =\n      static_cast<size_t>(sheet.DesignWidth * sheet.DesignHeight * 4);\n  assert(outBuffer.size() >= bufferSize);\n\n  // Create a staging buffer to copy the image data to\n  AllocatedBuffer stagingBuffer = CreateBuffer(\n      bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT, VMA_MEMORY_USAGE_CPU_ONLY);\n\n  // Transition the image layout to VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL\n  VkImageSubresourceRange subresourceRange = {};\n  subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;\n  subresourceRange.baseMipLevel = 0;\n  subresourceRange.levelCount = 1;\n  subresourceRange.baseArrayLayer = 0;\n  subresourceRange.layerCount = 1;\n\n  ImmediateSubmit([&](VkCommandBuffer cmd) {\n    VkImageMemoryBarrier barrier{};\n    barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;\n    barrier.oldLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;\n    barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;\n    barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;\n    barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;\n    barrier.image = Textures[sheet.Texture].Image.Image;\n    barrier.subresourceRange = subresourceRange;\n    barrier.srcAccessMask = VK_ACCESS_SHADER_READ_BIT;\n    barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;\n\n    vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,\n                         VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0,\n                         nullptr, 1, &barrier);\n\n    // Copy the image to the staging buffer\n    VkBufferImageCopy copyRegion = {};\n    copyRegion.bufferOffset = 0;\n    copyRegion.bufferRowLength = 0;\n    copyRegion.bufferImageHeight = 0;\n    copyRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;\n    copyRegion.imageSubresource.mipLevel = 0;\n    copyRegion.imageSubresource.baseArrayLayer = 0;\n    copyRegion.imageSubresource.layerCount = 1;\n    copyRegion.imageOffset = {0, 0, 0};\n    copyRegion.imageExtent = {static_cast<uint32_t>(sheet.DesignWidth),\n                              static_cast<uint32_t>(sheet.DesignHeight), 1};\n\n    vkCmdCopyImageToBuffer(cmd, Textures[sheet.Texture].Image.Image,\n                           VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,\n                           stagingBuffer.Buffer, 1, &copyRegion);\n\n    // Transition the image layout back to\n    // VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL\n    barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;\n    barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;\n    barrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;\n    barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;\n\n    vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TRANSFER_BIT,\n                         VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, nullptr,\n                         0, nullptr, 1, &barrier);\n  });\n\n  // Map the staging buffer and copy the data to the output buffer\n  void* data;\n  vmaMapMemory(Allocator, stagingBuffer.Allocation, &data);\n  memcpy(outBuffer.data(), data, bufferSize);\n  vmaUnmapMemory(Allocator, stagingBuffer.Allocation);\n\n  // Clean up the staging buffer\n  vmaDestroyBuffer(Allocator, stagingBuffer.Buffer, stagingBuffer.Allocation);\n  return static_cast<int>(bufferSize);\n}\n\nvoid Renderer::EnableScissor() {}\n\nvoid Renderer::SetScissorRect(RectF const& rect) {\n  if (rect.X != PreviousScissorRect.X && rect.Y != PreviousScissorRect.Y &&\n      rect.Width != PreviousScissorRect.Width &&\n      rect.Height != PreviousScissorRect.Height) {\n    float scale = fmin((float)Window->WindowWidth / Profile::DesignWidth,\n                       (float)Window->WindowHeight / Profile::DesignHeight);\n    float rectX = rect.X * scale;\n    float rectY = rect.Y * scale;\n    float rectWidth = rect.Width * scale;\n    float rectHeight = rect.Height * scale;\n    VkExtent2D scissorExtent;\n    scissorExtent.width = (int)(rectWidth);\n    scissorExtent.height = (int)(rectHeight);\n    VkRect2D scissor{};\n    scissor.offset = {(int)rectX, (int)rectY};\n    scissor.extent = scissorExtent;\n    Flush();\n    vkCmdSetScissor(CommandBuffers[CurrentFrameIndex], 0, 1, &scissor);\n    PreviousScissorRect = rect;\n  }\n}\n\nvoid Renderer::DisableScissor() {\n  if (PreviousScissorRect.X != 0.0f && PreviousScissorRect.Y != 0.0f &&\n      PreviousScissorRect.Width != SwapChainExtent.width &&\n      PreviousScissorRect.Height != SwapChainExtent.height) {\n    Flush();\n    VkRect2D scissor{};\n    scissor.offset = {0, 0};\n    scissor.extent = SwapChainExtent;\n    vkCmdSetScissor(CommandBuffers[CurrentFrameIndex], 0, 1, &scissor);\n    PreviousScissorRect = RectF(0.0f, 0.0f, (float)SwapChainExtent.width,\n                                (float)SwapChainExtent.height);\n  }\n}\n\nglm::vec2 Renderer::DesignToNDC(const glm::vec2 designCoord) const {\n  glm::vec2 result;\n  result.x = (designCoord.x / (Profile::DesignWidth * 0.5f)) - 1.0f;\n  result.y = (designCoord.y / (Profile::DesignHeight * 0.5f)) - 1.0f;\n  return result;\n}\n\nvoid Renderer::Clear(glm::vec4 color) {\n  if (!Drawing) {\n    ImpLog(LogLevel::Error, LogChannel::Render,\n           \"Renderer->Clear() called before BeginFrame()\\n\");\n    return;\n  }\n\n  Flush();\n\n  VkClearAttachment clearAttachment = {\n      .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,\n      .colorAttachment = 0,\n      .clearValue =\n          {\n              .color = {{color.r, color.g, color.b, color.a}},\n          },\n  };\n\n  VkClearRect clearRect = {\n      .rect = {.offset = {0, 0}, .extent = SwapChainExtent},\n      .baseArrayLayer = 0,\n      .layerCount = 1};\n\n  vkCmdClearAttachments(CommandBuffers[CurrentFrameIndex], 1, &clearAttachment,\n                        1, &clearRect);\n}\n\n}  // namespace Vulkan\n}  // namespace Impacto\n"
  },
  {
    "path": "src/renderer/vulkan/renderer.h",
    "content": "#pragma once\n\n#include <vulkan/vulkan.h>\n#include <map>\n#include <array>\n\n#include \"../renderer.h\"\n#include \"utils.h\"\n#include \"window.h\"\n#include \"yuvframe.h\"\n#include \"nv12frame.h\"\n\nnamespace Impacto {\nnamespace Vulkan {\n\nconst std::vector<const char*> validationLayers = {\n    \"VK_LAYER_KHRONOS_validation\"};\n\n#ifdef NDEBUG\nconst bool EnableValidationLayers = false;\n#else\nconst bool EnableValidationLayers = true;\n#endif\n\nstruct SpritePushConstants {\n  VkBool32 IsInverted;\n  VkBool32 IsSameTexture;\n  glm::vec2 Alpha;\n};\n\nstruct MaskedNoAlphaPushConstants {\n  glm::vec2 Alpha;\n  VkBool32 IsInverted;\n};\n\nstruct YUVFramePushConstants {\n  VkBool32 IsAlpha;\n};\n\nstruct CCBoxPushConstants {\n  glm::vec4 CCBoxAlpha;\n};\n\nclass Renderer : public BaseRenderer {\n public:\n  void RecreateSwapChain();\n\n  void Init() override;\n  void Shutdown() override;\n\n#ifndef IMPACTO_DISABLE_IMGUI\n  void ImGuiBeginFrame() override;\n#endif\n\n  void BeginFrame() override;\n  void BeginFrame2D() override;\n  void EndFrame() override;\n\n  uint32_t SubmitTexture(TexFmt format, uint8_t* buffer, int width,\n                         int height) override;\n  int GetSpriteSheetImage(SpriteSheet const& sheet,\n                          std::span<uint8_t> outBuffer) override;\n  void FreeTexture(uint32_t id) override;\n  YUVFrame* CreateYUVFrame(float width, float height) override;\n  NV12Frame* CreateNV12Frame(float width, float height) override;\n\n  void DrawSprite(const Sprite& sprite, const CornersQuad& dest,\n                  glm::mat4 transformation, std::span<const glm::vec4, 4> tints,\n                  glm::vec3 colorShift, bool inverted, bool disableBlend,\n                  bool textureWrapRepeat) override;\n\n  void DrawMaskedSprite(const Sprite& sprite, const Sprite& mask,\n                        const CornersQuad& spriteDest,\n                        const CornersQuad& maskDest, int alpha, int fadeRange,\n                        glm::mat4 spriteTransformation,\n                        glm::mat4 maskTransformation,\n                        std::span<const glm::vec4, 4> tints, bool isInverted,\n                        bool isSameTexture) override;\n\n  // TODO: implement\n  void DrawMaskedBinarySprite(const Sprite& sprite, const Sprite& mask,\n                              const CornersQuad& spriteDest,\n                              const CornersQuad& maskDest,\n                              glm::mat4 spriteTransformation,\n                              std::optional<glm::mat4> maskTransformation,\n                              std::span<const glm::vec4, 4> tints,\n                              bool isInverted) override {}\n\n  void DrawMaskedSpriteOverlay(const Sprite& sprite, const Sprite& mask,\n                               const CornersQuad& spriteDest,\n                               const CornersQuad& maskDest, int alpha,\n                               int fadeRange, glm::mat4 spriteTransformation,\n                               glm::mat4 maskTransformation,\n                               std::span<const glm::vec4, 4> tints,\n                               bool isInverted, bool useMaskAlpha) override;\n\n  void DrawPrimitives(const SpriteSheet& sheet, const SpriteSheet* mask,\n                      ShaderProgramType shaderType,\n                      std::span<const VertexBufferSprites> vertices,\n                      std::span<const uint16_t> indices,\n                      glm::mat4 spriteTransformation,\n                      glm::mat4 maskTransformation, bool inverted,\n                      TopologyMode topologyMode,\n                      bool textureWrapRepeat = false) override;\n\n  void DrawCCMessageBox(Sprite const& sprite, Sprite const& mask,\n                        RectF const& dest, glm::vec4 tint, int alpha,\n                        int fadeRange, float effectCt) override;\n\n  void DrawCHLCCMenuBackground(const Sprite& sprite, const Sprite& mask,\n                               const RectF& dest, float alpha) override;\n\n  void DrawBlurredSprite(const Sprite& sprite, const CornersQuad& dest,\n                         glm::mat4 transformation,\n                         RendererBlurDirection blurDirection,\n                         glm::vec4 tint) override {};  // TODO: Implement\n\n  void DrawMosaic(const Sprite& sprite, const CornersQuad dest, float tileSize,\n                  glm::mat4 transformation,\n                  glm::vec4 tint) override {};  // TODO: Implement\n\n  void DrawVideoTexture(const YUVFrame& frame, const RectF& dest,\n                        glm::vec4 tint, bool alphaVideo) override;\n  void DrawVideoTexture(const NV12Frame& frame, const RectF& dest,\n                        glm::vec4 tint, bool alphaVideo = false) override;\n  void DrawSubtitleGlyph(const Sprite& sprite, const CornersQuad& dest,\n                         const glm::mat4 transformation,\n                         const glm::vec4 tint) override {};  // TODO: Implement\n\n  void CaptureScreencap(Sprite& sprite) override;\n\n  void SetFramebuffer(size_t buffer) override {};  // TODO: Implement\n  int GetFramebufferTexture(size_t buffer) override {\n    return 0;\n  };  // TODO: Implement\n\n  void EnableScissor() override;\n  void SetScissorRect(RectF const& rect) override;\n  void DisableScissor() override;\n\n  void SetStencilMode(StencilBufferMode mode) override {};  // TODO: implement\n  void ClearStencilBuffer() override {};                    // TODO: implement\n\n  void SetBlendMode(RendererBlendMode blendMode) override {\n  };  // TODO: Implement\n\n  void Clear(glm::vec4 color) override;\n\n private:\n  void CreateInstance();\n  void SetupDebug();\n  void PickPhysicalDevice();\n  void CreateLogicalDevice();\n  void FindQueues();\n  void CreateSurface();\n  void CreateSwapChain();\n  void CreateImageViews();\n  void CreateRenderPass();\n  void CreateFramebuffers();\n  void CreateCommandPool();\n  void CreateColorAndDepthImage();\n  void CreateCommandBuffer();\n  void CreateSyncObjects();\n  void CreateDescriptors();\n\n  void CreateVertexBuffer();\n  void CreateIndexBuffer();\n\n  void CleanupSwapChain();\n\n  void EnsureTextureBound(unsigned int texture);\n  void EnsureMode(Pipeline* pipeline, bool flush = true);\n  void Flush() override;\n\n  void MakeQuad();\n\n  glm::vec2 DesignToNDC(glm::vec2 designCoord) const override;\n\n  bool Drawing = false;\n\n  VulkanWindow* VkWindow;\n\n  VkDebugUtilsMessengerEXT DebugMessenger;\n\n  VkInstance Instance;\n  VkPhysicalDevice PhysicalDevice;\n  VkDevice Device;\n  VkQueueFamilies QueueIndices;\n  VkQueue GraphicsQueue;\n  VkQueue PresentQueue;\n\n  VkSurfaceKHR Surface;\n\n  VkSwapchainKHR SwapChain;\n  std::vector<VkImage> SwapChainImages;\n  VkFormat SwapChainImageFormat;\n  VkExtent2D SwapChainExtent;\n  std::vector<VkImageView> SwapChainImageViews;\n  std::vector<VkFramebuffer> SwapChainFramebuffers;\n\n  AllocatedImage ColorImage;\n  VkImageView ColorImageView;\n  AllocatedImage DepthImage;\n  VkImageView DepthImageView;\n\n  VkRenderPass RenderPass;\n\n  VkCommandPool CommandPool;\n  VkCommandBuffer CommandBuffers[MAX_FRAMES_IN_FLIGHT];\n\n  VkSemaphore ImageAvailableSemaphores[MAX_FRAMES_IN_FLIGHT];\n  VkSemaphore RenderFinishedSemaphores[MAX_FRAMES_IN_FLIGHT];\n  VkFence InFlightFences[MAX_FRAMES_IN_FLIGHT];\n\n  VkDescriptorPool DescriptorPool;\n  VkDescriptorSetLayout SingleTextureSetLayout;\n  VkDescriptorSetLayout DoubleTextureSetLayout;\n  VkDescriptorSetLayout TripleTextureSetLayout;\n\n  Pipeline* PipelineSprite;\n  Pipeline* PipelineSpriteNoBlending;\n  Pipeline* PipelineSpriteInverted;\n  Pipeline* PipelineMaskedSprite;\n  Pipeline* PipelineMaskedSpriteNoAlpha;\n  Pipeline* PipelineYUVFrame;\n  Pipeline* PipelineNV12Frame;\n  Pipeline* PipelineCCMessageBox;\n  Pipeline* PipelineCHLCCMenuBackground;\n\n  AllocatedBuffer VertexBufferAlloc;\n  AllocatedBuffer IndexBufferAlloc;\n\n  uint32_t CurrentTexture = 0;\n  uint32_t NextTextureId = 1;\n\n  VkYUVFrame* VideoFrameInternalYUV;\n  VkNV12Frame* VideoFrameInternalNV12;\n\n  static int constexpr VertexBufferSize = 4096 * 4096;\n  static int constexpr IndexBufferCount =\n      VertexBufferSize / (4 * sizeof(VertexBufferSprites)) * 6;\n\n  uint8_t* VertexBuffer;\n  uint16_t* IndexBuffer;\n\n  size_t VertexBufferFill = 0;\n  size_t VertexBufferOffset = 0;\n  size_t VertexBufferCount = 0;\n  size_t IndexBufferFill = 0;\n  size_t IndexBufferOffset = 0;\n\n  RectF PreviousScissorRect;\n\n  static VkVertexInputBindingDescription GetBindingDescription();\n\n  static std::array<VkVertexInputAttributeDescription, 4>\n  GetAttributeDescriptions();\n};\n\ninline Renderer* MainRendererInstance;\n\n}  // namespace Vulkan\n}  // namespace Impacto\n"
  },
  {
    "path": "src/renderer/vulkan/utils.cpp",
    "content": "#include \"utils.h\"\n\n#define VMA_STATIC_VULKAN_FUNCTIONS 0\n#define VMA_DYNAMIC_VULKAN_FUNCTIONS 1\n#define VMA_IMPLEMENTATION\n#include <vma/vk_mem_alloc.h>\n\nnamespace Impacto {\nnamespace Vulkan {\n\nvoid CreateAllocator(VkPhysicalDevice physicalDevice, VkDevice device,\n                     VkInstance instance) {\n  VmaAllocatorCreateInfo allocatorInfo = {};\n  VmaVulkanFunctions func = {};\n  func.vkGetInstanceProcAddr = vkGetInstanceProcAddr;\n  func.vkGetDeviceProcAddr = vkGetDeviceProcAddr;\n  allocatorInfo.pVulkanFunctions = &func;\n  allocatorInfo.physicalDevice = physicalDevice;\n  allocatorInfo.device = device;\n  allocatorInfo.instance = instance;\n  vmaCreateAllocator(&allocatorInfo, &Allocator);\n}\n\nAllocatedBuffer CreateBuffer(size_t allocSize, VkBufferUsageFlags usage,\n                             VmaMemoryUsage memoryUsage) {\n  // allocate vertex buffer\n  VkBufferCreateInfo bufferInfo = {};\n  bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;\n  bufferInfo.pNext = nullptr;\n  bufferInfo.size = allocSize;\n  bufferInfo.usage = usage;\n  VmaAllocationCreateInfo vmaallocInfo = {};\n  vmaallocInfo.usage = memoryUsage;\n\n  AllocatedBuffer newBuffer;\n\n  // allocate the buffer\n  vmaCreateBuffer(Allocator, &bufferInfo, &vmaallocInfo, &newBuffer.Buffer,\n                  &newBuffer.Allocation, nullptr);\n\n  return newBuffer;\n}\n\nvoid ImmediateSubmit(std::function<void(VkCommandBuffer cmd)>&& function) {\n  VkCommandBuffer cmd = MainUploadContext.CommandBuffer;\n  VkCommandBufferBeginInfo cmdBeginInfo = {};\n  cmdBeginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;\n  cmdBeginInfo.pNext = nullptr;\n  cmdBeginInfo.pInheritanceInfo = nullptr;\n  cmdBeginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;\n\n  vkResetFences(MainUploadContext.Device, 1, &MainUploadContext.UploadFence);\n\n  vkBeginCommandBuffer(cmd, &cmdBeginInfo);\n  function(cmd);\n  vkEndCommandBuffer(cmd);\n\n  VkSubmitInfo submit = {};\n  submit.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;\n  submit.pNext = nullptr;\n  submit.waitSemaphoreCount = 0;\n  submit.pWaitSemaphores = nullptr;\n  submit.pWaitDstStageMask = nullptr;\n  submit.commandBufferCount = 1;\n  submit.pCommandBuffers = &MainUploadContext.CommandBuffer;\n  submit.signalSemaphoreCount = 0;\n  submit.pSignalSemaphores = nullptr;\n  vkQueueSubmit(MainUploadContext.GraphicsQueue, 1, &submit,\n                MainUploadContext.UploadFence);\n\n  vkWaitForFences(MainUploadContext.Device, 1, &MainUploadContext.UploadFence,\n                  true, UINT64_MAX);\n  vkResetFences(MainUploadContext.Device, 1, &MainUploadContext.UploadFence);\n\n  vkResetCommandPool(MainUploadContext.Device, MainUploadContext.CommandPool,\n                     0);\n}\n\nVkImageCreateInfo GetImageCreateInfo(VkFormat format, VkExtent3D extent,\n                                     VkSampleCountFlagBits msaaCount) {\n  VkImageCreateInfo dimgInfo = {};\n  dimgInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;\n  dimgInfo.pNext = nullptr;\n  dimgInfo.imageType = VK_IMAGE_TYPE_2D;\n  dimgInfo.format = format;\n  dimgInfo.extent = extent;\n  dimgInfo.mipLevels = 1;\n  dimgInfo.arrayLayers = 1;\n  dimgInfo.samples = msaaCount;\n  dimgInfo.tiling = VK_IMAGE_TILING_OPTIMAL;\n  dimgInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;\n\n  return dimgInfo;\n}\n\n}  // namespace Vulkan\n}  // namespace Impacto\n"
  },
  {
    "path": "src/renderer/vulkan/utils.h",
    "content": "#pragma once\n\n#include <vulkan/vulkan.h>\n#include <functional>\n#include <ankerl/unordered_dense.h>\n#include <vma/vk_mem_alloc.h>\n#include \"pipeline.h\"\n\nnamespace Impacto {\nnamespace Vulkan {\n\nint constexpr MAX_FRAMES_IN_FLIGHT = 2;\n\ninline uint32_t CurrentFrameIndex = 0;\ninline uint32_t CurrentImageIndex = 0;\n\ninline Pipeline* CurrentPipeline = nullptr;\n\ninline VkSampler Sampler;\n\ninline PFN_vkCmdPushDescriptorSetKHR vkCmdPushDescriptorSetKHR;\n\nstruct VkQueueFamilies {\n  uint32_t GraphicsQueueIdx;\n  uint32_t PresentQueueIdx;\n};\n\nstruct AllocatedBuffer {\n  VkBuffer Buffer;\n  VmaAllocation Allocation;\n};\n\nstruct AllocatedImage {\n  VkImage Image;\n  VmaAllocation Allocation;\n};\n\ntypedef struct VkTexture {\n  AllocatedImage Image;\n  VkImageView ImageView;\n  VkDescriptorSet Descriptor;\n} VkTexture;\n\ninline ankerl::unordered_dense::map<uint32_t, VkTexture> Textures;\n\nstruct UploadContext {\n  VkDevice Device;\n  VkQueue GraphicsQueue;\n  VkFence UploadFence;\n  VkCommandPool CommandPool;\n  alignas(16) VkCommandBuffer CommandBuffer;\n};\n\ninline VmaAllocator Allocator;\ninline UploadContext MainUploadContext;\n\nvoid CreateAllocator(VkPhysicalDevice physicalDevice, VkDevice device,\n                     VkInstance instance);\n\nAllocatedBuffer CreateBuffer(size_t allocSize, VkBufferUsageFlags usage,\n                             VmaMemoryUsage memoryUsage);\n\nvoid ImmediateSubmit(std::function<void(VkCommandBuffer cmd)>&& function);\n\nVkImageCreateInfo GetImageCreateInfo(VkFormat format, VkExtent3D extent,\n                                     VkSampleCountFlagBits msaaCount);\n\n}  // namespace Vulkan\n}  // namespace Impacto\n"
  },
  {
    "path": "src/renderer/vulkan/window.cpp",
    "content": "#include \"window.h\"\n\n#include <SDL_vulkan.h>\n\n#include \"../renderer.h\"\n#include \"../../log.h\"\n#include \"../../profile/game.h\"\n#include \"../../game.h\"\n\n#include \"renderer.h\"\n\nnamespace Impacto {\nnamespace Vulkan {\n\nvoid VulkanWindow::UpdateDimensions() {\n  WindowDimensionsChanged = false;\n  SDL_Vulkan_GetDrawableSize(SDLWindow, &WindowWidth, &WindowHeight);\n  if (WindowWidth != lastWidth || WindowHeight != lastHeight ||\n      MsaaCount != lastMsaa || RenderScale != lastRenderScale) {\n    WindowDimensionsChanged = true;\n    ImpLog(LogLevel::Debug, LogChannel::General,\n           \"Drawable size (pixels): {:d} x {:d} ({:d}x MSAA requested, render \"\n           \"scale {:f})\\n\",\n           WindowWidth, WindowHeight, MsaaCount, RenderScale);\n  }\n  lastWidth = WindowWidth;\n  lastHeight = WindowHeight;\n  lastMsaa = MsaaCount;\n  lastRenderScale = RenderScale;\n\n  int osWindowWidth, osWindowHeight;\n  SDL_GetWindowSize(SDLWindow, &osWindowWidth, &osWindowHeight);\n  DpiScaleX = (float)WindowWidth / (float)osWindowWidth;\n  DpiScaleY = (float)WindowHeight / (float)osWindowHeight;\n  // SDL_SetWindowInputFocus(SDLWindow);\n  MainRendererInstance->RecreateSwapChain();\n}\n\nRectF VulkanWindow::GetViewport() {\n  RectF viewport;\n  float scale = fmin((float)WindowWidth / Profile::DesignWidth,\n                     (float)WindowHeight / Profile::DesignHeight);\n  viewport.Width = Profile::DesignWidth * scale;\n  viewport.Height = Profile::DesignHeight * scale;\n  viewport.X = ((float)WindowWidth - viewport.Width) / 2.0f;\n  viewport.Y = ((float)WindowHeight - viewport.Height) / 2.0f;\n  return viewport;\n}\n\nRectF VulkanWindow::GetScaledViewport() {\n  RectF viewport = GetViewport();\n  viewport.Width *= RenderScale;\n  viewport.Height *= RenderScale;\n  viewport.X *= RenderScale;\n  viewport.Y *= RenderScale;\n  return viewport;\n}\n\nvoid VulkanWindow::Init() {\n  assert(IsInit == false);\n  ImpLog(LogLevel::Info, LogChannel::General, \"Creating window\\n\");\n  IsInit = true;\n\n#ifdef __ANDROID__\n  SDL_SetHint(SDL_HINT_ORIENTATIONS, \"LandscapeLeft LandscapeRight\");\n#endif\n  if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER) != 0) {\n    ImpLog(LogLevel::Fatal, LogChannel::General,\n           \"SDL initialisation failed: {:s}\\n\", SDL_GetError());\n    Shutdown();\n    return;\n  }\n\n  SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, \"0\");\n\n  uint32_t windowFlags = SDL_WINDOW_VULKAN;\n#if IMPACTO_USE_SDL_HIGHDPI\n  windowFlags |= SDL_WINDOW_ALLOW_HIGHDPI;\n#endif\n  if (Profile::Fullscreen) {\n    windowFlags |= SDL_WINDOW_FULLSCREEN;\n  }\n\n  SDLWindow = SDL_CreateWindow(\n      Profile::WindowName, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,\n      Profile::ResolutionWidth, Profile::ResolutionHeight, windowFlags);\n\n  if (SDLWindow == NULL) {\n    ImpLog(LogLevel::Error, LogChannel::General,\n           \"Window creation failed: {:s}\\n\", SDL_GetError());\n    return;\n  }\n\n  SDL_GetWindowSize(SDLWindow, &WindowWidth, &WindowHeight);\n  ImpLog(LogLevel::Debug, LogChannel::General,\n         \"Window size (screen coords): {:d} x {:d}\\n\", WindowWidth,\n         WindowHeight);\n}\n\nvoid VulkanWindow::SetDimensions(int width, int height, int msaa,\n                                 float renderScale) {}\n\nvoid VulkanWindow::SwapRTs() {}\n\nvoid VulkanWindow::Update() {}\n\nvoid VulkanWindow::Draw() {\n#ifndef IMPACTO_DISABLE_IMGUI\n  if (ImGui::GetIO().ConfigFlags & ImGuiConfigFlags_ViewportsEnable) {\n    ImGui::UpdatePlatformWindows();\n    ImGui::RenderPlatformWindowsDefault();\n  }\n#endif\n}\n\nvoid VulkanWindow::Shutdown() {\n  SDL_DestroyWindow(SDLWindow);\n  SDL_Quit();\n  // TODO move exit to users\n  exit(0);\n}\n\n}  // namespace Vulkan\n}  // namespace Impacto"
  },
  {
    "path": "src/renderer/vulkan/window.h",
    "content": "#pragma once\n\n#include \"../window.h\"\n\nnamespace Impacto {\nnamespace Vulkan {\n\nclass VulkanWindow : public BaseWindow {\n public:\n  void Init() override;\n  void SetDimensions(int width, int height, int msaa,\n                     float renderScale) override;\n  RectF GetViewport() override;\n  RectF GetScaledViewport() override;\n  void SwapRTs() override;\n  void Update() override;\n  void Draw() override;\n  void Shutdown() override;\n\n private:\n  void UpdateDimensions() override;\n};\n\n}  // namespace Vulkan\n}  // namespace Impacto"
  },
  {
    "path": "src/renderer/vulkan/yuvframe.cpp",
    "content": "#include \"yuvframe.h\"\n\nnamespace Impacto {\nnamespace Vulkan {\n\nvoid VkYUVFrame::Init(float width, float height) {\n  Width = width;\n  Height = height;\n\n  VkDeviceSize imageSize = (VkDeviceSize)(width * height);\n  VkDeviceSize bufferSize =\n      imageSize + 2 * (((VkDeviceSize)width / 2) * ((VkDeviceSize)height / 2));\n  VkFormat imageFormat = VK_FORMAT_R8_UNORM;\n\n  StagingBuffer = CreateBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT,\n                               VMA_MEMORY_USAGE_CPU_ONLY);\n  vmaMapMemory(Allocator, StagingBuffer.Allocation, &MappedStagingBuffer);\n\n  VkExtent3D lumaImageExtent;\n  lumaImageExtent.width = static_cast<uint32_t>(width);\n  lumaImageExtent.height = static_cast<uint32_t>(height);\n  lumaImageExtent.depth = 1;\n\n  VkExtent3D cbCrImageExtent;\n  cbCrImageExtent.width = static_cast<uint32_t>(width / 2);\n  cbCrImageExtent.height = static_cast<uint32_t>(height / 2);\n  cbCrImageExtent.depth = 1;\n\n  auto lumaImageInfo =\n      GetImageCreateInfo(imageFormat, lumaImageExtent,\n                         VkSampleCountFlagBits::VK_SAMPLE_COUNT_1_BIT);\n  VmaAllocationCreateInfo dimgAllocinfo = {};\n  dimgAllocinfo.usage = VMA_MEMORY_USAGE_GPU_ONLY;\n  vmaCreateImage(Allocator, &lumaImageInfo, &dimgAllocinfo,\n                 &LumaImage.Image.Image, &LumaImage.Image.Allocation, nullptr);\n\n  auto cbImageInfo =\n      GetImageCreateInfo(imageFormat, cbCrImageExtent,\n                         VkSampleCountFlagBits::VK_SAMPLE_COUNT_1_BIT);\n  vmaCreateImage(Allocator, &cbImageInfo, &dimgAllocinfo, &CbImage.Image.Image,\n                 &CbImage.Image.Allocation, nullptr);\n\n  auto crImageInfo =\n      GetImageCreateInfo(imageFormat, cbCrImageExtent,\n                         VkSampleCountFlagBits::VK_SAMPLE_COUNT_1_BIT);\n  vmaCreateImage(Allocator, &crImageInfo, &dimgAllocinfo, &CrImage.Image.Image,\n                 &CrImage.Image.Allocation, nullptr);\n\n  VkImageViewCreateInfo imageInfo = {};\n  imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;\n  imageInfo.pNext = nullptr;\n  imageInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;\n  imageInfo.image = LumaImage.Image.Image;\n  imageInfo.format = imageFormat;\n  imageInfo.subresourceRange.baseMipLevel = 0;\n  imageInfo.subresourceRange.levelCount = 1;\n  imageInfo.subresourceRange.baseArrayLayer = 0;\n  imageInfo.subresourceRange.layerCount = 1;\n  imageInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;\n\n  vkCreateImageView(MainUploadContext.Device, &imageInfo, nullptr,\n                    &LumaImage.ImageView);\n\n  imageInfo.image = CbImage.Image.Image;\n  vkCreateImageView(MainUploadContext.Device, &imageInfo, nullptr,\n                    &CbImage.ImageView);\n\n  imageInfo.image = CrImage.Image.Image;\n  vkCreateImageView(MainUploadContext.Device, &imageInfo, nullptr,\n                    &CrImage.ImageView);\n}\n\nvoid VkYUVFrame::Submit(const void* luma, const void* cb, const void* cr) {\n  int cbOffset = (int)(Width * Height);\n  int crOffset = (int)((Width * Height) + ((Width / 2) * (Height / 2)));\n\n  uint8_t* mappedStagingBuffer = (uint8_t*)MappedStagingBuffer;\n\n  memcpy(mappedStagingBuffer, luma, cbOffset);\n  memcpy(mappedStagingBuffer + cbOffset, cb,\n         (size_t)((Width / 2) * (Height / 2)));\n  memcpy(mappedStagingBuffer + crOffset, cr,\n         (size_t)((Width / 2) * (Height / 2)));\n\n  ImmediateSubmit([&](VkCommandBuffer cmd) {\n    VkExtent3D lumaImageExtent;\n    lumaImageExtent.width = static_cast<uint32_t>(Width);\n    lumaImageExtent.height = static_cast<uint32_t>(Height);\n    lumaImageExtent.depth = 1;\n    VkExtent3D cbCrImageExtent;\n    cbCrImageExtent.width = static_cast<uint32_t>(Width / 2);\n    cbCrImageExtent.height = static_cast<uint32_t>(Height / 2);\n    cbCrImageExtent.depth = 1;\n\n    VkImageSubresourceRange range;\n    range.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;\n    range.baseMipLevel = 0;\n    range.levelCount = 1;\n    range.baseArrayLayer = 0;\n    range.layerCount = 1;\n\n    VkImageMemoryBarrier imageBarrierToTransfer = {};\n    imageBarrierToTransfer.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;\n    imageBarrierToTransfer.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;\n    imageBarrierToTransfer.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;\n    imageBarrierToTransfer.image = LumaImage.Image.Image;\n    imageBarrierToTransfer.subresourceRange = range;\n    imageBarrierToTransfer.srcAccessMask = 0;\n    imageBarrierToTransfer.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;\n\n    vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,\n                         VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0,\n                         nullptr, 1, &imageBarrierToTransfer);\n\n    VkBufferImageCopy copyRegion = {};\n    copyRegion.bufferOffset = 0;\n    copyRegion.bufferRowLength = 0;\n    copyRegion.bufferImageHeight = 0;\n    copyRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;\n    copyRegion.imageSubresource.mipLevel = 0;\n    copyRegion.imageSubresource.baseArrayLayer = 0;\n    copyRegion.imageSubresource.layerCount = 1;\n    copyRegion.imageExtent = lumaImageExtent;\n    vkCmdCopyBufferToImage(cmd, StagingBuffer.Buffer, LumaImage.Image.Image,\n                           VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1,\n                           &copyRegion);\n\n    VkImageMemoryBarrier imageBarrierToReadable = imageBarrierToTransfer;\n    imageBarrierToReadable.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;\n    imageBarrierToReadable.newLayout = VK_IMAGE_LAYOUT_GENERAL;\n    imageBarrierToReadable.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;\n    imageBarrierToReadable.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;\n    vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TRANSFER_BIT,\n                         VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, nullptr,\n                         0, nullptr, 1, &imageBarrierToReadable);\n\n    imageBarrierToTransfer.image = CbImage.Image.Image;\n    vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,\n                         VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0,\n                         nullptr, 1, &imageBarrierToTransfer);\n\n    copyRegion.bufferOffset = (VkDeviceSize)(Width * Height);\n    copyRegion.imageExtent = cbCrImageExtent;\n    vkCmdCopyBufferToImage(cmd, StagingBuffer.Buffer, CbImage.Image.Image,\n                           VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1,\n                           &copyRegion);\n\n    imageBarrierToReadable.image = CbImage.Image.Image;\n    vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TRANSFER_BIT,\n                         VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, nullptr,\n                         0, nullptr, 1, &imageBarrierToReadable);\n\n    imageBarrierToTransfer.image = CrImage.Image.Image;\n    vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,\n                         VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0,\n                         nullptr, 1, &imageBarrierToTransfer);\n\n    copyRegion.bufferOffset = crOffset;\n    vkCmdCopyBufferToImage(cmd, StagingBuffer.Buffer, CrImage.Image.Image,\n                           VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1,\n                           &copyRegion);\n\n    imageBarrierToReadable.image = CrImage.Image.Image;\n    vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TRANSFER_BIT,\n                         VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, nullptr,\n                         0, nullptr, 1, &imageBarrierToReadable);\n  });\n}\n\nvoid VkYUVFrame::Release() {\n  vmaUnmapMemory(Allocator, StagingBuffer.Allocation);\n}\n\n}  // namespace Vulkan\n}  // namespace Impacto"
  },
  {
    "path": "src/renderer/vulkan/yuvframe.h",
    "content": "#pragma once\n\n#include \"../yuvframe.h\"\n#include \"utils.h\"\n\n#include <vulkan/vulkan.h>\n\nnamespace Impacto {\nnamespace Vulkan {\n\nclass VkYUVFrame : public YUVFrame {\n  friend class Renderer;\n\n public:\n  void Init(float width, float height) override;\n\n  void Submit(const void* luma, const void* cb, const void* cr) override;\n  void Release() override;\n\n protected:\n  VkTexture LumaImage{};\n  VkTexture CbImage{};\n  VkTexture CrImage{};\n\n private:\n  AllocatedBuffer StagingBuffer;\n  void* MappedStagingBuffer;\n};\n\n}  // namespace Vulkan\n}  // namespace Impacto"
  },
  {
    "path": "src/renderer/window.cpp",
    "content": "#include \"window.h\"\n#include \"../io/assetpath.h\"\n#include \"../profile/game.h\"\n#include \"../log.h\"\n#include <stb_image.h>\n#include <memory>\n#include <vector>\n#include <optional>\n#include \"../inputsystem.h\"\n\nnamespace Impacto {\n\nvoid SetWindowIcon(SDL_Window* window) {\n  if (!Profile::WindowIconPath.has_value()) {\n    return;\n  }\n\n  Io::AssetPath asset;\n  asset.FileName = Profile::WindowIconPath->c_str();\n  Io::Stream* streamPtr;\n  IoError err = asset.Open(&streamPtr);\n  if (err != IoError_OK) {\n    ImpLog(LogLevel::Warning, LogChannel::General,\n           \"Could not open window icon file {:s}\\n\",\n           Profile::WindowIconPath->c_str());\n    return;\n  }\n  std::unique_ptr<Io::Stream> stream(streamPtr);\n  size_t fileSize = stream->Meta.Size;\n  std::vector<uint8_t> fileData(fileSize);\n  int64_t bytesRead = stream->Read(fileData.data(), fileSize);\n\n  if (bytesRead != (int64_t)fileSize) {\n    ImpLog(LogLevel::Warning, LogChannel::General,\n           \"Could not read window icon file {:s}\\n\",\n           Profile::WindowIconPath->c_str());\n    return;\n  }\n\n  int width, height, channels;\n  uint8_t* image =\n      stbi_load_from_memory((const stbi_uc*)fileData.data(), (int)fileSize,\n                            &width, &height, &channels, STBI_rgb_alpha);\n\n  if (!image) {\n    ImpLog(LogLevel::Warning, LogChannel::General,\n           \"Could not load window icon from {:s}\\n\",\n           Profile::WindowIconPath->c_str());\n    return;\n  }\n\n  SDL_Surface* surface =\n      SDL_CreateRGBSurfaceFrom(image, width, height, 32, width * 4, 0x000000FF,\n                               0x0000FF00, 0x00FF0000, 0xFF000000);\n  if (!surface) {\n    ImpLog(LogLevel::Error, LogChannel::General,\n           \"Could not create SDL surface for window icon from {:s}: {:s}\\n\",\n           Profile::WindowIconPath->c_str(), SDL_GetError());\n    stbi_image_free(image);\n    return;\n  }\n\n  SDL_SetWindowIcon(window, surface);\n  SDL_FreeSurface(surface);\n  stbi_image_free(image);\n}\n\nstatic SDL_Cursor* CursorArrow = nullptr;\nstatic SDL_Cursor* CursorPointer = nullptr;\nstatic SDL_Cursor* CurrentCursor = nullptr;\nstatic std::optional<CursorType> RequestedCursorType;\nstatic Input::Device PreviousInputDevice = Input::Device::Mouse;\n\nstatic SDL_Cursor* LoadCursorFromFile(const std::string& path) {\n  Io::AssetPath asset;\n  asset.FileName = path.c_str();\n  Io::Stream* streamPtr;\n  IoError err = asset.Open(&streamPtr);\n  if (err != IoError_OK) {\n    ImpLog(LogLevel::Warning, LogChannel::General,\n           \"Could not open cursor file {:s}\\n\", path.c_str());\n    return nullptr;\n  }\n  std::unique_ptr<Io::Stream> stream(streamPtr);\n  size_t fileSize = stream->Meta.Size;\n  std::vector<uint8_t> fileData(fileSize);\n  int64_t bytesRead = stream->Read(fileData.data(), fileSize);\n  if (bytesRead != (int64_t)fileSize) {\n    ImpLog(LogLevel::Warning, LogChannel::General,\n           \"Could not read cursor file {:s}\\n\", path.c_str());\n    return nullptr;\n  }\n\n  int width, height, channels;\n  uint8_t* image =\n      stbi_load_from_memory((const stbi_uc*)fileData.data(), (int)fileSize,\n                            &width, &height, &channels, STBI_rgb_alpha);\n  if (!image) {\n    ImpLog(LogLevel::Warning, LogChannel::General,\n           \"Could not load cursor image from {:s}\\n\", path.c_str());\n    return nullptr;\n  }\n\n  SDL_Surface* surface =\n      SDL_CreateRGBSurfaceFrom(image, width, height, 32, width * 4, 0x000000FF,\n                               0x0000FF00, 0x00FF0000, 0xFF000000);\n  if (!surface) {\n    ImpLog(LogLevel::Error, LogChannel::General,\n           \"Could not create SDL surface for cursor from {:s}: {:s}\\n\",\n           path.c_str(), SDL_GetError());\n    stbi_image_free(image);\n    return nullptr;\n  }\n\n  SDL_Cursor* cursor = SDL_CreateColorCursor(surface, 0, 0);\n  if (!cursor) {\n    ImpLog(LogLevel::Error, LogChannel::General,\n           \"SDL_CreateColorCursor failed for {:s}: {:s}\\n\", path.c_str(),\n           SDL_GetError());\n  }\n\n  SDL_FreeSurface(surface);\n  stbi_image_free(image);\n  return cursor;\n}\n\nvoid InitCursors() {\n  if (Profile::CursorArrowPath.has_value()) {\n    CursorArrow = LoadCursorFromFile(*Profile::CursorArrowPath);\n  }\n  if (!CursorArrow) {\n    CursorArrow = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW);\n  }\n\n  if (Profile::CursorPointerPath.has_value()) {\n    CursorPointer = LoadCursorFromFile(*Profile::CursorPointerPath);\n  }\n  if (!CursorPointer) {\n    CursorPointer = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_HAND);\n  }\n\n  if (CursorArrow) {\n    SDL_SetCursor(CursorArrow);\n    CurrentCursor = CursorArrow;\n  }\n}\n\nvoid RequestCursor(CursorType type) {\n  if (Input::CurrentInputDevice == Input::Device::Mouse) {\n    if (type == CursorType::Pointer && CursorPointer) {\n      if (CurrentCursor != CursorPointer) {\n        RequestedCursorType = CursorType::Pointer;\n      } else {\n        RequestedCursorType = std::nullopt;\n      }\n    } else {\n      if (CurrentCursor != CursorArrow) {\n        RequestedCursorType = CursorType::Default;\n      } else {\n        RequestedCursorType = std::nullopt;\n      }\n    }\n  }\n}\n\nvoid ApplyCursorForFrame() {\n  SDL_Cursor* actualCursor = SDL_GetCursor();\n\n  if (Input::CurrentInputDevice != PreviousInputDevice) {\n    CurrentCursor = actualCursor;\n    PreviousInputDevice = Input::CurrentInputDevice;\n  }\n\n  SDL_Cursor* desired = CursorArrow;\n\n  if (Input::CurrentInputDevice == Input::Device::Mouse) {\n    if (RequestedCursorType.has_value()) {\n      if (*RequestedCursorType == CursorType::Pointer && CursorPointer) {\n        desired = CursorPointer;\n      } else if (*RequestedCursorType == CursorType::Default) {\n        desired = CursorArrow;\n      }\n      ActiveCursorType = *RequestedCursorType;\n      RequestedCursorType = std::nullopt;\n    } else {\n      if (actualCursor != CursorArrow && actualCursor != CursorPointer) {\n        desired = CursorArrow;\n      } else {\n        desired = actualCursor;\n      }\n    }\n  } else {\n    RequestedCursorType = std::nullopt;\n    desired = CursorArrow;\n  }\n\n  if (desired && desired != actualCursor) {\n    SDL_SetCursor(desired);\n    CurrentCursor = desired;\n  } else {\n    CurrentCursor = actualCursor;\n  }\n}\n\n}  // namespace Impacto\n"
  },
  {
    "path": "src/renderer/window.h",
    "content": "#pragma once\n\n#include <SDL.h>\n#include \"../util.h\"\n\nnamespace Impacto {\n\nenum class CursorType { Default, Pointer };\ninline CursorType ActiveCursorType = CursorType::Default;\n\nvoid SetWindowIcon(SDL_Window* window);\nvoid InitCursors();\nvoid RequestCursor(CursorType type);\nvoid ApplyCursorForFrame();\n\nenum GraphicsApi {\n  GfxApi_GL,\n  // Forces the use of a GLES driver (e.g. ANGLE on Windows)\n  GfxApi_ForceNativeGLES,\n  // Forces GLES context on desktop GL driver\n  GfxApi_ForceDesktopGLES\n};\n\nclass BaseWindow {\n public:\n  virtual void Init() = 0;\n  virtual void SetDimensions(int width, int height, int msaa,\n                             float renderScale) = 0;\n  // Aspect ratio corrected viewport in window coordinates\n  virtual RectF GetViewport() = 0;\n  // Aspect ratio corrected viewport in window coordinates scaled by RenderScale\n  virtual RectF GetScaledViewport() = 0;\n  virtual void SwapRTs() = 0;\n  virtual void Update() = 0;\n  virtual void Draw() = 0;\n  virtual void Shutdown() = 0;\n\n  SDL_Window* SDLWindow;\n\n  // Raw dimensions without aspect ratio correction. Only use for\n  // setting/determining resolution and drawing to window framebuffer!\n  int WindowWidth = 0;\n  int WindowHeight = 0;\n\n  // OS window dimensions * DpiScaleX/Y => WindowWidth/Height (real pixels)\n  // Always 1 unless high DPI support is SDL_WINDOW_ALLOW_HIGHDPI\n  float DpiScaleX = 1.0f;\n  float DpiScaleY = 1.0f;\n\n  int MsaaCount = 4;\n  float RenderScale = 1.0f;\n\n  bool WindowDimensionsChanged;\n\n protected:\n  virtual void UpdateDimensions() = 0;\n  bool IsInit = false;\n\n  int lastWidth = -1;\n  int lastHeight = -1;\n  int lastMsaa = 0;\n  float lastRenderScale = 1.0f;\n};\n\n}  // namespace Impacto"
  },
  {
    "path": "src/renderer/yuvframe.h",
    "content": "#pragma once\n\n#include \"../impacto.h\"\n\nnamespace Impacto {\n\nclass YUVFrame {\n public:\n  float Width;\n  float Height;\n  uint32_t LumaId;\n  uint32_t CbId;\n  uint32_t CrId;\n\n  virtual void Init(float width, float height) = 0;\n\n  virtual void Submit(const void* luma, const void* cb, const void* cr) = 0;\n  virtual void Release() = 0;\n};\n\n}  // namespace Impacto"
  },
  {
    "path": "src/rng.h",
    "content": "#pragma once\n\n#include <pcg_basic.h>\n#include <math.h>\n\nnamespace Impacto {\n\nclass RNG {\n public:\n  RNG() { Seed(0, 0); }\n  RNG(uint64_t Seed1, uint64_t Seed2) { Seed(Seed1, Seed2); }\n  void Seed(uint64_t Seed1, uint64_t Seed2) {\n    pcg32_srandom_r(&Pcg, Seed1, Seed2);\n  }\n\n  uint32_t UintBetween(uint32_t min, uint32_t maxExclusive) {\n    return pcg32_boundedrand_r(&Pcg, (maxExclusive - min)) + min;\n  }\n\n  float FloatBetween(float min, float maxExclusive) {\n    return (maxExclusive - min) * ldexpf((float)pcg32_random_r(&Pcg), -32) +\n           min;\n  }\n\n private:\n  pcg32_random_t Pcg;\n};\n\n}  // namespace Impacto"
  },
  {
    "path": "src/scriptvars.h",
    "content": "// ScrWork\nV(SW_TITLECT)\nV(LR_DATE)\nV(SW_MENUCT)\nV(SW_MOVIEMODE_ALPHA)\nV(SW_MOVIEMODE_CUR)\nV(SW_ACTORVOICE_ALPHA)\nV(SW_ACTORVOICE_CUR)\nV(SW_ALBUM_ALPHA)\nV(SW_ALBUM_LOADFILE)\nV(SW_ALBUM_LOADBUF)\nV(SW_PLAYDATA_ALPHA)\nV(SW_MUSICMODE_ALPHA)\nV(SW_OPTIONALPHA)\nV(SW_FILEALPHA)\nV(SW_TIPSALPHA)\nV(SW_TOTALPLAYTIME)\nV(SW_CHARACTERIDMAPPING)\nV(SW_ANIME0CHANO)\nV(SW_ANIME1CHANO)\nV(SW_ANIME2CHANO)\nV(SW_MESWINDOW0ALPHA)\nV(SW_MESWINDOW1ALPHA)\nV(SW_MESWINDOW2ALPHA)\nV(SW_AUTOSAVERESTART)\nV(SW_GAMESTATE)\nV(SW_TITLEMODE)\nV(SW_TITLEMOVIECT)\nV(SW_TITLEDISPCT)\nV(SW_SYSSEL)\nV(SW_TITLEMASKALPHA)\nV(SW_TITLEMASKCOLOR)\nV(SW_TITLECGNO)\nV(SW_TITLECUR)\nV(SW_SYSTEMMENUCHG)\nV(SW_SYSTEMMENUALPHA)\nV(SW_SYSMENUCT)\nV(SW_SYSMENUALPHA)\nV(SW_SYSSUBMENUCT)\nV(SW_SYSSUBMENUNO)\nV(SW_SYSSUBMENUALPHA)\nV(SW_CLRALPHA)\nV(SW_SYSMESCOL1)\nV(SW_OPTIONCHAALPHA)\nV(SW_OPTIONCHANO)\nV(SW_TIMEYEAR)\nV(SW_TIMEMONTH)\nV(SW_TIMEDAY)\nV(SW_TIMEHOUR)\nV(SW_TIMEMINUTE)\nV(SW_TIMESECOND)\nV(SW_TIMEWEEK)\nV(SW_LINEID)\nV(SW_SCRIPTID)\nV(SW_CAP1POSX_OFS)\nV(SW_CAP1POSY_OFS)\nV(SW_CAP1SX_OFS)\nV(SW_CAP1SY_OFS)\nV(SW_CAP1SIZE_OFS)\nV(SW_CAP1LX_OFS)\nV(SW_CAP1LY_OFS)\nV(SW_CAP1ROTZ_OFS)\nV(SW_CAP1ALPHA_OFS)\nV(SW_MESNAMEID0)\nV(SW_BG1POSX_OFS)\nV(SW_BG1POSY_OFS)\nV(SW_BG1SX_OFS)\nV(SW_BG1SY_OFS)\nV(SW_BG1SIZE_OFS)\nV(SW_BG1LX_OFS)\nV(SW_BG1LY_OFS)\nV(SW_BG1ROTZ_OFS)\nV(SW_BG1ALPHA_OFS)\nV(SW_CHA1POSX_OFS)\nV(SW_CHA1POSY_OFS)\nV(SW_CHA1ROTZ_OFS)\nV(SW_CHA1ROTX_OFS)\nV(SW_CHA1ROTY_OFS)\nV(SW_CHA1SIZEX_OFS)\nV(SW_CHA1SIZEY_OFS)\nV(SW_CHA1ALPHA_OFS)\nV(SW_MASK1ALPHA_OFS)\nV(SW_MASK2ALPHA_OFS)\nV(SW_MASK3ALPHA_OFS)\nV(SW_BGEFF1_OFSX)\nV(SW_BGEFF1_OFSY)\nV(SW_BGEFF1_SIZE_OFS)\nV(SW_BGEFF1_ROTX_OFS)\nV(SW_BGEFF1_ROTY_OFS)\nV(SW_BGEFF1_ROTZ_OFS)\nV(SW_BGEFF1_ALPHA_OFS)\nV(SW_BGEFF1_SX_OFS)\nV(SW_BGEFF1_SY_OFS)\nV(SW_BGEFF1_MASK_VERTEX1_OFSX)\nV(SW_BGEFF1_MASK_VERTEX1_OFSY)\nV(SW_MAINTHDP)\nV(SW_RESTARTMASK)\nV(SW_PLATFORM)\nV(SW_SAVEERRORCODE)\nV(SW_SYSMENUCNO)\nV(SW_TITLECUR1)\nV(SW_TITLECUR2)\nV(SW_SYSMESALPHA)\nV(SW_SYSMESANIMCTCUR)\nV(SW_SYSMESANIMCTF)\nV(SW_SINSTALL_ALL)\nV(SW_BG1SURF)\nV(SW_MAP_PRI)\nV(SW_MAP_ALPHA)\nV(SW_DELUSION_OVERLAY_BUF)\nV(SW_DELUSION_CIRCLE_BUF)\nV(SW_DELUSION_STATE)\nV(SW_DELUSION_BG_COUNTER)\nV(SW_DELUSION_LIMIT)\nV(SW_DELUSION_SPIN_COUNTER)\nV(SW_DELUSION_PRI)\nV(SW_YESNO_PRI)\nV(SW_CHA1SURF)\nV(SW_FACE1SURF)\nV(SW_SHORTCUT)\nV(SW_SVSENO)\nV(SW_SVBGMNO)\nV(SW_SVSCRNO1)\nV(SW_SVSCRNO2)\nV(SW_SVSCRNO3)\nV(SW_SVSCRNO4)\nV(SW_SVBGNO1)\nV(SW_SVCHANO1)\nV(SW_SVBGM2NO)\nV(SW_SAVEFILESTATUS)\nV(SW_SAVEFILENO)\nV(SW_SAVEFILETYPE)\nV(SW_TITLE)\nV(SW_PLAYTIME)\nV(SW_MESWINDOW_COLOR)\nV(SW_BGMREQNO)\nV(SW_SEREQNO)\nV(SW_BGMVOL)\nV(SW_SEVOL)\nV(SW_SELNO)\nV(SW_BGMREQNO2)\nV(SW_SCRIPTNO0)\nV(SW_SCRIPTNO1)\nV(SW_SCRIPTNO2)\nV(SW_SCRIPTNO3)\nV(SW_SCRIPTNO4)\nV(SW_SCRIPTNO5)\nV(SW_SCRIPTNO6)\nV(SW_SCRIPTNO7)\nV(SW_SCRIPTNO8)\nV(SW_SCRIPTNO9)\nV(SW_SCRIPTNO10)\nV(SW_SCRIPTNO11)\nV(SW_SCRIPTNO12)\nV(SW_SCRIPTNO13)\nV(SW_SCRIPTNO14)\nV(SW_SCRIPTNO15)\nV(SW_MASK1COLOR)\nV(SW_MASK1ALPHA)\nV(SW_MASK1PRI)\nV(SW_MASK1POSX)\nV(SW_MASK1POSY)\nV(SW_MASK1SIZEX)\nV(SW_MASK1SIZEY)\nV(SW_MESWIN0POSX)\nV(SW_MESWIN0POSY)\nV(SW_MESWIN0TYPE)\nV(SW_MESMODE0)\nV(SW_BGLINK)\nV(SW_BGLINK2)\nV(SW_BG1POSX)\nV(SW_BG1POSY)\nV(SW_BG1SX)\nV(SW_BG1SY)\nV(SW_BG1SIZE)\nV(SW_BG1LX)\nV(SW_BG1LY)\nV(SW_BG1NO)\nV(SW_BG1PRI)\nV(SW_BG1DISPMODE)\nV(SW_BG1FADECT)\nV(SW_BG1FADETYPE)\nV(SW_BG1ROTZ)\nV(SW_BG1ALPHA)\nV(SW_BG1MASKNO)\nV(SW_BG1MASKFADERANGE)\nV(SW_BG1CLIP_X)\nV(SW_BG1CLIP_Y)\nV(SW_BG1PRI2)\nV(SW_BG1FILTER)\nV(SW_BG1EFFPRI)\nV(SW_BG1EFFPRI2)\nV(SW_CAP1POSX)\nV(SW_CAP1POSY)\nV(SW_CAP1SX)\nV(SW_CAP1SY)\nV(SW_CAP1SIZE)\nV(SW_CAP1LX)\nV(SW_CAP1LY)\nV(SW_CAP1NO)\nV(SW_CAP1PRI)\nV(SW_CAP1DISPMODE)\nV(SW_CAP1FADECT)\nV(SW_CAP1FADETYPE)\nV(SW_CAP1ROTZ)\nV(SW_CAP1ALPHA)\nV(SW_CAP1MASKNO)\nV(SW_CAP1MASKFADERANGE)\nV(SW_CAP1PRI2)\nV(SW_CAP1FILTER)\nV(SW_FEATHERING_PRI)\nV(SW_FEATHERING2_PRI)\nV(SW_FEATHERING)\nV(SW_FEATHERING2)\nV(SW_MDL1POSX)\nV(SW_MDL1POSY)\nV(SW_MDL1POSZ)\nV(SW_MDL1ROTX)\nV(SW_MDL1ROTY)\nV(SW_MDL1ROTZ)\nV(SW_MDL1FACENO)\nV(SW_MDL1ANIME)\nV(SW_MDL1FILENO)\nV(SW_MDL1ROTOY)\nV(SW_MDL1CENY)\nV(SW_MDL1TARDIR)\nV(SW_MDL1CHANO)\nV(SW_MDL1FACEDIR)\nV(SW_MDL1FACEROT)\nV(SW_MDL1FACEELV)\nV(SW_MDL1FACESLA)\nV(SW_MDL1ROT_INTER)\nV(SW_MDL1FROT_INTER)\nV(SW_MDL1POS_INTER)\nV(SW_MDL1EYEU)\nV(SW_MDL1EYEV)\nV(SW_MDL1EYEROT)\nV(SW_MDL1EYESIZE)\nV(SW_MDL1EYEANIMENO)\nV(SW_MDL1MOUTHANIMENO)\nV(SW_MDL1OPTPARTS)\nV(SW_MDL1LIGHTNO)\nV(SW_CHA1POSX)\nV(SW_CHA1POSY)\nV(SW_CHA1ROTZ)\nV(SW_CHA1ROTX)\nV(SW_CHA1ROTY)\nV(SW_CHA1SIZEX)\nV(SW_CHA1SIZEY)\nV(SW_CHA1ALPHA)\nV(SW_CHA1FILTER)\nV(SW_CHA1NO)\nV(SW_CHA1PRI)\nV(SW_CHA1POSE)\nV(SW_CHA1FACE)\nV(SW_CHA1EX)\nV(SW_CHA1FADECT)\nV(SW_CHA1FADETYPE)\nV(SW_CHA1ANIME_EYE)\nV(SW_CHA1ANIME_MOUTH)\nV(SW_CHA1PRI2)\nV(SW_CHA1BASESIZE)\nV(SW_IRUOCAMERAPOSX)\nV(SW_IRUOCAMERAPOSY)\nV(SW_IRUOCAMERAPOSZ)\nV(SW_IRUOCAMERAROTX)\nV(SW_IRUOCAMERAROTY)\nV(SW_CAMSHTARDIR)\nV(SW_IRUOCAMERAHFOV)\nV(SW_IRUOCAMERAHFOVDELTA)\nV(SW_MAINCAMERAPOSX)\nV(SW_MAINCAMERAPOSY)\nV(SW_MAINCAMERAPOSZ)\nV(SW_MAINCAMERAROTX)\nV(SW_MAINCAMERAROTY)\nV(SW_MAINCAMERAHFOV)\nV(SW_MAINCAMERAHFOVDELTA)\nV(SW_MAINLIGHTCOLOR)\nV(SW_MAINLIGHTPOSX)\nV(SW_MAINLIGHTPOSY)\nV(SW_MAINLIGHTPOSZ)\nV(SW_MAINLIGHTWEIGHT)\nV(SW_MAINLIGHTDARKMODE)\nV(SW_LIGHT1_COLORR)\nV(SW_LIGHT1_COLORG)\nV(SW_LIGHT1_COLORB)\nV(SW_LIGHT1_POSX)\nV(SW_LIGHT1_POSY)\nV(SW_LIGHT1_POSZ)\nV(SW_FACEEX1NO)\nV(SW_FACEEX1FACE)\nV(SW_FACEEX1FILTER)\nV(SW_FACEPOSX)\nV(SW_FACEPOSY)\nV(SW_CAMSHROT_WORK)\nV(SW_CAMSHELV_WORK)\nV(SW_CAMSHPOSX_WORK)\nV(SW_CAMSHPOSY_WORK)\nV(SW_CAMSHPOSZ_WORK)\nV(SW_CAMROT_WORK)\nV(SW_CAMELV_WORK)\nV(SW_CAMPOSX_WORK)\nV(SW_CAMPOSY_WORK)\nV(SW_CAMPOSZ_WORK)\nV(SW_IRUOCAMERAHFOVCUR)\nV(SW_MAINCAMERAHFOVCUR)\nV(SW_MOSAIC)\nV(SW_MOSAIC_PRI)\nV(SW_EFF_WAVE_PRI)\nV(SW_EFF_WAVE_ALPHA)\nV(SW_MASK_CAPTURE_PRI)\nV(SW_EFF_CAP_PRI)\nV(SW_EFF_CAP_BUF)\nV(SW_EFF_CAP_PRI2)\nV(SW_EFF_CAP_BUF2)\nV(SW_MOVIEPRI)\nV(SW_ALPHAMOVIE_PRI)\nV(SW_ALPHAMOVIE_ALPHA)\nV(SW_MOVIE_PLAYNO)\nV(SW_MOVIE_PLAYMODE)\nV(SW_MOVIE_PLAYVIEW)\nV(SW_MOVIE_LOADNO)\nV(SW_ALPHAMOVIE_MODE)\nV(SW_MOVIEALPHA)\nV(SW_MOVIEPRI2)\nV(SW_MOVIEALPHA2)\nV(SW_MOVIEPRI3)\nV(SW_MOVIEALPHA3)\nV(SW_MOVIEPRI4)\nV(SW_MOVIEALPHA4)\nV(SW_MOVIEFRAME)\nV(SW_MOVIETOTALFRAME)\nV(SW_POKECON_OFSX)\nV(SW_POKECON_OFSY)\nV(SW_POKECON_PRI)\nV(SW_POKECON_BOOTANIMECT)\nV(SW_POKECON_SHUTDOWNANIMECT)\nV(SW_POKECON_MENUCUR)\nV(SW_POKECON_MENUSELANIMECT)\nV(SW_POKECON_MODE)\nV(SW_AR_POSX)\nV(SW_AR_POSY)\nV(SW_AR_ELV)\nV(SW_PHONE_DISP_CT)\nV(SW_AR_ELVMIN)\nV(SW_AR_ELVMAX)\nV(SW_AR_ROTMIN)\nV(SW_AR_ROTMAX)\nV(SW_POKECOMIRUOHFOV)\nV(SW_BGEFF1_MASK_VERTEX1_X)\nV(SW_BGEFF1_MASK_VERTEX1_Y)\nV(SW_BGEFF1_SX)\nV(SW_BGEFF1_SY)\nV(SW_BGEFF1_SIZE)\nV(SW_BGEFF1_PRI)\nV(SW_BGEFF1_MASK_TYPE)\nV(SW_BGEFF1_FADECT)\nV(SW_BGEFF1_MODE)\nV(SW_BGEFF1_ROTX)\nV(SW_BGEFF1_ROTY)\nV(SW_BGEFF1_ROTZ)\nV(SW_BGEFF1_ALPHA)\nV(SW_BGEFF1_MASKNO)\nV(SW_BGEFF1_MASKFADERANGE)\nV(SW_BGEFF1_PRI2)\nV(SW_BGEFF1_FILTER)\nV(SW_BGEFF1_POSX)\nV(SW_BGEFF1_POSY)\nV(SW_RENDERTARGET)\nV(SW_INTROVOICE)\nV(SW_UI_BTNGUIDE_REQ)\nV(SW_UI_BTNGUIDE_PROG)\nV(SW_UI_BTNGUIDE_TYPE)\nV(SW_ATCHAN_SCROLL_MAX)\n\n// FlagWork\nV(SF_CLR_FLAG)\nV(SF_CLR_END1)\nV(SF_CLR_TRUE_CC)\nV(SF_CLR_TRUE_CCLCC)\nV(SF_ACTORSVOICE_UNLOCK1)\nV(SF_MOVIE_UNLOCK1)\nV(SF_SYSTEMMENUDIRECT)\nV(SF_MESSKIP)\nV(SF_MESALLSKIP)\nV(SF_MESREAD)\nV(SF_MOVIE_DRAWWAIT)\nV(SF_SELECTMODE)\nV(SF_TITLEMODE)\nV(SF_TITLEEND)\nV(SF_UIHIDDEN)\nV(SF_SHOWWAITICON)\nV(SF_LOADING)\nV(SF_BG1LOADEXEC)\nV(SF_SAVEDISABLE)\nV(SF_AUTOSAVEENABLE)\nV(SF_MESREVDISABLE)\nV(SF_MESSAVEPOINT_SSP)\nV(SF_SYSMENUDISABLE)\nV(SF_SYSTEMMENUDISABLE)\nV(SF_SYSTEMMENUDISABLE2)\nV(SF_GAMEPAUSE)\nV(SF_ALBUMRELOAD)\nV(SF_ALBUMEND)\nV(SF_ALBUMCHA1)\nV(SF_ALBUMLOAD_COMPLETE)\nV(SF_ALBUMLOAD)\nV(SF_LOADINGFROMSAVE)\nV(SF_BACKLOG_NOLOG)\nV(SF_BG1DISP)\nV(SF_MDL1DISP)\nV(SF_CHA1DISP)\nV(SF_MDL1SHDISP)\nV(SF_CAP1DISP)\nV(SF_FACEEX1DISP)\nV(SF_CAP1NOALPHACHK)\nV(SF_BGEFF1DISP)\nV(SF_BGEXPLOSIONVISIBLE)\nV(SF_REVADDDISABLE)\nV(SF_MOVIELOADPLAYFL)\nV(SF_MASK_CAPTURE)\nV(SF_MOVIEFL)\nV(SF_MESWINDOW0OPENFL)\nV(SF_CHA1BGEFFECT)\nV(SF_Phone_Open)\nV(SF_DATEDISPLAY)\nV(SF_IRUOENABLE)\nV(SF_AR_SETUP_ADD_MDLBUF1)\nV(SF_IRUOAUTO)\nV(SF_IRUO)\nV(SF_NEKOMIMIICON)\nV(SF_DELUSIONACTIVE)\nV(SF_DELUSIONSELECTED)\nV(SF_DELUSION_UI_ANIM_WAIT)\nV(SF_DELUSION_UI_ANIMSWITCH_WAIT)\nV(SW_DELUSION_POS_TXT_IDX)      // CHLCC specific\nV(SW_DELUSION_NEG_TXT_IDX)      // CHLCC specific\nV(SW_MONITOR_SCANLINE_PRI)      // CHLCC specific\nV(SW_MONITOR_SCANLINE_ENABLED)  // CHLCC specific\nV(SW_EYECATCH_COUNT)            // CHLCC specific\nV(SW_EYECATCH_BUF)              // CHLCC specific\nV(SW_EYECATCH_PRI)              // CHLCC specific\nV(SW_BUTTERFLY_COUNT)           // CHLCC specific\nV(SW_BUTTERFLY_PRI)             // CHLCC specific\nV(SW_BUTTERFLY_ALPHA)           // CHLCC specific\nV(SW_BUBBLES_COUNT)             // CHLCC specific\nV(SW_BUBBLES_PRI)               // CHLCC specific\nV(SW_BUBBLES_ALPHA)             // CHLCC specific\nV(SF_CLR_RIMI)                  // CHLCC specific\nV(SF_CLR_NANAMI)                // CHLCC specific\nV(SF_CLR_YUA)                   // CHLCC specific\nV(SF_CLR_MIA)                   // CHLCC specific\nV(SF_CLR_AYASE)                 // CHLCC specific\nV(SF_CLR_SENA)                  // CHLCC specific\nV(SF_CLR_KOZUE)                 // CHLCC specific\nV(SF_CLR_SEIRA)                 // CHLCC specific\nV(SW_TITLE_PRI)                 // CHLCC (or old version) specific\nV(SF_ALLCLEAR)\nV(SF_CONGRATULATED)\nV(SF_Pokecon_Open)\nV(SF_Pokecon_Cancel)\nV(SF_Pokecon_End)\nV(SF_Pokecon_ManualMode)\nV(SF_Pokecon_Disable)\nV(SF_SCN_CLR1)\nV(SF_HELPMENU)\nV(SF_BACKLOGMENU)\nV(SF_SAVEMENU)\nV(SF_SAVEPROTECTED)\nV(SF_SAVEALLPROTECTED)\nV(SF_SAVEPROTECTCHANGED)\nV(SF_OPTIONMENU)\nV(SF_SYSTEMMENU)\nV(SF_TIPSMENU)\nV(SF_ALBUMMENU)\nV(SF_CLEARLISTMENU)\nV(SF_SOUNDMENU)\nV(SF_MOVIEMENU)\nV(SF_RETURNTITLE)\nV(SF_MOVIEPLAY)\nV(SF_MOVIECANCEL)\nV(SF_RESTARTMASK)\nV(SF_ACHIEVEMENTMENU)\nV(SF_SYSTEMMENUCAPTURE)\nV(SF_EXTRA_ENA)\nV(SF_SUBMENUEXIT)\nV(SF_SAVEICON)\nV(SF_SAVECAPTURE)\nV(SF_CHAANIME)"
  },
  {
    "path": "src/sequencedanimation.cpp",
    "content": "#include \"sequencedanimation.h\"\n\nnamespace Impacto {\n\nvoid SequencedAnimation::AddAnimation(Animation& animation, float startTime,\n                                      float duration,\n                                      AnimationDirection direction) {\n  float endTime = startTime + duration;\n  Children.push_back({std::ref(animation), startTime, endTime, direction});\n\n  // There are multiple conflicting ways to interpret playing a sequenced\n  // animation in reverse, therefore doing so is currently undefined\n  DurationIn = std::max(DurationIn, endTime);\n  DurationOut = DurationIn;\n}\n\nvoid SequencedAnimation::StartInImpl(bool reset) {\n  for (ChildAnimation& child : Children) {\n    Animation& childAnimation = child.ChildAnimation.get();\n    if (reset) {\n      childAnimation.Stop();\n      childAnimation.Progress =\n          child.Direction == AnimationDirection::In ? 0.0f : 1.0f;\n    }\n\n    float time = Progress * DurationIn;\n    if (child.StartTime <= time && time <= child.EndTime) {\n      childAnimation.Start(child.Direction);\n    }\n  }\n}\n\nvoid SequencedAnimation::StartOutImpl(bool reset) {\n  for (ChildAnimation& child : Children) {\n    Animation& childAnimation = child.ChildAnimation.get();\n\n    if (reset) {\n      childAnimation.Stop();\n      childAnimation.Progress =\n          child.Direction == AnimationDirection::In ? 1.0f : 0.0f;\n    }\n\n    float time = Progress * DurationOut;\n    if (child.StartTime <= time && time <= child.EndTime) {\n      childAnimation.Start(-child.Direction);\n    }\n  }\n}\n\nvoid SequencedAnimation::ResetImpl(\n    std::optional<AnimationDirection> direction) {\n  for (ChildAnimation& child : Children) {\n    // Sequenced animations do not support reverse animations\n    child.ChildAnimation.get().Reset();\n  }\n}\n\nvoid SequencedAnimation::FinishImpl() {\n  for (ChildAnimation& child : Children) {\n    child.ChildAnimation.get().Finish();\n  }\n}\n\nvoid SequencedAnimation::UpdateImpl(float dt) {\n  float duration = GetDuration(Direction);\n  float previousTime = Progress * duration;\n\n  AddDelta(dt);\n\n  duration = GetDuration(Direction);\n  float time = Progress * duration;\n\n  for (ChildAnimation& child : Children) {\n    Animation& childAnimation = child.ChildAnimation.get();\n\n    if (time < child.StartTime || child.EndTime < time) {\n      if (childAnimation.State == AnimationState::Playing) {\n        childAnimation.Stop();\n        childAnimation.Progress = time > child.StartTime;\n\n        if (childAnimation.Direction == AnimationDirection::Out) {\n          childAnimation.Progress = 1.0f - childAnimation.Progress;\n        }\n      }\n\n      continue;\n    }\n\n    if (Direction == AnimationDirection::In) {\n      float delta = time - previousTime;\n      bool shouldStart = delta < 0 || (previousTime <= child.StartTime &&\n                                       time >= child.StartTime);\n\n      if (shouldStart) {\n        childAnimation.Start(child.Direction, true);\n        childAnimation.Update(time - child.StartTime);\n        continue;\n      }\n\n      childAnimation.Update(delta);\n    } else {\n      float delta = previousTime - time;\n      bool shouldStart =\n          delta < 0 || (previousTime >= child.EndTime && time <= child.EndTime);\n\n      if (shouldStart) {\n        childAnimation.Start(-child.Direction, true);\n        childAnimation.Update(child.EndTime - time);\n        continue;\n      }\n\n      childAnimation.Update(delta);\n    }\n  }\n}\n\n}  // namespace Impacto"
  },
  {
    "path": "src/sequencedanimation.h",
    "content": "#include \"animation.h\"\n#include <vector>\n\nnamespace Impacto {\n\nstruct ChildAnimation {\n  std::reference_wrapper<Animation> ChildAnimation;\n  float StartTime;\n  float EndTime;\n  AnimationDirection Direction;\n};\n\nclass SequencedAnimation : public Animation {\n public:\n  void AddAnimation(Animation& animation, float startTime, float duration,\n                    AnimationDirection direction = AnimationDirection::In);\n\n  void AddAnimation(Animation& animation, float startTime,\n                    AnimationDirection direction = AnimationDirection::In) {\n    AddAnimation(animation, startTime, animation.GetDuration(direction),\n                 direction);\n  }\n\n  void AddAnimation(Animation& animation,\n                    AnimationDirection direction = AnimationDirection::In) {\n    AddAnimation(animation, DurationIn, direction);\n  }\n\n protected:\n  void UpdateImpl(float dt) override;\n  void StartInImpl(bool reset) override;\n  void StartOutImpl(bool reset) override;\n  void ResetImpl(std::optional<AnimationDirection> direction) override;\n  void FinishImpl() override;\n\n private:\n  std::vector<ChildAnimation> Children;\n};\n\n}  // namespace Impacto"
  },
  {
    "path": "src/shaders/dx9/CCMessageBoxSprite_frag.hlsl",
    "content": "float4 Alpha : register(c0);\n\nstruct PS_INPUT {\n  float4 position : POSITION;\n  float2 uv : TEXCOORD0;\n  float4 tint : COLOR;\n  float2 maskUV : TEXCOORD1;\n};\n\nsampler ColorMap : register(s0);\nsampler Mask : register(s1);\n\nfloat4 main(PS_INPUT input) : COLOR {\n  float4 color = tex2D(ColorMap, input.uv);\n  color *= input.tint;\n  float4 alp = tex2D(Mask, input.maskUV);\n  float ga = alp.r;\n  float ea = 1.0 - alp.g;\n  float fa = 1.0 - alp.b;\n  fa *= Alpha.r;\n  fa -= Alpha.g;\n  fa = clamp(fa, 0.0, 1.0);\n  color.a *= fa;\n\n  ea += Alpha.b;\n  if (ea > 1.0) ea = 2.0 - ea;\n  ea *= 2.0;\n  if (ea > 1.0) ea = 2.0 - ea;\n\n  ga *= ea;\n  ga = clamp(ga, 0.0, 1.0);\n  color.a -= ga;\n  color.a = clamp(color.a, 0.0, 1.0);\n\n  return color;\n}\n"
  },
  {
    "path": "src/shaders/dx9/CCMessageBoxSprite_vert.hlsl",
    "content": "struct VS_INPUT {\n  float2 Position : POSITION;\n  float2 UV : TEXCOORD0;\n  float4 Tint : COLOR;\n  float2 MaskUV : TEXCOORD1;\n};\n\nstruct VS_OUTPUT {\n  float4 position : POSITION;\n  float2 uv : TEXCOORD0;\n  float4 tint : COLOR;\n  float2 maskUV : TEXCOORD1;\n};\n\nVS_OUTPUT main(VS_INPUT input) {\n  VS_OUTPUT output;\n\n  output.position = float4(input.Position, 0.0, 1.0);\n  output.uv = input.UV;\n  output.tint = input.Tint;\n  output.maskUV = input.MaskUV;\n\n  return output;\n}\n"
  },
  {
    "path": "src/shaders/dx9/CHLCCMenuBackground_frag.hlsl",
    "content": "float4 Alpha : register(c0);\n\nstruct PS_INPUT {\n  float4 position : POSITION;\n  float2 uv : TEXCOORD0;\n  float4 tint : COLOR;\n  float2 maskUV : TEXCOORD1;\n};\n\nsampler ColorMap : register(s0);\nsampler Mask : register(s1);\n\nfloat remapMiddle(float value, float newMiddle) {\n  if (value < 0.5) {\n      return lerp(0.0, newMiddle, 2.0 * value);\n  } else {\n      return lerp(newMiddle, 1.0, 2.0 * value - 1.0);\n  }\n}\n\nfloat4 main(PS_INPUT input) : COLOR {\n  float4 color = tex2D(ColorMap, input.uv);\n  float4 mask = tex2D(Mask, input.maskUV);\n  float4 outColor = float4(\n      remapMiddle(color.r, mask.r),\n      remapMiddle(color.g, mask.g),\n      remapMiddle(color.b, mask.b),\n      mask.a * Alpha.r\n  );\n\n  return outColor;\n}\n"
  },
  {
    "path": "src/shaders/dx9/CHLCCMenuBackground_vert.hlsl",
    "content": "struct VS_INPUT {\n  float2 Position : POSITION;\n  float2 UV : TEXCOORD0;\n  float4 Tint : COLOR;\n  float2 MaskUV : TEXCOORD1;\n};\n\nstruct VS_OUTPUT {\n  float4 position : POSITION;\n  float2 uv : TEXCOORD0;\n  float4 tint : COLOR;\n  float2 maskUV : TEXCOORD1;\n};\n\nVS_OUTPUT main(VS_INPUT input) {\n  VS_OUTPUT output;\n\n  output.position = float4(input.Position, 0.0, 1.0);\n  output.uv = input.UV;\n  output.tint = input.Tint;\n  output.maskUV = input.MaskUV;\n\n  return output;\n}\n"
  },
  {
    "path": "src/shaders/dx9/MaskedSpriteNoAlpha_frag.hlsl",
    "content": "float2 Alpha : register(c0);\nbool IsInverted : register(b0);\n\nstruct PS_INPUT {\n  float4 position : POSITION;\n  float2 uv : TEXCOORD0;\n  float4 tint : COLOR;\n  float2 maskUV : TEXCOORD1;\n};\n\nsampler ColorMap : register(s0);\nsampler Mask : register(s1);\n\nfloat rgbToLightness(float3 rgb) {\n  float maxVal = max(max(rgb.r, rgb.g), rgb.b);\n  float minVal = min(min(rgb.r, rgb.g), rgb.b);\n\n  return (maxVal + minVal) / 2.0;\n}\n\nfloat4 main(PS_INPUT input) : COLOR {\n  float4 color = tex2D(ColorMap, input.uv);\n  float4 alp = tex2D(Mask, input.maskUV);\n  float maskAlpha = rgbToLightness(alp.rgb);\n  if (IsInverted) maskAlpha = 1.0f - maskAlpha;\n  maskAlpha *= Alpha.x;\n  maskAlpha -= Alpha.y;\n  maskAlpha = clamp(maskAlpha, 0.0, 1.0);\n  color *= input.tint;\n  color.a *= maskAlpha;\n  return color;\n}\n"
  },
  {
    "path": "src/shaders/dx9/MaskedSpriteNoAlpha_vert.hlsl",
    "content": "struct VS_INPUT {\n  float2 Position : POSITION;\n  float2 UV : TEXCOORD0;\n  float4 Tint : COLOR;\n  float2 MaskUV : TEXCOORD1;\n};\n\nstruct VS_OUTPUT {\n  float4 position : POSITION;\n  float2 uv : TEXCOORD0;\n  float4 tint : COLOR;\n  float2 maskUV : TEXCOORD1;\n};\n\nVS_OUTPUT main(VS_INPUT input) {\n  VS_OUTPUT output;\n\n  output.position = float4(input.Position, 0.0, 1.0);\n  output.uv = input.UV;\n  output.tint = input.Tint;\n  output.maskUV = input.MaskUV;\n\n  return output;\n}\n"
  },
  {
    "path": "src/shaders/dx9/MaskedSprite_frag.hlsl",
    "content": "float2 Alpha : register(c0);\nbool IsInverted : register(b0);\nbool IsSameTexture : register(b1);\n\nstruct PS_INPUT {\n  float4 position : POSITION;\n  float2 uv : TEXCOORD0;\n  float4 tint : COLOR;\n  float2 maskUV : TEXCOORD1;\n};\n\nsampler ColorMap : register(s0);\nsampler Mask : register(s1);\n\nfloat4 main(PS_INPUT input) : COLOR {\n  float4 color = tex2D(ColorMap, input.uv);\n  float4 alp = tex2D(Mask, input.maskUV);\n  if (IsInverted) alp.a = 1.0f - alp.a;\n  alp.a *= Alpha.x;\n  alp.a -= Alpha.y;\n  alp.a = clamp(alp.a, 0.0, 1.0);\n  if (IsSameTexture) {\n    color.a = alp.a;\n    color.a *= input.tint.a;\n  } else {\n    color *= input.tint;\n    color.a *= alp.a;\n  }\n\n  return color;\n}\n"
  },
  {
    "path": "src/shaders/dx9/MaskedSprite_vert.hlsl",
    "content": "struct VS_INPUT {\n  float2 Position : POSITION;\n  float2 UV : TEXCOORD0;\n  float4 Tint : COLOR;\n  float2 MaskUV : TEXCOORD1;\n};\n\nstruct VS_OUTPUT {\n  float4 position : POSITION;\n  float2 uv : TEXCOORD0;\n  float4 tint : COLOR;\n  float2 maskUV : TEXCOORD1;\n};\n\nVS_OUTPUT main(VS_INPUT input) {\n  VS_OUTPUT output;\n\n  output.position = float4(input.Position, 0.0, 1.0);\n  output.uv = input.UV;\n  output.tint = input.Tint;\n  output.maskUV = input.MaskUV;\n\n  return output;\n}\n"
  },
  {
    "path": "src/shaders/dx9/NV12Frame_frag.hlsl",
    "content": "bool IsAlpha : register(b0);\n\nstruct PS_INPUT {\n  float4 position : POSITION;\n  float2 uv : TEXCOORD0;\n  float4 tint : COLOR;\n  float2 maskUV : TEXCOORD1;\n};\n\nsampler Luma : register(s0);\nsampler CbCr : register(s1);\n\nstatic float4x4 yuv_to_rgb_rec601 = {\n    1.16438, 0.00000, 1.59603, -0.87079, 1.16438, -0.39176, -0.81297, 0.52959,\n    1.16438, 2.01723, 0.00000, -1.08139, 0,       0,        0,        1};\n\nfloat4 getRGBA(float2 texUv) {\n  float4 yuvcolor = {1.0, 1.0, 1.0, 1.0};\n  yuvcolor.r = tex2D(Luma, texUv).a;\n  yuvcolor.gb = tex2D(CbCr, texUv).ra;\n\n  return mul(yuv_to_rgb_rec601, yuvcolor);\n}\n\nfloat4 main(PS_INPUT input) : COLOR {\n  float4 color = {1.0, 1.0, 1.0, 1.0};\n\n  if (IsAlpha) {\n    color = getRGBA(float2(input.uv.x, input.uv.y / 2.0));\n    color.a = getRGBA(float2(input.uv.x, input.uv.y / 2.0 + 0.5)).a;\n  } else {\n    color = getRGBA(input.uv);\n  }\n  color *= input.tint;\n\n  return color;\n}\n"
  },
  {
    "path": "src/shaders/dx9/NV12Frame_vert.hlsl",
    "content": "struct VS_INPUT {\n  float2 Position : POSITION;\n  float2 UV : TEXCOORD0;\n  float4 Tint : COLOR;\n  float2 MaskUV : TEXCOORD1;\n};\n\nstruct VS_OUTPUT {\n  float4 position : POSITION;\n  float2 uv : TEXCOORD0;\n  float4 tint : COLOR;\n  float2 maskUV : TEXCOORD1;\n};\n\nVS_OUTPUT main(VS_INPUT input) {\n  VS_OUTPUT output;\n\n  output.position = float4(input.Position, 0.0, 1.0);\n  output.uv = input.UV;\n  output.tint = input.Tint;\n  output.maskUV = input.MaskUV;\n\n  return output;\n}\n"
  },
  {
    "path": "src/shaders/dx9/Renderable3D_Background_frag.hlsl",
    "content": "struct PS_INPUT {\n  float2 uv : TEXCOORD0;\n};\n\nsampler ColorMap : register(s0);\n\nfloat4 main(PS_INPUT input) : COLOR {\n  return tex2D(ColorMap, input.uv);\n}\n"
  },
  {
    "path": "src/shaders/dx9/Renderable3D_Background_vert.hlsl",
    "content": "float4x4 MVP : register(c0);\n\nstruct VS_INPUT {\n  float3 Position : POSITION;\n  float2 UV : TEXCOORD0;\n};\n\nstruct VS_OUTPUT {\n  float4 position : POSITION;\n  float2 uv : TEXCOORD0;\n};\n\nVS_OUTPUT main(VS_INPUT input) {\n  VS_OUTPUT output;\n\n  output.position = mul(MVP, float4(input.Position, 1.0));\n  output.uv = input.UV;\n\n  return output;\n}\n"
  },
  {
    "path": "src/shaders/dx9/Renderable3D_Character_frag.hlsl",
    "content": "// Character3DScene\nfloat4x4 ViewProjection : register(c0);\nfloat4 Tint : register(c4);\nfloat3 WorldLightPosition : register(c5);\nfloat3 WorldEyePosition : register(c6);\nbool DarkMode : register(b0);\n\n// Character3DModel\nfloat4x4 Model : register(c7);\n\n// Character3DMesh\nbool HasShadowColorMap : register(b1);\nfloat ModelOpacity : register(c11);\nfloat4x4 Bones[32] : register(c12);\n\nstatic const float FALLOFF_POWER = 0.8;\n\nstatic const float4 g_ToonFalloffGradVals =\n    float4(0.546875, 0.421875, 0.468750, 0.281250);\nstatic const float4 g_ToonFalloffGradScale =\n    float4(10.66667, 10.66667, 32.0, 32.0);\nstatic const float4 g_ToonFalloffGradDarkVals =\n    float4(0.59375, 0.484375, 0.609375, 0.453125);\nstatic const float4 g_ToonFalloffGradDarkMax = float4(0.79, 0.79, 0.61, 0.61);\n\nstruct PS_INPUT {\n  float4 position : POSITION;\n  float2 uv : TEXCOORD0;\n  float3 worldFragPosition : TEXCOORD1;\n  float3 worldNormal : TEXCOORD2;\n};\n\nsampler ColorMap : register(s0);\nsampler GradientMaskMap : register(s1);\nsampler SpecularColorMap : register(s2);\nsampler ShadowColorMap : register(s3);\n\nfloat4 main(PS_INPUT input) : COLOR {\n  float4 texColor = tex2D(ColorMap, input.uv);\n  float maskParam = tex2D(GradientMaskMap, input.uv).r;\n  float3 specColor = tex2D(SpecularColorMap, input.uv).rgb;\n\n  float3 normal = normalize(input.worldNormal);\n  float3 dirToLight = normalize(WorldLightPosition - input.worldFragPosition);\n  float3 dirToEye = normalize(WorldEyePosition - input.worldFragPosition);\n\n  float NdotL = dot(normal, dirToLight);\n  float scaledDiff = 0.5 * NdotL + 0.5;\n\n  // Note: this is a uniform in R;NE ???\n  float3 H = normalize(dirToLight + dirToEye);\n\n  float4 toonFalloffGradInput;\n  toonFalloffGradInput.xy = (scaledDiff).xx;\n\n  float normDotEye = 1.0 - dot(normal, dirToEye);\n  toonFalloffGradInput.zw = (scaledDiff * normDotEye).xx;\n\n  // Original just uses step here but that looks bad\n  float4 toonFalloffGradParamDark =\n      smoothstep(g_ToonFalloffGradDarkVals * 0.95,\n                 g_ToonFalloffGradDarkVals * 1.05, toonFalloffGradInput) *\n      g_ToonFalloffGradDarkMax;\n\n  float4 toonFalloffGradParamLight = saturate(\n      (toonFalloffGradInput - g_ToonFalloffGradVals) * g_ToonFalloffGradScale);\n\n  float4 toonFalloffGradParam;\n  if (DarkMode) {\n    toonFalloffGradParam = toonFalloffGradParamDark;\n  } else {\n    toonFalloffGradParam = toonFalloffGradParamLight;\n  }\n\n  float2 toonFalloffInterpParam =\n      lerp(toonFalloffGradParam.xz, toonFalloffGradParam.yw, maskParam);\n\n  float3 shadowColor;\n#if defined(IsDaSH)\n  if (HasShadowColorMap) {\n    shadowColor = tex2D(ShadowColorMap, input.uv).rgb;\n  } else {\n#endif\n    shadowColor = max(texColor.rgb, (0.5).xxx) * texColor.rgb;\n#if IsDaSH\n  }\n#endif\n\n  float3 toonColor = lerp(shadowColor, texColor.rgb, toonFalloffInterpParam.x);\n\n  // Tint\n\n  float3 baseTintColor = toonColor * Tint.rgb;\n  toonColor = lerp(toonColor, baseTintColor, Tint.a);\n\n  // Rimlight\n\n  float3 falloffColor = FALLOFF_POWER * toonFalloffInterpParam.y * texColor.rgb;\n  toonColor += falloffColor * ((1.0).xxx - toonColor);\n\n  // Specular\n  // R;NE does not use max() here - maybe manual control of H ensures it doesn't\n  // go negative?\n  float specIntensity = pow(max(dot(normal, H), 0.0), 20.0);\n  toonColor += specIntensity * specColor;\n\n  return float4(toonColor, texColor.a * ModelOpacity);\n}\n"
  },
  {
    "path": "src/shaders/dx9/Renderable3D_Character_vert.hlsl",
    "content": "// Character3DScene\nfloat4x4 ViewProjection : register(c0);\nfloat4 Tint : register(c4);\nfloat3 WorldLightPosition : register(c5);\nfloat3 WorldEyePosition : register(c6);\nbool DarkMode : register(b0);\n\n// Character3DModel\nfloat4x4 Model : register(c7);\n\n// Character3DMesh\nbool HasShadowColorMap : register(b1);\nfloat ModelOpacity : register(c11);\nfloat4x4 Bones[32] : register(c12);\n\nstruct VS_INPUT {\n  float3 Position : POSITION;\n  float3 Normal : NORMAL;\n  float2 UV : TEXCOORD0;\n  uint4 BoneIndices : BLENDINDICES;\n  float4 BoneWeights : BLENDWEIGHT;\n};\n\nstruct VS_OUTPUT {\n  float4 position : POSITION;\n  float2 uv : TEXCOORD0;\n  float3 worldFragPosition : TEXCOORD1;\n  float3 worldNormal : TEXCOORD2;\n};\n\nVS_OUTPUT main(VS_INPUT input) {\n  VS_OUTPUT output;\n\n  // Accumulated skinning, thanks\n  // https://developer.nvidia.com/gpugems/GPUGems/gpugems_ch04.html\n  float4x4 skeletalTransform =\n      mul(Bones[input.BoneIndices.x], input.BoneWeights.x) +\n      mul(Bones[input.BoneIndices.y], input.BoneWeights.y) +\n      mul(Bones[input.BoneIndices.z], input.BoneWeights.z) +\n      mul(Bones[input.BoneIndices.w], input.BoneWeights.w);\n\n  float4x4 transform = mul(Model, skeletalTransform);\n  float3x3 normalMatrix = (float3x3)transform;\n\n  float4 worldPosition = mul(transform, float4(input.Position, 1.0));\n  output.worldFragPosition = worldPosition.xyz;\n\n  output.position = mul(ViewProjection, worldPosition);\n  output.worldNormal = mul(normalMatrix, input.Normal);\n  output.uv = input.UV;\n\n  return output;\n}\n"
  },
  {
    "path": "src/shaders/dx9/Renderable3D_Eye_frag.hlsl",
    "content": "// Character3DScene\nfloat4x4 ViewProjection : register(c0);\nfloat4 Tint : register(c4);\nfloat3 WorldLightPosition : register(c5);\nfloat3 WorldEyePosition : register(c6);\nbool DarkMode : register(b0);\n\n// Character3DModel\nfloat4x4 Model : register(c7);\n\n// Character3DMesh\nbool HasShadowColorMap : register(b1);\nfloat ModelOpacity : register(c11);\nfloat4x4 Bones[32] : register(c12);\n\nstruct PS_INPUT {\n  float4 position : POSITION;\n  float2 uv : TEXCOORD0;\n};\n\nstatic const float FALLOFF_POWER = 0.8;\nstatic const float HIGHLIGHT_TINT_INTENSITY = 0.6;\n\nsampler IrisColorMap : register(s0);\nsampler WhiteColorMap : register(s1);\nsampler HighlightColorMap : register(s2);\nsampler IrisSpecularColorMap : register(s3);\n\nfloat4 main(PS_INPUT input) : COLOR {\n  float4 eyeWhiteColor = tex2D(WhiteColorMap, input.uv);\n  float4 irisColor = tex2D(IrisColorMap, input.uv);\n  float4 highlightColor = tex2D(HighlightColorMap, input.uv);\n\n  float3 eyeColor = lerp(eyeWhiteColor.rgb, irisColor.rgb, irisColor.a);\n\n#if !defined(IsDaSH)\n  // \"Specular\"\n  float4 irisSpecularColor = tex2D(IrisSpecularColorMap, input.uv);\n  eyeColor += irisSpecularColor.rgb * 0.1;\n#endif\n\n  // Tint\n  float3 baseTintColor = eyeColor * Tint.rgb;\n  eyeColor = lerp(eyeColor, baseTintColor, Tint.a);\n\n  // Highlight\n  float3 highlightTintColor = highlightColor.rgb * Tint.rgb;\n  highlightColor.rgb = lerp(highlightColor.rgb, highlightTintColor,\n                            HIGHLIGHT_TINT_INTENSITY * Tint.a);\n  eyeColor = lerp(eyeColor, highlightColor.rgb, highlightColor.a);\n\n  return float4(eyeColor, ModelOpacity);\n}\n"
  },
  {
    "path": "src/shaders/dx9/Renderable3D_Eye_vert.hlsl",
    "content": "// Character3DScene\nfloat4x4 ViewProjection : register(c0);\nfloat4 Tint : register(c4);\nfloat3 WorldLightPosition : register(c5);\nfloat3 WorldEyePosition : register(c6);\nbool DarkMode : register(b0);\n\n// Character3DModel\nfloat4x4 Model : register(c7);\n\n// Character3DMesh\nbool HasShadowColorMap : register(b1);\nfloat ModelOpacity : register(c11);\nfloat4x4 Bones[32] : register(c12);\n\nstruct VS_INPUT {\n  float3 Position : POSITION;\n  float3 Normal : NORMAL;\n  float2 UV : TEXCOORD0;\n  uint4 BoneIndices : BLENDINDICES;\n  float4 BoneWeights : BLENDWEIGHT;\n};\n\nstruct VS_OUTPUT {\n  float4 position : POSITION;\n  float2 uv : TEXCOORD0;\n};\n\nVS_OUTPUT main(VS_INPUT input) {\n  VS_OUTPUT output;\n\n  // Accumulated skinning, thanks\n  // https://developer.nvidia.com/gpugems/GPUGems/gpugems_ch04.html\n  float4x4 skeletalTransform =\n      mul(Bones[input.BoneIndices.x], input.BoneWeights.x) +\n      mul(Bones[input.BoneIndices.y], input.BoneWeights.y) +\n      mul(Bones[input.BoneIndices.z], input.BoneWeights.z) +\n      mul(Bones[input.BoneIndices.w], input.BoneWeights.w);\n\n  float4x4 transform = mul(Model, skeletalTransform);\n\n  float4 worldPosition = mul(transform, float4(input.Position, 1.0));\n\n  output.position = mul(ViewProjection, worldPosition);\n  output.uv = input.UV;\n\n  return output;\n}\n"
  },
  {
    "path": "src/shaders/dx9/Renderable3D_Outline_frag.hlsl",
    "content": "// Character3DScene\nfloat4x4 ViewProjection : register(c0);\nfloat4 Tint : register(c4);\nfloat3 WorldLightPosition : register(c5);\nfloat3 WorldEyePosition : register(c6);\nbool DarkMode : register(b0);\n\n// Character3DModel\nfloat4x4 Model : register(c7);\n\n// Character3DMesh\nbool HasShadowColorMap : register(b1);\nfloat ModelOpacity : register(c11);\nfloat4x4 Bones[32] : register(c12);\n\nstruct PS_INPUT {\n  float4 position : POSITION;\n  float2 uv : TEXCOORD0;\n  float2 uvNoise : TEXCOORD1;\n};\n\nsampler ColorMap : register(s0);\nsampler NoiseMap : register(s1);\n\nfloat4 main(PS_INPUT input) : COLOR {\n  float4 color;\n  float4 texColor = tex2D(ColorMap, input.uv);\n  if (texColor.a <= 0.1) discard;\n\n#if defined(IsDaSH)\n  float3 factor1 = (0.70588).xxx;\n  float3 factor2 = (8.0).xxx;\n\n  float3 c1 =\n      factor1 * texColor.rgb + clamp(texColor.rgb * factor2, 0.0, 1.0) * 0.25;\n  // 0.4, 0.2, 0.2 is from mesh info unk9\n  float3 c2 = clamp(c1 + vec3(0.4, 0.2, 0.2) + vec3(-1.0), 0.0, 1.0);\n  float3 c3 = -c1 + c2;\n\n  float noiseFactor = tex2D(NoiseMap, input.uvNoise).r;\n\n  color = float4(c3 * noiseFactor + c1, 1.0);\n#else\n  static const float4 outlineColor =\n      float4(0.46484375, 0.27734375, 0.22265625, 1.0);\n  color = outlineColor * texColor;\n#endif\n\n  color.a *= ModelOpacity;\n\n  return color;\n}\n"
  },
  {
    "path": "src/shaders/dx9/Renderable3D_Outline_vert.hlsl",
    "content": "// Character3DScene\nfloat4x4 ViewProjection : register(c0);\nfloat4 Tint : register(c4);\nfloat3 WorldLightPosition : register(c5);\nfloat3 WorldEyePosition : register(c6);\nbool DarkMode : register(b0);\n\n// Character3DModel\nfloat4x4 Model : register(c7);\n\n// Character3DMesh\nbool HasShadowColorMap : register(b1);\nfloat ModelOpacity : register(c11);\nfloat4x4 Bones[32] : register(c12);\n\nstruct VS_INPUT {\n  float3 Position : POSITION;\n  float3 Normal : NORMAL;\n  float2 UV : TEXCOORD0;\n  uint4 BoneIndices : BLENDINDICES;\n  float4 BoneWeights : BLENDWEIGHT;\n};\n\nstruct VS_OUTPUT {\n  float4 position : POSITION;\n  float2 uv : TEXCOORD0;\n  float3 uvNoise : TEXCOORD1;\n};\n\n// TODO there's a uniform for this somewhere...\n#if defined(IsDaSH)\nstatic const float OutlineThickness = 0.0035;\n#else\nstatic const float OutlineThickness = 0.035;\n#endif\n\nVS_OUTPUT main(VS_INPUT input) {\n  VS_OUTPUT output;\n\n  // Accumulated skinning, thanks\n  // https://developer.nvidia.com/gpugems/GPUGems/gpugems_ch04.html\n  float4x4 skeletalTransform =\n      mul(Bones[input.BoneIndices.x], input.BoneWeights.x) +\n      mul(Bones[input.BoneIndices.y], input.BoneWeights.y) +\n      mul(Bones[input.BoneIndices.z], input.BoneWeights.z) +\n      mul(Bones[input.BoneIndices.w], input.BoneWeights.w);\n\n  float4x4 transform = mul(Model, skeletalTransform);\n  float3x3 normalMatrix = (float3x3)transform;\n\n  float3 worldNormal = mul(normalMatrix, input.Normal);\n\n  float4 viewNormal = normalize(mul(ViewProjection, float4(worldNormal, 0.0)));\n\n  float4 worldPosition = mul(transform, float4(input.Position, 1.0));\n\n  output.position = mul(ViewProjection, worldPosition);\n  output.position += viewNormal * OutlineThickness;\n\n  output.uv = input.UV;\n\n#if defined(IsDaSH)\n  output.uvNoise = ((output.position.xy / output.position.w) * 0.2).xyy;\n#else\n  output.uvNoise = (0.0).xxx;\n#endif\n\n  return output;\n}\n"
  },
  {
    "path": "src/shaders/dx9/Sprite_frag.hlsl",
    "content": "struct PS_INPUT {\n  float4 position : POSITION;\n  float2 uv : TEXCOORD0;\n  float4 tint : COLOR;\n  float2 maskUV : TEXCOORD1;\n};\n\nsampler ColorMap : register(s0);\n\nfloat4 main(PS_INPUT input) : COLOR {\n  return input.tint * tex2D(ColorMap, input.uv);\n}\n"
  },
  {
    "path": "src/shaders/dx9/Sprite_inverted_frag.hlsl",
    "content": "struct PS_INPUT {\n  float4 position : POSITION;\n  float2 uv : TEXCOORD0;\n  float4 tint : COLOR;\n  float2 maskUV : TEXCOORD1;\n};\n\nsampler ColorMap : register(s0);\n\nfloat4 main(PS_INPUT input) : COLOR {\n  float4 color = tex2D(ColorMap, input.uv);\n  color.r = 1.0 - color.r;\n  color.g = 1.0 - color.g;\n  color.b = 1.0 - color.b;\n  return input.tint * color;\n}\n"
  },
  {
    "path": "src/shaders/dx9/Sprite_inverted_vert.hlsl",
    "content": "struct VS_INPUT {\n  float2 Position : POSITION;\n  float2 UV : TEXCOORD0;\n  float4 Tint : COLOR;\n  float2 MaskUV : TEXCOORD1;\n};\n\nstruct VS_OUTPUT {\n  float4 position : POSITION;\n  float2 uv : TEXCOORD0;\n  float4 tint : COLOR;\n  float2 maskUV : TEXCOORD1;\n};\n\nVS_OUTPUT main(VS_INPUT input) {\n  VS_OUTPUT output;\n\n  output.position = float4(input.Position, 0.0, 1.0);\n  output.uv = input.UV;\n  output.tint = input.Tint;\n  output.maskUV = input.MaskUV;\n\n  return output;\n}\n"
  },
  {
    "path": "src/shaders/dx9/Sprite_vert.hlsl",
    "content": "struct VS_INPUT {\n  float2 Position : POSITION;\n  float2 UV : TEXCOORD0;\n  float4 Tint : COLOR;\n  float2 MaskUV : TEXCOORD1;\n};\n\nstruct VS_OUTPUT {\n  float4 position : POSITION;\n  float2 uv : TEXCOORD0;\n  float4 tint : COLOR;\n  float2 maskUV : TEXCOORD1;\n};\n\nVS_OUTPUT main(VS_INPUT input) {\n  VS_OUTPUT output;\n\n  output.position = float4(input.Position, 0.0, 1.0);\n  output.uv = input.UV;\n  output.tint = input.Tint;\n  output.maskUV = input.MaskUV;\n\n  return output;\n}\n"
  },
  {
    "path": "src/shaders/dx9/YUVFrame_frag.hlsl",
    "content": "bool IsAlpha : register(b0);\n\nstruct PS_INPUT {\n  float4 position : POSITION;\n  float2 uv : TEXCOORD0;\n  float4 tint : COLOR;\n  float2 maskUV : TEXCOORD1;\n};\n\nsampler Luma : register(s0);\nsampler Cb : register(s1);\nsampler Cr : register(s2);\n\nstatic float4x4 yuv_to_rgb_rec601 = {\n    1.16438, 0.00000, 1.59603, -0.87079, 1.16438, -0.39176, -0.81297, 0.52959,\n    1.16438, 2.01723, 0.00000, -1.08139, 0,       0,        0,        1};\n\nfloat4 getRGBA(float2 texUv) {\n  float4 yuvcolor = {1.0, 1.0, 1.0, 1.0};\n  yuvcolor.r = tex2D(Luma, texUv).a;\n  yuvcolor.g = tex2D(Cb, texUv).a;\n  yuvcolor.b = tex2D(Cr, texUv).a;\n\n  return mul(yuv_to_rgb_rec601, yuvcolor);\n}\n\nfloat4 main(PS_INPUT input) : COLOR {\n  float4 color = {1.0, 1.0, 1.0, 1.0};\n\n  if (IsAlpha) {\n    color = getRGBA(float2(input.uv.x, input.uv.y / 2.0));\n    color.a = getRGBA(float2(input.uv.x, input.uv.y / 2.0 + 0.5)).a;\n  } else {\n    color = getRGBA(input.uv);\n  }\n  color *= input.tint;\n\n  return color;\n}\n"
  },
  {
    "path": "src/shaders/dx9/YUVFrame_vert.hlsl",
    "content": "struct VS_INPUT {\n  float2 Position : POSITION;\n  float2 UV : TEXCOORD0;\n  float4 Tint : COLOR;\n  float2 MaskUV : TEXCOORD1;\n};\n\nstruct VS_OUTPUT {\n  float4 position : POSITION;\n  float2 uv : TEXCOORD0;\n  float4 tint : COLOR;\n  float2 maskUV : TEXCOORD1;\n};\n\nVS_OUTPUT main(VS_INPUT input) {\n  VS_OUTPUT output;\n\n  output.position = float4(input.Position, 0.0, 1.0);\n  output.uv = input.UV;\n  output.tint = input.Tint;\n  output.maskUV = input.MaskUV;\n\n  return output;\n}\n"
  },
  {
    "path": "src/shaders/opengl/AdditiveMaskedSprite_frag.glsl",
    "content": "in vec2 uv;\nin vec4 tint;\nin vec2 maskUV;\n\nout vec4 color;\n\nuniform sampler2D ColorMap;\nuniform sampler2D Mask;\n\nvoid main() {\n    vec4 spriteColor = texture(ColorMap, uv);\n    vec4 maskColor = texture(Mask, maskUV);\n\n    color.rgb = clamp(spriteColor.rgb + maskColor.rgb, 0.0, 1.0);\n\n    color.rgb = mix(spriteColor.rgb, color.rgb, maskColor.a);\n    color.a = spriteColor.a * tint.a;\n}\n"
  },
  {
    "path": "src/shaders/opengl/AdditiveMaskedSprite_vert.glsl",
    "content": "layout(location = 0) in vec2 Position;\nlayout(location = 1) in vec2 UV;\nlayout(location = 2) in vec4 Tint;\nlayout(location = 3) in vec2 MaskUV;\n\nout vec2 uv;\nout vec4 tint;\nout vec2 maskUV;\n\nuniform mat4 Projection;\nuniform mat4 SpriteTransformation;\nuniform mat4 MaskTransformation;\n\nvoid main() {\n  gl_Position = Projection * SpriteTransformation * vec4(Position, 0.0, 1.0);\n\n  uv = UV;\n  tint = Tint;\n\n  maskUV = vec2(MaskTransformation * vec4(MaskUV, 0.0, 1.0));\n  maskUV.y = 1.0 - maskUV.y;\n}\n"
  },
  {
    "path": "src/shaders/opengl/CCMessageBoxSprite_frag.glsl",
    "content": "in vec2 uv;\nin vec4 tint;\nin vec2 maskUV;\n\nout vec4 color;\n\nuniform sampler2D ColorMap;\nuniform sampler2D Mask;\nuniform vec4 Alpha;\n\nvoid main() { \n\tcolor = texture(ColorMap, uv);\n\tcolor *= tint;\n\tvec4 alp = texture(Mask, maskUV);\n\tfloat ga = alp.r;\n\tfloat ea = 1.0 - alp.g;\n\tfloat fa = 1.0 - alp.b;\n\tfa *= Alpha.r;\n\tfa -= Alpha.g;\n\tfa = clamp(fa, 0.0, 1.0);\n\tcolor.a *= fa;\n\n\tea += Alpha.b;\n\tif (ea > 1.0) ea = 2.0 - ea;\n\tea *= 2.0;\n\tif (ea > 1.0) ea = 2.0 - ea;\n\n\tga *= ea;\n\tga = clamp(ga, 0.0, 1.0);\n\tcolor.a -= ga;\n\tcolor.a = clamp(color.a, 0.0, 1.0);\n}"
  },
  {
    "path": "src/shaders/opengl/CCMessageBoxSprite_vert.glsl",
    "content": "layout(location = 0) in vec2 Position;\nlayout(location = 1) in vec2 UV;\nlayout(location = 2) in vec4 Tint;\nlayout(location = 3) in vec2 MaskUV;\n\nout vec2 uv;\nout vec4 tint;\nout vec2 maskUV;\n\nuniform mat4 Projection;\n\nvoid main() {\n  gl_Position = Projection * vec4(Position, 0.0, 1.0);\n\n  uv = UV;\n  tint = Tint;\n  maskUV = MaskUV;\n}"
  },
  {
    "path": "src/shaders/opengl/CHLCCMenuBackground_frag.glsl",
    "content": "uniform float Alpha;\n\nin vec2 uv;\nin vec2 maskUV;\n\nout vec4 outColor;\n\nuniform sampler2D ColorMap;\nuniform sampler2D Mask;\n\nfloat remapMiddle(float value, float newMiddle) {\n    if (value < 0.5) {\n        return mix(0.0, newMiddle, 2.0 * value);\n    } else {\n        return mix(newMiddle, 1.0, 2.0 * value - 1.0);\n    }\n}\n\nvoid main() {\n    vec4 color = texture(ColorMap, uv);\n    vec4 maskColor = texture(Mask, maskUV);\n    outColor = vec4(\n        remapMiddle(color.r, maskColor.r),\n        remapMiddle(color.g, maskColor.g),\n        remapMiddle(color.b, maskColor.b),\n        maskColor.a * Alpha\n    );\n}"
  },
  {
    "path": "src/shaders/opengl/CHLCCMenuBackground_vert.glsl",
    "content": "layout (location = 0) in vec2 Position;\nlayout (location = 1) in vec2 UV;\nlayout (location = 3) in vec2 MaskUV;\n\nout vec2 uv;\nout vec2 maskUV;\n\nuniform mat4 Projection;\n\nvoid main() {\n    gl_Position = Projection * vec4(Position, 0.0, 1.0);\n\n    uv = UV;\n    maskUV = MaskUV;\n}"
  },
  {
    "path": "src/shaders/opengl/ColorBurnMaskedSprite_frag.glsl",
    "content": "in vec2 uv;\nin vec4 tint;\nin vec2 maskUV;\n\nout vec4 color;\n\nuniform sampler2D ColorMap;\nuniform sampler2D Mask;\n\nvoid main() {\n    vec4 spriteColor = texture(ColorMap, uv);\n    vec4 maskColor = texture(Mask, maskUV);\n\n    // color.r = (maskColor.r == 0.0) ? 1.0 :\n    //     clamp(1.0 - (1.0 - spriteColor.r) / maskColor.r, 0.0, 1.0),\n    color.rgb = mix(\n        clamp(vec3(1.0) - (vec3(1.0) - spriteColor.rgb) / maskColor.rgb, 0.0, 1.0),\n        vec3(1.0),\n        vec3(maskColor.r <= 0.0, maskColor.g <= 0.0, maskColor.b <= 0.0)\n    );\n\n    color.rgb = mix(spriteColor.rgb, color.rgb, maskColor.a);\n    color.a = spriteColor.a * tint.a;\n}\n"
  },
  {
    "path": "src/shaders/opengl/ColorBurnMaskedSprite_vert.glsl",
    "content": "layout(location = 0) in vec2 Position;\nlayout(location = 1) in vec2 UV;\nlayout(location = 2) in vec4 Tint;\nlayout(location = 3) in vec2 MaskUV;\n\nout vec2 uv;\nout vec4 tint;\nout vec2 maskUV;\n\nuniform mat4 Projection;\nuniform mat4 SpriteTransformation;\nuniform mat4 MaskTransformation;\n\nvoid main() {\n  gl_Position = Projection * SpriteTransformation * vec4(Position, 0.0, 1.0);\n\n  uv = UV;\n  tint = Tint;\n\n  maskUV = vec2(MaskTransformation * vec4(MaskUV, 0.0, 1.0));\n  maskUV.y = 1.0 - maskUV.y;\n}\n"
  },
  {
    "path": "src/shaders/opengl/ColorDodgeMaskedSprite_frag.glsl",
    "content": "in vec2 uv;\nin vec4 tint;\nin vec2 maskUV;\n\nout vec4 color;\n\nuniform sampler2D ColorMap;\nuniform sampler2D Mask;\n\nvoid main() {\n    vec4 spriteColor = texture(ColorMap, uv);\n    vec4 maskColor = texture(Mask, maskUV);\n\n    color.rgb = mix(\n        clamp(spriteColor.rgb / (vec3(1.0) - maskColor.rgb), 0.0, 1.0),\n        vec3(1.0),\n        vec3(maskColor.r >= 1.0, maskColor.g >= 1.0, maskColor.b >= 1.0)\n    );\n\n    color.rgb = mix(spriteColor.rgb, color.rgb, maskColor.a);\n    color.a = spriteColor.a * tint.a;\n}\n"
  },
  {
    "path": "src/shaders/opengl/ColorDodgeMaskedSprite_vert.glsl",
    "content": "layout(location = 0) in vec2 Position;\nlayout(location = 1) in vec2 UV;\nlayout(location = 2) in vec4 Tint;\nlayout(location = 3) in vec2 MaskUV;\n\nout vec2 uv;\nout vec4 tint;\nout vec2 maskUV;\n\nuniform mat4 Projection;\nuniform mat4 SpriteTransformation;\nuniform mat4 MaskTransformation;\n\nvoid main() {\n  gl_Position = Projection * SpriteTransformation * vec4(Position, 0.0, 1.0);\n\n  uv = UV;\n  tint = Tint;\n\n  maskUV = vec2(MaskTransformation * vec4(MaskUV, 0.0, 1.0));\n  maskUV.y = 1.0 - maskUV.y;\n}\n"
  },
  {
    "path": "src/shaders/opengl/ColorMaskedSprite_frag.glsl",
    "content": "in vec2 uv;\nin vec4 tint;\nin vec2 maskUV;\n\nout vec4 color;\n\nuniform sampler2D ColorMap;\nuniform sampler2D Mask;\n\nvoid main() {\n    vec4 spriteColor = texture(ColorMap, uv);\n    vec4 maskColor = texture(Mask, maskUV);\n\n    color.rgb = spriteColor.rgb * maskColor.rgb;\n\n    color.rgb = mix(spriteColor.rgb, color.rgb, maskColor.a);\n    color.a = spriteColor.a * tint.a;\n}\n"
  },
  {
    "path": "src/shaders/opengl/ColorMaskedSprite_vert.glsl",
    "content": "layout(location = 0) in vec2 Position;\nlayout(location = 1) in vec2 UV;\nlayout(location = 2) in vec4 Tint;\nlayout(location = 3) in vec2 MaskUV;\n\nout vec2 uv;\nout vec4 tint;\nout vec2 maskUV;\n\nuniform mat4 Projection;\nuniform mat4 SpriteTransformation;\nuniform mat4 MaskTransformation;\n\nvoid main() {\n  gl_Position = Projection * SpriteTransformation * vec4(Position, 0.0, 1.0);\n\n  uv = UV;\n  tint = Tint;\n\n  maskUV = vec2(MaskTransformation * vec4(MaskUV, 0.0, 1.0));\n  maskUV.y = 1.0 - maskUV.y;\n}\n"
  },
  {
    "path": "src/shaders/opengl/GaussianBlur_frag.glsl",
    "content": "in vec2 uv;\nin vec4 tint;\n\nout vec4 color;\n\nuniform sampler2D ColorMap;\nuniform vec2 TextureDimensions;\nuniform bool IsHorizontal;\n\nconst int WEIGHTS_COUNT = 4;\n\n// Taken from Vita\nfloat WEIGHTS[WEIGHTS_COUNT] = float[](\n    0.3992 / 2.0,\n    0.2421,\n    0.05402,\n    0.004433\n);\n\n/*\n    To prevent banding on higher resolutions, we sample each pixel over the requested distance,\n    instead of merely at four equidistant points. We then lerp the weights respecting the blur\n    distance the original game expects on a 480x255 texture, but on a texture of TextureDimensions\n    instead.\n*/\nfloat interpolateWeight(float progress) {\n    int lowerWeightIdx = min(int(floor(progress)), WEIGHTS_COUNT - 2);\n    int upperWeightIdx = lowerWeightIdx + 1;\n\n    float weightProgress = clamp(progress - float(lowerWeightIdx), 0.0, 1.0);\n    return mix(WEIGHTS[lowerWeightIdx], WEIGHTS[upperWeightIdx], weightProgress);\n}\n\nvoid main() {\n    // Divide by TextureDimensions to normalize sprite dimensions to [0.0, 1.0]\n    vec2 normalizedOffset = (IsHorizontal ? vec2(1.0, 0.0) : vec2(0.0, 1.0)) / TextureDimensions;\n\n    // Vita's blur effect operates on a 480 x 255 texture\n    float weightDistance = IsHorizontal ? TextureDimensions.x / 480.0 : TextureDimensions.y / 255.0;\n    int maxDistance = int(ceil(weightDistance * float(WEIGHTS_COUNT)));\n\n    color = vec4(0.0);\n    float totalWeight = 0.0;\n    for (int i = 0; i < maxDistance; i++) {\n        float weight = interpolateWeight(float(i) / weightDistance);\n        totalWeight += weight * 2.0f;\n\n        color += weight * texture(ColorMap, uv + normalizedOffset * float(i));\n        color += weight * texture(ColorMap, uv - normalizedOffset * float(i));\n    }\n    color /= totalWeight;\n\n    color.a = 1.0;\n    color *= tint;\n}\n"
  },
  {
    "path": "src/shaders/opengl/GaussianBlur_vert.glsl",
    "content": "layout(location = 0) in vec2 Position;\nlayout(location = 1) in vec2 UV;\nlayout(location = 2) in vec4 Tint;\n\nuniform mat4 Projection;\nuniform mat4 Transformation;\n\nout vec2 uv;\nout vec4 tint;\n\nvoid main() {\n  gl_Position = Projection * Transformation * vec4(Position, 0.0, 1.0);\n\n  uv = UV;\n  tint = Tint;\n}"
  },
  {
    "path": "src/shaders/opengl/HardLightMaskedSprite_frag.glsl",
    "content": "in vec2 uv;\nin vec4 tint;\nin vec2 maskUV;\n\nout vec4 color;\n\nuniform sampler2D ColorMap;\nuniform sampler2D Mask;\n\nvoid main() {\n    vec4 spriteColor = texture(ColorMap, uv);\n    vec4 maskColor = texture(Mask, maskUV);\n\n    // color.r = (maskColor.r < 0.5) ?\n    //     2.0 * spriteColor.r * maskColor.r :\n    //     1.0 - 2.0 * (1.0 - spriteColor.r) * (1.0 - maskColor.r);\n    color.rgb = mix(\n        2.0 * spriteColor.rgb *  maskColor.rgb,\n        vec3(1.0) - 2.0 * (vec3(1.0) - spriteColor.rgb) * (vec3(1.0) - maskColor.rgb),\n        vec3(maskColor.r >= 0.5, maskColor.g >= 0.5, maskColor.b >= 0.5)\n    );\n\n    color.rgb = mix(spriteColor.rgb, color.rgb, maskColor.a);\n    color.a = spriteColor.a * tint.a;\n}\n"
  },
  {
    "path": "src/shaders/opengl/HardLightMaskedSprite_vert.glsl",
    "content": "layout(location = 0) in vec2 Position;\nlayout(location = 1) in vec2 UV;\nlayout(location = 2) in vec4 Tint;\nlayout(location = 3) in vec2 MaskUV;\n\nout vec2 uv;\nout vec4 tint;\nout vec2 maskUV;\n\nuniform mat4 Projection;\nuniform mat4 SpriteTransformation;\nuniform mat4 MaskTransformation;\n\nvoid main() {\n  gl_Position = Projection * SpriteTransformation * vec4(Position, 0.0, 1.0);\n\n  uv = UV;\n  tint = Tint;\n\n  maskUV = vec2(MaskTransformation * vec4(MaskUV, 0.0, 1.0));\n  maskUV.y = 1.0 - maskUV.y;\n}\n"
  },
  {
    "path": "src/shaders/opengl/LinearBurnMaskedSprite_frag.glsl",
    "content": "in vec2 uv;\nin vec4 tint;\nin vec2 maskUV;\n\nout vec4 color;\n\nuniform sampler2D ColorMap;\nuniform sampler2D Mask;\n\nvoid main() {\n    vec4 spriteColor = texture(ColorMap, uv);\n    vec4 maskColor = texture(Mask, maskUV);\n\n    color.rgb = clamp(spriteColor.rgb + maskColor.rgb - vec3(1.0), 0.0, 1.0);\n\n    color.rgb = mix(spriteColor.rgb, color.rgb, maskColor.a);\n    color.a = spriteColor.a * tint.a;\n}\n"
  },
  {
    "path": "src/shaders/opengl/LinearBurnMaskedSprite_vert.glsl",
    "content": "layout(location = 0) in vec2 Position;\nlayout(location = 1) in vec2 UV;\nlayout(location = 2) in vec4 Tint;\nlayout(location = 3) in vec2 MaskUV;\n\nout vec2 uv;\nout vec4 tint;\nout vec2 maskUV;\n\nuniform mat4 Projection;\nuniform mat4 SpriteTransformation;\nuniform mat4 MaskTransformation;\n\nvoid main() {\n  gl_Position = Projection * SpriteTransformation * vec4(Position, 0.0, 1.0);\n\n  uv = UV;\n  tint = Tint;\n\n  maskUV = vec2(MaskTransformation * vec4(MaskUV, 0.0, 1.0));\n  maskUV.y = 1.0 - maskUV.y;\n}\n"
  },
  {
    "path": "src/shaders/opengl/MaskedSpriteBinary_frag.glsl",
    "content": "in vec2 uv;\nin vec4 tint;\nin vec2 maskUV;\n\nout vec4 color;\n\nuniform sampler2D ColorMap;\nuniform sampler2D Mask;\nuniform bool IsInverted;\n\nvoid main() {\n    color = tint;\n\n    vec4 textureColor = texture(ColorMap, uv);\n    color.rgb *= textureColor.rgb * vec3(textureColor.a);\n\n    vec4 maskColor = texture(Mask, maskUV);\n    vec3 invisibleColor = vec3(IsInverted ? 1.0 : 0.0);\n    if (maskColor.rgb == invisibleColor) color.a = 0.0;\n}\n"
  },
  {
    "path": "src/shaders/opengl/MaskedSpriteBinary_vert.glsl",
    "content": "layout(location = 0) in vec2 Position;\nlayout(location = 1) in vec2 UV;\nlayout(location = 2) in vec4 Tint;\nlayout(location = 3) in vec2 MaskUV;\n\nout vec2 uv;\nout vec4 tint;\nout vec2 maskUV;\n\nuniform mat4 Projection;\nuniform mat4 SpriteTransformation;\n\nuniform mat4 MaskTransformation;\nuniform bool FullscreenMask;\n\nvoid main() {\n  gl_Position = Projection * SpriteTransformation * vec4(Position, 0.0, 1.0);\n\n  uv = UV;\n  tint = Tint;\n\n  if (FullscreenMask) {\n    maskUV = (vec2(gl_Position) + vec2(1.0)) / 2.0;\n  } else {\n    maskUV = vec2(MaskTransformation * vec4(MaskUV, 0.0, 1.0));\n  }\n}\n"
  },
  {
    "path": "src/shaders/opengl/MaskedSpriteNoAlpha_frag.glsl",
    "content": "in vec2 uv;\nin vec4 tint;\nin vec2 maskUV;\n\nout vec4 color;\n\nuniform sampler2D ColorMap;\nuniform sampler2D Mask;\nuniform vec2 Alpha;\nuniform bool IsInverted;\n\nfloat rgbToLightness(vec3 rgb) {\n    float maxVal = max(max(rgb.r, rgb.g), rgb.b);\n    float minVal = min(min(rgb.r, rgb.g), rgb.b);\n    \n    return (maxVal + minVal) / 2.0;\n}\n\nvoid main() { \n\tcolor = texture(ColorMap, uv);\n\tvec4 alp = texture(Mask, maskUV);\n\tfloat maskAlpha = rgbToLightness(alp.rgb);\n\tif (IsInverted) maskAlpha = 1.0f - maskAlpha;\n\t\n\tmaskAlpha *= Alpha.x;\n\tmaskAlpha -= Alpha.y;\n\tmaskAlpha = clamp(maskAlpha,0.0,1.0);\n\tcolor *= tint;\n\tcolor.a *= maskAlpha;\n\t\n}"
  },
  {
    "path": "src/shaders/opengl/MaskedSpriteNoAlpha_vert.glsl",
    "content": "layout(location = 0) in vec2 Position;\nlayout(location = 1) in vec2 UV;\nlayout(location = 2) in vec4 Tint;\nlayout(location = 3) in vec2 MaskUV;\n\nout vec2 uv;\nout vec4 tint;\nout vec2 maskUV;\n\nuniform mat4 Projection;\nuniform mat4 SpriteTransformation;\nuniform mat4 MaskTransformation;\n\nvoid main() {\n  gl_Position = Projection * SpriteTransformation * vec4(Position, 0.0, 1.0);\n\n  uv = UV;\n  tint = Tint;\n  maskUV = vec2(MaskTransformation * vec4(MaskUV, 0.0, 1.0));\n}"
  },
  {
    "path": "src/shaders/opengl/MaskedSprite_frag.glsl",
    "content": "in vec2 uv;\nin vec4 tint;\nin vec2 maskUV;\n\nout vec4 color;\n\nuniform sampler2D ColorMap;\nuniform sampler2D Mask;\nuniform vec2 Alpha;\nuniform bool IsInverted;\nuniform bool IsSameTexture;\n\nvoid main() { \n\tcolor = texture(ColorMap, uv);\n\tvec4 alp = texture(Mask, maskUV);\n\tif (IsInverted) alp.a = 1.0f - alp.a;\n\talp.a *= Alpha.x;\n\talp.a -= Alpha.y;\n\talp.a = clamp(alp.a,0.0,1.0);\n\tif (IsSameTexture) {\n\t\tcolor.a = alp.a;\n\t\tcolor.a *= tint.a;\n\t} else {\n\t\tcolor *= tint;\n\t\tcolor.a *= alp.a;\n\t}\n}"
  },
  {
    "path": "src/shaders/opengl/MaskedSprite_vert.glsl",
    "content": "layout(location = 0) in vec2 Position;\nlayout(location = 1) in vec2 UV;\nlayout(location = 2) in vec4 Tint;\nlayout(location = 3) in vec2 MaskUV;\n\nout vec2 uv;\nout vec4 tint;\nout vec2 maskUV;\n\nuniform mat4 Projection;\nuniform mat4 SpriteTransformation;\nuniform mat4 MaskTransformation;\n\nvoid main() {\n  gl_Position = Projection * SpriteTransformation * vec4(Position, 0.0, 1.0);\n\n  uv = UV;\n  tint = Tint;\n  maskUV = vec2(MaskTransformation * vec4(MaskUV, 0.0, 1.0));\n}"
  },
  {
    "path": "src/shaders/opengl/Mosaic_frag.glsl",
    "content": "in vec2 uv;\nin vec4 tint;\n\nout vec4 color;\n\nuniform sampler2D ColorMap;\nuniform vec2 TextureDimensions;\nuniform float TileSize;\n\nvoid main() {\n    color = tint;\n\n    if (TileSize <= 1.0) {\n        color *= texture(ColorMap, uv);\n        return;\n    }\n\n    vec2 tileUvSize = TileSize / TextureDimensions;\n    vec2 tileCoord = floor(uv / tileUvSize);\n    vec2 tileCenterPos = (tileCoord + vec2(0.5)) * tileUvSize;\n\n    color *= texture(ColorMap, tileCenterPos);\n}\n"
  },
  {
    "path": "src/shaders/opengl/Mosaic_vert.glsl",
    "content": "layout(location = 0) in vec2 Position;\nlayout(location = 1) in vec2 UV;\nlayout(location = 2) in vec4 Tint;\n\nuniform mat4 Projection;\nuniform mat4 Transformation;\n\nout vec2 uv;\nout vec4 tint;\n\nvoid main() {\n    gl_Position = Projection * Transformation * vec4(Position, 0.0, 1.0);\n\n    uv = UV;\n    tint = Tint;\n}\n"
  },
  {
    "path": "src/shaders/opengl/NV12Frame_frag.glsl",
    "content": "in vec2 uv;\nin vec4 tint;\n\nout vec4 color;\n\nuniform sampler2D Luma;\nuniform sampler2D CbCr;\nuniform bool IsAlpha;\n\nconst mat4 yuv_to_rgb_rec601 = mat4(\n    1.16438,  0.00000,  1.59603, -0.87079,\n    1.16438, -0.39176, -0.81297,  0.52959,\n    1.16438,  2.01723,  0.00000, -1.08139,\n    0, 0, 0, 1\n);\n\nvec4 getRGBA(vec2 texUv) {\n    vec4 yuvcolor = vec4(1.0);\n\n    vec2 cbcr = texture(CbCr, texUv).rg;\n    yuvcolor.r = texture(Luma, texUv).r;\n    yuvcolor.g = cbcr.r;\n    yuvcolor.b = cbcr.g;\n\n    return yuvcolor * yuv_to_rgb_rec601;\n}\n\nvoid main() {\n    if (IsAlpha) {\n        color = getRGBA(vec2(uv.x, uv.y / 2.0));\n        color.a = getRGBA(vec2(uv.x, uv.y / 2.0 + 0.5)).r;\n    } else {\n        color = getRGBA(uv);\n    }\n\tcolor *= tint;\n}"
  },
  {
    "path": "src/shaders/opengl/NV12Frame_vert.glsl",
    "content": "layout(location = 0) in vec2 Position;\nlayout(location = 1) in vec2 UV;\nlayout(location = 2) in vec4 Tint;\n\nout vec2 uv;\nout vec4 tint;\n\nuniform mat4 Projection;\n\nvoid main() {\n  gl_Position = Projection * vec4(Position, 0.0, 1.0);\n  uv = UV;\n  tint = Tint;\n}"
  },
  {
    "path": "src/shaders/opengl/OverlayMaskedSprite_frag.glsl",
    "content": "in vec2 uv;\nin vec4 tint;\nin vec2 maskUV;\n\nout vec4 color;\n\nuniform sampler2D ColorMap;\nuniform sampler2D Mask;\n\nvoid main() {\n    vec4 spriteColor = texture(ColorMap, uv);\n    vec4 maskColor = texture(Mask, maskUV);\n\n    // if (spriteColor.r < 0.5) {\n    //     color.r = 2.0 * spriteColor.r * maskColor.r;\n    // } else {\n    //     color.r = 1.0 - 2.0 * (1.0 - spriteColor.r) * (1.0 - maskColor.r);\n    // }\n    color.rgb = mix(\n        2.0 * spriteColor.rgb * maskColor.rgb,\n        vec3(1.0) - 2.0 * (vec3(1.0) - spriteColor.rgb) * (vec3(1.0) - maskColor.rgb),\n        vec3(spriteColor.r >= 0.5, spriteColor.g >= 0.5, spriteColor.b >= 0.5)\n    );\n\n    color.rgb = mix(spriteColor.rgb, color.rgb, maskColor.a);\n    color.a = spriteColor.a * tint.a;\n}\n"
  },
  {
    "path": "src/shaders/opengl/OverlayMaskedSprite_vert.glsl",
    "content": "layout(location = 0) in vec2 Position;\nlayout(location = 1) in vec2 UV;\nlayout(location = 2) in vec4 Tint;\nlayout(location = 3) in vec2 MaskUV;\n\nout vec2 uv;\nout vec4 tint;\nout vec2 maskUV;\n\nuniform mat4 Projection;\nuniform mat4 SpriteTransformation;\nuniform mat4 MaskTransformation;\n\nvoid main() {\n  gl_Position = Projection * SpriteTransformation * vec4(Position, 0.0, 1.0);\n\n  uv = UV;\n  tint = Tint;\n\n  maskUV = vec2(MaskTransformation * vec4(MaskUV, 0.0, 1.0));\n  maskUV.y = 1.0 - maskUV.y;\n}\n"
  },
  {
    "path": "src/shaders/opengl/Renderable3D_Background_frag.glsl",
    "content": "in vec2 uv;\n\nout vec4 color;\n\nuniform sampler2D ColorMap;\n\nvoid main() { color = texture(ColorMap, uv); }"
  },
  {
    "path": "src/shaders/opengl/Renderable3D_Background_vert.glsl",
    "content": "layout(location = 0) in vec3 Position;\nlayout(location = 1) in vec2 UV;\n\nout vec2 uv;\n\nuniform mat4 MVP;\n\nvoid main() {\n  gl_Position = MVP * vec4(Position, 1.0);\n  uv = UV;\n}"
  },
  {
    "path": "src/shaders/opengl/Renderable3D_Character_frag.glsl",
    "content": "in vec3 worldFragPosition;\nin vec3 worldNormal;\nin vec2 uv;\n\nout vec4 color;\n\nuniform sampler2D ColorMap;\n#ifdef DASH\nuniform sampler2D ShadowColorMap;\n#endif\nuniform sampler2D GradientMaskMap;\nuniform sampler2D SpecularColorMap;\n\nlayout(std140) uniform Character3DScene {\n  UNIFORM_PRECISION mat4 ViewProjection;\n  UNIFORM_PRECISION vec4 Tint;\n  UNIFORM_PRECISION vec3 WorldLightPosition;\n  UNIFORM_PRECISION vec3 WorldEyePosition;\n  bool DarkMode;\n};\n\nlayout(std140) uniform Character3DModel { UNIFORM_PRECISION mat4 Model; };\n\nlayout(std140) uniform Character3DMesh {\n  UNIFORM_PRECISION mat4 Bones[ModelMaxBonesPerMesh];\n  UNIFORM_PRECISION float ModelOpacity;\n  bool HasShadowColorMap;\n};\n\nconst float FALLOFF_POWER = 0.8;\n\nconst vec4 g_ToonFalloffGradVals = vec4(0.546875, 0.421875, 0.468750, 0.281250);\nconst vec4 g_ToonFalloffGradScale = vec4(10.66667, 10.66667, 32.0, 32.0);\nconst vec4 g_ToonFalloffGradDarkVals =\n    vec4(0.59375, 0.484375, 0.609375, 0.453125);\nconst vec4 g_ToonFalloffGradDarkMax = vec4(0.79, 0.79, 0.61, 0.61);\n\nvec4 saturate(vec4 x) { return clamp(x, 0.0, 1.0); }\n\nvoid main() {\n  vec4 texColor = texture(ColorMap, uv);\n  float maskParam = texture(GradientMaskMap, uv).r;\n  vec3 specColor = texture(SpecularColorMap, uv).rgb;\n\n  vec3 normal = normalize(worldNormal);\n  vec3 dirToLight = normalize(WorldLightPosition - worldFragPosition);\n  vec3 dirToEye = normalize(WorldEyePosition - worldFragPosition);\n\n  float NdotL = dot(normal, dirToLight);\n  float scaledDiff = 0.5 * NdotL + 0.5;\n\n  // Note: this is a uniform in R;NE ???\n  vec3 H = normalize(dirToLight + dirToEye);\n\n  vec4 toonFalloffGradInput;\n  toonFalloffGradInput.xy = vec2(scaledDiff);\n\n  float normDotEye = 1.0 - dot(normal, dirToEye);\n  toonFalloffGradInput.zw = vec2(scaledDiff * normDotEye);\n\n  // Original just uses step here but that looks bad\n  vec4 toonFalloffGradParamDark =\n      smoothstep(g_ToonFalloffGradDarkVals * 0.95,\n                 g_ToonFalloffGradDarkVals * 1.05, toonFalloffGradInput) *\n      g_ToonFalloffGradDarkMax;\n\n  vec4 toonFalloffGradParamLight = saturate(\n      (toonFalloffGradInput - g_ToonFalloffGradVals) * g_ToonFalloffGradScale);\n\n  vec4 toonFalloffGradParam =\n      DarkMode ? toonFalloffGradParamDark : toonFalloffGradParamLight;\n\n  vec2 toonFalloffInterpParam =\n      mix(toonFalloffGradParam.xz, toonFalloffGradParam.yw, maskParam);\n\n  vec3 shadowColor;\n#if DASH\n  if (HasShadowColorMap) {\n    shadowColor = texture(ShadowColorMap, uv).rgb;\n  } else {\n#endif\n    shadowColor = max(texColor.rgb, vec3(0.5)) * texColor.rgb;\n#if DASH\n  }\n#endif\n\n  vec3 toonColor = mix(shadowColor, texColor.rgb, toonFalloffInterpParam.x);\n\n  // Tint\n\n  vec3 baseTintColor = toonColor * Tint.rgb;\n  toonColor = mix(toonColor, baseTintColor, Tint.a);\n\n  // Rimlight\n\n  vec3 falloffColor = FALLOFF_POWER * toonFalloffInterpParam.y * texColor.rgb;\n  toonColor += falloffColor * (vec3(1.0) - toonColor);\n\n  // Specular\n  // R;NE does not use max() here - maybe manual control of H ensures it doesn't\n  // go negative?\n  float specIntensity = pow(max(dot(normal, H), 0.0), 20.0);\n  toonColor += specIntensity * specColor;\n\n  color = vec4(toonColor, texColor.a * ModelOpacity);\n}"
  },
  {
    "path": "src/shaders/opengl/Renderable3D_Character_vert.glsl",
    "content": "layout(location = 0) in vec3 Position;\nlayout(location = 1) in vec3 Normal;\nlayout(location = 2) in vec2 UV;\nlayout(location = 3) in lowp ivec4 BoneIndices;  // indices into Mesh.BoneMap\nlayout(location = 4) in vec4 BoneWeights;\n\nout vec3 worldFragPosition;\nout vec3 worldNormal;\nout vec2 uv;\n\nlayout(std140) uniform Character3DScene {\n  UNIFORM_PRECISION mat4 ViewProjection;\n  UNIFORM_PRECISION vec4 Tint;\n  UNIFORM_PRECISION vec3 WorldLightPosition;\n  UNIFORM_PRECISION vec3 WorldEyePosition;\n  bool DarkMode;\n};\n\nlayout(std140) uniform Character3DModel { UNIFORM_PRECISION mat4 Model; };\n\nlayout(std140) uniform Character3DMesh {\n  UNIFORM_PRECISION mat4 Bones[ModelMaxBonesPerMesh];\n  UNIFORM_PRECISION float ModelOpacity;\n  bool HasShadowColorMap;\n};\n\nvoid main() {\n  // Accumulated skinning, thanks\n  // https://developer.nvidia.com/gpugems/GPUGems/gpugems_ch04.html\n  mat4 skeletalTransform = Bones[BoneIndices.x] * BoneWeights.x +\n                           Bones[BoneIndices.y] * BoneWeights.y +\n                           Bones[BoneIndices.z] * BoneWeights.z +\n                           Bones[BoneIndices.w] * BoneWeights.w;\n\n  mat4 transform = Model * skeletalTransform;\n  mat3 normalMatrix = mat3(transpose(inverse(transform)));\n\n  vec4 worldPosition = transform * vec4(Position, 1.0);\n  worldFragPosition = worldPosition.xyz;\n\n  gl_Position = ViewProjection * worldPosition;\n  worldNormal = normalMatrix * Normal;\n  uv = UV;\n}"
  },
  {
    "path": "src/shaders/opengl/Renderable3D_Eye_frag.glsl",
    "content": "in vec2 uv;\n\nout vec4 color;\n\nuniform sampler2D IrisColorMap;\nuniform sampler2D WhiteColorMap;\n#if DASH == 0\nuniform sampler2D IrisSpecularColorMap;\n#endif\nuniform sampler2D HighlightColorMap;\n\nlayout(std140) uniform Character3DScene {\n  UNIFORM_PRECISION mat4 ViewProjection;\n  UNIFORM_PRECISION vec4 Tint;\n  UNIFORM_PRECISION vec3 WorldLightPosition;\n  UNIFORM_PRECISION vec3 WorldEyePosition;\n  bool DarkMode;\n};\n\nlayout(std140) uniform Character3DModel { UNIFORM_PRECISION mat4 Model; };\n\nlayout(std140) uniform Character3DMesh {\n  UNIFORM_PRECISION mat4 Bones[ModelMaxBonesPerMesh];\n  UNIFORM_PRECISION float ModelOpacity;\n  bool HasShadowColorMap;\n};\n\nconst float FALLOFF_POWER = 0.8;\nconst float HIGHLIGHT_TINT_INTENSITY = 0.6;\n\nvoid main() {\n  vec4 eyeWhiteColor = texture(WhiteColorMap, uv);\n  vec4 irisColor = texture(IrisColorMap, uv);\n#if DASH == 0\n  vec4 irisSpecularColor = texture(IrisSpecularColorMap, uv);\n#endif\n  vec4 highlightColor = texture(HighlightColorMap, uv);\n\n  vec3 eyeColor = mix(eyeWhiteColor.rgb, irisColor.rgb, irisColor.a);\n\n#if DASH == 0\n  // \"Specular\"\n  eyeColor += irisSpecularColor.rgb * 0.1;\n#endif\n\n  // Tint\n  vec3 baseTintColor = eyeColor * Tint.rgb;\n  eyeColor = mix(eyeColor, baseTintColor, Tint.a);\n\n  // Highlight\n  vec3 highlightTintColor = highlightColor.rgb * Tint.rgb;\n  highlightColor.rgb = mix(highlightColor.rgb, highlightTintColor,\n                           HIGHLIGHT_TINT_INTENSITY * Tint.a);\n  eyeColor = mix(eyeColor, highlightColor.rgb, highlightColor.a);\n\n  color = vec4(eyeColor, ModelOpacity);\n}"
  },
  {
    "path": "src/shaders/opengl/Renderable3D_Eye_vert.glsl",
    "content": "layout(location = 0) in vec3 Position;\nlayout(location = 1) in vec3 Normal;\nlayout(location = 2) in vec2 UV;\nlayout(location = 3) in lowp ivec4 BoneIndices;  // indices into Mesh.BoneMap\nlayout(location = 4) in vec4 BoneWeights;\n\nout vec2 uv;\n\nlayout(std140) uniform Character3DScene {\n  UNIFORM_PRECISION mat4 ViewProjection;\n  UNIFORM_PRECISION vec4 Tint;\n  UNIFORM_PRECISION vec3 WorldLightPosition;\n  UNIFORM_PRECISION vec3 WorldEyePosition;\n  bool DarkMode;\n};\n\nlayout(std140) uniform Character3DModel { UNIFORM_PRECISION mat4 Model; };\n\nlayout(std140) uniform Character3DMesh {\n  UNIFORM_PRECISION mat4 Bones[ModelMaxBonesPerMesh];\n  UNIFORM_PRECISION float ModelOpacity;\n  bool HasShadowColorMap;\n};\n\nvoid main() {\n  // Accumulated skinning, thanks\n  // https://developer.nvidia.com/gpugems/GPUGems/gpugems_ch04.html\n  mat4 skeletalTransform = Bones[BoneIndices.x] * BoneWeights.x +\n                           Bones[BoneIndices.y] * BoneWeights.y +\n                           Bones[BoneIndices.z] * BoneWeights.z +\n                           Bones[BoneIndices.w] * BoneWeights.w;\n\n  mat4 transform = Model * skeletalTransform;\n\n  vec4 worldPosition = transform * vec4(Position, 1.0);\n\n  gl_Position = ViewProjection * worldPosition;\n  uv = UV;\n}"
  },
  {
    "path": "src/shaders/opengl/Renderable3D_Outline_frag.glsl",
    "content": "in vec2 uv;\n#if DASH\nin vec2 uvNoise;\n#endif\n\nout vec4 color;\n\nuniform sampler2D ColorMap;\n#if DASH\nuniform sampler2D NoiseMap;\n#endif\n\nlayout(std140) uniform Character3DScene {\n  UNIFORM_PRECISION mat4 ViewProjection;\n  UNIFORM_PRECISION vec4 Tint;\n  UNIFORM_PRECISION vec3 WorldLightPosition;\n  UNIFORM_PRECISION vec3 WorldEyePosition;\n  bool DarkMode;\n};\n\nlayout(std140) uniform Character3DModel { UNIFORM_PRECISION mat4 Model; };\n\nlayout(std140) uniform Character3DMesh {\n  UNIFORM_PRECISION mat4 Bones[ModelMaxBonesPerMesh];\n  UNIFORM_PRECISION float ModelOpacity;\n  bool HasShadowColorMap;\n};\n\nvoid main() {\n  vec4 texColor = texture(ColorMap, uv);\n  if (texColor.a <= 0.1) discard;\n\n#if DASH\n  // These vary by scene - also seen: 0.75 and 1275, 0.58824 and 1.81818\n  vec3 factor1 = vec3(0.70588);\n  vec3 factor2 = vec3(8.0);\n\n  vec3 c1 =\n      factor1 * texColor.rgb + clamp(texColor.rgb * factor2, 0.0, 1.0) * 0.25;\n  // 0.4, 0.2, 0.2 is from mesh info unk9\n  vec3 c2 = clamp(c1 + vec3(0.4, 0.2, 0.2) + vec3(-1.0), 0.0, 1.0);\n  vec3 c3 = -c1 + c2;\n\n  float noiseFactor = texture(NoiseMap, uvNoise).r;\n\n  color = vec4(c3 * noiseFactor + c1, 1.0);\n#else\n  const vec4 outlineColor = vec4(0.46484375, 0.27734375, 0.22265625, 1.0);\n  color = outlineColor * texColor;\n#endif\n\n  color.a *= ModelOpacity;\n}"
  },
  {
    "path": "src/shaders/opengl/Renderable3D_Outline_vert.glsl",
    "content": "layout(location = 0) in vec3 Position;\nlayout(location = 1) in vec3 Normal;\nlayout(location = 2) in vec2 UV;\nlayout(location = 3) in lowp ivec4 BoneIndices;  // indices into Mesh.BoneMap\nlayout(location = 4) in vec4 BoneWeights;\n\nout vec2 uv;\n#if DASH\nout vec2 uvNoise;\n#endif\n\nlayout(std140) uniform Character3DScene {\n  UNIFORM_PRECISION mat4 ViewProjection;\n  UNIFORM_PRECISION vec4 Tint;\n  UNIFORM_PRECISION vec3 WorldLightPosition;\n  UNIFORM_PRECISION vec3 WorldEyePosition;\n  bool DarkMode;\n};\n\nlayout(std140) uniform Character3DModel { UNIFORM_PRECISION mat4 Model; };\n\nlayout(std140) uniform Character3DMesh {\n  UNIFORM_PRECISION mat4 Bones[ModelMaxBonesPerMesh];\n  UNIFORM_PRECISION float ModelOpacity;\n  bool HasShadowColorMap;\n};\n\n// TODO there's a uniform for this somewhere...\n#if DASH\nconst float OutlineThickness = 0.0035;\n#else\nconst float OutlineThickness = 0.035;\n#endif\n\nvoid main() {\n  // Accumulated skinning, thanks\n  // https://developer.nvidia.com/gpugems/GPUGems/gpugems_ch04.html\n  mat4 skeletalTransform = Bones[BoneIndices.x] * BoneWeights.x +\n                           Bones[BoneIndices.y] * BoneWeights.y +\n                           Bones[BoneIndices.z] * BoneWeights.z +\n                           Bones[BoneIndices.w] * BoneWeights.w;\n\n  mat4 transform = Model * skeletalTransform;\n  mat3 normalMatrix = mat3(transpose(inverse(transform)));\n\n  vec3 worldNormal = normalMatrix * Normal;\n\n  vec4 viewNormal = normalize(ViewProjection * vec4(worldNormal, 0.0));\n\n  vec4 worldPosition = transform * vec4(Position, 1.0);\n\n  gl_Position = ViewProjection * worldPosition;\n  gl_Position += viewNormal * OutlineThickness;\n\n  uv = UV;\n\n#if DASH\n  uvNoise = (gl_Position.xy / gl_Position.w) * 0.2;\n#endif\n}"
  },
  {
    "path": "src/shaders/opengl/SceneToRT_frag.glsl",
    "content": "in vec2 uv;\n\nout vec4 color;\n\n#ifdef MSAA_MODE_MULTISAMPLE_TEXTURE\nuniform sampler2DMS Framebuffer3D;\n\nvec4 textureMS(sampler2DMS tex, vec2 texcoord) {\n  ivec2 iTexcoord = ivec2(floor(WindowDimensions * RenderScale * texcoord));\n  vec4 result = vec4(0.0);\n  for (int i = 0; i < MultisampleCount; i++) {\n    result += texelFetch(tex, iTexcoord, i);\n  }\n  return result / MultisampleCount;\n}\n#else\nuniform sampler2D Framebuffer3D;\n#endif\n\n// const int MultisampleCount;\n// const vec2 WindowDimensions;\n// const float RenderScale;\n// Knowing these (especially MultisampleCount) at compile time allows\n// optimisations\n\nvoid main() {\n#ifdef MSAA_MODE_MULTISAMPLE_TEXTURE\n  color = textureMS(Framebuffer3D, uv);\n#else\n  color = texture(Framebuffer3D, uv);\n#endif\n}"
  },
  {
    "path": "src/shaders/opengl/SceneToRT_vert.glsl",
    "content": "layout(location = 0) in vec2 Position;\nlayout(location = 1) in vec2 UV;\n\nout vec2 uv;\n\nvoid main() {\n  gl_Position = vec4(Position.x, Position.y, 0.0, 1.0);\n  uv = UV;\n}"
  },
  {
    "path": "src/shaders/opengl/ScreenMaskedSprite_frag.glsl",
    "content": "in vec2 uv;\nin vec4 tint;\nin vec2 maskUV;\n\nout vec4 color;\n\nuniform sampler2D ColorMap;\nuniform sampler2D Mask;\n\nvoid main() {\n    vec4 spriteColor = texture(ColorMap, uv);\n    vec4 maskColor = texture(Mask, maskUV);\n\n    color.rgb = vec3(1.0) - (vec3(1.0) - spriteColor.rgb) * (vec3(1.0) - maskColor.rgb);\n\n    color.rgb = mix(spriteColor.rgb, color.rgb, maskColor.a);\n    color.a = spriteColor.a * tint.a;\n}\n"
  },
  {
    "path": "src/shaders/opengl/ScreenMaskedSprite_vert.glsl",
    "content": "layout(location = 0) in vec2 Position;\nlayout(location = 1) in vec2 UV;\nlayout(location = 2) in vec4 Tint;\nlayout(location = 3) in vec2 MaskUV;\n\nout vec2 uv;\nout vec4 tint;\nout vec2 maskUV;\n\nuniform mat4 Projection;\nuniform mat4 SpriteTransformation;\nuniform mat4 MaskTransformation;\n\nvoid main() {\n  gl_Position = Projection * SpriteTransformation * vec4(Position, 0.0, 1.0);\n\n  uv = UV;\n  tint = Tint;\n\n  maskUV = vec2(MaskTransformation * vec4(MaskUV, 0.0, 1.0));\n  maskUV.y = 1.0 - maskUV.y;\n}\n"
  },
  {
    "path": "src/shaders/opengl/SoftLightMaskedSprite_frag.glsl",
    "content": "in vec2 uv;\nin vec4 tint;\nin vec2 maskUV;\n\nout vec4 color;\n\nuniform sampler2D ColorMap;\nuniform sampler2D Mask;\n\nfloat softLight(float spriteVal, float maskVal) {\n    float spriteFactor = spriteVal - spriteVal * spriteVal;\n    float maskFactor = 2.0 * maskVal - 1.0;\n\n    float val = spriteVal;\n    if (maskVal < 0.5) {\n        val += spriteFactor * maskFactor;\n    } else if (maskVal <= 32.0 / 255.0) {\n        val += spriteFactor * maskFactor * (3.0 - 8.0 * spriteVal);\n    } else {\n        val += (sqrt(spriteVal) - spriteVal) * maskFactor;\n    }\n\n    return val;\n}\n\nvoid main() {\n    vec4 spriteColor = texture(ColorMap, uv);\n    vec4 maskColor = texture(Mask, maskUV);\n\n    color.r = softLight(spriteColor.r, maskColor.r);\n    color.g = softLight(spriteColor.g, maskColor.g);\n    color.b = softLight(spriteColor.b, maskColor.b);\n\n    color.rgb = mix(spriteColor.rgb, color.rgb, maskColor.a);\n    color.a = spriteColor.a * tint.a;\n}\n"
  },
  {
    "path": "src/shaders/opengl/SoftLightMaskedSprite_vert.glsl",
    "content": "layout(location = 0) in vec2 Position;\nlayout(location = 1) in vec2 UV;\nlayout(location = 2) in vec4 Tint;\nlayout(location = 3) in vec2 MaskUV;\n\nout vec2 uv;\nout vec4 tint;\nout vec2 maskUV;\n\nuniform mat4 Projection;\nuniform mat4 SpriteTransformation;\nuniform mat4 MaskTransformation;\n\nvoid main() {\n  gl_Position = Projection * SpriteTransformation * vec4(Position, 0.0, 1.0);\n\n  uv = UV;\n  tint = Tint;\n\n  maskUV = vec2(MaskTransformation * vec4(MaskUV, 0.0, 1.0));\n  maskUV.y = 1.0 - maskUV.y;\n}\n"
  },
  {
    "path": "src/shaders/opengl/SpriteInverted_frag.glsl",
    "content": "in vec2 uv;\nin vec4 tint;\n\nout vec4 color;\n\nuniform sampler2D ColorMap;\n\nvoid main() {\n  color = texture(ColorMap, uv);\n  color.rgb = vec3(1.0) - color.rgb;\n  color *= tint;\n}"
  },
  {
    "path": "src/shaders/opengl/SpriteInverted_vert.glsl",
    "content": "layout(location = 0) in vec2 Position;\nlayout(location = 1) in vec2 UV;\nlayout(location = 2) in vec4 Tint;\n\nout vec2 uv;\nout vec4 tint;\n\nuniform mat4 Projection;\nuniform mat4 Transformation;\n\nvoid main() {\n  gl_Position = Projection * Transformation * vec4(Position, 0.0, 1.0);\n\n  uv = UV;\n  tint = Tint;\n}"
  },
  {
    "path": "src/shaders/opengl/Sprite_frag.glsl",
    "content": "in vec2 uv;\nin vec4 tint;\n\nout vec4 color;\n\nuniform sampler2D ColorMap;\nuniform vec3 ColorShift;\n\nvoid main() {\n    color = tint * texture(ColorMap, uv);\n    color.rgb = clamp(color.rgb + ColorShift, 0.0, 1.0);\n}"
  },
  {
    "path": "src/shaders/opengl/Sprite_vert.glsl",
    "content": "layout(location = 0) in vec2 Position;\nlayout(location = 1) in vec2 UV;\nlayout(location = 2) in vec4 Tint;\n\nout vec2 uv;\nout vec4 tint;\n\nuniform mat4 Projection;\nuniform mat4 Transformation;\n\nvoid main() {\n  gl_Position = Projection * Transformation * vec4(Position, 0.0, 1.0);\n\n  uv = UV;\n  tint = Tint;\n}"
  },
  {
    "path": "src/shaders/opengl/SubtitleGlyph_frag.glsl",
    "content": "in vec2 uv;\nin vec4 tint;\n\nout vec4 color;\n\nuniform sampler2D CoverageMap;\n\nvoid main() {\n    float mask = texture(CoverageMap, uv).r;\n    float alpha = tint.a * mask;\n    color = vec4(tint.rgb * alpha, alpha);\n}"
  },
  {
    "path": "src/shaders/opengl/SubtitleGlyph_vert.glsl",
    "content": "layout(location = 0) in vec2 Position;\nlayout(location = 1) in vec2 UV;\nlayout(location = 2) in vec4 Tint;\n\nout vec2 uv;\nout vec4 tint;\n\nuniform mat4 Projection;\nuniform mat4 Transformation;\n\nvoid main() {\n  gl_Position = Projection * Transformation * vec4(Position, 0.0, 1.0);\n\n  uv = UV;\n  tint = Tint;\n}"
  },
  {
    "path": "src/shaders/opengl/YUVFrame_frag.glsl",
    "content": "in vec2 uv;\nin vec4 tint;\n\nout vec4 color;\n\nuniform sampler2D Luma;\nuniform sampler2D Cb;\nuniform sampler2D Cr;\nuniform bool IsAlpha;\n\nconst mat4 yuv_to_rgb_rec601 = mat4(\n    1.16438,  0.00000,  1.59603, -0.87079,\n    1.16438, -0.39176, -0.81297,  0.52959,\n    1.16438,  2.01723,  0.00000, -1.08139,\n    0, 0, 0, 1\n);\n\nvec4 getRGBA(vec2 texUv) {\n    vec4 yuvcolor = vec4(1.0);\n    yuvcolor.r = texture(Luma, texUv).r;\n    yuvcolor.g = texture(Cb, texUv).r;\n    yuvcolor.b = texture(Cr, texUv).r;\n    return yuvcolor * yuv_to_rgb_rec601;\n}\n\nvoid main() {\n    if (IsAlpha) {\n        color = getRGBA(vec2(uv.x, uv.y / 2.0));\n        color.a = getRGBA(vec2(uv.x, uv.y / 2.0 + 0.5)).r;\n    } else {\n        color = getRGBA(uv);\n    }\n\tcolor *= tint;\n}"
  },
  {
    "path": "src/shaders/opengl/YUVFrame_vert.glsl",
    "content": "layout(location = 0) in vec2 Position;\nlayout(location = 1) in vec2 UV;\nlayout(location = 2) in vec4 Tint;\n\nout vec2 uv;\nout vec4 tint;\n\nuniform mat4 Projection;\n\nvoid main() {\n  gl_Position = Projection * vec4(Position, 0.0, 1.0);\n  uv = UV;\n  tint = Tint;\n}"
  },
  {
    "path": "src/shaders/vulkan/CCMessageBoxSprite_frag.glsl",
    "content": "#version 450\n#pragma shader_stage(fragment)\n\nlayout(location = 0) in vec2 uv;\nlayout(location = 1) in vec4 tint;\nlayout(location = 2) in vec2 maskUV;\n\nlayout(location = 0) out vec4 color;\n\nlayout(binding = 0) uniform sampler2D ColorMap[2];\n\nlayout(push_constant) uniform constants\n{\n\tvec4 CCBoxAlpha;\n} PushConstants;\n\nvoid main() { \n\tcolor = texture(ColorMap[0], uv);\n\tcolor *= tint;\n\tvec4 alp = texture(ColorMap[1], maskUV);\n\tfloat ga = alp.r;\n\tfloat ea = 1.0 - alp.g;\n\tfloat fa = 1.0 - alp.b;\n\tfa *= PushConstants.CCBoxAlpha.r;\n\tfa -= PushConstants.CCBoxAlpha.g;\n\tfa = clamp(fa,0.0,1.0);\n\tcolor.a *= fa;\n\n\tea += PushConstants.CCBoxAlpha.b;\n\tif (ea > 1.0) ea = 2.0 - ea;\n\tea *= 2.0;\n\tif (ea > 1.0) ea = 2.0 - ea;\n\n\tga *= ea;\n\tga = clamp(ga, 0.0, 1.0);\n\tcolor.a -= ga;\n\tcolor.a = clamp(color.a,0.0,1.0);\n}"
  },
  {
    "path": "src/shaders/vulkan/CCMessageBoxSprite_vert.glsl",
    "content": "#version 450\n#pragma shader_stage(vertex)\n\nlayout(location = 0) in vec2 Position;\nlayout(location = 1) in vec2 UV;\nlayout(location = 2) in vec4 Tint;\nlayout(location = 3) in vec2 MaskUV;\n\nlayout(location = 0) out vec2 uv;\nlayout(location = 1) out vec4 tint;\nlayout(location = 2) out vec2 maskUV;\n\nvoid main() {\n  gl_Position = vec4(Position.x, Position.y, 0.0, 1.0);\n  uv = UV;\n  tint = Tint;\n  maskUV = MaskUV;\n}\n"
  },
  {
    "path": "src/shaders/vulkan/CHLCCMenuBackground_frag.glsl",
    "content": "#version 450\n#pragma shader_stage(fragment)\n\nlayout(location = 0) in vec2 uv;\nlayout(location = 2) in vec2 maskUV;\n\nlayout(location = 0) out vec4 outColor;\n\nlayout(binding = 0) uniform sampler2D ColorMap[2];\n\nlayout(push_constant) uniform constants\n{\n\tvec4 Alpha;\n} PushConstants;\n\nfloat remapMiddle(float value, float newMiddle) {\n    if (value < 0.5) {\n        return mix(0.0, newMiddle, 2.0 * value);\n    } else {\n        return mix(newMiddle, 1.0, 2.0 * value - 1.0);\n    }\n}\n\nvoid main() {\n    vec4 color = texture(ColorMap[0], uv);\n    vec4 mask = texture(ColorMap[1], maskUV);\n    outColor = vec4(\n        remapMiddle(color.r, mask.r),\n        remapMiddle(color.g, mask.g),\n        remapMiddle(color.b, mask.b),\n        mask.a * PushConstants.Alpha.r\n    );\n}"
  },
  {
    "path": "src/shaders/vulkan/CHLCCMenuBackground_vert.glsl",
    "content": "#version 450\n#pragma shader_stage(vertex)\n\nlayout(location = 0) in vec2 Position;\nlayout(location = 1) in vec2 UV;\nlayout(location = 3) in vec2 MaskUV;\n\nlayout(location = 0) out vec2 uv;\nlayout(location = 2) out vec2 maskUV;\n\nvoid main() {\n    gl_Position = vec4(Position.x, Position.y, 0.0, 1.0);\n    uv = UV;\n    maskUV = MaskUV;\n}"
  },
  {
    "path": "src/shaders/vulkan/MaskedSpriteNoAlpha_frag.glsl",
    "content": "#version 450\n#pragma shader_stage(fragment)\n\nlayout(location = 0) in vec2 uv;\nlayout(location = 1) in vec4 tint;\nlayout(location = 2) in vec2 maskUV;\n\nlayout(location = 0) out vec4 color;\n\nlayout(binding = 0) uniform sampler2D ColorMap[2];\n\nlayout(push_constant) uniform constants\n{\n\tvec2 Alpha;\n\tbool IsInverted;\n} PushConstants;\n\nfloat rgbToLightness(vec3 rgb) {\n    float maxVal = max(max(rgb.r, rgb.g), rgb.b);\n    float minVal = min(min(rgb.r, rgb.g), rgb.b);\n    \n    return (maxVal + minVal) / 2.0;\n}\n\nvoid main() { \n\tcolor = texture(ColorMap[0], uv);\n\tvec4 alp = texture(ColorMap[1], maskUV);\n\tfloat maskAlpha = rgbToLightness(alp.rgb);\n\tif (PushConstants.IsInverted) maskAlpha = 1.0f - maskAlpha;\n\tmaskAlpha *= PushConstants.Alpha.x;\n\tmaskAlpha -= PushConstants.Alpha.y;\n\tmaskAlpha = clamp(maskAlpha,0.0,1.0);\n\tcolor *= tint;\n\tcolor.a *= maskAlpha;\n}"
  },
  {
    "path": "src/shaders/vulkan/MaskedSpriteNoAlpha_vert.glsl",
    "content": "#version 450\n#pragma shader_stage(vertex)\n\nlayout(location = 0) in vec2 Position;\nlayout(location = 1) in vec2 UV;\nlayout(location = 2) in vec4 Tint;\nlayout(location = 3) in vec2 MaskUV;\n\nlayout(location = 0) out vec2 uv;\nlayout(location = 1) out vec4 tint;\nlayout(location = 2) out vec2 maskUV;\n\nvoid main() {\n  gl_Position = vec4(Position.x, Position.y, 0.0, 1.0);\n  uv = UV;\n  tint = Tint;\n  maskUV = MaskUV;\n}\n"
  },
  {
    "path": "src/shaders/vulkan/MaskedSprite_frag.glsl",
    "content": "#version 450\n#pragma shader_stage(fragment)\n\nlayout(location = 0) in vec2 uv;\nlayout(location = 1) in vec4 tint;\nlayout(location = 2) in vec2 maskUV;\n\nlayout(location = 0) out vec4 color;\n\nlayout(binding = 0) uniform sampler2D ColorMap[2];\n\nlayout(push_constant) uniform constants\n{\n\tbool IsInverted;\n\tbool IsSameTexture;\n\tvec2 Alpha;\n} PushConstants;\n\nvoid main() { \n\tcolor = texture(ColorMap[0], uv);\n\tvec4 alp = texture(ColorMap[1], maskUV);\n\tif (PushConstants.IsInverted) alp.a = 1.0f - alp.a;\n\talp.a *= PushConstants.Alpha.x;\n\talp.a -= PushConstants.Alpha.y;\n\talp.a = clamp(alp.a,0.0,1.0);\n\tif (PushConstants.IsSameTexture) {\n\t\tcolor.a = alp.a;\n\t\tcolor.a *= tint.a;\n\t} else {\n\t\tcolor *= tint;\n\t\tcolor.a *= alp.a;\n\t}\n}"
  },
  {
    "path": "src/shaders/vulkan/MaskedSprite_vert.glsl",
    "content": "#version 450\n#pragma shader_stage(vertex)\n\nlayout(location = 0) in vec2 Position;\nlayout(location = 1) in vec2 UV;\nlayout(location = 2) in vec4 Tint;\nlayout(location = 3) in vec2 MaskUV;\n\nlayout(location = 0) out vec2 uv;\nlayout(location = 1) out vec4 tint;\nlayout(location = 2) out vec2 maskUV;\n\nvoid main() {\n  gl_Position = vec4(Position.x, Position.y, 0.0, 1.0);\n  uv = UV;\n  tint = Tint;\n  maskUV = MaskUV;\n}\n"
  },
  {
    "path": "src/shaders/vulkan/NV12Frame_frag.glsl",
    "content": "#version 450\n#pragma shader_stage(fragment)\n\nlayout(location = 0) in vec2 uv;\nlayout(location = 1) in vec4 tint;\n\nlayout(location = 0) out vec4 color;\n\nlayout(binding = 0) uniform sampler2D NV12Frame[2];\n\nlayout(push_constant) uniform constants\n{\n    bool IsAlpha;\n} PushConstants;\n\nconst mat4 yuv_to_rgb_rec601 = mat4(\n    1.16438,  0.00000,  1.59603, -0.87079,\n    1.16438, -0.39176, -0.81297,  0.52959,\n    1.16438,  2.01723,  0.00000, -1.08139,\n    0, 0, 0, 1\n);\n\nvec4 getRGBA(vec2 texUv) {\n    vec4 yuvcolor = vec4(1.0);\n    yuvcolor.r = texture(NV12Frame[0], texUv).r;\n    yuvcolor.gb = texture(NV12Frame[1], texUv).rg;\n    return yuvcolor * yuv_to_rgb_rec601;\n}\n\nvoid main() {\n    if (PushConstants.IsAlpha) {\n        color = getRGBA(vec2(uv.x, uv.y / 2.0));\n        color.a = getRGBA(vec2(uv.x, uv.y / 2.0 + 0.5)).r;\n    } else {\n        color = getRGBA(uv);\n    }\n\tcolor *= tint;\n}"
  },
  {
    "path": "src/shaders/vulkan/NV12Frame_vert.glsl",
    "content": "#version 450\n#pragma shader_stage(vertex)\n\nlayout(location = 0) in vec2 Position;\nlayout(location = 1) in vec2 UV;\nlayout(location = 2) in vec4 Tint;\nlayout(location = 3) in vec2 MaskUV;\n\nlayout(location = 0) out vec2 uv;\nlayout(location = 1) out vec4 tint;\n\nvoid main() {\n  gl_Position = vec4(Position, 0.0, 1.0);\n  uv = UV;\n  tint = Tint;\n}\n"
  },
  {
    "path": "src/shaders/vulkan/Renderable3D_Background_frag.glsl",
    "content": "#version 450\n#pragma shader_stage(fragment)\n\nlayout(location = 0) in vec2 uv;\n\nlayout(location = 0) out vec4 color;\n\nlayout(binding = 3) uniform sampler2D ColorMap;\n\nlayout(push_constant) uniform constants\n{\n\tbool IsDash;\n} PushConstants;\n\nvoid main() { color = texture(ColorMap, uv); }\n"
  },
  {
    "path": "src/shaders/vulkan/Renderable3D_Background_vert.glsl",
    "content": "#version 450\n#pragma shader_stage(vertex)\n\nlayout(location = 0) in vec3 Position;\nlayout(location = 1) in vec2 UV;\n\nlayout(location = 0) out vec2 uv;\n\nlayout(binding = 0) uniform UniformBufferObject {\n    mat4 MVP;\n} ubo;\n\nlayout(push_constant) uniform constants\n{\n\tbool IsDash;\n} PushConstants;\n\nvoid main() {\n  gl_Position = ubo.MVP * vec4(Position, 1.0);\n  uv = UV;\n}"
  },
  {
    "path": "src/shaders/vulkan/Renderable3D_Character_frag.glsl",
    "content": "#version 450\n#pragma shader_stage(fragment)\n\nlayout(location = 0) in vec3 worldFragPosition;\nlayout(location = 1) in vec3 worldNormal;\nlayout(location = 2) in vec2 uv;\n\nlayout(location = 0) out vec4 color;\n\n// ColorMap - ColorMap[0], GradientMaskMap - ColorMap[1], SpecularColorMap - ColorMap[2], ShadowColorMap - ColorMap[3]\nlayout(binding = 3) uniform sampler2D ColorMap[4];\n\nlayout(binding = 0) uniform Character3DScene {\n  mat4 ViewProjection;\n  vec4 Tint;\n  vec3 WorldLightPosition;\n  vec3 WorldEyePosition;\n  bool DarkMode;\n};\n\nlayout(binding = 1) uniform Character3DModel { mat4 Model; };\n\nlayout(binding = 2) uniform Character3DMesh {\n  mat4 Bones[32];\n  float ModelOpacity;\n  bool HasShadowColorMap;\n};\n\nlayout(push_constant) uniform constants\n{\n\tbool IsDash;\n} PushConstants;\n\nconst float FALLOFF_POWER = 0.8;\n\nconst vec4 g_ToonFalloffGradVals = vec4(0.546875, 0.421875, 0.468750, 0.281250);\nconst vec4 g_ToonFalloffGradScale = vec4(10.66667, 10.66667, 32.0, 32.0);\nconst vec4 g_ToonFalloffGradDarkVals =\n    vec4(0.59375, 0.484375, 0.609375, 0.453125);\nconst vec4 g_ToonFalloffGradDarkMax = vec4(0.79, 0.79, 0.61, 0.61);\n\nvec4 saturate(vec4 x) { return clamp(x, 0.0, 1.0); }\n\nvoid main() {\n  vec4 texColor = texture(ColorMap[0], uv);\n  float maskParam = texture(ColorMap[1], uv).r;\n  vec3 specColor = texture(ColorMap[2], uv).rgb;\n\n  vec3 normal = normalize(worldNormal);\n  vec3 dirToLight = normalize(WorldLightPosition - worldFragPosition);\n  vec3 dirToEye = normalize(WorldEyePosition - worldFragPosition);\n\n  float NdotL = dot(normal, dirToLight);\n  float scaledDiff = 0.5 * NdotL + 0.5;\n\n  // Note: this is a uniform in R;NE ???\n  vec3 H = normalize(dirToLight + dirToEye);\n\n  vec4 toonFalloffGradInput;\n  toonFalloffGradInput.xy = vec2(scaledDiff);\n\n  float normDotEye = 1.0 - dot(normal, dirToEye);\n  toonFalloffGradInput.zw = vec2(scaledDiff * normDotEye);\n\n  // Original just uses step here but that looks bad\n  vec4 toonFalloffGradParamDark =\n      smoothstep(g_ToonFalloffGradDarkVals * 0.95,\n                 g_ToonFalloffGradDarkVals * 1.05, toonFalloffGradInput) *\n      g_ToonFalloffGradDarkMax;\n\n  vec4 toonFalloffGradParamLight = saturate(\n      (toonFalloffGradInput - g_ToonFalloffGradVals) * g_ToonFalloffGradScale);\n\n  vec4 toonFalloffGradParam =\n      DarkMode ? toonFalloffGradParamDark : toonFalloffGradParamLight;\n\n  vec2 toonFalloffInterpParam =\n      mix(toonFalloffGradParam.xz, toonFalloffGradParam.yw, maskParam);\n\n  vec3 shadowColor;\n  if (PushConstants.IsDash && HasShadowColorMap) {\n    shadowColor = texture(ColorMap[3], uv).rgb;\n  } else {\n    shadowColor = max(texColor.rgb, vec3(0.5)) * texColor.rgb;\n  }\n\n  vec3 toonColor = mix(shadowColor, texColor.rgb, toonFalloffInterpParam.x);\n\n  // Tint\n\n  vec3 baseTintColor = toonColor * Tint.rgb;\n  toonColor = mix(toonColor, baseTintColor, Tint.a);\n\n  // Rimlight\n\n  vec3 falloffColor = FALLOFF_POWER * toonFalloffInterpParam.y * texColor.rgb;\n  toonColor += falloffColor * (vec3(1.0) - toonColor);\n\n  // Specular\n  // R;NE does not use max() here - maybe manual control of H ensures it doesn't\n  // go negative?\n  float specIntensity = pow(max(dot(normal, H), 0.0), 20.0);\n  toonColor += specIntensity * specColor;\n\n  color = vec4(toonColor, texColor.a * ModelOpacity);\n}"
  },
  {
    "path": "src/shaders/vulkan/Renderable3D_Character_vert.glsl",
    "content": "#version 450\n#pragma shader_stage(vertex)\n\nlayout(location = 0) in vec3 Position;\nlayout(location = 1) in vec3 Normal;\nlayout(location = 2) in vec2 UV;\nlayout(location = 3) in lowp ivec4 BoneIndices;  // indices into Mesh.BoneMap\nlayout(location = 4) in vec4 BoneWeights;\n\nlayout(location = 0) out vec3 worldFragPosition;\nlayout(location = 1) out vec3 worldNormal;\nlayout(location = 2) out vec2 uv;\n\nlayout(binding = 0) uniform Character3DScene {\n  mat4 ViewProjection;\n  vec4 Tint;\n  vec3 WorldLightPosition;\n  vec3 WorldEyePosition;\n  bool DarkMode;\n};\n\nlayout(binding = 1) uniform Character3DModel { highp mat4 Model; };\n\nlayout(binding = 2) uniform Character3DMesh {\n  mat4 Bones[32];\n  float ModelOpacity;\n  bool HasShadowColorMap;\n};\n\nlayout(push_constant) uniform constants\n{\n\tbool IsDash;\n} PushConstants;\n\nvoid main() {\n  // Accumulated skinning, thanks\n  // https://developer.nvidia.com/gpugems/GPUGems/gpugems_ch04.html\n  mat4 skeletalTransform = Bones[BoneIndices.x] * BoneWeights.x +\n                           Bones[BoneIndices.y] * BoneWeights.y +\n                           Bones[BoneIndices.z] * BoneWeights.z +\n                           Bones[BoneIndices.w] * BoneWeights.w;\n\n  mat4 transform = Model * skeletalTransform;\n  mat3 normalMatrix = mat3(transform);\n\n  vec4 worldPosition = transform * vec4(Position, 1.0);\n  worldFragPosition = worldPosition.xyz;\n\n  gl_Position = ViewProjection * worldPosition;\n  worldNormal = normalMatrix * Normal;\n  uv = UV;\n}"
  },
  {
    "path": "src/shaders/vulkan/Renderable3D_Eye_frag.glsl",
    "content": "#version 450\n#pragma shader_stage(fragment)\n\nlayout(location = 0) in vec2 uv;\n\nlayout(location = 0) out vec4 color;\n\n// IrisColorMap[0] - IrisColorMap, IrisColorMap[1] - WhiteColorMap, IrisColorMap[2] - HighlightColorMap, IrisColorMap[3] - IrisSpecularColorMap\nlayout(binding = 3) uniform sampler2D IrisColorMap[4];\n\nlayout(binding = 0) uniform Character3DScene {\n  mat4 ViewProjection;\n  vec4 Tint;\n  vec3 WorldLightPosition;\n  vec3 WorldEyePosition;\n  bool DarkMode;\n};\n\nlayout(binding = 1) uniform Character3DModel { mat4 Model; };\n\nlayout(binding = 2) uniform Character3DMesh {\n  mat4 Bones[32];\n  float ModelOpacity;\n  bool HasShadowColorMap;\n};\n\nlayout(push_constant) uniform constants\n{\n\tbool IsDash;\n} PushConstants;\n\nconst float FALLOFF_POWER = 0.8;\nconst float HIGHLIGHT_TINT_INTENSITY = 0.6;\n\nvoid main() {\n  vec4 eyeWhiteColor = texture(IrisColorMap[1], uv);\n  vec4 irisColor = texture(IrisColorMap[0], uv);\n  vec4 highlightColor = texture(IrisColorMap[2], uv);\n\n  vec3 eyeColor = mix(eyeWhiteColor.rgb, irisColor.rgb, irisColor.a);\n\n  if (!PushConstants.IsDash) {\n\t// \"Specular\"\n    vec4 irisSpecularColor = texture(IrisColorMap[3], uv);\n    eyeColor += irisSpecularColor.rgb * 0.1;\n  }\n\n  // Tint\n  vec3 baseTintColor = eyeColor * Tint.rgb;\n  eyeColor = mix(eyeColor, baseTintColor, Tint.a);\n\n  // Highlight\n  vec3 highlightTintColor = highlightColor.rgb * Tint.rgb;\n  highlightColor.rgb = mix(highlightColor.rgb, highlightTintColor,\n                           HIGHLIGHT_TINT_INTENSITY * Tint.a);\n  eyeColor = mix(eyeColor, highlightColor.rgb, highlightColor.a);\n\n  color = vec4(eyeColor, ModelOpacity);\n}"
  },
  {
    "path": "src/shaders/vulkan/Renderable3D_Eye_vert.glsl",
    "content": "#version 450\n#pragma shader_stage(vertex)\n\nlayout(location = 0) in vec3 Position;\nlayout(location = 1) in vec3 Normal;\nlayout(location = 2) in vec2 UV;\nlayout(location = 3) in lowp ivec4 BoneIndices;  // indices into Mesh.BoneMap\nlayout(location = 4) in vec4 BoneWeights;\n\nlayout(location = 0) out vec2 uv;\n\nlayout(binding = 0) uniform Character3DScene {\n  mat4 ViewProjection;\n  vec4 Tint;\n  vec3 WorldLightPosition;\n  vec3 WorldEyePosition;\n  bool DarkMode;\n};\n\nlayout(binding = 1) uniform Character3DModel { highp mat4 Model; };\n\nlayout(binding = 2) uniform Character3DMesh {\n  mat4 Bones[32];\n  float ModelOpacity;\n  bool HasShadowColorMap;\n};\n\nlayout(push_constant) uniform constants\n{\n\tbool IsDash;\n} PushConstants;\n\nvoid main() {\n  // Accumulated skinning, thanks\n  // https://developer.nvidia.com/gpugems/GPUGems/gpugems_ch04.html\n  mat4 skeletalTransform = Bones[BoneIndices.x] * BoneWeights.x +\n                           Bones[BoneIndices.y] * BoneWeights.y +\n                           Bones[BoneIndices.z] * BoneWeights.z +\n                           Bones[BoneIndices.w] * BoneWeights.w;\n\n  mat4 transform = Model * skeletalTransform;\n\n  vec4 worldPosition = transform * vec4(Position, 1.0);\n\n  gl_Position = ViewProjection * worldPosition;\n\n  uv = UV;\n}"
  },
  {
    "path": "src/shaders/vulkan/Renderable3D_Outline_frag.glsl",
    "content": "#version 450\n#pragma shader_stage(fragment)\n\nlayout(location = 0) in vec2 uv;\nlayout(location = 1) in vec2 uvNoise;\n\nlayout(location = 0) out vec4 color;\n\n// ColorMap[0] - ColorMap, ColorMap[1] - NoiseMap\nlayout(binding = 3) uniform sampler2D ColorMap[4];\n\nlayout(binding = 0) uniform Character3DScene {\n  mat4 ViewProjection;\n  vec4 Tint;\n  vec3 WorldLightPosition;\n  vec3 WorldEyePosition;\n  bool DarkMode;\n};\n\nlayout(binding = 1) uniform Character3DModel { mat4 Model; };\n\nlayout(binding = 2) uniform Character3DMesh {\n  mat4 Bones[32];\n  float ModelOpacity;\n  bool HasShadowColorMap;\n};\n\nlayout(push_constant) uniform constants\n{\n\tbool IsDash;\n} PushConstants;\n\nvoid main() {\n  vec4 texColor = texture(ColorMap[0], uv);\n  if (texColor.a <= 0.1) discard;\n\n  if (PushConstants.IsDash) {\n    vec3 factor1 = vec3(0.70588);\n    vec3 factor2 = vec3(8.0);\n\n    vec3 c1 =\n        factor1 * texColor.rgb + clamp(texColor.rgb * factor2, 0.0, 1.0) * 0.25;\n    // 0.4, 0.2, 0.2 is from mesh info unk9\n    vec3 c2 = clamp(c1 + vec3(0.4, 0.2, 0.2) + vec3(-1.0), 0.0, 1.0);\n    vec3 c3 = -c1 + c2;\n\n    float noiseFactor = texture(ColorMap[1], uvNoise).r;\n\n    color = vec4(c3 * noiseFactor + c1, 1.0);\n  } else {\n    const vec4 outlineColor = vec4(0.46484375, 0.27734375, 0.22265625, 1.0);\n    color = outlineColor * texColor;\n  }\n\n  color.a *= ModelOpacity;\n}"
  },
  {
    "path": "src/shaders/vulkan/Renderable3D_Outline_vert.glsl",
    "content": "#version 450\n#pragma shader_stage(vertex)\n\nlayout(location = 0) in vec3 Position;\nlayout(location = 1) in vec3 Normal;\nlayout(location = 2) in vec2 UV;\nlayout(location = 3) in lowp ivec4 BoneIndices;  // indices into Mesh.BoneMap\nlayout(location = 4) in vec4 BoneWeights;\n\nlayout(location = 0) out vec2 uv;\nlayout(location = 1) out vec2 uvNoise;\n\nlayout(binding = 0) uniform Character3DScene {\n  mat4 ViewProjection;\n  vec4 Tint;\n  vec3 WorldLightPosition;\n  vec3 WorldEyePosition;\n  bool DarkMode;\n};\n\nlayout(binding = 1) uniform Character3DModel { highp mat4 Model; };\n\nlayout(binding = 2) uniform Character3DMesh {\n  mat4 Bones[32];\n  float ModelOpacity;\n  bool HasShadowColorMap;\n};\n\n// TODO there's a uniform for this somewhere...\n// #if DASH\n// const float OutlineThickness = 0.0035;\n// #else\n// const float OutlineThickness = 0.035;\n// #endif\n\nlayout(push_constant) uniform constants\n{\n\tbool IsDash;\n} PushConstants;\n\nvoid main() {\n  // Accumulated skinning, thanks\n  // https://developer.nvidia.com/gpugems/GPUGems/gpugems_ch04.html\n  mat4 skeletalTransform = Bones[BoneIndices.x] * BoneWeights.x +\n                           Bones[BoneIndices.y] * BoneWeights.y +\n                           Bones[BoneIndices.z] * BoneWeights.z +\n                           Bones[BoneIndices.w] * BoneWeights.w;\n\n  mat4 transform = Model * skeletalTransform;\n  mat3 normalMatrix = mat3(inverse(transform));\n\n  vec3 worldNormal = normalMatrix * Normal;\n\n  vec4 viewNormal = normalize(ViewProjection * vec4(worldNormal, 0.0));\n\n  vec4 worldPosition = transform * vec4(Position, 1.0);\n\n  float OutlineThickness = 0.035;\n  if (PushConstants.IsDash) {\n    OutlineThickness = 0.0035;\n  }\n\n  gl_Position = ViewProjection * worldPosition;\n  gl_Position += viewNormal * OutlineThickness;\n\n  uv = UV;\n\n  uvNoise = (gl_Position.xy / gl_Position.w) * 0.2;\n}"
  },
  {
    "path": "src/shaders/vulkan/Sprite_frag.glsl",
    "content": "#version 450\n#pragma shader_stage(fragment)\n\nlayout(location = 0) in vec2 uv;\nlayout(location = 1) in vec4 tint;\n\nlayout(location = 0) out vec4 color;\n\nlayout(binding = 0) uniform sampler2D ColorMap;\n\nvoid main() { color = tint * texture(ColorMap, uv); }"
  },
  {
    "path": "src/shaders/vulkan/Sprite_inverted_frag.glsl",
    "content": "#version 450\n#pragma shader_stage(fragment)\n\nlayout(location = 0) in vec2 uv;\nlayout(location = 1) in vec4 tint;\n\nlayout(location = 0) out vec4 color;\n\nlayout(binding = 0) uniform sampler2D ColorMap;\n\nvoid main() {\n  color = texture(ColorMap, uv);\n  color.rgb = vec3(1.0) - color.rgb;\n  color *= tint;\n}"
  },
  {
    "path": "src/shaders/vulkan/Sprite_inverted_vert.glsl",
    "content": "#version 450\n#pragma shader_stage(vertex)\n\nlayout(location = 0) in vec2 Position;\nlayout(location = 1) in vec2 UV;\nlayout(location = 2) in vec4 Tint;\nlayout(location = 3) in vec2 MaskUV;\n\nlayout(location = 0) out vec2 uv;\nlayout(location = 1) out vec4 tint;\n\nvoid main() {\n  gl_Position = vec4(Position, 0.0, 1.0);\n  uv = UV;\n  tint = Tint;\n}\n"
  },
  {
    "path": "src/shaders/vulkan/Sprite_vert.glsl",
    "content": "#version 450\n#pragma shader_stage(vertex)\n\nlayout(location = 0) in vec2 Position;\nlayout(location = 1) in vec2 UV;\nlayout(location = 2) in vec4 Tint;\nlayout(location = 3) in vec2 MaskUV;\n\nlayout(location = 0) out vec2 uv;\nlayout(location = 1) out vec4 tint;\n\nvoid main() {\n  gl_Position = vec4(Position, 0.0, 1.0);\n  uv = UV;\n  tint = Tint;\n}\n"
  },
  {
    "path": "src/shaders/vulkan/YUVFrame_frag.glsl",
    "content": "#version 450\n#pragma shader_stage(fragment)\n\nlayout(location = 0) in vec2 uv;\nlayout(location = 1) in vec4 tint;\n\nlayout(location = 0) out vec4 color;\n\nlayout(binding = 0) uniform sampler2D YUVFrame[3];\n\nlayout(push_constant) uniform constants\n{\n    bool IsAlpha;\n} PushConstants;\n\nconst mat4 yuv_to_rgb_rec601 = mat4(\n    1.16438,  0.00000,  1.59603, -0.87079,\n    1.16438, -0.39176, -0.81297,  0.52959,\n    1.16438,  2.01723,  0.00000, -1.08139,\n    0, 0, 0, 1\n);\n\nvec4 getRGBA(vec2 texUv) {\n    vec4 yuvcolor = vec4(1.0);\n    yuvcolor.r = texture(YUVFrame[0], texUv).r;\n    yuvcolor.g = texture(YUVFrame[1], texUv).r;\n    yuvcolor.b = texture(YUVFrame[2], texUv).r;\n    return yuvcolor * yuv_to_rgb_rec601;\n}\n\nvoid main() {\n    if (PushConstants.IsAlpha) {\n        color = getRGBA(vec2(uv.x, uv.y / 2.0));\n        color.a = getRGBA(vec2(uv.x, uv.y / 2.0 + 0.5)).r;\n    } else {\n        color = getRGBA(uv);\n    }\n\tcolor *= tint;\n}"
  },
  {
    "path": "src/shaders/vulkan/YUVFrame_vert.glsl",
    "content": "#version 450\n#pragma shader_stage(vertex)\n\nlayout(location = 0) in vec2 Position;\nlayout(location = 1) in vec2 UV;\nlayout(location = 2) in vec4 Tint;\nlayout(location = 3) in vec2 MaskUV;\n\nlayout(location = 0) out vec2 uv;\nlayout(location = 1) out vec4 tint;\n\nvoid main() {\n  gl_Position = vec4(Position, 0.0, 1.0);\n  uv = UV;\n  tint = Tint;\n}\n"
  },
  {
    "path": "src/spriteanimation.cpp",
    "content": "#include \"spriteanimation.h\"\n\nnamespace Impacto {\n\nbool SpriteAnimation::Exists() { return Def != 0; }\n\nSprite SpriteAnimation::CurrentSprite() const {\n  int frame = (int)(Progress * (float)Def->FrameCount);\n\n  if (frame >= Def->FrameCount)\n    frame = Def->FrameCount - 1;\n  else if (frame < 0)\n    frame = 0;\n\n  return Def->Frames[frame];\n}\n\nvoid FixedSpriteAnimation::StartInImpl(bool reset) {\n  if (reset) Progress = GetFixedSpriteProgress();\n}\n\nvoid FixedSpriteAnimation::StartOutImpl(bool reset) {\n  if (reset) Progress = GetFixedSpriteProgress();\n}\n\nvoid FixedSpriteAnimation::UpdateImpl(float dt) {\n  float fixedSpriteProgress = GetFixedSpriteProgress();\n  AnimationDirection animationRequest = Direction;\n\n  if ((Progress == 1.0f && Direction == AnimationDirection::Out) ||\n      (Progress == 0.0f && Direction == AnimationDirection::In)) {\n    Progress = fixedSpriteProgress;\n  }\n\n  // Always play both parts of the animation in the correct direction\n  // (At the start of the function \"Direction\" only signifies the\n  //  whether the in or out animation should be played; not the actual\n  //  direction of the animation)\n  if (Progress != fixedSpriteProgress) {\n    Direction = Progress > fixedSpriteProgress ? AnimationDirection::In\n                                               : AnimationDirection::Out;\n  }\n\n  // Coordinate transformation and normalization for AddDelta\n  if (Direction == AnimationDirection::In) {\n    Progress = (Progress - fixedSpriteProgress) / (1.0f - fixedSpriteProgress);\n    dt /= 1.0f - fixedSpriteProgress;\n  } else {\n    Progress /= fixedSpriteProgress;\n    dt /= fixedSpriteProgress;\n  }\n\n  SpriteAnimation::UpdateImpl(dt);\n\n  // Revert coordinate transformation and normalization\n  if (Direction == AnimationDirection::In)\n    Progress = Progress * (1.0f - fixedSpriteProgress) + fixedSpriteProgress;\n  else\n    Progress *= fixedSpriteProgress;\n\n  // Start requested animation after already playing, non-requested, animation\n  // has finished\n  bool progressAtExtremum = (Progress == 0.0f || Progress == 1.0f);\n  if (animationRequest != Direction && progressAtExtremum) {\n    Direction = animationRequest;\n    State = AnimationState::Playing;\n  }\n}\n\nSprite FixedSpriteAnimation::CurrentSprite() const {\n  int frame;\n  float fixedSpriteProgress = GetFixedSpriteProgress();\n\n  if (Progress > fixedSpriteProgress)  // In animation\n    frame = (int)((Progress - fixedSpriteProgress) * (float)Def->FrameCount);\n  else if (Progress < fixedSpriteProgress)  // Out animation\n    frame = (int)((1.0f - Progress) * (float)Def->FrameCount);\n  else {  // Progress = fixedSpriteProgress\n    frame = Def->FixSpriteId;\n    if (Direction == AnimationDirection::Out) frame++;\n  }\n\n  if (frame >= Def->FrameCount)\n    frame = Def->FrameCount - 1;\n  else if (frame < 0)\n    frame = 0;\n\n  return Def->Frames[frame];\n}\n\nfloat FixedSpriteAnimation::GetFixedSpriteProgress() const {\n  /* Converse because in- and out directions are reversed\n    between spritesheet and implementation */\n  return 1.0f - (float)Def->FixSpriteId / Def->FrameCount;\n}\n\nSpriteAnimation SpriteAnimationDef::Instantiate() {\n  SpriteAnimation result;\n  result.Def = this;\n  result.DurationIn = this->Duration;\n  result.DurationOut = this->Duration;\n\n  if (this->FixSpriteId != 0) {\n    result.Progress =\n        static_cast<FixedSpriteAnimation&>(result).GetFixedSpriteProgress();\n  }\n\n  return result;\n}\n\n}  // namespace Impacto"
  },
  {
    "path": "src/spriteanimation.h",
    "content": "#pragma once\n\n#include \"spritesheet.h\"\n#include \"animation.h\"\n\nnamespace Impacto {\n\nclass SpriteAnimationDef;\n\nstruct SpriteAnimation : public Animation {\n  SpriteAnimationDef* Def = 0;\n\n  bool Exists();\n  virtual Sprite CurrentSprite() const;\n};\n\n/*\n  Sprite animation that has a separate in and out animation\n  Consists of one contiguous Sprite array, with an int FixSpriteId\n  denoting the 0-indexed id of the last frame of the in animation.\n\n  If the animation is out and gets told to move in, it will play\n  the in animation to completion.\n  Similarly, if the animation is in and gets told to move out, it will\n  play the out animation to completion.\n  If either the in or out animation is playing, and it suddenly gets told\n  to do the other animation, it will finish the currently playing animation\n  and then immediately move on to the requested animation.\n\n  The progress is split up such that the interval\n  [0, 1 - FixSpriteId / FrameCount] is the out animation (with 0 being fully\n  out), and the interval [1 - FixSpriteId / FrameCount, 1] is the in animation\n  (with 1 being fully in).\n*/\nstruct FixedSpriteAnimation : public SpriteAnimation {\n  void StartInImpl(bool reset) override;\n  void StartOutImpl(bool reset) override;\n  void UpdateImpl(float dt) override;\n  Sprite CurrentSprite() const override;\n  float GetFixedSpriteProgress() const;\n};\n\nclass SpriteAnimationDef {\n public:\n  float Duration;\n  int FrameCount;\n  Sprite* Frames;\n\n  // Sprite to hold on in case of SpriteAnimFixed type\n  int FixSpriteId = 0;\n\n  SpriteAnimation Instantiate();\n};\n\n}  // namespace Impacto"
  },
  {
    "path": "src/spritesheet.h",
    "content": "﻿#pragma once\n\n#include \"util.h\"\n\nnamespace Impacto {\n\nstruct SpriteSheet {\n  SpriteSheet() {}\n  SpriteSheet(float width, float height)\n      : DesignWidth(width), DesignHeight(height) {}\n\n  float DesignWidth = 0;\n  float DesignHeight = 0;\n\n  glm::vec2 GetDimensions() const { return {DesignWidth, DesignHeight}; }\n\n  uint32_t Texture = 0;\n  bool IsScreenCap = false;\n};\n\n// TODO replace BaseScale with scaled width/height and unscaled width/height\n\nstruct Sprite {\n  Sprite() : BaseScale(1.0f) {}\n  Sprite(SpriteSheet const& sheet, float x, float y, float width, float height,\n         glm::vec2 baseScale = glm::vec2(1.0f))\n      : Sheet(sheet), Bounds(x, y, width, height), BaseScale(baseScale) {}\n\n  SpriteSheet Sheet;\n  RectF Bounds{0.0f, 0.0f, 0.0f, 0.0f};\n  glm::vec2 BaseScale;\n\n  float ScaledWidth() const { return Bounds.Width * BaseScale.x; }\n  float ScaledHeight() const { return Bounds.Height * BaseScale.y; }\n  void SetScaledWidth(float scaledWidth) {\n    Bounds.Width = scaledWidth / BaseScale.x;\n  }\n  void SetScaledHeight(float scaledHeight) {\n    Bounds.Height = scaledHeight / BaseScale.y;\n  }\n  RectF ScaledBounds() const {\n    return {0.0f, 0.0f, ScaledWidth(), ScaledHeight()};\n  }\n\n  glm::vec2 Center() const { return ScaledBounds().Center(); }\n\n  RectF NormalizedBounds() const {\n    return RectF(Bounds).Scale(\n        glm::vec2(1.0f / Sheet.DesignWidth, 1.0f / Sheet.DesignHeight),\n        {0.0f, 0.0f});\n  }\n};\n\n}  // namespace Impacto"
  },
  {
    "path": "src/stbi_impl.c",
    "content": "#define STB_IMAGE_IMPLEMENTATION\n#include \"stb_image.h\""
  },
  {
    "path": "src/subtitle/ass/subtitlerenderer.cpp",
    "content": "#include \"subtitlerenderer.h\"\n\n#include <ass/ass.h>\n#include <glm/gtc/type_ptr.hpp>\n#include \"../../log.h\"\n#include \"../../renderer/renderer.h\"\n#include \"../../profile/subtitle.h\"\n#include \"../../profile/game.h\"\n\nnamespace Impacto {\nnamespace Subtitle {\nnamespace Ass {\nstd::unique_ptr<ASS_Library, AssDeleter> SubtitleRenderer::AssLibrary;\n\nusing namespace Profile::Subtitle;\n\nvoid AssMsgHandler(int level, const char* fmt, va_list va, void* data) {\n  LogLevel impLevel = [&level] {\n    switch (level) {\n      case 0:\n        return LogLevel::Fatal;\n      case 1:\n      case 2:\n      case 3:\n        return LogLevel::Error;\n      case 4:\n        return LogLevel::Warning;\n      case 5:\n        return LogLevel::Info;\n      case 6:\n        return LogLevel::Debug;\n      case 7:\n        return LogLevel::Trace;\n    }\n    return LogLevel::Off;\n  }();\n  std::array<char, 512> buffer{};\n  int result = vsnprintf(buffer.data(), buffer.size(), fmt, va);\n  size_t actualSize = std::min(static_cast<size_t>(result), buffer.size() - 1);\n  if (level > 4)\n    ImpLogSlow(impLevel, LogChannel::Subtitle, \"LibAss: {}\\n\",\n               std::string_view{buffer.data(), actualSize});\n  else\n    ImpLog(impLevel, LogChannel::Subtitle, \"LibAss: {}\\n\",\n           std::string_view{buffer.data(), actualSize});\n}\n\nSubtitleRenderTrack::SubtitleRenderTrack(SubtitleRenderer& renderer,\n                                         std::string_view header)\n    : SubRenderer(renderer),\n      AssTrack(ass_new_track(SubtitleRenderer::GetAssLibrary())) {\n  if (!AssTrack) {\n    ImpLog(LogLevel::Error, LogChannel::Subtitle,\n           \"Failed to initialize ass track\\n\");\n    throw std::runtime_error(\"Failed to initialize ass track\\n\");\n  }\n  ass_process_data(AssTrack.get(), header.data(),\n                   static_cast<int>(header.size()));\n}\n\nSubtitleRenderTrack::SubtitleRenderTrack(SubtitleRenderer& renderer,\n                                         std::span<const char> fileBuffer)\n    : SubRenderer(renderer),\n      AssTrack(ass_read_memory(SubtitleRenderer::GetAssLibrary(),\n                               const_cast<char*>(fileBuffer.data()),\n                               fileBuffer.size(), \"UTF-8\")) {\n  if (!AssTrack) {\n    ImpLog(LogLevel::Error, LogChannel::Subtitle,\n           \"Failed to initialize ass track\\n\");\n    throw std::runtime_error(\"Failed to initialize ass track\\n\");\n  }\n}\n\nvoid SubtitleRenderTrack::Push(SubtitleEntry entry) {\n  auto&& pushedEntry = Entries.emplace_back(std::move(entry));\n  EntryCounter++;\n  auto const& assStr = std::get<std::string>(pushedEntry.Data);\n  ass_process_chunk(AssTrack.get(), assStr.c_str(),\n                    static_cast<int>(assStr.size()), entry.StartMs.count(),\n                    entry.Duration.count());\n}\n\nSubtitleRenderTrack::GlyphKey::GlyphKey(ASS_Image const& img)\n    : BitmapHash(0),\n      Dimensions{img.w, img.h},\n      Type(static_cast<AssImageType>(img.type)) {\n  const std::span<const uint8_t> bitmapSpan(img.bitmap,\n                                            (img.stride * (img.h - 1)) + img.w);\n  BitmapHash = GetHashCode(bitmapSpan);\n}\n\nvoid SubtitleRenderTrack::UpdateSubtitleGlyphs(ASS_Image* images) {\n  ankerl::unordered_dense::set<GlyphKey, GlyphKey::hash> removalKeys;\n  std::transform(GlyphTextures.begin(), GlyphTextures.end(),\n                 std::inserter(removalKeys, removalKeys.end()),\n                 [](auto pair) { return pair.first; });\n  SubtitleGlyphs.clear();\n\n  // Hash the bitmap coverage & some ass_img metadata, then cache the texture\n  // This allows us to reuse textures when only some parts of subtitle line\n  // changes, which should be more performant for karaoke effect\n  for (ASS_Image* img = images; img; img = img->next) {\n    SpriteSheet sheet{float(img->w), float(img->h)};\n    auto itr = GlyphTextures.find(*img);\n    if (itr != GlyphTextures.end()) {\n      removalKeys.erase(*img);\n      sheet.Texture = itr->second;\n    } else {\n      Texture t;\n      t.Init(TexFmt_U8, img->w, img->h);\n      for (int y = 0; y < img->h; ++y) {\n        const uint8_t* srcRow = img->bitmap + y * img->stride;\n        std::span<uint8_t> dstRow =\n            std::span(t.Buffer).subspan(y * img->w, img->w);\n        std::memcpy(dstRow.data(), srcRow, img->w);\n      }\n      sheet.Texture = t.Submit();\n      GlyphTextures.try_emplace(GlyphKey(*img), sheet.Texture);\n    }\n    const glm::vec4 tint{\n        ((img->color >> 24) & 0xFF) / 255.0f,\n        ((img->color >> 16) & 0xFF) / 255.0f,\n        ((img->color >> 8) & 0xFF) / 255.0f,\n        (255 - (img->color & 0xFF)) / 255.0f,\n    };\n    const glm::vec2 pos{(float)img->dst_x, (float)img->dst_y};\n    SubtitleGlyphs.emplace_back(\n        Sprite{sheet, 0, 0, (float)img->w, (float)img->h}, tint, pos);\n  }\n\n  for (const auto& key : removalKeys) {\n    Renderer->FreeTexture(GlyphTextures[key]);\n    GlyphTextures.erase(key);\n  }\n}\n\nvoid SubtitleRenderTrack::Render() {\n  int wasChanged = 0;\n  ASS_Image* images =\n      ass_render_frame(SubRenderer.get().GetAssRenderer(), AssTrack.get(),\n                       SubRenderer.get().GetTime().count(), &wasChanged);\n  Change = static_cast<ChangeStatus>(wasChanged);\n  if (Change == ChangeStatus::CONTENT || !images) {\n    UpdateSubtitleGlyphs(images);\n  } else if (Change == ChangeStatus::POSITION) {\n    int i = 0;\n    for (ASS_Image* img = images; img; img = img->next) {\n      SubtitleGlyphs[i].Position =\n          glm::vec2{float(img->dst_x), float(img->dst_y)};\n      i++;\n    }\n  }\n\n  if (!SubtitleGlyphs.empty()) {\n    Renderer->SetBlendMode(RendererBlendMode::Premultiplied);\n    for (const auto& subtitleGlyph : SubtitleGlyphs) {\n      Renderer->DrawSubtitleGlyph(subtitleGlyph.GlyphSprite,\n                                  subtitleGlyph.Position, subtitleGlyph.Tint);\n    }\n    Renderer->SetBlendMode(RendererBlendMode::Normal);\n  }\n}\n\nvoid SubtitleRenderer::InitSystem() {\n  if (!AssLibrary) {\n    AssLibrary.reset(ass_library_init());\n    ass_set_message_cb(AssLibrary.get(), AssMsgHandler, nullptr);\n    ass_set_extract_fonts(AssLibrary.get(), 1);\n\n    for (const auto& dir : SubtitleFontsDir) {\n      ass_set_fonts_dir(AssLibrary.get(), dir.c_str());\n    }\n  }\n  if (!AssLibrary) {\n    ImpLog(LogLevel::Error, LogChannel::Subtitle,\n           \"Failed to initialize ass renderer\\n\");\n    throw std::runtime_error(\"Failed to initialize libass\\n\");\n  }\n}\n\nSubtitleRenderer::SubtitleRenderer(SubtitlePlayer& player, float width,\n                                   float height)\n    : Subtitle::SubtitleRenderer(player, width, height),\n      AssRenderer(ass_renderer_init(AssLibrary.get())) {\n  if (!AssRenderer) {\n    ImpLog(LogLevel::Error, LogChannel::Subtitle,\n           \"Failed to initialize ass renderer\\n\");\n    throw std::runtime_error(\"Failed to initialize ass renderer\\n\");\n  }\n  ass_set_storage_size(AssRenderer.get(), static_cast<int>(Width),\n                       static_cast<int>(Height));\n\n  ass_set_frame_size(AssRenderer.get(), static_cast<int>(Width),\n                     static_cast<int>(Height));\n  ass_set_fonts(AssRenderer.get(), NULL, \"sans-serif\",\n                ASS_FONTPROVIDER_AUTODETECT, NULL, 1);\n}\n\nvoid SubtitleRenderer::AddTrack(int trackId, SubtitleRenderTrack&& track) {\n  auto [ref, inserted] = Tracks.try_emplace(trackId, std::move(track));\n  if (!inserted) {\n    ImpLog(LogLevel::Error, LogChannel::Subtitle,\n           \"Attempted to insert to existing Track ID {:d}\", trackId);\n  }\n}\n\nvoid SubtitleRenderer::AddTrackFile(int trackId,\n                                    std::span<const char> fileBuffer) {\n  auto [ref, inserted] = Tracks.try_emplace(trackId, *this, fileBuffer);\n  if (!inserted) {\n    ImpLog(LogLevel::Error, LogChannel::Subtitle,\n           \"Attempted to insert to existing Track ID {:d}\", trackId);\n  }\n}\n\nvoid SubtitleRenderer::SetTrackVisibility(int trackId, bool enable) {\n  if (auto trackItr = Tracks.find(trackId); trackItr != Tracks.end()) {\n    trackItr->second.Visible = enable;\n  }\n}\n\nvoid SubtitleRenderer::AddSubtitleEntry(int trackId, SubtitleEntry entry) {\n  if (auto trackItr = Tracks.find(trackId); trackItr != Tracks.end()) {\n    trackItr->second.Push(std::move(entry));\n  } else {\n    ImpLog(LogLevel::Error, LogChannel::Subtitle, \"Track ID {:d} not found\",\n           trackId);\n  }\n}\n\nvoid SubtitleRenderer::Update(Video::Clock::Microseconds elapsedTime) {\n  ImpLogSlow(LogLevel::Trace, LogChannel::Subtitle,\n             \"Updating subtitle time: {}\", elapsedTime);\n  Timer = std::chrono::duration_cast<std::chrono::milliseconds>(elapsedTime);\n}\n\nvoid SubtitleRenderer::Render() {\n  for (auto& [id, track] : Tracks) {\n    if (!track.Visible) continue;\n    track.Render();\n  }\n}\n\n};  // namespace Ass\n};  // namespace Subtitle\n};  // namespace Impacto\n"
  },
  {
    "path": "src/subtitle/ass/subtitlerenderer.h",
    "content": "#pragma once\n#include <vector>\n#include <ass/ass.h>\n#include \"../subtitlerenderer.h\"\n#include \"../../spritesheet.h\"\n#include \"../../profile/subtitle.h\"\n\nnamespace Impacto {\nnamespace Subtitle {\nnamespace Ass {\n\nclass SubtitleRenderer;\nstruct AssDeleter {\n  void operator()(ASS_Track* ptr) const {\n    ass_free_track(ptr);\n    ptr = nullptr;\n  }\n  void operator()(ASS_Renderer* ptr) const {\n    ass_renderer_done(ptr);\n    ptr = nullptr;\n  }\n  void operator()(ASS_Library* ptr) const {\n    ass_library_done(ptr);\n    ptr = nullptr;\n  }\n};\n\nclass SubtitleRenderTrack {\n public:\n  using BackendType = SubtitleRenderer;\n  SubtitleRenderTrack(SubtitleRenderer& renderer, std::string_view header);\n  SubtitleRenderTrack(SubtitleRenderer& renderer,\n                      std::span<const char> fileBuffer);\n  void Push(SubtitleEntry entry);\n  void Render();\n\n  bool Visible = true;\n\n private:\n  enum class ChangeStatus : uint8_t {\n    UNCHANGED = 0,\n    POSITION = 1,\n    CONTENT = 2\n  };\n\n  struct GlyphKey {\n    enum AssImageType : uint8_t {\n      Character,\n      Outline,\n      Shadow,\n    };\n    GlyphKey(ASS_Image const& img);\n    uint64_t BitmapHash;\n    glm::ivec2 Dimensions;\n    AssImageType Type;\n\n    friend bool operator==(const GlyphKey& lhs, const GlyphKey& rhs) {\n      return lhs.BitmapHash == rhs.BitmapHash &&\n             lhs.Dimensions == rhs.Dimensions && lhs.Type == rhs.Type;\n    }\n\n    struct hash {\n      size_t operator()(const GlyphKey& k) const {\n        std::size_t seed = 0;\n        HashCombine(seed, k.BitmapHash, k.Dimensions.x, k.Dimensions.y,\n                    to_underlying(k.Type));\n        return seed;\n      }\n    };\n  };\n\n  struct SubtitleGlyph {\n    Sprite GlyphSprite;\n    glm::vec4 Tint;\n    glm::vec2 Position;\n  };\n\n  void UpdateSubtitleGlyphs(ASS_Image* images);\n\n  std::vector<SubtitleEntry> Entries;\n  std::vector<SubtitleGlyph> SubtitleGlyphs;\n  ankerl::unordered_dense::map<GlyphKey, uint32_t, GlyphKey::hash>\n      GlyphTextures;\n\n  std::reference_wrapper<SubtitleRenderer> SubRenderer;\n  std::unique_ptr<ASS_Track, AssDeleter> AssTrack;\n  size_t EntryCounter = 0;\n  ChangeStatus Change;\n};\n\nclass SubtitleRenderer : public Subtitle::SubtitleRenderer {\n public:\n  constexpr static Profile::Subtitle::SubtitleType Type =\n      Profile::Subtitle::SubtitleType::Ass;\n  SubtitleRenderer(SubtitlePlayer& player, float width, float height);\n  void AddTrack(int trackId, SubtitleRenderTrack&& track);\n  void AddTrackFile(int trackId, std::span<const char> fileBuffer) override;\n  void AddSubtitleEntry(int trackId, SubtitleEntry entry) override;\n\n  void Update(Video::Clock::Microseconds elapsedTime) override;\n  void Render() override;\n  void SetTrackVisibility(int trackId, bool enable) override;\n  std::chrono::milliseconds GetTime() const { return Timer; }\n\n  static void InitSystem();\n  static ASS_Library* GetAssLibrary() { return AssLibrary.get(); }\n  ASS_Renderer* GetAssRenderer() const { return AssRenderer.get(); }\n\n private:\n  static std::unique_ptr<ASS_Library, AssDeleter> AssLibrary;\n  std::unique_ptr<ASS_Renderer, AssDeleter> AssRenderer;\n  ankerl::unordered_dense::map<int, SubtitleRenderTrack> Tracks;\n  std::chrono::milliseconds Timer;\n};\n\n}  // namespace Ass\n}  // namespace Subtitle\n}  // namespace Impacto"
  },
  {
    "path": "src/subtitle/ffmpegsubtitlehelper.cpp",
    "content": "#include \"ffmpegsubtitlehelper.h\"\n\n#include <avcpp/packet.h>\n\nnamespace Impacto::Subtitle {\n// avcpp codeccontext.cpp\nstd::pair<int, const std::error_category*> make_error_pair(av::Errors errc) {\n  return std::make_pair(static_cast<int>(errc), &av::avcpp_category());\n}\n\n// avcpp codeccontext.cpp\nstd::pair<int, const std::error_category*> make_error_pair(int status) {\n  if (status < 0) return std::make_pair(status, &av::ffmpeg_category());\n  return std::make_pair(status, nullptr);\n}\n\nvoid SubtitleData::setTimeBase(const av::Rational& value) {\n  if (m_timeBase == value) return;\n\n  int64_t rescaledPts = av::NoPts;\n\n  if (m_timeBase != av::Rational() && value != av::Rational()) {\n    if (m_raw.pts != av::NoPts)\n      rescaledPts = m_timeBase.rescale(m_raw.pts, value);\n  } else {\n    rescaledPts = m_raw.pts;\n  }\n\n  if (m_timeBase != av::Rational()) {\n    m_raw.pts = rescaledPts;\n  }\n\n  m_timeBase = value;\n}\n\nSubtitleDecoderContext::SubtitleDecoderContext(SubtitleDecoderContext&& other)\n    : Parent(std::move(other)) {}\n\n// avcpp codeccontext.cpp\nstd::pair<int, const std::error_category*> SubtitleDecoderContext::decodeCommon(\n    AVSubtitle* outSub, const av::Packet& inPacket, size_t offset,\n    int& frameFinished,\n    int (*decodeProc)(AVCodecContext*, AVSubtitle*, int*,\n                      const AVPacket*)) noexcept {\n  if (!isValid()) return make_error_pair(av::Errors::CodecInvalid);\n\n  if (!isOpened()) return make_error_pair(av::Errors::CodecNotOpened);\n\n  if (!decodeProc) return make_error_pair(av::Errors::CodecInvalidDecodeProc);\n\n  if (offset && inPacket.size() && offset >= inPacket.size())\n    return make_error_pair(av::Errors::CodecDecodingOffsetToLarge);\n\n  frameFinished = 0;\n\n  AVPacket pkt = *inPacket.raw();\n  pkt.data += offset;\n  pkt.size -= static_cast<int>(offset);\n\n  int decoded = decodeProc(m_raw, outSub, &frameFinished, &pkt);\n  return make_error_pair(decoded);\n}\nstd::pair<int, const std::error_category*> SubtitleDecoderContext::decodeCommon(\n    SubtitleData& outSubtitle, const av::Packet& inPacket, size_t offset,\n    int& frameFinished,\n    int (*decodeProc)(AVCodecContext*, AVSubtitle*, int*,\n                      const AVPacket*)) noexcept {\n  auto st = decodeCommon(outSubtitle.raw(), inPacket, offset, frameFinished,\n                         decodeProc);\n  if (std::get<1>(st)) return st;\n\n  if (!frameFinished) return std::make_pair(0u, nullptr);\n\n  // Dial with PTS/DTS in packet/stream timebase\n\n  if (inPacket.timeBase() != av::Rational())\n    outSubtitle.setTimeBase(inPacket.timeBase());\n#if AVCPP_HAS_AVFORMAT\n  else\n    outSubtitle.setTimeBase(stream().timeBase());\n#endif  // if AVCPP_HAS_AVFORMAT\n\n  AVSubtitle* subtitle = outSubtitle.raw();\n\n  if (subtitle->pts == av::NoPts) subtitle->pts = inPacket.raw()->pts;\n\n  // Convert to decoder/frame time base. Seems not nessesary.\n  outSubtitle.setTimeBase(timeBase());\n\n  if (inPacket) outSubtitle.setStreamIndex(inPacket.streamIndex());\n#if AVCPP_HAS_AVFORMAT\n  else\n    outSubtitle.setStreamIndex(stream().index());\n#endif  // if AVCPP_HAS_AVFORMAT\n\n  outSubtitle.setComplete(true);\n\n  return st;\n}\n\nSubtitleData SubtitleDecoderContext::decodeSubtitle(av::OptionalErrorCode ec,\n                                                    const av::Packet& packet,\n                                                    size_t offset,\n                                                    size_t* decodedBytes) {\n  clear_if(ec);\n\n  SubtitleData outFrame;\n\n  int gotFrame = 0;\n  auto st = decodeCommon(outFrame, packet, offset, gotFrame,\n                         [](AVCodecContext* avctx, AVSubtitle* subtitle,\n                            int* got_picture_ptr, const AVPacket* avpkt) {\n                           return avcodec_decode_subtitle2(\n                               avctx, subtitle, got_picture_ptr, avpkt);\n                         });\n\n  if (get<1>(st)) {\n    throws_if(ec, get<0>(st), *get<1>(st));\n    return SubtitleData();\n  }\n\n  if (!gotFrame) return SubtitleData();\n\n  if (decodedBytes) *decodedBytes = get<0>(st);\n\n  return outFrame;\n}\n\nSubtitleData SubtitleDecoderContext::decode(const av::Packet& packet,\n                                            av::OptionalErrorCode ec) {\n  return decodeSubtitle(ec, packet, 0, nullptr);\n}\n\nSubtitleData SubtitleDecoderContext::decode(const av::Packet& packet,\n                                            size_t offset, size_t& decodedBytes,\n                                            av::OptionalErrorCode ec) {\n  return decodeSubtitle(ec, packet, offset, &decodedBytes);\n}\n\n}  // namespace Impacto::Subtitle"
  },
  {
    "path": "src/subtitle/ffmpegsubtitlehelper.h",
    "content": "#pragma once\n\n#include <avcpp/codeccontext.h>\n#include <avcpp/ffmpeg.h>\n\n// Since these are basically extension classes for avcpp, we'll use their naming\n// conventions for it for consistency\n\nnamespace Impacto::Subtitle {\n\nstruct SubtitleData {\n  SubtitleData() = default;\n  SubtitleData(AVSubtitle sub) : m_raw(sub) {};\n  SubtitleData(SubtitleData&& other) {\n    if (this == &other) return;\n    m_raw = other.m_raw;\n    m_timeBase = other.m_timeBase;\n    m_streamIndex = other.m_streamIndex;\n    m_isComplete = other.m_isComplete;\n\n    // invalidate the moved-from object so it doesn't free the AVSubtitle\n    other.m_raw.rects = nullptr;\n  }\n  SubtitleData(SubtitleData const& other) = delete;\n  ~SubtitleData() {\n    if (m_raw.rects) avsubtitle_free(&m_raw);\n  }\n\n  SubtitleData& operator=(SubtitleData const& other) = delete;\n  SubtitleData& operator=(SubtitleData&& other) {\n    if (this == &other) return *this;\n    avsubtitle_free(&m_raw);\n    m_raw = other.m_raw;\n    m_timeBase = other.m_timeBase;\n    m_streamIndex = other.m_streamIndex;\n    m_isComplete = other.m_isComplete;\n\n    // invalidate the moved-from object so it doesn't free the AVSubtitle\n    other.m_raw.rects = nullptr;\n\n    return *this;\n  }\n\n  AVSubtitle* raw() { return &m_raw; }\n\n  void setTimeBase(const av::Rational& value);\n\n  av::Timestamp pts() const { return {m_raw.pts, m_timeBase}; }\n\n  void setPts(const av::Timestamp& ts) {\n    if (m_timeBase == av::Rational()) m_timeBase = ts.timebase();\n    m_raw.pts = ts.timestamp(m_timeBase);\n  }\n\n  const av::Rational& timeBase() const { return m_timeBase; }\n\n  int streamIndex() const { return m_streamIndex; }\n\n  void setStreamIndex(int streamIndex) { m_streamIndex = streamIndex; }\n\n  void setComplete(bool isComplete) { m_isComplete = isComplete; }\n\n  bool isComplete() const { return m_isComplete; }\n\n  bool isValid() const { return m_raw.rects; }\n\n  operator bool() const { return isValid() && isComplete(); }\n\n  AVSubtitle m_raw{};\n\n protected:\n  av::Rational m_timeBase{};\n  int m_streamIndex{-1};\n  bool m_isComplete{false};\n};\n\n// extension of avcpp, codeccontext.h\ntemplate <typename Clazz, av::Direction _direction>\nclass SubtitleCodecContext\n    : public av::CodecContextBase<Clazz, _direction, AVMEDIA_TYPE_SUBTITLE> {\n public:\n  using Parent = av::CodecContextBase<Clazz, _direction, AVMEDIA_TYPE_SUBTITLE>;\n\n  using Parent::_log;\n  using Parent::isOpened;\n  using Parent::isValid;\n  using Parent::Parent;\n\n  std::string_view subtitleHeader() const {\n    return std::string_view{reinterpret_cast<char*>(m_raw->subtitle_header),\n                            static_cast<size_t>(m_raw->subtitle_header_size)};\n  }\n\n protected:\n  using Parent::m_raw;\n  using Parent::moveOperator;\n};\n\nclass SubtitleDecoderContext\n    : public SubtitleCodecContext<SubtitleDecoderContext,\n                                  av::Direction::Decoding> {\n public:\n  using Parent =\n      SubtitleCodecContext<SubtitleDecoderContext, av::Direction::Decoding>;\n  using Parent::Parent;\n  SubtitleDecoderContext() = default;\n  SubtitleDecoderContext(SubtitleDecoderContext&& other);\n\n  std::pair<int, const std::error_category*> decodeCommon(\n      AVSubtitle* outSubtitle, const class av::Packet& inPacket, size_t offset,\n      int& frameFinished,\n      int (*decodeProc)(AVCodecContext*, AVSubtitle*, int*,\n                        const AVPacket*)) noexcept;\n\n  std::pair<int, const std::error_category*> decodeCommon(\n      SubtitleData& outSubtitle, const class av::Packet& inPacket,\n      size_t offset, int& frameFinished,\n      int (*decodeProc)(AVCodecContext*, AVSubtitle*, int*,\n                        const AVPacket*)) noexcept;\n\n  SubtitleDecoderContext& operator=(SubtitleDecoderContext&& other);\n\n  /**\n   * @brief decode  - decode subtitle packet\n   *\n   * @param packet   packet to decode\n   * @param[in,out] ec     this represents the error status on exit, if this is\n   * pre-initialized to av#throws the function will throw on error instead\n   * @return decoded subtitle frame, if error: exception thrown or error code\n   * returns, in both cases output undefined.\n   */\n  SubtitleData decode(const av::Packet& packet,\n                      av::OptionalErrorCode ec = av::throws());\n\n  /**\n   * @brief decode - decode subtitle packet with additional parameters\n   *\n   * @param[in] packet         packet to decode\n   * @param[in] offset         data offset in packet\n   * @param[out] decodedBytes  amount of decoded bytes\n   * @param[in,out] ec     this represents the error status on exit, if this is\n   * pre-initialized to av#throws the function will throw on error instead\n   * @return decoded subtitle frame, if error: exception thrown or error code\n   * returns, in both cases output undefined.\n   */\n  SubtitleData decode(const av::Packet& packet, size_t offset,\n                      size_t& decodedBytes,\n                      av::OptionalErrorCode ec = av::throws());\n\n private:\n  SubtitleData decodeSubtitle(av::OptionalErrorCode ec,\n                              const av::Packet& packet, size_t offset,\n                              size_t* decodedBytes);\n};\n\n}  // namespace Impacto::Subtitle"
  },
  {
    "path": "src/subtitle/subtitlerenderer.h",
    "content": "#pragma once\n\n#include <vector>\n#include <libavcodec/avcodec.h>\n#include <avcpp/timestamp.h>\n#include <ankerl/unordered_dense.h>\n#include <memory>\n#include <concepts>\n#include <variant>\n\n#include \"../video/clock.h\"\n\nnamespace Impacto::Subtitle {\nclass SubtitlePlayer;\n\nstruct SubtitleEntry {\n  struct BitmapData {\n    int X{};\n    int Y{};\n    int W{};\n    int H{};\n    int NbColors{};\n    std::array<std::unique_ptr<uint8_t[]>, 4> Data{};\n    int LineSize[4]{};\n  };\n\n  std::variant<std::monostate, BitmapData, std::string> Data;\n  int Flags{};\n  std::chrono::milliseconds StartMs{};\n  std::chrono::milliseconds Duration{};\n};\n\nclass SubtitleRenderer {\n public:\n  SubtitleRenderer(SubtitlePlayer& player, float width, float height)\n      : Player(player), Width(width), Height(height) {}\n  virtual ~SubtitleRenderer() noexcept = default;\n  virtual void AddTrackFile(int trackId, std::span<const char> fileBuffer) = 0;\n  virtual void AddSubtitleEntry(int trackId, SubtitleEntry entry) = 0;\n  virtual void Update(Video::Clock::Microseconds elapsedTime) = 0;\n  virtual void Render() = 0;\n  virtual void SetTrackVisibility(int trackId, bool enable) = 0;\n  float GetWidth() const { return Width; }\n  float GetHeight() const { return Height; }\n\n protected:\n  std::reference_wrapper<SubtitlePlayer> Player;\n  float Width;\n  float Height;\n};\n\n}  // namespace Impacto::Subtitle"
  },
  {
    "path": "src/subtitle/subtitlesystem.cpp",
    "content": "#include <fmt/format.h>\n#include \"subtitlesystem.h\"\n\n#ifndef IMPACTO_DISABLE_ASS\n#include \"ass/subtitlerenderer.h\"\n#endif\n\n#include \"../util.h\"\n#include \"../profile/game.h\"\n#include \"../profile/subtitle.h\"\n#include \"../log.h\"\n#include \"../io/physicalfilestream.h\"\n\nnamespace Impacto::Subtitle {\nusing namespace Profile::Subtitle;\n\nvoid SubtitleInit() {\n  switch (Profile::SubtitleAssBackend) {\n#ifndef IMPACTO_DISABLE_LIBASS\n    case SubtitleAssBackendType::LibAss:\n      Ass::SubtitleRenderer::InitSystem();\n      break;\n#endif\n    case SubtitleAssBackendType::None:\n      break;\n    default:\n      ImpLog(LogLevel::Warning, LogChannel::Subtitle,\n             \"Unknown or unsupported ass subtitle backend selected!.\\n\");\n  }\n  switch (Profile::SubtitleTextBackend) {\n    case SubtitleTextBackendType::None:\n      break;\n    default:\n      ImpLog(LogLevel::Warning, LogChannel::Subtitle,\n             \"Unknown or unsupported text subtitle backend selected!.\\n\");\n  }\n  switch (Profile::SubtitleBmpBackend) {\n    case SubtitleBmpBackendType::None:\n      break;\n    default:\n      ImpLog(LogLevel::Warning, LogChannel::Subtitle,\n             \"Unknown or unsupported bitmap subtitle backend selected!.\\n\");\n  }\n}\n\nSubtitlePlayer::SubtitlePlayer(float width, float height) {\n  switch (Profile::SubtitleAssBackend) {\n#ifndef IMPACTO_DISABLE_LIBASS\n    case SubtitleAssBackendType::LibAss:\n      Backends[GetBackendIndex(SubtitleType::Ass)] =\n          std::make_unique<Ass::SubtitleRenderer>(*this, width, height);\n      break;\n#endif\n    case SubtitleAssBackendType::None:\n      break;\n  }\n  switch (Profile::SubtitleTextBackend) {\n    case SubtitleTextBackendType::None:\n      break;\n  }\n  switch (Profile::SubtitleBmpBackend) {\n    case SubtitleBmpBackendType::None:\n      break;\n  }\n}\n\nbool SubtitlePlayer::CanAddTrack(int trackId, SubtitleType type,\n                                 Profile::SubtitleConfigType config) const {\n  const auto& backend = Backends[GetBackendIndex(type)];\n  if (!backend) {\n    ImpLog(LogLevel::Warning, LogChannel::Subtitle,\n           \"Subtitle backend not initialized for {}\", type);\n    return false;\n  }\n  if ((+config & +Profile::SubtitleConfig) == 0) {\n    ImpLog(LogLevel::Info, LogChannel::Subtitle,\n           \"Current subtitle mode is {}, skipping track {}, which is mode {}\",\n           Profile::SubtitleConfig, trackId, config);\n    return false;\n  }\n  return true;\n}\n\nvoid SubtitlePlayer::AddTrackFile(int trackId, SubtitleType type,\n                                  std::string const& path,\n                                  Profile::SubtitleConfigType config) {\n  if (!CanAddTrack(trackId, type, config)) return;\n  auto& backend = Backends[GetBackendIndex(type)];\n  Io::Stream* subtitleFileStream{};\n  if (Io::PhysicalFileStream::Create(path, &subtitleFileStream) !=\n      IoError::IoError_OK) {\n    ImpLog(LogLevel::Warning, LogChannel::Subtitle,\n           \"Failed to read subtitle file {}\", path);\n    return;\n  }\n  std::vector<char> dataBuffer;\n\n  dataBuffer.resize(subtitleFileStream->Meta.Size);\n  subtitleFileStream->Read(dataBuffer.data(), dataBuffer.size());\n  backend->AddTrackFile(trackId, dataBuffer);\n  delete subtitleFileStream;\n}\n\nvoid SubtitlePlayer::PushEntry(int trackId, SubtitleEntry entry) {\n  auto trackTypeItr = TrackTypeMap.find(trackId);\n  if (trackTypeItr == TrackTypeMap.end()) {\n    ImpLog(LogLevel::Warning, LogChannel::Subtitle,\n           \"Track {} not found in Subtitle Track Map\", trackId);\n    return;\n  }\n  auto type = trackTypeItr->second;\n  const auto backendIndex = GetBackendIndex(type);\n  auto& backend = Backends[backendIndex];\n  if (!backend) {\n    ImpLog(LogLevel::Warning, LogChannel::Subtitle,\n           \"Subtitle backend not initialized for {}\", type);\n    return;\n  }\n  backend->AddSubtitleEntry(trackId, std::move(entry));\n}\n\nvoid SubtitlePlayer::UpdateElapsedTime(Video::Clock::Microseconds elapsedTime) {\n  for (auto& backend : Backends) {\n    if (backend) {\n      backend->Update(elapsedTime);\n    };\n  }\n}\n\nvoid SubtitlePlayer::Render() {\n  for (auto& backend : Backends) {\n    if (backend) {\n      backend->Render();\n    };\n  }\n}\n\nint8_t SubtitlePlayer::GetBackendIndex(SubtitleType type) const {\n  switch (type) {\n    case SubtitleType::Ass:\n      return 0;\n    case SubtitleType::Bitmap:\n      return 1;\n    case SubtitleType::Text:\n      return 2;\n    case SubtitleType::None:\n      break;\n  }\n  throw std::runtime_error(fmt::format(\"Invalid SubtitleType {}\", type));\n}\n\n}  // namespace Impacto::Subtitle"
  },
  {
    "path": "src/subtitle/subtitlesystem.h",
    "content": "#pragma once\n\n#include <array>\n#include <memory>\n#include <vector>\n#include <variant>\n#include <string>\n#include <utility>\n\n#include \"subtitlerenderer.h\"\n#include \"../video/clock.h\"\n#include \"../profile/subtitle.h\"\n\nnamespace Impacto::Subtitle {\n\ntemplate <typename Track, typename... TrackArgs>\nconcept HasAddTrack = requires {\n  typename Track::BackendType;\n  requires std::is_base_of_v<SubtitleRenderer, typename Track::BackendType>;\n\n  {\n    std::declval<typename Track::BackendType&>().AddTrack(\n        std::declval<int>(), Track(std::declval<typename Track::BackendType&>(),\n                                   std::declval<TrackArgs>()...))\n  } -> std::same_as<void>;\n};\nclass SubtitlePlayer {\n public:\n  SubtitlePlayer(float width, float height);\n\n  template <typename Track, typename... TrackArgs>\n    requires HasAddTrack<Track, TrackArgs...>\n  void AddTrack(int trackId, Profile::SubtitleConfigType config,\n                TrackArgs&&... args);\n  void AddTrackFile(int trackId, Profile::Subtitle::SubtitleType type,\n                    std::string const& path,\n                    Profile::SubtitleConfigType config);\n  void PushEntry(int trackId, SubtitleEntry entry);\n  void UpdateElapsedTime(Video::Clock::Microseconds elapsedTime);\n  void Render();\n\n protected:\n  int8_t GetBackendIndex(Profile::Subtitle::SubtitleType type) const;\n  bool CanAddTrack(int trackId, Profile::Subtitle::SubtitleType type,\n                   Profile::SubtitleConfigType config) const;\n\n  std::array<std::unique_ptr<SubtitleRenderer>, 3> Backends;\n  ankerl::unordered_dense::map<int, Profile::Subtitle::SubtitleType>\n      TrackTypeMap;\n};\n\nvoid SubtitleInit();\n\ntemplate <typename Track, typename... TrackArgs>\n  requires HasAddTrack<Track, TrackArgs...>\ninline void SubtitlePlayer::AddTrack(int trackId,\n                                     Profile::SubtitleConfigType config,\n                                     TrackArgs&&... args) {\n  constexpr auto type = Track::BackendType::Type;\n  if (!CanAddTrack(trackId, type, config)) return;\n  auto* backend =\n      dynamic_cast<Track::BackendType*>(Backends[GetBackendIndex(type)].get());\n  if (!backend) {\n    throw std::runtime_error(\"Invalid subtitle track type\");\n  }\n  backend->AddTrack(trackId, Track(*backend, std::forward<TrackArgs>(args)...));\n  TrackTypeMap.try_emplace(trackId, type);\n}\n}  // namespace Impacto::Subtitle"
  },
  {
    "path": "src/text/dialoguepage.cpp",
    "content": "#include \"dialoguepage.h\"\n\n#include \"../profile/configsystem.h\"\n#include \"../profile/dialogue.h\"\n#include \"../profile/games/chlcc/dialoguebox.h\"\n\n#include \"../data/tipssystem.h\"\n\n#include \"../hud/waiticondisplay.h\"\n#include \"../hud/autoicondisplay.h\"\n#include \"../hud/skipicondisplay.h\"\n#include \"../hud/nametagdisplay.h\"\n#include \"../hud/dialoguebox.h\"\n#include \"../hud/tipsnotification.h\"\n\n#include \"../audio/audiostream.h\"\n#include \"../audio/audiosystem.h\"\n#include \"../audio/audiostream.h\"\n#include \"../audio/audiochannel.h\"\n\nnamespace Impacto {\nusing namespace Impacto::Profile::Dialogue;\nusing namespace Impacto::Profile::ScriptVars;\n\nDialoguePage::State DialoguePage::GetState() const {\n  using enum State;\n\n  if (Typewriter.IsOut()) return Initial;\n  if (Typewriter.IsPlaying()) return Showing;\n  // Typewriter is in\n\n  if (TextFadeAnimation.IsIn()) return Shown;\n  if (TextFadeAnimation.IsPlaying()) return Hiding;\n  // Text fade animation is out\n\n  return Hidden;\n}\n\nbool DialoguePage::TextIsFullyOpaque() { return Typewriter.Progress == 1.0f; }\n\nvoid DialoguePage::Init() {\n  Profile::Dialogue::Configure();\n\n  WaitIconDisplay::Init();\n  AutoIconDisplay::Init();\n  SkipIconDisplay::Init();\n\n  DialoguePages.resize(PageCount);\n  for (DialoguePage& page : DialoguePages) {\n    page.Glyphs.reserve(MaxPageSize);\n  }\n\n  for (int i = 0; i < std::ssize(DialoguePages); i++) {\n    DialoguePages[i].Clear();\n    DialoguePages[i].Mode = DPM_NVL;\n    DialoguePages[i].Id = i;\n    DialoguePages[i].AnimationId = 0;\n    DialoguePages[i].FadeAnimation.DurationIn = FadeInDuration;\n    DialoguePages[i].FadeAnimation.DurationOut = FadeOutDuration;\n    DialoguePages[i].FadeAnimation.SkipOnSkipMode = true;\n\n    DialoguePages[i].TextFadeAnimation.DurationIn = TextFadeInDuration;\n    DialoguePages[i].TextFadeAnimation.DurationOut = TextFadeOutDuration;\n    DialoguePages[i].TextFadeAnimation.SkipOnSkipMode = true;\n    DialoguePages[i].TextFadeAnimation.Progress = 1.0f;\n  }\n}\n\nvoid DialoguePage::Clear() {\n  Glyphs.clear();\n  Name.clear();\n  std::fill(RubyChunks.begin(), RubyChunks.end(), RubyChunk{});\n  RubyChunkCount = 0;\n  CurrentRubyChunk = 0;\n  FirstRubyChunkOnLine = 0;\n  if (Mode == DPM_ADV) {\n    CurrentLineTop = ADVBounds.Y;\n  } else if (Mode == DPM_REV) {\n    if (ScrWork[SW_MESWIN0TYPE] == 0) {\n      CurrentLineTop = REVBounds.Y;\n    } else {\n      CurrentLineTop = SecondaryREVBounds.Y;\n    }\n  } else if (Mode == DPM_TIPS) {\n    CurrentLineTop = TipsBounds.Y;\n  } else {\n    CurrentLineTop = NVLBounds.Y;\n  }\n  CurrentLineTopMargin = 0.0f;\n  AdvanceMethod = AdvanceMethodType::Skip;\n}\n\nenum TextParseState { TPS_Normal, TPS_Name, TPS_Ruby };\n\nvoid DialoguePage::FinishLine(Vm::Sc3VmThread* ctx, size_t nextLineStart,\n                              const RectF& boxBounds, TextAlignment alignment) {\n  // Lay out all ruby chunks on this line (before we change CurrentLineTop and\n  // thus can't find where to put them)\n  for (size_t i = FirstRubyChunkOnLine; i < RubyChunkCount; i++) {\n    RubyChunk& chunk = RubyChunks[i];\n\n    if (chunk.FirstBaseCharacter >= nextLineStart) break;\n\n    Vm::Sc3Stream rubyText(chunk.RawText.data());\n\n    glm::vec2 pos =\n        glm::vec2(Glyphs[chunk.FirstBaseCharacter].DestRect.X,\n                  CurrentLineTop + CurrentLineTopMargin + RubyYOffset);\n\n    // ruby base length > ruby text length: block align\n    // ruby base length > ruby text length and 0x1E: center per character\n    // ruby base length == ruby text length: center per character\n    // ruby base length < ruby text length: center over block (handled by\n    // block align)\n\n    if (chunk.Length == chunk.BaseLength ||\n        (chunk.CenterPerCharacter && chunk.BaseLength > chunk.Length)) {\n      // center every ruby character over the base character below it\n      for (size_t j = 0; j < chunk.Length; j++) {\n        RectF const& baseGlyphRect =\n            Glyphs[chunk.FirstBaseCharacter + j].DestRect;\n        pos.x = baseGlyphRect.Center().x;\n        TextLayoutPlainLine(rubyText, 1, std::span(chunk.Text.begin() + j, 1),\n                            DialogueFont, RubyFontSize, ColorTable[0], 1.0f,\n                            pos, TextAlignment::Center);\n      }\n    } else {\n      TextLayoutPlainLine(rubyText, static_cast<int>(chunk.Length), chunk.Text,\n                          DialogueFont, RubyFontSize, ColorTable[0], 1.0f, pos,\n                          TextAlignment::Left);\n      const float baseWidth =\n          (Glyphs[chunk.FirstBaseCharacter + chunk.BaseLength - 1].DestRect.X +\n           Glyphs[chunk.FirstBaseCharacter + chunk.BaseLength - 1]\n               .DestRect.Width) -\n          Glyphs[chunk.FirstBaseCharacter].DestRect.X;\n      const float nonSpacedWidth =\n          (chunk.Text[chunk.Length - 1].DestRect.X +\n           chunk.Text[chunk.Length - 1].DestRect.Width) -\n          chunk.Text[0].DestRect.X;\n      const float excessWidth = baseWidth - nonSpacedWidth;\n\n      if (chunk.Length == 1) {\n        chunk.Text[0].DestRect.X += baseWidth / 2.0f;\n      } else if (excessWidth <= 0.0f) {\n        // Ruby overflows => center over base with normal spacing\n        const float offsetX = (baseWidth - nonSpacedWidth) / 2.0f;\n        for (size_t rubyGlyphId = 0; rubyGlyphId < chunk.Length;\n             rubyGlyphId++) {\n          chunk.Text[rubyGlyphId].DestRect.X += offsetX;\n        }\n      } else {\n        // Evenly space out all ruby characters over the block of base text\n        const float extraSpacing = excessWidth / (chunk.Length - 1);\n        for (size_t rubyGlyphId = 0; rubyGlyphId < chunk.Length;\n             rubyGlyphId++) {\n          chunk.Text[rubyGlyphId].DestRect.X += extraSpacing * rubyGlyphId;\n        }\n      }\n\n      // TODO is this really the right behaviour for\n      // CenterPerCharacter(0x1E) and ruby base length < ruby text length?\n    }\n    FirstRubyChunkOnLine++;\n  }\n\n  float lineHeight = FontSize;\n  // Erin DialogueBox\n  if (DialogueBoxCurrentType == DialogueBoxType::CHLCC && Mode == DPM_REV &&\n      ScrWork[SW_MESWIN0TYPE] == 1) {\n    lineHeight = Impacto::Profile::CHLCC::DialogueBox::REVLineHeight;\n  }\n  // Glyphs of different font sizes are bottom-aligned within the line\n  for (size_t i = LastLineStart; i < nextLineStart; i++) {\n    if (Glyphs[i].DestRect.Height > lineHeight)\n      lineHeight = Glyphs[i].DestRect.Height;\n  }\n\n  // completely trial and error guess\n  if (CurrentLineTopMargin) {\n    CurrentLineTopMargin *= (FontSize / DefaultFontSize);\n  }\n  float marginXOffset = 0;\n  if (LastLineStart < nextLineStart &&\n      Glyphs[LastLineStart].DestRect.X > boxBounds.X) {\n    marginXOffset = (Glyphs[LastLineStart].DestRect.X - boxBounds.X) *\n                    ((FontSize / DefaultFontSize) - 1.0f);\n  }\n  for (size_t i = LastLineStart; i < nextLineStart; i++) {\n    Glyphs[i].DestRect.Y = CurrentLineTop + CurrentLineTopMargin +\n                           (lineHeight - Glyphs[i].DestRect.Height);\n    float lastGlyphX = Glyphs[nextLineStart - 1].DestRect.X +\n                       Glyphs[nextLineStart - 1].DestRect.Width;\n    switch (alignment) {\n      case TextAlignment::Center:\n        Glyphs[i].DestRect.X +=\n            (boxBounds.Width - (lastGlyphX - boxBounds.X)) / 2.0f;\n        break;\n      case TextAlignment::Right:\n        Glyphs[i].DestRect.X += boxBounds.Width - lastGlyphX - marginXOffset;\n        break;\n      case TextAlignment::Left:\n        Glyphs[i].DestRect.X += marginXOffset;\n      default:\n        break;\n    }\n  }\n  float lineSpacing = DialogueFont->LineSpacing;\n  // Erin DialogueBox\n  if (DialogueBoxCurrentType == DialogueBoxType::CHLCC && Mode == DPM_REV &&\n      ScrWork[SW_MESWIN0TYPE] == 1) {\n    lineSpacing = Impacto::Profile::CHLCC::DialogueBox::REVLineSpacing;\n  }\n  if (Mode == DPM_TIPS) lineSpacing = TipsLineSpacing;\n  CurrentLineTop =\n      CurrentLineTop + CurrentLineTopMargin + lineHeight + lineSpacing;\n  CurrentLineTopMargin = 0.0f;\n  LastLineStart = nextLineStart;\n}\n\nvoid DialoguePage::EndRubyBase(int lastBaseCharacter) {\n  if (BuildingRubyBase) {\n    RubyChunks[CurrentRubyChunk].BaseLength =\n        (lastBaseCharacter - RubyChunks[CurrentRubyChunk].FirstBaseCharacter) +\n        1;\n    BuildingRubyBase = false;\n  }\n}\n\nvoid DialoguePage::AddString(Vm::Sc3VmThread* ctx, Audio::AudioStream* voice,\n                             bool acted, int animId, int charId,\n                             bool shouldUpdateCharId) {\n  CurrentVoice = voice;\n  CurrentLineVoiced = voice != nullptr;\n\n  bool hasName = false;\n  if (shouldUpdateCharId) {\n    CharacterId = charId;\n    ScrWork[Id + SW_ANIME0CHANO] = CharacterId;\n  }\n\n  const int nextAnimId = acted ? animId : charId;\n\n  // Hold last voiced animation id\n  if (CurrentLineVoiced) NextAnimationId = std::max(nextAnimId, 31);\n\n  if (Mode == DPM_ADV || Mode == DPM_REV ||\n      AdvanceMethod == AdvanceMethodType::PresentClear || PrevMode != Mode) {\n    Clear();\n  }\n  TextFadeAnimation.Reset(AnimationDirection::Out);\n  PrevMode = Mode;\n\n  const size_t typeWriterStart = Glyphs.size();\n  std::set<size_t> parallelStartGlyphs;\n\n  // TODO should we reset HasName here?\n  // It shouldn't really matter since names are an ADV thing and we clear\n  // before every add on ADV anyway...\n\n  AdvanceMethod = AdvanceMethodType::Skip;\n\n  TextParseState state = TPS_Normal;\n  // TODO respect alignment\n  Alignment = TextAlignment::Left;\n  size_t lastWordStart = Glyphs.size();\n  LastLineStart = Glyphs.size();\n  DialogueColorPair currentColors = ColorTable[0];\n  if (Mode == DPM_REV) currentColors = ColorTable[REVColor];\n\n  FontSize = DefaultFontSize;\n  // Erin DialogueBox\n  if (DialogueBoxCurrentType == DialogueBoxType::CHLCC && Mode == DPM_REV &&\n      ScrWork[SW_MESWIN0TYPE] == 1) {\n    FontSize = Impacto::Profile::CHLCC::DialogueBox::REVFontSize;\n  } else {\n    FontSize = DefaultFontSize;\n  }\n\n  if (Mode == DPM_ADV) {\n    BoxBounds = ADVBounds;\n  } else if (Mode == DPM_REV) {\n    if (ScrWork[SW_MESWIN0TYPE] == 0) {\n      BoxBounds = REVBounds;\n    } else {\n      BoxBounds = SecondaryREVBounds;\n    }\n  } else if (Mode == DPM_TIPS) {\n    BoxBounds = TipsBounds;\n    currentColors = ColorTable[TipsColorIndex];\n  } else {\n    BoxBounds = NVLBounds;\n  }\n\n  float currentX = 0.0f;\n\n  std::vector<uint16_t> name;\n\n  BuildingRubyBase = false;\n\n  StringToken token;\n  bool prevGlyphWordStarting = false;\n  bool prevGlyphWordEnding = false;\n  do {\n    token.Read(ctx);\n    switch (token.Type) {\n      case STT_LineBreak:\n      case STT_AltLineBreak: {\n        FinishLine(ctx, Glyphs.size(), BoxBounds, Alignment);\n        lastWordStart = Glyphs.size();\n        currentX = 0.0f;\n      } break;\n\n      case STT_CharacterNameStart: {\n        hasName = true;\n        name.reserve(64);\n        state = TPS_Name;\n        if (Mode == DPM_REV &&\n            REVNameLocation != REVNameLocationType::LeftTop) {\n          CurrentLineTop += REVNameOffset;\n        }\n        lastWordStart = Glyphs.size();\n      } break;\n\n      case STT_Present: {\n        AdvanceMethod = AdvanceMethodType::Present;\n      } break;\n\n      case STT_RubyTextStart: {\n        EndRubyBase((int)Glyphs.size() - 1);\n        state = TPS_Ruby;\n      } break;\n\n      case STT_RubyCenterPerCharacter: {\n        RubyChunks[CurrentRubyChunk].CenterPerCharacter = true;\n      } break;\n\n      case STT_DialogueLineStart: {\n        state = TPS_Normal;\n        lastWordStart = Glyphs.size();\n      } break;\n\n      case STT_RubyTextEnd: {\n        // At least S;G uses [ruby-base]link text[ruby-text-end] for mails,\n        // with no ruby-text-start\n        EndRubyBase((int)Glyphs.size() - 1);\n        state = TPS_Normal;\n        lastWordStart = Glyphs.size();\n      } break;\n\n      case STT_CenterText: {\n        Alignment = TextAlignment::Center;\n      } break;\n\n      case STT_Present_Clear: {\n        AdvanceMethod = AdvanceMethodType::PresentClear;\n      } break;\n\n      case STT_SetLeftMargin: {\n        float addX = token.Val_Uint16;\n        if (currentX + addX > BoxBounds.Width) {\n          FinishLine(ctx, Glyphs.size(), BoxBounds, Alignment);\n          addX -= (BoxBounds.Width - currentX);\n          currentX = 0.0f;\n        }\n        while (addX > BoxBounds.Width) {\n          FinishLine(ctx, Glyphs.size(), BoxBounds, Alignment);\n          addX -= BoxBounds.Width;\n        }\n        currentX += addX;\n        lastWordStart = Glyphs.size();\n      } break;\n\n      case STT_SetTopMargin: {\n        CurrentLineTopMargin = token.Val_Uint16;\n      } break;\n\n      case STT_SetFontSize: {\n        FontSize = DefaultFontSize * (token.Val_Uint16 / SetFontSizeRatio);\n      } break;\n\n      case STT_RubyBaseStart: {\n        CurrentRubyChunk = static_cast<int>(RubyChunkCount);\n        RubyChunkCount++;\n        RubyChunks[CurrentRubyChunk].FirstBaseCharacter = Glyphs.size();\n        BuildingRubyBase = true;\n        lastWordStart = Glyphs.size();\n      } break;\n\n      case STT_Present_0x18: {\n        AdvanceMethod = AdvanceMethodType::Present0x18;\n      } break;\n\n      case STT_AutoForward_SyncVoice: {\n        AdvanceMethod = AdvanceMethodType::AutoForwardSyncVoice;\n      } break;\n\n      case STT_AutoForward: {\n        AdvanceMethod = AdvanceMethodType::AutoForward;\n      } break;\n\n      case STT_SetColor: {\n        if (Mode == DPM_REV) break;\n\n        assert(token.Val_Expr < ColorCount);\n        currentColors = ColorTable[token.Val_Expr];\n      } break;\n\n      case STT_UnlockTip: {\n        if ((Mode == DPM_ADV ||\n             (ScrWork[SW_MESWIN0TYPE] == 1 && Mode == DPM_REV) ||\n             Mode == DPM_NVL) &&\n            TipsSystem::GetTipLockedState(token.Val_Uint16)) {\n          TipsSystem::SetTipLockedState(token.Val_Uint16, false);\n          TipsNotification::AddTip(token.Val_Uint16);\n          TipsSystem::GetNewTipsIndices().push_back(token.Val_Uint16);\n        }\n      } break;\n\n      case STT_Character: {\n        if (state == TPS_Name) {\n          name.emplace_back(SDL_Swap16(token.Val_Uint16 | 0x8000));\n        } else if (state == TPS_Ruby) {\n          RubyChunks[CurrentRubyChunk]\n              .RawText[RubyChunks[CurrentRubyChunk].Length] =\n              SDL_Swap16(token.Val_Uint16 | 0x8000);\n          RubyChunks[CurrentRubyChunk].Length++;\n        } else {\n          // TODO respect TA_Center\n          // TODO what to do about left margin if text alignment is center?\n          Glyphs.push_back(ProcessedTextGlyph());\n          ProcessedTextGlyph& ptg = Glyphs.back();\n          ptg.CharId = token.Val_Uint16;\n          if (Mode == DPM_REV || Mode == DPM_TIPS)\n            ptg.Opacity = 1.0f;\n          else\n            ptg.Opacity = 0.0f;\n          ptg.Colors = currentColors;\n\n          if (token.Flags & +CharacterTypeFlags::WordStartingPunct) {\n            // Ensure only the leftmost consecutive WordStartingPunct is counted\n            if (!prevGlyphWordStarting) {\n              // Still *before* this character\n              lastWordStart = Glyphs.size() - 1;\n              prevGlyphWordStarting = true;\n            }\n          } else {\n            prevGlyphWordStarting = false;\n          }\n\n          if (token.Flags & +CharacterTypeFlags::WordEndingPunct) {\n            prevGlyphWordEnding = true;\n            // Ensure only the rightmost consecutive WordEndingPunct is counted\n          } else if (prevGlyphWordEnding) {\n            // Previous character was word ending, so this character marks the\n            // beginning of the next word\n            lastWordStart = Glyphs.size() - 1;\n            prevGlyphWordEnding = false;\n          }\n\n          ptg.DestRect.X = BoxBounds.X + currentX;\n          ptg.DestRect.Width = (FontSize / DialogueFont->BitmapEmWidth) *\n                               DialogueFont->AdvanceWidths[ptg.CharId];\n          ptg.DestRect.Height = FontSize;\n\n          currentX += ptg.DestRect.Width;\n\n          // Line breaking\n          if (ptg.DestRect.X + ptg.DestRect.Width >\n              BoxBounds.X + BoxBounds.Width) {\n            if (LastLineStart == lastWordStart) {\n              // Word doesn't fit on a line, gotta break in the middle of it\n              ptg.DestRect.X = BoxBounds.X;\n              currentX = ptg.DestRect.Width;\n              FinishLine(ctx, Glyphs.size() - 1, BoxBounds, Alignment);\n              lastWordStart = Glyphs.size() - 1;\n            } else {\n              size_t firstNonSpace = lastWordStart;\n              // Skip spaces at start of (new) line\n              for (size_t i = lastWordStart; i < Glyphs.size(); i++) {\n                const bool isSpace = StringToken::GetFlags(Glyphs[i].CharId) &\n                                     +CharacterTypeFlags::Space;\n                if (!isSpace) break;\n                firstNonSpace = i + 1;\n              }\n\n              FinishLine(ctx, firstNonSpace, BoxBounds, Alignment);\n              lastWordStart = firstNonSpace;\n\n              currentX = 0.0f;\n              for (size_t i = firstNonSpace; i < Glyphs.size(); i++) {\n                Glyphs[i].DestRect.X = BoxBounds.X + currentX;\n                currentX += Glyphs[i].DestRect.Width;\n              }\n            }\n          }\n        }\n      } break;\n\n      case STT_PrintInParallel:\n        parallelStartGlyphs.insert(Glyphs.size());\n        break;\n\n      default:\n        break;\n    }\n  } while (token.Type != STT_EndOfString);\n\n  FinishLine(ctx, Glyphs.size(), BoxBounds, Alignment);\n  currentX = 0.0f;\n\n  RectF boundingBox = Glyphs.empty() ? RectF() : Glyphs.begin()->DestRect;\n  for (const ProcessedTextGlyph& glyph : Glyphs) {\n    boundingBox = RectF::Coalesce(boundingBox, glyph.DestRect);\n  }\n  Dimensions = glm::vec2(boundingBox.Width, boundingBox.Height);\n\n  // Even if there is a name in the string it should not be\n  // rendered when in NVL mode\n  if (Mode == DPM_NVL) {\n    hasName = false;\n  }\n\n  if (DialogueBoxCurrentType == DialogueBoxType::CHLCC && Mode == DPM_REV &&\n      ScrWork[SW_MESWIN0TYPE] == 1) {\n    hasName = false;\n  }\n\n  RenderName = hasName;\n  if (hasName) {\n    ScrWork[SW_MESNAMEID0 + Id] = GetNameId(name).value_or(0xFFFF);\n\n    float fontSize = ADVNameFontSize;\n    glm::vec2 pos = ADVNamePos;\n    TextAlignment alignment = ADVNameAlignment;\n    int colorIndex = 0;\n    if (Mode == DPM_REV) {\n      fontSize = REVNameFontSize;\n      colorIndex = REVNameColor;\n\n      switch (REVNameLocation) {\n        case REVNameLocationType::None:\n        case REVNameLocationType::TopLeft:\n          pos = glm::vec2(REVBounds.X, REVBounds.Y);\n          alignment = TextAlignment::Left;\n          break;\n        case REVNameLocationType::LeftTop:\n          pos = glm::vec2(REVBounds.X - REVNameOffset, Glyphs[0].DestRect.Y);\n          alignment = TextAlignment::Right;\n          break;\n      }\n    }\n    Vm::Sc3Stream nameStream(name.data());\n    Name = TextLayoutPlainLine(nameStream, static_cast<int>(name.size()),\n                               DialogueFont, fontSize, ColorTable[colorIndex],\n                               1.0f, pos, alignment);\n    assert(name.size() == Name.size());\n  } else {\n    ScrWork[SW_MESNAMEID0 + Id] = 0xFFFF;\n  }\n\n  // resetting typewriter for a new line and setting new params\n  Typewriter.Reset(AnimationDirection::In);\n  Typewriter.SetFirstGlyph(typeWriterStart);\n  Typewriter.SetGlyphCount(Glyphs.size() - typeWriterStart);\n  Typewriter.SetParallelStartGlyphs(parallelStartGlyphs);\n  Typewriter.Start(CurrentLineVoiced);\n\n  if (CurrentLineVoiced) {\n    AnimationId = NextAnimationId;\n    Audio::Channels[Audio::AC_VOICE0]->Play(\n        std::unique_ptr<Audio::AudioStream>(CurrentVoice), false, 0.0f);\n  }\n\n  AutoWaitTime = static_cast<float>(Typewriter.GetGlyphCount());\n  if (AdvanceMethod == AdvanceMethodType::AutoForwardSyncVoice) {\n    AutoWaitTime *= 2.0f;\n  }\n}\n\nvoid DialoguePage::Update(float dt) {\n  if (GetFlag(SF_UIHIDDEN)) return;\n  if ((ScrWork[SW_GAMESTATE] & 4) != 0) return;\n\n  Typewriter.Update(dt);\n\n  if (Typewriter.IsPlaying() || Typewriter.IsFinished(AnimationDirection::In)) {\n    for (size_t i = 0; i < Glyphs.size(); i++) {\n      Glyphs[i].Opacity = Typewriter.CalcOpacity(i);\n    }\n\n    for (size_t rubyChunkId = 0; rubyChunkId < RubyChunkCount; rubyChunkId++) {\n      for (size_t rubyGlyphId = 0; rubyGlyphId < RubyChunks[rubyChunkId].Length;\n           rubyGlyphId++) {\n        RubyChunks[rubyChunkId].Text[rubyGlyphId].Opacity =\n            Typewriter.CalcRubyOpacity(rubyGlyphId, RubyChunks[rubyChunkId]);\n      }\n    }\n\n    if (AdvanceMethod == AdvanceMethodType::AutoForwardSyncVoice) {\n      const float speed = AutoWaitTime > Typewriter.GetGlyphCount()\n                              ? Profile::ConfigSystem::TextSpeed\n                              : Profile::ConfigSystem::AutoSpeed;\n      AutoWaitTime = std::max(0.0f, AutoWaitTime - speed * dt);\n    } else if (TextIsFullyOpaque() &&\n               (AdvanceMethod == AdvanceMethodType::AutoForward ||\n                AutoModeEnabled)) {\n      AutoWaitTime =\n          std::max(0.0f, AutoWaitTime - Profile::ConfigSystem::AutoSpeed * dt);\n    }\n  }\n\n  const State visibilityState = GetState();\n  const bool shouldHide =\n      visibilityState == State::Hiding || visibilityState == State::Hidden;\n  if (RenderName && !shouldHide) {\n    DialogueBoxInst->ShowName();\n  } else {\n    DialogueBoxInst->HideName();\n  }\n\n  DialogueBoxInst->Update(dt);\n  FadeAnimation.Update(dt);\n  TextFadeAnimation.Update(dt);\n\n  WaitIconDisplay::Update(dt);\n  AutoIconDisplay::Update(dt);\n  SkipIconDisplay::Update(dt);\n}\n\nvoid DialoguePage::Render() {\n  // dialogue text\n  if (GetFlag(SF_UIHIDDEN)) return;\n  if (FadeAnimation.IsOut()) return;\n\n  glm::vec4 opacityTint(1.0f);\n  opacityTint.a = glm::smoothstep(0.0f, 1.0f, FadeAnimation.Progress);\n\n  const NameInfo nameInfo{\n      .RenderWindow = RenderName,\n      .NameId = ScrWork[SW_MESNAMEID0 + Id] == 0xffff\n                    ? std::nullopt\n                    : std::optional(ScrWork[SW_MESNAMEID0 + Id]),\n      .Name = Name,\n  };\n  DialogueBoxInst->Render(Mode, nameInfo, opacityTint);\n\n  // TODO: Figure out what's up with text box coloring\n  glm::vec4 col = glm::vec4(1.0f);  // ScrWorkGetColor(SW_MESWINDOW_COLOR);\n  col.a = opacityTint.a;\n\n  const float textFadeOpacity =\n      GetFlag(SF_MESALLSKIP) ? 1.0f\n                             : opacityTint.a * TextFadeAnimation.Progress;\n  Renderer->DrawProcessedText(Glyphs, DialogueFont, textFadeOpacity,\n                              RendererOutlineMode::Full);\n  for (size_t rubyChunkId = 0; rubyChunkId < RubyChunkCount; rubyChunkId++) {\n    Renderer->DrawProcessedText(RubyChunks[rubyChunkId].Text, DialogueFont,\n                                textFadeOpacity, RendererOutlineMode::Full);\n  }\n\n  // Wait icon\n  const RectF& lastGlyphDest =\n      Glyphs.empty() ? RectF() : Glyphs.back().DestRect;\n  glm::vec2 waitIconPos(lastGlyphDest.X + lastGlyphDest.Width, lastGlyphDest.Y);\n  WaitIconDisplay::Render(waitIconPos, col, Mode, Id);\n\n  AutoIconDisplay::Render(col);\n  SkipIconDisplay::Render(col);\n}\n\nvoid DialoguePage::Move(glm::vec2 relativePos) {\n  for (ProcessedTextGlyph& glyph : Glyphs) {\n    glyph.DestRect.X += relativePos.x;\n    glyph.DestRect.Y += relativePos.y;\n  }\n  for (ProcessedTextGlyph& glyph : Name) {\n    glyph.DestRect.X += relativePos.x;\n    glyph.DestRect.Y += relativePos.y;\n  }\n  for (RubyChunk rubyChunk : std::span(RubyChunks.begin(), RubyChunkCount)) {\n    for (auto glyph : std::span(rubyChunk.Text.begin(), rubyChunk.Length)) {\n      glyph.DestRect.X += relativePos.x;\n      glyph.DestRect.Y += relativePos.y;\n    }\n  }\n}\n\nvoid DialoguePage::MoveTo(glm::vec2 pos) {\n  if (Glyphs.empty()) return;\n  glm::vec2 relativePos =\n      pos - glm::vec2(Glyphs[0].DestRect.X, Glyphs[0].DestRect.Y);\n  Move(relativePos);\n}\n\nvoid DialoguePage::Hide() { FadeAnimation.StartOut(); }\n\nvoid DialoguePage::Show() {\n  FadeAnimation.StartIn(true);\n  TextFadeAnimation.Progress = 1.0f;\n}\n}  // namespace Impacto"
  },
  {
    "path": "src/text/dialoguepage.h",
    "content": "#pragma once\n\n#include \"text.h\"\n#include \"typewritereffect.h\"\n\n#include \"../hud/dialoguebox.h\"\n\n#include \"../audio/audiostream.h\"\n\nnamespace Impacto {\nenum DialoguePageMode : uint8_t {\n  DPM_ADV = 0,\n  DPM_NVL = 1,\n  DPM_REV = 2,\n  DPM_TIPS = 3\n};\n\nstruct DialoguePage {\n  static void Init();\n\n  int Id = 0;\n  int AnimationId = 0;\n  int NextAnimationId = 0;\n  int CharacterId = -1;\n\n  std::array<RubyChunk, 32> RubyChunks;\n  std::vector<ProcessedTextGlyph> Glyphs;\n\n  TypewriterEffect Typewriter;\n  Animation FadeAnimation;\n  Animation TextFadeAnimation;\n\n  std::vector<ProcessedTextGlyph> Name;\n  bool RenderName = false;\n\n  RectF BoxBounds;\n\n  Audio::AudioStream* CurrentVoice;\n\n  glm::vec2 Dimensions;\n  int Length;\n  float FontSize;\n\n  size_t RubyChunkCount;\n  int CurrentRubyChunk;\n\n  enum class AdvanceMethodType : uint8_t {\n    Skip,\n    Present,\n    PresentClear,\n    Present0x18,\n    AutoForwardSyncVoice,\n    AutoForward,\n  };\n  AdvanceMethodType AdvanceMethod = AdvanceMethodType::Skip;\n\n  float AutoWaitTime = 0.0f;\n\n  TextAlignment Alignment = TextAlignment::Left;\n  DialoguePageMode Mode;\n\n  bool CurrentLineVoiced = false;\n\n  enum class State { Initial, Showing, Hiding, Shown, Hidden };\n  State GetState() const;\n\n  // TODO get rid of this\n  bool TextIsFullyOpaque();\n  void Clear();\n  void AddString(Vm::Sc3VmThread* ctx, Audio::AudioStream* voice = 0,\n                 bool acted = true, int animId = 0, int charId = -1,\n                 bool shouldUpdateCharId = false);\n  void Update(float dt);\n  void Move(glm::vec2 relativePos);\n  void MoveTo(glm::vec2 pos);\n  void Render();\n  void Hide();\n  void Show();\n  bool HasName() const { return !Name.empty(); }\n\n private:\n  void FinishLine(Vm::Sc3VmThread* ctx, size_t nextLineStart,\n                  const RectF& boxBounds, TextAlignment alignment);\n  void EndRubyBase(int lastBaseCharacter);\n\n  DialoguePageMode PrevMode = DPM_ADV;\n\n  bool BuildingRubyBase;\n  size_t FirstRubyChunkOnLine;\n\n  size_t LastLineStart;\n  float CurrentLineTop;\n  float CurrentLineTopMargin;\n\n  std::unique_ptr<DialogueBox> DialogueBoxInst = DialogueBox::Create();\n};\n\ninline std::vector<DialoguePage> DialoguePages;\ninline bool SkipModeEnabled = false;\ninline bool AutoModeEnabled = false;\n\n}  // namespace Impacto"
  },
  {
    "path": "src/text/text.cpp",
    "content": "#include \"text.h\"\n\n#include <memory>\n\n#include <utf8.h>\n\n#include \"../log.h\"\n#include \"../animation.h\"\n#include \"../mem.h\"\n\n#include \"../profile/scriptvars.h\"\n#include \"../profile/charset.h\"\n#include \"../profile/dialogue.h\"\n#include \"../profile/game.h\"\n#include \"../profile/vm.h\"\n\n#include \"../vm/expression.h\"\n#include \"../vm/interface/input.h\"\n#include \"../vm/sc3stream.h\"\n\nnamespace Impacto {\nusing namespace Impacto::Profile::ScriptVars;\nusing namespace Impacto::Profile::Dialogue;\n\nint StringToken::Read(Vm::Sc3VmThread* ctx) {\n  int bytesRead = 0;\n  Flags = 0;\n\n  uint8_t c = *ctx->GetIp();\n  ctx->IpOffset++;\n  bytesRead++;\n  switch (c) {\n    case STT_LineBreak:\n    case STT_CharacterNameStart:\n    case STT_DialogueLineStart:\n    case STT_Present:\n    case STT_Present_Clear:\n    case STT_RubyBaseStart:\n    case STT_RubyTextStart:\n    case STT_RubyTextEnd:\n    case STT_PrintInParallel:\n    case STT_CenterText:\n    case STT_Present_0x18:\n    case STT_AutoForward_SyncVoice:\n    case STT_AutoForward:\n    case STT_RubyCenterPerCharacter:\n    case STT_AltLineBreak:\n    case STT_EndOfString: {\n      Type = (StringTokenType)c;\n      break;\n    }\n\n    case STT_SetFontSize:\n    case STT_SetTopMargin:\n    case STT_SetLeftMargin:\n    case STT_GetHardcodedValue:\n    case STT_UnlockTip: {\n      Type = (StringTokenType)c;\n      Val_Uint16 = (*ctx->GetIp() << 8) | *(ctx->GetIp() + 1);\n      ctx->IpOffset += 2;\n      bytesRead += 2;\n      break;\n    }\n\n    case STT_SetColor: {\n      Type = (StringTokenType)c;\n      if (ColorTagIsUint8) {\n        Val_Expr = (*(uint8_t*)(ctx->GetIp()));\n        ctx->IpOffset += 1;\n        bytesRead += 1;\n      } else {\n        uint32_t oldIp = ctx->IpOffset;\n        // TODO is this really okay to do in parsing code?\n        Val_Expr = Vm::ExpressionEval(ctx);\n        bytesRead += (int)(ctx->IpOffset - oldIp);\n      }\n      break;\n    }\n\n    case STT_EvaluateExpression: {\n      Type = (StringTokenType)c;\n      uint32_t oldIp = ctx->IpOffset;\n      // TODO is this really okay to do in parsing code?\n      Val_Expr = Vm::ExpressionEval(ctx);\n      bytesRead += (int)(ctx->IpOffset - oldIp);\n      break;\n    }\n\n    default: {\n      if (c < 0x80) {\n        if (c == STT_Character) {\n          ImpLog(LogLevel::Error, LogChannel::VM,\n                 \"STT_Character encountered, uh oh...\");\n        }\n        ImpLog(LogLevel::Error, LogChannel::VM,\n               \"Encountered unrecognized token 0x{:02x} in string\\n\", c);\n        Type = STT_EndOfString;\n      } else {\n        uint16_t glyphId = (((uint16_t)c & 0x7F) << 8) | *ctx->GetIp();\n        ctx->IpOffset++;\n\n        Flags |= GetFlags(glyphId);\n\n        Type = STT_Character;\n        Val_Uint16 = glyphId;\n      }\n      break;\n    }\n  }\n\n  return bytesRead;\n}\n\nint StringToken::Read(Vm::Sc3Stream& stream) {\n  uint8_t c = stream.ReadU8();\n  if (c == STT_Character) {\n    ImpLog(LogLevel::Error, LogChannel::VM,\n           \"STT_Character encountered, uh oh...\");\n    Type = STT_EndOfString;\n  } else if (c < 0x80) {\n    ImpLog(LogLevel::Error, LogChannel::VM,\n           \"Encountered non-character token 0x{:02x} in string\\n\", c);\n    Type = STT_EndOfString;\n  } else if (c == STT_EndOfString) {\n    Type = STT_EndOfString;\n  } else {\n    uint16_t glyphId = (((uint16_t)c & 0x7F) << 8) | stream.ReadU8();\n    Type = STT_Character;\n    Val_Uint16 = glyphId;\n    return 2;\n  }\n  return 1;\n}\n\nvoid StringToken::AddFlags(const Vm::BufferOffsetContext scrCtx,\n                           const uint8_t flags) {\n  Vm::Sc3VmThread dummy;\n  dummy.ScriptBufferId = scrCtx.ScriptBufferId;\n  dummy.IpOffset = scrCtx.IpOffset;\n\n  StringToken token;\n  token.Read(&dummy);\n  for (; token.Type != STT_EndOfString; token.Read(&dummy)) {\n    if (token.Type != STT_Character) {\n      ImpLog(LogLevel::Error, LogChannel::VM,\n             \"Encountered non-character token 0x{:02x} in flag string\\n\",\n             static_cast<uint8_t>(token.Type));\n      return;\n    }\n\n    const uint16_t glyphId = token.Val_Uint16;\n    if (auto it = FlagsMap.find(glyphId); it != FlagsMap.end()) {\n      it->second = it->second | flags;\n    } else {\n      FlagsMap.emplace(glyphId, flags);\n    }\n  }\n}\n\nint TextGetStringLength(Vm::Sc3Stream& stream) {\n  int result = 0;\n  StringToken token;\n  do {\n    result += token.Read(stream);\n  } while (token.Type != STT_EndOfString);\n  return result;\n}\nint TextGetStringLength(Vm::Sc3VmThread* ctx) {\n  int result = 0;\n  StringToken token;\n  do {\n    result += token.Read(ctx);\n  } while (token.Type != STT_EndOfString);\n  return result;\n}\n\nint TextGetMainCharacterCount(Vm::Sc3VmThread* ctx) {\n  int result = 0;\n  StringToken token;\n  bool isMain = true;\n  do {\n    token.Read(ctx);\n    switch (token.Type) {\n      case STT_CharacterNameStart:\n      case STT_RubyTextStart: {\n        isMain = false;\n        break;\n      }\n      case STT_DialogueLineStart: {\n        isMain = true;\n        break;\n      }\n      case STT_Character: {\n        if (isMain) result++;\n        break;\n      }\n      default:\n        // Probably safe to ignore this\n        break;\n    }\n  } while (token.Type != STT_EndOfString);\n  return result;\n}\n\ntemplate <typename T>\nconcept Sc3Type =\n    (std::is_lvalue_reference_v<T> &&\n     std::is_base_of_v<Vm::Sc3Stream, std::remove_reference_t<T>>) ||\n    std::is_same_v<std::decay_t<T>, Vm::Sc3VmThread*>;\n\nstd::pair<int, float> TextLayoutPlainLineHelper(\n    Sc3Type auto&& sc3, int stringLength,\n    std::output_iterator<ProcessedTextGlyph> auto outIt, Font* font,\n    float fontSize, DialogueColorPair colors, float opacity, glm::vec2 pos,\n    TextAlignment alignment, float blockWidth) {\n  size_t characterCount = 0;\n  StringToken token;\n\n  float currentX = 0;\n  DialogueColorPair currentColors = colors;\n  for (int i = 0; i < stringLength; i++) {\n    token.Read(sc3);\n    if (token.Type == STT_EndOfString) break;\n\n    switch (token.Type) {\n      default:\n        break;\n\n      case STT_SetColor: {\n        if (253 <= token.Val_Expr && token.Val_Expr <= 255) {\n          token.Val_Expr = ScrWork[SW_SYSMESCOL1 + (255 - token.Val_Expr)];\n        }\n\n        assert(token.Val_Expr < ColorCount);\n        currentColors = ColorTable[token.Val_Expr];\n      } break;\n\n      case STT_Character: {\n        ProcessedTextGlyph ptg;\n        ptg.CharId = token.Val_Uint16;\n        ptg.Colors = currentColors;\n        ptg.Opacity = opacity;\n\n        ptg.DestRect.X = currentX;\n        ptg.DestRect.Y = pos.y;\n        ptg.DestRect.Width = std::floor((fontSize / font->BitmapEmWidth) *\n                                        font->AdvanceWidths[ptg.CharId]);\n        ptg.DestRect.Height = fontSize;\n\n        currentX += ptg.DestRect.Width;\n\n        *outIt++ = ptg;\n        characterCount++;\n      } break;\n    }\n  }\n  // currentX is now line width\n  // If you want to align, you can pass a span or vector to the alignment\n  // function\n  return {static_cast<int>(characterCount), currentX};\n}\n\nint TextLayoutPlainLine(Vm::Sc3Stream& stream, int stringLength,\n                        std::span<ProcessedTextGlyph> outGlyphs, Font* font,\n                        float fontSize, DialogueColorPair colors, float opacity,\n                        glm::vec2 pos, TextAlignment alignment,\n                        float blockWidth) {\n  auto [count, currentX] = TextLayoutPlainLineHelper(\n      stream, stringLength, outGlyphs.begin(), font, fontSize, colors, opacity,\n      pos, alignment, blockWidth);\n  assert(outGlyphs.size() >= static_cast<size_t>(count));\n  TextLayoutAlignment(alignment, blockWidth, currentX, pos, count, outGlyphs);\n  return count;\n}\n\nstd::vector<ProcessedTextGlyph> TextLayoutPlainLine(\n    Vm::Sc3Stream& stream, int maxLength, Font* font, float fontSize,\n    DialogueColorPair colors, float opacity, glm::vec2 pos,\n    TextAlignment alignment, float blockWidth) {\n  std::vector<ProcessedTextGlyph> outGlyphs;\n  outGlyphs.reserve(maxLength);\n  auto [count, currentX] = TextLayoutPlainLineHelper(\n      stream, maxLength, std::back_inserter(outGlyphs), font, fontSize, colors,\n      opacity, pos, alignment, blockWidth);\n  TextLayoutAlignment(alignment, blockWidth, currentX, pos, count, outGlyphs);\n  return outGlyphs;\n}\n\nint TextLayoutPlainLine(Vm::Sc3VmThread* thd, int stringLength,\n                        std::span<ProcessedTextGlyph> outGlyphs, Font* font,\n                        float fontSize, DialogueColorPair colors, float opacity,\n                        glm::vec2 pos, TextAlignment alignment,\n                        float blockWidth) {\n  auto [count, currentX] = TextLayoutPlainLineHelper(\n      thd, stringLength, outGlyphs.begin(), font, fontSize, colors, opacity,\n      pos, alignment, blockWidth);\n  assert(outGlyphs.size() >= static_cast<size_t>(count));\n  TextLayoutAlignment(alignment, blockWidth, currentX, pos, count, outGlyphs);\n  return count;\n}\n\nstd::vector<ProcessedTextGlyph> TextLayoutPlainLine(\n    Vm::Sc3VmThread* thd, int maxLength, Font* font, float fontSize,\n    DialogueColorPair colors, float opacity, glm::vec2 pos,\n    TextAlignment alignment, float blockWidth) {\n  std::vector<ProcessedTextGlyph> outGlyphs;\n  outGlyphs.reserve(maxLength);\n  auto [count, currentX] = TextLayoutPlainLineHelper(\n      thd, maxLength, std::back_inserter(outGlyphs), font, fontSize, colors,\n      opacity, pos, alignment, blockWidth);\n  TextLayoutAlignment(alignment, blockWidth, currentX, pos, count, outGlyphs);\n  if (blockWidth > 0.0f) {\n    float containerRight = pos.x + blockWidth;\n    FitGlyphsForPlainLine(outGlyphs, containerRight);\n  }\n  return outGlyphs;\n}\n\nvoid FitGlyphsForPlainLine(std::span<ProcessedTextGlyph> glyphs,\n                           float containerRight) {\n  if (glyphs.empty()) return;\n\n  float lineLeft = glyphs.front().DestRect.X;\n  float lineRight = glyphs.back().DestRect.Right();\n\n  if (lineRight <= containerRight) return;\n\n  float totalWidth = lineRight - lineLeft;\n  float availableWidth = containerRight - lineLeft;\n  float scale = availableWidth / totalWidth;\n\n  for (auto& glyph : glyphs) {\n    float localX = glyph.DestRect.X - lineLeft;\n    glyph.DestRect.X = lineLeft + localX * scale;\n    glyph.DestRect.Width = glyph.DestRect.Width * scale;\n  }\n}\n\nint TextLayoutAlignment(Impacto::TextAlignment& alignment, float blockWidth,\n                        float currentX, glm::vec2& pos, int characterCount,\n                        std::span<Impacto::ProcessedTextGlyph> outGlyphs) {\n  // Block alignment:\n  //\n  //  l  i  n  e\n  // block__below\n  //\n  // If block below is shorter than line, line is just centered over the block\n\n  if (alignment == TextAlignment::Block && blockWidth < currentX) {\n    pos.x += blockWidth / 2.0f;\n    alignment = TextAlignment::Center;\n  }\n\n  switch (alignment) {\n    case TextAlignment::Left: {\n      // pos is top left\n      for (int i = 0; i < characterCount; i++) {\n        outGlyphs[i].DestRect.X += pos.x;\n      }\n      break;\n    }\n    case TextAlignment::Right: {\n      // pos is top right\n      for (int i = 0; i < characterCount; i++) {\n        outGlyphs[i].DestRect.X += (pos.x - currentX);\n      }\n      break;\n    }\n    case TextAlignment::Center: {\n      // pos is top center\n      for (int i = 0; i < characterCount; i++) {\n        outGlyphs[i].DestRect.X += (pos.x - (currentX / 2.0f));\n      }\n      break;\n    }\n    case TextAlignment::Block: {\n      float blockSpacing = blockWidth / (float)currentX;\n      if (characterCount >= 1) {\n        outGlyphs[0].DestRect.X +=\n            pos.x + blockSpacing / 2.0f - outGlyphs[0].DestRect.Width / 2.0f;\n      }\n      for (int i = 1; i < characterCount; i++) {\n        outGlyphs[i].DestRect.X +=\n            blockSpacing - outGlyphs[i].DestRect.Width / 2.0f;\n      }\n      break;\n    }\n  }\n\n  return characterCount;\n}\n\nfloat TextGetPlainLineWidth(Vm::Sc3VmThread* ctx, Font* font, float fontSize) {\n  StringToken token;\n\n  float width = 0.0f;\n  while (true) {\n    token.Read(ctx);\n    if (token.Type == STT_EndOfString) break;\n    if (token.Type != STT_Character) continue;\n\n    width += std::floor((fontSize / font->BitmapEmWidth) *\n                        font->AdvanceWidths[token.Val_Uint16]);\n  }\n\n  return width;\n}\n\nfloat TextGetPlainLineWidth(Vm::Sc3Stream& stream, Font* font, float fontSize) {\n  StringToken token;\n\n  float width = 0.0f;\n  while (true) {\n    token.Read(stream);\n    if (token.Type == STT_EndOfString) break;\n    if (token.Type != STT_Character) continue;\n\n    width += std::floor((fontSize / font->BitmapEmWidth) *\n                        font->AdvanceWidths[token.Val_Uint16]);\n  }\n\n  return width;\n}\n\nint TextLayoutPlainString(std::string_view str,\n                          std::span<ProcessedTextGlyph> outGlyphs, Font* font,\n                          float fontSize, DialogueColorPair colors,\n                          float opacity, glm::vec2 pos, TextAlignment alignment,\n                          float blockWidth) {\n  std::string_view::iterator strIt = str.begin();\n  std::string_view::iterator strEnd = str.end();\n\n  int sc3StrLength = (int)utf8::distance(strIt, strEnd) + 1;\n  std::unique_ptr<uint16_t[]> sc3StrPtr(new uint16_t[sc3StrLength]);\n\n  TextGetSc3String(str,\n                   std::span(sc3StrPtr.get(), sc3StrPtr.get() + sc3StrLength));\n\n  Vm::Sc3Stream stream(sc3StrPtr.get());\n  return TextLayoutPlainLine(stream, sc3StrLength, outGlyphs, font, fontSize,\n                             colors, opacity, pos, alignment, blockWidth);\n}\n\nstd::vector<ProcessedTextGlyph> TextLayoutPlainString(\n    std::string_view str, Font* font, float fontSize, DialogueColorPair colors,\n    float opacity, glm::vec2 pos, TextAlignment alignment, float blockWidth) {\n  std::string_view::iterator strIt = str.begin();\n  std::string_view::iterator strEnd = str.end();\n\n  int sc3StrLength = (int)utf8::distance(strIt, strEnd) + 1;\n  std::unique_ptr<uint16_t[]> sc3StrPtr(new uint16_t[sc3StrLength]);\n\n  TextGetSc3String(str,\n                   std::span(sc3StrPtr.get(), sc3StrPtr.get() + sc3StrLength));\n\n  Vm::Sc3Stream stream(sc3StrPtr.get());\n  return TextLayoutPlainLine(stream, sc3StrLength, font, fontSize, colors,\n                             opacity, pos, alignment, blockWidth);\n}\n\nvoid TextGetSc3String(std::string_view str, std::span<uint16_t> out) {\n  std::string_view::iterator strIt = str.begin();\n  std::string_view::iterator strEnd = str.end();\n\n  [[maybe_unused]] size_t sc3StrLength = (int)utf8::distance(strIt, strEnd) + 1;\n  assert(sc3StrLength <= out.size());\n  size_t sc3Idx = 0;\n  while (strIt != strEnd) {\n    auto codePoint = utf8::next(strIt, strEnd);\n\n    uint16_t sc3Val = Profile::Charset::CharacterToSc3[codePoint];\n    out[sc3Idx++] = SDL_Swap16(sc3Val);\n  }\n  out[sc3Idx++] = 0xFF;\n\n  assert(sc3Idx == sc3StrLength);\n}\n\nvoid InitNamePlateData(Vm::Sc3Stream& stream) {\n  do {\n    uint16_t id = stream.ReadU16();\n    uint16_t stringId = stream.ReadU16();\n    uint32_t nameAddr =\n        Vm::ScriptGetStrAddress(Profile::Vm::SystemScriptBuffer, stringId);\n    Vm::Sc3VmThread dummy;\n    dummy.IpOffset = nameAddr;\n    dummy.ScriptBufferId = Profile::Vm::SystemScriptBuffer;\n    int nameLength = (TextGetStringLength(&dummy) - 1) * 2;\n    dummy.IpOffset = nameAddr;\n    uint32_t nameHash =\n        GetHashCode(std::span<uint8_t>(dummy.GetIp(), nameLength));\n    NamePlateData[nameHash] = id;\n  } while (stream.PeekU16() != 0xFFFF);\n}\n\nstd::optional<uint32_t> GetNameId(const std::span<const uint16_t> name) {\n  uint32_t nameHash = GetHashCode(std::span<const uint8_t>(\n      std::bit_cast<uint8_t*>(name.data()), name.size_bytes()));\n  if (NamePlateData.find(nameHash) != NamePlateData.end())\n    return NamePlateData[nameHash];\n  else\n    return std::nullopt;\n}\n}  // namespace Impacto"
  },
  {
    "path": "src/text/text.h",
    "content": "#pragma once\n\n#include <span>\n#include <array>\n#include <magic_enum/magic_enum.hpp>\n#include <ankerl/unordered_dense.h>\n\n#include \"../font.h\"\n#include \"../animation.h\"\n#include \"../vm/thread.h\"\n#include \"../vm/sc3stream.h\"\n\nnamespace Impacto {\n\nenum class TextAlignment : int {\n  Left = 0,\n  Center,\n  Right,\n  Block,  // Block alignment only supported for ruby\n};\n\nenum class CharacterTypeFlags : uint8_t {\n  Space = (1 << 0),\n  WordStartingPunct = (1 << 1),\n  WordEndingPunct = (1 << 2),\n};\n\n// TODO: think about / profile memory access patterns\n\nenum StringTokenType : uint8_t {\n  STT_LineBreak = 0x00,\n  STT_CharacterNameStart = 0x01,\n  STT_DialogueLineStart = 0x02,\n  STT_Present = 0x03,\n  STT_SetColor = 0x04,\n  STT_Present_Clear = 0x08,\n  STT_RubyBaseStart = 0x09,\n  STT_RubyTextStart = 0x0A,\n  STT_RubyTextEnd = 0x0B,\n  STT_SetFontSize = 0x0C,\n  STT_PrintInParallel = 0x0E,\n  STT_CenterText = 0x0F,\n  STT_SetTopMargin = 0x11,\n  STT_SetLeftMargin = 0x12,\n  STT_GetHardcodedValue = 0x13,\n  STT_EvaluateExpression = 0x15,\n  STT_UnlockTip = 0x16,\n  STT_Present_0x18 = 0x18,\n  STT_AutoForward_SyncVoice = 0x19,\n  STT_AutoForward = 0x1A,\n  STT_RubyCenterPerCharacter = 0x1E,\n  STT_AltLineBreak = 0x1F,\n\n  // This is our own!\n  STT_Character = 0xFE,\n\n  STT_EndOfString = 0xFF\n};\n\nstruct StringToken {\n public:\n  StringTokenType Type;\n\n  uint16_t Val_Uint16;\n  int Val_Expr;\n\n  uint8_t Flags{};\n\n  static void AddFlags(Vm::BufferOffsetContext scrCtx, uint8_t flags);\n  static void AddFlags(uint16_t glyphId, uint8_t flags) {\n    const auto found = FlagsMap.find(glyphId);\n    if (found != FlagsMap.end()) {\n      found->second |= flags;\n    } else {\n      StringToken::FlagsMap.emplace(glyphId, flags);\n    }\n  }\n  static uint8_t GetFlags(uint16_t glyphId) {\n    const auto found = FlagsMap.find(glyphId);\n    return found == FlagsMap.end() ? 0 : found->second;\n  }\n\n  int Read(Vm::Sc3VmThread* ctx);\n  int Read(Vm::Sc3Stream& stream);\n\n private:\n  static inline ankerl::unordered_dense::map<uint16_t, uint8_t> FlagsMap;\n};\n\nstruct DialogueColorPair {\n  uint32_t TextColor;\n  uint32_t OutlineColor;\n};\n\nstruct ProcessedTextGlyph {\n  DialogueColorPair Colors;\n  uint16_t CharId;\n  float Opacity;\n  RectF DestRect;\n};\n\nstruct RubyChunk {\n  size_t FirstBaseCharacter;\n  size_t Length;\n  size_t BaseLength;\n  std::array<ProcessedTextGlyph, 32> Text;\n  std::array<uint16_t, 32> RawText;\n  bool CenterPerCharacter;\n};\n\nint TextGetStringLength(Vm::Sc3Stream& stream);\nint TextGetStringLength(Vm::Sc3VmThread* ctx);\n[[maybe_unused]] int TextGetMainCharacterCount(Vm::Sc3VmThread* ctx);\nint TextLayoutPlainLine(Vm::Sc3Stream& stream, int stringLength,\n                        std::span<ProcessedTextGlyph> outGlyphs, Font* font,\n                        float fontSize, DialogueColorPair colors, float opacity,\n                        glm::vec2 pos, TextAlignment alignment,\n                        float blockWidth = 0.0f);\nint TextLayoutPlainLine(Vm::Sc3VmThread* ctx, int stringLength,\n                        std::span<ProcessedTextGlyph> outGlyphs, Font* font,\n                        float fontSize, DialogueColorPair colors, float opacity,\n                        glm::vec2 pos, TextAlignment alignment,\n                        float blockWidth = 0.0f);\nstd::vector<ProcessedTextGlyph> TextLayoutPlainLine(\n    Vm::Sc3Stream& stream, int maxLength, Font* font, float fontSize,\n    DialogueColorPair colors, float opacity, glm::vec2 pos,\n    TextAlignment alignment, float blockWidth = 0.0f);\nstd::vector<ProcessedTextGlyph> TextLayoutPlainLine(\n    Vm::Sc3VmThread* thd, int maxLength, Font* font, float fontSize,\n    DialogueColorPair colors, float opacity, glm::vec2 pos,\n    TextAlignment alignment, float blockWidth = 0.0f);\nint TextLayoutAlignment(Impacto::TextAlignment& alignment, float blockWidth,\n                        float currentX, glm::vec2& pos, int characterCount,\n                        std::span<Impacto::ProcessedTextGlyph> outGlyphs);\nfloat TextGetPlainLineWidth(Vm::Sc3VmThread* ctx, Font* font, float fontSize);\nfloat TextGetPlainLineWidth(Vm::Sc3Stream& stream, Font* font, float fontSize);\nint TextLayoutPlainString(std::string_view str,\n                          std::span<ProcessedTextGlyph> outGlyphs, Font* font,\n                          float fontSize, DialogueColorPair colors,\n                          float opacity, glm::vec2 pos, TextAlignment alignment,\n                          float blockWidth = 0.0f);\nstd::vector<ProcessedTextGlyph> TextLayoutPlainString(\n    std::string_view str, Font* font, float fontSize, DialogueColorPair colors,\n    float opacity, glm::vec2 pos, TextAlignment alignment,\n    float blockWidth = 0.0f);\n\nvoid TextGetSc3String(std::string_view str, std::span<uint16_t> out);\n\ninline ankerl::unordered_dense::map<uint32_t, uint32_t> NamePlateData;\nvoid InitNamePlateData(Vm::Sc3Stream& stream);\nstd::optional<uint32_t> GetNameId(std::span<const uint16_t> name);\nvoid FitGlyphsForPlainLine(std::span<ProcessedTextGlyph> glyphs,\n                           float containerRight);\n\n}  // namespace Impacto"
  },
  {
    "path": "src/text/typewritereffect.cpp",
    "content": "#include \"typewritereffect.h\"\n\n#include \"../profile/configsystem.h\"\n#include \"../profile/dialogue.h\"\n\n#include \"../audio/audiochannel.h\"\n#include \"../audio/audiosystem.h\"\n\nnamespace Impacto {\nusing namespace Profile::ConfigSystem;\nusing namespace Profile::Dialogue;\n\nvoid TypewriterEffect::Start(const bool voiced) {\n  DurationIn = 1.0f;\n  Voiced = voiced;\n  IsCancelled = false;\n  SkipOnSkipMode = true;\n  ProgressOnCancel = 0.0f;\n  StartIn(true);\n}\n\nvoid TypewriterEffect::Update(float dt) {\n  if (State == AnimationState::Stopped) return;\n\n  if (CancelRequested && !IsCancelled) {\n    IsCancelled = true;\n    CancelRequested = false;\n    ProgressOnCancel = Progress;\n    DurationIn = 0.25f;\n  }\n\n  if (!IsCancelled) {\n    if (Voiced && Profile::ConfigSystem::SyncVoice) {\n      // Effectively progress at the constant pace such that the line ends\n      // the moment the voice line ends\n      const float remainingAudioDuration =\n          Audio::Channels[Audio::AC_VOICE0]->DurationInSeconds() -\n          Audio::Channels[Audio::AC_VOICE0]->PositionInSeconds();\n      const float progressLeft = DurationIn - Progress;\n      const float remainingAudioCompletionFraction =\n          remainingAudioDuration > 0.0f ? dt / remainingAudioDuration : 1.0f;\n      const float progressAdded =\n          progressLeft * remainingAudioCompletionFraction;\n      dt = progressAdded;\n    } else {\n      // Progress at the characters-per-second speed defined by TextSpeed\n      const float progressLeft = 1.0f - Progress;\n\n      if (Profile::ConfigSystem::TextSpeed >=\n          Profile::ConfigSystem::TextSpeedBounds.y) {\n        if (TextFadeInDuration > 0.0f) {\n          dt = dt / TextFadeInDuration;\n        } else {\n          dt = progressLeft;\n        }\n      } else {\n        const float glyphsLeft = static_cast<float>(GlyphCount) * progressLeft;\n        const float secondsLeft =\n            Profile::ConfigSystem::TextSpeed > 0.0f\n                ? glyphsLeft / Profile::ConfigSystem::TextSpeed\n                : 0.0f;\n        const float secondsLeftFractionCompleted =\n            secondsLeft > 0.0f ? dt / secondsLeft : 1.0f;\n        const float progressAdded = progressLeft * secondsLeftFractionCompleted;\n        dt = progressAdded;\n      }\n    }\n  }\n\n  UpdateImpl(dt);\n}\n\nTypewriterEffect::ParallelBlock TypewriterEffect::GetParallelBlock(\n    const size_t glyph) {\n  if (ParallelStartGlyphs.empty()) {  // No parallel blocks\n    return {FirstGlyph, GlyphCount};\n\n  } else if (glyph < *ParallelStartGlyphs.begin()) {  // First parallel block\n    return {FirstGlyph, *ParallelStartGlyphs.begin() - FirstGlyph};\n\n  } else if (glyph >= *ParallelStartGlyphs.rbegin()) {  // Last parallel block\n    return {*ParallelStartGlyphs.rbegin(),\n            (FirstGlyph + GlyphCount) - *ParallelStartGlyphs.rbegin()};\n\n  } else {\n    const size_t firstGlyphOfBlock = *std::max_element(\n        ParallelStartGlyphs.begin(), ParallelStartGlyphs.upper_bound(glyph));\n    return {firstGlyphOfBlock,\n            *ParallelStartGlyphs.upper_bound(glyph) - firstGlyphOfBlock};\n  }\n}\n\nstd::pair<float, float> TypewriterEffect::GetGlyphWritingProgresses(\n    const size_t glyph) {\n  const ParallelBlock block = GetParallelBlock(glyph);\n\n  const size_t parallelBlockGlyphNo = glyph - block.Start;\n\n  // We start displaying a glyph after the previous one is 25% opaque, hence\n  // totalDisplayTime = glyphCount * (0.25 * glyphDisplayTime) +\n  //                    glyphDisplayTime * 0.75\n\n  constexpr float singleGlyphDuration = 1.0f;\n  constexpr float glyphPropagateProgress = 0.25f;\n  constexpr float glyphPropagateDuration =\n      singleGlyphDuration * glyphPropagateProgress;\n  const float totalDuration =\n      block.Size * glyphPropagateDuration +\n      (1.0f - glyphPropagateProgress) * singleGlyphDuration;\n\n  const float startTime = glyphPropagateDuration * parallelBlockGlyphNo;\n  const float endTime = startTime + singleGlyphDuration;\n\n  return {startTime / totalDuration, endTime / totalDuration};\n}\n\nfloat TypewriterEffect::CalcOpacity(size_t glyph) {\n  if (glyph < FirstGlyph) return 1.0f;\n  if (glyph >= FirstGlyph + GlyphCount) return 0.0f;\n\n  if (!IsCancelled &&\n      Profile::ConfigSystem::TextSpeed >=\n          Profile::ConfigSystem::TextSpeedBounds.y &&\n      !(Voiced && Profile::ConfigSystem::SyncVoice)) {\n    return Progress;\n  }\n\n  const auto [startProgress, endProgress] = GetGlyphWritingProgresses(glyph);\n\n  if (IsCancelled) {\n    // On cancellation, all non-opaque glyphs start appearing simultaneously\n\n    // Opaque glyphs remain opaque\n    if (ProgressOnCancel >= endProgress) return 1.0f;\n\n    // Transparent glyphs fade in according to the progress made\n    // since cancelling\n    const float cancelProgress =\n        (Progress - ProgressOnCancel) / (1.0f - ProgressOnCancel);\n    if (ProgressOnCancel <= startProgress) return cancelProgress;\n\n    // Translucent glyphs fade in further, in addition to the opacity they\n    // already had\n    const float glyphProgressBeforeCancellation =\n        (ProgressOnCancel - startProgress) / (endProgress - startProgress);\n    return glyphProgressBeforeCancellation +\n           cancelProgress * (1.0f - glyphProgressBeforeCancellation);\n  }\n\n  return std::clamp((Progress - startProgress) / (endProgress - startProgress),\n                    0.0f, 1.0f);\n}\n\nfloat TypewriterEffect::CalcRubyOpacity(const size_t rubyGlyphId,\n                                        const RubyChunk& chunk) {\n  // Ruby fade as a regular textbox would, with the start and end times of\n  // the animation being the start and end fade times of the ruby chunk's base\n  //\n  // On cancellation, the ruby all fade in simultaneously, like regular text\n\n  const size_t baseStart = chunk.FirstBaseCharacter;\n  const size_t baseEnd = chunk.FirstBaseCharacter + chunk.BaseLength;\n\n  // Base is already fully opaque / still fully transparent\n  if (baseEnd <= FirstGlyph) return 1.0f;\n  if (baseStart > FirstGlyph + GlyphCount) return 0.0f;\n\n  const float baseStartProgress = GetGlyphWritingProgresses(baseStart).first;\n  const float baseEndProgress = GetGlyphWritingProgresses(baseEnd - 1).second;\n\n  if (IsCancelled) {\n    if (ProgressOnCancel >= baseEndProgress) return 1.0f;\n\n    const float cancelProgress =\n        (Progress - ProgressOnCancel) / (1.0f - ProgressOnCancel);\n    if (ProgressOnCancel <= baseStartProgress) return cancelProgress;\n  } else {\n    if (Progress >= baseEndProgress) return 1.0f;\n    if (Progress <= baseStartProgress) return 0.0f;\n  }\n\n  const float baseProgressLength = baseEndProgress - baseStartProgress;\n\n  // We start displaying a glyph after the previous one is 25% opaque, hence\n  // totalDisplayTime = glyphCount * (0.25 * glyphDisplayTime) +\n  //                    glyphDisplayTime * 0.75\n\n  constexpr float singleGlyphDuration = 1.0f;\n  constexpr float glyphPropagateProgress = 0.25f;\n  constexpr float glyphPropagateDuration =\n      singleGlyphDuration * glyphPropagateProgress;\n  const float totalDuration =\n      chunk.Length * glyphPropagateDuration +\n      (1.0f - glyphPropagateProgress) * singleGlyphDuration;\n\n  const float glyphStartTime = glyphPropagateDuration * rubyGlyphId;\n  const float glyphEndTime = glyphStartTime + singleGlyphDuration;\n\n  // Convert back to progress-space\n  const float startProgress =\n      baseStartProgress + (glyphStartTime / totalDuration) * baseProgressLength;\n  const float endProgress =\n      baseStartProgress + (glyphEndTime / totalDuration) * baseProgressLength;\n\n  if (IsCancelled) {\n    if (ProgressOnCancel >= endProgress) return 1.0f;\n\n    const float cancelProgress =\n        (Progress - ProgressOnCancel) / (1.0f - ProgressOnCancel);\n    if (ProgressOnCancel <= startProgress) return cancelProgress;\n\n    const float glyphProgressBeforeCancellation =\n        (ProgressOnCancel - startProgress) / (endProgress - startProgress);\n    return glyphProgressBeforeCancellation +\n           cancelProgress * (1.0f - glyphProgressBeforeCancellation);\n  }\n\n  return std::clamp((Progress - startProgress) / (endProgress - startProgress),\n                    0.0f, 1.0f);\n}\n}  // namespace Impacto"
  },
  {
    "path": "src/text/typewritereffect.h",
    "content": "#pragma once\n\n#include <set>\n\n#include \"text.h\"\n#include \"../animation.h\"\n\nnamespace Impacto {\n\nstruct TypewriterEffect : public Animation {\n public:\n  void Start(bool voiced);\n  void Update(float dt);\n\n  float CalcOpacity(size_t glyph);\n  float CalcRubyOpacity(size_t rubyGlyphId, const RubyChunk& chunk);\n\n  void SetGlyphCount(size_t glyphCount) { GlyphCount = glyphCount; }\n  size_t GetGlyphCount() const { return GlyphCount; }\n\n  void SetParallelStartGlyphs(const std::set<size_t>& parallelStartGlyphs) {\n    ParallelStartGlyphs = parallelStartGlyphs;\n  }\n  void SetFirstGlyph(size_t firstGlyph) { FirstGlyph = firstGlyph; }\n\n  bool CancelRequested = false;\n  bool IsCancelled = false;\n\n private:\n  size_t FirstGlyph = 0;\n  size_t GlyphCount = 0;\n\n  std::set<size_t> ParallelStartGlyphs;\n  float ProgressOnCancel;\n\n  bool Voiced = false;\n\n  struct ParallelBlock {\n    size_t Start;\n    size_t Size;\n  };\n  ParallelBlock GetParallelBlock(size_t glyph);\n\n  // {startProgress, endProgress}\n  std::pair<float, float> GetGlyphWritingProgresses(size_t glyph);\n};\n}  // namespace Impacto"
  },
  {
    "path": "src/texture/bcdecode.cpp",
    "content": "﻿/*\n * decoder for DXTn-compressed data\n *\n * Format documentation:\n *   http://oss.sgi.com/projects/ogl-sample/registry/EXT/texture_compression_s3tc.txt\n *\n * The contents of this file are in the public domain (CC0)\n * Full text of the CC0 license:\n *   https://creativecommons.org/publicdomain/zero/1.0/\n *\n * To test:\n *   compile: gcc -Iinclude -DBCN_DECODER_TEST BcnDecode.c -o bcndecode\n *   run: dd bs=1 skip=128 if=bc3_test.dds | ./bcndecode 256 256 3 1 >\n * bc3_test.png\n */\n\n#ifdef BCN_DECODER_TEST\n#include <cstdio>\n#define STB_IMAGE_WRITE_IMPLEMENTATION\n#include <stb_image_write.h>\n#endif\n\n#include <cstring>\n#include <cstdint>\n#include \"bcdecode.h\"\n\ntypedef struct {\n  uint8_t r, g, b, a;\n} rgba;\n\ntypedef struct {\n  uint8_t l;\n} lum;\n\ntypedef struct {\n  float r, g, b;\n} rgb32f;\n\ntypedef struct {\n  uint16_t c0, c1;\n  uint32_t lut;\n} bc1_color;\n\ntypedef struct {\n  uint8_t a0, a1;\n  uint8_t lut[6];\n} bc3_alpha;\n\n#define LOAD16(p) (p)[0] | ((p)[1] << 8)\n\n#define LOAD32(p) (p)[0] | ((p)[1] << 8) | ((p)[2] << 16) | ((p)[3] << 24)\n\nstatic void bc1_color_load(bc1_color* dst, const uint8_t* src) {\n  dst->c0 = LOAD16(src);\n  dst->c1 = LOAD16(src + 2);\n  dst->lut = LOAD32(src + 4);\n}\n\nstatic void bc3_alpha_load(bc3_alpha* dst, const uint8_t* src) {\n  memcpy(dst, src, sizeof(bc3_alpha));\n}\n\nstatic rgba decode_565(uint16_t x) {\n  rgba c;\n  uint32_t r, g, b;\n  r = (x & 0xf800) >> 8;\n  r |= r >> 5;\n  c.r = (uint8_t)r;\n  g = (x & 0x7e0) >> 3;\n  g |= g >> 6;\n  c.g = (uint8_t)g;\n  b = (x & 0x1f) << 3;\n  b |= b >> 5;\n  c.b = (uint8_t)b;\n  c.a = 0xff;\n  return c;\n}\n\nstatic void decode_bc1_color(rgba* dst, const uint8_t* src) {\n  bc1_color col;\n  rgba p[4];\n  int n, cw;\n  uint16_t r0, g0, b0, r1, g1, b1;\n  bc1_color_load(&col, src);\n\n  p[0] = decode_565(col.c0);\n  r0 = p[0].r;\n  g0 = p[0].g;\n  b0 = p[0].b;\n  p[1] = decode_565(col.c1);\n  r1 = p[1].r;\n  g1 = p[1].g;\n  b1 = p[1].b;\n  if (col.c0 > col.c1) {\n    p[2].r = (uint8_t)((2 * r0 + 1 * r1) / 3);\n    p[2].g = (uint8_t)((2 * g0 + 1 * g1) / 3);\n    p[2].b = (uint8_t)((2 * b0 + 1 * b1) / 3);\n    p[2].a = 0xff;\n    p[3].r = (uint8_t)((1 * r0 + 2 * r1) / 3);\n    p[3].g = (uint8_t)((1 * g0 + 2 * g1) / 3);\n    p[3].b = (uint8_t)((1 * b0 + 2 * b1) / 3);\n    p[3].a = 0xff;\n  } else {\n    p[2].r = (uint8_t)((r0 + r1) / 2);\n    p[2].g = (uint8_t)((g0 + g1) / 2);\n    p[2].b = (uint8_t)((b0 + b1) / 2);\n    p[2].a = 0xff;\n    p[3].r = 0;\n    p[3].g = 0;\n    p[3].b = 0;\n    p[3].a = 0;\n  }\n  for (n = 0; n < 16; n++) {\n    cw = 3 & (col.lut >> (2 * n));\n    dst[n] = p[cw];\n  }\n}\n\nstatic void decode_bc3_alpha(char* dst, const uint8_t* src, int stride, int o) {\n  bc3_alpha b;\n  uint16_t a0, a1;\n  uint8_t a[8];\n  int n, lut, aw;\n  bc3_alpha_load(&b, src);\n\n  a0 = b.a0;\n  a1 = b.a1;\n  a[0] = (uint8_t)a0;\n  a[1] = (uint8_t)a1;\n  if (a0 > a1) {\n    a[2] = (uint8_t)((6 * a0 + 1 * a1) / 7);\n    a[3] = (uint8_t)((5 * a0 + 2 * a1) / 7);\n    a[4] = (uint8_t)((4 * a0 + 3 * a1) / 7);\n    a[5] = (uint8_t)((3 * a0 + 4 * a1) / 7);\n    a[6] = (uint8_t)((2 * a0 + 5 * a1) / 7);\n    a[7] = (uint8_t)((1 * a0 + 6 * a1) / 7);\n  } else {\n    a[2] = (uint8_t)((4 * a0 + 1 * a1) / 5);\n    a[3] = (uint8_t)((3 * a0 + 2 * a1) / 5);\n    a[4] = (uint8_t)((2 * a0 + 3 * a1) / 5);\n    a[5] = (uint8_t)((1 * a0 + 4 * a1) / 5);\n    a[6] = 0;\n    a[7] = 0xff;\n  }\n  lut = b.lut[0] | (b.lut[1] << 8) | (b.lut[2] << 16);\n  for (n = 0; n < 8; n++) {\n    aw = 7 & (lut >> (3 * n));\n    dst[stride * n + o] = a[aw];\n  }\n  lut = b.lut[3] | (b.lut[4] << 8) | (b.lut[5] << 16);\n  for (n = 0; n < 8; n++) {\n    aw = 7 & (lut >> (3 * n));\n    dst[stride * (8 + n) + o] = a[aw];\n  }\n}\n\nstatic void decode_bc1_block(rgba* col, const uint8_t* src) {\n  decode_bc1_color(col, src);\n}\n\nstatic void decode_bc2_block(rgba* col, const uint8_t* src) {\n  int n, bitI, byI;\n  uint8_t av;\n  decode_bc1_color(col, src + 8);\n  for (n = 0; n < 16; n++) {\n    bitI = n * 4;\n    byI = bitI >> 3;\n    av = 0xf & (src[byI] >> (bitI & 7));\n    av = (av << 4) | av;\n    col[n].a = av;\n  }\n}\n\nstatic void decode_bc3_block(rgba* col, const uint8_t* src) {\n  decode_bc1_color(col, src + 8);\n  decode_bc3_alpha((char*)col, src, sizeof(col[0]), 3);\n}\n\nstatic void decode_bc4_block(lum* col, const uint8_t* src) {\n  decode_bc3_alpha((char*)col, src, sizeof(col[0]), 0);\n}\n\nstatic void decode_bc5_block(rgba* col, const uint8_t* src) {\n  decode_bc3_alpha((char*)col, src, sizeof(col[0]), 0);\n  decode_bc3_alpha((char*)col, src + 8, sizeof(col[0]), 1);\n}\n\n/* BC6 and BC7 are described here:\n https://www.opengl.org/registry/specs/ARB/texture_compression_bptc.txt */\n\nstatic uint8_t get_bit(const uint8_t* src, int bit) {\n  int by = bit >> 3;\n  bit &= 7;\n  return (src[by] >> bit) & 1;\n}\n\nstatic uint8_t get_bits(const uint8_t* src, int bit, int count) {\n  uint8_t v;\n  int x;\n  int by = bit >> 3;\n  bit &= 7;\n  if (!count) {\n    return 0;\n  }\n  if (bit + count <= 8) {\n    v = (src[by] >> bit) & ((1 << count) - 1);\n  } else {\n    x = src[by] | (src[by + 1] << 8);\n    v = (x >> bit) & ((1 << count) - 1);\n  }\n  return v;\n}\n\n/* BC7 */\ntypedef struct {\n  char ns;\n  char pb;\n  char rb;\n  char isb;\n  char cb;\n  char ab;\n  char epb;\n  char spb;\n  char ib;\n  char ib2;\n} bc7_mode_info;\n\nstatic const bc7_mode_info bc7_modes[] = {\n    {3, 4, 0, 0, 4, 0, 1, 0, 3, 0}, {2, 6, 0, 0, 6, 0, 0, 1, 3, 0},\n    {3, 6, 0, 0, 5, 0, 0, 0, 2, 0}, {2, 6, 0, 0, 7, 0, 1, 0, 2, 0},\n    {1, 0, 2, 1, 5, 6, 0, 0, 2, 3}, {1, 0, 2, 0, 7, 8, 0, 0, 2, 2},\n    {1, 0, 0, 0, 7, 7, 1, 0, 4, 0}, {2, 6, 0, 0, 5, 5, 1, 0, 2, 0}};\n\n/* Subset indices:\n Table.P2, 1 bit per index */\nstatic const uint16_t bc7_si2[] = {\n    0xcccc, 0x8888, 0xeeee, 0xecc8, 0xc880, 0xfeec, 0xfec8, 0xec80,\n    0xc800, 0xffec, 0xfe80, 0xe800, 0xffe8, 0xff00, 0xfff0, 0xf000,\n    0xf710, 0x008e, 0x7100, 0x08ce, 0x008c, 0x7310, 0x3100, 0x8cce,\n    0x088c, 0x3110, 0x6666, 0x366c, 0x17e8, 0x0ff0, 0x718e, 0x399c,\n    0xaaaa, 0xf0f0, 0x5a5a, 0x33cc, 0x3c3c, 0x55aa, 0x9696, 0xa55a,\n    0x73ce, 0x13c8, 0x324c, 0x3bdc, 0x6996, 0xc33c, 0x9966, 0x0660,\n    0x0272, 0x04e4, 0x4e40, 0x2720, 0xc936, 0x936c, 0x39c6, 0x639c,\n    0x9336, 0x9cc6, 0x817e, 0xe718, 0xccf0, 0x0fcc, 0x7744, 0xee22};\n\n/* Table.P3, 2 bits per index */\nstatic const uint32_t bc7_si3[] = {\n    0xaa685050, 0x6a5a5040, 0x5a5a4200, 0x5450a0a8, 0xa5a50000, 0xa0a05050,\n    0x5555a0a0, 0x5a5a5050, 0xaa550000, 0xaa555500, 0xaaaa5500, 0x90909090,\n    0x94949494, 0xa4a4a4a4, 0xa9a59450, 0x2a0a4250, 0xa5945040, 0x0a425054,\n    0xa5a5a500, 0x55a0a0a0, 0xa8a85454, 0x6a6a4040, 0xa4a45000, 0x1a1a0500,\n    0x0050a4a4, 0xaaa59090, 0x14696914, 0x69691400, 0xa08585a0, 0xaa821414,\n    0x50a4a450, 0x6a5a0200, 0xa9a58000, 0x5090a0a8, 0xa8a09050, 0x24242424,\n    0x00aa5500, 0x24924924, 0x24499224, 0x50a50a50, 0x500aa550, 0xaaaa4444,\n    0x66660000, 0xa5a0a5a0, 0x50a050a0, 0x69286928, 0x44aaaa44, 0x66666600,\n    0xaa444444, 0x54a854a8, 0x95809580, 0x96969600, 0xa85454a8, 0x80959580,\n    0xaa141414, 0x96960000, 0xaaaa1414, 0xa05050a0, 0xa0a5a5a0, 0x96000000,\n    0x40804080, 0xa9a8a9a8, 0xaaaaaa44, 0x2a4a5254};\n\n/* Anchor indices:\n Table.A2 */\nstatic const char bc7_ai0[] = {\n    15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,\n    15, 2,  8,  2,  2,  8,  8,  15, 2,  8,  2,  2,  8,  8,  2,  2,\n    15, 15, 6,  8,  2,  8,  15, 15, 2,  8,  2,  2,  2,  15, 15, 6,\n    6,  2,  6,  8,  15, 15, 2,  2,  15, 15, 15, 15, 15, 2,  2,  15};\n\n/* Table.A3a */\nstatic const char bc7_ai1[] = {\n    3, 3,  15, 15, 8, 3,  15, 15, 8,  8,  6,  6,  6,  5,  3,  3,\n    3, 3,  8,  15, 3, 3,  6,  10, 5,  8,  8,  6,  8,  5,  15, 15,\n    8, 15, 3,  5,  6, 10, 8,  15, 15, 3,  15, 5,  15, 15, 15, 15,\n    3, 15, 5,  5,  5, 8,  5,  10, 5,  10, 8,  13, 15, 12, 3,  3};\n\n/* Table.A3b */\nstatic const char bc7_ai2[] = {\n    15, 8, 8,  3,  15, 15, 3,  8,  15, 15, 15, 15, 15, 15, 15, 8,\n    15, 8, 15, 3,  15, 8,  15, 8,  3,  15, 6,  10, 15, 15, 10, 8,\n    15, 3, 15, 10, 10, 8,  9,  10, 6,  15, 8,  15, 3,  6,  6,  8,\n    15, 3, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 3,  15, 15, 8};\n\n/* Interpolation weights */\nstatic const char bc7_weights2[] = {0, 21, 43, 64};\nstatic const char bc7_weights3[] = {0, 9, 18, 27, 37, 46, 55, 64};\nstatic const char bc7_weights4[] = {0,  4,  9,  13, 17, 21, 26, 30,\n                                    34, 38, 43, 47, 51, 55, 60, 64};\n\nstatic const char* bc7_get_weights(int n) {\n  if (n == 2) {\n    return bc7_weights2;\n  }\n  if (n == 3) {\n    return bc7_weights3;\n  }\n  return bc7_weights4;\n}\n\nstatic int bc7_get_subset(int ns, int partition, int n) {\n  if (ns == 2) {\n    return 1 & (bc7_si2[partition] >> n);\n  }\n  if (ns == 3) {\n    return 3 & (bc7_si3[partition] >> (2 * n));\n  }\n  return 0;\n}\n\nstatic uint8_t expand_quantized(uint8_t v, int bits) {\n  v = v << (8 - bits);\n  return v | (v >> bits);\n}\n\nstatic void bc7_lerp(rgba* dst, const rgba* e, int s0, int s1) {\n  int t0 = 64 - s0;\n  int t1 = 64 - s1;\n  dst->r = (uint8_t)((t0 * e[0].r + s0 * e[1].r + 32) >> 6);\n  dst->g = (uint8_t)((t0 * e[0].g + s0 * e[1].g + 32) >> 6);\n  dst->b = (uint8_t)((t0 * e[0].b + s0 * e[1].b + 32) >> 6);\n  dst->a = (uint8_t)((t1 * e[0].a + s1 * e[1].a + 32) >> 6);\n}\n\nstatic void decode_bc7_block(rgba* col, const uint8_t* src) {\n  rgba endpoints[6];\n  int bit = 0, cibit, aibit;\n  int mode = src[0];\n  int i, j;\n  int numep, cb, ab, ib, ib2, i0, i1, s;\n  uint8_t index_sel, partition, rotation, val;\n  const char *cw, *aw;\n  const bc7_mode_info* info;\n\n  /* mode is the number of unset bits before the first set bit: */\n  if (!mode) {\n    /* degenerate case when no bits set */\n    for (i = 0; i < 16; i++) {\n      col[i].r = col[i].g = col[i].b = 0;\n      col[i].a = 255;\n    }\n    return;\n  }\n  while (!(mode & (1 << bit++)));\n  mode = bit - 1;\n  info = &bc7_modes[mode];\n  /* color selection bits: {subset}{endpoint} */\n  cb = info->cb;\n  ab = info->ab;\n  cw = bc7_get_weights(info->ib);\n  aw = bc7_get_weights((ab && info->ib2) ? info->ib2 : info->ib);\n\n#define LOAD(DST, N)           \\\n  DST = get_bits(src, bit, N); \\\n  bit += N;\n  LOAD(partition, info->pb);\n  LOAD(rotation, info->rb);\n  LOAD(index_sel, info->isb);\n  numep = info->ns << 1;\n\n  /* red */\n  for (i = 0; i < numep; i++) {\n    LOAD(val, cb);\n    endpoints[i].r = val;\n  }\n\n  /* green */\n  for (i = 0; i < numep; i++) {\n    LOAD(val, cb);\n    endpoints[i].g = val;\n  }\n\n  /* blue */\n  for (i = 0; i < numep; i++) {\n    LOAD(val, cb);\n    endpoints[i].b = val;\n  }\n\n  /* alpha */\n  for (i = 0; i < numep; i++) {\n    if (ab) {\n      LOAD(val, ab);\n    } else {\n      val = 255;\n    }\n    endpoints[i].a = val;\n  }\n\n/* p-bits */\n#define ASSIGN_P(x) x = (x << 1) | val\n  if (info->epb) {\n    /* per endpoint */\n    cb++;\n    if (ab) {\n      ab++;\n    }\n    for (i = 0; i < numep; i++) {\n      LOAD(val, 1);\n      ASSIGN_P(endpoints[i].r);\n      ASSIGN_P(endpoints[i].g);\n      ASSIGN_P(endpoints[i].b);\n      if (ab) {\n        ASSIGN_P(endpoints[i].a);\n      }\n    }\n  }\n  if (info->spb) {\n    /* per subset */\n    cb++;\n    if (ab) {\n      ab++;\n    }\n    for (i = 0; i < numep; i += 2) {\n      LOAD(val, 1);\n      for (j = 0; j < 2; j++) {\n        ASSIGN_P(endpoints[i + j].r);\n        ASSIGN_P(endpoints[i + j].g);\n        ASSIGN_P(endpoints[i + j].b);\n        if (ab) {\n          ASSIGN_P(endpoints[i + j].a);\n        }\n      }\n    }\n  }\n#undef ASSIGN_P\n#define EXPAND(x, b) x = expand_quantized(x, b)\n  for (i = 0; i < numep; i++) {\n    EXPAND(endpoints[i].r, cb);\n    EXPAND(endpoints[i].g, cb);\n    EXPAND(endpoints[i].b, cb);\n    if (ab) {\n      EXPAND(endpoints[i].a, ab);\n    }\n  }\n#undef EXPAND\n#undef LOAD\n  cibit = bit;\n  aibit = cibit + 16 * info->ib - info->ns;\n  for (i = 0; i < 16; i++) {\n    s = bc7_get_subset(info->ns, partition, i) << 1;\n    ib = info->ib;\n    if (i == 0) {\n      ib--;\n    } else if (info->ns == 2) {\n      if (i == bc7_ai0[partition]) {\n        ib--;\n      }\n    } else if (info->ns == 3) {\n      if (i == bc7_ai1[partition]) {\n        ib--;\n      } else if (i == bc7_ai2[partition]) {\n        ib--;\n      }\n    }\n    i0 = get_bits(src, cibit, ib);\n    cibit += ib;\n\n    if (ab && info->ib2) {\n      ib2 = info->ib2;\n      if (ib2 && i == 0) {\n        ib2--;\n      }\n      i1 = get_bits(src, aibit, ib2);\n      aibit += ib2;\n      if (index_sel) {\n        bc7_lerp(&col[i], &endpoints[s], aw[i1], cw[i0]);\n      } else {\n        bc7_lerp(&col[i], &endpoints[s], cw[i0], aw[i1]);\n      }\n    } else {\n      bc7_lerp(&col[i], &endpoints[s], cw[i0], cw[i0]);\n    }\n#define ROTATE(x, y) \\\n  val = x;           \\\n  x = y;             \\\n  y = val\n    if (rotation == 1) {\n      ROTATE(col[i].r, col[i].a);\n    } else if (rotation == 2) {\n      ROTATE(col[i].g, col[i].a);\n    } else if (rotation == 3) {\n      ROTATE(col[i].b, col[i].a);\n    }\n#undef ROTATE\n  }\n}\n\n/* BC6 */\ntypedef struct {\n  char ns;  /* number of subsets (also called regions) */\n  char tr;  /* whether endpoints are delta-compressed */\n  char pb;  /* partition bits */\n  char epb; /* endpoint bits */\n  char rb;  /* red bits (delta) */\n  char gb;  /* green bits (delta) */\n  char bb;  /* blue bits (delta) */\n} bc6_mode_info;\n\nstatic const bc6_mode_info bc6_modes[] = {\n    // 00\n    {2, 1, 5, 10, 5, 5, 5},\n    // 01\n    {2, 1, 5, 7, 6, 6, 6},\n    // 10\n    {2, 1, 5, 11, 5, 4, 4},\n    {2, 1, 5, 11, 4, 5, 4},\n    {2, 1, 5, 11, 4, 4, 5},\n    {2, 1, 5, 9, 5, 5, 5},\n    {2, 1, 5, 8, 6, 5, 5},\n    {2, 1, 5, 8, 5, 6, 5},\n    {2, 1, 5, 8, 5, 5, 6},\n    {2, 0, 5, 6, 6, 6, 6},\n    // 11\n    {1, 0, 0, 10, 10, 10, 10},\n    {1, 1, 0, 11, 9, 9, 9},\n    {1, 1, 0, 12, 8, 8, 8},\n    {1, 1, 0, 16, 4, 4, 4}};\n\n/* Table.F, encoded as a sequence of bit indices */\nstatic const uint8_t bc6_bit_packings[][75] = {\n    {116, 132, 176, 0,   1,   2,   3,   4,   5,   6,   7,   8,   9,   16,  17,\n     18,  19,  20,  21,  22,  23,  24,  25,  32,  33,  34,  35,  36,  37,  38,\n     39,  40,  41,  48,  49,  50,  51,  52,  164, 112, 113, 114, 115, 64,  65,\n     66,  67,  68,  172, 160, 161, 162, 163, 80,  81,  82,  83,  84,  173, 128,\n     129, 130, 131, 96,  97,  98,  99,  100, 174, 144, 145, 146, 147, 148, 175},\n    {117, 164, 165, 0,  1,   2,   3,   4,   5,   6,   172, 173, 132, 16,  17,\n     18,  19,  20,  21, 22,  133, 174, 116, 32,  33,  34,  35,  36,  37,  38,\n     175, 177, 176, 48, 49,  50,  51,  52,  53,  112, 113, 114, 115, 64,  65,\n     66,  67,  68,  69, 160, 161, 162, 163, 80,  81,  82,  83,  84,  85,  128,\n     129, 130, 131, 96, 97,  98,  99,  100, 101, 144, 145, 146, 147, 148, 149},\n    {0,   1,   2,   3,   4,   5,   6,   7,   8,   9,   16,  17,  18,  19,  20,\n     21,  22,  23,  24,  25,  32,  33,  34,  35,  36,  37,  38,  39,  40,  41,\n     48,  49,  50,  51,  52,  10,  112, 113, 114, 115, 64,  65,  66,  67,  26,\n     172, 160, 161, 162, 163, 80,  81,  82,  83,  42,  173, 128, 129, 130, 131,\n     96,  97,  98,  99,  100, 174, 144, 145, 146, 147, 148, 175},\n    {0,  1,   2,   3,   4,   5,   6,   7,   8,   9,   16,  17,  18,  19,  20,\n     21, 22,  23,  24,  25,  32,  33,  34,  35,  36,  37,  38,  39,  40,  41,\n     48, 49,  50,  51,  10,  164, 112, 113, 114, 115, 64,  65,  66,  67,  68,\n     26, 160, 161, 162, 163, 80,  81,  82,  83,  42,  173, 128, 129, 130, 131,\n     96, 97,  98,  99,  172, 174, 144, 145, 146, 147, 116, 175},\n    {0,   1,   2,   3,   4,   5,   6,   7,   8,   9,   16,  17,  18,  19,  20,\n     21,  22,  23,  24,  25,  32,  33,  34,  35,  36,  37,  38,  39,  40,  41,\n     48,  49,  50,  51,  10,  132, 112, 113, 114, 115, 64,  65,  66,  67,  26,\n     172, 160, 161, 162, 163, 80,  81,  82,  83,  84,  42,  128, 129, 130, 131,\n     96,  97,  98,  99,  173, 174, 144, 145, 146, 147, 176, 175},\n    {0,   1,   2,   3,   4,   5,   6,   7,   8,   132, 16,  17,  18,  19,  20,\n     21,  22,  23,  24,  116, 32,  33,  34,  35,  36,  37,  38,  39,  40,  176,\n     48,  49,  50,  51,  52,  164, 112, 113, 114, 115, 64,  65,  66,  67,  68,\n     172, 160, 161, 162, 163, 80,  81,  82,  83,  84,  173, 128, 129, 130, 131,\n     96,  97,  98,  99,  100, 174, 144, 145, 146, 147, 148, 175},\n    {0,   1,   2,   3,   4,   5,   6,   7,   164, 132, 16,  17,  18,  19,  20,\n     21,  22,  23,  174, 116, 32,  33,  34,  35,  36,  37,  38,  39,  175, 176,\n     48,  49,  50,  51,  52,  53,  112, 113, 114, 115, 64,  65,  66,  67,  68,\n     172, 160, 161, 162, 163, 80,  81,  82,  83,  84,  173, 128, 129, 130, 131,\n     96,  97,  98,  99,  100, 101, 144, 145, 146, 147, 148, 149},\n    {0,  1,   2,   3,   4,   5,   6,   7,   172, 132, 16,  17,  18,  19,  20,\n     21, 22,  23,  117, 116, 32,  33,  34,  35,  36,  37,  38,  39,  165, 176,\n     48, 49,  50,  51,  52,  164, 112, 113, 114, 115, 64,  65,  66,  67,  68,\n     69, 160, 161, 162, 163, 80,  81,  82,  83,  84,  173, 128, 129, 130, 131,\n     96, 97,  98,  99,  100, 174, 144, 145, 146, 147, 148, 175},\n    {0,   1,   2,   3,   4,   5,   6,   7,   173, 132, 16,  17,  18,  19,  20,\n     21,  22,  23,  133, 116, 32,  33,  34,  35,  36,  37,  38,  39,  177, 176,\n     48,  49,  50,  51,  52,  164, 112, 113, 114, 115, 64,  65,  66,  67,  68,\n     172, 160, 161, 162, 163, 80,  81,  82,  83,  84,  85,  128, 129, 130, 131,\n     96,  97,  98,  99,  100, 174, 144, 145, 146, 147, 148, 175},\n    {0,  1,   2,   3,   4,   5,   164, 172, 173, 132, 16,  17,  18,  19,  20,\n     21, 117, 133, 174, 116, 32,  33,  34,  35,  36,  37,  165, 175, 177, 176,\n     48, 49,  50,  51,  52,  53,  112, 113, 114, 115, 64,  65,  66,  67,  68,\n     69, 160, 161, 162, 163, 80,  81,  82,  83,  84,  85,  128, 129, 130, 131,\n     96, 97,  98,  99,  100, 101, 144, 145, 146, 147, 148, 149},\n    {0,  1,  2,  3,  4,  5,  6,  7,  8,  9,  16, 17, 18, 19, 20,\n     21, 22, 23, 24, 25, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41,\n     48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 64, 65, 66, 67, 68,\n     69, 70, 71, 72, 73, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89},\n    {0,  1,  2,  3,  4,  5,  6,  7,  8,  9,  16, 17, 18, 19, 20,\n     21, 22, 23, 24, 25, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41,\n     48, 49, 50, 51, 52, 53, 54, 55, 56, 10, 64, 65, 66, 67, 68,\n     69, 70, 71, 72, 26, 80, 81, 82, 83, 84, 85, 86, 87, 88, 42},\n    {0,  1,  2,  3,  4,  5,  6,  7,  8,  9,  16, 17, 18, 19, 20,\n     21, 22, 23, 24, 25, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41,\n     48, 49, 50, 51, 52, 53, 54, 55, 11, 10, 64, 65, 66, 67, 68,\n     69, 70, 71, 27, 26, 80, 81, 82, 83, 84, 85, 86, 87, 43, 42},\n    {0,  1,  2,  3,  4,  5,  6,  7,  8,  9,  16, 17, 18, 19, 20,\n     21, 22, 23, 24, 25, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41,\n     48, 49, 50, 51, 15, 14, 13, 12, 11, 10, 64, 65, 66, 67, 31,\n     30, 29, 28, 27, 26, 80, 81, 82, 83, 47, 46, 45, 44, 43, 42}};\n\nstatic void bc6_sign_extend(uint16_t* v, int prec) {\n  int x = *v;\n  if (x & (1 << (prec - 1))) {\n    x |= -1 << prec;\n  }\n  *v = (uint16_t)x;\n}\n\nstatic int bc6_unquantize(uint16_t v, int prec, int sign) {\n  int s = 0;\n  int x;\n  if (!sign) {\n    x = v;\n    if (prec >= 15) return x;\n    if (x == 0) return 0;\n    if (x == ((1 << prec) - 1)) {\n      return 0xffff;\n    }\n    return ((x << 15) + 0x4000) >> (prec - 1);\n  } else {\n    x = (int16_t)v;\n    if (prec >= 16) return x;\n    if (x < 0) {\n      s = 1;\n      x = -x;\n    }\n\n    if (x != 0) {\n      if (x >= ((1 << (prec - 1)) - 1)) {\n        x = 0x7fff;\n      } else {\n        x = ((x << 15) + 0x4000) >> (prec - 1);\n      }\n    }\n\n    if (s) {\n      return -x;\n    }\n    return x;\n  }\n}\n\nstatic float half_to_float(uint16_t h) {\n  /* https://gist.github.com/rygorous/2144712 */\n  union {\n    uint32_t u;\n    float f;\n  } o, m;\n  m.u = 0x77800000;\n  o.u = (h & 0x7fff) << 13;\n  o.f *= m.f;\n  m.u = 0x47800000;\n  if (o.f >= m.f) {\n    o.u |= 255 << 23;\n  }\n  o.u |= (h & 0x8000) << 16;\n  return o.f;\n}\n\nstatic float bc6_finalize(int v, int sign) {\n  if (sign) {\n    if (v < 0) {\n      v = ((-v) * 31) / 32;\n      return half_to_float((uint16_t)(0x8000 | v));\n    } else {\n      return half_to_float((uint16_t)((v * 31) / 32));\n    }\n  } else {\n    return half_to_float((uint16_t)((v * 31) / 64));\n  }\n}\n\nstatic void bc6_lerp(rgb32f* col, int* e0, int* e1, int s, int sign) {\n  int r, g, b;\n  int t = 64 - s;\n  r = (e0[0] * t + e1[0] * s) >> 6;\n  g = (e0[1] * t + e1[1] * s) >> 6;\n  b = (e0[2] * t + e1[2] * s) >> 6;\n  col->r = bc6_finalize(r, sign);\n  col->g = bc6_finalize(g, sign);\n  col->b = bc6_finalize(b, sign);\n}\n\nstatic void decode_bc6_block(rgb32f* col, const uint8_t* src, int sign) {\n  uint16_t endpoints[12]; /* storage for r0, g0, b0, r1, ... */\n  int ueps[12];\n  int i, i0, ib2, di, dw, mask, numep, s;\n  uint8_t partition;\n  const bc6_mode_info* info;\n  const char* cw;\n  int bit = 5;\n  int epbits = 75;\n  int ib = 3;\n  int mode = src[0] & 0x1f;\n  if ((mode & 3) == 0 || (mode & 3) == 1) {\n    mode &= 3;\n    bit = 2;\n  } else if ((mode & 3) == 2) {\n    mode = 2 + (mode >> 2);\n    epbits = 72;\n  } else {\n    mode = 10 + (mode >> 2);\n    epbits = 60;\n    ib = 4;\n  }\n  if (mode >= 14) {\n    /* invalid block */\n    memset(col, 0, 16 * sizeof(col[0]));\n    return;\n  }\n  info = &bc6_modes[mode];\n  cw = bc7_get_weights(ib);\n  numep = info->ns == 2 ? 12 : 6;\n  for (i = 0; i < 12; i++) {\n    endpoints[i] = 0;\n  }\n  for (i = 0; i < epbits; i++) {\n    di = bc6_bit_packings[mode][i];\n    dw = di >> 4;\n    di &= 15;\n    endpoints[dw] |= (uint16_t)get_bit(src, bit + i) << di;\n  }\n  bit += epbits;\n  partition = get_bits(src, bit, info->pb);\n  bit += info->pb;\n  mask = (1 << info->epb) - 1;\n  if (sign) { /* sign-extend e0 if signed */\n    bc6_sign_extend(&endpoints[0], info->epb);\n    bc6_sign_extend(&endpoints[1], info->epb);\n    bc6_sign_extend(&endpoints[2], info->epb);\n  }\n  if (sign || info->tr) { /* sign-extend e1,2,3 if signed or deltas */\n    for (i = 3; i < numep; i += 3) {\n      bc6_sign_extend(&endpoints[i + 0], info->rb);\n      bc6_sign_extend(&endpoints[i + 1], info->gb);\n      bc6_sign_extend(&endpoints[i + 2], info->bb);\n    }\n  }\n  if (info->tr) { /* apply deltas */\n    for (i = 3; i < numep; i++) {\n      endpoints[i] = (endpoints[i] + endpoints[0]) & mask;\n    }\n    if (sign) {\n      for (i = 3; i < numep; i += 3) {\n        bc6_sign_extend(&endpoints[i + 0], info->rb);\n        bc6_sign_extend(&endpoints[i + 1], info->gb);\n        bc6_sign_extend(&endpoints[i + 2], info->bb);\n      }\n    }\n  }\n  for (i = 0; i < numep; i++) {\n    ueps[i] = bc6_unquantize(endpoints[i], info->epb, sign);\n  }\n  for (i = 0; i < 16; i++) {\n    s = bc7_get_subset(info->ns, partition, i) * 6;\n    ib2 = ib;\n    if (i == 0) {\n      ib2--;\n    } else if (info->ns == 2) {\n      if (i == bc7_ai0[partition]) {\n        ib2--;\n      }\n    }\n    i0 = get_bits(src, bit, ib2);\n    bit += ib2;\n\n    bc6_lerp(&col[i], &ueps[s], &ueps[s + 3], cw[i0], sign);\n  }\n}\n\ntypedef struct {\n  // Destination buffer, a bitmap.\n  // For N=1, 2, 3, 5, 7: 4 bytes-per-pixel\n  // For N=4, 1 byte-per-pixel\n  // For N=6, 16 bytes-per-pixel (32-bit float)\n  uint8_t* dst;\n  // Destination region offset\n  int xoff, yoff;\n  // Destination region size\n  int width, height;\n  // Current pixel to be written\n  int x, y;\n  // If < 0, the image will be flipped on the y-axis\n  int8_t ystep;\n  // For bc6, data is signed numbers if true.\n  uint8_t sign;\n  // Swizzle components as necessary to match the bitmap format\n  // 2 bits per component; least-significant two are index of red channel,\n  // then green, blue, alpha\n  uint8_t swizzle;\n} BcnDecoderState;\n\nstatic void swizzle_copy(int swizzle, uint8_t* dst, const uint8_t* src,\n                         int sz) {\n  if (sz < 4 || swizzle == 0 || swizzle == 0xe4) {\n    memcpy(dst, src, sz);\n    return;\n  }\n\n  // bring sz down to size-per-component\n  sz >>= 2;\n  memcpy(dst + sz * ((swizzle & 3)), src, sz);\n  memcpy(dst + sz * ((swizzle & 0x0c) >> 2), src + sz, sz);\n  memcpy(dst + sz * ((swizzle & 0x30) >> 4), src + 2 * sz, sz);\n  memcpy(dst + sz * ((swizzle & 0xc0) >> 6), src + 3 * sz, sz);\n}\n\nstatic void put_block(BcnDecoderState* state, const uint8_t* col, int sz,\n                      int C) {\n  int width = state->width;\n  int height = state->height;\n  int xmax = width + state->xoff;\n  int ymax = height + state->yoff;\n  int j, i, y, x;\n  uint8_t* dst;\n  const uint8_t* src;\n  for (j = 0; j < 4; j++) {\n    y = state->y + j;\n    if (C) {\n      if (y >= height) {\n        continue;\n      }\n      if (state->ystep < 0) {\n        y = state->yoff + ymax - y - 1;\n      }\n      dst = state->dst + (sz * state->width * y);\n      for (i = 0; i < 4; i++) {\n        x = state->x + i;\n        if (x >= width) {\n          continue;\n        }\n        swizzle_copy(state->swizzle, dst + sz * x, col + sz * (j * 4 + i), sz);\n      }\n    } else {\n      if (state->ystep < 0) {\n        y = state->yoff + ymax - y - 1;\n      }\n      x = state->x;\n      dst = state->dst + (sz * state->width * y) + sz * x;\n      src = col + sz * (j * 4);\n      for (i = 0; i < 4; i++) {\n        swizzle_copy(state->swizzle, dst, src, sz);\n        dst += sz;\n        src += sz;\n      }\n    }\n  }\n  state->x += 4;\n  if (state->x >= xmax) {\n    state->y += 4;\n    state->x = state->xoff;\n  }\n}\n\nstatic int decode_bcn(BcnDecoderState* state, const uint8_t* src, int bytes,\n                      int N, int C) {\n  int ymax = state->height + state->yoff;\n  const uint8_t* ptr = src;\n  switch (N) {\n#define DECODE_LOOP(NN, SZ, TY, ...)                            \\\n  case NN:                                                      \\\n    while (bytes >= SZ) {                                       \\\n      TY col[16];                                               \\\n      memset(col, 0, 16 * sizeof(col[0]));                      \\\n      decode_bc##NN##_block(col, ptr);                          \\\n      put_block(state, (const uint8_t*)col, sizeof(col[0]), C); \\\n      ptr += SZ;                                                \\\n      bytes -= SZ;                                              \\\n      if (state->y >= ymax) break;                              \\\n    }                                                           \\\n    break\n    DECODE_LOOP(1, 8, rgba);\n    DECODE_LOOP(2, 16, rgba);\n    DECODE_LOOP(3, 16, rgba);\n    DECODE_LOOP(4, 8, lum);\n    DECODE_LOOP(5, 16, rgba);\n    case 6:\n      while (bytes >= 16) {\n        rgb32f col[16];\n        decode_bc6_block(col, ptr, state->sign);\n        put_block(state, (const uint8_t*)col, sizeof(col[0]), C);\n        ptr += 16;\n        bytes -= 16;\n        if (state->y >= ymax) break;\n      }\n      break;\n      DECODE_LOOP(7, 16, rgba);\n#undef DECODE_LOOP\n  }\n  return (int)(ptr - src);\n}\n\nint BcnDecode(uint8_t* dst, int dst_size, const uint8_t* src, int src_size,\n              int width, int height, int N, int dst_format, int flip) {\n  BcnDecoderState state = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};\n  if (width == 0 || height == 0) {\n    return 0;\n  }\n  if (dst_size < 4 * width * height) {\n    return -1;\n  }\n  if (N < 1 || N > 6) {\n    return -1;\n  }\n  switch (dst_format) {\n    case BcnDecoderFormatRGBA:\n      state.swizzle = 0b11100100;\n      break;\n    case BcnDecoderFormatBGRA:\n      state.swizzle = 0b11000110;\n      break;\n    case BcnDecoderFormatARGB:\n      state.swizzle = 0b10010011;\n      break;\n    case BcnDecoderFormatABGR:\n      state.swizzle = 0b00011011;\n      break;\n    default:\n      return -1;\n  }\n  state.width = width;\n  state.height = height;\n  state.dst = dst;\n  state.ystep = flip ? -1 : 1;\n  if ((width & 3) | (height & 3)) {\n    return decode_bcn(&state, src, src_size, N, 1);\n  } else {\n    return decode_bcn(&state, src, src_size, N, 0);\n  }\n}\n\n#ifdef BCN_DECODER_TEST\n\nstatic void wcb(void* ctx, void* data, int size) {\n  fwrite(data, 1, size, stdout);\n}\n/*\nint main(int argc, char **argv) {\n    int width = 0;\n    int height = 0;\n    int N = 0;\n    int dst_format = 1;\n    int flip = 0;\n    if (argc <= 1 || strchr(argv[1], 'h')) {\n        puts(\"Usage: bcndecode [width] [height] [BCn type] [component layout] [y\nflip]\"); return 0;\n    }\n    for (int i = 1; i < argc; i++) {\n        int v = atoi(argv[i]);\n        switch (i) {\n            case 1: width = v; break;\n            case 2: height = v; break;\n            case 3: N = v; break;\n            case 4: dst_format = v; break;\n            case 5: flip = v; break;\n        }\n    }\n\n\n\n    int src_size = 4 * width * height;\n    int dst_size = src_size;\n    if (N == 1 || N == 4) {\n        src_size >>= 3;\n    } else {\n        src_size >>= 2;\n    }\n    if (N == 4) {\n        dst_size >>= 2;\n    } else if (N == 6) {\n        dst_size <<= 2;\n    }\n    uint8_t *src = (uint8_t *)malloc(src_size + dst_size);\n    uint8_t *dst = src + src_size;\n    fread(src, 1, src_size, stdin);\n    if (BcnDecode(dst, dst_size, src, src_size, width, height, N, dst_format,\nflip) < 0) { return 2;\n    }\n    if (N != 6) {\n        int sz = N == 4 ? 1 : 4;\n        return !stbi_write_png_to_func(wcb, 0, width, height, sz, dst, sz *\nwidth); } else { return !stbi_write_hdr_to_func(wcb, 0, width, height, 4, (float\n*)dst);\n    }\n}*/\n#endif"
  },
  {
    "path": "src/texture/bcdecode.h",
    "content": "﻿#pragma once\n\n#include \"../io/io.h\"\n#include \"texture.h\"\n\ntypedef enum {\n  BcnDecoderFormatRGBA = 1,\n  BcnDecoderFormatBGRA = 2,\n  BcnDecoderFormatARGB = 3,\n  BcnDecoderFormatABGR = 4\n} BcnDecoderFormat;\n\nint BcnDecode(uint8_t* dst, int dst_size, const uint8_t* src, int src_size,\n              int width, int height, int N, int dst_format, int flip);"
  },
  {
    "path": "src/texture/bntxloader.cpp",
    "content": "#include \"../log.h\"\n\n#include \"bntxloader.h\"\n#include \"bcdecode.h\"\n\nusing namespace Impacto::Io;\n\nnamespace Impacto {\n\nnamespace TexLoad {\n\nint CountLsbZeros(int value) {\n  int count = 0;\n\n  while (((value >> count) & 1) == 0) {\n    count++;\n  }\n\n  return count;\n}\n\nint Pow2RoundUp(int value) {\n  value--;\n\n  value |= (value >> 1);\n  value |= (value >> 2);\n  value |= (value >> 4);\n  value |= (value >> 8);\n  value |= (value >> 16);\n\n  return ++value;\n}\n\nint DivRoundUp(int lhs, int rhs) { return (lhs + (rhs - 1)) / rhs; }\n\nuint8_t* UnSwizzle(int width, int height, int blkWidth, int blkHeight, int bpp,\n                   int blkHeightLog2, uint8_t* data) {\n  width = DivRoundUp(width, blkWidth);\n  height = DivRoundUp(height, blkHeight);\n\n  blkHeight = 1 << blkHeightLog2;\n\n  int bppShift = CountLsbZeros(bpp);\n  int widthInGobs = DivRoundUp(width * bpp, 64);\n  int pow2Height = Pow2RoundUp(height);\n\n  while (blkHeight * 8 > pow2Height && blkHeight > 1) {\n    blkHeight >>= 1;\n  }\n\n  int bhMask = (blkHeight * 8) - 1;\n  int bhShift = CountLsbZeros(blkHeight * 8);\n  int robSize = 512 * blkHeight * widthInGobs;\n  int xShift = CountLsbZeros(512 * blkHeight);\n  int pitch = (width * bpp + 3) & ~3;\n  auto result = (uint8_t*)malloc(height * pitch);\n\n  for (int y = 0; y < height; y++) {\n    for (int x = 0; x < width; x++) {\n      int outOffs = (y * width + x) * bpp;\n      int x1 = x << bppShift;\n      int position = (y >> bhShift) * robSize;\n      position += (x1 >> 6) << xShift;\n      position += ((y & bhMask) >> 3) << 9;\n      position += ((x1 & 0x3f) >> 5) << 8;\n      position += ((y & 0x07) >> 1) << 6;\n      position += ((x1 & 0x1f) >> 4) << 5;\n      position += ((y & 0x01) >> 0) << 4;\n      position += ((x1 & 0x0f) >> 0) << 0;\n\n      for (int i = 0; i < bpp; i++) result[outOffs + i] = data[position + i];\n    }\n  }\n\n  return result;\n}\n\nstatic uint64_t const magic = 0x424E5458;\n\nint BPPbyFormat(TextureFormatType format) {\n  switch (format) {\n    case R5G6B5:\n      break;\n    case R8G8:\n      break;\n    case R16:\n      break;\n    case R8G8B8A8:\n      return 4;\n      break;\n    case R11G11B10:\n      break;\n    case R32:\n      break;\n    case BC1:\n      return 8;\n      break;\n    case BC2:\n      return 16;\n      break;\n    case BC3:\n      return 16;\n      break;\n    case BC4:\n      return 8;\n      break;\n    case BC5:\n      return 16;\n      break;\n    case BC6:\n      return 16;\n      break;\n    case BC7:\n      return 8;\n      break;\n    case ASTC4x4:\n      break;\n    case ASTC5x4:\n      break;\n    case ASTC5x5:\n      break;\n    case ASTC6x5:\n      break;\n    case ASTC6x6:\n      break;\n    case ASTC8x5:\n      break;\n    case ASTC8x6:\n      break;\n    case ASTC8x8:\n      break;\n    case ASTC10x5:\n      break;\n    case ASTC10x6:\n      break;\n    case ASTC10x8:\n      break;\n    case ASTC10x10:\n      break;\n    case ASTC12x10:\n      break;\n    case ASTC12x12:\n      break;\n    default:;\n  }\n  ImpLog(LogLevel::Warning, LogChannel::TextureLoad,\n         \"Unknown texture format, returning 0!\\n\");\n  return 0;\n}\n\nTextureNX::TextureNX() {}\n\nuint32_t TextureNX::Pow2RoundUp(uint32_t Value) {\n  Value--;\n\n  Value |= (Value >> 1);\n  Value |= (Value >> 2);\n  Value |= (Value >> 4);\n  Value |= (Value >> 8);\n  Value |= (Value >> 16);\n\n  return ++Value;\n}\n\nuint32_t TextureNX::GetPow2HeightInTexels() {\n  uint32_t Pow2Height = Pow2RoundUp(Height);\n\n  switch (FormatType) {\n    case BC1:\n    case BC2:\n    case BC3:\n    case BC4:\n    case BC5:\n    case ASTC4x4:\n    case ASTC5x4:\n      return (Pow2Height + 3) / 4;\n\n    case ASTC5x5:\n    case ASTC6x5:\n    case ASTC8x5:\n      return (Pow2Height + 4) / 5;\n\n    case ASTC6x6:\n    case ASTC8x6:\n    case ASTC10x6:\n      return (Pow2Height + 5) / 6;\n\n    case ASTC8x8:\n    case ASTC10x8:\n      return (Pow2Height + 7) / 8;\n\n    case ASTC10x10:\n    case ASTC12x10:\n      return (Pow2Height + 9) / 10;\n\n    case ASTC12x12:\n      return (Pow2Height + 11) / 12;\n    default:\n      break;\n  }\n\n  return Pow2Height;\n}\n\nuint32_t TextureNX::GetBytesPerTexel() {\n  switch (FormatType) {\n    case R5G6B5:\n    case R8G8:\n    case R16:\n      return 2;\n\n    case R8G8B8A8:\n    case R11G11B10:\n    case R32:\n      return 4;\n\n    case BC1:\n    case BC4:\n      return 8;\n\n    case BC2:\n    case BC3:\n    case BC5:\n    case ASTC4x4:\n    case ASTC5x4:\n    case ASTC5x5:\n    case ASTC6x5:\n    case ASTC6x6:\n    case ASTC8x5:\n    case ASTC8x6:\n    case ASTC8x8:\n    case ASTC10x5:\n    case ASTC10x6:\n    case ASTC10x8:\n    case ASTC10x10:\n    case ASTC12x10:\n    case ASTC12x12:\n      return 16;\n    default:\n      break;\n  }\n  ImpLog(LogLevel::Warning, LogChannel::TextureLoad,\n         \"Unknown number of bytes per texel, returning 0!\\n\");\n  return 0;\n}\n\nuint32_t TextureNX::GetBlockHeight() { return 1 << BlockHeightLog2; }\n\nuint8_t* BCnDecompress(uint8_t* dataBuff, TextureNX element, int n) {\n  int s = element.Height * element.Width * 4;\n  uint8_t* dst = (uint8_t*)malloc(s);\n\n  BcnDecode(dst, s, dataBuff,\n            element.GetBytesPerTexel() * element.Height * element.Width,\n            element.Width, element.Height, n, BcnDecoderFormatRGBA, 0);\n\n  return dst;\n}\n\nbool TextureLoadBNTX(Stream* stream, Texture* outTexture) {\n  // Read metadata\n\n  if (ReadBE<uint32_t>(stream) != magic) {\n    stream->Seek(0, RW_SEEK_SET);\n    return false;\n  }\n  bool result = false;\n  ImpLogSlow(LogLevel::Debug, LogChannel::TextureLoad,\n             \"Loading BNTX texture\\n\");\n\n  stream->Seek(4, RW_SEEK_CUR);\n  // uint64_t BnTxSignature = ReadLE<uint64_t>(stream);\n  uint32_t DataLength = ReadLE<uint32_t>(stream);\n  (void)DataLength;\n  uint16_t ByteOrderMark = ReadLE<uint16_t>(stream);\n  (void)ByteOrderMark;\n  uint16_t FormatRevision = ReadLE<uint16_t>(stream);\n  (void)FormatRevision;\n  uint32_t NameAddress = ReadLE<uint32_t>(stream);\n  (void)NameAddress;\n  uint32_t StringsAddress = ReadLE<uint32_t>(stream);\n  (void)StringsAddress;\n  uint32_t RelocAddress = ReadLE<uint32_t>(stream);\n  (void)RelocAddress;\n  uint32_t FileLength = ReadLE<uint32_t>(stream);\n  (void)FileLength;\n  uint32_t NXSignature = ReadLE<uint32_t>(stream);\n  (void)NXSignature;\n\n  uint32_t TexturesCount = ReadLE<uint32_t>(stream);\n  ImpLog(LogLevel::Debug, LogChannel::General, \"{:d}\\n\", TexturesCount);\n  uint64_t InfoPtrsAddress = ReadLE<uint64_t>(stream);\n  uint64_t DataBlkAddress = ReadLE<uint64_t>(stream);\n  (void)DataBlkAddress;\n  uint64_t DictAddress = ReadLE<uint64_t>(stream);\n  (void)DictAddress;\n  uint32_t StrDictLength = ReadLE<uint32_t>(stream);\n  (void)StrDictLength;\n\n  for (uint32_t Index = 0; Index < TexturesCount; Index++) {\n    stream->Seek(InfoPtrsAddress + Index * 8, 0);\n    uint64_t offset = ReadLE<uint64_t>(stream);\n\n    stream->Seek(offset, 0);\n\n    uint32_t BRTISignature = ReadLE<uint32_t>(stream);\n    (void)BRTISignature;\n\n    // CheckSignature(L\"BRTI\", BRTISignature);\n\n    uint32_t BRTILength0 = ReadLE<uint32_t>(stream);\n    (void)BRTILength0;\n\n    uint64_t BRTILength1 = ReadLE<uint64_t>(stream);\n    (void)BRTILength1;\n\n    uint8_t TileMode = ReadLE<uint8_t>(stream);\n\n    uint8_t Dimensions = ReadLE<uint8_t>(stream);\n    (void)Dimensions;\n\n    uint16_t unknown = ReadLE<uint16_t>(stream);\n    (void)unknown;\n    uint16_t SwizzleSize = ReadLE<uint16_t>(stream);\n    (void)SwizzleSize;\n    uint16_t MipmapCount = ReadLE<uint16_t>(stream);\n    uint16_t MultiSampleCount = ReadLE<uint16_t>(stream);\n    (void)MultiSampleCount;\n    uint16_t Reversed1A = ReadLE<uint16_t>(stream);\n    (void)Reversed1A;\n\n    uint32_t Format = ReadLE<uint32_t>(stream);\n\n    uint32_t AccessFlags = ReadLE<uint32_t>(stream);\n    (void)AccessFlags;\n\n    uint32_t Width = ReadLE<uint32_t>(stream);\n    uint32_t Height = ReadLE<uint32_t>(stream);\n    uint32_t Depth = ReadLE<uint32_t>(stream);\n    (void)Depth;\n    uint32_t ArrayCount = ReadLE<uint32_t>(stream);\n    uint32_t BlockHeightLog2 = ReadLE<uint32_t>(stream);\n    uint32_t Reserved38 = ReadLE<uint32_t>(stream);\n    (void)Reserved38;\n    uint32_t Reserved3C = ReadLE<uint32_t>(stream);\n    (void)Reserved3C;\n    uint32_t Reserved40 = ReadLE<uint32_t>(stream);\n    (void)Reserved40;\n    uint32_t Reserved44 = ReadLE<uint32_t>(stream);\n    (void)Reserved44;\n    uint32_t Reserved48 = ReadLE<uint32_t>(stream);\n    (void)Reserved48;\n    uint32_t Reserved4C = ReadLE<uint32_t>(stream);\n    (void)Reserved4C;\n    DataLength = ReadLE<uint32_t>(stream);\n    uint32_t Alignment = ReadLE<uint32_t>(stream);\n    uint32_t ChannelTypes = ReadLE<uint32_t>(stream);\n    uint32_t TextureType2 = ReadLE<uint32_t>(stream);\n    NameAddress = (uint32_t)ReadLE<uint64_t>(stream);\n    uint64_t ParentAddress = ReadLE<uint64_t>(stream);\n    (void)ParentAddress;\n    uint64_t PtrsAddress = ReadLE<uint64_t>(stream);\n\n    stream->Seek(NameAddress, 0);\n\n    std::string Name;\n\n    std::vector<uint64_t> MipOffsets(MipmapCount);\n\n    stream->Seek(PtrsAddress, 0);\n\n    uint64_t BaseOffset = ReadLE<uint64_t>(stream);\n    /*\n    for (int Mip = 1; Mip < MipmapCount; Mip++) {\n      uint64_t mipOffset = ReadLE<uint64_t>(stream);\n\n      MipOffsets.push_back(mipOffset - BaseOffset);\n      ImpLog(LogLevel::Debug, LogChannel::General, \"{:d}\\n\", mipOffset);\n    }*/\n\n    stream->Seek(BaseOffset, RW_SEEK_SET);\n\n    uint8_t* dataBuff = (uint8_t*)malloc(DataLength);\n    stream->Read(dataBuff, DataLength);\n\n    TextureNX element = TextureNX();\n    element.Name = Name;\n    element.Width = Width;\n    element.Height = Height;\n    element.ArrayCount = ArrayCount;\n    element.BlockHeightLog2 = BlockHeightLog2;\n    element.MipmapCount = MipmapCount;\n    element.MipOffsets = MipOffsets;\n    element.Channel0Type = (ChannelType)((ChannelTypes >> 0) & 0xff);\n    element.Channel1Type = (ChannelType)((ChannelTypes >> 8) & 0xff);\n    element.Channel2Type = (ChannelType)((ChannelTypes >> 16) & 0xff);\n    element.Channel3Type = (ChannelType)((ChannelTypes >> 24) & 0xff);\n    element.Type = (TextureType)(TextureType2);\n    element.FormatType = (TextureFormatType)((Format >> 8) & 0xff);\n    element.FormatVariant = (TextureFormatVar)((Format >> 0) & 0xff);\n    element.TilingMode = TileMode;\n    element.Alignment = Alignment;\n\n    if (element.MipmapCount >= 1) {\n      if (element.TilingMode) {\n        int bpp = BPPbyFormat(element.FormatType);\n        int blk_width = 4;\n        int blk_height = 4;\n        auto unswizzled =\n            UnSwizzle(element.Width, element.Height, blk_width, blk_height, bpp,\n                      element.BlockHeightLog2, dataBuff);\n        free(dataBuff);\n        dataBuff = unswizzled;\n      }\n\n      switch (element.FormatType) {\n        case BC1: {\n          void* oldBuff = dataBuff;\n          dataBuff = BCnDecompress(dataBuff, element, 1);\n          free(oldBuff);\n        } break;\n        case BC2: {\n          void* oldBuff = dataBuff;\n          dataBuff = BCnDecompress(dataBuff, element, 2);\n          free(oldBuff);\n        } break;\n\n        case BC3: {\n          void* oldBuff = dataBuff;\n          dataBuff = BCnDecompress(dataBuff, element, 3);\n          free(oldBuff);\n        } break;\n\n        case BC5: {\n          void* oldBuff = dataBuff;\n          dataBuff = BCnDecompress(dataBuff, element, 5);\n          free(oldBuff);\n        } break;\n\n        case TextureFormatType::R8G8B8A8:\n\n          break;\n\n        default:\n          ImpLog(LogLevel::Warning, LogChannel::TextureLoad,\n                 \"Unknown texture format!\\n\");\n          break;\n      }\n\n      outTexture->Init(TexFmt_RGBA, element.Width, element.Height);\n      outTexture->Buffer.assign(dataBuff, dataBuff + outTexture->Buffer.size());\n      free(dataBuff);\n\n      result = true;\n      break;\n    }\n  }\n\n  // Get result\n\n  return result;\n}  // namespace TexLoad\n\nstatic bool _registered = Texture::AddTextureLoader(&TextureLoadBNTX);\n\n}  // namespace TexLoad\n}  // namespace Impacto"
  },
  {
    "path": "src/texture/bntxloader.h",
    "content": "﻿#pragma once\n\n#include \"../io/io.h\"\n#include \"texture.h\"\n\nnamespace Impacto {\nnamespace TexLoad {\nenum TextureType {\n  Image1D = 0,\n  Image2D = 1,\n  Image3D = 2,\n  Cube = 3,\n  CubeFar = 8\n};\nenum ChannelType { Zero, One, Red, Green, Blue, Alpha };\n\nenum TextureFormatType {\n  R5G6B5 = 0x07,\n  R8G8 = 0x09,\n  R16 = 0x0a,\n  R8G8B8A8 = 0x0b,\n  R11G11B10 = 0x0f,\n  R32 = 0x14,\n  BC1 = 0x1a,\n  BC2 = 0x1b,\n  BC3 = 0x1c,\n  BC4 = 0x1d,\n  BC5 = 0x1e,\n  BC6 = 0x1f,\n  BC7 = 0x20,\n\n  ASTC4x4 = 0x2d,\n  ASTC5x4 = 0x2e,\n  ASTC5x5 = 0x2f,\n  ASTC6x5 = 0x30,\n  ASTC6x6 = 0x31,\n  ASTC8x5 = 0x32,\n  ASTC8x6 = 0x33,\n  ASTC8x8 = 0x34,\n  ASTC10x5 = 0x35,\n  ASTC10x6 = 0x36,\n  ASTC10x8 = 0x37,\n  ASTC10x10 = 0x38,\n  ASTC12x10 = 0x39,\n  ASTC12x12 = 0x3a\n};\n\nenum TextureFormatVar {\n  UNorm = 1,\n  SNorm = 2,\n  UInt = 3,\n  SInt = 4,\n  Single = 5,\n  SRGB = 6,\n  UHalf = 10\n};\n\nint BPPbyFormat(TextureFormatType format);\n\nclass TextureNX {\n public:\n  TextureNX();\n  std::string Name;\n\n  uint32_t Width;\n  uint32_t Height;\n  uint32_t ArrayCount;\n  uint32_t BlockHeightLog2;\n  uint32_t MipmapCount;\n\n  std::vector<uint64_t> MipOffsets;\n\n  std::vector<uint8_t> Data;\n\n  ChannelType Channel0Type;\n  ChannelType Channel1Type;\n  ChannelType Channel2Type;\n  ChannelType Channel3Type;\n\n  TextureType Type;\n  TextureFormatType FormatType;\n  TextureFormatVar FormatVariant;\n  uint16_t TilingMode;\n  int Alignment;\n\n  uint32_t GetWidthInTexels();\n\n  uint32_t Pow2RoundUp(uint32_t Value);\n\n  uint32_t GetPow2HeightInTexels();\n\n  uint32_t GetBytesPerTexel();\n\n  uint32_t GetBlockHeight();\n};\n}  // namespace TexLoad\n}  // namespace Impacto"
  },
  {
    "path": "src/texture/ddsloader.cpp",
    "content": "// SPDX-License-Identifier: BSD-3-Clause\n// https://github.com/OpenImageIO/oiio/blob/master/LICENSE.md\n\n#include <string>\n\n#include \"../log.h\"\n\n#include \"ddsloader.h\"\n\n#include <squish/squish.h>\n\nusing namespace Impacto::Io;\n\nnamespace Impacto {\nnamespace Gxm {\n// IneQuation was here\n\n#define DDS_MAKE4CC(a, b, c, d) (a | b << 8 | c << 16 | d << 24)\n#define DDS_4CC_DXT1 DDS_MAKE4CC('D', 'X', 'T', '1')\n#define DDS_4CC_DXT2 DDS_MAKE4CC('D', 'X', 'T', '2')\n#define DDS_4CC_DXT3 DDS_MAKE4CC('D', 'X', 'T', '3')\n#define DDS_4CC_DXT4 DDS_MAKE4CC('D', 'X', 'T', '4')\n#define DDS_4CC_DXT5 DDS_MAKE4CC('D', 'X', 'T', '5')\n\n/// DDS pixel format flags. Channel flags are only applicable for uncompressed\n/// images.\n///\nenum {\n  DDS_PF_ALPHA = 0x00000001,      ///< image has alpha channel\n  DDS_PF_FOURCC = 0x00000004,     ///< image is compressed\n  DDS_PF_LUMINANCE = 0x00020000,  ///< image has luminance data\n  DDS_PF_RGB = 0x00000040         ///< image has RGB data\n};\n\n/// DDS pixel format structure.\n///\ntypedef struct {\n  uint32_t size;    ///< structure size, must be 32\n  uint32_t flags;   ///< flags to indicate valid fields\n  uint32_t fourCC;  ///< compression four-character code\n  uint32_t bpp;     ///< bits per pixel\n  uint32_t rmask;   ///< bitmask for the red channel\n  uint32_t gmask;   ///< bitmask for the green channel\n  uint32_t bmask;   ///< bitmask for the blue channel\n  uint32_t amask;   ///< bitmask for the alpha channel\n} dds_pixformat;\n\n/// DDS caps flags, field 1.\n///\nenum {\n  DDS_CAPS1_COMPLEX = 0x00000008,  ///< >2D image or cube map\n  DDS_CAPS1_TEXTURE = 0x00001000,  ///< should be set for all DDS files\n  DDS_CAPS1_MIPMAP = 0x00400000    ///< image has mipmaps\n};\n\n/// DDS caps flags, field 2.\n///\nenum {\n  DDS_CAPS2_CUBEMAP = 0x00000200,            ///< image is a cube map\n  DDS_CAPS2_CUBEMAP_POSITIVEX = 0x00000400,  ///< +x side\n  DDS_CAPS2_CUBEMAP_NEGATIVEX = 0x00000800,  ///< -x side\n  DDS_CAPS2_CUBEMAP_POSITIVEY = 0x00001000,  ///< +y side\n  DDS_CAPS2_CUBEMAP_NEGATIVEY = 0x00002000,  ///< -y side\n  DDS_CAPS2_CUBEMAP_POSITIVEZ = 0x00004000,  ///< +z side\n  DDS_CAPS2_CUBEMAP_NEGATIVEZ = 0x00008000,  ///< -z side\n  DDS_CAPS2_VOLUME = 0x00200000              ///< image is a 3D texture\n};\n\n/// DDS caps structure.\n///\ntypedef struct {\n  uint32_t flags1;  ///< flags to indicate certain surface properties\n  uint32_t flags2;  ///< flags to indicate certain surface properties\n} dds_caps;\n\n/// DDS global flags - indicate valid header fields.\n///\nenum {\n  DDS_CAPS = 0x00000001,\n  DDS_HEIGHT = 0x00000002,\n  DDS_WIDTH = 0x00000004,\n  DDS_PITCH = 0x00000008,\n  DDS_PIXELFORMAT = 0x00001000,\n  DDS_MIPMAPCOUNT = 0x00020000,\n  DDS_LINEARSIZE = 0x00080000,\n  DDS_DEPTH = 0x00800000\n};\n\n/// DDS file header.\n/// Please note that this layout is not identical to the one found in a file.\n///\ntypedef struct {\n  uint32_t fourCC;   ///< file four-character code\n  uint32_t size;     ///< structure size, must be 124\n  uint32_t flags;    ///< flags to indicate valid fields\n  uint32_t height;   ///< image height\n  uint32_t width;    ///< image width\n  uint32_t pitch;    ///< bytes per scanline (uncmp.)/total byte size (cmp.)\n  uint32_t depth;    ///< image depth (for 3D textures)\n  uint32_t mipmaps;  ///< number of mipmaps\n  // 11 reserved 4-byte fields come in here\n  dds_pixformat fmt;  ///< pixel format\n  dds_caps caps;      ///< DirectDraw Surface caps\n} dds_header;\n\nstd::vector<uint8_t> m_buf;  ///< Buffer the image pixels\nint m_nchans;                ///< Number of colour channels in image\nint m_nfaces;                ///< Number of cube map sides in image\nint m_Bpp;                   ///< Number of bytes per pixel\nint m_redL, m_redR;          ///< Bit shifts to extract red channel\nint m_greenL, m_greenR;      ///< Bit shifts to extract green channel\nint m_blueL, m_blueR;        ///< Bit shifts to extract blue channel\nint m_alphaL, m_alphaR;      ///< Bit shifts to extract alpha channel\ndds_header m_dds;\n\ninline void calc_shifts(int mask, int& left, int& right) {\n  if (mask == 0) {\n    left = right = 0;\n    return;\n  }\n\n  int i, tmp = mask;\n  for (i = 0; i < 32; i++, tmp >>= 1) {\n    if (tmp & 1) break;\n  }\n  right = i;\n\n  for (i = 0; i < 8; i++, tmp >>= 1) {\n    if (!(tmp & 1)) break;\n  }\n  left = 8 - i;\n}\n\nbool internal_readimg(Stream* stream, uint8_t* dst, int w, int h, int d) {\n  if (m_dds.fmt.flags & DDS_PF_FOURCC) {\n    // compressed image\n    int flags = 0;\n    switch (m_dds.fmt.fourCC) {\n      case DDS_4CC_DXT1:\n        flags = squish::kDxt1;\n        break;\n      // DXT2 and 3 are the same, only 2 has pre-multiplied alpha\n      case DDS_4CC_DXT2:\n      case DDS_4CC_DXT3:\n        flags = squish::kDxt3;\n        break;\n      // DXT4 and 5 are the same, only 4 has pre-multiplied alpha\n      case DDS_4CC_DXT4:\n      case DDS_4CC_DXT5:\n        flags = squish::kDxt5;\n        break;\n    }\n    // create source buffer\n    std::vector<squish::u8> tmp(squish::GetStorageRequirements(w, h, flags));\n    // load image into buffer\n    if (!stream->Read(&tmp[0], tmp.size())) return false;\n    // decompress image\n    squish::DecompressImage(dst, w, h, &tmp[0], flags);\n    tmp.clear();\n    // correct pre-multiplied alpha, if necessary\n    if (m_dds.fmt.fourCC == DDS_4CC_DXT2 || m_dds.fmt.fourCC == DDS_4CC_DXT4) {\n      int k;\n      for (int y = 0; y < h; y++) {\n        for (int x = 0; x < w; x++) {\n          k = (y * w + x) * 4;\n          dst[k + 0] = (uint8_t)((int)dst[k + 0] * 255 / (int)dst[k + 3]);\n          dst[k + 1] = (uint8_t)((int)dst[k + 1] * 255 / (int)dst[k + 3]);\n          dst[k + 2] = (uint8_t)((int)dst[k + 2] * 255 / (int)dst[k + 3]);\n        }\n      }\n    }\n  } else {\n    // uncompressed image\n\n    // HACK: shortcut for luminance\n    if (m_dds.fmt.flags & DDS_PF_LUMINANCE) {\n      return stream->Read(dst, w * m_Bpp * h);\n    }\n\n    int k, pixel = 0;\n    for (int z = 0; z < d; z++) {\n      for (int y = 0; y < h; y++) {\n        for (int x = 0; x < w; x++) {\n          if (!stream->Read(&pixel, m_Bpp)) return false;\n          k = (z * h * w + y * w + x) * m_nchans;\n          dst[k + 0] =\n              (uint8_t)(((pixel & m_dds.fmt.rmask) >> m_redR) << m_redL);\n          dst[k + 1] =\n              (uint8_t)(((pixel & m_dds.fmt.gmask) >> m_greenR) << m_greenL);\n          dst[k + 2] =\n              (uint8_t)(((pixel & m_dds.fmt.bmask) >> m_blueR) << m_blueL);\n          if (m_dds.fmt.flags & DDS_PF_ALPHA)\n            dst[k + 3] =\n                (uint8_t)(((pixel & m_dds.fmt.amask) >> m_alphaR) << m_alphaL);\n        }\n      }\n    }\n  }\n  return true;\n}\n\nbool TextureLoadDDS(Stream* stream, Texture* outTexture) {\n  stream->Seek(0, RW_SEEK_SET);\n\n  m_dds.fourCC = ReadLE<uint32_t>(stream);\n  m_dds.size = ReadLE<uint32_t>(stream);\n  m_dds.flags = ReadLE<uint32_t>(stream);\n  m_dds.height = ReadLE<uint32_t>(stream);\n  m_dds.width = ReadLE<uint32_t>(stream);\n  m_dds.pitch = ReadLE<uint32_t>(stream);\n  m_dds.depth = ReadLE<uint32_t>(stream);\n  m_dds.mipmaps = ReadLE<uint32_t>(stream);\n\n  // advance the file pointer by 44 bytes (reserved fields)\n  stream->Seek(44, RW_SEEK_CUR);\n\n  // pixel format struct\n  m_dds.fmt.size = ReadLE<uint32_t>(stream);\n  m_dds.fmt.flags = ReadLE<uint32_t>(stream);\n  m_dds.fmt.fourCC = ReadLE<uint32_t>(stream);\n  m_dds.fmt.bpp = ReadLE<uint32_t>(stream);\n  m_dds.fmt.rmask = ReadLE<uint32_t>(stream);\n  m_dds.fmt.gmask = ReadLE<uint32_t>(stream);\n  m_dds.fmt.bmask = ReadLE<uint32_t>(stream);\n  m_dds.fmt.amask = ReadLE<uint32_t>(stream);\n\n  // caps\n  m_dds.caps.flags1 = ReadLE<uint32_t>(stream);\n  m_dds.caps.flags2 = ReadLE<uint32_t>(stream);\n\n  // advance the file pointer by 12 bytes (reserved fields)\n  stream->Seek(12, RW_SEEK_CUR);\n\n  // sanity checks - valid 4CC, correct struct sizes and flags which should\n  // be always present, regardless of the image type, size etc., also check\n  // for impossible flag combinations\n  if (m_dds.fourCC != DDS_MAKE4CC('D', 'D', 'S', ' ') || m_dds.size != 124 ||\n      m_dds.fmt.size != 32 || !(m_dds.caps.flags1 & DDS_CAPS1_TEXTURE) ||\n      !(m_dds.flags & DDS_CAPS) || !(m_dds.flags & DDS_PIXELFORMAT) ||\n      (m_dds.caps.flags2 & DDS_CAPS2_VOLUME &&\n       !(m_dds.caps.flags1 & DDS_CAPS1_COMPLEX && m_dds.flags & DDS_DEPTH)) ||\n      (m_dds.caps.flags2 & DDS_CAPS2_CUBEMAP &&\n       !(m_dds.caps.flags1 & DDS_CAPS1_COMPLEX))) {\n    stream->Seek(0, RW_SEEK_SET);\n    return false;\n  }\n\n  // make sure all dimensions are > 0 and that we have at least one channel\n  // (for uncompressed images)\n  if (!(m_dds.flags & DDS_WIDTH) || !m_dds.width ||\n      !(m_dds.flags & DDS_HEIGHT) || !m_dds.height ||\n      ((m_dds.flags & DDS_DEPTH) && !m_dds.depth) ||\n      (!(m_dds.fmt.flags & DDS_PF_FOURCC) &&\n       !((m_dds.fmt.flags & DDS_PF_RGB) | (m_dds.fmt.flags & DDS_PF_LUMINANCE) |\n         (m_dds.fmt.flags & DDS_PF_ALPHA)))) {\n    stream->Seek(0, RW_SEEK_SET);\n    return false;\n  }\n\n  // validate the pixel format\n  // TODO: support DXGI and the \"wackier\" uncompressed formats\n  if (m_dds.fmt.flags & DDS_PF_FOURCC && m_dds.fmt.fourCC != DDS_4CC_DXT1 &&\n      m_dds.fmt.fourCC != DDS_4CC_DXT2 && m_dds.fmt.fourCC != DDS_4CC_DXT3 &&\n      m_dds.fmt.fourCC != DDS_4CC_DXT4 && m_dds.fmt.fourCC != DDS_4CC_DXT5) {\n    stream->Seek(0, RW_SEEK_SET);\n    return false;\n  }\n\n  // determine the number of channels we have\n  if (m_dds.fmt.flags & DDS_PF_FOURCC) {\n    // squish decompresses everything to RGBA anyway\n    m_nchans = 4;\n  } else {\n    m_nchans = ((m_dds.fmt.flags & DDS_PF_LUMINANCE) ? 1 : 3) +\n               ((m_dds.fmt.flags & DDS_PF_ALPHA) ? 1 : 0);\n    // also calculate bytes per pixel and the bit shifts\n    m_Bpp = (m_dds.fmt.bpp + 7) >> 3;\n    if (!(m_dds.fmt.flags & DDS_PF_LUMINANCE)) {\n      calc_shifts(m_dds.fmt.rmask, m_redL, m_redR);\n      calc_shifts(m_dds.fmt.gmask, m_greenL, m_greenR);\n      calc_shifts(m_dds.fmt.bmask, m_blueL, m_blueR);\n      calc_shifts(m_dds.fmt.amask, m_alphaL, m_alphaR);\n    }\n  }\n\n  // fix depth, pitch and mipmaps for later use, if needed\n  if (!(m_dds.fmt.flags & DDS_PF_FOURCC && m_dds.flags & DDS_PITCH))\n    m_dds.pitch = m_dds.width * m_Bpp;\n  if (!(m_dds.caps.flags2 & DDS_CAPS2_VOLUME)) m_dds.depth = 1;\n  if (!(m_dds.flags & DDS_MIPMAPCOUNT)) m_dds.mipmaps = 1;\n  // count cube map faces\n  if (m_dds.caps.flags2 & DDS_CAPS2_CUBEMAP) {\n    m_nfaces = 0;\n    for (int flag = DDS_CAPS2_CUBEMAP_POSITIVEX;\n         flag <= DDS_CAPS2_CUBEMAP_NEGATIVEZ; flag <<= 1) {\n      if (m_dds.caps.flags2 & flag) m_nfaces++;\n    }\n  } else\n    m_nfaces = 1;\n\n  TexFmt texFmt = TexFmt_RGBA;\n  if (m_nchans == 3) {\n    texFmt = TexFmt_RGB;\n  } else if (m_nchans != 4) {\n    texFmt = TexFmt_U8;\n  }\n\n  outTexture->Init(texFmt, m_dds.width, m_dds.height);\n  internal_readimg(stream, outTexture->Buffer.data(), m_dds.width, m_dds.height,\n                   m_dds.depth);\n\n  return true;\n}\n\nstatic bool _registered = Texture::AddTextureLoader(&TextureLoadDDS);\n\n}  // namespace Gxm\n}  // namespace Impacto\n"
  },
  {
    "path": "src/texture/ddsloader.h",
    "content": "#pragma once\n\n#include \"../io/io.h\"\n#include \"texture.h\"\n"
  },
  {
    "path": "src/texture/gxtloader.cpp",
    "content": "#include \"gxtloader.h\"\n\n#include \"../log.h\"\n#include \"../util.h\"\n\n#include \"s3tc.h\"\n\nusing namespace Impacto::Io;\n\nnamespace Impacto {\n\nnamespace Gxm {\nenum SceGxmTextureType : uint32_t {\n  Swizzled = 0x00000000,\n  Cube = 0x40000000,\n  Linear = 0x60000000,\n  Tiled = 0x80000000,\n  SwizzledArbitrary = 0xA0000000,\n  LinearStrided = 0xC0000000,\n  CubeArbitrary = 0xE0000000\n};\n\nenum SceGxmTextureBaseFormat : uint32_t {\n  U8 = 0x00000000,\n  S8 = 0x01000000,\n  U4U4U4U4 = 0x02000000,\n  U8U3U3U2 = 0x03000000,\n  U1U5U5U5 = 0x04000000,\n  U5U6U5 = 0x05000000,\n  S5S5U6 = 0x06000000,\n  U8U8 = 0x07000000,\n  S8S8 = 0x08000000,\n  U16 = 0x09000000,\n  S16 = 0x0A000000,\n  F16 = 0x0B000000,\n  U8U8U8U8 = 0x0C000000,\n  S8S8S8S8 = 0x0D000000,\n  U2U10U10U10 = 0x0E000000,\n  U16U16 = 0x0F000000,\n  S16S16 = 0x10000000,\n  F16F16 = 0x11000000,\n  F32 = 0x12000000,\n  F32M = 0x13000000,\n  X8S8S8U8 = 0x14000000,\n  X8U24 = 0x15000000,\n  U32 = 0x17000000,\n  S32 = 0x18000000,\n  SE5M9M9M9 = 0x19000000,\n  F11F11F10 = 0x1A000000,\n  F16F16F16F16 = 0x1B000000,\n  U16U16U16U16 = 0x1C000000,\n  S16S16S16S16 = 0x1D000000,\n  F32F32 = 0x1E000000,\n  U32U32 = 0x1F000000,\n  PVRT2BPP = 0x80000000,\n  PVRT4BPP = 0x81000000,\n  PVRTII2BPP = 0x82000000,\n  PVRTII4BPP = 0x83000000,\n  UBC1 = 0x85000000,\n  UBC2 = 0x86000000,\n  UBC3 = 0x87000000,\n  YUV420P2 = 0x90000000,\n  YUV420P3 = 0x91000000,\n  YUV422 = 0x92000000,\n  P4 = 0x94000000,\n  P8 = 0x95000000,\n  U8U8U8 = 0x98000000,\n  S8S8S8 = 0x99000000,\n  U2F10F10F10 = 0x9A000000\n};\n\nenum SceGxmTextureSwizzle4Mode : uint32_t {\n  ABGR = 0x00000000,\n  ARGB = 0x00001000,\n  RGBA = 0x00002000,\n  BGRA = 0x00003000,\n  _1BGR = 0x00004000,\n  _1RGB = 0x00005000,\n  RGB1 = 0x00006000,\n  BGR1 = 0x00007000\n};\n\nenum SceGxmTextureSwizzle3Mode : uint32_t { BGR = 0x0000, RGB = 0x1000 };\n}  // namespace Gxm\n\nstruct SubtextureHeader {\n  uint32_t Offset;\n  uint32_t Size;\n  uint32_t PaletteIdx;\n  uint32_t Flags;  // or unused\n  uint32_t PixelOrder;\n  uint32_t Format;\n\n  uint16_t Width;\n  uint16_t Height;\n  uint16_t MipmapCount;\n};\n\n#define TexfmtCheck(condition)                                    \\\n  if (!((condition))) {                                           \\\n    ImpLog(LogLevel::Error, LogChannel::TextureLoad,              \\\n           \"Unsupported texture format 0x{:08x}\\n\", stx->Format); \\\n    return false;                                                 \\\n  }                                                               \\\n  (void)0\n\n// Vita unswizzle\n//\n// Thanks @xdanieldzd, @FireyFly and ryg\n// https://github.com/xdanieldzd/Scarlet/blob/d8aabf430307d35a81b131e40bb3c9a4828bdd7b/Scarlet/Drawing/ImageBinary.cs\n// http://xen.firefly.nu/up/rearrange.c.html\n// https://fgiesen.wordpress.com/2009/12/13/decoding-morton-codes/\n\n/* clang-format off */\n\n// Inverse of Part1By1 - \"delete\" all odd-indexed bits\nuint32_t Compact1By1(uint32_t x) {\n  x &= 0x55555555;                  // x = -f-e -d-c -b-a -9-8 -7-6 -5-4 -3-2 -1-0\n  x = (x ^ (x >>  1)) & 0x33333333; // x = --fe --dc --ba --98 --76 --54 --32 --10\n  x = (x ^ (x >>  2)) & 0x0f0f0f0f; // x = ---- fedc ---- ba98 ---- 7654 ---- 3210\n  x = (x ^ (x >>  4)) & 0x00ff00ff; // x = ---- ---- fedc ba98 ---- ---- 7654 3210\n  x = (x ^ (x >>  8)) & 0x0000ffff; // x = ---- ---- ---- ---- fedc ba98 7654 3210\n  return x;\n}\nuint32_t DecodeMorton2X(uint32_t code) { return Compact1By1(code >> 0); }\nuint32_t DecodeMorton2Y(uint32_t code) { return Compact1By1(code >> 1); }\n\nnamespace TexLoad {\n\nvoid VitaUnswizzle(int* x, int* y, int width, int height) {\n  // TODO: verify this is even sensible\n  int origX = *x, origY = *y;\n  if (width == 0) width = 16;\n  if (height == 0) height = 16;\n\n  int i = (origY * width) + origX;\n  int min = width < height ? width : height;\n  int k = Uint32Log2(min);\n\n  if (height < width) {\n    // XXXyxyxyx -> XXXxxxyyy\n    int j = i >> (2 * k) << (2 * k)\n        | (DecodeMorton2Y(i) & (min - 1)) << k\n        | (DecodeMorton2X(i) & (min - 1)) << 0;\n    *x = j / height;\n    *y = j % height;\n  }\n  else {\n    // YYYyxyxyx -> YYYyyyxxx\n    int j = i >> (2 * k) << (2 * k)\n        | (DecodeMorton2X(i) & (min - 1)) << k\n        | (DecodeMorton2Y(i) & (min - 1)) << 0;\n    *x = j % width;\n    *y = j / width;\n  }\n}\n\n/* clang-format on */\n\nbool GXTLoadSubtexture(Stream* stream, Texture* outTexture,\n                       SubtextureHeader* stx, uint8_t* p4Palettes,\n                       uint8_t* p8Palettes, uint32_t p4count) {\n  *outTexture = Texture();\n  stream->Seek(stx->Offset, RW_SEEK_SET);\n  uint32_t baseFormat = (stx->Format & 0xFF000000U);\n  uint32_t channelOrder = (stx->Format & 0x0000FFFFU);\n\n  if (stx->PixelOrder != Gxm::Swizzled && stx->PixelOrder != Gxm::Linear) {\n    ImpLog(LogLevel::Error, LogChannel::TextureLoad,\n           \"Unsupported pixel order 0x{:08x}\\n\", stx->PixelOrder);\n    return false;\n  }\n\n  switch (baseFormat) {\n    // 24bpp RGB\n    case Gxm::U8U8U8: {\n      TexfmtCheck(channelOrder == Gxm::BGR || channelOrder == Gxm::RGB);\n\n      outTexture->Init(TexFmt_RGB, stx->Width, stx->Height);\n\n      if (channelOrder == Gxm::BGR && stx->PixelOrder == Gxm::Linear) {\n        stream->Read(outTexture->Buffer.data(), outTexture->Buffer.size());\n      } else {\n        uint8_t* inBuffer = (uint8_t*)malloc(outTexture->Buffer.size());\n        uint8_t* reader = inBuffer;\n        stream->Read(inBuffer, outTexture->Buffer.size());\n\n        for (int y = 0; y < stx->Height; y++) {\n          for (int x = 0; x < stx->Width; x++) {\n            int outX = x, outY = y;\n            if (stx->PixelOrder == Gxm::Swizzled) {\n              VitaUnswizzle(&outX, &outY, stx->Width, stx->Height);\n            }\n\n            int px = (outX + stx->Width * outY) * 3;\n\n            if (channelOrder == Gxm::RGB) {\n              outTexture->Buffer[px + 2] = *reader++;\n              outTexture->Buffer[px + 1] = *reader++;\n              outTexture->Buffer[px + 0] = *reader++;\n            } else if (channelOrder == Gxm::BGR) {\n              std::copy(reader, reader + 3, outTexture->Buffer.begin() + px);\n              reader += 3;\n            }\n          }\n        }\n\n        free(inBuffer);\n      }\n      break;\n    }\n\n    // 32bpp RGBA\n    case Gxm::U8U8U8U8: {\n      TexfmtCheck(channelOrder == Gxm::ARGB);\n\n      outTexture->Init(TexFmt_RGBA, stx->Width, stx->Height);\n\n      uint8_t* inBuffer = (uint8_t*)malloc(outTexture->Buffer.size());\n      uint8_t* reader = inBuffer;\n      stream->Read(inBuffer, outTexture->Buffer.size());\n\n      for (int y = 0; y < stx->Height; y++) {\n        for (int x = 0; x < stx->Width; x++) {\n          int outX = x, outY = y;\n          if (stx->PixelOrder == Gxm::Swizzled) {\n            VitaUnswizzle(&outX, &outY, stx->Width, stx->Height);\n          }\n\n          int px = (outX + stx->Width * outY) * 4;\n\n          outTexture->Buffer[px + 2] = *reader++;\n          outTexture->Buffer[px + 1] = *reader++;\n          outTexture->Buffer[px + 0] = *reader++;\n          outTexture->Buffer[px + 3] = *reader++;\n        }\n      }\n\n      free(inBuffer);\n      break;\n    }\n\n    // 256 color paletted\n    case Gxm::P8: {\n      TexfmtCheck(channelOrder == Gxm::_1RGB || channelOrder == Gxm::ARGB);\n\n      // PaletteIdx is into *all* palettes (P8s following all P4s), we have P4\n      // and P8 separate\n      uint8_t* palette = p8Palettes + 4 * 256 * (stx->PaletteIdx - p4count);\n\n      int bytesPerPixel = 0;\n      if (channelOrder == Gxm::_1RGB) {\n        outTexture->Init(TexFmt_RGB, stx->Width, stx->Height);\n        bytesPerPixel = 3;\n      } else if (channelOrder == Gxm::ARGB) {\n        outTexture->Init(TexFmt_RGBA, stx->Width, stx->Height);\n        bytesPerPixel = 4;\n      } else {\n        throw std::invalid_argument(fmt::format(\n            \"Unimplemented channel order {} requested\", channelOrder));\n      }\n\n      uint8_t* inBuffer = (uint8_t*)malloc(stx->Width * stx->Height);\n      uint8_t* reader = inBuffer;\n      stream->Read(inBuffer, stx->Width * stx->Height);\n\n      for (int y = 0; y < stx->Height; y++) {\n        for (int x = 0; x < stx->Width; x++) {\n          int outX = x, outY = y;\n          if (stx->PixelOrder == Gxm::Swizzled) {\n            VitaUnswizzle(&outX, &outY, stx->Width, stx->Height);\n          }\n\n          uint8_t colorIdx = *reader++;\n          uint8_t* color = palette + 4 * colorIdx;\n\n          int px = (outX + stx->Width * outY) * bytesPerPixel;\n\n          outTexture->Buffer[px + 2] = color[0];\n          outTexture->Buffer[px + 1] = color[1];\n          outTexture->Buffer[px + 0] = color[2];\n          if (bytesPerPixel == 4) {\n            outTexture->Buffer[px + 3] = color[3];\n          }\n        }\n      }\n\n      free(inBuffer);\n      break;\n    }\n\n    // DXT1, no alpha\n    case Gxm::UBC1: {\n      outTexture->Init(TexFmt_RGBA, stx->Width, stx->Height);\n\n      if (stx->PixelOrder == Gxm::Swizzled) {\n        BlockDecompressImageDXT1VitaSwizzled(stx->Width, stx->Height, stream,\n                                             outTexture->Buffer.data());\n      } else {\n        BlockDecompressImageDXT1(stx->Width, stx->Height, stream,\n                                 outTexture->Buffer.data());\n      }\n      break;\n    }\n\n    // DXT5\n    case Gxm::UBC3: {\n      outTexture->Init(TexFmt_RGBA, stx->Width, stx->Height);\n\n      if (stx->PixelOrder == Gxm::Swizzled) {\n        BlockDecompressImageDXT5VitaSwizzled(stx->Width, stx->Height, stream,\n                                             outTexture->Buffer.data());\n      } else {\n        BlockDecompressImageDXT5(stx->Width, stx->Height, stream,\n                                 outTexture->Buffer.data());\n      }\n      break;\n    }\n\n    // 8-bit grayscale\n    case Gxm::U8: {\n      outTexture->Init(TexFmt_U8, stx->Width, stx->Height);\n\n      if (stx->PixelOrder == Gxm::Swizzled) {\n        uint8_t* inBuffer = (uint8_t*)malloc(outTexture->Buffer.size());\n        uint8_t* reader = inBuffer;\n        stream->Read(inBuffer, outTexture->Buffer.size());\n\n        for (int y = 0; y < stx->Height; y++) {\n          for (int x = 0; x < stx->Width; x++) {\n            int outX = x, outY = y;\n            VitaUnswizzle(&outX, &outY, stx->Width, stx->Height);\n\n            uint8_t v = *reader++;\n\n            outTexture->Buffer[(outX + stx->Width * outY)] = v;\n          }\n        }\n\n        free(inBuffer);\n      } else {\n        stream->Read(outTexture->Buffer.data(), outTexture->Buffer.size());\n      }\n      break;\n    }\n\n    default: {\n      TexfmtCheck(0);\n    }\n  }\n\n  return true;\n}\n\nstatic uint32_t const magic = 0x47585400;\n\nbool TextureLoadGXT(Stream* stream, Texture* outTexture) {\n  // Read metadata\n\n  if (ReadBE<uint32_t>(stream) != magic) {\n    stream->Seek(0, RW_SEEK_SET);\n    return false;\n  }\n\n  ImpLogSlow(LogLevel::Debug, LogChannel::TextureLoad, \"Loading GXT texture\\n\");\n\n  uint32_t version = ReadLE<uint32_t>(stream);\n  (void)version;\n  [[maybe_unused]] uint32_t subtextureCount = ReadLE<uint32_t>(stream);\n  assert(subtextureCount == 1);\n  uint32_t subtexturesOffset = ReadLE<uint32_t>(stream);\n  (void)subtexturesOffset;\n  uint32_t totalTexSize = ReadLE<uint32_t>(stream);\n  (void)totalTexSize;\n  uint32_t p4Count = ReadLE<uint32_t>(stream);\n  uint32_t p8Count = ReadLE<uint32_t>(stream);\n  // padding\n  stream->Seek(4, RW_SEEK_CUR);\n\n  ImpLogSlow(LogLevel::Debug, LogChannel::TextureLoad,\n             \"GXT version=0x{:08x}, subtextureCount=0x{:08x}, \"\n             \"subtexturesOffset=0x{:08x}, totalTexSize=0x{:08x}, \"\n             \"p4Count=0x{:08x}, p8Count=0x{:08x}\\n\",\n             version, subtextureCount, subtexturesOffset, totalTexSize, p4Count,\n             p8Count);\n\n  SubtextureHeader stx;\n  stx.Offset = ReadLE<uint32_t>(stream);\n  stx.Size = ReadLE<uint32_t>(stream);\n  stx.PaletteIdx = ReadLE<uint32_t>(stream);\n  stx.Flags = ReadLE<uint32_t>(stream);\n  stx.PixelOrder = ReadLE<uint32_t>(stream);\n  stx.Format = ReadLE<uint32_t>(stream);\n\n  stx.Width = ReadLE<uint16_t>(stream);\n  stx.Height = ReadLE<uint16_t>(stream);\n  stx.MipmapCount = ReadLE<uint16_t>(stream);\n  assert(stx.MipmapCount == 1);\n  // subtexture header padding\n  stream->Seek(2, RW_SEEK_CUR);\n\n  stream->Seek(stx.Size, RW_SEEK_CUR);\n\n  ImpLogSlow(\n      LogLevel::Debug, LogChannel::TextureLoad,\n      \"Subtexture Offset=0x{:08x}, Size=0x{:08x}, PaletteIdx=0x{:08x}, \"\n      \"Flags=0x{:08x}, PixelOrder=0x{:08x}, Format=0x{:08x}, Width=0x{:08x}, \"\n      \"Height=0x{:08x}, MipmapCount=0x{:08x}\\n\",\n      stx.Offset, stx.Size, stx.PaletteIdx, stx.Flags, stx.PixelOrder,\n      stx.Format, stx.Width, stx.Height, stx.MipmapCount);\n\n  // Read palettes\n\n  // 4bpp/8bpp => 32bpp palettes\n  uint8_t* P4Palettes = NULL;\n  uint8_t* P8Palettes = NULL;\n  if (p4Count > 0) {\n    P4Palettes = (uint8_t*)ImpStackAlloc(4 * 16 * p4Count);\n    stream->Read(P4Palettes, 4 * 16 * p4Count);\n  }\n  if (p8Count > 0) {\n    P8Palettes = (uint8_t*)ImpStackAlloc(4 * 256 * p8Count);\n    stream->Read(P8Palettes, 4 * 256 * p8Count);\n  }\n\n  // Get result\n  bool result = GXTLoadSubtexture(stream, outTexture, &stx, P4Palettes,\n                                  P8Palettes, p4Count);\n\n  if (P4Palettes) ImpStackFree(P4Palettes);\n  if (P8Palettes) ImpStackFree(P8Palettes);\n\n  return result;\n}\n\nstatic bool _registered = Texture::AddTextureLoader(&TextureLoadGXT);\n\n}  // namespace TexLoad\n}  // namespace Impacto"
  },
  {
    "path": "src/texture/gxtloader.h",
    "content": "#pragma once\n\n#include \"../io/io.h\"\n#include \"texture.h\"\n\nnamespace Impacto {\nnamespace TexLoad {\nvoid VitaUnswizzle(int* x, int* y, int width, int height);\n}  // namespace TexLoad\n}  // namespace Impacto"
  },
  {
    "path": "src/texture/plainloader.cpp",
    "content": "#include \"plainloader.h\"\n\nnamespace Impacto {\nnamespace TexLoad {\n\nusing namespace Impacto::Io;\n\n// This is for a very simple format used for some graphics by various console\n// releases\n// TODO: big-endian (360/PS3)\n\nenum PlainPixelMode : uint32_t {\n  Plain_8Bit_Paletted = 8,\n  Plain_32Bit_ARGB = 32,\n  Plain_8Bit_Alpha1 = 8 | (1 << 16),\n  // Literally the same format, no idea why they made a new version\n  Plain_8Bit_Alpha2 = 8 | (2 << 16)\n};\n\nbool TextureIsPlain(Stream* stream) {\n  stream->Seek(4, RW_SEEK_SET);\n  uint32_t mode = ReadLE<uint32_t>(stream);\n  bool result = (mode == Plain_8Bit_Paletted || mode == Plain_32Bit_ARGB ||\n                 mode == Plain_8Bit_Alpha1 || mode == Plain_8Bit_Alpha2);\n  stream->Seek(0, RW_SEEK_SET);\n  return result;\n}\n\nbool TextureLoadPlain(Stream* stream, Texture* outTexture) {\n  uint16_t width = ReadLE<uint16_t>(stream);\n  uint16_t height = ReadLE<uint16_t>(stream);\n  PlainPixelMode mode = (PlainPixelMode)ReadLE<uint32_t>(stream);\n\n  switch (mode) {\n    case Plain_8Bit_Paletted: {  // 8-bit paletted; palette is BGRA\n      uint8_t palette[256 * 4];\n      stream->Read(palette, 4 * 256);\n\n      outTexture->Init(TexFmt_RGBA, width, height);\n\n      uint8_t* inBuffer = (uint8_t*)malloc(width * height);\n      stream->Read(inBuffer, width * height);\n\n      uint8_t* reader = inBuffer;\n      for (size_t i = 0; i < outTexture->Buffer.size(); i += 4) {\n        uint8_t colorIdx = *reader;\n        uint8_t* color = palette + 4 * colorIdx;\n\n        outTexture->Buffer[i + 0] = color[2];\n        outTexture->Buffer[i + 1] = color[1];\n        outTexture->Buffer[i + 2] = color[0];\n        outTexture->Buffer[i + 3] = color[3];\n\n        reader++;\n      }\n\n      free(inBuffer);\n      return true;\n    }\n\n    case Plain_32Bit_ARGB: {  // 32-bit ARGB\n      outTexture->Init(TexFmt_RGBA, width, height);\n\n      uint8_t* inBuffer = (uint8_t*)malloc(outTexture->Buffer.size());\n      stream->Read(inBuffer, outTexture->Buffer.size());\n\n      for (size_t i = 0; i < outTexture->Buffer.size(); i += 4) {\n        outTexture->Buffer[i + 0] = inBuffer[i + 1];\n        outTexture->Buffer[i + 1] = inBuffer[i + 2];\n        outTexture->Buffer[i + 2] = inBuffer[i + 3];\n        outTexture->Buffer[i + 3] = inBuffer[i + 0];\n      }\n\n      free(inBuffer);\n      return true;\n    }\n\n    case Plain_8Bit_Alpha1:\n    case Plain_8Bit_Alpha2: {  // No palette, alpha channel only\n      // TODO alpha textures\n      outTexture->Init(TexFmt_RGBA, width, height);\n      std::ranges::fill(outTexture->Buffer, 0xFF);\n\n      uint8_t* inBuffer = (uint8_t*)malloc(width * height);\n      stream->Read(inBuffer, width * height);\n\n      uint8_t* reader = inBuffer;\n      for (size_t i = 0; i < outTexture->Buffer.size(); i += 4) {\n        outTexture->Buffer[i + 3] = *reader;\n        reader++;\n      }\n\n      free(inBuffer);\n      return true;\n    }\n\n    default:\n      return false;\n  }\n}\n\n}  // namespace TexLoad\n}  // namespace Impacto"
  },
  {
    "path": "src/texture/plainloader.h",
    "content": "#pragma once\n\n#include \"../io/io.h\"\n#include \"texture.h\"\n\nnamespace Impacto {\nnamespace TexLoad {\nbool TextureIsPlain(Io::Stream* stream);\nbool TextureLoadPlain(Io::Stream* stream, Texture* outTexture);\n}  // namespace TexLoad\n}  // namespace Impacto"
  },
  {
    "path": "src/texture/s3tc.cpp",
    "content": "//\n// Heavily based on\n// https://github.com/Benjamin-Dobell/s3tc-dxt-decompression\n//\n// -----------------------------------------------------------------------------\n// Copyright (c) 2009 Benjamin Dobell, Glass Echidna\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#include \"s3tc.h\"\n#include \"gxtloader.h\"\n\nnamespace Impacto {\nnamespace TexLoad {\n\nusing namespace Impacto::Io;\n\nvoid DecompressBlockDXT1(uint32_t startX, uint32_t startY, uint32_t imageWidth,\n                         Stream* stream, uint8_t* outputImage) {\n  uint16_t color0 = ReadLE<uint16_t>(stream);\n  uint16_t color1 = ReadLE<uint16_t>(stream);\n\n  uint32_t temp;\n\n  temp = (color0 >> 11) * 255 + 16;\n  uint8_t r0 = (uint8_t)((temp / 32 + temp) / 32);\n  temp = ((color0 & 0x07E0) >> 5) * 255 + 32;\n  uint8_t g0 = (uint8_t)((temp / 64 + temp) / 64);\n  temp = (color0 & 0x001F) * 255 + 16;\n  uint8_t b0 = (uint8_t)((temp / 32 + temp) / 32);\n\n  temp = (color1 >> 11) * 255 + 16;\n  uint8_t r1 = (uint8_t)((temp / 32 + temp) / 32);\n  temp = ((color1 & 0x07E0) >> 5) * 255 + 32;\n  uint8_t g1 = (uint8_t)((temp / 64 + temp) / 64);\n  temp = (color1 & 0x001F) * 255 + 16;\n  uint8_t b1 = (uint8_t)((temp / 32 + temp) / 32);\n\n  uint32_t code = ReadLE<uint32_t>(stream);\n\n  uint8_t r, g, b, a;\n  r = g = b = a = 0;\n\n  for (int j = 0; j < 4; j++) {\n    for (int i = 0; i < 4; i++) {\n      uint32_t x = startX + i, y = startY + j;\n\n      if (x >= imageWidth) continue;\n\n      uint8_t positionCode = (code >> 2 * (4 * j + i)) & 0x03;\n      a = 255;\n      switch (positionCode) {\n        case 0:\n          r = r0, g = g0, b = b0;\n          break;\n        case 1:\n          r = r1, g = g1, b = b1;\n          break;\n        case 2:\n          if (color0 > color1) {\n            r = (2 * r0 + r1) / 3;\n            g = (2 * g0 + g1) / 3;\n            b = (2 * b0 + b1) / 3;\n          } else {\n            r = (r0 + 2 * r1) / 3;\n            g = (g0 + 2 * g1) / 3;\n            b = (b0 + 2 * b1) / 3;\n          }\n          break;\n        case 3:\n          if (color0 > color1) {\n            r = (r0 + r1) / 2, g = (g0 + g1) / 2, b = (b0 + b1) / 2;\n          } else {\n            r = g = b = a = 0;\n          }\n          break;\n      }\n\n      outputImage[(x + imageWidth * y) * 4 + 0] = r;\n      outputImage[(x + imageWidth * y) * 4 + 1] = g;\n      outputImage[(x + imageWidth * y) * 4 + 2] = b;\n      outputImage[(x + imageWidth * y) * 4 + 3] = a;\n    }\n  }\n}\n\nvoid BlockDecompressImageDXT1(uint32_t width, uint32_t height, Stream* stream,\n                              uint8_t* outputImage) {\n  uint32_t blockCountX = (width + 3) / 4;\n  uint32_t blockCountY = (height + 3) / 4;\n\n  for (uint32_t j = 0; j < blockCountY; j++) {\n    for (uint32_t i = 0; i < blockCountX; i++) {\n      DecompressBlockDXT1(i * 4, j * 4, width, stream, outputImage);\n    }\n  }\n}\n\nvoid BlockDecompressImageDXT1VitaSwizzled(uint32_t width, uint32_t height,\n                                          Stream* stream,\n                                          uint8_t* outputImage) {\n  uint32_t blockCountX = (width + 3) / 4;\n  uint32_t blockCountY = (height + 3) / 4;\n\n  for (uint32_t j = 0; j < blockCountY; j++) {\n    for (uint32_t i = 0; i < blockCountX; i++) {\n      int x = i, y = j;\n      VitaUnswizzle(&x, &y, blockCountX, blockCountY);\n      DecompressBlockDXT1(x * 4, y * 4, width, stream, outputImage);\n    }\n  }\n}\n\n// TODO: Kai's eyes are broken, a problem in here might be why\nvoid DecompressBlockDXT5(uint32_t startX, uint32_t startY, uint32_t imageWidth,\n                         Stream* stream, uint8_t* outputImage) {\n  uint8_t alpha0 = ReadU8(stream);\n  uint8_t alpha1 = ReadU8(stream);\n\n  uint8_t alphaBits[6];\n  stream->Read(alphaBits, 6);\n\n  uint32_t alphaCode1 = alphaBits[2] | (alphaBits[3] << 8) |\n                        (alphaBits[4] << 16) | (alphaBits[5] << 24);\n  uint16_t alphaCode2 = alphaBits[0] | (alphaBits[1] << 8);\n\n  uint16_t color0 = ReadLE<uint16_t>(stream);\n  uint16_t color1 = ReadLE<uint16_t>(stream);\n\n  uint32_t temp;\n\n  temp = (color0 >> 11) * 255 + 16;\n  uint8_t r0 = (uint8_t)((temp / 32 + temp) / 32);\n  temp = ((color0 & 0x07E0) >> 5) * 255 + 32;\n  uint8_t g0 = (uint8_t)((temp / 64 + temp) / 64);\n  temp = (color0 & 0x001F) * 255 + 16;\n  uint8_t b0 = (uint8_t)((temp / 32 + temp) / 32);\n\n  temp = (color1 >> 11) * 255 + 16;\n  uint8_t r1 = (uint8_t)((temp / 32 + temp) / 32);\n  temp = ((color1 & 0x07E0) >> 5) * 255 + 32;\n  uint8_t g1 = (uint8_t)((temp / 64 + temp) / 64);\n  temp = (color1 & 0x001F) * 255 + 16;\n  uint8_t b1 = (uint8_t)((temp / 32 + temp) / 32);\n\n  uint32_t code = ReadLE<uint32_t>(stream);\n\n  uint8_t r, g, b, a;\n  r = g = b = a = 0;\n\n  for (int j = 0; j < 4; j++) {\n    for (int i = 0; i < 4; i++) {\n      uint32_t x = startX + i, y = startY + j;\n\n      if (x >= imageWidth) continue;\n\n      int alphaCodeIndex = 3 * (4 * j + i);\n      int alphaCode;\n\n      if (alphaCodeIndex <= 12) {\n        alphaCode = (alphaCode2 >> alphaCodeIndex) & 0x07;\n      } else if (alphaCodeIndex == 15) {\n        alphaCode = (alphaCode2 >> 15) | ((alphaCode1 << 1) & 0x06);\n      } else {\n        alphaCode = (alphaCode1 >> (alphaCodeIndex - 16)) & 0x07;\n      }\n\n      if (alphaCode == 0) {\n        a = alpha0;\n      } else if (alphaCode == 1) {\n        a = alpha1;\n      } else {\n        if (alpha0 > alpha1) {\n          a = (uint8_t)(((8 - alphaCode) * alpha0 + (alphaCode - 1) * alpha1) /\n                        7);\n        } else {\n          if (alphaCode == 6)\n            a = 0;\n          else if (alphaCode == 7)\n            a = 255;\n          else {\n            a = (uint8_t)(((6 - alphaCode) * alpha0 +\n                           (alphaCode - 1) * alpha1) /\n                          5);\n          }\n        }\n      }\n\n      uint8_t positionCode = (code >> 2 * (4 * j + i)) & 0x03;\n\n      switch (positionCode) {\n        case 0:\n          r = r0, g = g0, b = b0;\n          break;\n        case 1:\n          r = r1, g = g1, b = b1;\n          break;\n        case 2:\n          if (color0 > color1) {\n            r = (2 * r0 + r1) / 3;\n            g = (2 * g0 + g1) / 3;\n            b = (2 * b0 + b1) / 3;\n          } else {\n            r = (r0 + 2 * r1) / 3;\n            g = (g0 + 2 * g1) / 3;\n            b = (b0 + 2 * b1) / 3;\n          }\n          break;\n        case 3:\n          if (color0 > color1) {\n            r = (r0 + r1) / 2, g = (g0 + g1) / 2, b = (b0 + b1) / 2;\n          } else {\n            r = g = b = 0;\n          }\n          break;\n      }\n\n      outputImage[(x + imageWidth * y) * 4 + 0] = r;\n      outputImage[(x + imageWidth * y) * 4 + 1] = g;\n      outputImage[(x + imageWidth * y) * 4 + 2] = b;\n      outputImage[(x + imageWidth * y) * 4 + 3] = a;\n    }\n  }\n}\n\nvoid BlockDecompressImageDXT5(uint32_t width, uint32_t height, Stream* stream,\n                              uint8_t* outputImage) {\n  uint32_t blockCountX = (width + 3) / 4;\n  uint32_t blockCountY = (height + 3) / 4;\n\n  for (uint32_t j = 0; j < blockCountY; j++) {\n    for (uint32_t i = 0; i < blockCountX; i++) {\n      DecompressBlockDXT5(i * 4, j * 4, width, stream, outputImage);\n    }\n  }\n}\n\nvoid BlockDecompressImageDXT5VitaSwizzled(uint32_t width, uint32_t height,\n                                          Stream* stream,\n                                          uint8_t* outputImage) {\n  uint32_t blockCountX = (width + 3) / 4;\n  uint32_t blockCountY = (height + 3) / 4;\n\n  for (uint32_t j = 0; j < blockCountY; j++) {\n    for (uint32_t i = 0; i < blockCountX; i++) {\n      int x = i, y = j;\n      VitaUnswizzle(&x, &y, blockCountX, blockCountY);\n      DecompressBlockDXT5(x * 4, y * 4, width, stream, outputImage);\n    }\n  }\n}\n}  // namespace TexLoad\n}  // namespace Impacto"
  },
  {
    "path": "src/texture/s3tc.h",
    "content": "#pragma once\n\n#include \"../io/stream.h\"\n\nnamespace Impacto {\nnamespace TexLoad {\n// Note: Unlike Benjamin's original implementation, these decompress to 24-bit\n// RGB, not 32-bit 1RGB\nvoid DecompressBlockDXT1(uint32_t startX, uint32_t startY, uint32_t imageWidth,\n                         Io::Stream* stream, uint8_t* outputImage);\nvoid BlockDecompressImageDXT1(uint32_t width, uint32_t height,\n                              Io::Stream* stream, uint8_t* outputImage);\nvoid BlockDecompressImageDXT1VitaSwizzled(uint32_t width, uint32_t height,\n                                          Io::Stream* stream,\n                                          uint8_t* outputImage);\n\nvoid DecompressBlockDXT5(uint32_t startX, uint32_t startY, uint32_t imageWidth,\n                         Io::Stream* stream, uint8_t* outputImage);\nvoid BlockDecompressImageDXT5(uint32_t width, uint32_t height,\n                              Io::Stream* stream, uint8_t* outputImage);\nvoid BlockDecompressImageDXT5VitaSwizzled(uint32_t width, uint32_t height,\n                                          Io::Stream* stream,\n                                          uint8_t* outputImage);\n}  // namespace TexLoad\n}  // namespace Impacto"
  },
  {
    "path": "src/texture/stbiloader.cpp",
    "content": "#include \"texture.h\"\n#include \"../io/stream.h\"\n#include \"../log.h\"\n#include <stb_image.h>\n\nnamespace Impacto {\nnamespace TexLoad {\n\nint StbiRead(void* user, char* data, int size) {\n  return (int)(((Io::Stream*)user)->Read(data, size));\n}\nvoid StbiSkip(void* user, int n) { ((Io::Stream*)user)->Seek(n, RW_SEEK_CUR); }\nint StbiEof(void* user) {\n  Io::Stream* stream = (Io::Stream*)user;\n  return stream->Position >= stream->Meta.Size;\n}\n\nstatic stbi_io_callbacks const StbiCallbacks{StbiRead, StbiSkip, StbiEof};\n\nbool TextureLoadSTBI(Io::Stream* stream, Texture* outTexture) {\n  int x, y, channels_in_file;\n\n  uint8_t* image = stbi_load_from_callbacks(&StbiCallbacks, stream, &x, &y,\n                                            &channels_in_file, STBI_default);\n  if (image == 0) {\n    stream->Seek(0, RW_SEEK_SET);\n    return false;\n  }\n\n  switch (channels_in_file) {\n    case 1: {\n      outTexture->Format = TexFmt_U8;\n      break;\n    }\n    case 3: {\n      outTexture->Format = TexFmt_RGB;\n      break;\n    }\n    case 4: {\n      outTexture->Format = TexFmt_RGBA;\n      break;\n    }\n    default: {\n      ImpLog(LogLevel::Error, LogChannel::TextureLoad,\n             \"STBI: unsupported channel count {:d}\\n\", channels_in_file);\n      free(image);\n      stream->Seek(0, RW_SEEK_SET);\n      return false;\n    }\n  }\n\n  outTexture->Width = x;\n  outTexture->Height = y;\n  outTexture->Buffer.assign(image, image + (channels_in_file * x * y));\n\n  free(image);\n  return true;\n}\n\nstatic bool _registered = Texture::AddTextureLoader(&TextureLoadSTBI);\n\n}  // namespace TexLoad\n}  // namespace Impacto"
  },
  {
    "path": "src/texture/texture.cpp",
    "content": "#include \"texture.h\"\n\n#include <string.h>\n\n#include \"../log.h\"\n\n#include \"../renderer/renderer.h\"\n\n#include \"plainloader.h\"\n\nnamespace Impacto {\n\nstd::vector<Texture::TextureLoader>& Texture::GetRegistry() {\n  static std::vector<TextureLoader> registry;\n  return registry;\n}\n\nbool Texture::Load(Io::Stream* stream) {\n  using namespace TexLoad;\n\n  for (auto f : GetRegistry()) {\n    if (f(stream, this)) return true;\n  }\n\n  // no registry for this one, since it has no real magic - we must try it last\n  if (TextureIsPlain(stream)) return TextureLoadPlain(stream, this);\n\n  uint32_t magic = Io::ReadBE<uint32_t>(stream);\n  ImpLog(LogLevel::Error, LogChannel::TextureLoad,\n         \"No loader for texture, possible magic 0x{:08x}\\n\", magic);\n  return false;\n}\n\nvoid Texture::Init(TexFmt format, int width, int height) {\n  Width = width;\n  Height = height;\n  Format = format;\n\n  const size_t bufferSize = [&]() {\n    switch (format) {\n      case TexFmt_RGBA:\n        return width * height * 4;\n      case TexFmt_RGB:\n        return width * height * 3;\n      case TexFmt_U8:\n        return width * height;\n      default:\n        ImpLog(LogLevel::Warning, LogChannel::TextureLoad,\n               \"Unknown texture buffer format {:d}\", static_cast<int>(format));\n        return 0;\n    }\n  }();\n  Buffer.resize(bufferSize);\n}\n\nvoid Texture::Load1x1(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha) {\n  Init(TexFmt_RGBA, 1, 1);\n  Buffer[0] = red;\n  Buffer[1] = green;\n  Buffer[2] = blue;\n  Buffer[3] = alpha;\n}\n\nvoid Texture::LoadSolidColor(int width, int height, uint32_t color) {\n  Init(TexFmt_RGBA, width, height);\n\n  const auto colorArray =\n      std::bit_cast<std::array<const uint8_t, sizeof(uint32_t)>>(color);\n  for (size_t i = 0; i + colorArray.size() <= Buffer.size();\n       i += colorArray.size()) {\n    std::ranges::copy(colorArray, Buffer.begin() + i);\n  }\n}\n\nvoid Texture::LoadCheckerboard() {\n  Init(TexFmt_U8, 128, 128);\n  uint8_t color = 0xFF;\n  for (uint8_t& byte : Buffer) {\n    byte = color;\n    color = ~color;\n  }\n}\n\nvoid Texture::LoadPoliticalCompass() {\n  Init(TexFmt_RGBA, 512, 512);\n\n  for (int x = 0; x < Width / 2; x++) {\n    for (int y = 0; y < Height / 2; y++) {\n      Buffer[4 * x + 4 * y * Height + 0] = 0xE0;\n      Buffer[4 * x + 4 * y * Height + 1] = 0x77;\n      Buffer[4 * x + 4 * y * Height + 2] = 0x77;\n      Buffer[4 * x + 4 * y * Height + 3] = 0xFF;\n\n      Buffer[4 * (Width / 2 + x) + 4 * y * Height + 0] = 0x38;\n      Buffer[4 * (Width / 2 + x) + 4 * y * Height + 1] = 0xBE;\n      Buffer[4 * (Width / 2 + x) + 4 * y * Height + 2] = 0xE0;\n      Buffer[4 * (Width / 2 + x) + 4 * y * Height + 3] = 0xFF;\n\n      Buffer[4 * x + 4 * (Height / 2 + y) * Height + 0] = 0x89;\n      Buffer[4 * x + 4 * (Height / 2 + y) * Height + 1] = 0xC7;\n      Buffer[4 * x + 4 * (Height / 2 + y) * Height + 2] = 0x72;\n      Buffer[4 * x + 4 * (Height / 2 + y) * Height + 3] = 0xFF;\n\n      Buffer[4 * (Width / 2 + x) + 4 * (Height / 2 + y) * Height + 0] = 0xC6;\n      Buffer[4 * (Width / 2 + x) + 4 * (Height / 2 + y) * Height + 1] = 0x8D;\n      Buffer[4 * (Width / 2 + x) + 4 * (Height / 2 + y) * Height + 2] = 0xC3;\n      Buffer[4 * (Width / 2 + x) + 4 * (Height / 2 + y) * Height + 3] = 0xFF;\n    }\n  }\n}\n\nuint32_t Texture::Submit() {\n  ImpLog(LogLevel::Debug, LogChannel::Render, \"Submitting texture\\n\");\n\n  if (Buffer.empty()) return std::numeric_limits<uint32_t>::max();\n\n  uint32_t result =\n      Renderer->SubmitTexture(Format, Buffer.data(), Width, Height);\n\n  // TODO I meant to do this elsewhere but we gotta do it somewhere\n  Buffer.clear();\n\n  return result;\n}\n\nbool Texture::AddTextureLoader(Texture::TextureLoader c) {\n  GetRegistry().push_back(c);\n  return true;\n}\n\n}  // namespace Impacto"
  },
  {
    "path": "src/texture/texture.h",
    "content": "#pragma once\n\n#include \"../impacto.h\"\n#include <vector>\n#include \"../io/stream.h\"\n\nnamespace Impacto {\n\nenum TexFmt { TexFmt_RGB, TexFmt_RGBA, TexFmt_U8 };\n\nstruct Texture {\n  int Width = 0;\n  int Height = 0;\n  TexFmt Format = TexFmt_U8;\n  std::vector<uint8_t> Buffer;\n\n  void Init(TexFmt format, int width, int height);\n\n  bool Load(Io::Stream* stream);\n  void Load1x1(uint8_t red = 0, uint8_t green = 0, uint8_t blue = 0,\n               uint8_t alpha = 0);\n  void LoadSolidColor(int width, int height, uint32_t color = 0xFFFFFFFF);\n  void LoadCheckerboard();\n  void LoadPoliticalCompass();\n  uint32_t Submit();\n\n  using TextureLoader = auto (*)(Io::Stream* stream, Texture* texture) -> bool;\n  static bool AddTextureLoader(TextureLoader c);\n\n private:\n  static std::vector<TextureLoader>& GetRegistry();\n};\n\n}  // namespace Impacto"
  },
  {
    "path": "src/texture/webpdecode.cpp",
    "content": "#include \"texture.h\"\n#include \"../io/stream.h\"\n#include \"../log.h\"\n#include <webp/decode.h>\n\nnamespace Impacto {\nnamespace TexLoad {\n\nbool TextureLoadWebP(Io::Stream* stream, Texture* outTexture) {\n  stream->Seek(0, RW_SEEK_END);\n  size_t dataSize = stream->Position;\n  stream->Seek(0, RW_SEEK_SET);\n  uint8_t* rawData = (uint8_t*)malloc(dataSize);\n  stream->Read(rawData, dataSize);\n\n  int width = 0, height = 0;\n  int res = WebPGetInfo(rawData, dataSize, &width, &height);\n\n  if (!res) {\n    stream->Seek(0, RW_SEEK_SET);\n    free(rawData);\n    return false;\n  }\n\n  WebPBitstreamFeatures features;\n  auto status = WebPGetFeatures(rawData, dataSize, &features);\n\n  if (status != VP8_STATUS_OK) {\n    stream->Seek(0, RW_SEEK_SET);\n    free(rawData);\n    return false;\n  }\n\n  uint8_t* image = 0;\n  if (features.has_alpha) {\n    outTexture->Format = TexFmt_RGBA;\n    image = WebPDecodeRGBA(rawData, dataSize, &width, &height);\n  } else {\n    outTexture->Format = TexFmt_RGB;\n    image = WebPDecodeRGB(rawData, dataSize, &width, &height);\n  }\n\n  if (image == 0) {\n    stream->Seek(0, RW_SEEK_SET);\n    free(rawData);\n    return false;\n  }\n\n  outTexture->Width = width;\n  outTexture->Height = height;\n  const size_t bufferSize = (3 + features.has_alpha) * width * height;\n  outTexture->Buffer.assign(image, image + bufferSize);\n\n  WebPFree(image);\n  free(rawData);\n  return true;\n}\n\nstatic bool _registered = Texture::AddTextureLoader(&TextureLoadWebP);\n\n}  // namespace TexLoad\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/backlogmenu.cpp",
    "content": "#include \"backlogmenu.h\"\n\n#include <limits>\n\n#include \"ui.h\"\n#include \"../profile/game.h\"\n#include \"../profile/vm.h\"\n#include \"../renderer/renderer.h\"\n#include \"../mem.h\"\n#include \"../vm/interface/input.h\"\n#include \"../profile/scriptvars.h\"\n#include \"../profile/scriptinput.h\"\n#include \"../profile/dialogue.h\"\n#include \"../profile/configsystem.h\"\n#include \"../profile/ui/backlogmenu.h\"\n#include \"../profile/ui/systemmenu.h\"\n#include \"../profile/games/mo6tw/backlogmenu.h\"\n#include \"../profile/games/cc/backlogmenu.h\"\n#include \"../profile/games/cclcc/systemmenu.h\"\n#include \"../inputsystem.h\"\n#include \"../io/vfs.h\"\n#include \"../audio/audiosystem.h\"\n\nnamespace Impacto {\nnamespace UI {\n\nusing namespace Impacto::Profile::ScriptVars;\nusing namespace Impacto::Profile::ScriptInput;\nusing namespace Impacto::Profile::BacklogMenu;\nusing namespace Impacto::Vm::Interface;\n\nusing namespace Impacto::UI::Widgets;\n\nvoid BacklogMenu::MenuButtonOnClick(Widgets::BacklogEntry* target) {\n  if (target->AudioId != -1) {\n    const float volume =\n        Profile::ConfigSystem::VoiceMuted[target->CharacterId]\n            ? 0.0f\n            : Profile::ConfigSystem::VoiceVolume[target->CharacterId];\n    Audio::Channels[Audio::AC_REV]->SetVolume(volume);\n    Audio::Channels[Audio::AC_REV]->Play(\"voice\", target->AudioId, false, 0.0f);\n  }\n}\n\nBacklogMenu::BacklogMenu() {\n  InputConfig = InputRate::RepeatFast;\n  MainItems = new Widgets::Group(this, EntriesStart);\n  MainItems->RenderingBounds = RenderingBounds;\n  MainItems->HoverBounds = HoverBounds;\n  MainItems->WrapFocus = false;\n\n  MainScrollbar =\n      new Scrollbar(0, ScrollbarPosition, 0.0f, 1.0f, &PageY, SBDIR_VERTICAL,\n                    ScrollbarTrack, ScrollbarThumb, glm::vec2(0),\n                    ScrollbarThumbLength, RenderingBounds, -5000);\n  MainScrollbar->Enabled = false;\n  CurrentEntryPos = EntriesStart;\n\n  FadeAnimation.Direction = AnimationDirection::In;\n  FadeAnimation.LoopMode = AnimationLoopMode::Stop;\n  FadeAnimation.DurationIn = FadeInDuration;\n  FadeAnimation.DurationOut = FadeOutDuration;\n}\n\nvoid BacklogMenu::Show() {\n  if (State == Hidden) {\n    State = Showing;\n    FadeAnimation.StartIn();\n    MainItems->Show();\n\n    if (!MainItems->Children.empty()) {\n      auto el = MainItems->Children.back();\n      FocusStart[FDIR_UP] = el;\n      FocusStart[FDIR_DOWN] = el;\n    }\n\n    if (UI::FocusedMenu != nullptr) {\n      LastFocusedMenu = UI::FocusedMenu;\n      LastFocusedMenu->IsFocused = false;\n    }\n    UI::FocusedMenu = this;\n\n    ResetPADHoldTimer(PAD1UP | PAD1DOWN | PAD1RIGHT | PAD1LEFT);\n\n    // Set scrollbar back to default position\n    if (ItemsHeight > MainItems->RenderingBounds.Height) {\n      PageY = MainScrollbar->EndValue;\n      MainScrollbar->Update(0);\n      MainItems->MoveTo(glm::vec2(EntriesStart.x, PageY));\n    }\n  }\n}\nvoid BacklogMenu::Hide() {\n  if (State == Shown) {\n    State = Hiding;\n    FadeAnimation.StartOut();\n\n    if (LastFocusedMenu != nullptr) {\n      UI::FocusedMenu = LastFocusedMenu;\n      LastFocusedMenu->IsFocused = true;\n    } else {\n      UI::FocusedMenu = nullptr;\n    }\n  }\n}\n\nstatic bool IsBeyondShiftedHoverBounds(const Widget* el, float delta, bool up) {\n  if (up) return el->Bounds.Y < HoverBounds.Y + delta;\n\n  return el->Bounds.Y + el->Bounds.Height >\n         HoverBounds.Y + HoverBounds.Height + delta;\n}\n\nvoid BacklogMenu::UpdatePageUpDownInput(float dt) {\n  const uint32_t shouldFire =\n      Vm::Interface::PADinputButtonRepeatDown & (PAD1LEFT | PAD1RIGHT);\n\n  const bool move =\n      (bool)(shouldFire & PAD1LEFT) ^ (bool)(shouldFire & PAD1RIGHT);\n  if (!move) {\n    if (shouldFire) ResetPADHoldTimer(PAD1LEFT | PAD1RIGHT);\n    return;\n  }\n\n  const FocusDirection dir = (shouldFire & PAD1LEFT) ? FDIR_UP : FDIR_DOWN;\n\n  if (MainScrollbar->Enabled) {\n    const float delta = (dir == FDIR_UP) ? PageUpDownHeight : -PageUpDownHeight;\n    PageY += delta;\n    MainScrollbar->ClampValue();\n\n    if (!CurrentlyFocusedElement) {\n      CurrentlyFocusedElement = (dir == FDIR_UP) ? MainItems->Children.front()\n                                                 : MainItems->Children.back();\n    }\n\n    CurrentlyFocusedElement->HasFocus = false;\n\n    Widget* nextEl = CurrentlyFocusedElement->GetFocus(dir);\n    while (nextEl &&\n           IsBeyondShiftedHoverBounds(nextEl, delta, dir == FDIR_UP)) {\n      CurrentlyFocusedElement = nextEl;\n      nextEl = CurrentlyFocusedElement->GetFocus(dir);\n    }\n\n  } else {\n    if (CurrentlyFocusedElement) CurrentlyFocusedElement->HasFocus = false;\n\n    CurrentlyFocusedElement = (dir == FDIR_UP) ? MainItems->Children.front()\n                                               : MainItems->Children.back();\n  }\n\n  CurrentlyFocusedElement->HasFocus = true;\n}\n\nstatic bool InVerticalHoverBounds(const Widget* entry) {\n  if (entry == nullptr) return false;\n\n  return (HoverBounds.Y <= entry->Bounds.Y &&\n          entry->Bounds.Y + entry->Bounds.Height <=\n              HoverBounds.Y + HoverBounds.Height);\n}\n\nvoid BacklogMenu::UpdateScrollingInput(float dt) {\n  const uint32_t held = PADinputButtonIsDown & (PAD1DOWN | PAD1UP);\n  const bool padScrolling = (bool)(held & PAD1DOWN) ^ (bool)(held & PAD1UP);\n  if (!padScrolling) return;\n\n  FocusDirection dir = (held & PAD1DOWN) ? FDIR_DOWN : FDIR_UP;\n\n  bool focusOnEdge = false;\n  const Widget* nextEl = nullptr;\n  if (CurrentlyFocusedElement != nullptr) {\n    nextEl = CurrentlyFocusedElement->GetFocus(dir);\n    focusOnEdge = !InVerticalHoverBounds(nextEl);\n  }\n\n  // Gradual scrolling\n  if (MainScrollbar->Enabled && focusOnEdge) {\n    PageY += (dir == FDIR_UP) ? ScrollingSpeed * dt : -ScrollingSpeed * dt;\n    MainScrollbar->ClampValue();\n    MainItems->Update(dt);\n  }\n\n  const uint32_t shouldFire = PADinputButtonRepeatDown & (PAD1DOWN | PAD1UP);\n\n  if (focusOnEdge) {\n    if (nextEl) {\n      const float excess = (dir == FDIR_UP)\n                               ? HoverBounds.Y - nextEl->Bounds.Y\n                               : nextEl->Bounds.Y + nextEl->Bounds.Height -\n                                     HoverBounds.Y - HoverBounds.Height;\n      if (excess < ScrollingSpeed * dt) AdvanceFocus(dir);\n    }\n  } else {\n    ImpLog(LogLevel::Trace, LogChannel::General, \"Should Fire: {}\", shouldFire);\n    if ((shouldFire & PAD1UP) ^ (shouldFire & PAD1DOWN)) {\n      dir = (shouldFire & PAD1DOWN) ? FDIR_DOWN : FDIR_UP;\n      AdvanceFocus(dir);\n    }\n  }\n  if (((shouldFire & PAD1UP) ^ (shouldFire & PAD1DOWN)) == 0 && shouldFire) {\n    ResetPADHoldTimer(PAD1DOWN | PAD1UP);\n  }\n}\n\nvoid BacklogMenu::UpdateInput(float dt) {\n  MainScrollbar->UpdateInput(dt);\n  UpdatePageUpDownInput(dt);\n  UpdateScrollingInput(dt);\n\n  if (!(State == Shown && IsFocused)) {\n    AtBottomPrev = false;\n  } else if (Profile::CloseBacklogWhenReachedEnd) {\n    const float epsilon = std::numeric_limits<float>::epsilon();\n    const bool atBottomNow = !MainScrollbar->Enabled ||\n                             (PageY <= (MainScrollbar->EndValue + epsilon));\n\n    if (AtBottomPrev && atBottomNow && Input::MouseWheelDeltaY < 0) {\n      Vm::Interface::PADinputMouseWentDown |= Vm::Interface::PADcustom[6];\n    }\n    AtBottomPrev = atBottomNow;\n  }\n}\n\nvoid BacklogMenu::Update(float dt) {\n  if (State != Hidden && State != Shown) FadeAnimation.Update(dt);\n  UpdateVisibility();\n  SetFlag(SF_BACKLOG_NOLOG, MainItems->Children.empty());\n\n  if (State == Shown && IsFocused) {\n    UpdateInput(dt);\n\n    if (ItemsHeight > MainItems->RenderingBounds.Height) {\n      MainScrollbar->Enabled = true;\n      MainScrollbar->StartValue = MainItems->RenderingBounds.Y + EntryYPadding;\n      MainScrollbar->EndValue = -ItemsHeight +\n                                MainItems->RenderingBounds.Height +\n                                MainItems->RenderingBounds.Y;\n    }\n\n    if (MainScrollbar->Enabled) {\n      MainItems->MoveTo(glm::vec2(EntriesStart.x, PageY));\n      auto lastEntry = MainItems->Children.back();\n      CurrentEntryPos.y =\n          lastEntry->Bounds.Y + lastEntry->Bounds.Height + EntryYPadding;\n    }\n\n    MainItems->Update(dt);\n    if (!MainScrollbar->IsScrollHeld()) MainItems->UpdateInput(dt);\n    MainScrollbar->Update(dt);\n\n    // Handle entry moving out of hover bounds\n    if (CurrentlyFocusedElement &&\n        !InVerticalHoverBounds(CurrentlyFocusedElement)) {\n      FocusDirection dir = (CurrentlyFocusedElement->Bounds.Y < HoverBounds.Y)\n                               ? FDIR_DOWN\n                               : FDIR_UP;\n      Widget* newFocusedElement = CurrentlyFocusedElement->GetFocus(dir);\n      while (newFocusedElement && !InVerticalHoverBounds(newFocusedElement))\n        newFocusedElement = newFocusedElement->GetFocus(dir);\n\n      CurrentlyFocusedElement->Hovered = false;\n      CurrentlyFocusedElement->HasFocus = false;\n      CurrentlyFocusedElement = newFocusedElement;\n      if (newFocusedElement != nullptr) newFocusedElement->HasFocus = true;\n    }\n  }\n}\n\nvoid BacklogMenu::RenderHighlight(const glm::vec2 offset) const {\n  if (EntryHighlightLocation == EntryHighlightLocationType::None ||\n      CurrentlyFocusedElement == nullptr ||\n      !MainItems->RenderingBounds.Intersects(CurrentlyFocusedElement->Bounds))\n    return;\n\n  RectF pos;\n  const Widget& el = *CurrentlyFocusedElement;\n\n  switch (EntryHighlightLocation) {\n    default:\n    case EntryHighlightLocationType::BottomLeftOfEntry:\n      pos = RectF(\n          el.Bounds.X,\n          el.Bounds.Y + el.Bounds.Height - EntryHighlight.ScaledHeight(),\n          Profile::Dialogue::REVBounds.Width, EntryHighlight.ScaledHeight());\n      break;\n    case EntryHighlightLocationType::TopLineLeftOfScreen:\n      pos = RectF(0.0f, el.Bounds.Y - EntryHighlightPadding,\n                  EntryHighlight.ScaledWidth(),\n                  EntryHighlight.ScaledHeight() + EntryHighlightPadding * 2);\n      break;\n    case EntryHighlightLocationType::AllLinesLeftOfScreen:\n      pos = RectF(0.0f, el.Bounds.Y - EntryHighlightPadding,\n                  EntryHighlight.ScaledWidth(),\n                  el.Bounds.Height + EntryHighlightPadding * 2);\n      break;\n  }\n\n  pos.X += EntryHighlightOffset.x;\n  pos.Y += EntryHighlightOffset.y;\n\n  float opacity = glm::smoothstep(0.0f, 1.0f, FadeAnimation.Progress);\n  Renderer->DrawSprite(EntryHighlight, pos + offset,\n                       glm::vec4(1.0f, 1.0f, 1.0f, opacity));\n}\n\nvoid BacklogMenu::Render() {}\n\nvoid BacklogMenu::AddMessage(Vm::BufferOffsetContext scrCtx, int audioId,\n                             int characterId) {\n  if (!GetFlag(SF_REVADDDISABLE) || ScrWork[SW_MESWIN0TYPE] == 0) {\n    auto onClick = [this](auto* btn) { return MenuButtonOnClick(btn); };\n\n    Widgets::BacklogEntry* backlogEntry = CreateBacklogEntry(\n        CurrentId, scrCtx, audioId, characterId, CurrentEntryPos, HoverBounds);\n    backlogEntry->OnClickHandler = onClick;\n    MainItems->Add(backlogEntry, FDIR_DOWN);\n    CurrentId++;\n\n    CurrentEntryPos.y += backlogEntry->TextHeight + EntryYPadding;\n    ItemsHeight += backlogEntry->TextHeight + EntryYPadding;\n\n    if (ItemsHeight > MainItems->RenderingBounds.Height) {\n      MainScrollbar->EndValue = -ItemsHeight +\n                                MainItems->RenderingBounds.Height +\n                                MainItems->RenderingBounds.Y;\n      PageY = MainScrollbar->EndValue;\n      MainItems->MoveTo(glm::vec2(EntriesStart.x, PageY));\n      CurrentEntryPos.y =\n          backlogEntry->Bounds.Y + backlogEntry->Bounds.Height + EntryYPadding;\n    }\n  }\n}\n\nvoid BacklogMenu::Clear() {\n  MainItems->Clear();\n  MainItems->MoveTo(EntriesStart);\n  PageY = 0.0f;\n  CurrentId = 0;\n  ItemsHeight = 0.0f;\n  MainScrollbar->StartValue = 0.0f;\n  MainScrollbar->EndValue = 1.0f;\n  MainScrollbar->Enabled = false;\n  CurrentEntryPos = EntriesStart;\n}\n\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/backlogmenu.h",
    "content": "#pragma once\n\n#include \"menu.h\"\n#include \"widgets/group.h\"\n#include \"widgets/backlogentry.h\"\n#include \"widgets/scrollbar.h\"\n\nnamespace Impacto {\nnamespace UI {\n\nclass BacklogMenu : public Menu {\n public:\n  BacklogMenu();\n\n  virtual void Show() override;\n  virtual void Hide() override;\n  virtual void Update(float dt) override;\n  virtual void UpdateInput(float dt) override;\n  virtual void Render() override;\n\n  virtual Widgets::BacklogEntry* CreateBacklogEntry(\n      int id, Vm::BufferOffsetContext scrCtx, int audioId, int characterId,\n      glm::vec2 pos, const RectF& hoverBounds) const {\n    return new Widgets::BacklogEntry(id, scrCtx, audioId, characterId, pos,\n                                     hoverBounds);\n  }\n\n  virtual void AddMessage(Vm::BufferOffsetContext scrCtx, int audioId = -1,\n                          int characterId = 0);\n  virtual void MenuButtonOnClick(Widgets::BacklogEntry* target);\n  void Clear();\n\n  float PageY = 0.0f;\n\n protected:\n  virtual void UpdateVisibility() {};\n\n  int CurrentId = 0;\n  float ItemsHeight = 0.0f;\n  glm::vec2 CurrentEntryPos;\n  Widgets::Group* MainItems;\n  Animation FadeAnimation;\n  Widgets::Scrollbar* MainScrollbar;\n\n  void RenderHighlight(glm::vec2 offset = {0.0f, 0.0f}) const;\n  void UpdatePageUpDownInput(float dt);\n  void UpdateScrollingInput(float dt);\n\n private:\n  bool AtBottomPrev = false;\n};\n\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/gamespecific.cpp",
    "content": "#include \"gamespecific.h\"\n#include \"../profile/game.h\"\n#include \"../profile/vm.h\"\n#include \"../profile/scene3d.h\"\n#include \"../profile/sprites.h\"\n#include \"../profile/ui/gamespecific.h\"\n#include \"../profile/games/cclcc/delusiontrigger.h\"\n#include \"../profile/games/chlcc/delusiontrigger.h\"\n\n#include \"../games/chlcc/delusiontrigger.h\"\n#include \"../effects/chlcc/butterflyeffect.h\"\n#include \"../effects/chlcc/bubbleseffect.h\"\n#include \"../effects/chlcc/eyecatch.h\"\n\n#include \"../games/cclcc/delusiontrigger.h\"\n#include \"../games/cclcc/yesnotrigger.h\"\n#include \"../games/cclcc/mapsystem.h\"\n\n#include \"../background2d.h\"\n#include \"../inputsystem.h\"\n#include \"../audio/audiosystem.h\"\n#include \"../vm/interface/input.h\"\n\nusing namespace Impacto::Profile::GameSpecific;\nusing namespace Impacto::Profile::ScriptVars;\n\nnamespace Impacto {\nnamespace UI {\nnamespace GameSpecific {\n\nstatic float CHLCCScanlineOffsetY = 0.0f;\nstatic void UpdateCCAtChanScrollbar();\n\nvoid Init() {\n  switch (Profile::GameSpecific::GameSpecificType) {\n    case GameSpecificType::CHLCC: {\n      CHLCC::DelusionTrigger::GetInstance().Reset();\n      CHLCC::ButterflyEffect::GetInstance().Init();\n      CHLCC::BubblesEffect::GetInstance().Init();\n    } break;\n    case GameSpecificType::CC: {\n    } break;\n    case GameSpecificType::CCLCC: {\n      CCLCC::DelusionTrigger::GetInstance();\n      CCLCC::YesNoTrigger::GetInstance();\n      CCLCC::MapSystem::GetInstance();\n    } break;\n    case GameSpecificType::RNE:\n      break;\n    case GameSpecificType::Dash:\n      break;\n    case GameSpecificType::None:\n      break;\n  }\n}\n\n// Update that is run before and independently of ScrWork[SW_GAMESTATE] & 5 &&\n// !GetFlag(SF_GAMEPAUSE)\nvoid NonGameplayUpdate(float dt) {\n  switch (Profile::GameSpecific::GameSpecificType) {\n    case GameSpecificType::CHLCC: {\n      if (UI ::TitleMenuPtr) UI::TitleMenuPtr->Update(dt);\n    } break;\n    case GameSpecificType::Dash:\n    case GameSpecificType::RNE:\n    case GameSpecificType::CC:\n    case GameSpecificType::CCLCC:\n    case GameSpecificType::None:\n      break;\n  }\n  return;\n}\n\nvoid Update(float dt) {\n  switch (Profile::GameSpecific::GameSpecificType) {\n    case GameSpecificType::CHLCC: {\n      CHLCC::DelusionTrigger::GetInstance().Update(dt);\n      CHLCC::ButterflyEffect::GetInstance().Update(dt);\n      CHLCC::BubblesEffect::GetInstance().Update(dt);\n      CHLCCScanlineOffsetY = fmod(dt * 60 + CHLCCScanlineOffsetY, 300.0f);\n    } break;\n    case GameSpecificType::CC: {\n      UpdateCCButtonGuide(dt);\n      UpdateCCAtChanScrollbar();\n    } break;\n    case GameSpecificType::CCLCC: {\n      UpdateCCButtonGuide(dt);\n      UpdateCCAtChanScrollbar();\n      CCLCC::YesNoTrigger::GetInstance().Update(dt);\n      CCLCC::DelusionTrigger::GetInstance().Update(dt);\n      CCLCC::MapSystem::GetInstance().Update(dt);\n    } break;\n    case GameSpecificType::RNE:\n      break;\n    case GameSpecificType::Dash:\n      break;\n    case GameSpecificType::None:\n      break;\n  }\n}\nvoid RenderEarlyMain() {\n  switch (Profile::GameSpecific::GameSpecificType) {\n    case GameSpecificType::CHLCC: {\n      CHLCC::EyecatchEffect::GetInstance().RenderMain();\n    } break;\n    case GameSpecificType::Dash:\n    case GameSpecificType::RNE:\n    case GameSpecificType::CC:\n    case GameSpecificType::CCLCC:\n    case GameSpecificType::None:\n      break;\n  }\n}\n\nvoid RenderMain() {\n  switch (Profile::GameSpecific::GameSpecificType) {\n    case GameSpecificType::CHLCC: {\n      CHLCC::DelusionTrigger::GetInstance().Render();\n    } break;\n    case GameSpecificType::Dash: {\n      /////////// DaSH hack kind of? ///////\n      if (GetFlag(SF_Pokecon_Disable) || GetFlag(SF_Pokecon_Open) ||\n          Renderer->Scene->MainCamera.CameraTransform.Position !=\n              Profile::Scene3D::DefaultCameraPosition)\n        SetFlag(SF_DATEDISPLAY, 0);\n      else\n        SetFlag(SF_DATEDISPLAY, 1);\n    }\n      [[fallthrough]];\n    case GameSpecificType::RNE: {\n      if (GetFlag(SF_Pokecon_Open)) {\n        SetFlag(SF_DATEDISPLAY, 0);\n        // hack\n        ScrWork[SW_POKECON_BOOTANIMECT] = 0;\n        ScrWork[SW_POKECON_SHUTDOWNANIMECT] = 0;\n        ScrWork[SW_POKECON_MENUSELANIMECT] = 0;\n      }\n    } break;\n    case GameSpecificType::CC:\n      break;\n    case GameSpecificType::CCLCC:\n      break;\n    case GameSpecificType::None:\n      break;\n  }\n}\n\nvoid RenderLayer(uint32_t layer) {\n  int layerInt = static_cast<int>(layer);\n  switch (Profile::GameSpecific::GameSpecificType) {\n    case GameSpecificType::CHLCC: {\n      CHLCC::EyecatchEffect::GetInstance().RenderLayer(layer);\n      if (ScrWork[SW_MONITOR_SCANLINE_ENABLED] &&\n          layerInt == ScrWork[SW_MONITOR_SCANLINE_PRI]) {\n        Renderer->DrawSprite(\n            MonitorScanline,\n            RectF{0.0f, 0.0f, Profile::DesignWidth, Profile::DesignHeight},\n            glm::vec4{glm::vec3{1.0f}, 26 / 255.0f});\n        float y = (299 - CHLCCScanlineOffsetY) * 1000.0f / 300 - 200;\n        Renderer->DrawQuad(RectF{0.0f, y, Profile::DesignWidth, 200},\n                           glm::vec4{glm::vec3{0.0f}, 88 / 255.0f});\n      }\n      if (ScrWork[SW_BUTTERFLY_ALPHA] &&\n          layerInt == ScrWork[SW_BUTTERFLY_PRI]) {\n        CHLCC::ButterflyEffect::GetInstance().Render();\n      }\n      if (ScrWork[SW_BUBBLES_ALPHA] && layerInt == ScrWork[SW_BUBBLES_PRI]) {\n        CHLCC::BubblesEffect::GetInstance().Render();\n      }\n\n      if (GetFlag(SF_TITLEMODE) && ScrWork[SW_TITLE_PRI] == layerInt) {\n        if (UI ::TitleMenuPtr) UI::TitleMenuPtr->Render();\n      }\n\n    } break;\n    case GameSpecificType::CC: {\n    } break;\n    case GameSpecificType::CCLCC: {\n      if (ScrWork[SW_MAP_PRI] == layerInt && ScrWork[SW_MAP_ALPHA]) {\n        CCLCC::MapSystem::GetInstance().Render();\n      }\n\n      if (ScrWork[SW_DELUSION_PRI] == layerInt)\n        CCLCC::DelusionTrigger::GetInstance().Render();\n      if (ScrWork[SW_YESNO_PRI] == layerInt) {\n        CCLCC::YesNoTrigger::GetInstance().Render();\n      }\n    } break;\n    case GameSpecificType::RNE:\n      break;\n    case GameSpecificType::Dash:\n      break;\n    case GameSpecificType::None:\n      break;\n  }\n}\n\nvoid RenderCCButtonGuide() {\n  if (!GetFlag(SF_UIHIDDEN) && !GetFlag(2487)) {\n    if (ScrWork[SW_UI_BTNGUIDE_PROG] != 0) {\n      Sprite guideSprite =\n          (*UIButtonGuideSprites)[ScrWork[SW_UI_BTNGUIDE_TYPE] - 1];\n      float guideXWidth =\n          (ScrWork[SW_UI_BTNGUIDE_PROG] * Profile::DesignWidth) / 32.0f;\n      guideSprite.Bounds.Width = guideXWidth;\n      Renderer->DrawSprite(guideSprite,\n                           glm::vec2{0.0f, UIButtonGuideEndDisp->Y});\n      if (guideXWidth < Profile::DesignWidth) {\n        Sprite guideSprite2 = guideSprite;\n        std::array<glm::vec4, 4> tints = {\n            glm::vec4{1.0f, 1.0f, 1.0f, 1.0f},\n            glm::vec4{1.0f, 1.0f, 1.0f, 1.0f},\n            glm::vec4{1.0f, 1.0f, 1.0f, 0.0f},\n            glm::vec4{1.0f, 1.0f, 1.0f, 0.0f},\n        };\n        guideSprite2.Bounds.X = guideXWidth;\n        guideSprite2.Bounds.Width = 60;\n\n        CornersQuad dest = *UIButtonGuideEndDisp;\n        dest.Translate(glm::vec2{guideXWidth, 0});\n        Renderer->DrawSprite(guideSprite2, dest, glm::mat4(1.0f), tints,\n                             glm::vec3(0.0f), false);\n      }\n    }\n  }\n}\n\nvoid UpdateCCButtonGuide([[maybe_unused]] float dt) {\n  if (ScrWork[SW_UI_BTNGUIDE_REQ] == 0) {\n    if (ScrWork[SW_UI_BTNGUIDE_PROG] == 0) {\n      ScrWork[SW_UI_BTNGUIDE_TYPE] = 0;\n    } else {\n      ScrWork[SW_UI_BTNGUIDE_PROG]--;\n    }\n  } else {\n    ScrWork[SW_UI_BTNGUIDE_TYPE] = ScrWork[SW_UI_BTNGUIDE_REQ];\n    if (ScrWork[SW_UI_BTNGUIDE_PROG] < 32) {\n      ScrWork[SW_UI_BTNGUIDE_PROG]++;\n    }\n  }\n}\n\nstatic void UpdateCCAtChanScrollbar() {\n  if (ScrWork[SW_UI_BTNGUIDE_TYPE] == 1) {\n    static int dragOffset = 0;\n    bool mouseHover = false;\n\n    using Profile::Vm::ScrWorkBgStructSize;\n    // From decompile\n    const RectF thumbBounds{\n        (float)-ScrWork[SW_BG1POSX + ScrWorkBgStructSize * 2] * 1.5f - 2.0f,\n        (float)-ScrWork[SW_BG1POSY + ScrWorkBgStructSize * 2] * 1.5f - 2.0f,\n        22.0f,\n        112.0f,\n    };\n    const std::pair<int, int> bgYRange{-45, ScrWork[SW_ATCHAN_SCROLL_MAX]};\n\n    // Empirical values\n    const std::pair<float, float> mouseYRange{83.5f, 908.5f};\n\n    const float slope = (bgYRange.second - bgYRange.first) /\n                        (mouseYRange.second - mouseYRange.first);\n    const float offset = bgYRange.first - (mouseYRange.first * slope);\n    const int conversion = (int)(Input::CurMousePos.y * slope + offset);\n\n    if (Input::CurrentInputDevice == Input::Device::Mouse) {\n      ScrWork[SW_BG1POSY] -= Input::MouseWheelDeltaY * 16;\n      mouseHover = thumbBounds.ContainsPoint(Input::CurMousePos);\n      if (mouseHover) {\n        if (Input::MouseButtonWentDown[SDL_BUTTON_LEFT]) {\n          Audio::PlayInGroup(Audio::ACG_SE, \"sysse\", 2, false, 0.0f);\n\n          dragOffset = conversion - ScrWork[SW_BG1POSY];\n          Vm::Interface::PADinputMouseWentDown &= ~Vm::Interface::PAD1A;\n        }\n        RequestCursor(CursorType::Pointer);\n      }\n\n      if (ActiveCursorType == CursorType::Pointer) {\n        if (Input::MouseButtonIsDown[SDL_BUTTON_LEFT]) {\n          ScrWork[SW_BG1POSY] = conversion - dragOffset;\n          RequestCursor(CursorType::Pointer);\n        } else if (!mouseHover) {\n          RequestCursor(CursorType::Default);\n          dragOffset = 0;\n        }\n      }\n    } else {\n      RequestCursor(CursorType::Default);\n      dragOffset = 0;\n    }\n\n    ScrWork[SW_BG1POSY] =\n        std::clamp(ScrWork[SW_BG1POSY], bgYRange.first, bgYRange.second);\n  }\n}\n}  // namespace GameSpecific\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/gamespecific.h",
    "content": "#pragma once\nnamespace Impacto {\nnamespace UI {\nnamespace GameSpecific {\nvoid Init();\nvoid NonGameplayUpdate(float dt);\nvoid Update(float dt);\nvoid RenderEarlyMain();\nvoid RenderMain();\nvoid RenderLayer(uint32_t layer);\nvoid RenderCCButtonGuide();\nvoid UpdateCCButtonGuide(float dt);\n\n}  // namespace GameSpecific\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/mapsystem.cpp",
    "content": "#include \"../profile/ui/mapsystem.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace MapSystem {\n\nvoid Init() {\n  Profile::MapSystem::Configure();\n  Profile::MapSystem::CreateInstance();\n}\nvoid MapInit() { return MapSystemPtr->MapInit(); }\nint MapLoad(uint8_t* data) { return MapSystemPtr->MapLoad(data); }\nint MapSave(uint8_t* data) { return MapSystemPtr->MapSave(data); }\nvoid MapSetFadein(int partId, int partType) {\n  return MapSystemPtr->MapSetFadein(partId, partType);\n}\nvoid MapSetGroup(int index, int mappedId1, int mappedId2, int mappedId3) {\n  return MapSystemPtr->MapSetGroup(index, mappedId1, mappedId2, mappedId3);\n}\nvoid MapSetFadeout(int partId, int partType) {\n  return MapSystemPtr->MapSetFadeout(partId, partType);\n}\nvoid MapSetDisp(int partId, int partType) {\n  return MapSystemPtr->MapSetDisp(partId, partType);\n}\nvoid MapSetHide(int arg1, int arg2) {\n  return MapSystemPtr->MapSetHide(arg1, arg2);\n}\nbool MapPoolFadeEndChk_Wait() { return MapSystemPtr->MapPoolFadeEndChk_Wait(); }\nvoid MapMoveAnimeInit(int arg1, int arg2, int arg3) {\n  return MapSystemPtr->MapMoveAnimeInit(arg1, arg2, arg3);\n}\nbool MapMoveAnimeMain() { return MapSystemPtr->MapMoveAnimeMain(); }\nvoid MapGetPos(int partId, int partType, int& getX, int& getY) {\n  return MapSystemPtr->MapGetPos(partId, partType, getX, getY);\n}\nvoid MapSetPool(int index, int id, int type) {\n  return MapSystemPtr->MapSetPool(index, id, type);\n}\nvoid MapResetPoolAll(int arg1) { return MapSystemPtr->MapResetPoolAll(arg1); }\nbool MapFadeEndChk_Wait() { return MapSystemPtr->MapFadeEndChk_Wait(); }\nvoid MapPoolShuffle(int param_1) {\n  return MapSystemPtr->MapPoolShuffle(param_1);\n}\nvoid MapPoolSetDisp(int arg1, int arg2) {\n  return MapSystemPtr->MapPoolSetDisp(arg1, arg2);\n}\nvoid MapPoolSetHide(int arg1, int arg2) {\n  return MapSystemPtr->MapPoolSetHide(arg1, arg2);\n}\nvoid MapPoolSetFadein(int unused, int poolIdx) {\n  return MapSystemPtr->MapPoolSetFadein(unused, poolIdx);\n}\nvoid MapPoolSetFadeout(int unused, int poolIdx) {\n  return MapSystemPtr->MapPoolSetFadeout(unused, poolIdx);\n}\nbool MapPlayerPhotoSelect(int unused) {\n  return MapSystemPtr->MapPlayerPhotoSelect(unused);\n}\nvoid MapResetPool(int poolIdx) { return MapSystemPtr->MapResetPool(poolIdx); }\nvoid MapSetGroupEx(int index, int type, int mappedId) {\n  return MapSystemPtr->MapSetGroupEx(index, type, mappedId);\n}\nvoid MapZoomInit(int mapX, int mapY, int size) {\n  return MapSystemPtr->MapZoomInit(mapX, mapY, size);\n}\nbool MapZoomMain() { return MapSystemPtr->MapZoomMain(); }\nvoid MapZoomInit2(int arg1, int arg2) {\n  return MapSystemPtr->MapZoomInit2(arg1, arg2);\n}\nbool MapZoomMain3() { return MapSystemPtr->MapZoomMain3(); }\nbool MapZoomInit3(int setMapX, int setMapY, int setMapSize, bool halfZoom) {\n  return MapSystemPtr->MapZoomInit3(setMapX, setMapY, setMapSize, halfZoom);\n}\nbool MapMoveAnimeInit2(int setMapX, int setMapY, int setTransitionSize) {\n  return MapSystemPtr->MapMoveAnimeInit2(setMapX, setMapY, setTransitionSize);\n}\nbool MapMoveAnimeMain2() { return MapSystemPtr->MapMoveAnimeMain2(); }\nvoid MapPlayerPotalSelectInit() {\n  return MapSystemPtr->MapPlayerPotalSelectInit();\n}\nbool MapPlayerPotalSelect() { return MapSystemPtr->MapPlayerPotalSelect(); }\nvoid MapSystem_28() { return MapSystemPtr->MapSystem_28(); }\nvoid Update(float dt) { return MapSystemPtr->Update(dt); }\nvoid RenderButtonGuide() { return MapSystemPtr->RenderButtonGuide(); }\nvoid Render() { return MapSystemPtr->Render(); }\n\n}  // namespace MapSystem\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/mapsystem.h",
    "content": "#pragma once\n#include <cstdint>\nnamespace Impacto {\nnamespace UI {\nnamespace MapSystem {\n\nclass MapSystemBase {\n public:\n  virtual void MapInit() = 0;\n  virtual int MapLoad(uint8_t* data) = 0;\n  virtual int MapSave(uint8_t* data) = 0;\n  virtual void MapSetFadein(int partId, int partType) = 0;\n  virtual void MapSetGroup(int index, int mappedId1, int mappedId2,\n                           int mappedId3) = 0;\n  virtual void MapSetFadeout(int partId, int partType) = 0;\n  virtual void MapSetDisp(int partId, int partType) = 0;\n  virtual void MapSetHide(int arg1, int arg2) = 0;\n  virtual bool MapPoolFadeEndChk_Wait() = 0;\n  virtual void MapMoveAnimeInit(int arg1, int arg2, int arg3) = 0;\n  virtual bool MapMoveAnimeMain() = 0;\n  virtual void MapGetPos(int partId, int partType, int& getX, int& getY) = 0;\n  virtual void MapSetPool(int index, int id, int type) = 0;\n  virtual void MapResetPoolAll(int arg1) = 0;\n  virtual bool MapFadeEndChk_Wait() = 0;\n  virtual void MapPoolShuffle(int param_1) = 0;\n  virtual void MapPoolSetDisp(int arg1, int arg2) = 0;\n  virtual void MapPoolSetHide(int arg1, int arg2) = 0;\n  virtual void MapPoolSetFadein(int unused, int poolIdx) = 0;\n  virtual void MapPoolSetFadeout(int unused, int poolIdx) = 0;\n  virtual bool MapPlayerPhotoSelect(int unused) = 0;\n  virtual void MapResetPool(int poolIdx) = 0;\n  virtual void MapSetGroupEx(int index, int type, int mappedId) = 0;\n  virtual void MapZoomInit(int mapX, int mapY, int size) = 0;\n  virtual bool MapZoomMain() = 0;\n  virtual void MapZoomInit2(int arg1, int arg2) = 0;\n  virtual bool MapZoomMain3() = 0;\n  virtual bool MapZoomInit3(int setMapX, int setMapY, int setMapSize,\n                            bool halfZoom = false) = 0;\n  virtual bool MapMoveAnimeInit2(int setMapX, int setMapY,\n                                 int setTransitionSize) = 0;\n  virtual bool MapMoveAnimeMain2() = 0;\n  virtual void MapPlayerPotalSelectInit() = 0;\n  virtual bool MapPlayerPotalSelect() = 0;\n  virtual void MapSystem_28() = 0;\n  virtual void Update(float dt) = 0;\n  virtual void RenderButtonGuide() = 0;\n  virtual void Render() = 0;\n};\n\ninline MapSystemBase* MapSystemPtr = nullptr;\n\nvoid Init();\nint MapLoad(uint8_t* data);\nint MapSave(uint8_t* data);\nvoid MapInit();\nvoid MapSetFadein(int partId, int partType);\nvoid MapSetGroup(int index, int mappedId1, int mappedId2, int mappedId3);\nvoid MapSetFadeout(int partId, int partType);\nvoid MapSetDisp(int partId, int partType);\nvoid MapSetHide(int arg1, int arg2);\nbool MapPoolFadeEndChk_Wait();\nvoid MapMoveAnimeInit(int arg1, int arg2, int arg3);\nbool MapMoveAnimeMain();\nvoid MapGetPos(int partId, int partType, int& getX, int& getY);\nvoid MapSetPool(int index, int id, int type);\nvoid MapResetPoolAll(int arg1);\nbool MapFadeEndChk_Wait();\nvoid MapPoolShuffle(int param_1);\nvoid MapPoolSetDisp(int arg1, int arg2);\nvoid MapPoolSetHide(int arg1, int arg2);\nvoid MapPoolSetFadein(int unused, int poolIdx);\nvoid MapPoolSetFadeout(int unused, int poolIdx);\nbool MapPlayerPhotoSelect(int unused);\nvoid MapResetPool(int poolIdx);\nvoid MapSetGroupEx(int index, int type, int mappedId);\nvoid MapZoomInit(int mapX, int mapY, int size);\nbool MapZoomMain();\nvoid MapZoomInit2(int arg1, int arg2);\nbool MapZoomMain3();\nbool MapZoomInit3(int setMapX, int setMapY, int setMapSize,\n                  bool halfZoom = false);\nbool MapMoveAnimeInit2(int setMapX, int setMapY, int setTransitionSize);\nbool MapMoveAnimeMain2();\nvoid MapPlayerPotalSelectInit();\nbool MapPlayerPotalSelect();\nvoid MapSystem_28();\nvoid Update(float dt);\nvoid RenderButtonGuide();\nvoid Render();\n\n}  // namespace MapSystem\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/menu.cpp",
    "content": "#include \"menu.h\"\n#include \"ui.h\"\n#include \"../inputsystem.h\"\n#include \"../vm/interface/input.h\"\n\nnamespace Impacto {\nnamespace UI {\n\nusing namespace Impacto::Vm::Interface;\n\nvoid Menu::Show() {\n  if (State != Shown) {\n    State = Showing;\n    if (UI::FocusedMenu != 0) {\n      LastFocusedMenu = UI::FocusedMenu;\n      LastFocusedMenu->IsFocused = false;\n    }\n    IsFocused = true;\n    UI::FocusedMenu = this;\n  }\n}\nvoid Menu::Hide() {\n  if (State != Hidden) {\n    State = Hiding;\n    if (LastFocusedMenu != 0) {\n      UI::FocusedMenu = LastFocusedMenu;\n      LastFocusedMenu->IsFocused = true;\n    } else {\n      UI::FocusedMenu = 0;\n    }\n    IsFocused = false;\n  }\n}\n\nvoid Menu::UpdateInput(float dt) {\n  const auto& padBtn = [this] {\n    switch (InputConfig) {\n      case InputRate::SingleTap:\n        return PADinputButtonWentDown;\n      case InputRate::RepeatSlow:\n        return PADinputButtonRepeatDown;\n      case InputRate::RepeatFast:\n        return PADinputButtonRepeatAccelDown;\n      case InputRate::Hold:\n        return PADinputButtonIsDown;\n    }\n    throw std::invalid_argument(\"Invalid InputRate\");\n  }();\n\n  if (IsFocused) {\n    if (padBtn & PAD1DOWN) {\n      AdvanceFocus(FDIR_DOWN);\n    } else if (padBtn & PAD1UP) {\n      AdvanceFocus(FDIR_UP);\n    } else if (padBtn & PAD1RIGHT) {\n      AdvanceFocus(FDIR_RIGHT);\n    } else if (padBtn & PAD1LEFT) {\n      AdvanceFocus(FDIR_LEFT);\n    }\n  }\n}\n\nvoid Menu::AdvanceFocus(FocusDirection dir) {\n  if (!CurrentlyFocusedElement) {\n    if (!FocusStart[dir]) return;\n\n    CurrentlyFocusedElement = FocusStart[dir];\n    if (CurrentlyFocusedElement->GetType() == WT_GROUP)\n      CurrentlyFocusedElement = CurrentlyFocusedElement->GetFocus(dir);\n\n    if (CurrentlyFocusedElement) CurrentlyFocusedElement->HasFocus = true;\n    return;\n  }\n\n  auto el = CurrentlyFocusedElement->GetFocus(dir);\n  if (el && el->GetType() == WT_GROUP) el = el->GetFocus(dir);\n\n  if (el) {\n    CurrentlyFocusedElement->HasFocus = false;\n    CurrentlyFocusedElement = el;\n    CurrentlyFocusedElement->HasFocus = true;\n  }\n}\n\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/menu.h",
    "content": "#pragma once\n\n#include <vector>\n#include \"../impacto.h\"\n#include \"widget.h\"\n#include \"../game.h\"\n\nnamespace Impacto {\nnamespace UI {\n\nenum MenuState : uint8_t { Hidden, Hiding, Showing, Shown };\nenum class InputRate : uint8_t { SingleTap, RepeatSlow, RepeatFast, Hold };\n\nclass Menu {\n public:\n  virtual void Init() {};\n  virtual void Show();\n  virtual void Hide();\n  virtual void Update(float dt) = 0;\n  virtual void Render() = 0;\n\n  virtual void UpdateInput(float dt);\n\n  Menu* LastFocusedMenu = 0;\n  Widget* FocusStart[4] = {0, 0, 0, 0};\n  Widget* CurrentlyFocusedElement = 0;\n  InputRate InputConfig = InputRate::RepeatSlow;\n  MenuState State = Hidden;\n\n  bool IsFocused = false;\n  bool ChoiceMade = false;\n  bool AllowsScriptInput = true;\n  uint8_t DrawType = +Game::DrawComponentType::Main;\n\n protected:\n  void AdvanceFocus(FocusDirection dir);\n};\n\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/nullmenu.cpp",
    "content": "#include \"nullmenu.h\"\n\nnamespace Impacto {\nnamespace UI {\n\nvoid NullMenu::Show() {}\nvoid NullMenu::Hide() {}\nvoid NullMenu::Update(float dt) {}\nvoid NullMenu::Render() {}\n\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/nullmenu.h",
    "content": "#pragma once\n\n#include \"menu.h\"\n\nnamespace Impacto {\nnamespace UI {\n\nclass NullMenu : public Menu {\n public:\n  void Show() override;\n  void Hide() override;\n  void Update(float dt) override;\n  void Render() override;\n};\n\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/optionsmenu.cpp",
    "content": "#include \"optionsmenu.h\"\n\n#include \"../profile/game.h\"\n#include \"../profile/ui/optionsmenu.h\"\n#include \"../profile/scriptinput.h\"\n#include \"../vm/interface/input.h\"\n\nnamespace Impacto {\nnamespace UI {\n\nusing namespace Impacto::Profile::OptionsMenu;\nusing namespace Impacto::Profile::ScriptVars;\nusing namespace Impacto::UI::Widgets;\nusing namespace Impacto::Vm::Interface;\n\nOptionsMenu::OptionsMenu()\n    : PADinputButtonHoldMask(PAD1UP | PAD1DOWN | PAD1LEFT | PAD1RIGHT) {\n  FadeAnimation.Direction = AnimationDirection::In;\n  FadeAnimation.LoopMode = AnimationLoopMode::Stop;\n  FadeAnimation.DurationIn = FadeInDuration;\n  FadeAnimation.DurationOut = FadeOutDuration;\n  FadeAnimation.SkipOnSkipMode = false;\n}\n\nvoid OptionsMenu::Show() {\n  if (State != Shown) {\n    if (State != Showing) {\n      FadeAnimation.StartIn();\n\n      if (!RememberLastPage) CurrentPage = 0;\n      Pages[CurrentPage]->HasFocus = true;\n      Pages[CurrentPage]->Show();\n\n      Highlight(RememberHighlightedEntries\n                    ? HighlightedEntriesPerPage[CurrentPage]\n                    : Pages[CurrentPage]->GetFirstFocusableChild());\n      UpdateValues();\n\n      ResetPADHoldTimer(PAD1UP | PAD1DOWN | PAD1LEFT | PAD1RIGHT | PAD1L1 |\n                        PAD1R1);\n    }\n    State = Showing;\n\n    if (UI::FocusedMenu != nullptr) {\n      LastFocusedMenu = UI::FocusedMenu;\n      LastFocusedMenu->IsFocused = false;\n    }\n    IsFocused = true;\n    UI::FocusedMenu = this;\n  }\n}\n\nvoid OptionsMenu::Hide() {\n  if (State != Hidden) {\n    if (State != Hiding) FadeAnimation.StartOut();\n    State = Hiding;\n\n    Pages[CurrentPage]->Hide();\n\n    if (LastFocusedMenu != nullptr) {\n      UI::FocusedMenu = LastFocusedMenu;\n      LastFocusedMenu->IsFocused = true;\n    } else {\n      UI::FocusedMenu = nullptr;\n    }\n    IsFocused = false;\n\n    if (CurrentlyFocusedElement) {\n      CurrentlyFocusedElement->HasFocus = false;\n      CurrentlyFocusedElement = nullptr;\n    }\n  }\n}\n\nvoid OptionsMenu::Update(float dt) {\n  FadeAnimation.Update(dt);\n  UpdateVisibility();\n\n  if (State == Shown && IsFocused) {\n    Pages[CurrentPage]->Update(dt);\n    UpdateInput(dt);\n  }\n}\n\nvoid OptionsMenu::UpdatePageInput(float dt) {\n  Pages[CurrentPage]->UpdateInput(dt);\n  const int shouldFire = PADinputButtonRepeatDown & (PAD1L1 | PAD1R1);\n\n  // Button input\n  const int direction =\n      (bool)(shouldFire & PAD1R1) - (bool)(shouldFire & PAD1L1);\n\n  if (direction == 0 && shouldFire) {\n    ResetPADHoldTimer(PAD1L1 | PAD1R1);\n    return;\n  }\n\n  const auto nextPage =\n      (static_cast<int>(CurrentPage) + direction + std::ssize(Pages)) %\n      std::ssize(Pages);\n  GoToPage(static_cast<size_t>(nextPage));\n}\n\nvoid OptionsMenu::UpdateEntryMovementInput(float dt) {\n  const uint32_t shouldFire = PADinputButtonRepeatDown & PADinputButtonHoldMask;\n\n  const int verticalMovement =\n      (bool)(shouldFire & PAD1DOWN) - (bool)(shouldFire & PAD1UP);\n  const FocusDirection verticalDirection = verticalMovement == -1\n                                               ? FocusDirection::FDIR_UP\n                                               : FocusDirection::FDIR_DOWN;\n  const int horizontalMovement =\n      (bool)(shouldFire & PAD1RIGHT) - (bool)(shouldFire & PAD1LEFT);\n  const FocusDirection horizontalDirection = horizontalMovement == -1\n                                                 ? FocusDirection::FDIR_LEFT\n                                                 : FocusDirection::FDIR_RIGHT;\n\n  if (!verticalMovement && !horizontalMovement) {\n    if (shouldFire) ResetPADHoldTimer(PADinputButtonHoldMask);\n    return;\n  }\n\n  if (horizontalMovement != 0) AdvanceFocus(horizontalDirection);\n  if (verticalMovement != 0) AdvanceFocus(verticalDirection);\n\n  Highlight(CurrentlyFocusedElement);\n}\n\nvoid OptionsMenu::UpdateInput(float dt) {\n  UpdatePageInput(dt);\n\n  UpdateEntryMovementInput(dt);\n}\n\nvoid OptionsMenu::GoToPage(size_t pageNumber) {\n  if (CurrentPage == pageNumber) return;\n\n  Pages[CurrentPage]->Hide();\n\n  CurrentPage = pageNumber;\n  std::unique_ptr<Group>& page = Pages[CurrentPage];\n\n  page->HasFocus = true;\n  page->Show();\n\n  Highlight(RememberHighlightedEntries ? HighlightedEntriesPerPage[CurrentPage]\n                                       : page->GetFirstFocusableChild());\n}\n\nvoid OptionsMenu::Highlight(Widget* toHighlight) {\n  for (Widget* entry : Pages[CurrentPage]->Children) {\n    entry->HasFocus = false;\n  }\n\n  if (toHighlight) {\n    toHighlight->HasFocus = true;\n\n    PADinputButtonHoldMask =\n        (PAD1UP * (bool)toHighlight->GetFocus(FDIR_UP)) |\n        (PAD1DOWN * (bool)toHighlight->GetFocus(FDIR_DOWN)) |\n        (PAD1LEFT * (bool)toHighlight->GetFocus(FDIR_LEFT)) |\n        (PAD1RIGHT * (bool)toHighlight->GetFocus(FDIR_RIGHT));\n  }\n  CurrentlyFocusedElement = toHighlight;\n  if (RememberHighlightedEntries) {\n    HighlightedEntriesPerPage[CurrentPage] = CurrentlyFocusedElement;\n  }\n}\n\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/optionsmenu.h",
    "content": "#pragma once\n\n#include \"menu.h\"\n#include \"widgets/group.h\"\n#include \"../profile/configsystem.h\"\n\nnamespace Impacto {\nnamespace UI {\n\nclass OptionsMenu : public Menu {\n public:\n  virtual void Show() override;\n  virtual void Hide() override;\n  virtual void Update(float dt) override;\n  virtual void UpdateInput(float dt) override;\n  virtual void ResetToDefault() {\n    Profile::ConfigSystem::ResetToDefault();\n    UpdateValues();\n  }\n\n protected:\n  OptionsMenu();\n\n  virtual void UpdateValues() {}\n\n  virtual void GoToPage(size_t pageNumber);\n  virtual void UpdatePageInput(float dt);\n  virtual void UpdateEntryMovementInput(float dt);\n  virtual void UpdateVisibility() = 0;\n\n  virtual void Highlight(Widget* toHighlight);\n\n  std::vector<Widget*> HighlightedEntriesPerPage;\n\n  Animation FadeAnimation;\n\n  size_t CurrentPage = 0;\n  std::vector<std::unique_ptr<Widgets::Group>> Pages;\n\n  uint32_t PADinputButtonHoldMask;\n\n  bool RememberLastPage = false;\n  bool RememberHighlightedEntries = false;\n};\n\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/savemenu.h",
    "content": "#pragma once\n\n#include <optional>\n#include \"menu.h\"\n#include \"../text/text.h\"\n#include \"../data/savesystem.h\"\n\nnamespace Impacto {\nnamespace UI {\nenum class SaveMenuPageType : int {\n  QuickLoad,\n  Save,\n  Load,\n};\nclass SaveMenu : public Menu {\n public:\n  SaveMenu() { InputConfig = InputRate::RepeatFast; }\n  virtual void Update(float dt) override {};\n  virtual void Render() override {};\n\n  virtual void RefreshCurrentEntryInfo() {};\n\n  uint8_t ActivePage = 0;\n  std::optional<SaveMenuPageType> ActiveMenuType;\n};\n\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/selectionmenu.cpp",
    "content": "#include \"selectionmenu.h\"\n\n#include \"../profile/ui/selectionmenu.h\"\n#include \"../profile/dialogue.h\"\n#include \"../profile/game.h\"\n#include \"../renderer/renderer.h\"\n\nnamespace Impacto {\nnamespace UI {\n\nusing namespace Impacto::UI::Widgets;\nusing namespace Impacto::Profile::SelectionMenu;\n\nvoid SelectionMenu::ChoiceItemOnClick(Button* target) {\n  SelectedChoiceId = target->Id;\n  ChoiceMade = true;\n}\n\nvoid SelectionMenu::InitSelectionMenu(bool isPlain) {\n  IsPlain = isPlain;\n  ChoiceCount = 0;\n  ChoiceMade = false;\n  ChoiceHeight = 0.0f;\n  ChoiceWidthMax = FLT_MIN;\n  ChoiceXMin = FLT_MAX;\n\n  FadeAnimation.DurationIn = FadeAnimationDurationInOut;\n  FadeAnimation.DurationOut = FadeAnimationDurationInOut;\n  FadeAnimation.Direction = AnimationDirection::In;\n  FadeAnimation.LoopMode = AnimationLoopMode::Stop;\n}\n\nvoid SelectionMenu::AddChoice(Vm::BufferOffsetContext ctx) {\n  Impacto::Vm::Sc3VmThread dummy;\n  dummy.IpOffset = ctx.IpOffset;\n  dummy.ScriptBufferId = ctx.ScriptBufferId;\n  Choices[ChoiceCount] = TextLayoutPlainLine(\n      &dummy, 255, Profile::Dialogue::DialogueFont,\n      Profile::Dialogue::DefaultFontSize, Profile::Dialogue::ColorTable[0],\n      1.0f, glm::vec2(0.0f, 0.0f), TextAlignment::Left);\n\n  float mesLen = 0.0f;\n  for (const ProcessedTextGlyph& glyph : Choices[ChoiceCount]) {\n    mesLen += glyph.DestRect.Width;\n  }\n\n  ChoiceWidths[ChoiceCount] = mesLen;\n  ChoiceCount++;\n}\n\nvoid SelectionMenu::Show() {\n  ChoiceItems = new Widgets::Group(this);\n\n  Sprite nullSprite = Sprite();\n  nullSprite.Bounds = RectF(0.0f, 0.0f, 0.0f, 0.0f);\n\n  auto onClick = [this](auto* btn) { return ChoiceItemOnClick(btn); };\n\n  if (IsPlain) {\n    float diff = 0.0f;\n    float choiceY = (Profile::DesignHeight -\n                     (ChoiceCount * (Profile::Dialogue::DefaultFontSize +\n                                     PlainSelectionYSpacing))) /\n                    2.0f;\n\n    for (int i = 0; i < ChoiceCount; i++) {\n      if (ChoiceWidths[i] > ChoiceWidthMax) ChoiceWidthMax = ChoiceWidths[i];\n\n      diff = (Profile::DesignWidth - ChoiceWidths[i]) / 2.0f;\n      for (ProcessedTextGlyph& glyph : Choices[i]) {\n        glyph.DestRect.X += diff;\n        glyph.DestRect.Y = choiceY;\n      }\n\n      if (Choices[i][0].DestRect.X < ChoiceXMin)\n        ChoiceXMin = Choices[i][0].DestRect.X;\n\n      ChoiceHeight +=\n          Profile::Dialogue::DefaultFontSize + PlainSelectionYSpacing;\n      choiceY += Profile::Dialogue::DefaultFontSize + PlainSelectionYSpacing;\n\n      Button* choice = new Button(\n          i, nullSprite, nullSprite, SelectionHighlight,\n          glm::vec2(Choices[i][0].DestRect.X, Choices[i][0].DestRect.Y));\n\n      choice->SetText(Choices[i], ChoiceWidths[i],\n                      Profile::Dialogue::DefaultFontSize,\n                      RendererOutlineMode::Full);\n      choice->OnClickHandler = onClick;\n\n      ChoiceItems->Add(choice, FDIR_DOWN);\n    }\n    ChoiceHeight -= PlainSelectionYSpacing;\n  } else {\n    float diff = 0.0f;\n    float choiceY = 0.0f;\n\n    CurrentSelBackgroundY = SelectionBackgroundY[ChoiceCount - 1];\n    choiceY = SelectionBackgroundY[ChoiceCount - 1] +\n              (SelectionBackground.Bounds.Height -\n               Profile::Dialogue::DefaultFontSize) /\n                  2.0f;\n\n    for (int i = 0; i < ChoiceCount; i++) {\n      diff = (Profile::DesignWidth - ChoiceWidths[i]) / 2.0f;\n      for (ProcessedTextGlyph& glyph : Choices[i]) {\n        glyph.DestRect.X += diff;\n        glyph.DestRect.Y = choiceY;\n      }\n      choiceY += SelectionYSpacing;\n\n      Button* choice = new Button(\n          i, nullSprite, SelectionFocused, SelectionHighlight,\n          glm::vec2(Choices[i][0].DestRect.X, Choices[i][0].DestRect.Y));\n\n      choice->SetText(Choices[i], ChoiceWidths[i],\n                      Profile::Dialogue::DefaultFontSize,\n                      RendererOutlineMode::Full);\n      choice->OnClickHandler = onClick;\n      if (!HighlightTextOnly) {\n        choice->Bounds = RectF(SelectionBackgroundX,\n                               CurrentSelBackgroundY + (i * SelectionYSpacing),\n                               SelectionBackground.ScaledWidth(),\n                               SelectionBackground.ScaledHeight());\n      }\n\n      ChoiceItems->Add(choice, FDIR_DOWN);\n    }\n  }\n\n  ChoiceItems->Show();\n  FadeAnimation.StartIn();\n  State = Showing;\n  Menu::Show();\n}\n\nvoid SelectionMenu::Hide() {\n  FadeAnimation.StartOut();\n  State = Hiding;\n  memset(FocusStart, 0, sizeof(FocusStart));\n  if (CurrentlyFocusedElement) CurrentlyFocusedElement->HasFocus = false;\n  CurrentlyFocusedElement = 0;\n  Menu::Hide();\n}\n\nvoid SelectionMenu::Update(float dt) {\n  UpdateInput(dt);\n  FadeAnimation.Update(dt);\n  if (State != Hidden) {\n    if (FadeAnimation.IsIn()) State = Shown;\n    if (FadeAnimation.IsOut()) State = Hidden;\n    ChoiceItems->Update(dt);\n    ChoiceItems->UpdateInput(dt);\n  }\n}\nvoid SelectionMenu::Render() {\n  if (State == Hidden) return;\n\n  glm::vec4 col(1.0f, 1.0f, 1.0f, FadeAnimation.Progress);\n  if (IsPlain) {\n    Renderer->DrawSprite(\n        PlainSelectionFrameTopLeft,\n        glm::vec2(ChoiceXMin - PlainSelectionFrameTopLeft.Bounds.Width,\n                  Choices[0][0].DestRect.Y -\n                      PlainSelectionFrameTopLeft.Bounds.Height),\n        col);\n    Renderer->DrawSprite(\n        PlainSelectionFrameTopSide,\n        RectF(\n            ChoiceXMin,\n            Choices[0][0].DestRect.Y - PlainSelectionFrameTopLeft.Bounds.Height,\n            ChoiceWidthMax, PlainSelectionFrameTopLeft.Bounds.Height),\n        col);\n    Renderer->DrawSprite(\n        PlainSelectionFrameTopRight,\n        glm::vec2(ChoiceXMin + ChoiceWidthMax,\n                  Choices[0][0].DestRect.Y -\n                      PlainSelectionFrameTopLeft.Bounds.Height),\n        col);\n    Renderer->DrawSprite(\n        PlainSelectionFrameLeftSide,\n        RectF(ChoiceXMin - PlainSelectionFrameTopLeft.Bounds.Width,\n              Choices[0][0].DestRect.Y, PlainSelectionFrameTopLeft.Bounds.Width,\n              ChoiceHeight),\n        col);\n    Renderer->DrawSprite(\n        PlainSelectionFrameRightSide,\n        RectF(ChoiceXMin + ChoiceWidthMax, Choices[0][0].DestRect.Y,\n              PlainSelectionFrameTopLeft.Bounds.Width, ChoiceHeight),\n        col);\n    Renderer->DrawSprite(\n        PlainSelectionFrameBottomLeft,\n        glm::vec2(ChoiceXMin - PlainSelectionFrameTopLeft.Bounds.Width,\n                  Choices[0][0].DestRect.Y + ChoiceHeight),\n        col);\n    Renderer->DrawSprite(PlainSelectionFrameBottomRight,\n                         glm::vec2(ChoiceXMin + ChoiceWidthMax,\n                                   Choices[0][0].DestRect.Y + ChoiceHeight),\n                         col);\n    Renderer->DrawSprite(\n        PlainSelectionFrameBottomSide,\n        RectF(ChoiceXMin, Choices[0][0].DestRect.Y + ChoiceHeight,\n              ChoiceWidthMax, PlainSelectionFrameTopLeft.Bounds.Height),\n        col);\n    Renderer->DrawSprite(PlainSelectionFrameMiddle,\n                         RectF(ChoiceXMin, Choices[0][0].DestRect.Y,\n                               ChoiceWidthMax, ChoiceHeight),\n                         col);\n  } else {\n    for (int i = 0; i < ChoiceCount; i++) {\n      Renderer->DrawSprite(\n          SelectionBackground,\n          glm::vec2(SelectionBackgroundX,\n                    CurrentSelBackgroundY + (i * SelectionYSpacing)),\n          col);\n    }\n  }\n\n  ChoiceItems->Tint = col;\n  ChoiceItems->Render();\n}\n\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/selectionmenu.h",
    "content": "#pragma once\n\n#include \"menu.h\"\n#include \"widgets/button.h\"\n#include \"widgets/group.h\"\n#include \"../text/text.h\"\n\nnamespace Impacto {\nnamespace UI {\n\nclass SelectionMenu : public Menu {\n public:\n  void Show() override;\n  void Hide() override;\n  void Update(float dt) override;\n  void Render() override;\n\n  void InitSelectionMenu(bool isPlain);\n  void AddChoice(Vm::BufferOffsetContext ctx);\n\n  void ChoiceItemOnClick(Widgets::Button* target);\n\n  int SelectedChoiceId = 0;\n\n private:\n  Widgets::Group* ChoiceItems;\n  Animation FadeAnimation;\n  bool IsPlain;\n  std::array<std::vector<ProcessedTextGlyph>, 20> Choices;\n  std::array<float, 20> ChoiceWidths;\n  int ChoiceCount = 0;\n\n  float CurrentSelBackgroundY = 0.0f;\n  float ChoiceHeight = 0.0f;\n  float ChoiceWidthMax = FLT_MIN;\n  float ChoiceXMin = FLT_MAX;\n};\n\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/sysmesbox.cpp",
    "content": "#include \"sysmesbox.h\"\n\nnamespace Impacto {\nnamespace UI {\n\nvoid SysMesBox::Show() {}\nvoid SysMesBox::Hide() {}\nvoid SysMesBox::Update(float dt) {}\nvoid SysMesBox::Render() {}\nvoid SysMesBox::Init() {}\nvoid SysMesBox::AddMessage(Vm::BufferOffsetContext) {}\nvoid SysMesBox::AddChoice(Vm::BufferOffsetContext) {}\n\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/sysmesbox.h",
    "content": "#pragma once\n\n#include \"menu.h\"\n#include \"../vm/vm.h\"\n#include \"../text/text.h\"\n#include \"../ui/widgets/group.h\"\n\nnamespace Impacto {\nnamespace UI {\n\nclass SysMesBox : public Menu {\n public:\n  virtual void Show() override;\n  virtual void Hide() override;\n  virtual void Update(float dt) override;\n  virtual void Render() override;\n\n  virtual void Init() override;\n  virtual void AddMessage(Vm::BufferOffsetContext str);\n  virtual void AddChoice(Vm::BufferOffsetContext str);\n\n  int MessageCount;\n  int ChoiceCount;\n\n protected:\n  Widgets::Group* MessageItems;\n  Widgets::Group* ChoiceItems;\n\n  float BoxOpacity;\n  std::array<std::vector<ProcessedTextGlyph>, 8> Messages;\n  std::array<float, 8> MessageWidths;\n  std::array<std::vector<ProcessedTextGlyph>, 8> Choices;\n  std::array<float, 8> ChoiceWidths;\n  float ChoiceX = 0.0f;\n\n  Animation FadeAnimation;\n};\n\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/tipsmenu.cpp",
    "content": "#include \"tipsmenu.h\"\n\nnamespace Impacto {\nnamespace UI {\n\nvoid TipsMenu::Init() {}\nvoid TipsMenu::Show() {}\nvoid TipsMenu::Hide() {}\nvoid TipsMenu::Update(float dt) {}\nvoid TipsMenu::Render() {}\nvoid TipsMenu::SwitchToTipId(int id) {}\nvoid TipsMenu::AdvanceTipPage(TipAdvanceMode mode) {}\n\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/tipsmenu.h",
    "content": "#pragma once\n\n#include \"menu.h\"\n#include \"../text/dialoguepage.h\"\n#include \"../ui/widgets/label.h\"\n\nnamespace Impacto {\nnamespace UI {\n\n// \"Clamped\" can't go outside [min,max], \"Looped\" loopes from last to first\nenum class TipAdvanceMode : uint8_t { PrevClamped, NextClamped, NextLooped };\n\nclass TipsMenu : public Menu {\n public:\n  TipsMenu() { InputConfig = InputRate::RepeatFast; }\n  virtual void Init() override;\n  virtual void Show() override;\n  virtual void Hide() override;\n  virtual void Update(float dt) override;\n  virtual void Render() override;\n\n protected:\n  virtual void SwitchToTipId(int id);\n  virtual void AdvanceTipPage(TipAdvanceMode mode);\n\n  int CurrentlyDisplayedTipId = -1;\n\n  Animation FadeAnimation;\n  DialoguePage TextPage;\n  Widgets::Label* Name;\n  Widgets::Label* Pronounciation;\n  Widgets::Label* Category;\n  Widgets::Label* NumberText;\n  Widgets::Label* Number;\n};\n\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/ui.h",
    "content": "#pragma once\n\n#include \"backlogmenu.h\"\n#include \"selectionmenu.h\"\n#include \"nullmenu.h\"\n#include \"sysmesbox.h\"\n#include \"tipsmenu.h\"\n#include \"optionsmenu.h\"\n#include \"savemenu.h\"\n#include \"../game.h\"\n\n#include <ankerl/unordered_dense.h>\n#include <vector>\n\nnamespace Impacto {\nnamespace UI {\n\nenum class CommonMenuType : int {\n  None,\n  CHLCC,\n};\nenum class SystemMenuType : int {\n  None,\n  RNE,\n  MO6TW,\n  CHLCC,\n  MO8,\n  CCLCC,\n};\nenum class SaveMenuType : int {\n  None,\n  MO6TW,\n  MO8,\n  CHLCC,\n  CCLCC,\n};\nenum class SysMesBoxType : int {\n  None,\n  RNE,\n  Dash,\n  CHLCC,\n  MO6TW,\n  Darling,\n  CC,\n};\nenum class TitleMenuType : int {\n  None,\n  RNE,\n  Dash,\n  CHLCC,\n  MO6TW,\n  MO8,\n  CC,\n  CCLCC,\n};\nenum class OptionsMenuType : int {\n  None,\n  MO6TW,\n  MO8,\n  CHLCC,\n  CCLCC,\n};\nenum class TipsMenuType : int {\n  None,\n  MO6TW,\n  CHLCC,\n  CCLCC,\n};\nenum class ClearListMenuType : int {\n  None,\n  MO6TW,\n  CHLCC,\n  CCLCC,\n};\nenum class LibraryMenuType : int {\n  None,\n  CCLCC,\n};\nenum class AlbumMenuType : int {\n  None,\n  MO6TW,\n  CHLCC,\n};\nenum class MusicMenuType : int {\n  None,\n  MO6TW,\n  CHLCC,\n};\nenum class MovieMenuType : int {\n  None,\n  MO6TW,\n  CHLCC,\n};\nenum class ActorsVoiceMenuType : int {\n  None,\n  MO6TW,\n};\nenum class TrophyMenuType : int {\n  None,\n  CHLCC,\n};\nenum class HelpMenuType : int {\n  None,\n  CCLCC,\n};\nenum class GameSpecificType : int {\n  None,\n  CHLCC,\n  CC,\n  CCLCC,\n  RNE,\n  Dash,\n};\nint constexpr MaxExtraMenus = 10;\n\ninline ankerl::unordered_dense::map<Game::DrawComponentType, std::vector<Menu*>>\n    Menus;\n\n// Current focused menu\ninline Menu* FocusedMenu = nullptr;\n\n// Common menus\ninline Menu* SystemMenuPtr = new NullMenu();\ninline Menu* TitleMenuPtr = new NullMenu();\ninline Menu* TrophyMenuPtr = new NullMenu();\ninline Menu* HelpMenuPtr = new NullMenu();\ninline SaveMenu* SaveMenuPtr = new SaveMenu();\n\n//\ninline SelectionMenu* SelectionMenuPtr = nullptr;\ninline SysMesBox* SysMesBoxPtr = nullptr;\ninline BacklogMenu* BacklogMenuPtr = nullptr;\ninline TipsMenu* TipsMenuPtr = nullptr;\ninline OptionsMenu* OptionsMenuPtr = nullptr;\n\n// Extras menus\ninline Menu* LibraryMenuPtr = nullptr;\ninline Menu* ClearListMenuPtr = nullptr;\ninline Menu* AlbumMenuPtr = nullptr;\ninline Menu* MusicMenuPtr = nullptr;\ninline Menu* MovieMenuPtr = nullptr;\ninline Menu* ActorsVoiceMenuPtr = nullptr;\n\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/widget.cpp",
    "content": "#include \"widget.h\"\n#include \"../inputsystem.h\"\n#include \"../renderer/window.h\"\n\nnamespace Impacto {\nnamespace UI {\nvoid Widget::Update(float dt) {\n  if (MoveAnimation.State == AnimationState::Playing) {\n    MoveAnimation.Update(dt);\n    auto move = glm::mix(MoveOrigin, MoveTarget, MoveAnimation.Progress);\n    MoveTo(move);\n  }\n\n  if (Enabled && Hovered && Input::CurrentInputDevice == Input::Device::Mouse) {\n    RequestCursor(CursorType::Pointer);\n  }\n}\n\nvoid Widget::Show() {}\nvoid Widget::Hide() {\n  HasFocus = false;\n  Hovered = false;\n}\n\nWidgetType Widget::GetType() { return WT_NORMAL; }\n\nvoid Widget::Move(glm::vec2 relativePosition, float duration) {\n  MoveOrigin = Bounds.GetPos();\n  MoveTarget = MoveOrigin + relativePosition;\n  if (MoveToAnchor.has_value()) *MoveToAnchor += relativePosition;\n\n  MoveAnimation.SetDuration(duration);\n  MoveAnimation.StartIn(true);\n}\n\nWidget* Widget::GetFocus(FocusDirection dir) {\n  Widget* nextFocus = FocusElements[dir];\n  while (nextFocus && !nextFocus->Enabled) {\n    nextFocus = nextFocus->FocusElements[dir];\n    assert(nextFocus != this && \"Entered an infinite loop\");\n  }\n  return nextFocus;\n}\n\nvoid Widget::SetFocus(Widget* widget, FocusDirection dir) {\n  FocusElements[dir] = widget;\n}\n\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/widget.h",
    "content": "#pragma once\n\n#include <magic_enum/magic_enum.hpp>\n\n#include \"../impacto.h\"\n#include \"../util.h\"\n#include \"../animation.h\"\n\nnamespace Impacto {\nnamespace UI {\n\nenum FocusDirection { FDIR_LEFT, FDIR_RIGHT, FDIR_UP, FDIR_DOWN };\nenum WidgetType { WT_NORMAL, WT_GROUP };\n\nclass Widget {\n public:\n  bool Enabled = true;\n  bool HasFocus = false;\n  bool Hovered = false;\n\n  glm::vec4 Tint = glm::vec4(1.0f);\n\n  virtual ~Widget() = default;\n\n  virtual void Update(float dt);\n  virtual void UpdateInput(float dt) = 0;\n  virtual void Render() = 0;\n\n  virtual void Show();\n  virtual void Hide();\n\n  virtual WidgetType GetType();\n\n  // TODO: Text movement in widgets with text\n  virtual void Move(glm::vec2 relativePosition) { Bounds += relativePosition; }\n  void Move(glm::vec2 relativePosition, float duration);\n  void MoveTo(glm::vec2 pos) {\n    Move(pos - MoveToAnchor.value_or(Bounds.GetPos()));\n  }\n  void MoveTo(glm::vec2 pos, float duration) {\n    Move(pos - MoveToAnchor.value_or(Bounds.GetPos()), duration);\n  }\n\n  virtual Widget* GetFocus(FocusDirection dir);\n  virtual void SetFocus(Widget* widget, FocusDirection dir);\n\n  RectF Bounds = RectF(0.0f, 0.0f, 0.0f, 0.0f);\n\n  // Will move this point to the desired location with MoveTo\n  std::optional<glm::vec2> MoveToAnchor;\n\n  glm::vec2 MoveTarget{};\n  glm::vec2 MoveOrigin{};\n  Animation MoveAnimation;\n\n private:\n  Widget* FocusElements[4] = {0, 0, 0, 0};\n};\n\n}  // namespace UI\n\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/widgets/backlogentry.cpp",
    "content": "#include \"backlogentry.h\"\n#include \"../../renderer/renderer.h\"\n#include \"../../inputsystem.h\"\n#include \"../../mem.h\"\n#include \"../../profile/scriptvars.h\"\n#include \"../../vm/interface/input.h\"\n#include \"../../vm/thread.h\"\n#include \"../../profile/dialogue.h\"\n#include \"../../profile/ui/backlogmenu.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\n\nusing namespace Impacto::Profile::BacklogMenu;\nusing namespace Impacto::Profile::ScriptVars;\n\nBacklogEntry::BacklogEntry(int id, Vm::BufferOffsetContext scrCtx, int audioId,\n                           int characterId, glm::vec2 pos,\n                           const RectF& hoverBounds)\n    : Id(id),\n      AudioId(audioId),\n      CharacterId(characterId),\n      Position(pos),\n      HoverBounds(hoverBounds) {\n  Enabled = true;\n\n  BacklogPage = new DialoguePage();\n  BacklogPage->Glyphs.reserve(512);\n  BacklogPage->Clear();\n  BacklogPage->Mode = DPM_REV;\n\n  Impacto::Vm::Sc3VmThread dummy;\n  dummy.IpOffset = scrCtx.IpOffset;\n  dummy.ScriptBufferId = scrCtx.ScriptBufferId;\n  // CHLCC uses DPM_REV for the Erin DialogueBox\n  if (ScrWork[SW_MESWIN0TYPE] == 0) {\n    REVBounds.X = pos.x;\n    REVBounds.Y = pos.y;\n  }\n  BacklogPage->AddString(&dummy);\n\n  TextLength = BacklogPage->Length;\n\n  Bounds = !BacklogPage->Glyphs.empty() ? BacklogPage->Glyphs.begin()->DestRect\n                                        : RectF(pos.x, pos.y, 0, 0);\n  for (const ProcessedTextGlyph& glyph : BacklogPage->Glyphs) {\n    Bounds = RectF::Coalesce(Bounds, glyph.DestRect);\n  }\n  Position.x = Bounds.X;  // X position should not take name into account\n  for (const ProcessedTextGlyph& glyph : BacklogPage->Name) {\n    Bounds = RectF::Coalesce(Bounds, glyph.DestRect);\n  }\n  TextHeight = Bounds.Height;\n  Position.y = Bounds.Y;  // Y position should\n\n  switch (BacklogPage->Alignment) {\n    default:\n    case TextAlignment::Left:\n      break;\n    case TextAlignment::Center: {\n      const RectF& revBounds = REVBounds;\n      pos.x = revBounds.X + (revBounds.Width - BacklogPage->Dimensions.x) / 2;\n      break;\n    }\n  }\n\n  MoveToAnchor = Position;\n  MoveTo(pos);\n}\n\nBacklogEntry::~BacklogEntry() { delete BacklogPage; }\n\nvoid BacklogEntry::UpdateInput(float dt) {\n  if (Enabled) {\n    RectF entryHoverBounds =\n        RectF(HoverBounds.X, Bounds.Y, HoverBounds.Width, Bounds.Height);\n    if (Input::CurrentInputDevice == Input::Device::Mouse) {\n      Hovered =\n          entryHoverBounds.ContainsPoint(Input::CurMousePos) &&\n          HoverBounds.Y <= Bounds.Y &&\n          (Bounds.Y + Bounds.Height) <= (HoverBounds.Y + HoverBounds.Height);\n    } else if (Input::CurrentInputDevice == Input::Device::Touch &&\n               Input::TouchIsDown[0]) {\n      Hovered =\n          entryHoverBounds.ContainsPoint(Input::CurTouchPos) &&\n          HoverBounds.Y <= Bounds.Y &&\n          (Bounds.Y + Bounds.Height) <= (HoverBounds.Y + HoverBounds.Height);\n    }\n    if (HasFocus &&\n        ((Hovered &&\n          Vm::Interface::PADinputMouseWentDown & Vm::Interface::PAD1A) ||\n         (Vm::Interface::PADinputButtonWentDown & Vm::Interface::PAD1A))) {\n      OnClickHandler(this);\n    }\n  }\n}\n\nvoid BacklogEntry::Move(glm::vec2 relativePosition) {\n  Position += relativePosition;\n  Widget::Move(relativePosition);\n  BacklogPage->Move(relativePosition);\n}\n\nvoid BacklogEntry::Render() {\n  if (AudioId != -1) {\n    Renderer->DrawSprite(\n        VoiceIcon,\n        glm::vec2(Bounds.X - VoiceIcon.ScaledWidth(), Bounds.Y) +\n            VoiceIconOffset,\n        Tint);\n  }\n\n  if (BacklogPage->HasName()) {\n    Renderer->DrawProcessedText(BacklogPage->Name,\n                                Profile::Dialogue::DialogueFont, Tint.a,\n                                Profile::Dialogue::REVNameOutlineMode);\n  }\n\n  Renderer->DrawProcessedText(BacklogPage->Glyphs,\n                              Profile::Dialogue::DialogueFont, Tint.a,\n                              Profile::Dialogue::REVOutlineMode);\n}\n\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/widgets/backlogentry.h",
    "content": "#pragma once\n\n#include <functional>\n\n#include \"../../profile/dialogue.h\"\n#include \"../widget.h\"\n#include \"../../text/dialoguepage.h\"\n#include \"../../vm/vm.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\n\nclass BacklogEntry : public Widget {\n public:\n  BacklogEntry(int id, Vm::BufferOffsetContext scrCtx, int audioId,\n               int characterId, glm::vec2 pos, const RectF& hoverBounds);\n  ~BacklogEntry();\n\n  void UpdateInput(float dt) override;\n  void Render() override;\n\n  using Widget::Move;\n  void Move(glm::vec2 relativePosition) override;\n\n  int Id;\n  int AudioId = -1;\n  int CharacterId = 0;\n  float TextHeight = 0.0f;\n\n  std::function<void(BacklogEntry*)> OnClickHandler;\n\n protected:\n  DialoguePage* BacklogPage;\n  int TextLength = 0;\n\n private:\n  glm::vec2 Position;\n\n  RectF REVBounds = Profile::Dialogue::REVBounds;\n  const RectF& HoverBounds;\n};\n\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/widgets/button.cpp",
    "content": "#include \"button.h\"\n#include \"../../renderer/renderer.h\"\n#include \"../../inputsystem.h\"\n#include \"../../mem.h\"\n#include \"../../profile/scriptvars.h\"\n#include \"../../vm/interface/input.h\"\n#include \"../../vm/thread.h\"\n#include \"../../profile/dialogue.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\n\nusing namespace Impacto::Profile::ScriptVars;\n\nButton::Button(int id, Sprite const& norm, Sprite const& focused,\n               Sprite const& highlight, glm::vec2 pos, RectF hoverBounds) {\n  Enabled = true;\n  Id = id;\n  NormalSprite = norm;\n  FocusedSprite = focused;\n  HighlightSprite = highlight;\n  Bounds = RectF(pos.x, pos.y, NormalSprite.Bounds.Width,\n                 NormalSprite.Bounds.Height);\n  HoverBounds = hoverBounds;\n}\n\nvoid Button::UpdateInput(float dt) {\n  if (Enabled) {\n    const RectF& bounds = (HoverBounds != RectF{}) ? HoverBounds : Bounds;\n    if (Input::CurrentInputDevice == Input::Device::Mouse) {\n      Hovered = bounds.ContainsPoint(Input::CurMousePos);\n    } else if (Input::CurrentInputDevice == Input::Device::Touch &&\n               Input::TouchIsDown[0]) {\n      Hovered = bounds.ContainsPoint(Input::CurTouchPos);\n    }\n    if (OnClickHandler && HasFocus &&\n        ((Hovered &&\n          Vm::Interface::PADinputMouseWentDown & Vm::Interface::PAD1A) ||\n         (Vm::Interface::PADinputButtonWentDown & Vm::Interface::PAD1A))) {\n      OnClickHandler(this);\n    }\n  }\n}\n\nvoid Button::Render() {\n  if (HasFocus) {\n    const RectF dest =\n        HighlightSprite.ScaledBounds()\n            .Scale({Bounds.Width / HighlightSprite.ScaledWidth(), 1.0f},\n                   {0.0f, 0.0f})\n            .Translate(Bounds.GetPos() + HighlightOffset);\n    Renderer->DrawSprite(HighlightSprite, dest, Tint);\n  }\n\n  if (IsLocked) {\n    Renderer->DrawSprite(LockedSprite, glm::vec2(Bounds.X, Bounds.Y), Tint);\n  } else if (HasFocus && Enabled) {\n    Renderer->DrawSprite(FocusedSprite, glm::vec2(Bounds.X, Bounds.Y), Tint);\n  } else if (Enabled) {\n    Renderer->DrawSprite(NormalSprite, glm::vec2(Bounds.X, Bounds.Y), Tint);\n  } else {\n    Renderer->DrawSprite(DisabledSprite, glm::vec2(Bounds.X, Bounds.Y), Tint);\n  }\n\n  if (HasText) {\n    Renderer->DrawProcessedText(Text, Profile::Dialogue::DialogueFont, Tint.a,\n                                OutlineMode, true);\n  }\n}\n\nvoid Button::SetText(Vm::BufferOffsetContext scrCtx, float fontSize,\n                     RendererOutlineMode outlineMode,\n                     DialogueColorPair colorPair) {\n  HasText = true;\n  Impacto::Vm::Sc3VmThread dummy;\n  dummy.IpOffset = scrCtx.IpOffset;\n  dummy.ScriptBufferId = scrCtx.ScriptBufferId;\n  Text = TextLayoutPlainLine(\n      &dummy, 255, Profile::Dialogue::DialogueFont, fontSize, colorPair, 1.0f,\n      glm::vec2(Bounds.X, Bounds.Y), TextAlignment::Left);\n  OutlineMode = outlineMode;\n  TextWidth = 0.0f;\n  for (const ProcessedTextGlyph& glyph : Text) {\n    TextWidth += glyph.DestRect.Width;\n  }\n  Bounds = RectF(Text[0].DestRect.X, Text[0].DestRect.Y, TextWidth, fontSize);\n  HoverBounds = Bounds;\n}\nvoid Button::SetText(Vm::BufferOffsetContext scrCtx, float fontSize,\n                     RendererOutlineMode outlineMode, int colorIndex) {\n  SetText(scrCtx, fontSize, outlineMode,\n          Profile::Dialogue::ColorTable[colorIndex]);\n}\n\nvoid Button::SetText(Vm::Sc3Stream& stream, float fontSize,\n                     RendererOutlineMode outlineMode,\n                     DialogueColorPair colorPair) {\n  HasText = true;\n  Text = TextLayoutPlainLine(\n      stream, 255, Profile::Dialogue::DialogueFont, fontSize, colorPair, 1.0f,\n      glm::vec2(Bounds.X, Bounds.Y), TextAlignment::Left);\n  OutlineMode = outlineMode;\n  for (const ProcessedTextGlyph& glyph : Text) {\n    TextWidth += glyph.DestRect.Width;\n  }\n  Bounds = RectF(Text[0].DestRect.X, Text[0].DestRect.Y, TextWidth, fontSize);\n  HoverBounds = Bounds;\n}\n\nvoid Button::SetText(Vm::Sc3Stream& stream, float fontSize,\n                     RendererOutlineMode outlineMode, int colorIndex) {\n  SetText(stream, fontSize, outlineMode,\n          Profile::Dialogue::ColorTable[colorIndex]);\n}\n\nvoid Button::SetText(std::vector<ProcessedTextGlyph> text, float textWidth,\n                     float fontSize, RendererOutlineMode outlineMode) {\n  HasText = true;\n  Text = std::move(text);\n  TextWidth = textWidth;\n  OutlineMode = outlineMode;\n  Bounds = RectF(Text[0].DestRect.X, Text[0].DestRect.Y, TextWidth, fontSize);\n  HoverBounds = Bounds;\n}\n\nvoid Button::Move(glm::vec2 relativePosition) {\n  if (HasText) {\n    for (ProcessedTextGlyph& glyph : Text) {\n      glyph.DestRect += relativePosition;\n    }\n  }\n  Widget::Move(relativePosition);\n  if (HoverBounds != RectF{}) HoverBounds += relativePosition;\n}\n\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/widgets/button.h",
    "content": "#pragma once\n\n#include <functional>\n\n#include \"../widget.h\"\n#include \"../../spritesheet.h\"\n#include \"../../text/text.h\"\n#include \"../../renderer/renderer.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\n\nclass Button : public Widget {\n public:\n  Button() {}\n  Button(int id, Sprite const& norm, Sprite const& focused,\n         Sprite const& highlight, glm::vec2 pos, RectF hoverBounds = RectF{});\n\n  virtual void UpdateInput(float dt) override;\n  virtual void Render() override;\n\n  using Widget::Move;\n  virtual void Move(glm::vec2 relativePosition) override;\n\n  void SetText(Vm::BufferOffsetContext scrCtx, float fontSize,\n               RendererOutlineMode outlineMode, int colorIndex = 10);\n  void SetText(Vm::BufferOffsetContext scrCtx, float fontSize,\n               RendererOutlineMode outlineMode, DialogueColorPair colorPair);\n  void SetText(Vm::Sc3Stream& stream, float fontSize,\n               RendererOutlineMode outlineMode, int colorIndex = 10);\n  void SetText(Vm::Sc3Stream& stream, float fontSize,\n               RendererOutlineMode outlineMode, DialogueColorPair colorPair);\n  virtual void SetText(std::vector<ProcessedTextGlyph> text, float textWidth,\n                       float fontSize, RendererOutlineMode outlineMode);\n  void ClearText() {\n    Text.clear();\n    Bounds = {};\n    HoverBounds = {};\n    HasText = false;\n  }\n\n  std::function<void(Button*)> OnClickHandler;\n\n  Sprite NormalSprite;\n  Sprite FocusedSprite;\n  Sprite HighlightSprite;\n  Sprite DisabledSprite;\n  Sprite LockedSprite;\n  RectF HoverBounds;\n  glm::vec2 HighlightOffset = glm::vec2(0.0f, 3.0f);\n\n  int Id;\n  bool IsLocked = false;\n\n protected:\n  bool HasText = false;\n  std::vector<ProcessedTextGlyph> Text;\n  float TextWidth = 0.0f;\n  RendererOutlineMode OutlineMode;\n};\n\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/widgets/carousel.cpp",
    "content": "#include \"carousel.h\"\n#include \"../../vm/interface/input.h\"\n#include \"../../profile/scriptinput.h\"\n#include \"../../inputsystem.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\n\nusing namespace Impacto::Profile::ScriptInput;\nusing namespace Impacto::Vm::Interface;\n\nCarousel::Carousel(CarouselDirection dir) {\n  Direction = dir;\n  OnAdvanceHandler = [this](Widget* current, Widget* next) {\n    return OnChange(current, next);\n  };\n  OnBackHandler = [this](Widget* current, Widget* next) {\n    return OnChange(current, next);\n  };\n}\n\nCarousel::Carousel(CarouselDirection dir,\n                   std::function<void(Widget*, Widget*)> onAdvanceHandler,\n                   std::function<void(Widget*, Widget*)> onBackHandler) {\n  Direction = dir;\n  OnAdvanceHandler = onAdvanceHandler;\n  OnBackHandler = onBackHandler;\n}\n\nvoid Carousel::Update(float dt) {\n  for (const auto& el : Children) {\n    el->Update(dt);\n  }\n}\n\nvoid Carousel::UpdateInput(float dt) {\n  if (!Children.empty()) {\n    auto buttonAdvance = Direction == CDIR_HORIZONTAL ? PAD1RIGHT : PAD1DOWN;\n    auto buttonBack = Direction == CDIR_HORIZONTAL ? PAD1LEFT : PAD1UP;\n\n    if (PADinputButtonWentDown & buttonBack) {\n      Previous();\n    }\n    if (PADinputButtonWentDown & buttonAdvance) {\n      Next();\n    }\n  }\n}\n\nvoid Carousel::Clear() {\n  for (const auto& el : Children) {\n    delete el;\n  }\n  Children.clear();\n  Iterator = Children.end();\n}\n\nvoid Carousel::Render() {\n  for (const auto& el : Children) {\n    auto tint = el->Tint;\n    el->Tint *= Tint;\n    el->Render();\n    el->Tint = tint;\n  }\n}\n\nvoid Carousel::Add(Widget* widget) {\n  Children.push_back(widget);\n  Iterator = Children.begin();\n}\n\nvoid Carousel::Next() {\n  auto current = *Iterator;\n  Iterator++;\n  if (Iterator == Children.end()) {\n    Iterator = Children.begin();\n  }\n  auto next = *Iterator;\n  OnAdvanceHandler(current, next);\n}\n\nvoid Carousel::Previous() {\n  auto current = *Iterator;\n  if (Iterator == Children.begin()) {\n    Iterator = Children.end();\n  }\n  Iterator--;\n  auto next = *Iterator;\n  OnBackHandler(current, next);\n}\n\nvoid Carousel::OnChange(Widget* current, Widget* next) {\n  if (current != next) {\n    current->Hide();\n    next->Show();\n  }\n}\n\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/widgets/carousel.h",
    "content": "#pragma once\n\n#include \"../widget.h\"\n#include <vector>\n#include <functional>\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\n\nenum CarouselDirection { CDIR_VERTICAL, CDIR_HORIZONTAL };\n\nclass Carousel : public Widget {\n public:\n  Carousel(CarouselDirection dir);\n  Carousel(CarouselDirection dir,\n           std::function<void(Widget*, Widget*)> onAdvanceHandler,\n           std::function<void(Widget*, Widget*)> onBackHandler);\n  ~Carousel() { Clear(); }\n  void Update(float dt) override;\n  void UpdateInput(float dt) override;\n  void Render() override;\n\n  void Add(Widget* widget);\n  void Next();\n  void Previous();\n  void Clear();\n  auto GetCurrent() const { return Iterator; }\n\n  std::vector<Widget*> Children;\n\n private:\n  void OnChange(Widget* current, Widget* next);\n\n  std::function<void(Widget*, Widget*)> OnAdvanceHandler;\n  std::function<void(Widget*, Widget*)> OnBackHandler;\n\n  CarouselDirection Direction;\n  std::vector<Widget*>::iterator Iterator;\n};\n\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/widgets/cc/backlogentry.cpp",
    "content": "#include \"backlogentry.h\"\n#include \"../../../renderer/renderer.h\"\n#include \"../../../profile/dialogue.h\"\n#include \"../../../profile/ui/backlogmenu.h\"\n#include \"../../../profile/games/cc/backlogmenu.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\nnamespace CC {\n\nusing namespace Impacto::Profile::BacklogMenu;\nusing namespace Impacto::Profile::CC::BacklogMenu;\n\nvoid BacklogEntry::Render() {\n  if (AudioId != -1) {\n    RectF bounds = RectF(Bounds.X - VoiceIcon.ScaledWidth() + VoiceIconOffset.x,\n                         Bounds.Y + VoiceIconOffset.y, VoiceIcon.ScaledWidth(),\n                         VoiceIcon.ScaledHeight());\n    Sprite mask;\n    mask.Sheet = BacklogMaskSheet;\n    mask.Bounds = bounds;\n\n    Renderer->DrawMaskedSpriteOverlay(VoiceIcon, mask, bounds,\n                                      (int)(Tint.a * 255), 256, glm::mat4(1.0f),\n                                      Tint, false, false);\n  }\n\n  if (BacklogPage->HasName()) {\n    Renderer->DrawProcessedText(\n        BacklogPage->Name, Profile::Dialogue::DialogueFont, Tint.a,\n        Profile::Dialogue::REVNameOutlineMode, true, &BacklogMaskSheet);\n  }\n\n  Renderer->DrawProcessedText(\n      BacklogPage->Glyphs, Profile::Dialogue::DialogueFont, Tint.a,\n      Profile::Dialogue::REVOutlineMode, true, &BacklogMaskSheet);\n}\n\n}  // namespace CC\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/widgets/cc/backlogentry.h",
    "content": "#pragma once\n\n#include \"../backlogentry.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\nnamespace CC {\n\nclass BacklogEntry : public Widgets::BacklogEntry {\n public:\n  using Widgets::BacklogEntry::BacklogEntry;\n\n  void Render() override;\n};\n\n}  // namespace CC\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/widgets/cc/titlebutton.cpp",
    "content": "#include \"titlebutton.h\"\n\n#include \"../../../renderer/renderer.h\"\n#include \"../../../profile/games/cc/titlemenu.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\nnamespace CC {\n\nusing namespace Impacto::Profile::CC::TitleMenu;\n\nvoid TitleButton::Render() {\n  if (HasFocus) {\n    if (!IsSubButton) {  // Main buttons\n      Renderer->DrawSprite(HighlightSprite,\n                           glm::vec2(Bounds.X - ItemHighlightOffsetX,\n                                     Bounds.Y - ItemHighlightOffsetY),\n                           Tint);\n      Renderer->DrawSprite(\n          ItemHighlightPointerSprite,\n          glm::vec2(Bounds.X - ItemHighlightPointerY, Bounds.Y), Tint);\n      Renderer->DrawSprite(FocusedSprite, glm::vec2(Bounds.X, Bounds.Y), Tint);\n    } else {  // Sub buttons\n      Renderer->DrawSprite(HighlightSprite, glm::vec2(Bounds.X, Bounds.Y),\n                           Tint);\n      Renderer->DrawSprite(FocusedSprite, glm::vec2(Bounds.X, Bounds.Y), Tint);\n    }\n  } else {\n    if (Enabled) {\n      Renderer->DrawSprite(NormalSprite, glm::vec2(Bounds.X, Bounds.Y), Tint);\n    } else {\n      Renderer->DrawSprite(DisabledSprite, glm::vec2(Bounds.X, Bounds.Y), Tint);\n    }\n  }\n}\n\n}  // namespace CC\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/widgets/cc/titlebutton.h",
    "content": "#pragma once\n\n#include \"../button.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\nnamespace CC {\n\nclass TitleButton : public Widgets::Button {\n public:\n  TitleButton(int id, Sprite const& norm, Sprite const& focused,\n              Sprite const& highlight, glm::vec2 pos)\n      : Widgets::Button(id, norm, focused, highlight, pos) {}\n  void Render() override;\n  bool IsSubButton = false;\n};\n\n}  // namespace CC\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/widgets/cclcc/librarymenubutton.h",
    "content": "#pragma once\n\n#include <string>\n#include <functional>\n\n#include \"../../../profile/games/cclcc/librarymenu.h\"\n#include \"../../../animation.h\"\n#include \"../button.h\"\n#include \"../label.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\nnamespace CCLCC {\n\nclass LibraryMenuButton : public Widgets::Button {\n public:\n  LibraryMenuButton(int id, Sprite const& hovered, Sprite const& selected,\n                    glm::vec2 pos, Animation& blinkAnimation)\n      : Widgets::Button(\n            id, Sprite(), selected, hovered, pos,\n            RectF(pos.x, pos.y, hovered.Bounds.Width, hovered.Bounds.Height)),\n        BlinkAnimation(blinkAnimation) {\n    Bounds = HoverBounds;\n  }\n  void Render() override {\n    if (!Enabled) {\n      return;\n    }\n    if (Selected) {\n      Renderer->DrawSprite(FocusedSprite, glm::vec2(Bounds.X, Bounds.Y), Tint);\n    } else if (HasFocus) {\n      using namespace Profile::CCLCC::LibraryMenu;\n\n      const glm::vec4 blinkMask{glm::vec3{1 - BlinkAnimation.Progress} *\n                                        (1.0f - ButtonBlinkTintMinimum) +\n                                    ButtonBlinkTintMinimum,\n                                1};\n      Renderer->DrawSprite(HighlightSprite, glm::vec2(Bounds.X, Bounds.Y),\n                           Tint * blinkMask);\n    }\n  }\n  bool Selected = false;\n  Animation& BlinkAnimation;\n};\n\n}  // namespace CCLCC\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto\n"
  },
  {
    "path": "src/ui/widgets/cclcc/optionsbinarybutton.cpp",
    "content": "#include \"optionsbinarybutton.h\"\n\n#include \"../../../profile/games/cclcc/optionsmenu.h\"\n#include \"../../../renderer/renderer.h\"\n#include \"../../../vm/interface/input.h\"\n#include \"../../../inputsystem.h\"\n#include \"../../../audio/audiosystem.h\"\n\nusing namespace Impacto::Profile::CCLCC::OptionsMenu;\nusing namespace Impacto::Vm::Interface;\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\nnamespace CCLCC {\n\nOptionsBinaryButton::OptionsBinaryButton(\n    bool& value, const Sprite& box, const Sprite& trueLabel,\n    const Sprite& falseLabel, const Sprite& label, glm::vec2 pos,\n    glm::vec4 highlightTint, std::function<void(OptionsEntry*)> select,\n    std::function<void(Widget*)> highlight)\n    : OptionsEntry(label, pos, highlightTint, select, highlight),\n      TrueSprite(trueLabel),\n      FalseSprite(falseLabel),\n      BoxSprite(box),\n      State(value) {\n  Bounds.Width = BinaryBoxOffset.x + BoxSprite.ScaledWidth();\n  EntryButton.Bounds.Width = Bounds.Width;\n\n  glm::vec2 truePosition = pos + BinaryBoxOffset;\n  TrueButton =\n      ClickArea(0,\n                RectF(truePosition.x, truePosition.y, TrueSprite.ScaledWidth(),\n                      TrueSprite.ScaledHeight()),\n                [this](auto* btn) { return TrueOnClick(btn); });\n  FalseButton = ClickArea(\n      0, TrueButton.Bounds + glm::vec2(box.ScaledWidth() / 2.0f, 0.0f),\n      [this](auto* btn) { return FalseOnClick(btn); });\n}\n\nvoid OptionsBinaryButton::Render() {\n  OptionsEntry::Render();\n\n  RectF highlightBounds(0.0f, 0.0f, BoxSprite.ScaledWidth() / 2,\n                        BoxSprite.ScaledHeight());\n  glm::vec2 highlightPos = ((State) ? TrueButton : FalseButton).Bounds.GetPos();\n  highlightBounds.X = highlightPos.x;\n  highlightBounds.Y = highlightPos.y;\n\n  Renderer->DrawQuad(highlightBounds, HighlightTint);\n  Renderer->DrawSprite(BoxSprite, TrueButton.Bounds.GetPos(), Tint);\n\n  Renderer->DrawSprite(TrueSprite, TrueButton.Bounds.GetPos(), Tint, !State);\n  Renderer->DrawSprite(FalseSprite, FalseButton.Bounds.GetPos(), Tint, State);\n}\n\nvoid OptionsBinaryButton::Update(float dt) {\n  OptionsEntry::Update(dt);\n\n  TrueButton.Update(dt);\n  FalseButton.Update(dt);\n}\n\nvoid OptionsBinaryButton::UpdateInput(float dt) {\n  // Handle mouse/touch input\n  TrueButton.UpdateInput(dt);\n  FalseButton.UpdateInput(dt);\n\n  OptionsEntry::UpdateInput(dt);\n\n  if (!Selected) return;\n\n  // Handle keyboard/controller input\n  if (PADinputButtonWentDown & (PAD1LEFT | PAD1RIGHT)) {\n    const bool newState = PADinputButtonWentDown & PAD1LEFT;\n\n    if (State != newState)\n      Audio::PlayInGroup(Audio::ACG_SE, \"sysse\", 1, false, 0.0f);\n\n    State = newState;\n  }\n}\n\nvoid OptionsBinaryButton::Show() {\n  OptionsEntry::Show();\n\n  TrueButton.Show();\n  FalseButton.Show();\n}\n\nvoid OptionsBinaryButton::Hide() {\n  OptionsEntry::Hide();\n\n  TrueButton.Hide();\n  FalseButton.Hide();\n}\n\nvoid OptionsBinaryButton::Move(glm::vec2 relativePos) {\n  OptionsEntry::Move(relativePos);\n  TrueButton.Move(relativePos);\n  FalseButton.Move(relativePos);\n}\n\nvoid OptionsBinaryButton::TrueOnClick(ClickArea* target) {\n  if (Selected && State != true)\n    Audio::PlayInGroup(Audio::ACG_SE, \"sysse\", 1, false, 0.0f);\n\n  State = true;\n}\n\nvoid OptionsBinaryButton::FalseOnClick(ClickArea* target) {\n  if (Selected && State != false)\n    Audio::PlayInGroup(Audio::ACG_SE, \"sysse\", 1, false, 0.0f);\n\n  State = false;\n}\n\n}  // namespace CCLCC\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/widgets/cclcc/optionsbinarybutton.h",
    "content": "#pragma once\n\n#include \"./optionsentry.h\"\n#include \"../clickarea.h\"\n#include \"../../../spritesheet.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\nnamespace CCLCC {\n\nclass OptionsBinaryButton : public OptionsEntry {\n public:\n  OptionsBinaryButton(bool& value, const Sprite& box, const Sprite& trueLabel,\n                      const Sprite& falseLabel, const Sprite& label,\n                      glm::vec2 pos, glm::vec4 highlightTint,\n                      std::function<void(OptionsEntry*)> select,\n                      std::function<void(Widget*)> highlight);\n\n  void Render() override;\n  void Update(float dt) override;\n  void UpdateInput(float dt) override;\n\n  void Show() override;\n  void Hide() override;\n\n  using Widget::Move;\n  void Move(glm::vec2 relativePos) override;\n\n private:\n  ClickArea TrueButton;\n  ClickArea FalseButton;\n\n  const Sprite& TrueSprite;\n  const Sprite& FalseSprite;\n\n  void TrueOnClick(ClickArea* target);\n  void FalseOnClick(ClickArea* target);\n\n  const Sprite& BoxSprite;\n\n  bool& State;\n};\n\n}  // namespace CCLCC\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/widgets/cclcc/optionsentry.cpp",
    "content": "#include \"optionsentry.h\"\n\n#include \"../../../profile/games/cclcc/optionsmenu.h\"\n#include \"../../../renderer/renderer.h\"\n#include \"../../../vm/interface/input.h\"\n#include \"../../../audio/audiosystem.h\"\n\nusing namespace Impacto::Profile::CCLCC::OptionsMenu;\nusing namespace Impacto::Vm::Interface;\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\nnamespace CCLCC {\n\nOptionsEntry::OptionsEntry(const Sprite& label, glm::vec2 pos,\n                           glm::vec4 highlightTint,\n                           std::function<void(OptionsEntry*)> select,\n                           std::function<void(Widget*)> highlight)\n    : Select(select),\n      Highlight(highlight),\n      LabelSprite(label),\n      HighlightTint(highlightTint) {\n  Bounds = RectF(pos.x, pos.y, LabelOffset.x + LabelSprite.ScaledWidth(),\n                 LabelOffset.y + LabelSprite.ScaledHeight());\n\n  std::function<void(ClickArea*)> onClick = [this](auto* btn) {\n    return EntryButtonOnClick(btn);\n  };\n  EntryButton = ClickArea(0, Bounds, onClick);\n}\n\nvoid OptionsEntry::Render() {\n  HighlightTint.a = Tint.a;\n\n  if (HasFocus) {\n    RectF highlightBoundBox(Bounds.X, Bounds.Y, EntryDimensions.x,\n                            EntryDimensions.y);\n    Renderer->DrawQuad(highlightBoundBox, HighlightTint);\n    Renderer->DrawQuad(\n        RectF(highlightBoundBox.X + 2.0f, highlightBoundBox.Y + 2.0f,\n              highlightBoundBox.Width - 4.0f, highlightBoundBox.Height - 4.0f),\n        glm::vec4(1.0f, 1.0f, 1.0f, Tint.a));\n\n    Renderer->DrawSprite(PointerSprite, Bounds.GetPos() + PointerOffset, Tint);\n  }\n\n  Renderer->DrawSprite(LabelSprite, Bounds.GetPos() + LabelOffset,\n                       Selected ? Tint : glm::vec4(0.0f, 0.0f, 0.0f, Tint.a));\n}\n\nvoid OptionsEntry::Update(float dt) {\n  Widget::Update(dt);\n\n  EntryButton.Update(dt);\n}\n\nvoid OptionsEntry::UpdateInput(float dt) {\n  const bool wasHovered = EntryButton.Hovered;\n  EntryButton.UpdateInput(dt);\n  if (!HasFocus && !wasHovered && EntryButton.Hovered) {\n    Audio::PlayInGroup(Audio::ACG_SE, \"sysse\", 1, false, 0.0f);\n    Highlight(this);\n  }\n\n  if (!HasFocus) return;\n\n  if (PADinputButtonWentDown & PAD1A) {\n    Selected = !Selected;\n    Audio::PlayInGroup(Audio::ACG_SE, \"sysse\", 2, false, 0.0f);\n    return;\n  }\n\n  if (Selected && PADinputButtonWentDown & PAD1B) {\n    Selected = false;\n    Audio::PlayInGroup(Audio::ACG_SE, \"sysse\", 3, false, 0.0f);\n    return;\n  }\n}\n\nvoid OptionsEntry::Show() { EntryButton.Show(); }\n\nvoid OptionsEntry::Hide() {\n  Widget::Hide();\n  EntryButton.Hide();\n  Selected = false;\n}\n\nvoid OptionsEntry::Move(glm::vec2 relativePos) {\n  Widget::Move(relativePos);\n  EntryButton.Move(relativePos);\n}\n\nvoid OptionsEntry::EntryButtonOnClick(ClickArea* target) {\n  if (Selected) return;\n\n  Audio::PlayInGroup(Audio::ACG_SE, \"sysse\", 2, false, 0.0f);\n  Select(this);\n}\n\n}  // namespace CCLCC\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/widgets/cclcc/optionsentry.h",
    "content": "#pragma once\n\n#include <functional>\n\n#include \"../clickarea.h\"\n#include \"../../widget.h\"\n#include \"../../../spritesheet.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\nnamespace CCLCC {\n\nclass OptionsEntry : public Widget {\n public:\n  OptionsEntry(const Sprite& label, glm::vec2 pos, glm::vec4 highlightTint,\n               std::function<void(OptionsEntry*)> select,\n               std::function<void(Widget*)> highlight);\n\n  void Render() override;\n  void Update(float dt) override;\n  void UpdateInput(float dt) override;\n\n  void Show() override;\n  void Hide() override;\n\n  using Widget::Move;\n  void Move(glm::vec2 relativePos) override;\n\n  bool Selected = false;\n\n protected:\n  ClickArea EntryButton;\n  std::function<void(OptionsEntry*)> Select;\n  std::function<void(OptionsEntry*)> Highlight;\n  void EntryButtonOnClick(ClickArea* target);\n\n  const Sprite& LabelSprite;\n  glm::vec4 HighlightTint;\n};\n\n}  // namespace CCLCC\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/widgets/cclcc/optionsslider.cpp",
    "content": "#include \"optionsslider.h\"\n\n#include \"../../../profile/games/cclcc/optionsmenu.h\"\n#include \"../../../renderer/renderer.h\"\n#include \"../../../vm/interface/input.h\"\n\nusing namespace Impacto::Profile::CCLCC::OptionsMenu;\nusing namespace Impacto::Vm::Interface;\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\nnamespace CCLCC {\n\nOptionsSlider::OptionsSlider(float& value, float min, float max,\n                             const Sprite& box, const Sprite& label,\n                             glm::vec2 pos, glm::vec4 highlightTint,\n                             float sliderSpeed,\n                             std::function<void(OptionsEntry*)> select,\n                             std::function<void(Widget*)> highlight)\n    : OptionsEntry(label, pos, highlightTint, select, highlight),\n      BoxSprite(box),\n      Slider(0, pos + SliderTrackOffset, min, max, &value, SBDIR_HORIZONTAL,\n             glm::vec2(BoxSprite.ScaledWidth(), BoxSprite.ScaledHeight())),\n      Progress(value) {\n  Bounds.Width = SliderTrackOffset.x + BoxSprite.ScaledWidth();\n  EntryButton.Bounds.Width = Bounds.Width;\n}\n\nOptionsSlider::OptionsSlider(float& value, float min, float max,\n                             const Sprite& box, const Sprite& label,\n                             glm::vec2 pos, glm::vec4 highlightTint,\n                             RectF sliderBounds, float sliderSpeed,\n                             std::function<void(OptionsEntry*)> select,\n                             std::function<void(Widget*)> highlight)\n    : OptionsEntry(label, pos, highlightTint, select, highlight),\n      BoxSprite(box),\n      Slider(0, sliderBounds.GetPos(), min, max, &value, SBDIR_HORIZONTAL,\n             sliderBounds.GetSize()),\n      Progress(value) {}\n\nvoid OptionsSlider::Render() {\n  OptionsEntry::Render();\n\n  RectF highlightBounds(Bounds.X + SliderTrackOffset.x,\n                        Bounds.Y + SliderTrackOffset.y,\n                        Slider.GetNormalizedValue() * BoxSprite.ScaledWidth(),\n                        BoxSprite.ScaledHeight());\n  Renderer->DrawQuad(highlightBounds, HighlightTint);\n\n  Renderer->DrawSprite(BoxSprite, Bounds.GetPos() + SliderTrackOffset, Tint);\n}\n\nvoid OptionsSlider::Update(float dt) {\n  OptionsEntry::Update(dt);\n\n  Slider.Update(dt);\n}\n\nvoid OptionsSlider::UpdateInput(float dt) {\n  OptionsEntry::UpdateInput(dt);\n\n  Slider.HasFocus = Selected;\n  Slider.UpdateInput(dt);\n  Slider.ClampValue();\n}\n\nvoid OptionsSlider::Move(glm::vec2 relativePos) {\n  OptionsEntry::Move(relativePos);\n  Slider.Move(relativePos);\n}\n\n}  // namespace CCLCC\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/widgets/cclcc/optionsslider.h",
    "content": "#pragma once\n\n#include \"./optionsentry.h\"\n#include \"../scrollbar.h\"\n#include \"../../../spritesheet.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\nnamespace CCLCC {\n\nclass OptionsSlider : public OptionsEntry {\n public:\n  OptionsSlider(float& value, float min, float max, const Sprite& box,\n                const Sprite& label, glm::vec2 pos, glm::vec4 highlightTint,\n                float sliderSpeed, std::function<void(OptionsEntry*)> select,\n                std::function<void(Widget*)> highlight);\n\n  void Render() override;\n  void Update(float dt) override;\n  void UpdateInput(float dt) override;\n\n  using Widget::Move;\n  void Move(glm::vec2 relativePos) override;\n\n protected:\n  OptionsSlider(float& value, float min, float max, const Sprite& box,\n                const Sprite& label, glm::vec2 pos, glm::vec4 highlightTint,\n                RectF sliderBounds, float sliderSpeed,\n                std::function<void(OptionsEntry*)> select,\n                std::function<void(Widget*)> highlight);\n\n  const Sprite& BoxSprite;\n\n  Scrollbar Slider;\n  float& Progress;\n};\n\n}  // namespace CCLCC\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/widgets/cclcc/optionsvoiceslider.cpp",
    "content": "#include \"optionsvoiceslider.h\"\n\n#include \"../../../profile/games/cclcc/optionsmenu.h\"\n#include \"../../../renderer/renderer.h\"\n#include \"../../../vm/interface/input.h\"\n#include \"../../../audio/audiosystem.h\"\n\nusing namespace Impacto::Profile::CCLCC::OptionsMenu;\nusing namespace Impacto::Vm::Interface;\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\nnamespace CCLCC {\n\nOptionsVoiceSlider::OptionsVoiceSlider(\n    float& volume, float min, float max, bool& muted, const Sprite& box,\n    const Sprite& label, const Sprite& portrait, const Sprite& mutedPortrait,\n    glm::vec2 pos, glm::vec4 highlightTint, float sliderSpeed,\n    std::function<void(OptionsEntry*)> select,\n    std::function<void(Widget*)> highlight)\n    : OptionsSlider(\n          volume, min, max, box, label, pos, highlightTint,\n          RectF(pos.x + VoiceSliderOffset.x, pos.y + VoiceSliderOffset.y,\n                box.ScaledWidth(), box.ScaledHeight()),\n          sliderSpeed, select, highlight),\n      Portrait(portrait),\n      MutedPortrait(mutedPortrait),\n      Muted(muted) {\n  Bounds =\n      RectF(Bounds.X, Bounds.Y, VoiceEntryDimensions.x, VoiceEntryDimensions.y);\n  EntryButton.Bounds = Bounds;\n\n  std::function<void(ClickArea*)> onClick = [this](auto* btn) {\n    return MuteButtonOnClick(btn);\n  };\n  const RectF muteButtonBounds(Bounds.GetPos().x + PortraitOffset.x,\n                               Bounds.GetPos().y + PortraitOffset.y,\n                               portrait.ScaledWidth(), portrait.ScaledHeight());\n  MuteButton = ClickArea(0, muteButtonBounds, onClick);\n}\n\nvoid OptionsVoiceSlider::Render() {\n  HighlightTint.a = Tint.a;\n\n  if (HasFocus) {\n    RectF highlightBoundBox(Bounds.X, Bounds.Y, VoiceEntryDimensions.x,\n                            VoiceEntryDimensions.y);\n    Renderer->DrawQuad(highlightBoundBox, HighlightTint);\n    Renderer->DrawQuad(\n        RectF(highlightBoundBox.X + 2.0f, highlightBoundBox.Y + 2.0f,\n              highlightBoundBox.Width - 4.0f, highlightBoundBox.Height - 4.0f),\n        glm::vec4(1.0f, 1.0f, 1.0f, Tint.a));\n  }\n\n  Renderer->DrawSprite(Muted ? MutedPortrait : Portrait,\n                       Bounds.GetPos() + PortraitOffset, Tint);\n  Renderer->DrawSprite(LabelSprite, Bounds.GetPos() + NametagOffset,\n                       Selected ? Tint : glm::vec4(0.0f, 0.0f, 0.0f, Tint.a));\n\n  RectF highlightBounds(Bounds.X + VoiceSliderOffset.x,\n                        Bounds.Y + VoiceSliderOffset.y,\n                        Slider.GetNormalizedValue() * BoxSprite.ScaledWidth(),\n                        BoxSprite.ScaledHeight());\n  Renderer->DrawQuad(highlightBounds, HighlightTint);\n  Renderer->DrawSprite(BoxSprite, Bounds.GetPos() + VoiceSliderOffset, Tint);\n}\n\nvoid OptionsVoiceSlider::Update(float dt) {\n  OptionsSlider::Update(dt);\n\n  MuteButton.Update(dt);\n}\n\nvoid OptionsVoiceSlider::UpdateInput(float dt) {\n  OptionsSlider::UpdateInput(dt);\n  MuteButton.UpdateInput(dt);\n\n  if (!HasFocus) return;\n\n  if (PADinputButtonWentDown & PAD1Y) {\n    Muted = !Muted;\n    Audio::PlayInGroup(Audio::ACG_SE, \"sysse\", 2, false, 0.0f);\n  }\n}\n\nvoid OptionsVoiceSlider::Show() {\n  OptionsSlider::Show();\n  MuteButton.Show();\n}\n\nvoid OptionsVoiceSlider::Hide() {\n  OptionsSlider::Hide();\n  MuteButton.Hide();\n}\n\nvoid OptionsVoiceSlider::Move(glm::vec2 relativePos) {\n  OptionsSlider::Move(relativePos);\n  MuteButton.Move(relativePos);\n}\n\nvoid OptionsVoiceSlider::MuteButtonOnClick(ClickArea* target) {\n  if (HasFocus) Audio::PlayInGroup(Audio::ACG_SE, \"sysse\", 2, false, 0.0f);\n\n  Muted = !Muted;\n}\n\n}  // namespace CCLCC\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/widgets/cclcc/optionsvoiceslider.h",
    "content": "#pragma once\n\n#include \"./optionsslider.h\"\n#include \"../../../spritesheet.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\nnamespace CCLCC {\n\nclass OptionsVoiceSlider : public OptionsSlider {\n public:\n  OptionsVoiceSlider(float& volume, float min, float max, bool& muted,\n                     const Sprite& box, const Sprite& label,\n                     const Sprite& portrait, const Sprite& mutedPortrait,\n                     glm::vec2 pos, glm::vec4 highlightTint, float sliderSpeed,\n                     std::function<void(OptionsEntry*)> select,\n                     std::function<void(Widget*)> highlight);\n  void Render() override;\n  void Update(float dt) override;\n  void UpdateInput(float dt) override;\n\n  void Show() override;\n  void Hide() override;\n\n  using Widget::Move;\n  void Move(glm::vec2 relativePos) override;\n\n private:\n  const Sprite& Portrait;\n  const Sprite& MutedPortrait;\n\n  bool& Muted;\n  ClickArea MuteButton;\n  void MuteButtonOnClick(ClickArea* target);\n};\n\n}  // namespace CCLCC\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/widgets/cclcc/saveentrybutton.cpp",
    "content": "#include \"saveentrybutton.h\"\n\n#include \"../../../renderer/renderer.h\"\n#include \"../../../profile/dialogue.h\"\n#include \"../../../profile/games/cclcc/savemenu.h\"\n#include \"../../../vm/vm.h\"\n#include \"../../../ui/ui.h\"\n#include \"../../../profile/game.h\"\n#include <fmt/format.h>\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\nnamespace CCLCC {\nusing namespace Impacto::Profile::CCLCC::SaveMenu;\nusing namespace Impacto::Profile::ScriptVars;\nusing namespace Impacto::SaveSystem;\n\nSaveEntryButton::SaveEntryButton(int id, Sprite const& focusedBox,\n                                 Sprite const& focusedText, int page,\n                                 glm::vec2 pos, Sprite lockedSymbol,\n                                 SaveSystem::SaveType saveType,\n                                 Sprite noDataSprite, Sprite brokenDataSprite)\n    : Widgets::Button(\n          id,\n          Sprite(SpriteSheet(), focusedBox.Bounds.X, focusedBox.Bounds.Y,\n                 focusedBox.Bounds.Width, focusedBox.Bounds.Height),\n          Sprite(SpriteSheet(), 0, 0, 0, 0), focusedBox, pos),\n      Page(page),\n      Type(saveType),\n      FocusedSpriteLabel(focusedText, glm::vec2{pos.x, pos.y - 34}),\n      LockedSymbol(lockedSymbol,\n                   glm::vec2(Bounds.X, Bounds.Y) + SlotLockedSpritePosition),\n      NoDataSymbol(noDataSprite,\n                   glm::vec2(Bounds.X, Bounds.Y) + NoDataSpritePosition),\n      BrokenDataSymbol(brokenDataSprite,\n                       glm::vec2(Bounds.X, Bounds.Y) +\n                           glm::vec2(211.0f, 20.0f + 1.0f - 12.0f)) {\n  glm::vec2 relativeThumbnailPosition = {20, 20};\n  Thumbnail.Bounds.X = Bounds.X + relativeThumbnailPosition.x;\n  Thumbnail.Bounds.Y = Bounds.Y + relativeThumbnailPosition.y;\n\n  glm::vec2 relativeTitlePosition = {310, 39};\n  CharacterRouteLabel.Bounds.X = Bounds.X + relativeTitlePosition.x;\n  CharacterRouteLabel.Bounds.Y = Bounds.Y + relativeTitlePosition.y;\n\n  SceneTitleLabel.Bounds.X = Bounds.X + relativeTitlePosition.x;\n  SceneTitleLabel.Bounds.Y = Bounds.Y + relativeTitlePosition.y + 30;\n\n  glm::vec2 relativeTimePosition{315, 147};\n  SaveDateLabel.Bounds.X = Bounds.X + relativeTimePosition.x;\n  SaveDateLabel.Bounds.Y = Bounds.Y + relativeTimePosition.y;\n\n  SaveEntryButton::Update(0);\n}\n\nvoid SaveEntryButton::Render() {\n  const glm::vec2 scale = {Bounds.Width / HighlightSprite.ScaledWidth(), 1.0f};\n\n  NormalSpriteLabel.Tint = Tint;\n  NormalSpriteLabel.Render();\n  if (HasFocus) {\n    FocusedSpriteLabel.Tint = Tint;\n    FocusedSpriteLabel.Render();\n\n    const RectF highlightDest =\n        HighlightSprite.ScaledBounds()\n            .Scale(scale, {0.0f, 0.0f})\n            .Translate(Bounds.GetPos() + HighlightOffset);\n    Renderer->DrawSprite(HighlightSprite, highlightDest, Tint);\n  }\n\n  const RectF numberDigitDest =\n      NumberDigitSprite[*UI::SaveMenuPtr->ActiveMenuType][0]\n          .ScaledBounds()\n          .Scale(scale, {0.0f, 0.0f})\n          .Translate(Bounds.GetPos());\n  Renderer->DrawSprite(\n      NumberDigitSprite[*UI::SaveMenuPtr->ActiveMenuType][(Id + 1) / 10],\n      RectF(numberDigitDest).Translate({720, 120}), Tint);\n  Renderer->DrawSprite(\n      NumberDigitSprite[*UI::SaveMenuPtr->ActiveMenuType][(Id + 1) % 10],\n      RectF(numberDigitDest).Translate({752, 120}), Tint);\n\n  if (SaveStatus == 1) {\n    const RectF separationLineDest =\n        SeparationLineSprite[*UI::SaveMenuPtr->ActiveMenuType]\n            .ScaledBounds()\n            .Scale(scale, {0.0f, 0.0f})\n            .Translate(Bounds.GetPos() + glm::vec2(308, 112));\n    Renderer->DrawSprite(SeparationLineSprite[*UI::SaveMenuPtr->ActiveMenuType],\n                         separationLineDest, Tint);\n\n    if (IsLocked) {\n      LockedSymbol.Tint = Tint;\n      LockedSymbol.Render();\n    }\n\n    Thumbnail.Bounds.Width = 270;\n    Thumbnail.Bounds.Height = 152;\n    Thumbnail.Tint = Tint;\n    Thumbnail.Render();\n\n    CharacterRouteLabel.Tint = Tint;\n    CharacterRouteLabel.Render();\n    SceneTitleLabel.Tint = Tint;\n    SceneTitleLabel.Render();\n    SaveDateLabel.Tint = Tint;\n    SaveDateLabel.Render();\n\n  } else if (SaveStatus == 0) {\n    NoDataSymbol.Tint = Tint;\n    NoDataSymbol.Render();\n\n  } else {\n    BrokenDataSymbol.Tint = Tint;\n    BrokenDataSymbol.Render();\n  }\n}\n\nint SaveEntryButton::GetPage() const { return Page; }\n\nvoid SaveEntryButton::Move(glm::vec2 relativePosition) {\n  Button::Move(relativePosition);\n  NormalSpriteLabel.Move(relativePosition);\n  FocusedSpriteLabel.Move(relativePosition);\n  LockedSymbol.Move(relativePosition);\n  CharacterRouteLabel.Move(relativePosition);\n  SceneTitleLabel.Move(relativePosition);\n  SaveDateLabel.Move(relativePosition);\n  Thumbnail.Move(relativePosition);\n  NoDataSymbol.Move(relativePosition);\n  BrokenDataSymbol.Move(relativePosition);\n}\n\nvoid SaveEntryButton::RefreshCharacterRouteText(int strIndex) {\n  auto strAddr = Vm::ScriptGetTextTableStrAddress(1, strIndex);\n  float fontSize = 28;\n  RendererOutlineMode outlineMode = RendererOutlineMode::Full;\n  DialogueColorPair colorPair =\n      *UI::SaveMenuPtr->ActiveMenuType == SaveMenuPageType::Save\n          ? DialogueColorPair{SaveEntryPrimaryColor, SaveEntryPrimaryColor}\n          : DialogueColorPair{LoadEntryPrimaryColor, LoadEntryPrimaryColor};\n  CharacterRouteLabel.SetText(strAddr, fontSize, outlineMode, colorPair);\n}\n\nvoid SaveEntryButton::RefreshSceneTitleText(int strIndex) {\n  auto strAddr = Vm::ScriptGetTextTableStrAddress(1, strIndex + 1);\n  float fontSize = 28;\n  RendererOutlineMode outlineMode = RendererOutlineMode::Full;\n  SceneTitleLabel.SetText(strAddr, fontSize, outlineMode,\n                          {SaveEntrySecondaryColor, SaveEntrySecondaryColor});\n}\n\nvoid SaveEntryButton::RefreshSaveDateText() {\n  tm const& date = SaveSystem::GetSaveDate(Type, Id);\n  float fontSize = 32;\n  RendererOutlineMode outlineMode = RendererOutlineMode::Full;\n  // Maybe fmt will merge my PR for space padded month\n  SaveDateLabel.SetText(\n      fmt::format(fmt::runtime(Profile::DateFormat.FormattedString()), date) +\n          fmt::format(\" {:%H:%M:%S}\", date),\n      fontSize, outlineMode,\n      {SaveEntrySecondaryColor, SaveEntrySecondaryColor});\n}\n\nvoid SaveEntryButton::ToggleLock() {\n  const uint8_t newLock = SaveSystem::GetSaveFlags(Type, Id) ^ WriteProtect;\n  SaveSystem::SetSaveFlags(Type, Id, newLock);\n  IsLocked = newLock & WriteProtect;\n}\n\nvoid SaveEntryButton::RefreshInfo() {\n  SaveStatus = SaveSystem::GetSaveStatus(Type, Id);\n  IsLocked = SaveSystem::GetSaveFlags(Type, Id) & WriteProtect;\n\n  if (SaveStatus == 1) {\n    auto strIndex = SaveSystem::GetSaveTitle(Type, Id);\n    if (strIndex > 21) {\n      strIndex = 0;\n    }\n    RefreshCharacterRouteText(strIndex * 2);\n    RefreshSceneTitleText(strIndex * 2);\n    RefreshSaveDateText();\n    Thumbnail.SetSprite(SaveSystem::GetSaveThumbnail(Type, Id));\n  }\n}\n}  // namespace CCLCC\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto\n"
  },
  {
    "path": "src/ui/widgets/cclcc/saveentrybutton.h",
    "content": "#pragma once\n\n#include <string>\n\n#include \"../button.h\"\n#include \"../label.h\"\n#include \"../../../renderer/renderer.h\"\n#include \"../../../data/savesystem.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\nnamespace CCLCC {\n\nclass SaveEntryButton : public Widgets::Button {\n public:\n  SaveEntryButton(int id, Sprite const& focusedBox, Sprite const& focusedText,\n                  int page, glm::vec2 pos, Sprite lockedSymbol,\n                  SaveSystem::SaveType saveType, Sprite noDataSprite,\n                  Sprite brokenDataSprite);\n\n  void Render() override;\n\n  using Widget::Move;\n  void Move(glm::vec2 pos) override;\n\n  int GetPage() const;\n\n  void AddThumbnail(Sprite thumbnail, glm::vec2 pos);\n  void RefreshInfo();\n  void ToggleLock();\n\n  void AddNormalSpriteLabel(Sprite norm, glm::vec2 pos);\n  void RefreshSaveDateText();\n  void RefreshSceneTitleText(int strIndex);\n  void RefreshCharacterRouteText(int strIndex);\n\n private:\n  int Page;\n  int SaveStatus;\n  SaveSystem::SaveType Type;\n\n  Label NormalSpriteLabel;\n  Label FocusedSpriteLabel;\n  Label LockedSymbol;\n  Label Thumbnail;\n  Label NoDataSymbol;\n  Label BrokenDataSymbol;\n  Label SaveDateLabel;\n  Label SceneTitleLabel;\n  Label CharacterRouteLabel;\n};\n\n}  // namespace CCLCC\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/widgets/cclcc/sysmenubutton.cpp",
    "content": "#include \"sysmenubutton.h\"\n\n#include \"../../../renderer/renderer.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\nnamespace CCLCC {\n\nvoid SysMenuButton::Render() {\n  glm::vec4 tint = IsLocked ? glm::vec4(0.5f) : glm::vec4(1.0f);\n  tint.a = Tint.a;\n\n  if (HasFocus) {\n    Renderer->DrawSprite(HighlightSprite, RenderPos, tint);\n  } else {\n    if (Enabled) {\n      Renderer->DrawSprite(NormalSprite, RenderPos, tint);\n    } else {\n      Renderer->DrawSprite(DisabledSprite, RenderPos, Tint);\n    }\n  }\n}\n\n}  // namespace CCLCC\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/widgets/cclcc/sysmenubutton.h",
    "content": "#pragma once\n\n#include \"../button.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\nnamespace CCLCC {\n\nclass SysMenuButton : public Widgets::Button {\n private:\n public:\n  SysMenuButton(int id, Sprite const& norm, Sprite const& focused,\n                Sprite const& highlight, glm::vec2 pos, RectF buttonBounds)\n      : Widgets::Button(id, norm, focused, highlight, pos, buttonBounds),\n        RenderPos(pos) {}\n  void Render() override;\n  glm::vec2 RenderPos;\n};\n\n}  // namespace CCLCC\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/widgets/cclcc/tipsentrybutton.cpp",
    "content": "#include \"tipsentrybutton.h\"\n\n#include \"../../../renderer/renderer.h\"\n#include \"../../../profile/dialogue.h\"\n#include \"../../../profile/games/cclcc/tipsmenu.h\"\n#include \"../../../text/text.h\"\n#include \"../../../vm/vm.h\"\n#include \"../../../inputsystem.h\"\n#include \"../../../vm/interface/input.h\"\n#include \"../../../audio/audiosystem.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\nnamespace CCLCC {\n\nusing namespace Impacto::TipsSystem;\n\nusing namespace Impacto::Profile::CCLCC::TipsMenu;\n\nTipsEntryButton::TipsEntryButton(int tipId, int dispId, RectF const& dest,\n                                 Sprite const& highlight, bool isNew)\n    : Button(dispId, Sprite(), highlight, highlight, {dest.X, dest.Y}),\n      IsNewState(isNew) {\n  Id = dispId;\n  TipEntryRecord = TipsSystem::GetTipRecord(tipId);\n  Enabled = true;\n  HighlightOffset = {0, 0};\n  PrevUnreadState = TipEntryRecord->IsUnread && !TipEntryRecord->IsLocked;\n  TextLayoutPlainString(fmt::format(\"{:03d}.\", dispId), TipNumber,\n                        Profile::Dialogue::DialogueFont,\n                        (float)TipsEntryNumberFontSize,\n                        {TipsMenuDarkTextColor, 0}, 1.0f,\n                        glm::vec2(Bounds.X, Bounds.Y) + TipsEntryNumberOffset,\n                        TextAlignment::Left);\n  Vm::Sc3VmThread dummy;\n  dummy.ScriptBufferId = TipsSystem::GetTipsScriptBufferId();\n  glm::vec2 nameDest = glm::vec2(Bounds.X, Bounds.Y) + TipsEntryNameOffset;\n  dummy.IpOffset = TipEntryRecord->StringAdr[1];\n  HasText = true;\n\n  uint32_t initColorName =\n      PrevUnreadState ? TipsEntryNameUnreadColor : TipsMenuDarkTextColor;\n  Text = TextLayoutPlainLine(&dummy, 255, Profile::Dialogue::DialogueFont,\n                             (float)TipsEntryNameFontSize, {initColorName, 0},\n                             1.0f, nameDest, TextAlignment::Left);\n\n  dummy.IpOffset = Vm::ScriptGetStrAddress(TipsSystem::GetTipsScriptBufferId(),\n                                           TipsTextEntryLockedIndex);\n  TextLayoutPlainLine(&dummy, static_cast<int>(TipLockedText.size()),\n                      TipLockedText, Profile::Dialogue::DialogueFont,\n                      (float)TipsEntryNameFontSize, {initColorName, 0}, 1.0f,\n                      nameDest, TextAlignment::Left);\n  HighlightOffset = TipsEntryHighlightOffset;\n  Bounds = dest;\n  HoverBounds = dest;\n}\n\nvoid TipsEntryButton::Update(float dt) {\n  Button::Update(dt);\n  bool curUnreadState = TipEntryRecord->IsUnread && !TipEntryRecord->IsLocked;\n  if (PrevUnreadState != curUnreadState) {\n    uint32_t colorName =\n        curUnreadState ? TipsEntryNameUnreadColor : TipsMenuDarkTextColor;\n    for (ProcessedTextGlyph& glyph : Text) {\n      glyph.Colors = {colorName, 0};\n    }\n    PrevUnreadState = curUnreadState;\n  }\n  if (!PrevFocusState && HasFocus)\n    Audio::PlayInGroup(Audio::ACG_SE, \"sysse\", 1, false, 0);\n  if (PrevFocusState != HasFocus) {\n    PrevFocusState = HasFocus;\n  }\n}\n\nvoid TipsEntryButton::UpdateInput(float dt) {\n  if (TipsTabBounds.Intersects(Bounds) || TipsTabBounds.Contains(Bounds)) {\n    Button::UpdateInput(dt);\n  } else {\n    Hovered = false;\n  }\n}\n\nvoid TipsEntryButton::Render() {\n  if (HasFocus) {\n    const RectF dest =\n        HighlightSprite.ScaledBounds()\n            .Scale({Bounds.Width / HighlightSprite.ScaledWidth(), 1.0f},\n                   {0.0f, 0.0f})\n            .Translate(Bounds.GetPos() + HighlightOffset);\n    Renderer->DrawSprite(HighlightSprite, dest, Tint);\n  }\n\n  Renderer->DrawProcessedText(TipNumber, Profile::Dialogue::DialogueFont,\n                              Tint.a, RendererOutlineMode::None);\n  if (TipEntryRecord->IsLocked) {\n    Renderer->DrawProcessedText(TipLockedText, Profile::Dialogue::DialogueFont,\n                                Tint.a, RendererOutlineMode::None);\n  } else {\n    Renderer->DrawProcessedText(Text, Profile::Dialogue::DialogueFont, Tint.a,\n                                RendererOutlineMode::None);\n    if (IsNewState) {\n      Renderer->DrawSprite(TipsNewSprite,\n                           glm::vec2{Bounds.X, Bounds.Y} + TipEntryNewOffset,\n                           Tint);\n    }\n  }\n}\n\nvoid TipsEntryButton::Move(glm::vec2 relativePos) {\n  Button::Move(relativePos);\n  for (size_t i = 0; i < TipNumber.size(); i++) {\n    TipNumber[i].DestRect.X += relativePos.x;\n    TipNumber[i].DestRect.Y += relativePos.y;\n  }\n  for (size_t i = 0; i < TipLockedText.size(); i++) {\n    TipLockedText[i].DestRect.X += relativePos.x;\n    TipLockedText[i].DestRect.Y += relativePos.y;\n  }\n}\n\n}  // namespace CCLCC\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/widgets/cclcc/tipsentrybutton.h",
    "content": "#pragma once\n\n#include \"../button.h\"\n#include \"../../../data/tipssystem.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\nnamespace CCLCC {\n\nint constexpr TipLockedTextLength = 5;\n\nclass TipsEntryButton : public Widgets::Button {\n public:\n  TipsEntryButton(int tipId, int dispId, RectF const& dest,\n                  Sprite const& highlight, bool isNew);\n  void Update(float dt) override;\n  void UpdateInput(float dt) override;\n  void Render() override;\n\n  using Widget::Move;\n  void Move(glm::vec2 pos) override;\n\n  TipsSystem::TipsDataRecord const* TipEntryRecord;\n  bool PrevFocusState = false;\n\n private:\n  std::array<ProcessedTextGlyph, 4> TipNumber;\n  std::array<ProcessedTextGlyph, 5> TipLockedText;\n  bool PrevUnreadState;\n  bool IsNewState;\n};\n\n}  // namespace CCLCC\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/widgets/cclcc/tipstabgroup.cpp",
    "content": "#include \"tipstabgroup.h\"\n\n#include \"../../../renderer/renderer.h\"\n#include \"../../../profile/dialogue.h\"\n#include \"../../../profile/ui/tipsmenu.h\"\n#include \"../../../profile/games/cclcc/tipsmenu.h\"\n#include \"../../../inputsystem.h\"\n#include \"../../../audio/audiosystem.h\"\n#include \"../../../vm/interface/input.h\"\n#include \"../../../ui/ui.h\"\n#include \"../../../text/text.h\"\n#include \"../../../vm/vm.h\"\n#include \"../../../profile/game.h\"\n#include <cmath>\n#include <limits>\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\nnamespace CCLCC {\n\nusing namespace Impacto::TipsSystem;\nusing namespace Impacto::Profile::CCLCC::TipsMenu;\nusing namespace Impacto::UI::CCLCC;\n\nTipsTabButton::TipsTabButton(\n    TipsTabType type, std::function<void(Widgets::Button*)> onClickHandler)\n\n    : Button(\n          type, TipsHighlightedTabSprite, Sprite(), Sprite(),\n          TipsTabNameDisplay + glm::vec2(type * TipsHighlightedTabAdder, 0)) {\n  OnClickHandler = std::move(onClickHandler);\n  NormalSprite.Bounds.X += type * TipsHighlightedTabAdder;\n}\n\nvoid TipsTabButton::Reset() {\n  Bounds.X = TipsTabNameDisplay.x + Id * TipsHighlightedTabAdder;\n  Bounds.Y = TipsTabNameDisplay.y;\n}\n\nvoid TipsTabButton::UpdateInput(float dt) {\n  if (Enabled) {\n    if (Input::CurrentInputDevice == Input::Device::Mouse &&\n        Input::PrevMousePos != Input::CurMousePos) {\n      Hovered = Bounds.ContainsPoint(Input::CurMousePos);\n    } else if (Input::CurrentInputDevice == Input::Device::Touch &&\n               Input::TouchIsDown[0] &&\n               Input::PrevTouchPos != Input::CurTouchPos) {\n      Hovered = Bounds.ContainsPoint(Input::CurTouchPos);\n    }\n    if (OnClickHandler && HasFocus &&\n        ((Hovered &&\n          Vm::Interface::PADinputMouseWentDown & Vm::Interface::PAD1A))) {\n      OnClickHandler(this);\n    }\n  }\n}\n\nTipsTabGroup::TipsTabGroup(\n    TipsTabType type, std::function<void(Widgets::Button*)> tabClickHandler,\n    std::function<void(Widgets::Button*)> tipClickHandler)\n    : TipsEntriesGroup(this),\n      TabName(type, tabClickHandler),\n      Type(type),\n      TipClickHandler(tipClickHandler) {\n  TipsEntriesGroup.WrapFocus = true;\n\n  TipsScrollStartPos = {TipsScrollEntriesX, TipsScrollYStart};\n\n  TipsScrollTrackBounds = {TipsScrollThumbSprite.Bounds.Width,\n                           TipsScrollYEnd - TipsScrollYStart};\n  EntriesPerPage =\n      (int)std::ceil(TipsTabBounds.Height / TipsEntryBounds.Height);\n}\n\nvoid TipsTabGroup::UpdatePageInput(float dt) {\n  using namespace Vm::Interface;\n  if (IsFocused) {\n    auto prevEntry = CurrentlyFocusedElement;\n    TipsEntriesScrollbar->UpdateInput(dt);\n    if ((PADinputMouseWentDown & PAD1A) &&\n        TipsEntriesScrollbar->IsScrollHeld()) {\n      Audio::PlayInGroup(Audio::ACG_SE, \"sysse\", 2, false, 0);\n    }\n\n    auto checkScrollBounds = [&]() {\n      return !TipsTabBounds.Contains(CurrentlyFocusedElement->Bounds);\n    };\n\n    const uint32_t btnUp = PADcustom[28];\n    const uint32_t btnDown = PADcustom[29];\n    const uint32_t btnLeft = PADcustom[30];\n    const uint32_t btnRight = PADcustom[31];\n\n    const uint32_t directionShouldFire =\n        PADinputButtonRepeatDown & (btnUp | btnDown);\n    const bool pageButtonDown = PADinputButtonIsDown & (btnLeft | btnRight);\n    const bool directionMovement =\n        !pageButtonDown && ((bool)(directionShouldFire & btnUp) ^\n                            (bool)(directionShouldFire & btnDown));\n\n    if (directionMovement) {\n      if (directionShouldFire & btnDown) {\n        AdvanceFocus(FDIR_DOWN);\n        if (CurrentlyFocusedElement != prevEntry && checkScrollBounds()) {\n          if (CurrentlyFocusedElement == TipsEntriesGroup.Children.front()) {\n            ScrollPosY = 0;\n          } else {\n            ScrollPosY += TipsEntryBounds.Height;\n          }\n        }\n      } else {\n        AdvanceFocus(FDIR_UP);\n        if (CurrentlyFocusedElement != prevEntry && checkScrollBounds()) {\n          if (CurrentlyFocusedElement == TipsEntriesGroup.Children.back()) {\n            ScrollPosY = TipsEntriesScrollbar->EndValue;\n          } else {\n            ScrollPosY -= TipsEntryBounds.Height;\n          }\n        }\n      }\n    }\n\n    const uint32_t pageUpDownShouldFire =\n        PADinputButtonRepeatDown & (btnLeft | btnRight);\n    const bool pageMovement = (bool)(pageUpDownShouldFire & btnRight) ^\n                              (bool)(pageUpDownShouldFire & btnLeft);\n\n    if (pageMovement) {\n      if (pageUpDownShouldFire & btnRight) {\n        AdvanceFocus(FDIR_RIGHT);\n        if (CurrentlyFocusedElement != prevEntry) {\n          if (checkScrollBounds())\n            ScrollPosY += TipsEntryBounds.Height * EntriesPerPage;\n        } else if (CurrentlyFocusedElement ==\n                   TipsEntriesGroup.Children.back()) {\n          CurrentlyFocusedElement->HasFocus = false;\n          CurrentlyFocusedElement = TipsEntriesGroup.Children.front();\n          CurrentlyFocusedElement->HasFocus = true;\n          if (checkScrollBounds()) {\n            ScrollPosY = 0;\n          }\n        } else {\n          CurrentlyFocusedElement->HasFocus = false;\n          CurrentlyFocusedElement = TipsEntriesGroup.Children.back();\n          CurrentlyFocusedElement->HasFocus = true;\n          if (checkScrollBounds()) {\n            ScrollPosY = TipsEntriesScrollbar->EndValue;\n          }\n        }\n      } else {\n        AdvanceFocus(FDIR_LEFT);\n        if (CurrentlyFocusedElement != prevEntry) {\n          if (checkScrollBounds())\n            ScrollPosY -= TipsEntryBounds.Height * EntriesPerPage;\n        } else if (CurrentlyFocusedElement ==\n                   TipsEntriesGroup.Children.front()) {\n          CurrentlyFocusedElement->HasFocus = false;\n          CurrentlyFocusedElement = TipsEntriesGroup.Children.back();\n          CurrentlyFocusedElement->HasFocus = true;\n          if (checkScrollBounds()) {\n            ScrollPosY = TipsEntriesScrollbar->EndValue;\n          }\n        } else {\n          CurrentlyFocusedElement->HasFocus = false;\n          CurrentlyFocusedElement = TipsEntriesGroup.Children.front();\n          CurrentlyFocusedElement->HasFocus = true;\n          if (checkScrollBounds()) {\n            ScrollPosY = 0;\n          }\n        }\n      }\n    }\n  }\n}\n\nvoid TipsTabGroup::Update(float dt) {\n  TabName.Enabled = true;\n  TabName.Update(dt);\n  TabName.UpdateInput(dt);\n  if (State == Hidden) {\n    TipsEntriesGroup.Enabled = false;\n    // Inverting since we want buttons to be clickable when the tab is not\n    // shown\n    TabName.HasFocus = true;\n  } else {\n    TipsEntriesGroup.Enabled = true;\n    TabName.HasFocus = false;\n    TipsEntriesGroup.Update(dt);\n\n    float oldScrollPosY = ScrollPosY;\n    UpdatePageInput(dt);\n    if (TipsEntriesScrollbar) {\n      TipsEntriesScrollbar->Update(dt);\n      TipsEntriesScrollbar->UpdateInput(dt);\n      if (oldScrollPosY != ScrollPosY) {\n        float delta = oldScrollPosY - ScrollPosY;\n        // taken from CHLCC\n        if (std::fmod(std::abs(delta), TipsEntryBounds.Height) >\n            std::numeric_limits<float>::epsilon()) {\n          const float newDelta = std::round(delta / TipsEntryBounds.Height) *\n                                 TipsEntryBounds.Height;\n          ScrollPosY = oldScrollPosY - newDelta;\n          delta = newDelta;\n        }\n        TipsEntriesGroup.Move({0, delta});\n\n        if (TipsEntriesScrollbar->IsScrollHeld() && CurrentlyFocusedElement) {\n          // advance focus during drag\n          FocusDirection dir = (delta < 0) ? FDIR_DOWN : FDIR_UP;\n          int steps =\n              static_cast<int>(std::abs(delta) / TipsEntryBounds.Height);\n          for (int i = 0; i < steps; i++) {\n            AdvanceFocus(dir);\n          }\n        }\n      }\n\n      if (!TipsEntriesScrollbar->IsScrollHeld())\n        TipsEntriesGroup.UpdateInput(dt);\n\n      if (oldScrollPosY != ScrollPosY &&\n          Input::CurrentInputDevice == Input::Device::Mouse &&\n          (Input::MouseWheelDeltaY != 0 ||\n           TipsEntriesScrollbar->IsScrollHeld())) {\n        for (auto* entry : TipsEntryButtons) {\n          entry->PrevFocusState = entry->HasFocus;\n        }\n      }\n    }\n  }\n}\n\nvoid TipsTabGroup::Render() {\n  if (State == Hidden) return;\n\n  TabName.Tint = Tint;\n  TabName.Render();\n  TipsEntriesGroup.Tint = Tint;\n  TipsEntriesGroup.Render();\n  TipsEntriesScrollbar->Tint = Tint;\n  TipsEntriesScrollbar->Render();\n}\n\nvoid TipsTabGroup::UpdateTipsEntries(std::vector<int> const& SortedTipIds) {\n  auto tipsFilterPredicate = [&](TipsSystem::TipsDataRecord const& record) {\n    switch (Type) {\n      case TipsTabType::AllTips:\n        return true;\n      case TipsTabType::UnlockedTips:\n        return !record.IsLocked;\n      case TipsTabType::UnreadTips:\n        return record.IsUnread && !record.IsLocked;\n      case TipsTabType::NewTips:\n        return record.IsNew && !record.IsLocked;\n    }\n\n    throw std::runtime_error(fmt::format(\n        \"Tried to update unimplemented tips tab type {}\", (int)Type));\n  };\n\n  int sortIndex = 1;\n  TipsEntriesGroup.Clear();\n  TipsEntryButtons.clear();\n  for (auto& tipId : SortedTipIds) {\n    auto& record = *TipsSystem::GetTipRecord(tipId);\n    if (!tipsFilterPredicate(record)) {\n      sortIndex++;\n      continue;\n    }\n    bool dispNew = record.IsNew && !record.IsLocked;\n    RectF buttonBounds = TipsEntryBounds;\n    buttonBounds.Y += TipsEntryButtons.size() * buttonBounds.Height;\n    TipsEntryButton* button = new TipsEntryButton(\n        tipId, sortIndex++, buttonBounds, TipsHighlightedSprite, dispNew);\n    button->OnClickHandler = TipClickHandler;\n    TipsEntriesGroup.Add(button, FDIR_DOWN);\n    TipsEntryButtons.push_back(button);\n    if (Type == TipsTabType::NewTips) {\n      TipsSystem::SetTipNewState(tipId, false);\n    }\n  }\n\n  for (int i = 0;\n       i < static_cast<int>(TipsEntriesGroup.Children.size()) - EntriesPerPage;\n       i++) {\n    TipsEntriesGroup.Children[i]->SetFocus(\n        TipsEntriesGroup.Children[i + EntriesPerPage], FDIR_RIGHT);\n    TipsEntriesGroup.Children[i + EntriesPerPage]->SetFocus(\n        TipsEntriesGroup.Children[i], FDIR_LEFT);\n  }\n\n  auto roundUpMultiple = [](float numToRound, float multiple) {\n    return std::ceil(numToRound / multiple) * multiple;\n  };\n  int scrollDistance =\n      (int)(TipsEntryBounds.Height * TipsEntryButtons.size() -\n            roundUpMultiple(TipsTabBounds.Height, TipsEntryBounds.Height));\n\n  TipsEntriesScrollbar = std::make_unique<Scrollbar>(\n      0, TipsScrollStartPos, 0.0f, std::max(0.0f, (float)scrollDistance),\n      &ScrollPosY, SBDIR_VERTICAL, TipsScrollThumbSprite, TipsScrollTrackBounds,\n      TipsScrollThumbLength, TipsTabBounds);\n  TipsEntriesScrollbar->Step = TipsEntryBounds.Height;\n  TipsEntriesGroup.RenderingBounds = TipsTabBounds;\n  TipsEntriesGroup.HoverBounds = TipsTabBounds;\n  TabName.Reset();\n}\n\nvoid TipsTabGroup::Show() {\n  using namespace Vm::Interface;\n  if (State != Shown) {\n    State = Shown;\n    IsFocused = true;\n    TipsEntriesGroup.Show();\n    CurrentlyFocusedElement = TipsEntriesGroup.GetFocus(FDIR_DOWN);\n\n    ResetPADHoldTimer(PADcustom[28] | PADcustom[29] | PADcustom[30] |\n                      PADcustom[31]);\n\n    if (CurrentlyFocusedElement) {\n      static_cast<TipsEntryButton*>(CurrentlyFocusedElement)->PrevFocusState =\n          true;\n      CurrentlyFocusedElement->HasFocus = true;\n    }\n  }\n}\nvoid TipsTabGroup::Hide() {\n  if (State != Hidden) {\n    State = Hidden;\n    IsFocused = false;\n    TipsEntriesGroup.Move({0, ScrollPosY});\n    ScrollPosY = 0.0f;\n    TipsEntriesGroup.Hide();\n  }\n}\n\nvoid TipsTabGroup::Move(glm::vec2 relativePosition) {\n  TabName.Move(relativePosition);\n  TipsEntriesGroup.Move(relativePosition);\n  TipsEntriesScrollbar->Move(relativePosition);\n  TipsEntriesGroup.RenderingBounds.X += relativePosition.x;\n  TipsEntriesGroup.RenderingBounds.Y += relativePosition.y;\n}\n\nvoid TipsTabGroup::MoveTo(glm::vec2 pos) {\n  TabName.MoveTo(pos);\n  TipsEntriesGroup.MoveTo(pos);\n  TipsEntriesScrollbar->MoveTo(pos);\n  TipsEntriesGroup.RenderingBounds.X = pos.x;\n  TipsEntriesGroup.RenderingBounds.Y = pos.y;\n}\n\n}  // namespace CCLCC\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/widgets/cclcc/tipstabgroup.h",
    "content": "#pragma once\n\n#include \"../button.h\"\n#include \"../group.h\"\n#include \"../../../data/tipssystem.h\"\n#include \"../../../games/cclcc/tipsmenu.h\"\n#include \"tipsentrybutton.h\"\n#include <vector>\n#include <memory>\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\nnamespace CCLCC {\n\nclass TipsTabButton : public Widgets::Button {\n public:\n  TipsTabButton(Impacto::UI::CCLCC::TipsTabType type,\n                std::function<void(Widgets::Button*)> onClickHandler);\n  void UpdateInput(float dt) override;\n  void Reset();\n};\n\nclass TipsTabGroup : public Menu {\n public:\n  TipsTabGroup(Impacto::UI::CCLCC::TipsTabType type,\n               std::function<void(Widgets::Button*)> tabClickHandler,\n               std::function<void(Widgets::Button*)> tipClickHandler);\n\n  void Show() override;\n  void Hide() override;\n  void Update(float dt) override;\n  void UpdatePageInput(float dt);\n  void Render() override;\n  void UpdateTipsEntries(std::vector<int> const& SortedTipIds);\n  size_t GetTipEntriesCount() { return TipsEntryButtons.size(); }\n  void Move(glm::vec2 offset);\n  void MoveTo(glm::vec2 pos);\n\n  glm::vec4 Tint = glm::vec4(1.0f, 1.0f, 1.0f, 1.0f);\n\n private:\n  Widgets::Group TipsEntriesGroup;\n  TipsTabButton TabName;\n  Impacto::UI::CCLCC::TipsTabType Type;\n  std::function<void(Widgets::Button*)> TipClickHandler;\n  std::unique_ptr<Widgets::Scrollbar> TipsEntriesScrollbar;\n  std::vector<TipsEntryButton*> TipsEntryButtons;\n  float ScrollPosY = 0.0f;\n  glm::vec2 TipsScrollStartPos;\n  glm::vec2 TipsScrollTrackBounds;\n  int EntriesPerPage;\n};\n\n}  // namespace CCLCC\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/widgets/cclcc/titlebutton.cpp",
    "content": "#include \"titlebutton.h\"\n\n#include \"../../../renderer/renderer.h\"\n#include \"../../../inputsystem.h\"\n#include \"../../../profile/games/cclcc/titlemenu.h\"\n#include \"../../../vm/interface/input.h\"\n#include \"../../../audio/audiosystem.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\nnamespace CCLCC {\n\nusing namespace Impacto::Profile::CCLCC::TitleMenu;\n\nTitleButton::TitleButton(int id, Sprite const& norm, Sprite const& focused,\n                         Sprite const& highlight, glm::vec2 pos)\n    : Widgets::Button(id, norm, focused, highlight, pos) {\n  HighlightAnimation.DurationIn = HighlightAnimationDurationIn;\n  HighlightAnimation.DurationOut = HighlightAnimationDurationOut;\n  ChoiceBlinkAnimation.DurationIn = ChoiceBlinkAnimationDurationIn;\n  ChoiceBlinkAnimation.DurationOut = 0;\n}\n\nvoid TitleButton::UpdateInput(float dt) {\n  if (Enabled &&\n      (IsSubButton || HighlightAnimation.State == AnimationState::Stopped) &&\n      ChoiceBlinkAnimation.IsOut()) {\n    Button::UpdateInput(dt);\n  }\n}\n\nvoid TitleButton::Update(float dt) {\n  Widget::Update(dt);\n  HighlightAnimation.Update(dt);\n  ChoiceBlinkAnimation.Update(dt);\n  if (ChoiceBlinkAnimation.IsIn()) {\n    ChoiceBlinkAnimation.Progress = 0.0f;\n    if (OnClickAnimCompleteHandler) {\n      OnClickAnimCompleteHandler(this);\n    }\n  }\n  if (PrevFocusState != HasFocus) {\n    PrevFocusState = HasFocus;\n    if (Input::CurrentInputDevice != Input::Device::Mouse &&\n        ((Vm::Interface::PADinputButtonWentDown |\n          Vm::Interface::PADinputButtonRepeatDown |\n          Vm::Interface::PADinputButtonRepeatAccelDown) &\n         (IsSubButton ? (Vm::Interface::PAD1LEFT | Vm::Interface::PAD1RIGHT)\n                      : (Vm::Interface::PAD1UP | Vm::Interface::PAD1DOWN)))) {\n      Audio::PlayInGroup(Audio::ACG_SE, \"sysse\", 1, false, 0);\n    }\n    if (!IsSubButton) {\n      if (HasFocus) {\n        if (Input::CurrentInputDevice == Input::Device::Mouse ||\n            Input::CurrentInputDevice == Input::Device::Touch) {\n          HighlightAnimation.Progress = 1.0f;\n        } else if (HighlightAnimation.IsOut()) {\n          HighlightAnimation.StartIn();\n        }\n      } else {\n        if (Input::CurrentInputDevice == Input::Device::Mouse ||\n            Input::CurrentInputDevice == Input::Device::Touch) {\n          HighlightAnimation.Progress = 0.0f;\n        } else if (HighlightAnimation.IsIn()) {\n          HighlightAnimation.StartOut();\n        }\n      }\n    }\n  }\n}\n\nvoid TitleButton::Hide() {\n  Button::Hide();\n  HighlightAnimation.Progress = 0.0f;\n  ChoiceBlinkAnimation.Progress = 0.0f;\n  PrevFocusState = false;\n}\n\nvoid TitleButton::Render() {\n  // Calculate the blink effect to occur 4 times during the animation\n  float blinkProgress = ChoiceBlinkAnimation.Progress * 4.0f;\n  float blinkAlpha = 0.5f * (1.0f + cos(blinkProgress * glm::two_pi<float>()));\n  glm::vec4 BlinkTint = glm::vec4(1.0f, 1.0f, 1.0f, Tint.a);\n  if (ChoiceBlinkAnimation.State == AnimationState::Playing) {\n    BlinkTint.a = blinkAlpha;\n  }\n  if (HasFocus ||\n      (!IsSubButton && HighlightAnimation.State == AnimationState::Playing) ||\n      ChoiceBlinkAnimation.State == AnimationState::Playing) {\n    if (!IsSubButton) {  // Main buttons\n      Sprite newHighlightSprite = HighlightSprite;\n      float smoothProgress =\n          HighlightAnimation.State == AnimationState::Playing\n              ? glm::smoothstep(0.0f, 1.0f, HighlightAnimation.Progress)\n              : 1.0f;\n\n      newHighlightSprite.Bounds.Width *= smoothProgress;\n      Renderer->DrawSprite(newHighlightSprite,\n                           glm::vec2(Bounds.X - ItemHighlightOffsetX,\n                                     Bounds.Y - ItemHighlightOffsetY),\n                           BlinkTint);\n      glm::vec4 pointerTint =\n          glm::vec4(1.0f, 1.0f, 1.0f, smoothProgress * blinkAlpha);\n      Renderer->DrawSprite(\n          ItemHighlightPointerSprite,\n          glm::vec2(Bounds.X - ItemHighlightPointerY, Bounds.Y), pointerTint);\n      Renderer->DrawSprite(FocusedSprite, glm::vec2(Bounds.X, Bounds.Y), Tint);\n    } else {  // Sub buttons\n      Renderer->DrawSprite(HighlightSprite, glm::vec2(Bounds.X, Bounds.Y),\n                           BlinkTint);\n      Renderer->DrawSprite(FocusedSprite, glm::vec2(Bounds.X, Bounds.Y),\n                           BlinkTint);\n    }\n  } else {\n    Renderer->DrawSprite(NormalSprite, glm::vec2(Bounds.X, Bounds.Y), Tint);\n  }\n}\n\n}  // namespace CCLCC\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/widgets/cclcc/titlebutton.h",
    "content": "#pragma once\n\n#include \"../button.h\"\n#include \"../../../animation.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\nnamespace CCLCC {\n\nclass TitleButton : public Widgets::Button {\n public:\n  TitleButton(int id, Sprite const& norm, Sprite const& focused,\n              Sprite const& highlight, glm::vec2 pos);\n  void Render() override;\n  void Update(float dt) override;\n  void UpdateInput(float dt) override;\n  void Hide() override;\n  bool IsSubButton = false;\n  Animation HighlightAnimation;\n  Animation ChoiceBlinkAnimation;\n  std::function<void(TitleButton*)> OnClickAnimCompleteHandler;\n  bool PrevFocusState = false;\n};\n\n}  // namespace CCLCC\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/widgets/cgviewer.cpp",
    "content": "#include \"cgviewer.h\"\n#include \"../../renderer/renderer.h\"\n#include \"../../io/vfs.h\"\n#include \"../../inputsystem.h\"\n#include \"../../vm/interface/input.h\"\n#include \"../../profile/game.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\n\nusing namespace Impacto::Vm::Interface;\n\nfloat const CgMovementStep = 10.0f;\nfloat const ScrollwheelDeltaDivider = 10.0f;\nfloat const ScaleStep = 0.01f;\nfloat const MouseAdvanceTime = 0.2f;\n\nCgViewer::CgViewer(std::optional<float> fadeDuration) {\n  FadeAnimation.Direction = AnimationDirection::In;\n  FadeAnimation.LoopMode = AnimationLoopMode::Stop;\n  FadeAnimation.SetDuration(fadeDuration.value_or(0.5f));\n}\n\nvoid CgViewer::Show() {\n  if (State != Shown && State != Showing) {\n    FadeAnimation.StartIn();\n    Widget::Show();\n    State = Showing;\n  }\n}\n\nvoid CgViewer::Hide() {\n  if (State != Hidden && State != Hiding) {\n    FadeAnimation.StartOut(true);\n    Widget::Hide();\n    State = Hiding;\n  }\n}\n\nvoid CgViewer::UpdateInput(float dt) {\n  const auto isLMBDown = PADinputMouseIsDown & PAD1A;\n  if (isLMBDown) {\n    Position[CurrentVariation] += Input::CurMousePos - Input::PrevMousePos;\n  }\n\n  if (PADinputButtonIsDown & PAD1LEFT) {\n    Position[CurrentVariation].x -= CgMovementStep;\n  }\n  if (PADinputButtonIsDown & PAD1RIGHT) {\n    Position[CurrentVariation].x += CgMovementStep;\n  }\n  if (PADinputButtonIsDown & PAD1UP) {\n    Position[CurrentVariation].y -= CgMovementStep;\n  }\n  if (PADinputButtonIsDown & PAD1DOWN) {\n    Position[CurrentVariation].y += CgMovementStep;\n  }\n\n  Scale += Input::MouseWheelDeltaY / ScrollwheelDeltaDivider;\n  if (PADinputButtonIsDown & PAD1L1) {\n    Scale -= ScaleStep;\n  }\n  if (PADinputButtonIsDown & PAD1R1) {\n    Scale += ScaleStep;\n  }\n  Scale = std::max(Scale, MinScale[CurrentVariation]);\n\n  if ((PADinputButtonIsDown & (PAD1LEFT | PAD1RIGHT | PAD1UP | PAD1DOWN)) ||\n      isLMBDown) {\n    const glm::vec2 halfScreen =\n        glm::vec2(Profile::DesignWidth, Profile::DesignHeight) * 0.5f;\n\n    auto currentSize = glm::vec2(0.0f);\n    if (HorizontalRendering[CurrentVariation]) {\n      currentSize.y = CgSprites[CurrentVariation][0].ScaledHeight();\n      for (int i = 0; i < CgCount[CurrentVariation]; i++) {\n        currentSize.x += CgSprites[CurrentVariation][i].ScaledWidth();\n      }\n    } else {\n      currentSize.x = CgSprites[CurrentVariation][0].ScaledWidth();\n      for (int i = 0; i < CgCount[CurrentVariation]; i++) {\n        currentSize.y += CgSprites[CurrentVariation][i].ScaledHeight();\n      }\n    }\n\n    Position[CurrentVariation] = glm::clamp(\n        Position[CurrentVariation], halfScreen - currentSize, halfScreen);\n  }\n\n  bool mouseAdvance = false;\n  if (PADinputMouseWentDown & PAD1A) {\n    if (MouseDownTime == 0.0f) {\n      MouseDownTime = SDL_GetPerformanceCounter() /\n                      static_cast<float>(SDL_GetPerformanceFrequency());\n    }\n  }\n\n  if (MouseDownTime != 0.0f) {\n    const float timeDiff =\n        SDL_GetPerformanceCounter() /\n            static_cast<float>(SDL_GetPerformanceFrequency()) -\n        MouseDownTime;\n    if (!isLMBDown) {\n      if (timeDiff < MouseAdvanceTime) {\n        mouseAdvance = true;\n      }\n      MouseDownTime = 0.0f;\n    } else if (timeDiff >= MouseAdvanceTime ||\n               ((Input::CurMousePos - Input::PrevMousePos) != glm::vec2(0))) {\n      RequestCursor(CursorType::Pointer);\n    }\n  }\n\n  if (PADinputButtonWentDown & PAD1A || mouseAdvance) {\n    while (true) {\n      if (static_cast<int>(CurrentVariation + 1) == VariationCount) {\n        if (OnVariationEndHandler) {\n          OnVariationEndHandler(this);\n          return;\n        }\n      }\n      CurrentVariation += 1;\n      FadeAnimation.StartIn(true);\n      if (SaveSystem::GetEVVariationIsUnlocked(EvId, CurrentVariation)) break;\n    }\n\n    const bool sameOrientation =\n        CgCount[CurrentVariation] == CgCount[CurrentVariation - 1] &&\n        HorizontalRendering[CurrentVariation] ==\n            HorizontalRendering[CurrentVariation - 1];\n\n    if (sameOrientation) {\n      Position[CurrentVariation] = Position[CurrentVariation - 1];\n      Scale = std::max(PrevScale, MinScale[CurrentVariation]);\n    } else {\n      Position[CurrentVariation] =\n          HorizontalRendering[CurrentVariation]\n              ? glm::vec2(0.0f,\n                          (Profile::DesignHeight -\n                           CgSprites[CurrentVariation][0].ScaledHeight()) /\n                              2)\n              : glm::vec2((Profile::DesignWidth -\n                           CgSprites[CurrentVariation][0].ScaledWidth()) /\n                              2,\n                          0.0f);\n      Scale = MinScale[CurrentVariation];\n    }\n    PrevScale = Scale;\n    for (int i = 0; i < CgCount[CurrentVariation]; i++) {\n      CgSprites[CurrentVariation][i].BaseScale.x = Scale;\n      CgSprites[CurrentVariation][i].BaseScale.y = Scale;\n    }\n  }\n}\n\nvoid CgViewer::Update(float dt) {\n  Widget::Update(dt);\n  FadeAnimation.Update(dt);\n  if (FadeAnimation.IsIn() && State == Showing) {\n    State = Shown;\n  } else if (FadeAnimation.IsOut() && State == Hiding) {\n    State = Hidden;\n  }\n  RectF screen = RectF(0.0f, 0.0f, Profile::DesignWidth, Profile::DesignHeight);\n  auto pointBefore = screen.Center() - Position[CurrentVariation];\n  auto pointAfter = pointBefore * (Scale / PrevScale);\n  auto diff = pointAfter - pointBefore;\n  for (int i = 0; i < CgCount[CurrentVariation]; i++) {\n    CgSprites[CurrentVariation][i].BaseScale.x = Scale;\n    CgSprites[CurrentVariation][i].BaseScale.y = Scale;\n  }\n  Position[CurrentVariation] -= diff;\n  PrevScale = Scale;\n}\n\nvoid CgViewer::Render() {\n  if (State == Hidden) return;\n  glm::vec4 col(1.0f);\n  if (State != Shown) {\n    col.a = glm::smoothstep(0.0f, 1.0f, FadeAnimation.Progress);\n  }\n\n  Renderer->DrawQuad(\n      RectF(0.0f, 0.0f, Profile::DesignWidth, Profile::DesignHeight),\n      glm::vec4(0.0f, 0.0f, 0.0f, col.a));\n\n  if (State != Shown || CurrentVariation == 0) {\n    RenderVariation(CurrentVariation, col);\n  } else {\n    if (FadeAnimation.IsPlaying() &&\n        FadeAnimation.Direction == AnimationDirection::In) {\n      RenderVariation(CurrentVariation - 1, glm::vec4(1.0f));\n    }\n    glm::vec4 tint(1.0f, 1.0f, 1.0f,\n                   glm::smoothstep(0.0f, 1.0f, FadeAnimation.Progress));\n    RenderVariation(CurrentVariation, tint);\n  }\n}\n\nvoid CgViewer::RenderVariation(size_t variation, glm::vec4 col) const {\n  if (CgCount[variation] == 1) {\n    Renderer->DrawSprite(CgSprites[variation][0], Position[variation], col);\n    return;\n  }\n\n  Renderer->SetFramebuffer(1);\n  Renderer->Clear(glm::vec4(0.0f));\n\n  glm::vec2 pos;\n  for (int i = 0; i < CgCount[variation]; i++) {\n    if (i == 0)\n      pos = Position[variation];\n    else {\n      pos =\n          Position[variation] +\n          (HorizontalRendering[variation]\n               ? glm::vec2(CgSprites[variation][i - 1].ScaledWidth() - Scale, 0)\n               : glm::vec2(0,\n                           CgSprites[variation][i - 1].ScaledHeight() - Scale));\n    }\n    Renderer->DrawSprite(CgSprites[variation][i], pos, glm::vec4(1.0f));\n  }\n  Sprite linkedSprite =\n      Sprite(SpriteSheet(Profile::DesignWidth, Profile::DesignHeight), 0.0f,\n             0.0f, Profile::DesignWidth, Profile::DesignHeight);\n  linkedSprite.Sheet.Texture = Renderer->GetFramebufferTexture(1);\n  linkedSprite.Sheet.IsScreenCap = true;\n  Renderer->SetFramebuffer(0);\n  Renderer->DrawSprite(linkedSprite, glm::mat4(1.0f), col);\n}\n\nvoid CgViewer::LoadCgSprites(\n    size_t evId, std::string mountPoint,\n    uint16_t loadIds[][Profile::SaveSystem::MaxCGSprites]) {\n  Clear();\n\n  EvId = evId;\n\n  int variationIdx = 0;\n  while (loadIds[variationIdx][0] != 0xFFFF) {\n    int idx = 0;\n    float totalHeight = 0.0f;\n    float totalWidth = 0.0f;\n    bool sideways = false;\n\n    while (loadIds[variationIdx][idx] != 0xFFFF) {\n      if (loadIds[variationIdx][idx] == 0x51D3) {\n        sideways = true;\n        idx += 1;\n        continue;\n      }\n      Io::Stream* stream;\n      int64_t err =\n          Io::VfsOpen(mountPoint, loadIds[variationIdx][idx], &stream);\n      if (err != IoError_OK) return;\n      CgTexture.Load(stream);\n      delete stream;\n      CgSpriteSheets[variationIdx][idx].Texture = CgTexture.Submit();\n      CgSpriteSheets[variationIdx][idx].DesignWidth =\n          static_cast<float>(CgTexture.Width);\n      CgSpriteSheets[variationIdx][idx].DesignHeight =\n          static_cast<float>(CgTexture.Height);\n      CgSprites[variationIdx][idx].Sheet = CgSpriteSheets[variationIdx][idx];\n      CgSprites[variationIdx][idx].BaseScale = glm::vec2(1.0f);\n      CgSprites[variationIdx][idx].Bounds =\n          RectF(0.0f, 0.0f, CgSpriteSheets[variationIdx][idx].DesignWidth,\n                CgSpriteSheets[variationIdx][idx].DesignHeight);\n      idx += 1;\n      totalHeight += CgTexture.Height;\n      totalWidth += CgTexture.Width;\n      if (idx == MaxCgViewerCgs) break;\n    }\n    const int count = idx - static_cast<int>(sideways);\n    CgCount[variationIdx] = count;\n    if (count > 1) {\n      if (sideways)\n        totalWidth -= 2 * (count - 1);\n      else\n        totalHeight -= 2 * (count - 1);\n      // prevents pixel bleeding out in seams\n      for (int i = 1; i < count; i++) {\n        if (sideways)\n          CgSprites[variationIdx][i].Bounds.X++;\n        else\n          CgSprites[variationIdx][i].Bounds.Y++;\n      }\n    }\n    MinScale[variationIdx] = sideways ? Profile::DesignWidth / totalWidth\n                                      : Profile::DesignHeight / totalHeight;\n    HorizontalRendering[variationIdx] = sideways;\n    CgSprites[variationIdx][0].BaseScale.x = MinScale[variationIdx];\n    CgSprites[variationIdx][0].BaseScale.y = MinScale[variationIdx];\n    Position[variationIdx] =\n        sideways ? glm::vec2(0.0f, (Profile::DesignHeight -\n                                    CgSprites[variationIdx][0].ScaledHeight()) /\n                                       2)\n                 : glm::vec2((Profile::DesignWidth -\n                              CgSprites[variationIdx][0].ScaledWidth()) /\n                                 2,\n                             0.0f);\n    variationIdx += 1;\n  }\n\n  Scale = MinScale[0];\n  PrevScale = Scale;\n  VariationCount = variationIdx;\n  CurrentVariation = 0;\n}\n\nbool CgViewer::IsOnLastVariation() const {\n  return static_cast<int>(CurrentVariation) == VariationCount - 1;\n}\n\nvoid CgViewer::Clear() {\n  for (int i = 0; i < VariationCount; i++) {\n    for (int j = 0; j < CgCount[i]; j++) {\n      if (CgSpriteSheets[i][j].DesignWidth != 0.0f &&\n          CgSpriteSheets[i][j].DesignHeight != 0.0f) {\n        Renderer->FreeTexture(CgSpriteSheets[i][j].Texture);\n        CgSpriteSheets[i][j].DesignHeight = 0.0f;\n        CgSpriteSheets[i][j].DesignWidth = 0.0f;\n        CgSpriteSheets[i][j].Texture = 0;\n      }\n    }\n  }\n}\n\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/widgets/cgviewer.h",
    "content": "#pragma once\n\n#include <functional>\n\n#include \"../widget.h\"\n#include \"../menu.h\"\n#include \"../../texture/texture.h\"\n#include \"../../spritesheet.h\"\n#include \"../../profile/data/savesystem.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\n\nint constexpr MaxCgViewerCgs = 3;\nint constexpr MaxCgViewerVariations = 15;\n\nclass CgViewer : public Widget {\n public:\n  CgViewer(std::optional<float> fadeDuration = std::nullopt);\n\n  void Show() override;\n  void Hide() override;\n\n  void Update(float dt) override;\n  void UpdateInput(float dt) override;\n  void Render() override;\n\n  void RenderVariation(size_t index, glm::vec4 col) const;\n\n  void LoadCgSprites(size_t evId, std::string mountPoint,\n                     uint16_t loadIds[][Profile::SaveSystem::MaxCGSprites]);\n\n  bool IsOnLastVariation() const;\n  std::function<void(CgViewer*)> OnVariationEndHandler;\n\n  bool IsFadingBetweenVariations() const {\n    return State == Shown && this->FadeAnimation.IsPlaying() &&\n           this->FadeAnimation.Direction == AnimationDirection::In;\n  };\n\n  float GetVariationFadeProgress() const {\n    return this->FadeAnimation.Progress;\n  }\n\n  bool IsShown() const { return State == Shown; };\n  bool IsHidden() const { return State == Hidden; };\n\n protected:\n  Texture CgTexture;\n  Sprite CgSprites[MaxCgViewerVariations][MaxCgViewerCgs];\n  SpriteSheet CgSpriteSheets[MaxCgViewerVariations][MaxCgViewerCgs];\n\n  size_t EvId;\n\n  std::array<int, MaxCgViewerVariations> CgCount;\n  std::array<bool, MaxCgViewerVariations> HorizontalRendering;\n  MenuState State = Hidden;\n\n  glm::vec2 Position[MaxCgViewerVariations];\n  float PrevScale = 1.0f;\n  float Scale = 1.0f;\n  float MinScale[MaxCgViewerVariations];\n  size_t CurrentVariation = 0;\n  int VariationCount = 0;\n\n  float MouseDownTime = 0.0f;\n\n  void Clear();\n\n  Animation FadeAnimation;\n};\n\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/widgets/chlcc/albumthumbnailbutton.cpp",
    "content": "#include \"albumthumbnailbutton.h\"\n#include \"../../../renderer/renderer.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\nnamespace CHLCC {\n\nglm::vec4 AlbumThumbnailButton::FocusedAlpha = glm::vec4(1.0f);\nAnimation AlbumThumbnailButton::FocusedAlphaFade;\n\nvoid AlbumThumbnailButton::Render() {\n  Renderer->DrawSprite((IsLocked ? LockedSprite : NormalSprite),\n                       glm::vec2(Bounds.X, Bounds.Y));\n  if (!IsLocked) {\n    for (int variations = 0; variations < TotalVariations; variations++) {\n      Renderer->DrawSprite(\n          (ViewedVariations > variations ? VariationUnlocked : VariationLocked),\n          glm::vec2(Bounds.X + VariationTemplateOffset.x -\n                        VariationUnlocked.Bounds.Width * (float)variations,\n                    Bounds.Y + VariationTemplateOffset.y));\n    }\n  }\n  if (HasFocus) {\n    Renderer->DrawSprite(SelectionMarker,\n                         glm::vec2(Bounds.X, Bounds.Y) + SelectionMarkerOffset);\n    Renderer->DrawSprite(HighlightSprite, glm::vec2(Bounds.X, Bounds.Y),\n                         FocusedAlpha);\n  }\n}\n\nvoid AlbumThumbnailButton::FocusedAlphaFadeStart() {\n  if (FocusedAlphaFade.State == AnimationState::Stopped) {\n    FocusedAlphaFade.Direction = AnimationDirection::In;\n    FocusedAlphaFade.SetDuration(0.5f);\n    FocusedAlphaFade.LoopMode = AnimationLoopMode::ReverseDirection;\n    FocusedAlphaFade.StartIn();\n  }\n}\n\nvoid AlbumThumbnailButton::FocusedAlphaFadeReset() {\n  FocusedAlphaFade.State = AnimationState::Stopped;\n  FocusedAlphaFade.Progress = 0.0f;\n}\n\nvoid AlbumThumbnailButton::UpdateFocusedAlphaFade(float dt) {\n  FocusedAlphaFade.Update(dt);\n  FocusedAlpha =\n      glm::vec4(glm::vec3(1.0f), ((FocusedAlphaFade.Progress * 30) + 1) / 85);\n}\n\nvoid AlbumThumbnailButton::UpdateVariations(int total, int viewed) {\n  TotalVariations = total;\n  ViewedVariations = viewed;\n  IsLocked = (ViewedVariations < TotalVariations);\n}\n\n}  // namespace CHLCC\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto\n"
  },
  {
    "path": "src/ui/widgets/chlcc/albumthumbnailbutton.h",
    "content": "#pragma once\n\n#include \"../button.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\nnamespace CHLCC {\n\nclass AlbumThumbnailButton : public Button {\n public:\n  AlbumThumbnailButton(int id, Sprite const& norm, Sprite const& focused,\n                       Sprite const& highlight, glm::vec2 pos,\n                       int totalVariations, int viewedVariations,\n                       Sprite const& unlockedVariation,\n                       Sprite const& lockedVariation, glm::vec2 variationOffset,\n                       Sprite locked, Sprite SelectionMarker,\n                       glm::vec2 SelectionMarkerOffset)\n      : Button(id, norm, focused, highlight, pos),\n        TotalVariations(totalVariations),\n        ViewedVariations(viewedVariations),\n        VariationUnlocked(unlockedVariation),\n        VariationLocked(lockedVariation),\n        VariationTemplateOffset(variationOffset),\n        SelectionMarker(SelectionMarker),\n        SelectionMarkerOffset(SelectionMarkerOffset) {\n    IsLocked = (viewedVariations == 0);\n    LockedSprite = locked;\n  }\n\n  void Render() override;\n\n  void UpdateVariations(int total, int viewed);\n  static void FocusedAlphaFadeStart();\n  static void FocusedAlphaFadeReset();\n  static void UpdateFocusedAlphaFade(float dt);\n\n private:\n  int TotalVariations;\n  int ViewedVariations;\n  Sprite VariationUnlocked;\n  Sprite VariationLocked;\n  glm::vec2 VariationTemplateOffset;\n  Sprite SelectionMarker;\n  glm::vec2 SelectionMarkerOffset;\n\n  static glm::vec4 FocusedAlpha;\n  static Animation FocusedAlphaFade;\n};\n\n}  // namespace CHLCC\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/widgets/chlcc/backlogentry.cpp",
    "content": "#include \"backlogentry.h\"\n#include \"../../../renderer/renderer.h\"\n#include \"../../../profile/dialogue.h\"\n#include \"../../../profile/ui/backlogmenu.h\"\n#include \"../../../profile/games/chlcc/backlogmenu.h\"\n#include \"../backlogentry.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\nnamespace CHLCC {\n\nusing namespace Impacto::Profile::BacklogMenu;\nusing namespace Impacto::Profile::CHLCC::BacklogMenu;\n\nconstexpr std::array<std::string_view, 2> NametagCommonStrings = {\n    \"【　\",\n    \"　】\",\n};\n\nBacklogEntry::BacklogEntry(int id, Vm::BufferOffsetContext scrCtx, int audioId,\n                           int characterId, glm::vec2 pos,\n                           const RectF& hoverBounds)\n    : Widgets::BacklogEntry(id, scrCtx, audioId, characterId, pos,\n                            hoverBounds) {\n  if (BacklogPage->HasName()) {\n    beforeNametagLabel.SetText(\n        NametagCommonStrings[0], Profile::Dialogue::REVNameFontSize,\n        Profile::Dialogue::REVNameOutlineMode, Profile::Dialogue::REVNameColor);\n    nametagLabel.SetText(BacklogPage->Name,\n                         Profile::Dialogue::REVNameOutlineMode);\n    afterNametagLabel.SetText(\n        NametagCommonStrings[1], Profile::Dialogue::REVNameFontSize,\n        Profile::Dialogue::REVNameOutlineMode, Profile::Dialogue::REVNameColor);\n  }\n}\n\nvoid BacklogEntry::Render() {\n  if (AudioId != -1) {\n    Renderer->DrawSprite(\n        VoiceIcon,\n        glm::vec2(Profile::CHLCC::BacklogMenu::RenderingBounds.X,\n                  Bounds.Y + VoiceIconOffset.y),\n        Tint);\n  }\n\n  if (BacklogPage->HasName()) {\n    beforeNametagLabel.MoveTo({Bounds.X, Bounds.Y});\n    nametagLabel.MoveTo(beforeNametagLabel.Bounds.GetPos() +\n                        glm::vec2(beforeNametagLabel.Bounds.Width, 0.0f));\n\n    afterNametagLabel.MoveTo(nametagLabel.Bounds.GetPos() +\n                             glm::vec2(nametagLabel.Bounds.Width, 0.0f));\n\n    beforeNametagLabel.Render();\n    nametagLabel.Render();\n    afterNametagLabel.Render();\n  }\n  Renderer->DrawProcessedText(BacklogPage->Glyphs,\n                              Profile::Dialogue::DialogueFont, Tint.a,\n                              Profile::Dialogue::REVOutlineMode, true);\n}\n\n}  // namespace CHLCC\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/widgets/chlcc/backlogentry.h",
    "content": "#pragma once\n\n#include \"../backlogentry.h\"\n#include \"../label.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\nnamespace CHLCC {\n\nclass BacklogEntry : public Widgets::BacklogEntry {\n public:\n  BacklogEntry(int id, Vm::BufferOffsetContext scrCtx, int audioId,\n               int characterId, glm::vec2 pos, const RectF& hoverBounds);\n\n  void Render() override;\n\n private:\n  Label beforeNametagLabel;\n  Label nametagLabel;\n  Label afterNametagLabel;\n};\n\n}  // namespace CHLCC\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/widgets/chlcc/moviemenuentrybutton.cpp",
    "content": "#include \"moviemenuentrybutton.h\"\n\n#include \"../../../renderer/renderer.h\"\n#include \"../../../profile/games/chlcc/moviemenu.h\"\n#include \"../../../games/chlcc/moviemenu.h\"\n#include \"../../../profile/scriptvars.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\nnamespace CHLCC {\n\nusing namespace Impacto::Profile::CHLCC::MovieMenu;\nusing namespace Impacto::Profile::ScriptVars;\n\nMovieMenuEntryButton::MovieMenuEntryButton(int id, Sprite const& movieThumbnail,\n                                           Sprite const& lockedMovieThumbnail,\n                                           glm::vec2 thumbnailPos,\n                                           glm::vec2 boxPos,\n                                           bool& isMovieExtraMode)\n    : IsExtraMovieModeOn(isMovieExtraMode) {\n  Id = id;\n  NormalSprite = movieThumbnail;\n  DisabledSprite = lockedMovieThumbnail;\n  Enabled = true;\n  Bounds = RectF(boxPos.x, boxPos.y, MovieBox.ScaledWidth(),\n                 MovieBox.ScaledHeight());\n\n  MovieBoxAnim = SelectedMovieAnimation.Instantiate();\n  MovieBoxAnim.Direction = AnimationDirection::In;\n  MovieBoxAnim.LoopMode = AnimationLoopMode::Loop;\n  MovieBoxAnim.StartIn();\n\n  MovieBoxAnimExtra = SelectedMovieExtraAnimation.Instantiate();\n  MovieBoxAnimExtra.Direction = AnimationDirection::In;\n  MovieBoxAnimExtra.LoopMode = AnimationLoopMode::Loop;\n  MovieBoxAnimExtra.StartIn();\n}\n\nvoid MovieMenuEntryButton::Update(float dt) {\n  Button::Update(dt);\n  MovieBoxAnim.Update(dt);\n  MovieBoxAnimExtra.Update(dt);\n}\n\nvoid MovieMenuEntryButton::Render() {\n  if (HasFocus) {\n    // Make MovieBoxAnimExtra Jade Green when in ExtraMovieMode\n    Renderer->DrawSprite(IsExtraMovieModeOn ? MovieBoxAnimExtra.CurrentSprite()\n                                            : MovieBoxAnim.CurrentSprite(),\n                         Bounds);\n    Renderer->DrawSprite(SelectedMovieYellowDot,\n                         glm::vec2(Bounds.X - 7, Bounds.Y + 53));\n  } else {\n    // Make MovieBox Jade Green when in ExtraMovieMode\n    Renderer->DrawSprite(IsExtraMovieModeOn ? MovieBoxExtra : MovieBox, Bounds);\n  }\n  if (!IsLocked) {\n    // Have a new thumbnail for the PSP OP\n    if (IsExtraMovieModeOn && Id == 0) {\n      Renderer->DrawSprite(MovieThumbnailExtraOp,\n                           glm::vec2(Bounds.X + 20, Bounds.Y + 16), Tint);\n    } else if (IsExtraMovieModeOn && Id == 1) {\n      Renderer->DrawSprite(MovieThumbnailExtraOp2,\n                           glm::vec2(Bounds.X + 20, Bounds.Y + 16), Tint);\n    } else {\n      Renderer->DrawSprite(NormalSprite,\n                           glm::vec2(Bounds.X + 20, Bounds.Y + 16), Tint);\n    }\n  } else {\n    Renderer->DrawSprite(DisabledSprite,\n                         glm::vec2(Bounds.X + 20, Bounds.Y + 16), Tint);\n  }\n}\n\n}  // namespace CHLCC\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/widgets/chlcc/moviemenuentrybutton.h",
    "content": "#pragma once\n\n#include \"../button.h\"\n#include \"../../../spriteanimation.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\nnamespace CHLCC {\n\nclass MovieMenuEntryButton : public Widgets::Button {\n public:\n  MovieMenuEntryButton(int id, Sprite const& movieThumbnail,\n                       Sprite const& lockedMovieThumbnail,\n                       glm::vec2 thumbnailPos, glm::vec2 boxPos,\n                       bool& isMovieExtraMode);\n\n  void Update(float dt) override;\n  void Render() override;\n\n  bool IsLocked = true;\n\n private:\n  SpriteAnimation MovieBoxAnim;\n  SpriteAnimation MovieBoxAnimExtra;\n\n  std::reference_wrapper<bool> IsExtraMovieModeOn;\n};\n\n}  // namespace CHLCC\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/widgets/chlcc/optionsbutton.cpp",
    "content": "#include \"optionsbutton.h\"\n\n#include \"../../../profile/games/chlcc/optionsmenu.h\"\n#include \"../../../renderer/renderer.h\"\n#include \"../../../inputsystem.h\"\n#include \"../../../vm/interface/input.h\"\n#include \"../../../log.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\nnamespace CHLCC {\n\nusing namespace Impacto::Vm::Interface;\nusing namespace Impacto::Profile::CHLCC::OptionsMenu;\n\ntemplate <typename T>\nOptionsButton<T>::OptionsButton(\n    T& value, const std::span<const T> optionsValues,\n    const std::span<const std::reference_wrapper<const Sprite>> optionsSprites,\n    const glm::vec2 topRight, const RectF hoverBounds,\n    const std::function<void(OptionsEntry*)> highlight)\n    : OptionsEntry(hoverBounds, highlight),\n      Value(value),\n      OptionsValues(optionsValues),\n      OptionsSprites(optionsSprites),\n      OptionClickArea(0, hoverBounds,\n                      [this](ClickArea* area) {\n                        this->OptionId =\n                            (this->OptionId + 1) % this->OptionCount;\n                        this->Value = this->OptionsValues[this->OptionId];\n                      }),\n      OptionCount(optionsValues.size()),\n      TopRight(topRight) {\n  assert(OptionsValues.size() == OptionsSprites.size());\n  OptionId = GetCurrentOptionId();\n}\n\ntemplate <typename T>\nvoid OptionsButton<T>::Render() {\n  const Sprite& optionSprite = OptionsSprites[OptionId];\n  Renderer->DrawSprite(optionSprite,\n                       TopRight + SettingButtonTopRightOffset -\n                           glm::vec2(optionSprite.Bounds.Width, 0.0f));\n}\n\ntemplate <typename T>\nvoid OptionsButton<T>::UpdateInput(float dt) {\n  OptionsEntry::UpdateInput(dt);\n\n  OptionClickArea.UpdateInput(dt);\n\n  if (!HasFocus) return;\n\n  const int direction =\n      static_cast<int>(\n          static_cast<bool>(PADinputButtonWentDown & (PAD1RIGHT | PAD1A))) -\n      static_cast<int>(static_cast<bool>(PADinputButtonWentDown & PAD1LEFT));\n\n  if (direction != 0) {\n    OptionId = (OptionId + direction + OptionCount) % OptionCount;\n    Value = OptionsValues[OptionId];\n  }\n}\n\ntemplate <typename T>\nvoid OptionsButton<T>::Move(glm::vec2 relativePos) {\n  OptionsEntry::Move(relativePos);\n\n  TopRight += relativePos;\n  EntryButton.Move(relativePos);\n}\n\ntemplate <typename T>\nsize_t OptionsButton<T>::GetCurrentOptionId() {\n  const auto found = std::ranges::find(OptionsValues, Value);\n\n  if (found == OptionsValues.end()) {\n    ImpLog(LogLevel::Error, LogChannel::General,\n           \"Failed to find option id. Replacing with first option\\n\");\n    Value = OptionsValues[0];\n    return 0;\n  }\n\n  return std::distance(OptionsValues.begin(), found);\n}\n\ntemplate <>\nsize_t OptionsButton<float>::GetCurrentOptionId() {\n  const auto shorterDistance = [&](float lhs, float rhs) {\n    return std::abs(lhs - Value) < std::abs(rhs - Value);\n  };\n  const auto closestValue =\n      std::ranges::min_element(OptionsValues, shorterDistance);\n  return std::distance(OptionsValues.begin(), closestValue);\n}\n\ntemplate class OptionsButton<bool>;\ntemplate class OptionsButton<uint8_t>;\ntemplate class OptionsButton<float>;\n\n}  // namespace CHLCC\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto\n"
  },
  {
    "path": "src/ui/widgets/chlcc/optionsbutton.h",
    "content": "#pragma once\n\n#include \"optionsentry.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\nnamespace CHLCC {\n\ntemplate <typename T>\nclass OptionsButton : public OptionsEntry {\n public:\n  OptionsButton(\n      T& value, std::span<const T> optionsValues,\n      std::span<const std::reference_wrapper<const Sprite>> optionsSprites,\n      glm::vec2 topRight, RectF hoverBounds,\n      std::function<void(OptionsEntry*)> highlight);\n\n  void Render() override;\n  void UpdateInput(float dt) override;\n\n  void UpdateValue() override { OptionId = GetCurrentOptionId(); };\n\n  void Move(glm::vec2 relativePos) override;\n\n private:\n  size_t GetCurrentOptionId();\n\n  T& Value;\n\n  std::span<const T> OptionsValues;\n  std::span<const std::reference_wrapper<const Sprite>> OptionsSprites;\n  ClickArea OptionClickArea;\n\n  size_t OptionCount;\n  size_t OptionId;\n\n  glm::vec2 TopRight;\n};\n\n}  // namespace CHLCC\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto\n"
  },
  {
    "path": "src/ui/widgets/chlcc/optionsentry.cpp",
    "content": "#include \"optionsentry.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\nnamespace CHLCC {\n\nOptionsEntry::OptionsEntry(const RectF hoverBounds,\n                           const std::function<void(OptionsEntry*)> highlight)\n    : EntryButton(0, hoverBounds), Highlight(highlight) {\n  Bounds = hoverBounds;\n}\n\nvoid OptionsEntry::Update(float dt) {\n  Widget::Update(dt);\n\n  EntryButton.Update(dt);\n}\n\nvoid OptionsEntry::UpdateInput(float dt) {\n  const bool wasHovered = EntryButton.Hovered;\n  EntryButton.UpdateInput(dt);\n  if (!HasFocus && !wasHovered && EntryButton.Hovered) {\n    Highlight(this);\n  }\n}\n\nvoid OptionsEntry::Show() {\n  Widget::Show();\n  EntryButton.Show();\n}\n\nvoid OptionsEntry::Hide() {\n  EntryButton.Hide();\n  Widget::Hide();\n}\n\nvoid OptionsEntry::Move(glm::vec2 relativePos) {\n  Widget::Move(relativePos);\n  EntryButton.Move(relativePos);\n}\n\n}  // namespace CHLCC\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto\n"
  },
  {
    "path": "src/ui/widgets/chlcc/optionsentry.h",
    "content": "#pragma once\n\n#include <functional>\n\n#include \"../clickarea.h\"\n#include \"../../widget.h\"\n#include \"../../../spritesheet.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\nnamespace CHLCC {\n\nclass OptionsEntry : public Widget {\n public:\n  OptionsEntry(RectF hoverBounds, std::function<void(OptionsEntry*)> highlight);\n\n  virtual void Update(float dt) override;\n  virtual void UpdateInput(float dt) override;\n\n  virtual void UpdateValue() = 0;\n\n  virtual void Show() override;\n  virtual void Hide() override;\n\n  virtual void Move(glm::vec2 relativePos) override;\n\n protected:\n  bool Enabled = false;\n\n  ClickArea EntryButton;\n  std::function<void(OptionsEntry*)> Highlight;\n  void EntryButtonOnClick(ClickArea* target);\n};\n\n}  // namespace CHLCC\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto\n"
  },
  {
    "path": "src/ui/widgets/chlcc/optionsslider.cpp",
    "content": "#include \"optionsslider.h\"\n\n#include \"../../../profile/games/chlcc/optionsmenu.h\"\n#include \"../../../renderer/renderer.h\"\n#include \"../../../inputsystem.h\"\n#include \"../../../vm/interface/input.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\nnamespace CHLCC {\n\nusing namespace Impacto::Vm::Interface;\nusing namespace Impacto::Profile::CHLCC::OptionsMenu;\n\nOptionsSlider::OptionsSlider(float& value, const float min, const float max,\n                             const Sprite bar, const Sprite fill,\n                             const glm::vec2 topRight, const RectF hoverBounds,\n                             const std::function<void(OptionsEntry*)> highlight,\n                             const std::optional<Sprite> mutedSprite)\n    : OptionsEntry(hoverBounds, highlight),\n      OldProgress(value),\n      Slider(0,\n             topRight + SliderBarTopRightOffset + SliderBarFillOffset -\n                 glm::vec2(bar.ScaledWidth(), 0.0f),\n             min, max, &value, ScrollbarDirection::SBDIR_HORIZONTAL,\n             fill.Bounds.GetSize()),\n      BarSprite(bar),\n      MutedSprite(mutedSprite),\n      BeforeMutedProgress(min) {\n  Slider.FillSprite = fill;\n\n  ChangingFadeAnimation.SetDuration(SliderBarFadeDuration);\n  ChangingFadeAnimation.LoopMode = AnimationLoopMode::ReverseDirection;\n  ChangingFadeAnimation.StartIn();\n\n  if (MutedSprite.has_value()) {\n    const glm::vec2 clickAreaPos = Bounds.GetPos() + VoiceMutedOffset;\n    const RectF clickRect(clickAreaPos.x, clickAreaPos.y,\n                          MutedSprite->ScaledWidth(),\n                          MutedSprite->ScaledHeight());\n    const auto onClick = [this](const auto* clickArea) {\n      if (*this->Slider.Value == this->Slider.StartValue) {\n        *this->Slider.Value = this->BeforeMutedProgress;\n      } else {\n        this->BeforeMutedProgress = *this->Slider.Value;\n        *this->Slider.Value = this->Slider.StartValue;\n      }\n\n      this->OldProgress = *this->Slider.Value;\n      this->Changing = false;\n    };\n\n    MuteClickArea = ClickArea(0, clickRect, onClick);\n  }\n}\n\nvoid OptionsSlider::Render() {\n  Renderer->DrawSprite(BarSprite,\n                       Slider.GetTrackBounds().GetPos() - SliderBarFillOffset);\n\n  const float fillAlpha =\n      Changing ? 0.5f + ChangingFadeAnimation.Progress * 0.5f : 1.0f;\n  Slider.Tint.a = fillAlpha;\n  Slider.Render();\n\n  if (MutedSprite.has_value() && *Slider.Value <= Slider.StartValue) {\n    const float mutedAlpha =\n        (Changing && OldProgress > Slider.StartValue) ? fillAlpha : 1.0f;\n\n    Renderer->DrawSprite(*MutedSprite, Bounds.GetPos() + VoiceMutedOffset,\n                         {1.0f, 1.0f, 1.0f, mutedAlpha});\n  }\n}\n\nvoid OptionsSlider::Update(float dt) {\n  OptionsEntry::Update(dt);\n\n  if (!HasFocus && Changing) {\n    *Slider.Value = OldProgress;\n    Changing = false;\n  }\n\n  ChangingFadeAnimation.Update(dt);\n  Slider.Update(dt);\n  if (MuteClickArea) MuteClickArea->Update(dt);\n}\n\nvoid OptionsSlider::UpdateInput(float dt) {\n  static bool slidingByMouse = false;\n\n  slidingByMouse |=\n      (Slider.GetTrackBounds().ContainsPoint(Input::CurMousePos) &&\n       Input::MouseButtonWentDown[SDL_BUTTON_LEFT]) ||\n      (Slider.GetTrackBounds().ContainsPoint(Input::CurTouchPos) &&\n       Input::TouchWentDown[0]);\n  slidingByMouse &=\n      Input::MouseButtonIsDown[SDL_BUTTON_LEFT] || Input::TouchIsDown[0];\n\n  OptionsEntry::UpdateInput(dt);\n\n  Slider.HasFocus = HasFocus;\n  Slider.UpdateInput(dt);\n  Slider.ClampValue();\n\n  if (MuteClickArea) MuteClickArea->UpdateInput(dt);\n\n  Changing |= *Slider.Value != OldProgress;\n  if (Changing && (PADinputButtonWentDown & PAD1A || slidingByMouse)) {\n    UpdateValue();\n  }\n}\n\nvoid OptionsSlider::UpdateValue() {\n  OldProgress = *Slider.Value;\n  Changing = false;\n}\n\nvoid OptionsSlider::Show() {\n  OptionsEntry::Show();\n  Slider.Show();\n  if (MuteClickArea) MuteClickArea->Show();\n\n  OldProgress = *Slider.Value;\n}\n\nvoid OptionsSlider::Hide() {\n  Changing = false;\n  *Slider.Value = OldProgress;\n\n  Slider.Hide();\n  if (MuteClickArea) MuteClickArea->Hide();\n  OptionsEntry::Hide();\n}\n\nvoid OptionsSlider::Move(glm::vec2 relativePos) {\n  OptionsEntry::Move(relativePos);\n  Slider.Move(relativePos);\n  if (MuteClickArea) MuteClickArea->Move(relativePos);\n}\n\n}  // namespace CHLCC\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto\n"
  },
  {
    "path": "src/ui/widgets/chlcc/optionsslider.h",
    "content": "#pragma once\n\n#include \"optionsentry.h\"\n#include \"../scrollbar.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\nnamespace CHLCC {\n\nclass OptionsSlider : public OptionsEntry {\n public:\n  OptionsSlider(float& value, float min, float max, Sprite bar, Sprite fill,\n                glm::vec2 topRight, RectF hoverBounds,\n                std::function<void(OptionsEntry*)> highlight,\n                std::optional<Sprite> mutedSprite = std::nullopt);\n\n  void Render() override;\n  void Update(float dt) override;\n  void UpdateInput(float dt) override;\n\n  void UpdateValue() override;\n\n  void Show() override;\n  void Hide() override;\n\n  void Move(glm::vec2 relativePos) override;\n\n private:\n  float OldProgress;\n  bool Changing = false;\n  Animation ChangingFadeAnimation;\n\n  Scrollbar Slider;\n  Sprite BarSprite;\n\n  std::optional<Sprite> MutedSprite;\n  std::optional<ClickArea> MuteClickArea;\n  float BeforeMutedProgress;\n};\n\n}  // namespace CHLCC\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto\n"
  },
  {
    "path": "src/ui/widgets/chlcc/saveentrybutton.cpp",
    "content": "#include \"saveentrybutton.h\"\n\n#include \"../../../renderer/renderer.h\"\n#include \"../../../profile/dialogue.h\"\n#include \"../../../profile/ui/savemenu.h\"\n#include \"../../../profile/games/chlcc/savemenu.h\"\n#include \"../../../games/chlcc/savesystem.h\"\n#include \"../../../profile/game.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\nnamespace CHLCC {\n\nusing namespace Impacto::Profile::SaveMenu;\nusing namespace Impacto::Profile::CHLCC::SaveMenu;\nusing namespace Impacto::SaveSystem;\n\nglm::vec4 SaveEntryButton::FocusedAlpha = glm::vec4(1.0f);\nAnimation SaveEntryButton::FocusedAlphaFade;\n\nSaveEntryButton::SaveEntryButton(int id, Sprite const& norm,\n                                 Sprite const& focused, Sprite const& highlight,\n                                 glm::vec2 pos, int page, uint8_t locked,\n                                 Sprite lockedSymbol)\n    : Widgets::Button(id, norm, focused, highlight, pos),\n      Page(page),\n      FocusedSpriteLabel(focused, pos),\n      LockedSymbol(lockedSymbol, pos) {\n  IsLocked = locked == 1;\n}\n\nvoid SaveEntryButton::Render() {\n  NormalSpriteLabel.Render();\n  if (HasFocus) {\n    SelectionMarkerLabel.Render();\n    FocusedSpriteLabel.Tint = FocusedAlpha;\n    FocusedSpriteLabel.Render();\n  }\n  ThumbnailLabel.Render();\n  EntryNumberHint.Render();\n  EntryNumber.Render();\n  if (EntryActive) {\n    if (IsLocked) {\n      LockedSymbol.Render();\n    }\n    SceneTitle.Render();\n    PlayTimeHint.Render();\n    PlayTime.Render();\n    SaveDateHint.Render();\n    SaveDate.Render();\n    SaveHour.Render();\n  } else {\n    SceneTitle.Render();\n  }\n}\n\nint SaveEntryButton::GetPage() const { return Page; }\n\nvoid SaveEntryButton::AddNormalSpriteLabel(Sprite norm, glm::vec2 pos) {\n  NormalSpriteLabel = Label(norm, pos);\n}\n\nvoid SaveEntryButton::AddSelectionMarkerLabel(Sprite norm, glm::vec2 pos) {\n  SelectionMarkerLabel = Label(norm, pos);\n}\n\nvoid SaveEntryButton::AddEntryNumberHintText(Vm::BufferOffsetContext strAdr,\n                                             float fontSize,\n                                             RendererOutlineMode outlineMode,\n                                             glm::vec2 relativePosition) {\n  EntryNumberHint =\n      Label(strAdr, glm::vec2(Bounds.X, Bounds.Y) + relativePosition, fontSize,\n            outlineMode, IsLocked ? 69 : 0);\n}\n\nvoid SaveEntryButton::AddEntryNumberText(std::string_view str, float fontSize,\n                                         RendererOutlineMode outlineMode,\n                                         glm::vec2 relativePosition) {\n  EntryNumber = Label(str, glm::vec2(Bounds.X, Bounds.Y) + relativePosition,\n                      fontSize, outlineMode, IsLocked ? 69 : 0);\n}\n\nvoid SaveEntryButton::AddSceneTitleText(Vm::BufferOffsetContext strAdr,\n                                        float fontSize,\n                                        RendererOutlineMode outlineMode,\n                                        glm::vec2 relativeTitlePosition,\n                                        int colorIndex) {\n  SceneTitle =\n      Label(strAdr, glm::vec2(Bounds.X, Bounds.Y) + relativeTitlePosition,\n            fontSize, outlineMode, colorIndex, MaxTitleWidth);\n}\n\nvoid SaveEntryButton::AddPlayTimeHintText(Vm::BufferOffsetContext strAdr,\n                                          float fontSize,\n                                          RendererOutlineMode outlineMode,\n                                          glm::vec2 relativePosition) {\n  PlayTimeHint = Label(strAdr, glm::vec2(Bounds.X, Bounds.Y) + relativePosition,\n                       fontSize, outlineMode, IsLocked ? 69 : 0);\n}\n\nvoid SaveEntryButton::AddPlayTimeText(std::string_view str, float fontSize,\n                                      RendererOutlineMode outlineMode,\n                                      glm::vec2 relativePosition) {\n  // Spacing is currently set for the C;HLCC font, more or less\n  PlayTime = Label(str, glm::vec2(Bounds.X, Bounds.Y) + relativePosition,\n                   fontSize, outlineMode, IsLocked ? 69 : 0);\n}\n\nvoid SaveEntryButton::AddSaveDateHintText(Vm::BufferOffsetContext strAdr,\n                                          float fontSize,\n                                          RendererOutlineMode outlineMode,\n                                          glm::vec2 relativePosition) {\n  SaveDateHint = Label(strAdr, glm::vec2(Bounds.X, Bounds.Y) + relativePosition,\n                       fontSize, outlineMode, IsLocked ? 69 : 0);\n}\n\nvoid SaveEntryButton::AddSaveDateText(std::string_view str, float fontSize,\n                                      RendererOutlineMode outlineMode,\n                                      glm::vec2 relativePosition) {\n  // Spacing is currently set for the C;HLCC font, more or less\n  SaveDate = Label(str, glm::vec2(Bounds.X, Bounds.Y) + relativePosition,\n                   fontSize, outlineMode, IsLocked ? 69 : 0);\n}\n\nvoid SaveEntryButton::AddSaveHourText(std::string_view str, float fontSize,\n                                      RendererOutlineMode outlineMode,\n                                      glm::vec2 relativePosition) {\n  // Spacing is currently set for the C;HLCC font, more or less\n  SaveHour = Label(str, glm::vec2(Bounds.X, Bounds.Y) + relativePosition,\n                   fontSize, outlineMode, IsLocked ? 69 : 0);\n}\n\nvoid SaveEntryButton::AddThumbnail(Sprite thumbnail, glm::vec2 pos) {\n  ThumbnailLabel = Label(thumbnail, pos);\n}\n\nvoid SaveEntryButton::Move(glm::vec2 relativePosition) {\n  NormalSpriteLabel.Move(relativePosition);\n  SelectionMarkerLabel.Move(relativePosition);\n  FocusedSpriteLabel.Move(relativePosition);\n  LockedSymbol.Move(relativePosition);\n  ThumbnailLabel.Move(relativePosition);\n  EntryNumberHint.Move(relativePosition);\n  EntryNumber.Move(relativePosition);\n  SceneTitle.Move(relativePosition);\n  PlayTimeHint.Move(relativePosition);\n  PlayTime.Move(relativePosition);\n  SaveDateHint.Move(relativePosition);\n  SaveDate.Move(relativePosition);\n  SaveHour.Move(relativePosition);\n}\n\nvoid SaveEntryButton::FocusedAlphaFadeStart() {\n  if (FocusedAlphaFade.State == AnimationState::Stopped) {\n    FocusedAlphaFade.Direction = AnimationDirection::In;\n    FocusedAlphaFade.SetDuration(0.5f);\n    FocusedAlphaFade.LoopMode = AnimationLoopMode::ReverseDirection;\n    FocusedAlphaFade.StartIn();\n  }\n}\n\nvoid SaveEntryButton::FocusedAlphaFadeReset() {\n  FocusedAlphaFade.State = AnimationState::Stopped;\n  FocusedAlphaFade.Progress = 0.0f;\n}\n\nvoid SaveEntryButton::UpdateFocusedAlphaFade(float dt) {\n  FocusedAlphaFade.Update(dt);\n  FocusedAlpha =\n      glm::vec4(glm::vec3(1.0f), ((FocusedAlphaFade.Progress * 30) + 1) / 85);\n}\n\nvoid SaveEntryButton::RefreshInfo(const SaveSystem::SaveType entryType) {\n  const Sprite entrySprite = [] {\n    switch (*UI::SaveMenuPtr->ActiveMenuType) {\n      case SaveMenuPageType::QuickLoad:\n        return QuickLoadEntrySprite;\n        break;\n      case SaveMenuPageType::Save:\n        return SaveEntrySprite;\n        break;\n      case SaveMenuPageType::Load:\n        return LoadEntrySprite;\n        break;\n\n      default:\n        throw std::runtime_error(\n            fmt::format(\"Unexpected SaveMenuPageType: %d\",\n                        static_cast<int>(*UI::SaveMenuPtr->ActiveMenuType)));\n    }\n  }();\n\n  IsLocked = SaveSystem::GetSaveFlags(entryType, Id) & WriteProtect;\n\n  AddNormalSpriteLabel(entrySprite, EntryPositions[Id % 6]);\n  AddSelectionMarkerLabel(SelectionMarkerSprite,\n                          {EntryPositions[Id % 6].x - SelectionMarkerOffset.x,\n                           EntryPositions[Id % 6].y + SelectionMarkerOffset.y});\n  AddEntryNumberHintText(Vm::ScriptGetTextTableStrAddress(0, 6), 18,\n                         RendererOutlineMode::BottomRight,\n                         EntryNumberHintTextRelativePos);\n  AddEntryNumberText(fmt::format(\"{:02}\", Id + 1), 18,\n                     RendererOutlineMode::BottomRight,\n                     EntryNumberTextRelativePos);\n\n  FocusedSpriteLabel.MoveTo(EntryPositions[Id % 6]);\n  LockedSymbol.MoveTo(EntryPositions[Id % 6] + LockedSymbolRelativePos);\n\n  switch (SaveSystem::GetSaveStatus(entryType, Id)) {\n    case 0: {  // No data\n      EntryActive = false;\n\n      AddSceneTitleText(Vm::ScriptGetTextTableStrAddress(0, 1), 24,\n                        RendererOutlineMode::BottomRight, NoDataTextRelativePos,\n                        0);\n      AddThumbnail(EmptyThumbnailSprite,\n                   EntryPositions[Id % 6] + ThumbnailRelativePos);\n    } break;\n\n    case 1: {  // Filled\n      EntryActive = true;\n\n      AddSceneTitleText(Vm::ScriptGetTextTableStrAddress(\n                            1, SaveSystem::GetSaveTitle(entryType, Id)),\n                        24, RendererOutlineMode::BottomRight,\n                        SceneTitleTextRelativePos, IsLocked ? 69 : 0);\n      AddPlayTimeHintText(Vm::ScriptGetTextTableStrAddress(0, 2), 18,\n                          RendererOutlineMode::BottomRight,\n                          PlayTimeHintTextRelativePos);\n\n      const uint32_t time = SaveSystem::GetSavePlayTime(entryType, Id);\n      const uint32_t hours = time / 3600;\n      const uint32_t minutes = (time % 3600) / 60;\n      const uint32_t seconds = (time % 3600) % 60;\n      AddPlayTimeText(fmt::format(\"{:3}:{:02}:{:02}\", hours, minutes, seconds),\n                      18, RendererOutlineMode::BottomRight,\n                      {PlayTimeTextRelativePos.x + (float)((hours < 10) * 10),\n                       PlayTimeTextRelativePos.y});\n\n      AddSaveDateHintText(Vm::ScriptGetTextTableStrAddress(0, 3), 18,\n                          RendererOutlineMode::BottomRight,\n                          SaveDateHintTextRelativePos);\n      const tm& date = SaveSystem::GetSaveDate(entryType, Id);\n      AddSaveDateText(\n          \"  \" + fmt::format(\n                     fmt::runtime(Profile::DateFormat.FormattedString()), date),\n          18, RendererOutlineMode::BottomRight, SaveDateTextRelativePos);\n      AddSaveHourText(fmt::format(\"  {:%H:%M:%S}\", date), 18,\n                      RendererOutlineMode::BottomRight,\n                      SaveHourTextRelativePos);\n\n      AddThumbnail(SaveSystem::GetSaveThumbnail(entryType, Id),\n                   EntryPositions[Id % 6] + ThumbnailRelativePos);\n    } break;\n\n    default:\n      ImpLogSlow(LogLevel::Error, LogChannel::General,\n                 \"Unexpected entry status\");\n      [[fallthrough]];\n    case 2: {  // Corrupted\n      EntryActive = false;\n\n      AddSceneTitleText(Vm::ScriptGetTextTableStrAddress(0, 0), 24,\n                        RendererOutlineMode::BottomRight, NoDataTextRelativePos,\n                        53);\n      AddThumbnail(EmptyThumbnailSprite,\n                   EntryPositions[Id % 6] + ThumbnailRelativePos);\n    } break;\n  }\n}\n\nvoid SaveEntryButton::ToggleLock(const SaveSystem::SaveType entryType) {\n  const uint8_t newLock =\n      SaveSystem::GetSaveFlags(entryType, Id) ^ WriteProtect;\n  SaveSystem::SetSaveFlags(entryType, Id, newLock);\n  IsLocked = newLock & WriteProtect;\n}\n\n}  // namespace CHLCC\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto\n"
  },
  {
    "path": "src/ui/widgets/chlcc/saveentrybutton.h",
    "content": "#pragma once\n\n#include <string>\n\n#include \"../button.h\"\n#include \"../label.h\"\n#include \"../../../renderer/renderer.h\"\n#include \"../../../data/savesystem.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\nnamespace CHLCC {\n\nclass SaveEntryButton : public Widgets::Button {\n public:\n  SaveEntryButton(int id, Sprite const& norm, Sprite const& focused,\n                  Sprite const& highlight, glm::vec2 pos, int page,\n                  uint8_t locked, Sprite lockedSymbol);\n  void Render() override;\n  int GetPage() const;\n  void AddNormalSpriteLabel(Sprite norm, glm::vec2 pos);\n  void AddSelectionMarkerLabel(Sprite norm, glm::vec2 pos);\n  void AddEntryNumberHintText(Vm::BufferOffsetContext strAdr, float fontSize,\n                              RendererOutlineMode outlineMode,\n                              glm::vec2 relativePosition);\n  void AddEntryNumberText(std::string_view str, float fontSize,\n                          RendererOutlineMode outlineMode,\n                          glm::vec2 relativePosition);\n  void AddPlayTimeHintText(Vm::BufferOffsetContext strAdr, float fontSize,\n                           RendererOutlineMode outlineMode,\n                           glm::vec2 relativePosition);\n  void AddPlayTimeText(std::string_view str, float fontSize,\n                       RendererOutlineMode outlineMode,\n                       glm::vec2 relativePosition);\n  void AddSaveDateHintText(Vm::BufferOffsetContext strAdr, float fontSize,\n                           RendererOutlineMode outlineMode,\n                           glm::vec2 relativePosition);\n  void AddSaveDateText(std::string_view str, float fontSize,\n                       RendererOutlineMode outlineMode,\n                       glm::vec2 relativePosition);\n  void AddSaveHourText(std::string_view str, float fontSize,\n                       RendererOutlineMode outlineMode,\n                       glm::vec2 relativePosition);\n  void AddSceneTitleText(Vm::BufferOffsetContext strAdr, float fontSize,\n                         RendererOutlineMode outlineMode,\n                         glm::vec2 relativeTitlePosition, int colorIndex);\n  void AddThumbnail(Sprite thumbnail, glm::vec2 pos);\n\n  using Widget::Move;\n  void Move(glm::vec2 pos) override;\n\n  static void FocusedAlphaFadeStart();\n  static void FocusedAlphaFadeReset();\n  static void UpdateFocusedAlphaFade(float dt);\n\n  void RefreshInfo(SaveSystem::SaveType entryType);\n\n  void ToggleLock(SaveSystem::SaveType entryType);\n  bool EntryActive = false;\n\n private:\n  int Page;\n  Label NormalSpriteLabel;\n  Label SelectionMarkerLabel;\n  Label FocusedSpriteLabel;\n  Label LockedSymbol;\n  static glm::vec4 FocusedAlpha;\n  static Animation FocusedAlphaFade;\n  Label ThumbnailLabel;\n  Label EntryNumberHint;\n  Label EntryNumber;\n  Label SceneTitle;\n  Label PlayTimeHint;\n  Label PlayTime;\n  Label SaveDateHint;\n  Label SaveDate;\n  Label SaveHour;\n};\n\n}  // namespace CHLCC\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/widgets/chlcc/systemmenuentrybutton.cpp",
    "content": "#include \"systemmenuentrybutton.h\"\n\n#include \"../../../profile/games/chlcc/systemmenu.h\"\n\n#include \"../../../renderer/renderer.h\"\n#include \"../../../vm/interface/input.h\"\n#include \"../../../inputsystem.h\"\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\nnamespace CHLCC {\n\nusing namespace Profile::CHLCC::SystemMenu;\n\nSystemMenuEntryButton::SystemMenuEntryButton(int id, Sprite const& norm,\n                                             Sprite const& focused,\n                                             glm::vec4 focusTint,\n                                             Sprite const& highlight,\n                                             glm::vec2 pos, RectF hoverBounds)\n    : Button(id, norm, focused, highlight, pos, hoverBounds),\n      FocusTint(focusTint) {\n  HighlightOffset = glm::vec2(3.0f, 3.0f);\n  StarAnimation.SetDuration(StarAnimationDuration);\n}\n\nvoid SystemMenuEntryButton::Render() {\n  if (HasFocus) {\n    Renderer->DrawSprite(\n        NormalSprite,\n        glm::vec2(Bounds.X + HighlightOffset.x, Bounds.Y + HighlightOffset.y),\n        RgbIntToFloat(0x28537f));\n  }\n\n  glm::vec4 tint = IsLocked   ? RgbIntToFloat(0x808080)\n                   : HasFocus ? FocusTint\n                              : glm::vec4(1.0f);\n  Renderer->DrawSprite(NormalSprite, glm::vec2(Bounds.X, Bounds.Y), tint);\n  if (!StarAnimation.IsPlaying()) return;\n\n  const glm::vec2 starAnchor = glm::vec2(Bounds.X, Bounds.Y + Bounds.Height);\n\n  const float rotationAngle =\n      StarAnimationDuration * StarAnimation.Progress * StarRotationSpeed -\n      LeftAngle;\n\n  const glm::vec4 starOpacity =\n      glm::vec4({1.0, 1.0, 1.0, 1.0f - StarAnimation.Progress});\n\n  for (int i = 0; i < STAR_COUNT; i++) {\n    const glm::vec2 currentPos =\n        starAnchor + glm::mix(StarsOffsetsStart[i], StarsOffsetsEnd[i],\n                              StarAnimation.Progress);\n    // left stars rotate clockwise, right stars rotate counter-clockwise\n    const float currentAngle =\n        i < STAR_COUNT / 2 ? rotationAngle : -rotationAngle;\n    const CornersQuad dest = StarSprite.ScaledBounds()\n                                 .RotateAroundCenter(currentAngle)\n                                 .Translate(currentPos);\n\n    Renderer->DrawSprite(StarSprite, dest, starOpacity);\n  }\n}\nglm::vec2 SystemMenuEntryButton::RotatePoint(const glm::vec2& point,\n                                             const glm::vec2& center,\n                                             float angleRadians) {\n  float s = sin(-angleRadians);\n  float c = cos(-angleRadians);\n\n  glm::vec2 translated = point - center;\n\n  float xnew = translated.x * c - translated.y * s;\n  float ynew = translated.x * s + translated.y * c;\n\n  return glm::vec2{xnew, ynew} + center;\n}\n\nvoid SystemMenuEntryButton::Update(float dt) {\n  Button::Update(dt);\n  StarAnimation.Update(dt);\n}\n\nvoid SystemMenuEntryButton::UpdateInput(float dt) {\n  if (Enabled) {\n    constexpr float rotationAngle = -15.0f * std::numbers::pi_v<float> / 180.0f;\n    const RectF& bounds = (HoverBounds != RectF{}) ? HoverBounds : Bounds;\n    glm::vec2 center = bounds.Center();\n    glm::vec2 rotatedPos;\n    if (Input::CurrentInputDevice == Input::Device::Mouse &&\n        Input::PrevMousePos != Input::CurMousePos) {\n      rotatedPos = RotatePoint(Input::CurMousePos, center, rotationAngle);\n      Hovered = bounds.ContainsPoint(rotatedPos);\n    } else if (Input::CurrentInputDevice == Input::Device::Touch &&\n               Input::TouchIsDown[0] &&\n               Input::PrevTouchPos != Input::CurTouchPos) {\n      rotatedPos = RotatePoint(Input::CurTouchPos, center, rotationAngle);\n      Hovered = bounds.ContainsPoint(rotatedPos);\n    }\n    if (OnClickHandler && HasFocus &&\n        ((Hovered &&\n          Vm::Interface::PADinputMouseWentDown & Vm::Interface::PAD1A) ||\n         (Vm::Interface::PADinputButtonWentDown & Vm::Interface::PAD1A))) {\n      OnClickHandler(this);\n      if (IsLocked) return;\n      // reset animation only if it's not playing already\n      StarAnimation.StartIn(StarAnimation.State != AnimationState::Playing);\n    }\n  }\n}\n\n}  // namespace CHLCC\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/widgets/chlcc/systemmenuentrybutton.h",
    "content": "#pragma once\n\n#include \"../button.h\"\n#include \"../../../animation.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\nnamespace CHLCC {\n\nclass SystemMenuEntryButton : public Button {\n public:\n  SystemMenuEntryButton(int id, Sprite const& norm, Sprite const& focused,\n                        glm::vec4 focusTint, Sprite const& highlight,\n                        glm::vec2 pos, RectF hoverBounds);\n  void Render() override;\n  void Update(float dt) override;\n  void UpdateInput(float dt) override;\n\n private:\n  glm::vec2 RotatePoint(const glm::vec2& point, const glm::vec2& center,\n                        float angleRadians);\n\n protected:\n  Animation StarAnimation;\n  glm::vec4 FocusTint;\n};\n\n}  // namespace CHLCC\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/widgets/chlcc/systemmessagebutton.cpp",
    "content": "#include \"systemmessagebutton.h\"\n\n#include \"../../../profile/dialogue.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\nnamespace CHLCC {\n\nSystemMessageButton::SystemMessageButton(int id, Sprite const& norm,\n                                         Sprite const& focused,\n                                         Sprite const& highlightLeft,\n                                         Sprite const& highlightMiddle,\n                                         Sprite const& highlightRight,\n                                         glm::vec2 pos, RectF hoverBounds)\n    : Button(id, norm, focused, highlightMiddle, pos, hoverBounds) {\n  LeftHighlightSprite = highlightLeft;\n  RightHighlightSprite = highlightRight;\n}\n\nvoid SystemMessageButton::Render() {\n  if (HasFocus) {\n    const RectF middleDest =\n        HighlightSprite.ScaledBounds()\n            .Scale({Bounds.Width / HighlightSprite.ScaledWidth(), 1.0f},\n                   {0.0f, 0.0f})\n            .Translate(Bounds.GetPos());\n    const RectF leftDest =\n        LeftHighlightSprite.ScaledBounds().Translate(LeftHighlightPos);\n\n    const RectF rightDest =\n        RightHighlightSprite.ScaledBounds().Translate(RightHighlightPos);\n\n    Renderer->DrawSprite(LeftHighlightSprite, leftDest, Tint);\n    Renderer->DrawSprite(HighlightSprite, middleDest, Tint);\n    Renderer->DrawSprite(RightHighlightSprite, rightDest, Tint);\n  }\n\n  if (HasText) {\n    Renderer->DrawProcessedText(Text, Profile::Dialogue::DialogueFont, Tint.a,\n                                OutlineMode, true);\n  }\n}\n\nvoid SystemMessageButton::SetText(std::vector<ProcessedTextGlyph> text,\n                                  float textWidth, float fontSize,\n                                  RendererOutlineMode outlineMode) {\n  HasText = true;\n  Text = std::move(text);\n  TextWidth = textWidth;\n  OutlineMode = outlineMode;\n  HighlightOffset = glm::vec2(0.0f, -1.0f);\n\n  Bounds = RectF(Text[0].DestRect.X, Text[0].DestRect.Y, TextWidth, fontSize) +\n           HighlightOffset;\n\n  const glm::vec2 leftSize = LeftHighlightSprite.ScaledBounds().GetSize();\n  const float leftWidth = std::round(leftSize.x * fontSize / leftSize.y);\n\n  const glm::vec2 rightSize = RightHighlightSprite.ScaledBounds().GetSize();\n  const float rightWidth = std::round(rightSize.x * fontSize / rightSize.y);\n\n  LeftHighlightPos = {Bounds.X - leftWidth - 1.0f, Bounds.Y};\n  RightHighlightPos = {Bounds.X + TextWidth, Bounds.Y};\n  HoverBounds = RectF(LeftHighlightPos.x, Bounds.Y,\n                      TextWidth + leftWidth + rightWidth, fontSize);\n}\n\n}  // namespace CHLCC\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/widgets/chlcc/systemmessagebutton.h",
    "content": "#pragma once\n\n#include \"../button.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\nnamespace CHLCC {\n\nclass SystemMessageButton : public Button {\n public:\n  SystemMessageButton(int id, Sprite const& norm, Sprite const& focused,\n                      Sprite const& highlightLeft,\n                      Sprite const& highlightMiddle,\n                      Sprite const& highlightRight, glm::vec2 pos,\n                      RectF hoverBounds = RectF{});\n\n  virtual void Render() override;\n\n  void SetText(std::vector<ProcessedTextGlyph> text, float textWidth,\n               float fontSize, RendererOutlineMode outlineMode) override;\n\n protected:\n  Sprite LeftHighlightSprite;\n  Sprite RightHighlightSprite;\n  glm::vec2 LeftHighlightPos;\n  glm::vec2 RightHighlightPos;\n};\n\n}  // namespace CHLCC\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/widgets/chlcc/tipsentrybutton.cpp",
    "content": "#include \"tipsentrybutton.h\"\n\n#include \"../../../renderer/renderer.h\"\n#include \"../../../profile/dialogue.h\"\n#include \"../../../profile/games/chlcc/tipsmenu.h\"\n#include \"../../../text/text.h\"\n#include \"../../../vm/vm.h\"\n#include \"../../../inputsystem.h\"\n#include \"../../../vm/interface/input.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\nnamespace CHLCC {\n\nusing namespace Impacto::TipsSystem;\n\nusing namespace Impacto::Profile::CHLCC::TipsMenu;\nTipsEntryButton::TipsEntryButton(int id, TipsDataRecord* tipRecord,\n                                 RectF const& dest, Sprite const& highlight)\n    : TipEntryRecord(tipRecord), PrevUnreadState(tipRecord->IsUnread) {\n  Id = id;\n  Bounds = dest;\n  HighlightSprite = highlight;\n  Enabled = true;\n  HasText = true;\n  HighlightOffset = {0.0f, 0.0f};\n  TextLayoutPlainString(fmt::format(\"{:3d}.\", id + 1), TipNumber,\n                        Profile::Dialogue::DialogueFont, TipListEntryFontSize,\n                        Profile::Dialogue::ColorTable[DefaultColorIndex], 1.0f,\n                        glm::vec2(Bounds.X + TipListEntryNameXOffset - 5.0f +\n                                      TipListEntryTextOffsetX,\n                                  Bounds.Y),\n                        TextAlignment::Right);\n\n  Vm::Sc3VmThread dummy;\n  dummy.IpOffset = tipRecord->StringAdr[0];\n  dummy.ScriptBufferId = TipsSystem::GetTipsScriptBufferId();\n  auto textColorIndex =\n      (tipRecord->IsUnread) ? UnreadColorIndex : DefaultColorIndex;\n  Text = TextLayoutPlainLine(\n      &dummy, 255, Profile::Dialogue::DialogueFont, TipListEntryFontSize,\n      Profile::Dialogue::ColorTable[textColorIndex], 1.0f,\n      glm::vec2(Bounds.X + TipListEntryNameXOffset + TipListEntryTextOffsetX,\n                Bounds.Y),\n      TextAlignment::Left);\n\n  auto lockedScrPos =\n      Vm::ScriptGetTextTableStrAddress(TipsStringTable, LockedTipsIndex);\n  dummy.IpOffset = lockedScrPos.IpOffset;\n  dummy.ScriptBufferId = lockedScrPos.ScriptBufferId;\n  TextLayoutPlainLine(\n      &dummy, static_cast<int>(TipLockedText.size()), TipLockedText,\n      Profile::Dialogue::DialogueFont, TipListEntryFontSize,\n      Profile::Dialogue::ColorTable[UnreadColorIndex], 1.0f,\n      glm::vec2(Bounds.X + TipListEntryNameXOffset + TipListEntryTextOffsetX,\n                Bounds.Y),\n      TextAlignment::Left);\n}\n\nvoid TipsEntryButton::Move(glm::vec2 relativePos) {\n  Button::Move(relativePos);\n  for (size_t i = 0; i < TipNumber.size(); i++) {\n    TipNumber[i].DestRect += relativePos;\n  }\n  for (size_t i = 0; i < TipLockedText.size(); i++) {\n    TipLockedText[i].DestRect += relativePos;\n  }\n}\n\nvoid TipsEntryButton::Update(float dt) {\n  Button::Update(dt);\n  if (PrevUnreadState != TipEntryRecord->IsUnread) {\n    int colorIndex = DefaultColorIndex;\n    if (TipEntryRecord->IsUnread) {\n      colorIndex = UnreadColorIndex;\n    }\n    for (ProcessedTextGlyph& glyph : Text) {\n      glyph.Colors = Profile::Dialogue::ColorTable[colorIndex];\n    }\n    PrevUnreadState = TipEntryRecord->IsUnread;\n  }\n}\n\nvoid TipsEntryButton::Render() {\n  if (HasFocus) {\n    Renderer->DrawSprite(HighlightSprite, Bounds.GetPos() + HighlightOffset,\n                         Tint);\n    Renderer->DrawSprite(\n        TipsEntryHighlightDot,\n        Bounds.GetPos() + HighlightOffset + TipsListEntryDotOffset, Tint);\n  }\n\n  Renderer->DrawProcessedText(TipNumber, Profile::Dialogue::DialogueFont,\n                              Tint.a, RendererOutlineMode::Full);\n  if (TipEntryRecord->IsLocked) {\n    Renderer->DrawProcessedText(TipLockedText, Profile::Dialogue::DialogueFont,\n                                Tint.a, RendererOutlineMode::Full);\n  } else {\n    Renderer->DrawProcessedText(Text, Profile::Dialogue::DialogueFont, Tint.a,\n                                RendererOutlineMode::Full);\n    if (TipEntryRecord->IsNew) {\n      Renderer->DrawSprite(TipsEntryNewDot,\n                           Bounds.GetPos() + TipsListNewDotOffset, Tint);\n    }\n  }\n}\n\n}  // namespace CHLCC\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/widgets/chlcc/tipsentrybutton.h",
    "content": "#pragma once\n\n#include \"../button.h\"\n#include \"../../../data/tipssystem.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\nnamespace CHLCC {\nclass TipsEntryButton : public Button {\n public:\n  TipsEntryButton(int id, TipsSystem::TipsDataRecord* tipRecord,\n                  RectF const& dest, Sprite const& highlight);\n  void Render() override;\n  void Update(float dt) override;\n  void Move(glm::vec2 relativePos) override;\n\n  TipsSystem::TipsDataRecord* TipEntryRecord;\n\n private:\n  std::array<ProcessedTextGlyph, 4> TipNumber;\n  std::array<ProcessedTextGlyph, 3> TipLockedText;\n  bool PrevUnreadState;\n};\n\n}  // namespace CHLCC\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/widgets/chlcc/titlebutton.cpp",
    "content": "#include \"titlebutton.h\"\n\n#include \"../../../renderer/renderer.h\"\n#include \"../../../profile/games/chlcc/titlemenu.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\nnamespace CHLCC {\n\nusing namespace Impacto::Profile::CHLCC::TitleMenu;\n\nvoid TitleButton::Render() {\n  if (HasFocus) {\n    if (!IsSubButton) {  // Main buttons\n      Renderer->DrawSprite(HighlightSprite,\n                           Bounds.GetPos() - ItemHighlightOffset, Tint);\n      Renderer->DrawSprite(FocusedSprite, Bounds.GetPos(), Tint);\n    } else {  // Sub buttons\n      Renderer->DrawSprite(\n          HighlightSprite,\n          glm::vec2(SecondaryItemHighlightX, Bounds.Y - ItemHighlightOffset.y),\n          Tint);\n      Renderer->DrawSprite(FocusedSprite, Bounds.GetPos(), Tint);\n      Renderer->DrawSprite(LineDecoration, glm::vec2(SecondaryMenuLineX, LineY),\n                           Tint);\n    }\n  } else {\n    if (Enabled) {\n      Renderer->DrawSprite(NormalSprite, Bounds.GetPos(), Tint);\n    } else {\n      Renderer->DrawSprite(DisabledSprite, Bounds.GetPos(), Tint);\n    }\n  }\n}\n\n}  // namespace CHLCC\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/widgets/chlcc/titlebutton.h",
    "content": "#pragma once\n\n#include \"../button.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\nnamespace CHLCC {\n\nclass TitleButton : public Widgets::Button {\n public:\n  TitleButton(int id, Sprite const& norm, Sprite const& focused,\n              Sprite const& highlight, glm::vec2 pos)\n      : Widgets::Button(id, norm, focused, highlight, pos) {}\n  void Render() override;\n  bool IsSubButton = false;\n  Sprite LineDecoration;\n  float LineY;\n};\n\n}  // namespace CHLCC\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/widgets/chlcc/trackselectbutton.cpp",
    "content": "#include \"trackselectbutton.h\"\n#include \"../../../renderer/renderer.h\"\n#include \"../../../profile/games/chlcc/musicmenu.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\nnamespace CHLCC {\nusing namespace Impacto::Profile::CHLCC::MusicMenu;\n\nTrackSelectButton::TrackSelectButton(int id, Sprite const& focused,\n                                     glm::vec2 pos, glm::vec2 numOffset,\n                                     glm::vec2 trackOffset,\n                                     glm::vec2 artistOffset)\n    : Button(id, Sprite(), focused, Sprite(), pos),\n      TrackNumPos(pos + numOffset),\n      TrackTextPos(pos + trackOffset),\n      ArtistTextPos(pos + artistOffset) {\n  const std::string label = fmt::sprintf(\"%02d\", id + 1);\n  TrackNum = Label(label, TrackNumPos, 20, RendererOutlineMode::None, 0);\n  HasText = true;\n  Bounds =\n      RectF(pos.x, pos.y, FocusedSprite.Bounds.Width - pos.x, TrackOffset.y);\n}\n\nvoid TrackSelectButton::SetTrackText(Vm::BufferOffsetContext strAdr) {\n  TrackName = Label(strAdr, TrackTextPos, 20, RendererOutlineMode::None, 0);\n}\n\nvoid TrackSelectButton::SetArtistText(Vm::BufferOffsetContext strAdr) {\n  Artist = Label(strAdr, ArtistTextPos, 20, RendererOutlineMode::None, 0);\n}\n\nvoid TrackSelectButton::Render() {\n  if (HasFocus) {\n    // adjusts sprite height to prevent visual bug tied to mouse support (1px of\n    // out of bounds highlight sprite can be visible)\n    RectF dest = RectF(0, Bounds.Y, FocusedSprite.ScaledWidth(), TrackOffset.y);\n    Renderer->DrawSprite(FocusedSprite, dest);\n  }\n\n  TrackNum.Render();\n  TrackName.Render();\n  Artist.Render();\n}\n\nvoid TrackSelectButton::MoveTracks(glm::vec2 baseline) {\n  TrackNum.MoveTo(TrackNumPos + baseline);\n  TrackName.MoveTo(TrackTextPos + baseline);\n  Artist.MoveTo(ArtistTextPos + baseline);\n}\n\n}  // namespace CHLCC\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto\n"
  },
  {
    "path": "src/ui/widgets/chlcc/trackselectbutton.h",
    "content": "#pragma once\n\n#include \"../button.h\"\n#include \"../../../profile/dialogue.h\"\n#include \"../label.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\nnamespace CHLCC {\n\nclass TrackSelectButton : public Button {\n public:\n  TrackSelectButton(int id, Sprite const& focused, glm::vec2 pos,\n                    glm::vec2 numOffset, glm::vec2 trackOffset,\n                    glm::vec2 artistOffset);\n  void SetTrackText(Vm::BufferOffsetContext strAdr);\n  void SetArtistText(Vm::BufferOffsetContext strAdr);\n  void Render() override;\n  void MoveTracks(glm::vec2 baseline);\n\n private:\n  Label TrackNum;\n  glm::vec2 TrackNumPos;\n\n  Label TrackName;\n  glm::vec2 TrackTextPos;\n\n  Label Artist;\n  glm::vec2 ArtistTextPos;\n};\n}  // namespace CHLCC\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto\n"
  },
  {
    "path": "src/ui/widgets/chlcc/trophymenuentry.cpp",
    "content": "#include \"trophymenuentry.h\"\n\n#include \"../../../data/achievementsystem.h\"\n#include \"../../../games/chlcc/trophymenu.h\"\n#include \"../../../profile/games/chlcc/trophymenu.h\"\n#include \"../../../profile/game.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\nnamespace CHLCC {\n\nusing namespace Impacto::Profile::CHLCC::TrophyMenu;\nusing namespace Impacto::AchievementSystem;\n\nTrophyMenuEntry::TrophyMenuEntry(int achievementId)\n    : AchievementId(achievementId) {\n  Bounds.SetPos(\n      FirstEntryPos +\n      glm::vec2(0.0f, EntryHeight * (AchievementId % EntriesPerPage)));\n\n  const auto* ach = AchievementSystem::GetAchievement(AchievementId);\n  if (ach == nullptr) {\n    NameLabel =\n        Label(Vm::ScriptGetTextTableStrAddress(EntryDefaultNameTextTableId,\n                                               EntryDefaultNameStringNum),\n              Bounds.GetPos() + EntryNameOffset, EntryNameFontSize,\n              RendererOutlineMode::BottomRight, 0);\n    DescriptionLabel =\n        Label(\"\", Bounds.GetPos() + EntryDescriptionOffset,\n              EntryDescriptionFontSize, RendererOutlineMode::BottomRight, 0);\n    Icon = DefaultTrophyIconSprite;\n  } else {\n    NameLabel = Label(ach->Name(), Bounds.GetPos() + EntryNameOffset,\n                      EntryNameFontSize, RendererOutlineMode::BottomRight, 0);\n    DescriptionLabel =\n        Label(ach->Description(), Bounds.GetPos() + EntryDescriptionOffset,\n              EntryDescriptionFontSize, RendererOutlineMode::BottomRight, 0);\n    Icon = ach->Icon();\n  }\n  IconDest = RectF{Bounds.X, Bounds.Y, DefaultTrophyIconSprite.ScaledWidth(),\n                   DefaultTrophyIconSprite.ScaledHeight()} +\n             EntryIconOffset;\n};\n\nvoid TrophyMenuEntry::Render() {\n  Renderer->DrawSprite(TrophyEntryCardSprite,\n                       Bounds.GetPos() + EntryCardOffset);\n  Renderer->DrawSprite(Icon, IconDest);\n\n  NameLabel.Render();\n  DescriptionLabel.Render();\n}\n\nvoid TrophyMenuEntry::Move(glm::vec2 relativePosition) {\n  Widget::Move(relativePosition);\n\n  NameLabel.Move(relativePosition);\n  DescriptionLabel.Move(relativePosition);\n  IconDest += relativePosition;\n}\n\n}  // namespace CHLCC\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/widgets/chlcc/trophymenuentry.h",
    "content": "#pragma once\n\n#include \"../label.h\"\n#include \"../../../spritesheet.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\nnamespace CHLCC {\n\nclass TrophyMenuEntry : public Widget {\n public:\n  TrophyMenuEntry(int achievementId);\n\n  void Render() override;\n\n  void UpdateInput(float dt) override {};\n\n  using Widget::Move;\n  void Move(glm::vec2 relativePosition) override;\n\n private:\n  int AchievementId;\n\n  Label NameLabel;\n  Label DescriptionLabel;\n\n  Sprite Icon;\n  RectF IconDest;\n};\n\n}  // namespace CHLCC\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/widgets/clickarea.cpp",
    "content": "#include \"clickarea.h\"\n\n#include \"../../inputsystem.h\"\n#include \"../../vm/interface/input.h\"\n#include \"../../renderer/window.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\n\nClickArea::ClickArea(int id, RectF bounds) : Id(id), Clickable(false) {\n  Bounds = bounds;\n}\n\nClickArea::ClickArea(int id, RectF bounds,\n                     std::function<void(ClickArea*)> onClickHandler)\n    : Id(id), Clickable(true), OnClickHandler(onClickHandler) {\n  Bounds = bounds;\n}\n\nvoid ClickArea::UpdateInput(float dt) {\n  if (!Enabled) return;\n\n  if (Input::CurrentInputDevice == Input::Device::Mouse &&\n      ((Input::PrevMousePos != Input::CurMousePos &&\n        !(Vm::Interface::PADinputMouseIsDown & Vm::Interface::PAD1A)) ||\n       Vm::Interface::PADinputMouseWentDown)) {\n    Hovered = Bounds.ContainsPoint(Input::CurMousePos);\n  } else if (Input::CurrentInputDevice == Input::Device::Touch &&\n             Input::TouchIsDown[0] &&\n             Input::PrevTouchPos != Input::CurTouchPos) {\n    Hovered = Bounds.ContainsPoint(Input::CurTouchPos);\n  }\n\n  if (Clickable && Hovered &&\n      Vm::Interface::PADinputMouseWentDown & Vm::Interface::PAD1A) {\n    OnClickHandler(this);\n  }\n}\n\nvoid ClickArea::Show() {\n  Enabled = true;\n\n  switch (Input::CurrentInputDevice) {\n    case Input::Device::Mouse:\n      Hovered = Bounds.ContainsPoint(Input::CurMousePos);\n      break;\n\n    case Input::Device::Touch:\n      Hovered = Bounds.ContainsPoint(Input::CurTouchPos);\n      break;\n\n    case Input::Device::Keyboard:\n    case Input::Device::Controller:\n      Hovered = false;\n      break;\n  }\n}\n\nvoid ClickArea::Hide() {\n  Enabled = false;\n  Hovered = false;\n}\n\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/widgets/clickarea.h",
    "content": "#pragma once\n\n#include <functional>\n\n#include \"../widget.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\n\nclass ClickArea : public Widget {\n public:\n  ClickArea() {}\n  ClickArea(int id, RectF bounds);\n  ClickArea(int id, RectF bounds,\n            std::function<void(ClickArea*)> onClickHandler);\n\n  virtual void UpdateInput(float dt) override;\n\n  virtual void Show() override;\n  virtual void Hide() override;\n  virtual void Render() override {}\n\n  int Id;\n\n private:\n  bool Clickable = false;\n  std::function<void(ClickArea*)> OnClickHandler;\n};\n\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/widgets/group.cpp",
    "content": "#include \"group.h\"\n\n#include \"../../profile/game.h\"\n#include \"../../inputsystem.h\"\n#include \"../../renderer/renderer.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\n\nGroup::Group(Menu* ctx) {\n  Enabled = true;\n  MenuContext = ctx;\n  Bounds = RectF(0.0f, 0.0f, 0.0f, 0.0f);\n  RenderingBounds =\n      RectF(0.0f, 0.0f, Profile::DesignWidth, Profile::DesignHeight);\n  HoverBounds = RenderingBounds;\n}\n\nGroup::Group(Menu* ctx, glm::vec2 pos) : Group(ctx) {\n  Bounds = RectF(pos.x, pos.y, 0.0f, 0.0f);\n}\n\nGroup::~Group() { Clear(); }\n\nvoid Group::Add(Widget* widget) { Children.push_back(widget); }\n\nvoid Group::Add(Widget* widget, FocusDirection dir) {\n  const FocusDirection oppositeDir = [dir]() {\n    switch (dir) {\n      case FDIR_LEFT:\n        return FDIR_RIGHT;\n\n      case FDIR_RIGHT:\n        return FDIR_LEFT;\n\n      case FDIR_UP:\n        return FDIR_DOWN;\n\n      case FDIR_DOWN:\n        return FDIR_UP;\n\n      default:\n        throw std::invalid_argument(\n            fmt::format(\"Unknown focus direction {}\", (int)dir));\n    }\n  }();\n\n  if (LastFocusableElementId != -1) {\n    auto el = Children.at(LastFocusableElementId);\n    el->SetFocus(widget, dir);\n    widget->SetFocus(el, oppositeDir);\n    if (!FocusStart[dir]) FocusStart[dir] = el;\n    if (!FocusStart[oppositeDir]) FocusStart[oppositeDir] = el;\n  } else {\n    FirstFocusableElementId = (int)Children.size();\n  }\n\n  Add(widget);\n  LastFocusableElementId = (int)Children.size() - 1;\n  if (WrapFocus) {\n    auto firstEl = Children.at(FirstFocusableElementId);\n    widget->SetFocus(firstEl, dir);\n    firstEl->SetFocus(widget, oppositeDir);\n    FocusStart[oppositeDir] = widget;\n  }\n}\n\nWidgetType Group::GetType() { return WT_GROUP; }\n\nvoid Group::UpdateInput(float dt) {\n  if (VisibilityState != Shown) return;\n  if (FocusLock && !HasFocus) return;\n\n  for (const auto& el : Children) {\n    if (el->GetType() == WT_NORMAL) {\n      el->UpdateInput(dt);\n      el->Hovered &= el->Bounds.Intersects(HoverBounds);\n      if (el->Enabled && el->Hovered &&\n          (Input::CurrentInputDevice == Input::Device::Mouse ||\n           Input::CurrentInputDevice == Input::Device::Touch)) {\n        if (MenuContext->CurrentlyFocusedElement &&\n            el != MenuContext->CurrentlyFocusedElement)\n          MenuContext->CurrentlyFocusedElement->HasFocus = false;\n        el->HasFocus = true;\n        MenuContext->CurrentlyFocusedElement = el;\n      }\n    }\n  }\n}\n\nvoid Group::Update(float dt) {\n  if (VisibilityState != Hidden) {\n    Widget::Update(dt);\n    bool isFocused = false;\n    for (const auto& el : Children) {\n      if (!FocusLock) {\n        isFocused = isFocused ||\n                    (el == MenuContext->CurrentlyFocusedElement ? true : false);\n        HasFocus = isFocused;\n      }\n      el->Update(dt);\n    }\n  }\n}\n\nvoid Group::Render() {\n  if (VisibilityState != Hidden) {\n    Renderer->EnableScissor();\n    Renderer->SetScissorRect(RenderingBounds);\n    for (const auto& el : Children) {\n      auto tint = el->Tint;\n      el->Tint *= Tint;\n      el->Render();\n      el->Tint = tint;\n    }\n    Renderer->DisableScissor();\n  }\n}\n\nWidget* Group::GetFocus(FocusDirection dir) {\n  if (!Children.empty()) {\n    if (dir == FDIR_DOWN || dir == FDIR_RIGHT)\n      return Children.front();\n    else if (dir == FDIR_UP || dir == FDIR_LEFT)\n      return Children.back();\n  }\n  return NULL;\n}\n\nvoid Group::Show() {\n  Tint.a = 1.0f;\n  VisibilityState = Shown;\n  if (FocusLock) {\n    HasFocus = true;\n    if (!Children.empty()) {\n      PreviousFocusElement = MenuContext->CurrentlyFocusedElement;\n      MenuContext->CurrentlyFocusedElement = nullptr;\n      memcpy(PreviousFocusStart, MenuContext->FocusStart,\n             sizeof(MenuContext->FocusStart));\n      memcpy(MenuContext->FocusStart, FocusStart,\n             sizeof(MenuContext->FocusStart));\n    }\n  }\n  for (const auto& el : Children) {\n    el->Show();\n  }\n}\n\nvoid Group::Hide() {\n  Tint.a = 0.0f;\n  VisibilityState = Hidden;\n  HasFocus = false;\n  if (FocusLock) {\n    if (MenuContext->CurrentlyFocusedElement)\n      MenuContext->CurrentlyFocusedElement->HasFocus = false;\n    MenuContext->CurrentlyFocusedElement = PreviousFocusElement;\n    memcpy(MenuContext->FocusStart, PreviousFocusStart,\n           sizeof(MenuContext->FocusStart));\n  }\n  for (const auto& el : Children) {\n    el->Hide();\n  }\n}\n\nvoid Group::Move(glm::vec2 relativePosition) {\n  for (const auto& el : Children) {\n    el->Move(relativePosition);\n  }\n  Widget::Move(relativePosition);\n}\n\nvoid Group::Clear() {\n  for (auto el : Children) {\n    delete el;\n  }\n  Children.clear();\n  LastFocusableElementId = -1;\n  std::fill(std::begin(FocusStart), std::end(FocusStart), nullptr);\n}\n\nWidget* Group::GetFirstFocusableChild() {\n  return Children.at(FirstFocusableElementId);\n}\n\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/widgets/group.h",
    "content": "#pragma once\n\n#include \"../widget.h\"\n#include \"../menu.h\"\n\n#include <vector>\n#include <magic_enum/magic_enum.hpp>\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\n\nclass Group : public Widget {\n public:\n  Group(Menu* ctx);\n  Group(Menu* ctx, glm::vec2 pos);\n  ~Group();\n\n  std::vector<Widget*> Children;\n  void Add(Widget* widget);\n  void Add(Widget* widget, FocusDirection dir);\n\n  RectF RenderingBounds{};\n  RectF HoverBounds{};\n\n  MenuState VisibilityState = Hidden;\n  bool FocusLock = true;\n  bool WrapFocus = true;\n\n  void Update(float dt) override;\n  void Render() override;\n  void UpdateInput(float dt) override;\n\n  Widget* GetFocus(FocusDirection dir) override;\n\n  void Show() override;\n  void Hide() override;\n\n  WidgetType GetType() override;\n\n  void Clear();\n\n  using Widget::Move;\n  void Move(glm::vec2 relativePosition) override;\n\n  Widget* GetFirstFocusableChild();\n\n protected:\n  Menu* MenuContext;\n  int FirstFocusableElementId = -1;\n  int LastFocusableElementId = -1;\n  Widget* FocusStart[4] = {0, 0, 0, 0};\n  Widget* PreviousFocusElement = 0;\n  Widget* PreviousFocusStart[4] = {0, 0, 0, 0};\n};\n\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/widgets/label.cpp",
    "content": "#include \"label.h\"\n#include \"../../vm/thread.h\"\n#include \"../../profile/dialogue.h\"\n\n#include <numeric>\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\n\nLabel::Label() {}\n\nLabel::Label(Sprite const& label, glm::vec2 pos) {\n  IsText = false;\n  LabelSprite = label;\n  Bounds =\n      RectF(pos.x, pos.y, LabelSprite.Bounds.Width, LabelSprite.Bounds.Height);\n}\n\nLabel::Label(std::vector<ProcessedTextGlyph> str, float textWidth,\n             float fontSize, RendererOutlineMode outlineMode) {\n  FontSize = fontSize;\n  SetText(std::move(str), textWidth, fontSize, outlineMode);\n}\n\nLabel::Label(std::span<ProcessedTextGlyph> str, float textWidth, float fontSize,\n             RendererOutlineMode outlineMode) {\n  FontSize = fontSize;\n  SetText(std::move(str), textWidth, fontSize, outlineMode);\n}\n\nLabel::Label(Vm::BufferOffsetContext scrCtx, glm::vec2 pos, float fontSize,\n             RendererOutlineMode outlineMode, int colorIndex, float maxWidth)\n    : Label(scrCtx, pos, fontSize, outlineMode,\n            Profile::Dialogue::ColorTable[colorIndex], maxWidth) {}\n\nLabel::Label(std::string_view str, glm::vec2 pos, float fontSize,\n             RendererOutlineMode outlineMode, int colorIndex)\n    : Label(str, pos, fontSize, outlineMode,\n            Profile::Dialogue::ColorTable[colorIndex]) {}\n\nLabel::Label(Vm::Sc3Stream& stream, glm::vec2 pos, float fontSize,\n             RendererOutlineMode outlineMode, int colorIndex)\n    : Label(stream, pos, fontSize, outlineMode,\n            Profile::Dialogue::ColorTable[colorIndex]) {}\n\nLabel::Label(Vm::BufferOffsetContext scrCtx, glm::vec2 pos, float fontSize,\n             RendererOutlineMode outlineMode, DialogueColorPair colorPair,\n             float maxWidth) {\n  FontSize = fontSize;\n  Bounds = RectF(pos.x, pos.y, 0, FontSize);\n  SetText(scrCtx, fontSize, outlineMode, colorPair, maxWidth);\n}\n\nLabel::Label(Vm::Sc3Stream& stream, glm::vec2 pos, float fontSize,\n             RendererOutlineMode outlineMode, DialogueColorPair colorPair) {\n  FontSize = fontSize;\n  Bounds = RectF(pos.x, pos.y, 0, FontSize);\n  SetText(stream, fontSize, outlineMode, colorPair);\n}\n\nLabel::Label(std::string_view str, glm::vec2 pos, float fontSize,\n             RendererOutlineMode outlineMode, DialogueColorPair colorPair) {\n  FontSize = fontSize;\n  Bounds = RectF(pos.x, pos.y, 0, FontSize);\n  SetText(str, fontSize, outlineMode, colorPair);\n}\n\nvoid Label::UpdateInput(float dt) {}\n\nvoid Label::Update(float dt) { Widget::Update(dt); }\n\nvoid Label::Render() {\n  if (IsText) {\n    if (OutlineAlphaEnabled) {\n      Renderer->DrawProcessedText(Text, Profile::Dialogue::DialogueFont, Tint.a,\n                                  OutlineAlpha, OutlineMode, true);\n    } else {\n      Renderer->DrawProcessedText(Text, Profile::Dialogue::DialogueFont, Tint.a,\n                                  OutlineMode, true);\n    }\n  } else {\n    Renderer->DrawSprite(LabelSprite, Bounds, Tint);\n  }\n}\n\nvoid Label::Move(glm::vec2 relativePosition) {\n  for (ProcessedTextGlyph& glyph : Text) {\n    glyph.DestRect += relativePosition;\n  }\n  Widget::Move(relativePosition);\n}\n\nvoid Label::SetSprite(Sprite const& label) {\n  IsText = false;\n  LabelSprite = label;\n  Bounds = RectF(Bounds.X, Bounds.Y, LabelSprite.Bounds.Width,\n                 LabelSprite.Bounds.Height);\n}\n\nvoid Label::SetText(std::vector<ProcessedTextGlyph> str, float textWidth,\n                    float fontSize, RendererOutlineMode outlineMode) {\n  IsText = true;\n  OutlineMode = outlineMode;\n  Text = std::move(str);\n  Bounds = RectF(Text[0].DestRect.X, Text[0].DestRect.Y, TextWidth, fontSize);\n}\n\nvoid Label::SetText(std::span<ProcessedTextGlyph> str,\n                    RendererOutlineMode outlineMode) {\n  IsText = true;\n  OutlineMode = outlineMode;\n  Text = std::vector<ProcessedTextGlyph>(str.begin(), str.end());\n\n  Bounds = std::reduce(str.begin() + 1, str.end(), str[0],\n                       [](auto lhs, const auto& rhs) {\n                         lhs.DestRect =\n                             RectF::Coalesce(lhs.DestRect, rhs.DestRect);\n                         return lhs;\n                       })\n               .DestRect;\n}\n\nvoid Label::SetText(std::span<ProcessedTextGlyph> str, float textWidth,\n                    float fontSize, RendererOutlineMode outlineMode) {\n  IsText = true;\n  OutlineMode = outlineMode;\n  Text = std::vector<ProcessedTextGlyph>(str.begin(), str.end());\n  Bounds = RectF(Text[0].DestRect.X, Text[0].DestRect.Y, TextWidth, fontSize);\n}\n\nvoid Label::SetText(std::string_view str, float fontSize,\n                    RendererOutlineMode outlineMode, int colorIndex) {\n  SetText(str, fontSize, outlineMode,\n          Profile::Dialogue::ColorTable[colorIndex]);\n}\n\nvoid Label::SetText(Vm::BufferOffsetContext scrCtx, float fontSize,\n                    RendererOutlineMode outlineMode, int colorIndex) {\n  SetText(scrCtx, fontSize, outlineMode,\n          Profile::Dialogue::ColorTable[colorIndex]);\n}\nvoid Label::SetText(Vm::Sc3Stream& stream, float fontSize,\n                    RendererOutlineMode outlineMode, int colorIndex) {\n  SetText(stream, fontSize, outlineMode,\n          Profile::Dialogue::ColorTable[colorIndex]);\n}\n\nvoid Label::SetText(Vm::Sc3Stream& stream, float fontSize,\n                    RendererOutlineMode outlineMode,\n                    DialogueColorPair colorPair) {\n  IsText = true;\n  FontSize = fontSize;\n  Text = TextLayoutPlainLine(\n      stream, 255, Profile::Dialogue::DialogueFont, fontSize, colorPair, 1.0f,\n      glm::vec2(Bounds.X, Bounds.Y), TextAlignment::Left);\n  OutlineMode = outlineMode;\n  TextWidth = 0.0f;\n  for (const ProcessedTextGlyph& glyph : Text) {\n    TextWidth += glyph.DestRect.Width;\n  }\n  Bounds = RectF(Text[0].DestRect.X, Text[0].DestRect.Y, TextWidth, fontSize);\n}\n\nvoid Label::SetText(Vm::BufferOffsetContext scrCtx, float fontSize,\n                    RendererOutlineMode outlineMode,\n                    DialogueColorPair colorPair, float maxWidth) {\n  IsText = true;\n  Impacto::Vm::Sc3VmThread dummy;\n  dummy.IpOffset = scrCtx.IpOffset;\n  dummy.ScriptBufferId = scrCtx.ScriptBufferId;\n  FontSize = fontSize;\n  Text = TextLayoutPlainLine(\n      &dummy, 255, Profile::Dialogue::DialogueFont, fontSize, colorPair, 1.0f,\n      glm::vec2(Bounds.X, Bounds.Y), TextAlignment::Left, maxWidth);\n  OutlineMode = outlineMode;\n  TextWidth = 0.0f;\n  for (const ProcessedTextGlyph& glyph : Text) {\n    TextWidth += glyph.DestRect.Width;\n  }\n  Bounds = Text.empty() ? RectF()\n                        : RectF(Text[0].DestRect.X, Text[0].DestRect.Y,\n                                TextWidth, fontSize);\n}\n\nvoid Label::SetText(std::string_view str, float fontSize,\n                    RendererOutlineMode outlineMode,\n                    DialogueColorPair colorPair) {\n  IsText = true;\n  Text = TextLayoutPlainString(str, Profile::Dialogue::DialogueFont, fontSize,\n                               colorPair, 1.0f, glm::vec2(Bounds.X, Bounds.Y),\n                               TextAlignment::Left);\n  OutlineMode = outlineMode;\n  TextWidth = 0.0f;\n  for (const ProcessedTextGlyph& glyph : Text) {\n    TextWidth += glyph.DestRect.Width;\n  }\n  if (Text.size() > 0) {\n    Bounds = RectF(Text[0].DestRect.X, Text[0].DestRect.Y, TextWidth, fontSize);\n  }\n}\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/widgets/label.h",
    "content": "#pragma once\n\n#include <span>\n#include \"../../vm/vm.h\"\n#include \"../../vm/sc3stream.h\"\n#include \"../widget.h\"\n#include \"../../text/text.h\"\n#include \"../../renderer/renderer.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\n\nclass Label : public Widget {\n public:\n  Label();\n  Label(Sprite const& label, glm::vec2 pos);\n  Label(std::vector<ProcessedTextGlyph> str, float textWidth, float fontSize,\n        RendererOutlineMode outlineMode);\n  Label(std::span<ProcessedTextGlyph> str, float textWidth, float fontSize,\n        RendererOutlineMode outlineMode);\n  Label(Vm::BufferOffsetContext scrCtx, glm::vec2 pos, float fontSize,\n        RendererOutlineMode outlineMode, int colorIndex = 10,\n        float maxWidth = 0.0f);\n  Label(Vm::BufferOffsetContext scrCtx, glm::vec2 pos, float fontSize,\n        RendererOutlineMode outlineMode, DialogueColorPair colorPair,\n        float maxWidth = 0.0f);\n  Label(Vm::Sc3Stream& stream, glm::vec2 pos, float fontSize,\n        RendererOutlineMode outlineMode, int colorIndex = 10);\n  Label(Vm::Sc3Stream& stream, glm::vec2 pos, float fontSize,\n        RendererOutlineMode outlineMode, DialogueColorPair colorPair);\n  Label(std::string_view str, glm::vec2 pos, float fontSize,\n        RendererOutlineMode outlineMode, int colorIndex = 10);\n  Label(std::string_view str, glm::vec2 pos, float fontSize,\n        RendererOutlineMode outlineMode, DialogueColorPair colorPair);\n\n  void Update(float dt) override;\n  void UpdateInput(float dt) override;\n  void Render() override;\n\n  using Widget::Move;\n  void Move(glm::vec2 relativePosition) override;\n\n  void SetSprite(Sprite const& label);\n  void SetText(std::vector<ProcessedTextGlyph> text, float textWidth,\n               float fontSize, RendererOutlineMode outlineMode);\n  void SetText(std::span<ProcessedTextGlyph> str, float textWidth,\n               float fontSize, RendererOutlineMode outlineMode);\n  void SetText(std::span<ProcessedTextGlyph> str,\n               RendererOutlineMode outlineMode);\n  void SetText(Vm::BufferOffsetContext scrCtx, float fontSize,\n               RendererOutlineMode outlineMode, int colorIndex = 10);\n  void SetText(Vm::BufferOffsetContext scrCtx, float fontSize,\n               RendererOutlineMode outlineMode, DialogueColorPair colorPair,\n               float maxWidth = 0.0f);\n  void SetText(std::string_view str, float fontSize,\n               RendererOutlineMode outlineMode, int colorIndex = 10);\n  void SetText(std::string_view str, float fontSize,\n               RendererOutlineMode outlineMode, DialogueColorPair colorPair);\n  void ClearText() {\n    Text.clear();\n    IsText = false;\n    Bounds = {};\n  }\n  void SetText(Vm::Sc3Stream& stream, float fontSize,\n               RendererOutlineMode outlineMode, int colorIndex = 10);\n  void SetText(Vm::Sc3Stream& stream, float fontSize,\n               RendererOutlineMode outlineMode, DialogueColorPair colorPair);\n\n  size_t GetTextLength() { return Text.size(); }\n  float GetFontSize() { return FontSize; }\n\n  float OutlineAlpha = 1.0f;\n  bool OutlineAlphaEnabled = false;\n\n protected:\n  bool IsText;\n  Sprite LabelSprite;\n  float FontSize;\n  std::vector<ProcessedTextGlyph> Text;\n  float TextWidth = 0.0f;\n  RendererOutlineMode OutlineMode = RendererOutlineMode::None;\n};\n\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/widgets/mo6tw/actorsvoicebutton.cpp",
    "content": "#include \"actorsvoicebutton.h\"\n\n#include \"../../../renderer/renderer.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\nnamespace MO6TW {\n\nActorsVoiceButton::ActorsVoiceButton(int id, Sprite const& norm,\n                                     Sprite const& locked,\n                                     Sprite const& highlight,\n                                     Sprite const& lockedHighlight,\n                                     glm::vec2 pos) {\n  Id = id;\n  NormalSprite = norm;\n  HighlightSprite = highlight;\n  LockedSprite = locked;\n  LockedHighlightSprite = lockedHighlight;\n  Enabled = true;\n  Bounds = RectF(pos.x, pos.y, NormalSprite.ScaledWidth(),\n                 NormalSprite.ScaledHeight());\n}\n\nvoid ActorsVoiceButton::Render() {\n  if (IsLocked) {\n    Renderer->DrawSprite(LockedSprite, glm::vec2(Bounds.X, Bounds.Y), Tint);\n  } else {\n    Renderer->DrawSprite(NormalSprite, glm::vec2(Bounds.X, Bounds.Y), Tint);\n  }\n  if (HasFocus) {\n    if (IsLocked) {\n      Renderer->DrawSprite(LockedHighlightSprite, glm::vec2(Bounds.X, Bounds.Y),\n                           Tint);\n\n    } else {\n      Renderer->DrawSprite(HighlightSprite, glm::vec2(Bounds.X, Bounds.Y),\n                           Tint);\n    }\n  }\n}\n\n}  // namespace MO6TW\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/widgets/mo6tw/actorsvoicebutton.h",
    "content": "#pragma once\n\n#include \"../button.h\"\n#include \"../label.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\nnamespace MO6TW {\n\nclass ActorsVoiceButton : public Widgets::Button {\n public:\n  ActorsVoiceButton(int id, Sprite const& norm, Sprite const& locked,\n                    Sprite const& highlight, Sprite const& lockedHighlight,\n                    glm::vec2 pos);\n  void Render() override;\n\n private:\n  Sprite LockedHighlightSprite;\n};\n\n}  // namespace MO6TW\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/widgets/mo6tw/albumcharacterbutton.cpp",
    "content": "#include \"albumcharacterbutton.h\"\n\n#include \"../../../renderer/renderer.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\nnamespace MO6TW {\n\nAlbumCharacterButton::AlbumCharacterButton(int id, Sprite const& norm,\n                                           Sprite const& locked,\n                                           Sprite const& highlight,\n                                           Sprite const& lockedHighlight,\n                                           glm::vec2 pos,\n                                           float highlightAnimationDuration) {\n  Id = id;\n  NormalSprite = norm;\n  HighlightSprite = highlight;\n  LockedSprite = locked;\n  LockedHighlightSprite = lockedHighlight;\n  Enabled = true;\n  Bounds = RectF(pos.x, pos.y, NormalSprite.ScaledWidth(),\n                 NormalSprite.ScaledHeight());\n\n  HighlightAnimation.Direction = AnimationDirection::In;\n  HighlightAnimation.LoopMode = AnimationLoopMode::ReverseDirection;\n  HighlightAnimation.SetDuration(highlightAnimationDuration);\n  HighlightAnimation.StartIn();\n}\n\nvoid AlbumCharacterButton::Update(float dt) {\n  Button::Update(dt);\n  HighlightAnimation.Update(dt);\n}\n\nvoid AlbumCharacterButton::Render() {\n  if (IsLocked) {\n    Renderer->DrawSprite(LockedSprite, glm::vec2(Bounds.X, Bounds.Y), Tint);\n  } else {\n    Renderer->DrawSprite(NormalSprite, glm::vec2(Bounds.X, Bounds.Y), Tint);\n  }\n\n  glm::vec4 col = Tint;\n  col.a = glm::smoothstep(0.0f, 1.0f, HighlightAnimation.Progress);\n  if (HasFocus) {\n    if (IsLocked) {\n      Renderer->DrawSprite(LockedHighlightSprite, glm::vec2(Bounds.X, Bounds.Y),\n                           col);\n    } else {\n      Renderer->DrawSprite(HighlightSprite, glm::vec2(Bounds.X, Bounds.Y), col);\n    }\n  }\n}\n\n}  // namespace MO6TW\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/widgets/mo6tw/albumcharacterbutton.h",
    "content": "#pragma once\n\n#include \"../button.h\"\n#include \"../label.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\nnamespace MO6TW {\n\nclass AlbumCharacterButton : public Widgets::Button {\n public:\n  AlbumCharacterButton(int id, Sprite const& norm, Sprite const& locked,\n                       Sprite const& highlight, Sprite const& lockedHighlight,\n                       glm::vec2 pos, float highlightAnimationDuration);\n  void Update(float dt) override;\n  void Render() override;\n\n private:\n  Sprite LockedHighlightSprite;\n  Animation HighlightAnimation;\n};\n\n}  // namespace MO6TW\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/widgets/mo6tw/albumthumbnailbutton.cpp",
    "content": "#include \"albumthumbnailbutton.h\"\n\n#include \"../../../renderer/renderer.h\"\n#include \"../../../profile/dialogue.h\"\n#include \"../../../profile/games/mo6tw/albummenu.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\nnamespace MO6TW {\n\nusing namespace Impacto::Profile::MO6TW::AlbumMenu;\n\nAlbumThumbnailButton::AlbumThumbnailButton(\n    int id, Sprite const& norm, Sprite const& disabled,\n    Sprite const& focusedTopLeft, Sprite const& focusedTopRight,\n    Sprite const& focusedBottomLeft, Sprite const& focusedBottomRight,\n    Sprite const& border, int unlockedVariations, int totalVariations,\n    glm::vec2 pos)\n    : ImageThumbnailButton(id, norm, disabled, focusedTopLeft, focusedTopRight,\n                           focusedBottomLeft, focusedBottomRight, pos) {\n  TotalVariations = totalVariations;\n  UnlockedVariations = unlockedVariations;\n  uint16_t sc3StringBuffer[10];\n\n  std::string variationsCount =\n      (unlockedVariations > 0)\n          ? fmt::format(\"{:d}/{:d}\", unlockedVariations, totalVariations)\n          : \"\\?\\?/\\?\\?\";\n  TextGetSc3String(variationsCount, sc3StringBuffer);\n  Vm::Sc3Stream stream(sc3StringBuffer);\n\n  InfoTextWidth = TextGetPlainLineWidth(stream, Profile::Dialogue::DialogueFont,\n                                        ThumbnailButtonTextFontSize);\n  stream = Vm::Sc3Stream(sc3StringBuffer);\n  InfoText = new Label(\n      stream,\n      pos +\n          glm::vec2(norm.Bounds.Width - InfoTextWidth,\n                    norm.Bounds.Height - ThumbnailButtonTextFontSize) +\n          ThumbnailButtonTextOffset,\n      ThumbnailButtonTextFontSize, RendererOutlineMode::Full,\n      ThumbnailButtonTextColorIndex);\n  Border = border;\n}\n\nvoid AlbumThumbnailButton::Render() {\n  Renderer->DrawSprite(\n      Border, glm::vec2(Bounds.X, Bounds.Y) + ThumbnailButtonBorderOffset,\n      Tint);\n  ImageThumbnailButton::Render();\n  InfoText->Render();\n}\n\nvoid AlbumThumbnailButton::Move(glm::vec2 relativePosition) {\n  InfoText->Move(relativePosition);\n  Widget::Move(relativePosition);\n}\n\n}  // namespace MO6TW\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/widgets/mo6tw/albumthumbnailbutton.h",
    "content": "#pragma once\n\n#include \"imagethumbnailbutton.h\"\n#include \"../label.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\nnamespace MO6TW {\n\nclass AlbumThumbnailButton : public ImageThumbnailButton {\n public:\n  AlbumThumbnailButton(int id, Sprite const& norm, Sprite const& disabled,\n                       Sprite const& focusedTopLeft,\n                       Sprite const& focusedTopRight,\n                       Sprite const& focusedBottomLeft,\n                       Sprite const& focusedBottomRight, Sprite const& border,\n                       int unlockedVariations, int totalVariations,\n                       glm::vec2 pos);\n  void Render() override;\n\n  using Widget::Move;\n  void Move(glm::vec2 relativePosition) override;\n\n private:\n  Sprite Border;\n  int UnlockedVariations = 0;\n  int TotalVariations = 0;\n  Widgets::Label* InfoText;\n  float InfoTextWidth;\n};\n\n}  // namespace MO6TW\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/widgets/mo6tw/imagethumbnailbutton.cpp",
    "content": "#include \"imagethumbnailbutton.h\"\n\n#include \"../../../renderer/renderer.h\"\n#include \"../../../profile/games/mo6tw/moviemenu.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\nnamespace MO6TW {\n\nusing namespace Impacto::Profile::MO6TW::MovieMenu;\n\nImageThumbnailButton::ImageThumbnailButton(int id, Sprite const& norm,\n                                           Sprite const& disabled,\n                                           Sprite const& focusedTopLeft,\n                                           Sprite const& focusedTopRight,\n                                           Sprite const& focusedBottomLeft,\n                                           Sprite const& focusedBottomRight,\n                                           glm::vec2 pos) {\n  Id = id;\n  NormalSprite = norm;\n  DisabledSprite = disabled;\n  HighlightTopLeft = focusedTopLeft;\n  HighlightTopRight = focusedTopRight;\n  HighlightBottomLeft = focusedBottomLeft;\n  HighlightBottomRight = focusedBottomRight;\n  Enabled = true;\n  Bounds = RectF(pos.x, pos.y, NormalSprite.ScaledWidth(),\n                 NormalSprite.ScaledHeight());\n\n  HighlightAnimation.Direction = AnimationDirection::In;\n  HighlightAnimation.LoopMode = AnimationLoopMode::Loop;\n  HighlightAnimation.SetDuration(HighlightAnimationDuration);\n  HighlightAnimation.StartIn();\n}\n\nImageThumbnailButton::ImageThumbnailButton(\n    int id, Sprite const& normTopPart, Sprite const& normBottomPart,\n    Sprite const& disabled, Sprite const& focusedTopLeft,\n    Sprite const& focusedTopRight, Sprite const& focusedBottomLeft,\n    Sprite const& focusedBottomRight, glm::vec2 pos)\n    : ImageThumbnailButton(id, normTopPart, disabled, focusedTopLeft,\n                           focusedTopRight, focusedBottomLeft,\n                           focusedBottomRight, pos) {\n  IsSplit = true;\n  BottomPart = normBottomPart;\n  Bounds = RectF(pos.x, pos.y, NormalSprite.ScaledWidth(),\n                 NormalSprite.ScaledHeight() + BottomPart.ScaledHeight());\n}\n\nvoid ImageThumbnailButton::Update(float dt) {\n  Button::Update(dt);\n  HighlightAnimation.Update(dt);\n}\n\nvoid ImageThumbnailButton::Render() {\n  if (!IsLocked) {\n    Renderer->DrawSprite(NormalSprite, glm::vec2(Bounds.X, Bounds.Y), Tint);\n    if (IsSplit) {\n      Renderer->DrawSprite(\n          BottomPart,\n          glm::vec2(Bounds.X, Bounds.Y + NormalSprite.ScaledHeight()), Tint);\n    }\n  } else {\n    Renderer->DrawSprite(DisabledSprite, glm::vec2(Bounds.X, Bounds.Y), Tint);\n  }\n  if (HasFocus) {\n    auto offset = 2.0f * glm::step(0.5f, HighlightAnimation.Progress);\n    Renderer->DrawSprite(\n        HighlightTopLeft,\n        glm::vec2(Bounds.X - HighlightTopLeftOffset.x + offset,\n                  Bounds.Y - HighlightTopLeftOffset.y + offset),\n        Tint);\n    Renderer->DrawSprite(\n        HighlightTopRight,\n        glm::vec2(Bounds.X + Bounds.Width - HighlightTopRightOffset.x - offset,\n                  Bounds.Y - HighlightTopRightOffset.y + offset),\n        Tint);\n    Renderer->DrawSprite(\n        HighlightBottomLeft,\n        glm::vec2(\n            Bounds.X - HighlightBottomLeftOffset.x + offset,\n            Bounds.Y + Bounds.Height - HighlightBottomLeftOffset.y - offset),\n        Tint);\n    Renderer->DrawSprite(\n        HighlightBottomRight,\n        glm::vec2(\n            Bounds.X + Bounds.Width - HighlightBottomRightOffset.x - offset,\n            Bounds.Y + Bounds.Height - HighlightBottomRightOffset.y - offset),\n        Tint);\n  }\n}\n\n}  // namespace MO6TW\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/widgets/mo6tw/imagethumbnailbutton.h",
    "content": "#pragma once\n\n#include \"../button.h\"\n#include \"../label.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\nnamespace MO6TW {\n\nclass ImageThumbnailButton : public Widgets::Button {\n public:\n  ImageThumbnailButton(int id, Sprite const& norm, Sprite const& disabled,\n                       Sprite const& focusedTopLeft,\n                       Sprite const& focusedTopRight,\n                       Sprite const& focusedBottomLeft,\n                       Sprite const& focusedBottomRight, glm::vec2 pos);\n  ImageThumbnailButton(int id, Sprite const& normTopPart,\n                       Sprite const& normBottomPart, Sprite const& disabled,\n                       Sprite const& focusedTopLeft,\n                       Sprite const& focusedTopRight,\n                       Sprite const& focusedBottomLeft,\n                       Sprite const& focusedBottomRight, glm::vec2 pos);\n  void Update(float dt) override;\n  void Render() override;\n\n  bool IsLocked = true;\n\n private:\n  bool IsSplit = false;\n  Sprite BottomPart;\n  Sprite HighlightTopLeft;\n  Sprite HighlightTopRight;\n  Sprite HighlightBottomLeft;\n  Sprite HighlightBottomRight;\n\n  Animation HighlightAnimation;\n};\n\n}  // namespace MO6TW\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/widgets/mo6tw/saveentrybutton.cpp",
    "content": "#include \"saveentrybutton.h\"\n\n#include \"../../../renderer/renderer.h\"\n#include \"../../../profile/dialogue.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\nnamespace MO6TW {\n\nvoid SaveEntryButton::Render() {\n  if (HasFocus) {\n    Renderer->DrawSprite(FocusedSprite, glm::vec2(Bounds.X, Bounds.Y), Tint);\n  } else {\n    if (Enabled) {\n      Renderer->DrawSprite(NormalSprite, glm::vec2(Bounds.X, Bounds.Y), Tint);\n    } else {\n      Renderer->DrawSprite(DisabledSprite, glm::vec2(Bounds.X, Bounds.Y), Tint);\n    }\n  }\n\n  if (EntryActive) {\n    Renderer->DrawProcessedText(SceneTitle, Profile::Dialogue::DialogueFont,\n                                Tint.a, RendererOutlineMode::Full, true);\n    Renderer->DrawProcessedText(PlayTimeHint, Profile::Dialogue::DialogueFont,\n                                Tint.a, RendererOutlineMode::Full, true);\n    Renderer->DrawProcessedText(PlayTime, Profile::Dialogue::DialogueFont,\n                                Tint.a, RendererOutlineMode::Full, true);\n    Renderer->DrawProcessedText(SaveDateHint, Profile::Dialogue::DialogueFont,\n                                Tint.a, RendererOutlineMode::Full, true);\n    Renderer->DrawProcessedText(SaveDate, Profile::Dialogue::DialogueFont,\n                                Tint.a, RendererOutlineMode::Full, true);\n  } else {\n    Renderer->DrawProcessedText(SceneTitle, Profile::Dialogue::DialogueFont,\n                                Tint.a, RendererOutlineMode::Full, true);\n  }\n\n  Renderer->DrawSprite(Thumbnail, glm::vec2(Bounds.X + 41.0f, Bounds.Y + 5.0f),\n                       Tint);\n}\n\nvoid SaveEntryButton::AddSceneTitleText(Vm::BufferOffsetContext strAdr,\n                                        float fontSize, bool outline) {\n  Vm::Sc3VmThread dummy;\n  dummy.SetIp(strAdr);\n  if (EntryActive) {\n    SceneTitle = TextLayoutPlainLine(\n        &dummy, 255, Profile::Dialogue::DialogueFont, fontSize,\n        Profile::Dialogue::ColorTable[0], 1.0f,\n        glm::vec2(Bounds.X + 228.0f, Bounds.Y + 10.0f), TextAlignment::Left);\n  } else {\n    SceneTitle = TextLayoutPlainLine(\n        &dummy, 255, Profile::Dialogue::DialogueFont, fontSize,\n        Profile::Dialogue::ColorTable[0], 1.0f,\n        glm::vec2(Bounds.X + 228.0f, Bounds.Y + 50.0f), TextAlignment::Left);\n  }\n}\n\nvoid SaveEntryButton::AddPlayTimeHintText(Vm::BufferOffsetContext strAdr,\n                                          float fontSize, bool outline) {\n  Vm::Sc3VmThread dummy;\n  dummy.SetIp(strAdr);\n  PlayTimeHint = TextLayoutPlainLine(\n      &dummy, 255, Profile::Dialogue::DialogueFont, fontSize,\n      Profile::Dialogue::ColorTable[0], 1.0f,\n      glm::vec2(Bounds.X + 238.0f, Bounds.Y + 45.0f), TextAlignment::Left);\n}\n\nvoid SaveEntryButton::AddPlayTimeText(Vm::BufferOffsetContext strAdr,\n                                      float fontSize, bool outline) {\n  Vm::Sc3VmThread dummy;\n  dummy.SetIp(strAdr);\n  PlayTime = TextLayoutPlainLine(\n      &dummy, 255, Profile::Dialogue::DialogueFont, fontSize,\n      Profile::Dialogue::ColorTable[0], 1.0f,\n      glm::vec2(Bounds.X + 372.0f, Bounds.Y + 61.0f), TextAlignment::Left);\n}\n\nvoid SaveEntryButton::AddSaveDateHintText(Vm::BufferOffsetContext strAdr,\n                                          float fontSize, bool outline) {\n  Vm::Sc3VmThread dummy;\n  dummy.SetIp(strAdr);\n  SaveDateHint = TextLayoutPlainLine(\n      &dummy, 255, Profile::Dialogue::DialogueFont, fontSize,\n      Profile::Dialogue::ColorTable[0], 1.0f,\n      glm::vec2(Bounds.X + 238.0f, Bounds.Y + 75.0f), TextAlignment::Left);\n}\n\nvoid SaveEntryButton::AddSaveDateText(Vm::BufferOffsetContext strAdr,\n                                      float fontSize, bool outline) {\n  Vm::Sc3VmThread dummy;\n  dummy.SetIp(strAdr);\n  SaveDate = TextLayoutPlainLine(\n      &dummy, 255, Profile::Dialogue::DialogueFont, fontSize,\n      Profile::Dialogue::ColorTable[0], 1.0f,\n      glm::vec2(Bounds.X + 283.0f, Bounds.Y + 93.0f), TextAlignment::Left);\n}\n\n}  // namespace MO6TW\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/widgets/mo6tw/saveentrybutton.h",
    "content": "#pragma once\n\n#include \"../button.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\nnamespace MO6TW {\n\nclass SaveEntryButton : public Widgets::Button {\n public:\n  SaveEntryButton(int id, Sprite const& norm, Sprite const& focused,\n                  Sprite const& highlight, glm::vec2 pos)\n      : Widgets::Button(id, norm, focused, highlight, pos) {}\n  void Render() override;\n  void AddPlayTimeHintText(Vm::BufferOffsetContext strAdr, float fontSize,\n                           bool outline);\n  void AddPlayTimeText(Vm::BufferOffsetContext strAdr, float fontSize,\n                       bool outline);\n  void AddSaveDateHintText(Vm::BufferOffsetContext strAdr, float fontSize,\n                           bool outline);\n  void AddSaveDateText(Vm::BufferOffsetContext strAdr, float fontSize,\n                       bool outline);\n  void AddSceneTitleText(Vm::BufferOffsetContext strAdr, float fontSize,\n                         bool outline);\n\n  Sprite Thumbnail;\n\n  bool EntryActive = false;\n\n private:\n  std::vector<ProcessedTextGlyph> SceneTitle;\n  std::vector<ProcessedTextGlyph> PlayTimeHint;\n  std::vector<ProcessedTextGlyph> PlayTime;\n  std::vector<ProcessedTextGlyph> SaveDateHint;\n  std::vector<ProcessedTextGlyph> SaveDate;\n};\n\n}  // namespace MO6TW\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/widgets/mo6tw/scenelistentry.cpp",
    "content": "#include \"scenelistentry.h\"\n\n#include \"../../../renderer/renderer.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\nnamespace MO6TW {\n\nSceneListEntry::SceneListEntry(int id, Widgets::Label* number,\n                               Widgets::Label* lockedText,\n                               Widgets::Label* unlockedText,\n                               Sprite const& highlight, bool isLocked) {\n  Id = id;\n  Number = number;\n  LockedText = lockedText;\n  UnlockedText = unlockedText;\n  HighlightSprite = highlight;\n  IsLocked = isLocked;\n  Enabled = true;\n  Bounds =\n      RectF(Number->Bounds.X, Number->Bounds.Y, 740.0f, Number->Bounds.Height);\n}\n\nvoid SceneListEntry::Render() {\n  if (HasFocus) Renderer->DrawSprite(HighlightSprite, Bounds, Tint);\n  Number->Render();\n  if (IsLocked) {\n    LockedText->Render();\n  } else {\n    UnlockedText->Render();\n  }\n}\n\nvoid SceneListEntry::Move(glm::vec2 relativePosition) {\n  Number->Move(relativePosition);\n  LockedText->Move(relativePosition);\n  UnlockedText->Move(relativePosition);\n\n  Widget::Move(relativePosition);\n}\n\n}  // namespace MO6TW\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/widgets/mo6tw/scenelistentry.h",
    "content": "#pragma once\n\n#include \"../button.h\"\n#include \"../label.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\nnamespace MO6TW {\n\nclass SceneListEntry : public Widgets::Button {\n public:\n  SceneListEntry(int id, Widgets::Label* number, Widgets::Label* lockedText,\n                 Widgets::Label* unlockedText, Sprite const& highlight,\n                 bool isLocked);\n  void Render() override;\n\n  using Widget::Move;\n  void Move(glm::vec2 relativePosition) override;\n\n  bool IsLocked = false;\n\n private:\n  Widgets::Label* Number;\n  Widgets::Label* LockedText;\n  Widgets::Label* UnlockedText;\n};\n\n}  // namespace MO6TW\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/widgets/mo6tw/tipsentrybutton.cpp",
    "content": "#include \"tipsentrybutton.h\"\n\n#include \"../../../renderer/renderer.h\"\n#include \"../../../profile/dialogue.h\"\n#include \"../../../profile/games/mo6tw/tipsmenu.h\"\n#include \"../../../text/text.h\"\n#include \"../../../vm/vm.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\nnamespace MO6TW {\n\nusing namespace Impacto::TipsSystem;\n\nusing namespace Impacto::Profile::MO6TW::TipsMenu;\n\nTipsEntryButton::TipsEntryButton(int id, TipsDataRecord* tipRecord,\n                                 RectF const& dest, Sprite const& highlight) {\n  Id = id;\n  TipEntryRecord = tipRecord;\n  Bounds = dest;\n  HighlightSprite = highlight;\n  Enabled = true;\n  HighlightOffset = TipListEntryHighlightOffset;\n  PrevUnreadState = TipEntryRecord->IsUnread;\n  TextLayoutPlainString(fmt::format(\"{:3d}.\", tipRecord->Id + 1), TipNumber,\n                        Profile::Dialogue::DialogueFont, TipListEntryFontSize,\n                        Profile::Dialogue::ColorTable[DefaultColorIndex], 1.0f,\n                        glm::vec2(Bounds.X, Bounds.Y), TextAlignment::Left);\n  Vm::Sc3VmThread dummy;\n  dummy.IpOffset = tipRecord->StringAdr[0];\n  dummy.ScriptBufferId = TipsSystem::GetTipsScriptBufferId();\n  Text = TextLayoutPlainLine(\n      &dummy, 255, Profile::Dialogue::DialogueFont, TipListEntryFontSize,\n      Profile::Dialogue::ColorTable[DefaultColorIndex], 1.0f,\n      glm::vec2(Bounds.X + TipListEntryNameXOffset, Bounds.Y),\n      TextAlignment::Left);\n  TextLayoutPlainString(TipListEntryNewText, NewText,\n                        Profile::Dialogue::DialogueFont, TipListEntryFontSize,\n                        Profile::Dialogue::ColorTable[DefaultColorIndex], 1.0f,\n                        glm::vec2(Bounds.X + TipListEntryNewOffset, Bounds.Y),\n                        TextAlignment::Left);\n  auto lockedScrPos = Vm::ScriptGetTextTableStrAddress(TipListEntryLockedTable,\n                                                       TipListEntryLockedIndex);\n  dummy.IpOffset = lockedScrPos.IpOffset;\n  dummy.ScriptBufferId = lockedScrPos.ScriptBufferId;\n  TextLayoutPlainLine(&dummy, 3, TipLockedText, Profile::Dialogue::DialogueFont,\n                      TipListEntryFontSize,\n                      Profile::Dialogue::ColorTable[UnreadColorIndex], 1.0f,\n                      glm::vec2(Bounds.X + TipListEntryNameXOffset, Bounds.Y),\n                      TextAlignment::Left);\n}\n\nvoid TipsEntryButton::Update(float dt) {\n  Button::Update(dt);\n  if (PrevUnreadState != TipEntryRecord->IsUnread) {\n    int colorIndex = DefaultColorIndex;\n    if (TipEntryRecord->IsUnread) {\n      colorIndex = UnreadColorIndex;\n    }\n    for (ProcessedTextGlyph& glyph : Text) {\n      glyph.Colors = Profile::Dialogue::ColorTable[colorIndex];\n    }\n    PrevUnreadState = TipEntryRecord->IsUnread;\n  }\n}\n\nvoid TipsEntryButton::Render() {\n  if (HasFocus) {\n    const RectF dest =\n        HighlightSprite.ScaledBounds()\n            .Scale({Bounds.Width / HighlightSprite.ScaledWidth(), 1.0f},\n                   {0.0f, 0.0f})\n            .Translate(Bounds.GetPos() + HighlightOffset);\n    Renderer->DrawSprite(HighlightSprite, dest, Tint);\n  }\n\n  Renderer->DrawProcessedText(TipNumber, Profile::Dialogue::DialogueFont,\n                              Tint.a, RendererOutlineMode::Full);\n  if (TipEntryRecord->IsLocked) {\n    Renderer->DrawProcessedText(TipLockedText, Profile::Dialogue::DialogueFont,\n                                Tint.a, RendererOutlineMode::Full);\n  } else {\n    Renderer->DrawProcessedText(Text, Profile::Dialogue::DialogueFont, Tint.a,\n                                RendererOutlineMode::Full);\n    if (TipEntryRecord->IsNew) {\n      Renderer->DrawProcessedText(NewText, Profile::Dialogue::DialogueFont,\n                                  Tint.a, RendererOutlineMode::Full);\n    }\n  }\n}\n\n}  // namespace MO6TW\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/widgets/mo6tw/tipsentrybutton.h",
    "content": "#pragma once\n\n#include \"../button.h\"\n#include \"../../../data/tipssystem.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\nnamespace MO6TW {\n\nclass TipsEntryButton : public Widgets::Button {\n public:\n  TipsEntryButton(int id, Impacto::TipsSystem::TipsDataRecord* tipRecord,\n                  RectF const& dest, Sprite const& highlight);\n  void Update(float dt) override;\n  void Render() override;\n\n  Impacto::TipsSystem::TipsDataRecord* TipEntryRecord;\n\n private:\n  std::array<ProcessedTextGlyph, 3> TipNumber;\n  std::array<ProcessedTextGlyph, 3> TipLockedText;\n  std::array<ProcessedTextGlyph, 3> NewText;\n\n  bool PrevUnreadState;\n};\n\n}  // namespace MO6TW\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/widgets/mo6tw/titlebutton.cpp",
    "content": "#include \"titlebutton.h\"\n\n#include \"../../../renderer/renderer.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\nnamespace MO6TW {\n\nvoid TitleButton::Render() {\n  glm::vec4 black(0.0f);\n  black.a = Tint.a;\n  glm::vec4 white(1.0f);\n  white.a = Tint.a;\n\n  if (HasFocus && Enabled) {\n    Renderer->DrawSprite(FocusedSprite, glm::vec2(Bounds.X, Bounds.Y + 1.0f),\n                         black);\n    Renderer->DrawSprite(FocusedSprite, glm::vec2(Bounds.X, Bounds.Y), white);\n  } else if (Enabled) {\n    Renderer->DrawSprite(NormalSprite, glm::vec2(Bounds.X, Bounds.Y + 1.0f),\n                         black);\n    Renderer->DrawSprite(NormalSprite, glm::vec2(Bounds.X, Bounds.Y), white);\n  } else {\n    Renderer->DrawSprite(DisabledSprite, glm::vec2(Bounds.X, Bounds.Y + 1.0f),\n                         black);\n    Renderer->DrawSprite(DisabledSprite, glm::vec2(Bounds.X, Bounds.Y), white);\n  }\n}\n\n}  // namespace MO6TW\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/widgets/mo6tw/titlebutton.h",
    "content": "#pragma once\n\n#include \"../button.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\nnamespace MO6TW {\n\nclass TitleButton : public Widgets::Button {\n public:\n  TitleButton(int id, Sprite const& norm, Sprite const& focused,\n              Sprite const& highlight, glm::vec2 pos)\n      : Widgets::Button(id, norm, focused, highlight, pos) {}\n  void Render() override;\n};\n\n}  // namespace MO6TW\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/widgets/optiongroup.cpp",
    "content": "#include \"optiongroup.h\"\n\n#include \"../../inputsystem.h\"\n#include \"../../vm/interface/input.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\n\nusing namespace Impacto::Vm::Interface;\n\nOptionGroup::OptionGroup(Menu* menuContext, Sprite const& background,\n                         Sprite const& highlight, Sprite const& itemHighlight,\n                         glm::vec2 pos, glm::vec2 itemsOffset) {\n  MenuContext = menuContext;\n  BackgroundSprite = background;\n  BackgroundHSprite = highlight;\n  ItemHighlightSprite = itemHighlight;\n  Position = pos;\n  CurrentItemPosition = pos + itemsOffset;\n  Bounds = RectF(pos.x, pos.y, BackgroundSprite.ScaledWidth(),\n                 BackgroundSprite.ScaledHeight());\n}\n\nvoid OptionGroup::Update(float dt) {\n  for (const auto& item : Items) {\n    item->Update(dt);\n  }\n}\n\nvoid OptionGroup::UpdateInput(float dt) {\n  if (Enabled) {\n    bool mouseInput = Input::CurrentInputDevice == Input::Device::Mouse ||\n                      Input::CurrentInputDevice == Input::Device::Touch;\n\n    if (GroupEntered || mouseInput) {\n      for (const auto& item : Items) {\n        if (item->Enabled && item->Hovered && mouseInput) {\n          if (MenuContext->CurrentlyFocusedElement)\n            MenuContext->CurrentlyFocusedElement->HasFocus = false;\n          item->HasFocus = true;\n          MenuContext->CurrentlyFocusedElement = item;\n        }\n        item->UpdateInput(dt);\n      }\n\n      if (!mouseInput && (GetControlState(CT_OK) || GetControlState(CT_Back))) {\n        GroupEntered = false;\n        MenuContext->CurrentlyFocusedElement->HasFocus = false;\n        MenuContext->CurrentlyFocusedElement = this;\n        MenuContext->CurrentlyFocusedElement->HasFocus = true;\n        PADinputButtonWentDown &= ~PADcustom[6];\n        PADinputButtonWentDown &= ~PADcustom[5];\n      }\n    }\n\n    if (HasFocus) {\n      GroupEntered = false;\n\n      if (!mouseInput && GetControlState(CT_OK)) {\n        GroupEntered = true;\n        if (Items.size() > 0) {\n          MenuContext->CurrentlyFocusedElement->HasFocus = false;\n          MenuContext->CurrentlyFocusedElement = Items.at(0);\n          MenuContext->CurrentlyFocusedElement->HasFocus = true;\n        }\n      }\n    }\n\n    if (Input::CurrentInputDevice == Input::Device::Mouse &&\n        Input::PrevMousePos != Input::CurMousePos) {\n      Hovered = Bounds.ContainsPoint(Input::CurMousePos);\n    } else if (Input::CurrentInputDevice == Input::Device::Mouse &&\n               Input::TouchIsDown[0] &&\n               Input::PrevTouchPos != Input::CurTouchPos) {\n      Hovered = Bounds.ContainsPoint(Input::CurTouchPos);\n    }\n  }\n}\n\nvoid OptionGroup::Render() {\n  if (HasFocus || GroupEntered)\n    Renderer->DrawSprite(BackgroundHSprite, Position, Tint);\n  else\n    Renderer->DrawSprite(BackgroundSprite, Position, Tint);\n\n  for (const auto& item : Items) {\n    item->Tint = Tint;\n    item->Render();\n\n    if (GroupEntered && item->HasFocus) {\n      Renderer->DrawSprite(ItemHighlightSprite,\n                           glm::vec2(item->Bounds.X, item->Bounds.Y), Tint);\n    }\n  }\n}\n\nvoid OptionGroup::AddOption(Toggle* item) {\n  item->MoveTo(CurrentItemPosition);\n  CurrentItemPosition.x += (item->Bounds.Width + ItemPadding);\n  Items.push_back(item);\n\n  if (LastFocusableElementId != -1) {\n    auto el = Items.at(LastFocusableElementId);\n    el->SetFocus(item, FDIR_RIGHT);\n    item->SetFocus(el, FDIR_LEFT);\n  }\n  LastFocusableElementId = (int)Items.size() - 1;\n  auto firstEl = Items.at(0);\n  item->SetFocus(firstEl, FDIR_RIGHT);\n  firstEl->SetFocus(item, FDIR_LEFT);\n}\n\nvoid OptionGroup::AddOption(Scrollbar* item) {\n  item->MoveTo(CurrentItemPosition);\n  CurrentItemPosition.x += (item->Bounds.Width + ItemPadding);\n  Items.push_back(item);\n}\n\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/widgets/optiongroup.h",
    "content": "#pragma once\n\n#include \"toggle.h\"\n#include \"scrollbar.h\"\n#include \"../widget.h\"\n#include \"../menu.h\"\n#include \"../../text/text.h\"\n#include \"../../renderer/renderer.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\n\nclass OptionGroup : public Widget {\n public:\n  OptionGroup(Menu* menuContext, Sprite const& background,\n              Sprite const& highlight, Sprite const& itemHighlight,\n              glm::vec2 pos, glm::vec2 itemsOffset);\n\n  void Update(float dt) override;\n  void UpdateInput(float dt) override;\n  void Render() override;\n\n  float ItemPadding = 0.0f;\n\n  void AddOption(Toggle* item);\n  void AddOption(Scrollbar* item);\n\n private:\n  bool GroupEntered = false;\n  int LastFocusableElementId = -1;\n\n  Menu* MenuContext;\n  glm::vec2 Position;\n  glm::vec2 CurrentItemPosition;\n  Sprite BackgroundSprite;\n  Sprite BackgroundHSprite;\n  Sprite ItemHighlightSprite;\n  std::vector<Widget*> Items;\n};\n\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/widgets/rne/sysmenubutton.cpp",
    "content": "#include \"sysmenubutton.h\"\n\n#include \"../../../renderer/renderer.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\nnamespace RNE {\n\nvoid SysMenuButton::Render() {\n  glm::vec4 black = glm::vec4(0.0f);\n  black.a = Tint.a;\n\n  if (HasFocus) {\n    Renderer->DrawSprite(HighlightSprite, glm::vec2(Bounds.X, Bounds.Y), Tint);\n    Renderer->DrawSprite(FocusedSprite, glm::vec2(Bounds.X, Bounds.Y), Tint);\n  } else {\n    if (Enabled) {\n      Renderer->DrawSprite(NormalSprite, glm::vec2(Bounds.X, Bounds.Y), black);\n    } else {\n      Renderer->DrawSprite(DisabledSprite, glm::vec2(Bounds.X, Bounds.Y), Tint);\n    }\n  }\n}\n\n}  // namespace RNE\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/widgets/rne/sysmenubutton.h",
    "content": "#pragma once\n\n#include \"../button.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\nnamespace RNE {\n\nclass SysMenuButton : public Widgets::Button {\n public:\n  SysMenuButton(int id, Sprite const& norm, Sprite const& focused,\n                Sprite const& highlight, glm::vec2 pos)\n      : Widgets::Button(id, norm, focused, highlight, pos) {}\n  void Render() override;\n};\n\n}  // namespace RNE\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/widgets/scrollbar.cpp",
    "content": "#include \"scrollbar.h\"\n#include \"../../renderer/renderer.h\"\n#include \"../../inputsystem.h\"\n#include \"../../vm/interface/input.h\"\n#include \"../../renderer/window.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\n\nusing namespace Impacto::Profile::ScriptVars;\nusing namespace Impacto::Vm::Interface;\n\nScrollbar::Scrollbar(int id, glm::vec2 pos, float start, float end,\n                     float* value, ScrollbarDirection dir,\n                     glm::vec2 trackBounds)\n    : Id(id),\n      Direction(dir),\n      Length(dir == SBDIR_VERTICAL ? trackBounds.y : trackBounds.x),\n      StartValue(start),\n      EndValue(end),\n      Value(value),\n      Step((end - start) * 0.01f),\n      TrackBounds(pos.x, pos.y, trackBounds.x, trackBounds.y),\n      ThumbBounds(0.0f, 0.0f, 0.0f, 0.0f),\n      ThumbLength(0.0f) {\n  Bounds.SetPos(pos);\n  UpdatePosition();\n}\n\nScrollbar::Scrollbar(int id, glm::vec2 pos, float start, float end,\n                     float* value, ScrollbarDirection dir, Sprite const& thumb,\n                     glm::vec2 trackBounds, float thumbLength,\n                     RectF wheelBounds, float wheelSpeedMultiplier)\n    : Id(id),\n      Direction(dir),\n      ThumbSprite(thumb),\n      StartValue(start),\n      EndValue(end),\n      Value(value),\n      TrackBounds(pos.x, pos.y, trackBounds.x, trackBounds.y),\n      ScrollWheelBounds(wheelBounds == RectF() ? TrackBounds : wheelBounds),\n      ThumbLength(thumbLength),\n      WheelSpeedMultiplier(wheelSpeedMultiplier) {\n  Enabled = true;\n  Bounds.SetPos(pos);\n  Step = (EndValue - StartValue) * 0.01f;\n  Length = Direction == SBDIR_VERTICAL ? trackBounds.y : trackBounds.x;\n  UpdatePosition();\n}\n\nScrollbar::Scrollbar(int id, glm::vec2 pos, float start, float end,\n                     float* value, ScrollbarDirection dir, Sprite const& track,\n                     Sprite const& thumb, glm::vec2 thumbOffset,\n                     float thumbLength, RectF wheelBounds,\n                     float wheelSpeedMultiplier)\n    : Id(id),\n      Direction(dir),\n      ThumbSprite(thumb),\n      TrackSprite(track),\n      StartValue(start),\n      EndValue(end),\n      Value(value),\n      Step((EndValue - StartValue) * 0.01f),\n      ThumbSpriteOffset(thumbOffset),\n      TrackBounds(pos.x, pos.y, track.ScaledWidth(), track.ScaledHeight()),\n      ScrollWheelBounds(wheelBounds == RectF() ? TrackBounds : wheelBounds),\n      ThumbLength(thumbLength),\n      WheelSpeedMultiplier(wheelSpeedMultiplier) {\n  Enabled = true;\n  Bounds.SetPos(pos);\n  Length = Direction == SBDIR_VERTICAL ? TrackSprite->Bounds.Height\n                                       : TrackSprite->Bounds.Width;\n  ThumbBounds =\n      RectF(0.0f, 0.0f, ThumbSprite.ScaledWidth(), ThumbSprite.ScaledHeight());\n  if (Direction == SBDIR_VERTICAL) {\n    ThumbBounds.X = (TrackBounds.X + (TrackBounds.Width / 2.0f)) -\n                    (ThumbSprite.ScaledWidth() / 2.0f);\n    ThumbBounds.Y = TrackBounds.Y - (ThumbSprite.ScaledHeight() / 2.0f);\n  } else if (Direction == SBDIR_HORIZONTAL) {\n    ThumbBounds.X = TrackBounds.X - (ThumbSprite.ScaledWidth() / 2.0f);\n    ThumbBounds.Y = (TrackBounds.Y + (TrackBounds.Height / 2.0f)) -\n                    (ThumbSprite.ScaledHeight() / 2.0f);\n  }\n  UpdatePosition();\n}\n\nScrollbar::Scrollbar(int id, glm::vec2 pos, float start, float end,\n                     float* value, ScrollbarDirection dir, Sprite const& track,\n                     Sprite const& thumb, Sprite const& fill,\n                     glm::vec2 thumbOffset, float thumbLength,\n                     RectF wheelBounds, float wheelSpeedMultiplier)\n    : Scrollbar(id, pos, start, end, value, dir, track, thumb, thumbOffset,\n                thumbLength, wheelBounds, wheelSpeedMultiplier) {\n  FillSprite = fill;\n  UpdatePosition();\n}\n\nvoid Scrollbar::UpdateInput(float dt) {\n  if (Enabled) {\n    if (HasFocus) {\n      switch (Direction) {\n        case SBDIR_VERTICAL:\n          if (PADinputButtonIsDown & PAD1DOWN) {\n            *Value += Step;\n          } else if (PADinputButtonIsDown & PAD1UP) {\n            *Value -= Step;\n          }\n          break;\n        case SBDIR_HORIZONTAL:\n          if (PADinputButtonIsDown & PAD1RIGHT) {\n            *Value += Step;\n          } else if (PADinputButtonIsDown & PAD1LEFT) {\n            *Value -= Step;\n          }\n          break;\n      }\n    }\n\n    if (Input::PrevMousePos != Input::CurMousePos) {\n      Hovered = TrackBounds.ContainsPoint(Input::CurMousePos) ||\n                ThumbBounds.ContainsPoint(Input::CurMousePos);\n      HoveredWheelBounds = ScrollWheelBounds.ContainsPoint(Input::CurMousePos);\n    }\n    if (Hovered && Input::MouseButtonWentDown[SDL_BUTTON_LEFT]) {\n      ScrollHeld = true;\n    }\n    if (Input::MouseButtonIsDown[SDL_BUTTON_LEFT] && ScrollHeld) {\n      float mouseP, trackP1, trackP2;\n      switch (Direction) {\n        case SBDIR_VERTICAL:\n          mouseP = Input::CurMousePos.y;\n          trackP1 = TrackBounds.Y;\n          trackP2 = TrackBounds.Height;\n          break;\n\n        case SBDIR_HORIZONTAL:\n          mouseP = Input::CurMousePos.x;\n          trackP1 = TrackBounds.X;\n          trackP2 = TrackBounds.Width;\n          break;\n\n        default:\n          throw std::runtime_error(\n              fmt::format(\"Unexpected scrollbar direction {}\", (int)Direction));\n      }\n\n      float thumbNormalizedLength =\n          (trackP2 - ThumbLength) / (EndValue - StartValue);\n      *Value = StartValue + ((mouseP - (trackP1 + ThumbLength / 2.0f)) /\n                             thumbNormalizedLength);\n      ClampValue();\n    } else {\n      ScrollHeld = false;\n    }\n\n    if (HoveredWheelBounds && Input::MouseWheelDeltaY) {\n      *Value -= Input::MouseWheelDeltaY * WheelSpeedMultiplier * Step;\n      ClampValue();\n    }\n  }\n}\n\nvoid Scrollbar::Render() {\n  if (FillSprite.has_value() && FillBeforeTrack) {\n    Renderer->DrawSprite(*FillSprite, glm::vec2(TrackBounds.X, TrackBounds.Y),\n                         Tint);\n  }\n  if (TrackSprite.has_value()) {\n    Renderer->DrawSprite(*TrackSprite, TrackBounds, Tint);\n  }\n  if (FillSprite.has_value() && !FillBeforeTrack) {\n    Renderer->DrawSprite(*FillSprite, glm::vec2(TrackBounds.X, TrackBounds.Y),\n                         Tint);\n  }\n  Renderer->DrawSprite(ThumbSprite, glm::vec2(ThumbBounds.X, ThumbBounds.Y),\n                       Tint);\n}\n\nvoid Scrollbar::Hide() {\n  Widget::Hide();\n  ScrollHeld = false;\n}\n\nvoid Scrollbar::Move(glm::vec2 relativePosition) {\n  Widget::Move(relativePosition);\n\n  TrackBounds += relativePosition;\n  ThumbBounds += relativePosition;\n}\n\nvoid Scrollbar::ClampValue() {\n  auto [minValue, maxValue] = std::minmax(StartValue, EndValue);\n  *Value = std::clamp(*Value, minValue, maxValue);\n}\n\nvoid Scrollbar::Update(float dt) {\n  Widget::Update(dt);\n  UpdatePosition();\n\n  if (Enabled && HoveredWheelBounds &&\n      Input::CurrentInputDevice == Input::Device::Mouse) {\n    RequestCursor(CursorType::Pointer);\n  }\n}\n\nvoid Scrollbar::UpdatePosition() {\n  TrackProgress = ((*Value - StartValue) / (EndValue - StartValue)) * Length;\n  if (TrackProgress > Length) {\n    *Value = EndValue;\n    TrackProgress = Length;\n  } else if (TrackProgress < 0.0f) {\n    *Value = StartValue;\n    TrackProgress = 0.0f;\n  }\n\n  float thumbNormalizedProgress =\n      (TrackProgress / Length) * (Length - ThumbLength);\n  if (Direction == SBDIR_VERTICAL) {\n    ThumbBounds.X = (TrackBounds.X + (TrackBounds.Width / 2.0f)) -\n                    (ThumbSprite.ScaledWidth() / 2.0f);\n    ThumbBounds.Y =\n        (TrackBounds.Y + ThumbLength / 2.0f + thumbNormalizedProgress) -\n        (ThumbSprite.ScaledHeight() / 2.0f);\n\n    if (FillSprite) FillSprite->Bounds.Height = TrackProgress;\n  } else if (Direction == SBDIR_HORIZONTAL) {\n    ThumbBounds.X =\n        (TrackBounds.X + ThumbLength / 2.0f + thumbNormalizedProgress) -\n        (ThumbSprite.ScaledWidth() / 2.0f);\n    ThumbBounds.Y = (TrackBounds.Y + (TrackBounds.Height / 2.0f)) -\n                    (ThumbSprite.ScaledHeight() / 2.0f);\n\n    if (FillSprite) FillSprite->Bounds.Width = TrackProgress;\n  }\n\n  ThumbBounds.X += ThumbSpriteOffset.x;\n  ThumbBounds.Y += ThumbSpriteOffset.y;\n}\n\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/widgets/scrollbar.h",
    "content": "#pragma once\n\n#include \"../widget.h\"\n#include \"../../spritesheet.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\n\nenum ScrollbarDirection { SBDIR_VERTICAL, SBDIR_HORIZONTAL };\n\nclass Scrollbar : public Widget {\n public:\n  Scrollbar() = default;\n  Scrollbar(int id, glm::vec2 pos, float start, float end, float* value,\n            ScrollbarDirection dir, glm::vec2 trackBounds);\n  Scrollbar(int id, glm::vec2 pos, float start, float end, float* value,\n            ScrollbarDirection dir, Sprite const& thumb, glm::vec2 trackBounds,\n            float thumbLength, RectF wheelBounds = RectF(),\n            float wheelSpeedMultiplier = 1.0f);\n  Scrollbar(int id, glm::vec2 pos, float start, float end, float* value,\n            ScrollbarDirection dir, Sprite const& track, Sprite const& thumb,\n            glm::vec2 thumbOffset = glm::vec2(0.0f, 0.0f),\n            float thumbLength = 0.0f, RectF wheelBounds = RectF(),\n            float wheelSpeedMultiplier = 1.0f);\n  Scrollbar(int id, glm::vec2 pos, float start, float end, float* value,\n            ScrollbarDirection dir, Sprite const& track, Sprite const& thumb,\n            Sprite const& fill, glm::vec2 thumbOffset = glm::vec2(0.0f, 0.0f),\n            float thumbLength = 0.0f, RectF wheelBounds = RectF(),\n            float wheelSpeedMultiplier = 1.0f);\n  virtual void UpdateInput(float dt) override;\n  virtual void Update(float dt) override;\n  virtual void Render() override;\n\n  void Hide() override;\n\n  using Widget::Move;\n  void Move(glm::vec2 relativePosition) override;\n\n  void ClampValue();\n  float GetNormalizedValue() const {\n    return (*Value - StartValue) / (EndValue - StartValue);\n  };\n\n  RectF GetTrackBounds() const { return TrackBounds; }\n  bool IsScrollHeld() const { return ScrollHeld; }\n\n  int Id;\n  ScrollbarDirection Direction;\n  Sprite ThumbSprite;\n  std::optional<Sprite> TrackSprite;\n  std::optional<Sprite> FillSprite;\n  float Length;\n  float StartValue;\n  float EndValue;\n  float* Value;\n\n  bool FillBeforeTrack = false;\n  bool HoveredWheelBounds = false;\n  float Step = 0.0f;\n\n protected:\n  float TrackProgress = 0.0f;\n  glm::vec2 ThumbPosition;\n  glm::vec2 ThumbSpriteOffset = glm::vec2(0.0f, 0.0f);\n  RectF TrackBounds;\n  RectF ThumbBounds;\n  RectF ScrollWheelBounds;\n  float ThumbLength;\n  bool ScrollHeld = false;\n  int LastScrollPos = 0;\n  float WheelSpeedMultiplier = 1.0f;\n  void UpdatePosition();\n};\n\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/widgets/toggle.cpp",
    "content": "#include \"toggle.h\"\n#include \"../../vm/thread.h\"\n#include \"../../profile/dialogue.h\"\n#include \"../../renderer/renderer.h\"\n#include \"../../inputsystem.h\"\n#include \"../../vm/interface/input.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\n\nToggle::Toggle(int id, bool* value, Sprite const& enabled,\n               Sprite const& disabled, Sprite const& highlight, glm::vec2 pos,\n               bool isCheckbox) {\n  Id = id;\n  Value = value;\n  EnabledSprite = enabled;\n  DisabledSprite = disabled;\n  HighlightSprite = highlight;\n  IsCheckbox = isCheckbox;\n  Bounds = RectF(pos.x, pos.y, EnabledSprite.ScaledWidth(),\n                 EnabledSprite.ScaledHeight());\n}\n\nToggle::Toggle(int id, bool* value, Sprite const& enabled,\n               Sprite const& disabled, Sprite const& highlight, glm::vec2 pos,\n               bool isCheckbox, Vm::Sc3Stream& stream, glm::vec2 labelOfs,\n               float fontSize, RendererOutlineMode outlineMode)\n    : Toggle(id, value, enabled, disabled, highlight, pos, isCheckbox) {\n  HasTextLabel = true;\n  SetText(stream, fontSize, outlineMode);\n}\n\nToggle::Toggle(int id, bool* value, Sprite const& enabled,\n               Sprite const& disabled, Sprite const& highlight, glm::vec2 pos,\n               bool isCheckbox, Sprite const& label, glm::vec2 labelOfs)\n    : Toggle(id, value, enabled, disabled, highlight, pos, isCheckbox) {\n  HasSpriteLabel = true;\n  LabelSprite = label;\n  LabelOffset = labelOfs;\n}\n\nvoid Toggle::UpdateInput(float dt) {\n  if (Enabled) {\n    if (Input::CurrentInputDevice == Input::Device::Mouse &&\n        Input::PrevMousePos != Input::CurMousePos) {\n      Hovered = Bounds.ContainsPoint(Input::CurMousePos);\n    } else if (Input::CurrentInputDevice == Input::Device::Touch &&\n               Input::TouchIsDown[0] &&\n               Input::PrevTouchPos != Input::CurTouchPos) {\n      Hovered = Bounds.ContainsPoint(Input::CurTouchPos);\n    }\n    if (HasFocus &&\n        ((Hovered &&\n          Vm::Interface::PADinputMouseWentDown & Vm::Interface::PAD1A) ||\n         (Vm::Interface::PADinputButtonWentDown & Vm::Interface::PAD1A))) {\n      *Value = !*Value;\n      if (OnClickHandler) OnClickHandler(this);\n    }\n  }\n}\n\nvoid Toggle::Update(float dt) { Widget::Update(dt); }\n\nvoid Toggle::Render() {\n  if (!*Value || IsCheckbox) {\n    Renderer->DrawSprite(DisabledSprite, glm::vec2(Bounds.X, Bounds.Y), Tint);\n  }\n  if (*Value) {\n    Renderer->DrawSprite(EnabledSprite, glm::vec2(Bounds.X, Bounds.Y), Tint);\n  }\n  if (HasFocus) {\n    auto tint = Tint;\n    if (IsCheckbox) tint.a *= 0.5f;\n    Renderer->DrawSprite(HighlightSprite, glm::vec2(Bounds.X, Bounds.Y), tint);\n  }\n  if (HasSpriteLabel) {\n    Renderer->DrawSprite(LabelSprite,\n                         glm::vec2(Bounds.X, Bounds.Y) + LabelOffset, Tint);\n  }\n  if (HasTextLabel) {\n    Renderer->DrawProcessedText(Label, Profile::Dialogue::DialogueFont, Tint.a,\n                                OutlineMode, true);\n  }\n}\n\nvoid Toggle::SetText(Vm::Sc3Stream& stream, float fontSize,\n                     RendererOutlineMode outlineMode) {\n  Label = TextLayoutPlainLine(stream, 255, Profile::Dialogue::DialogueFont,\n                              fontSize, Profile::Dialogue::ColorTable[10], 1.0f,\n                              glm::vec2(Bounds.X, Bounds.Y) + LabelOffset,\n                              TextAlignment::Left);\n  OutlineMode = outlineMode;\n  FontSize = fontSize;\n  for (const ProcessedTextGlyph& glyph : Label) {\n    TextWidth += glyph.DestRect.Width;\n  }\n}\n\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/ui/widgets/toggle.h",
    "content": "#pragma once\n\n#include <functional>\n\n#include \"../widget.h\"\n#include \"../../text/text.h\"\n#include \"../../renderer/renderer.h\"\n\nnamespace Impacto {\nnamespace UI {\nnamespace Widgets {\n\nclass Toggle : public Widget {\n public:\n  Toggle(int id, bool* value, Sprite const& enabled, Sprite const& disabled,\n         Sprite const& highlight, glm::vec2 pos, bool isCheckbox);\n  Toggle(int id, bool* value, Sprite const& enabled, Sprite const& disabled,\n         Sprite const& highlight, glm::vec2 pos, bool isCheckbox,\n         Vm::Sc3Stream& stream, glm::vec2 labelOfs, float fontSize,\n         RendererOutlineMode outlineMode);\n  Toggle(int id, bool* value, Sprite const& enabled, Sprite const& disabled,\n         Sprite const& highlight, glm::vec2 pos, bool isCheckbox,\n         Sprite const& label, glm::vec2 labelOfs);\n\n  void Update(float dt) override;\n  void UpdateInput(float dt) override;\n  void Render() override;\n\n  int Id;\n  bool* Value;\n  std::function<void(Toggle*)> OnClickHandler;\n\n private:\n  void SetText(Vm::Sc3Stream& stream, float fontSize,\n               RendererOutlineMode outlineMode);\n\n  Sprite EnabledSprite;\n  Sprite DisabledSprite;\n  Sprite HighlightSprite;\n\n  bool IsCheckbox = false;\n\n  bool HasSpriteLabel = false;\n  Sprite LabelSprite;\n\n  glm::vec2 LabelOffset;\n\n  bool HasTextLabel = false;\n  std::vector<ProcessedTextGlyph> Label;\n  float FontSize;\n  float TextWidth = 0.0f;\n  RendererOutlineMode OutlineMode;\n};\n\n}  // namespace Widgets\n}  // namespace UI\n}  // namespace Impacto"
  },
  {
    "path": "src/util.cpp",
    "content": "#include \"util.h\"\n\n#include \"profile/game.h\"\n#include \"log.h\"\n#include <memory>\n#include <ctime>\n\nnamespace Impacto {\n\nCornersQuad& CornersQuad::Transform(const glm::mat4 transformation) {\n  TopLeft = transformation * glm::vec4(TopLeft, 0.0f, 1.0f);\n  TopRight = transformation * glm::vec4(TopRight, 0.0f, 1.0f);\n  BottomRight = transformation * glm::vec4(BottomRight, 0.0f, 1.0f);\n  BottomLeft = transformation * glm::vec4(BottomLeft, 0.0f, 1.0f);\n\n  return *this;\n}\n\nCornersQuad& CornersQuad::Transform(\n    const std::function<glm::vec2(glm::vec2)>& transformation) {\n  TopLeft = transformation(TopLeft);\n  TopRight = transformation(TopRight);\n  BottomLeft = transformation(BottomLeft);\n  BottomRight = transformation(BottomRight);\n\n  return *this;\n}\n\nCornersQuad& CornersQuad::Translate(const glm::vec2 offset) {\n  TopLeft += offset;\n  TopRight += offset;\n  BottomRight += offset;\n  BottomLeft += offset;\n\n  return *this;\n}\n\nCornersQuad& CornersQuad::Scale(const glm::vec2 scaling,\n                                const glm::vec2 origin) {\n  Translate(-origin);\n\n  TopLeft *= scaling;\n  TopRight *= scaling;\n  BottomRight *= scaling;\n  BottomLeft *= scaling;\n\n  Translate(origin);\n\n  return *this;\n}\n\nCornersQuad& CornersQuad::Rotate(const float angle, const glm::vec2 origin) {\n  Translate(-origin);\n\n  const glm::mat2 rotation = Rotate2D(angle);\n  TopLeft = rotation * TopLeft;\n  TopRight = rotation * TopRight;\n  BottomRight = rotation * BottomRight;\n  BottomLeft = rotation * BottomLeft;\n\n  Translate(origin);\n\n  return *this;\n}\n\nCornersQuad& CornersQuad::Rotate(const glm::quat rotation, glm::vec3 origin) {\n  glm::vec3 topLeft = glm::vec3(TopLeft, 0.0f) - origin;\n  glm::vec3 topRight = glm::vec3(TopRight, 0.0f) - origin;\n  glm::vec3 bottomRight = glm::vec3(BottomRight, 0.0f) - origin;\n  glm::vec3 bottomLeft = glm::vec3(BottomLeft, 0.0f) - origin;\n\n  const glm::mat3 rotationMatrix = glm::mat3_cast(rotation);\n  topLeft = rotationMatrix * topLeft;\n  topRight = rotationMatrix * topRight;\n  bottomRight = rotationMatrix * bottomRight;\n  topRight = rotationMatrix * topRight;\n\n  TopLeft = topLeft + origin;\n  TopRight = topRight + origin;\n  BottomRight = bottomRight + origin;\n  BottomLeft = bottomLeft + origin;\n\n  return *this;\n}\n\nCornersQuad& CornersQuad::Rotate(const glm::quat rotation,\n                                 const glm::vec3 origin, const float depth,\n                                 const glm::vec2 vanishingPoint,\n                                 const bool stayInScreen) {\n  std::array<glm::vec4, 4> vertices = {\n      glm::vec4(TopLeft, 0.0f, 1.0f), glm::vec4(BottomLeft, 0.0f, 1.0f),\n      glm::vec4(TopRight, 0.0f, 1.0f), glm::vec4(BottomRight, 0.0f, 1.0f)};\n\n  // Rotate\n  const glm::mat4 rotationMatrix = glm::mat4_cast(rotation);\n  for (glm::vec4& vertex : vertices) {\n    vertex -= glm::vec4(origin, 0.0f);\n    vertex = rotationMatrix * vertex;\n    vertex += glm::vec4(origin, 0.0f);\n  }\n\n  // Move into screen\n  if (stayInScreen) {\n    float maxZ =\n        std::max_element(vertices.begin(), vertices.end(),\n                         [](auto lhs, auto rhs) { return lhs.z < rhs.z; })\n            ->z;\n\n    for (glm::vec4& vertex : vertices) vertex.z -= maxZ;\n  }\n\n  // Project\n  for (glm::vec4& vertex : vertices) {\n    vertex -= glm::vec4(vanishingPoint, 0.0f, 0.0f);\n    const float normalizedZ = vertex.z / glm::length(glm::vec3(vertex));\n    vertex *= depth / (depth - normalizedZ);\n    vertex += glm::vec4(vanishingPoint, 0.0f, 0.0f);\n  }\n\n  *this = {glm::vec2(vertices[0]), glm::vec2(vertices[1]),\n           glm::vec2(vertices[2]), glm::vec2(vertices[3])};\n  return *this;\n}\n\nCornersQuad RectF::Transform(glm::mat4 transformation) const {\n  return CornersQuad(*this).Transform(transformation);\n}\n\nCornersQuad RectF::Transform(\n    const std::function<glm::vec2(glm::vec2)>& transformation) const {\n  return CornersQuad(*this).Transform(transformation);\n}\n\nRectF& RectF::Scale(const glm::vec2 scaling, const glm::vec2 origin) {\n  Translate(-origin);\n\n  X *= scaling.x;\n  Y *= scaling.y;\n  Width *= scaling.x;\n  Height *= scaling.y;\n\n  Translate(origin);\n\n  return *this;\n}\n\nCornersQuad RectF::Rotate(float angle, glm::vec2 origin) const {\n  return CornersQuad(*this).Rotate(angle, origin);\n}\n\nCornersQuad RectF::RotateAroundCenter(float angle) const {\n  return Rotate(angle, Center());\n}\n\nCornersQuad RectF::Rotate(glm::quat rotation, glm::vec3 origin) const {\n  return CornersQuad(*this).Rotate(rotation, origin);\n}\n\nCornersQuad RectF::Rotate(const glm::quat rotation, const glm::vec3 origin,\n                          const float depth, const glm::vec2 vanishingPoint,\n                          const bool stayInScreen) const {\n  return CornersQuad(*this).Rotate(rotation, origin, depth, vanishingPoint,\n                                   stayInScreen);\n}\n\nCornersQuad RectF::FlipVertical() const {\n  return CornersQuad(*this).FlipVertical();\n}\n\nCornersQuad RectF::FlipHorizontal() const {\n  return CornersQuad(*this).FlipHorizontal();\n}\n\ninline CornersQuad operator*(const glm::mat4 transformation, RectF rect) {\n  return rect.Transform(transformation);\n}\n\nglm::mat2 Rotate2D(float angle) {\n  glm::mat2 result;\n  float cosa = cosf(angle);\n  float sina = sinf(angle);\n  result[0][0] = cosa;\n  result[0][1] = sina;\n  result[1][0] = -sina;\n  result[1][1] = cosa;\n  return result;\n}\n\nglm::mat4 TransformationMatrix(const glm::vec2 scalingOrigin,\n                               const glm::vec2 scaling,\n                               const glm::vec2 rotationOrigin,\n                               const float rotation,\n                               const glm::vec2 translation) {\n  return TransformationMatrix(\n      scalingOrigin, scaling, {rotationOrigin, 0.0f},\n      AxisAngleToQuaternion({0.0f, 0.0f, 1.0f}, rotation), translation);\n}\n\nglm::mat4 TransformationMatrix(const glm::vec3 scalingOrigin,\n                               const glm::vec3 scaling,\n                               const glm::vec3 rotationOrigin,\n                               const glm::quat rotation,\n                               const glm::vec3 translation) {\n  glm::mat4 matrix(1.0f);\n\n  if (rotationOrigin + translation != glm::vec3(0.0f)) {\n    matrix = glm::translate(matrix, rotationOrigin + translation);\n  }\n\n  if (rotation != glm::quat()) {\n    matrix = matrix * glm::mat4_cast(glm::normalize(rotation));\n  }\n\n  if (scalingOrigin - rotationOrigin != glm::vec3(0.0f)) {\n    matrix = glm::translate(matrix, scalingOrigin - rotationOrigin);\n  }\n\n  if (scaling != glm::vec3(1.0f)) {\n    matrix = glm::scale(matrix, scaling);\n  }\n\n  if (scalingOrigin != glm::vec3(0.0f)) {\n    matrix = glm::translate(matrix, -scalingOrigin);\n  }\n\n  return matrix;\n}\n\nglm::vec4 TransformVector(const glm::vec2 pos, const glm::vec2 scalingOrigin,\n                          const glm::vec2 scaling,\n                          const glm::vec2 rotationOrigin, const float rotation,\n                          const glm::vec2 translation) {\n  return TransformationMatrix(scalingOrigin, scaling, rotationOrigin, rotation,\n                              translation) *\n         glm::vec4(pos, 0.0f, 1.0f);\n}\n\nglm::vec4 TransformVector(const glm::vec3 pos, const glm::vec3 scalingOrigin,\n                          const glm::vec3 scaling,\n                          const glm::vec3 rotationOrigin,\n                          const glm::quat rotation,\n                          const glm::vec3 translation) {\n  return TransformationMatrix(scalingOrigin, scaling, rotationOrigin, rotation,\n                              translation) *\n         glm::vec4(pos, 1.0f);\n}\n\nuint32_t GetHashCode(const std::span<const uint8_t> data) {\n  uint32_t hash = 2166136261;\n  for (const uint8_t byte : data) {\n    hash = (hash ^ byte) * 16777619;\n  }\n\n  return hash;\n}\n\nstd::string DumpMat4(glm::mat4* matrix, std::string_view columnSeparator,\n                     std::string_view rowSeparator) {\n  return fmt::sprintf(\n      \"%s%.03f%s%.03f%s%.03f%s%.03f%s%s%.03f%s%.03f%s%.03f%s%.03f%s%s\"\n      \"%.03f%s%.03f%s%.03f%s%.03f%s%s%.03f%s%.03f%s%.03f%s%.03f\",\n      columnSeparator, (*matrix)[0][0], columnSeparator, (*matrix)[1][0],\n      columnSeparator, (*matrix)[2][0], columnSeparator, (*matrix)[3][0],\n      rowSeparator, columnSeparator, (*matrix)[0][1], columnSeparator,\n      (*matrix)[1][1], columnSeparator, (*matrix)[2][1], columnSeparator,\n      (*matrix)[3][1], rowSeparator, columnSeparator, (*matrix)[0][2],\n      columnSeparator, (*matrix)[1][2], columnSeparator, (*matrix)[2][2],\n      columnSeparator, (*matrix)[3][2], rowSeparator, columnSeparator,\n      (*matrix)[0][3], columnSeparator, (*matrix)[1][3], columnSeparator,\n      (*matrix)[2][3], columnSeparator, (*matrix)[3][3]);\n}\n\nint ResizeImage(Rect const& srcRect, Rect const& dstRect,\n                std::span<uint8_t> src, std::span<uint8_t> dst) {\n  using SurfacePtr = std::unique_ptr<SDL_Surface, decltype(&SDL_FreeSurface)>;\n  if (srcRect.Width == dstRect.Width && srcRect.Height == dstRect.Height) {\n    assert(dst.size() >= src.size());\n    memcpy(dst.data(), src.data(), src.size());\n    return 0;\n  }\n  const uint32_t sdlFormat = SDL_PIXELFORMAT_RGBA32;\n  const int bytesPerPixel = 4;\n\n  assert(src.size() >=\n         static_cast<size_t>(srcRect.Width * srcRect.Height * bytesPerPixel));\n  SurfacePtr srcSurface(\n      SDL_CreateRGBSurfaceWithFormatFrom(\n          src.data(), srcRect.Width, srcRect.Height, bytesPerPixel * 8,\n          srcRect.Width * bytesPerPixel, sdlFormat),\n      SDL_FreeSurface);\n  SDL_SetSurfaceBlendMode(srcSurface.get(), SDL_BLENDMODE_NONE);\n  if (!srcSurface) {\n    ImpLog(LogLevel::Error, LogChannel::Render,\n           \"SDL_CreateRGBSurfaceWithFormatFrom failed: {:s}\\n\", SDL_GetError());\n    return -1;\n  }\n  assert(dst.size() >= static_cast<size_t>(dstRect.Width * dstRect.Height * 4));\n  SurfacePtr dstSurface(\n      SDL_CreateRGBSurfaceWithFormatFrom(\n          dst.data(), dstRect.Width, dstRect.Height, bytesPerPixel * 8,\n          dstRect.Width * bytesPerPixel, sdlFormat),\n      SDL_FreeSurface);\n  if (!dstSurface) {\n    ImpLog(LogLevel::Error, LogChannel::Render,\n           \"SDL_CreateRGBSurfaceWithFormat failed: {:s}\\n\", SDL_GetError());\n    return -1;\n  }\n  SDL_SetSurfaceBlendMode(dstSurface.get(), SDL_BLENDMODE_NONE);\n\n  SDL_Rect srcRectSDL = {srcRect.X, srcRect.Y, srcRect.Width, srcRect.Height};\n  SDL_Rect dstRectSDL = {dstRect.X, dstRect.Y, dstRect.Width, dstRect.Height};\n  if (SDL_BlitScaled(srcSurface.get(), &srcRectSDL, dstSurface.get(),\n                     &dstRectSDL) != 0) {\n    ImpLog(LogLevel::Error, LogChannel::Render, \"SDL_BlitScaled failed: {:s}\\n\",\n           SDL_GetError());\n    return -1;\n  }\n  return 0;\n}\n\nRectF RectF::Coalesce(const RectF& first, const RectF& second) {\n  RectF rect;\n\n  rect.X = std::min(first.X, second.X);\n  rect.Y = std::min(first.Y, second.Y);\n  rect.Width =\n      std::max(first.X + first.Width, second.X + second.Width) - rect.X;\n  rect.Height =\n      std::max(first.Y + first.Height, second.Y + second.Height) - rect.Y;\n\n  return rect;\n}\n\nRectF RectF::BoundingBox(const RectF& first, const CornersQuad& second) {\n  RectF rect;\n  rect.X = std::min({first.X, second.TopLeft.x, second.BottomLeft.x,\n                     second.TopRight.x, second.BottomRight.x});\n\n  rect.Y = std::min({first.Y, second.TopLeft.y, second.BottomLeft.y,\n                     second.TopRight.y, second.BottomRight.y});\n  rect.Width =\n      std::max({first.X + first.Width, second.TopLeft.x, second.BottomLeft.x,\n                second.TopRight.x, second.BottomRight.x}) -\n      rect.X;\n  rect.Height =\n      std::max({first.Y + first.Height, second.TopLeft.y, second.BottomLeft.y,\n                second.TopRight.y, second.BottomRight.y}) -\n      rect.Y;\n  return rect;\n}\n\nRectF RectF::BoundingBox(const CornersQuad& first, const RectF& second) {\n  return BoundingBox(second, first);\n}\n\nRectF RectF::Intersection(const RectF& first, const RectF& second) {\n  RectF rect;\n\n  rect.X = std::max(first.X, second.X);\n  rect.Y = std::max(first.Y, second.Y);\n  rect.Width = std::min(first.Right(), second.Right()) - rect.X;\n  rect.Height = std::min(first.Bottom(), second.Bottom()) - rect.Y;\n\n  return (rect.Width > 0.0f && rect.Height > 0.0f) ? rect : RectF();\n}\n\ntm CurrentDateTime() {\n  std::time_t now = std::time(nullptr);\n  tm result;\n#if defined(_WIN32)\n  localtime_s(&result, &now);\n#else\n  localtime_r(&now, &result);\n#endif\n  return result;\n}\n\n}  // namespace Impacto"
  },
  {
    "path": "src/util.h",
    "content": "#pragma once\n\n#include \"impacto.h\"\n#include <glm/glm.hpp>\n#include <glm/gtc/quaternion.hpp>\n#include <algorithm>\n#include <SDL_stdinc.h>\n#include <random>\n#include <string>\n#include <cctype>\n#include <chrono>\n#include <span>\n#include <numbers>\n\n#if defined(WIN32) || defined(_WIN32)\n#include <malloc.h>\n#endif\n\n// TODO own _malloca for gcc\n\n#if defined(_malloca)\n#define ImpStackAlloc _malloca\n#define ImpStackFree _freea\n#else\n#define ImpStackAlloc malloc\n#define ImpStackFree free\n#endif\n\n#define TIME_CODE(code)                                      \\\n  do {                                                       \\\n    auto start = std::chrono::high_resolution_clock::now();  \\\n    code;                                                    \\\n    auto end = std::chrono::high_resolution_clock::now();    \\\n    std::chrono::duration<double> elapsed = end - start;     \\\n    ImpLog(LogLevel::Info, LogChannel::General,              \\\n           \"Time elapsed: {:f} seconds\\n\", elapsed.count()); \\\n  } while (0)\n\nnamespace Impacto {\nstruct string_hash {\n  using is_transparent = void;  // enable heterogeneous overloads\n  using is_avalanching = void;  // mark class as high quality avalanching hash\n\n  [[nodiscard]] auto operator()(std::string_view str) const noexcept\n      -> uint64_t {\n    return ankerl::unordered_dense::hash<std::string_view>{}(str);\n  }\n};\n\ntemplate <typename T, typename... Ts>\nusing is_any_of = std::disjunction<std::is_same<T, Ts>...>;\n\ntemplate <typename T, typename... Ts>\nconcept is_any_of_v = is_any_of<T, Ts...>::value;\n\ntemplate <typename>\nstruct is_std_array : std::false_type {};\ntemplate <typename T, std::size_t N>\nstruct is_std_array<std::array<T, N>> : std::true_type {};\n\nglm::mat2 Rotate2D(float angle);\n\nglm::mat4 TransformationMatrix(glm::vec2 scalingOrigin, glm::vec2 scaling,\n                               glm::vec2 rotationOrigin = glm::vec2(0.0f),\n                               float rotation = 0.0f,\n                               glm::vec2 translation = glm::vec2(0.0f));\nglm::mat4 TransformationMatrix(glm::vec3 scalingOrigin, glm::vec3 scaling,\n                               glm::vec3 rotationOrigin = glm::vec3(0.0f),\n                               glm::quat rotation = glm::quat(),\n                               glm::vec3 translation = glm::vec3(0.0f));\ninline glm::mat4 TransformationMatrix(glm::vec2 scalingOrigin,\n                                      glm::vec2 scaling,\n                                      glm::vec3 rotationOrigin,\n                                      glm::quat rotation,\n                                      glm::vec2 translation = glm::vec2(0.0f)) {\n  return TransformationMatrix(glm::vec3(scalingOrigin, 0.0f),\n                              glm::vec3(scaling, 1.0f), rotationOrigin,\n                              rotation, glm::vec3(translation, 0.0f));\n}\n\nglm::vec4 TransformVector(glm::vec2 pos, glm::vec2 scalingOrigin,\n                          glm::vec2 scaling,\n                          glm::vec2 rotationOrigin = glm::vec2(0.0f),\n                          float rotation = 0.0f,\n                          glm::vec2 translation = glm::vec2(0.0f));\nglm::vec4 TransformVector(glm::vec3 pos, glm::vec3 scalingOrigin,\n                          glm::vec3 scaling,\n                          glm::vec3 rotationOrigin = glm::vec3(0.0f),\n                          glm::quat rotation = glm::quat(),\n                          glm::vec3 translation = glm::vec3(0.0f));\ninline glm::vec4 TransformVector(glm::vec2 pos, glm::vec2 scalingOrigin,\n                                 glm::vec2 scaling, glm::vec3 rotationOrigin,\n                                 glm::quat rotation,\n                                 glm::vec2 translation = glm::vec2(0.0f)) {\n  return TransformVector({pos, 0.0f}, glm::vec3(scalingOrigin, 0.0f),\n                         glm::vec3(scaling, 1.0f), rotationOrigin, rotation,\n                         {translation, 0.0f});\n}\n\nstruct Rect;\nstruct CornersQuad;\n\nstruct RectF {\n  float X = 0;\n  float Y = 0;\n  float Width = 0;\n  float Height = 0;\n\n  constexpr RectF() {}\n  constexpr RectF(float x, float y, float width, float height)\n      : X(x), Y(y), Width(width), Height(height) {}\n  constexpr RectF(Rect const& rect);\n\n  constexpr float Left() const { return X; }\n  constexpr float Right() const { return X + Width; }\n  constexpr float Top() const { return Y; }\n  constexpr float Bottom() const { return Y + Height; }\n\n  constexpr glm::vec2 TopLeft() const { return {Left(), Top()}; }\n  constexpr glm::vec2 TopRight() const { return {Right(), Top()}; }\n  constexpr glm::vec2 BottomRight() const { return {Right(), Bottom()}; }\n  constexpr glm::vec2 BottomLeft() const { return {Left(), Bottom()}; }\n\n  // RectF is rotated around center\n  constexpr glm::vec2 Center() const {\n    return glm::vec2(X + Width / 2.0f, Y + Height / 2.0f);\n  }\n\n  constexpr bool ContainsPoint(glm::vec2 point, float angle = 0.0f) const {\n    point -= Center();\n    if (angle != 0.0f) {\n      point = Rotate2D(-angle) * point;\n    }\n\n    return point.x >= -Width / 2.0f && point.x <= Width / 2.0f &&\n           point.y >= -Height / 2.0f && point.y <= Height / 2.0f;\n  }\n\n  constexpr bool Intersects(RectF const& rect) const {\n    return (Left() <= rect.Right() && Right() >= rect.Left() &&\n            Top() <= rect.Bottom() && Bottom() >= rect.Top());\n  }\n\n  constexpr bool Contains(RectF const& rect) const {\n    return (Left() <= rect.Left() && rect.Right() <= Right() &&\n            Top() <= rect.Top() && rect.Bottom() <= Bottom());\n  }\n\n  constexpr RectF operator+(const glm::vec2 movementVector) const {\n    return RectF(X + movementVector.x, Y + movementVector.y, Width, Height);\n  }\n\n  constexpr RectF operator-(const glm::vec2 movementVector) const {\n    return RectF(X - movementVector.x, Y - movementVector.y, Width, Height);\n  }\n\n  constexpr RectF& operator+=(const glm::vec2 movementVector) {\n    X += movementVector.x;\n    Y += movementVector.y;\n\n    return *this;\n  }\n\n  constexpr RectF& operator-=(const glm::vec2 movementVector) {\n    X -= movementVector.x;\n    Y -= movementVector.y;\n\n    return *this;\n  }\n\n  constexpr bool operator==(RectF const& other) const {\n    constexpr auto fltCmp = [](float a, float b) {\n      const auto absDiff = (a - b) < 0 ? b - a : a - b;\n      return absDiff < std::numeric_limits<float>::epsilon();\n    };\n    return fltCmp(X, other.X) && fltCmp(Y, other.Y) &&\n           fltCmp(Width, other.Width) && fltCmp(Height, other.Height);\n  }\n  constexpr bool operator!=(RectF const& other) const {\n    return !(*this == other);\n  }\n\n  constexpr glm::vec2 GetPos() const { return glm::vec2(X, Y); }\n  void SetPos(float x, float y) {\n    X = x;\n    Y = y;\n  }\n  void SetPos(glm::vec2 position) { SetPos(position.x, position.y); }\n\n  constexpr glm::vec2 GetSize() const { return glm::vec2(Width, Height); }\n  void SetSize(float width, float height) {\n    Width = width;\n    Height = height;\n  }\n  void SetSize(glm::vec2 size) { SetSize(size.x, size.y); }\n\n  static RectF Coalesce(const RectF& first, const RectF& second);\n  static RectF BoundingBox(const RectF& first, const CornersQuad& second);\n  static RectF BoundingBox(const CornersQuad& first, const RectF& second);\n  static RectF Intersection(const RectF& first, const RectF& second);\n\n  CornersQuad Transform(glm::mat4 transformation) const;\n  CornersQuad Transform(\n      const std::function<glm::vec2(glm::vec2)>& transformation) const;\n\n  RectF& Translate(glm::vec2 offset) { return *this += offset; }\n  RectF& Translate(float dx, float dy) { return Translate({dx, dy}); }\n\n  RectF& Scale(glm::vec2 scaling, glm::vec2 origin);\n  RectF& ScaleAroundCenter(glm::vec2 scaling) {\n    return Scale(scaling, Center());\n  }\n\n  CornersQuad Rotate(float angle, glm::vec2 origin) const;\n  CornersQuad RotateAroundCenter(float angle) const;\n  CornersQuad Rotate(glm::quat rotation, glm::vec3 origin) const;\n  CornersQuad Rotate(glm::quat rotation, glm::vec3 origin, float depth,\n                     glm::vec2 vanishingPoint, bool stayInScreen = false) const;\n\n  CornersQuad FlipVertical() const;\n  CornersQuad FlipHorizontal() const;\n};\n\ninline CornersQuad operator*(const glm::mat4 transformation, RectF rect);\n\nstruct Rect {\n  int X = 0;\n  int Y = 0;\n  int Width = 0;\n  int Height = 0;\n\n  constexpr Rect() {}\n  constexpr Rect(int x, int y, int width, int height)\n      : X(x), Y(y), Width(width), Height(height) {}\n  constexpr Rect(RectF const& rect)\n      : Rect((int)rect.X, (int)rect.Y, (int)rect.Width, (int)rect.Height) {}\n\n  constexpr int Left() const { return X; }\n  constexpr int Right() const { return X + Width; }\n  constexpr int Top() const { return Y; }\n  constexpr int Bottom() const { return Y + Height; }\n\n  constexpr glm::ivec2 TopLeft() const { return {Left(), Top()}; }\n  constexpr glm::ivec2 TopRight() const { return {Right(), Top()}; }\n  constexpr glm::ivec2 BottomRight() const { return {Right(), Bottom()}; }\n  constexpr glm::ivec2 BottomLeft() const { return {Left(), Bottom()}; }\n\n  // Rect is rotated around center\n  constexpr glm::ivec2 Center() const {\n    return glm::ivec2(X + Width / 2, Y + Height / 2);\n  }\n  constexpr bool ContainsPoint(glm::vec2 point, float angle = 0.0f) const {\n    point -= Center();\n    if (angle != 0.0f) {\n      point = (glm::ivec2)(Rotate2D(-angle) * (glm::vec2)point);\n    }\n    return point.x >= -Width / 2 && point.x <= Width / 2 &&\n           point.y >= -Height / 2 && point.y <= Height / 2;\n  }\n\n  constexpr bool Intersects(Rect const& rect) const {\n    return (Left() <= rect.Right() && Right() >= rect.Left() &&\n            Top() <= rect.Bottom() && Bottom() >= rect.Top());\n  }\n\n  constexpr bool operator==(Rect const& other) const {\n    return X == other.X && Y == other.Y && Width == other.Width &&\n           Height == other.Height;\n  }\n\n  constexpr bool operator!=(Rect const& other) const {\n    return !(*this == other);\n  }\n};\n\nstruct CornersQuad {\n private:\n  struct Args {\n    glm::vec2 TL;\n    glm::vec2 BL;\n    glm::vec2 TR;\n    glm::vec2 BR;\n  };\n\n public:\n  glm::vec2 TopLeft;\n  glm::vec2 BottomLeft;\n  glm::vec2 TopRight;\n  glm::vec2 BottomRight;\n\n  constexpr CornersQuad(Args args)\n      : TopLeft(args.TL),\n        BottomLeft(args.BL),\n        TopRight(args.TR),\n        BottomRight(args.BR) {}\n  constexpr CornersQuad(glm::vec2 tl, glm::vec2 bl, glm::vec2 tr, glm::vec2 br)\n      : TopLeft(tl), BottomLeft(bl), TopRight(tr), BottomRight(br) {}\n\n  constexpr CornersQuad(RectF const& rect)\n      : CornersQuad({rect.X, rect.Y}, {rect.X, rect.Y + rect.Height},\n                    {rect.X + rect.Width, rect.Y},\n                    {rect.X + rect.Width, rect.Y + rect.Height}) {}\n\n  constexpr CornersQuad(Rect const& rect)\n      : CornersQuad(\n            {(float)rect.X, (float)rect.Y},\n            {(float)rect.X, (float)(rect.Y + rect.Height)},\n            {(float)(rect.X + rect.Width), (float)rect.Y},\n            {(float)(rect.X + rect.Width), (float)(rect.Y + rect.Height)}) {}\n\n  constexpr glm::vec2 Center() const {\n    return (TopLeft + BottomLeft + TopRight + BottomRight) / 4.0f;\n  }\n\n  CornersQuad& Transform(glm::mat4 transformation);\n  CornersQuad& Transform(\n      const std::function<glm::vec2(glm::vec2)>& transformation);\n\n  CornersQuad& Translate(glm::vec2 offset);\n  CornersQuad& Translate(float dx, float dy) { return Translate({dx, dy}); }\n\n  CornersQuad& Scale(glm::vec2 scaling, glm::vec2 origin);\n  CornersQuad& ScaleAroundCenter(glm::vec2 scaling, glm::vec2 origin) {\n    return Scale(scaling, Center());\n  }\n\n  CornersQuad& Rotate(float angle, glm::vec2 origin);\n  CornersQuad& RotateAroundCenter(float angle) {\n    return Rotate(angle, Center());\n  }\n  CornersQuad& Rotate(glm::quat rotation, glm::vec3 origin);\n  CornersQuad& Rotate(glm::quat rotation, glm::vec3 origin, float depth,\n                      glm::vec2 vanishingPoint, bool stayInScreen = false);\n  CornersQuad& RotateAroundCenter(glm::quat rotation) {\n    return Rotate(rotation, glm::vec3(Center(), 0.0f));\n  }\n\n  CornersQuad& FlipVertical() {\n    std::swap(TopLeft, BottomLeft);\n    std::swap(TopRight, BottomRight);\n    return *this;\n  }\n\n  CornersQuad& FlipHorizontal() {\n    std::swap(TopLeft, TopRight);\n    std::swap(BottomLeft, BottomRight);\n    return *this;\n  }\n};\n\ninline CornersQuad operator*(const glm::mat4 transformation, CornersQuad quad) {\n  return quad.Transform(transformation);\n}\n\ninline constexpr RectF::RectF(Rect const& rect)\n    : RectF((float)rect.X, (float)rect.Y, (float)rect.Width,\n            (float)rect.Height) {}\n\nconstexpr glm::vec4 RgbIntToFloat(uint32_t rgb, float opacity = 1.0f) {\n  return glm::vec4{(float)((rgb >> 16) & 0xFF) / 255.0f,\n                   (float)((rgb >> 8) & 0xFF) / 255.0f,\n                   (float)((rgb >> 0) & 0xFF) / 255.0f, opacity};\n}\n\nuint32_t GetHashCode(std::span<const uint8_t> data);\n\nstd::string DumpMat4(glm::mat4* matrix, std::string_view columnSeparator = \"\\t\",\n                     std::string_view rowSeparator = \"\\n\");\n\n// Thanks https://graphics.stanford.edu/~seander/bithacks.html#IntegerLog\nconstexpr int Uint32Log2(uint32_t v) {\n  unsigned int const b[] = {0x2, 0xC, 0xF0, 0xFF00, 0xFFFF0000};\n  unsigned int const S[] = {1, 2, 4, 8, 16};\n\n  unsigned int r = 0;           // result of log2(v) will go here\n  for (int i = 4; i >= 0; i--)  // unroll for speed...\n  {\n    if (v & b[i]) {\n      v >>= S[i];\n      r |= S[i];\n    }\n  }\n  return r;\n}\n\n// lots of guessing, again...\ninline glm::vec3 LookAtEulerZYX(glm::vec3 from, glm::vec3 to,\n                                glm::vec3 up = glm::vec3(0.0f, 1.0f, 0.0f)) {\n  glm::vec3 result(0.0f);\n\n  glm::vec3 forward = glm::normalize(from - to);\n\n  result.x =\n      atan2f(forward.y, sqrtf(forward.x * forward.x + forward.z * forward.z));\n  result.y = atan2f(forward.x, forward.z) - std::numbers::pi_v<float>;\n\n  return result;\n}\n\n// https://en.wikipedia.org/wiki/Conversion_between_quaternions_and_Euler_angles#Euler_Angles_to_Quaternion_Conversion\n// + guessing at the order :)\ninline void eulerZYXToQuat(glm::vec3 const* zyx, glm::quat* quat) {\n  float cosy = cos(zyx->z * 0.5f);\n  float siny = sin(zyx->z * 0.5f);\n  float cosr = cos(zyx->x * 0.5f);\n  float sinr = sin(zyx->x * 0.5f);\n  float cosp = cos(zyx->y * 0.5f);\n  float sinp = sin(zyx->y * 0.5f);\n\n  // This is currently slower than the scalar variant\n  // TODO use sse swizzle instead of set\n#if defined(__SSE2__) && 0\n  __m128 a = _mm_set_ps(cosy, siny, cosy, cosy);\n  __m128 b = _mm_set_ps(cosr, cosr, cosr, sinr);\n  a = _mm_mul_ps(a, b);\n  b = _mm_set_ps(cosp, cosp, sinp, cosp);\n  a = _mm_mul_ps(a, b);\n\n  b = _mm_set_ps(siny, cosy, siny, siny);\n  __m128 c = _mm_set_ps(sinr, sinr, sinr, cosr);\n  b = _mm_mul_ps(b, c);\n  c = _mm_set_ps(sinp, sinp, cosp, sinp);\n  b = _mm_mul_ps(b, c);\n\n  const __m128 d = _mm_set_ps(1.0f, -1.0f, 1.0f, -1.0f);\n  b = _mm_mul_ps(b, d);\n\n  *(__m128*)quat = _mm_add_ps(a, b);\n#else\n  quat->x = cosy * sinr * cosp - siny * cosr * sinp;\n  quat->y = cosy * cosr * sinp + siny * sinr * cosp;\n  quat->z = siny * cosr * cosp - cosy * sinr * sinp;\n  quat->w = cosy * cosr * cosp + siny * sinr * sinp;\n#endif\n}\n\nconstexpr float DegToRad(float deg) {\n  return deg * std::numbers::pi_v<float> / 180.0f;\n}\nconstexpr float RadToDeg(float rad) {\n  return rad * 180.0f / std::numbers::pi_v<float>;\n}\ninline float NormalizeDeg(float deg) {\n  deg = fmodf(deg + 180, 360);\n  if (deg < 0) deg += 360;\n  return deg - 180;\n}\ninline float NormalizeRad(float rad) {\n  rad =\n      fmodf(rad + std::numbers::pi_v<float>, 2.0f * std::numbers::pi_v<float>);\n  if (rad < 0) rad += 2.0f * std::numbers::pi_v<float>;\n  return rad - std::numbers::pi_v<float>;\n}\nconstexpr float ScrWorkAngleToRad(int angle) {\n  return ((float)angle * 2.0f * std::numbers::pi_v<float>) / (float)(1 << 16);\n}\n\ninline glm::quat ScrWorkAnglesToQuaternion(int x, int y, int z) {\n  return glm::quat(\n      {ScrWorkAngleToRad(x), ScrWorkAngleToRad(y), ScrWorkAngleToRad(z)});\n}\n\ninline glm::quat ScrWorkAngleZToQuaternion(int angle) {\n  return ScrWorkAnglesToQuaternion(0, 0, angle);\n}\n\ninline glm::quat AxisAngleToQuaternion(glm::vec3 axis, float angle) {\n  return glm::quat(cos(angle / 2.0f), sin(angle / 2.0f) * axis);\n}\n\ninline bool StringEndsWith(std::string const& str, std::string const& ending) {\n  if (str.length() < ending.length()) return false;\n  return std::equal(ending.rbegin(), ending.rend(), str.rbegin());\n}\n\ninline bool StringEndsWithCi(std::string const& str,\n                             std::string const& ending) {\n  if (str.length() < ending.length()) return false;\n  return 0 == SDL_strcasecmp(str.c_str() + str.length() - ending.length(),\n                             ending.c_str());\n}\n\n// Trim whitespace in-place\ninline void TrimString(std::string& str) {\n  // from the left\n  str.erase(str.begin(),\n            std::find_if(str.begin(), str.end(),\n                         [](unsigned char c) { return !std::isspace(c); }));\n\n  // from the right\n  str.erase(std::find_if(str.rbegin(), str.rend(),\n                         [](unsigned char c) { return !std::isspace(c); })\n                .base(),\n            str.end());\n}\n\ninline void MakeLowerCase(std::string& str) {\n  std::transform(str.begin(), str.end(), str.begin(),\n                 [](const char c) { return std::tolower(c); });\n}\n\ntemplate <typename T>\nT UnalignedRead(void* ptr) {\n  static_assert(std::is_trivially_copyable<T>::value,\n                \"!std::is_trivially_copyable<T>\");\n  T value;\n  memcpy(&value, ptr, sizeof value);\n  return value;\n}\n\ntemplate <typename T>\nvoid UnalignedWrite(void* ptr, T value) {\n  static_assert(std::is_trivially_copyable<T>::value,\n                \"!std::is_trivially_copyable<T>\");\n  memcpy(ptr, &value, sizeof value);\n}\n\ntemplate <class Enum>\nconstexpr std::underlying_type_t<Enum> to_underlying(Enum e) noexcept {\n  return static_cast<std::underlying_type_t<Enum>>(e);\n}\n\nint ResizeImage(Rect const& srcRect, Rect const& dstRect,\n                std::span<uint8_t> src, std::span<uint8_t> dst);\n\ninline int CALCrnd(int max) {\n  static std::random_device rd;\n  static std::mt19937 gen(std::random_device{}());\n  static std::uniform_int_distribution<> distr(0, 0x7FFF);\n  return distr(gen) * max >> 0xf;\n}\n\ntm CurrentDateTime();\n\n// Because yes\ninline int GetBufferId(int bufIdByScript) {\n  return static_cast<int>(std::log2(bufIdByScript));\n}\ninline int GetScriptBufferId(int bufIdBySurf) {\n  return bufIdBySurf <= 0 ? 0 : (1 << (bufIdBySurf - 1));\n}\n\n// https://stackoverflow.com/a/58037981/27686485\nconstexpr time_t timegm(tm const& t) {\n  int year = t.tm_year + 1900;\n  int month = t.tm_mon;  // 0-11\n  if (month > 11) {\n    year += month / 12;\n    month %= 12;\n  } else if (month < 0) {\n    const int years_diff = (11 - month) / 12;\n    year -= years_diff;\n    month += 12 * years_diff;\n  }\n  constexpr auto daysFromEpoch = [](int y, int m, int d) {\n    y -= m <= 2;\n    const int era = y / 400;\n    const int yoe = y - era * 400;                                   // [0, 399]\n    const int doy = (153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1;  // [0, 365]\n    const int doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;  // [0, 146096]\n    return era * 146097 + doe - 719468;\n  };\n\n  const int daysSinceEpoch = daysFromEpoch(year, month + 1, t.tm_mday);\n\n  return 60 * (60 * (24L * daysSinceEpoch + t.tm_hour) + t.tm_min) + t.tm_sec;\n}\n\n// Boost's hash_combine algorithm\ntemplate <typename T, typename... Rest>\nvoid HashCombine(std::size_t& seed, const T& v, const Rest&... rest) {\n  seed ^= std::hash<T>{}(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);\n  (HashCombine(seed, rest), ...);\n}\n\n// quick converter for all enums\ntemplate <typename E>\n  requires std::is_enum_v<E>\nconstexpr auto operator+(E e) noexcept {\n  return to_underlying(e);\n}\n\n}  // namespace Impacto"
  },
  {
    "path": "src/video/clock.cpp",
    "content": "#include \"clock.h\"\n#include \"../impacto.h\"\n\nnamespace Impacto::Video {\n\nusing namespace std::chrono;\nClock::Clock()\n    : Pts(), LastUpdated(Now()), Speed(1.0), Serial(-1), Paused(false) {}\n\nvoid Clock::SyncTo(Clock* target) {\n  Microseconds clock = Get();\n  Microseconds targetClock = target->Get();\n  if (targetClock != Microseconds{} &&\n      (clock == Microseconds{} || abs(clock - targetClock) > 10s))\n    Set(targetClock, target->Serial);\n}\n\nvoid Clock::Set(Microseconds pts, int serial) {\n  Pts = pts;\n  LastUpdated = std::chrono::steady_clock::now();\n  Serial = serial;\n}\n\nClock::Microseconds Clock::Get() {\n  if (Paused) {\n    return Pts;\n  } else {\n    auto elapsed = Now() - LastUpdated;\n    auto scaled = duration<double, std::micro>(elapsed * Speed);\n    return Pts + round<Microseconds>(scaled);\n  }\n}\n\n}  // namespace Impacto::Video"
  },
  {
    "path": "src/video/clock.h",
    "content": "#pragma once\n#include <chrono>\nnamespace Impacto::Video {\n\nclass Clock {\n public:\n  using Microseconds = std::chrono::microseconds;\n  using MonotonicTime = std::chrono::steady_clock::time_point;\n  Microseconds Pts;\n  MonotonicTime LastUpdated;\n  float Speed;\n  int Serial;\n  bool Paused;\n\n  Clock();\n  void SyncTo(Clock* target);\n  void Set(Microseconds pts, int serial);\n  Microseconds Get();\n  static MonotonicTime Now() { return std::chrono::steady_clock::now(); };\n};\n}  // namespace Impacto::Video"
  },
  {
    "path": "src/video/ffmpegplayer.cpp",
    "content": "#include \"ffmpegplayer.h\"\n\n#include <avcpp/av.h>\n#include <avcpp/avtime.h>\n#include <avcpp/avutils.h>\n#include <avcpp/codec.h>\n#include <avcpp/formatcontext.h>\n#include <avcpp/codeccontext.h>\n#include <avcpp/timestamp.h>\n#include <avcpp/packet.h>\n#include <avcpp/rational.h>\n\nextern \"C\" {\n#include <libavutil/avutil.h>\n#include <libavutil/time.h>\n}\n\n#include <algorithm>\n#include <cstdint>\n#include <mutex>\n#include <optional>\n#include <system_error>\n#include <utility>\n#include \"ffmpegstream.h\"\n\n#include \"../log.h\"\n#include \"../profile/game.h\"\n#include \"../io/stream.h\"\n#include \"../audio/ffmpegaudioplayer.h\"\n#ifndef IMPACTO_DISABLE_OPENAL\n#include \"../audio/openal/ffmpegaudioplayer.h\"\n#endif\n#include \"../profile/scriptvars.h\"\n#include \"../profile/subtitle.h\"\n#ifndef IMPACTO_DISABLE_LIBASS\n#include \"../subtitle/ass/subtitlerenderer.h\"\n#endif\n\nnamespace Impacto {\nnamespace Video {\n\nusing namespace Impacto::Profile::ScriptVars;\nProfile::Subtitle::SubtitleType SubtitleCodecToType(AVCodecID id) {\n  switch (id) {\n    case AV_CODEC_ID_TEXT:\n    case AV_CODEC_ID_SRT:\n    case AV_CODEC_ID_MOV_TEXT:\n    case AV_CODEC_ID_SUBRIP:\n      return Profile::Subtitle::SubtitleType::Text;\n    case AV_CODEC_ID_SSA:\n    case AV_CODEC_ID_ASS:\n      return Profile::Subtitle::SubtitleType::Ass;\n    case AV_CODEC_ID_HDMV_PGS_SUBTITLE:\n      return Profile::Subtitle::SubtitleType::Bitmap;\n    default:\n      return Profile::Subtitle::SubtitleType::None;\n  }\n}\n\nint FFmpegFileIO::read(uint8_t* data, size_t size) {\n  if (!FileStream) return -1;\n  uint64_t bytesRead = FileStream->Read(data, size);\n  if ((bytesRead == static_cast<uint64_t>(IoError_Fail) ||\n       bytesRead == static_cast<uint64_t>(IoError_Eof)))\n    return AVERROR_EOF;\n\n  return (int)bytesRead;\n}\n\nint64_t FFmpegFileIO::seek(int64_t offset, int whence) {\n  if (!FileStream) return -1;\n  if (whence == AVSEEK_SIZE) return FileStream->Meta.Size;\n  int64_t newPos = FileStream->Seek(offset, whence);\n  if (newPos == IoError_Fail) return -1;\n  return newPos;\n}\n\nFFmpegPlayer::~FFmpegPlayer() { IsInit = false; }\n\nvoid FFmpegPlayer::Init() {\n  assert(IsInit == false);\n\n  switch (Profile::ActiveAudioBackend) {\n#ifndef IMPACTO_DISABLE_OPENAL\n    case AudioBackendType::OpenAL: {\n      AudioPlayer.reset(new Audio::OpenAL::FFmpegAudioPlayer(this));\n    } break;\n#endif\n    case AudioBackendType::None:\n    default: {\n      AudioPlayer.reset(new Audio::FFmpegAudioPlayer(this));\n      NoAudio = true;\n      ImpLog(\n          LogLevel::Warning, LogChannel::Video,\n          \"No audio backend available, you will not hear audio in videos.\\n\");\n    } break;\n  }\n  av::init();\n  // av::set_logging_level(\"debug\");\n  AudioPlayer->Init();\n\n  IsInit = true;\n}\n\nAVBufferRef* FFmpegPlayer::HwDecoderInit(const AVCodec* codec) {\n  for (int i = 0;; i++) {\n    const AVCodecHWConfig* cfg = avcodec_get_hw_config(codec, i);\n    if (!cfg) break;\n    if (!(cfg->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX)) continue;\n\n    AVBufferRef* hw_device_ctx = NULL;\n    if (av_hwdevice_ctx_create(&hw_device_ctx, cfg->device_type, NULL, NULL,\n                               0) >= 0) {\n      HwVideoPixelFormat = static_cast<AVPixelFormat>(cfg->pix_fmt);\n      return hw_device_ctx;\n    }\n  }\n  return NULL;\n}\n\ntemplate <AVMediaType MediaType>\nstd::optional<av::Codec> findDecoderCodec(av::Stream const& avStream) {\n  const AVCodecID codecId = avStream.codecParameters().codecId();\n  std::optional<av::Codec> result;\n\n  auto checkDecode = [](av::Codec&& codec) -> std::optional<av::Codec> {\n    if (!codec.canDecode()) {\n      const auto channel = [] {\n        switch (MediaType) {\n          case AVMEDIA_TYPE_VIDEO:\n            return LogChannel::Video;\n          case AVMEDIA_TYPE_AUDIO:\n            return LogChannel::Audio;\n          case AVMEDIA_TYPE_SUBTITLE:\n            return LogChannel::Subtitle;\n          default:\n            return LogChannel::General;\n        }\n      }();\n      ImpLog(LogLevel::Error, channel, \"Unsupported codec: {}!\\n\",\n             codec.name());\n      return std::nullopt;\n    }\n    return std::optional<av::Codec>{std::move(codec)};\n  };\n\n  if constexpr (MediaType == AVMEDIA_TYPE_VIDEO) {\n#ifdef __ANDROID__\n    const AVCodecDescriptor* desc = avcodec_descriptor_get(codecId);\n    const std::string decoderName = fmt::format(\"{}_mediacodec\", desc->name);\n    result = checkDecode(av::findDecodingCodec(decoderName));\n#endif\n  }\n\n  if (!result) {\n    result = checkDecode(av::findDecodingCodec(codecId));\n  }\n\n  return result;\n}\n\ntemplate <AVMediaType MediaType>\nvoid FFmpegPlayer::OpenCodec(std::optional<FFmpegStream<MediaType>>& streamOpt,\n                             av::Stream&& avStream, int streamId) {\n  if constexpr (MediaType != AVMEDIA_TYPE_VIDEO &&\n                MediaType != AVMEDIA_TYPE_AUDIO &&\n                MediaType != AVMEDIA_TYPE_SUBTITLE) {\n    static_assert(MediaType && false, \"Unsupported MediaType\");\n  }\n  std::optional<av::Codec> codec = findDecoderCodec<MediaType>(avStream);\n  if (!codec) {\n    avStream.reset();\n  }\n\n  DecodingContext_t<MediaType> decoderContext{avStream, *codec};\n  if constexpr (MediaType == AVMEDIA_TYPE_VIDEO) {\n    if (auto* ctx = HwDecoderInit(decoderContext.codec().raw()); ctx) {\n      decoderContext.raw()->hw_device_ctx = ctx;\n      decoderContext.raw()->opaque = this;\n      decoderContext.raw()->get_format =\n          [](AVCodecContext* ctx,\n             const AVPixelFormat* pix_fmts) -> AVPixelFormat {\n        auto const* self = static_cast<FFmpegPlayer*>(ctx->opaque);\n        for (const auto* p = pix_fmts; *p != AV_PIX_FMT_NONE; p++) {\n          if (*p == self->HwVideoPixelFormat) return *p;\n        }\n        for (const auto* p = pix_fmts; *p != AV_PIX_FMT_NONE; p++) {\n          const AVPixFmtDescriptor* desc = av_pix_fmt_desc_get(*p);\n          if (!(desc->flags & AV_PIX_FMT_FLAG_HWACCEL)) return *p;\n        }\n        return AV_PIX_FMT_NONE;\n      };\n    }\n  }\n\n  std::error_code ec;\n  decoderContext.open({{{\"threads\", \"auto\"}}}, decoderContext.codec(), ec);\n  decoderContext.setRefCountedFrames(true);\n  if (ec) {\n    const auto channel = [] {\n      switch (MediaType) {\n        case AVMEDIA_TYPE_VIDEO:\n          return LogChannel::Video;\n        case AVMEDIA_TYPE_AUDIO:\n          return LogChannel::Audio;\n        case AVMEDIA_TYPE_SUBTITLE:\n          return LogChannel::Subtitle;\n        default:\n          return LogChannel::General;\n      }\n    }();\n    ImpLog(LogLevel::Error, channel, \"Failed to open codec, error: {:s}\",\n           ec.message());\n  }\n\n  double rate = 1;\n  if constexpr (MediaType == AVMEDIA_TYPE_VIDEO) {\n    rate = avStream.averageFrameRate().getDouble();\n  } else if constexpr (MediaType == AVMEDIA_TYPE_AUDIO) {\n    rate = decoderContext.sampleRate();\n    decoderContext.setChannelLayout(AV_CH_LAYOUT_STEREO);\n  } else if constexpr (MediaType == AVMEDIA_TYPE_SUBTITLE) {\n    rate = avStream.averageFrameRate().getDouble();\n  }\n\n  double duration = FormatContext.duration().seconds();\n  double frameMultiplier = (rate);\n  streamOpt.emplace(std::move(avStream), std::move(decoderContext),\n                    (int)(frameMultiplier * duration));\n};\n\ntemplate void FFmpegPlayer::OpenCodec(\n    std::optional<FFmpegStream<AVMEDIA_TYPE_VIDEO>>& streamOpt,\n    av::Stream&& avStream, int streamId);\ntemplate void FFmpegPlayer::OpenCodec(\n    std::optional<FFmpegStream<AVMEDIA_TYPE_AUDIO>>& streamOpt,\n    av::Stream&& avStream, int streamId);\ntemplate void FFmpegPlayer::OpenCodec(\n    std::optional<FFmpegStream<AVMEDIA_TYPE_SUBTITLE>>& streamOpt,\n    av::Stream&& avStream, int streamId);\n\nvoid FFmpegPlayer::Play(Io::Stream* stream, bool looping, bool alpha) {\n  // Don't do anything if we don't have the video system\n  if (!IsInit) return;\n  if (stream == nullptr) {\n    ImpLog(LogLevel::Error, LogChannel::Video,\n           \"Stream was a nullptr! This means the caller is buggy. Backing \"\n           \"out.\\n\");\n    return;\n  }\n  StreamPtr.reset(stream);\n  AbortRequest = false;\n  SeekRequest = false;\n  Looping = looping;\n  IsAlpha = alpha;\n  ImpLog(LogLevel::Info, LogChannel::Video, \"Opening file: {:s} from: {:s}\\n\",\n         stream->Meta.FileName, stream->Meta.ArchiveFileName);\n\n  std::error_code ec;\n  IoContext = FFmpegFileIO{stream};\n  FormatContext.openInput(&IoContext, ec, FILESTREAMBUFFERSZ);\n  if (ec) {\n    ImpLog(LogLevel::Error, LogChannel::Video,\n           \"Error opening file, error: {:s}\\n\", ec.message());\n    StreamPtr.reset();\n    return;\n  }\n\n  FormatContext.findStreamInfo(ec);\n  if (ec) {\n    ImpLog(LogLevel::Error, LogChannel::Video,\n           \"Error opening file, error: {:s}\\n\", ec.message());\n    StreamPtr.reset();\n    return;\n  }\n\n  av::Stream videoStream;\n  int videoStreamId = AVERROR_STREAM_NOT_FOUND;\n  av::Stream audioStream;\n  int audioStreamId = AVERROR_STREAM_NOT_FOUND;\n  std::vector<std::pair<av::Stream, int>> embeddedSubStreams;\n  for (size_t i = 0; i < FormatContext.streamsCount(); ++i) {\n    auto st = FormatContext.stream(i);\n    if (st.isVideo()) {\n      videoStreamId = (int)i;\n      videoStream = st;\n    } else if (st.isAudio() && !NoAudio) {\n      audioStreamId = (int)i;\n      audioStream = st;\n    } else if (st.isSubtitle()) {\n      embeddedSubStreams.emplace_back(st, (int)i);\n    } else {\n      st.raw()->discard = AVDISCARD_ALL;\n    }\n  }\n\n  if (videoStream.isVideo() && videoStream.isValid()) {\n    OpenCodec<AVMEDIA_TYPE_VIDEO>(VideoStream, std::move(videoStream),\n                                  videoStreamId);\n    ScrWork[SW_MOVIEFRAME] = 0;\n    ScrWork[SW_MOVIETOTALFRAME] = VideoStream->Duration;\n    std::visit(\n        [this](auto& videoText) {\n          if constexpr (std::is_same_v<std::decay_t<decltype(videoText)>,\n                                       std::monostate>) {\n            if (HwVideoPixelFormat == AV_PIX_FMT_NONE) {\n              VideoTexture = Renderer->CreateYUVFrame(\n                  (float)VideoStream->CodecContext.width(),\n                  (float)VideoStream->CodecContext.height());\n            } else {\n              VideoTexture = Renderer->CreateNV12Frame(\n                  (float)VideoStream->CodecContext.width(),\n                  (float)VideoStream->CodecContext.height());\n            }\n          } else {\n            videoText->Width = (float)VideoStream->CodecContext.width();\n            videoText->Height = (float)VideoStream->CodecContext.height();\n          }\n        },\n        VideoTexture);\n  }\n  if (audioStream.isAudio() && audioStream.isValid()) {\n    OpenCodec<AVMEDIA_TYPE_AUDIO>(AudioStream, std::move(audioStream),\n                                  audioStreamId);\n    AudioPlayer->InitConvertContext(AudioStream->CodecContext.raw());\n  }\n  VideoClock = Clock();\n  MasterClock = &VideoClock;\n  using namespace std::literals::chrono_literals;\n  MaxFrameDuration = {FormatContext.inputFormat().flags() & AVFMT_TS_DISCONT\n                          ? Clock::Microseconds(10s)\n                          : Clock::Microseconds(3600s)};\n\n  // Danger zone\n  ReadThread = std::thread{&FFmpegPlayer::Read, this};\n  if (VideoStream) {\n    VideoStream->DecoderThread =\n        std::thread{&FFmpegPlayer::Decode<AVMEDIA_TYPE_VIDEO>, this,\n                    std::ref(*VideoStream)};\n  }\n  if (AudioStream && !NoAudio) {\n    MasterClock = &AudioPlayer->GetClock();\n    AudioStream->DecoderThread =\n        std::thread{&FFmpegPlayer::Decode<AVMEDIA_TYPE_AUDIO>, this,\n                    std::ref(*AudioStream)};\n  }\n  if (+Profile::GameFeatures & +GameFeature::Subtitles) {\n    InitSubtitles(embeddedSubStreams);\n  }\n\n  IsPlaying = true;\n}\n\nvoid FFmpegPlayer::InitSubtitles(\n    std::vector<std::pair<av::Stream, int>>& embeddedSubStreams) {\n  using Profile::Subtitle::SubtitleMappings;\n  using Profile::Subtitle::SubtitleTrackFile;\n  using Profile::Subtitle::SubtitleType;\n  using namespace Subtitle;\n  auto initSubPlayer = [this] {\n    if (SubPlayer) return;\n    SubPlayer.emplace(Profile::DesignWidth, Profile::DesignHeight);\n  };\n  const auto subtitleMappings =\n      [this]() -> std::vector<SubtitleTrackFile> const* {\n    const auto mountMapItr =\n        SubtitleMappings.find(StreamPtr->Meta.ArchiveMountPoint);\n    if (mountMapItr == SubtitleMappings.end()) return nullptr;\n    auto subMapItr = mountMapItr->second.find(StreamPtr->Meta.FileName);\n    if (subMapItr == mountMapItr->second.end())\n      subMapItr = mountMapItr->second.find(StreamPtr->Meta.Id);\n    if (subMapItr == mountMapItr->second.end()) return nullptr;\n    return &(subMapItr->second);\n  }();\n\n  // Tracks embedded in video\n  for (auto& [subtitleStream, id] : embeddedSubStreams) {\n    assert(subtitleStream.isSubtitle() && subtitleStream.isValid());\n\n    std::optional<Impacto::Video::FFmpegStream<AVMEDIA_TYPE_SUBTITLE>>\n        streamOpt;\n    OpenCodec<AVMEDIA_TYPE_SUBTITLE>(streamOpt, std::move(subtitleStream), id);\n    if (!streamOpt) continue;\n\n    initSubPlayer();\n    auto& subStream =\n        EmbeddedSubtitleStreams.emplace_back(std::move(*streamOpt));\n    const auto subtitleType =\n        SubtitleCodecToType(subStream.CodecContext.codec().id());\n    subStream.DecoderThread =\n        std::thread{&FFmpegPlayer::Decode<AVMEDIA_TYPE_SUBTITLE>, this,\n                    std::ref(subStream)};\n\n    // Optionally tag embedded subtitle tracks in lua\n    Profile::SubtitleConfigType subConfig = Profile::SubtitleConfigType::All;\n    if (subtitleMappings) {\n      const auto subFileItr = std::find_if(\n          subtitleMappings->begin(), subtitleMappings->end(),\n          [&subStream](const auto& subFile) {\n            return subFile.Id && subStream.AvStream.id() == *subFile.Id;\n          });\n      if (subFileItr != subtitleMappings->end()) subConfig = subFileItr->Config;\n    }\n#ifndef IMPACTO_DISABLE_LIBASS\n    if (subtitleType == SubtitleType::Ass) {\n      std::string_view subHeader = subStream.CodecContext.subtitleHeader();\n      SubPlayer->AddTrack<Ass::SubtitleRenderTrack>(subStream.AvStream.id(),\n                                                    subConfig, subHeader);\n    }\n#endif\n  }\n\n  // External subs mapped through lua\n  if (subtitleMappings) {\n    initSubPlayer();\n    int trackId = 0;\n    for (auto const& subFile : *subtitleMappings) {\n      if (!subFile.Path) continue;\n      SubPlayer->AddTrackFile(trackId++, subFile.Type, *subFile.Path,\n                              subFile.Config);\n    }\n  }\n}\n\nvoid FFmpegPlayer::HandleSeekRequest() {\n  std::error_code ec;\n  FormatContext.seek(av::Timestamp(SeekPosition, av::TimeBaseQ), ec);\n  if (ec) {\n    ImpLog(LogLevel::Error, LogChannel::Video,\n           \"Error encountered while seeking, error: {:s}\", ec.message());\n  }\n\n  if (VideoStream) {\n    VideoStream->FlushPacketQueue();\n    VideoStream->FlushFrameQueue();\n  }\n\n  if (AudioStream) {\n    AudioStream->FlushPacketQueue();\n    AudioStream->FlushFrameQueue();\n  }\n\n  FrameTimer = Clock::MonotonicTime{};\n  PreviousFrameTimestamp = std::nullopt;\n  SeekRequest = false;\n  ReaderEOF = false;\n}\n\nvoid FFmpegPlayer::Read() {\n  while (!AbortRequest) {\n    if (SeekRequest) {\n      HandleSeekRequest();\n    }\n\n    AVPacketItem item;\n    if (ReaderEOF) {\n      continue;\n    }\n    std::error_code ec;\n    item.Packet = FormatContext.readPacket(ec);\n    if (ec) {\n      ImpLog(LogLevel::Error, LogChannel::Video, \"Uh oh {:s}\\n\", ec.message());\n    }\n    if (item.Packet) {\n      if (AudioStream &&\n          item.Packet.streamIndex() == AudioStream->AvStream.index()) {\n        item.Serial = AudioStream->PacketQueueSerial;\n        while (!AudioStream->PacketQueue.wait_enqueue_timed(std::move(item),\n                                                            300)) {\n          if (AbortRequest) break;\n        };\n      } else if (item.Packet.streamIndex() == VideoStream->AvStream.index()) {\n        item.Serial = VideoStream->PacketQueueSerial;\n        while (!VideoStream->PacketQueue.wait_enqueue_timed(std::move(item),\n                                                            300)) {\n          if (AbortRequest) break;\n        };\n      } else {\n        for (auto& subtitleStream : EmbeddedSubtitleStreams) {\n          if (item.Packet.streamIndex() != subtitleStream.AvStream.index())\n            continue;\n          item.Serial = subtitleStream.PacketQueueSerial;\n          while (!subtitleStream.PacketQueue.wait_enqueue_timed(std::move(item),\n                                                                300)) {\n            if (AbortRequest) break;\n          };\n        }\n      }\n    } else {\n      ImpLog(LogLevel::Debug, LogChannel::Video, \"EOF!\\n\");\n      ReaderEOF = true;\n      item.Serial = INT32_MIN;\n      if (AudioStream) {\n        while (!AudioStream->PacketQueue.wait_enqueue_timed(std::move(item),\n                                                            300)) {\n          if (AbortRequest) break;\n        };\n      }\n      for (auto& subtitleStream : EmbeddedSubtitleStreams) {\n        while (!subtitleStream.PacketQueue.wait_enqueue_timed(std::move(item),\n                                                              300)) {\n          if (AbortRequest) break;\n        };\n      }\n      while (\n          !VideoStream->PacketQueue.wait_enqueue_timed(std::move(item), 300)) {\n        if (AbortRequest) break;\n      };\n    }\n  }\n}\n\nvoid FFmpegPlayer::ProcessVideoFrame(Frame_t<AVMEDIA_TYPE_VIDEO>& avFrame) {\n  auto rawFrame = avFrame.raw();\n  if (rawFrame->format == AV_PIX_FMT_NONE ||\n      !(av_pix_fmt_desc_get(avFrame.pixelFormat())->flags &\n        AV_PIX_FMT_FLAG_HWACCEL)) {\n    return;\n  }\n  AVFrame* swFrame = av_frame_alloc();\n  if (av_hwframe_transfer_data(swFrame, rawFrame, 0) < 0) {\n    av_frame_free(&swFrame);\n    return;\n  }\n  av_frame_copy_props(swFrame, rawFrame);\n  av_frame_free(&rawFrame);\n  avFrame.reset(swFrame);\n}\n\ntemplate <AVMediaType MediaType>\nvoid FFmpegPlayer::Decode(FFmpegStream<MediaType>& stream) {\n  auto verifyPacket =\n      [this, &stream](Impacto::Video::AVPacketItem const* peekedPacket) {\n        while (true) {\n          int prevSerial = stream.CurrentPacketSerial;\n          stream.CurrentPacketSerial = peekedPacket->Serial;\n          if (stream.CurrentPacketSerial == INT32_MIN) {\n            break;\n          }\n          if (prevSerial != stream.CurrentPacketSerial) {\n            avcodec_flush_buffers(VideoStream->CodecContext.raw());\n            if constexpr (MediaType == AVMEDIA_TYPE_AUDIO) {\n              AudioPlayer->Stop();\n            }\n          }\n          if (stream.PacketQueueSerial == stream.CurrentPacketSerial) {\n            break;\n          }\n          if (AbortRequest) return AVPacketItem{};\n        }\n        AVPacketItem packet;\n        auto& packetQueue = stream.PacketQueue;\n        while (!packetQueue.wait_dequeue_timed(packet, 300)) {\n          if (AbortRequest) return AVPacketItem{};\n        }\n        return packet;\n      };\n\n  auto pushFrame = [&stream, this](Frame_t<MediaType>&& frame) {\n    AVDecodedItem<MediaType> item;\n    item.Frame = std::move(frame);\n    if (!item.Frame) {\n      item.Serial = INT32_MIN;\n    } else {\n      item.Serial = stream.PacketQueueSerial;\n      item.Timestamp = item.Frame.pts();\n    }\n    auto& frameQueue = stream.FrameQueue;\n\n    while (!frameQueue.wait_enqueue_timed(std::move(item), 300)) {\n      if (AbortRequest) return;\n    }\n  };\n\n  auto processAndPush = [&](AVPacketItem const& packet, Frame_t<MediaType>&& f,\n                            std::error_code const& ec) {\n    if (ec) {\n      ImpLog(LogLevel::Error, LogChannel::Video, \"Failed to decode {:s}\",\n             ec.message());\n      return false;\n    }\n    if (!f) {\n      if (packet.Serial == INT32_MIN) pushFrame(std::move(f));\n      return false;\n    }\n    if constexpr (MediaType == AVMEDIA_TYPE_VIDEO) {\n      ProcessVideoFrame(f);\n    }\n    if constexpr (MediaType == AVMEDIA_TYPE_SUBTITLE) {\n      auto* rawSubtitle = f.raw();\n      if (rawSubtitle->start_display_time == 0 &&\n          rawSubtitle->end_display_time == 0) {\n        rawSubtitle->end_display_time = packet.Packet.duration();\n      }\n    }\n    pushFrame(std::move(f));\n    return true;\n  };\n\n  while (!AbortRequest) {\n    AVPacketItem const* peek = stream.PacketQueue.peek();\n    if (peek == nullptr) continue;\n\n    std::error_code ec;\n    AVPacketItem packet = verifyPacket(peek);\n\n    if (packet.Serial != INT32_MIN) {\n      Frame_t<MediaType> frame = stream.CodecContext.decode(packet.Packet, ec);\n\n      while (processAndPush(packet, std::move(frame), ec)) {\n        frame = stream.CodecContext.decode(av::Packet{nullptr}, ec);\n      }\n    } else {\n      // Flush decoder since packets are finished\n      Frame_t<MediaType> frame = stream.CodecContext.decode({}, ec);\n      while (processAndPush(packet, std::move(frame), ec) && !AbortRequest) {\n        frame = stream.CodecContext.decode({}, ec);\n      }\n    }\n  }\n}\n\ntemplate void FFmpegPlayer::Decode<AVMEDIA_TYPE_VIDEO>(\n    FFmpegStream<AVMEDIA_TYPE_VIDEO>& stream);\ntemplate void FFmpegPlayer::Decode<AVMEDIA_TYPE_AUDIO>(\n    FFmpegStream<AVMEDIA_TYPE_AUDIO>& stream);\ntemplate void FFmpegPlayer::Decode<AVMEDIA_TYPE_SUBTITLE>(\n    FFmpegStream<AVMEDIA_TYPE_SUBTITLE>& stream);\n\nvoid FFmpegPlayer::Stop() {\n  if (IsPlaying) {\n    IsPlaying = false;\n    PlaybackStarted = false;\n    AbortRequest = true;\n    ReadThread.join();\n    ReaderEOF = false;\n    if (AudioStream) {\n      AudioStream->DecoderThread.join();\n      AudioStream.reset();\n      AudioPlayer->Unload();\n    }\n    if (VideoStream) {\n      VideoStream->DecoderThread.join();\n      VideoStream.reset();\n    }\n    for (auto& subStream : EmbeddedSubtitleStreams) {\n      subStream.DecoderThread.join();\n    }\n    EmbeddedSubtitleStreams.clear();\n    SubPlayer.reset();\n    FrameTimer = {};\n    PreviousFrameTimestamp = {};\n    FormatContext.close();\n    StreamPtr.reset();\n    std::visit(\n        [this](auto& videoText) {\n          if constexpr (!std::is_same_v<std::decay_t<decltype(videoText)>,\n                                        std::monostate>) {\n            videoText->Release();\n            VideoTexture = std::monostate{};\n          }\n        },\n        VideoTexture);\n\n    SetFlag(SF_MOVIEPLAY, false);\n  }\n}\n\nvoid FFmpegPlayer::Seek(int64_t pos) {\n  SeekRequest = true;\n  SeekPosition = pos;\n  ReadCond.notify_one();\n}\n\nvoid FFmpegPlayer::Update(float dt) {\n  if (IsPlaying) {\n    using namespace std::literals::chrono_literals;\n    if (AudioStream) AudioPlayer->Process();\n\n    if (+Profile::GameFeatures & +GameFeature::Subtitles && SubPlayer) {\n      SubPlayer->UpdateElapsedTime(MasterClock->Get());\n      UpdateSubtitles();\n    }\n\n    Clock::Microseconds duration{};\n    Clock::MonotonicTime time;\n\n    AVDecodedItem<AVMEDIA_TYPE_VIDEO>* const frame =\n        VideoStream->FrameQueue.peek();\n    if (frame == nullptr) return;\n\n    SetFlag(SF_MOVIE_DRAWWAIT, false);\n\n    if (frame->Serial == INT32_MIN) {\n      if (Looping) {\n        Seek(0);\n      } else {\n        Stop();\n      }\n      return;\n    }\n    time = Clock::Now();\n    if (FrameTimer == Clock::MonotonicTime{}) {\n      FrameTimer = time;\n    }\n    if (PreviousFrameTimestamp) {\n      auto inverseFrameRate = av::Rational(1, 30);\n      size_t frameNum = av_rescale_q(frame->Timestamp.timestamp(),\n                                     frame->Timestamp.timebase().getValue(),\n                                     inverseFrameRate.getValue());\n      // This isn't the place for it but I can't think of\n      // anything right now\n      ScrWork[SW_MOVIEFRAME] = (int)frameNum;\n      duration = (frame->Timestamp.toDuration<Clock::Microseconds>() -\n                  PreviousFrameTimestamp->toDuration<Clock::Microseconds>());\n    }\n\n    if (AudioStream) {\n      duration = GetTargetDelay(Clock::Microseconds(duration));\n    } else {\n      auto fps = VideoStream->AvStream.averageFrameRate();\n      duration = std::chrono::duration_cast<Clock::Microseconds>(\n                     std::chrono::seconds(fps.getDenominator())) /\n                 fps.getNumerator();\n    }\n\n    if (time < FrameTimer + duration) {\n      return;\n    }\n    FrameTimer += duration_cast<Clock::MonotonicTime::duration>(duration);\n    if (duration > 0s && (time - FrameTimer) > 0.1s) {\n      FrameTimer = time;\n    }\n\n    PreviousFrameTimestamp = frame->Timestamp;\n    if (frame->Frame.pixelFormat() == AV_PIX_FMT_NV12) {\n      auto& nv12Frame = std::get<NV12Frame*>(VideoTexture);\n      nv12Frame->Submit(frame->Frame.data(0), frame->Frame.raw()->linesize[0],\n                        frame->Frame.data(1), frame->Frame.raw()->linesize[1]);\n    } else if (frame->Frame.pixelFormat() == AV_PIX_FMT_YUV420P) {\n      auto& yuvFrame = std::get<YUVFrame*>(VideoTexture);\n      yuvFrame->Submit(frame->Frame.data(0), frame->Frame.data(1),\n                       frame->Frame.data(2));\n    } else {\n      ImpLog(LogLevel::Warning, LogChannel::Video,\n             \"Unsupported frame pixel format, video will not display!\\n\");\n    }\n    VideoClock.Set(frame->Timestamp.toDuration<Clock::Microseconds>(),\n                   frame->Serial);\n    MasterClock->SyncTo(&VideoClock);\n    AVDecodedItem<AVMEDIA_TYPE_VIDEO> unusedFrame;\n    VideoStream->FrameQueue.wait_dequeue(unusedFrame);\n    PlaybackStarted = true;\n  }\n}\n\nvoid FFmpegPlayer::UpdateSubtitles() {\n  for (auto& subtitleStream : EmbeddedSubtitleStreams) {\n    Video::AVDecodedItem<AVMEDIA_TYPE_SUBTITLE> subtitle;\n    const bool hasSubtitle = subtitleStream.FrameQueue.try_dequeue(subtitle);\n    if (!hasSubtitle) continue;\n\n    auto const& rawSubtitleData = subtitle.Frame.raw();\n    for (unsigned rectI = 0; rectI < rawSubtitleData->num_rects; rectI++) {\n      auto const& rect = rawSubtitleData->rects[rectI];\n      Subtitle::SubtitleEntry subEntry;\n      switch (rect->type) {\n        case SUBTITLE_ASS:\n        case SUBTITLE_TEXT:\n          subEntry.Data = std::string(rect->ass);\n          break;\n        case SUBTITLE_BITMAP: {\n          Subtitle::SubtitleEntry::BitmapData bmp{\n              .X = rect->x,\n              .Y = rect->y,\n              .W = rect->w,\n              .H = rect->h,\n              .NbColors = rect->nb_colors,\n          };\n          for (uint8_t bmpI = 0; bmpI < bmp.Data.size(); ++bmpI) {\n            bmp.LineSize[bmpI] = rect->linesize[bmpI];\n            bmp.Data[bmpI] = std::make_unique<uint8_t[]>(rect->linesize[bmpI]);\n            std::copy(rect->data[bmpI], rect->data[bmpI] + rect->linesize[bmpI],\n                      bmp.Data[bmpI].get());\n          }\n        } break;\n        case SUBTITLE_NONE:\n        default:\n          break;\n      }\n      subEntry.Flags = rect->flags;\n      subEntry.StartMs =\n          subtitle.Timestamp.toDuration<std::chrono::milliseconds>() +\n          std::chrono::milliseconds(rawSubtitleData->start_display_time);\n      subEntry.Duration =\n          std::chrono::milliseconds(rawSubtitleData->end_display_time -\n                                    rawSubtitleData->start_display_time);\n      SubPlayer->PushEntry(subtitleStream.AvStream.id(), std::move(subEntry));\n    }\n  }\n}\n\nvoid FFmpegPlayer::Render(float videoAlpha) {\n  if (!IsPlaying || !PlaybackStarted) return;\n\n  const RectF dest = {0.0f, 0.0f, Profile::DesignWidth, Profile::DesignHeight};\n  const glm::vec4 tint = {1.0f, 1.0f, 1.0f, videoAlpha};\n  std::visit(\n      [this, &dest, &tint](auto& videoText) {\n        if constexpr (!std::is_same_v<std::decay_t<decltype(videoText)>,\n                                      std::monostate>) {\n          Renderer->DrawVideoTexture(*videoText, dest, tint, IsAlpha);\n        }\n      },\n      VideoTexture);\n  if (SubPlayer) SubPlayer->Render();\n}\n\nClock::Microseconds FFmpegPlayer::GetTargetDelay(Clock::Microseconds duration) {\n  using namespace std::literals::chrono_literals;\n  using namespace std::chrono;\n  using Us = Clock::Microseconds;\n  const Us videoClockTime = VideoClock.Get();\n  const Us masterClockTime = MasterClock->Get();\n  const Us diff = videoClockTime - masterClockTime;\n  const Us sync_threshold = std::clamp<Us>(duration, 40ms, 100ms);\n  if ((videoClockTime != Us{} && masterClockTime != Us{}) &&\n      abs(diff) < MaxFrameDuration) {\n    if (diff <= -sync_threshold)\n      duration = std::max<Us>(0s, duration + diff);\n    else if (diff >= sync_threshold && duration > 100ms)\n      duration = duration + diff;\n    else if (diff >= sync_threshold)\n      duration = 2 * duration;\n  }\n  ImpLogSlow(LogLevel::Trace, LogChannel::Video, \"Target delay: {:f}\\n\",\n             duration_cast<std::chrono::duration<double>>(duration).count());\n\n  return duration;\n}\n}  // namespace Video\n}  // namespace Impacto\n"
  },
  {
    "path": "src/video/ffmpegplayer.h",
    "content": "#pragma once\n#include <chrono>\n#include <condition_variable>\n#include <memory>\n#include <thread>\n#include <optional>\n\n#include <avcpp/formatcontext.h>\n#include <avcpp/timestamp.h>\n\n#include \"clock.h\"\n#include \"videoplayer.h\"\n#include \"ffmpegstream.h\"\n#include \"../io/stream.h\"\n#include \"../renderer/yuvframe.h\"\n#include \"../audio/ffmpegaudioplayer.h\"\n#include \"../subtitle/subtitlesystem.h\"\n\nnamespace Impacto {\nnamespace Io {\nclass Stream;\n}\n\nnamespace Video {\nstruct FFmpegFileIO : public av::CustomIO {\n  FFmpegFileIO() = default;\n  FFmpegFileIO(Io::Stream* Stream) : FileStream(Stream) {}\n  Io::Stream* FileStream;\n  int read(uint8_t* data, size_t size) override;\n  int64_t seek(int64_t offset, int whence) override;\n  int seekable() const override {\n    return FileStream != nullptr ? AVIO_SEEKABLE_NORMAL : 0;\n  }\n};\n\nclass FFmpegPlayer : public VideoPlayer {\n public:\n  ~FFmpegPlayer();\n\n  void Init() override;\n\n  void Play(Io::Stream* stream, bool loop, bool alpha) override;\n  void Stop() override;\n  void Seek(int64_t pos) override;\n\n  void Update(float dt) override;\n  void Render(float videoAlpha) override;\n\n  void Read();\n  template <AVMediaType avType>\n  void Decode(FFmpegStream<avType>& stream);\n  void ProcessAudio();\n\n  std::atomic<bool> AbortRequest;\n  bool SeekRequest;\n  std::thread ReadThread;\n  std::optional<FFmpegStream<AVMEDIA_TYPE_VIDEO>> VideoStream;\n  std::optional<FFmpegStream<AVMEDIA_TYPE_AUDIO>> AudioStream;\n\n  std::vector<FFmpegStream<AVMEDIA_TYPE_SUBTITLE>> EmbeddedSubtitleStreams;\n  std::optional<Subtitle::SubtitlePlayer> SubPlayer;\n\n private:\n  void InitSubtitles(std::vector<std::pair<av::Stream, int>>& subtitleStreams);\n  void FillAudioBuffers();\n  Clock::Microseconds GetTargetDelay(Clock::Microseconds duration);\n  bool QueuesHaveEnoughPackets();\n\n  void HandleSeekRequest();\n\n  template <AVMediaType MediaType>\n  void OpenCodec(std::optional<FFmpegStream<MediaType>>& streamOpt,\n                 av::Stream&& avStream, int streamId);\n\n  void UpdateSubtitles();\n  void ProcessVideoFrame(Frame_t<AVMEDIA_TYPE_VIDEO>& avFrame);\n  AVBufferRef* HwDecoderInit(const AVCodec* codec);\n\n  static int constexpr FILESTREAMBUFFERSZ = 64 * 8192;\n  std::condition_variable ReadCond;\n\n  uint64_t Time;\n  int64_t SeekPosition;\n\n  std::unique_ptr<Io::Stream> StreamPtr;\n  av::FormatContext FormatContext;\n  AVPixelFormat HwVideoPixelFormat = AV_PIX_FMT_NONE;\n  FFmpegFileIO IoContext;\n\n  Clock VideoClock;\n  Clock* MasterClock{};\n\n  std::unique_ptr<Audio::FFmpegAudioPlayer> AudioPlayer;\n\n  bool IsInit = false;\n\n  std::variant<std::monostate, YUVFrame*, NV12Frame*> VideoTexture;\n\n  bool IsAlpha = false;\n  bool Looping = false;\n  bool ReaderEOF = false;\n  bool PlaybackStarted = false;\n  std::optional<av::Timestamp> PreviousFrameTimestamp{};\n  Clock::MonotonicTime FrameTimer{};\n  Clock::Microseconds MaxFrameDuration{};\n  bool NoAudio = false;\n  int FrameCount = 0;\n};\n\n}  // namespace Video\n}  // namespace Impacto\n"
  },
  {
    "path": "src/video/ffmpegstream.cpp",
    "content": "#include \"ffmpegstream.h\"\n#include \"ffmpegplayer.h\"\n\nnamespace Impacto {\nnamespace Video {\n\ntemplate <AVMediaType MediaType>\nvoid FFmpegStream<MediaType>::FlushPacketQueue() {\n  AVPacketItem item;\n  bool isNotEmpty = false;\n  while (PacketQueue.try_dequeue(item)) {\n    isNotEmpty = true;\n  };\n  if (isNotEmpty) {\n    PacketQueueSerial++;\n  }\n}\n\ntemplate void FFmpegStream<AVMEDIA_TYPE_AUDIO>::FlushPacketQueue();\ntemplate void FFmpegStream<AVMEDIA_TYPE_VIDEO>::FlushPacketQueue();\n\ntemplate <AVMediaType MediaType>\nvoid FFmpegStream<MediaType>::FlushFrameQueue() {\n  AVDecodedItem<MediaType> item;\n  while (FrameQueue.try_dequeue(item));\n}\ntemplate void FFmpegStream<AVMEDIA_TYPE_AUDIO>::FlushFrameQueue();\ntemplate void FFmpegStream<AVMEDIA_TYPE_VIDEO>::FlushFrameQueue();\n\n}  // namespace Video\n}  // namespace Impacto"
  },
  {
    "path": "src/video/ffmpegstream.h",
    "content": "#pragma once\n\n#include <thread>\n#include <memory>\n#include <type_traits>\n\n#include <avcpp/codeccontext.h>\n#include <avcpp/stream.h>\n#include <avcpp/frame.h>\n#include <avcpp/packet.h>\n\n#include \"../impacto.h\"\n#include \"clock.h\"\n\n#if __SWITCH__\n#define __unix__\n#endif\n#include <readerwritercircularbuffer.h>\n#if __SWITCH__\n#undef __unix__\n#endif\n\n#include \"../subtitle/ffmpegsubtitlehelper.h\"\n\nstruct AVFrame;\nstruct AVPacket;\nstruct AVCodecContext;\nstruct AVStream;\n\nnamespace Impacto {\nnamespace Video {\n\ntemplate <AVMediaType MediaType>\nstruct AVTypes {\n  using type = void;\n};\n\ntemplate <>\nstruct AVTypes<AVMEDIA_TYPE_VIDEO> {\n  using DecodingContextType = av::VideoDecoderContext;\n  using FrameType = av::VideoFrame;\n};\ntemplate <>\nstruct AVTypes<AVMEDIA_TYPE_AUDIO> {\n  using DecodingContextType = av::AudioDecoderContext;\n  using FrameType = av::AudioSamples;\n};\n\ntemplate <>\nstruct AVTypes<AVMEDIA_TYPE_SUBTITLE> {\n  using DecodingContextType = Subtitle::SubtitleDecoderContext;\n  using FrameType = Subtitle::SubtitleData;\n};\n\ntemplate <AVMediaType MediaType>\nusing DecodingContext_t = typename AVTypes<MediaType>::DecodingContextType;\n\ntemplate <AVMediaType MediaType>\nusing Frame_t = typename AVTypes<MediaType>::FrameType;\n\nstruct AVPacketItem {\n  av::Packet Packet;\n  int Serial = -1;\n};\n\ntemplate <AVMediaType MediaType>\nstruct AVDecodedItem {\n  Frame_t<MediaType> Frame;\n  int Serial = -1;\n  av::Timestamp Timestamp;\n};\n\ntemplate <AVMediaType MediaType>\nstruct FFmpegStream {\n  DecodingContext_t<MediaType> CodecContext;\n  av::Stream AvStream;\n  moodycamel::BlockingReaderWriterCircularBuffer<AVPacketItem> PacketQueue{25};\n  moodycamel::BlockingReaderWriterCircularBuffer<AVDecodedItem<MediaType>>\n      FrameQueue{60};\n  int Duration;\n  int PacketQueueSerial = 0;\n  int CurrentPacketSerial = 0;\n\n  std::thread DecoderThread;\n\n  FFmpegStream() = default;\n  FFmpegStream(av::Stream&& avStream, DecodingContext_t<MediaType>&& codecCtx)\n      : CodecContext(std::move(codecCtx)), AvStream(std::move(avStream)) {}\n\n  FFmpegStream(av::Stream&& avStream, DecodingContext_t<MediaType>&& codecCtx,\n               int duration)\n      : CodecContext(std::move(codecCtx)),\n        AvStream(std::move(avStream)),\n        Duration(duration) {}\n\n  void FlushPacketQueue();\n  void FlushFrameQueue();\n};\n\n}  // namespace Video\n}  // namespace Impacto\n"
  },
  {
    "path": "src/video/videoplayer.cpp",
    "content": "#include \"videoplayer.h\"\n#include \"../log.h\"\n\nnamespace Impacto {\nnamespace Video {\n\nVideoPlayer* VideoPlayer::Create(Io::Stream* stream) {\n  for (auto f : Registry) {\n    VideoPlayer* result = f(stream);\n    if (result) return result;\n  }\n  ImpLog(LogLevel::Error, LogChannel::Video, \"No video player found\\n\");\n  return 0;\n}\n\nbool VideoPlayer::AddVideoPlayerCreator(VideoPlayerCreator c) {\n  Registry.push_back(c);\n  return true;\n}\n\nstd::vector<VideoPlayer::VideoPlayerCreator> VideoPlayer::Registry;\n\n}  // namespace Video\n}  // namespace Impacto"
  },
  {
    "path": "src/video/videoplayer.h",
    "content": "#pragma once\n\n#include \"../impacto.h\"\n#include \"../io/stream.h\"\n\n#include <vector>\n\nnamespace Impacto {\nnamespace Video {\n\nclass VideoPlayer {\n public:\n  static VideoPlayer* Create(Io::Stream* stream);\n\n  virtual void Init() {};\n  virtual void Play(Io::Stream* stream, bool loop, bool alpha) {};\n  virtual void Stop() {};\n  virtual void Seek(int64_t pos) {};\n\n  virtual void Update(float dt) {};\n  virtual void Render(float videoAlpha) {};\n\n  bool IsPlaying = false;\n\n  bool CancelFlag = false;\n  uint8_t CancelWaitTime = 0;\n\n protected:\n  using VideoPlayerCreator = auto (*)(Io::Stream* stream) -> VideoPlayer*;\n  static bool AddVideoPlayerCreator(VideoPlayerCreator c);\n\n private:\n  static std::vector<VideoPlayerCreator> Registry;\n};\n\n}  // namespace Video\n}  // namespace Impacto"
  },
  {
    "path": "src/video/videosystem.cpp",
    "content": "#include \"videosystem.h\"\n#ifndef IMPACTO_DISABLE_FFMPEG\n#include \"ffmpegplayer.h\"\n#endif\n#include \"../profile/game.h\"\n#include \"../log.h\"\n\nnamespace Impacto {\nnamespace Video {\n\nvoid VideoShutdown() {\n  for (int i = 0; i < VP_Count; i++) {\n    Players[i]->Stop();\n  }\n}\n\nvoid VideoInit() {\n  ImpLog(LogLevel::Info, LogChannel::Video, \"Initialising video system\\n\");\n\n  switch (Profile::VideoPlayer) {\n#ifndef IMPACTO_DISABLE_FFMPEG\n    case VideoPlayerType::FFmpeg: {\n      for (int i = 0; i < VP_Count; i++) {\n        Players[i] = new FFmpegPlayer();\n        Players[i]->Init();\n      }\n    } break;\n#endif\n    case VideoPlayerType::None:\n    default: {\n      ImpLog(LogLevel::Warning, LogChannel::Video,\n             \"No suitable video player found! Using a null one, which means \"\n             \"you will not see any videos.\\n\");\n      for (int i = 0; i < VP_Count; i++) {\n        Players[i] = new VideoPlayer();\n      }\n    } break;\n  }\n}\n\nvoid VideoUpdate(float dt) {\n  for (int i = 0; i < VP_Count; i++) {\n    Players[i]->Update(dt);\n  }\n}\n\nvoid VideoRender(float videoAlpha) {\n  for (int i = 0; i < VP_Count; i++) {\n    Players[i]->Render(videoAlpha);\n  }\n}\n\n}  // namespace Video\n}  // namespace Impacto\n"
  },
  {
    "path": "src/video/videosystem.h",
    "content": "#pragma once\n\n#include \"videoplayer.h\"\n#include \"../impacto.h\"\n\nnamespace Impacto {\nnamespace Video {\n\nenum VideoPlayerId { VP_Main = 0, VP_Secondary, VP_Count };\n\ninline VideoPlayer* Players[VP_Count];\n\nvoid VideoInit();\nvoid VideoUpdate(float dt);\nvoid VideoRender(float videoAlpha);\nvoid VideoShutdown();\n\n}  // namespace Video\n}  // namespace Impacto\n"
  },
  {
    "path": "src/vm/expression.cpp",
    "content": "#include \"expression.h\"\n\n#include \"../log.h\"\n#include \"../mem.h\"\n\n#include <vector>\n#include <memory>\n\nnamespace Impacto {\n\nnamespace Vm {\n\nenum ExprTokenType {\n  ET_EndOfExpression = 0x00,\n  ET_ImmediateValue = 0xFF,\n\n  ET_Multiply = 0x01,\n  ET_Divide = 0x02,\n  ET_Add = 0x03,\n  ET_Subtract = 0x04,\n  ET_Modulo = 0x05,\n  ET_LeftShift = 0x06,\n  ET_RightShift = 0x07,\n  ET_BitwiseAnd = 0x08,\n  ET_BitwiseXor = 0x09,\n  ET_BitwiseOr = 0x0A,\n  ET_Equal = 0x0C,\n  ET_NotEqual = 0x0D,\n  ET_LessThanEqual = 0x0E,\n  ET_MoreThanEqual = 0x0F,\n  ET_LessThan = 0x10,\n  ET_GreaterThan = 0x11,\n\n  ET_Negation = 0x0B,\n  ET_Increment = 0x20,\n  ET_Decrement = 0x21,\n\n  ET_Assign = 0x14,\n  ET_MultiplyAssign = 0x15,\n  ET_DivideAssign = 0x16,\n  ET_AddAssign = 0x17,\n  ET_SubtractAssign = 0x18,\n  ET_ModuloAssign = 0x19,\n  ET_LeftShiftAssign = 0x1A,\n  ET_RightShiftAssign = 0x1B,\n  ET_BitwiseAndAssign = 0x1C,\n  ET_BitwiseOrAssign = 0x1D,\n  ET_BitwiseXorAssign = 0x1E,\n\n  ET_FuncGlobalVars = 0x28,\n  ET_FuncFlags = 0x29,\n  ET_FuncDataAccess = 0x2A,\n  ET_FuncLabelTable = 0x2B,\n  ET_FuncFarLabelTable = 0x2C,\n  ET_FuncThreadVars = 0x2D,\n  ET_FuncDMA = 0x2E,\n  ET_FuncUnk2F = 0x2F,\n  ET_FuncUnk30 = 0x30,\n  ET_FuncNop31 = 0x31,\n  ET_FuncNop32 = 0x32,\n  ET_FuncRandom = 0x33\n};\n\nstruct ExprToken {\n  ExprTokenType Type;\n  int Value;\n  int Precedence;\n};\n\nclass ExpressionNode {\n public:\n  ExprTokenType ExprType;\n\n  std::unique_ptr<ExpressionNode> LeftExpr;\n  std::unique_ptr<ExpressionNode> RightExpr;\n\n  int Value;\n\n  int Evaluate(Sc3VmThread* thd);\n  void AssignValue(Sc3VmThread* thd);\n};\n\nclass ExpressionParser {\n public:\n  ExpressionParser(Sc3VmThread* thd);\n  ExpressionNode* ParseSubExpression(int minPrecidence);\n\n private:\n  int CurrentToken;\n  std::vector<ExprToken> Tokens;\n  void GetTokens(Sc3VmThread* thd);\n\n  ExpressionNode* ParseTerm();\n};\n\nint ExpressionEval(Sc3VmThread* thd) {\n  std::unique_ptr<ExpressionParser> parser =\n      std::make_unique<ExpressionParser>(thd);\n\n  ExpressionNode* root = parser->ParseSubExpression(0);\n  std::unique_ptr<ExpressionNode> rootPtr =\n      std::unique_ptr<ExpressionNode>(root);\n\n  return root == nullptr ? 0 : rootPtr->Evaluate(thd);\n}\n\nint ExpressionNode::Evaluate(Sc3VmThread* thd) {\n  int leftVal, rightVal;\n\n  switch (ExprType) {\n    case ET_Multiply:\n      leftVal = LeftExpr->Evaluate(thd);\n      rightVal = RightExpr->Evaluate(thd);\n      return leftVal * rightVal;\n    case ET_Divide:\n      leftVal = LeftExpr->Evaluate(thd);\n      rightVal = RightExpr->Evaluate(thd);\n      if (!rightVal) return 0x7FFFFFFF;\n      return leftVal / rightVal;\n    case ET_Add:\n      leftVal = LeftExpr->Evaluate(thd);\n      rightVal = RightExpr->Evaluate(thd);\n      return leftVal + rightVal;\n    case ET_Subtract:\n      leftVal = LeftExpr->Evaluate(thd);\n      rightVal = RightExpr->Evaluate(thd);\n      return leftVal - rightVal;\n    case ET_Modulo:\n      leftVal = LeftExpr->Evaluate(thd);\n      rightVal = RightExpr->Evaluate(thd);\n      if (!rightVal) return 0x7FFFFFFF;\n      return leftVal % rightVal;\n    case ET_LeftShift:\n      leftVal = LeftExpr->Evaluate(thd);\n      rightVal = RightExpr->Evaluate(thd);\n      return leftVal << rightVal;\n    case ET_RightShift:\n      leftVal = LeftExpr->Evaluate(thd);\n      rightVal = RightExpr->Evaluate(thd);\n      return leftVal >> rightVal;\n    case ET_BitwiseAnd:\n      leftVal = LeftExpr->Evaluate(thd);\n      rightVal = RightExpr->Evaluate(thd);\n      return leftVal & rightVal;\n    case ET_BitwiseXor:\n      leftVal = LeftExpr->Evaluate(thd);\n      rightVal = RightExpr->Evaluate(thd);\n      return leftVal ^ rightVal;\n    case ET_BitwiseOr:\n      leftVal = LeftExpr->Evaluate(thd);\n      rightVal = RightExpr->Evaluate(thd);\n      return leftVal | rightVal;\n    case ET_Equal:\n      leftVal = LeftExpr->Evaluate(thd);\n      rightVal = RightExpr->Evaluate(thd);\n      return leftVal == rightVal;\n    case ET_NotEqual:\n      leftVal = LeftExpr->Evaluate(thd);\n      rightVal = RightExpr->Evaluate(thd);\n      return leftVal != rightVal;\n    case ET_LessThanEqual:\n      leftVal = LeftExpr->Evaluate(thd);\n      rightVal = RightExpr->Evaluate(thd);\n      return leftVal <= rightVal;\n    case ET_MoreThanEqual:\n      leftVal = LeftExpr->Evaluate(thd);\n      rightVal = RightExpr->Evaluate(thd);\n      return leftVal >= rightVal;\n    case ET_LessThan:\n      leftVal = LeftExpr->Evaluate(thd);\n      rightVal = RightExpr->Evaluate(thd);\n      return leftVal < rightVal;\n    case ET_GreaterThan:\n      leftVal = LeftExpr->Evaluate(thd);\n      rightVal = RightExpr->Evaluate(thd);\n      return leftVal > rightVal;\n    case ET_Negation:\n      leftVal = LeftExpr->Evaluate(thd);\n      return ~leftVal;\n    case ET_Assign:\n    case ET_MultiplyAssign:\n    case ET_DivideAssign:\n    case ET_AddAssign:\n    case ET_SubtractAssign:\n    case ET_ModuloAssign:\n    case ET_LeftShiftAssign:\n    case ET_RightShiftAssign:\n    case ET_BitwiseAndAssign:\n    case ET_BitwiseOrAssign:\n    case ET_BitwiseXorAssign:\n    case ET_Increment:\n    case ET_Decrement:\n      AssignValue(thd);\n      return 0;\n      break;\n    case ET_ImmediateValue:\n      return Value;\n    case ET_FuncGlobalVars:\n      return ScrWork[RightExpr->Evaluate(thd)];\n    case ET_FuncFlags:\n      return GetFlag(RightExpr->Evaluate(thd));\n    case ET_FuncDataAccess:\n      leftVal = LeftExpr->Evaluate(thd);\n      rightVal = RightExpr->Evaluate(thd);\n      if (leftVal >= 0) {\n        auto scrBuf = ScriptBuffers[thd->ScriptBufferId];\n        uint8_t* dataArray = (uint8_t*)&scrBuf[leftVal];\n        return UnalignedRead<int>(&dataArray[rightVal * sizeof(int)]);\n      } else {\n        ImpLogSlow(LogLevel::Warning, LogChannel::Expr,\n                   \"STUB token 0x{:02x} evaluate\\n\", to_underlying(ExprType));\n        // TODO: Handle this\n        return 0;\n      }\n    case ET_FuncLabelTable:\n      return ScriptGetLabelAddress(thd->ScriptBufferId,\n                                   RightExpr->Evaluate(thd));\n    case ET_FuncFarLabelTable:\n      return 0;\n    case ET_FuncThreadVars:\n      return UnalignedRead<uint32_t>(\n          thd->GetMemberPointer(RightExpr->Evaluate(thd)));\n    case ET_FuncDMA:\n    case ET_FuncUnk2F:\n    case ET_FuncUnk30:\n    case ET_FuncNop31:\n    case ET_FuncNop32:\n      ImpLogSlow(LogLevel::Warning, LogChannel::Expr,\n                 \"STUB token 0x{:02x} evaluate\\n\", to_underlying(ExprType));\n      return 0;\n    case ET_FuncRandom:\n      // TODO use our own RNG with our own seed\n      return RightExpr->Evaluate(thd) * (rand() & 0x7FFF) >> 15;\n    default:\n      ImpLogSlow(LogLevel::Warning, LogChannel::Expr,\n                 \"STUB token 0x{:02x} evaluate\\n\", to_underlying(ExprType));\n      return 0;\n  }\n}\n\nvoid ExpressionNode::AssignValue(Sc3VmThread* thd) {\n  int leftVal = LeftExpr->Evaluate(thd);\n  int rightVal = 0;\n  if (ExprType != ET_Increment && ExprType != ET_Decrement)\n    rightVal = RightExpr->Evaluate(thd);\n\n  switch (ExprType) {\n    case ET_Assign:\n      leftVal = rightVal;\n      break;\n    case ET_MultiplyAssign:\n      leftVal *= rightVal;\n      break;\n    case ET_DivideAssign:\n      leftVal /= rightVal;\n      break;\n    case ET_AddAssign:\n      leftVal += rightVal;\n      break;\n    case ET_SubtractAssign:\n      leftVal -= rightVal;\n      break;\n    case ET_ModuloAssign:\n      leftVal %= rightVal;\n      break;\n    case ET_LeftShiftAssign:\n      leftVal <<= rightVal;\n      break;\n    case ET_RightShiftAssign:\n      leftVal >>= rightVal;\n      break;\n    case ET_BitwiseAndAssign:\n      leftVal &= rightVal;\n      break;\n    case ET_BitwiseOrAssign:\n      leftVal |= rightVal;\n      break;\n    case ET_BitwiseXorAssign:\n      leftVal ^= rightVal;\n      break;\n    case ET_Increment:\n      leftVal++;\n      break;\n    case ET_Decrement:\n      leftVal--;\n      break;\n    default:\n      ImpLogSlow(LogLevel::Error, LogChannel::Expr,\n                 \"Tried to assign with unknown token 0x{:02x}\\n\",\n                 to_underlying(ExprType));\n      break;\n  }\n\n  int index = LeftExpr->RightExpr->Evaluate(thd);\n\n  switch (LeftExpr->ExprType) {\n    case ET_FuncGlobalVars:\n      ScrWork[index] = leftVal;\n      break;\n    case ET_FuncFlags:\n      SetFlag(index, leftVal);\n      break;\n    case ET_FuncThreadVars: {\n      void* thdWork = thd->GetMemberPointer(index);\n      UnalignedWrite<int>(thdWork, leftVal);\n      break;\n    }\n    case ET_FuncDMA:\n      ImpLogSlow(LogLevel::Warning, LogChannel::Expr,\n                 \"STUB token 0x{:02x} assign\\n\",\n                 to_underlying(LeftExpr->ExprType));\n      break;\n    default:\n      ImpLogSlow(LogLevel::Warning, LogChannel::Expr,\n                 \"STUB token 0x{:02x} assign\\n\",\n                 to_underlying(LeftExpr->ExprType));\n      break;\n  }\n}\n\nExpressionParser::ExpressionParser(Sc3VmThread* thd) {\n  GetTokens(thd);\n  CurrentToken = 0;\n}\n\nExpressionNode* ExpressionParser::ParseSubExpression(int minPrecidence) {\n  ExpressionNode* leftExpr = ParseTerm();\n  if (leftExpr == nullptr) return leftExpr;\n\n  if (static_cast<size_t>(CurrentToken) < Tokens.size()) {\n    ExprToken peek = Tokens[CurrentToken];\n    if ((peek.Type == ET_Increment || peek.Type == ET_Decrement) &&\n        peek.Precedence >= minPrecidence) {\n      CurrentToken++;\n      ExpressionNode* result = new ExpressionNode();\n      result->ExprType = peek.Type;\n      result->LeftExpr = std::unique_ptr<ExpressionNode>(leftExpr);\n      leftExpr = result;\n      if (static_cast<size_t>(CurrentToken) >= Tokens.size()) return leftExpr;\n    }\n\n    peek = Tokens[CurrentToken];\n\n    while (peek.Precedence >= minPrecidence) {\n      switch (peek.Type) {\n        case ET_Multiply:\n        case ET_Divide:\n        case ET_Add:\n        case ET_Subtract:\n        case ET_Modulo:\n        case ET_LeftShift:\n        case ET_RightShift:\n        case ET_BitwiseAnd:\n        case ET_BitwiseXor:\n        case ET_BitwiseOr:\n        case ET_Equal:\n        case ET_NotEqual:\n        case ET_LessThanEqual:\n        case ET_MoreThanEqual:\n        case ET_LessThan:\n        case ET_GreaterThan:\n        case ET_Negation:\n        case ET_Assign:\n        case ET_MultiplyAssign:\n        case ET_DivideAssign:\n        case ET_AddAssign:\n        case ET_SubtractAssign:\n        case ET_ModuloAssign:\n        case ET_LeftShiftAssign:\n        case ET_RightShiftAssign:\n        case ET_BitwiseAndAssign:\n        case ET_BitwiseOrAssign:\n        case ET_BitwiseXorAssign: {\n          CurrentToken++;\n          ExpressionNode* rightExpr = ParseSubExpression(peek.Precedence + 1);\n          ExpressionNode* result = new ExpressionNode();\n          result->ExprType = peek.Type;\n          result->LeftExpr = std::unique_ptr<ExpressionNode>(leftExpr);\n          result->RightExpr = std::unique_ptr<ExpressionNode>(rightExpr);\n          leftExpr = result;\n          if (static_cast<size_t>(CurrentToken) < Tokens.size())\n            peek = Tokens[CurrentToken];\n          else\n            return leftExpr;\n          break;\n        }\n        default:\n          return leftExpr;\n      }\n    }\n  } else\n    return leftExpr;\n\n  return leftExpr;\n}\n\nExpressionNode* ExpressionParser::ParseTerm() {\n  if (Tokens.empty()) return nullptr;\n\n  ExprToken tok = Tokens[CurrentToken++];\n  ExpressionNode* term = nullptr;\n  switch (tok.Type) {\n    case ET_ImmediateValue:\n      term = new ExpressionNode();\n      term->ExprType = tok.Type;\n      term->Value = tok.Value;\n      break;\n    case ET_Negation:\n    case ET_FuncGlobalVars:\n    case ET_FuncFlags:\n    case ET_FuncLabelTable:\n    case ET_FuncThreadVars:\n    case ET_FuncRandom:\n      term = new ExpressionNode();\n      term->ExprType = tok.Type;\n      term->RightExpr = std::unique_ptr<ExpressionNode>(\n          ParseSubExpression(tok.Precedence + 1));\n      break;\n    case ET_FuncDataAccess:\n    case ET_FuncFarLabelTable:\n      term = new ExpressionNode();\n      term->ExprType = tok.Type;\n      term->LeftExpr = std::unique_ptr<ExpressionNode>(\n          ParseSubExpression(tok.Precedence + 1));\n      term->RightExpr = std::unique_ptr<ExpressionNode>(\n          ParseSubExpression(tok.Precedence + 1));\n      break;\n      // ????\n      // in RN03_16A.scr there is an instruction parameter that is just \"=43\"\n    case ET_Assign:\n      tok = Tokens[CurrentToken++];\n      term = new ExpressionNode();\n      term->ExprType = tok.Type;\n      term->Value = tok.Value;\n      break;\n    default:\n      return nullptr;\n  }\n\n  return term;\n}\n\nvoid ExpressionParser::GetTokens(Sc3VmThread* thd) {\n  ExprToken curToken;\n\n  if (*thd->GetIp()) {\n    do {\n      int8_t tokenType = *thd->GetIp();\n      if (tokenType >= 0) {\n        curToken.Type = (ExprTokenType)tokenType;\n        thd->IpOffset++;\n        curToken.Precedence = *(thd->GetIp());\n        thd->IpOffset++;\n        curToken.Value = 0;\n        Tokens.push_back(curToken);\n      } else {\n        uint8_t* immValue = thd->GetIp();\n        curToken.Type = ET_ImmediateValue;\n        switch (tokenType & 0x60) {\n          case 0:\n            curToken.Value = tokenType & 0x1F;\n            if (tokenType & 0x10) curToken.Value |= 0xFFFFFFE0;\n            thd->IpOffset++;\n            break;\n          case 0x20:\n            curToken.Value = ((immValue[0] & 0x1F) << 8) + immValue[1];\n            if (tokenType & 0x10) curToken.Value |= 0xFFFFE000;\n            thd->IpOffset += 2;\n            break;\n          case 0x40:\n            curToken.Value =\n                ((immValue[0] & 0x1F) << 16) + (immValue[2] << 8) + immValue[1];\n            if (tokenType & 0x10) curToken.Value |= 0xFFE00000;\n            thd->IpOffset += 3;\n            break;\n          case 0x60:\n            curToken.Value = immValue[1] + (immValue[2] << 8) +\n                             (immValue[3] << 16) + (immValue[4] << 24);\n            thd->IpOffset += 5;\n            break;\n          default:\n            break;\n        }\n        thd->IpOffset++;\n        curToken.Precedence = *(thd->GetIp());\n        Tokens.push_back(curToken);\n      }\n    } while (*thd->GetIp());\n  }\n\n  thd->IpOffset++;\n}\n\n}  // namespace Vm\n\n}  // namespace Impacto"
  },
  {
    "path": "src/vm/expression.h",
    "content": "#pragma once\n\n#include \"vm.h\"\n#include \"sc3stream.h\"\n\nnamespace Impacto {\n\nnamespace Vm {\n\nint ExpressionEval(Sc3VmThread* thread);\n\n}  // namespace Vm\n\n}  // namespace Impacto"
  },
  {
    "path": "src/vm/inst_controlflow.cpp",
    "content": "#include \"inst_controlflow.h\"\n\n#include \"inst_macros.inc\"\n\n#include \"expression.h\"\n#include \"../mem.h\"\n#include \"../log.h\"\n#include \"../profile/vm.h\"\n#include \"../inputsystem.h\"\n#include \"interface/input.h\"\n\nnamespace Impacto {\n\nnamespace Vm {\n\nVmInstruction(InstJump) {\n  StartInstruction;\n  PopLocalLabel(labelAdr);\n\n  thread->IpOffset = labelAdr;\n}\nVmInstruction(InstJumpTable) {\n  StartInstruction;\n  PopExpression(labelNumIndex);\n  PopUint16(dataLabelNum);\n  uint32_t dataAdr =\n      ScriptGetLabelAddress(thread->ScriptBufferId, dataLabelNum);\n  uint8_t* dataPtr =\n      &ScriptBuffers[thread->ScriptBufferId][dataAdr + 2 * labelNumIndex];\n  uint32_t labelAdr = ScriptGetLabelAddress(\n      thread->ScriptBufferId, SDL_SwapLE16(UnalignedRead<uint16_t>(dataPtr)));\n\n  thread->IpOffset = labelAdr;\n}\nVmInstruction(InstIf) {\n  StartInstruction;\n  PopUint8(check);\n  PopExpression(condition);\n\n  PopUint16(labelNum);\n  uint32_t labelAdr = ScriptGetLabelAddress(thread->ScriptBufferId, labelNum);\n\n  if ((bool)check == (bool)condition) {\n    thread->IpOffset = labelAdr;\n  }\n\n  // Hack: Pokecom infinite recursion in DaSH macrosys2\n  if (thread->Variables[0] == 1 && labelNum == 107 &&\n      thread->ScriptBufferId == 7) {\n    thread->Variables[0] = 0;\n    thread->IpOffset = labelAdr;\n  }\n}\nVmInstruction(InstCall) {\n  StartInstruction;\n  PopLocalLabel(labelAdr);\n\n  if (thread->CallStackDepth != MaxCallStackDepth) {\n    if (Profile::Vm::UseReturnIds) {\n      PopUint16(retNum);\n      thread->ReturnIds[thread->CallStackDepth] = retNum;\n    } else {\n      thread->ReturnAddresses[thread->CallStackDepth] = thread->IpOffset;\n    }\n    thread->ReturnScriptBufferIds[thread->CallStackDepth++] =\n        thread->ScriptBufferId;\n    thread->IpOffset = labelAdr;\n  } else {\n    ImpLog(LogLevel::Error, LogChannel::VM,\n           \"Call error, call stack overflow.\\n\");\n  }\n}\nVmInstruction(InstJumpFar) {\n  StartInstruction;\n  PopExpression(scriptBufferId);\n  PopFarLabel(labelAdr, scriptBufferId);\n\n  thread->ScriptBufferId = scriptBufferId;\n  thread->IpOffset = labelAdr;\n}\nVmInstruction(InstCallFar) {\n  StartInstruction;\n  PopExpression(scriptBufferId);\n  PopFarLabel(labelAdr, scriptBufferId);\n\n  if (thread->CallStackDepth != MaxCallStackDepth) {\n    if (Profile::Vm::UseReturnIds) {\n      PopUint16(retNum);\n      thread->ReturnIds[thread->CallStackDepth] = retNum;\n    } else {\n      thread->ReturnAddresses[thread->CallStackDepth] = thread->IpOffset;\n    }\n    thread->ReturnScriptBufferIds[thread->CallStackDepth++] =\n        thread->ScriptBufferId;\n    thread->IpOffset = labelAdr;\n    thread->ScriptBufferId = scriptBufferId;\n  } else {\n    ImpLog(LogLevel::Error, LogChannel::VM,\n           \"CallFar error, call stack overflow.\\n\");\n  }\n}\nVmInstruction(InstReturn) {\n  StartInstruction;\n  if (thread->CallStackDepth) {\n    thread->CallStackDepth--;\n    uint32_t retBufferId =\n        thread->ReturnScriptBufferIds[thread->CallStackDepth];\n    if (Profile::Vm::UseReturnIds) {\n      thread->IpOffset = ScriptGetRetAddress(\n          retBufferId, thread->ReturnIds[thread->CallStackDepth]);\n    } else {\n      thread->IpOffset = thread->ReturnAddresses[thread->CallStackDepth];\n    }\n    thread->ScriptBufferId = retBufferId;\n  } else {\n    ImpLog(LogLevel::Error, LogChannel::VM,\n           \"Return error, call stack empty.\\n\");\n  }\n}\nVmInstruction(InstReturnIfFlag) {\n  StartInstruction;\n  PopUint8(value);\n  PopExpression(flagId);\n  if (thread->CallStackDepth) {\n    if (GetFlag(flagId) == (bool)value) {\n      thread->CallStackDepth--;\n      uint32_t retBufferId =\n          thread->ReturnScriptBufferIds[thread->CallStackDepth];\n      if (Profile::Vm::UseReturnIds) {\n        thread->IpOffset = ScriptGetRetAddress(\n            retBufferId, thread->ReturnIds[thread->CallStackDepth]);\n      } else {\n        thread->IpOffset = thread->ReturnAddresses[thread->CallStackDepth];\n      }\n      thread->ScriptBufferId = retBufferId;\n    }\n  } else {\n    ImpLog(LogLevel::Error, LogChannel::VM,\n           \"Return error, call stack empty.\\n\");\n  }\n}\nVmInstruction(InstLoop) {\n  StartInstruction;\n  PopUint16(labelNum);\n  PopExpression(loopCount);\n\n  uint32_t labelAdr = ScriptGetLabelAddress(thread->ScriptBufferId, labelNum);\n\n  if (thread->LoopLabelNum == labelNum) {\n    loopCount = thread->LoopCounter;\n  } else {\n    thread->LoopLabelNum = labelNum;\n    thread->LoopCounter = loopCount;\n  }\n  thread->LoopCounter--;\n  if (thread->LoopCounter) {\n    thread->IpOffset = labelAdr;\n  } else {\n    thread->LoopLabelNum = 0xFFFF;\n  }\n}\nVmInstruction(InstFlagOnJump) {\n  StartInstruction;\n  PopUint8(value);\n  PopExpression(flagId);\n  PopLocalLabel(labelAdr);\n\n  if (GetFlag(flagId) == (bool)value) {\n    thread->IpOffset = labelAdr;\n  }\n}\nVmInstruction(InstKeyOnJump) {\n  using namespace Interface;\n\n  StartInstruction;\n  PopUint8(type);\n  PopExpression(buttonsArg);\n  PopExpression(downTypeId);\n  PopUint16(labelNum);\n  uint32_t labelAdr = ScriptGetLabelAddress(thread->ScriptBufferId, labelNum);\n\n  const InputDownType downType = static_cast<InputDownType>(downTypeId);\n\n  const bool inputResult = [&]() -> bool {\n    const uint32_t bitfield = (type & 2) == 0 ? buttonsArg\n                              : buttonsArg < std::ssize(PADcustom)\n                                  ? PADcustom[buttonsArg]\n                                  : 0;\n\n    uint32_t padInputDown = GetPadInputButtonDown(downType);\n    if (downType == InputDownType::IsDown) {\n      padInputDown |= PADinputMouseIsDown;\n    } else if (downType == InputDownType::WentDown) {\n      padInputDown |= PADinputMouseWentDown;\n    }\n\n    return bitfield & padInputDown;\n  }();\n\n  if (inputResult) {\n    thread->IpOffset = labelAdr;\n  }\n\n  ImpLogSlow(LogLevel::Trace, LogChannel::VM,\n             \"KeyOnJump(type: {:d}, buttons: {:d}, downType: {:d}, \"\n             \"labelNum: {:d})\\n\",\n             type, buttonsArg, downTypeId, labelNum);\n}\nVmInstruction(InstKeyOnJump_Dash) {\n  StartInstruction;\n  PopUint8(arg1);\n  PopExpression(arg2);\n  if (arg1 < 10) {\n    PopExpression(arg3);\n  }\n  PopUint16(labelNum);\n  uint32_t labelAdr = ScriptGetLabelAddress(thread->ScriptBufferId, labelNum);\n  if (arg1 & 2) {\n    arg2 = Interface::PADcustom[arg2];\n  }\n  if (arg2 & Interface::PADinputButtonWentDown ||\n      arg2 & Interface::PADinputMouseWentDown) {\n    thread->IpOffset = labelAdr;\n    Interface::PADinputButtonWentDown = 0;\n    Interface::PADinputMouseWentDown = 0;\n  }\n\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction KeyOnJump(arg1: {:d}, arg2: {:d}, \"\n             \"labelNum: {:d})\\n\",\n             arg1, arg2, labelNum);\n}\nVmInstruction(InstClickOnJump) {\n  StartInstruction;\n  PopUint8(arg1);\n  if ((arg1 & 0xFE) == 2) {\n    PopExpression(_arg1);\n    PopExpression(_arg2);\n    PopExpression(_arg3);\n  }\n  PopExpression(arg2);\n  PopUint16(labelNum);\n  uint32_t labelAdr = ScriptGetLabelAddress(thread->ScriptBufferId, labelNum);\n  if (Input::KeyboardButtonWentDown[SDL_SCANCODE_D]) {\n    thread->IpOffset = labelAdr;\n  }\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction ClickOnJump(arg1: {:d}, arg2: {:d}, \"\n             \"labelNum: {:d})\\n\",\n             arg1, arg2, labelNum);\n}\nVmInstruction(InstKeyboardOnJump) {\n  using namespace Interface;\n  using namespace Input;\n  StartInstruction;\n  PopUint8(type);\n  PopExpression(buttonsArg);\n  PopExpression(downTypeId);\n  PopUint16(labelNum);\n  uint32_t labelAdr = ScriptGetLabelAddress(thread->ScriptBufferId, labelNum);\n\n  const InputDownType downType = static_cast<InputDownType>(downTypeId);\n  const bool useKBcustom = (type & 2);\n  const bool inputResult = [&]() -> bool {\n    std::span<const bool> const kbBtnArr =\n        (downType == InputDownType::IsDown     ? KeyboardButtonIsDown\n         : downType == InputDownType::WentDown ? KeyboardButtonWentDown\n                                               : std::span<const bool>{});\n    if (kbBtnArr.empty()) return true;\n    if (useKBcustom) {\n      auto kbCustomItr = KBcustom.find(static_cast<uint8_t>(buttonsArg));\n      if (kbCustomItr == KBcustom.end()) return false;\n      return std::any_of(kbCustomItr->second.begin(), kbCustomItr->second.end(),\n                         [&kbBtnArr](int id) { return kbBtnArr[id]; });\n    } else\n      return kbBtnArr[buttonsArg];\n  }();\n\n  if (inputResult) {\n    thread->IpOffset = labelAdr;\n  }\n\n  ImpLogSlow(LogLevel::Trace, LogChannel::VM,\n             \"Instruction KeyboardOnJump(type: {:d}, buttonsArg: {:d}, \"\n             \"downTypeId: {:d}, \"\n             \"labelNum: {:d})\\n\",\n             type, buttonsArg, downTypeId, labelNum);\n}\nVmInstruction(InstControlOnJump) {\n  StartInstruction;\n  PopUint8(controlStateTarget);\n  PopExpression(controlId);\n  PopLocalLabel(labelAdr);\n  if ((bool)controlStateTarget == Interface::GetControlState(controlId)) {\n    thread->IpOffset = labelAdr;\n  }\n}\nVmInstruction(InstGetControl) { StartInstruction; }\nVmInstruction(InstPackFileAddBind) { StartInstruction; }\nVmInstruction(InstLoadJump) {\n  StartInstruction;\n  PopExpression(scriptId);\n  PopUint16(labelNum);\n  if (Profile::Vm::UseMsbStrings) {\n    LoadMsb(thread->ScriptBufferId, scriptId);\n    if (!Profile::Vm::UseSeparateMsbArchive) scriptId += 1;\n  }\n  LoadScript(thread->ScriptBufferId, scriptId);\n  uint32_t labelAdr = ScriptGetLabelAddress(thread->ScriptBufferId, labelNum);\n  thread->IpOffset = labelAdr;\n}\nVmInstruction(InstSwitch) {\n  StartInstruction;\n  PopExpression(switchVal);\n  SwitchValue = switchVal;\n}\nVmInstruction(InstCase) {\n  StartInstruction;\n  PopExpression(caseVal);\n  PopLocalLabel(labelAdr);\n\n  if (static_cast<int>(SwitchValue) == caseVal) {\n    thread->IpOffset = labelAdr;\n  }\n}\n\nVmInstruction(InstFlagOffReturn) {\n  StartInstruction;\n  PopUint8(arg1);\n  PopExpression(arg2);\n  if ((bool)arg1 == GetFlag(arg2)) {\n    if (thread->CallStackDepth) {\n      thread->CallStackDepth--;\n      uint32_t retBufferId =\n          thread->ReturnScriptBufferIds[thread->CallStackDepth];\n      if (Profile::Vm::UseReturnIds) {\n        thread->IpOffset = ScriptGetRetAddress(\n            retBufferId, thread->ReturnIds[thread->CallStackDepth]);\n      } else {\n        thread->IpOffset = thread->ReturnAddresses[thread->CallStackDepth];\n      }\n      thread->ScriptBufferId = retBufferId;\n    } else {\n      ImpLog(LogLevel::Error, LogChannel::VM,\n             \"Return error, call stack empty.\\n\");\n    }\n  }\n}\n\n}  // namespace Vm\n\n}  // namespace Impacto"
  },
  {
    "path": "src/vm/inst_controlflow.h",
    "content": "#pragma once\n\n#include \"vm.h\"\n\nnamespace Impacto {\n\nnamespace Vm {\n\nVmInstruction(InstJump);\nVmInstruction(InstJumpTable);\nVmInstruction(InstIf);\nVmInstruction(InstCall);\nVmInstruction(InstJumpFar);\nVmInstruction(InstCallFar);\nVmInstruction(InstReturn);\nVmInstruction(InstReturnIfFlag);\nVmInstruction(InstLoop);\nVmInstruction(InstFlagOnJump);\nVmInstruction(InstKeyOnJump);\nVmInstruction(InstKeyOnJump_Dash);\nVmInstruction(InstClickOnJump);\nVmInstruction(InstKeyboardOnJump);\nVmInstruction(InstControlOnJump);\nVmInstruction(InstGetControl);\nVmInstruction(InstPackFileAddBind);\nVmInstruction(InstLoadJump);\nVmInstruction(InstSwitch);\nVmInstruction(InstCase);\nVmInstruction(InstFlagOffReturn);\n\n}  // namespace Vm\n\n}  // namespace Impacto"
  },
  {
    "path": "src/vm/inst_dialogue.cpp",
    "content": "#include \"inst_dialogue.h\"\n\n#include \"inst_macros.inc\"\n\n#include \"expression.h\"\n#include \"../profile/scriptvars.h\"\n#include \"../profile/configsystem.h\"\n#include \"../profile/dialogue.h\"\n#include \"../mem.h\"\n#include \"../log.h\"\n#include \"../audio/audiostream.h\"\n#include \"../profile/vm.h\"\n#include \"../hud/saveicondisplay.h\"\n#include \"../hud/tipsnotification.h\"\n#include \"../hud/nametagdisplay.h\"\n#include \"../data/savesystem.h\"\n#include \"../data/tipssystem.h\"\n#include \"../ui/ui.h\"\n#include \"interface/input.h\"\n#include \"../text/text.h\"\n#include \"../audio/audiosystem.h\"\n#include \"vm.h\"\n\nnamespace Impacto {\n\nnamespace Vm {\n\nusing namespace Impacto::Profile::ScriptVars;\n\nVmInstruction(InstMesViewFlag) {\n  StartInstruction;\n  PopUint8(type);\n  switch (type) {\n    case 0: {  // Set\n      PopExpression(scriptId);\n      PopExpression(lineId);\n      SaveSystem::SetLineRead(scriptId, lineId);\n    } break;\n    case 1: {  // Check\n      PopExpression(scrWorkEntry);\n      PopExpression(scriptId);\n      PopExpression(lineId);\n      ScrWork[scrWorkEntry] = SaveSystem::IsLineRead(scriptId, lineId);\n    } break;\n  }\n}\nVmInstruction(InstSetMesWinPri) {\n  StartInstruction;\n  PopExpression(arg1);\n  PopExpression(arg2);\n  PopExpression(unused);\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction MesViewFlag(arg1: {:d}, arg2: \"\n             \"{:d}, unused: {:d})\\n\",\n             arg1, arg2, unused);\n}\nVmInstruction(InstMesSync) {\n  StartInstruction;\n\n  PopUint8(type);\n  switch (type) {\n    case 0:\n    case 1:\n    case 2:\n    case 3:\n    case 4:\n    case 11:\n    case 12:\n    case 13:\n    case 14:\n    case 20:\n      break;\n    case 10: {\n      PopExpression(unknown);\n    } break;\n  }\n\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction MesSync(type: {:d})\\n\", type);\n}\nVmInstruction(InstMesSetID) {\n  StartInstruction;\n\n  int dialoguePageId = 0;\n\n  PopUint8(type);\n  switch (type) {\n    case 0: {  // SetSavePointPage0\n      if (Profile::Vm::UseReturnIds) {\n        PopUint16(savePointId);\n        if (!GetFlag(SF_MESSAVEPOINT_SSP)) {\n          SaveSystem::SetCheckpointId(savePointId);\n        }\n        ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                   \"STUB instruction MesSetID(type: SetSavePoint, savePointId: \"\n                   \"{:d})\\n\",\n                   savePointId);\n      } else {\n        ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                   \"STUB instruction MesSetID(type: SetSavePoint)\\n\");\n      }\n    } break;\n    case 1: {  // SetSavePointForPage\n      PopUint16(savePointId);\n      dialoguePageId = ExpressionEval(thread);\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction MesSetID(type: SetSavePoint1, \"\n                 \"savePointId: {:d}, arg1: {:d})\\n\",\n                 savePointId, dialoguePageId);\n      if (!GetFlag(SF_MESSAVEPOINT_SSP + dialoguePageId)) {\n        SaveSystem::SetCheckpointId(savePointId);\n      }\n    } break;\n    case 2: {  // SetPage\n      dialoguePageId = ExpressionEval(thread);\n      ImpLogSlow(\n          LogLevel::Warning, LogChannel::VMStub,\n          \"STUB instruction MesSetID(type: SetPage, dialoguePageId: {:d})\\n\",\n          dialoguePageId);\n    } break;\n  }\n\n  thread->DialoguePageId = dialoguePageId;\n}\nVmInstruction(InstMesCls) {\n  StartInstruction;\n  PopUint8(type);  // TODO: Implement types 0, 1, 2, 3, 4, 5, 6, 7, 8\n  if ((type & 0xFE) != 4 && !(type & 1)) {\n    PopExpression(arg1);\n    ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n               \"STUB instruction MesCls(type: {:d}, arg1: {:d})\\n\", type, arg1);\n  } else {\n    ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n               \"STUB instruction MesCls(type: {:d})\\n\", type);\n  }\n  switch (type) {\n    case 1: {\n      for (DialoguePage& page : DialoguePages) {\n        page.Clear();\n      }\n      SetFlag(SF_SHOWWAITICON, 0);\n      SetFlag(SF_SHOWWAITICON + 1, 0);\n      SetFlag(SF_SHOWWAITICON + 2, 0);\n    } break;\n  }\n}\nVmInstruction(InstMesVoiceWait) {\n  StartInstruction;\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction MesVoiceWait()\\n\");\n}\nVmInstruction(InstMes) {\n  StartInstruction;\n\n  DialoguePage& dialoguePage = DialoguePages[thread->DialoguePageId];\n  uint32_t scriptId = LoadedScriptMetas[thread->ScriptBufferId].Id;\n\n  // After loading a save we need to make sure the textbox is actually shown\n  if (dialoguePage.FadeAnimation.IsOut() &&\n      GetFlag(thread->DialoguePageId + SF_MESWINDOW0OPENFL)) {\n    dialoguePage.Mode =\n        (DialoguePageMode)ScrWork[thread->DialoguePageId * 10 + SW_MESMODE0];\n    dialoguePage.FadeAnimation.StartIn(true);\n  }\n  SaveSystem::SetQSavedOnCurrentLine(false);\n\n  PopUint8(type);\n  bool voiced = type & 1;\n  bool acted = type & (1 << 1);\n  bool MSB = type & (1 << 7);\n\n  int audioId = -1;\n  int animationId = 0;\n  if (voiced) audioId = ExpressionEval(thread);\n  PopExpression(characterId);\n  if (acted) animationId = ExpressionEval(thread);\n\n  if (characterId == 32) characterId = 0;\n  PopUint16(lineId);\n  uint32_t line = MSB ? MsbGetStrAddress(thread->ScriptBufferId, lineId)\n                      : ScriptGetStrAddress(thread->ScriptBufferId, lineId);\n\n  if (!(ScrWork[10 * thread->DialoguePageId + SW_MESWIN0TYPE] & (1 << 6))) {\n    SetFlag(SF_MESREAD, SaveSystem::IsLineRead(scriptId, lineId));\n    ChkMesSkip();\n  }\n\n  ScrWork[2 * dialoguePage.Id + SW_LINEID] = lineId;\n  ScrWork[2 * dialoguePage.Id + SW_SCRIPTID] = scriptId;\n\n  Audio::AudioStream* audioStream = nullptr;\n  if (GetFlag(SF_MESALLSKIP)) {\n    Audio::Channels[Audio::AC_VOICE0]->Stop(0.0f);\n  }\n  if (voiced) {\n    Io::Stream* stream;\n    IoError err = Io::VfsOpen(\"voice\", audioId, &stream);\n\n    bool playAudio = (err == IoError_OK && !GetFlag(SF_MESALLSKIP));\n    if (playAudio) audioStream = Audio::AudioStream::Create(stream);\n  }\n\n  uint32_t oldIp = thread->IpOffset;\n  thread->IpOffset = line;\n  dialoguePage.AddString(thread, audioStream, acted, animationId, characterId,\n                         true);\n  ResetInstruction;\n  if (!GetFlag(SF_MESSAVEPOINT_SSP + thread->DialoguePageId)) {\n    if ((ScrWork[thread->DialoguePageId * 10 + SW_MESWIN0TYPE] & 4) == 0 &&\n        ScrWork[SW_TITLE] != 0xffff) {\n      SaveSystem::SaveMemory();\n      SetFlag(SF_SAVECAPTURE, 1);\n      SetFlag(SF_AUTOSAVEENABLE, 1);\n    }\n  } else {\n    SetFlag(SF_MESSAVEPOINT_SSP + thread->DialoguePageId, 0);\n  }\n\n  thread->IpOffset = oldIp;\n  UI::BacklogMenuPtr->AddMessage(\n      {.ScriptBufferId = thread->ScriptBufferId, .IpOffset = line}, audioId,\n      acted ? animationId : characterId);\n\n  if (!(type & 0b1000)) {\n    SetFlag(SF_CHAANIME + thread->DialoguePageId, true);\n  }\n\n  SetFlag(SF_SYSTEMMENUDISABLE2, false);\n}\nVmInstruction(InstMesMain) {\n  using enum DialoguePage::AdvanceMethodType;\n  using enum DialoguePage::State;\n\n  StartInstruction;\n  PopUint8(type);\n  DialoguePage& currentPage = DialoguePages[thread->DialoguePageId];\n  DialoguePage::State pageState = currentPage.GetState();\n\n  if (pageState == Initial || pageState == Showing) {\n    if (GetFlag(SF_MESALLSKIP)) {\n      currentPage.Typewriter.Finish();\n    } else if (GetFlag(SF_MESSKIP)) {\n      currentPage.Typewriter.CancelRequested = true;\n    }\n  }\n\n  if (pageState == Initial || pageState == Showing || pageState == Hiding) {\n    ResetInstruction;\n    BlockThread;\n    return;\n  }\n\n  if (pageState == Hidden) {\n    currentPage.Clear();\n\n    // TODO: Add backlog entry\n\n    SaveSystem::SetLineRead(ScrWork[currentPage.Id * 2 + SW_SCRIPTID],\n                            ScrWork[currentPage.Id * 2 + SW_LINEID]);\n    return;\n  }\n\n  assert(pageState == Shown);\n\n  const bool autoForward = currentPage.AdvanceMethod == AutoForward ||\n                           currentPage.AdvanceMethod == AutoForwardSyncVoice;\n  if (!autoForward) {\n    SetFlag(SF_SYSMENUDISABLE, false);\n\n    if (currentPage.AdvanceMethod == Skip && type != 1) {\n      // TODO: Add backlog entry\n\n      currentPage.Typewriter.Reset();\n\n      return;\n    }\n\n    SetFlag(currentPage.Id + SF_SHOWWAITICON, true);\n  }\n\n  if (!GetFlag(SF_UIHIDDEN) && GetFlag(SF_MESSKIP)) {\n    currentPage.AutoWaitTime = 0.0f;\n\n    if (Profile::ConfigSystem::SkipVoice) {\n      Audio::Channels[Audio::AC_VOICE0]->Stop(0.0f);\n    }\n  }\n\n  const Audio::AudioChannelState audioState =\n      Audio::Channels[Audio::AC_VOICE0]->GetState();\n  const bool audioPlaying = audioState == Audio::ACS_Playing ||\n                            audioState == Audio::ACS_FadingIn ||\n                            audioState == Audio::ACS_FadingOut;\n  if (currentPage.AutoWaitTime == 0.0f &&\n      (!audioPlaying || GetFlag(SF_MESSKIP))) {\n    if (Profile::ConfigSystem::SkipVoice) {\n      Audio::Channels[Audio::AC_VOICE0]->Stop(0.0f);\n    }\n\n    SetFlag(currentPage.Id + SF_CHAANIME, false);\n    SetFlag(currentPage.Id + SF_SHOWWAITICON, false);\n    SetFlag(SF_SYSMENUDISABLE, true);\n\n    const bool advanceWithoutHiding =\n        currentPage.AdvanceMethod == Present0x18 ||\n        currentPage.AdvanceMethod == AutoForward ||\n        (currentPage.AdvanceMethod != PresentClear && type != 1 &&\n         currentPage.Mode == DPM_NVL);\n    if (advanceWithoutHiding) {\n      // TODO: Add backlog entry\n\n      SaveSystem::SetLineRead(ScrWork[currentPage.Id * 2 + SW_SCRIPTID],\n                              ScrWork[currentPage.Id * 2 + SW_LINEID]);\n\n      currentPage.Typewriter.Reset();\n\n      BlockThread;\n      return;\n    }\n\n    if (GetFlag(SF_MESALLSKIP)) {\n      currentPage.TextFadeAnimation.Finish(AnimationDirection::Out);\n    } else {\n      currentPage.TextFadeAnimation.StartOut();\n    }\n  }\n\n  BlockThread;\n  ResetInstruction;\n}\nVmInstruction(InstSetMesModeFormat) {\n  StartInstruction;\n  PopExpression(id);\n  PopLocalLabel(modeDataAdr);\n  (void)modeDataAdr;\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction SetMesModeFormat(id: {:d})\\n\", id);\n}\nVmInstruction(InstSetNGmoji) {\n  StartInstruction;\n  PopString(endingPuncts);\n  PopString(startingPuncts);\n\n  StringToken::AddFlags({thread->ScriptBufferId, startingPuncts},\n                        +CharacterTypeFlags::WordStartingPunct);\n  StringToken::AddFlags({thread->ScriptBufferId, endingPuncts},\n                        +CharacterTypeFlags::WordEndingPunct);\n}\nVmInstruction(InstMesRev) {\n  StartInstruction;\n  PopUint8(type);\n  switch (type) {\n    case 0:  // DispInit\n      UI::BacklogMenuPtr->Show();\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction SetMesModeFormat(type: DispInit)\\n\");\n      break;\n    case 1:  // Main\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction SetMesModeFormat(type: Main)\\n\");\n      break;\n    case 2:  // AllCls\n      UI::BacklogMenuPtr->Hide();\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction SetMesModeFormat(type: AllCls)\\n\");\n      break;\n    case 3:  // ChkLoad\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction SetMesModeFormat(type: ChkLoad)\\n\");\n      break;\n    case 4:  // SAVELoad\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction SetMesModeFormat(type: SAVELoad)\\n\");\n      break;\n    case 5:  // SoundUnk\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction SetMesModeFormat(type: SoundUnk)\\n\");\n      break;\n    case 0xA:  // DispInit\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction SetMesModeFormat(type: DispInit)\\n\");\n      break;\n  }\n}\nVmInstruction(InstMessWindow) {\n  StartInstruction;\n\n  const auto getCurrentPage = [&thread]() -> DialoguePage& {\n    return DialoguePages[thread->DialoguePageId];\n  };\n\n  PopUint8(type);\n  switch (type) {\n    case 0: {  // Close\n      DialoguePage& currentPage = getCurrentPage();\n\n      if (!currentPage.FadeAnimation.IsOut()) {\n        currentPage.Hide();\n        SetFlag(currentPage.Id + SF_MESWINDOW0OPENFL, 0);\n        BlockThread;\n      }\n    } break;\n\n    case 1: {  // Open\n      DialoguePage& currentPage = getCurrentPage();\n\n      if (!currentPage.FadeAnimation.IsIn()) {\n        currentPage.Show();\n\n        SetFlag(SF_SYSTEMMENUDISABLE2, true);\n        SetFlag(thread->DialoguePageId + SF_MESWINDOW0OPENFL, 1);\n\n        if (ScrWork[currentPage.Id + SW_MESWINDOW0ALPHA] == 0) {\n          currentPage.RenderName = false;\n        }\n\n        BlockThread;\n      }\n    } break;\n\n    case 2:    // OpenedWait\n    case 3: {  // ClosedWait\n      const bool fading = getCurrentPage().FadeAnimation.IsPlaying();\n      SetFlag(SF_SYSTEMMENUDISABLE2, fading);\n\n      if (fading) {\n        ResetInstruction;\n        BlockThread;\n      }\n    } break;\n\n    case 4: {  // HideCurrent04\n      DialoguePage& currentPage = getCurrentPage();\n\n      SetFlag(currentPage.Id + SF_MESWINDOW0OPENFL, false);\n\n      currentPage.Hide();\n      currentPage.FadeAnimation.Finish();\n      currentPage.Clear();\n      currentPage.RenderName = false;\n    } break;\n\n    case 5: {  // CloseEx\n      PopExpression(dialoguePageId);\n      thread->DialoguePageId = dialoguePageId;\n      DialoguePage& currentPage = getCurrentPage();\n\n      if (!currentPage.FadeAnimation.IsOut()) {\n        currentPage.Hide();\n        currentPage.Name.clear();\n        SetFlag(dialoguePageId + SF_MESWINDOW0OPENFL, 0);\n        BlockThread;\n      }\n    } break;\n\n    case 6: {  // OpenEx\n      PopExpression(dialoguePageId);\n      thread->DialoguePageId = dialoguePageId;\n      DialoguePage& currentPage = getCurrentPage();\n\n      if (!currentPage.FadeAnimation.IsIn()) {\n        if (ScrWork[dialoguePageId + SW_MESWINDOW0ALPHA] == 0) {\n          currentPage.RenderName = false;\n        }\n\n        currentPage.Show();\n\n        SetFlag(dialoguePageId + SF_MESWINDOW0OPENFL, true);\n        BlockThread;\n      }\n    } break;\n\n    case 7: {  // FastClose\n      PopExpression(dialoguePageId);\n      thread->DialoguePageId = dialoguePageId;\n\n      SetFlag(dialoguePageId + SF_MESWINDOW0OPENFL, false);\n      ScrWork[dialoguePageId + SW_MESWINDOW0ALPHA] = 0;\n      getCurrentPage().Name.clear();\n    } break;\n  }\n}\nVmInstruction(InstSel) {\n  StartInstruction;\n  PopUint8(type);\n  switch (type) {\n    case 0: {  // SelInit\n      if (Profile::Vm::GameInstructionSet == InstructionSet::Dash ||\n          Profile::Vm::GameInstructionSet == InstructionSet::CC ||\n          Profile::Vm::GameInstructionSet == InstructionSet::MO8) {\n        PopUint16(savepointid);\n        // SF_MESSAVEPOINT_SSP + dialog page's field 5 in decompile?\n        // if (GetFlag(SF_MESSAVEPOINT_SSP + thread->DialoguePageId) == 0) {\n        if (ScrWork[SW_TITLE] != 0xffff) {\n          SaveSystem::SetCheckpointId(savepointid);\n          SaveSystem::SaveMemory();\n          SetFlag(SF_SAVECAPTURE, 1);\n          SetFlag(SF_AUTOSAVEENABLE, 1);\n          BlockThread;\n        }\n        // }\n      }\n      PopExpression(arg1);\n      UI::SelectionMenuPtr->InitSelectionMenu((bool)arg1);\n      // SaveIconDisplay::Show();\n      break;\n    }\n    case 1: {\n      PopUint16(selStrNum);\n      auto offset = ScriptGetStrAddress(thread->ScriptBufferId, selStrNum);\n      UI::SelectionMenuPtr->AddChoice(\n          {.ScriptBufferId = thread->ScriptBufferId, .IpOffset = offset});\n      break;\n    }\n    case 0x81: {\n      PopMsbString(line);\n      UI::SelectionMenuPtr->AddChoice(\n          {.ScriptBufferId = thread->ScriptBufferId, .IpOffset = line});\n    } break;\n    case 2: {\n      PopUint16(selStrNum);\n      auto offset = ScriptGetStrAddress(thread->ScriptBufferId, selStrNum);\n      UI::SelectionMenuPtr->AddChoice(\n          {.ScriptBufferId = thread->ScriptBufferId, .IpOffset = offset});\n      PopExpression(arg2);\n      break;\n    }\n    case 0x82: {\n      PopMsbString(line);\n      PopExpression(arg2);\n      UI::SelectionMenuPtr->AddChoice(\n          {.ScriptBufferId = thread->ScriptBufferId, .IpOffset = line});\n    } break;\n  }\n}\nVmInstruction(InstSelect) {\n  StartInstruction;\n  PopUint8(type);\n  switch (type) {\n    case 0: {\n      UI::SelectionMenuPtr->Show();\n      bool flag = GetFlag(SF_SAVEDISABLE);\n      SetFlag(thread->DialoguePageId + SF_CHAANIME, false);\n      if (ScrWork[SW_AUTOSAVERESTART] == 2) {\n        thread->IpOffset += 12;\n        return;\n      } else {\n        SaveSystem::SaveMemory();\n        auto quicksaveEntries = SaveSystem::GetQuickSaveOpenSlot();\n        if (!flag && quicksaveEntries.has_value()) {\n          SaveIconDisplay::ShowFor(2.4f);\n          SaveSystem::FlushWorkingSaveEntry(SaveSystem::SaveType::Quick,\n                                            *quicksaveEntries, 2);\n          SaveSystem::SaveThumbnailData();\n        }\n        ScrWork[SW_AUTOSAVERESTART] = 0;\n        if (quicksaveEntries == -1) {\n          thread->IpOffset += 12;\n        }\n      }\n      SetFlag(SF_SYSMENUDISABLE, 0);\n    } break;\n    case 1: {\n      if (!UI::SelectionMenuPtr->ChoiceMade) {\n        ResetInstruction;\n        BlockThread;\n      } else if (UI::SelectionMenuPtr->State == UI::MenuState::Hiding) {\n        ResetInstruction;\n        BlockThread;\n      } else if (UI::SelectionMenuPtr->State == UI::MenuState::Hidden) {\n        BlockThread;\n      } else {\n        UI::SelectionMenuPtr->Hide();\n        ResetInstruction;\n        BlockThread;\n      }\n    } break;\n    case 2: {\n      PopExpression(arg1);\n      ScrWork[arg1] = UI::SelectionMenuPtr->SelectedChoiceId;\n    } break;\n  }\n}\nVmInstruction(InstSysSel) {\n  StartInstruction;\n  PopUint8(type);  // TODO: Implement type 0, 1\n  if (type >= 2) {\n    PopString(arg1);\n    (void)arg1;\n  }\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction SysSel(type: {:d})\\n\", type);\n}\nVmInstruction(InstSysSelect) {\n  StartInstruction;\n  PopUint8(type);\n  switch (type & 0xF) {\n    case 0: {\n      PopExpression(arg1);\n      PopExpression(arg2);\n      ImpLogSlow(\n          LogLevel::Warning, LogChannel::VMStub,\n          \"STUB instruction SysSelect(type: {:d}, arg1: {:d}, arg2: {:d})\\n\",\n          type, arg1, arg2);\n    } break;\n    case 2:\n    case 3: {\n      PopExpression(destination);\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction SysSelect(type: {:d}, destination: {:d})\\n\",\n                 type, destination);\n    } break;\n  }\n}\nVmInstruction(InstSetTextTable) {\n  StartInstruction;\n  PopExpression(id);\n  PopLocalLabel(tableDataAdr);\n  TextTable[id].scriptBufferId = static_cast<uint8_t>(thread->ScriptBufferId);\n  TextTable[id].labelAdr = tableDataAdr;\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction SetTextTable(id: {:d})\\n\", id);\n}\nVmInstruction(InstSetDic) {\n  StartInstruction;\n  PopUint8(type);\n  PopExpression(tipId);\n  switch (type) {\n    case 0:    // NewTip\n    case 1: {  // Check\n      bool tipLocked = TipsSystem::GetTipLockedState(tipId);\n      if (tipLocked) {\n        TipsSystem::SetTipLockedState(tipId, false);\n        TipsSystem::SetTipNewState(tipId, true);\n        TipsSystem::SetTipUnreadState(tipId, true);\n        TipsNotification::AddTip(tipId);\n        TipsSystem::GetNewTipsIndices().push_back(static_cast<uint16_t>(tipId));\n      }\n      if (type == 1) {\n        PopExpression(flagId);\n        SetFlag(flagId, tipLocked);\n      }\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction SetDic(type: NewTip, tipId: {:d})\\n\", tipId);\n    } break;\n    case 2:  // SetDic02\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction SetDic(type: {:d}, tipId: {:d})\\n\", type,\n                 tipId);\n      break;\n  }\n}\nVmInstruction(InstEncyclopedia) {\n  StartInstruction;\n  PopExpression(tipId);\n  if (TipsSystem::GetTipLockedState(tipId)) {\n    TipsSystem::SetTipLockedState(tipId, false);\n    TipsNotification::AddTip(tipId);\n    TipsSystem::GetNewTipsIndices().push_back(static_cast<uint16_t>(tipId));\n  }\n}\nVmInstruction(InstNameID) {\n  StartInstruction;\n  PopUint8(type);\n  switch (type) {\n    case 0:\n      if (Profile::Vm::GameInstructionSet == InstructionSet::CC ||\n          Profile::Vm::GameInstructionSet == InstructionSet::MO8 ||\n          Profile::Vm::GameInstructionSet == InstructionSet::CHN) {\n        PopLocalLabel(namePlateDataBlock);\n        Sc3Stream namePlateData(\n            &ScriptBuffers[thread->ScriptBufferId][namePlateDataBlock]);\n        if (!Profile::Vm::UseMsbStrings) InitNamePlateData(namePlateData);\n      } else if (Profile::Vm::GameInstructionSet == InstructionSet::MO6TW) {\n        PopExpression(arg1);\n        PopExpression(arg2);\n        PopExpression(arg3);\n      }\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction NameID(type: {:d})\\n\", type);\n      break;\n    case 1: {\n      PopLocalLabel(namePlateDataBlock);\n      (void)namePlateDataBlock;\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction NameID(type: {:d})\\n\", type);\n    } break;\n    case 2:\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction NameID(type: {:d})\\n\", type);\n      break;\n  }\n}\nVmInstruction(InstTips) {\n  StartInstruction;\n  PopUint8(type);\n  switch (type) {\n    case 0: {  // TipsDataInit\n      PopUint16(tipsLabelNum);\n      uint32_t tipsDataAdr =\n          ScriptGetLabelAddress(thread->ScriptBufferId, tipsLabelNum);\n      if (Profile::Vm::GameInstructionSet == InstructionSet::MO8 ||\n          Profile::Vm::GameInstructionSet == InstructionSet::CHN) {\n        PopLocalLabel(tipsDataAdr1);\n        (void)tipsDataAdr1;\n      }\n      uint32_t tipsDataSize =\n          ScriptGetLabelSize(thread->ScriptBufferId, tipsLabelNum);\n      TipsSystem::DataInit(thread->ScriptBufferId, tipsDataAdr, tipsDataSize);\n      if (Profile::Vm::GameInstructionSet == InstructionSet::CC &&\n          UI::TipsMenuPtr) {\n        UI::TipsMenuPtr->Init();\n      }\n    } break;\n    case 1:  // TipsInit\n      TipsSystem::UpdateTipRecords();\n      if (Profile::Vm::GameInstructionSet != InstructionSet::CC &&\n          UI::TipsMenuPtr) {\n        UI::TipsMenuPtr->Init();\n      }\n      break;\n    case 2:  // TipsMain\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction Tips(type: TipsMain)\\n\");\n      break;\n    case 3:  // TipsEnd\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction Tips(type: TipsEnd)\\n\");\n      break;\n    case 4:  // TipsSet\n      TipsSystem::UpdateTipRecords();\n      break;\n    case 5:\n      TipsSystem::UpdateTipRecords();\n      if (Profile::Vm::GameInstructionSet != InstructionSet::CC &&\n          UI::TipsMenuPtr) {\n        UI::TipsMenuPtr->Init();\n      }\n      break;\n    case 10:  // Tips_ProfSetXboxEvent\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction Tips(type: Tips_ProfSetXboxEvent)\\n\");\n      break;\n  }\n}\nVmInstruction(InstSetRevMes) {\n  StartInstruction;\n  PopUint8(type);\n\n  bool voiced = type & (1 << 0);\n  bool savep = (type & (1 << 1)) && voiced;\n  bool expression = type & (1 << 7);\n\n  if ((type & (1 << 1)) || expression) {\n    ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n               \"STUB instruction SetRevMes(type: {:d})\\n\", type);\n  }\n\n  int audioId = -1;\n  int animationId = 0;\n  if (voiced) {\n    audioId = ExpressionEval(thread);\n    animationId = ExpressionEval(thread);\n  }\n\n  if (savep) {\n    // TODO: use?\n    ExpressionEval(thread);\n  }\n\n  int lineId;\n  if (expression) {\n    lineId = ExpressionEval(thread);\n  } else {\n    PopUint16(lineIdTemp);\n    lineId = lineIdTemp;\n  }\n  uint32_t line = ScriptGetStrAddress(thread->ScriptBufferId, lineId);\n\n  uint32_t scriptId = LoadedScriptMetas[thread->ScriptBufferId].Id;\n\n  SaveSystem::SetLineRead(scriptId, lineId);\n  UI::BacklogMenuPtr->AddMessage(\n      {.ScriptBufferId = thread->ScriptBufferId, .IpOffset = line}, audioId,\n      animationId);\n}\n\nvoid ChkMesSkip() {\n  bool mesSkip = false;\n  bool mesAllSkip = false;\n\n  if (Profile::Vm::GameInstructionSet != InstructionSet::CHLCC &&\n      ScrWork[SW_SYSMESALPHA] != 255) {\n    SkipModeEnabled = false;\n    AutoModeEnabled = false;\n  }\n\n  if ((ScrWork[SW_GAMESTATE] & 0b101) == 0b001 && !GetFlag(SF_UIHIDDEN)) {\n    mesSkip |= Interface::GetControlState(Interface::CT_NextMessage);\n\n    if (Interface::GetControlState(Interface::CT_ForceSkip,\n                                   Interface::InputDownType::IsDown)) {\n      mesSkip = true;\n      mesAllSkip = true;\n    };\n\n    if (Interface::PADinputButtonWentDown & Interface::PADcustom[8]) {\n      SkipModeEnabled = !SkipModeEnabled;\n    }\n\n    if (Interface::PADinputButtonWentDown & Interface::PADcustom[9]) {\n      AutoModeEnabled = !AutoModeEnabled;\n    }\n\n    if (SkipModeEnabled &&\n        (!Profile::ConfigSystem::SkipRead || GetFlag(SF_MESREAD))) {\n      mesSkip = true;\n      mesAllSkip = true;\n    }\n  }\n\n  SetFlag(SF_MESSKIP, mesSkip);\n  SetFlag(SF_MESALLSKIP, mesAllSkip);\n}\n\n}  // namespace Vm\n\n}  // namespace Impacto"
  },
  {
    "path": "src/vm/inst_dialogue.h",
    "content": "#pragma once\n\n#include \"vm.h\"\n\nnamespace Impacto {\n\nnamespace Vm {\n\nVmInstruction(InstMesViewFlag);\nVmInstruction(InstSetMesWinPri);\nVmInstruction(InstMesSync);\nVmInstruction(InstMesSetID);\nVmInstruction(InstMesCls);\nVmInstruction(InstMesVoiceWait);\nVmInstruction(InstMes);\nVmInstruction(InstMesMain);\nVmInstruction(InstSetMesModeFormat);\nVmInstruction(InstSetNGmoji);\nVmInstruction(InstMesRev);\nVmInstruction(InstMessWindow);\nVmInstruction(InstSel);\nVmInstruction(InstSelect);\nVmInstruction(InstSysSel);\nVmInstruction(InstSysSelect);\nVmInstruction(InstSetTextTable);\nVmInstruction(InstSetDic);\nVmInstruction(InstEncyclopedia);\nVmInstruction(InstNameID);\nVmInstruction(InstTips);\nVmInstruction(InstSetRevMes);\n\nvoid ChkMesSkip();\n\n}  // namespace Vm\n\n}  // namespace Impacto"
  },
  {
    "path": "src/vm/inst_gamespecific.cpp",
    "content": "#include \"inst_gamespecific.h\"\n\n#include \"inst_macros.inc\"\n\n#include \"expression.h\"\n#include \"../log.h\"\n#include \"../mem.h\"\n#include \"../profile/scriptvars.h\"\n#include \"../video/videosystem.h\"\n#include \"../profile/vm.h\"\n\n#include \"../ui/gamespecific.h\"\n\n#include \"../games/cclcc/delusiontrigger.h\"\n#include \"../games/chlcc/delusiontrigger.h\"\n#include \"../games/cclcc/yesnotrigger.h\"\n#include \"../games/cclcc/mapsystem.h\"\n#include \"../data/savesystem.h\"\n#include \"../data/tipssystem.h\"\n#include \"../profile/games/chlcc/albummenu.h\"\n#include \"../profile/games/chlcc/musicmenu.h\"\n\nnamespace Impacto {\n\nnamespace Vm {\n\nusing namespace Impacto::Profile::ScriptVars;\nusing namespace Impacto::Profile::CHLCC::AlbumMenu;\nusing namespace Impacto::Profile::CHLCC::MusicMenu;\n\nVmInstruction(InstUnk0041) {\n  StartInstruction;\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction Unk0041()\\n\");\n}\nVmInstruction(InstUnk0052) {\n  StartInstruction;\n  PopExpression(arg1);\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction Unk0052(arg1: {:d})\\n\", arg1);\n}\nVmInstruction(InstUnk0053) {\n  StartInstruction;\n  PopExpression(arg1);\n  PopExpression(arg2);\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction Unk0053(arg1: {:d}, arg2: {:d})\\n\", arg1, arg2);\n}\nVmInstruction(InstUnk0054) {\n  StartInstruction;\n  PopExpression(arg1);\n  PopExpression(arg2);\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction Unk0054(arg1: {:d}, arg2: {:d})\\n\", arg1, arg2);\n}\nVmInstruction(InstAddContents) {\n  StartInstruction;\n  PopUint8(type);\n  switch (type) {\n    case 0: {\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction AddContents(type: {:d})\\n\", type);\n    } break;\n    case 1: {  // CheckDownloadComp\n      PopExpression(arg1);\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction AddContents(type: {:d}, arg1: {:d})\\n\", type,\n                 arg1);\n    } break;\n    case 2: {  // OpenContents\n      PopExpression(arg1);\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction AddContents(type: {:d}, arg1: {:d})\\n\", type,\n                 arg1);\n    } break;\n    case 3: {  // CloseContents\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction AddContents(type: {:d})\\n\", type);\n    } break;\n  }\n}\nVmInstruction(InstUnk011F) {\n  StartInstruction;\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction Unk011F()\\n\");\n}\nVmInstruction(InstUnk012D) {\n  StartInstruction;\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction Unk012D()\\n\");\n}\nVmInstruction(InstAllClearChkCHLCC) {\n  StartInstruction;\n  // if already have seen 100% congrats\n  if (GetFlag(SF_CONGRATULATED)) {\n    SetFlag(SF_ALLCLEAR, false);\n    return;\n  }\n\n  const std::array<int, 7> endingFlags = {\n      SF_CLR_RIMI, SF_CLR_NANAMI, SF_CLR_YUA,  SF_CLR_MIA,\n      SF_CLR_SENA, SF_CLR_KOZUE,  SF_CLR_SEIRA};\n  const bool gotRequiredEndings = std::ranges::all_of(endingFlags, GetFlag);\n  if (!gotRequiredEndings) {\n    SetFlag(SF_ALLCLEAR, false);\n    return;\n  }\n  int totalVariations = 0;\n  int viewedVariations = 0;\n  for (int i = 0; i < AlbumPages * EntriesPerPage; i++) {\n    SaveSystem::GetEVStatus(i, &totalVariations, &viewedVariations);\n    if (totalVariations != viewedVariations) {\n      SetFlag(SF_ALLCLEAR, false);\n      return;\n    }\n  }\n\n  const size_t tipCount = TipsSystem::GetTipCount();\n  for (size_t id = 0; id < tipCount; id++) {\n    if (TipsSystem::GetTipLockedState(id)) {\n      SetFlag(SF_ALLCLEAR, false);\n      return;\n    }\n  }\n  // skipping songs check due to song flags init \"delayed\" nature, which makes\n  // congratulations easier to miss\n  SetFlag(SF_ALLCLEAR, true);\n}\nVmInstruction(InstRINNS) {\n  StartInstruction;\n  PopUint8(type);\n  switch (type) {\n    case 0: {  // RINNSInit\n      PopUint16(arg1);\n      PopUint8(arg2);\n      PopExpression(arg3);\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction RINNSInit(arg1: {:d}, arg2: {:d}, \"\n                 \"arg3: {:d})\\n\",\n                 arg1, arg2, arg3);\n    } break;\n    case 0x81: {  // RINNSAddID\n      PopExpression(arg1);\n      PopExpression(arg2);\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction RINNSAddID(arg1: {:d}, arg2: {:d})\\n\", arg1,\n                 arg2);\n    } break;\n    case 0x91: {  // RINNSTimeupLogID\n      PopExpression(arg1);\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction RINNSTimeupLogID(arg1: {:d})\\n\", arg1);\n    } break;\n  }\n}\nVmInstruction(InstRINNSMain) {\n  StartInstruction;\n  PopUint8(type);\n  switch (type) {\n    case 0: {\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction RINNSMain(type: {:d})\\n\", type);\n    } break;\n    case 1: {\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction RINNSMain(type: {:d})\\n\", type);\n    } break;\n    case 2: {\n      PopExpression(arg1);\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction RINNSMain(type: {:d}, arg1: {:d})\\n\", type,\n                 arg1);\n    } break;\n  }\n}\nVmInstruction(InstChatMO8) {\n  StartInstruction;\n  PopUint8(type);\n  switch (type) {\n    case 0: {  // ChatInit\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction ChatMO8(type: {:d})\\n\", type);\n    } break;\n    case 1: {  // ChatAdd\n      PopExpression(arg1);\n      PopExpression(arg2);\n      PopExpression(arg3);\n      PopExpression(arg4);\n      PopUint8(arg5);\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction ChatMO8(type: {:d}, arg1: {:d}, arg2: {:d}, \"\n                 \"arg3: {:d}, arg4: {:d}, arg5: {:d})\\n\",\n                 type, arg1, arg2, arg3, arg4, arg5);\n    } break;\n    case 2: {\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction ChatMO8(type: {:d})\\n\", type);\n    } break;\n    case 3: {\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction ChatMO8(type: {:d})\\n\", type);\n    } break;\n    case 4: {\n      PopExpression(arg1);\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction ChatMO8(type: {:d}, arg1: {:d})\\n\", type,\n                 arg1);\n    } break;\n    case 5: {\n      PopExpression(arg1);\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction ChatMO8(type: {:d}, arg1: {:d})\\n\", type,\n                 arg1);\n    } break;\n    case 6: {\n      PopExpression(arg1);\n      PopExpression(arg2);\n      PopExpression(arg3);\n      PopExpression(arg4);\n      PopExpression(arg5);\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction ChatMO8(type: {:d}, arg1: {:d}, arg2: {:d}, \"\n                 \"arg3: {:d}, arg4: {:d}, arg5: {:d})\\n\",\n                 type, arg1, arg2, arg3, arg4, arg5);\n    } break;\n  }\n}\nVmInstruction(InstGeotag) {\n  StartInstruction;\n  PopUint8(type);\n  switch (type) {\n    case 0: {\n      PopLocalLabel(label1);\n      (void)label1;\n      PopLocalLabel(label2);\n      (void)label2;\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction Geotag(type: {:d})\\n\", type);\n    } break;\n    case 1: {\n      PopExpression(arg1);\n      PopExpression(arg2);\n      ImpLogSlow(\n          LogLevel::Warning, LogChannel::VMStub,\n          \"STUB instruction Geotag(type: {:d}, arg1: {:d}, arg2: {:d})\\n\", type,\n          arg1, arg2);\n    } break;\n    case 2: {\n      PopExpression(arg1);\n      PopExpression(arg2);\n      ImpLogSlow(\n          LogLevel::Warning, LogChannel::VMStub,\n          \"STUB instruction Geotag(type: {:d}, arg1: {:d}, arg2: {:d})\\n\", type,\n          arg1, arg2);\n    } break;\n    case 3: {\n      PopExpression(arg1);\n      PopLocalLabel(label1);\n      (void)label1;\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction Geotag(type: {:d}, arg1: {:d})\\n\", type,\n                 arg1);\n    } break;\n    case 4: {\n      PopExpression(arg1);\n      PopLocalLabel(label1);\n      (void)label1;\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction Geotag(type: {:d}, arg1: {:d})\\n\", type,\n                 arg1);\n    } break;\n    case 5: {\n      PopExpression(arg1);\n      PopLocalLabel(label1);\n      (void)label1;\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction Geotag(type: {:d}, arg1: {:d})\\n\", type,\n                 arg1);\n    } break;\n    case 6: {\n      PopExpression(arg1);\n      PopLocalLabel(label1);\n      (void)label1;\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction Geotag(type: {:d}, arg1: {:d})\\n\", type,\n                 arg1);\n    } break;\n    case 7: {\n      PopExpression(arg1);\n      PopExpression(arg2);\n      ImpLogSlow(\n          LogLevel::Warning, LogChannel::VMStub,\n          \"STUB instruction Geotag(type: {:d}, arg1: {:d}, arg2: {:d})\\n\", type,\n          arg1, arg2);\n    } break;\n    case 8: {\n      PopExpression(arg1);\n      PopExpression(arg2);\n      ImpLogSlow(\n          LogLevel::Warning, LogChannel::VMStub,\n          \"STUB instruction Geotag(type: {:d}, arg1: {:d}, arg2: {:d})\\n\", type,\n          arg1, arg2);\n    } break;\n    case 9: {\n      PopExpression(arg1);\n      PopExpression(arg2);\n      ImpLogSlow(\n          LogLevel::Warning, LogChannel::VMStub,\n          \"STUB instruction Geotag(type: {:d}, arg1: {:d}, arg2: {:d})\\n\", type,\n          arg1, arg2);\n    } break;\n    case 10: {\n      for (int i = 0; i < 8; i++) {\n        if (ScrWork[SW_CHA1EX + 30 * i] == 0)\n          SetFlag(SF_MDL1SHDISP + i, 1);\n        else {\n          if (GetFlag(SF_AR_SETUP_ADD_MDLBUF1 + i))\n            SetFlag(SF_MDL1SHDISP + i, 1);\n          else\n            SetFlag(SF_MDL1SHDISP + i, 0);\n        }\n      }\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction Geotag(type: {:d})\\n\", type);\n    } break;\n    case 11: {\n      PopExpression(arg1);\n      PopExpression(arg2);\n      PopExpression(arg3);\n      PopExpression(arg4);\n      PopExpression(arg5);\n      ImpLogSlow(\n          LogLevel::Warning, LogChannel::VMStub,\n          \"STUB instruction Geotag(type: {:d}, arg1: {:d}, arg2: {:d}, arg3: \"\n          \"{:d}, arg4: {:d}, arg5: {:d})\\n\",\n          type, arg1, arg2, arg3, arg4, arg5);\n    } break;\n    case 12: {\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction Geotag(type: {:d})\\n\", type);\n    } break;\n    case 13: {\n      PopExpression(arg1);\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction Geotag(type: {:d}, arg1: {:d})\\n\", type,\n                 arg1);\n    } break;\n    case 14: {\n      PopExpression(arg1);\n      PopExpression(arg2);\n      ImpLogSlow(\n          LogLevel::Warning, LogChannel::VMStub,\n          \"STUB instruction Geotag(type: {:d}, arg1: {:d}, arg2: {:d})\\n\", type,\n          arg1, arg2);\n    } break;\n    case 15: {\n      PopExpression(arg1);\n      PopExpression(arg2);\n      PopExpression(arg3);\n      PopExpression(arg4);\n      ImpLogSlow(\n          LogLevel::Warning, LogChannel::VMStub,\n          \"STUB instruction Geotag(type: {:d}, arg1: {:d}, arg2: {:d}, arg3: \"\n          \"{:d}, arg4: {:d})\\n\",\n          type, arg1, arg2, arg3, arg4);\n    } break;\n    case 20: {\n      PopExpression(arg1);\n      PopExpression(arg2);\n      PopExpression(arg3);\n      PopExpression(arg4);\n      PopExpression(arg5);\n      PopExpression(arg6);\n      PopExpression(arg7);\n      PopExpression(arg8);\n      PopExpression(arg9);\n      PopExpression(arg10);\n      PopExpression(arg11);\n      PopExpression(arg12);\n      ImpLogSlow(\n          LogLevel::Warning, LogChannel::VMStub,\n          \"STUB instruction Geotag(type: {:d}, arg1: {:d}, arg2: {:d}, arg3: \"\n          \"{:d}, arg4: {:d}, arg5: {:d}, arg6: {:d}, arg7: {:d}, arg8: {:d}, \"\n          \"arg9: {:d}, arg10: {:d}, arg11: {:d}, arg12: {:d})\\n\",\n          type, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10,\n          arg11, arg12);\n    } break;\n    case 21: {\n      PopExpression(arg1);\n      PopExpression(arg2);\n      PopExpression(arg3);\n    } break;\n    case 22: {\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction Geotag(type: {:d})\\n\", type);\n    } break;\n  }\n}\nVmInstruction(InstUnk100FMO6) {\n  StartInstruction;\n  PopUint8(type);\n  PopExpression(arg1);\n  PopExpression(arg2);\n  PopExpression(arg3);\n  ImpLogSlow(\n      LogLevel::Warning, LogChannel::VMStub,\n      \"STUB instruction Unk100FMO6(type: {:d}, arg1: {:d}, arg2: {:d}, arg3: \"\n      \"{:d})\\n\",\n      type, arg1, arg2, arg3);\n}\nVmInstruction(InstUnk1010MO6) {\n  StartInstruction;\n  PopUint8(type);\n  PopExpression(arg1);\n  PopExpression(arg2);\n  ImpLogSlow(\n      LogLevel::Warning, LogChannel::VMStub,\n      \"STUB instruction Unk100FMO6(type: {:d}, arg1: {:d}, arg2: {:d})\\n\", type,\n      arg1, arg2);\n}\nVmInstruction(InstUnk1011MO6) {\n  StartInstruction;\n  PopUint8(type);\n  PopUint8(type2);\n  if (type2 != 2) {\n    PopExpression(arg1);\n    PopExpression(arg2);\n    PopExpression(arg3);\n    if (type2 != 0) {\n      PopExpression(arg4);\n      PopExpression(arg5);\n      PopExpression(arg6);\n      PopExpression(arg7);\n    }\n  }\n}\nVmInstruction(InstUnk1037MO7) {\n  StartInstruction;\n  PopUint8(type);\n}\nVmInstruction(InstUnk1037) {\n  StartInstruction;\n  PopExpression(arg1);\n  PopExpression(arg2);\n  PopExpression(arg3);\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction Unk1037(arg1: {:d}, arg2: {:d}, arg3: {:d})\\n\",\n             arg1, arg2, arg3);\n}\nVmInstruction(InstMapSystem) {\n  StartInstruction;\n  PopUint8(type);\n  auto& inst = UI::CCLCC::MapSystem::GetInstance();\n  switch (type) {\n    case 1:\n      inst.MapInit();\n      break;\n    case 2: {\n      PopExpression(arg1);\n      PopExpression(arg2);\n      inst.MapSetFadein(arg1, arg2);\n    } break;\n    case 3: {\n      PopExpression(arg1);\n      PopExpression(arg2);\n      PopExpression(arg3);\n      PopExpression(arg4);\n      ImpLogSlow(\n          LogLevel::Warning, LogChannel::VMStub,\n          \"STUB instruction MapSetGroup(arg1: {:d}, arg2: {:d}, arg3: {:d}, \"\n          \"arg4: {:d})\\n\",\n          arg1, arg2, arg3, arg4);\n      inst.MapSetGroup(arg1, arg2, arg3, arg4);\n    } break;\n    case 4: {\n      PopExpression(arg1);\n      PopExpression(arg2);\n      inst.MapSetFadeout(arg1, arg2);\n    } break;\n    case 5: {\n      PopExpression(arg1);\n      PopExpression(arg2);\n      inst.MapSetDisp(arg1, arg2);\n    } break;\n    case 6: {\n      PopExpression(arg1);\n      PopExpression(arg2);\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction MapSetHide(arg1: {:d}, arg2: {:d})\\n\", arg1,\n                 arg2);\n      inst.MapSetHide(arg1, arg2);\n    } break;\n    case 7:\n      if (!inst.MapFadeEndChk_Wait()) {\n        ResetInstruction;\n        BlockThread;\n      }\n      break;\n    case 8: {\n      PopExpression(arg1);\n      PopExpression(arg2);\n      PopExpression(arg3);\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction MapMoveAnimeInit(arg1: {:d}, arg2: {:d}, \"\n                 \"arg3: {:d})\\n\",\n                 arg1, arg2, arg3);\n      inst.MapMoveAnimeInit(arg1, arg2, arg3);\n    } break;\n    case 9:\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction MapMoveAnimeMain\\n\");\n      if (!inst.MapMoveAnimeMain()) {\n        ResetInstruction;\n        BlockThread;\n      }\n      break;\n    case 0xA: {\n      PopExpression(arg1);\n      PopExpression(arg2);\n      inst.MapGetPos(arg1, arg2, ScrWork[6365], ScrWork[6366]);\n\n    } break;\n    case 0xB: {\n      PopExpression(arg1);\n      PopExpression(arg2);\n      PopExpression(arg3);\n      PopExpression(arg4);\n      inst.MapSetPool(arg2 + arg1 * 10, arg3, arg4);\n    } break;\n    case 0xC: {\n      PopExpression(arg1);\n      inst.MapResetPoolAll(arg1);\n    } break;\n    case 0xD:\n      if (!inst.MapPoolFadeEndChk_Wait()) {\n        ResetInstruction;\n        BlockThread;\n      }\n      break;\n    case 0xE: {\n      PopExpression(arg1);\n      inst.MapPoolShuffle(arg1);\n    } break;\n    case 0xF: {\n      PopExpression(arg1);\n      PopExpression(arg2);\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction MapPoolSetDisp(arg1: {:d}, arg2: {:d})\\n\",\n                 arg1, arg2);\n      inst.MapPoolSetDisp(arg1, arg2);\n    } break;\n    case 0x10: {\n      PopExpression(arg1);\n      PopExpression(arg2);\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction MapPoolSetHide(arg1: {:d}, arg2: {:d})\\n\",\n                 arg1, arg2);\n      inst.MapPoolSetHide(arg1, arg2);\n    } break;\n    case 0x11: {\n      PopExpression(arg1);\n      PopExpression(arg2);\n      inst.MapPoolSetFadein(arg1, arg2);\n    } break;\n    case 0x12: {\n      PopExpression(arg1);\n      PopExpression(arg2);\n      inst.MapPoolSetFadeout(arg1, arg2);\n    } break;\n    case 0x13: {\n      PopExpression(arg1);\n      if (!inst.MapPlayerPhotoSelect(arg1)) {\n        ResetInstruction;\n        BlockThread;\n      }\n    } break;\n    case 0x14: {\n      PopExpression(arg1);\n      PopExpression(arg2);\n      inst.MapResetPool(arg1 * 10 + arg2);\n    } break;\n    case 0x15: {\n      PopExpression(arg1);\n      PopExpression(arg2);\n      PopExpression(arg3);\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction MapSetGroupEx(arg1: {:d}, arg2: {:d}, arg3: \"\n                 \"{:d})\\n\",\n                 arg1, arg2, arg3);\n      inst.MapSetGroupEx(arg1, arg2, arg3);\n    } break;\n    case 0x16: {\n      PopExpression(arg1);\n      PopExpression(arg2);\n      PopExpression(arg3);\n      ImpLogSlow(\n          LogLevel::Warning, LogChannel::VMStub,\n          \"STUB instruction MapZoomInit(arg1: {:d}, arg2: {:d}, arg3: {:d})\\n\",\n          arg1, arg2, arg3);\n      if (arg1 != ScrWork[6363] || arg2 != ScrWork[6364] ||\n          arg3 != ScrWork[6362]) {\n        inst.MapZoomInit(arg1, arg2, arg3);\n      } else {\n        thread->IpOffset += 3;\n      }\n    } break;\n    case 0x17:\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction MapZoomMain\\n\");\n      if (!inst.MapZoomMain()) {\n        ResetInstruction;\n        BlockThread;\n      }\n      break;\n    case 0x18: {\n      PopExpression(arg1);\n      PopExpression(arg2);\n      PopExpression(arg3);\n      ImpLogSlow(\n          LogLevel::Warning, LogChannel::VMStub,\n          \"STUB instruction MapZoomInit2(arg1: {:d}, arg2: {:d}, arg3: {:d})\\n\",\n          arg1, arg2, arg3);\n      if (arg1 != ScrWork[6363] || arg2 != ScrWork[6364] ||\n          arg3 != ScrWork[6362]) {\n        inst.MapZoomInit2(arg1, arg2);\n      } else {\n        thread->IpOffset += 3;\n      }\n    } break;\n    case 0x19:\n      if (!inst.MapZoomMain3()) {\n        ResetInstruction;\n        BlockThread;\n      }\n      break;\n    case 0x1A: {\n      PopExpression(arg1);\n      PopExpression(arg2);\n      PopExpression(arg3);\n      if (arg1 != ScrWork[6363] || arg2 != ScrWork[6364] ||\n          arg3 != ScrWork[6362]) {\n        if (!inst.MapZoomInit3(arg1, arg2, arg3)) {\n          thread->IpOffset += 3;\n        }\n      } else {\n        thread->IpOffset += 3;\n      }\n    } break;\n    case 0x1B: {\n      PopExpression(arg1);\n      PopExpression(arg2);\n      PopExpression(arg3);\n      if (arg1 != ScrWork[6363] || arg2 != ScrWork[6364] ||\n          arg3 != ScrWork[6362]) {\n        if (!inst.MapMoveAnimeInit2(arg1, arg2, arg3)) {\n          thread->IpOffset += 3;\n        }\n      } else {\n        thread->IpOffset += 3;\n      }\n    } break;\n    case 0x1C:\n      if (!inst.MapMoveAnimeMain2()) {\n        ResetInstruction;\n        BlockThread;\n      }\n      break;\n    case 0x1E:\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction MapPlayerPotalSelectInit\\n\");\n      inst.MapPlayerPotalSelectInit();\n      break;\n    case 0x1F:\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction MapPlayerPotalSelect\\n\");\n      if (!inst.MapPlayerPotalSelect()) {\n        ResetInstruction;\n        BlockThread;\n      }\n      break;\n    case 0x28:\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction MapSystem_28\\n\");\n      inst.MapSystem_28();\n      break;\n  }\n}\nVmInstruction(InstPhoneSG) {\n  StartInstruction;\n  PopUint8(type);\n  switch (type) {\n    case 0: {\n      PopUint8(arg1);\n      PopExpression(arg2);\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction Unk1037(arg1: {:d}, arg2: {:d})\\n\", arg1,\n                 arg2);\n      break;\n    }\n    case 1: {\n      PopUint8(arg1);\n      PopExpression(arg2);\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction Unk1037(arg1: {:d}, arg2: {:d})\\n\", arg1,\n                 arg2);\n      break;\n    }\n    case 2: {\n      PopUint8(arg1);\n      PopExpression(arg2);\n      PopUint16(arg3);\n      ImpLogSlow(\n          LogLevel::Warning, LogChannel::VMStub,\n          \"STUB instruction Unk1037(arg1: {:d}, arg2: {:d}, arg3: {:d})\\n\",\n          arg1, arg2, arg3);\n      break;\n    }\n    case 3: {\n      PopUint8(arg1);\n      PopExpression(arg2);\n      PopUint16(arg3);\n      ImpLogSlow(\n          LogLevel::Warning, LogChannel::VMStub,\n          \"STUB instruction Unk1037(arg1: {:d}, arg2: {:d}, arg3: {:d})\\n\",\n          arg1, arg2, arg3);\n      if (GetFlag(SF_Phone_Open)) {\n        ScrWork[SW_PHONE_DISP_CT] = 20;\n      } else {\n        ScrWork[SW_PHONE_DISP_CT] = 0;\n      }\n      break;\n    }\n    case 4: {\n      PopUint16(arg1);\n      PopUint16(arg2);\n      PopUint16(arg3);\n      PopUint16(arg4);\n      PopUint16(arg5);\n      PopUint16(arg6);\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction CHAmoveSetSeqDirect(arg1: {:d}, arg2: {:d}, \"\n                 \"arg3: {:d}, arg4: {:d}, arg5: {:d}, arg6: {:d})\\n\",\n                 arg1, arg2, arg3, arg4, arg5, arg6);\n      break;\n    }\n    case 5: {  // PhoneInit\n      break;\n    }\n    case 15: {\n      PopExpression(arg1);\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction Unk1037(arg1: {:d})\\n\", arg1);\n      break;\n    }\n    case 18: {\n      PopExpression(arg1);\n      PopExpression(arg2);\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction Unk1037(arg1: {:d}, arg2: {:d})\\n\", arg1,\n                 arg2);\n      break;\n    }\n  }\n}\nVmInstruction(InstMail) {\n  StartInstruction;\n  PopUint8(type);\n  switch (type) {\n    case 0:\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction Mail(type: {:d})\\n\", type);\n      break;\n    case 1:\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction Mail(type: {:d})\\n\", type);\n      break;\n    case 2:\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction Mail(type: {:d})\\n\", type);\n      break;\n    case 3: {\n      PopLocalLabel(label1);\n      (void)label1;\n      PopLocalLabel(label2);\n      (void)label2;\n      PopLocalLabel(label3);\n      (void)label3;\n      PopLocalLabel(label4);\n      (void)label4;\n      PopLocalLabel(label5);\n      (void)label5;\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction Mail(type: {:d})\\n\", type);\n    } break;\n    case 20:\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction Mail(type: {:d})\\n\", type);\n      break;\n    case 21: {\n      PopExpression(arg1);\n      PopExpression(arg2);\n      PopExpression(arg3);\n      PopExpression(arg4);\n      ImpLogSlow(\n          LogLevel::Warning, LogChannel::VMStub,\n          \"STUB instruction Mail(type: {:d}, arg1: {:d}, arg2: {:d}, arg3: \"\n          \"{:d}, arg4: {:d})\\n\",\n          type, arg1, arg2, arg3, arg4);\n    } break;\n    case 22: {\n      PopExpression(arg1);\n      PopExpression(arg2);\n      PopExpression(arg3);\n      PopExpression(arg4);\n      ImpLogSlow(\n          LogLevel::Warning, LogChannel::VMStub,\n          \"STUB instruction Mail(type: {:d}, arg1: {:d}, arg2: {:d}, arg3: \"\n          \"{:d}, arg4: {:d})\\n\",\n          type, arg1, arg2, arg3, arg4);\n    } break;\n    case 40:\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction Mail(type: {:d})\\n\", type);\n      break;\n    case 50:\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction Mail(type: {:d})\\n\", type);\n      break;\n    case 51:\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction Mail(type: {:d})\\n\", type);\n      break;\n    case 60:\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction Mail(type: {:d})\\n\", type);\n      break;\n    case 61: {\n      PopExpression(arg1);\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction Mail(type: {:d}, arg1: {:d})\\n\", type, arg1);\n    } break;\n    case 70:\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction Mail(type: {:d})\\n\", type);\n      break;\n    case 71: {\n      PopLocalLabel(label1);\n      (void)label1;\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction Mail(type: {:d})\\n\", type);\n    } break;\n    case 72: {\n      PopLocalLabel(label1);\n      (void)label1;\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction Mail(type: {:d})\\n\", type);\n    } break;\n    case 73:\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction Mail(type: {:d})\\n\", type);\n      break;\n    case 74:\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction Mail(type: {:d})\\n\", type);\n      break;\n  }\n}\nVmInstruction(InstUnk1038Darling) {\n  StartInstruction;\n  PopUint8(type);\n  if (type == 0) {\n    PopUint16(arg1);\n    (void)arg1;\n  }\n}\nVmInstruction(InstUnk1038MO7) {\n  StartInstruction;\n  PopExpression(arg1);\n  PopExpression(arg2);\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction Unk1038MO7(arg1: {:d}, arg2: {:d})\\n\", arg1,\n             arg2);\n}\nVmInstruction(InstTwipo) {\n  StartInstruction;\n  PopUint8(type);\n  switch (type) {\n    case 0: {\n      PopLocalLabel(label1);\n      (void)label1;\n      PopLocalLabel(label2);\n      (void)label2;\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction Twipo(type: {:d})\\n\", type);\n    } break;\n    case 1: {\n    } break;\n    case 2: {\n      PopExpression(arg1);\n      PopLocalLabel(label1);\n      (void)label1;\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction Twipo(type: {:d}, arg1: {:d})\\n\", type,\n                 arg1);\n    } break;\n    case 3: {\n      PopExpression(arg1);\n      PopLocalLabel(label1);\n      (void)label1;\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction Twipo(type: {:d}, arg1: {:d})\\n\", type,\n                 arg1);\n    } break;\n    case 4: {\n      PopExpression(arg1);\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction Twipo(type: {:d}, arg1: {:d})\\n\", type,\n                 arg1);\n    } break;\n    case 5: {\n      PopExpression(arg1);\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction Twipo(type: {:d}, arg1: {:d})\\n\", type,\n                 arg1);\n    } break;\n    case 6: {\n      PopExpression(arg1);\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction Twipo(type: {:d}, arg1: {:d})\\n\", type,\n                 arg1);\n    } break;\n    case 7: {\n      PopExpression(arg1);\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction Twipo(type: {:d}, arg1: {:d})\\n\", type,\n                 arg1);\n    } break;\n    case 10: {\n      PopExpression(arg1);\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction Twipo(type: {:d}, arg1: {:d})\\n\", type,\n                 arg1);\n    } break;\n    case 11: {\n      PopExpression(arg1);\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction Twipo(type: {:d}, arg1: {:d})\\n\", type,\n                 arg1);\n    } break;\n  }\n}\nVmInstruction(InstTwipo_Dash) {\n  StartInstruction;\n  PopUint8(type);\n  switch (type) {\n    case 0: {\n      PopLocalLabel(label1);\n      (void)label1;\n      PopLocalLabel(label2);\n      (void)label2;\n      PopLocalLabel(label3);\n      (void)label3;\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction Twipo(type: {:d})\\n\", type);\n    } break;\n    case 1:\n    case 11:\n    case 12:\n      break;\n    case 2: {\n      PopExpression(arg1);\n      PopLocalLabel(label1);\n      (void)label1;\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction Twipo(type: {:d}, arg1: {:d})\\n\", type,\n                 arg1);\n    } break;\n    case 3: {\n      PopExpression(arg1);\n      PopLocalLabel(label1);\n      (void)label1;\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction Twipo(type: {:d}, arg1: {:d})\\n\", type,\n                 arg1);\n    } break;\n    case 4: {\n      PopExpression(arg1);\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction Twipo(type: {:d}, arg1: {:d})\\n\", type,\n                 arg1);\n    } break;\n    case 5: {\n      PopExpression(arg1);\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction Twipo(type: {:d}, arg1: {:d})\\n\", type,\n                 arg1);\n    } break;\n    case 6: {\n      PopExpression(arg1);\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction Twipo(type: {:d}, arg1: {:d})\\n\", type,\n                 arg1);\n    } break;\n    case 7: {\n      PopExpression(arg1);\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction Twipo(type: {:d}, arg1: {:d})\\n\", type,\n                 arg1);\n    } break;\n    case 10: {\n      PopExpression(arg1);\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction Twipo(type: {:d}, arg1: {:d})\\n\", type,\n                 arg1);\n    } break;\n  }\n}\nVmInstruction(InstDelusionTriggerCHLCC) {\n  StartInstruction;\n  PopUint8(type);\n  auto& inst = UI::CHLCC::DelusionTrigger::GetInstance();\n  switch (type) {\n    case 1: {\n      // start show\n      inst.Show();\n    } break;\n    case 5: {\n      // restore delusion from load save\n      inst.Load();\n    } break;\n    case 6: {\n      // begin hide anim\n      inst.Hide();\n    } break;\n    case 4:\n      inst.State = UI::Shown;\n      break;\n    case 2:\n    case 7:\n      // handled by update\n      BlockThread;\n      break;\n    case 0:\n    case 3:\n    default:\n      // unused\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction DelusionTriggerCHLCC(type: {:d})\\n\", type);\n  }\n}\n\nVmInstruction(InstYesNoTriggerCCLCC) {\n  StartInstruction;\n  PopUint8(type);\n  using UI::CCLCC::YesNoTrigger;\n  using YesNoState = UI::CCLCC::YesNoTrigger::YesNoState;\n  switch (type) {\n    case 0: {\n      PopExpression(arg1);  // 0, 1, 2, 3\n      PopExpression(arg2);\n      PopExpression(arg3);\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction Unk103A(type: {:d}, arg1: {:d}, arg2: {:d}, \"\n                 \"arg3: {:d})\\n\",\n                 type, arg1, arg2, arg3);\n      YesNoTrigger::GetInstance().Start(arg1, arg2, arg3);\n    } break;\n    case 1: {\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction YesNoTriggerCCLCC(type: {:d})\\n\", type);\n      // Load images instruction\n    } break;\n    case 2: {\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction YesNoTriggerCCLCC(type: {:d})\\n\", type);\n      YesNoTrigger::GetInstance().Show();\n      BlockThread;\n    } break;\n    case 3: {\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction YesNoTriggerCCLCC(type: {:d})\\n\", type);\n      YesNoTrigger::GetInstance().Hide();\n    } break;\n    case 4: {\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction YesNoTriggerCCLCC(type: {:d})\\n\", type);\n      if (YesNoTrigger::GetInstance().State != YesNoState::MainInput) {\n        ResetInstruction;\n        BlockThread;\n      }\n    } break;\n    case 5: {\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction YesNoTriggerCCLCC(type: {:d})\\n\", type);\n      YesNoTrigger::GetInstance().AllowInput = true;\n    } break;\n    case 6: {\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction YesNoTriggerCCLCC(type: {:d})\\n\", type);\n      if (YesNoTrigger::GetInstance().State == YesNoState::MainInput) {\n        ResetInstruction;\n        BlockThread;\n      }\n    } break;\n    case 7: {\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction YesNoTriggerCCLCC(type: {:d})\\n\", type);\n      YesNoTrigger::GetInstance().GoToNextQuestion = true;\n    } break;\n    case 8: {\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction YesNoTriggerCCLCC(type: {:d})\\n\", type);\n      PopLocalLabel(branchAddress);\n      if (YesNoTrigger::GetInstance().State == YesNoState::PanToNext ||\n          YesNoTrigger::GetInstance().State == YesNoState::ZoomStart) {\n        ResetInstruction;\n        BlockThread;\n      } else if (YesNoTrigger::GetInstance().State != YesNoState::Complete) {\n        thread->IpOffset = branchAddress;\n        BlockThread;\n      }\n\n    } break;\n    case 10: {\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction YesNoTriggerCCLCC(type: {:d})\\n\", type);\n      YesNoTrigger::GetInstance().Reset();\n\n    } break;\n    default: {\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction YesNoTriggerCCLCC(type: {:d})\\n\", type);\n    } break;\n  }\n}\n\nVmInstruction(InstUnk103A) {\n  StartInstruction;\n  PopUint8(type);\n  switch (type) {\n    case 0: {\n      PopExpression(arg1);\n      PopExpression(arg2);\n      ImpLogSlow(\n          LogLevel::Warning, LogChannel::VMStub,\n          \"STUB instruction Unk103A(type: {:d}, arg1: {:d}, arg2: {:d})\\n\",\n          type, arg1, arg2);\n    } break;\n    case 1: {\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction Unk103A(type: {:d})\\n\", type);\n    } break;\n    case 2: {\n      SetFlag(2951, 1);  // Always win KillBallad\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction Unk103A(type: {:d})\\n\", type);\n    } break;\n  }\n}\nVmInstruction(InstUnk1037Noah) {\n  StartInstruction;\n  PopUint8(type);\n  switch (type) {\n    case 0x0: {\n      PopExpression(arg1);\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub, \"1037_00\\n\");\n    } break;\n    case 0x1: {\n      PopExpression(arg1);\n      PopExpression(arg2);\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub, \"1037_01\\n\");\n    } break;\n    case 0x2: {\n      PopExpression(arg1);\n      PopExpression(arg2);\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub, \"1037_01\\n\");\n    } break;\n    case 0x3: {\n      PopExpression(arg1);\n      PopExpression(arg2);\n      PopExpression(arg3);\n      PopExpression(arg4);\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub, \"1037_03\");\n    } break;\n    case 0x4: {\n      PopExpression(arg1);\n      PopExpression(arg2);\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub, \"1037_04\");\n    } break;\n    case 0x5: {\n      PopExpression(arg1);\n      PopExpression(arg2);\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub, \"1037_05\");\n    } break;\n    case 0x6: {\n      PopExpression(arg1);\n      PopExpression(arg2);\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub, \"1037_06\");\n    } break;\n    case 0x7: {\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub, \"1037_07\");\n    } break;\n    case 0x8: {\n      PopExpression(arg1);\n      PopExpression(arg2);\n      PopExpression(arg3);\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub, \"1037_08\");\n    } break;\n    case 0x9: {\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub, \"1037_09\");\n    } break;\n    case 0xA: {\n      PopExpression(arg1);\n      PopExpression(arg2);\n      PopExpression(arg3);\n      PopExpression(arg4);\n      PopExpression(arg5);\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub, \"1037_0A\");\n    } break;\n    case 0xB: {\n      PopExpression(arg1);\n      PopExpression(arg2);\n      PopExpression(arg3);\n      PopExpression(arg4);\n      PopExpression(arg5);\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub, \"1037_0B\");\n    } break;\n    case 0xC: {\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub, \"1037_0C\");\n    } break;\n    case 0xD: {\n      PopExpression(arg1);\n      PopExpression(arg2);\n      PopExpression(arg3);\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub, \"1037_0D\");\n    } break;\n    case 0xE: {\n      PopExpression(arg1);\n      PopExpression(arg2);\n      PopExpression(arg3);\n      PopExpression(arg4);\n      PopExpression(arg5);\n      PopExpression(arg6);\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub, \"1037_0E\");\n    } break;\n    case 0xF: {\n      PopExpression(arg1);\n      PopExpression(arg2);\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub, \"1037_0F\");\n    } break;\n    case 0x10: {\n      PopExpression(arg1);\n      PopExpression(arg2);\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub, \"1037_10\");\n    } break;\n    case 0x11: {\n      PopExpression(arg1);\n      PopExpression(arg2);\n      PopExpression(arg3);\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub, \"1037_13\");\n    } break;\n    case 0x12: {\n      PopExpression(arg1);\n      PopExpression(arg2);\n      PopExpression(arg3);\n      PopExpression(arg4);\n      PopExpression(arg5);\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub, \"1037_12\");\n    } break;\n    case 0x13: {\n      PopExpression(arg1);\n      PopExpression(arg2);\n      PopExpression(arg3);\n      PopExpression(arg4);\n      PopExpression(arg5);\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub, \"1037_13\");\n    } break;\n    case 0x14: {\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub, \"1037_14\");\n    } break;\n    case 0x15: {\n      PopExpression(arg1);\n      PopExpression(arg2);\n      PopExpression(arg3);\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub, \"1037_05\");\n    } break;\n    case 0x16: {\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub, \"1037_16\");\n    } break;\n    case 0x17: {\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub, \"1037_07\");\n    } break;\n    case 0x18: {\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub, \"1037_18\");\n    } break;\n    case 0x19: {\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub, \"1037_19\");\n    } break;\n    case 0x1A: {\n      PopExpression(arg1);\n      PopExpression(arg2);\n      PopExpression(arg3);\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub, \"1037_1A\");\n    } break;\n    case 0x1B: {\n      PopExpression(arg1);\n      PopExpression(arg2);\n      PopExpression(arg3);\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub, \"1037_1B\");\n    } break;\n    case 0x1C: {\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub, \"1037_1C\");\n    } break;\n    case 0x1E: {\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub, \"1037_1E\");\n    } break;\n    case 0x1F: {\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub, \"1037_1F\");\n    } break;\n    case 0x20: {\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub, \"1037_20\");\n    } break;\n    case 0x21: {\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub, \"1037_21\");\n    } break;\n    case 0x22: {\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub, \"1037_22\");\n    } break;\n    case 0x28: {\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub, \"1037_28\");\n    } break;\n    case 0x29: {\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub, \"1037_29\");\n    } break;\n    case 0x2A: {\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub, \"1037_2A\");\n    } break;\n    case 0x2B: {\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub, \"1037_2B\");\n    } break;\n    case 0x2C: {\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub, \"1037_2C\");\n    } break;\n    case 0x52: {\n      PopExpression(arg1);\n      PopExpression(arg2);\n      PopExpression(arg3);\n      PopExpression(arg4);\n      PopExpression(arg5);\n      PopExpression(arg6);\n      PopExpression(arg7);\n      PopExpression(arg8);\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub, \"1037_53\");\n    } break;\n    case 0x53: {\n      PopExpression(arg1);\n      PopExpression(arg2);\n      PopExpression(arg3);\n      PopExpression(arg4);\n      PopExpression(arg5);\n      PopExpression(arg6);\n      PopExpression(arg7);\n      PopExpression(arg8);\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub, \"1037_53\");\n    } break;\n    case 0x78: {\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub, \"1037_78\");\n    } break;\n    case 0x81: {\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub, \"1037_81\");\n    } break;\n    case 0x8C: {\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub, \"1037_8C\");\n    } break;\n    case 0x8D: {\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub, \"1037_8D\");\n    } break;\n    case 0x8E: {\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub, \"1037_8E\");\n    } break;\n    case 0x8F: {\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub, \"1037_8F\");\n    } break;\n    case 0x90: {\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub, \"1037_90\");\n    } break;\n  }\n}\nVmInstruction(InstMtrg) {\n  StartInstruction;\n  PopUint8(type);\n\n  auto& inst = UI::CCLCC::DelusionTrigger::GetInstance();\n  switch (type) {\n    case 0: {\n      PopExpression(arg1);\n      PopExpression(arg2);\n      PopExpression(arg3);\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction MtrgStart(type: {:d})\\n\", type);\n      if (inst.Show(arg1, arg2, arg3)) {\n        return;\n      }\n    } break;\n    case 1: {\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction MtrgEnd_Wait(type: {:d})\\n\", type);\n      inst.Hide();\n      if (ScrWork[SW_DELUSION_BG_COUNTER] != 0) {\n        ResetInstruction;\n      }\n      BlockThread;\n      return;\n    } break;\n    case 2: {\n      if (!GetFlag(SF_MOVIEPLAY)) {\n        Impacto::Video::Players[0]->Stop();\n        SetFlag(SF_MOVIELOADPLAYFL, 0);\n        ScrWork[SW_MOVIE_PLAYNO] = 0xffff;\n        ScrWork[SW_MOVIE_LOADNO] = 0xffff;\n        ScrWork[6344] = 0;\n        return;\n      }\n      if (GetFlag(SF_MESALLSKIP)) {\n        Impacto::Video::Players[0]->Stop();\n        if ((ScrWork[SW_MOVIE_PLAYNO] != 0xffff) &&\n            (ScrWork[SW_MOVIE_LOADNO] != 0xffff)) {\n          return;\n        }\n        SetFlag(SF_MOVIELOADPLAYFL, 0);\n        ScrWork[SW_MOVIE_PLAYNO] = 0xffff;\n        ScrWork[SW_MOVIE_LOADNO] = 0xffff;\n        ScrWork[6344] = 0;\n        return;\n      }\n    } break;\n    case 3: {\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction MtrgStop_Wait(type: {:d})\\n\", type);\n      if (inst.CheckTransitionAnimationComplete()) {\n        SetFlag(SF_DELUSIONSELECTED, 1);\n        return;\n      }\n    } break;\n    case 4: {\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction MtrgStart_Wait(type: {:d})\\n\", type);\n      if (inst.CheckStartTransitionComplete()) {\n        return;\n      }\n    } break;\n    case 5: {\n      PopExpression(arg1);\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction MtrgSetEvent(type: {:d})\\n\", type);\n      return;\n    };\n    default:\n      return;\n  }\n  ResetInstruction;\n  BlockThread;\n}\n\n}  // namespace Vm\n\n}  // namespace Impacto"
  },
  {
    "path": "src/vm/inst_gamespecific.h",
    "content": "#pragma once\n\n#include \"vm.h\"\n\nnamespace Impacto {\n\nnamespace Vm {\n\nVmInstruction(InstUnk0041);\nVmInstruction(InstUnk0052);\nVmInstruction(InstUnk0053);\nVmInstruction(InstUnk0054);\nVmInstruction(InstAddContents);\nVmInstruction(InstUnk011F);\nVmInstruction(InstRINNS);\nVmInstruction(InstRINNSMain);\nVmInstruction(InstUnk012D);\nVmInstruction(InstAllClearChkCHLCC);\nVmInstruction(InstChatMO8);\nVmInstruction(InstGeotag);\nVmInstruction(InstUnk100FMO6);\nVmInstruction(InstUnk1010MO6);\nVmInstruction(InstUnk1011MO6);\nVmInstruction(InstUnk1037);\nVmInstruction(InstUnk1037Noah);\nVmInstruction(InstUnk1037MO7);\nVmInstruction(InstMapSystem);\nVmInstruction(InstPhoneSG);\nVmInstruction(InstMail);\nVmInstruction(InstMtrg);\nVmInstruction(InstUnk1038Darling);\nVmInstruction(InstUnk1038MO7);\nVmInstruction(InstTwipo);\nVmInstruction(InstTwipo_Dash);\nVmInstruction(InstDelusionTriggerCHLCC);\nVmInstruction(InstYesNoTriggerCCLCC);\nVmInstruction(InstUnk103A);\n\n}  // namespace Vm\n\n}  // namespace Impacto"
  },
  {
    "path": "src/vm/inst_graphics2d.cpp",
    "content": "#include \"inst_graphics2d.h\"\n\n#include \"inst_macros.inc\"\n\n#include \"expression.h\"\n#include \"../game.h\"\n#include \"../log.h\"\n#include \"../profile/scriptvars.h\"\n#include \"../mem.h\"\n#include \"../background2d.h\"\n#include \"../character2d.h\"\n#include \"../profile/vm.h\"\n#include \"../effects/wave.h\"\n\nnamespace Impacto {\n\nnamespace Vm {\n\nusing namespace Impacto::Profile::ScriptVars;\nusing namespace Impacto::Profile::Vm;\nusing namespace Impacto::Effects;\n\nVmInstruction(InstCreateSurf) {\n  StartInstruction;\n  PopUint8(type);\n  PopExpression(surfaceId);\n  PopExpression(width);\n  PopExpression(height);\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction CreateSurf(type: {:d}, surfaceId: \"\n             \"{:d}, width: {:d}, height: {:d})\\n\",\n             type, surfaceId, width, height);\n}\nVmInstruction(InstReleaseSurf) {\n  StartInstruction;\n  PopExpression(surfaceId);\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction ReleaseSurf(surfaceId: {:d})\\n\", surfaceId);\n  if (surfaceId < 8) {\n    if (Backgrounds2D[surfaceId]->Status == LoadStatus::Loaded) {\n      Backgrounds2D[surfaceId]->Unload();\n    }\n  }\n}\nVmInstruction(InstLoadPic) {\n  StartInstruction;\n  PopExpression(surfaceId);\n  PopExpression(archiveId);\n  PopExpression(fileId);\n  ImpLogSlow(\n      LogLevel::Warning, LogChannel::VMStub,\n      \"STUB instruction LoadPic(surfaceId: {:d}, width: {:d}, height: {:d})\\n\",\n      surfaceId, archiveId, fileId);\n  if (surfaceId < 8) {\n    switch (archiveId) {\n      case 0: {  // bg archive\n        if (Backgrounds2D[surfaceId]->Status == LoadStatus::Loading) {\n          ResetInstruction;\n          BlockThread;\n        } else if (ScrWork[SW_BG1NO + ScrWorkBgStructSize * surfaceId] !=\n                   fileId) {\n          ScrWork[SW_BG1NO + ScrWorkBgStructSize * surfaceId] = fileId;\n          Backgrounds2D[surfaceId]->LoadAsync(fileId);\n          ResetInstruction;\n          BlockThread;\n        }\n      } break;\n    }\n  }\n}\nVmInstruction(InstSurfFill) {\n  StartInstruction;\n  PopExpression(surfaceId);\n  PopExpression(r);\n  PopExpression(g);\n  PopExpression(b);\n  PopExpression(a);\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction SurfFill(surfaceId: {:d}, r: \"\n             \"{:d}, g: {:d}, \"\n             \"b: {:d}, a: {:d})\\n\",\n             surfaceId, r, g, b, a);\n}\nVmInstruction(InstSCcapture) {\n  StartInstruction;\n  PopExpression(surfaceId);\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction SCcapture(surfaceId: {:d})\\n\", surfaceId);\n}\nVmInstruction(InstBGload) {\n  StartInstruction;\n  PopExpression(bufferId);\n  PopExpression(backgroundId);\n  int actualBufId = GetBufferId(bufferId);\n  int bgBufId = ScrWork[SW_BG1SURF + actualBufId];\n  if (Backgrounds2D[bgBufId]->Status == LoadStatus::Loading) {\n    ResetInstruction;\n    BlockThread;\n  } else if (ScrWork[SW_BG1NO + ScrWorkBgStructSize * actualBufId] !=\n             backgroundId) {\n    ScrWork[SW_BG1NO + ScrWorkBgStructSize * actualBufId] = backgroundId;\n    Backgrounds2D[bgBufId]->LoadAsync(backgroundId);\n    ResetInstruction;\n    BlockThread;\n  }\n}\nVmInstruction(InstBGswap) {\n  StartInstruction;\n  PopExpression(srcBufferId);\n  PopExpression(dstBufferId);\n\n  srcBufferId = GetBufferId(srcBufferId);\n  dstBufferId = GetBufferId(dstBufferId);\n\n  bool bg1fl = GetFlag(SF_BG1DISP + srcBufferId);\n  bool bg2fl = GetFlag(SF_BG1DISP + dstBufferId);\n  SetFlag(SF_BG1DISP + srcBufferId, bg2fl);\n  SetFlag(SF_BG1DISP + dstBufferId, bg1fl);\n\n  int counter = 0;\n  do {\n    int temp =\n        ScrWork[SW_BG1POSX + (srcBufferId * ScrWorkBgStructSize) + counter];\n    ScrWork[SW_BG1POSX + (srcBufferId * ScrWorkBgStructSize) + counter] =\n        ScrWork[SW_BG1POSX + (dstBufferId * ScrWorkBgStructSize) + counter];\n    ScrWork[SW_BG1POSX + (dstBufferId * ScrWorkBgStructSize) + counter] = temp;\n    counter++;\n  } while (counter != ScrWorkBgStructSize);\n\n  int tempb = ScrWork[SW_BG1SURF + srcBufferId];\n  ScrWork[SW_BG1SURF + srcBufferId] = ScrWork[SW_BG1SURF + dstBufferId];\n  ScrWork[SW_BG1SURF + dstBufferId] = tempb;\n}\nVmInstruction(InstBGsetColor) {\n  StartInstruction;\n  PopExpression(bufferId);\n  PopExpression(color);\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction BGsetColor(bufferId: {:d}, color: {:d})\\n\",\n             bufferId, color);\n}\nVmInstruction(InstBGsetLink) {\n  StartInstruction;\n  PopUint8(id);\n  PopExpression(arg1);\n  PopExpression(arg2);\n  int arg3 = 0;\n  if (id >= 4) {\n    arg3 = ExpressionEval(thread);\n    id -= 4;\n  }\n  PopExpression(arg4);\n  int link = (arg3 << 24) + (arg4 << 16) + (arg1 << 8) + arg2;\n  ScrWork[SW_BGLINK + id] = link;\n}\nVmInstruction(InstBGsetLinkOld) {\n  StartInstruction;\n  PopUint8(target);\n  PopExpression(arg1);\n  PopExpression(arg2);\n  PopExpression(arg3);\n  int link = (arg3 << 16) + (arg1 << 8) + arg2;\n  if (target == 0)\n    ScrWork[SW_BGLINK] = link;\n  else\n    ScrWork[SW_BGLINK2] = link;\n}\nVmInstruction(InstCHAload) {\n  StartInstruction;\n  PopUint8(arg1);\n  PopExpression(bufferId);\n  PopExpression(characterId);\n\n  const int actualBufId = GetBufferId(bufferId);\n  const size_t chaStructOffset = ScrWorkChaStructSize * actualBufId;\n\n  const int chaBufId = ScrWork[SW_CHA1SURF + actualBufId];\n\n  switch (Characters2D[chaBufId].Status) {\n    case LoadStatus::Unloaded:\n    case LoadStatus::Loaded: {\n      if (characterId >> 16 != 0) {\n        ScrWork[SW_CHA1FACE + chaStructOffset] = characterId >> 16;\n      }\n\n      if (ScrWork[SW_CHA1NO + chaStructOffset] != (characterId & 0xFFFF)) {\n        ScrWork[SW_CHA1NO + chaStructOffset] = characterId & 0xFFFF;\n\n        Characters2D[chaBufId].LoadAsync(characterId);\n\n        ResetInstruction;\n        BlockThread;\n      }\n\n      return;\n    }\n\n    case LoadStatus::Loading: {\n      ResetInstruction;\n      BlockThread;\n      return;\n    }\n  }\n}\nVmInstruction(InstCHAswap) {\n  StartInstruction;\n  PopExpression(srcBufferId);\n  PopExpression(dstBufferId);\n\n  srcBufferId = GetBufferId(srcBufferId);\n  dstBufferId = GetBufferId(dstBufferId);\n\n  bool cha1fl = GetFlag(SF_CHA1DISP + srcBufferId);\n  bool cha2fl = GetFlag(SF_CHA1DISP + dstBufferId);\n  SetFlag(SF_CHA1DISP + srcBufferId, cha2fl);\n  SetFlag(SF_CHA1DISP + dstBufferId, cha1fl);\n\n  int counter = 0;\n  do {\n    int temp =\n        ScrWork[SW_CHA1POSX + (srcBufferId * ScrWorkChaStructSize) + counter];\n    ScrWork[SW_CHA1POSX + (srcBufferId * ScrWorkChaStructSize) + counter] =\n        ScrWork[SW_CHA1POSX + (dstBufferId * ScrWorkChaStructSize) + counter];\n    ScrWork[SW_CHA1POSX + (dstBufferId * ScrWorkChaStructSize) + counter] =\n        temp;\n    counter++;\n  } while (counter != ScrWorkChaStructSize);\n\n  int tempb = ScrWork[SW_CHA1SURF + srcBufferId];\n  ScrWork[SW_CHA1SURF + srcBufferId] = ScrWork[SW_CHA1SURF + dstBufferId];\n  ScrWork[SW_CHA1SURF + dstBufferId] = tempb;\n}\nVmInstruction(InstBGrelease) {\n  StartInstruction;\n  PopExpression(bufferId);\n  bufferId = GetBufferId(bufferId);\n  int surfId = ScrWork[SW_BG1SURF + bufferId];\n  ScrWork[SW_BG1NO + ScrWorkBgStructSize * bufferId] = 0xFFFF;\n  if (Backgrounds2D[surfId]->Status == LoadStatus::Loaded) {\n    Backgrounds2D[surfId]->Unload();\n  }\n}\nVmInstruction(InstBGcopy) {\n  StartInstruction;\n  PopExpression(srcBufferId);\n  PopExpression(dstBufferId);\n\n  srcBufferId = GetBufferId(srcBufferId);\n  dstBufferId = GetBufferId(dstBufferId);\n\n  int bgId = ScrWork[SW_BG1NO + ScrWorkBgStructSize * srcBufferId];\n  int dstSurfId = ScrWork[SW_BG1SURF + dstBufferId];\n\n  if (Backgrounds2D[dstSurfId]->Status == LoadStatus::Loading) {\n    ResetInstruction;\n    BlockThread;\n  } else if (ScrWork[SW_BG1NO + ScrWorkBgStructSize * dstBufferId] != bgId) {\n    ScrWork[SW_BG1NO + ScrWorkBgStructSize * dstBufferId] = bgId;\n    Backgrounds2D[dstSurfId]->LoadAsync(bgId);\n    ResetInstruction;\n    BlockThread;\n  }\n}\nVmInstruction(InstCHAcopy) {\n  StartInstruction;\n  PopExpression(srcBufferId);\n  PopExpression(dstBufferId);\n\n  srcBufferId = GetBufferId(srcBufferId);\n  dstBufferId = GetBufferId(dstBufferId);\n\n  int chaId = (ScrWork[SW_CHA1FACE + ScrWorkBgStructSize * srcBufferId] << 16) +\n              ScrWork[SW_CHA1NO + ScrWorkBgStructSize * srcBufferId];\n  int dstSurfId = ScrWork[SW_CHA1SURF + dstBufferId];\n\n  if (Characters2D[dstSurfId].Status == LoadStatus::Loading) {\n    ResetInstruction;\n    BlockThread;\n  } else if (ScrWork[SW_CHA1NO + ScrWorkChaStructSize * dstBufferId] !=\n             (chaId & 0xFFFF)) {\n    ScrWork[SW_CHA1NO + ScrWorkChaStructSize * dstBufferId] = chaId & 0xFFFF;\n    Characters2D[dstSurfId].LoadAsync(chaId);\n    ResetInstruction;\n    BlockThread;\n  }\n}\nVmInstruction(InstCharaLayerLoad) { StartInstruction; }\nVmInstruction(InstCHAmove) {\n  StartInstruction;\n  PopUint8(type);\n  switch (type) {\n    case 0:\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction CHAmoveInit()\\n\");\n      break;\n    case 1: {\n      PopExpression(arg1);\n      PopLocalLabel(seqDataBlock);\n      (void)seqDataBlock;\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction CHAmoveSetSeq(arg1: {:d})\\n\", arg1);\n    } break;\n    case 2: {\n      PopExpression(arg1);\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction CHAmoveExec_Blocking(arg1: {:d})\\n\", arg1);\n    } break;\n    case 3:\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction CHAmoveExec_NonBlocking()\\n\");\n      break;\n    case 4: {\n      PopExpression(arg1);\n      PopExpression(destination);\n      ScrWork[destination] = 0;\n      ImpLogSlow(\n          LogLevel::Warning, LogChannel::VMStub,\n          \"STUB instruction CHAmoveChkEnd(arg1: {:d}, destination: {:d})\\n\",\n          arg1, destination);\n    } break;\n    case 5: {\n      PopExpression(arg1);\n      PopExpression(arg2);\n      PopExpression(arg3);\n      PopExpression(arg4);\n      PopExpression(arg5);\n      PopExpression(arg6);\n      PopExpression(arg7);\n      ImpLogSlow(\n          LogLevel::Warning, LogChannel::VMStub,\n          \"STUB instruction CHAmoveSetSeqDirect(arg1: {:d}, arg2: {:d}, \"\n          \"arg3: {:d}, arg4: {:d}, arg5: {:d}, arg6: {:d}, arg7: {:d})\\n\",\n          arg1, arg2, arg3, arg4, arg5, arg6, arg7);\n    } break;\n  }\n}\nVmInstruction(InstBGloadEx) {\n  StartInstruction;\n  PopExpression(bufferId);\n  PopExpression(backgroundId);\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction BGloadEx(bufferId: {:d}, backgroundId: {:d})\\n\",\n             bufferId, backgroundId);\n}\nVmInstruction(InstCHArelease) {\n  StartInstruction;\n  PopExpression(bufferId);\n  bufferId = GetBufferId(bufferId);\n  int surfId = ScrWork[SW_CHA1SURF + bufferId];\n  ScrWork[SW_CHA1NO + ScrWorkChaStructSize * bufferId] = 0xFFFF;\n  if (Characters2D[surfId].Status == LoadStatus::Loaded) {\n    Characters2D[surfId].Unload();\n  }\n}\nVmInstruction(InstGetCharaPause) {\n  StartInstruction;\n  PopExpression(bufferId);\n  PopExpression(dest);\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction GetCharaPause(bufferId: {:d}, dest: {:d})\\n\",\n             bufferId, dest);\n}\nVmInstruction(InstBGfadeExpInit) {\n  StartInstruction;\n\n  PopExpression(bgId);\n  bgId = static_cast<int>(std::floor(std::log2(bgId)));\n\n  Background2D& background = Backgrounds[bgId];\n  background.UpdateState(bgId);\n  ResetExplodeTris(background.RenderSprite);\n}\nVmInstruction(InstBGeffectWave) {\n  StartInstruction;\n  PopUint8(type);\n  switch (type) {\n    case 0:\n      WaveBG.ClearWaves();\n      break;\n    case 1: {\n      PopExpression(flags);\n      PopExpression(amplitude);\n      PopExpression(initialPhase);\n      PopExpression(temporalFreq);\n      PopExpression(spatialFreq);\n      WaveBG.AddWave(WaveParams{.Flags = flags,\n                                .Amplitude = amplitude,\n                                .TemporalFrequency = temporalFreq,\n                                .Phase = initialPhase,\n                                .SpatialFrequency = spatialFreq});\n    } break;\n    case 2: {\n      WaveCHA.ClearWaves();\n      break;\n    }\n    case 3: {\n      PopExpression(flags);\n      PopExpression(amplitude);\n      PopExpression(initialPhase);\n      PopExpression(temporalFreq);\n      PopExpression(spatialFreq);\n      WaveCHA.AddWave(WaveParams{.Flags = flags,\n                                 .Amplitude = amplitude,\n                                 .TemporalFrequency = temporalFreq,\n                                 .Phase = initialPhase,\n                                 .SpatialFrequency = spatialFreq});\n    } break;\n    case 4: {\n      PopExpression(index);\n      PopExpression(flags);\n      PopExpression(amplitude);\n      PopExpression(initialPhase);\n      PopExpression(temporalFreq);\n      PopExpression(spatialFreq);\n      WaveBG.SetWave(index, WaveParams{.Flags = flags,\n                                       .Amplitude = amplitude,\n                                       .TemporalFrequency = temporalFreq,\n                                       .Phase = initialPhase,\n                                       .SpatialFrequency = spatialFreq});\n    } break;\n    case 5: {\n      PopExpression(index);\n      PopExpression(flags);\n      PopExpression(amplitude);\n      PopExpression(initialPhase);\n      PopExpression(temporalFreq);\n      PopExpression(spatialFreq);\n      WaveCHA.SetWave(index, WaveParams{.Flags = flags,\n                                        .Amplitude = amplitude,\n                                        .TemporalFrequency = temporalFreq,\n                                        .Phase = initialPhase,\n                                        .SpatialFrequency = spatialFreq});\n\n    } break;\n    case 10:\n      WaveEFF.ClearWaves();\n      break;\n    case 11: {\n      PopExpression(flags);\n      PopExpression(amplitude);\n      PopExpression(initialPhase);\n      PopExpression(temporalFreq);\n      PopExpression(spatialFreq);\n      WaveEFF.AddWave(WaveParams{.Flags = flags,\n                                 .Amplitude = amplitude,\n                                 .TemporalFrequency = temporalFreq,\n                                 .Phase = initialPhase,\n                                 .SpatialFrequency = spatialFreq});\n    } break;\n    case 12: {\n      PopExpression(index);\n      PopExpression(flags);\n      PopExpression(amplitude);\n      PopExpression(initialPhase);\n      PopExpression(temporalFreq);\n      PopExpression(spatialFreq);\n      WaveEFF.SetWave(index, WaveParams{.Flags = flags,\n                                        .Amplitude = amplitude,\n                                        .TemporalFrequency = temporalFreq,\n                                        .Phase = initialPhase,\n                                        .SpatialFrequency = spatialFreq});\n    } break;\n    default: {\n      // also has EFFripples & EFFwater in later versions of the engine\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction BGeffectWave(type: {:d})\\n\", type);\n    }\n  }\n}\nVmInstruction(InstBGeffect) {\n  StartInstruction;\n  PopUint8(type);\n  switch (type) {\n    case 0:\n    case 5: {\n      PopExpression(arg1);\n      PopExpression(arg2);\n      PopExpression(arg3);\n      PopExpression(arg4);\n    } break;\n    case 1:\n    case 3:\n    case 9: {\n      PopExpression(arg1);\n      PopExpression(arg2);\n    } break;\n    case 6:\n    case 7: {\n      PopExpression(arg1);\n    } break;\n  }\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction BGeffect(arg1: {:d})\\n\", type);\n}\n\nVmInstruction(InstBGeffectMO7) {\n  StartInstruction;\n  PopUint8(type);\n  switch (type) {\n    case 0: {\n      PopExpression(arg1);\n    } break;\n  }\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction BGeffectMO7(arg1: {:d})\\n\", type);\n}\n\nVmInstruction(InstFACEload) {\n  StartInstruction;\n  PopUint8(type);\n  switch (type) {\n    case 0: {\n      PopExpression(bufferId);\n      PopExpression(faceId);\n      int actualBufId =\n          GetBufferId(bufferId) - Profile::Vm::SpeakerPortraitsScrWorkOffset;\n      int chaBufId = ScrWork[SW_FACE1SURF + actualBufId];\n      if (((faceId & 0xFFFF0000) >> 16) == 0) {\n        faceId = (ScrWork[SW_FACEEX1FACE + 5 * actualBufId] << 16) | faceId;\n      }\n      if (SpeakerPortraits[chaBufId].Status == LoadStatus::Loading) {\n        ResetInstruction;\n        BlockThread;\n      } else if (ScrWork[SW_FACEEX1NO + 5 * actualBufId] != (faceId & 0xFFFF)) {\n        ScrWork[SW_FACEEX1NO + 5 * actualBufId] = faceId & 0xFFFF;\n        ScrWork[SW_FACEEX1FACE + 5 * actualBufId] = faceId >> 16;\n        SpeakerPortraits[chaBufId].MountPoint = \"face\";\n        SpeakerPortraits[chaBufId].LoadAsync(faceId);\n        ResetInstruction;\n        BlockThread;\n      }\n    } break;\n  }\n}\n\nVmInstruction(InstFACErelease) {\n  StartInstruction;\n  PopExpression(bufferId);\n  bufferId = GetBufferId(bufferId) - Profile::Vm::SpeakerPortraitsScrWorkOffset;\n  int surfId = ScrWork[SW_FACE1SURF + bufferId];\n  ScrWork[SW_FACEEX1NO + 5 * bufferId] = 0xFFFF;\n  if (SpeakerPortraits[surfId].Status == LoadStatus::Loaded) {\n    SpeakerPortraits[surfId].Unload();\n  }\n}\n\n}  // namespace Vm\n\n}  // namespace Impacto"
  },
  {
    "path": "src/vm/inst_graphics2d.h",
    "content": "#pragma once\n\n#include \"vm.h\"\n\nnamespace Impacto {\n\nnamespace Vm {\n\nVmInstruction(InstCreateSurf);\nVmInstruction(InstReleaseSurf);\nVmInstruction(InstLoadPic);\nVmInstruction(InstSurfFill);\nVmInstruction(InstSCcapture);\nVmInstruction(InstBGload);\nVmInstruction(InstBGswap);\nVmInstruction(InstBGsetColor);\nVmInstruction(InstBGsetLink);\nVmInstruction(InstBGsetLinkOld);\nVmInstruction(InstCHAload);\nVmInstruction(InstCHAswap);\nVmInstruction(InstBGrelease);\nVmInstruction(InstCHArelease);\nVmInstruction(InstBGcopy);\nVmInstruction(InstCHAcopy);\nVmInstruction(InstCharaLayerLoad);\nVmInstruction(InstCHAmove);\nVmInstruction(InstBGloadEx);\nVmInstruction(InstGetCharaPause);\nVmInstruction(InstBGfadeExpInit);\nVmInstruction(InstBGeffectWave);\nVmInstruction(InstBGeffect);\nVmInstruction(InstBGeffectMO7);\nVmInstruction(InstFACEload);\nVmInstruction(InstFACErelease);\n\n}  // namespace Vm\n\n}  // namespace Impacto"
  },
  {
    "path": "src/vm/inst_graphics3d.cpp",
    "content": "#include \"inst_graphics3d.h\"\n\n#include \"inst_macros.inc\"\n\n#include \"expression.h\"\n#include \"../game.h\"\n#include \"../mem.h\"\n#include \"../log.h\"\n#include \"../profile/scriptvars.h\"\n\n#include \"../renderer/3d/scene.h\"\n\n#include \"interface/scene3d.h\"\n\n#include \"../profile/scene3d.h\"\n#include \"../profile/vm.h\"\n\nnamespace Impacto {\n\nnamespace Vm {\n\nusing namespace Impacto::Profile::ScriptVars;\n\nusing namespace Interface;\n\nfloat someGlobalFloat1 = 0.0f;\nfloat someGlobalFloat2 = 0.0f;\nfloat someGlobalFloat3 = 0.0f;\nfloat someGlobalFloat4 = 0.0f;\nfloat someGlobalFloat5 = 0.0f;\nfloat someGlobalFloat6 = 0.0f;\n\nVmInstruction(InstCHAload3D) {\n  StartInstruction;\n  PopExpression(bufferId);\n  switch (Profile::Vm::GameInstructionSet) {\n    default:\n      break;\n    case InstructionSet::RNE:\n      PopExpression(unk01);\n      break;\n  }\n  PopExpression(modelId);\n  if (Renderer->Scene->Renderables[bufferId]->Status == LoadStatus::Loading) {\n    ResetInstruction;\n    BlockThread;\n  } else if (ScrWork[SW_MDL1FILENO + 30 * bufferId] != modelId) {\n    ScrWork[SW_MDL1FILENO + 30 * bufferId] = modelId;\n\n    auto charId = Profile::Scene3D::ModelsToCharacters.find(modelId);\n    if (charId != Profile::Scene3D::ModelsToCharacters.end()) {\n      ScrWork[SW_MDL1CHANO + 30 * bufferId] = charId->second;\n    } else {\n      // background\n      ScrWork[SW_MDL1CHANO + 30 * bufferId] = 0;\n    }\n\n    Renderer->Scene->Renderables[bufferId]->LoadAsync(modelId);\n    ResetInstruction;\n    BlockThread;\n  }\n}\nVmInstruction(InstCHArelease3D) {\n  StartInstruction;\n  PopExpression(bufferId);\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction CHArelease3D(bufferId: {:d})\\n\", bufferId);\n}\nVmInstruction(InstUnk0204) {  // Not implemented\n  StartInstruction;\n  PopExpression(arg1);\n  PopExpression(arg2);\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction Unk0204(arg1: {:d}, arg2: {:d})\\n\", arg1, arg2);\n}\nVmInstruction(InstCHAswap3DMaybe) {\n  StartInstruction;\n  PopExpression(dstBufferId);\n  PopExpression(srcBufferId);\n  ImpLogSlow(\n      LogLevel::Warning, LogChannel::VMStub,\n      \"STUB instruction CHAswap3DMaybe(dstBufferId: {:d}, srcBufferId: {:d})\\n\",\n      dstBufferId, srcBufferId);\n}\nVmInstruction(InstCHAplayAnim3DMaybe) {\n  StartInstruction;\n  PopExpression(bufferId);\n  PopExpression(animationId);\n  switch (Profile::Vm::GameInstructionSet) {\n    default:\n      break;\n    case InstructionSet::Dash: {\n      PopUint16(unk01);\n    } break;\n    case InstructionSet::RNE: {\n      PopUint8(unk01);\n    } break;\n  }\n  if (Renderer->Scene->Renderables[bufferId]->Status == LoadStatus::Loaded &&\n      animationId != 0) {\n    // TODO shouldn't this wait for that renderable to be loaded?\n    if (Profile::Vm::GameInstructionSet == InstructionSet::Dash &&\n        Renderer->Scene->Renderables[bufferId]->Animator.IsPlaying &&\n        Renderer->Scene->Renderables[bufferId]\n            ->Animator.CurrentAnimation->OneShot) {\n      ResetInstruction;\n      BlockThread;\n    } else {\n      Renderer->Scene->Renderables[bufferId]->SwitchAnimation(\n          (int16_t)animationId, 0.66f);\n      BlockThread;\n    }\n  }\n}\nVmInstruction(InstCHAUnk02073D) {\n  StartInstruction;\n  PopExpression(bufferId);\n  PopUint8(unk01);\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction CHAUnk02073D(bufferId: {:d}, unk01: {:d})\\n\",\n             bufferId, unk01);\n}\nVmInstruction(InstCHAUnk02073D_Dash) {\n  StartInstruction;\n  PopUint8(arg1);\n  PopExpression(arg2);\n  if (arg1 == 2) {\n    PopExpression(arg3);\n    PopExpression(arg4);\n    PopLocalLabel(arg5);\n    (void)arg5;\n    ImpLogSlow(\n        LogLevel::Warning, LogChannel::VMStub,\n        \"STUB instruction CHAUnk02073D_Dash(arg1: {:d}, arg2: {:d}, arg3: \"\n        \"{:d}, arg4: {:d}, arg5: {:d})\\n\",\n        arg1, arg2, arg3, arg4, arg5);\n  } else {\n    PopLocalLabel(arg3);\n    auto dataIp = arg3;\n    uint16_t testNum = ScriptBuffers[thread->ScriptBufferId][dataIp + 1];\n    if (Renderer->Scene->Renderables[arg2]->Status == LoadStatus::Loaded &&\n        testNum != 0) {\n      // TODO shouldn't this wait for that renderable to be loaded?\n      if (Renderer->Scene->Renderables[arg2]->Animator.IsPlaying &&\n          Renderer->Scene->Renderables[arg2]\n              ->Animator.CurrentAnimation->OneShot) {\n        ResetInstruction;\n        BlockThread;\n      } else {\n        SetFlag(SF_MDL1DISP + arg2, true);\n        Renderer->Scene->Renderables[arg2]->SwitchAnimation(testNum, 0);\n      }\n    }\n    ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n               \"STUB instruction CHAUnk02073D_Dash(arg1: {:d}, arg2: {:d})\\n\",\n               arg1, arg2);\n  }\n}\n\ninline bool ObjectIsRenderable(int objectId) {\n  return objectId >= 10 && objectId <= 17;\n}\n// Note: runtime model, not model file\ninline int ObjectIdToRenderableId(int objectId) { return objectId - 10; }\n\n// Objects are positioned in a nonstandard spherical coordinate system in\n// relation to a parent\nVmInstruction(InstPositionObject) {\n  StartInstruction;\n  PopExpression(\n      parentObjId);  // 1 -> mainCamera, 2 -> iruoCamera, 10 -> background,\n  PopExpression(objectId);  // 11-17 -> characters, 30-38 -> ???\n  PopExpression(theta_);\n  PopExpression(phi_);\n  PopExpression(radius_);\n\n  float theta = DegToRad(ScrRealToFloat(theta_));\n  float phi = DegToRad(ScrRealToFloat(phi_));\n  float radius = ScrRealToFloat(radius_);\n\n  // if(ObjectIsRenderable(parentObjId))\n  int parentRenderableId = ObjectIdToRenderableId(parentObjId);\n  // if(ObjectIsRenderable(objectId))\n  int renderableId = ObjectIdToRenderableId(objectId);\n\n  glm::vec3 pos = glm::vec3(0.0f);\n\n  if (parentObjId && objectId) {\n    if (parentObjId == 1 || parentObjId == 2) {  // camera\n      pos.z += Profile::Scene3D::DefaultCameraPosition.z;\n    } else if (ObjectIsRenderable(parentObjId)) {\n      // note, these are different than SW_CHAnPOSa ???\n      pos = ScrWorkGetVec3(20 * parentRenderableId + 5706,\n                           20 * parentRenderableId + 5707,\n                           20 * parentRenderableId + 5708);\n      pos.y += ScrWorkGetFloat(30 * parentRenderableId + SW_MDL1CENY);\n    } else if (parentObjId >= 30 && parentObjId <= 38) {\n      pos = ScrWorkGetVec3(20 * parentObjId + 4900, 20 * parentObjId + 4901,\n                           20 * parentObjId + 4902);\n    }\n    pos.y += Profile::Scene3D::DefaultCameraPosition.y;\n\n    if (parentObjId != 1 && parentObjId != 2) {\n      // I don't even know\n      theta += std::numbers::pi_v<float>;\n    }\n\n    theta = NormalizeRad(theta);\n    phi = NormalizeRad(phi);\n\n    glm::vec3 sphericalOffset =\n        glm::vec3(sinf(theta) * cosf(phi), sinf(phi), cosf(theta) * cosf(phi));\n    pos -= radius * sphericalOffset;\n\n    // undo offsets\n    pos.y -= Profile::Scene3D::DefaultCameraPosition.y;\n    if (objectId == 1 || objectId == 2) {\n      pos.z -= Profile::Scene3D::DefaultCameraPosition.z;\n    }\n    if (ObjectIsRenderable(objectId)) {\n      pos.y -= ScrWorkGetFloat(30 * renderableId + SW_MDL1CENY);\n    }\n\n    if (objectId == 1) {  // main camera\n      ScrWorkSetVec3(SW_MAINCAMERAPOSX, SW_MAINCAMERAPOSY, SW_MAINCAMERAPOSZ,\n                     pos);\n    } else if (objectId == 2) {  // iruo camera\n      ScrWorkSetVec3(SW_IRUOCAMERAPOSX, SW_IRUOCAMERAPOSY, SW_IRUOCAMERAPOSZ,\n                     pos);\n    } else if (ObjectIsRenderable(objectId)) {\n      ScrWorkSetVec3(SW_MDL1POSX + 30 * renderableId,\n                     SW_MDL1POSY + 30 * renderableId,\n                     SW_MDL1POSZ + 30 * renderableId, pos);\n    } else if (objectId >= 30 && objectId <= 38) {\n      ScrWorkSetVec3(4900 + 20 * objectId, 4901 + 20 * objectId,\n                     4902 + 20 * objectId, pos);\n    }\n  }\n}\nVmInstruction(InstPositionObject_Dash) {\n  StartInstruction;\n  PopExpression(\n      parentObjId);  // 1 -> mainCamera, 2 -> iruoCamera, 10 -> background,\n  PopExpression(objectId);  // 11-17 -> characters, 30-38 -> ???\n  PopExpression(theta_);\n  PopExpression(phi_);\n  PopExpression(radius_);\n\n  float theta = DegToRad(ScrRealToFloat(theta_));\n  float phi = DegToRad(ScrRealToFloat(phi_));\n  float radius = ScrRealToFloat(radius_);\n\n  // if(ObjectIsRenderable(parentObjId))\n  // int parentRenderableId = ObjectIdToRenderableId(parentObjId);\n  // if(ObjectIsRenderable(objectId))\n  int renderableId = ObjectIdToRenderableId(objectId);\n\n  glm::vec3 pos = glm::vec3(0.0f);\n\n  if (parentObjId && objectId) {\n    if (parentObjId == 1 || parentObjId == 2) {  // camera\n      pos = ScrWorkGetVec3(10 * parentObjId + 5390, 10 * parentObjId + 5391,\n                           10 * parentObjId + 5392);\n    } else if (ObjectIsRenderable(parentObjId)) {\n      // note, these are different than SW_CHAnPOSa ???\n      pos = ScrWorkGetVec3(20 * parentObjId + 4900, 20 * parentObjId + 4901,\n                           20 * parentObjId + 4902);\n    } else if (parentObjId >= 30 && parentObjId <= 38) {\n      pos = ScrWorkGetVec3(30 * parentObjId + 4800, 30 * parentObjId + 4801,\n                           30 * parentObjId + 4802);\n    }\n\n    theta = NormalizeRad(theta);\n    phi = NormalizeRad(phi);\n\n    pos.x += radius * sinf(theta);\n    pos.y -= radius * sinf(phi);\n    pos.z -= radius * ((cosf(theta) + cosf(phi)) * 0.5f);\n\n    if (objectId == 1) {  // main camera\n      ScrWorkSetVec3(SW_MAINCAMERAPOSX, SW_MAINCAMERAPOSY, SW_MAINCAMERAPOSZ,\n                     pos);\n    } else if (objectId == 2) {  // iruo camera\n      ScrWorkSetVec3(SW_IRUOCAMERAPOSX, SW_IRUOCAMERAPOSY, SW_IRUOCAMERAPOSZ,\n                     pos);\n    } else if (ObjectIsRenderable(objectId)) {\n      ScrWorkSetVec3(SW_MDL1POSX + 30 * renderableId,\n                     SW_MDL1POSY + 30 * renderableId,\n                     SW_MDL1POSZ + 30 * renderableId, pos);\n    } else if (objectId >= 30 && objectId <= 38) {\n      ScrWorkSetVec3(4900 + 20 * objectId, 4901 + 20 * objectId,\n                     4902 + 20 * objectId, pos);\n    }\n  }\n}\nVmInstruction(InstCHAsetAnim3D) {\n  StartInstruction;\n  PopExpression(bufferId);\n  PopExpression(animationId);\n  ImpLogSlow(\n      LogLevel::Warning, LogChannel::VMStub,\n      \"STUB instruction CHAsetAnim3D(bufferId: {:d}, animationId: {:d})\\n\",\n      bufferId, animationId);\n}\nVmInstruction(InstUnk0210) {\n  StartInstruction;\n  PopUint8(arg1);\n  PopExpression(arg2);\n  int arg3, arg4;\n  if (arg1 & 0x10) {\n    arg3 = ExpressionEval(thread);\n    arg4 = ExpressionEval(thread);\n  } else {\n    arg3 = 0;\n    arg4 = 0;\n  }\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction Unk0210(arg1: {:d}, arg2: {:d}, arg3: \"\n             \"{:d}, arg4: {:d})\\n\",\n             arg1, arg2, arg3, arg4);\n  if (arg1 & 4) {\n    int arg5 = 0;\n    if (arg2 <= 29) {\n      for (int i = 1; i <= arg2; i++) arg5 += i;\n    } else {\n      arg5 = 30 * arg2 - 870;\n      for (int i = 1; i <= 29; i++) arg5 += i;\n    }\n    if (arg1 & 1) {\n      someGlobalFloat1 = (float)ScrWork[SW_MAINCAMERAROTX];\n      someGlobalFloat2 = (float)arg3;\n      someGlobalFloat3 = (arg3 - (float)ScrWork[SW_MAINCAMERAROTX]) / arg5;\n    }\n    if (arg1 & 2) {\n      someGlobalFloat4 = (float)ScrWork[SW_MAINCAMERAROTY];\n      someGlobalFloat5 = (float)arg4;\n      someGlobalFloat6 = (arg4 - (float)ScrWork[SW_MAINCAMERAROTY]) / arg5;\n    }\n  } else {\n    if (arg1 & 1) {\n      float arg5;\n      if (arg2 <= 29) {\n        if (arg2 == 1) {\n          arg5 = someGlobalFloat2;\n        } else {\n          arg5 = someGlobalFloat1 + (someGlobalFloat3 * arg2);\n        }\n      } else {\n        arg5 = someGlobalFloat1 + (someGlobalFloat3 * 30.0f);\n      }\n      someGlobalFloat2 = arg5;\n      ScrWork[SW_MAINCAMERAROTX] = (int)arg5;\n    }\n    if (arg1 & 2) {\n      float arg5;\n      if (arg2 <= 29) {\n        if (arg2 == 1) {\n          arg5 = someGlobalFloat5;\n        } else {\n          arg5 = someGlobalFloat4 + (someGlobalFloat6 * arg2);\n        }\n      } else {\n        arg5 = someGlobalFloat4 + (someGlobalFloat6 * 30.0f);\n      }\n      someGlobalFloat5 = arg5;\n      ScrWork[SW_MAINCAMERAROTY] = (int)arg5;\n    }\n  }\n}\nVmInstruction(InstUnk0211) {\n  StartInstruction;\n  PopExpression(arg1);\n  PopExpression(arg2);\n  PopExpression(arg3);\n  PopExpression(arg4);\n  PopExpression(arg5);\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction Unk0211(arg1: {:d}, arg2: {:d}, arg3: \"\n             \"{:d}, arg4: {:d}, arg5: {:d})\\n\",\n             arg1, arg2, arg3, arg4, arg5);\n}\nVmInstruction(InstUnk0212) {\n  StartInstruction;\n  PopExpression(arg1);\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction Unk0212(arg1: {:d})\\n\", arg1);\n}\nVmInstruction(InstUnk0213) {  // Set Camera position ???\n  StartInstruction;\n  PopUint8(type);\n  switch (type) {\n    case 0:  // main camera default position\n    case 3:  // iruo camera default position\n    {\n      PopExpression(outX);\n      PopExpression(outY);\n      PopExpression(outZ);\n      ScrWorkSetVec3(outX, outY, outZ, Profile::Scene3D::DefaultCameraPosition);\n    } break;\n    case 1: {\n      PopExpression(charId);\n      PopExpression(outX);\n      PopExpression(outY);\n      PopExpression(outZ);\n      glm::vec3 pos = ScrWorkGetVec3(5706 + 20 * charId, 5707 + 20 * charId,\n                                     5708 + 20 * charId);\n      pos.y += ScrWorkGetFloat(SW_MDL1CENY + 30 * charId);\n      pos.y += Profile::Scene3D::DefaultCameraPosition.y;\n      ScrWorkSetVec3(outX, outY, outZ, pos);\n    } break;\n    case 2: {\n      PopExpression(arg1);\n      PopExpression(outX);\n      PopExpression(outY);\n      PopExpression(outZ);\n      glm::vec3 pos =\n          ScrWorkGetVec3(5500 + 20 * arg1, 5501 + 20 * arg1, 5502 + 20 * arg1);\n      pos.y += Profile::Scene3D::DefaultCameraPosition.y;\n      ScrWorkSetVec3(outX, outY, outZ, pos);\n    } break;\n  }\n}\nVmInstruction(InstUnk0214) {\n  StartInstruction;\n  PopUint8(arg1);\n  PopExpression(arg2);\n  PopExpression(arg3);\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction Unk0214(arg1: {:d}, arg2: {:d}, arg3: {:d})\\n\",\n             arg1, arg2, arg3);\n}\nVmInstruction(InstUnk0215) {\n  StartInstruction;\n  PopExpression(arg1);\n  PopExpression(arg2);\n  PopExpression(arg3);\n  PopExpression(arg4);\n  PopExpression(arg5);\n  PopExpression(arg6);\n  PopExpression(arg7);\n  PopExpression(arg8);\n  PopExpression(arg9);\n  PopExpression(arg10);\n  PopExpression(arg11);\n  PopExpression(arg12);\n  PopExpression(arg13);\n  PopExpression(arg14);\n  PopExpression(arg15);\n  PopExpression(arg16);\n  PopExpression(arg17);\n  ImpLogSlow(\n      LogLevel::Warning, LogChannel::VMStub,\n      \"STUB instruction Unk0215(arg1: {:d}, arg2: {:d}, arg3: \"\n      \"{:d}, arg4: {:d}, arg5: {:d}, arg6: {:d}, arg7: {:d}, arg8: {:d}, \"\n      \"arg9: {:d}, arg10: {:d}, arg11: {:d}, arg12: {:d}, arg13: {:d}, arg14: \"\n      \"{:d}, arg15: {:d}, arg16: {:d}, arg17: {:d})\\n\",\n      arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12,\n      arg13, arg14, arg15, arg16, arg17);\n}\n\n// Temporary test stuff, needs to be done properly\nint movementVarDestY[2];\nfloat movementStartY[2];\nfloat movementEndY[2];\nfloat movementYDelta[2];\n\nint movementVarDestX[2];\nfloat movementStartX[2];\nfloat movementEndX[2];\nfloat movementXDelta[2];\n\nVmInstruction(InstMoveCamera) {\n  StartInstruction;\n  PopUint8(type);\n  switch (type) {\n    case 0: {\n      PopExpression(cameraId);\n      PopExpression(movStartY);\n      PopExpression(movStartX);\n      PopExpression(movEndY);\n      PopExpression(movEndX);\n      PopExpression(length);\n      PopExpression(movDestVarY);\n      PopExpression(movDestVarX);\n      int totalIterations = 0;\n      if (length <= 29) {\n        for (int i = 1; i <= length; i++) {\n          totalIterations += i;\n        }\n      } else {\n        totalIterations = 30 * length - 870;\n        int i = 1;\n        do {\n          totalIterations += i++;\n        } while (i <= 29);\n      }\n\n      movementVarDestY[cameraId] = movDestVarY;\n      movementStartY[cameraId] = ScrRealToFloat(movStartY);\n      movementEndY[cameraId] = ScrRealToFloat(movEndY);\n      movementYDelta[cameraId] =\n          (movementEndY[cameraId] - movementStartY[cameraId]) / totalIterations;\n\n      movementVarDestX[cameraId] = movDestVarX;\n      movementStartX[cameraId] = ScrRealToFloat(movStartX);\n      movementEndX[cameraId] = ScrRealToFloat(movEndX);\n      movementXDelta[cameraId] =\n          (movementEndX[cameraId] - movementStartX[cameraId]) / totalIterations;\n\n    } break;\n    case 1: {\n      PopExpression(cameraId);\n      PopExpression(iterations);\n      float moveYCur = 0.0f, moveXCur = 0.0f;\n      if (iterations <= 29) {\n        moveYCur =\n            movementStartY[cameraId] + (movementYDelta[cameraId] * iterations);\n        moveXCur =\n            movementStartX[cameraId] + (movementXDelta[cameraId] * iterations);\n      } else {\n        moveYCur =\n            movementStartY[cameraId] + (movementYDelta[cameraId] * 30.0f);\n        moveXCur =\n            movementStartX[cameraId] + (movementXDelta[cameraId] * 30.0f);\n      }\n      movementStartY[cameraId] = moveYCur;\n      movementStartX[cameraId] = moveXCur;\n      ScrWork[movementVarDestY[cameraId]] = FloatToScrReal(moveYCur);\n      ScrWork[movementVarDestX[cameraId]] = FloatToScrReal(moveXCur);\n      // meh (hack... I *love* ScrWork crap)\n      thread->Variables[7] = FloatToScrReal(moveYCur);\n      thread->Variables[8] = FloatToScrReal(moveXCur);\n    } break;\n  }\n}\nVmInstruction(InstUnk0217) {\n  StartInstruction;\n  PopUint8(arg1);\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction Unk0217(arg1: {:d})\\n\", arg1);\n}\nVmInstruction(InstUnk0218) {\n  StartInstruction;\n  PopUint8(type);\n  switch (type) {\n    case 0:\n    case 1: {\n      PopExpression(arg1);\n      PopExpression(arg2);\n      PopExpression(arg3);\n      PopExpression(outYRot);\n      PopExpression(outXRot);\n      glm::vec3 lookat =\n          LookAtEulerZYX(glm::vec3(ScrRealToFloat(arg1), ScrRealToFloat(arg2),\n                                   ScrRealToFloat(arg3)),\n                         Profile::Scene3D::DefaultCameraPosition);\n\n      ScrWork[outXRot] = -FloatToScrReal(RadToDeg(NormalizeRad(lookat.x)));\n      ScrWork[outYRot] = FloatToScrReal(RadToDeg(NormalizeRad(lookat.y)));\n    } break;\n    case 2: {\n      PopExpression(arg1);\n      PopExpression(arg2);\n      PopExpression(arg3);\n      PopExpression(arg4);\n      PopExpression(arg5);\n      PopExpression(arg6);\n      PopExpression(arg7);\n      PopExpression(arg8);\n      ImpLogSlow(\n          LogLevel::Warning, LogChannel::VMStub,\n          \"STUB instruction Unk0218(type: {:d}, arg1: {:d}, arg2: {:d}, arg3: \"\n          \"{:d}, arg4: {:d}, arg5: {:d}, arg6: {:d}, arg7: {:d}, arg8: {:d})\\n\",\n          type, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8);\n    } break;\n    case 3: {\n      PopExpression(arg1);\n      PopExpression(arg2);\n      PopExpression(arg3);\n      PopExpression(arg4);\n      PopExpression(arg5);\n      PopExpression(arg6);\n      PopExpression(arg7);\n      PopExpression(arg8);\n      ImpLogSlow(\n          LogLevel::Warning, LogChannel::VMStub,\n          \"STUB instruction Unk0218(type: {:d}, arg1: {:d}, arg2: {:d}, arg3: \"\n          \"{:d}, arg4: {:d}, arg5: {:d}, arg6: {:d}, arg7: {:d}, arg8: {:d})\\n\",\n          type, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8);\n    } break;\n    case 4: {\n      PopExpression(arg1);\n      PopExpression(arg2);\n      PopExpression(arg3);\n      PopExpression(arg4);\n      PopExpression(arg5);\n      PopExpression(arg6);\n      PopExpression(arg7);\n      ImpLogSlow(\n          LogLevel::Warning, LogChannel::VMStub,\n          \"STUB instruction Unk0218(type: {:d}, arg1: {:d}, arg2: {:d}, arg3: \"\n          \"{:d}, arg4: {:d}, arg5: {:d}, arg6: {:d}, arg7: {:d})\\n\",\n          type, arg1, arg2, arg3, arg4, arg5, arg6, arg7);\n    } break;\n    default: {\n      PopExpression(arg1);\n      PopExpression(arg2);\n      ImpLogSlow(\n          LogLevel::Warning, LogChannel::VMStub,\n          \"STUB instruction Unk0218(type: {:d}, arg1: {:d}, arg2: {:d})\\n\",\n          type, arg1, arg2);\n    } break;\n  }\n}\nVmInstruction(InstUnk0219) {  // Not implemented\n  StartInstruction;\n  PopExpression(bufferId);\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction Unk0219(bufferId: {:d})\\n\", bufferId);\n}\nVmInstruction(InstUnk0220_Dash) {\n  StartInstruction;\n  PopUint8(type);\n  switch (type) {\n    case 0: {\n      PopExpression(arg1);\n      PopExpression(arg2);\n      PopExpression(arg3);\n      ScrWorkAnimations[arg1].MainAnimation.DurationIn = arg3 / 1000.0f;\n      ScrWorkAnimations[arg1].MainAnimation.DurationOut =\n          ScrWorkAnimations[arg1].MainAnimation.DurationIn;\n      ScrWorkAnimations[arg1].AltTarget = arg2 != 2;\n    } break;\n    case 1: {\n      PopExpression(arg1);\n      PopExpression(arg2);\n      PopExpression(arg3);\n      PopExpression(arg4);\n      PopExpression(arg5);\n      ScrWorkAnimationData anim;\n      anim.Target = arg2;\n      anim.From = arg3;\n      anim.To = arg4;\n      // if (anim.From == anim.To) {\n      //  anim.To = 256;\n      //}\n      if (ScrWorkAnimations[arg1].AltTarget) anim.To = arg5;\n      ScrWorkAnimations[arg1].AnimationData.push_back(anim);\n    } break;\n    case 2: {\n      PopExpression(arg1);\n      CurrentScrWorkAnimations.push_back(arg1);\n      ScrWorkAnimations[arg1].MainAnimation.StartIn();\n    } break;\n    case 3: {\n      PopExpression(arg1);\n      PopUint8(arg2);\n      ImpLogSlow(\n          LogLevel::Warning, LogChannel::VMStub,\n          \"STUB instruction Unk0220(type: {:d}, arg1: {:d}, arg2: {:d})\\n\",\n          type, arg1, arg2);\n      if (ScrWorkAnimations[arg1].MainAnimation.IsIn() ||\n          CurrentScrWorkAnimations.size() == 0) {\n        ScrWorkAnimations.erase(arg1);\n      } else {\n        ResetInstruction;\n        BlockThread;\n      }\n    } break;\n    case 4: {\n      PopUint8(arg1);\n      if (!arg1) {\n        PopExpression(arg2);\n      }\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction Unk0220(type: {:d}, arg1: {:d})\\n\", type,\n                 arg1);\n    } break;\n    case 10:\n      break;\n  }\n}\nVmInstruction(InstUnk0240) {  // Debug opcode\n  StartInstruction;\n  PopUint8(type);\n  switch (type) {\n    case 0:\n      break;\n    case 1:\n      break;\n    case 2:\n      break;\n    case 3:\n      break;\n    case 4:\n      break;\n    case 5:\n      break;\n    case 6:\n      break;\n  }\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction Unk0240(type: {:d})\\n\", type);\n}\n\n}  // namespace Vm\n\n}  // namespace Impacto"
  },
  {
    "path": "src/vm/inst_graphics3d.h",
    "content": "#pragma once\n\n#include \"vm.h\"\n\nnamespace Impacto {\n\nnamespace Vm {\n\nVmInstruction(InstCHAload3D);\nVmInstruction(InstCHArelease3D);\nVmInstruction(InstUnk0204);\nVmInstruction(InstCHAswap3DMaybe);\nVmInstruction(InstCHAplayAnim3DMaybe);\nVmInstruction(InstCHAUnk02073D);\nVmInstruction(InstCHAUnk02073D_Dash);\nVmInstruction(InstPositionObject);\nVmInstruction(InstPositionObject_Dash);\nVmInstruction(InstCHAsetAnim3D);\nVmInstruction(InstUnk0210);\nVmInstruction(InstUnk0211);\nVmInstruction(InstUnk0212);\nVmInstruction(InstUnk0213);\nVmInstruction(InstUnk0214);\nVmInstruction(InstUnk0215);\nVmInstruction(InstMoveCamera);\nVmInstruction(InstUnk0217);\nVmInstruction(InstUnk0218);\nVmInstruction(InstUnk0219);\nVmInstruction(InstUnk0220_Dash);\nVmInstruction(InstUnk0240);\n\n}  // namespace Vm\n\n}  // namespace Impacto"
  },
  {
    "path": "src/vm/inst_macros.inc",
    "content": "#define StartInstruction                                     \\\n  uint8_t* _oldIp = thread->GetIp();                         \\\n  bool noExpressions = (bool)(*thread->GetIp() & 0x80);      \\\n  /* Sometimes needed by subsequent code, silence warning */ \\\n  (void)noExpressions;                                       \\\n  (void)_oldIp;                                              \\\n  thread->IpOffset += 2\n#define ResetInstruction thread->SetIp(_oldIp)\n\n#define BlockThread BlockCurrentScriptThread = true\n\n#define PopUint8(name)                                       \\\n  uint8_t name = *thread->GetIp();                           \\\n  /* Sometimes needed by subsequent code, silence warning */ \\\n  (void)name;                                                \\\n  thread->IpOffset += 1;\n#define PopUint16(name)                                                   \\\n  uint16_t name = SDL_SwapLE16(UnalignedRead<uint16_t>(thread->GetIp())); \\\n  /* Sometimes needed by subsequent code, silence warning */              \\\n  (void)name;                                                             \\\n  thread->IpOffset += 2;\n#define PopLocalLabel(name)                                         \\\n  uint32_t name;                                                    \\\n  {                                                                 \\\n    PopUint16(labelNum);                                            \\\n    name = ScriptGetLabelAddress(thread->ScriptBufferId, labelNum); \\\n  }                                                                 \\\n  (void)0\n#define PopFarLabel(name, scriptBufferId)                   \\\n  uint32_t name;                                            \\\n  {                                                         \\\n    PopUint16(labelNum);                                    \\\n    name = ScriptGetLabelAddress(scriptBufferId, labelNum); \\\n  }                                                         \\\n  (void)0\n#define PopExpression(name) [[maybe_unused]] int name = ExpressionEval(thread)\n#define PopString(name)                                            \\\n  uint32_t name;                                                   \\\n  {                                                                \\\n    PopUint16(stringNum);                                          \\\n    name = ScriptGetStrAddress(thread->ScriptBufferId, stringNum); \\\n  }                                                                \\\n  (void)0\n#define PopMsbString(name)                                      \\\n  uint32_t name;                                                \\\n  {                                                             \\\n    PopExpression(stringNum);                                   \\\n    name = MsbGetStrAddress(thread->ScriptBufferId, stringNum); \\\n  }                                                             \\\n  (void)0\n"
  },
  {
    "path": "src/vm/inst_misc.cpp",
    "content": "#include \"inst_misc.h\"\n\n#include \"inst_macros.inc\"\n\n#include \"expression.h\"\n#include \"interface/input.h\"\n#include \"../profile/scriptvars.h\"\n#include \"../profile/configsystem.h\"\n#include \"../game.h\"\n#include \"../mem.h\"\n#include \"../log.h\"\n#include \"../hud/saveicondisplay.h\"\n#include \"../ui/ui.h\"\n#include \"../data/savesystem.h\"\n#include \"../audio/audiosystem.h\"\n\n#include \"../profile/vm.h\"\n#include \"../games/cclcc/systemmenu.h\"\n#include \"../games/cclcc/helpmenu.h\"\nnamespace Impacto {\n\nnamespace Vm {\n\nusing namespace Impacto::Profile::ScriptVars;\n\nVmInstruction(InstUPLmenuUI) {\n  StartInstruction;\n  PopUint8(arg1);\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction UPLmenuUI(arg1: {:d})\\n\", arg1);\n}\nVmInstruction(InstUPLxTitle) {\n  StartInstruction;\n  PopUint8(arg1);\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction UPLxTitle(arg1: {:d})\\n\", arg1);\n}\nVmInstruction(InstPresence) {\n  StartInstruction;\n  PopUint8(arg1);\n  PopExpression(arg2);\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction Presence(arg1: {:d}, arg2: {:d})\\n\", arg1, arg2);\n}\nVmInstruction(InstPresenceMO6) {\n  StartInstruction;\n  PopExpression(arg1);\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction Presence(arg1: {:d})\\n\", arg1);\n}\nVmInstruction(InstSetAchievement) {\n  StartInstruction;\n  PopUint8(type);\n  if (type == 1) {\n    PopExpression(arg1);\n    ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n               \"STUB instruction Achievement(type: {:d}, arg1: {:d})\\n\", type,\n               arg1);\n  } else {\n    ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n               \"STUB instruction Achievement(type: {:d})\\n\", type);\n  }\n}\nVmInstruction(InstSetPlayer) {\n  StartInstruction;\n  PopUint8(arg1);\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction SetPlayer(arg1: {:d})\\n\", arg1);\n}\nVmInstruction(InstSignIn) {\n  StartInstruction;\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction SignIn()\\n\");\n}\nVmInstruction(InstAchievementIcon) {\n  StartInstruction;\n  PopUint8(arg1);\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction AchievementIcon(arg1: {:d})\\n\", arg1);\n}\nVmInstruction(InstSetX360SysMesPos) {\n  StartInstruction;\n  PopExpression(arg1);\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction SetX360SysMesPos(arg1: {:d})\\n\", arg1);\n}\nVmInstruction(InstSystemMenu) {\n  StartInstruction;\n  PopUint8(mode);\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction SystemMenu(mode: {:d})\\n\", mode);\n  switch (mode) {\n    case 0:\n      if (Profile::Vm::GameInstructionSet == InstructionSet::MO6TW) {\n      } else if (Profile::Vm::GameInstructionSet == InstructionSet::CC) {\n        auto* sysMenuPtr =\n            static_cast<UI::CCLCC::SystemMenu*>(UI::SystemMenuPtr);\n        sysMenuPtr->Init();\n        // Block input during animation\n        if (sysMenuPtr->State == UI::MenuState::Hiding ||\n            sysMenuPtr->State == UI::MenuState::Showing) {\n          ResetInstruction;\n          BlockThread;\n        }\n      }\n\n      break;\n    case 1: {\n      if (UI::SystemMenuPtr->ChoiceMade) {\n        UI::SystemMenuPtr->ChoiceMade = false;\n        Interface::PADinputButtonWentDown |= Interface::PAD1A;\n      }\n    } break;\n  }\n}\nVmInstruction(InstPressStart) {\n  StartInstruction;\n  PopUint8(type);\n  switch (type) {\n    case 0: {\n      PopExpression(arg1);\n      PopExpression(arg2);\n      ImpLogSlow(\n          LogLevel::Warning, LogChannel::VMStub,\n          \"STUB instruction PressStart(type: {:d}, arg1: {:d}, arg2: {:d})\\n\",\n          type, arg1, arg2);\n      switch (Profile::Vm::GameInstructionSet) {\n        case InstructionSet::Dash: {\n          SaveIconDisplay::ShowAt(glm::vec2(arg1 * 1.5f, arg2 * 1.5f));\n        } break;\n        default: {\n          SaveIconDisplay::ShowAt(glm::vec2(arg1, arg2));\n        } break;\n      }\n    } break;\n    case 1: {\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction PressStart(type: {:d})\\n\", type);\n      SaveIconDisplay::Hide();\n    } break;\n    case 2: {\n      PopExpression(arg1);\n      PopExpression(arg2);\n      PopExpression(arg3);\n      ImpLogSlow(\n          LogLevel::Warning, LogChannel::VMStub,\n          \"STUB instruction PressStart(type: {:d}, arg1: {:d}, arg2: {:d}, \"\n          \"arg3: {:d})\\n\",\n          type, arg1, arg2, arg3);\n    } break;\n    case 3: {\n      PopExpression(arg1);\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction PressStart(type: {:d}, arg1: {:d})\\n\", type,\n                 arg1);\n    } break;\n    case 4: {\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction PressStart(type: {:d})\\n\", type);\n      SaveIconDisplay::Show();\n    } break;\n    case 5: {\n      PopExpression(arg1);\n      PopExpression(arg2);\n      ImpLogSlow(\n          LogLevel::Warning, LogChannel::VMStub,\n          \"STUB instruction PressStart(type: {:d}, arg1: {:d}, arg2: {:d})\\n\",\n          type, arg1, arg2);\n    } break;\n    case 6: {\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction PressStart(type: {:d})\\n\", type);\n      SaveIconDisplay::Hide();\n    } break;\n    case 7: {\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction PressStart(type: {:d})\\n\", type);\n      SaveIconDisplay::Hide();\n    } break;\n  }\n}\nVmInstruction(InstPressStartNew) {\n  StartInstruction;\n  PopUint8(type);\n  if (type == 0 || type == 3) {\n    PopLocalLabel(labelAdr);\n    thread->IpOffset = labelAdr;\n  }\n}\nVmInstruction(InstClearFlagChk) {\n  StartInstruction;\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction ClearFlagChk()\\n\");\n}\nVmInstruction(InstEVinit) {\n  StartInstruction;\n  PopUint8(arg1);\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction ClearFlagInit(arg1: {:d})\\n\", arg1);\n}\nVmInstruction(InstEVload) {\n  StartInstruction;\n  PopUint8(arg1);\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction ClearFlagLoad(arg1: {:d})\\n\", arg1);\n}\nVmInstruction(InstEVset) {\n  StartInstruction;\n  PopUint8(arg1);\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction ClearFlagSet(arg1: {:d})\\n\", arg1);\n}\nVmInstruction(InstClearFlagChkOld) {\n  StartInstruction;\n  PopUint8(arg1);\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction ClearFlagChk(arg1: {:d})\\n\", arg1);\n}\nVmInstruction(InstOption) {\n  StartInstruction;\n  PopUint8(type);\n  switch (type) {\n    case 0:\n    case 0xA:\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction Option(type: Init)\\n\");\n      break;\n    case 1:\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction Option(type: Main)\\n\");\n      break;\n    case 2:\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction Option(type: Cancel)\\n\");\n      break;\n    case 3:\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction Option(type: V2toV1vol)\\n\");\n      break;\n    case 4:\n      if (Profile::Vm::GameInstructionSet == InstructionSet::CHLCC) {\n        PopExpression(unusedPageNo);\n      }\n\n      UI::OptionsMenuPtr->ResetToDefault();\n      break;\n  }\n}\nVmInstruction(InstHelp) {\n  StartInstruction;\n  PopUint8(type);\n  switch (type) {\n    case 0:  // Init\n      SetFlag(SF_HELPMENU, true);\n      break;\n    case 1:  // Main\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction Help(type: Main)\\n\");\n      break;\n  }\n}\nVmInstruction(InstAchievementMenu) {\n  StartInstruction;\n  PopUint8(type);\n  switch (type) {\n    case 0:  // Init\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction AchievementMenu(type: Init)\\n\");\n      break;\n    case 1:  // Main\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction AchievementMenu(type: Main)\\n\");\n      break;\n    case 0xA:  // ProfSetXboxEvent\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction AchievementMenu(type: ProfSetXboxEvent)\\n\");\n      break;\n  }\n}\nVmInstruction(InstSoundMenu) {\n  StartInstruction;\n  PopUint8(type);\n  switch (type) {\n    case 0:  // MusicInit\n      if (UI::MusicMenuPtr) UI::MusicMenuPtr->Init();\n      break;\n    case 1:  // MusicMain\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction SoundMenu(type: MusicMain)\\n\");\n      break;\n    case 0xA:  // ProfSetXboxEvent\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction SoundMenu(type: ProfSetXboxEvent)\\n\");\n      break;\n  }\n}\nVmInstruction(InstAllClear) {\n  StartInstruction;\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction AllClear()\\n\");\n}\nVmInstruction(InstAlbum) {\n  StartInstruction;\n  PopUint8(type);\n  switch (type) {\n    case 0:  // EXmenuInit\n      if (UI::LibraryMenuPtr) UI::LibraryMenuPtr->Init();\n      if (UI::AlbumMenuPtr) UI::AlbumMenuPtr->Init();\n      if (UI::MusicMenuPtr) UI::MusicMenuPtr->Init();\n      if (UI::MovieMenuPtr) UI::MovieMenuPtr->Init();\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction Album(type: EXmenuInit)\\n\");\n      break;\n    case 1:  // EXmenuMain\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction Album(type: EXmenuMain)\\n\");\n      break;\n    case 0xA:  // ProfSetXboxEvent\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction Album(type: ProfSetXboxEvent)\\n\");\n      break;\n  }\n}\nVmInstruction(InstMovieMode) {\n  StartInstruction;\n  PopUint8(type);\n  switch (type) {\n    case 0:  // Init\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction MovieMode(type: Init)\\n\");\n      break;\n    case 1:  // Main\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction MovieMode(type: Main)\\n\");\n      break;\n  }\n}\nVmInstruction(InstClistInit) {\n  StartInstruction;\n  PopUint8(type);\n  switch (type) {\n    case 0:  // PDmenuInit\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction ClistInit(type: EXmenuInit)\\n\");\n      break;\n    case 1:  // PlayDataMain\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction ClistInit(type: EXmenuMain)\\n\");\n      break;\n    case 3:  // Unused\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction ClistInit(type: {:d})\\n\", type);\n      break;\n    case 0xA:  // ProfSetXboxEvent\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction ClistInit(type: ProfSetXboxEvent)\\n\");\n      break;\n  }\n}\nVmInstruction(InstSaveMenu) {\n  StartInstruction;\n  PopUint8(type);\n  switch (type) {\n    case 0: {  // SaveMenuInit\n      PopUint8(arg1);\n      UI::SaveMenuPtr->ActiveMenuType =\n          magic_enum::enum_cast<UI::SaveMenuPageType>(arg1);\n      ScrWork[SW_SAVEFILESTATUS] = 0;\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction SaveMenu(type: SaveMenuInit)\\n\");\n    } break;\n    case 1:  // SaveMenuMain\n      if (!UI::SaveMenuPtr->ChoiceMade) {\n        if (!((Interface::PADinputButtonWentDown & Interface::PAD1B) ||\n              (Interface::PADinputMouseWentDown & Interface::PAD1B))) {\n          ResetInstruction;\n          BlockThread;\n        }\n      } else {\n        UI::SaveMenuPtr->ChoiceMade = false;\n        Interface::PADinputButtonWentDown |= Interface::PAD1A;\n      }\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction SaveMenu(type: SaveMenuMain)\\n\");\n      break;\n    case 2:  // SaveResetThumnail\n      if (UI::SaveMenuPtr) UI::SaveMenuPtr->RefreshCurrentEntryInfo();\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction SaveMenu(type: SaveResetThumnail)\\n\");\n      break;\n    case 10:\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction SaveMenu(type: {:d})\\n\", type);\n      break;\n    case 11:\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction SaveMenu(type: {:d})\\n\", type);\n      break;\n    case 12:\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction SaveMenu(type: {:d})\\n\", type);\n      break;\n    case 13:\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction SaveMenu(type: {:d})\\n\", type);\n      break;\n    case 20:\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction SaveMenu(type: {:d})\\n\", type);\n      break;\n  }\n}\nVmInstruction(InstSaveMenuOld) {\n  StartInstruction;\n  PopUint8(type);\n  switch (type) {\n    case 0: {\n      PopUint8(arg1);\n      UI::SaveMenuPtr->ActiveMenuType =\n          magic_enum::enum_cast<UI::SaveMenuPageType>(arg1);\n      ScrWork[SW_SAVEFILESTATUS] = 0;\n      if (UI::SaveMenuPtr) UI::SaveMenuPtr->Init();\n    } break;\n    case 1:\n      if (!UI::SaveMenuPtr->ChoiceMade) {\n        if (!((Interface::PADinputButtonWentDown & Interface::PAD1B) ||\n              (Interface::PADinputMouseWentDown & Interface::PAD1B))) {\n          ResetInstruction;\n          BlockThread;\n        }\n      } else {\n        UI::SaveMenuPtr->ChoiceMade = false;\n        Interface::PADinputButtonWentDown |= Interface::PAD1A;\n      }\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction SaveMenu(type: {:d})\\n\", type);\n      break;\n    case 2: {\n      if (UI::SaveMenuPtr) UI::SaveMenuPtr->RefreshCurrentEntryInfo();\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction SaveMenu(type: {:d})\\n\", type);\n    } break;\n    case 10: {\n      PopUint8(arg1);\n      if (UI::SaveMenuPtr) UI::SaveMenuPtr->Init();\n    } break;\n  }\n}\nVmInstruction(InstLoadData) {\n  StartInstruction;\n  PopUint8(type);\n  switch (type) {\n    case 0:\n    case 10: {\n      PopExpression(arg1);\n      PopExpression(arg2);\n      SaveSystem::LoadEntry(static_cast<SaveSystem::SaveType>(arg1), arg2);\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction LoadData(type: {:d}, arg1: {:d})\\n\", type,\n                 arg1);\n    } break;\n    case 1:\n    case 2:\n    case 11:\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction LoadData(type: {:d})\\n\", type);\n      break;\n  }\n  SaveSystem::LoadMemoryNew(static_cast<SaveSystem::LoadProcess>(type));\n}\nVmInstruction(InstLoadDataOld) {\n  StartInstruction;\n  PopExpression(arg1);\n  SaveSystem::SaveType saveType;\n  if (*UI::SaveMenuPtr->ActiveMenuType == UI::SaveMenuPageType::QuickLoad) {\n    saveType = SaveSystem::SaveType::Quick;\n  } else {\n    saveType = SaveSystem::SaveType::Full;\n  }\n  SaveSystem::LoadEntry(saveType, arg1);\n  if (ScrWork[SW_MESWINDOW_COLOR] == 0) ScrWork[SW_MESWINDOW_COLOR] = 0xFFFFFF;\n}\nVmInstruction(InstTitleMenu) {\n  StartInstruction;\n  PopUint8(type);\n  switch (type) {\n    case 0:  // Init\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction TitleMenu(type: Init)\\n\");\n      SetFlag(SF_TITLEMODE, 1);\n      break;\n    case 1:  // Main\n      // Hack to kickstart into \"New Game\"\n      switch (Profile::Vm::GameInstructionSet) {\n        default:\n          break;\n        case InstructionSet::RNE:\n        case InstructionSet::MO7:\n          ScrWork[SW_TITLECUR1] = 0;\n          break;\n        case InstructionSet::Dash:\n          ScrWork[SW_TITLECUR1] = 2;\n          break;\n      }\n      ScrWork[SW_TITLECUR2] = 255;\n      SetFlag(SF_TITLEMODE, 0);\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction TitleMenu(type: Main)\\n\");\n      break;\n    case 2:  // Init2\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction TitleMenu(type: Init2)\\n\");\n      break;\n  }\n}\nVmInstruction(InstTitleMenuNew) {\n  StartInstruction;\n  PopUint8(type);\n  switch (type) {\n    case 0:  // Init\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction TitleMenu(type: Init)\\n\");\n      break;\n    case 1:  // Main\n      switch (Profile::Vm::GameInstructionSet) {\n        default:\n          break;\n        case InstructionSet::CC:\n        case InstructionSet::CHN: {\n          if (ScrWork[SW_TITLEMODE] == 3) {\n            if (!UI::TitleMenuPtr->AllowsScriptInput) {\n              ResetInstruction;\n              BlockThread;\n            }\n          } else if (ScrWork[SW_TITLEMODE] == 1 &&\n                     ScrWork[SW_TITLEDISPCT] ==\n                         (Profile::Vm::GameInstructionSet == InstructionSet::CC\n                              ? 60\n                              : 400)) {\n            // Check \"PRESS TO START\" here\n            if (((Interface::PADinputButtonWentDown & Interface::PAD1A) ||\n                 (Interface::PADinputMouseWentDown & Interface::PAD1A))) {\n              ScrWork[SW_TITLEMODE] = 2;\n              ScrWork[SW_TITLEDISPCT] = 0;\n              ScrWork[SW_TITLEMOVIECT] = 0;\n              SetFlag(SF_TITLEEND, 1);\n            } else {\n              ScrWork[SW_TITLEMOVIECT]++;\n            }\n          }\n        } break;\n        case InstructionSet::MO8: {\n          if (ScrWork[SW_TITLEMODE] == 1) {\n            ScrWork[SW_TITLEMOVIECT] += 1;\n            // Check \"PRESS TO START\" here\n            if (((Interface::PADinputButtonWentDown & Interface::PAD1A) ||\n                 (Interface::PADinputMouseWentDown & Interface::PAD1A))) {\n              Audio::PlayInGroup(Audio::ACG_SE, \"sysse\", 0, false, 0.0f);\n              ScrWork[SW_TITLEMODE] = 2;\n              ScrWork[SW_TITLEDISPCT] = 0;\n              ScrWork[SW_TITLEMOVIECT] = 0;\n              SetFlag(SF_TITLEEND, 1);\n            }\n          }\n        } break;\n      }\n      break;\n    case 2:  // Init2\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction TitleMenu(type: Init2)\\n\");\n      break;\n      break;\n  }\n}\nVmInstruction(InstTitleMenuOld) {\n  StartInstruction;\n\n  if (UI::TitleMenuPtr == nullptr) return;\n\n  if (ScrWork[SW_TITLECUR1] == 0xff) {\n    ScrWork[SW_TITLECUR1] = 0;\n  }\n\n  // Sometimes in order to make something sane\n  // you have to sacrifice the sanity of others...\n  if (!UI::TitleMenuPtr->ChoiceMade) {\n    if (!((Interface::PADinputButtonWentDown & Interface::PAD1B) ||\n          (Interface::PADinputMouseWentDown & Interface::PAD1B))) {\n      ResetInstruction;\n      BlockThread;\n    }\n  } else {\n    UI::TitleMenuPtr->ChoiceMade = false;\n    Interface::PADinputButtonWentDown |= Interface::PAD1A;\n  }\n}\nVmInstruction(InstSetPlayMode) {\n  StartInstruction;\n  PopExpression(arg1);\n\n  if (arg1 == 4) {\n    AutoModeEnabled = false;\n    SkipModeEnabled = true;\n  } else {\n    AutoModeEnabled = arg1 & 0b100;\n    SkipModeEnabled = arg1 & 0b011;\n  }\n}\nVmInstruction(InstSetEVflag) {\n  StartInstruction;\n  if (Profile::Vm::GameInstructionSet == InstructionSet::MO8 ||\n      Profile::Vm::GameInstructionSet == InstructionSet::CHN) {\n    PopUint8(unk01);\n  }\n  PopExpression(arg1);\n  SaveSystem::SetEVStatus(arg1);\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction SetEVflag(arg1: {:d})\\n\", arg1);\n}\nVmInstruction(InstSetCutin) {\n  StartInstruction;\n  PopUint8(type);\n  PopExpression(arg1);\n  PopExpression(arg2);\n  PopExpression(arg3);\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction SetCutin(type: {:d}, arg1: {:d}, arg2: {:d}, \"\n             \"arg3: {:d})\\n\",\n             type, arg1, arg2, arg3);\n}\nVmInstruction(InstAchChkTitle) {\n  StartInstruction;\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction AchChkTitle()\\n\");\n}\nVmInstruction(InstSetSceneViewFlag) {\n  StartInstruction;\n  PopExpression(sceneId);\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction SetSceneViewFlag(sceneId: {:d})\\n\", sceneId);\n}\nVmInstruction(InstChkClearFlag) {\n  StartInstruction;\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction ChkClearFlag()\\n\");\n}\nVmInstruction(InstScreenChange) { StartInstruction; }\nVmInstruction(InstExitGame) {\n  StartInstruction;\n  Game::ShouldQuit = true;\n}\n\n}  // namespace Vm\n\n}  // namespace Impacto"
  },
  {
    "path": "src/vm/inst_misc.h",
    "content": "#pragma once\n\n#include \"vm.h\"\n\nnamespace Impacto {\n\nnamespace Vm {\n\nVmInstruction(InstUPLmenuUI);\nVmInstruction(InstUPLxTitle);\nVmInstruction(InstPresence);\nVmInstruction(InstPresenceMO6);\nVmInstruction(InstSetAchievement);\nVmInstruction(InstSetPlayer);\nVmInstruction(InstSignIn);\nVmInstruction(InstAchievementIcon);\nVmInstruction(InstSetX360SysMesPos);\nVmInstruction(InstSystemMenu);\nVmInstruction(InstPressStart);\nVmInstruction(InstPressStartNew);\nVmInstruction(InstClearFlagChk);\nVmInstruction(InstEVload);\nVmInstruction(InstEVinit);\nVmInstruction(InstEVset);\nVmInstruction(InstClearFlagChkOld);\nVmInstruction(InstOption);\nVmInstruction(InstHelp);\nVmInstruction(InstAchievementMenu);\nVmInstruction(InstSoundMenu);\nVmInstruction(InstAllClear);\nVmInstruction(InstAlbum);\nVmInstruction(InstMovieMode);\nVmInstruction(InstClistInit);\nVmInstruction(InstSaveMenu);\nVmInstruction(InstSaveMenuOld);\nVmInstruction(InstLoadData);\nVmInstruction(InstLoadDataOld);\nVmInstruction(InstTitleMenu);\nVmInstruction(InstTitleMenuOld);\nVmInstruction(InstTitleMenuNew);\nVmInstruction(InstSetPlayMode);\nVmInstruction(InstSetEVflag);\nVmInstruction(InstSetCutin);\nVmInstruction(InstAchChkTitle);\nVmInstruction(InstSetSceneViewFlag);\nVmInstruction(InstChkClearFlag);\nVmInstruction(InstScreenChange);\nVmInstruction(InstExitGame);\n\n}  // namespace Vm\n\n}  // namespace Impacto"
  },
  {
    "path": "src/vm/inst_movie.cpp",
    "content": "#include \"inst_movie.h\"\n\n#include \"inst_macros.inc\"\n\n#include <math.h>\n\n#include \"expression.h\"\n#include \"../game.h\"\n#include \"../log.h\"\n#include \"../mem.h\"\n#include \"../profile/scriptvars.h\"\n#include \"../profile/vm.h\"\n#include \"../profile/game.h\"\n#include \"../video/videosystem.h\"\n#include \"interface/input.h\"\n\nnamespace Impacto {\n\nnamespace Vm {\n\nusing namespace Impacto::Profile::ScriptVars;\n\nVmInstruction(InstPlayMovie) {\n  StartInstruction;\n  PopUint8(playMode);\n\n  int playView;\n  if (playMode == 99) {  // PlayMovieRecover - used when loading a save file\n    // playMode of the video at save time\n    playMode = static_cast<uint8_t>(ExpressionEval(thread));\n    playView = ExpressionEval(thread);\n  } else {\n    playView = *thread->GetIp();\n    thread->IpOffset++;\n  }\n\n  PopExpression(playNo);\n  PopExpression(movCancelFlag);\n\n  if (+Profile::GameFeatures & +GameFeature::Video) {\n    Io::Stream* stream;\n    auto err = Io::VfsOpen(\"movie\", playNo, &stream);\n    if (err != IoError_OK) {\n      ImpLog(LogLevel::Error, LogChannel::Video,\n             \"Failed to open movie for playback: IO error {}\\n\", err);\n      return;\n    }\n\n    const uint8_t channel = (playMode / 20) == 0 ? 0 : 1;\n    Video::Players[channel]->CancelFlag = movCancelFlag;\n    Video::Players[channel]->CancelWaitTime = 0;\n    SetFlag(SF_MOVIEFL + channel, movCancelFlag);\n    ScrWork[SW_MOVIE_PLAYNO + 20 * channel] = playNo;\n    ScrWork[SW_MOVIE_PLAYMODE + 20 * channel] = playMode + 20 * channel;\n    ScrWork[SW_MOVIE_PLAYVIEW + 20 * channel] = playView;\n    ScrWork[SW_MOVIE_LOADNO + 20 * channel] = 0xffff;\n\n    int flags = 0;\n    if (playMode >= 8) {\n      playMode -= 8;\n      flags |= 4;\n    }\n    switch (playMode) {\n      case 0:\n        flags |= 0b10;\n        break;\n      case 1:\n        flags |= 0b1;\n        break;\n      case 2:\n        flags |= 0b1010;\n        break;\n      case 3:\n        flags |= 0b1001;\n        break;\n      case 4:\n        flags |= 0b100000;\n        break;\n      case 5:\n        flags |= 0b101000;\n        break;\n      default:\n        break;\n    }\n\n    if (Video::Players[channel]->IsPlaying) Video::Players[channel]->Stop();\n    Video::Players[channel]->Play(stream, flags & 8, flags & 4);\n\n    SetFlag(SF_MOVIE_DRAWWAIT + channel, true);\n    SetFlag(SF_MOVIEPLAY + channel, true);\n    SetFlag(SF_MOVIECANCEL + channel, false);\n  }\n\n  BlockThread;\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction PlayMovie(playMode: {:d}, playView: {:d}, \"\n             \"playNo: {:d}, movCancelFlag: {:d})\\n\",\n             playMode, playView, playNo, movCancelFlag);\n}\n\nstatic void PlayMovieOldCommon(Sc3VmThread* thread, uint8_t instType) {\n  StartInstruction;\n  PopUint8(playMode);\n\n  int playView;\n  if (playMode == 99) {  // PlayMovieRecover - used when loading a save file\n    // playMode of the video at save time\n    playMode = static_cast<uint8_t>(ExpressionEval(thread));\n    playView = ExpressionEval(thread);\n  } else {\n    playView = *thread->GetIp();\n    thread->IpOffset++;\n  }\n\n  PopExpression(playNo);\n  PopExpression(movCancelFlag);\n\n  if (+Profile::GameFeatures & +GameFeature::Video) {\n    Io::Stream* stream;\n    auto err = Io::VfsOpen(\"movie\", playNo, &stream);\n    if (err != IoError_OK) {\n      ImpLog(LogLevel::Error, LogChannel::Video,\n             \"Failed to open movie for playback: IO error {}\\n\", err);\n      return;\n    }\n\n    const uint8_t channel = (playMode / 20) == 0 ? 0 : 1;\n    Video::Players[channel]->CancelFlag = movCancelFlag;\n    Video::Players[channel]->CancelWaitTime = 0;\n    SetFlag(SF_MOVIEFL + channel, movCancelFlag);\n    ScrWork[SW_MOVIE_PLAYNO + 20 * channel] = playNo + (1000 * (instType == 2));\n    ScrWork[SW_MOVIE_PLAYMODE + 20 * channel] = playMode + 20 * channel;\n    ScrWork[SW_MOVIE_PLAYVIEW + 20 * channel] = playView;\n    ScrWork[SW_MOVIE_LOADNO + 20 * channel] = 0xffff;\n\n    if (Video::Players[channel]->IsPlaying) Video::Players[channel]->Stop();\n    Video::Players[channel]->Play(stream, playMode == 5, playMode == 5);\n\n    SetFlag(SF_MOVIE_DRAWWAIT + channel, true);\n    SetFlag(SF_MOVIEPLAY + channel, true);\n    SetFlag(SF_MOVIECANCEL + channel, false);\n  }\n\n  BlockThread;\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction PlayMovieOld(playMode: {:d}, playView: {:d}, \"\n             \"playNo: {:d}, movCancelFlag: {:d})\\n\",\n             playMode, playView, playNo, movCancelFlag);\n}\n\nVmInstruction(InstPlayMovieOld) { PlayMovieOldCommon(thread, 1); }\nVmInstruction(InstPlayMovieOld2) { PlayMovieOldCommon(thread, 2); }\n\nVmInstruction(InstMovie) {\n  StartInstruction;\n  PopUint8(type);\n  switch (type) {\n    case 0: {  // Restart\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction Movie(type: Restart)\\n\");\n    } break;\n    case 1: {  // Pause\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction Movie(type: Pause)\\n\");\n    } break;\n  }\n}\nVmInstruction(InstMovieMain) {\n  using namespace Video;\n  StartInstruction;\n\n  const bool videoEnabled = +Profile::GameFeatures & +GameFeature::Video;\n\n  PopUint8(type);\n  const uint8_t playerId = type < 20 ? 0 : 1;\n  VideoPlayer& player = *Players[playerId];\n\n  switch (type) {\n    case 0: {\n      if (!videoEnabled) {\n        SetFlag(SF_MOVIELOADPLAYFL + playerId, false);\n        SetFlag(SF_MOVIE_DRAWWAIT + playerId, false);\n        ScrWork[SW_MOVIE_PLAYNO + playerId * 20] = 0xffff;\n        ScrWork[SW_MOVIE_LOADNO + playerId * 20] = 0xffff;\n        break;\n      }\n\n      if (GetFlag(SF_MOVIEPLAY + playerId) && player.CancelWaitTime != 0) {\n        player.CancelWaitTime++;\n        if (player.CancelWaitTime < 8) {\n          ResetInstruction;\n          BlockThread;\n          return;\n        }\n\n        SetFlag(SF_MOVIEPLAY + playerId, false);\n        SetFlag(SF_MOVIECANCEL + playerId, true);\n        player.CancelWaitTime = 0;\n        BlockThread;\n      }\n\n      if (!player.CancelFlag ||\n          !Interface::GetControlState(Interface::CT_MovieCancel)) {\n        if (GetFlag(SF_MOVIEPLAY + playerId)) {\n          ResetInstruction;\n          BlockThread;\n        } else {\n          player.Stop();\n\n          SetFlag(SF_MOVIELOADPLAYFL + playerId, false);\n          SetFlag(SF_MOVIE_DRAWWAIT + playerId, false);\n          ScrWork[SW_MOVIE_PLAYNO + playerId * 20] = 0xffff;\n          ScrWork[SW_MOVIE_LOADNO + playerId * 20] = 0xffff;\n        }\n\n      } else {\n        player.CancelWaitTime++;\n        player.CancelFlag = false;\n\n        ResetInstruction;\n        BlockThread;\n      }\n    } break;\n\n    case 1:\n    case 21: {\n      thread->ScriptParam = GetFlag(SF_MOVIEPLAY + playerId);\n    } break;\n\n    case 2:\n    case 22: {  // Stop\n      if (videoEnabled) player.Stop();\n\n      SetFlag(SF_MOVIEPLAY + playerId, false);\n      SetFlag(SF_MOVIELOADPLAYFL + playerId, false);\n      SetFlag(SF_MOVIE_DRAWWAIT + playerId, false);\n      ScrWork[SW_MOVIE_PLAYNO + playerId * 20] = 0xffff;\n      ScrWork[SW_MOVIE_LOADNO + playerId * 20] = 0xffff;\n    } break;\n\n    case 3:\n    case 23: {  // StopWait\n      if (player.CancelFlag &&\n          Interface::GetControlState(Interface::CT_MovieCancel)) {\n        SetFlag(SF_MOVIEPLAY + playerId, false);\n        SetFlag(SF_MOVIECANCEL + playerId, true);\n        BlockThread;\n      }\n\n      if (!GetFlag(SF_MOVIEPLAY + playerId)) {\n        if (videoEnabled) player.Stop();\n\n        SetFlag(SF_MOVIELOADPLAYFL + playerId, false);\n        ScrWork[SW_MOVIE_PLAYNO + playerId * 20] = 0xffff;\n        ScrWork[SW_MOVIE_LOADNO + playerId * 20] = 0xffff;\n      }\n    } break;\n\n    case 4:\n    case 24: {\n      if (videoEnabled && !player.IsPlaying) {\n        ResetInstruction;\n        BlockThread;\n      } else {\n        // TODO: Set frame numbers\n      }\n    } break;\n\n    case 20: {\n      if (!videoEnabled) {\n        SetFlag(SF_MOVIEPLAY + playerId, false);\n        SetFlag(SF_MOVIECANCEL + playerId, false);\n        ScrWork[SW_MOVIE_PLAYNO + playerId * 20] = 0xffff;\n        ScrWork[SW_MOVIE_LOADNO + playerId * 20] = 0xffff;\n        break;\n      }\n\n      if (player.CancelFlag &&\n          Interface::GetControlState(Interface::CT_MovieCancel)) {\n        SetFlag(SF_MOVIEPLAY + playerId, true);\n        SetFlag(SF_MOVIECANCEL + playerId, true);\n        BlockThread;\n      }\n\n      if (!GetFlag(SF_MOVIEPLAY + playerId)) {\n        player.Stop();\n        ScrWork[SW_MOVIE_PLAYNO + playerId * 20] = 0xffff;\n        ScrWork[SW_MOVIE_LOADNO + playerId * 20] = 0xffff;\n      } else {\n        ResetInstruction;\n        BlockThread;\n      }\n    } break;\n  }\n}\nVmInstruction(InstLoadMovie) {\n  StartInstruction;\n  PopExpression(arg1);\n  ScrWork[SW_MOVIE_LOADNO] = arg1 + 1000;\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction LoadMovie(arg1: {:d})\\n\", arg1);\n}\nVmInstruction(InstPlayMovieMemory) {\n  StartInstruction;\n  PopUint8(playMode);\n  if (playMode == 99) {\n    PopExpression(playModeEx);\n    PopExpression(playView);\n    PopExpression(movCancelFlag);\n    ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n               \"STUB instruction PlayMovie(playMode: {:d}, playModeEx: {:d}, \"\n               \"playView: {:d}, movCancelFlag: {:d})\\n\",\n               playMode, playModeEx, playView, movCancelFlag);\n\n  } else {\n    PopUint8(playView);\n    PopExpression(movCancelFlag);\n    ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n               \"STUB instruction PlayMovie(playMode: {:d}, playView: {:d}, \"\n               \"movCancelFlag: {:d})\\n\",\n               playMode, playView, movCancelFlag);\n  }\n}\nVmInstruction(InstSFDpause) {\n  StartInstruction;\n  PopUint8(paused);\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction SFDpause(paused: {:d})\\n\", paused);\n}\n\n}  // namespace Vm\n\n}  // namespace Impacto\n"
  },
  {
    "path": "src/vm/inst_movie.h",
    "content": "#pragma once\n\n#include \"vm.h\"\n\nnamespace Impacto {\n\nnamespace Vm {\n\nVmInstruction(InstPlayMovie);\nVmInstruction(InstPlayMovieOld);\nVmInstruction(InstPlayMovieOld2);\nVmInstruction(InstMovie);\nVmInstruction(InstMovieMain);\nVmInstruction(InstLoadMovie);\nVmInstruction(InstPlayMovieMemory);\nVmInstruction(InstSFDpause);\n\n}  // namespace Vm\n\n}  // namespace Impacto"
  },
  {
    "path": "src/vm/inst_sound.cpp",
    "content": "#include \"inst_sound.h\"\n\n#include \"inst_macros.inc\"\n\n#include <math.h>\n\n#include \"expression.h\"\n#include \"../game.h\"\n#include \"../mem.h\"\n#include \"../log.h\"\n#include \"../audio/audiosystem.h\"\n#include \"../audio/audiostream.h\"\n#include \"../audio/audiochannel.h\"\n#include \"../profile/scriptvars.h\"\n#include \"../profile/vm.h\"\n#include \"../data/savesystem.h\"\n\nnamespace Impacto {\n\nnamespace Vm {\n\nusing namespace Impacto::Profile::ScriptVars;\n\nVmInstruction(InstBGMplay) {\n  StartInstruction;\n  PopUint8(loop);\n  PopExpression(track);\n  if (loop == 2) {\n    PopExpression(unk);\n  }\n\n  if (ScrWork[SW_BGMREQNO] != track) {\n    ScrWork[SW_BGMREQNO] = track;\n    SaveSystem::SetBgmFlag(track, true);\n    Audio::Channels[Audio::AC_BGM0]->Play(\"bgm\", track, (bool)loop, 0.0f);\n  }\n}\nVmInstruction(InstBGMstop) {\n  StartInstruction;\n  PopUint8(channel);\n  Audio::Channels[Audio::AC_BGM0 + channel]->Stop(1.0f);\n  ScrWork[SW_BGMREQNO] = 0xFFFF;\n}\nVmInstruction(InstSEplay) {\n  StartInstruction;\n  PopUint8(channel);\n  PopUint8(type);\n\n  if (type != 2) {\n    PopExpression(effect);\n    PopExpression(loop);\n    if (loop) {\n      ScrWork[SW_SEREQNO + channel] = effect;\n    } else {\n      ScrWork[SW_SEREQNO + channel] = 0xFFFF;\n    }\n    Audio::Channels[Audio::AC_SE0 + channel]->SetVolume(\n        (ScrWork[SW_SEVOL + channel] / 100.0f) * 0.3f);\n\n    Audio::Channels[Audio::AC_SE0 + channel]->Play(\"se\", effect, (bool)loop,\n                                                   0.0f);\n    if (type == 1) {\n      Audio::Channels[Audio::AC_SE0 + channel]->Pause();\n    }\n  } else {\n    Audio::Channels[Audio::AC_SE0 + channel]->Resume();\n  }\n}\nVmInstruction(InstSEplayMO6) {\n  StartInstruction;\n  PopUint8(channel);\n  PopExpression(effect);\n  PopExpression(loop);\n  Audio::Channels[Audio::AC_SE0 + channel]->SetVolume(\n      (ScrWork[SW_SEVOL + channel] / 100.0f) * 0.3f);\n  Audio::Channels[Audio::AC_SE0 + channel]->Play(\"se\", effect, (bool)loop,\n                                                 0.0f);\n}\nVmInstruction(InstSEstop) {\n  StartInstruction;\n  PopUint8(channel);\n  Audio::Channels[Audio::AC_SE0 + channel]->Stop(1.0f);\n  ScrWork[SW_SEREQNO + channel] = 0xFFFF;\n}\nVmInstruction(InstSSEplay) {\n  StartInstruction;\n  if (Profile::Vm::GameInstructionSet == InstructionSet::CHN) {\n    PopUint8(channel);\n  }\n  PopExpression(sysSeId);\n  Audio::PlayInGroup(Audio::ACG_SE, \"sysse\", sysSeId, false, 0.0f);\n}\nVmInstruction(InstSSEstop) {\n  StartInstruction;\n  Audio::StopChannelGroup(Audio::ACG_SE, 1.0f);\n}\nVmInstruction(InstBGMflag) {\n  StartInstruction;\n  PopExpression(arg1);\n  SaveSystem::SetBgmFlag(arg1, true);\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction BGMflag(arg1: {:d})\\n\", arg1);\n}\nVmInstruction(InstVoicePlay) {\n  StartInstruction;\n  PopUint8(channel);\n  PopExpression(fileId);\n  PopExpression(loop);\n  Audio::Channels[Audio::AC_VOICE0 + channel]->Play(\"voice\", fileId, (bool)loop,\n                                                    0.0f);\n}\nVmInstruction(InstVoicePlayOld) {\n  StartInstruction;\n  PopUint8(channel);\n  PopExpression(fileId);\n  Audio::Channels[Audio::AC_VOICE0 + channel]->Play(\"voice\", fileId, false,\n                                                    0.0f);\n}\nVmInstruction(InstVoiceStop) {\n  StartInstruction;\n  PopUint8(channel);\n  Audio::Channels[Audio::AC_VOICE0 + channel]->Stop(0.0f);\n}\nVmInstruction(InstVoiceStopNew) {\n  StartInstruction;\n  PopUint8(channel);\n  PopExpression(fade);\n  Audio::Channels[Audio::AC_VOICE0 + channel]->Stop((float)fade);\n}\nVmInstruction(InstVoicePlayWait) {\n  StartInstruction;\n  PopUint8(channel);\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction VoicePlayWait(channel: {:d})\\n\", channel);\n}\nVmInstruction(InstBGMduelPlay) {\n  StartInstruction;\n  PopUint8(type);\n  switch (type) {\n    case 0: {\n      PopExpression(arg1);\n      PopExpression(arg2);\n      ImpLogSlow(\n          LogLevel::Warning, LogChannel::VMStub,\n          \"STUB instruction BGMduelPlay(type: {:d}, arg1: {:d}, arg2: {:d})\\n\",\n          type, arg1, arg2);\n    } break;\n    case 1: {\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction BGMduelPlay(type: {:d})\\n\", type);\n    } break;\n    case 2: {\n      PopExpression(arg1);\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction BGMduelPlay(type: {:d}, arg1: {:d})\\n\", type,\n                 arg1);\n    } break;\n    case 3: {\n      PopExpression(arg1);\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction BGMduelPlay(type: {:d}, arg1: {:d})\\n\", type,\n                 arg1);\n    } break;\n  }\n}\nVmInstruction(InstSNDpause) {\n  StartInstruction;\n  PopUint8(paused);\n\n  for (int i = 0; i < 6; i++) {\n    (paused) ? Audio::Channels[Audio::AC_SE0 + i]->Pause()\n             : Audio::Channels[Audio::AC_SE0 + i]->Resume();\n  }\n\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction SNDpause(paused: {:d})\\n\", paused);\n}\nVmInstruction(InstSEplayWait) {\n  StartInstruction;\n  PopUint8(channel);\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction SEplayWait(channel: {:d})\\n\", channel);\n}\nVmInstruction(InstResetSoundAll) {\n  StartInstruction;\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction ResetSoundAll()\\n\");\n}\nVmInstruction(InstSNDloadStop) {\n  StartInstruction;\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction SNDloadStop()\\n\");\n}\nVmInstruction(InstBGMstopWait) {\n  StartInstruction;\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction BGMstopWait()\\n\");\n}\nVmInstruction(InstSysVoicePlay) {\n  PopUint8(arg1);\n  PopExpression(arg2);\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction SysVoicePlay(arg1: {:d}, arg2: {:d})\\n\", arg1,\n             arg2);\n}\nVmInstruction(InstSysSeload) {\n  StartInstruction;\n  if (Profile::Vm::GameInstructionSet == InstructionSet::MO8 ||\n      Profile::Vm::GameInstructionSet == InstructionSet::CHN) {\n    PopUint8(arg1);\n  }\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction SysSeload()\\n\");\n}\n\n}  // namespace Vm\n\n}  // namespace Impacto"
  },
  {
    "path": "src/vm/inst_sound.h",
    "content": "#pragma once\n\n#include \"vm.h\"\n\nnamespace Impacto {\n\nnamespace Vm {\n\nVmInstruction(InstBGMplay);\nVmInstruction(InstBGMstop);\nVmInstruction(InstSEplay);\nVmInstruction(InstSEplayMO6);\nVmInstruction(InstSEstop);\nVmInstruction(InstSSEplay);\nVmInstruction(InstSSEstop);\nVmInstruction(InstBGMflag);\nVmInstruction(InstVoicePlay);\nVmInstruction(InstVoicePlayOld);\nVmInstruction(InstVoiceStop);\nVmInstruction(InstVoiceStopNew);\nVmInstruction(InstVoicePlayWait);\nVmInstruction(InstBGMduelPlay);\nVmInstruction(InstSNDpause);\nVmInstruction(InstSEplayWait);\nVmInstruction(InstResetSoundAll);\nVmInstruction(InstSNDloadStop);\nVmInstruction(InstBGMstopWait);\nVmInstruction(InstSysVoicePlay);\nVmInstruction(InstSysSeload);\n\n}  // namespace Vm\n\n}  // namespace Impacto"
  },
  {
    "path": "src/vm/inst_system.cpp",
    "content": "#include \"inst_system.h\"\n\n#include \"inst_macros.inc\"\n\n#include <math.h>\n#include <ctime>\n\n#include \"expression.h\"\n#include \"../profile/scriptvars.h\"\n#include \"../profile/scriptinput.h\"\n#include \"../mem.h\"\n#include \"../log.h\"\n#include \"../audio/audiosystem.h\"\n#include \"../audio/audiochannel.h\"\n#include \"../background2d.h\"\n#include \"../character2d.h\"\n#include \"../profile/vm.h\"\n#include \"../profile/dialogue.h\"\n#include \"../profile/hud/tipsnotification.h\"\n#include \"../profile/data/tipssystem.h\"\n#include \"../profile/ui/backlogmenu.h\"\n#include \"../hud/saveicondisplay.h\"\n#include \"interface/input.h\"\n#include \"../data/savesystem.h\"\n#include \"../data/achievementsystem.h\"\n#include \"../ui/ui.h\"\n#include \"../ui/gamespecific.h\"\n#include \"../voicetable.h\"\n\nnamespace Impacto {\n\nnamespace Vm {\n\nusing namespace Impacto::Profile::ScriptVars;\nusing namespace Impacto::Vm::Interface;\nusing Impacto::SaveSystem::SaveError;\n\nVmInstruction(InstDummy) {}\n\nVmInstruction(InstEnd) {\n  StartInstruction;\n  thread->Flags = TF_Destroy;\n  BlockThread;\n}\nVmInstruction(InstCreateThread) {\n  StartInstruction;\n  PopExpression(groupId);\n  PopExpression(scriptBufferId);\n  PopFarLabel(labelAdr, scriptBufferId);\n  Sc3VmThread* newThread = CreateThread(groupId);\n  newThread->GroupId = groupId;\n  newThread->ScriptBufferId = scriptBufferId;\n  newThread->IpOffset = labelAdr;\n  thread->ScriptParam = newThread->Id;\n  newThread->ScriptParam = thread->Id;\n  RunThread(newThread);\n  BlockCurrentScriptThread = false;\n}\nVmInstruction(InstKillThread) {\n  StartInstruction;\n  PopExpression(threadId);\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction KillThread(threadId: {:d})\\n\", threadId);\n}\nVmInstruction(InstReset) {\n  StartInstruction;\n  BlockThread;\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction Reset()\\n\");\n}\nVmInstruction(InstScriptLoad) {\n  StartInstruction;\n  PopExpression(bufferId);\n  PopExpression(scriptId);\n  if (Profile::Vm::UseMsbStrings) {\n    LoadMsb(bufferId, scriptId);\n    if (!Profile::Vm::UseSeparateMsbArchive) scriptId += 1;\n  }\n  LoadScript(bufferId, scriptId);\n}\nVmInstruction(InstWait) {\n  StartInstruction;\n  PopExpression(waitCycles);\n  if (thread->WaitCounter <= 0) {\n    thread->WaitCounter = waitCycles + 1;\n  }\n  thread->WaitCounter--;\n  if (thread->WaitCounter > 0) {\n    ResetInstruction;\n    BlockThread;\n  }\n}\nVmInstruction(InstHalt) {\n  StartInstruction;\n  thread->Flags |= TF_Pause;\n  BlockThread;\n  ResetInstruction;\n}\nVmInstruction(InstGetLabelAdr) {\n  StartInstruction;\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction GetLabelAdr(unknown)\\n\");\n}\nVmInstruction(InstFlagOnWait) {\n  StartInstruction;\n  PopUint8(checkVal);\n  PopExpression(flagId);\n  if (GetFlag(flagId) == (bool)checkVal) {\n    ResetInstruction;\n    BlockThread;\n  }\n}\nVmInstruction(InstSetFlag) {\n  StartInstruction;\n  PopExpression(flagId);\n  SetFlag(flagId, 1);\n}\nVmInstruction(InstResetFlag) {\n  StartInstruction;\n  PopExpression(flagId);\n  SetFlag(flagId, 0);\n}\nVmInstruction(InstCopyFlag) {\n  StartInstruction;\n  PopExpression(flagIdSrc);\n  PopExpression(flagIdDst);\n  bool flagValSrc = GetFlag(flagIdSrc);\n  SetFlag(flagIdDst, flagValSrc);\n}\nVmInstruction(InstKeyWait) {\n  StartInstruction;\n  PopUint8(type);\n  PopExpression(buttonsArg);\n  PopExpression(downTypeId);\n\n  const Interface::InputDownType downType =\n      static_cast<Interface::InputDownType>(downTypeId);\n\n  const bool inputResult = [&]() -> bool {\n    const uint32_t bitfield = (type & 2) == 0 ? buttonsArg\n                              : buttonsArg < std::ssize(PADcustom)\n                                  ? PADcustom[buttonsArg]\n                                  : 0;\n\n    uint32_t padInputDown = GetPadInputButtonDown(downType);\n    if (downType == InputDownType::IsDown) {\n      padInputDown |= PADinputMouseIsDown;\n    } else if (downType == InputDownType::WentDown) {\n      padInputDown |= PADinputMouseWentDown;\n    }\n\n    return bitfield & padInputDown;\n  }();\n\n  if (!inputResult) {\n    ResetInstruction;\n    BlockThread;\n  }\n}\nVmInstruction(InstKeyWaitTimer) {\n  StartInstruction;\n  PopUint8(type);\n  PopExpression(timer);\n  PopExpression(buttonsArg);\n  PopExpression(downTypeId);\n\n  if (thread->WaitCounter <= 0) {\n    thread->WaitCounter = timer + 1;\n  } else {\n    thread->WaitCounter--;\n  }\n  const Interface::InputDownType downType =\n      static_cast<Interface::InputDownType>(downTypeId);\n  const bool inputResult = [&]() -> bool {\n    const uint32_t bitfield = (type & 2) == 0 ? buttonsArg\n                              : buttonsArg < std::ssize(PADcustom)\n                                  ? PADcustom[buttonsArg]\n                                  : 0;\n\n    uint32_t padInputDown = GetPadInputButtonDown(downType);\n    if (downType == InputDownType::IsDown) {\n      padInputDown |= PADinputMouseIsDown;\n    } else if (downType == InputDownType::WentDown) {\n      padInputDown |= PADinputMouseWentDown;\n    }\n\n    return bitfield & padInputDown;\n  }();\n\n  if (inputResult) {\n    thread->WaitCounter = 0;\n    return;\n  }\n\n  if (thread->WaitCounter > 0) {\n    ResetInstruction;\n    BlockThread;\n  }\n}\nVmInstruction(InstMemberWrite) {\n  StartInstruction;\n  if (noExpressions) {\n    PopUint8(index);\n    void* thdElement = thread->GetMemberPointer(index);\n    uint8_t* immValue = thread->GetIp();\n    int value = immValue[0] + (immValue[1] << 8) + (immValue[2] << 16) +\n                (immValue[3] << 24);\n    thread->IpOffset += 4;\n    UnalignedWrite<uint32_t>(thdElement, value);\n  } else {\n    PopExpression(index);\n    void* thdElement = thread->GetMemberPointer(index);\n    PopExpression(value);\n    UnalignedWrite<uint32_t>(thdElement, value);\n  }\n}\nVmInstruction(InstThreadControl) {\n  StartInstruction;\n  PopExpression(groupId);\n  PopExpression(controlType);\n  ControlThreadGroup((ThreadGroupControlType)controlType, groupId);\n}\nVmInstruction(InstGetSelfPointer) {\n  StartInstruction;\n  thread->ScriptParam = thread->Id ^ 0x80000000;\n}\nVmInstruction(InstVsync) {\n  StartInstruction;\n  BlockThread;\n}\nVmInstruction(InstTest) {\n  StartInstruction;\n  PopExpression(testVal);\n}\nVmInstruction(InstThreadControlStore) {\n  StartInstruction;\n  PopUint8(type);\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction ThreadControlStore(type: {:d})\\n\", type);\n}\nVmInstruction(InstPadAct) {\n  StartInstruction;\n  PopExpression(unused);\n  PopExpression(vib1);\n  PopExpression(vib2);\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction PadAct(unused: {:d}, vib1: {:d}, vib2: {:d})\\n\",\n             unused, vib1, vib2);\n}\nVmInstruction(InstCopyThreadWork) {\n  StartInstruction;\n  PopExpression(threadIdSrc);\n  PopExpression(threadIdDst);\n  PopExpression(beginIndex);\n  PopExpression(count);\n  Sc3VmThread *srcThread, *dstThread;\n  srcThread = threadIdSrc == 0 ? thread : &ThreadPool[threadIdSrc];\n  dstThread = threadIdDst == 0 ? thread : &ThreadPool[threadIdDst];\n\n  for (int i = 0; i < count; i++) {\n    dstThread->Variables[beginIndex + i] = srcThread->Variables[beginIndex + i];\n  }\n}\n\nVmInstruction(InstSave) {\n  StartInstruction;\n  PopUint8(type);\n  switch (type) {  // TODO: Types 1, 2, 3, 6, 7, 8, 9, 10, 11, 12, 19, 40,\n                   // 41, 45, 46, 47, 50, 51, 52, 53, 71, 88, 89, 150, 151\n    case 0: {\n      SaveSystem::SaveSystemData();\n      break;\n    }\n    case 4: {\n      ScrWork[SW_SAVEERRORCODE] =\n          static_cast<int>(SaveSystem::LoadSystemData());\n\n      if (ScrWork[SW_SAVEERRORCODE] == static_cast<int>(SaveError::OK)) {\n        TipsSystem::UpdateTipRecords();\n      }\n\n      break;\n    }\n    case 5:  // NOOP by design\n      break;\n    case 16: {\n      SaveSystem::FlushWorkingSaveEntry(SaveSystem::SaveType::Full,\n                                        ScrWork[SW_SAVEFILENO]);\n      SaveSystem::SaveThumbnailData();\n\n      if (UI::SaveMenuPtr) UI::SaveMenuPtr->RefreshCurrentEntryInfo();\n      break;\n    }\n    case 30: {\n      SetFlag(SF_SAVEICON, true);\n      SaveSystem::WriteSaveFile();\n      break;\n    }\n    case 31: {\n      if (SaveSystem::GetLoadStatus() == LoadStatus::Loading) {\n        ResetInstruction;\n        BlockThread;\n      } else {\n        SetFlag(SF_SAVEICON, false);\n      }\n      break;\n    }\n    case 40: {  // SystemDataCheck\n      if (Profile::Vm::GameInstructionSet == InstructionSet::RNE) {\n        PopExpression(unused1);\n        PopExpression(unused2);\n        PopExpression(unused3);\n        PopExpression(unused4);\n      }\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction Save(type: {:d})\\n\", type);\n      break;\n    }\n    case 60: {\n      SetFlag(SF_SAVEICON, true);\n      SaveSystem::WriteSaveFile();\n      break;\n    }\n    case 61: {\n      if (SaveSystem::GetLoadStatus() == LoadStatus::Loading) {\n        ResetInstruction;\n        BlockThread;\n      } else {\n        SetFlag(SF_SAVEICON, false);\n      }\n      break;\n    }\n    case 70: {\n      SetFlag(SF_SAVEICON, true);\n      SaveSystem::MountSaveFile();\n      break;\n    }\n    case 71: {\n      if (SaveSystem::GetLoadStatus() == LoadStatus::Loading) {\n        ResetInstruction;\n        BlockThread;\n        break;\n      }\n\n      SetFlag(SF_SAVEICON, false);\n      break;\n    }\n    case 80: {\n      SetFlag(SF_SAVEICON, true);\n      SaveSystem::CheckSaveFile();\n      break;\n    }\n    case 81: {  // SystemDataCheck\n      if (SaveSystem::GetLoadStatus() == LoadStatus::Loading) {\n        ResetInstruction;\n        BlockThread;\n      } else {\n        SetFlag(SF_SAVEICON, false);\n      }\n      break;\n    }\n    case 86:  // NOOP by design\n    case 87:  // NOOP by design\n      break;\n    case 1:\n    case 2:\n    case 3:\n    case 6:\n    case 7:\n    case 8:\n    case 9:\n    case 10:\n    case 11:\n    case 12:\n    case 19:\n    case 41:\n    case 45:\n    case 46:\n    case 47:\n    case 50:\n    case 51:\n    case 52:\n    case 53:\n    case 88:\n    case 89:\n    case 150:\n    case 151:\n      break;\n    default: {\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction Save(type: {:d})\\n\", type);\n      break;\n    }\n  }\n}\nVmInstruction(InstSaveOld) {\n  StartInstruction;\n  PopUint8(type);\n  switch (type) {\n    // TODO: Types 1, 3, 4, 5, 10, 11, 12, 20, 21, 33, 36, 37, 52, 53\n    case 0: {\n      SaveSystem::SaveSystemData();\n      break;\n    }\n    case 2: {\n      ScrWork[SW_SAVEERRORCODE] =\n          static_cast<int>(SaveSystem::LoadSystemData());\n\n      if (ScrWork[SW_SAVEERRORCODE] == static_cast<int>(SaveError::OK)) {\n        TipsSystem::UpdateTipRecords();\n      }\n\n      break;\n    }\n    case 13: {\n      SaveSystem::SaveSystemData();\n      break;\n    }\n    case 16: {\n      SaveSystem::FlushWorkingSaveEntry(SaveSystem::SaveType::Full,\n                                        ScrWork[SW_SAVEFILENO]);\n      SaveSystem::SaveThumbnailData();\n      break;\n    }\n    case 30: {\n      SetFlag(SF_SAVEICON, true);\n      SaveSystem::WriteSaveFile();\n      break;\n    }\n    case 31: {\n      if (SaveSystem::GetLoadStatus() == LoadStatus::Loading) {\n        ResetInstruction;\n        BlockThread;\n      } else {\n        SetFlag(SF_SAVEICON, false);\n      }\n      break;\n    }\n    case 32: {\n      SetFlag(SF_SAVEICON, true);\n      SaveSystem::MountSaveFile();\n      break;\n    }\n    case 33: {\n      if (SaveSystem::GetLoadStatus() == LoadStatus::Loading) {\n        ResetInstruction;\n        BlockThread;\n        break;\n      }\n\n      SetFlag(SF_SAVEICON, false);\n      break;\n    }\n    case 34: {\n      SetFlag(SF_SAVEICON, true);\n      SaveSystem::CheckSaveFile();\n      break;\n    }\n    case 35: {\n      if (SaveSystem::GetLoadStatus() == LoadStatus::Loading) {\n        ResetInstruction;\n        BlockThread;\n      } else {\n        SetFlag(SF_SAVEICON, false);\n      }\n      break;\n    }\n    case 50: {\n      SetFlag(SF_SAVEICON, true);\n      AchievementSystem::MountAchievementFile();\n      break;\n    }\n    case 51: {\n      if (AchievementSystem::GetLoadStatus() == LoadStatus::Loading) {\n        ResetInstruction;\n        BlockThread;\n      } else {\n        SetFlag(SF_SAVEICON, false);\n      }\n      break;\n    }\n    case 1:\n    case 3:\n    case 4:\n    case 5:\n    case 10:\n    case 11:\n    case 12:\n    case 20:\n    case 21:\n    case 36:\n    case 37:\n    case 52:\n    case 53:\n      break;\n    default: {\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction SaveOld(type: {:d})\\n\", type);\n      break;\n    }\n  }\n}\nVmInstruction(InstSaveIconLoad) {\n  StartInstruction;\n  PopExpression(arg1);\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction SaveIconLoad(arg1: {:d})\\n\", arg1);\n}\nVmInstruction(InstVoiceTableLoadMaybe) {\n  StartInstruction;\n  PopExpression(fileId);\n\n  switch (VoiceTableData.Status) {\n    case LoadStatus::Unloaded:\n      VoiceTableData.LoadAsync(fileId);\n      ResetInstruction;\n      BlockThread;\n      break;\n    case LoadStatus::Loading:\n      ResetInstruction;\n      BlockThread;\n      break;\n    case LoadStatus::Loaded:\n      break;\n  }\n}\nVmInstruction(InstSetPadCustom) {\n  StartInstruction;\n  Interface::UpdatePADcustomType(Profile::ConfigSystem::ControllerType);\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction SetPadCustom()\\n\");\n}\nVmInstruction(InstMwait) {\n  StartInstruction;\n  PopExpression(waitCycles);\n  PopExpression(unused);\n  if (thread->WaitCounter <= 0) {\n    thread->WaitCounter = waitCycles + 1;\n  }\n  thread->WaitCounter--;\n  // This wait is ignored if skip mode is enabled\n  if (GetFlag(SF_MESALLSKIP)) {\n    thread->WaitCounter = 0;\n  }\n  if (thread->WaitCounter > 0) {\n    ResetInstruction;\n    BlockThread;\n  }\n}\nVmInstruction(InstTerminate) {\n  StartInstruction;\n\n  ImpLog(LogLevel::Info, LogChannel::VM, \"VM requested shutdown!\\n\");\n  Window->Shutdown();\n  // BlockThread;\n  // ResetInstruction;\n}\nVmInstruction(InstDebugPrint) {\n  StartInstruction;\n  PopExpression(arg1);\n  PopExpression(arg2);\n  ImpLogSlow(LogLevel::Debug, LogChannel::VM,\n             \"DebugPrint(arg1: {:d}, arg2: {:d})\\n\", arg1, arg2);\n}\nVmInstruction(InstSystemMes) {\n  StartInstruction;\n  PopUint8(mode);\n  switch (Profile::Vm::GameInstructionSet) {\n    default:\n      break;\n    case InstructionSet::Dash:\n    case InstructionSet::CC:\n      PopUint8(unk01);\n      break;\n  }\n\n  switch (mode) {\n    case 0:  // SystemMesInit0\n    case 1:  // SystemMesInit1\n      UI::SysMesBoxPtr->Init();\n      break;\n    case 2: {  // SystemMesInit2\n      PopExpression(sysMesInit2Arg);\n      ScrWork[SW_SYSMESANIMCTF] = 2 * UI::SysMesBoxPtr->MessageCount + 33;\n    } break;\n    case 3: {  // SystemMesSetMes\n      PopUint16(sysMesStrNum);\n      const uint32_t message =\n          ScriptGetStrAddress(thread->ScriptBufferId, sysMesStrNum);\n      UI::SysMesBoxPtr->AddMessage(\n          {.ScriptBufferId = thread->ScriptBufferId, .IpOffset = message});\n    } break;\n    case 4: {  // SystemMesSetSel\n      PopUint16(sysSelStrNum);\n      auto message = ScriptGetStrAddress(thread->ScriptBufferId, sysSelStrNum);\n      UI::SysMesBoxPtr->AddChoice(\n          {.ScriptBufferId = thread->ScriptBufferId, .IpOffset = message});\n    } break;\n    case 5:  // SystemMesMain\n      if (!UI::SysMesBoxPtr->ChoiceMade &&\n          (UI::SysMesBoxPtr->ChoiceCount > 0)) {\n        ResetInstruction;\n        BlockThread;\n      } else if (UI::SysMesBoxPtr->ChoiceCount == 0 &&\n                 !(Interface::PADinputButtonWentDown & Interface::PAD1A ||\n                   Interface::PADinputMouseWentDown & Interface::PAD1A)) {\n        ResetInstruction;\n        BlockThread;\n      }\n      break;\n    case 6:  // SystemMesFadeIn\n      if (UI::SysMesBoxPtr->State == UI::MenuState::Hidden) {\n        UI::SysMesBoxPtr->Show();\n        ResetInstruction;\n        BlockThread;\n      } else if (UI::SysMesBoxPtr->State != UI::MenuState::Shown) {\n        ResetInstruction;\n        BlockThread;\n      }\n      break;\n    case 7:  // SystemMesFadeOut\n      if (UI::SysMesBoxPtr->State == UI::MenuState::Shown) {\n        UI::SysMesBoxPtr->Hide();\n        ResetInstruction;\n        BlockThread;\n      } else if (UI::SysMesBoxPtr->State != UI::MenuState::Hidden) {\n        ResetInstruction;\n        BlockThread;\n      }\n      break;\n    case 8:\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction SystemMes(mode: {:d})\\n\", mode);\n      break;\n    case 20:\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction SystemMes(mode: {:d})\\n\", mode);\n      break;\n    case 0x83: {\n      PopMsbString(message);\n      UI::SysMesBoxPtr->AddMessage(\n          {.ScriptBufferId = thread->ScriptBufferId, .IpOffset = message});\n    } break;\n    case 0x84: {  // SystemMesSetSel\n      PopMsbString(message);\n      UI::SysMesBoxPtr->AddChoice(\n          {.ScriptBufferId = thread->ScriptBufferId, .IpOffset = message});\n    } break;\n    default:\n      ImpLog(LogLevel::Warning, LogChannel::VMStub,\n             \"Unknown mode for instruction SystemMes(mode: {:d})\\n\", mode);\n      break;\n  }\n}\nVmInstruction(InstGetNowTime) {\n  StartInstruction;\n  const tm dateTime = CurrentDateTime();\n  ScrWork[SW_TIMEYEAR] = dateTime.tm_year + 1900;\n  ScrWork[SW_TIMEMONTH] = dateTime.tm_mon + 1;\n  ScrWork[SW_TIMEDAY] = dateTime.tm_mday;\n  ScrWork[SW_TIMEHOUR] = dateTime.tm_hour;\n  ScrWork[SW_TIMEMINUTE] = dateTime.tm_min;\n  ScrWork[SW_TIMESECOND] = dateTime.tm_sec;\n  ScrWork[SW_TIMEWEEK] = dateTime.tm_wday;\n}\nVmInstruction(InstGetSystemStatus) {\n  StartInstruction;\n  PopExpression(type);\n  switch (type) {\n    case 1: {  // SYSSTAT_VOICEPLAY (Note, called voiceplay, but actually\n               // !voiceplay...)\n      thread->ScriptParam =\n          Audio::Channels[Audio::AC_VOICE0]->GetState() != Audio::ACS_Playing;\n      break;\n    }\n    case 5: {  // SYSSTAT_SKIP\n      thread->ScriptParam = GetFlag(SF_MESALLSKIP);\n    } break;\n    case 10: {  // Auto mode bit\n      thread->ScriptParam = AutoModeEnabled;\n    } break;\n    case 11: {\n      thread->ScriptParam = 1;\n      break;\n    }\n  }\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction GetSystemStatus(type: {:d})\\n\", type);\n}\nVmInstruction(InstReboot) {\n  StartInstruction;\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction Reboot()\\n\");\n  BlockThread;\n}\nVmInstruction(InstReloadScript) {\n  StartInstruction;\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction ReloadScript()\\n\");\n  BlockThread;\n  ResetInstruction;\n}\nVmInstruction(InstReloadScriptMenu) {\n  StartInstruction;\n  PopUint8(arg1);\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction ReloadScriptMenu(arg1: {:d})\\n\", arg1);\n}\nVmInstruction(InstDebugEditer) {\n  StartInstruction;\n  PopUint8(arg1);\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction DebugEditer(arg1: {:d})\\n\", arg1);\n}\nVmInstruction(InstPadActEx) {\n  StartInstruction;\n  PopExpression(arg1);\n  PopExpression(arg2);\n  PopExpression(arg3);\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction PadActEx(arg1: {:d}, arg2: {:d}, arg3: \"\n             \"{:d})\\n\",\n             arg1, arg2, arg3);\n}\nVmInstruction(InstDebugSetup) {\n  StartInstruction;\n  PopExpression(arg1);\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction DebugSetup(arg1: {:d})\\n\", arg1);\n}\nVmInstruction(InstGlobalSystemMessage) {\n  StartInstruction;\n  PopUint8(type);\n  switch (type) {\n    case 0: {\n      PopExpression(alpha);\n      // TODO: set GlobalSystemMessageCur to 255\n      ScrWork[SW_SYSMESALPHA] = alpha;\n    } break;\n    case 1:\n      ScrWork[SW_SYSMESANIMCTCUR] += 8;\n      break;\n    case 2:\n      ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                 \"STUB instruction GlobalSystemMessage(type: 2)\\n\");\n      break;\n    case 3:\n      ScrWork[SW_SYSMESANIMCTCUR] -= 8;\n      if (!ScrWork[SW_SYSMESANIMCTCUR]) ScrWork[3370] = 255;\n      break;\n  }\n}\n// TODO find the value ranges for atan2\nVmInstruction(InstCalc) {\n  StartInstruction;\n  PopUint8(type);\n  switch (type) {\n    case 0: {  // CalcSin\n      PopExpression(dest);\n      PopExpression(angle);\n      ScrWork[dest] =\n          (int)(std::sin(angle / (0x10000 / (2.0 * std::numbers::pi))) *\n                0x10000);\n    } break;\n    case 1: {  // CalcCos\n      PopExpression(dest);\n      PopExpression(angle);\n      ScrWork[dest] =\n          (int)(std::cos(angle / (0x10000 / (2.0 * std::numbers::pi))) *\n                0x10000);\n    } break;\n    case 2: {  // CalcAtan2\n      PopExpression(dest);\n      PopExpression(x);\n      PopExpression(y);\n      ScrWork[dest] = (int)(0x4000 * std::atan2(y, x) / std::numbers::pi);\n    } break;\n    case 3: {  // CalcSinL\n      PopExpression(dest);\n      PopExpression(base);\n      PopExpression(angle);\n      PopExpression(offset);\n      ScrWork[dest] =\n          offset +\n          base * (int)(std::sin(angle / (0x10000 / (2.0 * std::numbers::pi))) *\n                       0x10000);\n    } break;\n    case 4: {  // CalcCosL\n      PopExpression(dest);\n      PopExpression(base);\n      PopExpression(angle);\n      PopExpression(offset);\n      ScrWork[dest] =\n          offset +\n          base * (int)(std::cos(angle / (0x10000 / (2.0 * std::numbers::pi))) *\n                       0x10000);\n    } break;\n    case 5: {  // CalcRound\n      PopExpression(dest);\n      PopExpression(value);\n      PopExpression(multiplier);\n      PopExpression(divider);\n      ScrWork[dest] =\n          (int)(((((multiplier * value) * 10.0) / divider) + 5.0) / 10.0);\n    } break;\n    case 6: {  // CalcAccel\n      PopExpression(dest);\n      PopExpression(x);\n      PopExpression(a);\n      PopExpression(b);\n      if (b >= 2) {\n        ScrWork[dest] = (20 * a * x / b + 5 - 10 * a * a * x / b / b) / 10;\n      } else {\n        ScrWork[dest] = x;\n      }\n    } break;\n  }\n}\nVmInstruction(InstMSinit) {\n  StartInstruction;\n  PopExpression(\n      initType);  // TODO: There's only one type in R;NE - initType <= 10\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction MSinit(initType: {:d})\\n\", initType);\n  SaveSystem::SetCheckpointId(0xffff);\n  if (initType == 0) {\n    if (Profile::Vm::GameInstructionSet == InstructionSet::MO6TW ||\n        Profile::Vm::GameInstructionSet == InstructionSet::CHLCC) {\n      memset(&FlagWork, 0, 500);\n      memset(&ScrWork, 0, 24000);\n    } else if (Profile::Vm::GameInstructionSet == InstructionSet::CC) {\n      memset(&FlagWork, 0, 1000);\n      memset(&ScrWork, 0, 32000);\n    }\n\n    ScrWork[SW_SYSMESALPHA] = 255;\n\n    ScrWork[SW_PLATFORM] = Profile::PlatformId;\n  }\n\n  if (initType == 0 || initType == 1) {\n    ScrWork[SW_GAMESTATE] = 0;\n    for (int i = 0; i < std::ssize(DialoguePages); i++) {\n      DialoguePages[i].Clear();\n      DialoguePages[i].FadeAnimation.Progress = 0;\n      SetFlag(i + SF_MESWINDOW0OPENFL, 0);\n    }\n\n    for (int i = 0; i < std::ssize(Backgrounds); i++) {\n      size_t offset = Profile::Vm::ScrWorkBgStructSize * i;\n      ScrWork[SW_BG1POSX + offset] = 0;\n      ScrWork[SW_BG1POSY + offset] = 0;\n      ScrWork[SW_BG1SX + offset] = 0;\n      ScrWork[SW_BG1SY + offset] = 0;\n      ScrWork[SW_BG1SIZE + offset] = 0;\n      ScrWork[SW_BG1LX + offset] = 0;\n      ScrWork[SW_BG1LY + offset] = 0;\n      ScrWork[SW_BG1NO + offset] = 0xffff;\n      ScrWork[SW_BG1PRI + offset] = 0;\n      ScrWork[SW_BG1DISPMODE + offset] = 0;\n      ScrWork[SW_BG1FADECT + offset] = 0;\n      ScrWork[SW_BG1FADETYPE + offset] = 0;\n      ScrWork[SW_BG1ROTZ + offset] = 0;\n      ScrWork[SW_BG1ALPHA + offset] = 256;\n      ScrWork[SW_BG1MASKNO + offset] = 0;\n      ScrWork[SW_BG1MASKFADERANGE + offset] = 0;\n\n      if (Profile::Vm::ScrWorkBgEffStructSize > 20) {\n        ScrWork[SW_BG1PRI2 + offset] = 0xffff;\n        ScrWork[SW_BG1FILTER + offset] = 0xffffff;\n\n        ScrWork[SW_BG1CLIP_X + offset] = 1280;\n        ScrWork[SW_BG1CLIP_Y + offset] = 720;\n      } else {\n        ScrWork[SW_BG1CLIP_X + 2 * i] = 1280;\n        ScrWork[SW_BG1CLIP_Y + 2 * i] = 720;\n      }\n\n      ScrWork[SW_BG1SURF + i] = i;\n      SetFlag(SF_BG1DISP + i, false);\n    }\n  }\n\n  if (initType == 1) {\n    Profile::TipsNotification::CreateInstance();\n    UI::GameSpecific::Init();\n    SetFlag(SF_MESREVDISABLE, false);\n    ScrWork[SW_BGLINK] = 0;\n    ScrWork[SW_BGLINK2] = 0;\n  }\n\n  if (initType == 5) {\n    UI::BacklogMenuPtr->Clear();\n  }\n\n  if (initType == 10) {\n    UI::GameSpecific::Init();\n\n    // Technically not done here in the MAGES. engine, but we have to do it\n    // *somewhere* on boot...\n    Profile::TipsNotification::CreateInstance();\n  }\n\n  if (initType == 2) {\n    UI::BacklogMenuPtr->Clear();\n    memset(&FlagWork, 0, 100);\n    memset(&FlagWork[150], 0, 75);\n    memset(&FlagWork[300], 0, 100);\n\n    if (Profile::Vm::GameInstructionSet == InstructionSet::MO6TW ||\n        Profile::Vm::GameInstructionSet == InstructionSet::CHLCC) {\n      std::fill(ScrWork.begin(), ScrWork.begin() + 600, 0);\n      std::fill(ScrWork.begin() + 1000, ScrWork.begin() + 1700, 0);\n      std::fill(ScrWork.begin() + 2300, ScrWork.begin() + 2320, 0);\n      std::fill(ScrWork.begin() + 2328, ScrWork.begin() + 3600, 0);\n    } else {\n      std::fill(ScrWork.begin(), ScrWork.begin() + 1600, 0);\n      std::fill(ScrWork.begin() + 2100, ScrWork.begin() + 3300, 0);\n      std::fill(ScrWork.begin() + 4347, ScrWork.begin() + 7300, 0);\n    }\n  }\n\n  for (int i = 0; i < std::ssize(Backgrounds); i++) {\n    ScrWork[SW_BG1SURF + i] = i;\n    ScrWork[SW_BG1ALPHA + Profile::Vm::ScrWorkBgStructSize * i] = 256;\n    ScrWork[SW_BG1NO + Profile::Vm::ScrWorkBgStructSize * i] = 0xFFFF;\n    ScrWork[SW_BG1FILTER + Profile::Vm::ScrWorkBgStructSize * i] = 0xFFFFFF;\n  }\n  for (int i = 0; i < std::ssize(Characters2D); i++) {\n    ScrWork[SW_CHA1SURF + i] = i;\n    ScrWork[SW_CHA1ALPHA + Profile::Vm::ScrWorkChaStructSize * i] = 256;\n  }\n\n  if (Profile::Dialogue::HasSpeakerPortraits) {\n    for (int i = 0; i < std::ssize(SpeakerPortraits); i++) {\n      ScrWork[SW_FACE1SURF + i] = i;\n    }\n  }\n\n  ScrWork[SW_MESWINDOW_COLOR] = 0xFFFFFF;\n  ScrWork[SW_BGMREQNO] = 0xFFFF;\n  ScrWork[SW_SEREQNO] = 0xFFFF;\n  ScrWork[SW_SEREQNO + 1] = 0xFFFF;\n  ScrWork[SW_SEREQNO + 2] = 0xFFFF;\n  ScrWork[SW_BGMVOL] = 100;\n  ScrWork[SW_SEVOL] = 100;\n  ScrWork[SW_SEVOL + 1] = 100;\n  ScrWork[SW_SEVOL + 2] = 100;\n  ScrWork[SW_MOVIEALPHA] = 256;\n\n  if (Profile::Vm::GameInstructionSet == InstructionSet::RNE ||\n      Profile::Vm::GameInstructionSet == InstructionSet::Dash) {\n    ScrWork[SW_IRUOCAMERAHFOVCUR] = 40000;\n    ScrWork[SW_MAINCAMERAHFOVCUR] = 40000;\n    ScrWork[SW_AR_POSX] = 640;\n    ScrWork[SW_AR_POSY] = 360;\n    // Hack for now\n    ScrWork[SW_AR_ELVMIN] = -14674;\n    ScrWork[SW_AR_ELVMAX] = 13974;\n    ScrWork[SW_AR_ROTMIN] = -19588;\n    ScrWork[SW_AR_ROTMAX] = 19088;\n  }\n\n  if (Profile::Vm::GameInstructionSet == InstructionSet::MO7) {\n    ScrWork[SW_SHORTCUT] = 0xFFFF;\n  }\n\n  if (Profile::Vm::GameInstructionSet == InstructionSet::CHLCC) {\n    ScrWork[SW_INTROVOICE] = 999;\n  }\n\n  ScrWork[SW_SINSTALL_ALL] = 100;\n  SkipModeEnabled = false;\n  AutoModeEnabled = false;\n}\nVmInstruction(InstSaveSlot) {\n  StartInstruction;\n  PopExpression(arg1);\n  PopExpression(arg2);\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction SaveSlot(arg1: {:d}, arg2: {:d})\\n\", arg1, arg2);\n}\nVmInstruction(InstSystemMain) {\n  StartInstruction;\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction SystemMain()\\n\");\n}\nVmInstruction(InstGameInfoInit) {\n  StartInstruction;\n  PopUint8(arg1);\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction GameInfoInit(arg1: {:d})\\n\", arg1);\n}\nVmInstruction(InstSystemDataReset) {\n  StartInstruction;\n  PopUint8(arg1);\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction SystemDataReset(arg1: {:d})\\n\", arg1);\n}\nVmInstruction(InstDebugData) {\n  StartInstruction;\n  PopUint8(arg1);\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction DebugData(arg1: {:d})\\n\", arg1);\n}\nVmInstruction(InstAutoSave) {\n  using namespace Profile::ConfigSystem;\n  const auto quickSave = [](const int saveType) {\n    auto quicksaveEntries = SaveSystem::GetQuickSaveOpenSlot();\n    if (quicksaveEntries) {\n      SaveIconDisplay::ShowFor(2.4f);\n      SaveSystem::FlushWorkingSaveEntry(SaveSystem::SaveType::Quick,\n                                        *quicksaveEntries, saveType);\n      SaveSystem::SaveThumbnailData();\n    }\n  };\n\n  StartInstruction;\n\n  uint8_t autoQuickSave = AutoQuickSave;\n  if (GetFlag(SF_SAVEDISABLE)) autoQuickSave = 0;\n\n  PopUint8(type);\n  switch (type) {\n    case 0: {  // QuickSave\n      if (ScrWork[SW_TITLE] == 0xffff) break;\n\n      SaveSystem::SaveMemory();\n\n      if (ScrWork[SW_AUTOSAVERESTART] != 1 &&\n          (autoQuickSave & +AutoQuickSaveType::OnScene)) {\n        quickSave(1);\n      }\n\n      SetFlag(SF_AUTOSAVEENABLE, 1);\n      ScrWork[SW_AUTOSAVERESTART] = 0;\n    } break;\n\n    case 1: {  // AutoSaveRestart (?)\n      if (ScrWork[SW_TITLE] == 0xffff) break;\n\n      SaveSystem::SaveMemory();\n\n      if (ScrWork[SW_AUTOSAVERESTART] != 3 &&\n          (autoQuickSave & +AutoQuickSaveType::OnTrigger)) {\n        quickSave(3);\n      }\n\n      SetFlag(SF_AUTOSAVEENABLE, 1);\n      ScrWork[SW_AUTOSAVERESTART] = 0;\n    } break;\n\n    case 3: {  // DisableAutoSave\n      auto quicksaveEntries = SaveSystem::GetQuickSaveOpenSlot();\n      if (quicksaveEntries.has_value()) {\n        SaveIconDisplay::ShowFor(2.4f);\n        SaveSystem::FlushWorkingSaveEntry(SaveSystem::SaveType::Quick,\n                                          *quicksaveEntries, 0);\n        SaveSystem::SaveThumbnailData();\n      }\n      SaveSystem::SetQSavedOnCurrentLine(true);\n      SetFlag(SF_AUTOSAVEENABLE, 0);\n    } break;\n\n    case 4: {\n      SaveSystem::SaveMemory();\n\n      if (ScrWork[SW_AUTOSAVERESTART] != 4 &&\n          (autoQuickSave & +AutoQuickSaveType::OnTrigger)) {\n        quickSave(4);\n      }\n\n      SetFlag(SF_AUTOSAVEENABLE, 1);\n      ScrWork[SW_AUTOSAVERESTART] = 0;\n    } break;\n\n    case 5: {  // EnableAutoSave\n      if (ScrWork[SW_TITLE] == 0xffff) break;\n\n      SaveSystem::SaveMemory();\n\n      SetFlag(SF_AUTOSAVEENABLE, 1);\n      ScrWork[SW_AUTOSAVERESTART] = 0;\n    } break;\n\n    case 10: {  // SetCheckpointId\n      if (Profile::Vm::UseReturnIds) {\n        PopUint16(checkpointId);\n        SaveSystem::SetCheckpointId(checkpointId);\n        ImpLogSlow(\n            LogLevel::Warning, LogChannel::VMStub,\n            \"STUB instruction AutoSave(type: {:d}, checkpointId: {:d})\\n\", type,\n            checkpointId);\n      } else {\n        ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n                   \"STUB instruction AutoSave(type: {:d})\\n\", type);\n      }\n    } break;\n\n    case 20: {\n      if (ScrWork[SW_TITLE] == 0xffff) break;\n\n      if (ScrWork[SW_AUTOSAVERESTART] != 1 &&\n          (autoQuickSave & +AutoQuickSaveType::OnScene)) {\n        quickSave(1);\n      }\n\n      ScrWork[SW_AUTOSAVERESTART] = 0;\n    } break;\n\n    case 21: {\n      if (ScrWork[SW_TITLE] == 0xffff) break;\n\n      if (ScrWork[SW_AUTOSAVERESTART] != 3 &&\n          (autoQuickSave & +AutoQuickSaveType::OnTrigger)) {\n        quickSave(3);\n      }\n\n      ScrWork[SW_AUTOSAVERESTART] = 0;\n    } break;\n\n    case 0xff: {\n      SetFlag(SF_SAVECAPTURE, 1);\n      BlockThread;\n    } break;\n\n    case 2:\n    default:\n      ImpLog(LogLevel::Warning, LogChannel::VM,\n             \"Unimplemented InstAutoSave type {:d}\\n\", type);\n      break;\n  }\n}\nVmInstruction(InstAutoSaveOld) {\n  using namespace Impacto::Profile::ConfigSystem;\n  const auto quickSave = [](const int saveType) {\n    auto quicksaveId = SaveSystem::GetQuickSaveOpenSlot();\n    if (quicksaveId) {\n      SaveIconDisplay::ShowFor(2.4f);\n      SaveSystem::FlushWorkingSaveEntry(SaveSystem::SaveType::Quick,\n                                        *quicksaveId, saveType);\n      SaveSystem::SaveThumbnailData();\n    }\n  };\n\n  StartInstruction;\n\n  uint8_t autoQuickSave = AutoQuickSave;\n  if (GetFlag(SF_SAVEDISABLE)) autoQuickSave = 0;\n\n  PopUint8(type);\n  switch (type) {\n    case 0: {  // QuickSave\n      SaveSystem::SaveMemory();\n\n      if ((autoQuickSave & +AutoQuickSaveType::OnScene) &&\n          ScrWork[SW_AUTOSAVERESTART] != 1) {\n        quickSave(1);\n      }\n\n      SetFlag(SF_AUTOSAVEENABLE, 1);\n      ScrWork[SW_AUTOSAVERESTART] = 0;\n    } break;\n\n    case 1: {  // QuickSaveTrigger\n      SaveSystem::SaveMemory();\n\n      if ((autoQuickSave & +AutoQuickSaveType::OnTrigger) &&\n          ScrWork[SW_AUTOSAVERESTART] != 3) {\n        quickSave(3);\n      }\n\n      SetFlag(SF_AUTOSAVEENABLE, 1);\n      ScrWork[SW_AUTOSAVERESTART] = 0;\n    } break;\n\n    case 3: {  // DisableAutoSave\n      auto quicksaveEntries = SaveSystem::GetQuickSaveOpenSlot();\n      if (quicksaveEntries) {\n        SaveIconDisplay::ShowFor(2.4f);\n        SaveSystem::FlushWorkingSaveEntry(SaveSystem::SaveType::Quick,\n                                          *quicksaveEntries, 0);\n        SaveSystem::SaveThumbnailData();\n      }\n      SaveSystem::SetQSavedOnCurrentLine(true);\n      SetFlag(SF_AUTOSAVEENABLE, 0);\n    } break;\n\n    case 5: {  // EnableAutoSave\n      SaveSystem::SaveMemory();\n\n      SetFlag(SF_AUTOSAVEENABLE, 1);\n      ScrWork[SW_AUTOSAVERESTART] = 0;\n    } break;\n\n    case 10: {  // SetCheckpointId\n      BlockThread;\n      SetFlag(SF_SAVECAPTURE, 1);\n    } break;\n\n    case 20:\n    case 0xff: {\n      BlockThread;\n    } break;\n\n    default:\n      ImpLog(LogLevel::Warning, LogChannel::VM,\n             \"Unimplemented InstAutoSaveOld type {:d}\\n\", type);\n      break;\n  }\n}\nVmInstruction(InstLoadFontWidths) {\n  StartInstruction;\n  PopExpression(fontId);\n  PopExpression(archiveId);\n  PopExpression(fileId);\n  ImpLogSlow(LogLevel::Warning, LogChannel::VMStub,\n             \"STUB instruction LoadFontWidths(fontId: {:d}, archiveId: {:d}, \"\n             \"fileId: {:d})\\n\",\n             fontId, archiveId, fileId);\n}\n\n}  // namespace Vm\n\n}  // namespace Impacto"
  },
  {
    "path": "src/vm/inst_system.h",
    "content": "#pragma once\n\n#include \"vm.h\"\n\nnamespace Impacto {\n\nnamespace Vm {\n\nVmInstruction(InstDummy);\n\nVmInstruction(InstEnd);\nVmInstruction(InstCreateThread);\nVmInstruction(InstKillThread);\nVmInstruction(InstReset);\nVmInstruction(InstScriptLoad);\nVmInstruction(InstWait);\nVmInstruction(InstHalt);\nVmInstruction(InstGetLabelAdr);\nVmInstruction(InstFlagOnWait);\nVmInstruction(InstSetFlag);\nVmInstruction(InstResetFlag);\nVmInstruction(InstCopyFlag);\nVmInstruction(InstKeyWait);\nVmInstruction(InstKeyWaitTimer);\nVmInstruction(InstMemberWrite);\nVmInstruction(InstThreadControl);\nVmInstruction(InstGetSelfPointer);\nVmInstruction(InstVsync);\nVmInstruction(InstTest);\nVmInstruction(InstThreadControlStore);\nVmInstruction(InstPadAct);\nVmInstruction(InstCopyThreadWork);\nVmInstruction(InstSave);\nVmInstruction(InstSaveOld);\nVmInstruction(InstSaveIconLoad);\nVmInstruction(InstVoiceTableLoadMaybe);\nVmInstruction(InstSetPadCustom);\nVmInstruction(InstMwait);\nVmInstruction(InstTerminate);\nVmInstruction(InstDebugPrint);\nVmInstruction(InstSystemMes);\nVmInstruction(InstGetNowTime);\nVmInstruction(InstGetSystemStatus);\nVmInstruction(InstReboot);\nVmInstruction(InstReloadScript);\nVmInstruction(InstReloadScriptMenu);\nVmInstruction(InstDebugEditer);\nVmInstruction(InstPadActEx);\nVmInstruction(InstDebugSetup);\nVmInstruction(InstGlobalSystemMessage);\nVmInstruction(InstCalc);\nVmInstruction(InstMSinit);\nVmInstruction(InstSaveSlot);\nVmInstruction(InstSystemMain);\nVmInstruction(InstGameInfoInit);\nVmInstruction(InstSystemDataReset);\nVmInstruction(InstDebugData);\nVmInstruction(InstAutoSave);\nVmInstruction(InstAutoSaveOld);\nVmInstruction(InstLoadFontWidths);\n\n}  // namespace Vm\n\n}  // namespace Impacto"
  },
  {
    "path": "src/vm/interface/input.cpp",
    "content": "#include \"input.h\"\n\n#include \"../../impacto.h\"\n#include \"../../mem.h\"\n#include \"../../inputsystem.h\"\n#include \"../../profile/configsystem.h\"\n#include \"../../animation.h\"\n#include \"../../profile/vm.h\"\n#include \"../../profile/scriptinput.h\"\n#include \"../../profile/scriptvars.h\"\n#include \"../../profile/profile_internal.h\"\n\nnamespace Impacto {\nnamespace Vm {\nnamespace Interface {\n\nusing namespace Impacto::Profile::ScriptInput;\nusing namespace Impacto::Profile::ScriptVars;\n\nuint32_t GetPadInputButtonDown(InputDownType downType) {\n  switch (downType) {\n    case InputDownType::IsDown:\n      return PADinputButtonIsDown;\n    case InputDownType::WentDown:\n      return PADinputButtonWentDown;\n    case InputDownType::RepeatDown:\n      return PADinputButtonRepeatDown;\n    case InputDownType::RepeatAccelDown:\n      return PADinputButtonRepeatAccelDown;\n\n    default: {\n      ImpLog(LogLevel::Error, LogChannel::General,\n             \"Unexpected InputDownType {:d}\", static_cast<int>(downType));\n      return 0;\n    }\n  }\n}\n\nvoid UpdatePADcustomType(int type) {\n  if (type == 0) {\n    PADcustom = PADcustomA;\n  } else {\n    PADcustom = PADcustomB;\n  }\n}\n\nstatic std::array<float, 32> PADIsDownTime{};\nstatic float PADRepeatClock = 0.0f;\nstatic float PADAccel1Clock = 0.0f;\nstatic float PADAccel2Clock = 0.0f;\n\nenum class PADInputType { WentDown, IsDown };\n\nstatic void UpdateFromPADCode(uint32_t PADcode, PADInputType type) {\n  const auto KBcustomMappings = PADToKBcustom.find(PADcode);\n  const auto GPcode = PADToController.find(PADcode);\n  const auto GPAcode = PADToControllerAxis.find(PADcode);\n  const auto MScode = PADToMouse.find(PADcode);\n\n  const auto& axisDownLightArr = type == PADInputType::WentDown\n                                     ? Input::ControllerAxisWentDownLight\n                                     : Input::ControllerAxisIsDownLight;\n  [[maybe_unused]] const auto& axisDownHeavyArr =\n      type == PADInputType::WentDown ? Input::ControllerAxisWentDownHeavy\n                                     : Input::ControllerAxisIsDownHeavy;\n  const auto& mouseDownArr = type == PADInputType::WentDown\n                                 ? Input::MouseButtonWentDown\n                                 : Input::MouseButtonIsDown;\n  const auto& kbDownArr = type == PADInputType::WentDown\n                              ? Input::KeyboardButtonWentDown\n                              : Input::KeyboardButtonIsDown;\n  const auto& gpDownArr = type == PADInputType::WentDown\n                              ? Input::ControllerButtonWentDown\n                              : Input::ControllerButtonIsDown;\n\n  auto& padInputButton = type == PADInputType::WentDown ? PADinputButtonWentDown\n                                                        : PADinputButtonIsDown;\n  auto& padInputMouse = type == PADInputType::WentDown ? PADinputMouseWentDown\n                                                       : PADinputMouseIsDown;\n\n  const auto padToKbPred = [&kbDownArr, &type](auto const& kbMapping) {\n    using Profile::ScriptInput::KeyboardPadMapping;\n    using enum KeyboardPadMapping::InputMode;\n    KeyboardPadMapping::InputMode mask =\n        (type == PADInputType::WentDown) ? Tap : Held;\n    auto kbCustomItr = KBcustom.find(kbMapping.Id);\n    if (kbCustomItr == KBcustom.end()) return false;\n    return (kbMapping.Mode & mask) &&\n           std::any_of(kbCustomItr->second.begin(), kbCustomItr->second.end(),\n                       [&kbDownArr](int id) { return kbDownArr[id]; });\n  };\n\n  const bool isKbDown =\n      KBcustomMappings != PADToKBcustom.end() &&\n      std::any_of(KBcustomMappings->second.begin(),\n                  KBcustomMappings->second.end(), padToKbPred);\n\n  const bool isGpDown =\n      GPcode != PADToController.end() && gpDownArr[GPcode->second];\n  const auto checkAxis = [&](auto axisDownArr) {\n    if (GPAcode != PADToControllerAxis.end()) {\n      if (axisDownArr[GPAcode->second.first] &&\n          Input::ControllerAxisValue[GPAcode->second.first] *\n                  (float)GPAcode->second.second >\n              0.0f)\n        return true;\n    }\n    return false;\n  };\n\n  const auto checkPadDirectional = [](auto PADcode) {\n    if (PADcode == PAD1UP_DIRECT || PADcode == PAD1UP_RS ||\n        PADcode == PAD1UP_LS)\n      PADcode |= PAD1UP;\n    if (PADcode == PAD1DOWN_DIRECT || PADcode == PAD1DOWN_RS ||\n        PADcode == PAD1DOWN_LS)\n      PADcode |= PAD1DOWN;\n    if (PADcode == PAD1LEFT_DIRECT || PADcode == PAD1LEFT_RS ||\n        PADcode == PAD1LEFT_LS)\n      PADcode |= PAD1LEFT;\n    if (PADcode == PAD1RIGHT_DIRECT || PADcode == PAD1RIGHT_RS ||\n        PADcode == PAD1RIGHT_LS)\n      PADcode |= PAD1RIGHT;\n    return PADcode;\n  };\n\n  const bool isGPAxisDown = checkAxis(axisDownLightArr);\n  const bool isMsDown =\n      MScode != PADToMouse.end() && mouseDownArr[MScode->second];\n  if (isGPAxisDown &&\n      Input::ControllerAxisValue[GPAcode->second.first] >\n          (float)GPAcode->second.second * Input::ControllerAxisLightThreshold)\n    padInputButton |= PADcode;\n  if (isKbDown || isGpDown || isGPAxisDown) {\n    padInputButton |= checkPadDirectional(PADcode);\n  }\n  if (isMsDown) padInputMouse |= PADcode;\n}\n\nvoid UpdatePADHoldInput(float dt) {\n  constexpr float frameTime = 1 / 60.0f;\n\n  PADinputButtonRepeatDown = 0;\n  PADinputButtonRepeatAccelDown = 0;\n\n  PADRepeatClock += dt;\n  PADAccel1Clock += dt;\n  PADAccel2Clock += dt;\n\n  bool repeatCycle = false;\n  bool accel1Cycle = false;\n  bool accel2Cycle = false;\n\n  // Reset clocks at intervals so all buttons sync\n  if (PADRepeatClock >= 4 * frameTime) {\n    repeatCycle = true;\n    PADRepeatClock = 0.0f;\n  }\n  if (PADAccel1Clock >= 2 * frameTime) {\n    accel1Cycle = true;\n    PADAccel1Clock = 0.0f;\n  }\n  if (PADAccel2Clock >= 1 * frameTime) {\n    accel2Cycle = true;\n    PADAccel2Clock = 0.0f;\n  }\n\n  for (int i = 0; i < 32; i++) {\n    uint32_t PADcode = 1 << (uint8_t)i;\n    if ((PADinputButtonIsDown & PADcode) == 0) {\n      PADIsDownTime[i] = 0.0f;\n      continue;\n    }\n\n    if (PADinputButtonWentDown & PADcode) {\n      PADinputButtonRepeatDown |= PADcode;\n      PADinputButtonRepeatAccelDown |= PADcode;\n    }\n\n    PADIsDownTime[i] += dt;\n    if (PADIsDownTime[i] >= 90 * frameTime) {\n      if (PADIsDownTime[i] < 180) {\n        if (accel1Cycle) PADinputButtonRepeatAccelDown |= PADcode;\n\n      } else {\n        if (accel2Cycle) PADinputButtonRepeatAccelDown |= PADcode;\n      }\n    }\n\n    if (PADIsDownTime[i] >= 30 * frameTime) {\n      if (repeatCycle) {\n        PADinputButtonRepeatDown |= PADcode;\n        if (PADIsDownTime[i] < 90 * frameTime) {\n          PADinputButtonRepeatAccelDown |= PADcode;\n        }\n      }\n    }\n  }\n}\n\nvoid UpdateKBHoldInput(float dt) {\n  static std::array<float, SDL_NUM_SCANCODES> KBIsDownTime{};\n  constexpr float frameTime = 1 / 60.0f;\n  for (size_t i = 0; i < KBinputHeldDown.size(); i++) {\n    if (Input::KeyboardButtonIsDown[i]) {\n      KBIsDownTime[i] += dt;\n      KBinputHeldDown[i] = KBIsDownTime[i] >= frameTime * 8;\n    } else {\n      KBIsDownTime[i] = 0.0f;\n      KBinputHeldDown[i] = false;\n    }\n  }\n}\n\nvoid ResetPADHoldTimer(uint32_t PADcode) {\n  for (uint8_t i = 0; i < 32; ++i) {\n    if (PADcode & (1 << i)) PADIsDownTime[i] = 0.0f;\n  }\n}\n\nvoid UpdatePADInput() {\n  PADinputButtonWentDown = 0;\n  PADinputMouseWentDown = 0;\n  PADinputButtonIsDown = 0;\n  PADinputMouseIsDown = 0;\n  for (int i = 0; i < 32; i++) {\n    uint32_t PADcode = 1 << (uint8_t)i;\n    UpdateFromPADCode(PADcode, PADInputType::WentDown);\n    UpdateFromPADCode(PADcode, PADInputType::IsDown);\n  }\n\n  if (Input::TouchWentDown[0] && Input::TouchWentDown[1])\n    PADinputMouseWentDown |= PAD1B;\n  else if (Input::TouchWentDown[0])\n    PADinputMouseWentDown |= PAD1A;\n  if (Input::TouchIsDown[0]) PADinputMouseIsDown |= PAD1A;\n}\n\n// TODO: Make this configurable per game\n// I have no idea why they have a million things for controls...\nbool GetControlState(int controlId, InputDownType downType) {\n  using namespace Impacto::Profile::ConfigSystem;\n\n  uint32_t padInputDown = GetPadInputButtonDown(downType);\n  if (downType == InputDownType::IsDown) {\n    padInputDown |= Interface::PADinputMouseIsDown;\n  } else if (downType == InputDownType::WentDown) {\n    padInputDown |= Interface::PADinputMouseWentDown;\n  }\n\n  switch (controlId) {\n    case CT_OK:\n      return padInputDown & PADcustom[5];\n    case CT_Back:\n      return padInputDown & PADcustom[6];\n    case CT_HIDE:\n      return PADcustom[11] ? (PADcustom[11] & PADinputButtonWentDown)\n                           : (PADcustom[6] & PADinputButtonWentDown);\n    case CT_NextMessage:\n      return padInputDown & PADcustom[23];\n    case CT_ForceSkip:\n      return padInputDown & PADcustom[7];\n    case CT_SkipMode:\n      return padInputDown & PADcustom[8];\n    case CT_AutoMode:\n      return padInputDown & PADcustom[9];\n    case CT_QuickSave:\n      return padInputDown & PADcustom[13];\n    case CT_MainMenu:\n      return padInputDown & PADcustom[10];\n    case CT_Backlog:\n      return padInputDown & PADcustom[12];\n    case CT_Tips:\n      return false;\n    case CT_MovieCancel:\n    case CT_LogoSkip:\n      return padInputDown & PADcustom[14];\n    case CT_ResetOptions:\n      return padInputDown & PAD1Y;\n    case CT_DelusionTriggerL:\n      return padInputDown & PADcustom[36 + 2 * DirectionalInputForTrigger];\n    case CT_DelusionTriggerR:\n      return padInputDown & PADcustom[37 + 2 * DirectionalInputForTrigger];\n    default:\n      return false;\n  }\n}\n\n}  // namespace Interface\n}  // namespace Vm\n}  // namespace Impacto\n"
  },
  {
    "path": "src/vm/interface/input.h",
    "content": "#pragma once\n\n#include <span>\n\n#include \"../../impacto.h\"\n#include \"../../inputsystem.h\"\n\nnamespace Impacto {\nnamespace Vm {\nnamespace Interface {\n// Might have to take this from lua file if older games use different consts\nenum PADinput {\n  PAD1UP = 0x10000,\n  PAD1DOWN = 0x20000,\n  PAD1LEFT = 0x40000,\n  PAD1RIGHT = 0x80000,\n  PAD1START = 0x10,\n  PAD1SELECT = 0x20,\n  PAD1L3 = 0x40,\n  PAD1R3 = 0x80,\n  PAD1L1 = 0x100,\n  PAD1R1 = 0x200,\n  PAD1L2 = 0x400,\n  PAD1R2 = 0x800,\n  PAD1A = 0x1000,\n  PAD1B = 0x2000,\n  PAD1X = 0x4000,\n  PAD1Y = 0x8000,\n  PAD1UP_LS = 0x100000,\n  PAD1DOWN_LS = 0x200000,\n  PAD1LEFT_LS = 0x400000,\n  PAD1RIGHT_LS = 0x800000,\n  PAD1UP_RS = 0x1000000,\n  PAD1DOWN_RS = 0x2000000,\n  PAD1LEFT_RS = 0x4000000,\n  PAD1RIGHT_RS = 0x8000000,\n  PAD1UP_DIRECT = 0x1,\n  PAD1DOWN_DIRECT = 0x2,\n  PAD1LEFT_DIRECT = 0x4,\n  PAD1RIGHT_DIRECT = 0x8,\n};\n\ninline std::span<uint32_t> PADcustom;\n\nenum class InputDownType {\n  IsDown = 0,\n  WentDown = 1,\n  RepeatDown = 2,\n  RepeatAccelDown = 3,\n};\n\ninline uint32_t PADinputButtonWentDown = 0;         // padone\ninline uint32_t PADinputButtonIsDown = 0;           // padrep\ninline uint32_t PADinputButtonRepeatDown = 0;       // padref\ninline uint32_t PADinputButtonRepeatAccelDown = 0;  // padacc\ninline uint32_t PADinputMouseWentDown = 0;\ninline uint32_t PADinputMouseIsDown = 0;\n\ninline ankerl::unordered_dense::map<uint8_t,\n                                    std::vector<Input::KeyboardScanCode>>\n    KBcustom{};\ninline std::array<bool, SDL_NUM_SCANCODES> KBinputHeldDown = {false};\n\nenum ControlType {\n  CT_OK = 0,\n  CT_Back = 2,\n  CT_HIDE = 8,\n  CT_NextMessage = 21,\n  CT_ForceSkip = 22,\n  CT_SkipMode = 23,\n  CT_AutoMode = 24,\n  CT_QuickSave = 26,\n  CT_MainMenu = 28,\n  CT_Backlog = 29,\n  CT_Tips = 30,\n  CT_MovieCancel = 38,\n  CT_LogoSkip = 39,\n  CT_ResetOptions = 40,\n  CT_DelusionTriggerL = 41,\n  CT_DelusionTriggerR = 42,\n};\n\nuint32_t GetPadInputButtonDown(InputDownType downType);\nvoid UpdatePADInput();\nvoid UpdatePADHoldInput(float dt);\nvoid ResetPADHoldTimer(uint32_t PADcode);\nvoid UpdatePADcustomType(int type);\nbool GetControlState(int controlId,\n                     InputDownType downType = InputDownType::WentDown);\nvoid UpdateKBInput();\nvoid UpdateKBHoldInput(float dt);\n\n}  // namespace Interface\n}  // namespace Vm\n}  // namespace Impacto"
  },
  {
    "path": "src/vm/interface/scene3d.cpp",
    "content": "#include \"scene3d.h\"\n\n#include \"../../impacto.h\"\n#include \"../../profile/scriptvars.h\"\n#include \"../../renderer/renderer.h\"\n#include \"../../mem.h\"\n#include \"../../util.h\"\n#include \"../../log.h\"\n\n#include \"../../profile/scene3d.h\"\n#include \"../../profile/vm.h\"\n\nnamespace Impacto {\nnamespace Vm {\nnamespace Interface {\n\nusing namespace Impacto::Profile::ScriptVars;\n\nstatic void UpdateScrWorkAnimations(float dt) {\n  for (size_t i = 0; i < CurrentScrWorkAnimations.size(); i++) {\n    int id = CurrentScrWorkAnimations[i];\n    ScrWorkAnimations[id].MainAnimation.Update(dt);\n    for (ScrWorkAnimationData animationData :\n         ScrWorkAnimations[id].AnimationData) {\n      ScrWork[animationData.Target] = glm::mix(\n          animationData.From, animationData.To,\n          std::pow(ScrWorkAnimations[id].MainAnimation.Progress - 1, 3) + 1);\n    }\n    if (ScrWorkAnimations[id].MainAnimation.IsIn()) {\n      CurrentScrWorkAnimations.erase(CurrentScrWorkAnimations.begin() + i);\n    }\n  }\n}\n\nstatic void UpdateRenderableRot(int charId) {\n  int pose = ScrWork[30 * charId + SW_MDL1TARDIR] - 30;\n\n  switch (Profile::Vm::GameInstructionSet) {\n    default:\n      break;\n    case InstructionSet::RNE: {\n      if (pose >= 0) {\n        glm::vec3 target = ScrWorkGetVec3(20 * pose + 5500, 20 * pose + 5501,\n                                          20 * pose + 5502);\n        target.y += Profile::Scene3D::DefaultCameraPosition.y;\n\n        glm::vec3 object =\n            ScrWorkGetVec3(30 * charId + SW_MDL1POSX, 30 * charId + SW_MDL1POSY,\n                           30 * charId + SW_MDL1POSZ);\n        object.y += ScrWorkGetFloat(30 * charId + SW_MDL1CENY);\n\n        glm::vec3 lookat = LookAtEulerZYX(object, target);\n        lookat.x = 0.0f;\n\n        Renderer->Scene->Renderables[charId]\n            ->ModelTransform.SetRotationFromEuler(lookat);\n\n        ScrWorkSetAngle(30 * charId + SW_MDL1ROTY, lookat.y);\n      } else {\n        Renderer->Scene->Renderables[charId]\n            ->ModelTransform.SetRotationFromEuler(ScrWorkGetAngleVec3(\n                30 * charId + SW_MDL1ROTX, 30 * charId + SW_MDL1ROTY,\n                30 * charId + SW_MDL1ROTZ));\n      }\n    } break;\n    case InstructionSet::Dash: {\n      Renderer->Scene->Renderables[charId]->ModelTransform.SetRotationFromEuler(\n          ScrWorkGetAngleVec3(30 * charId + SW_MDL1ROTX,\n                              30 * charId + SW_MDL1ROTY,\n                              30 * charId + SW_MDL1ROTZ));\n    } break;\n  }\n}\n\nstatic void UpdateRenderablePos(int charId) {\n  Renderer->Scene->Renderables[charId]->ModelTransform.Position =\n      ScrWorkGetVec3(30 * charId + SW_MDL1POSX, 30 * charId + SW_MDL1POSY,\n                     30 * charId + SW_MDL1POSZ);\n}\n\nstatic void UpdateRenderables() {\n  for (int i = 0; i < Profile::Scene3D::MaxRenderables; i++) {\n    if (Renderer->Scene->Renderables[i]->Status == LoadStatus::Loaded) {\n      UpdateRenderableRot(i);\n      UpdateRenderablePos(i);\n      if (GetFlag(SF_IRUOENABLE) && GetFlag(SF_Pokecon_Open)) {\n        Renderer->Scene->Renderables[i]->IsVisible = GetFlag(SF_MDL1SHDISP + i);\n      } else {\n        Renderer->Scene->Renderables[i]->IsVisible = GetFlag(SF_MDL1DISP + i);\n      }\n    }\n  }\n}\n\nstatic void UpdateCamera() {\n  glm::vec3 posCam;\n  glm::vec3 lookatCam;\n  float hFovRad;\n  int camera = 0;\n  if (Profile::Vm::GameInstructionSet == InstructionSet::RNE)\n    camera = !GetFlag(SF_IRUOENABLE);\n  posCam = ScrWorkGetVec3(SW_IRUOCAMERAPOSX + 20 * camera,\n                          SW_IRUOCAMERAPOSY + 20 * camera,\n                          SW_IRUOCAMERAPOSZ + 20 * camera);\n  lookatCam = ScrWorkGetAngleVec3(SW_IRUOCAMERAROTX + 20 * camera,\n                                  SW_IRUOCAMERAROTY + 20 * camera,\n                                  SW_CAMSHTARDIR + 20 * camera);\n\n  switch (Profile::Vm::GameInstructionSet) {\n    default:\n      // Calculations are game-specific, can't do anything reasonable here\n      ImpLog(LogLevel::Error, LogChannel::Render,\n             \"Unknown instruction set, can't update camera!\\n\");\n      return;\n    case InstructionSet::Dash: {\n      posCam += ScrWorkGetVec3(2580, 2581, 2582);\n      lookatCam += ScrWorkGetAngleVec3(2583, 2584, 2585);\n      hFovRad = ScrWorkGetAngle(SW_IRUOCAMERAHFOV + 10 * camera);\n      lookatCam.z = -lookatCam.z;\n    } break;\n    case InstructionSet::RNE: {\n      posCam += Profile::Scene3D::DefaultCameraPosition;\n      // Camera zoom\n      if (ScrWork[SW_IRUOCAMERAHFOVDELTA + 20 * camera] == 0) {\n        ScrWork[SW_IRUOCAMERAHFOVCUR] = ScrWork[SW_IRUOCAMERAHFOV];\n        ScrWork[SW_MAINCAMERAHFOVCUR] = ScrWork[SW_MAINCAMERAHFOV];\n      } else if (ScrWork[SW_IRUOCAMERAHFOV + 20 * camera] >\n                 ScrWork[SW_IRUOCAMERAHFOVCUR + 10 * camera]) {\n        ScrWork[SW_IRUOCAMERAHFOVCUR + 10 * camera] +=\n            ScrWork[SW_IRUOCAMERAHFOVDELTA + 20 * camera];\n        if (ScrWork[SW_IRUOCAMERAHFOV + 20 * camera] <\n            ScrWork[SW_IRUOCAMERAHFOVCUR + 10 * camera])\n          ScrWork[SW_IRUOCAMERAHFOVCUR + 10 * camera] =\n              ScrWork[SW_IRUOCAMERAHFOV + 20 * camera];\n      } else if (ScrWork[SW_IRUOCAMERAHFOV + 20 * camera] <\n                 ScrWork[SW_IRUOCAMERAHFOVCUR + 10 * camera]) {\n        ScrWork[SW_IRUOCAMERAHFOVCUR + 10 * camera] -=\n            ScrWork[SW_IRUOCAMERAHFOVDELTA + 20 * camera];\n        if (ScrWork[SW_IRUOCAMERAHFOV + 20 * camera] >\n            ScrWork[SW_IRUOCAMERAHFOVCUR + 10 * camera])\n          ScrWork[SW_IRUOCAMERAHFOVCUR + 10 * camera] =\n              ScrWork[SW_IRUOCAMERAHFOV + 20 * camera];\n      }\n\n      // Copy some values\n      ScrWork[SW_CAMSHROT_WORK] = ScrWork[SW_IRUOCAMERAROTX];\n      ScrWork[SW_CAMSHELV_WORK] = ScrWork[SW_IRUOCAMERAROTY];\n      ScrWork[SW_CAMSHPOSX_WORK] = ScrWork[SW_IRUOCAMERAPOSX];\n      ScrWork[SW_CAMSHPOSY_WORK] = ScrWork[SW_IRUOCAMERAPOSY];\n      ScrWork[SW_CAMSHPOSZ_WORK] = ScrWork[SW_IRUOCAMERAPOSZ];\n      ScrWork[SW_CAMROT_WORK] = ScrWork[SW_MAINCAMERAROTX];\n      ScrWork[SW_CAMELV_WORK] = ScrWork[SW_MAINCAMERAROTY];\n      ScrWork[SW_CAMPOSX_WORK] = ScrWork[SW_MAINCAMERAPOSX];\n      ScrWork[SW_CAMPOSY_WORK] = ScrWork[SW_MAINCAMERAPOSY];\n      ScrWork[SW_CAMPOSZ_WORK] = ScrWork[SW_MAINCAMERAPOSZ];\n\n      if (GetFlag(SF_IRUOENABLE) && !GetFlag(SF_IRUOAUTO))\n        hFovRad = ScrWorkGetAngle(SW_POKECOMIRUOHFOV);\n      else\n        hFovRad = ScrWorkGetAngle(SW_IRUOCAMERAHFOVCUR + 10 * camera);\n    }\n  }\n\n  lookatCam.x = -lookatCam.x;\n\n  // Update position\n  Renderer->Scene->MainCamera.CameraTransform.Position = posCam;\n  // Update lookat\n  Renderer->Scene->MainCamera.CameraTransform.SetRotationFromEuler(lookatCam);\n  // Update fov\n  Renderer->Scene->MainCamera.Fov =\n      2.0f * atanf(tanf(hFovRad / 2.0f) *\n                   (1.0f / Renderer->Scene->MainCamera.AspectRatio));\n\n  // Update lighting\n  switch (Profile::Vm::GameInstructionSet) {\n    default:\n      break;\n    case InstructionSet::RNE: {\n      Renderer->Scene->Tint = ScrWorkGetColor(SW_MAINLIGHTCOLOR);\n      Renderer->Scene->Tint.a = ScrWorkGetFloat(SW_MAINLIGHTWEIGHT);\n      Renderer->Scene->LightPosition =\n          ScrWorkGetVec3(SW_MAINLIGHTPOSX, SW_MAINLIGHTPOSY, SW_MAINLIGHTPOSZ);\n      Renderer->Scene->DarkMode = (bool)ScrWork[SW_MAINLIGHTDARKMODE];\n    } break;\n    case InstructionSet::Dash: {\n      Renderer->Scene->Tint.r = ScrWork[SW_LIGHT1_COLORR] / 255.0f;\n      Renderer->Scene->Tint.g = ScrWork[SW_LIGHT1_COLORG] / 255.0f;\n      Renderer->Scene->Tint.b = ScrWork[SW_LIGHT1_COLORB] / 255.0f;\n      Renderer->Scene->Tint.a = 1.0f;\n      Renderer->Scene->LightPosition =\n          ScrWorkGetVec3(SW_LIGHT1_POSX, SW_LIGHT1_POSY, SW_LIGHT1_POSZ);\n    } break;\n  }\n}\n\nvoid UpdateScene3D(float dt) {\n  UpdateScrWorkAnimations(dt);\n  UpdateRenderables();\n  UpdateCamera();\n}\n\n}  // namespace Interface\n}  // namespace Vm\n}  // namespace Impacto"
  },
  {
    "path": "src/vm/interface/scene3d.h",
    "content": "#pragma once\n\n#include \"../../animation.h\"\n#include <vector>\n#include <ankerl/unordered_dense.h>\n\nnamespace Impacto {\nnamespace Vm {\nnamespace Interface {\n\nvoid UpdateScene3D(float dt);\n\nstruct ScrWorkAnimationData {\n  int From;\n  int To;\n  int Target;\n};\n\nstruct ScrWorkAnimation {\n  Animation MainAnimation;\n  std::vector<ScrWorkAnimationData> AnimationData;\n  bool AltTarget;\n};\n\ninline std::vector<int> CurrentScrWorkAnimations;\ninline ankerl::unordered_dense::map<int, ScrWorkAnimation> ScrWorkAnimations;\n\n}  // namespace Interface\n}  // namespace Vm\n}  // namespace Impacto"
  },
  {
    "path": "src/vm/opcodetables_cc.h",
    "content": "#include \"inst_system.h\"\n#include \"inst_controlflow.h\"\n#include \"inst_graphics2d.h\"\n#include \"inst_graphics3d.h\"\n#include \"inst_dialogue.h\"\n#include \"inst_sound.h\"\n#include \"inst_movie.h\"\n#include \"inst_gamespecific.h\"\n#include \"inst_misc.h\"\n\n// clang-format off\n\nnamespace Impacto {\n\nnamespace Vm {\n\nInstructionProc inline constexpr OpcodeTableSystem_CC[256] = {\n    InstEnd,                  // 00 00\n    InstCreateThread,         // 00 01\n    InstKillThread,           // 00 02\n    InstReset,                // 00 03\n    InstScriptLoad,           // 00 04\n    InstWait,                 // 00 05\n    InstHalt,                 // 00 06\n    InstJump,                 // 00 07\n    InstJumpTable,            // 00 08\n    InstGetLabelAdr,          // 00 09\n    InstIf,                   // 00 0A\n    InstCall,                 // 00 0B\n    InstJumpFar,              // 00 0C\n    InstCallFar,              // 00 0D\n    InstReturn,               // 00 0E\n    InstLoop,                 // 00 0F\n    InstFlagOnJump,           // 00 10\n    InstFlagOnWait,           // 00 11\n    InstSetFlag,              // 00 12\n    InstResetFlag,            // 00 13\n    InstCopyFlag,             // 00 14\n    InstKeyOnJump,            // 00 15\n    InstKeyWait,              // 00 16\n    InstKeyWaitTimer,         // 00 17\n    InstMemberWrite,          // 00 18\n    InstThreadControl,        // 00 19\n    InstGetSelfPointer,       // 00 1A\n    InstLoadJump,             // 00 1B\n    InstVsync,                // 00 1C\n    InstTest,                 // 00 1D\n    InstThreadControlStore,   // 00 1E\n    InstSwitch,               // 00 1F\n    InstCase,                 // 00 20\n    InstBGMplay,              // 00 21\n    InstBGMstop,              // 00 22\n    InstSEplay,               // 00 23\n    InstSEstop,               // 00 24\n    InstPadAct,               // 00 25\n    InstSSEplay,              // 00 26\n    InstSSEstop,              // 00 27\n    InstCopyThreadWork,       // 00 28\n    InstUPLmenuUI,            // 00 29\n    InstSave,                 // 00 2A\n    InstSaveIconLoad,         // 00 2B\n    InstBGMflag,              // 00 2C\n    InstUPLxTitle,            // 00 2D\n    InstPresence,             // 00 2E\n    InstSetAchievement,          // 00 2F\n    InstSetPlayer,            // 00 30\n    InstVoiceTableLoadMaybe,  // 00 31\n    InstSetPadCustom,         // 00 32\n    InstMwait,                // 00 33\n    InstTerminate,            // 00 34\n    InstSignIn,               // 00 35\n    InstAchievementIcon,      // 00 36\n    InstVoicePlay,            // 00 37\n    InstVoiceStopNew,         // 00 38\n    InstVoicePlayWait,        // 00 39\n    InstBGMduelPlay,          // 00 3A\n    InstSNDpause,             // 00 3B\n    InstSEplayWait,           // 00 3C\n    InstDebugPrint,           // 00 3D\n    InstResetSoundAll,        // 00 3E\n    InstSNDloadStop,          // 00 3F\n    InstBGMstopWait,          // 00 40\n    InstUnk0041,              // 00 41\n    InstSetX360SysMesPos,     // 00 42\n    InstSystemMes,            // 00 43\n    InstSystemMenu,           // 00 44\n    InstGetNowTime,           // 00 45\n    InstGetSystemStatus,      // 00 46\n    InstReboot,               // 00 47\n    InstReloadScript,         // 00 48\n    InstReloadScriptMenu,     // 00 49\n    InstDebugEditer,          // 00 4A\n    InstSysSeload,            // 00 4B\n    InstDummy,                // 00 4C\n    InstSysVoicePlay,         // 00 4D\n    InstPadActEx,             // 00 4E\n    InstDebugSetup,           // 00 4F\n    InstPressStartNew,        // 00 50\n    InstGlobalSystemMessage,  // 00 51\n    InstUnk0052,              // 00 52\n    InstClickOnJump,          // 00 53\n    InstKeyboardOnJump,       // 00 54\n    InstControlOnJump,        // 00 55\n    InstGetControl,           // 00 56\n    InstDummy,                // 00 57\n    InstDummy,                // 00 58\n    InstDummy,                // 00 59\n    InstDummy,                // 00 5A\n    InstDummy,                // 00 5B\n    InstFlagOffReturn,        // 00 5C\n    InstDummy,                // 00 5D\n    InstDummy,                // 00 5E\n    InstPackFileAddBind,      // 00 5F\n    InstDummy,                // 00 60\n    InstDummy,                // 00 61\n    InstDummy,                // 00 62\n    InstDummy,                // 00 63\n    InstDummy,                // 00 64\n    InstDummy,                // 00 65\n    InstDummy,                // 00 66\n    InstDummy,                // 00 67\n    InstDummy,                // 00 68\n    InstDummy,                // 00 69\n    InstDummy,                // 00 6A\n    InstDummy,                // 00 6B\n    InstDummy,                // 00 6C\n    InstDummy,                // 00 6D\n    InstDummy,                // 00 6E\n    InstDummy,                // 00 6F\n    InstDummy,                // 00 70\n    InstDummy,                // 00 71\n    InstDummy,                // 00 72\n    InstDummy,                // 00 73\n    InstDummy,                // 00 74\n    InstDummy,                // 00 75\n    InstDummy,                // 00 76\n    InstDummy,                // 00 77\n    InstDummy,                // 00 78\n    InstDummy,                // 00 79\n    InstDummy,                // 00 7A\n    InstDummy,                // 00 7B\n    InstDummy,                // 00 7C\n    InstDummy,                // 00 7D\n    InstDummy,                // 00 7E\n    InstDummy,                // 00 7F\n    InstDummy,                // 00 80\n    InstDummy,                // 00 81\n    InstDummy,                // 00 82\n    InstDummy,                // 00 83\n    InstDummy,                // 00 84\n    InstDummy,                // 00 85\n    InstDummy,                // 00 86\n    InstDummy,                // 00 87\n    InstDummy,                // 00 88\n    InstDummy,                // 00 89\n    InstDummy,                // 00 8A\n    InstDummy,                // 00 8B\n    InstDummy,                // 00 8C\n    InstDummy,                // 00 8D\n    InstDummy,                // 00 8E\n    InstDummy,                // 00 8F\n    InstDummy,                // 00 90\n    InstDummy,                // 00 91\n    InstDummy,                // 00 92\n    InstDummy,                // 00 93\n    InstDummy,                // 00 94\n    InstDummy,                // 00 95\n    InstDummy,                // 00 96\n    InstDummy,                // 00 97\n    InstDummy,                // 00 98\n    InstDummy,                // 00 99\n    InstDummy,                // 00 9A\n    InstDummy,                // 00 9B\n    InstDummy,                // 00 9C\n    InstDummy,                // 00 9D\n    InstDummy,                // 00 9E\n    InstDummy,                // 00 9F\n    InstDummy,                // 00 A0\n    InstDummy,                // 00 A1\n    InstDummy,                // 00 A2\n    InstDummy,                // 00 A3\n    InstDummy,                // 00 A4\n    InstDummy,                // 00 A5\n    InstDummy,                // 00 A6\n    InstDummy,                // 00 A7\n    InstDummy,                // 00 A8\n    InstDummy,                // 00 A9\n    InstDummy,                // 00 AA\n    InstDummy,                // 00 AB\n    InstDummy,                // 00 AC\n    InstDummy,                // 00 AD\n    InstDummy,                // 00 AE\n    InstDummy,                // 00 AF\n    InstDummy,                // 00 B0\n    InstDummy,                // 00 B1\n    InstDummy,                // 00 B2\n    InstDummy,                // 00 B3\n    InstDummy,                // 00 B4\n    InstDummy,                // 00 B5\n    InstDummy,                // 00 B6\n    InstDummy,                // 00 B7\n    InstDummy,                // 00 B8\n    InstDummy,                // 00 B9\n    InstDummy,                // 00 BA\n    InstDummy,                // 00 BB\n    InstDummy,                // 00 BC\n    InstDummy,                // 00 BD\n    InstDummy,                // 00 BE\n    InstDummy,                // 00 BF\n    InstDummy,                // 00 C0\n    InstDummy,                // 00 C1\n    InstDummy,                // 00 C2\n    InstDummy,                // 00 C3\n    InstDummy,                // 00 C4\n    InstDummy,                // 00 C5\n    InstDummy,                // 00 C6\n    InstDummy,                // 00 C7\n    InstDummy,                // 00 C8\n    InstDummy,                // 00 C9\n    InstDummy,                // 00 CA\n    InstDummy,                // 00 CB\n    InstDummy,                // 00 CC\n    InstDummy,                // 00 CD\n    InstDummy,                // 00 CE\n    InstDummy,                // 00 CF\n    InstDummy,                // 00 D0\n    InstDummy,                // 00 D1\n    InstDummy,                // 00 D2\n    InstDummy,                // 00 D3\n    InstDummy,                // 00 D4\n    InstDummy,                // 00 D5\n    InstDummy,                // 00 D6\n    InstDummy,                // 00 D7\n    InstDummy,                // 00 D8\n    InstDummy,                // 00 D9\n    InstDummy,                // 00 DA\n    InstDummy,                // 00 DB\n    InstDummy,                // 00 DC\n    InstDummy,                // 00 DD\n    InstDummy,                // 00 DE\n    InstDummy,                // 00 DF\n    InstDummy,                // 00 E0\n    InstDummy,                // 00 E1\n    InstDummy,                // 00 E2\n    InstDummy,                // 00 E3\n    InstDummy,                // 00 E4\n    InstDummy,                // 00 E5\n    InstDummy,                // 00 E6\n    InstDummy,                // 00 E7\n    InstDummy,                // 00 E8\n    InstDummy,                // 00 E9\n    InstDummy,                // 00 EA\n    InstDummy,                // 00 EB\n    InstDummy,                // 00 EC\n    InstDummy,                // 00 ED\n    InstDummy,                // 00 EE\n    InstDummy,                // 00 EF\n    InstDummy,                // 00 F0\n    InstDummy,                // 00 F1\n    InstDummy,                // 00 F2\n    InstDummy,                // 00 F3\n    InstDummy,                // 00 F4\n    InstDummy,                // 00 F5\n    InstDummy,                // 00 F6\n    InstDummy,                // 00 F7\n    InstDummy,                // 00 F8\n    InstDummy,                // 00 F9\n    InstDummy,                // 00 FA\n    InstDummy,                // 00 FB\n    InstDummy,                // 00 FC\n    InstDummy,                // 00 FD\n    InstDummy,                // 00 FE\n    InstDummy                 // 00 FF\n};\n\nInstructionProc inline constexpr OpcodeTableGraph_CC[256] = {\n    InstCreateSurf,        // 01 00\n    InstReleaseSurf,       // 01 01\n    InstLoadPic,           // 01 02\n    InstReleaseSurf,       // 01 03\n    InstSurfFill,          // 01 04\n    InstCalc,              // 01 05\n    InstMesViewFlag,       // 01 06\n    InstSetMesWinPri,      // 01 07\n    InstMesSync,           // 01 08\n    InstMesSetID,          // 01 09\n    InstMesCls,            // 01 0A\n    InstMesVoiceWait,      // 01 0B\n    InstMes,               // 01 0C\n    InstMesMain,           // 01 0D\n    InstSetMesModeFormat,  // 01 0E\n    InstSetNGmoji,         // 01 0F\n    InstMesRev,            // 01 10\n    InstMessWindow,        // 01 11\n    InstSel,               // 01 12\n    InstSelect,            // 01 13\n    InstSysSel,            // 01 14\n    InstSysSelect,         // 01 15\n    InstDummy,             // 01 16\n    InstDummy,             // 01 17\n    InstDummy,             // 01 18\n    InstDummy,             // 01 19\n    InstDummy,             // 01 1A\n    InstDummy,             // 01 1B\n    InstDummy,             // 01 1C\n    InstDummy,             // 01 1D\n    InstDummy,             // 01 1E\n    InstLoadFontWidths,    // 01 1F\n    InstSCcapture,         // 01 20\n    InstSetTextTable,      // 01 21\n    InstPlayMovie,         // 01 22\n    InstMovieMain,         // 01 23\n    InstLoadMovie,         // 01 24\n    InstSetRevMes,         // 01 25\n    InstPlayMovieMemory,   // 01 26\n    InstPlayMovie,         // 01 27\n    InstMovieMain,         // 01 28\n    InstLoadMovie,         // 01 29\n    InstPlayMovieMemory,   // 01 2A\n    InstSFDpause,          // 01 2B\n    InstPlayMovie,         // 01 2C\n    InstUnk012D,           // 01 2D\n    InstDummy,             // 01 2E\n    InstDummy,             // 01 2F\n    InstDummy,             // 01 30\n    InstDummy,             // 01 31\n    InstDummy,             // 01 32\n    InstDummy,             // 01 33\n    InstDummy,             // 01 34\n    InstDummy,             // 01 35\n    InstDummy,             // 01 36\n    InstDummy,             // 01 37\n    InstDummy,             // 01 38\n    InstDummy,             // 01 39\n    InstDummy,             // 01 3A\n    InstDummy,             // 01 3B\n    InstDummy,             // 01 3C\n    InstDummy,             // 01 3D\n    InstDummy,             // 01 3E\n    InstDummy,             // 01 3F\n    InstDummy,             // 01 40\n    InstDummy,             // 01 41\n    InstDummy,             // 01 42\n    InstDummy,             // 01 43\n    InstDummy,             // 01 44\n    InstDummy,             // 01 45\n    InstDummy,             // 01 46\n    InstDummy,             // 01 47\n    InstDummy,             // 01 48\n    InstDummy,             // 01 49\n    InstDummy,             // 01 4A\n    InstDummy,             // 01 4B\n    InstDummy,             // 01 4C\n    InstDummy,             // 01 4D\n    InstDummy,             // 01 4E\n    InstDummy,             // 01 4F\n    InstDummy,             // 01 50\n    InstDummy,             // 01 51\n    InstDummy,             // 01 52\n    InstDummy,             // 01 53\n    InstDummy,             // 01 54\n    InstDummy,             // 01 55\n    InstDummy,             // 01 56\n    InstDummy,             // 01 57\n    InstDummy,             // 01 58\n    InstDummy,             // 01 59\n    InstDummy,             // 01 5A\n    InstDummy,             // 01 5B\n    InstDummy,             // 01 5C\n    InstDummy,             // 01 5D\n    InstDummy,             // 01 5E\n    InstDummy,             // 01 5F\n    InstDummy,             // 01 60\n    InstDummy,             // 01 61\n    InstDummy,             // 01 62\n    InstDummy,             // 01 63\n    InstDummy,             // 01 64\n    InstDummy,             // 01 65\n    InstDummy,             // 01 66\n    InstDummy,             // 01 67\n    InstDummy,             // 01 68\n    InstDummy,             // 01 69\n    InstDummy,             // 01 6A\n    InstDummy,             // 01 6B\n    InstDummy,             // 01 6C\n    InstDummy,             // 01 6D\n    InstDummy,             // 01 6E\n    InstDummy,             // 01 6F\n    InstDummy,             // 01 70\n    InstDummy,             // 01 71\n    InstDummy,             // 01 72\n    InstDummy,             // 01 73\n    InstDummy,             // 01 74\n    InstDummy,             // 01 75\n    InstDummy,             // 01 76\n    InstDummy,             // 01 77\n    InstDummy,             // 01 78\n    InstDummy,             // 01 79\n    InstDummy,             // 01 7A\n    InstDummy,             // 01 7B\n    InstDummy,             // 01 7C\n    InstDummy,             // 01 7D\n    InstDummy,             // 01 7E\n    InstDummy,             // 01 7F\n    InstDummy,             // 01 80\n    InstDummy,             // 01 81\n    InstDummy,             // 01 82\n    InstDummy,             // 01 83\n    InstDummy,             // 01 84\n    InstDummy,             // 01 85\n    InstDummy,             // 01 86\n    InstDummy,             // 01 87\n    InstDummy,             // 01 88\n    InstDummy,             // 01 89\n    InstDummy,             // 01 8A\n    InstDummy,             // 01 8B\n    InstDummy,             // 01 8C\n    InstDummy,             // 01 8D\n    InstDummy,             // 01 8E\n    InstDummy,             // 01 8F\n    InstDummy,             // 01 90\n    InstDummy,             // 01 91\n    InstDummy,             // 01 92\n    InstDummy,             // 01 93\n    InstDummy,             // 01 94\n    InstDummy,             // 01 95\n    InstDummy,             // 01 96\n    InstDummy,             // 01 97\n    InstDummy,             // 01 98\n    InstDummy,             // 01 99\n    InstDummy,             // 01 9A\n    InstDummy,             // 01 9B\n    InstDummy,             // 01 9C\n    InstDummy,             // 01 9D\n    InstDummy,             // 01 9E\n    InstDummy,             // 01 9F\n    InstDummy,             // 01 A0\n    InstDummy,             // 01 A1\n    InstDummy,             // 01 A2\n    InstDummy,             // 01 A3\n    InstDummy,             // 01 A4\n    InstDummy,             // 01 A5\n    InstDummy,             // 01 A6\n    InstDummy,             // 01 A7\n    InstDummy,             // 01 A8\n    InstDummy,             // 01 A9\n    InstDummy,             // 01 AA\n    InstDummy,             // 01 AB\n    InstDummy,             // 01 AC\n    InstDummy,             // 01 AD\n    InstDummy,             // 01 AE\n    InstDummy,             // 01 AF\n    InstDummy,             // 01 B0\n    InstDummy,             // 01 B1\n    InstDummy,             // 01 B2\n    InstDummy,             // 01 B3\n    InstDummy,             // 01 B4\n    InstDummy,             // 01 B5\n    InstDummy,             // 01 B6\n    InstDummy,             // 01 B7\n    InstDummy,             // 01 B8\n    InstDummy,             // 01 B9\n    InstDummy,             // 01 BA\n    InstDummy,             // 01 BB\n    InstDummy,             // 01 BC\n    InstDummy,             // 01 BD\n    InstDummy,             // 01 BE\n    InstDummy,             // 01 BF\n    InstDummy,             // 01 C0\n    InstDummy,             // 01 C1\n    InstDummy,             // 01 C2\n    InstDummy,             // 01 C3\n    InstDummy,             // 01 C4\n    InstDummy,             // 01 C5\n    InstDummy,             // 01 C6\n    InstDummy,             // 01 C7\n    InstDummy,             // 01 C8\n    InstDummy,             // 01 C9\n    InstDummy,             // 01 CA\n    InstDummy,             // 01 CB\n    InstDummy,             // 01 CC\n    InstDummy,             // 01 CD\n    InstDummy,             // 01 CE\n    InstDummy,             // 01 CF\n    InstDummy,             // 01 D0\n    InstDummy,             // 01 D1\n    InstDummy,             // 01 D2\n    InstDummy,             // 01 D3\n    InstDummy,             // 01 D4\n    InstDummy,             // 01 D5\n    InstDummy,             // 01 D6\n    InstDummy,             // 01 D7\n    InstDummy,             // 01 D8\n    InstDummy,             // 01 D9\n    InstDummy,             // 01 DA\n    InstDummy,             // 01 DB\n    InstDummy,             // 01 DC\n    InstDummy,             // 01 DD\n    InstDummy,             // 01 DE\n    InstDummy,             // 01 DF\n    InstDummy,             // 01 E0\n    InstDummy,             // 01 E1\n    InstDummy,             // 01 E2\n    InstDummy,             // 01 E3\n    InstDummy,             // 01 E4\n    InstDummy,             // 01 E5\n    InstDummy,             // 01 E6\n    InstDummy,             // 01 E7\n    InstDummy,             // 01 E8\n    InstDummy,             // 01 E9\n    InstDummy,             // 01 EA\n    InstDummy,             // 01 EB\n    InstDummy,             // 01 EC\n    InstDummy,             // 01 ED\n    InstDummy,             // 01 EE\n    InstDummy,             // 01 EF\n    InstDummy,             // 01 F0\n    InstDummy,             // 01 F1\n    InstDummy,             // 01 F2\n    InstDummy,             // 01 F3\n    InstDummy,             // 01 F4\n    InstDummy,             // 01 F5\n    InstDummy,             // 01 F6\n    InstDummy,             // 01 F7\n    InstDummy,             // 01 F8\n    InstDummy,             // 01 F9\n    InstDummy,             // 01 FA\n    InstDummy,             // 01 FB\n    InstDummy,             // 01 FC\n    InstDummy,             // 01 FD\n    InstDummy,             // 01 FE\n    InstDummy              // 01 FF\n};\n\nInstructionProc inline constexpr OpcodeTableUser1_CC[256] = {\n    InstMSinit,            // 10 00\n    InstBGload,            // 10 01\n    InstBGswap,            // 10 02\n    InstBGsetColor,        // 10 03\n    InstBGsetLink,         // 10 04\n    InstCHAload,           // 10 05\n    InstCHAswap,           // 10 06\n    InstBGcopy,            // 10 07\n    InstCHAcopy,           // 10 08\n    InstSaveSlot,          // 10 09\n    InstSystemMain,        // 10 0A\n    InstCharaLayerLoad,    // 10 0B\n    InstGameInfoInit,      // 10 0C\n    InstCHAmove,           // 10 0D\n    InstBGloadEx,          // 10 0E\n    InstDummy,             // 10 0F\n    InstBGrelease,         // 10 10\n    InstCHArelease,        // 10 11\n    InstClearFlagChk,      // 10 12\n    InstOption,            // 10 13\n    InstSystemDataReset,   // 10 14\n    InstDebugData,         // 10 15\n    InstGetCharaPause,     // 10 16\n    InstBGfadeExpInit,     // 10 17\n    InstDummy,             // 10 18\n    InstDummy,             // 10 19\n    InstHelp,              // 10 1A\n    InstDummy,             // 10 1B\n    InstAchievementMenu,   // 10 1C\n    InstSoundMenu,         // 10 1D\n    InstAllClear,          // 10 1E\n    InstAlbum,             // 10 1F\n    InstMovieMode,         // 10 20\n    InstClistInit,         // 10 21\n    InstAutoSave,          // 10 22\n    InstSaveMenu,          // 10 23\n    InstLoadData,          // 10 24\n    InstDummy,             // 10 25\n    InstDummy,             // 10 26\n    InstSetDic,            // 10 27\n    InstSetPlayMode,       // 10 28\n    InstSetEVflag,         // 10 29\n    InstSetCutin,          // 10 2A\n    InstDummy,             // 10 2B\n    InstDummy,             // 10 2C\n    InstAchChkTitle,       // 10 2D\n    InstSetSceneViewFlag,  // 10 2E\n    InstChkClearFlag,      // 10 2F\n    InstBGeffectWave,      // 10 30\n    InstGeotag,            // 10 31\n    InstNameID,            // 10 32\n    InstTips,              // 10 33\n    InstTitleMenuNew,      // 10 34\n    InstDummy,             // 10 35\n    InstBGeffect,          // 10 36\n    InstMapSystem,         // 10 37\n    InstMtrg,              // 10 38\n    InstTwipo_Dash,        // 10 39\n    InstYesNoTriggerCCLCC, // 10 3A\n    InstDummy,             // 10 3B\n    InstDummy,             // 10 3C\n    InstDummy,             // 10 3D\n    InstDummy,             // 10 3E\n    InstDummy,             // 10 3F\n    InstScreenChange,      // 10 40\n    InstExitGame,          // 10 41\n    InstDummy,             // 10 42\n    InstDummy,             // 10 43\n    InstDummy,             // 10 44\n    InstDummy,             // 10 45\n    InstDummy,             // 10 46\n    InstDummy,             // 10 47\n    InstDummy,             // 10 48\n    InstDummy,             // 10 49\n    InstDummy,             // 10 4A\n    InstDummy,             // 10 4B\n    InstDummy,             // 10 4C\n    InstDummy,             // 10 4D\n    InstDummy,             // 10 4E\n    InstDummy,             // 10 4F\n    InstDummy,             // 10 50\n    InstDummy,             // 10 51\n    InstDummy,             // 10 52\n    InstDummy,             // 10 53\n    InstDummy,             // 10 54\n    InstDummy,             // 10 55\n    InstDummy,             // 10 56\n    InstDummy,             // 10 57\n    InstDummy,             // 10 58\n    InstDummy,             // 10 59\n    InstDummy,             // 10 5A\n    InstDummy,             // 10 5B\n    InstDummy,             // 10 5C\n    InstDummy,             // 10 5D\n    InstDummy,             // 10 5E\n    InstDummy,             // 10 5F\n    InstDummy,             // 10 60\n    InstDummy,             // 10 61\n    InstDummy,             // 10 62\n    InstDummy,             // 10 63\n    InstDummy,             // 10 64\n    InstDummy,             // 10 65\n    InstDummy,             // 10 66\n    InstDummy,             // 10 67\n    InstDummy,             // 10 68\n    InstDummy,             // 10 69\n    InstDummy,             // 10 6A\n    InstDummy,             // 10 6B\n    InstDummy,             // 10 6C\n    InstDummy,             // 10 6D\n    InstDummy,             // 10 6E\n    InstDummy,             // 10 6F\n    InstDummy,             // 10 70\n    InstDummy,             // 10 71\n    InstDummy,             // 10 72\n    InstDummy,             // 10 73\n    InstDummy,             // 10 74\n    InstDummy,             // 10 75\n    InstDummy,             // 10 76\n    InstDummy,             // 10 77\n    InstDummy,             // 10 78\n    InstDummy,             // 10 79\n    InstDummy,             // 10 7A\n    InstDummy,             // 10 7B\n    InstDummy,             // 10 7C\n    InstDummy,             // 10 7D\n    InstDummy,             // 10 7E\n    InstDummy,             // 10 7F\n    InstDummy,             // 10 80\n    InstDummy,             // 10 81\n    InstDummy,             // 10 82\n    InstDummy,             // 10 83\n    InstDummy,             // 10 84\n    InstDummy,             // 10 85\n    InstDummy,             // 10 86\n    InstDummy,             // 10 87\n    InstDummy,             // 10 88\n    InstDummy,             // 10 89\n    InstDummy,             // 10 8A\n    InstDummy,             // 10 8B\n    InstDummy,             // 10 8C\n    InstDummy,             // 10 8D\n    InstDummy,             // 10 8E\n    InstDummy,             // 10 8F\n    InstDummy,             // 10 90\n    InstDummy,             // 10 91\n    InstDummy,             // 10 92\n    InstDummy,             // 10 93\n    InstDummy,             // 10 94\n    InstDummy,             // 10 95\n    InstDummy,             // 10 96\n    InstDummy,             // 10 97\n    InstDummy,             // 10 98\n    InstDummy,             // 10 99\n    InstDummy,             // 10 9A\n    InstDummy,             // 10 9B\n    InstDummy,             // 10 9C\n    InstDummy,             // 10 9D\n    InstDummy,             // 10 9E\n    InstDummy,             // 10 9F\n    InstDummy,             // 10 A0\n    InstDummy,             // 10 A1\n    InstDummy,             // 10 A2\n    InstDummy,             // 10 A3\n    InstDummy,             // 10 A4\n    InstDummy,             // 10 A5\n    InstDummy,             // 10 A6\n    InstDummy,             // 10 A7\n    InstDummy,             // 10 A8\n    InstDummy,             // 10 A9\n    InstDummy,             // 10 AA\n    InstDummy,             // 10 AB\n    InstDummy,             // 10 AC\n    InstDummy,             // 10 AD\n    InstDummy,             // 10 AE\n    InstDummy,             // 10 AF\n    InstDummy,             // 10 B0\n    InstDummy,             // 10 B1\n    InstDummy,             // 10 B2\n    InstDummy,             // 10 B3\n    InstDummy,             // 10 B4\n    InstDummy,             // 10 B5\n    InstDummy,             // 10 B6\n    InstDummy,             // 10 B7\n    InstDummy,             // 10 B8\n    InstDummy,             // 10 B9\n    InstDummy,             // 10 BA\n    InstDummy,             // 10 BB\n    InstDummy,             // 10 BC\n    InstDummy,             // 10 BD\n    InstDummy,             // 10 BE\n    InstDummy,             // 10 BF\n    InstDummy,             // 10 C0\n    InstDummy,             // 10 C1\n    InstDummy,             // 10 C2\n    InstDummy,             // 10 C3\n    InstDummy,             // 10 C4\n    InstDummy,             // 10 C5\n    InstDummy,             // 10 C6\n    InstDummy,             // 10 C7\n    InstDummy,             // 10 C8\n    InstDummy,             // 10 C9\n    InstDummy,             // 10 CA\n    InstDummy,             // 10 CB\n    InstDummy,             // 10 CC\n    InstDummy,             // 10 CD\n    InstDummy,             // 10 CE\n    InstDummy,             // 10 CF\n    InstDummy,             // 10 D0\n    InstDummy,             // 10 D1\n    InstDummy,             // 10 D2\n    InstDummy,             // 10 D3\n    InstDummy,             // 10 D4\n    InstDummy,             // 10 D5\n    InstDummy,             // 10 D6\n    InstDummy,             // 10 D7\n    InstDummy,             // 10 D8\n    InstDummy,             // 10 D9\n    InstDummy,             // 10 DA\n    InstDummy,             // 10 DB\n    InstDummy,             // 10 DC\n    InstDummy,             // 10 DD\n    InstDummy,             // 10 DE\n    InstDummy,             // 10 DF\n    InstDummy,             // 10 E0\n    InstDummy,             // 10 E1\n    InstDummy,             // 10 E2\n    InstDummy,             // 10 E3\n    InstDummy,             // 10 E4\n    InstDummy,             // 10 E5\n    InstDummy,             // 10 E6\n    InstDummy,             // 10 E7\n    InstDummy,             // 10 E8\n    InstDummy,             // 10 E9\n    InstDummy,             // 10 EA\n    InstDummy,             // 10 EB\n    InstDummy,             // 10 EC\n    InstDummy,             // 10 ED\n    InstDummy,             // 10 EE\n    InstDummy,             // 10 EF\n    InstDummy,             // 10 F0\n    InstDummy,             // 10 F1\n    InstDummy,             // 10 F2\n    InstDummy,             // 10 F3\n    InstDummy,             // 10 F4\n    InstDummy,             // 10 F5\n    InstDummy,             // 10 F6\n    InstDummy,             // 10 F7\n    InstDummy,             // 10 F8\n    InstDummy,             // 10 F9\n    InstDummy,             // 10 FA\n    InstDummy,             // 10 FB\n    InstDummy,             // 10 FC\n    InstDummy,             // 10 FD\n    InstDummy,             // 10 FE\n    InstDummy              // 10 FF\n};\n\n}\n\n}\n\n// clang-format on"
  },
  {
    "path": "src/vm/opcodetables_chlcc.h",
    "content": "#include \"inst_system.h\"\n#include \"inst_controlflow.h\"\n#include \"inst_graphics2d.h\"\n#include \"inst_graphics3d.h\"\n#include \"inst_dialogue.h\"\n#include \"inst_sound.h\"\n#include \"inst_movie.h\"\n#include \"inst_gamespecific.h\"\n#include \"inst_misc.h\"\n\n// clang-format off\n\nnamespace Impacto {\n\nnamespace Vm {\n\nInstructionProc inline constexpr OpcodeTableSystem_CHLCC[256] = {\n    InstEnd,                  // 00 00\n    InstCreateThread,         // 00 01\n    InstKillThread,           // 00 02\n    InstReset,                // 00 03\n    InstScriptLoad,           // 00 04\n    InstWait,                 // 00 05\n    InstHalt,                 // 00 06\n    InstJump,                 // 00 07\n    InstJumpTable,            // 00 08\n    InstGetLabelAdr,          // 00 09\n    InstIf,                   // 00 0A\n    InstCall,                 // 00 0B\n    InstJumpFar,              // 00 0C\n    InstCallFar,              // 00 0D\n    InstReturn,               // 00 0E\n    InstLoop,                 // 00 0F\n    InstFlagOnJump,           // 00 10\n    InstFlagOnWait,           // 00 11\n    InstSetFlag,              // 00 12\n    InstResetFlag,            // 00 13\n    InstCopyFlag,             // 00 14\n    InstKeyOnJump,            // 00 15\n    InstKeyWait,              // 00 16\n    InstKeyWaitTimer,         // 00 17\n    InstMemberWrite,          // 00 18\n    InstThreadControl,        // 00 19\n    InstGetSelfPointer,       // 00 1A\n    InstLoadJump,             // 00 1B\n    InstVsync,                // 00 1C\n    InstTest,                 // 00 1D\n    InstThreadControlStore,   // 00 1E\n    InstSwitch,               // 00 1F\n    InstCase,                 // 00 20\n    InstBGMplay,              // 00 21\n    InstBGMstop,              // 00 22\n    InstSEplay,               // 00 23\n    InstSEstop,               // 00 24\n    InstPadAct,               // 00 25\n    InstSSEplay,              // 00 26\n    InstSSEstop,              // 00 27\n    InstCopyThreadWork,       // 00 28\n    InstUPLmenuUI,            // 00 29\n    InstSaveOld,              // 00 2A\n    InstSaveIconLoad,         // 00 2B\n    InstBGMflag,              // 00 2C\n    InstUPLxTitle,            // 00 2D\n    InstPresence,             // 00 2E\n    InstSetAchievement,       // 00 2F\n    InstSetPlayer,            // 00 30\n    InstVoiceTableLoadMaybe,  // 00 31\n    InstSetPadCustom,         // 00 32\n    InstMwait,                // 00 33\n    InstTerminate,            // 00 34\n    InstSignIn,               // 00 35\n    InstAchievementIcon,      // 00 36\n    InstVoicePlayOld,         // 00 37\n    InstVoiceStop,            // 00 38\n    InstVoicePlayWait,        // 00 39\n    InstBGMduelPlay,          // 00 3A\n    InstSNDpause,             // 00 3B\n    InstSEplayWait,           // 00 3C\n    InstDebugPrint,           // 00 3D\n    InstResetSoundAll,        // 00 3E\n    InstSNDloadStop,          // 00 3F\n    InstBGMstopWait,          // 00 40\n    InstUnk0041,              // 00 41\n    InstSetX360SysMesPos,     // 00 42\n    InstSystemMes,            // 00 43\n    InstSystemMenu,           // 00 44\n    InstGetNowTime,           // 00 45\n    InstGetSystemStatus,      // 00 46\n    InstReboot,               // 00 47\n    InstReloadScript,         // 00 48\n    InstReloadScriptMenu,     // 00 49\n    InstDebugEditer,          // 00 4A\n    InstDummy,                // 00 4B\n    InstDummy,                // 00 4C\n    InstSysVoicePlay,         // 00 4D\n    InstPadActEx,             // 00 4E\n    InstDebugSetup,           // 00 4F\n    InstPressStart,           // 00 50\n    InstGlobalSystemMessage,  // 00 51\n    InstUnk0052,              // 00 52\n    InstUnk0053,              // 00 53\n    InstKeyboardOnJump,       // 00 54\n    InstReturnIfFlag,         // 00 55\n    InstDummy,                // 00 56\n    InstDummy,                // 00 57\n    InstDummy,                // 00 58\n    InstDummy,                // 00 59\n    InstDummy,                // 00 5A\n    InstDummy,                // 00 5B\n    InstDummy,                // 00 5C\n    InstDummy,                // 00 5D\n    InstDummy,                // 00 5E\n    InstDummy,                // 00 5F\n    InstDummy,                // 00 60\n    InstDummy,                // 00 61\n    InstDummy,                // 00 62\n    InstDummy,                // 00 63\n    InstDummy,                // 00 64\n    InstDummy,                // 00 65\n    InstDummy,                // 00 66\n    InstDummy,                // 00 67\n    InstDummy,                // 00 68\n    InstDummy,                // 00 69\n    InstDummy,                // 00 6A\n    InstDummy,                // 00 6B\n    InstDummy,                // 00 6C\n    InstDummy,                // 00 6D\n    InstDummy,                // 00 6E\n    InstDummy,                // 00 6F\n    InstDummy,                // 00 70\n    InstDummy,                // 00 71\n    InstDummy,                // 00 72\n    InstDummy,                // 00 73\n    InstDummy,                // 00 74\n    InstDummy,                // 00 75\n    InstDummy,                // 00 76\n    InstDummy,                // 00 77\n    InstDummy,                // 00 78\n    InstDummy,                // 00 79\n    InstDummy,                // 00 7A\n    InstDummy,                // 00 7B\n    InstDummy,                // 00 7C\n    InstDummy,                // 00 7D\n    InstDummy,                // 00 7E\n    InstDummy,                // 00 7F\n    InstDummy,                // 00 80\n    InstDummy,                // 00 81\n    InstDummy,                // 00 82\n    InstDummy,                // 00 83\n    InstDummy,                // 00 84\n    InstDummy,                // 00 85\n    InstDummy,                // 00 86\n    InstDummy,                // 00 87\n    InstDummy,                // 00 88\n    InstDummy,                // 00 89\n    InstDummy,                // 00 8A\n    InstDummy,                // 00 8B\n    InstDummy,                // 00 8C\n    InstDummy,                // 00 8D\n    InstDummy,                // 00 8E\n    InstDummy,                // 00 8F\n    InstDummy,                // 00 90\n    InstDummy,                // 00 91\n    InstDummy,                // 00 92\n    InstDummy,                // 00 93\n    InstDummy,                // 00 94\n    InstDummy,                // 00 95\n    InstDummy,                // 00 96\n    InstDummy,                // 00 97\n    InstDummy,                // 00 98\n    InstDummy,                // 00 99\n    InstDummy,                // 00 9A\n    InstDummy,                // 00 9B\n    InstDummy,                // 00 9C\n    InstDummy,                // 00 9D\n    InstDummy,                // 00 9E\n    InstDummy,                // 00 9F\n    InstDummy,                // 00 A0\n    InstDummy,                // 00 A1\n    InstDummy,                // 00 A2\n    InstDummy,                // 00 A3\n    InstDummy,                // 00 A4\n    InstDummy,                // 00 A5\n    InstDummy,                // 00 A6\n    InstDummy,                // 00 A7\n    InstDummy,                // 00 A8\n    InstDummy,                // 00 A9\n    InstDummy,                // 00 AA\n    InstDummy,                // 00 AB\n    InstDummy,                // 00 AC\n    InstDummy,                // 00 AD\n    InstDummy,                // 00 AE\n    InstDummy,                // 00 AF\n    InstDummy,                // 00 B0\n    InstDummy,                // 00 B1\n    InstDummy,                // 00 B2\n    InstDummy,                // 00 B3\n    InstDummy,                // 00 B4\n    InstDummy,                // 00 B5\n    InstDummy,                // 00 B6\n    InstDummy,                // 00 B7\n    InstDummy,                // 00 B8\n    InstDummy,                // 00 B9\n    InstDummy,                // 00 BA\n    InstDummy,                // 00 BB\n    InstDummy,                // 00 BC\n    InstDummy,                // 00 BD\n    InstDummy,                // 00 BE\n    InstDummy,                // 00 BF\n    InstDummy,                // 00 C0\n    InstDummy,                // 00 C1\n    InstDummy,                // 00 C2\n    InstDummy,                // 00 C3\n    InstDummy,                // 00 C4\n    InstDummy,                // 00 C5\n    InstDummy,                // 00 C6\n    InstDummy,                // 00 C7\n    InstDummy,                // 00 C8\n    InstDummy,                // 00 C9\n    InstDummy,                // 00 CA\n    InstDummy,                // 00 CB\n    InstDummy,                // 00 CC\n    InstDummy,                // 00 CD\n    InstDummy,                // 00 CE\n    InstDummy,                // 00 CF\n    InstDummy,                // 00 D0\n    InstDummy,                // 00 D1\n    InstDummy,                // 00 D2\n    InstDummy,                // 00 D3\n    InstDummy,                // 00 D4\n    InstDummy,                // 00 D5\n    InstDummy,                // 00 D6\n    InstDummy,                // 00 D7\n    InstDummy,                // 00 D8\n    InstDummy,                // 00 D9\n    InstDummy,                // 00 DA\n    InstDummy,                // 00 DB\n    InstDummy,                // 00 DC\n    InstDummy,                // 00 DD\n    InstDummy,                // 00 DE\n    InstDummy,                // 00 DF\n    InstDummy,                // 00 E0\n    InstDummy,                // 00 E1\n    InstDummy,                // 00 E2\n    InstDummy,                // 00 E3\n    InstDummy,                // 00 E4\n    InstDummy,                // 00 E5\n    InstDummy,                // 00 E6\n    InstDummy,                // 00 E7\n    InstDummy,                // 00 E8\n    InstDummy,                // 00 E9\n    InstDummy,                // 00 EA\n    InstDummy,                // 00 EB\n    InstDummy,                // 00 EC\n    InstDummy,                // 00 ED\n    InstDummy,                // 00 EE\n    InstDummy,                // 00 EF\n    InstDummy,                // 00 F0\n    InstDummy,                // 00 F1\n    InstDummy,                // 00 F2\n    InstDummy,                // 00 F3\n    InstDummy,                // 00 F4\n    InstDummy,                // 00 F5\n    InstDummy,                // 00 F6\n    InstDummy,                // 00 F7\n    InstDummy,                // 00 F8\n    InstDummy,                // 00 F9\n    InstDummy,                // 00 FA\n    InstDummy,                // 00 FB\n    InstDummy,                // 00 FC\n    InstDummy,                // 00 FD\n    InstDummy,                // 00 FE\n    InstDummy                 // 00 FF\n};\n\nInstructionProc inline constexpr OpcodeTableGraph_CHLCC[256] = {\n    InstCreateSurf,        // 01 00\n    InstReleaseSurf,       // 01 01\n    InstLoadPic,           // 01 02\n    InstReleaseSurf,       // 01 03\n    InstSurfFill,          // 01 04\n    InstCalc,              // 01 05\n    InstMesViewFlag,       // 01 06\n    InstSetMesWinPri,      // 01 07\n    InstMesSync,           // 01 08\n    InstMesSetID,          // 01 09\n    InstMesCls,            // 01 0A\n    InstMesVoiceWait,      // 01 0B\n    InstMes,               // 01 0C\n    InstMesMain,           // 01 0D\n    InstSetMesModeFormat,  // 01 0E\n    InstSetNGmoji,         // 01 0F\n    InstMesRev,            // 01 10\n    InstMessWindow,        // 01 11\n    InstSel,               // 01 12\n    InstSelect,            // 01 13\n    InstSysSel,            // 01 14\n    InstSysSelect,         // 01 15\n    InstDummy,             // 01 16\n    InstDummy,             // 01 17\n    InstDummy,             // 01 18\n    InstDummy,             // 01 19\n    InstDummy,             // 01 1A\n    InstDummy,             // 01 1B\n    InstDummy,             // 01 1C\n    InstDummy,             // 01 1D\n    InstDummy,             // 01 1E\n    InstUnk011F,           // 01 1F\n    InstSCcapture,         // 01 20\n    InstSetTextTable,      // 01 21\n    InstPlayMovieOld,      // 01 22\n    InstMovieMain,         // 01 23\n    InstLoadMovie,         // 01 24\n    InstSetRevMes,         // 01 25\n    InstPlayMovieMemory,   // 01 26\n    InstPlayMovieOld2,     // 01 27\n    InstMovieMain,         // 01 28\n    InstLoadMovie,         // 01 29\n    InstPlayMovieMemory,   // 01 2A\n    InstSFDpause,          // 01 2B\n    InstPlayMovieOld,      // 01 2C\n    InstUnk012D,           // 01 2D\n    InstDummy,             // 01 2E\n    InstDummy,             // 01 2F\n    InstDummy,             // 01 30\n    InstDummy,             // 01 31\n    InstDummy,             // 01 32\n    InstDummy,             // 01 33\n    InstDummy,             // 01 34\n    InstDummy,             // 01 35\n    InstDummy,             // 01 36\n    InstDummy,             // 01 37\n    InstDummy,             // 01 38\n    InstDummy,             // 01 39\n    InstDummy,             // 01 3A\n    InstDummy,             // 01 3B\n    InstDummy,             // 01 3C\n    InstDummy,             // 01 3D\n    InstDummy,             // 01 3E\n    InstDummy,             // 01 3F\n    InstDummy,             // 01 40\n    InstDummy,             // 01 41\n    InstDummy,             // 01 42\n    InstDummy,             // 01 43\n    InstDummy,             // 01 44\n    InstDummy,             // 01 45\n    InstDummy,             // 01 46\n    InstDummy,             // 01 47\n    InstDummy,             // 01 48\n    InstDummy,             // 01 49\n    InstDummy,             // 01 4A\n    InstDummy,             // 01 4B\n    InstDummy,             // 01 4C\n    InstDummy,             // 01 4D\n    InstDummy,             // 01 4E\n    InstDummy,             // 01 4F\n    InstDummy,             // 01 50\n    InstDummy,             // 01 51\n    InstDummy,             // 01 52\n    InstDummy,             // 01 53\n    InstDummy,             // 01 54\n    InstDummy,             // 01 55\n    InstDummy,             // 01 56\n    InstDummy,             // 01 57\n    InstDummy,             // 01 58\n    InstDummy,             // 01 59\n    InstDummy,             // 01 5A\n    InstDummy,             // 01 5B\n    InstDummy,             // 01 5C\n    InstDummy,             // 01 5D\n    InstDummy,             // 01 5E\n    InstDummy,             // 01 5F\n    InstDummy,             // 01 60\n    InstDummy,             // 01 61\n    InstDummy,             // 01 62\n    InstDummy,             // 01 63\n    InstDummy,             // 01 64\n    InstDummy,             // 01 65\n    InstDummy,             // 01 66\n    InstDummy,             // 01 67\n    InstDummy,             // 01 68\n    InstDummy,             // 01 69\n    InstDummy,             // 01 6A\n    InstDummy,             // 01 6B\n    InstDummy,             // 01 6C\n    InstDummy,             // 01 6D\n    InstDummy,             // 01 6E\n    InstDummy,             // 01 6F\n    InstDummy,             // 01 70\n    InstDummy,             // 01 71\n    InstDummy,             // 01 72\n    InstDummy,             // 01 73\n    InstDummy,             // 01 74\n    InstDummy,             // 01 75\n    InstDummy,             // 01 76\n    InstDummy,             // 01 77\n    InstDummy,             // 01 78\n    InstDummy,             // 01 79\n    InstDummy,             // 01 7A\n    InstDummy,             // 01 7B\n    InstDummy,             // 01 7C\n    InstDummy,             // 01 7D\n    InstDummy,             // 01 7E\n    InstDummy,             // 01 7F\n    InstDummy,             // 01 80\n    InstDummy,             // 01 81\n    InstDummy,             // 01 82\n    InstDummy,             // 01 83\n    InstDummy,             // 01 84\n    InstDummy,             // 01 85\n    InstDummy,             // 01 86\n    InstDummy,             // 01 87\n    InstDummy,             // 01 88\n    InstDummy,             // 01 89\n    InstDummy,             // 01 8A\n    InstDummy,             // 01 8B\n    InstDummy,             // 01 8C\n    InstDummy,             // 01 8D\n    InstDummy,             // 01 8E\n    InstDummy,             // 01 8F\n    InstDummy,             // 01 90\n    InstDummy,             // 01 91\n    InstDummy,             // 01 92\n    InstDummy,             // 01 93\n    InstDummy,             // 01 94\n    InstDummy,             // 01 95\n    InstDummy,             // 01 96\n    InstDummy,             // 01 97\n    InstDummy,             // 01 98\n    InstDummy,             // 01 99\n    InstDummy,             // 01 9A\n    InstDummy,             // 01 9B\n    InstDummy,             // 01 9C\n    InstDummy,             // 01 9D\n    InstDummy,             // 01 9E\n    InstDummy,             // 01 9F\n    InstDummy,             // 01 A0\n    InstDummy,             // 01 A1\n    InstDummy,             // 01 A2\n    InstDummy,             // 01 A3\n    InstDummy,             // 01 A4\n    InstDummy,             // 01 A5\n    InstDummy,             // 01 A6\n    InstDummy,             // 01 A7\n    InstDummy,             // 01 A8\n    InstDummy,             // 01 A9\n    InstDummy,             // 01 AA\n    InstDummy,             // 01 AB\n    InstDummy,             // 01 AC\n    InstDummy,             // 01 AD\n    InstDummy,             // 01 AE\n    InstDummy,             // 01 AF\n    InstDummy,             // 01 B0\n    InstDummy,             // 01 B1\n    InstDummy,             // 01 B2\n    InstDummy,             // 01 B3\n    InstDummy,             // 01 B4\n    InstDummy,             // 01 B5\n    InstDummy,             // 01 B6\n    InstDummy,             // 01 B7\n    InstDummy,             // 01 B8\n    InstDummy,             // 01 B9\n    InstDummy,             // 01 BA\n    InstDummy,             // 01 BB\n    InstDummy,             // 01 BC\n    InstDummy,             // 01 BD\n    InstDummy,             // 01 BE\n    InstDummy,             // 01 BF\n    InstDummy,             // 01 C0\n    InstDummy,             // 01 C1\n    InstDummy,             // 01 C2\n    InstDummy,             // 01 C3\n    InstDummy,             // 01 C4\n    InstDummy,             // 01 C5\n    InstDummy,             // 01 C6\n    InstDummy,             // 01 C7\n    InstDummy,             // 01 C8\n    InstDummy,             // 01 C9\n    InstDummy,             // 01 CA\n    InstDummy,             // 01 CB\n    InstDummy,             // 01 CC\n    InstDummy,             // 01 CD\n    InstDummy,             // 01 CE\n    InstDummy,             // 01 CF\n    InstDummy,             // 01 D0\n    InstDummy,             // 01 D1\n    InstDummy,             // 01 D2\n    InstDummy,             // 01 D3\n    InstDummy,             // 01 D4\n    InstDummy,             // 01 D5\n    InstDummy,             // 01 D6\n    InstDummy,             // 01 D7\n    InstDummy,             // 01 D8\n    InstDummy,             // 01 D9\n    InstDummy,             // 01 DA\n    InstDummy,             // 01 DB\n    InstDummy,             // 01 DC\n    InstDummy,             // 01 DD\n    InstDummy,             // 01 DE\n    InstDummy,             // 01 DF\n    InstDummy,             // 01 E0\n    InstDummy,             // 01 E1\n    InstDummy,             // 01 E2\n    InstDummy,             // 01 E3\n    InstDummy,             // 01 E4\n    InstDummy,             // 01 E5\n    InstDummy,             // 01 E6\n    InstDummy,             // 01 E7\n    InstDummy,             // 01 E8\n    InstDummy,             // 01 E9\n    InstDummy,             // 01 EA\n    InstDummy,             // 01 EB\n    InstDummy,             // 01 EC\n    InstDummy,             // 01 ED\n    InstDummy,             // 01 EE\n    InstDummy,             // 01 EF\n    InstDummy,             // 01 F0\n    InstDummy,             // 01 F1\n    InstDummy,             // 01 F2\n    InstDummy,             // 01 F3\n    InstDummy,             // 01 F4\n    InstDummy,             // 01 F5\n    InstDummy,             // 01 F6\n    InstDummy,             // 01 F7\n    InstDummy,             // 01 F8\n    InstDummy,             // 01 F9\n    InstDummy,             // 01 FA\n    InstDummy,             // 01 FB\n    InstDummy,             // 01 FC\n    InstDummy,             // 01 FD\n    InstDummy,             // 01 FE\n    InstDummy              // 01 FF\n};\n\nInstructionProc inline constexpr OpcodeTableUser1_CHLCC[256] = {\n    InstMSinit,            // 10 00\n    InstBGload,            // 10 01\n    InstBGswap,            // 10 02\n    InstBGsetColor,        // 10 03\n    InstBGsetLinkOld,      // 10 04\n    InstCHAload,           // 10 05\n    InstCHAswap,           // 10 06\n    InstBGcopy,            // 10 07\n    InstCHAcopy,           // 10 08\n    InstSaveSlot,          // 10 09\n    InstSystemMain,        // 10 0A\n    InstCharaLayerLoad,    // 10 0B\n    InstGameInfoInit,      // 10 0C\n    InstCHAmove,           // 10 0D\n    InstBGloadEx,          // 10 0E\n    InstEVload,            // 10 0F\n    InstEVinit,            // 10 10\n    InstEVset,             // 10 11\n    InstClearFlagChkOld,   // 10 12\n    InstOption,            // 10 13\n    InstSystemDataReset,   // 10 14\n    InstDebugData,         // 10 15\n    InstGetCharaPause,     // 10 16\n    InstBGfadeExpInit,     // 10 17\n    InstDummy,             // 10 18\n    InstDummy,             // 10 19\n    InstDummy,             // 10 1A\n    InstAchievementMenu,   // 10 1B\n    InstDummy,             // 10 1C\n    InstSoundMenu,         // 10 1D\n    InstAllClear,          // 10 1E\n    InstAlbum,             // 10 1F\n    InstMovieMode,         // 10 20\n    InstClistInit,         // 10 21\n    InstAutoSaveOld,       // 10 22\n    InstSaveMenuOld,       // 10 23\n    InstLoadDataOld,       // 10 24\n    InstDummy,             // 10 25\n    InstDummy,             // 10 26\n    InstSetDic,            // 10 27\n    InstSetPlayMode,       // 10 28\n    InstSetEVflag,         // 10 29\n    InstSetCutin,          // 10 2A\n    InstDummy,             // 10 2B\n    InstDummy,             // 10 2C\n    InstDelusionTriggerCHLCC,// 10 2D\n    InstSetSceneViewFlag,  // 10 2E\n    InstChkClearFlag,      // 10 2F\n    InstBGeffectWave,      // 10 30\n    InstGeotag,            // 10 31\n    InstNameID,            // 10 32\n    InstTips,              // 10 33\n    InstTitleMenuOld,      // 10 34\n    InstAllClearChkCHLCC,  // 10 35\n    InstBGeffect,          // 10 36\n    InstPhoneSG,           // 10 37\n    InstUnk1038Darling,    // 10 38\n    InstTwipo,             // 10 39\n    InstUnk103A,           // 10 3A\n    InstDummy,             // 10 3B\n    InstDummy,             // 10 3C\n    InstDummy,             // 10 3D\n    InstDummy,             // 10 3E\n    InstDummy,             // 10 3F\n    InstDummy,             // 10 40\n    InstExitGame,          // 10 41\n    InstDummy,             // 10 42\n    InstDummy,             // 10 43\n    InstDummy,             // 10 44\n    InstDummy,             // 10 45\n    InstDummy,             // 10 46\n    InstDummy,             // 10 47\n    InstDummy,             // 10 48\n    InstDummy,             // 10 49\n    InstDummy,             // 10 4A\n    InstDummy,             // 10 4B\n    InstDummy,             // 10 4C\n    InstDummy,             // 10 4D\n    InstDummy,             // 10 4E\n    InstDummy,             // 10 4F\n    InstDummy,             // 10 50\n    InstDummy,             // 10 51\n    InstDummy,             // 10 52\n    InstDummy,             // 10 53\n    InstDummy,             // 10 54\n    InstDummy,             // 10 55\n    InstDummy,             // 10 56\n    InstDummy,             // 10 57\n    InstDummy,             // 10 58\n    InstDummy,             // 10 59\n    InstDummy,             // 10 5A\n    InstDummy,             // 10 5B\n    InstDummy,             // 10 5C\n    InstDummy,             // 10 5D\n    InstDummy,             // 10 5E\n    InstDummy,             // 10 5F\n    InstDummy,             // 10 60\n    InstDummy,             // 10 61\n    InstDummy,             // 10 62\n    InstDummy,             // 10 63\n    InstDummy,             // 10 64\n    InstDummy,             // 10 65\n    InstDummy,             // 10 66\n    InstDummy,             // 10 67\n    InstDummy,             // 10 68\n    InstDummy,             // 10 69\n    InstDummy,             // 10 6A\n    InstDummy,             // 10 6B\n    InstDummy,             // 10 6C\n    InstDummy,             // 10 6D\n    InstDummy,             // 10 6E\n    InstDummy,             // 10 6F\n    InstDummy,             // 10 70\n    InstDummy,             // 10 71\n    InstDummy,             // 10 72\n    InstDummy,             // 10 73\n    InstDummy,             // 10 74\n    InstDummy,             // 10 75\n    InstDummy,             // 10 76\n    InstDummy,             // 10 77\n    InstDummy,             // 10 78\n    InstDummy,             // 10 79\n    InstDummy,             // 10 7A\n    InstDummy,             // 10 7B\n    InstDummy,             // 10 7C\n    InstDummy,             // 10 7D\n    InstDummy,             // 10 7E\n    InstDummy,             // 10 7F\n    InstDummy,             // 10 80\n    InstDummy,             // 10 81\n    InstDummy,             // 10 82\n    InstDummy,             // 10 83\n    InstDummy,             // 10 84\n    InstDummy,             // 10 85\n    InstDummy,             // 10 86\n    InstDummy,             // 10 87\n    InstDummy,             // 10 88\n    InstDummy,             // 10 89\n    InstDummy,             // 10 8A\n    InstDummy,             // 10 8B\n    InstDummy,             // 10 8C\n    InstDummy,             // 10 8D\n    InstDummy,             // 10 8E\n    InstDummy,             // 10 8F\n    InstDummy,             // 10 90\n    InstDummy,             // 10 91\n    InstDummy,             // 10 92\n    InstDummy,             // 10 93\n    InstDummy,             // 10 94\n    InstDummy,             // 10 95\n    InstDummy,             // 10 96\n    InstDummy,             // 10 97\n    InstDummy,             // 10 98\n    InstDummy,             // 10 99\n    InstDummy,             // 10 9A\n    InstDummy,             // 10 9B\n    InstDummy,             // 10 9C\n    InstDummy,             // 10 9D\n    InstDummy,             // 10 9E\n    InstDummy,             // 10 9F\n    InstDummy,             // 10 A0\n    InstDummy,             // 10 A1\n    InstDummy,             // 10 A2\n    InstDummy,             // 10 A3\n    InstDummy,             // 10 A4\n    InstDummy,             // 10 A5\n    InstDummy,             // 10 A6\n    InstDummy,             // 10 A7\n    InstDummy,             // 10 A8\n    InstDummy,             // 10 A9\n    InstDummy,             // 10 AA\n    InstDummy,             // 10 AB\n    InstDummy,             // 10 AC\n    InstDummy,             // 10 AD\n    InstDummy,             // 10 AE\n    InstDummy,             // 10 AF\n    InstDummy,             // 10 B0\n    InstDummy,             // 10 B1\n    InstDummy,             // 10 B2\n    InstDummy,             // 10 B3\n    InstDummy,             // 10 B4\n    InstDummy,             // 10 B5\n    InstDummy,             // 10 B6\n    InstDummy,             // 10 B7\n    InstDummy,             // 10 B8\n    InstDummy,             // 10 B9\n    InstDummy,             // 10 BA\n    InstDummy,             // 10 BB\n    InstDummy,             // 10 BC\n    InstDummy,             // 10 BD\n    InstDummy,             // 10 BE\n    InstDummy,             // 10 BF\n    InstDummy,             // 10 C0\n    InstDummy,             // 10 C1\n    InstDummy,             // 10 C2\n    InstDummy,             // 10 C3\n    InstDummy,             // 10 C4\n    InstDummy,             // 10 C5\n    InstDummy,             // 10 C6\n    InstDummy,             // 10 C7\n    InstDummy,             // 10 C8\n    InstDummy,             // 10 C9\n    InstDummy,             // 10 CA\n    InstDummy,             // 10 CB\n    InstDummy,             // 10 CC\n    InstDummy,             // 10 CD\n    InstDummy,             // 10 CE\n    InstDummy,             // 10 CF\n    InstDummy,             // 10 D0\n    InstDummy,             // 10 D1\n    InstDummy,             // 10 D2\n    InstDummy,             // 10 D3\n    InstDummy,             // 10 D4\n    InstDummy,             // 10 D5\n    InstDummy,             // 10 D6\n    InstDummy,             // 10 D7\n    InstDummy,             // 10 D8\n    InstDummy,             // 10 D9\n    InstDummy,             // 10 DA\n    InstDummy,             // 10 DB\n    InstDummy,             // 10 DC\n    InstDummy,             // 10 DD\n    InstDummy,             // 10 DE\n    InstDummy,             // 10 DF\n    InstDummy,             // 10 E0\n    InstDummy,             // 10 E1\n    InstDummy,             // 10 E2\n    InstDummy,             // 10 E3\n    InstDummy,             // 10 E4\n    InstDummy,             // 10 E5\n    InstDummy,             // 10 E6\n    InstDummy,             // 10 E7\n    InstDummy,             // 10 E8\n    InstDummy,             // 10 E9\n    InstDummy,             // 10 EA\n    InstDummy,             // 10 EB\n    InstDummy,             // 10 EC\n    InstDummy,             // 10 ED\n    InstDummy,             // 10 EE\n    InstDummy,             // 10 EF\n    InstDummy,             // 10 F0\n    InstDummy,             // 10 F1\n    InstDummy,             // 10 F2\n    InstDummy,             // 10 F3\n    InstDummy,             // 10 F4\n    InstDummy,             // 10 F5\n    InstDummy,             // 10 F6\n    InstDummy,             // 10 F7\n    InstDummy,             // 10 F8\n    InstDummy,             // 10 F9\n    InstDummy,             // 10 FA\n    InstDummy,             // 10 FB\n    InstDummy,             // 10 FC\n    InstDummy,             // 10 FD\n    InstDummy,             // 10 FE\n    InstDummy              // 10 FF\n};\n\n}\n\n}\n\n// clang-format on"
  },
  {
    "path": "src/vm/opcodetables_chn.h",
    "content": "#include \"inst_system.h\"\n#include \"inst_controlflow.h\"\n#include \"inst_graphics2d.h\"\n#include \"inst_graphics3d.h\"\n#include \"inst_dialogue.h\"\n#include \"inst_sound.h\"\n#include \"inst_movie.h\"\n#include \"inst_gamespecific.h\"\n#include \"inst_misc.h\"\n\n// clang-format off\n\nnamespace Impacto {\n\nnamespace Vm {\n\nInstructionProc inline constexpr OpcodeTableSystem_CHN[256] = {\n    InstEnd,                  // 00 00\n    InstCreateThread,         // 00 01\n    InstKillThread,           // 00 02\n    InstReset,                // 00 03\n    InstScriptLoad,           // 00 04\n    InstWait,                 // 00 05\n    InstHalt,                 // 00 06\n    InstJump,                 // 00 07\n    InstJumpTable,            // 00 08\n    InstGetLabelAdr,          // 00 09\n    InstIf,                   // 00 0A\n    InstCall,                 // 00 0B\n    InstJumpFar,              // 00 0C\n    InstCallFar,              // 00 0D\n    InstReturn,               // 00 0E\n    InstLoop,                 // 00 0F\n    InstFlagOnJump,           // 00 10\n    InstFlagOnWait,           // 00 11\n    InstSetFlag,              // 00 12\n    InstResetFlag,            // 00 13\n    InstCopyFlag,             // 00 14\n    InstKeyOnJump,            // 00 15\n    InstKeyWait,              // 00 16\n    InstKeyWaitTimer,         // 00 17\n    InstMemberWrite,          // 00 18\n    InstThreadControl,        // 00 19\n    InstGetSelfPointer,       // 00 1A\n    InstLoadJump,             // 00 1B\n    InstVsync,                // 00 1C\n    InstTest,                 // 00 1D\n    InstThreadControlStore,   // 00 1E\n    InstSwitch,               // 00 1F\n    InstCase,                 // 00 20\n    InstBGMplay,              // 00 21\n    InstBGMstop,              // 00 22\n    InstSEplay,               // 00 23\n    InstSEstop,               // 00 24\n    InstPadAct,               // 00 25\n    InstSSEplay,              // 00 26\n    InstSSEstop,              // 00 27\n    InstCopyThreadWork,       // 00 28\n    InstUPLmenuUI,            // 00 29\n    InstSave,                 // 00 2A\n    InstSaveIconLoad,         // 00 2B\n    InstBGMflag,              // 00 2C\n    InstUPLxTitle,            // 00 2D\n    InstPresence,             // 00 2E\n    InstSetAchievement,          // 00 2F\n    InstSetPlayer,            // 00 30\n    InstVoiceTableLoadMaybe,  // 00 31\n    InstSetPadCustom,         // 00 32\n    InstMwait,                // 00 33\n    InstTerminate,            // 00 34\n    InstSignIn,               // 00 35\n    InstAchievementIcon,      // 00 36\n    InstVoicePlay,            // 00 37\n    InstVoiceStopNew,         // 00 38\n    InstVoicePlayWait,        // 00 39\n    InstBGMduelPlay,          // 00 3A\n    InstSNDpause,             // 00 3B\n    InstSEplayWait,           // 00 3C\n    InstDebugPrint,           // 00 3D\n    InstResetSoundAll,        // 00 3E\n    InstSNDloadStop,          // 00 3F\n    InstBGMstopWait,          // 00 40\n    InstUnk0041,              // 00 41\n    InstSetX360SysMesPos,     // 00 42\n    InstSystemMes,            // 00 43\n    InstSystemMenu,           // 00 44\n    InstGetNowTime,           // 00 45\n    InstGetSystemStatus,      // 00 46\n    InstReboot,               // 00 47\n    InstReloadScript,         // 00 48\n    InstReloadScriptMenu,     // 00 49\n    InstDebugEditer,          // 00 4A\n    InstSysSeload,            // 00 4B\n    InstDummy,                // 00 4C\n    InstSysVoicePlay,         // 00 4D\n    InstPadActEx,             // 00 4E\n    InstDebugSetup,           // 00 4F\n    InstPressStartNew,        // 00 50\n    InstGlobalSystemMessage,  // 00 51\n    InstUnk0052,              // 00 52\n    InstClickOnJump,          // 00 53\n    InstKeyboardOnJump,       // 00 54\n    InstReturnIfFlag,         // 00 55\n    InstDummy,                // 00 56\n    InstDummy,                // 00 57\n    InstDummy,                // 00 58\n    InstDummy,                // 00 59\n    InstDummy,                // 00 5A\n    InstDummy,                // 00 5B\n    InstDummy,                // 00 5C\n    InstDummy,                // 00 5D\n    InstDummy,                // 00 5E\n    InstDummy,                // 00 5F\n    InstDummy,                // 00 60\n    InstDummy,                // 00 61\n    InstDummy,                // 00 62\n    InstDummy,                // 00 63\n    InstDummy,                // 00 64\n    InstDummy,                // 00 65\n    InstDummy,                // 00 66\n    InstDummy,                // 00 67\n    InstDummy,                // 00 68\n    InstDummy,                // 00 69\n    InstDummy,                // 00 6A\n    InstDummy,                // 00 6B\n    InstDummy,                // 00 6C\n    InstDummy,                // 00 6D\n    InstDummy,                // 00 6E\n    InstDummy,                // 00 6F\n    InstDummy,                // 00 70\n    InstDummy,                // 00 71\n    InstDummy,                // 00 72\n    InstDummy,                // 00 73\n    InstDummy,                // 00 74\n    InstDummy,                // 00 75\n    InstDummy,                // 00 76\n    InstDummy,                // 00 77\n    InstDummy,                // 00 78\n    InstDummy,                // 00 79\n    InstDummy,                // 00 7A\n    InstDummy,                // 00 7B\n    InstDummy,                // 00 7C\n    InstDummy,                // 00 7D\n    InstDummy,                // 00 7E\n    InstDummy,                // 00 7F\n    InstDummy,                // 00 80\n    InstDummy,                // 00 81\n    InstDummy,                // 00 82\n    InstDummy,                // 00 83\n    InstDummy,                // 00 84\n    InstDummy,                // 00 85\n    InstDummy,                // 00 86\n    InstDummy,                // 00 87\n    InstDummy,                // 00 88\n    InstDummy,                // 00 89\n    InstDummy,                // 00 8A\n    InstDummy,                // 00 8B\n    InstDummy,                // 00 8C\n    InstDummy,                // 00 8D\n    InstDummy,                // 00 8E\n    InstDummy,                // 00 8F\n    InstDummy,                // 00 90\n    InstDummy,                // 00 91\n    InstDummy,                // 00 92\n    InstDummy,                // 00 93\n    InstDummy,                // 00 94\n    InstDummy,                // 00 95\n    InstDummy,                // 00 96\n    InstDummy,                // 00 97\n    InstDummy,                // 00 98\n    InstDummy,                // 00 99\n    InstDummy,                // 00 9A\n    InstDummy,                // 00 9B\n    InstDummy,                // 00 9C\n    InstDummy,                // 00 9D\n    InstDummy,                // 00 9E\n    InstDummy,                // 00 9F\n    InstDummy,                // 00 A0\n    InstDummy,                // 00 A1\n    InstDummy,                // 00 A2\n    InstDummy,                // 00 A3\n    InstDummy,                // 00 A4\n    InstDummy,                // 00 A5\n    InstDummy,                // 00 A6\n    InstDummy,                // 00 A7\n    InstDummy,                // 00 A8\n    InstDummy,                // 00 A9\n    InstDummy,                // 00 AA\n    InstDummy,                // 00 AB\n    InstDummy,                // 00 AC\n    InstDummy,                // 00 AD\n    InstDummy,                // 00 AE\n    InstDummy,                // 00 AF\n    InstDummy,                // 00 B0\n    InstDummy,                // 00 B1\n    InstDummy,                // 00 B2\n    InstDummy,                // 00 B3\n    InstDummy,                // 00 B4\n    InstDummy,                // 00 B5\n    InstDummy,                // 00 B6\n    InstDummy,                // 00 B7\n    InstDummy,                // 00 B8\n    InstDummy,                // 00 B9\n    InstDummy,                // 00 BA\n    InstDummy,                // 00 BB\n    InstDummy,                // 00 BC\n    InstDummy,                // 00 BD\n    InstDummy,                // 00 BE\n    InstDummy,                // 00 BF\n    InstDummy,                // 00 C0\n    InstDummy,                // 00 C1\n    InstDummy,                // 00 C2\n    InstDummy,                // 00 C3\n    InstDummy,                // 00 C4\n    InstDummy,                // 00 C5\n    InstDummy,                // 00 C6\n    InstDummy,                // 00 C7\n    InstDummy,                // 00 C8\n    InstDummy,                // 00 C9\n    InstDummy,                // 00 CA\n    InstDummy,                // 00 CB\n    InstDummy,                // 00 CC\n    InstDummy,                // 00 CD\n    InstDummy,                // 00 CE\n    InstDummy,                // 00 CF\n    InstDummy,                // 00 D0\n    InstDummy,                // 00 D1\n    InstDummy,                // 00 D2\n    InstDummy,                // 00 D3\n    InstDummy,                // 00 D4\n    InstDummy,                // 00 D5\n    InstDummy,                // 00 D6\n    InstDummy,                // 00 D7\n    InstDummy,                // 00 D8\n    InstDummy,                // 00 D9\n    InstDummy,                // 00 DA\n    InstDummy,                // 00 DB\n    InstDummy,                // 00 DC\n    InstDummy,                // 00 DD\n    InstDummy,                // 00 DE\n    InstDummy,                // 00 DF\n    InstDummy,                // 00 E0\n    InstDummy,                // 00 E1\n    InstDummy,                // 00 E2\n    InstDummy,                // 00 E3\n    InstDummy,                // 00 E4\n    InstDummy,                // 00 E5\n    InstDummy,                // 00 E6\n    InstDummy,                // 00 E7\n    InstDummy,                // 00 E8\n    InstDummy,                // 00 E9\n    InstDummy,                // 00 EA\n    InstDummy,                // 00 EB\n    InstDummy,                // 00 EC\n    InstDummy,                // 00 ED\n    InstDummy,                // 00 EE\n    InstDummy,                // 00 EF\n    InstDummy,                // 00 F0\n    InstDummy,                // 00 F1\n    InstDummy,                // 00 F2\n    InstDummy,                // 00 F3\n    InstDummy,                // 00 F4\n    InstDummy,                // 00 F5\n    InstDummy,                // 00 F6\n    InstDummy,                // 00 F7\n    InstDummy,                // 00 F8\n    InstDummy,                // 00 F9\n    InstDummy,                // 00 FA\n    InstDummy,                // 00 FB\n    InstDummy,                // 00 FC\n    InstDummy,                // 00 FD\n    InstDummy,                // 00 FE\n    InstDummy                 // 00 FF\n};\n\nInstructionProc inline constexpr OpcodeTableGraph_CHN[256] = {\n    InstCreateSurf,        // 01 00\n    InstReleaseSurf,       // 01 01\n    InstLoadPic,           // 01 02\n    InstReleaseSurf,       // 01 03\n    InstSurfFill,          // 01 04\n    InstCalc,              // 01 05\n    InstMesViewFlag,       // 01 06\n    InstSetMesWinPri,      // 01 07\n    InstMesSync,           // 01 08\n    InstMesSetID,          // 01 09\n    InstMesCls,            // 01 0A\n    InstMesVoiceWait,      // 01 0B\n    InstMes,               // 01 0C\n    InstMesMain,           // 01 0D\n    InstSetMesModeFormat,  // 01 0E\n    InstSetNGmoji,         // 01 0F\n    InstMesRev,            // 01 10\n    InstMessWindow,        // 01 11\n    InstSel,               // 01 12\n    InstSelect,            // 01 13\n    InstSysSel,            // 01 14\n    InstSysSelect,         // 01 15\n    InstDummy,             // 01 16\n    InstDummy,             // 01 17\n    InstDummy,             // 01 18\n    InstDummy,             // 01 19\n    InstDummy,             // 01 1A\n    InstDummy,             // 01 1B\n    InstDummy,             // 01 1C\n    InstDummy,             // 01 1D\n    InstDummy,             // 01 1E\n    InstLoadFontWidths,    // 01 1F\n    InstSCcapture,         // 01 20\n    InstSetTextTable,      // 01 21\n    InstPlayMovie,         // 01 22\n    InstMovieMain,         // 01 23\n    InstLoadMovie,         // 01 24\n    InstSetRevMes,         // 01 25\n    InstPlayMovieMemory,   // 01 26\n    InstPlayMovie,         // 01 27\n    InstMovieMain,         // 01 28\n    InstLoadMovie,         // 01 29\n    InstPlayMovieMemory,   // 01 2A\n    InstSFDpause,          // 01 2B\n    InstPlayMovie,         // 01 2C\n    InstUnk012D,           // 01 2D\n    InstDummy,             // 01 2E\n    InstChatMO8,           // 01 2F\n    InstDummy,             // 01 30\n    InstDummy,             // 01 31\n    InstDummy,             // 01 32\n    InstDummy,             // 01 33\n    InstDummy,             // 01 34\n    InstDummy,             // 01 35\n    InstDummy,             // 01 36\n    InstDummy,             // 01 37\n    InstDummy,             // 01 38\n    InstDummy,             // 01 39\n    InstDummy,             // 01 3A\n    InstDummy,             // 01 3B\n    InstDummy,             // 01 3C\n    InstDummy,             // 01 3D\n    InstDummy,             // 01 3E\n    InstDummy,             // 01 3F\n    InstDummy,             // 01 40\n    InstDummy,             // 01 41\n    InstDummy,             // 01 42\n    InstDummy,             // 01 43\n    InstDummy,             // 01 44\n    InstDummy,             // 01 45\n    InstDummy,             // 01 46\n    InstDummy,             // 01 47\n    InstDummy,             // 01 48\n    InstDummy,             // 01 49\n    InstDummy,             // 01 4A\n    InstDummy,             // 01 4B\n    InstDummy,             // 01 4C\n    InstDummy,             // 01 4D\n    InstDummy,             // 01 4E\n    InstDummy,             // 01 4F\n    InstDummy,             // 01 50\n    InstDummy,             // 01 51\n    InstDummy,             // 01 52\n    InstDummy,             // 01 53\n    InstDummy,             // 01 54\n    InstDummy,             // 01 55\n    InstDummy,             // 01 56\n    InstDummy,             // 01 57\n    InstDummy,             // 01 58\n    InstDummy,             // 01 59\n    InstDummy,             // 01 5A\n    InstDummy,             // 01 5B\n    InstDummy,             // 01 5C\n    InstDummy,             // 01 5D\n    InstDummy,             // 01 5E\n    InstDummy,             // 01 5F\n    InstDummy,             // 01 60\n    InstDummy,             // 01 61\n    InstDummy,             // 01 62\n    InstDummy,             // 01 63\n    InstDummy,             // 01 64\n    InstDummy,             // 01 65\n    InstDummy,             // 01 66\n    InstDummy,             // 01 67\n    InstDummy,             // 01 68\n    InstDummy,             // 01 69\n    InstDummy,             // 01 6A\n    InstDummy,             // 01 6B\n    InstDummy,             // 01 6C\n    InstDummy,             // 01 6D\n    InstDummy,             // 01 6E\n    InstDummy,             // 01 6F\n    InstDummy,             // 01 70\n    InstDummy,             // 01 71\n    InstDummy,             // 01 72\n    InstDummy,             // 01 73\n    InstDummy,             // 01 74\n    InstDummy,             // 01 75\n    InstDummy,             // 01 76\n    InstDummy,             // 01 77\n    InstDummy,             // 01 78\n    InstDummy,             // 01 79\n    InstDummy,             // 01 7A\n    InstDummy,             // 01 7B\n    InstDummy,             // 01 7C\n    InstDummy,             // 01 7D\n    InstDummy,             // 01 7E\n    InstDummy,             // 01 7F\n    InstDummy,             // 01 80\n    InstDummy,             // 01 81\n    InstDummy,             // 01 82\n    InstDummy,             // 01 83\n    InstDummy,             // 01 84\n    InstDummy,             // 01 85\n    InstDummy,             // 01 86\n    InstDummy,             // 01 87\n    InstDummy,             // 01 88\n    InstDummy,             // 01 89\n    InstDummy,             // 01 8A\n    InstDummy,             // 01 8B\n    InstDummy,             // 01 8C\n    InstDummy,             // 01 8D\n    InstDummy,             // 01 8E\n    InstDummy,             // 01 8F\n    InstDummy,             // 01 90\n    InstDummy,             // 01 91\n    InstDummy,             // 01 92\n    InstDummy,             // 01 93\n    InstDummy,             // 01 94\n    InstDummy,             // 01 95\n    InstDummy,             // 01 96\n    InstDummy,             // 01 97\n    InstDummy,             // 01 98\n    InstDummy,             // 01 99\n    InstDummy,             // 01 9A\n    InstDummy,             // 01 9B\n    InstDummy,             // 01 9C\n    InstDummy,             // 01 9D\n    InstDummy,             // 01 9E\n    InstDummy,             // 01 9F\n    InstDummy,             // 01 A0\n    InstDummy,             // 01 A1\n    InstDummy,             // 01 A2\n    InstDummy,             // 01 A3\n    InstDummy,             // 01 A4\n    InstDummy,             // 01 A5\n    InstDummy,             // 01 A6\n    InstDummy,             // 01 A7\n    InstDummy,             // 01 A8\n    InstDummy,             // 01 A9\n    InstDummy,             // 01 AA\n    InstDummy,             // 01 AB\n    InstDummy,             // 01 AC\n    InstDummy,             // 01 AD\n    InstDummy,             // 01 AE\n    InstDummy,             // 01 AF\n    InstDummy,             // 01 B0\n    InstDummy,             // 01 B1\n    InstDummy,             // 01 B2\n    InstDummy,             // 01 B3\n    InstDummy,             // 01 B4\n    InstDummy,             // 01 B5\n    InstDummy,             // 01 B6\n    InstDummy,             // 01 B7\n    InstDummy,             // 01 B8\n    InstDummy,             // 01 B9\n    InstDummy,             // 01 BA\n    InstDummy,             // 01 BB\n    InstDummy,             // 01 BC\n    InstDummy,             // 01 BD\n    InstDummy,             // 01 BE\n    InstDummy,             // 01 BF\n    InstDummy,             // 01 C0\n    InstDummy,             // 01 C1\n    InstDummy,             // 01 C2\n    InstDummy,             // 01 C3\n    InstDummy,             // 01 C4\n    InstDummy,             // 01 C5\n    InstDummy,             // 01 C6\n    InstDummy,             // 01 C7\n    InstDummy,             // 01 C8\n    InstDummy,             // 01 C9\n    InstDummy,             // 01 CA\n    InstDummy,             // 01 CB\n    InstDummy,             // 01 CC\n    InstDummy,             // 01 CD\n    InstDummy,             // 01 CE\n    InstDummy,             // 01 CF\n    InstDummy,             // 01 D0\n    InstDummy,             // 01 D1\n    InstDummy,             // 01 D2\n    InstDummy,             // 01 D3\n    InstDummy,             // 01 D4\n    InstDummy,             // 01 D5\n    InstDummy,             // 01 D6\n    InstDummy,             // 01 D7\n    InstDummy,             // 01 D8\n    InstDummy,             // 01 D9\n    InstDummy,             // 01 DA\n    InstDummy,             // 01 DB\n    InstDummy,             // 01 DC\n    InstDummy,             // 01 DD\n    InstDummy,             // 01 DE\n    InstDummy,             // 01 DF\n    InstDummy,             // 01 E0\n    InstDummy,             // 01 E1\n    InstDummy,             // 01 E2\n    InstDummy,             // 01 E3\n    InstDummy,             // 01 E4\n    InstDummy,             // 01 E5\n    InstDummy,             // 01 E6\n    InstDummy,             // 01 E7\n    InstDummy,             // 01 E8\n    InstDummy,             // 01 E9\n    InstDummy,             // 01 EA\n    InstDummy,             // 01 EB\n    InstDummy,             // 01 EC\n    InstDummy,             // 01 ED\n    InstDummy,             // 01 EE\n    InstDummy,             // 01 EF\n    InstDummy,             // 01 F0\n    InstDummy,             // 01 F1\n    InstDummy,             // 01 F2\n    InstDummy,             // 01 F3\n    InstDummy,             // 01 F4\n    InstDummy,             // 01 F5\n    InstDummy,             // 01 F6\n    InstDummy,             // 01 F7\n    InstDummy,             // 01 F8\n    InstDummy,             // 01 F9\n    InstDummy,             // 01 FA\n    InstDummy,             // 01 FB\n    InstDummy,             // 01 FC\n    InstDummy,             // 01 FD\n    InstDummy,             // 01 FE\n    InstDummy              // 01 FF\n};\n\nInstructionProc inline constexpr OpcodeTableUser1_CHN[256] = {\n    InstMSinit,            // 10 00\n    InstBGload,            // 10 01\n    InstBGswap,            // 10 02\n    InstBGsetColor,        // 10 03\n    InstBGsetLink,         // 10 04\n    InstCHAload,           // 10 05\n    InstCHAswap,           // 10 06\n    InstBGcopy,            // 10 07\n    InstCHAcopy,           // 10 08\n    InstSaveSlot,          // 10 09\n    InstSystemMain,        // 10 0A\n    InstCharaLayerLoad,    // 10 0B\n    InstGameInfoInit,      // 10 0C\n    InstCHAmove,           // 10 0D\n    InstBGloadEx,          // 10 0E\n    InstDummy,             // 10 0F\n    InstBGrelease,         // 10 10\n    InstCHArelease,        // 10 11\n    InstClearFlagChk,      // 10 12\n    InstOption,            // 10 13\n    InstSystemDataReset,   // 10 14\n    InstDebugData,         // 10 15\n    InstGetCharaPause,     // 10 16\n    InstBGfadeExpInit,     // 10 17\n    InstDummy,             // 10 18\n    InstDummy,             // 10 19\n    InstDummy,             // 10 1A\n    InstHelp,              // 10 1B\n    InstAchievementMenu,   // 10 1C\n    InstSoundMenu,         // 10 1D\n    InstAllClear,          // 10 1E\n    InstAlbum,             // 10 1F\n    InstMovieMode,         // 10 20\n    InstClistInit,         // 10 21\n    InstAutoSave,          // 10 22\n    InstSaveMenu,          // 10 23\n    InstLoadData,          // 10 24\n    InstDummy,             // 10 25\n    InstDummy,             // 10 26\n    InstSetDic,            // 10 27\n    InstSetPlayMode,       // 10 28\n    InstSetEVflag,         // 10 29\n    InstSetCutin,          // 10 2A\n    InstDummy,             // 10 2B\n    InstDummy,             // 10 2C\n    InstAchChkTitle,       // 10 2D\n    InstSetSceneViewFlag,  // 10 2E\n    InstChkClearFlag,      // 10 2F\n    InstBGeffectWave,      // 10 30\n    InstGeotag,            // 10 31\n    InstNameID,            // 10 32\n    InstTips,              // 10 33\n    InstTitleMenuNew,      // 10 34\n    InstDummy,             // 10 35\n    InstBGeffect,          // 10 36\n    InstUnk1037Noah,       // 10 37\n    InstMail,              // 10 38\n    InstTwipo_Dash,        // 10 39\n    InstUnk103A,           // 10 3A\n    InstDummy,             // 10 3B\n    InstFACEload,          // 10 3C\n    InstFACErelease,       // 10 3D\n    InstDummy,             // 10 3E\n    InstSysSeload,         // 10 3F\n    InstDummy,             // 10 40\n    InstDummy,             // 10 41\n    InstDummy,             // 10 42\n    InstDummy,             // 10 43\n    InstDummy,             // 10 44\n    InstDummy,             // 10 45\n    InstDummy,             // 10 46\n    InstDummy,             // 10 47\n    InstDummy,             // 10 48\n    InstDummy,             // 10 49\n    InstDummy,             // 10 4A\n    InstDummy,             // 10 4B\n    InstDummy,             // 10 4C\n    InstDummy,             // 10 4D\n    InstDummy,             // 10 4E\n    InstDummy,             // 10 4F\n    InstDummy,             // 10 50\n    InstDummy,             // 10 51\n    InstDummy,             // 10 52\n    InstDummy,             // 10 53\n    InstDummy,             // 10 54\n    InstDummy,             // 10 55\n    InstDummy,             // 10 56\n    InstDummy,             // 10 57\n    InstDummy,             // 10 58\n    InstDummy,             // 10 59\n    InstDummy,             // 10 5A\n    InstDummy,             // 10 5B\n    InstDummy,             // 10 5C\n    InstDummy,             // 10 5D\n    InstDummy,             // 10 5E\n    InstDummy,             // 10 5F\n    InstDummy,             // 10 60\n    InstDummy,             // 10 61\n    InstDummy,             // 10 62\n    InstDummy,             // 10 63\n    InstDummy,             // 10 64\n    InstDummy,             // 10 65\n    InstDummy,             // 10 66\n    InstDummy,             // 10 67\n    InstDummy,             // 10 68\n    InstDummy,             // 10 69\n    InstDummy,             // 10 6A\n    InstDummy,             // 10 6B\n    InstDummy,             // 10 6C\n    InstDummy,             // 10 6D\n    InstDummy,             // 10 6E\n    InstDummy,             // 10 6F\n    InstDummy,             // 10 70\n    InstDummy,             // 10 71\n    InstDummy,             // 10 72\n    InstDummy,             // 10 73\n    InstDummy,             // 10 74\n    InstDummy,             // 10 75\n    InstDummy,             // 10 76\n    InstDummy,             // 10 77\n    InstDummy,             // 10 78\n    InstDummy,             // 10 79\n    InstDummy,             // 10 7A\n    InstDummy,             // 10 7B\n    InstDummy,             // 10 7C\n    InstDummy,             // 10 7D\n    InstDummy,             // 10 7E\n    InstDummy,             // 10 7F\n    InstDummy,             // 10 80\n    InstDummy,             // 10 81\n    InstDummy,             // 10 82\n    InstDummy,             // 10 83\n    InstDummy,             // 10 84\n    InstDummy,             // 10 85\n    InstDummy,             // 10 86\n    InstDummy,             // 10 87\n    InstDummy,             // 10 88\n    InstDummy,             // 10 89\n    InstDummy,             // 10 8A\n    InstDummy,             // 10 8B\n    InstDummy,             // 10 8C\n    InstDummy,             // 10 8D\n    InstDummy,             // 10 8E\n    InstDummy,             // 10 8F\n    InstDummy,             // 10 90\n    InstDummy,             // 10 91\n    InstDummy,             // 10 92\n    InstDummy,             // 10 93\n    InstDummy,             // 10 94\n    InstDummy,             // 10 95\n    InstDummy,             // 10 96\n    InstDummy,             // 10 97\n    InstDummy,             // 10 98\n    InstDummy,             // 10 99\n    InstDummy,             // 10 9A\n    InstDummy,             // 10 9B\n    InstDummy,             // 10 9C\n    InstDummy,             // 10 9D\n    InstDummy,             // 10 9E\n    InstDummy,             // 10 9F\n    InstDummy,             // 10 A0\n    InstDummy,             // 10 A1\n    InstDummy,             // 10 A2\n    InstDummy,             // 10 A3\n    InstDummy,             // 10 A4\n    InstDummy,             // 10 A5\n    InstDummy,             // 10 A6\n    InstDummy,             // 10 A7\n    InstDummy,             // 10 A8\n    InstDummy,             // 10 A9\n    InstDummy,             // 10 AA\n    InstDummy,             // 10 AB\n    InstDummy,             // 10 AC\n    InstDummy,             // 10 AD\n    InstDummy,             // 10 AE\n    InstDummy,             // 10 AF\n    InstDummy,             // 10 B0\n    InstDummy,             // 10 B1\n    InstDummy,             // 10 B2\n    InstDummy,             // 10 B3\n    InstDummy,             // 10 B4\n    InstDummy,             // 10 B5\n    InstDummy,             // 10 B6\n    InstDummy,             // 10 B7\n    InstDummy,             // 10 B8\n    InstDummy,             // 10 B9\n    InstDummy,             // 10 BA\n    InstDummy,             // 10 BB\n    InstDummy,             // 10 BC\n    InstDummy,             // 10 BD\n    InstDummy,             // 10 BE\n    InstDummy,             // 10 BF\n    InstDummy,             // 10 C0\n    InstDummy,             // 10 C1\n    InstDummy,             // 10 C2\n    InstDummy,             // 10 C3\n    InstDummy,             // 10 C4\n    InstDummy,             // 10 C5\n    InstDummy,             // 10 C6\n    InstDummy,             // 10 C7\n    InstDummy,             // 10 C8\n    InstDummy,             // 10 C9\n    InstDummy,             // 10 CA\n    InstDummy,             // 10 CB\n    InstDummy,             // 10 CC\n    InstDummy,             // 10 CD\n    InstDummy,             // 10 CE\n    InstDummy,             // 10 CF\n    InstDummy,             // 10 D0\n    InstDummy,             // 10 D1\n    InstDummy,             // 10 D2\n    InstDummy,             // 10 D3\n    InstDummy,             // 10 D4\n    InstDummy,             // 10 D5\n    InstDummy,             // 10 D6\n    InstDummy,             // 10 D7\n    InstDummy,             // 10 D8\n    InstDummy,             // 10 D9\n    InstDummy,             // 10 DA\n    InstDummy,             // 10 DB\n    InstDummy,             // 10 DC\n    InstDummy,             // 10 DD\n    InstDummy,             // 10 DE\n    InstDummy,             // 10 DF\n    InstDummy,             // 10 E0\n    InstDummy,             // 10 E1\n    InstDummy,             // 10 E2\n    InstDummy,             // 10 E3\n    InstDummy,             // 10 E4\n    InstDummy,             // 10 E5\n    InstDummy,             // 10 E6\n    InstDummy,             // 10 E7\n    InstDummy,             // 10 E8\n    InstDummy,             // 10 E9\n    InstDummy,             // 10 EA\n    InstDummy,             // 10 EB\n    InstDummy,             // 10 EC\n    InstDummy,             // 10 ED\n    InstDummy,             // 10 EE\n    InstDummy,             // 10 EF\n    InstDummy,             // 10 F0\n    InstDummy,             // 10 F1\n    InstDummy,             // 10 F2\n    InstDummy,             // 10 F3\n    InstDummy,             // 10 F4\n    InstDummy,             // 10 F5\n    InstDummy,             // 10 F6\n    InstDummy,             // 10 F7\n    InstDummy,             // 10 F8\n    InstDummy,             // 10 F9\n    InstDummy,             // 10 FA\n    InstDummy,             // 10 FB\n    InstDummy,             // 10 FC\n    InstDummy,             // 10 FD\n    InstDummy,             // 10 FE\n    InstDummy              // 10 FF\n};\n\n}\n\n}\n\n// clang-format on"
  },
  {
    "path": "src/vm/opcodetables_darling.h",
    "content": "#include \"inst_system.h\"\n#include \"inst_controlflow.h\"\n#include \"inst_graphics2d.h\"\n#include \"inst_graphics3d.h\"\n#include \"inst_dialogue.h\"\n#include \"inst_sound.h\"\n#include \"inst_movie.h\"\n#include \"inst_gamespecific.h\"\n#include \"inst_misc.h\"\n\n// clang-format off\n\nnamespace Impacto {\n\nnamespace Vm {\n\nInstructionProc inline constexpr OpcodeTableSystem_Darling[256] = {\n    InstEnd,                  // 00 00\n    InstCreateThread,         // 00 01\n    InstKillThread,           // 00 02\n    InstReset,                // 00 03\n    InstScriptLoad,           // 00 04\n    InstWait,                 // 00 05\n    InstHalt,                 // 00 06\n    InstJump,                 // 00 07\n    InstJumpTable,            // 00 08\n    InstGetLabelAdr,          // 00 09\n    InstIf,                   // 00 0A\n    InstCall,                 // 00 0B\n    InstJumpFar,              // 00 0C\n    InstCallFar,              // 00 0D\n    InstReturn,               // 00 0E\n    InstLoop,                 // 00 0F\n    InstFlagOnJump,           // 00 10\n    InstFlagOnWait,           // 00 11\n    InstSetFlag,              // 00 12\n    InstResetFlag,            // 00 13\n    InstCopyFlag,             // 00 14\n    InstKeyOnJump,            // 00 15\n    InstKeyWait,              // 00 16\n    InstKeyWaitTimer,         // 00 17\n    InstMemberWrite,          // 00 18\n    InstThreadControl,        // 00 19\n    InstGetSelfPointer,       // 00 1A\n    InstLoadJump,             // 00 1B\n    InstVsync,                // 00 1C\n    InstTest,                 // 00 1D\n    InstThreadControlStore,   // 00 1E\n    InstSwitch,               // 00 1F\n    InstCase,                 // 00 20\n    InstBGMplay,              // 00 21\n    InstBGMstop,              // 00 22\n    InstSEplay,               // 00 23\n    InstSEstop,               // 00 24\n    InstPadAct,               // 00 25\n    InstSSEplay,              // 00 26\n    InstSSEstop,              // 00 27\n    InstCopyThreadWork,       // 00 28\n    InstUPLmenuUI,            // 00 29\n    InstSaveOld,              // 00 2A\n    InstSaveIconLoad,         // 00 2B\n    InstBGMflag,              // 00 2C\n    InstUPLxTitle,            // 00 2D\n    InstPresence,             // 00 2E\n    InstSetAchievement,          // 00 2F\n    InstSetPlayer,            // 00 30\n    InstVoiceTableLoadMaybe,  // 00 31\n    InstSetPadCustom,         // 00 32\n    InstMwait,                // 00 33\n    InstTerminate,            // 00 34\n    InstSignIn,               // 00 35\n    InstAchievementIcon,      // 00 36\n    InstVoicePlayOld,         // 00 37\n    InstVoiceStop,            // 00 38\n    InstVoicePlayWait,        // 00 39\n    InstBGMduelPlay,          // 00 3A\n    InstSNDpause,             // 00 3B\n    InstSEplayWait,           // 00 3C\n    InstDebugPrint,           // 00 3D\n    InstResetSoundAll,        // 00 3E\n    InstSNDloadStop,          // 00 3F\n    InstBGMstopWait,          // 00 40\n    InstUnk0041,              // 00 41\n    InstSetX360SysMesPos,     // 00 42\n    InstSystemMes,            // 00 43\n    InstSystemMenu,           // 00 44\n    InstGetNowTime,           // 00 45\n    InstGetSystemStatus,      // 00 46\n    InstReboot,               // 00 47\n    InstReloadScript,         // 00 48\n    InstReloadScriptMenu,     // 00 49\n    InstDebugEditer,          // 00 4A\n    InstDummy,                // 00 4B\n    InstDummy,                // 00 4C\n    InstSysVoicePlay,         // 00 4D\n    InstPadActEx,             // 00 4E\n    InstDebugSetup,           // 00 4F\n    InstPressStart,           // 00 50\n    InstGlobalSystemMessage,  // 00 51\n    InstUnk0052,              // 00 52\n    InstUnk0053,              // 00 53\n    InstUnk0054,              // 00 54\n    InstReturnIfFlag,         // 00 55\n    InstDummy,                // 00 56\n    InstDummy,                // 00 57\n    InstDummy,                // 00 58\n    InstDummy,                // 00 59\n    InstDummy,                // 00 5A\n    InstDummy,                // 00 5B\n    InstDummy,                // 00 5C\n    InstDummy,                // 00 5D\n    InstDummy,                // 00 5E\n    InstDummy,                // 00 5F\n    InstDummy,                // 00 60\n    InstDummy,                // 00 61\n    InstDummy,                // 00 62\n    InstDummy,                // 00 63\n    InstDummy,                // 00 64\n    InstDummy,                // 00 65\n    InstDummy,                // 00 66\n    InstDummy,                // 00 67\n    InstDummy,                // 00 68\n    InstDummy,                // 00 69\n    InstDummy,                // 00 6A\n    InstDummy,                // 00 6B\n    InstDummy,                // 00 6C\n    InstDummy,                // 00 6D\n    InstDummy,                // 00 6E\n    InstDummy,                // 00 6F\n    InstDummy,                // 00 70\n    InstDummy,                // 00 71\n    InstDummy,                // 00 72\n    InstDummy,                // 00 73\n    InstDummy,                // 00 74\n    InstDummy,                // 00 75\n    InstDummy,                // 00 76\n    InstDummy,                // 00 77\n    InstDummy,                // 00 78\n    InstDummy,                // 00 79\n    InstDummy,                // 00 7A\n    InstDummy,                // 00 7B\n    InstDummy,                // 00 7C\n    InstDummy,                // 00 7D\n    InstDummy,                // 00 7E\n    InstDummy,                // 00 7F\n    InstDummy,                // 00 80\n    InstDummy,                // 00 81\n    InstDummy,                // 00 82\n    InstDummy,                // 00 83\n    InstDummy,                // 00 84\n    InstDummy,                // 00 85\n    InstDummy,                // 00 86\n    InstDummy,                // 00 87\n    InstDummy,                // 00 88\n    InstDummy,                // 00 89\n    InstDummy,                // 00 8A\n    InstDummy,                // 00 8B\n    InstDummy,                // 00 8C\n    InstDummy,                // 00 8D\n    InstDummy,                // 00 8E\n    InstDummy,                // 00 8F\n    InstDummy,                // 00 90\n    InstDummy,                // 00 91\n    InstDummy,                // 00 92\n    InstDummy,                // 00 93\n    InstDummy,                // 00 94\n    InstDummy,                // 00 95\n    InstDummy,                // 00 96\n    InstDummy,                // 00 97\n    InstDummy,                // 00 98\n    InstDummy,                // 00 99\n    InstDummy,                // 00 9A\n    InstDummy,                // 00 9B\n    InstDummy,                // 00 9C\n    InstDummy,                // 00 9D\n    InstDummy,                // 00 9E\n    InstDummy,                // 00 9F\n    InstDummy,                // 00 A0\n    InstDummy,                // 00 A1\n    InstDummy,                // 00 A2\n    InstDummy,                // 00 A3\n    InstDummy,                // 00 A4\n    InstDummy,                // 00 A5\n    InstDummy,                // 00 A6\n    InstDummy,                // 00 A7\n    InstDummy,                // 00 A8\n    InstDummy,                // 00 A9\n    InstDummy,                // 00 AA\n    InstDummy,                // 00 AB\n    InstDummy,                // 00 AC\n    InstDummy,                // 00 AD\n    InstDummy,                // 00 AE\n    InstDummy,                // 00 AF\n    InstDummy,                // 00 B0\n    InstDummy,                // 00 B1\n    InstDummy,                // 00 B2\n    InstDummy,                // 00 B3\n    InstDummy,                // 00 B4\n    InstDummy,                // 00 B5\n    InstDummy,                // 00 B6\n    InstDummy,                // 00 B7\n    InstDummy,                // 00 B8\n    InstDummy,                // 00 B9\n    InstDummy,                // 00 BA\n    InstDummy,                // 00 BB\n    InstDummy,                // 00 BC\n    InstDummy,                // 00 BD\n    InstDummy,                // 00 BE\n    InstDummy,                // 00 BF\n    InstDummy,                // 00 C0\n    InstDummy,                // 00 C1\n    InstDummy,                // 00 C2\n    InstDummy,                // 00 C3\n    InstDummy,                // 00 C4\n    InstDummy,                // 00 C5\n    InstDummy,                // 00 C6\n    InstDummy,                // 00 C7\n    InstDummy,                // 00 C8\n    InstDummy,                // 00 C9\n    InstDummy,                // 00 CA\n    InstDummy,                // 00 CB\n    InstDummy,                // 00 CC\n    InstDummy,                // 00 CD\n    InstDummy,                // 00 CE\n    InstDummy,                // 00 CF\n    InstDummy,                // 00 D0\n    InstDummy,                // 00 D1\n    InstDummy,                // 00 D2\n    InstDummy,                // 00 D3\n    InstDummy,                // 00 D4\n    InstDummy,                // 00 D5\n    InstDummy,                // 00 D6\n    InstDummy,                // 00 D7\n    InstDummy,                // 00 D8\n    InstDummy,                // 00 D9\n    InstDummy,                // 00 DA\n    InstDummy,                // 00 DB\n    InstDummy,                // 00 DC\n    InstDummy,                // 00 DD\n    InstDummy,                // 00 DE\n    InstDummy,                // 00 DF\n    InstDummy,                // 00 E0\n    InstDummy,                // 00 E1\n    InstDummy,                // 00 E2\n    InstDummy,                // 00 E3\n    InstDummy,                // 00 E4\n    InstDummy,                // 00 E5\n    InstDummy,                // 00 E6\n    InstDummy,                // 00 E7\n    InstDummy,                // 00 E8\n    InstDummy,                // 00 E9\n    InstDummy,                // 00 EA\n    InstDummy,                // 00 EB\n    InstDummy,                // 00 EC\n    InstDummy,                // 00 ED\n    InstDummy,                // 00 EE\n    InstDummy,                // 00 EF\n    InstDummy,                // 00 F0\n    InstDummy,                // 00 F1\n    InstDummy,                // 00 F2\n    InstDummy,                // 00 F3\n    InstDummy,                // 00 F4\n    InstDummy,                // 00 F5\n    InstDummy,                // 00 F6\n    InstDummy,                // 00 F7\n    InstDummy,                // 00 F8\n    InstDummy,                // 00 F9\n    InstDummy,                // 00 FA\n    InstDummy,                // 00 FB\n    InstDummy,                // 00 FC\n    InstDummy,                // 00 FD\n    InstDummy,                // 00 FE\n    InstDummy                 // 00 FF\n};\n\nInstructionProc inline constexpr OpcodeTableGraph_Darling[256] = {\n    InstCreateSurf,        // 01 00\n    InstReleaseSurf,       // 01 01\n    InstLoadPic,           // 01 02\n    InstReleaseSurf,       // 01 03\n    InstSurfFill,          // 01 04\n    InstCalc,              // 01 05\n    InstMesViewFlag,       // 01 06\n    InstSetMesWinPri,      // 01 07\n    InstMesSync,           // 01 08\n    InstMesSetID,          // 01 09\n    InstMesCls,            // 01 0A\n    InstMesVoiceWait,      // 01 0B\n    InstMes,               // 01 0C\n    InstMesMain,           // 01 0D\n    InstSetMesModeFormat,  // 01 0E\n    InstSetNGmoji,         // 01 0F\n    InstMesRev,            // 01 10\n    InstMessWindow,        // 01 11\n    InstSel,               // 01 12\n    InstSelect,            // 01 13\n    InstSysSel,            // 01 14\n    InstSysSelect,         // 01 15\n    InstDummy,             // 01 16\n    InstDummy,             // 01 17\n    InstDummy,             // 01 18\n    InstDummy,             // 01 19\n    InstDummy,             // 01 1A\n    InstDummy,             // 01 1B\n    InstDummy,             // 01 1C\n    InstDummy,             // 01 1D\n    InstDummy,             // 01 1E\n    InstUnk011F,           // 01 1F\n    InstSCcapture,         // 01 20\n    InstSetTextTable,      // 01 21\n    InstPlayMovie,         // 01 22\n    InstMovieMain,         // 01 23\n    InstLoadMovie,         // 01 24\n    InstSetRevMes,         // 01 25\n    InstPlayMovieMemory,   // 01 26\n    InstPlayMovie,         // 01 27\n    InstMovieMain,         // 01 28\n    InstLoadMovie,         // 01 29\n    InstPlayMovieMemory,   // 01 2A\n    InstSFDpause,          // 01 2B\n    InstPlayMovie,         // 01 2C\n    InstUnk012D,           // 01 2D\n    InstDummy,             // 01 2E\n    InstDummy,             // 01 2F\n    InstDummy,             // 01 30\n    InstDummy,             // 01 31\n    InstDummy,             // 01 32\n    InstDummy,             // 01 33\n    InstDummy,             // 01 34\n    InstDummy,             // 01 35\n    InstDummy,             // 01 36\n    InstDummy,             // 01 37\n    InstDummy,             // 01 38\n    InstDummy,             // 01 39\n    InstDummy,             // 01 3A\n    InstDummy,             // 01 3B\n    InstDummy,             // 01 3C\n    InstDummy,             // 01 3D\n    InstDummy,             // 01 3E\n    InstDummy,             // 01 3F\n    InstDummy,             // 01 40\n    InstDummy,             // 01 41\n    InstDummy,             // 01 42\n    InstDummy,             // 01 43\n    InstDummy,             // 01 44\n    InstDummy,             // 01 45\n    InstDummy,             // 01 46\n    InstDummy,             // 01 47\n    InstDummy,             // 01 48\n    InstDummy,             // 01 49\n    InstDummy,             // 01 4A\n    InstDummy,             // 01 4B\n    InstDummy,             // 01 4C\n    InstDummy,             // 01 4D\n    InstDummy,             // 01 4E\n    InstDummy,             // 01 4F\n    InstDummy,             // 01 50\n    InstDummy,             // 01 51\n    InstDummy,             // 01 52\n    InstDummy,             // 01 53\n    InstDummy,             // 01 54\n    InstDummy,             // 01 55\n    InstDummy,             // 01 56\n    InstDummy,             // 01 57\n    InstDummy,             // 01 58\n    InstDummy,             // 01 59\n    InstDummy,             // 01 5A\n    InstDummy,             // 01 5B\n    InstDummy,             // 01 5C\n    InstDummy,             // 01 5D\n    InstDummy,             // 01 5E\n    InstDummy,             // 01 5F\n    InstDummy,             // 01 60\n    InstDummy,             // 01 61\n    InstDummy,             // 01 62\n    InstDummy,             // 01 63\n    InstDummy,             // 01 64\n    InstDummy,             // 01 65\n    InstDummy,             // 01 66\n    InstDummy,             // 01 67\n    InstDummy,             // 01 68\n    InstDummy,             // 01 69\n    InstDummy,             // 01 6A\n    InstDummy,             // 01 6B\n    InstDummy,             // 01 6C\n    InstDummy,             // 01 6D\n    InstDummy,             // 01 6E\n    InstDummy,             // 01 6F\n    InstDummy,             // 01 70\n    InstDummy,             // 01 71\n    InstDummy,             // 01 72\n    InstDummy,             // 01 73\n    InstDummy,             // 01 74\n    InstDummy,             // 01 75\n    InstDummy,             // 01 76\n    InstDummy,             // 01 77\n    InstDummy,             // 01 78\n    InstDummy,             // 01 79\n    InstDummy,             // 01 7A\n    InstDummy,             // 01 7B\n    InstDummy,             // 01 7C\n    InstDummy,             // 01 7D\n    InstDummy,             // 01 7E\n    InstDummy,             // 01 7F\n    InstDummy,             // 01 80\n    InstDummy,             // 01 81\n    InstDummy,             // 01 82\n    InstDummy,             // 01 83\n    InstDummy,             // 01 84\n    InstDummy,             // 01 85\n    InstDummy,             // 01 86\n    InstDummy,             // 01 87\n    InstDummy,             // 01 88\n    InstDummy,             // 01 89\n    InstDummy,             // 01 8A\n    InstDummy,             // 01 8B\n    InstDummy,             // 01 8C\n    InstDummy,             // 01 8D\n    InstDummy,             // 01 8E\n    InstDummy,             // 01 8F\n    InstDummy,             // 01 90\n    InstDummy,             // 01 91\n    InstDummy,             // 01 92\n    InstDummy,             // 01 93\n    InstDummy,             // 01 94\n    InstDummy,             // 01 95\n    InstDummy,             // 01 96\n    InstDummy,             // 01 97\n    InstDummy,             // 01 98\n    InstDummy,             // 01 99\n    InstDummy,             // 01 9A\n    InstDummy,             // 01 9B\n    InstDummy,             // 01 9C\n    InstDummy,             // 01 9D\n    InstDummy,             // 01 9E\n    InstDummy,             // 01 9F\n    InstDummy,             // 01 A0\n    InstDummy,             // 01 A1\n    InstDummy,             // 01 A2\n    InstDummy,             // 01 A3\n    InstDummy,             // 01 A4\n    InstDummy,             // 01 A5\n    InstDummy,             // 01 A6\n    InstDummy,             // 01 A7\n    InstDummy,             // 01 A8\n    InstDummy,             // 01 A9\n    InstDummy,             // 01 AA\n    InstDummy,             // 01 AB\n    InstDummy,             // 01 AC\n    InstDummy,             // 01 AD\n    InstDummy,             // 01 AE\n    InstDummy,             // 01 AF\n    InstDummy,             // 01 B0\n    InstDummy,             // 01 B1\n    InstDummy,             // 01 B2\n    InstDummy,             // 01 B3\n    InstDummy,             // 01 B4\n    InstDummy,             // 01 B5\n    InstDummy,             // 01 B6\n    InstDummy,             // 01 B7\n    InstDummy,             // 01 B8\n    InstDummy,             // 01 B9\n    InstDummy,             // 01 BA\n    InstDummy,             // 01 BB\n    InstDummy,             // 01 BC\n    InstDummy,             // 01 BD\n    InstDummy,             // 01 BE\n    InstDummy,             // 01 BF\n    InstDummy,             // 01 C0\n    InstDummy,             // 01 C1\n    InstDummy,             // 01 C2\n    InstDummy,             // 01 C3\n    InstDummy,             // 01 C4\n    InstDummy,             // 01 C5\n    InstDummy,             // 01 C6\n    InstDummy,             // 01 C7\n    InstDummy,             // 01 C8\n    InstDummy,             // 01 C9\n    InstDummy,             // 01 CA\n    InstDummy,             // 01 CB\n    InstDummy,             // 01 CC\n    InstDummy,             // 01 CD\n    InstDummy,             // 01 CE\n    InstDummy,             // 01 CF\n    InstDummy,             // 01 D0\n    InstDummy,             // 01 D1\n    InstDummy,             // 01 D2\n    InstDummy,             // 01 D3\n    InstDummy,             // 01 D4\n    InstDummy,             // 01 D5\n    InstDummy,             // 01 D6\n    InstDummy,             // 01 D7\n    InstDummy,             // 01 D8\n    InstDummy,             // 01 D9\n    InstDummy,             // 01 DA\n    InstDummy,             // 01 DB\n    InstDummy,             // 01 DC\n    InstDummy,             // 01 DD\n    InstDummy,             // 01 DE\n    InstDummy,             // 01 DF\n    InstDummy,             // 01 E0\n    InstDummy,             // 01 E1\n    InstDummy,             // 01 E2\n    InstDummy,             // 01 E3\n    InstDummy,             // 01 E4\n    InstDummy,             // 01 E5\n    InstDummy,             // 01 E6\n    InstDummy,             // 01 E7\n    InstDummy,             // 01 E8\n    InstDummy,             // 01 E9\n    InstDummy,             // 01 EA\n    InstDummy,             // 01 EB\n    InstDummy,             // 01 EC\n    InstDummy,             // 01 ED\n    InstDummy,             // 01 EE\n    InstDummy,             // 01 EF\n    InstDummy,             // 01 F0\n    InstDummy,             // 01 F1\n    InstDummy,             // 01 F2\n    InstDummy,             // 01 F3\n    InstDummy,             // 01 F4\n    InstDummy,             // 01 F5\n    InstDummy,             // 01 F6\n    InstDummy,             // 01 F7\n    InstDummy,             // 01 F8\n    InstDummy,             // 01 F9\n    InstDummy,             // 01 FA\n    InstDummy,             // 01 FB\n    InstDummy,             // 01 FC\n    InstDummy,             // 01 FD\n    InstDummy,             // 01 FE\n    InstDummy              // 01 FF\n};\n\nInstructionProc inline constexpr OpcodeTableUser1_Darling[256] = {\n    InstMSinit,            // 10 00\n    InstBGload,            // 10 01\n    InstBGswap,            // 10 02\n    InstBGsetColor,        // 10 03\n    InstBGsetLink,         // 10 04\n    InstCHAload,           // 10 05\n    InstCHAswap,           // 10 06\n    InstBGcopy,            // 10 07\n    InstCHAcopy,           // 10 08\n    InstSaveSlot,          // 10 09\n    InstSystemMain,        // 10 0A\n    InstCharaLayerLoad,    // 10 0B\n    InstGameInfoInit,      // 10 0C\n    InstCHAmove,           // 10 0D\n    InstBGloadEx,          // 10 0E\n    InstDummy,             // 10 0F\n    InstBGrelease,         // 10 10\n    InstCHArelease,        // 10 11\n    InstClearFlagChk,      // 10 12\n    InstOption,            // 10 13\n    InstSystemDataReset,   // 10 14\n    InstDebugData,         // 10 15\n    InstGetCharaPause,     // 10 16\n    InstBGfadeExpInit,     // 10 17\n    InstDummy,             // 10 18\n    InstDummy,             // 10 19\n    InstDummy,             // 10 1A\n    InstHelp,              // 10 1B\n    InstAchievementMenu,   // 10 1C\n    InstSoundMenu,         // 10 1D\n    InstAllClear,          // 10 1E\n    InstAlbum,             // 10 1F\n    InstMovieMode,         // 10 20\n    InstClistInit,         // 10 21\n    InstAutoSaveOld,       // 10 22\n    InstSaveMenu,          // 10 23\n    InstLoadData,          // 10 24\n    InstDummy,             // 10 25\n    InstDummy,             // 10 26\n    InstSetDic,            // 10 27\n    InstSetPlayMode,       // 10 28\n    InstSetEVflag,         // 10 29\n    InstSetCutin,          // 10 2A\n    InstDummy,             // 10 2B\n    InstDummy,             // 10 2C\n    InstAchChkTitle,       // 10 2D\n    InstSetSceneViewFlag,  // 10 2E\n    InstChkClearFlag,      // 10 2F\n    InstBGeffectWave,      // 10 30\n    InstGeotag,            // 10 31\n    InstNameID,            // 10 32\n    InstTips,              // 10 33\n    InstTitleMenuOld,      // 10 34\n    InstDummy,             // 10 35\n    InstBGeffect,          // 10 36\n    InstPhoneSG,           // 10 37\n    InstUnk1038Darling,    // 10 38\n    InstTwipo,             // 10 39\n    InstUnk103A,           // 10 3A\n    InstDummy,             // 10 3B\n    InstDummy,             // 10 3C\n    InstDummy,             // 10 3D\n    InstDummy,             // 10 3E\n    InstDummy,             // 10 3F\n    InstDummy,             // 10 40\n    InstDummy,             // 10 41\n    InstDummy,             // 10 42\n    InstDummy,             // 10 43\n    InstDummy,             // 10 44\n    InstDummy,             // 10 45\n    InstDummy,             // 10 46\n    InstDummy,             // 10 47\n    InstDummy,             // 10 48\n    InstDummy,             // 10 49\n    InstDummy,             // 10 4A\n    InstDummy,             // 10 4B\n    InstDummy,             // 10 4C\n    InstDummy,             // 10 4D\n    InstDummy,             // 10 4E\n    InstDummy,             // 10 4F\n    InstDummy,             // 10 50\n    InstDummy,             // 10 51\n    InstDummy,             // 10 52\n    InstDummy,             // 10 53\n    InstDummy,             // 10 54\n    InstDummy,             // 10 55\n    InstDummy,             // 10 56\n    InstDummy,             // 10 57\n    InstDummy,             // 10 58\n    InstDummy,             // 10 59\n    InstDummy,             // 10 5A\n    InstDummy,             // 10 5B\n    InstDummy,             // 10 5C\n    InstDummy,             // 10 5D\n    InstDummy,             // 10 5E\n    InstDummy,             // 10 5F\n    InstDummy,             // 10 60\n    InstDummy,             // 10 61\n    InstDummy,             // 10 62\n    InstDummy,             // 10 63\n    InstDummy,             // 10 64\n    InstDummy,             // 10 65\n    InstDummy,             // 10 66\n    InstDummy,             // 10 67\n    InstDummy,             // 10 68\n    InstDummy,             // 10 69\n    InstDummy,             // 10 6A\n    InstDummy,             // 10 6B\n    InstDummy,             // 10 6C\n    InstDummy,             // 10 6D\n    InstDummy,             // 10 6E\n    InstDummy,             // 10 6F\n    InstDummy,             // 10 70\n    InstDummy,             // 10 71\n    InstDummy,             // 10 72\n    InstDummy,             // 10 73\n    InstDummy,             // 10 74\n    InstDummy,             // 10 75\n    InstDummy,             // 10 76\n    InstDummy,             // 10 77\n    InstDummy,             // 10 78\n    InstDummy,             // 10 79\n    InstDummy,             // 10 7A\n    InstDummy,             // 10 7B\n    InstDummy,             // 10 7C\n    InstDummy,             // 10 7D\n    InstDummy,             // 10 7E\n    InstDummy,             // 10 7F\n    InstDummy,             // 10 80\n    InstDummy,             // 10 81\n    InstDummy,             // 10 82\n    InstDummy,             // 10 83\n    InstDummy,             // 10 84\n    InstDummy,             // 10 85\n    InstDummy,             // 10 86\n    InstDummy,             // 10 87\n    InstDummy,             // 10 88\n    InstDummy,             // 10 89\n    InstDummy,             // 10 8A\n    InstDummy,             // 10 8B\n    InstDummy,             // 10 8C\n    InstDummy,             // 10 8D\n    InstDummy,             // 10 8E\n    InstDummy,             // 10 8F\n    InstDummy,             // 10 90\n    InstDummy,             // 10 91\n    InstDummy,             // 10 92\n    InstDummy,             // 10 93\n    InstDummy,             // 10 94\n    InstDummy,             // 10 95\n    InstDummy,             // 10 96\n    InstDummy,             // 10 97\n    InstDummy,             // 10 98\n    InstDummy,             // 10 99\n    InstDummy,             // 10 9A\n    InstDummy,             // 10 9B\n    InstDummy,             // 10 9C\n    InstDummy,             // 10 9D\n    InstDummy,             // 10 9E\n    InstDummy,             // 10 9F\n    InstDummy,             // 10 A0\n    InstDummy,             // 10 A1\n    InstDummy,             // 10 A2\n    InstDummy,             // 10 A3\n    InstDummy,             // 10 A4\n    InstDummy,             // 10 A5\n    InstDummy,             // 10 A6\n    InstDummy,             // 10 A7\n    InstDummy,             // 10 A8\n    InstDummy,             // 10 A9\n    InstDummy,             // 10 AA\n    InstDummy,             // 10 AB\n    InstDummy,             // 10 AC\n    InstDummy,             // 10 AD\n    InstDummy,             // 10 AE\n    InstDummy,             // 10 AF\n    InstDummy,             // 10 B0\n    InstDummy,             // 10 B1\n    InstDummy,             // 10 B2\n    InstDummy,             // 10 B3\n    InstDummy,             // 10 B4\n    InstDummy,             // 10 B5\n    InstDummy,             // 10 B6\n    InstDummy,             // 10 B7\n    InstDummy,             // 10 B8\n    InstDummy,             // 10 B9\n    InstDummy,             // 10 BA\n    InstDummy,             // 10 BB\n    InstDummy,             // 10 BC\n    InstDummy,             // 10 BD\n    InstDummy,             // 10 BE\n    InstDummy,             // 10 BF\n    InstDummy,             // 10 C0\n    InstDummy,             // 10 C1\n    InstDummy,             // 10 C2\n    InstDummy,             // 10 C3\n    InstDummy,             // 10 C4\n    InstDummy,             // 10 C5\n    InstDummy,             // 10 C6\n    InstDummy,             // 10 C7\n    InstDummy,             // 10 C8\n    InstDummy,             // 10 C9\n    InstDummy,             // 10 CA\n    InstDummy,             // 10 CB\n    InstDummy,             // 10 CC\n    InstDummy,             // 10 CD\n    InstDummy,             // 10 CE\n    InstDummy,             // 10 CF\n    InstDummy,             // 10 D0\n    InstDummy,             // 10 D1\n    InstDummy,             // 10 D2\n    InstDummy,             // 10 D3\n    InstDummy,             // 10 D4\n    InstDummy,             // 10 D5\n    InstDummy,             // 10 D6\n    InstDummy,             // 10 D7\n    InstDummy,             // 10 D8\n    InstDummy,             // 10 D9\n    InstDummy,             // 10 DA\n    InstDummy,             // 10 DB\n    InstDummy,             // 10 DC\n    InstDummy,             // 10 DD\n    InstDummy,             // 10 DE\n    InstDummy,             // 10 DF\n    InstDummy,             // 10 E0\n    InstDummy,             // 10 E1\n    InstDummy,             // 10 E2\n    InstDummy,             // 10 E3\n    InstDummy,             // 10 E4\n    InstDummy,             // 10 E5\n    InstDummy,             // 10 E6\n    InstDummy,             // 10 E7\n    InstDummy,             // 10 E8\n    InstDummy,             // 10 E9\n    InstDummy,             // 10 EA\n    InstDummy,             // 10 EB\n    InstDummy,             // 10 EC\n    InstDummy,             // 10 ED\n    InstDummy,             // 10 EE\n    InstDummy,             // 10 EF\n    InstDummy,             // 10 F0\n    InstDummy,             // 10 F1\n    InstDummy,             // 10 F2\n    InstDummy,             // 10 F3\n    InstDummy,             // 10 F4\n    InstDummy,             // 10 F5\n    InstDummy,             // 10 F6\n    InstDummy,             // 10 F7\n    InstDummy,             // 10 F8\n    InstDummy,             // 10 F9\n    InstDummy,             // 10 FA\n    InstDummy,             // 10 FB\n    InstDummy,             // 10 FC\n    InstDummy,             // 10 FD\n    InstDummy,             // 10 FE\n    InstDummy              // 10 FF\n};\n\n}\n\n}\n\n// clang-format on"
  },
  {
    "path": "src/vm/opcodetables_dash.h",
    "content": "#include \"inst_system.h\"\n#include \"inst_controlflow.h\"\n#include \"inst_graphics2d.h\"\n#include \"inst_graphics3d.h\"\n#include \"inst_dialogue.h\"\n#include \"inst_sound.h\"\n#include \"inst_movie.h\"\n#include \"inst_gamespecific.h\"\n#include \"inst_misc.h\"\n\n// clang-format off\n\nnamespace Impacto {\n\nnamespace Vm {\n\nInstructionProc inline constexpr OpcodeTableSystem_Dash[256] = {\n    InstEnd,                  // 00 00\n    InstCreateThread,         // 00 01\n    InstKillThread,           // 00 02\n    InstReset,                // 00 03\n    InstScriptLoad,           // 00 04\n    InstWait,                 // 00 05\n    InstHalt,                 // 00 06\n    InstJump,                 // 00 07\n    InstJumpTable,            // 00 08\n    InstGetLabelAdr,          // 00 09\n    InstIf,                   // 00 0A\n    InstCall,                 // 00 0B\n    InstJumpFar,              // 00 0C\n    InstCallFar,              // 00 0D\n    InstReturn,               // 00 0E\n    InstLoop,                 // 00 0F\n    InstFlagOnJump,           // 00 10\n    InstFlagOnWait,           // 00 11\n    InstSetFlag,              // 00 12\n    InstResetFlag,            // 00 13\n    InstCopyFlag,             // 00 14\n    InstKeyOnJump_Dash,       // 00 15\n    InstKeyWait,              // 00 16\n    InstKeyWaitTimer,         // 00 17\n    InstMemberWrite,          // 00 18\n    InstThreadControl,        // 00 19\n    InstGetSelfPointer,       // 00 1A\n    InstLoadJump,             // 00 1B\n    InstVsync,                // 00 1C\n    InstTest,                 // 00 1D\n    InstThreadControlStore,   // 00 1E\n    InstSwitch,               // 00 1F\n    InstCase,                 // 00 20\n    InstBGMplay,              // 00 21\n    InstBGMstop,              // 00 22\n    InstSEplay,               // 00 23\n    InstSEstop,               // 00 24\n    InstPadAct,               // 00 25\n    InstSSEplay,              // 00 26\n    InstSSEstop,              // 00 27\n    InstCopyThreadWork,       // 00 28\n    InstUPLmenuUI,            // 00 29\n    InstSave,                 // 00 2A\n    InstSaveIconLoad,         // 00 2B\n    InstBGMflag,              // 00 2C\n    InstUPLxTitle,            // 00 2D\n    InstPresence,             // 00 2E\n    InstSetAchievement,          // 00 2F\n    InstSetPlayer,            // 00 30\n    InstVoiceTableLoadMaybe,  // 00 31\n    InstSetPadCustom,         // 00 32\n    InstMwait,                // 00 33\n    InstTerminate,            // 00 34\n    InstSignIn,               // 00 35\n    InstAchievementIcon,      // 00 36\n    InstVoicePlay,            // 00 37\n    InstVoiceStopNew,         // 00 38\n    InstVoicePlayWait,        // 00 39\n    InstBGMduelPlay,          // 00 3A\n    InstSNDpause,             // 00 3B\n    InstSEplayWait,           // 00 3C\n    InstDebugPrint,           // 00 3D\n    InstResetSoundAll,        // 00 3E\n    InstSNDloadStop,          // 00 3F\n    InstBGMstopWait,          // 00 40\n    InstUnk0041,              // 00 41\n    InstSetX360SysMesPos,     // 00 42\n    InstSystemMes,            // 00 43\n    InstSystemMenu,           // 00 44\n    InstGetNowTime,           // 00 45\n    InstGetSystemStatus,      // 00 46\n    InstReboot,               // 00 47\n    InstReloadScript,         // 00 48\n    InstReloadScriptMenu,     // 00 49\n    InstDebugEditer,          // 00 4A\n    InstDummy,                // 00 4B\n    InstDummy,                // 00 4C\n    InstSysVoicePlay,         // 00 4D\n    InstPadActEx,             // 00 4E\n    InstDebugSetup,           // 00 4F\n    InstPressStart,           // 00 50\n    InstGlobalSystemMessage,  // 00 51\n    InstUnk0052,              // 00 52\n    InstUnk0053,              // 00 53\n    InstUnk0054,              // 00 54\n    InstReturnIfFlag,         // 00 55\n    InstDummy,                // 00 56\n    InstDummy,                // 00 57\n    InstDummy,                // 00 58\n    InstDummy,                // 00 59\n    InstDummy,                // 00 5A\n    InstDummy,                // 00 5B\n    InstDummy,                // 00 5C\n    InstDummy,                // 00 5D\n    InstDummy,                // 00 5E\n    InstDummy,                // 00 5F\n    InstDummy,                // 00 60\n    InstDummy,                // 00 61\n    InstDummy,                // 00 62\n    InstDummy,                // 00 63\n    InstDummy,                // 00 64\n    InstDummy,                // 00 65\n    InstDummy,                // 00 66\n    InstDummy,                // 00 67\n    InstDummy,                // 00 68\n    InstDummy,                // 00 69\n    InstDummy,                // 00 6A\n    InstDummy,                // 00 6B\n    InstDummy,                // 00 6C\n    InstDummy,                // 00 6D\n    InstDummy,                // 00 6E\n    InstDummy,                // 00 6F\n    InstDummy,                // 00 70\n    InstDummy,                // 00 71\n    InstDummy,                // 00 72\n    InstDummy,                // 00 73\n    InstDummy,                // 00 74\n    InstDummy,                // 00 75\n    InstDummy,                // 00 76\n    InstDummy,                // 00 77\n    InstDummy,                // 00 78\n    InstDummy,                // 00 79\n    InstDummy,                // 00 7A\n    InstDummy,                // 00 7B\n    InstDummy,                // 00 7C\n    InstDummy,                // 00 7D\n    InstDummy,                // 00 7E\n    InstDummy,                // 00 7F\n    InstDummy,                // 00 80\n    InstDummy,                // 00 81\n    InstDummy,                // 00 82\n    InstDummy,                // 00 83\n    InstDummy,                // 00 84\n    InstDummy,                // 00 85\n    InstDummy,                // 00 86\n    InstDummy,                // 00 87\n    InstDummy,                // 00 88\n    InstDummy,                // 00 89\n    InstDummy,                // 00 8A\n    InstDummy,                // 00 8B\n    InstDummy,                // 00 8C\n    InstDummy,                // 00 8D\n    InstDummy,                // 00 8E\n    InstDummy,                // 00 8F\n    InstDummy,                // 00 90\n    InstDummy,                // 00 91\n    InstDummy,                // 00 92\n    InstDummy,                // 00 93\n    InstDummy,                // 00 94\n    InstDummy,                // 00 95\n    InstDummy,                // 00 96\n    InstDummy,                // 00 97\n    InstDummy,                // 00 98\n    InstDummy,                // 00 99\n    InstDummy,                // 00 9A\n    InstDummy,                // 00 9B\n    InstDummy,                // 00 9C\n    InstDummy,                // 00 9D\n    InstDummy,                // 00 9E\n    InstDummy,                // 00 9F\n    InstDummy,                // 00 A0\n    InstDummy,                // 00 A1\n    InstDummy,                // 00 A2\n    InstDummy,                // 00 A3\n    InstDummy,                // 00 A4\n    InstDummy,                // 00 A5\n    InstDummy,                // 00 A6\n    InstDummy,                // 00 A7\n    InstDummy,                // 00 A8\n    InstDummy,                // 00 A9\n    InstDummy,                // 00 AA\n    InstDummy,                // 00 AB\n    InstDummy,                // 00 AC\n    InstDummy,                // 00 AD\n    InstDummy,                // 00 AE\n    InstDummy,                // 00 AF\n    InstDummy,                // 00 B0\n    InstDummy,                // 00 B1\n    InstDummy,                // 00 B2\n    InstDummy,                // 00 B3\n    InstDummy,                // 00 B4\n    InstDummy,                // 00 B5\n    InstDummy,                // 00 B6\n    InstDummy,                // 00 B7\n    InstDummy,                // 00 B8\n    InstDummy,                // 00 B9\n    InstDummy,                // 00 BA\n    InstDummy,                // 00 BB\n    InstDummy,                // 00 BC\n    InstDummy,                // 00 BD\n    InstDummy,                // 00 BE\n    InstDummy,                // 00 BF\n    InstDummy,                // 00 C0\n    InstDummy,                // 00 C1\n    InstDummy,                // 00 C2\n    InstDummy,                // 00 C3\n    InstDummy,                // 00 C4\n    InstDummy,                // 00 C5\n    InstDummy,                // 00 C6\n    InstDummy,                // 00 C7\n    InstDummy,                // 00 C8\n    InstDummy,                // 00 C9\n    InstDummy,                // 00 CA\n    InstDummy,                // 00 CB\n    InstDummy,                // 00 CC\n    InstDummy,                // 00 CD\n    InstDummy,                // 00 CE\n    InstDummy,                // 00 CF\n    InstDummy,                // 00 D0\n    InstDummy,                // 00 D1\n    InstDummy,                // 00 D2\n    InstDummy,                // 00 D3\n    InstDummy,                // 00 D4\n    InstDummy,                // 00 D5\n    InstDummy,                // 00 D6\n    InstDummy,                // 00 D7\n    InstDummy,                // 00 D8\n    InstDummy,                // 00 D9\n    InstDummy,                // 00 DA\n    InstDummy,                // 00 DB\n    InstDummy,                // 00 DC\n    InstDummy,                // 00 DD\n    InstDummy,                // 00 DE\n    InstDummy,                // 00 DF\n    InstDummy,                // 00 E0\n    InstDummy,                // 00 E1\n    InstDummy,                // 00 E2\n    InstDummy,                // 00 E3\n    InstDummy,                // 00 E4\n    InstDummy,                // 00 E5\n    InstDummy,                // 00 E6\n    InstDummy,                // 00 E7\n    InstDummy,                // 00 E8\n    InstDummy,                // 00 E9\n    InstDummy,                // 00 EA\n    InstDummy,                // 00 EB\n    InstDummy,                // 00 EC\n    InstDummy,                // 00 ED\n    InstDummy,                // 00 EE\n    InstDummy,                // 00 EF\n    InstDummy,                // 00 F0\n    InstDummy,                // 00 F1\n    InstDummy,                // 00 F2\n    InstDummy,                // 00 F3\n    InstDummy,                // 00 F4\n    InstDummy,                // 00 F5\n    InstDummy,                // 00 F6\n    InstDummy,                // 00 F7\n    InstDummy,                // 00 F8\n    InstDummy,                // 00 F9\n    InstDummy,                // 00 FA\n    InstDummy,                // 00 FB\n    InstDummy,                // 00 FC\n    InstDummy,                // 00 FD\n    InstDummy,                // 00 FE\n    InstDummy                 // 00 FF\n};\n\nInstructionProc inline constexpr OpcodeTableGraph_Dash[256] = {\n    InstCreateSurf,        // 01 00\n    InstReleaseSurf,       // 01 01\n    InstLoadPic,           // 01 02\n    InstReleaseSurf,       // 01 03\n    InstSurfFill,          // 01 04\n    InstCalc,              // 01 05\n    InstMesViewFlag,       // 01 06\n    InstSetMesWinPri,      // 01 07\n    InstMesSync,           // 01 08\n    InstMesSetID,          // 01 09\n    InstMesCls,            // 01 0A\n    InstMesVoiceWait,      // 01 0B\n    InstMes,               // 01 0C\n    InstMesMain,           // 01 0D\n    InstSetMesModeFormat,  // 01 0E\n    InstSetNGmoji,         // 01 0F\n    InstMesRev,            // 01 10\n    InstMessWindow,        // 01 11\n    InstSel,               // 01 12\n    InstSelect,            // 01 13\n    InstSysSel,            // 01 14\n    InstSysSelect,         // 01 15\n    InstDummy,             // 01 16\n    InstDummy,             // 01 17\n    InstDummy,             // 01 18\n    InstDummy,             // 01 19\n    InstDummy,             // 01 1A\n    InstDummy,             // 01 1B\n    InstDummy,             // 01 1C\n    InstDummy,             // 01 1D\n    InstDummy,             // 01 1E\n    InstLoadFontWidths,    // 01 1F\n    InstSCcapture,         // 01 20\n    InstSetTextTable,      // 01 21\n    InstPlayMovie,         // 01 22\n    InstMovieMain,         // 01 23\n    InstLoadMovie,         // 01 24\n    InstSetRevMes,         // 01 25\n    InstPlayMovieMemory,   // 01 26\n    InstPlayMovie,         // 01 27\n    InstMovieMain,         // 01 28\n    InstLoadMovie,         // 01 29\n    InstPlayMovieMemory,   // 01 2A\n    InstSFDpause,          // 01 2B\n    InstPlayMovie,         // 01 2C\n    InstUnk012D,           // 01 2D\n    InstDummy,             // 01 2E\n    InstDummy,             // 01 2F\n    InstDummy,             // 01 30\n    InstDummy,             // 01 31\n    InstDummy,             // 01 32\n    InstDummy,             // 01 33\n    InstDummy,             // 01 34\n    InstDummy,             // 01 35\n    InstDummy,             // 01 36\n    InstDummy,             // 01 37\n    InstDummy,             // 01 38\n    InstDummy,             // 01 39\n    InstDummy,             // 01 3A\n    InstDummy,             // 01 3B\n    InstDummy,             // 01 3C\n    InstDummy,             // 01 3D\n    InstDummy,             // 01 3E\n    InstDummy,             // 01 3F\n    InstDummy,             // 01 40\n    InstDummy,             // 01 41\n    InstDummy,             // 01 42\n    InstDummy,             // 01 43\n    InstDummy,             // 01 44\n    InstDummy,             // 01 45\n    InstDummy,             // 01 46\n    InstDummy,             // 01 47\n    InstDummy,             // 01 48\n    InstDummy,             // 01 49\n    InstDummy,             // 01 4A\n    InstDummy,             // 01 4B\n    InstDummy,             // 01 4C\n    InstDummy,             // 01 4D\n    InstDummy,             // 01 4E\n    InstDummy,             // 01 4F\n    InstDummy,             // 01 50\n    InstDummy,             // 01 51\n    InstDummy,             // 01 52\n    InstDummy,             // 01 53\n    InstDummy,             // 01 54\n    InstDummy,             // 01 55\n    InstDummy,             // 01 56\n    InstDummy,             // 01 57\n    InstDummy,             // 01 58\n    InstDummy,             // 01 59\n    InstDummy,             // 01 5A\n    InstDummy,             // 01 5B\n    InstDummy,             // 01 5C\n    InstDummy,             // 01 5D\n    InstDummy,             // 01 5E\n    InstDummy,             // 01 5F\n    InstDummy,             // 01 60\n    InstDummy,             // 01 61\n    InstDummy,             // 01 62\n    InstDummy,             // 01 63\n    InstDummy,             // 01 64\n    InstDummy,             // 01 65\n    InstDummy,             // 01 66\n    InstDummy,             // 01 67\n    InstDummy,             // 01 68\n    InstDummy,             // 01 69\n    InstDummy,             // 01 6A\n    InstDummy,             // 01 6B\n    InstDummy,             // 01 6C\n    InstDummy,             // 01 6D\n    InstDummy,             // 01 6E\n    InstDummy,             // 01 6F\n    InstDummy,             // 01 70\n    InstDummy,             // 01 71\n    InstDummy,             // 01 72\n    InstDummy,             // 01 73\n    InstDummy,             // 01 74\n    InstDummy,             // 01 75\n    InstDummy,             // 01 76\n    InstDummy,             // 01 77\n    InstDummy,             // 01 78\n    InstDummy,             // 01 79\n    InstDummy,             // 01 7A\n    InstDummy,             // 01 7B\n    InstDummy,             // 01 7C\n    InstDummy,             // 01 7D\n    InstDummy,             // 01 7E\n    InstDummy,             // 01 7F\n    InstDummy,             // 01 80\n    InstDummy,             // 01 81\n    InstDummy,             // 01 82\n    InstDummy,             // 01 83\n    InstDummy,             // 01 84\n    InstDummy,             // 01 85\n    InstDummy,             // 01 86\n    InstDummy,             // 01 87\n    InstDummy,             // 01 88\n    InstDummy,             // 01 89\n    InstDummy,             // 01 8A\n    InstDummy,             // 01 8B\n    InstDummy,             // 01 8C\n    InstDummy,             // 01 8D\n    InstDummy,             // 01 8E\n    InstDummy,             // 01 8F\n    InstDummy,             // 01 90\n    InstDummy,             // 01 91\n    InstDummy,             // 01 92\n    InstDummy,             // 01 93\n    InstDummy,             // 01 94\n    InstDummy,             // 01 95\n    InstDummy,             // 01 96\n    InstDummy,             // 01 97\n    InstDummy,             // 01 98\n    InstDummy,             // 01 99\n    InstDummy,             // 01 9A\n    InstDummy,             // 01 9B\n    InstDummy,             // 01 9C\n    InstDummy,             // 01 9D\n    InstDummy,             // 01 9E\n    InstDummy,             // 01 9F\n    InstDummy,             // 01 A0\n    InstDummy,             // 01 A1\n    InstDummy,             // 01 A2\n    InstDummy,             // 01 A3\n    InstDummy,             // 01 A4\n    InstDummy,             // 01 A5\n    InstDummy,             // 01 A6\n    InstDummy,             // 01 A7\n    InstDummy,             // 01 A8\n    InstDummy,             // 01 A9\n    InstDummy,             // 01 AA\n    InstDummy,             // 01 AB\n    InstDummy,             // 01 AC\n    InstDummy,             // 01 AD\n    InstDummy,             // 01 AE\n    InstDummy,             // 01 AF\n    InstDummy,             // 01 B0\n    InstDummy,             // 01 B1\n    InstDummy,             // 01 B2\n    InstDummy,             // 01 B3\n    InstDummy,             // 01 B4\n    InstDummy,             // 01 B5\n    InstDummy,             // 01 B6\n    InstDummy,             // 01 B7\n    InstDummy,             // 01 B8\n    InstDummy,             // 01 B9\n    InstDummy,             // 01 BA\n    InstDummy,             // 01 BB\n    InstDummy,             // 01 BC\n    InstDummy,             // 01 BD\n    InstDummy,             // 01 BE\n    InstDummy,             // 01 BF\n    InstDummy,             // 01 C0\n    InstDummy,             // 01 C1\n    InstDummy,             // 01 C2\n    InstDummy,             // 01 C3\n    InstDummy,             // 01 C4\n    InstDummy,             // 01 C5\n    InstDummy,             // 01 C6\n    InstDummy,             // 01 C7\n    InstDummy,             // 01 C8\n    InstDummy,             // 01 C9\n    InstDummy,             // 01 CA\n    InstDummy,             // 01 CB\n    InstDummy,             // 01 CC\n    InstDummy,             // 01 CD\n    InstDummy,             // 01 CE\n    InstDummy,             // 01 CF\n    InstDummy,             // 01 D0\n    InstDummy,             // 01 D1\n    InstDummy,             // 01 D2\n    InstDummy,             // 01 D3\n    InstDummy,             // 01 D4\n    InstDummy,             // 01 D5\n    InstDummy,             // 01 D6\n    InstDummy,             // 01 D7\n    InstDummy,             // 01 D8\n    InstDummy,             // 01 D9\n    InstDummy,             // 01 DA\n    InstDummy,             // 01 DB\n    InstDummy,             // 01 DC\n    InstDummy,             // 01 DD\n    InstDummy,             // 01 DE\n    InstDummy,             // 01 DF\n    InstDummy,             // 01 E0\n    InstDummy,             // 01 E1\n    InstDummy,             // 01 E2\n    InstDummy,             // 01 E3\n    InstDummy,             // 01 E4\n    InstDummy,             // 01 E5\n    InstDummy,             // 01 E6\n    InstDummy,             // 01 E7\n    InstDummy,             // 01 E8\n    InstDummy,             // 01 E9\n    InstDummy,             // 01 EA\n    InstDummy,             // 01 EB\n    InstDummy,             // 01 EC\n    InstDummy,             // 01 ED\n    InstDummy,             // 01 EE\n    InstDummy,             // 01 EF\n    InstDummy,             // 01 F0\n    InstDummy,             // 01 F1\n    InstDummy,             // 01 F2\n    InstDummy,             // 01 F3\n    InstDummy,             // 01 F4\n    InstDummy,             // 01 F5\n    InstDummy,             // 01 F6\n    InstDummy,             // 01 F7\n    InstDummy,             // 01 F8\n    InstDummy,             // 01 F9\n    InstDummy,             // 01 FA\n    InstDummy,             // 01 FB\n    InstDummy,             // 01 FC\n    InstDummy,             // 01 FD\n    InstDummy,             // 01 FE\n    InstDummy              // 01 FF\n};\n\nInstructionProc inline constexpr OpcodeTableUser1_Dash[256] = {\n    InstMSinit,            // 10 00\n    InstBGload,            // 10 01\n    InstBGswap,            // 10 02\n    InstBGsetColor,        // 10 03\n    InstBGsetLink,         // 10 04\n    InstBGrelease,         // 10 05\n    InstDummy,             // 10 06\n    InstBGcopy,            // 10 07\n    InstDummy,             // 10 08\n    InstSaveSlot,          // 10 09\n    InstSystemMain,        // 10 0A\n    InstDummy,             // 10 0B\n    InstGameInfoInit,      // 10 0C\n    InstDummy,             // 10 0D\n    InstBGloadEx,          // 10 0E\n    InstDummy,             // 10 0F\n    InstDummy,             // 10 10\n    InstDummy,             // 10 11\n    InstClearFlagChk,      // 10 12\n    InstOption,            // 10 13\n    InstSystemDataReset,   // 10 14\n    InstDebugData,         // 10 15\n    InstGetCharaPause,     // 10 16\n    InstBGfadeExpInit,     // 10 17\n    InstDummy,             // 10 18\n    InstDummy,             // 10 19\n    InstDummy,             // 10 1A\n    InstHelp,              // 10 1B\n    InstAchievementMenu,   // 10 1C\n    InstSoundMenu,         // 10 1D\n    InstAllClear,          // 10 1E\n    InstAlbum,             // 10 1F\n    InstMovieMode,         // 10 20\n    InstClistInit,         // 10 21\n    InstAutoSave,          // 10 22\n    InstSaveMenu,          // 10 23\n    InstLoadData,          // 10 24\n    InstDummy,             // 10 25\n    InstDummy,             // 10 26\n    InstSetDic,            // 10 27\n    InstSetPlayMode,       // 10 28\n    InstSetEVflag,         // 10 29\n    InstSetCutin,          // 10 2A\n    InstDummy,             // 10 2B\n    InstDummy,             // 10 2C\n    InstAchChkTitle,       // 10 2D\n    InstSetSceneViewFlag,  // 10 2E\n    InstChkClearFlag,      // 10 2F\n    InstBGeffectWave,      // 10 30\n    InstGeotag,            // 10 31\n    InstNameID,            // 10 32\n    InstTips,              // 10 33\n    InstTitleMenu,         // 10 34\n    InstDummy,             // 10 35\n    InstBGeffect,          // 10 36\n    InstUnk1037,           // 10 37\n    InstMail,              // 10 38\n    InstTwipo_Dash,        // 10 39\n    InstUnk103A,           // 10 3A\n    InstDummy,             // 10 3B\n    InstDummy,             // 10 3C\n    InstDummy,             // 10 3D\n    InstDummy,             // 10 3E\n    InstDummy,             // 10 3F\n    InstDummy,             // 10 40\n    InstDummy,             // 10 41\n    InstDummy,             // 10 42\n    InstDummy,             // 10 43\n    InstDummy,             // 10 44\n    InstDummy,             // 10 45\n    InstDummy,             // 10 46\n    InstDummy,             // 10 47\n    InstDummy,             // 10 48\n    InstDummy,             // 10 49\n    InstDummy,             // 10 4A\n    InstDummy,             // 10 4B\n    InstDummy,             // 10 4C\n    InstDummy,             // 10 4D\n    InstDummy,             // 10 4E\n    InstDummy,             // 10 4F\n    InstDummy,             // 10 50\n    InstDummy,             // 10 51\n    InstDummy,             // 10 52\n    InstDummy,             // 10 53\n    InstDummy,             // 10 54\n    InstDummy,             // 10 55\n    InstDummy,             // 10 56\n    InstDummy,             // 10 57\n    InstDummy,             // 10 58\n    InstDummy,             // 10 59\n    InstDummy,             // 10 5A\n    InstDummy,             // 10 5B\n    InstDummy,             // 10 5C\n    InstDummy,             // 10 5D\n    InstDummy,             // 10 5E\n    InstDummy,             // 10 5F\n    InstDummy,             // 10 60\n    InstDummy,             // 10 61\n    InstDummy,             // 10 62\n    InstDummy,             // 10 63\n    InstDummy,             // 10 64\n    InstDummy,             // 10 65\n    InstDummy,             // 10 66\n    InstDummy,             // 10 67\n    InstDummy,             // 10 68\n    InstDummy,             // 10 69\n    InstDummy,             // 10 6A\n    InstDummy,             // 10 6B\n    InstDummy,             // 10 6C\n    InstDummy,             // 10 6D\n    InstDummy,             // 10 6E\n    InstDummy,             // 10 6F\n    InstDummy,             // 10 70\n    InstDummy,             // 10 71\n    InstDummy,             // 10 72\n    InstDummy,             // 10 73\n    InstDummy,             // 10 74\n    InstDummy,             // 10 75\n    InstDummy,             // 10 76\n    InstDummy,             // 10 77\n    InstDummy,             // 10 78\n    InstDummy,             // 10 79\n    InstDummy,             // 10 7A\n    InstDummy,             // 10 7B\n    InstDummy,             // 10 7C\n    InstDummy,             // 10 7D\n    InstDummy,             // 10 7E\n    InstDummy,             // 10 7F\n    InstDummy,             // 10 80\n    InstDummy,             // 10 81\n    InstDummy,             // 10 82\n    InstDummy,             // 10 83\n    InstDummy,             // 10 84\n    InstDummy,             // 10 85\n    InstDummy,             // 10 86\n    InstDummy,             // 10 87\n    InstDummy,             // 10 88\n    InstDummy,             // 10 89\n    InstDummy,             // 10 8A\n    InstDummy,             // 10 8B\n    InstDummy,             // 10 8C\n    InstDummy,             // 10 8D\n    InstDummy,             // 10 8E\n    InstDummy,             // 10 8F\n    InstDummy,             // 10 90\n    InstDummy,             // 10 91\n    InstDummy,             // 10 92\n    InstDummy,             // 10 93\n    InstDummy,             // 10 94\n    InstDummy,             // 10 95\n    InstDummy,             // 10 96\n    InstDummy,             // 10 97\n    InstDummy,             // 10 98\n    InstDummy,             // 10 99\n    InstDummy,             // 10 9A\n    InstDummy,             // 10 9B\n    InstDummy,             // 10 9C\n    InstDummy,             // 10 9D\n    InstDummy,             // 10 9E\n    InstDummy,             // 10 9F\n    InstDummy,             // 10 A0\n    InstDummy,             // 10 A1\n    InstDummy,             // 10 A2\n    InstDummy,             // 10 A3\n    InstDummy,             // 10 A4\n    InstDummy,             // 10 A5\n    InstDummy,             // 10 A6\n    InstDummy,             // 10 A7\n    InstDummy,             // 10 A8\n    InstDummy,             // 10 A9\n    InstDummy,             // 10 AA\n    InstDummy,             // 10 AB\n    InstDummy,             // 10 AC\n    InstDummy,             // 10 AD\n    InstDummy,             // 10 AE\n    InstDummy,             // 10 AF\n    InstDummy,             // 10 B0\n    InstDummy,             // 10 B1\n    InstDummy,             // 10 B2\n    InstDummy,             // 10 B3\n    InstDummy,             // 10 B4\n    InstDummy,             // 10 B5\n    InstDummy,             // 10 B6\n    InstDummy,             // 10 B7\n    InstDummy,             // 10 B8\n    InstDummy,             // 10 B9\n    InstDummy,             // 10 BA\n    InstDummy,             // 10 BB\n    InstDummy,             // 10 BC\n    InstDummy,             // 10 BD\n    InstDummy,             // 10 BE\n    InstDummy,             // 10 BF\n    InstDummy,             // 10 C0\n    InstDummy,             // 10 C1\n    InstDummy,             // 10 C2\n    InstDummy,             // 10 C3\n    InstDummy,             // 10 C4\n    InstDummy,             // 10 C5\n    InstDummy,             // 10 C6\n    InstDummy,             // 10 C7\n    InstDummy,             // 10 C8\n    InstDummy,             // 10 C9\n    InstDummy,             // 10 CA\n    InstDummy,             // 10 CB\n    InstDummy,             // 10 CC\n    InstDummy,             // 10 CD\n    InstDummy,             // 10 CE\n    InstDummy,             // 10 CF\n    InstDummy,             // 10 D0\n    InstDummy,             // 10 D1\n    InstDummy,             // 10 D2\n    InstDummy,             // 10 D3\n    InstDummy,             // 10 D4\n    InstDummy,             // 10 D5\n    InstDummy,             // 10 D6\n    InstDummy,             // 10 D7\n    InstDummy,             // 10 D8\n    InstDummy,             // 10 D9\n    InstDummy,             // 10 DA\n    InstDummy,             // 10 DB\n    InstDummy,             // 10 DC\n    InstDummy,             // 10 DD\n    InstDummy,             // 10 DE\n    InstDummy,             // 10 DF\n    InstDummy,             // 10 E0\n    InstDummy,             // 10 E1\n    InstDummy,             // 10 E2\n    InstDummy,             // 10 E3\n    InstDummy,             // 10 E4\n    InstDummy,             // 10 E5\n    InstDummy,             // 10 E6\n    InstDummy,             // 10 E7\n    InstDummy,             // 10 E8\n    InstDummy,             // 10 E9\n    InstDummy,             // 10 EA\n    InstDummy,             // 10 EB\n    InstDummy,             // 10 EC\n    InstDummy,             // 10 ED\n    InstDummy,             // 10 EE\n    InstDummy,             // 10 EF\n    InstDummy,             // 10 F0\n    InstDummy,             // 10 F1\n    InstDummy,             // 10 F2\n    InstDummy,             // 10 F3\n    InstDummy,             // 10 F4\n    InstDummy,             // 10 F5\n    InstDummy,             // 10 F6\n    InstDummy,             // 10 F7\n    InstDummy,             // 10 F8\n    InstDummy,             // 10 F9\n    InstDummy,             // 10 FA\n    InstDummy,             // 10 FB\n    InstDummy,             // 10 FC\n    InstDummy,             // 10 FD\n    InstDummy,             // 10 FE\n    InstDummy              // 10 FF\n};\n\nInstructionProc inline constexpr OpcodeTableGraph3D_Dash[256] = {\n    InstDummy,               // 02 00\n    InstCHAload3D,           // 02 01\n    InstCHArelease3D,        // 02 02\n    InstDummy,               // 02 03\n    InstDummy,               // 02 04\n    InstDummy,               // 02 05\n    InstCHAplayAnim3DMaybe,  // 02 06\n    InstCHAUnk02073D_Dash,   // 02 07\n    InstPositionObject_Dash, // 02 08\n    InstCHAsetAnim3D,        // 02 09\n    InstDummy,               // 02 0A\n    InstDummy,               // 02 0B\n    InstDummy,               // 02 0C\n    InstDummy,               // 02 0D\n    InstDummy,               // 02 0E\n    InstDummy,               // 02 0F\n    InstUnk0210,             // 02 10\n    InstUnk0211,             // 02 11\n    InstUnk0212,             // 02 12\n    InstUnk0213,             // 02 13\n    InstUnk0214,             // 02 14\n    InstUnk0215,             // 02 15\n    InstMoveCamera,          // 02 16\n    InstUnk0217,             // 02 17\n    InstUnk0218,             // 02 18\n    InstUnk0219,             // 02 19\n    InstDummy,               // 02 1A\n    InstDummy,               // 02 1B\n    InstDummy,               // 02 1C\n    InstDummy,               // 02 1D\n    InstDummy,               // 02 1E\n    InstDummy,               // 02 1F\n    InstUnk0220_Dash,        // 02 20\n    InstDummy,               // 02 21\n    InstDummy,               // 02 22\n    InstDummy,               // 02 23\n    InstDummy,               // 02 24\n    InstDummy,               // 02 25\n    InstDummy,               // 02 26\n    InstDummy,               // 02 27\n    InstDummy,               // 02 28\n    InstDummy,               // 02 29\n    InstDummy,               // 02 2A\n    InstDummy,               // 02 2B\n    InstDummy,               // 02 2C\n    InstDummy,               // 02 2D\n    InstDummy,               // 02 2E\n    InstDummy,               // 02 2F\n    InstDummy,               // 02 30\n    InstDummy,               // 02 31\n    InstDummy,               // 02 32\n    InstDummy,               // 02 33\n    InstDummy,               // 02 34\n    InstDummy,               // 02 35\n    InstDummy,               // 02 36\n    InstDummy,               // 02 37\n    InstDummy,               // 02 38\n    InstDummy,               // 02 39\n    InstDummy,               // 02 3A\n    InstDummy,               // 02 3B\n    InstDummy,               // 02 3C\n    InstDummy,               // 02 3D\n    InstDummy,               // 02 3E\n    InstDummy,               // 02 3F\n    InstDummy,               // 02 40\n    InstDummy,               // 02 41\n    InstDummy,               // 02 42\n    InstDummy,               // 02 43\n    InstDummy,               // 02 44\n    InstDummy,               // 02 45\n    InstDummy,               // 02 46\n    InstDummy,               // 02 47\n    InstDummy,               // 02 48\n    InstDummy,               // 02 49\n    InstDummy,               // 02 4A\n    InstDummy,               // 02 4B\n    InstDummy,               // 02 4C\n    InstDummy,               // 02 4D\n    InstDummy,               // 02 4E\n    InstDummy,               // 02 4F\n    InstDummy,               // 02 50\n    InstDummy,               // 02 51\n    InstDummy,               // 02 52\n    InstDummy,               // 02 53\n    InstDummy,               // 02 54\n    InstDummy,               // 02 55\n    InstDummy,               // 02 56\n    InstDummy,               // 02 57\n    InstDummy,               // 02 58\n    InstDummy,               // 02 59\n    InstDummy,               // 02 5A\n    InstDummy,               // 02 5B\n    InstDummy,               // 02 5C\n    InstDummy,               // 02 5D\n    InstDummy,               // 02 5E\n    InstDummy,               // 02 5F\n    InstDummy,               // 02 60\n    InstDummy,               // 02 61\n    InstDummy,               // 02 62\n    InstDummy,               // 02 63\n    InstDummy,               // 02 64\n    InstDummy,               // 02 65\n    InstDummy,               // 02 66\n    InstDummy,               // 02 67\n    InstDummy,               // 02 68\n    InstDummy,               // 02 69\n    InstDummy,               // 02 6A\n    InstDummy,               // 02 6B\n    InstDummy,               // 02 6C\n    InstDummy,               // 02 6D\n    InstDummy,               // 02 6E\n    InstDummy,               // 02 6F\n    InstDummy,               // 02 70\n    InstDummy,               // 02 71\n    InstDummy,               // 02 72\n    InstDummy,               // 02 73\n    InstDummy,               // 02 74\n    InstDummy,               // 02 75\n    InstDummy,               // 02 76\n    InstDummy,               // 02 77\n    InstDummy,               // 02 78\n    InstDummy,               // 02 79\n    InstDummy,               // 02 7A\n    InstDummy,               // 02 7B\n    InstDummy,               // 02 7C\n    InstDummy,               // 02 7D\n    InstDummy,               // 02 7E\n    InstDummy,               // 02 7F\n    InstDummy,               // 02 80\n    InstDummy,               // 02 81\n    InstDummy,               // 02 82\n    InstDummy,               // 02 83\n    InstDummy,               // 02 84\n    InstDummy,               // 02 85\n    InstDummy,               // 02 86\n    InstDummy,               // 02 87\n    InstDummy,               // 02 88\n    InstDummy,               // 02 89\n    InstDummy,               // 02 8A\n    InstDummy,               // 02 8B\n    InstDummy,               // 02 8C\n    InstDummy,               // 02 8D\n    InstDummy,               // 02 8E\n    InstDummy,               // 02 8F\n    InstDummy,               // 02 90\n    InstDummy,               // 02 91\n    InstDummy,               // 02 92\n    InstDummy,               // 02 93\n    InstDummy,               // 02 94\n    InstDummy,               // 02 95\n    InstDummy,               // 02 96\n    InstDummy,               // 02 97\n    InstDummy,               // 02 98\n    InstDummy,               // 02 99\n    InstDummy,               // 02 9A\n    InstDummy,               // 02 9B\n    InstDummy,               // 02 9C\n    InstDummy,               // 02 9D\n    InstDummy,               // 02 9E\n    InstDummy,               // 02 9F\n    InstDummy,               // 02 A0\n    InstDummy,               // 02 A1\n    InstDummy,               // 02 A2\n    InstDummy,               // 02 A3\n    InstDummy,               // 02 A4\n    InstDummy,               // 02 A5\n    InstDummy,               // 02 A6\n    InstDummy,               // 02 A7\n    InstDummy,               // 02 A8\n    InstDummy,               // 02 A9\n    InstDummy,               // 02 AA\n    InstDummy,               // 02 AB\n    InstDummy,               // 02 AC\n    InstDummy,               // 02 AD\n    InstDummy,               // 02 AE\n    InstDummy,               // 02 AF\n    InstDummy,               // 02 B0\n    InstDummy,               // 02 B1\n    InstDummy,               // 02 B2\n    InstDummy,               // 02 B3\n    InstDummy,               // 02 B4\n    InstDummy,               // 02 B5\n    InstDummy,               // 02 B6\n    InstDummy,               // 02 B7\n    InstDummy,               // 02 B8\n    InstDummy,               // 02 B9\n    InstDummy,               // 02 BA\n    InstDummy,               // 02 BB\n    InstDummy,               // 02 BC\n    InstDummy,               // 02 BD\n    InstDummy,               // 02 BE\n    InstDummy,               // 02 BF\n    InstDummy,               // 02 C0\n    InstDummy,               // 02 C1\n    InstDummy,               // 02 C2\n    InstDummy,               // 02 C3\n    InstDummy,               // 02 C4\n    InstDummy,               // 02 C5\n    InstDummy,               // 02 C6\n    InstDummy,               // 02 C7\n    InstDummy,               // 02 C8\n    InstDummy,               // 02 C9\n    InstDummy,               // 02 CA\n    InstDummy,               // 02 CB\n    InstDummy,               // 02 CC\n    InstDummy,               // 02 CD\n    InstDummy,               // 02 CE\n    InstDummy,               // 02 CF\n    InstDummy,               // 02 D0\n    InstDummy,               // 02 D1\n    InstDummy,               // 02 D2\n    InstDummy,               // 02 D3\n    InstDummy,               // 02 D4\n    InstDummy,               // 02 D5\n    InstDummy,               // 02 D6\n    InstDummy,               // 02 D7\n    InstDummy,               // 02 D8\n    InstDummy,               // 02 D9\n    InstDummy,               // 02 DA\n    InstDummy,               // 02 DB\n    InstDummy,               // 02 DC\n    InstDummy,               // 02 DD\n    InstDummy,               // 02 DE\n    InstDummy,               // 02 DF\n    InstDummy,               // 02 E0\n    InstDummy,               // 02 E1\n    InstDummy,               // 02 E2\n    InstDummy,               // 02 E3\n    InstDummy,               // 02 E4\n    InstDummy,               // 02 E5\n    InstDummy,               // 02 E6\n    InstDummy,               // 02 E7\n    InstDummy,               // 02 E8\n    InstDummy,               // 02 E9\n    InstDummy,               // 02 EA\n    InstDummy,               // 02 EB\n    InstDummy,               // 02 EC\n    InstDummy,               // 02 ED\n    InstDummy,               // 02 EE\n    InstDummy,               // 02 EF\n    InstDummy,               // 02 F0\n    InstDummy,               // 02 F1\n    InstDummy,               // 02 F2\n    InstDummy,               // 02 F3\n    InstDummy,               // 02 F4\n    InstDummy,               // 02 F5\n    InstDummy,               // 02 F6\n    InstDummy,               // 02 F7\n    InstDummy,               // 02 F8\n    InstDummy,               // 02 F9\n    InstDummy,               // 02 FA\n    InstDummy,               // 02 FB\n    InstDummy,               // 02 FC\n    InstDummy,               // 02 FD\n    InstDummy,               // 02 FE\n    InstDummy                // 02 FF\n};\n\n}\n\n}\n\n// clang-format on"
  },
  {
    "path": "src/vm/opcodetables_mo6tw.h",
    "content": "#include \"inst_system.h\"\n#include \"inst_controlflow.h\"\n#include \"inst_graphics2d.h\"\n#include \"inst_graphics3d.h\"\n#include \"inst_dialogue.h\"\n#include \"inst_sound.h\"\n#include \"inst_movie.h\"\n#include \"inst_gamespecific.h\"\n#include \"inst_misc.h\"\n\n// clang-format off\n\nnamespace Impacto {\n\nnamespace Vm {\n\nInstructionProc inline constexpr OpcodeTableSystem_MO6TW[256] = {\n    InstEnd,                  // 00 00\n    InstCreateThread,         // 00 01\n    InstKillThread,           // 00 02\n    InstReset,                // 00 03\n    InstScriptLoad,           // 00 04\n    InstWait,                 // 00 05\n    InstHalt,                 // 00 06\n    InstJump,                 // 00 07\n    InstJumpTable,            // 00 08\n    InstGetLabelAdr,          // 00 09\n    InstIf,                   // 00 0A\n    InstCall,                 // 00 0B\n    InstJumpFar,              // 00 0C\n    InstCallFar,              // 00 0D\n    InstReturn,               // 00 0E\n    InstLoop,                 // 00 0F\n    InstFlagOnJump,           // 00 10\n    InstFlagOnWait,           // 00 11\n    InstSetFlag,              // 00 12\n    InstResetFlag,            // 00 13\n    InstCopyFlag,             // 00 14\n    InstKeyOnJump,            // 00 15\n    InstKeyWait,              // 00 16\n    InstKeyWaitTimer,         // 00 17\n    InstMemberWrite,          // 00 18\n    InstThreadControl,        // 00 19\n    InstGetSelfPointer,       // 00 1A\n    InstLoadJump,             // 00 1B\n    InstVsync,                // 00 1C\n    InstTest,                 // 00 1D\n    InstThreadControlStore,   // 00 1E\n    InstSwitch,               // 00 1F\n    InstCase,                 // 00 20\n    InstBGMplay,              // 00 21\n    InstBGMstop,              // 00 22\n    InstSEplayMO6,            // 00 23\n    InstSEstop,               // 00 24\n    InstPadAct,               // 00 25\n    InstSSEplay,              // 00 26\n    InstSSEstop,              // 00 27\n    InstCopyThreadWork,       // 00 28\n    InstUPLmenuUI,            // 00 29\n    InstSaveOld,              // 00 2A\n    InstSaveIconLoad,         // 00 2B\n    InstBGMflag,              // 00 2C\n    InstUPLxTitle,            // 00 2D\n    InstPresenceMO6,          // 00 2E\n    InstSetAchievement,          // 00 2F\n    InstSetPlayer,            // 00 30\n    InstVoiceTableLoadMaybe,  // 00 31\n    InstSetPadCustom,         // 00 32\n    InstMwait,                // 00 33\n    InstTerminate,            // 00 34\n    InstSignIn,               // 00 35\n    InstAchievementIcon,      // 00 36\n    InstVoicePlayOld,         // 00 37\n    InstVoiceStop,            // 00 38\n    InstVoicePlayWait,        // 00 39\n    InstBGMduelPlay,          // 00 3A\n    InstSNDpause,             // 00 3B\n    InstSEplayWait,           // 00 3C\n    InstDebugPrint,           // 00 3D\n    InstResetSoundAll,        // 00 3E\n    InstSNDloadStop,          // 00 3F\n    InstBGMstopWait,          // 00 40\n    InstUnk0041,              // 00 41\n    InstSetX360SysMesPos,     // 00 42\n    InstSystemMes,            // 00 43\n    InstSystemMenu,           // 00 44\n    InstGetNowTime,           // 00 45\n    InstGetSystemStatus,      // 00 46\n    InstReboot,               // 00 47\n    InstReloadScript,         // 00 48\n    InstReloadScriptMenu,     // 00 49\n    InstDebugEditer,          // 00 4A\n    InstDebugEditer,          // 00 4B\n    InstDummy,                // 00 4C\n    InstSysVoicePlay,         // 00 4D\n    InstPadActEx,             // 00 4E\n    InstDebugSetup,           // 00 4F\n    InstPressStart,           // 00 50\n    InstGlobalSystemMessage,  // 00 51\n    InstUnk0052,              // 00 52\n    InstUnk0053,              // 00 53\n    InstUnk0054,              // 00 54\n    InstReturnIfFlag,         // 00 55\n    InstDummy,                // 00 56\n    InstDummy,                // 00 57\n    InstDummy,                // 00 58\n    InstDummy,                // 00 59\n    InstDummy,                // 00 5A\n    InstDummy,                // 00 5B\n    InstDummy,                // 00 5C\n    InstDummy,                // 00 5D\n    InstDummy,                // 00 5E\n    InstDummy,                // 00 5F\n    InstDummy,                // 00 60\n    InstDummy,                // 00 61\n    InstDummy,                // 00 62\n    InstDummy,                // 00 63\n    InstDummy,                // 00 64\n    InstDummy,                // 00 65\n    InstDummy,                // 00 66\n    InstDummy,                // 00 67\n    InstDummy,                // 00 68\n    InstDummy,                // 00 69\n    InstDummy,                // 00 6A\n    InstDummy,                // 00 6B\n    InstDummy,                // 00 6C\n    InstDummy,                // 00 6D\n    InstDummy,                // 00 6E\n    InstDummy,                // 00 6F\n    InstDummy,                // 00 70\n    InstDummy,                // 00 71\n    InstDummy,                // 00 72\n    InstDummy,                // 00 73\n    InstDummy,                // 00 74\n    InstDummy,                // 00 75\n    InstDummy,                // 00 76\n    InstDummy,                // 00 77\n    InstDummy,                // 00 78\n    InstDummy,                // 00 79\n    InstDummy,                // 00 7A\n    InstDummy,                // 00 7B\n    InstDummy,                // 00 7C\n    InstDummy,                // 00 7D\n    InstDummy,                // 00 7E\n    InstDummy,                // 00 7F\n    InstDummy,                // 00 80\n    InstDummy,                // 00 81\n    InstDummy,                // 00 82\n    InstDummy,                // 00 83\n    InstDummy,                // 00 84\n    InstDummy,                // 00 85\n    InstDummy,                // 00 86\n    InstDummy,                // 00 87\n    InstDummy,                // 00 88\n    InstDummy,                // 00 89\n    InstDummy,                // 00 8A\n    InstDummy,                // 00 8B\n    InstDummy,                // 00 8C\n    InstDummy,                // 00 8D\n    InstDummy,                // 00 8E\n    InstDummy,                // 00 8F\n    InstDummy,                // 00 90\n    InstDummy,                // 00 91\n    InstDummy,                // 00 92\n    InstDummy,                // 00 93\n    InstDummy,                // 00 94\n    InstDummy,                // 00 95\n    InstDummy,                // 00 96\n    InstDummy,                // 00 97\n    InstDummy,                // 00 98\n    InstDummy,                // 00 99\n    InstDummy,                // 00 9A\n    InstDummy,                // 00 9B\n    InstDummy,                // 00 9C\n    InstDummy,                // 00 9D\n    InstDummy,                // 00 9E\n    InstDummy,                // 00 9F\n    InstDummy,                // 00 A0\n    InstDummy,                // 00 A1\n    InstDummy,                // 00 A2\n    InstDummy,                // 00 A3\n    InstDummy,                // 00 A4\n    InstDummy,                // 00 A5\n    InstDummy,                // 00 A6\n    InstDummy,                // 00 A7\n    InstDummy,                // 00 A8\n    InstDummy,                // 00 A9\n    InstDummy,                // 00 AA\n    InstDummy,                // 00 AB\n    InstDummy,                // 00 AC\n    InstDummy,                // 00 AD\n    InstDummy,                // 00 AE\n    InstDummy,                // 00 AF\n    InstDummy,                // 00 B0\n    InstDummy,                // 00 B1\n    InstDummy,                // 00 B2\n    InstDummy,                // 00 B3\n    InstDummy,                // 00 B4\n    InstDummy,                // 00 B5\n    InstDummy,                // 00 B6\n    InstDummy,                // 00 B7\n    InstDummy,                // 00 B8\n    InstDummy,                // 00 B9\n    InstDummy,                // 00 BA\n    InstDummy,                // 00 BB\n    InstDummy,                // 00 BC\n    InstDummy,                // 00 BD\n    InstDummy,                // 00 BE\n    InstDummy,                // 00 BF\n    InstDummy,                // 00 C0\n    InstDummy,                // 00 C1\n    InstDummy,                // 00 C2\n    InstDummy,                // 00 C3\n    InstDummy,                // 00 C4\n    InstDummy,                // 00 C5\n    InstDummy,                // 00 C6\n    InstDummy,                // 00 C7\n    InstDummy,                // 00 C8\n    InstDummy,                // 00 C9\n    InstDummy,                // 00 CA\n    InstDummy,                // 00 CB\n    InstDummy,                // 00 CC\n    InstDummy,                // 00 CD\n    InstDummy,                // 00 CE\n    InstDummy,                // 00 CF\n    InstDummy,                // 00 D0\n    InstDummy,                // 00 D1\n    InstDummy,                // 00 D2\n    InstDummy,                // 00 D3\n    InstDummy,                // 00 D4\n    InstDummy,                // 00 D5\n    InstDummy,                // 00 D6\n    InstDummy,                // 00 D7\n    InstDummy,                // 00 D8\n    InstDummy,                // 00 D9\n    InstDummy,                // 00 DA\n    InstDummy,                // 00 DB\n    InstDummy,                // 00 DC\n    InstDummy,                // 00 DD\n    InstDummy,                // 00 DE\n    InstDummy,                // 00 DF\n    InstDummy,                // 00 E0\n    InstDummy,                // 00 E1\n    InstDummy,                // 00 E2\n    InstDummy,                // 00 E3\n    InstDummy,                // 00 E4\n    InstDummy,                // 00 E5\n    InstDummy,                // 00 E6\n    InstDummy,                // 00 E7\n    InstDummy,                // 00 E8\n    InstDummy,                // 00 E9\n    InstDummy,                // 00 EA\n    InstDummy,                // 00 EB\n    InstDummy,                // 00 EC\n    InstDummy,                // 00 ED\n    InstDummy,                // 00 EE\n    InstDummy,                // 00 EF\n    InstDummy,                // 00 F0\n    InstDummy,                // 00 F1\n    InstDummy,                // 00 F2\n    InstDummy,                // 00 F3\n    InstDummy,                // 00 F4\n    InstDummy,                // 00 F5\n    InstDummy,                // 00 F6\n    InstDummy,                // 00 F7\n    InstDummy,                // 00 F8\n    InstDummy,                // 00 F9\n    InstDummy,                // 00 FA\n    InstDummy,                // 00 FB\n    InstDummy,                // 00 FC\n    InstDummy,                // 00 FD\n    InstDummy,                // 00 FE\n    InstDummy                 // 00 FF\n};\n\nInstructionProc inline constexpr OpcodeTableGraph_MO6TW[256] = {\n    InstCreateSurf,        // 01 00\n    InstReleaseSurf,       // 01 01\n    InstLoadPic,           // 01 02\n    InstReleaseSurf,       // 01 03\n    InstSurfFill,          // 01 04\n    InstDummy,             // 01 05\n    InstDummy,             // 01 06\n    InstDummy,             // 01 07\n    InstDummy,             // 01 08\n    InstDummy,             // 01 09\n    InstCalc,              // 01 0A\n    InstMesVoiceWait,      // 01 0B\n    InstMes,               // 01 0C\n    InstMesMain,           // 01 0D\n    InstSetMesModeFormat,  // 01 0E\n    InstSetNGmoji,         // 01 0F\n    InstMesRev,            // 01 10\n    InstMessWindow,        // 01 11\n    InstSel,               // 01 12\n    InstSelect,            // 01 13\n    InstSysSel,            // 01 14\n    InstSysSelect,         // 01 15\n    InstDummy,             // 01 16\n    InstDummy,             // 01 17\n    InstDummy,             // 01 18\n    InstDummy,             // 01 19\n    InstDummy,             // 01 1A\n    InstDummy,             // 01 1B\n    InstDummy,             // 01 1C\n    InstDummy,             // 01 1D\n    InstDummy,             // 01 1E\n    InstUnk011F,           // 01 1F\n    InstSCcapture,         // 01 20\n    InstSetTextTable,      // 01 21\n    InstPlayMovieOld,      // 01 22\n    InstMovieMain,         // 01 23\n    InstLoadMovie,         // 01 24\n    InstSetRevMes,         // 01 25\n    InstPlayMovieMemory,   // 01 26\n    InstPlayMovie,         // 01 27\n    InstMovieMain,         // 01 28\n    InstLoadMovie,         // 01 29\n    InstPlayMovieMemory,   // 01 2A\n    InstSFDpause,          // 01 2B\n    InstPlayMovie,         // 01 2C\n    InstUnk012D,           // 01 2D\n    InstDummy,             // 01 2E\n    InstDummy,             // 01 2F\n    InstDummy,             // 01 30\n    InstDummy,             // 01 31\n    InstDummy,             // 01 32\n    InstDummy,             // 01 33\n    InstDummy,             // 01 34\n    InstDummy,             // 01 35\n    InstDummy,             // 01 36\n    InstDummy,             // 01 37\n    InstDummy,             // 01 38\n    InstDummy,             // 01 39\n    InstDummy,             // 01 3A\n    InstDummy,             // 01 3B\n    InstDummy,             // 01 3C\n    InstDummy,             // 01 3D\n    InstDummy,             // 01 3E\n    InstDummy,             // 01 3F\n    InstDummy,             // 01 40\n    InstDummy,             // 01 41\n    InstDummy,             // 01 42\n    InstDummy,             // 01 43\n    InstDummy,             // 01 44\n    InstDummy,             // 01 45\n    InstDummy,             // 01 46\n    InstDummy,             // 01 47\n    InstDummy,             // 01 48\n    InstDummy,             // 01 49\n    InstDummy,             // 01 4A\n    InstDummy,             // 01 4B\n    InstDummy,             // 01 4C\n    InstDummy,             // 01 4D\n    InstDummy,             // 01 4E\n    InstDummy,             // 01 4F\n    InstDummy,             // 01 50\n    InstDummy,             // 01 51\n    InstDummy,             // 01 52\n    InstDummy,             // 01 53\n    InstDummy,             // 01 54\n    InstDummy,             // 01 55\n    InstDummy,             // 01 56\n    InstDummy,             // 01 57\n    InstDummy,             // 01 58\n    InstDummy,             // 01 59\n    InstDummy,             // 01 5A\n    InstDummy,             // 01 5B\n    InstDummy,             // 01 5C\n    InstDummy,             // 01 5D\n    InstDummy,             // 01 5E\n    InstDummy,             // 01 5F\n    InstDummy,             // 01 60\n    InstDummy,             // 01 61\n    InstDummy,             // 01 62\n    InstDummy,             // 01 63\n    InstDummy,             // 01 64\n    InstDummy,             // 01 65\n    InstDummy,             // 01 66\n    InstDummy,             // 01 67\n    InstDummy,             // 01 68\n    InstDummy,             // 01 69\n    InstDummy,             // 01 6A\n    InstDummy,             // 01 6B\n    InstDummy,             // 01 6C\n    InstDummy,             // 01 6D\n    InstDummy,             // 01 6E\n    InstDummy,             // 01 6F\n    InstDummy,             // 01 70\n    InstDummy,             // 01 71\n    InstDummy,             // 01 72\n    InstDummy,             // 01 73\n    InstDummy,             // 01 74\n    InstDummy,             // 01 75\n    InstDummy,             // 01 76\n    InstDummy,             // 01 77\n    InstDummy,             // 01 78\n    InstDummy,             // 01 79\n    InstDummy,             // 01 7A\n    InstDummy,             // 01 7B\n    InstDummy,             // 01 7C\n    InstDummy,             // 01 7D\n    InstDummy,             // 01 7E\n    InstDummy,             // 01 7F\n    InstDummy,             // 01 80\n    InstDummy,             // 01 81\n    InstDummy,             // 01 82\n    InstDummy,             // 01 83\n    InstDummy,             // 01 84\n    InstDummy,             // 01 85\n    InstDummy,             // 01 86\n    InstDummy,             // 01 87\n    InstDummy,             // 01 88\n    InstDummy,             // 01 89\n    InstDummy,             // 01 8A\n    InstDummy,             // 01 8B\n    InstDummy,             // 01 8C\n    InstDummy,             // 01 8D\n    InstDummy,             // 01 8E\n    InstDummy,             // 01 8F\n    InstDummy,             // 01 90\n    InstDummy,             // 01 91\n    InstDummy,             // 01 92\n    InstDummy,             // 01 93\n    InstDummy,             // 01 94\n    InstDummy,             // 01 95\n    InstDummy,             // 01 96\n    InstDummy,             // 01 97\n    InstDummy,             // 01 98\n    InstDummy,             // 01 99\n    InstDummy,             // 01 9A\n    InstDummy,             // 01 9B\n    InstDummy,             // 01 9C\n    InstDummy,             // 01 9D\n    InstDummy,             // 01 9E\n    InstDummy,             // 01 9F\n    InstDummy,             // 01 A0\n    InstDummy,             // 01 A1\n    InstDummy,             // 01 A2\n    InstDummy,             // 01 A3\n    InstDummy,             // 01 A4\n    InstDummy,             // 01 A5\n    InstDummy,             // 01 A6\n    InstDummy,             // 01 A7\n    InstDummy,             // 01 A8\n    InstDummy,             // 01 A9\n    InstDummy,             // 01 AA\n    InstDummy,             // 01 AB\n    InstDummy,             // 01 AC\n    InstDummy,             // 01 AD\n    InstDummy,             // 01 AE\n    InstDummy,             // 01 AF\n    InstDummy,             // 01 B0\n    InstDummy,             // 01 B1\n    InstDummy,             // 01 B2\n    InstDummy,             // 01 B3\n    InstDummy,             // 01 B4\n    InstDummy,             // 01 B5\n    InstDummy,             // 01 B6\n    InstDummy,             // 01 B7\n    InstDummy,             // 01 B8\n    InstDummy,             // 01 B9\n    InstDummy,             // 01 BA\n    InstDummy,             // 01 BB\n    InstDummy,             // 01 BC\n    InstDummy,             // 01 BD\n    InstDummy,             // 01 BE\n    InstDummy,             // 01 BF\n    InstDummy,             // 01 C0\n    InstDummy,             // 01 C1\n    InstDummy,             // 01 C2\n    InstDummy,             // 01 C3\n    InstDummy,             // 01 C4\n    InstDummy,             // 01 C5\n    InstDummy,             // 01 C6\n    InstDummy,             // 01 C7\n    InstDummy,             // 01 C8\n    InstDummy,             // 01 C9\n    InstDummy,             // 01 CA\n    InstDummy,             // 01 CB\n    InstDummy,             // 01 CC\n    InstDummy,             // 01 CD\n    InstDummy,             // 01 CE\n    InstDummy,             // 01 CF\n    InstDummy,             // 01 D0\n    InstDummy,             // 01 D1\n    InstDummy,             // 01 D2\n    InstDummy,             // 01 D3\n    InstDummy,             // 01 D4\n    InstDummy,             // 01 D5\n    InstDummy,             // 01 D6\n    InstDummy,             // 01 D7\n    InstDummy,             // 01 D8\n    InstDummy,             // 01 D9\n    InstDummy,             // 01 DA\n    InstDummy,             // 01 DB\n    InstDummy,             // 01 DC\n    InstDummy,             // 01 DD\n    InstDummy,             // 01 DE\n    InstDummy,             // 01 DF\n    InstDummy,             // 01 E0\n    InstDummy,             // 01 E1\n    InstDummy,             // 01 E2\n    InstDummy,             // 01 E3\n    InstDummy,             // 01 E4\n    InstDummy,             // 01 E5\n    InstDummy,             // 01 E6\n    InstDummy,             // 01 E7\n    InstDummy,             // 01 E8\n    InstDummy,             // 01 E9\n    InstDummy,             // 01 EA\n    InstDummy,             // 01 EB\n    InstDummy,             // 01 EC\n    InstDummy,             // 01 ED\n    InstDummy,             // 01 EE\n    InstDummy,             // 01 EF\n    InstDummy,             // 01 F0\n    InstDummy,             // 01 F1\n    InstDummy,             // 01 F2\n    InstDummy,             // 01 F3\n    InstDummy,             // 01 F4\n    InstDummy,             // 01 F5\n    InstDummy,             // 01 F6\n    InstDummy,             // 01 F7\n    InstDummy,             // 01 F8\n    InstDummy,             // 01 F9\n    InstDummy,             // 01 FA\n    InstDummy,             // 01 FB\n    InstDummy,             // 01 FC\n    InstDummy,             // 01 FD\n    InstDummy,             // 01 FE\n    InstDummy              // 01 FF\n};\n\nInstructionProc inline constexpr OpcodeTableUser1_MO6TW[256] = {\n    InstMSinit,            // 10 00\n    InstBGload,            // 10 01\n    InstBGswap,            // 10 02\n    InstBGsetColor,        // 10 03\n    InstBGsetLinkOld,      // 10 04\n    InstCHAload,           // 10 05\n    InstCHAswap,           // 10 06\n    InstBGcopy,            // 10 07\n    InstCHAcopy,           // 10 08\n    InstSaveSlot,          // 10 09\n    InstSystemMain,        // 10 0A\n    InstCharaLayerLoad,    // 10 0B\n    InstGameInfoInit,      // 10 0C\n    InstCHAmove,           // 10 0D\n    InstBGloadEx,          // 10 0E\n    InstUnk100FMO6,        // 10 0F\n    InstUnk1010MO6,        // 10 10\n    InstUnk1011MO6,        // 10 11\n    InstClearFlagChkOld,   // 10 12\n    InstOption,            // 10 13\n    InstSystemDataReset,   // 10 14\n    InstDebugData,         // 10 15\n    InstGetCharaPause,     // 10 16\n    InstBGfadeExpInit,     // 10 17\n    InstDummy,             // 10 18\n    InstDummy,             // 10 19\n    InstDummy,             // 10 1A\n    InstHelp,              // 10 1B\n    InstAchievementMenu,   // 10 1C\n    InstSoundMenu,         // 10 1D\n    InstAllClear,          // 10 1E\n    InstAlbum,             // 10 1F\n    InstMovieMode,         // 10 20\n    InstClistInit,         // 10 21\n    InstAutoSaveOld,       // 10 22\n    InstSaveMenuOld,       // 10 23\n    InstLoadDataOld,       // 10 24\n    InstDummy,             // 10 25\n    InstDummy,             // 10 26\n    InstEncyclopedia,      // 10 27\n    InstSetPlayMode,       // 10 28\n    InstSetEVflag,         // 10 29\n    InstSetCutin,          // 10 2A\n    InstDummy,             // 10 2B\n    InstDummy,             // 10 2C\n    InstAchChkTitle,       // 10 2D\n    InstSetSceneViewFlag,  // 10 2E\n    InstChkClearFlag,      // 10 2F\n    InstBGeffectWave,      // 10 30\n    InstDummy,             // 10 31\n    InstNameID,            // 10 32\n    InstTips,              // 10 33\n    InstTitleMenuOld,      // 10 34\n    InstDummy,             // 10 35\n    InstBGeffect,          // 10 36\n    InstPhoneSG,           // 10 37\n    InstUnk1038Darling,    // 10 38\n    InstTwipo,             // 10 39\n    InstUnk103A,           // 10 3A\n    InstDummy,             // 10 3B\n    InstDummy,             // 10 3C\n    InstDummy,             // 10 3D\n    InstDummy,             // 10 3E\n    InstDummy,             // 10 3F\n    InstDummy,             // 10 40\n    InstDummy,             // 10 41\n    InstDummy,             // 10 42\n    InstDummy,             // 10 43\n    InstDummy,             // 10 44\n    InstDummy,             // 10 45\n    InstDummy,             // 10 46\n    InstDummy,             // 10 47\n    InstDummy,             // 10 48\n    InstDummy,             // 10 49\n    InstDummy,             // 10 4A\n    InstDummy,             // 10 4B\n    InstDummy,             // 10 4C\n    InstDummy,             // 10 4D\n    InstDummy,             // 10 4E\n    InstDummy,             // 10 4F\n    InstDummy,             // 10 50\n    InstDummy,             // 10 51\n    InstDummy,             // 10 52\n    InstDummy,             // 10 53\n    InstDummy,             // 10 54\n    InstDummy,             // 10 55\n    InstDummy,             // 10 56\n    InstDummy,             // 10 57\n    InstDummy,             // 10 58\n    InstDummy,             // 10 59\n    InstDummy,             // 10 5A\n    InstDummy,             // 10 5B\n    InstDummy,             // 10 5C\n    InstDummy,             // 10 5D\n    InstDummy,             // 10 5E\n    InstDummy,             // 10 5F\n    InstDummy,             // 10 60\n    InstDummy,             // 10 61\n    InstDummy,             // 10 62\n    InstDummy,             // 10 63\n    InstDummy,             // 10 64\n    InstDummy,             // 10 65\n    InstDummy,             // 10 66\n    InstDummy,             // 10 67\n    InstDummy,             // 10 68\n    InstDummy,             // 10 69\n    InstDummy,             // 10 6A\n    InstDummy,             // 10 6B\n    InstDummy,             // 10 6C\n    InstDummy,             // 10 6D\n    InstDummy,             // 10 6E\n    InstDummy,             // 10 6F\n    InstDummy,             // 10 70\n    InstDummy,             // 10 71\n    InstDummy,             // 10 72\n    InstDummy,             // 10 73\n    InstDummy,             // 10 74\n    InstDummy,             // 10 75\n    InstDummy,             // 10 76\n    InstDummy,             // 10 77\n    InstDummy,             // 10 78\n    InstDummy,             // 10 79\n    InstDummy,             // 10 7A\n    InstDummy,             // 10 7B\n    InstDummy,             // 10 7C\n    InstDummy,             // 10 7D\n    InstDummy,             // 10 7E\n    InstDummy,             // 10 7F\n    InstDummy,             // 10 80\n    InstDummy,             // 10 81\n    InstDummy,             // 10 82\n    InstDummy,             // 10 83\n    InstDummy,             // 10 84\n    InstDummy,             // 10 85\n    InstDummy,             // 10 86\n    InstDummy,             // 10 87\n    InstDummy,             // 10 88\n    InstDummy,             // 10 89\n    InstDummy,             // 10 8A\n    InstDummy,             // 10 8B\n    InstDummy,             // 10 8C\n    InstDummy,             // 10 8D\n    InstDummy,             // 10 8E\n    InstDummy,             // 10 8F\n    InstDummy,             // 10 90\n    InstDummy,             // 10 91\n    InstDummy,             // 10 92\n    InstDummy,             // 10 93\n    InstDummy,             // 10 94\n    InstDummy,             // 10 95\n    InstDummy,             // 10 96\n    InstDummy,             // 10 97\n    InstDummy,             // 10 98\n    InstDummy,             // 10 99\n    InstDummy,             // 10 9A\n    InstDummy,             // 10 9B\n    InstDummy,             // 10 9C\n    InstDummy,             // 10 9D\n    InstDummy,             // 10 9E\n    InstDummy,             // 10 9F\n    InstDummy,             // 10 A0\n    InstDummy,             // 10 A1\n    InstDummy,             // 10 A2\n    InstDummy,             // 10 A3\n    InstDummy,             // 10 A4\n    InstDummy,             // 10 A5\n    InstDummy,             // 10 A6\n    InstDummy,             // 10 A7\n    InstDummy,             // 10 A8\n    InstDummy,             // 10 A9\n    InstDummy,             // 10 AA\n    InstDummy,             // 10 AB\n    InstDummy,             // 10 AC\n    InstDummy,             // 10 AD\n    InstDummy,             // 10 AE\n    InstDummy,             // 10 AF\n    InstDummy,             // 10 B0\n    InstDummy,             // 10 B1\n    InstDummy,             // 10 B2\n    InstDummy,             // 10 B3\n    InstDummy,             // 10 B4\n    InstDummy,             // 10 B5\n    InstDummy,             // 10 B6\n    InstDummy,             // 10 B7\n    InstDummy,             // 10 B8\n    InstDummy,             // 10 B9\n    InstDummy,             // 10 BA\n    InstDummy,             // 10 BB\n    InstDummy,             // 10 BC\n    InstDummy,             // 10 BD\n    InstDummy,             // 10 BE\n    InstDummy,             // 10 BF\n    InstDummy,             // 10 C0\n    InstDummy,             // 10 C1\n    InstDummy,             // 10 C2\n    InstDummy,             // 10 C3\n    InstDummy,             // 10 C4\n    InstDummy,             // 10 C5\n    InstDummy,             // 10 C6\n    InstDummy,             // 10 C7\n    InstDummy,             // 10 C8\n    InstDummy,             // 10 C9\n    InstDummy,             // 10 CA\n    InstDummy,             // 10 CB\n    InstDummy,             // 10 CC\n    InstDummy,             // 10 CD\n    InstDummy,             // 10 CE\n    InstDummy,             // 10 CF\n    InstDummy,             // 10 D0\n    InstDummy,             // 10 D1\n    InstDummy,             // 10 D2\n    InstDummy,             // 10 D3\n    InstDummy,             // 10 D4\n    InstDummy,             // 10 D5\n    InstDummy,             // 10 D6\n    InstDummy,             // 10 D7\n    InstDummy,             // 10 D8\n    InstDummy,             // 10 D9\n    InstDummy,             // 10 DA\n    InstDummy,             // 10 DB\n    InstDummy,             // 10 DC\n    InstDummy,             // 10 DD\n    InstDummy,             // 10 DE\n    InstDummy,             // 10 DF\n    InstDummy,             // 10 E0\n    InstDummy,             // 10 E1\n    InstDummy,             // 10 E2\n    InstDummy,             // 10 E3\n    InstDummy,             // 10 E4\n    InstDummy,             // 10 E5\n    InstDummy,             // 10 E6\n    InstDummy,             // 10 E7\n    InstDummy,             // 10 E8\n    InstDummy,             // 10 E9\n    InstDummy,             // 10 EA\n    InstDummy,             // 10 EB\n    InstDummy,             // 10 EC\n    InstDummy,             // 10 ED\n    InstDummy,             // 10 EE\n    InstDummy,             // 10 EF\n    InstDummy,             // 10 F0\n    InstDummy,             // 10 F1\n    InstDummy,             // 10 F2\n    InstDummy,             // 10 F3\n    InstDummy,             // 10 F4\n    InstDummy,             // 10 F5\n    InstDummy,             // 10 F6\n    InstDummy,             // 10 F7\n    InstDummy,             // 10 F8\n    InstDummy,             // 10 F9\n    InstDummy,             // 10 FA\n    InstDummy,             // 10 FB\n    InstDummy,             // 10 FC\n    InstDummy,             // 10 FD\n    InstDummy,             // 10 FE\n    InstDummy              // 10 FF\n};\n\n}\n\n}\n\n// clang-format on"
  },
  {
    "path": "src/vm/opcodetables_mo7.h",
    "content": "#include \"inst_system.h\"\n#include \"inst_controlflow.h\"\n#include \"inst_graphics2d.h\"\n#include \"inst_graphics3d.h\"\n#include \"inst_dialogue.h\"\n#include \"inst_sound.h\"\n#include \"inst_movie.h\"\n#include \"inst_gamespecific.h\"\n#include \"inst_misc.h\"\n\n// clang-format off\n\nnamespace Impacto {\n\nnamespace Vm {\n\nInstructionProc inline constexpr OpcodeTableSystem_MO7[256] = {\n    InstEnd,                  // 00 00\n    InstCreateThread,         // 00 01\n    InstKillThread,           // 00 02\n    InstReset,                // 00 03\n    InstScriptLoad,           // 00 04\n    InstWait,                 // 00 05\n    InstHalt,                 // 00 06\n    InstJump,                 // 00 07\n    InstJumpTable,            // 00 08\n    InstGetLabelAdr,          // 00 09\n    InstIf,                   // 00 0A\n    InstCall,                 // 00 0B\n    InstJumpFar,              // 00 0C\n    InstCallFar,              // 00 0D\n    InstReturn,               // 00 0E\n    InstLoop,                 // 00 0F\n    InstFlagOnJump,           // 00 10\n    InstFlagOnWait,           // 00 11\n    InstSetFlag,              // 00 12\n    InstResetFlag,            // 00 13\n    InstCopyFlag,             // 00 14\n    InstKeyOnJump,            // 00 15\n    InstKeyWait,              // 00 16\n    InstKeyWaitTimer,         // 00 17\n    InstMemberWrite,          // 00 18\n    InstThreadControl,        // 00 19\n    InstGetSelfPointer,       // 00 1A\n    InstLoadJump,             // 00 1B\n    InstVsync,                // 00 1C\n    InstTest,                 // 00 1D\n    InstThreadControlStore,   // 00 1E\n    InstSwitch,               // 00 1F\n    InstCase,                 // 00 20\n    InstBGMplay,              // 00 21\n    InstBGMstop,              // 00 22\n    InstSEplay,               // 00 23\n    InstSEstop,               // 00 24\n    InstPadAct,               // 00 25\n    InstSSEplay,              // 00 26\n    InstSSEstop,              // 00 27\n    InstCopyThreadWork,       // 00 28\n    InstUPLmenuUI,            // 00 29\n    InstSaveOld,              // 00 2A\n    InstSaveIconLoad,         // 00 2B\n    InstBGMflag,              // 00 2C\n    InstUPLxTitle,            // 00 2D\n    InstPresence,             // 00 2E\n    InstSetAchievement,          // 00 2F\n    InstSetPlayer,            // 00 30\n    InstVoiceTableLoadMaybe,  // 00 31\n    InstSetPadCustom,         // 00 32\n    InstMwait,                // 00 33\n    InstTerminate,            // 00 34\n    InstSignIn,               // 00 35\n    InstAchievementIcon,      // 00 36\n    InstVoicePlayOld,         // 00 37\n    InstVoiceStop,            // 00 38\n    InstVoicePlayWait,        // 00 39\n    InstBGMduelPlay,          // 00 3A\n    InstSNDpause,             // 00 3B\n    InstSEplayWait,           // 00 3C\n    InstDebugPrint,           // 00 3D\n    InstResetSoundAll,        // 00 3E\n    InstSNDloadStop,          // 00 3F\n    InstBGMstopWait,          // 00 40\n    InstUnk0041,              // 00 41\n    InstSetX360SysMesPos,     // 00 42\n    InstSystemMes,            // 00 43\n    InstSystemMenu,           // 00 44\n    InstGetNowTime,           // 00 45\n    InstGetSystemStatus,      // 00 46\n    InstReboot,               // 00 47\n    InstReloadScript,         // 00 48\n    InstReloadScriptMenu,     // 00 49\n    InstDebugEditer,          // 00 4A\n    InstDebugEditer,          // 00 4B\n    InstDummy,                // 00 4C\n    InstSysVoicePlay,         // 00 4D\n    InstPadActEx,             // 00 4E\n    InstDebugSetup,           // 00 4F\n    InstPressStart,           // 00 50\n    InstGlobalSystemMessage,  // 00 51\n    InstUnk0052,              // 00 52\n    InstUnk0053,              // 00 53\n    InstUnk0054,              // 00 54\n    InstReturnIfFlag,         // 00 55\n    InstDummy,                // 00 56\n    InstDummy,                // 00 57\n    InstDummy,                // 00 58\n    InstDummy,                // 00 59\n    InstDummy,                // 00 5A\n    InstDummy,                // 00 5B\n    InstDummy,                // 00 5C\n    InstDummy,                // 00 5D\n    InstDummy,                // 00 5E\n    InstDummy,                // 00 5F\n    InstDummy,                // 00 60\n    InstDummy,                // 00 61\n    InstDummy,                // 00 62\n    InstDummy,                // 00 63\n    InstDummy,                // 00 64\n    InstDummy,                // 00 65\n    InstDummy,                // 00 66\n    InstDummy,                // 00 67\n    InstDummy,                // 00 68\n    InstDummy,                // 00 69\n    InstDummy,                // 00 6A\n    InstDummy,                // 00 6B\n    InstDummy,                // 00 6C\n    InstDummy,                // 00 6D\n    InstDummy,                // 00 6E\n    InstDummy,                // 00 6F\n    InstDummy,                // 00 70\n    InstDummy,                // 00 71\n    InstDummy,                // 00 72\n    InstDummy,                // 00 73\n    InstDummy,                // 00 74\n    InstDummy,                // 00 75\n    InstDummy,                // 00 76\n    InstDummy,                // 00 77\n    InstDummy,                // 00 78\n    InstDummy,                // 00 79\n    InstDummy,                // 00 7A\n    InstDummy,                // 00 7B\n    InstDummy,                // 00 7C\n    InstDummy,                // 00 7D\n    InstDummy,                // 00 7E\n    InstDummy,                // 00 7F\n    InstDummy,                // 00 80\n    InstDummy,                // 00 81\n    InstDummy,                // 00 82\n    InstDummy,                // 00 83\n    InstDummy,                // 00 84\n    InstDummy,                // 00 85\n    InstDummy,                // 00 86\n    InstDummy,                // 00 87\n    InstDummy,                // 00 88\n    InstDummy,                // 00 89\n    InstDummy,                // 00 8A\n    InstDummy,                // 00 8B\n    InstDummy,                // 00 8C\n    InstDummy,                // 00 8D\n    InstDummy,                // 00 8E\n    InstDummy,                // 00 8F\n    InstDummy,                // 00 90\n    InstDummy,                // 00 91\n    InstDummy,                // 00 92\n    InstDummy,                // 00 93\n    InstDummy,                // 00 94\n    InstDummy,                // 00 95\n    InstDummy,                // 00 96\n    InstDummy,                // 00 97\n    InstDummy,                // 00 98\n    InstDummy,                // 00 99\n    InstDummy,                // 00 9A\n    InstDummy,                // 00 9B\n    InstDummy,                // 00 9C\n    InstDummy,                // 00 9D\n    InstDummy,                // 00 9E\n    InstDummy,                // 00 9F\n    InstDummy,                // 00 A0\n    InstDummy,                // 00 A1\n    InstDummy,                // 00 A2\n    InstDummy,                // 00 A3\n    InstDummy,                // 00 A4\n    InstDummy,                // 00 A5\n    InstDummy,                // 00 A6\n    InstDummy,                // 00 A7\n    InstDummy,                // 00 A8\n    InstDummy,                // 00 A9\n    InstDummy,                // 00 AA\n    InstDummy,                // 00 AB\n    InstDummy,                // 00 AC\n    InstDummy,                // 00 AD\n    InstDummy,                // 00 AE\n    InstDummy,                // 00 AF\n    InstDummy,                // 00 B0\n    InstDummy,                // 00 B1\n    InstDummy,                // 00 B2\n    InstDummy,                // 00 B3\n    InstDummy,                // 00 B4\n    InstDummy,                // 00 B5\n    InstDummy,                // 00 B6\n    InstDummy,                // 00 B7\n    InstDummy,                // 00 B8\n    InstDummy,                // 00 B9\n    InstDummy,                // 00 BA\n    InstDummy,                // 00 BB\n    InstDummy,                // 00 BC\n    InstDummy,                // 00 BD\n    InstDummy,                // 00 BE\n    InstDummy,                // 00 BF\n    InstDummy,                // 00 C0\n    InstDummy,                // 00 C1\n    InstDummy,                // 00 C2\n    InstDummy,                // 00 C3\n    InstDummy,                // 00 C4\n    InstDummy,                // 00 C5\n    InstDummy,                // 00 C6\n    InstDummy,                // 00 C7\n    InstDummy,                // 00 C8\n    InstDummy,                // 00 C9\n    InstDummy,                // 00 CA\n    InstDummy,                // 00 CB\n    InstDummy,                // 00 CC\n    InstDummy,                // 00 CD\n    InstDummy,                // 00 CE\n    InstDummy,                // 00 CF\n    InstDummy,                // 00 D0\n    InstDummy,                // 00 D1\n    InstDummy,                // 00 D2\n    InstDummy,                // 00 D3\n    InstDummy,                // 00 D4\n    InstDummy,                // 00 D5\n    InstDummy,                // 00 D6\n    InstDummy,                // 00 D7\n    InstDummy,                // 00 D8\n    InstDummy,                // 00 D9\n    InstDummy,                // 00 DA\n    InstDummy,                // 00 DB\n    InstDummy,                // 00 DC\n    InstDummy,                // 00 DD\n    InstDummy,                // 00 DE\n    InstDummy,                // 00 DF\n    InstDummy,                // 00 E0\n    InstDummy,                // 00 E1\n    InstDummy,                // 00 E2\n    InstDummy,                // 00 E3\n    InstDummy,                // 00 E4\n    InstDummy,                // 00 E5\n    InstDummy,                // 00 E6\n    InstDummy,                // 00 E7\n    InstDummy,                // 00 E8\n    InstDummy,                // 00 E9\n    InstDummy,                // 00 EA\n    InstDummy,                // 00 EB\n    InstDummy,                // 00 EC\n    InstDummy,                // 00 ED\n    InstDummy,                // 00 EE\n    InstDummy,                // 00 EF\n    InstDummy,                // 00 F0\n    InstDummy,                // 00 F1\n    InstDummy,                // 00 F2\n    InstDummy,                // 00 F3\n    InstDummy,                // 00 F4\n    InstDummy,                // 00 F5\n    InstDummy,                // 00 F6\n    InstDummy,                // 00 F7\n    InstDummy,                // 00 F8\n    InstDummy,                // 00 F9\n    InstDummy,                // 00 FA\n    InstDummy,                // 00 FB\n    InstDummy,                // 00 FC\n    InstDummy,                // 00 FD\n    InstDummy,                // 00 FE\n    InstDummy                 // 00 FF\n};\n\nInstructionProc inline constexpr OpcodeTableGraph_MO7[256] = {\n    InstCreateSurf,        // 01 00\n    InstReleaseSurf,       // 01 01\n    InstLoadPic,           // 01 02\n    InstReleaseSurf,       // 01 03\n    InstSurfFill,          // 01 04\n    InstCalc,              // 01 05\n    InstMesViewFlag,       // 01 06\n    InstSetMesWinPri,      // 01 07\n    InstMesSync,           // 01 08\n    InstMesSetID,          // 01 09\n    InstMesCls,            // 01 0A\n    InstMesVoiceWait,      // 01 0B\n    InstMes,               // 01 0C\n    InstMesMain,           // 01 0D\n    InstSetMesModeFormat,  // 01 0E\n    InstSetNGmoji,         // 01 0F\n    InstMesRev,            // 01 10\n    InstMessWindow,        // 01 11\n    InstSel,               // 01 12\n    InstSelect,            // 01 13\n    InstSysSel,            // 01 14\n    InstSysSelect,         // 01 15\n    InstDummy,             // 01 16\n    InstDummy,             // 01 17\n    InstDummy,             // 01 18\n    InstDummy,             // 01 19\n    InstDummy,             // 01 1A\n    InstDummy,             // 01 1B\n    InstDummy,             // 01 1C\n    InstDummy,             // 01 1D\n    InstDummy,             // 01 1E\n    InstUnk011F,           // 01 1F\n    InstSCcapture,         // 01 20\n    InstSetTextTable,      // 01 21\n    InstPlayMovie,         // 01 22\n    InstMovieMain,         // 01 23\n    InstLoadMovie,         // 01 24\n    InstSetRevMes,         // 01 25\n    InstPlayMovieMemory,   // 01 26\n    InstPlayMovie,         // 01 27\n    InstMovieMain,         // 01 28\n    InstLoadMovie,         // 01 29\n    InstPlayMovieMemory,   // 01 2A\n    InstSFDpause,          // 01 2B\n    InstPlayMovie,         // 01 2C\n    InstUnk012D,           // 01 2D\n    InstDummy,             // 01 2E\n    InstDummy,             // 01 2F\n    InstDummy,             // 01 30\n    InstDummy,             // 01 31\n    InstDummy,             // 01 32\n    InstDummy,             // 01 33\n    InstDummy,             // 01 34\n    InstDummy,             // 01 35\n    InstDummy,             // 01 36\n    InstDummy,             // 01 37\n    InstDummy,             // 01 38\n    InstDummy,             // 01 39\n    InstDummy,             // 01 3A\n    InstDummy,             // 01 3B\n    InstDummy,             // 01 3C\n    InstDummy,             // 01 3D\n    InstDummy,             // 01 3E\n    InstDummy,             // 01 3F\n    InstDummy,             // 01 40\n    InstDummy,             // 01 41\n    InstDummy,             // 01 42\n    InstDummy,             // 01 43\n    InstDummy,             // 01 44\n    InstDummy,             // 01 45\n    InstDummy,             // 01 46\n    InstDummy,             // 01 47\n    InstDummy,             // 01 48\n    InstDummy,             // 01 49\n    InstDummy,             // 01 4A\n    InstDummy,             // 01 4B\n    InstDummy,             // 01 4C\n    InstDummy,             // 01 4D\n    InstDummy,             // 01 4E\n    InstDummy,             // 01 4F\n    InstDummy,             // 01 50\n    InstDummy,             // 01 51\n    InstDummy,             // 01 52\n    InstDummy,             // 01 53\n    InstDummy,             // 01 54\n    InstDummy,             // 01 55\n    InstDummy,             // 01 56\n    InstDummy,             // 01 57\n    InstDummy,             // 01 58\n    InstDummy,             // 01 59\n    InstDummy,             // 01 5A\n    InstDummy,             // 01 5B\n    InstDummy,             // 01 5C\n    InstDummy,             // 01 5D\n    InstDummy,             // 01 5E\n    InstDummy,             // 01 5F\n    InstDummy,             // 01 60\n    InstDummy,             // 01 61\n    InstDummy,             // 01 62\n    InstDummy,             // 01 63\n    InstDummy,             // 01 64\n    InstDummy,             // 01 65\n    InstDummy,             // 01 66\n    InstDummy,             // 01 67\n    InstDummy,             // 01 68\n    InstDummy,             // 01 69\n    InstDummy,             // 01 6A\n    InstDummy,             // 01 6B\n    InstDummy,             // 01 6C\n    InstDummy,             // 01 6D\n    InstDummy,             // 01 6E\n    InstDummy,             // 01 6F\n    InstDummy,             // 01 70\n    InstDummy,             // 01 71\n    InstDummy,             // 01 72\n    InstDummy,             // 01 73\n    InstDummy,             // 01 74\n    InstDummy,             // 01 75\n    InstDummy,             // 01 76\n    InstDummy,             // 01 77\n    InstDummy,             // 01 78\n    InstDummy,             // 01 79\n    InstDummy,             // 01 7A\n    InstDummy,             // 01 7B\n    InstDummy,             // 01 7C\n    InstDummy,             // 01 7D\n    InstDummy,             // 01 7E\n    InstDummy,             // 01 7F\n    InstDummy,             // 01 80\n    InstDummy,             // 01 81\n    InstDummy,             // 01 82\n    InstDummy,             // 01 83\n    InstDummy,             // 01 84\n    InstDummy,             // 01 85\n    InstDummy,             // 01 86\n    InstDummy,             // 01 87\n    InstDummy,             // 01 88\n    InstDummy,             // 01 89\n    InstDummy,             // 01 8A\n    InstDummy,             // 01 8B\n    InstDummy,             // 01 8C\n    InstDummy,             // 01 8D\n    InstDummy,             // 01 8E\n    InstDummy,             // 01 8F\n    InstDummy,             // 01 90\n    InstDummy,             // 01 91\n    InstDummy,             // 01 92\n    InstDummy,             // 01 93\n    InstDummy,             // 01 94\n    InstDummy,             // 01 95\n    InstDummy,             // 01 96\n    InstDummy,             // 01 97\n    InstDummy,             // 01 98\n    InstDummy,             // 01 99\n    InstDummy,             // 01 9A\n    InstDummy,             // 01 9B\n    InstDummy,             // 01 9C\n    InstDummy,             // 01 9D\n    InstDummy,             // 01 9E\n    InstDummy,             // 01 9F\n    InstDummy,             // 01 A0\n    InstDummy,             // 01 A1\n    InstDummy,             // 01 A2\n    InstDummy,             // 01 A3\n    InstDummy,             // 01 A4\n    InstDummy,             // 01 A5\n    InstDummy,             // 01 A6\n    InstDummy,             // 01 A7\n    InstDummy,             // 01 A8\n    InstDummy,             // 01 A9\n    InstDummy,             // 01 AA\n    InstDummy,             // 01 AB\n    InstDummy,             // 01 AC\n    InstDummy,             // 01 AD\n    InstDummy,             // 01 AE\n    InstDummy,             // 01 AF\n    InstDummy,             // 01 B0\n    InstDummy,             // 01 B1\n    InstDummy,             // 01 B2\n    InstDummy,             // 01 B3\n    InstDummy,             // 01 B4\n    InstDummy,             // 01 B5\n    InstDummy,             // 01 B6\n    InstDummy,             // 01 B7\n    InstDummy,             // 01 B8\n    InstDummy,             // 01 B9\n    InstDummy,             // 01 BA\n    InstDummy,             // 01 BB\n    InstDummy,             // 01 BC\n    InstDummy,             // 01 BD\n    InstDummy,             // 01 BE\n    InstDummy,             // 01 BF\n    InstDummy,             // 01 C0\n    InstDummy,             // 01 C1\n    InstDummy,             // 01 C2\n    InstDummy,             // 01 C3\n    InstDummy,             // 01 C4\n    InstDummy,             // 01 C5\n    InstDummy,             // 01 C6\n    InstDummy,             // 01 C7\n    InstDummy,             // 01 C8\n    InstDummy,             // 01 C9\n    InstDummy,             // 01 CA\n    InstDummy,             // 01 CB\n    InstDummy,             // 01 CC\n    InstDummy,             // 01 CD\n    InstDummy,             // 01 CE\n    InstDummy,             // 01 CF\n    InstDummy,             // 01 D0\n    InstDummy,             // 01 D1\n    InstDummy,             // 01 D2\n    InstDummy,             // 01 D3\n    InstDummy,             // 01 D4\n    InstDummy,             // 01 D5\n    InstDummy,             // 01 D6\n    InstDummy,             // 01 D7\n    InstDummy,             // 01 D8\n    InstDummy,             // 01 D9\n    InstDummy,             // 01 DA\n    InstDummy,             // 01 DB\n    InstDummy,             // 01 DC\n    InstDummy,             // 01 DD\n    InstDummy,             // 01 DE\n    InstDummy,             // 01 DF\n    InstDummy,             // 01 E0\n    InstDummy,             // 01 E1\n    InstDummy,             // 01 E2\n    InstDummy,             // 01 E3\n    InstDummy,             // 01 E4\n    InstDummy,             // 01 E5\n    InstDummy,             // 01 E6\n    InstDummy,             // 01 E7\n    InstDummy,             // 01 E8\n    InstDummy,             // 01 E9\n    InstDummy,             // 01 EA\n    InstDummy,             // 01 EB\n    InstDummy,             // 01 EC\n    InstDummy,             // 01 ED\n    InstDummy,             // 01 EE\n    InstDummy,             // 01 EF\n    InstDummy,             // 01 F0\n    InstDummy,             // 01 F1\n    InstDummy,             // 01 F2\n    InstDummy,             // 01 F3\n    InstDummy,             // 01 F4\n    InstDummy,             // 01 F5\n    InstDummy,             // 01 F6\n    InstDummy,             // 01 F7\n    InstDummy,             // 01 F8\n    InstDummy,             // 01 F9\n    InstDummy,             // 01 FA\n    InstDummy,             // 01 FB\n    InstDummy,             // 01 FC\n    InstDummy,             // 01 FD\n    InstDummy,             // 01 FE\n    InstDummy              // 01 FF\n};\n\nInstructionProc inline constexpr OpcodeTableUser1_MO7[256] = {\n    InstMSinit,            // 10 00\n    InstBGload,            // 10 01\n    InstBGswap,            // 10 02\n    InstBGsetColor,        // 10 03\n    InstBGsetLink,         // 10 04\n    InstCHAload,           // 10 05\n    InstCHAswap,           // 10 06\n    InstBGcopy,            // 10 07\n    InstCHAcopy,           // 10 08\n    InstSaveSlot,          // 10 09\n    InstSystemMain,        // 10 0A\n    InstCharaLayerLoad,    // 10 0B\n    InstGameInfoInit,      // 10 0C\n    InstCHAmove,           // 10 0D\n    InstBGloadEx,          // 10 0E\n    InstDummy,             // 10 0F\n    InstBGrelease,         // 10 10\n    InstCHArelease,        // 10 11\n    InstClearFlagChkOld,   // 10 12\n    InstOption,            // 10 13\n    InstSystemDataReset,   // 10 14\n    InstDebugData,         // 10 15\n    InstGetCharaPause,     // 10 16\n    InstBGfadeExpInit,     // 10 17\n    InstDummy,             // 10 18\n    InstDummy,             // 10 19\n    InstDummy,             // 10 1A\n    InstHelp,              // 10 1B\n    InstAchievementMenu,   // 10 1C\n    InstSoundMenu,         // 10 1D\n    InstAllClear,          // 10 1E\n    InstAlbum,             // 10 1F\n    InstMovieMode,         // 10 20\n    InstClistInit,         // 10 21\n    InstAutoSaveOld,       // 10 22\n    InstSaveMenuOld,       // 10 23\n    InstLoadDataOld,       // 10 24\n    InstDummy,             // 10 25\n    InstDummy,             // 10 26\n    InstSetDic,            // 10 27\n    InstSetPlayMode,       // 10 28\n    InstSetEVflag,         // 10 29\n    InstSetCutin,          // 10 2A\n    InstDummy,             // 10 2B\n    InstDummy,             // 10 2C\n    InstDelusionTriggerCHLCC,// 10 2D\n    InstSetSceneViewFlag,  // 10 2E\n    InstChkClearFlag,      // 10 2F\n    InstBGeffectWave,      // 10 30\n    InstGeotag,            // 10 31\n    InstNameID,            // 10 32\n    InstTips,              // 10 33\n    InstTitleMenu,         // 10 34\n    InstDummy,             // 10 35\n    InstBGeffectMO7,       // 10 36\n    InstUnk1037MO7,        // 10 37\n    InstUnk1038MO7,        // 10 38\n    InstTwipo,             // 10 39\n    InstUnk103A,           // 10 3A\n    InstDummy,             // 10 3B\n    InstDummy,             // 10 3C\n    InstDummy,             // 10 3D\n    InstDummy,             // 10 3E\n    InstDummy,             // 10 3F\n    InstDummy,             // 10 40\n    InstDummy,             // 10 41\n    InstDummy,             // 10 42\n    InstDummy,             // 10 43\n    InstDummy,             // 10 44\n    InstDummy,             // 10 45\n    InstDummy,             // 10 46\n    InstDummy,             // 10 47\n    InstDummy,             // 10 48\n    InstDummy,             // 10 49\n    InstDummy,             // 10 4A\n    InstDummy,             // 10 4B\n    InstDummy,             // 10 4C\n    InstDummy,             // 10 4D\n    InstDummy,             // 10 4E\n    InstDummy,             // 10 4F\n    InstDummy,             // 10 50\n    InstDummy,             // 10 51\n    InstDummy,             // 10 52\n    InstDummy,             // 10 53\n    InstDummy,             // 10 54\n    InstDummy,             // 10 55\n    InstDummy,             // 10 56\n    InstDummy,             // 10 57\n    InstDummy,             // 10 58\n    InstDummy,             // 10 59\n    InstDummy,             // 10 5A\n    InstDummy,             // 10 5B\n    InstDummy,             // 10 5C\n    InstDummy,             // 10 5D\n    InstDummy,             // 10 5E\n    InstDummy,             // 10 5F\n    InstDummy,             // 10 60\n    InstDummy,             // 10 61\n    InstDummy,             // 10 62\n    InstDummy,             // 10 63\n    InstDummy,             // 10 64\n    InstDummy,             // 10 65\n    InstDummy,             // 10 66\n    InstDummy,             // 10 67\n    InstDummy,             // 10 68\n    InstDummy,             // 10 69\n    InstDummy,             // 10 6A\n    InstDummy,             // 10 6B\n    InstDummy,             // 10 6C\n    InstDummy,             // 10 6D\n    InstDummy,             // 10 6E\n    InstDummy,             // 10 6F\n    InstDummy,             // 10 70\n    InstDummy,             // 10 71\n    InstDummy,             // 10 72\n    InstDummy,             // 10 73\n    InstDummy,             // 10 74\n    InstDummy,             // 10 75\n    InstDummy,             // 10 76\n    InstDummy,             // 10 77\n    InstDummy,             // 10 78\n    InstDummy,             // 10 79\n    InstDummy,             // 10 7A\n    InstDummy,             // 10 7B\n    InstDummy,             // 10 7C\n    InstDummy,             // 10 7D\n    InstDummy,             // 10 7E\n    InstDummy,             // 10 7F\n    InstDummy,             // 10 80\n    InstDummy,             // 10 81\n    InstDummy,             // 10 82\n    InstDummy,             // 10 83\n    InstDummy,             // 10 84\n    InstDummy,             // 10 85\n    InstDummy,             // 10 86\n    InstDummy,             // 10 87\n    InstDummy,             // 10 88\n    InstDummy,             // 10 89\n    InstDummy,             // 10 8A\n    InstDummy,             // 10 8B\n    InstDummy,             // 10 8C\n    InstDummy,             // 10 8D\n    InstDummy,             // 10 8E\n    InstDummy,             // 10 8F\n    InstDummy,             // 10 90\n    InstDummy,             // 10 91\n    InstDummy,             // 10 92\n    InstDummy,             // 10 93\n    InstDummy,             // 10 94\n    InstDummy,             // 10 95\n    InstDummy,             // 10 96\n    InstDummy,             // 10 97\n    InstDummy,             // 10 98\n    InstDummy,             // 10 99\n    InstDummy,             // 10 9A\n    InstDummy,             // 10 9B\n    InstDummy,             // 10 9C\n    InstDummy,             // 10 9D\n    InstDummy,             // 10 9E\n    InstDummy,             // 10 9F\n    InstDummy,             // 10 A0\n    InstDummy,             // 10 A1\n    InstDummy,             // 10 A2\n    InstDummy,             // 10 A3\n    InstDummy,             // 10 A4\n    InstDummy,             // 10 A5\n    InstDummy,             // 10 A6\n    InstDummy,             // 10 A7\n    InstDummy,             // 10 A8\n    InstDummy,             // 10 A9\n    InstDummy,             // 10 AA\n    InstDummy,             // 10 AB\n    InstDummy,             // 10 AC\n    InstDummy,             // 10 AD\n    InstDummy,             // 10 AE\n    InstDummy,             // 10 AF\n    InstDummy,             // 10 B0\n    InstDummy,             // 10 B1\n    InstDummy,             // 10 B2\n    InstDummy,             // 10 B3\n    InstDummy,             // 10 B4\n    InstDummy,             // 10 B5\n    InstDummy,             // 10 B6\n    InstDummy,             // 10 B7\n    InstDummy,             // 10 B8\n    InstDummy,             // 10 B9\n    InstDummy,             // 10 BA\n    InstDummy,             // 10 BB\n    InstDummy,             // 10 BC\n    InstDummy,             // 10 BD\n    InstDummy,             // 10 BE\n    InstDummy,             // 10 BF\n    InstDummy,             // 10 C0\n    InstDummy,             // 10 C1\n    InstDummy,             // 10 C2\n    InstDummy,             // 10 C3\n    InstDummy,             // 10 C4\n    InstDummy,             // 10 C5\n    InstDummy,             // 10 C6\n    InstDummy,             // 10 C7\n    InstDummy,             // 10 C8\n    InstDummy,             // 10 C9\n    InstDummy,             // 10 CA\n    InstDummy,             // 10 CB\n    InstDummy,             // 10 CC\n    InstDummy,             // 10 CD\n    InstDummy,             // 10 CE\n    InstDummy,             // 10 CF\n    InstDummy,             // 10 D0\n    InstDummy,             // 10 D1\n    InstDummy,             // 10 D2\n    InstDummy,             // 10 D3\n    InstDummy,             // 10 D4\n    InstDummy,             // 10 D5\n    InstDummy,             // 10 D6\n    InstDummy,             // 10 D7\n    InstDummy,             // 10 D8\n    InstDummy,             // 10 D9\n    InstDummy,             // 10 DA\n    InstDummy,             // 10 DB\n    InstDummy,             // 10 DC\n    InstDummy,             // 10 DD\n    InstDummy,             // 10 DE\n    InstDummy,             // 10 DF\n    InstDummy,             // 10 E0\n    InstDummy,             // 10 E1\n    InstDummy,             // 10 E2\n    InstDummy,             // 10 E3\n    InstDummy,             // 10 E4\n    InstDummy,             // 10 E5\n    InstDummy,             // 10 E6\n    InstDummy,             // 10 E7\n    InstDummy,             // 10 E8\n    InstDummy,             // 10 E9\n    InstDummy,             // 10 EA\n    InstDummy,             // 10 EB\n    InstDummy,             // 10 EC\n    InstDummy,             // 10 ED\n    InstDummy,             // 10 EE\n    InstDummy,             // 10 EF\n    InstDummy,             // 10 F0\n    InstDummy,             // 10 F1\n    InstDummy,             // 10 F2\n    InstDummy,             // 10 F3\n    InstDummy,             // 10 F4\n    InstDummy,             // 10 F5\n    InstDummy,             // 10 F6\n    InstDummy,             // 10 F7\n    InstDummy,             // 10 F8\n    InstDummy,             // 10 F9\n    InstDummy,             // 10 FA\n    InstDummy,             // 10 FB\n    InstDummy,             // 10 FC\n    InstDummy,             // 10 FD\n    InstDummy,             // 10 FE\n    InstDummy              // 10 FF\n};\n\n}\n\n}\n\n// clang-format on"
  },
  {
    "path": "src/vm/opcodetables_mo8.h",
    "content": "#include \"inst_system.h\"\n#include \"inst_controlflow.h\"\n#include \"inst_graphics2d.h\"\n#include \"inst_graphics3d.h\"\n#include \"inst_dialogue.h\"\n#include \"inst_sound.h\"\n#include \"inst_movie.h\"\n#include \"inst_gamespecific.h\"\n#include \"inst_misc.h\"\n\n// clang-format off\n\nnamespace Impacto {\n\nnamespace Vm {\n\nInstructionProc inline constexpr OpcodeTableSystem_MO8[256] = {\n    InstEnd,                  // 00 00\n    InstCreateThread,         // 00 01\n    InstKillThread,           // 00 02\n    InstReset,                // 00 03\n    InstScriptLoad,           // 00 04\n    InstWait,                 // 00 05\n    InstHalt,                 // 00 06\n    InstJump,                 // 00 07\n    InstJumpTable,            // 00 08\n    InstGetLabelAdr,          // 00 09\n    InstIf,                   // 00 0A\n    InstCall,                 // 00 0B\n    InstJumpFar,              // 00 0C\n    InstCallFar,              // 00 0D\n    InstReturn,               // 00 0E\n    InstLoop,                 // 00 0F\n    InstFlagOnJump,           // 00 10\n    InstFlagOnWait,           // 00 11\n    InstSetFlag,              // 00 12\n    InstResetFlag,            // 00 13\n    InstCopyFlag,             // 00 14\n    InstKeyOnJump,            // 00 15\n    InstKeyWait,              // 00 16\n    InstKeyWaitTimer,         // 00 17\n    InstMemberWrite,          // 00 18\n    InstThreadControl,        // 00 19\n    InstGetSelfPointer,       // 00 1A\n    InstLoadJump,             // 00 1B\n    InstVsync,                // 00 1C\n    InstTest,                 // 00 1D\n    InstThreadControlStore,   // 00 1E\n    InstSwitch,               // 00 1F\n    InstCase,                 // 00 20\n    InstBGMplay,              // 00 21\n    InstBGMstop,              // 00 22\n    InstSEplay,               // 00 23\n    InstSEstop,               // 00 24\n    InstPadAct,               // 00 25\n    InstSSEplay,              // 00 26\n    InstSSEstop,              // 00 27\n    InstCopyThreadWork,       // 00 28\n    InstUPLmenuUI,            // 00 29\n    InstSave,                 // 00 2A\n    InstSaveIconLoad,         // 00 2B\n    InstBGMflag,              // 00 2C\n    InstUPLxTitle,            // 00 2D\n    InstPresence,             // 00 2E\n    InstSetAchievement,          // 00 2F\n    InstSetPlayer,            // 00 30\n    InstVoiceTableLoadMaybe,  // 00 31\n    InstSetPadCustom,         // 00 32\n    InstMwait,                // 00 33\n    InstTerminate,            // 00 34\n    InstSignIn,               // 00 35\n    InstAchievementIcon,      // 00 36\n    InstVoicePlay,            // 00 37\n    InstVoiceStopNew,         // 00 38\n    InstVoicePlayWait,        // 00 39\n    InstBGMduelPlay,          // 00 3A\n    InstSNDpause,             // 00 3B\n    InstSEplayWait,           // 00 3C\n    InstDebugPrint,           // 00 3D\n    InstResetSoundAll,        // 00 3E\n    InstSNDloadStop,          // 00 3F\n    InstBGMstopWait,          // 00 40\n    InstUnk0041,              // 00 41\n    InstSetX360SysMesPos,     // 00 42\n    InstSystemMes,            // 00 43\n    InstSystemMenu,           // 00 44\n    InstGetNowTime,           // 00 45\n    InstGetSystemStatus,      // 00 46\n    InstReboot,               // 00 47\n    InstReloadScript,         // 00 48\n    InstReloadScriptMenu,     // 00 49\n    InstDebugEditer,          // 00 4A\n    InstSysSeload,            // 00 4B\n    InstDummy,                // 00 4C\n    InstSysVoicePlay,         // 00 4D\n    InstPadActEx,             // 00 4E\n    InstDebugSetup,           // 00 4F\n    InstPressStartNew,        // 00 50\n    InstGlobalSystemMessage,  // 00 51\n    InstUnk0052,              // 00 52\n    InstControlOnJump,        // 00 53\n    InstKeyboardOnJump,       // 00 54\n    InstReturnIfFlag,         // 00 55\n    InstDummy,                // 00 56\n    InstDummy,                // 00 57\n    InstDummy,                // 00 58\n    InstDummy,                // 00 59\n    InstDummy,                // 00 5A\n    InstDummy,                // 00 5B\n    InstDummy,                // 00 5C\n    InstDummy,                // 00 5D\n    InstDummy,                // 00 5E\n    InstAddContents,          // 00 5F\n    InstDummy,                // 00 60\n    InstDummy,                // 00 61\n    InstDummy,                // 00 62\n    InstDummy,                // 00 63\n    InstDummy,                // 00 64\n    InstDummy,                // 00 65\n    InstDummy,                // 00 66\n    InstDummy,                // 00 67\n    InstDummy,                // 00 68\n    InstDummy,                // 00 69\n    InstDummy,                // 00 6A\n    InstDummy,                // 00 6B\n    InstDummy,                // 00 6C\n    InstDummy,                // 00 6D\n    InstDummy,                // 00 6E\n    InstDummy,                // 00 6F\n    InstDummy,                // 00 70\n    InstDummy,                // 00 71\n    InstDummy,                // 00 72\n    InstDummy,                // 00 73\n    InstDummy,                // 00 74\n    InstDummy,                // 00 75\n    InstDummy,                // 00 76\n    InstDummy,                // 00 77\n    InstDummy,                // 00 78\n    InstDummy,                // 00 79\n    InstDummy,                // 00 7A\n    InstDummy,                // 00 7B\n    InstDummy,                // 00 7C\n    InstDummy,                // 00 7D\n    InstDummy,                // 00 7E\n    InstDummy,                // 00 7F\n    InstDummy,                // 00 80\n    InstDummy,                // 00 81\n    InstDummy,                // 00 82\n    InstDummy,                // 00 83\n    InstDummy,                // 00 84\n    InstDummy,                // 00 85\n    InstDummy,                // 00 86\n    InstDummy,                // 00 87\n    InstDummy,                // 00 88\n    InstDummy,                // 00 89\n    InstDummy,                // 00 8A\n    InstDummy,                // 00 8B\n    InstDummy,                // 00 8C\n    InstDummy,                // 00 8D\n    InstDummy,                // 00 8E\n    InstDummy,                // 00 8F\n    InstDummy,                // 00 90\n    InstDummy,                // 00 91\n    InstDummy,                // 00 92\n    InstDummy,                // 00 93\n    InstDummy,                // 00 94\n    InstDummy,                // 00 95\n    InstDummy,                // 00 96\n    InstDummy,                // 00 97\n    InstDummy,                // 00 98\n    InstDummy,                // 00 99\n    InstDummy,                // 00 9A\n    InstDummy,                // 00 9B\n    InstDummy,                // 00 9C\n    InstDummy,                // 00 9D\n    InstDummy,                // 00 9E\n    InstDummy,                // 00 9F\n    InstDummy,                // 00 A0\n    InstDummy,                // 00 A1\n    InstDummy,                // 00 A2\n    InstDummy,                // 00 A3\n    InstDummy,                // 00 A4\n    InstDummy,                // 00 A5\n    InstDummy,                // 00 A6\n    InstDummy,                // 00 A7\n    InstDummy,                // 00 A8\n    InstDummy,                // 00 A9\n    InstDummy,                // 00 AA\n    InstDummy,                // 00 AB\n    InstDummy,                // 00 AC\n    InstDummy,                // 00 AD\n    InstDummy,                // 00 AE\n    InstDummy,                // 00 AF\n    InstDummy,                // 00 B0\n    InstDummy,                // 00 B1\n    InstDummy,                // 00 B2\n    InstDummy,                // 00 B3\n    InstDummy,                // 00 B4\n    InstDummy,                // 00 B5\n    InstDummy,                // 00 B6\n    InstDummy,                // 00 B7\n    InstDummy,                // 00 B8\n    InstDummy,                // 00 B9\n    InstDummy,                // 00 BA\n    InstDummy,                // 00 BB\n    InstDummy,                // 00 BC\n    InstDummy,                // 00 BD\n    InstDummy,                // 00 BE\n    InstDummy,                // 00 BF\n    InstDummy,                // 00 C0\n    InstDummy,                // 00 C1\n    InstDummy,                // 00 C2\n    InstDummy,                // 00 C3\n    InstDummy,                // 00 C4\n    InstDummy,                // 00 C5\n    InstDummy,                // 00 C6\n    InstDummy,                // 00 C7\n    InstDummy,                // 00 C8\n    InstDummy,                // 00 C9\n    InstDummy,                // 00 CA\n    InstDummy,                // 00 CB\n    InstDummy,                // 00 CC\n    InstDummy,                // 00 CD\n    InstDummy,                // 00 CE\n    InstDummy,                // 00 CF\n    InstDummy,                // 00 D0\n    InstDummy,                // 00 D1\n    InstDummy,                // 00 D2\n    InstDummy,                // 00 D3\n    InstDummy,                // 00 D4\n    InstDummy,                // 00 D5\n    InstDummy,                // 00 D6\n    InstDummy,                // 00 D7\n    InstDummy,                // 00 D8\n    InstDummy,                // 00 D9\n    InstDummy,                // 00 DA\n    InstDummy,                // 00 DB\n    InstDummy,                // 00 DC\n    InstDummy,                // 00 DD\n    InstDummy,                // 00 DE\n    InstDummy,                // 00 DF\n    InstDummy,                // 00 E0\n    InstDummy,                // 00 E1\n    InstDummy,                // 00 E2\n    InstDummy,                // 00 E3\n    InstDummy,                // 00 E4\n    InstDummy,                // 00 E5\n    InstDummy,                // 00 E6\n    InstDummy,                // 00 E7\n    InstDummy,                // 00 E8\n    InstDummy,                // 00 E9\n    InstDummy,                // 00 EA\n    InstDummy,                // 00 EB\n    InstDummy,                // 00 EC\n    InstDummy,                // 00 ED\n    InstDummy,                // 00 EE\n    InstDummy,                // 00 EF\n    InstDummy,                // 00 F0\n    InstDummy,                // 00 F1\n    InstDummy,                // 00 F2\n    InstDummy,                // 00 F3\n    InstDummy,                // 00 F4\n    InstDummy,                // 00 F5\n    InstDummy,                // 00 F6\n    InstDummy,                // 00 F7\n    InstDummy,                // 00 F8\n    InstDummy,                // 00 F9\n    InstDummy,                // 00 FA\n    InstDummy,                // 00 FB\n    InstDummy,                // 00 FC\n    InstDummy,                // 00 FD\n    InstDummy,                // 00 FE\n    InstDummy                 // 00 FF\n};\n\nInstructionProc inline constexpr OpcodeTableGraph_MO8[256] = {\n    InstCreateSurf,        // 01 00\n    InstReleaseSurf,       // 01 01\n    InstLoadPic,           // 01 02\n    InstReleaseSurf,       // 01 03\n    InstSurfFill,          // 01 04\n    InstCalc,              // 01 05\n    InstMesViewFlag,       // 01 06\n    InstSetMesWinPri,      // 01 07\n    InstMesSync,           // 01 08\n    InstMesSetID,          // 01 09\n    InstMesCls,            // 01 0A\n    InstMesVoiceWait,      // 01 0B\n    InstMes,               // 01 0C\n    InstMesMain,           // 01 0D\n    InstSetMesModeFormat,  // 01 0E\n    InstSetNGmoji,         // 01 0F\n    InstMesRev,            // 01 10\n    InstMessWindow,        // 01 11\n    InstSel,               // 01 12\n    InstSelect,            // 01 13\n    InstSysSel,            // 01 14\n    InstSysSelect,         // 01 15\n    InstDummy,             // 01 16\n    InstDummy,             // 01 17\n    InstDummy,             // 01 18\n    InstDummy,             // 01 19\n    InstDummy,             // 01 1A\n    InstDummy,             // 01 1B\n    InstDummy,             // 01 1C\n    InstDummy,             // 01 1D\n    InstDummy,             // 01 1E\n    InstLoadFontWidths,    // 01 1F\n    InstSCcapture,         // 01 20\n    InstSetTextTable,      // 01 21\n    InstPlayMovie,         // 01 22\n    InstMovieMain,         // 01 23\n    InstLoadMovie,         // 01 24\n    InstSetRevMes,         // 01 25\n    InstPlayMovieMemory,   // 01 26\n    InstMovie,             // 01 27\n    InstMovieMain,         // 01 28\n    InstLoadMovie,         // 01 29\n    InstPlayMovieMemory,   // 01 2A\n    InstSFDpause,          // 01 2B\n    InstRINNS,             // 01 2C\n    InstRINNSMain,         // 01 2D\n    InstDummy,             // 01 2E\n    InstChatMO8,           // 01 2F\n    InstDummy,             // 01 30\n    InstDummy,             // 01 31\n    InstDummy,             // 01 32\n    InstDummy,             // 01 33\n    InstDummy,             // 01 34\n    InstDummy,             // 01 35\n    InstDummy,             // 01 36\n    InstDummy,             // 01 37\n    InstDummy,             // 01 38\n    InstDummy,             // 01 39\n    InstDummy,             // 01 3A\n    InstDummy,             // 01 3B\n    InstDummy,             // 01 3C\n    InstDummy,             // 01 3D\n    InstDummy,             // 01 3E\n    InstDummy,             // 01 3F\n    InstDummy,             // 01 40\n    InstDummy,             // 01 41\n    InstDummy,             // 01 42\n    InstDummy,             // 01 43\n    InstDummy,             // 01 44\n    InstDummy,             // 01 45\n    InstDummy,             // 01 46\n    InstDummy,             // 01 47\n    InstDummy,             // 01 48\n    InstDummy,             // 01 49\n    InstDummy,             // 01 4A\n    InstDummy,             // 01 4B\n    InstDummy,             // 01 4C\n    InstDummy,             // 01 4D\n    InstDummy,             // 01 4E\n    InstDummy,             // 01 4F\n    InstDummy,             // 01 50\n    InstDummy,             // 01 51\n    InstDummy,             // 01 52\n    InstDummy,             // 01 53\n    InstDummy,             // 01 54\n    InstDummy,             // 01 55\n    InstDummy,             // 01 56\n    InstDummy,             // 01 57\n    InstDummy,             // 01 58\n    InstDummy,             // 01 59\n    InstDummy,             // 01 5A\n    InstDummy,             // 01 5B\n    InstDummy,             // 01 5C\n    InstDummy,             // 01 5D\n    InstDummy,             // 01 5E\n    InstDummy,             // 01 5F\n    InstDummy,             // 01 60\n    InstDummy,             // 01 61\n    InstDummy,             // 01 62\n    InstDummy,             // 01 63\n    InstDummy,             // 01 64\n    InstDummy,             // 01 65\n    InstDummy,             // 01 66\n    InstDummy,             // 01 67\n    InstDummy,             // 01 68\n    InstDummy,             // 01 69\n    InstDummy,             // 01 6A\n    InstDummy,             // 01 6B\n    InstDummy,             // 01 6C\n    InstDummy,             // 01 6D\n    InstDummy,             // 01 6E\n    InstDummy,             // 01 6F\n    InstDummy,             // 01 70\n    InstDummy,             // 01 71\n    InstDummy,             // 01 72\n    InstDummy,             // 01 73\n    InstDummy,             // 01 74\n    InstDummy,             // 01 75\n    InstDummy,             // 01 76\n    InstDummy,             // 01 77\n    InstDummy,             // 01 78\n    InstDummy,             // 01 79\n    InstDummy,             // 01 7A\n    InstDummy,             // 01 7B\n    InstDummy,             // 01 7C\n    InstDummy,             // 01 7D\n    InstDummy,             // 01 7E\n    InstDummy,             // 01 7F\n    InstDummy,             // 01 80\n    InstDummy,             // 01 81\n    InstDummy,             // 01 82\n    InstDummy,             // 01 83\n    InstDummy,             // 01 84\n    InstDummy,             // 01 85\n    InstDummy,             // 01 86\n    InstDummy,             // 01 87\n    InstDummy,             // 01 88\n    InstDummy,             // 01 89\n    InstDummy,             // 01 8A\n    InstDummy,             // 01 8B\n    InstDummy,             // 01 8C\n    InstDummy,             // 01 8D\n    InstDummy,             // 01 8E\n    InstDummy,             // 01 8F\n    InstDummy,             // 01 90\n    InstDummy,             // 01 91\n    InstDummy,             // 01 92\n    InstDummy,             // 01 93\n    InstDummy,             // 01 94\n    InstDummy,             // 01 95\n    InstDummy,             // 01 96\n    InstDummy,             // 01 97\n    InstDummy,             // 01 98\n    InstDummy,             // 01 99\n    InstDummy,             // 01 9A\n    InstDummy,             // 01 9B\n    InstDummy,             // 01 9C\n    InstDummy,             // 01 9D\n    InstDummy,             // 01 9E\n    InstDummy,             // 01 9F\n    InstDummy,             // 01 A0\n    InstDummy,             // 01 A1\n    InstDummy,             // 01 A2\n    InstDummy,             // 01 A3\n    InstDummy,             // 01 A4\n    InstDummy,             // 01 A5\n    InstDummy,             // 01 A6\n    InstDummy,             // 01 A7\n    InstDummy,             // 01 A8\n    InstDummy,             // 01 A9\n    InstDummy,             // 01 AA\n    InstDummy,             // 01 AB\n    InstDummy,             // 01 AC\n    InstDummy,             // 01 AD\n    InstDummy,             // 01 AE\n    InstDummy,             // 01 AF\n    InstDummy,             // 01 B0\n    InstDummy,             // 01 B1\n    InstDummy,             // 01 B2\n    InstDummy,             // 01 B3\n    InstDummy,             // 01 B4\n    InstDummy,             // 01 B5\n    InstDummy,             // 01 B6\n    InstDummy,             // 01 B7\n    InstDummy,             // 01 B8\n    InstDummy,             // 01 B9\n    InstDummy,             // 01 BA\n    InstDummy,             // 01 BB\n    InstDummy,             // 01 BC\n    InstDummy,             // 01 BD\n    InstDummy,             // 01 BE\n    InstDummy,             // 01 BF\n    InstDummy,             // 01 C0\n    InstDummy,             // 01 C1\n    InstDummy,             // 01 C2\n    InstDummy,             // 01 C3\n    InstDummy,             // 01 C4\n    InstDummy,             // 01 C5\n    InstDummy,             // 01 C6\n    InstDummy,             // 01 C7\n    InstDummy,             // 01 C8\n    InstDummy,             // 01 C9\n    InstDummy,             // 01 CA\n    InstDummy,             // 01 CB\n    InstDummy,             // 01 CC\n    InstDummy,             // 01 CD\n    InstDummy,             // 01 CE\n    InstDummy,             // 01 CF\n    InstDummy,             // 01 D0\n    InstDummy,             // 01 D1\n    InstDummy,             // 01 D2\n    InstDummy,             // 01 D3\n    InstDummy,             // 01 D4\n    InstDummy,             // 01 D5\n    InstDummy,             // 01 D6\n    InstDummy,             // 01 D7\n    InstDummy,             // 01 D8\n    InstDummy,             // 01 D9\n    InstDummy,             // 01 DA\n    InstDummy,             // 01 DB\n    InstDummy,             // 01 DC\n    InstDummy,             // 01 DD\n    InstDummy,             // 01 DE\n    InstDummy,             // 01 DF\n    InstDummy,             // 01 E0\n    InstDummy,             // 01 E1\n    InstDummy,             // 01 E2\n    InstDummy,             // 01 E3\n    InstDummy,             // 01 E4\n    InstDummy,             // 01 E5\n    InstDummy,             // 01 E6\n    InstDummy,             // 01 E7\n    InstDummy,             // 01 E8\n    InstDummy,             // 01 E9\n    InstDummy,             // 01 EA\n    InstDummy,             // 01 EB\n    InstDummy,             // 01 EC\n    InstDummy,             // 01 ED\n    InstDummy,             // 01 EE\n    InstDummy,             // 01 EF\n    InstDummy,             // 01 F0\n    InstDummy,             // 01 F1\n    InstDummy,             // 01 F2\n    InstDummy,             // 01 F3\n    InstDummy,             // 01 F4\n    InstDummy,             // 01 F5\n    InstDummy,             // 01 F6\n    InstDummy,             // 01 F7\n    InstDummy,             // 01 F8\n    InstDummy,             // 01 F9\n    InstDummy,             // 01 FA\n    InstDummy,             // 01 FB\n    InstDummy,             // 01 FC\n    InstDummy,             // 01 FD\n    InstDummy,             // 01 FE\n    InstDummy              // 01 FF\n};\n\nInstructionProc inline constexpr OpcodeTableUser1_MO8[256] = {\n    InstMSinit,            // 10 00\n    InstBGload,            // 10 01\n    InstBGswap,            // 10 02\n    InstBGsetColor,        // 10 03\n    InstBGsetLink,         // 10 04\n    InstCHAload,           // 10 05\n    InstCHAswap,           // 10 06\n    InstBGcopy,            // 10 07\n    InstCHAcopy,           // 10 08\n    InstSaveSlot,          // 10 09\n    InstSystemMain,        // 10 0A\n    InstCharaLayerLoad,    // 10 0B\n    InstGameInfoInit,      // 10 0C\n    InstCHAmove,           // 10 0D\n    InstBGloadEx,          // 10 0E\n    InstDummy,             // 10 0F\n    InstBGrelease,         // 10 10\n    InstCHArelease,        // 10 11\n    InstClearFlagChk,      // 10 12\n    InstOption,            // 10 13\n    InstSystemDataReset,   // 10 14\n    InstDebugData,         // 10 15\n    InstGetCharaPause,     // 10 16\n    InstBGfadeExpInit,     // 10 17\n    InstDummy,             // 10 18\n    InstDummy,             // 10 19\n    InstDummy,             // 10 1A\n    InstHelp,              // 10 1B\n    InstAchievementMenu,   // 10 1C\n    InstSoundMenu,         // 10 1D\n    InstAllClear,          // 10 1E\n    InstAlbum,             // 10 1F\n    InstMovieMode,         // 10 20\n    InstClistInit,         // 10 21\n    InstAutoSave,          // 10 22\n    InstSaveMenu,          // 10 23\n    InstLoadData,          // 10 24\n    InstDummy,             // 10 25\n    InstDummy,             // 10 26\n    InstSetDic,            // 10 27\n    InstSetPlayMode,       // 10 28\n    InstSetEVflag,         // 10 29\n    InstSetCutin,          // 10 2A\n    InstDummy,             // 10 2B\n    InstDummy,             // 10 2C\n    InstAchChkTitle,       // 10 2D\n    InstSetSceneViewFlag,  // 10 2E\n    InstChkClearFlag,      // 10 2F\n    InstBGeffectWave,      // 10 30\n    InstGeotag,            // 10 31\n    InstNameID,            // 10 32\n    InstTips,              // 10 33\n    InstTitleMenuNew,      // 10 34\n    InstDummy,             // 10 35\n    InstBGeffect,          // 10 36\n    InstMapSystem,         // 10 37\n    InstMail,              // 10 38\n    InstTwipo_Dash,        // 10 39\n    InstUnk103A,           // 10 3A\n    InstDummy,             // 10 3B\n    InstFACEload,          // 10 3C\n    InstFACErelease,       // 10 3D\n    InstDummy,             // 10 3E\n    InstSysSeload,         // 10 3F\n    InstDummy,             // 10 40\n    InstDummy,             // 10 41\n    InstDummy,             // 10 42\n    InstDummy,             // 10 43\n    InstDummy,             // 10 44\n    InstDummy,             // 10 45\n    InstDummy,             // 10 46\n    InstDummy,             // 10 47\n    InstDummy,             // 10 48\n    InstDummy,             // 10 49\n    InstDummy,             // 10 4A\n    InstDummy,             // 10 4B\n    InstDummy,             // 10 4C\n    InstDummy,             // 10 4D\n    InstDummy,             // 10 4E\n    InstDummy,             // 10 4F\n    InstDummy,             // 10 50\n    InstDummy,             // 10 51\n    InstDummy,             // 10 52\n    InstDummy,             // 10 53\n    InstDummy,             // 10 54\n    InstDummy,             // 10 55\n    InstDummy,             // 10 56\n    InstDummy,             // 10 57\n    InstDummy,             // 10 58\n    InstDummy,             // 10 59\n    InstDummy,             // 10 5A\n    InstDummy,             // 10 5B\n    InstDummy,             // 10 5C\n    InstDummy,             // 10 5D\n    InstDummy,             // 10 5E\n    InstDummy,             // 10 5F\n    InstDummy,             // 10 60\n    InstDummy,             // 10 61\n    InstDummy,             // 10 62\n    InstDummy,             // 10 63\n    InstDummy,             // 10 64\n    InstDummy,             // 10 65\n    InstDummy,             // 10 66\n    InstDummy,             // 10 67\n    InstDummy,             // 10 68\n    InstDummy,             // 10 69\n    InstDummy,             // 10 6A\n    InstDummy,             // 10 6B\n    InstDummy,             // 10 6C\n    InstDummy,             // 10 6D\n    InstDummy,             // 10 6E\n    InstDummy,             // 10 6F\n    InstDummy,             // 10 70\n    InstDummy,             // 10 71\n    InstDummy,             // 10 72\n    InstDummy,             // 10 73\n    InstDummy,             // 10 74\n    InstDummy,             // 10 75\n    InstDummy,             // 10 76\n    InstDummy,             // 10 77\n    InstDummy,             // 10 78\n    InstDummy,             // 10 79\n    InstDummy,             // 10 7A\n    InstDummy,             // 10 7B\n    InstDummy,             // 10 7C\n    InstDummy,             // 10 7D\n    InstDummy,             // 10 7E\n    InstDummy,             // 10 7F\n    InstDummy,             // 10 80\n    InstDummy,             // 10 81\n    InstDummy,             // 10 82\n    InstDummy,             // 10 83\n    InstDummy,             // 10 84\n    InstDummy,             // 10 85\n    InstDummy,             // 10 86\n    InstDummy,             // 10 87\n    InstDummy,             // 10 88\n    InstDummy,             // 10 89\n    InstDummy,             // 10 8A\n    InstDummy,             // 10 8B\n    InstDummy,             // 10 8C\n    InstDummy,             // 10 8D\n    InstDummy,             // 10 8E\n    InstDummy,             // 10 8F\n    InstDummy,             // 10 90\n    InstDummy,             // 10 91\n    InstDummy,             // 10 92\n    InstDummy,             // 10 93\n    InstDummy,             // 10 94\n    InstDummy,             // 10 95\n    InstDummy,             // 10 96\n    InstDummy,             // 10 97\n    InstDummy,             // 10 98\n    InstDummy,             // 10 99\n    InstDummy,             // 10 9A\n    InstDummy,             // 10 9B\n    InstDummy,             // 10 9C\n    InstDummy,             // 10 9D\n    InstDummy,             // 10 9E\n    InstDummy,             // 10 9F\n    InstDummy,             // 10 A0\n    InstDummy,             // 10 A1\n    InstDummy,             // 10 A2\n    InstDummy,             // 10 A3\n    InstDummy,             // 10 A4\n    InstDummy,             // 10 A5\n    InstDummy,             // 10 A6\n    InstDummy,             // 10 A7\n    InstDummy,             // 10 A8\n    InstDummy,             // 10 A9\n    InstDummy,             // 10 AA\n    InstDummy,             // 10 AB\n    InstDummy,             // 10 AC\n    InstDummy,             // 10 AD\n    InstDummy,             // 10 AE\n    InstDummy,             // 10 AF\n    InstDummy,             // 10 B0\n    InstDummy,             // 10 B1\n    InstDummy,             // 10 B2\n    InstDummy,             // 10 B3\n    InstDummy,             // 10 B4\n    InstDummy,             // 10 B5\n    InstDummy,             // 10 B6\n    InstDummy,             // 10 B7\n    InstDummy,             // 10 B8\n    InstDummy,             // 10 B9\n    InstDummy,             // 10 BA\n    InstDummy,             // 10 BB\n    InstDummy,             // 10 BC\n    InstDummy,             // 10 BD\n    InstDummy,             // 10 BE\n    InstDummy,             // 10 BF\n    InstDummy,             // 10 C0\n    InstDummy,             // 10 C1\n    InstDummy,             // 10 C2\n    InstDummy,             // 10 C3\n    InstDummy,             // 10 C4\n    InstDummy,             // 10 C5\n    InstDummy,             // 10 C6\n    InstDummy,             // 10 C7\n    InstDummy,             // 10 C8\n    InstDummy,             // 10 C9\n    InstDummy,             // 10 CA\n    InstDummy,             // 10 CB\n    InstDummy,             // 10 CC\n    InstDummy,             // 10 CD\n    InstDummy,             // 10 CE\n    InstDummy,             // 10 CF\n    InstDummy,             // 10 D0\n    InstDummy,             // 10 D1\n    InstDummy,             // 10 D2\n    InstDummy,             // 10 D3\n    InstDummy,             // 10 D4\n    InstDummy,             // 10 D5\n    InstDummy,             // 10 D6\n    InstDummy,             // 10 D7\n    InstDummy,             // 10 D8\n    InstDummy,             // 10 D9\n    InstDummy,             // 10 DA\n    InstDummy,             // 10 DB\n    InstDummy,             // 10 DC\n    InstDummy,             // 10 DD\n    InstDummy,             // 10 DE\n    InstDummy,             // 10 DF\n    InstDummy,             // 10 E0\n    InstDummy,             // 10 E1\n    InstDummy,             // 10 E2\n    InstDummy,             // 10 E3\n    InstDummy,             // 10 E4\n    InstDummy,             // 10 E5\n    InstDummy,             // 10 E6\n    InstDummy,             // 10 E7\n    InstDummy,             // 10 E8\n    InstDummy,             // 10 E9\n    InstDummy,             // 10 EA\n    InstDummy,             // 10 EB\n    InstDummy,             // 10 EC\n    InstDummy,             // 10 ED\n    InstDummy,             // 10 EE\n    InstDummy,             // 10 EF\n    InstDummy,             // 10 F0\n    InstDummy,             // 10 F1\n    InstDummy,             // 10 F2\n    InstDummy,             // 10 F3\n    InstDummy,             // 10 F4\n    InstDummy,             // 10 F5\n    InstDummy,             // 10 F6\n    InstDummy,             // 10 F7\n    InstDummy,             // 10 F8\n    InstDummy,             // 10 F9\n    InstDummy,             // 10 FA\n    InstDummy,             // 10 FB\n    InstDummy,             // 10 FC\n    InstDummy,             // 10 FD\n    InstDummy,             // 10 FE\n    InstDummy              // 10 FF\n};\n\n}\n\n}\n\n// clang-format on"
  },
  {
    "path": "src/vm/opcodetables_rne.h",
    "content": "#include \"inst_system.h\"\n#include \"inst_controlflow.h\"\n#include \"inst_graphics2d.h\"\n#include \"inst_graphics3d.h\"\n#include \"inst_dialogue.h\"\n#include \"inst_sound.h\"\n#include \"inst_movie.h\"\n#include \"inst_gamespecific.h\"\n#include \"inst_misc.h\"\n\n// clang-format off\n\nnamespace Impacto {\n\nnamespace Vm {\n\nInstructionProc inline constexpr OpcodeTableSystem_RNE[256] = {\n    InstEnd,                  // 00 00\n    InstCreateThread,         // 00 01\n    InstKillThread,           // 00 02\n    InstReset,                // 00 03\n    InstScriptLoad,           // 00 04\n    InstWait,                 // 00 05\n    InstHalt,                 // 00 06\n    InstJump,                 // 00 07\n    InstJumpTable,            // 00 08\n    InstGetLabelAdr,          // 00 09\n    InstIf,                   // 00 0A\n    InstCall,                 // 00 0B\n    InstJumpFar,              // 00 0C\n    InstCallFar,              // 00 0D\n    InstReturn,               // 00 0E\n    InstLoop,                 // 00 0F\n    InstFlagOnJump,           // 00 10\n    InstFlagOnWait,           // 00 11\n    InstSetFlag,              // 00 12\n    InstResetFlag,            // 00 13\n    InstCopyFlag,             // 00 14\n    InstKeyOnJump,            // 00 15\n    InstKeyWait,              // 00 16\n    InstKeyWaitTimer,         // 00 17\n    InstMemberWrite,          // 00 18\n    InstThreadControl,        // 00 19\n    InstGetSelfPointer,       // 00 1A\n    InstLoadJump,             // 00 1B\n    InstVsync,                // 00 1C\n    InstTest,                 // 00 1D\n    InstThreadControlStore,   // 00 1E\n    InstSwitch,               // 00 1F\n    InstCase,                 // 00 20\n    InstBGMplay,              // 00 21\n    InstBGMstop,              // 00 22\n    InstSEplay,               // 00 23\n    InstSEstop,               // 00 24\n    InstPadAct,               // 00 25\n    InstSSEplay,              // 00 26\n    InstSSEstop,              // 00 27\n    InstCopyThreadWork,       // 00 28\n    InstUPLmenuUI,            // 00 29\n    InstSave,                 // 00 2A\n    InstSaveIconLoad,         // 00 2B\n    InstBGMflag,              // 00 2C\n    InstUPLxTitle,            // 00 2D\n    InstPresence,             // 00 2E\n    InstSetAchievement,          // 00 2F\n    InstSetPlayer,            // 00 30\n    InstVoiceTableLoadMaybe,  // 00 31\n    InstSetPadCustom,         // 00 32\n    InstMwait,                // 00 33\n    InstTerminate,            // 00 34\n    InstSignIn,               // 00 35\n    InstAchievementIcon,      // 00 36\n    InstVoicePlay,            // 00 37\n    InstVoiceStop,            // 00 38\n    InstVoicePlayWait,        // 00 39\n    InstBGMduelPlay,          // 00 3A\n    InstSNDpause,             // 00 3B\n    InstSEplayWait,           // 00 3C\n    InstDebugPrint,           // 00 3D\n    InstResetSoundAll,        // 00 3E\n    InstSNDloadStop,          // 00 3F\n    InstBGMstopWait,          // 00 40\n    InstUnk0041,              // 00 41\n    InstSetX360SysMesPos,     // 00 42\n    InstSystemMes,            // 00 43\n    InstSystemMenu,           // 00 44\n    InstGetNowTime,           // 00 45\n    InstGetSystemStatus,      // 00 46\n    InstReboot,               // 00 47\n    InstReloadScript,         // 00 48\n    InstReloadScriptMenu,     // 00 49\n    InstDebugEditer,          // 00 4A\n    InstDummy,                // 00 4B\n    InstDummy,                // 00 4C\n    InstSysVoicePlay,         // 00 4D\n    InstPadActEx,             // 00 4E\n    InstDebugSetup,           // 00 4F\n    InstPressStart,           // 00 50\n    InstGlobalSystemMessage,  // 00 51\n    InstUnk0052,              // 00 52\n    InstUnk0053,              // 00 53\n    InstUnk0054,              // 00 54\n    InstReturnIfFlag,         // 00 55\n    InstDummy,                // 00 56\n    InstDummy,                // 00 57\n    InstDummy,                // 00 58\n    InstDummy,                // 00 59\n    InstDummy,                // 00 5A\n    InstDummy,                // 00 5B\n    InstDummy,                // 00 5C\n    InstDummy,                // 00 5D\n    InstDummy,                // 00 5E\n    InstDummy,                // 00 5F\n    InstDummy,                // 00 60\n    InstDummy,                // 00 61\n    InstDummy,                // 00 62\n    InstDummy,                // 00 63\n    InstDummy,                // 00 64\n    InstDummy,                // 00 65\n    InstDummy,                // 00 66\n    InstDummy,                // 00 67\n    InstDummy,                // 00 68\n    InstDummy,                // 00 69\n    InstDummy,                // 00 6A\n    InstDummy,                // 00 6B\n    InstDummy,                // 00 6C\n    InstDummy,                // 00 6D\n    InstDummy,                // 00 6E\n    InstDummy,                // 00 6F\n    InstDummy,                // 00 70\n    InstDummy,                // 00 71\n    InstDummy,                // 00 72\n    InstDummy,                // 00 73\n    InstDummy,                // 00 74\n    InstDummy,                // 00 75\n    InstDummy,                // 00 76\n    InstDummy,                // 00 77\n    InstDummy,                // 00 78\n    InstDummy,                // 00 79\n    InstDummy,                // 00 7A\n    InstDummy,                // 00 7B\n    InstDummy,                // 00 7C\n    InstDummy,                // 00 7D\n    InstDummy,                // 00 7E\n    InstDummy,                // 00 7F\n    InstDummy,                // 00 80\n    InstDummy,                // 00 81\n    InstDummy,                // 00 82\n    InstDummy,                // 00 83\n    InstDummy,                // 00 84\n    InstDummy,                // 00 85\n    InstDummy,                // 00 86\n    InstDummy,                // 00 87\n    InstDummy,                // 00 88\n    InstDummy,                // 00 89\n    InstDummy,                // 00 8A\n    InstDummy,                // 00 8B\n    InstDummy,                // 00 8C\n    InstDummy,                // 00 8D\n    InstDummy,                // 00 8E\n    InstDummy,                // 00 8F\n    InstDummy,                // 00 90\n    InstDummy,                // 00 91\n    InstDummy,                // 00 92\n    InstDummy,                // 00 93\n    InstDummy,                // 00 94\n    InstDummy,                // 00 95\n    InstDummy,                // 00 96\n    InstDummy,                // 00 97\n    InstDummy,                // 00 98\n    InstDummy,                // 00 99\n    InstDummy,                // 00 9A\n    InstDummy,                // 00 9B\n    InstDummy,                // 00 9C\n    InstDummy,                // 00 9D\n    InstDummy,                // 00 9E\n    InstDummy,                // 00 9F\n    InstDummy,                // 00 A0\n    InstDummy,                // 00 A1\n    InstDummy,                // 00 A2\n    InstDummy,                // 00 A3\n    InstDummy,                // 00 A4\n    InstDummy,                // 00 A5\n    InstDummy,                // 00 A6\n    InstDummy,                // 00 A7\n    InstDummy,                // 00 A8\n    InstDummy,                // 00 A9\n    InstDummy,                // 00 AA\n    InstDummy,                // 00 AB\n    InstDummy,                // 00 AC\n    InstDummy,                // 00 AD\n    InstDummy,                // 00 AE\n    InstDummy,                // 00 AF\n    InstDummy,                // 00 B0\n    InstDummy,                // 00 B1\n    InstDummy,                // 00 B2\n    InstDummy,                // 00 B3\n    InstDummy,                // 00 B4\n    InstDummy,                // 00 B5\n    InstDummy,                // 00 B6\n    InstDummy,                // 00 B7\n    InstDummy,                // 00 B8\n    InstDummy,                // 00 B9\n    InstDummy,                // 00 BA\n    InstDummy,                // 00 BB\n    InstDummy,                // 00 BC\n    InstDummy,                // 00 BD\n    InstDummy,                // 00 BE\n    InstDummy,                // 00 BF\n    InstDummy,                // 00 C0\n    InstDummy,                // 00 C1\n    InstDummy,                // 00 C2\n    InstDummy,                // 00 C3\n    InstDummy,                // 00 C4\n    InstDummy,                // 00 C5\n    InstDummy,                // 00 C6\n    InstDummy,                // 00 C7\n    InstDummy,                // 00 C8\n    InstDummy,                // 00 C9\n    InstDummy,                // 00 CA\n    InstDummy,                // 00 CB\n    InstDummy,                // 00 CC\n    InstDummy,                // 00 CD\n    InstDummy,                // 00 CE\n    InstDummy,                // 00 CF\n    InstDummy,                // 00 D0\n    InstDummy,                // 00 D1\n    InstDummy,                // 00 D2\n    InstDummy,                // 00 D3\n    InstDummy,                // 00 D4\n    InstDummy,                // 00 D5\n    InstDummy,                // 00 D6\n    InstDummy,                // 00 D7\n    InstDummy,                // 00 D8\n    InstDummy,                // 00 D9\n    InstDummy,                // 00 DA\n    InstDummy,                // 00 DB\n    InstDummy,                // 00 DC\n    InstDummy,                // 00 DD\n    InstDummy,                // 00 DE\n    InstDummy,                // 00 DF\n    InstDummy,                // 00 E0\n    InstDummy,                // 00 E1\n    InstDummy,                // 00 E2\n    InstDummy,                // 00 E3\n    InstDummy,                // 00 E4\n    InstDummy,                // 00 E5\n    InstDummy,                // 00 E6\n    InstDummy,                // 00 E7\n    InstDummy,                // 00 E8\n    InstDummy,                // 00 E9\n    InstDummy,                // 00 EA\n    InstDummy,                // 00 EB\n    InstDummy,                // 00 EC\n    InstDummy,                // 00 ED\n    InstDummy,                // 00 EE\n    InstDummy,                // 00 EF\n    InstDummy,                // 00 F0\n    InstDummy,                // 00 F1\n    InstDummy,                // 00 F2\n    InstDummy,                // 00 F3\n    InstDummy,                // 00 F4\n    InstDummy,                // 00 F5\n    InstDummy,                // 00 F6\n    InstDummy,                // 00 F7\n    InstDummy,                // 00 F8\n    InstDummy,                // 00 F9\n    InstDummy,                // 00 FA\n    InstDummy,                // 00 FB\n    InstDummy,                // 00 FC\n    InstDummy,                // 00 FD\n    InstDummy,                // 00 FE\n    InstDummy                 // 00 FF\n};\n\nInstructionProc inline constexpr OpcodeTableGraph_RNE[256] = {\n    InstCreateSurf,        // 01 00\n    InstReleaseSurf,       // 01 01\n    InstLoadPic,           // 01 02\n    InstReleaseSurf,       // 01 03\n    InstSurfFill,          // 01 04\n    InstCalc,              // 01 05\n    InstMesViewFlag,       // 01 06\n    InstSetMesWinPri,      // 01 07\n    InstMesSync,           // 01 08\n    InstMesSetID,          // 01 09\n    InstMesCls,            // 01 0A\n    InstMesVoiceWait,      // 01 0B\n    InstMes,               // 01 0C\n    InstMesMain,           // 01 0D\n    InstSetMesModeFormat,  // 01 0E\n    InstSetNGmoji,         // 01 0F\n    InstMesRev,            // 01 10\n    InstMessWindow,        // 01 11\n    InstSel,               // 01 12\n    InstSelect,            // 01 13\n    InstSysSel,            // 01 14\n    InstSysSelect,         // 01 15\n    InstDummy,             // 01 16\n    InstDummy,             // 01 17\n    InstDummy,             // 01 18\n    InstDummy,             // 01 19\n    InstDummy,             // 01 1A\n    InstDummy,             // 01 1B\n    InstDummy,             // 01 1C\n    InstDummy,             // 01 1D\n    InstDummy,             // 01 1E\n    InstUnk011F,           // 01 1F\n    InstSCcapture,         // 01 20\n    InstSetTextTable,      // 01 21\n    InstPlayMovie,         // 01 22\n    InstMovieMain,         // 01 23\n    InstLoadMovie,         // 01 24\n    InstSetRevMes,         // 01 25\n    InstPlayMovieMemory,   // 01 26\n    InstPlayMovie,         // 01 27\n    InstMovieMain,         // 01 28\n    InstLoadMovie,         // 01 29\n    InstPlayMovieMemory,   // 01 2A\n    InstSFDpause,          // 01 2B\n    InstPlayMovie,         // 01 2C\n    InstUnk012D,           // 01 2D\n    InstDummy,             // 01 2E\n    InstDummy,             // 01 2F\n    InstDummy,             // 01 30\n    InstDummy,             // 01 31\n    InstDummy,             // 01 32\n    InstDummy,             // 01 33\n    InstDummy,             // 01 34\n    InstDummy,             // 01 35\n    InstDummy,             // 01 36\n    InstDummy,             // 01 37\n    InstDummy,             // 01 38\n    InstDummy,             // 01 39\n    InstDummy,             // 01 3A\n    InstDummy,             // 01 3B\n    InstDummy,             // 01 3C\n    InstDummy,             // 01 3D\n    InstDummy,             // 01 3E\n    InstDummy,             // 01 3F\n    InstDummy,             // 01 40\n    InstDummy,             // 01 41\n    InstDummy,             // 01 42\n    InstDummy,             // 01 43\n    InstDummy,             // 01 44\n    InstDummy,             // 01 45\n    InstDummy,             // 01 46\n    InstDummy,             // 01 47\n    InstDummy,             // 01 48\n    InstDummy,             // 01 49\n    InstDummy,             // 01 4A\n    InstDummy,             // 01 4B\n    InstDummy,             // 01 4C\n    InstDummy,             // 01 4D\n    InstDummy,             // 01 4E\n    InstDummy,             // 01 4F\n    InstDummy,             // 01 50\n    InstDummy,             // 01 51\n    InstDummy,             // 01 52\n    InstDummy,             // 01 53\n    InstDummy,             // 01 54\n    InstDummy,             // 01 55\n    InstDummy,             // 01 56\n    InstDummy,             // 01 57\n    InstDummy,             // 01 58\n    InstDummy,             // 01 59\n    InstDummy,             // 01 5A\n    InstDummy,             // 01 5B\n    InstDummy,             // 01 5C\n    InstDummy,             // 01 5D\n    InstDummy,             // 01 5E\n    InstDummy,             // 01 5F\n    InstDummy,             // 01 60\n    InstDummy,             // 01 61\n    InstDummy,             // 01 62\n    InstDummy,             // 01 63\n    InstDummy,             // 01 64\n    InstDummy,             // 01 65\n    InstDummy,             // 01 66\n    InstDummy,             // 01 67\n    InstDummy,             // 01 68\n    InstDummy,             // 01 69\n    InstDummy,             // 01 6A\n    InstDummy,             // 01 6B\n    InstDummy,             // 01 6C\n    InstDummy,             // 01 6D\n    InstDummy,             // 01 6E\n    InstDummy,             // 01 6F\n    InstDummy,             // 01 70\n    InstDummy,             // 01 71\n    InstDummy,             // 01 72\n    InstDummy,             // 01 73\n    InstDummy,             // 01 74\n    InstDummy,             // 01 75\n    InstDummy,             // 01 76\n    InstDummy,             // 01 77\n    InstDummy,             // 01 78\n    InstDummy,             // 01 79\n    InstDummy,             // 01 7A\n    InstDummy,             // 01 7B\n    InstDummy,             // 01 7C\n    InstDummy,             // 01 7D\n    InstDummy,             // 01 7E\n    InstDummy,             // 01 7F\n    InstDummy,             // 01 80\n    InstDummy,             // 01 81\n    InstDummy,             // 01 82\n    InstDummy,             // 01 83\n    InstDummy,             // 01 84\n    InstDummy,             // 01 85\n    InstDummy,             // 01 86\n    InstDummy,             // 01 87\n    InstDummy,             // 01 88\n    InstDummy,             // 01 89\n    InstDummy,             // 01 8A\n    InstDummy,             // 01 8B\n    InstDummy,             // 01 8C\n    InstDummy,             // 01 8D\n    InstDummy,             // 01 8E\n    InstDummy,             // 01 8F\n    InstDummy,             // 01 90\n    InstDummy,             // 01 91\n    InstDummy,             // 01 92\n    InstDummy,             // 01 93\n    InstDummy,             // 01 94\n    InstDummy,             // 01 95\n    InstDummy,             // 01 96\n    InstDummy,             // 01 97\n    InstDummy,             // 01 98\n    InstDummy,             // 01 99\n    InstDummy,             // 01 9A\n    InstDummy,             // 01 9B\n    InstDummy,             // 01 9C\n    InstDummy,             // 01 9D\n    InstDummy,             // 01 9E\n    InstDummy,             // 01 9F\n    InstDummy,             // 01 A0\n    InstDummy,             // 01 A1\n    InstDummy,             // 01 A2\n    InstDummy,             // 01 A3\n    InstDummy,             // 01 A4\n    InstDummy,             // 01 A5\n    InstDummy,             // 01 A6\n    InstDummy,             // 01 A7\n    InstDummy,             // 01 A8\n    InstDummy,             // 01 A9\n    InstDummy,             // 01 AA\n    InstDummy,             // 01 AB\n    InstDummy,             // 01 AC\n    InstDummy,             // 01 AD\n    InstDummy,             // 01 AE\n    InstDummy,             // 01 AF\n    InstDummy,             // 01 B0\n    InstDummy,             // 01 B1\n    InstDummy,             // 01 B2\n    InstDummy,             // 01 B3\n    InstDummy,             // 01 B4\n    InstDummy,             // 01 B5\n    InstDummy,             // 01 B6\n    InstDummy,             // 01 B7\n    InstDummy,             // 01 B8\n    InstDummy,             // 01 B9\n    InstDummy,             // 01 BA\n    InstDummy,             // 01 BB\n    InstDummy,             // 01 BC\n    InstDummy,             // 01 BD\n    InstDummy,             // 01 BE\n    InstDummy,             // 01 BF\n    InstDummy,             // 01 C0\n    InstDummy,             // 01 C1\n    InstDummy,             // 01 C2\n    InstDummy,             // 01 C3\n    InstDummy,             // 01 C4\n    InstDummy,             // 01 C5\n    InstDummy,             // 01 C6\n    InstDummy,             // 01 C7\n    InstDummy,             // 01 C8\n    InstDummy,             // 01 C9\n    InstDummy,             // 01 CA\n    InstDummy,             // 01 CB\n    InstDummy,             // 01 CC\n    InstDummy,             // 01 CD\n    InstDummy,             // 01 CE\n    InstDummy,             // 01 CF\n    InstDummy,             // 01 D0\n    InstDummy,             // 01 D1\n    InstDummy,             // 01 D2\n    InstDummy,             // 01 D3\n    InstDummy,             // 01 D4\n    InstDummy,             // 01 D5\n    InstDummy,             // 01 D6\n    InstDummy,             // 01 D7\n    InstDummy,             // 01 D8\n    InstDummy,             // 01 D9\n    InstDummy,             // 01 DA\n    InstDummy,             // 01 DB\n    InstDummy,             // 01 DC\n    InstDummy,             // 01 DD\n    InstDummy,             // 01 DE\n    InstDummy,             // 01 DF\n    InstDummy,             // 01 E0\n    InstDummy,             // 01 E1\n    InstDummy,             // 01 E2\n    InstDummy,             // 01 E3\n    InstDummy,             // 01 E4\n    InstDummy,             // 01 E5\n    InstDummy,             // 01 E6\n    InstDummy,             // 01 E7\n    InstDummy,             // 01 E8\n    InstDummy,             // 01 E9\n    InstDummy,             // 01 EA\n    InstDummy,             // 01 EB\n    InstDummy,             // 01 EC\n    InstDummy,             // 01 ED\n    InstDummy,             // 01 EE\n    InstDummy,             // 01 EF\n    InstDummy,             // 01 F0\n    InstDummy,             // 01 F1\n    InstDummy,             // 01 F2\n    InstDummy,             // 01 F3\n    InstDummy,             // 01 F4\n    InstDummy,             // 01 F5\n    InstDummy,             // 01 F6\n    InstDummy,             // 01 F7\n    InstDummy,             // 01 F8\n    InstDummy,             // 01 F9\n    InstDummy,             // 01 FA\n    InstDummy,             // 01 FB\n    InstDummy,             // 01 FC\n    InstDummy,             // 01 FD\n    InstDummy,             // 01 FE\n    InstDummy              // 01 FF\n};\n\nInstructionProc inline constexpr OpcodeTableUser1_RNE[256] = {\n    InstMSinit,            // 10 00\n    InstBGload,            // 10 01\n    InstBGswap,            // 10 02\n    InstBGsetColor,        // 10 03\n    InstBGsetLink,         // 10 04\n    InstBGrelease,         // 10 05\n    InstDummy,             // 10 06\n    InstBGcopy,            // 10 07\n    InstDummy,             // 10 08\n    InstSaveSlot,          // 10 09\n    InstSystemMain,        // 10 0A\n    InstDummy,             // 10 0B\n    InstGameInfoInit,      // 10 0C\n    InstDummy,             // 10 0D\n    InstBGloadEx,          // 10 0E\n    InstDummy,             // 10 0F\n    InstDummy,             // 10 10\n    InstDummy,             // 10 11\n    InstClearFlagChk,      // 10 12\n    InstOption,            // 10 13\n    InstSystemDataReset,   // 10 14\n    InstDebugData,         // 10 15\n    InstGetCharaPause,     // 10 16\n    InstBGfadeExpInit,     // 10 17\n    InstDummy,             // 10 18\n    InstDummy,             // 10 19\n    InstDummy,             // 10 1A\n    InstHelp,              // 10 1B\n    InstAchievementMenu,   // 10 1C\n    InstSoundMenu,         // 10 1D\n    InstAllClear,          // 10 1E\n    InstAlbum,             // 10 1F\n    InstMovieMode,         // 10 20\n    InstClistInit,         // 10 21\n    InstAutoSave,          // 10 22\n    InstSaveMenu,          // 10 23\n    InstLoadData,          // 10 24\n    InstDummy,             // 10 25\n    InstDummy,             // 10 26\n    InstSetDic,            // 10 27\n    InstSetPlayMode,       // 10 28\n    InstSetEVflag,         // 10 29\n    InstSetCutin,          // 10 2A\n    InstDummy,             // 10 2B\n    InstDummy,             // 10 2C\n    InstAchChkTitle,       // 10 2D\n    InstSetSceneViewFlag,  // 10 2E\n    InstChkClearFlag,      // 10 2F\n    InstBGeffectWave,      // 10 30\n    InstGeotag,            // 10 31\n    InstNameID,            // 10 32\n    InstTips,              // 10 33\n    InstTitleMenu,         // 10 34\n    InstDummy,             // 10 35\n    InstBGeffect,          // 10 36\n    InstUnk1037,           // 10 37\n    InstMail,              // 10 38\n    InstTwipo,             // 10 39\n    InstUnk103A,           // 10 3A\n    InstDummy,             // 10 3B\n    InstDummy,             // 10 3C\n    InstDummy,             // 10 3D\n    InstDummy,             // 10 3E\n    InstDummy,             // 10 3F\n    InstDummy,             // 10 40\n    InstDummy,             // 10 41\n    InstDummy,             // 10 42\n    InstDummy,             // 10 43\n    InstDummy,             // 10 44\n    InstDummy,             // 10 45\n    InstDummy,             // 10 46\n    InstDummy,             // 10 47\n    InstDummy,             // 10 48\n    InstDummy,             // 10 49\n    InstDummy,             // 10 4A\n    InstDummy,             // 10 4B\n    InstDummy,             // 10 4C\n    InstDummy,             // 10 4D\n    InstDummy,             // 10 4E\n    InstDummy,             // 10 4F\n    InstDummy,             // 10 50\n    InstDummy,             // 10 51\n    InstDummy,             // 10 52\n    InstDummy,             // 10 53\n    InstDummy,             // 10 54\n    InstDummy,             // 10 55\n    InstDummy,             // 10 56\n    InstDummy,             // 10 57\n    InstDummy,             // 10 58\n    InstDummy,             // 10 59\n    InstDummy,             // 10 5A\n    InstDummy,             // 10 5B\n    InstDummy,             // 10 5C\n    InstDummy,             // 10 5D\n    InstDummy,             // 10 5E\n    InstDummy,             // 10 5F\n    InstDummy,             // 10 60\n    InstDummy,             // 10 61\n    InstDummy,             // 10 62\n    InstDummy,             // 10 63\n    InstDummy,             // 10 64\n    InstDummy,             // 10 65\n    InstDummy,             // 10 66\n    InstDummy,             // 10 67\n    InstDummy,             // 10 68\n    InstDummy,             // 10 69\n    InstDummy,             // 10 6A\n    InstDummy,             // 10 6B\n    InstDummy,             // 10 6C\n    InstDummy,             // 10 6D\n    InstDummy,             // 10 6E\n    InstDummy,             // 10 6F\n    InstDummy,             // 10 70\n    InstDummy,             // 10 71\n    InstDummy,             // 10 72\n    InstDummy,             // 10 73\n    InstDummy,             // 10 74\n    InstDummy,             // 10 75\n    InstDummy,             // 10 76\n    InstDummy,             // 10 77\n    InstDummy,             // 10 78\n    InstDummy,             // 10 79\n    InstDummy,             // 10 7A\n    InstDummy,             // 10 7B\n    InstDummy,             // 10 7C\n    InstDummy,             // 10 7D\n    InstDummy,             // 10 7E\n    InstDummy,             // 10 7F\n    InstDummy,             // 10 80\n    InstDummy,             // 10 81\n    InstDummy,             // 10 82\n    InstDummy,             // 10 83\n    InstDummy,             // 10 84\n    InstDummy,             // 10 85\n    InstDummy,             // 10 86\n    InstDummy,             // 10 87\n    InstDummy,             // 10 88\n    InstDummy,             // 10 89\n    InstDummy,             // 10 8A\n    InstDummy,             // 10 8B\n    InstDummy,             // 10 8C\n    InstDummy,             // 10 8D\n    InstDummy,             // 10 8E\n    InstDummy,             // 10 8F\n    InstDummy,             // 10 90\n    InstDummy,             // 10 91\n    InstDummy,             // 10 92\n    InstDummy,             // 10 93\n    InstDummy,             // 10 94\n    InstDummy,             // 10 95\n    InstDummy,             // 10 96\n    InstDummy,             // 10 97\n    InstDummy,             // 10 98\n    InstDummy,             // 10 99\n    InstDummy,             // 10 9A\n    InstDummy,             // 10 9B\n    InstDummy,             // 10 9C\n    InstDummy,             // 10 9D\n    InstDummy,             // 10 9E\n    InstDummy,             // 10 9F\n    InstDummy,             // 10 A0\n    InstDummy,             // 10 A1\n    InstDummy,             // 10 A2\n    InstDummy,             // 10 A3\n    InstDummy,             // 10 A4\n    InstDummy,             // 10 A5\n    InstDummy,             // 10 A6\n    InstDummy,             // 10 A7\n    InstDummy,             // 10 A8\n    InstDummy,             // 10 A9\n    InstDummy,             // 10 AA\n    InstDummy,             // 10 AB\n    InstDummy,             // 10 AC\n    InstDummy,             // 10 AD\n    InstDummy,             // 10 AE\n    InstDummy,             // 10 AF\n    InstDummy,             // 10 B0\n    InstDummy,             // 10 B1\n    InstDummy,             // 10 B2\n    InstDummy,             // 10 B3\n    InstDummy,             // 10 B4\n    InstDummy,             // 10 B5\n    InstDummy,             // 10 B6\n    InstDummy,             // 10 B7\n    InstDummy,             // 10 B8\n    InstDummy,             // 10 B9\n    InstDummy,             // 10 BA\n    InstDummy,             // 10 BB\n    InstDummy,             // 10 BC\n    InstDummy,             // 10 BD\n    InstDummy,             // 10 BE\n    InstDummy,             // 10 BF\n    InstDummy,             // 10 C0\n    InstDummy,             // 10 C1\n    InstDummy,             // 10 C2\n    InstDummy,             // 10 C3\n    InstDummy,             // 10 C4\n    InstDummy,             // 10 C5\n    InstDummy,             // 10 C6\n    InstDummy,             // 10 C7\n    InstDummy,             // 10 C8\n    InstDummy,             // 10 C9\n    InstDummy,             // 10 CA\n    InstDummy,             // 10 CB\n    InstDummy,             // 10 CC\n    InstDummy,             // 10 CD\n    InstDummy,             // 10 CE\n    InstDummy,             // 10 CF\n    InstDummy,             // 10 D0\n    InstDummy,             // 10 D1\n    InstDummy,             // 10 D2\n    InstDummy,             // 10 D3\n    InstDummy,             // 10 D4\n    InstDummy,             // 10 D5\n    InstDummy,             // 10 D6\n    InstDummy,             // 10 D7\n    InstDummy,             // 10 D8\n    InstDummy,             // 10 D9\n    InstDummy,             // 10 DA\n    InstDummy,             // 10 DB\n    InstDummy,             // 10 DC\n    InstDummy,             // 10 DD\n    InstDummy,             // 10 DE\n    InstDummy,             // 10 DF\n    InstDummy,             // 10 E0\n    InstDummy,             // 10 E1\n    InstDummy,             // 10 E2\n    InstDummy,             // 10 E3\n    InstDummy,             // 10 E4\n    InstDummy,             // 10 E5\n    InstDummy,             // 10 E6\n    InstDummy,             // 10 E7\n    InstDummy,             // 10 E8\n    InstDummy,             // 10 E9\n    InstDummy,             // 10 EA\n    InstDummy,             // 10 EB\n    InstDummy,             // 10 EC\n    InstDummy,             // 10 ED\n    InstDummy,             // 10 EE\n    InstDummy,             // 10 EF\n    InstDummy,             // 10 F0\n    InstDummy,             // 10 F1\n    InstDummy,             // 10 F2\n    InstDummy,             // 10 F3\n    InstDummy,             // 10 F4\n    InstDummy,             // 10 F5\n    InstDummy,             // 10 F6\n    InstDummy,             // 10 F7\n    InstDummy,             // 10 F8\n    InstDummy,             // 10 F9\n    InstDummy,             // 10 FA\n    InstDummy,             // 10 FB\n    InstDummy,             // 10 FC\n    InstDummy,             // 10 FD\n    InstDummy,             // 10 FE\n    InstDummy              // 10 FF\n};\n\nInstructionProc inline constexpr OpcodeTableGraph3D_RNE[256] = {\n    InstDummy,               // 02 00\n    InstCHAload3D,           // 02 01\n    InstCHArelease3D,        // 02 02\n    InstDummy,               // 02 03\n    InstUnk0204,             // 02 04\n    InstCHAswap3DMaybe,      // 02 05\n    InstCHAplayAnim3DMaybe,  // 02 06\n    InstCHAUnk02073D,        // 02 07\n    InstPositionObject,      // 02 08\n    InstCHAsetAnim3D,        // 02 09\n    InstDummy,               // 02 0A\n    InstDummy,               // 02 0B\n    InstDummy,               // 02 0C\n    InstDummy,               // 02 0D\n    InstDummy,               // 02 0E\n    InstDummy,               // 02 0F\n    InstUnk0210,             // 02 10\n    InstUnk0211,             // 02 11\n    InstUnk0212,             // 02 12\n    InstUnk0213,             // 02 13\n    InstUnk0214,             // 02 14\n    InstUnk0215,             // 02 15\n    InstMoveCamera,          // 02 16\n    InstUnk0217,             // 02 17\n    InstUnk0218,             // 02 18\n    InstUnk0219,             // 02 19\n    InstDummy,               // 02 1A\n    InstDummy,               // 02 1B\n    InstDummy,               // 02 1C\n    InstDummy,               // 02 1D\n    InstDummy,               // 02 1E\n    InstDummy,               // 02 1F\n    InstDummy,               // 02 20\n    InstDummy,               // 02 21\n    InstDummy,               // 02 22\n    InstDummy,               // 02 23\n    InstDummy,               // 02 24\n    InstDummy,               // 02 25\n    InstDummy,               // 02 26\n    InstDummy,               // 02 27\n    InstDummy,               // 02 28\n    InstDummy,               // 02 29\n    InstDummy,               // 02 2A\n    InstDummy,               // 02 2B\n    InstDummy,               // 02 2C\n    InstDummy,               // 02 2D\n    InstDummy,               // 02 2E\n    InstDummy,               // 02 2F\n    InstDummy,               // 02 30\n    InstDummy,               // 02 31\n    InstDummy,               // 02 32\n    InstDummy,               // 02 33\n    InstDummy,               // 02 34\n    InstDummy,               // 02 35\n    InstDummy,               // 02 36\n    InstDummy,               // 02 37\n    InstDummy,               // 02 38\n    InstDummy,               // 02 39\n    InstDummy,               // 02 3A\n    InstDummy,               // 02 3B\n    InstDummy,               // 02 3C\n    InstDummy,               // 02 3D\n    InstDummy,               // 02 3E\n    InstDummy,               // 02 3F\n    InstUnk0240,             // 02 40\n    InstDummy,               // 02 41\n    InstDummy,               // 02 42\n    InstDummy,               // 02 43\n    InstDummy,               // 02 44\n    InstDummy,               // 02 45\n    InstDummy,               // 02 46\n    InstDummy,               // 02 47\n    InstDummy,               // 02 48\n    InstDummy,               // 02 49\n    InstDummy,               // 02 4A\n    InstDummy,               // 02 4B\n    InstDummy,               // 02 4C\n    InstDummy,               // 02 4D\n    InstDummy,               // 02 4E\n    InstDummy,               // 02 4F\n    InstDummy,               // 02 50\n    InstDummy,               // 02 51\n    InstDummy,               // 02 52\n    InstDummy,               // 02 53\n    InstDummy,               // 02 54\n    InstDummy,               // 02 55\n    InstDummy,               // 02 56\n    InstDummy,               // 02 57\n    InstDummy,               // 02 58\n    InstDummy,               // 02 59\n    InstDummy,               // 02 5A\n    InstDummy,               // 02 5B\n    InstDummy,               // 02 5C\n    InstDummy,               // 02 5D\n    InstDummy,               // 02 5E\n    InstDummy,               // 02 5F\n    InstDummy,               // 02 60\n    InstDummy,               // 02 61\n    InstDummy,               // 02 62\n    InstDummy,               // 02 63\n    InstDummy,               // 02 64\n    InstDummy,               // 02 65\n    InstDummy,               // 02 66\n    InstDummy,               // 02 67\n    InstDummy,               // 02 68\n    InstDummy,               // 02 69\n    InstDummy,               // 02 6A\n    InstDummy,               // 02 6B\n    InstDummy,               // 02 6C\n    InstDummy,               // 02 6D\n    InstDummy,               // 02 6E\n    InstDummy,               // 02 6F\n    InstDummy,               // 02 70\n    InstDummy,               // 02 71\n    InstDummy,               // 02 72\n    InstDummy,               // 02 73\n    InstDummy,               // 02 74\n    InstDummy,               // 02 75\n    InstDummy,               // 02 76\n    InstDummy,               // 02 77\n    InstDummy,               // 02 78\n    InstDummy,               // 02 79\n    InstDummy,               // 02 7A\n    InstDummy,               // 02 7B\n    InstDummy,               // 02 7C\n    InstDummy,               // 02 7D\n    InstDummy,               // 02 7E\n    InstDummy,               // 02 7F\n    InstDummy,               // 02 80\n    InstDummy,               // 02 81\n    InstDummy,               // 02 82\n    InstDummy,               // 02 83\n    InstDummy,               // 02 84\n    InstDummy,               // 02 85\n    InstDummy,               // 02 86\n    InstDummy,               // 02 87\n    InstDummy,               // 02 88\n    InstDummy,               // 02 89\n    InstDummy,               // 02 8A\n    InstDummy,               // 02 8B\n    InstDummy,               // 02 8C\n    InstDummy,               // 02 8D\n    InstDummy,               // 02 8E\n    InstDummy,               // 02 8F\n    InstDummy,               // 02 90\n    InstDummy,               // 02 91\n    InstDummy,               // 02 92\n    InstDummy,               // 02 93\n    InstDummy,               // 02 94\n    InstDummy,               // 02 95\n    InstDummy,               // 02 96\n    InstDummy,               // 02 97\n    InstDummy,               // 02 98\n    InstDummy,               // 02 99\n    InstDummy,               // 02 9A\n    InstDummy,               // 02 9B\n    InstDummy,               // 02 9C\n    InstDummy,               // 02 9D\n    InstDummy,               // 02 9E\n    InstDummy,               // 02 9F\n    InstDummy,               // 02 A0\n    InstDummy,               // 02 A1\n    InstDummy,               // 02 A2\n    InstDummy,               // 02 A3\n    InstDummy,               // 02 A4\n    InstDummy,               // 02 A5\n    InstDummy,               // 02 A6\n    InstDummy,               // 02 A7\n    InstDummy,               // 02 A8\n    InstDummy,               // 02 A9\n    InstDummy,               // 02 AA\n    InstDummy,               // 02 AB\n    InstDummy,               // 02 AC\n    InstDummy,               // 02 AD\n    InstDummy,               // 02 AE\n    InstDummy,               // 02 AF\n    InstDummy,               // 02 B0\n    InstDummy,               // 02 B1\n    InstDummy,               // 02 B2\n    InstDummy,               // 02 B3\n    InstDummy,               // 02 B4\n    InstDummy,               // 02 B5\n    InstDummy,               // 02 B6\n    InstDummy,               // 02 B7\n    InstDummy,               // 02 B8\n    InstDummy,               // 02 B9\n    InstDummy,               // 02 BA\n    InstDummy,               // 02 BB\n    InstDummy,               // 02 BC\n    InstDummy,               // 02 BD\n    InstDummy,               // 02 BE\n    InstDummy,               // 02 BF\n    InstDummy,               // 02 C0\n    InstDummy,               // 02 C1\n    InstDummy,               // 02 C2\n    InstDummy,               // 02 C3\n    InstDummy,               // 02 C4\n    InstDummy,               // 02 C5\n    InstDummy,               // 02 C6\n    InstDummy,               // 02 C7\n    InstDummy,               // 02 C8\n    InstDummy,               // 02 C9\n    InstDummy,               // 02 CA\n    InstDummy,               // 02 CB\n    InstDummy,               // 02 CC\n    InstDummy,               // 02 CD\n    InstDummy,               // 02 CE\n    InstDummy,               // 02 CF\n    InstDummy,               // 02 D0\n    InstDummy,               // 02 D1\n    InstDummy,               // 02 D2\n    InstDummy,               // 02 D3\n    InstDummy,               // 02 D4\n    InstDummy,               // 02 D5\n    InstDummy,               // 02 D6\n    InstDummy,               // 02 D7\n    InstDummy,               // 02 D8\n    InstDummy,               // 02 D9\n    InstDummy,               // 02 DA\n    InstDummy,               // 02 DB\n    InstDummy,               // 02 DC\n    InstDummy,               // 02 DD\n    InstDummy,               // 02 DE\n    InstDummy,               // 02 DF\n    InstDummy,               // 02 E0\n    InstDummy,               // 02 E1\n    InstDummy,               // 02 E2\n    InstDummy,               // 02 E3\n    InstDummy,               // 02 E4\n    InstDummy,               // 02 E5\n    InstDummy,               // 02 E6\n    InstDummy,               // 02 E7\n    InstDummy,               // 02 E8\n    InstDummy,               // 02 E9\n    InstDummy,               // 02 EA\n    InstDummy,               // 02 EB\n    InstDummy,               // 02 EC\n    InstDummy,               // 02 ED\n    InstDummy,               // 02 EE\n    InstDummy,               // 02 EF\n    InstDummy,               // 02 F0\n    InstDummy,               // 02 F1\n    InstDummy,               // 02 F2\n    InstDummy,               // 02 F3\n    InstDummy,               // 02 F4\n    InstDummy,               // 02 F5\n    InstDummy,               // 02 F6\n    InstDummy,               // 02 F7\n    InstDummy,               // 02 F8\n    InstDummy,               // 02 F9\n    InstDummy,               // 02 FA\n    InstDummy,               // 02 FB\n    InstDummy,               // 02 FC\n    InstDummy,               // 02 FD\n    InstDummy,               // 02 FE\n    InstDummy                // 02 FF\n};\n\n}\n\n}\n\n// clang-format on"
  },
  {
    "path": "src/vm/opcodetables_sgps3.h",
    "content": "#include \"inst_system.h\"\n#include \"inst_controlflow.h\"\n#include \"inst_graphics2d.h\"\n#include \"inst_graphics3d.h\"\n#include \"inst_dialogue.h\"\n#include \"inst_sound.h\"\n#include \"inst_movie.h\"\n#include \"inst_gamespecific.h\"\n#include \"inst_misc.h\"\n\n// clang-format off\n\nnamespace Impacto {\n\nnamespace Vm {\n\nInstructionProc inline constexpr OpcodeTableSystem_SGPS3[256] = {\n    InstEnd,                  // 00 00\n    InstCreateThread,         // 00 01\n    InstKillThread,           // 00 02\n    InstReset,                // 00 03\n    InstScriptLoad,           // 00 04\n    InstWait,                 // 00 05\n    InstHalt,                 // 00 06\n    InstJump,                 // 00 07\n    InstJumpTable,            // 00 08\n    InstGetLabelAdr,          // 00 09\n    InstIf,                   // 00 0A\n    InstCall,                 // 00 0B\n    InstJumpFar,              // 00 0C\n    InstCallFar,              // 00 0D\n    InstReturn,               // 00 0E\n    InstLoop,                 // 00 0F\n    InstFlagOnJump,           // 00 10\n    InstFlagOnWait,           // 00 11\n    InstSetFlag,              // 00 12\n    InstResetFlag,            // 00 13\n    InstCopyFlag,             // 00 14\n    InstKeyOnJump,            // 00 15\n    InstKeyWait,              // 00 16\n    InstKeyWaitTimer,         // 00 17\n    InstMemberWrite,          // 00 18\n    InstThreadControl,        // 00 19\n    InstGetSelfPointer,       // 00 1A\n    InstLoadJump,             // 00 1B\n    InstVsync,                // 00 1C\n    InstTest,                 // 00 1D\n    InstThreadControlStore,   // 00 1E\n    InstSwitch,               // 00 1F\n    InstCase,                 // 00 20\n    InstBGMplay,              // 00 21\n    InstBGMstop,              // 00 22\n    InstSEplayMO6,            // 00 23\n    InstSEstop,               // 00 24\n    InstPadAct,               // 00 25\n    InstSSEplay,              // 00 26\n    InstSSEstop,              // 00 27\n    InstCopyThreadWork,       // 00 28\n    InstUPLmenuUI,            // 00 29\n    InstSaveOld,              // 00 2A\n    InstSaveIconLoad,         // 00 2B\n    InstBGMflag,              // 00 2C\n    InstUPLxTitle,            // 00 2D\n    InstPresence,             // 00 2E\n    InstSetAchievement,          // 00 2F\n    InstSetPlayer,            // 00 30\n    InstVoiceTableLoadMaybe,  // 00 31\n    InstSetPadCustom,         // 00 32\n    InstMwait,                // 00 33\n    InstTerminate,            // 00 34\n    InstSignIn,               // 00 35\n    InstAchievementIcon,      // 00 36\n    InstVoicePlayOld,         // 00 37\n    InstVoiceStop,            // 00 38\n    InstVoicePlayWait,        // 00 39\n    InstBGMduelPlay,          // 00 3A\n    InstSNDpause,             // 00 3B\n    InstSEplayWait,           // 00 3C\n    InstDebugPrint,           // 00 3D\n    InstResetSoundAll,        // 00 3E\n    InstSNDloadStop,          // 00 3F\n    InstBGMstopWait,          // 00 40\n    InstUnk0041,              // 00 41\n    InstSetX360SysMesPos,     // 00 42\n    InstSystemMes,            // 00 43\n    InstSystemMenu,           // 00 44\n    InstGetNowTime,           // 00 45\n    InstGetSystemStatus,      // 00 46\n    InstReboot,               // 00 47\n    InstReloadScript,         // 00 48\n    InstReloadScriptMenu,     // 00 49\n    InstDebugEditer,          // 00 4A\n    InstDummy,                // 00 4B\n    InstDummy,                // 00 4C\n    InstSysVoicePlay,         // 00 4D\n    InstPadActEx,             // 00 4E\n    InstDebugSetup,           // 00 4F\n    InstPressStart,           // 00 50\n    InstGlobalSystemMessage,  // 00 51\n    InstUnk0052,              // 00 52\n    InstUnk0053,              // 00 53\n    InstUnk0054,              // 00 54\n    InstReturnIfFlag,         // 00 55\n    InstDummy,                // 00 56\n    InstDummy,                // 00 57\n    InstDummy,                // 00 58\n    InstDummy,                // 00 59\n    InstDummy,                // 00 5A\n    InstDummy,                // 00 5B\n    InstDummy,                // 00 5C\n    InstDummy,                // 00 5D\n    InstDummy,                // 00 5E\n    InstDummy,                // 00 5F\n    InstDummy,                // 00 60\n    InstDummy,                // 00 61\n    InstDummy,                // 00 62\n    InstDummy,                // 00 63\n    InstDummy,                // 00 64\n    InstDummy,                // 00 65\n    InstDummy,                // 00 66\n    InstDummy,                // 00 67\n    InstDummy,                // 00 68\n    InstDummy,                // 00 69\n    InstDummy,                // 00 6A\n    InstDummy,                // 00 6B\n    InstDummy,                // 00 6C\n    InstDummy,                // 00 6D\n    InstDummy,                // 00 6E\n    InstDummy,                // 00 6F\n    InstDummy,                // 00 70\n    InstDummy,                // 00 71\n    InstDummy,                // 00 72\n    InstDummy,                // 00 73\n    InstDummy,                // 00 74\n    InstDummy,                // 00 75\n    InstDummy,                // 00 76\n    InstDummy,                // 00 77\n    InstDummy,                // 00 78\n    InstDummy,                // 00 79\n    InstDummy,                // 00 7A\n    InstDummy,                // 00 7B\n    InstDummy,                // 00 7C\n    InstDummy,                // 00 7D\n    InstDummy,                // 00 7E\n    InstDummy,                // 00 7F\n    InstDummy,                // 00 80\n    InstDummy,                // 00 81\n    InstDummy,                // 00 82\n    InstDummy,                // 00 83\n    InstDummy,                // 00 84\n    InstDummy,                // 00 85\n    InstDummy,                // 00 86\n    InstDummy,                // 00 87\n    InstDummy,                // 00 88\n    InstDummy,                // 00 89\n    InstDummy,                // 00 8A\n    InstDummy,                // 00 8B\n    InstDummy,                // 00 8C\n    InstDummy,                // 00 8D\n    InstDummy,                // 00 8E\n    InstDummy,                // 00 8F\n    InstDummy,                // 00 90\n    InstDummy,                // 00 91\n    InstDummy,                // 00 92\n    InstDummy,                // 00 93\n    InstDummy,                // 00 94\n    InstDummy,                // 00 95\n    InstDummy,                // 00 96\n    InstDummy,                // 00 97\n    InstDummy,                // 00 98\n    InstDummy,                // 00 99\n    InstDummy,                // 00 9A\n    InstDummy,                // 00 9B\n    InstDummy,                // 00 9C\n    InstDummy,                // 00 9D\n    InstDummy,                // 00 9E\n    InstDummy,                // 00 9F\n    InstDummy,                // 00 A0\n    InstDummy,                // 00 A1\n    InstDummy,                // 00 A2\n    InstDummy,                // 00 A3\n    InstDummy,                // 00 A4\n    InstDummy,                // 00 A5\n    InstDummy,                // 00 A6\n    InstDummy,                // 00 A7\n    InstDummy,                // 00 A8\n    InstDummy,                // 00 A9\n    InstDummy,                // 00 AA\n    InstDummy,                // 00 AB\n    InstDummy,                // 00 AC\n    InstDummy,                // 00 AD\n    InstDummy,                // 00 AE\n    InstDummy,                // 00 AF\n    InstDummy,                // 00 B0\n    InstDummy,                // 00 B1\n    InstDummy,                // 00 B2\n    InstDummy,                // 00 B3\n    InstDummy,                // 00 B4\n    InstDummy,                // 00 B5\n    InstDummy,                // 00 B6\n    InstDummy,                // 00 B7\n    InstDummy,                // 00 B8\n    InstDummy,                // 00 B9\n    InstDummy,                // 00 BA\n    InstDummy,                // 00 BB\n    InstDummy,                // 00 BC\n    InstDummy,                // 00 BD\n    InstDummy,                // 00 BE\n    InstDummy,                // 00 BF\n    InstDummy,                // 00 C0\n    InstDummy,                // 00 C1\n    InstDummy,                // 00 C2\n    InstDummy,                // 00 C3\n    InstDummy,                // 00 C4\n    InstDummy,                // 00 C5\n    InstDummy,                // 00 C6\n    InstDummy,                // 00 C7\n    InstDummy,                // 00 C8\n    InstDummy,                // 00 C9\n    InstDummy,                // 00 CA\n    InstDummy,                // 00 CB\n    InstDummy,                // 00 CC\n    InstDummy,                // 00 CD\n    InstDummy,                // 00 CE\n    InstDummy,                // 00 CF\n    InstDummy,                // 00 D0\n    InstDummy,                // 00 D1\n    InstDummy,                // 00 D2\n    InstDummy,                // 00 D3\n    InstDummy,                // 00 D4\n    InstDummy,                // 00 D5\n    InstDummy,                // 00 D6\n    InstDummy,                // 00 D7\n    InstDummy,                // 00 D8\n    InstDummy,                // 00 D9\n    InstDummy,                // 00 DA\n    InstDummy,                // 00 DB\n    InstDummy,                // 00 DC\n    InstDummy,                // 00 DD\n    InstDummy,                // 00 DE\n    InstDummy,                // 00 DF\n    InstDummy,                // 00 E0\n    InstDummy,                // 00 E1\n    InstDummy,                // 00 E2\n    InstDummy,                // 00 E3\n    InstDummy,                // 00 E4\n    InstDummy,                // 00 E5\n    InstDummy,                // 00 E6\n    InstDummy,                // 00 E7\n    InstDummy,                // 00 E8\n    InstDummy,                // 00 E9\n    InstDummy,                // 00 EA\n    InstDummy,                // 00 EB\n    InstDummy,                // 00 EC\n    InstDummy,                // 00 ED\n    InstDummy,                // 00 EE\n    InstDummy,                // 00 EF\n    InstDummy,                // 00 F0\n    InstDummy,                // 00 F1\n    InstDummy,                // 00 F2\n    InstDummy,                // 00 F3\n    InstDummy,                // 00 F4\n    InstDummy,                // 00 F5\n    InstDummy,                // 00 F6\n    InstDummy,                // 00 F7\n    InstDummy,                // 00 F8\n    InstDummy,                // 00 F9\n    InstDummy,                // 00 FA\n    InstDummy,                // 00 FB\n    InstDummy,                // 00 FC\n    InstDummy,                // 00 FD\n    InstDummy,                // 00 FE\n    InstDummy                 // 00 FF\n};\n\nInstructionProc inline constexpr OpcodeTableGraph_SGPS3[256] = {\n    InstCreateSurf,        // 01 00\n    InstReleaseSurf,       // 01 01\n    InstLoadPic,           // 01 02\n    InstReleaseSurf,       // 01 03\n    InstSurfFill,          // 01 04\n    InstDummy,             // 01 05\n    InstDummy,             // 01 06\n    InstDummy,             // 01 07\n    InstDummy,             // 01 08\n    InstDummy,             // 01 09\n    InstCalc,              // 01 0A\n    InstMesVoiceWait,      // 01 0B\n    InstMes,               // 01 0C\n    InstMesMain,           // 01 0D\n    InstSetMesModeFormat,  // 01 0E\n    InstSetNGmoji,         // 01 0F\n    InstMesRev,            // 01 10\n    InstMessWindow,        // 01 11\n    InstSel,               // 01 12\n    InstSelect,            // 01 13\n    InstSysSel,            // 01 14\n    InstSysSelect,         // 01 15\n    InstDummy,             // 01 16\n    InstDummy,             // 01 17\n    InstDummy,             // 01 18\n    InstDummy,             // 01 19\n    InstDummy,             // 01 1A\n    InstDummy,             // 01 1B\n    InstDummy,             // 01 1C\n    InstDummy,             // 01 1D\n    InstDummy,             // 01 1E\n    InstUnk011F,           // 01 1F\n    InstSCcapture,         // 01 20\n    InstSetTextTable,      // 01 21\n    InstPlayMovie,         // 01 22\n    InstMovieMain,         // 01 23\n    InstLoadMovie,         // 01 24\n    InstSetRevMes,         // 01 25\n    InstPlayMovieMemory,   // 01 26\n    InstPlayMovie,         // 01 27\n    InstMovieMain,         // 01 28\n    InstLoadMovie,         // 01 29\n    InstPlayMovieMemory,   // 01 2A\n    InstSFDpause,          // 01 2B\n    InstPlayMovie,         // 01 2C\n    InstUnk012D,           // 01 2D\n    InstDummy,             // 01 2E\n    InstDummy,             // 01 2F\n    InstDummy,             // 01 30\n    InstDummy,             // 01 31\n    InstDummy,             // 01 32\n    InstDummy,             // 01 33\n    InstDummy,             // 01 34\n    InstDummy,             // 01 35\n    InstDummy,             // 01 36\n    InstDummy,             // 01 37\n    InstDummy,             // 01 38\n    InstDummy,             // 01 39\n    InstDummy,             // 01 3A\n    InstDummy,             // 01 3B\n    InstDummy,             // 01 3C\n    InstDummy,             // 01 3D\n    InstDummy,             // 01 3E\n    InstDummy,             // 01 3F\n    InstDummy,             // 01 40\n    InstDummy,             // 01 41\n    InstDummy,             // 01 42\n    InstDummy,             // 01 43\n    InstDummy,             // 01 44\n    InstDummy,             // 01 45\n    InstDummy,             // 01 46\n    InstDummy,             // 01 47\n    InstDummy,             // 01 48\n    InstDummy,             // 01 49\n    InstDummy,             // 01 4A\n    InstDummy,             // 01 4B\n    InstDummy,             // 01 4C\n    InstDummy,             // 01 4D\n    InstDummy,             // 01 4E\n    InstDummy,             // 01 4F\n    InstDummy,             // 01 50\n    InstDummy,             // 01 51\n    InstDummy,             // 01 52\n    InstDummy,             // 01 53\n    InstDummy,             // 01 54\n    InstDummy,             // 01 55\n    InstDummy,             // 01 56\n    InstDummy,             // 01 57\n    InstDummy,             // 01 58\n    InstDummy,             // 01 59\n    InstDummy,             // 01 5A\n    InstDummy,             // 01 5B\n    InstDummy,             // 01 5C\n    InstDummy,             // 01 5D\n    InstDummy,             // 01 5E\n    InstDummy,             // 01 5F\n    InstDummy,             // 01 60\n    InstDummy,             // 01 61\n    InstDummy,             // 01 62\n    InstDummy,             // 01 63\n    InstDummy,             // 01 64\n    InstDummy,             // 01 65\n    InstDummy,             // 01 66\n    InstDummy,             // 01 67\n    InstDummy,             // 01 68\n    InstDummy,             // 01 69\n    InstDummy,             // 01 6A\n    InstDummy,             // 01 6B\n    InstDummy,             // 01 6C\n    InstDummy,             // 01 6D\n    InstDummy,             // 01 6E\n    InstDummy,             // 01 6F\n    InstDummy,             // 01 70\n    InstDummy,             // 01 71\n    InstDummy,             // 01 72\n    InstDummy,             // 01 73\n    InstDummy,             // 01 74\n    InstDummy,             // 01 75\n    InstDummy,             // 01 76\n    InstDummy,             // 01 77\n    InstDummy,             // 01 78\n    InstDummy,             // 01 79\n    InstDummy,             // 01 7A\n    InstDummy,             // 01 7B\n    InstDummy,             // 01 7C\n    InstDummy,             // 01 7D\n    InstDummy,             // 01 7E\n    InstDummy,             // 01 7F\n    InstDummy,             // 01 80\n    InstDummy,             // 01 81\n    InstDummy,             // 01 82\n    InstDummy,             // 01 83\n    InstDummy,             // 01 84\n    InstDummy,             // 01 85\n    InstDummy,             // 01 86\n    InstDummy,             // 01 87\n    InstDummy,             // 01 88\n    InstDummy,             // 01 89\n    InstDummy,             // 01 8A\n    InstDummy,             // 01 8B\n    InstDummy,             // 01 8C\n    InstDummy,             // 01 8D\n    InstDummy,             // 01 8E\n    InstDummy,             // 01 8F\n    InstDummy,             // 01 90\n    InstDummy,             // 01 91\n    InstDummy,             // 01 92\n    InstDummy,             // 01 93\n    InstDummy,             // 01 94\n    InstDummy,             // 01 95\n    InstDummy,             // 01 96\n    InstDummy,             // 01 97\n    InstDummy,             // 01 98\n    InstDummy,             // 01 99\n    InstDummy,             // 01 9A\n    InstDummy,             // 01 9B\n    InstDummy,             // 01 9C\n    InstDummy,             // 01 9D\n    InstDummy,             // 01 9E\n    InstDummy,             // 01 9F\n    InstDummy,             // 01 A0\n    InstDummy,             // 01 A1\n    InstDummy,             // 01 A2\n    InstDummy,             // 01 A3\n    InstDummy,             // 01 A4\n    InstDummy,             // 01 A5\n    InstDummy,             // 01 A6\n    InstDummy,             // 01 A7\n    InstDummy,             // 01 A8\n    InstDummy,             // 01 A9\n    InstDummy,             // 01 AA\n    InstDummy,             // 01 AB\n    InstDummy,             // 01 AC\n    InstDummy,             // 01 AD\n    InstDummy,             // 01 AE\n    InstDummy,             // 01 AF\n    InstDummy,             // 01 B0\n    InstDummy,             // 01 B1\n    InstDummy,             // 01 B2\n    InstDummy,             // 01 B3\n    InstDummy,             // 01 B4\n    InstDummy,             // 01 B5\n    InstDummy,             // 01 B6\n    InstDummy,             // 01 B7\n    InstDummy,             // 01 B8\n    InstDummy,             // 01 B9\n    InstDummy,             // 01 BA\n    InstDummy,             // 01 BB\n    InstDummy,             // 01 BC\n    InstDummy,             // 01 BD\n    InstDummy,             // 01 BE\n    InstDummy,             // 01 BF\n    InstDummy,             // 01 C0\n    InstDummy,             // 01 C1\n    InstDummy,             // 01 C2\n    InstDummy,             // 01 C3\n    InstDummy,             // 01 C4\n    InstDummy,             // 01 C5\n    InstDummy,             // 01 C6\n    InstDummy,             // 01 C7\n    InstDummy,             // 01 C8\n    InstDummy,             // 01 C9\n    InstDummy,             // 01 CA\n    InstDummy,             // 01 CB\n    InstDummy,             // 01 CC\n    InstDummy,             // 01 CD\n    InstDummy,             // 01 CE\n    InstDummy,             // 01 CF\n    InstDummy,             // 01 D0\n    InstDummy,             // 01 D1\n    InstDummy,             // 01 D2\n    InstDummy,             // 01 D3\n    InstDummy,             // 01 D4\n    InstDummy,             // 01 D5\n    InstDummy,             // 01 D6\n    InstDummy,             // 01 D7\n    InstDummy,             // 01 D8\n    InstDummy,             // 01 D9\n    InstDummy,             // 01 DA\n    InstDummy,             // 01 DB\n    InstDummy,             // 01 DC\n    InstDummy,             // 01 DD\n    InstDummy,             // 01 DE\n    InstDummy,             // 01 DF\n    InstDummy,             // 01 E0\n    InstDummy,             // 01 E1\n    InstDummy,             // 01 E2\n    InstDummy,             // 01 E3\n    InstDummy,             // 01 E4\n    InstDummy,             // 01 E5\n    InstDummy,             // 01 E6\n    InstDummy,             // 01 E7\n    InstDummy,             // 01 E8\n    InstDummy,             // 01 E9\n    InstDummy,             // 01 EA\n    InstDummy,             // 01 EB\n    InstDummy,             // 01 EC\n    InstDummy,             // 01 ED\n    InstDummy,             // 01 EE\n    InstDummy,             // 01 EF\n    InstDummy,             // 01 F0\n    InstDummy,             // 01 F1\n    InstDummy,             // 01 F2\n    InstDummy,             // 01 F3\n    InstDummy,             // 01 F4\n    InstDummy,             // 01 F5\n    InstDummy,             // 01 F6\n    InstDummy,             // 01 F7\n    InstDummy,             // 01 F8\n    InstDummy,             // 01 F9\n    InstDummy,             // 01 FA\n    InstDummy,             // 01 FB\n    InstDummy,             // 01 FC\n    InstDummy,             // 01 FD\n    InstDummy,             // 01 FE\n    InstDummy              // 01 FF\n};\n\nInstructionProc inline constexpr OpcodeTableUser1_SGPS3[256] = {\n    InstMSinit,            // 10 00\n    InstBGload,            // 10 01\n    InstBGswap,            // 10 02\n    InstBGsetColor,        // 10 03\n    InstBGsetLink,         // 10 04\n    InstCHAload,           // 10 05\n    InstCHAswap,           // 10 06\n    InstBGcopy,            // 10 07\n    InstCHAcopy,           // 10 08\n    InstSaveSlot,          // 10 09\n    InstSystemMain,        // 10 0A\n    InstCharaLayerLoad,    // 10 0B\n    InstGameInfoInit,      // 10 0C\n    InstCHAmove,           // 10 0D\n    InstBGloadEx,          // 10 0E\n    InstDummy,             // 10 0F\n    InstBGrelease,         // 10 10\n    InstCHArelease,        // 10 11\n    InstClearFlagChkOld,   // 10 12\n    InstOption,            // 10 13\n    InstSystemDataReset,   // 10 14\n    InstDebugData,         // 10 15\n    InstGetCharaPause,     // 10 16\n    InstBGfadeExpInit,     // 10 17\n    InstDummy,             // 10 18\n    InstDummy,             // 10 19\n    InstDummy,             // 10 1A\n    InstHelp,              // 10 1B\n    InstAchievementMenu,   // 10 1C\n    InstSoundMenu,         // 10 1D\n    InstAllClear,          // 10 1E\n    InstAlbum,             // 10 1F\n    InstMovieMode,         // 10 20\n    InstClistInit,         // 10 21\n    InstAutoSaveOld,       // 10 22\n    InstSaveMenu,          // 10 23\n    InstLoadData,          // 10 24\n    InstDummy,             // 10 25\n    InstDummy,             // 10 26\n    InstEncyclopedia,      // 10 27\n    InstSetPlayMode,       // 10 28\n    InstSetEVflag,         // 10 29\n    InstSetCutin,          // 10 2A\n    InstDummy,             // 10 2B\n    InstDummy,             // 10 2C\n    InstAchChkTitle,       // 10 2D\n    InstSetSceneViewFlag,  // 10 2E\n    InstChkClearFlag,      // 10 2F\n    InstBGeffectWave,      // 10 30\n    InstGeotag,            // 10 31\n    InstNameID,            // 10 32\n    InstTips,              // 10 33\n    InstTitleMenuOld,      // 10 34\n    InstDummy,             // 10 35\n    InstBGeffect,          // 10 36\n    InstPhoneSG,           // 10 37\n    InstUnk1038Darling,    // 10 38\n    InstTwipo,             // 10 39\n    InstUnk103A,           // 10 3A\n    InstDummy,             // 10 3B\n    InstDummy,             // 10 3C\n    InstDummy,             // 10 3D\n    InstDummy,             // 10 3E\n    InstDummy,             // 10 3F\n    InstDummy,             // 10 40\n    InstDummy,             // 10 41\n    InstDummy,             // 10 42\n    InstDummy,             // 10 43\n    InstDummy,             // 10 44\n    InstDummy,             // 10 45\n    InstDummy,             // 10 46\n    InstDummy,             // 10 47\n    InstDummy,             // 10 48\n    InstDummy,             // 10 49\n    InstDummy,             // 10 4A\n    InstDummy,             // 10 4B\n    InstDummy,             // 10 4C\n    InstDummy,             // 10 4D\n    InstDummy,             // 10 4E\n    InstDummy,             // 10 4F\n    InstDummy,             // 10 50\n    InstDummy,             // 10 51\n    InstDummy,             // 10 52\n    InstDummy,             // 10 53\n    InstDummy,             // 10 54\n    InstDummy,             // 10 55\n    InstDummy,             // 10 56\n    InstDummy,             // 10 57\n    InstDummy,             // 10 58\n    InstDummy,             // 10 59\n    InstDummy,             // 10 5A\n    InstDummy,             // 10 5B\n    InstDummy,             // 10 5C\n    InstDummy,             // 10 5D\n    InstDummy,             // 10 5E\n    InstDummy,             // 10 5F\n    InstDummy,             // 10 60\n    InstDummy,             // 10 61\n    InstDummy,             // 10 62\n    InstDummy,             // 10 63\n    InstDummy,             // 10 64\n    InstDummy,             // 10 65\n    InstDummy,             // 10 66\n    InstDummy,             // 10 67\n    InstDummy,             // 10 68\n    InstDummy,             // 10 69\n    InstDummy,             // 10 6A\n    InstDummy,             // 10 6B\n    InstDummy,             // 10 6C\n    InstDummy,             // 10 6D\n    InstDummy,             // 10 6E\n    InstDummy,             // 10 6F\n    InstDummy,             // 10 70\n    InstDummy,             // 10 71\n    InstDummy,             // 10 72\n    InstDummy,             // 10 73\n    InstDummy,             // 10 74\n    InstDummy,             // 10 75\n    InstDummy,             // 10 76\n    InstDummy,             // 10 77\n    InstDummy,             // 10 78\n    InstDummy,             // 10 79\n    InstDummy,             // 10 7A\n    InstDummy,             // 10 7B\n    InstDummy,             // 10 7C\n    InstDummy,             // 10 7D\n    InstDummy,             // 10 7E\n    InstDummy,             // 10 7F\n    InstDummy,             // 10 80\n    InstDummy,             // 10 81\n    InstDummy,             // 10 82\n    InstDummy,             // 10 83\n    InstDummy,             // 10 84\n    InstDummy,             // 10 85\n    InstDummy,             // 10 86\n    InstDummy,             // 10 87\n    InstDummy,             // 10 88\n    InstDummy,             // 10 89\n    InstDummy,             // 10 8A\n    InstDummy,             // 10 8B\n    InstDummy,             // 10 8C\n    InstDummy,             // 10 8D\n    InstDummy,             // 10 8E\n    InstDummy,             // 10 8F\n    InstDummy,             // 10 90\n    InstDummy,             // 10 91\n    InstDummy,             // 10 92\n    InstDummy,             // 10 93\n    InstDummy,             // 10 94\n    InstDummy,             // 10 95\n    InstDummy,             // 10 96\n    InstDummy,             // 10 97\n    InstDummy,             // 10 98\n    InstDummy,             // 10 99\n    InstDummy,             // 10 9A\n    InstDummy,             // 10 9B\n    InstDummy,             // 10 9C\n    InstDummy,             // 10 9D\n    InstDummy,             // 10 9E\n    InstDummy,             // 10 9F\n    InstDummy,             // 10 A0\n    InstDummy,             // 10 A1\n    InstDummy,             // 10 A2\n    InstDummy,             // 10 A3\n    InstDummy,             // 10 A4\n    InstDummy,             // 10 A5\n    InstDummy,             // 10 A6\n    InstDummy,             // 10 A7\n    InstDummy,             // 10 A8\n    InstDummy,             // 10 A9\n    InstDummy,             // 10 AA\n    InstDummy,             // 10 AB\n    InstDummy,             // 10 AC\n    InstDummy,             // 10 AD\n    InstDummy,             // 10 AE\n    InstDummy,             // 10 AF\n    InstDummy,             // 10 B0\n    InstDummy,             // 10 B1\n    InstDummy,             // 10 B2\n    InstDummy,             // 10 B3\n    InstDummy,             // 10 B4\n    InstDummy,             // 10 B5\n    InstDummy,             // 10 B6\n    InstDummy,             // 10 B7\n    InstDummy,             // 10 B8\n    InstDummy,             // 10 B9\n    InstDummy,             // 10 BA\n    InstDummy,             // 10 BB\n    InstDummy,             // 10 BC\n    InstDummy,             // 10 BD\n    InstDummy,             // 10 BE\n    InstDummy,             // 10 BF\n    InstDummy,             // 10 C0\n    InstDummy,             // 10 C1\n    InstDummy,             // 10 C2\n    InstDummy,             // 10 C3\n    InstDummy,             // 10 C4\n    InstDummy,             // 10 C5\n    InstDummy,             // 10 C6\n    InstDummy,             // 10 C7\n    InstDummy,             // 10 C8\n    InstDummy,             // 10 C9\n    InstDummy,             // 10 CA\n    InstDummy,             // 10 CB\n    InstDummy,             // 10 CC\n    InstDummy,             // 10 CD\n    InstDummy,             // 10 CE\n    InstDummy,             // 10 CF\n    InstDummy,             // 10 D0\n    InstDummy,             // 10 D1\n    InstDummy,             // 10 D2\n    InstDummy,             // 10 D3\n    InstDummy,             // 10 D4\n    InstDummy,             // 10 D5\n    InstDummy,             // 10 D6\n    InstDummy,             // 10 D7\n    InstDummy,             // 10 D8\n    InstDummy,             // 10 D9\n    InstDummy,             // 10 DA\n    InstDummy,             // 10 DB\n    InstDummy,             // 10 DC\n    InstDummy,             // 10 DD\n    InstDummy,             // 10 DE\n    InstDummy,             // 10 DF\n    InstDummy,             // 10 E0\n    InstDummy,             // 10 E1\n    InstDummy,             // 10 E2\n    InstDummy,             // 10 E3\n    InstDummy,             // 10 E4\n    InstDummy,             // 10 E5\n    InstDummy,             // 10 E6\n    InstDummy,             // 10 E7\n    InstDummy,             // 10 E8\n    InstDummy,             // 10 E9\n    InstDummy,             // 10 EA\n    InstDummy,             // 10 EB\n    InstDummy,             // 10 EC\n    InstDummy,             // 10 ED\n    InstDummy,             // 10 EE\n    InstDummy,             // 10 EF\n    InstDummy,             // 10 F0\n    InstDummy,             // 10 F1\n    InstDummy,             // 10 F2\n    InstDummy,             // 10 F3\n    InstDummy,             // 10 F4\n    InstDummy,             // 10 F5\n    InstDummy,             // 10 F6\n    InstDummy,             // 10 F7\n    InstDummy,             // 10 F8\n    InstDummy,             // 10 F9\n    InstDummy,             // 10 FA\n    InstDummy,             // 10 FB\n    InstDummy,             // 10 FC\n    InstDummy,             // 10 FD\n    InstDummy,             // 10 FE\n    InstDummy              // 10 FF\n};\n\n}\n\n}\n\n// clang-format on"
  },
  {
    "path": "src/vm/sc3stream.h",
    "content": "#pragma once\n\n#include <cstdint>\n#include <span>\n#include \"thread.h\"\n\nnamespace Impacto {\nnamespace Vm {\n\nclass Sc3Stream {\n public:\n  Sc3Stream(uint16_t* ptr) : Ptr(reinterpret_cast<uint8_t*>(ptr)) {}\n  Sc3Stream(uint8_t* ptr) : Ptr(ptr) {}\n  uint8_t ReadU8() {\n    uint8_t result = PeekU8();\n    Advance(1);\n    return result;\n  }\n\n  uint16_t ReadU16() {\n    uint16_t result = PeekU16();\n    Advance(2);\n    return result;\n  }\n  uint16_t PeekU16(int64_t offset = 0) const {\n    auto adjOffset = offset * sizeof(uint16_t);\n    return (PeekU8(adjOffset + 1) << 8) | PeekU8(adjOffset);\n  }\n  uint8_t PeekU8(int64_t offset = 0) const { return *(Ptr + offset); };\n  uint8_t* Data() const { return Ptr; }\n  void Advance(int64_t n) { Ptr += n; }\n\n private:\n  uint8_t* Ptr{};\n};\n}  // namespace Vm\n}  // namespace Impacto"
  },
  {
    "path": "src/vm/thread.cpp",
    "content": "#include \"thread.h\"\n#include \"vm.h\"\n\nnamespace Impacto {\nnamespace Vm {\n\nvoid* Sc3VmThread::GetMemberPointer(uint32_t offset) {\n  switch (offset) {\n    case TO_Flags:\n      return &Flags;\n    case TO_ExecPri:\n      return &ExecPriority;\n    case TO_ScrBuf:\n      return &ScriptBufferId;\n    case TO_WaitCount:\n      return &WaitCounter;\n    case TO_ScrParam:\n      return &ScriptParam;\n    case TO_ScrAddr:\n      return &IpOffset;\n    case TO_LoopCount:\n      return &LoopCounter;\n    case TO_LoopAddr:\n      return &LoopLabelNum;\n    case TO_RetCount:\n      return &CallStackDepth;\n    case TO_RetAddr:\n      return &ReturnAddresses;\n    case TO_RetScrBuf:\n      return &ReturnScriptBufferIds;\n    case TO_DrawPri:\n      return &DrawPriority;\n    case TO_DrawType:\n      return &DrawType;\n    case TO_Alpha:\n      return &Alpha;\n    case TO_Temp1:\n      return &Temp1;\n    case TO_Temp2:\n      return &Temp2;\n    default:\n      return &Variables[offset - TO_ThdVarBegin];\n      break;\n  }\n}\n\nuint8_t* Sc3VmThread::GetIp() const {\n  return &ScriptBuffers[ScriptBufferId][IpOffset];\n}\nvoid Sc3VmThread::SetIp(uint8_t* ptr) {\n  assert(ptr >= ScriptBuffers[ScriptBufferId].data() &&\n         ptr < ScriptBuffers[ScriptBufferId].data() +\n                   ScriptBuffers[ScriptBufferId].size());\n  IpOffset = static_cast<uint32_t>(ptr - ScriptBuffers[ScriptBufferId].data());\n}\n\nvoid Sc3VmThread::SetIp(BufferOffsetContext ctx) {\n  assert(ctx.IpOffset < ScriptBuffers[ctx.ScriptBufferId].size());\n  IpOffset = ctx.IpOffset;\n  ScriptBufferId = ctx.ScriptBufferId;\n}\n\n}  // namespace Vm\n}  // namespace Impacto"
  },
  {
    "path": "src/vm/thread.h",
    "content": "#pragma once\n\n#include \"../impacto.h\"\n\nnamespace Impacto {\n\nnamespace Vm {\n\nint constexpr MaxCallStackDepth = 8;\nint constexpr MaxThreadVars = 32;\n\nenum ThreadStateFlag {  // Applies to both individual threads and thread groups\n  TF_None = 0x0,\n  TF_Destroy = 0x8000000,\n  TF_Animate = 0x10000000,\n  TF_Display = 0x20000000,\n  TF_Pause = 0x40000000,\n  TF_Message = 0x80000000,\n};\n\nenum ThreadGroupControlType {\n  TC_Destroy = 0,\n  TC_Pause = 1,\n  TC_Start = 2,\n  TC_Hide = 3,\n  TC_Display = 4\n};\n\nenum ThreadMemberOffset {\n  TO_Flags = 0,\n  TO_ExecPri = 4,\n  TO_ScrBuf = 5,\n  TO_WaitCount = 6,\n  TO_ScrParam = 7,\n  TO_ScrAddr = 8,\n  TO_LoopCount = 9,\n  TO_LoopAddr = 10,\n  TO_RetCount = 11,\n  TO_RetAddr = 12,\n  TO_RetScrBuf = 20,\n  TO_DrawPri = 32,\n  TO_DrawType = 33,\n  TO_Alpha = 34,\n  TO_Temp1 = 45,\n  TO_Temp2 = 46,\n  TO_ThdVarBegin = 47\n};\n\nstruct BufferOffsetContext {\n  uint32_t ScriptBufferId;\n  uint32_t IpOffset;\n};\n\nclass Vm;\n\nstruct Sc3VmThread {\n  uint32_t Id;\n  uint32_t Flags;\n  Sc3VmThread* PreviousContext;\n  Sc3VmThread* NextContext;\n  Sc3VmThread* NextFreeContext;\n  uint32_t ExecPriority;\n  uint32_t ScriptBufferId;\n  uint32_t GroupId;\n  uint32_t WaitCounter;\n  uint32_t ScriptParam;\n  uint32_t IpOffset;\n  uint32_t LoopCounter;\n  uint16_t LoopLabelNum;\n  uint32_t CallStackDepth;\n  union {\n    uint32_t ReturnAddresses[MaxCallStackDepth];\n    uint16_t ReturnIds[MaxCallStackDepth];\n  };\n  uint32_t ReturnScriptBufferIds[MaxCallStackDepth];\n  uint32_t DrawPriority;\n  uint8_t DrawType;\n  uint32_t Alpha;\n  uint32_t Temp1;\n  uint32_t Temp2;\n  uint32_t Variables[MaxThreadVars];\n  uint32_t DialoguePageId;\n\n  void* GetMemberPointer(uint32_t offset);\n  uint8_t* GetIp() const;\n  void SetIp(uint8_t* ptr);\n  void SetIp(BufferOffsetContext ctx);\n};\n\n}  // namespace Vm\n\n}  // namespace Impacto"
  },
  {
    "path": "src/vm/vm.cpp",
    "content": "#include \"vm.h\"\n\n#include \"expression.h\"\n#include \"../log.h\"\n#include \"../io/io.h\"\n#include \"../game.h\"\n#include \"../mem.h\"\n#include \"interface/scene3d.h\"\n#include \"interface/input.h\"\n#include \"../character2d.h\"\n#include \"opcodetables_rne.h\"\n#include \"opcodetables_darling.h\"\n#include \"opcodetables_chlcc.h\"\n#include \"opcodetables_mo6tw.h\"\n#include \"opcodetables_mo7.h\"\n#include \"opcodetables_mo8.h\"\n#include \"opcodetables_dash.h\"\n#include \"opcodetables_cc.h\"\n#include \"opcodetables_sgps3.h\"\n#include \"opcodetables_chn.h\"\n#include \"../profile/game.h\"\n#include \"../profile/vm.h\"\n#include \"../profile/scriptinput.h\"\n#include \"../profile/scriptvars.h\"\n#include \"../profile/dialogue.h\"\n\nnamespace Impacto {\nnamespace Vm {\n\nusing namespace Profile::ScriptVars;\n\nstatic Sc3VmThread*\n    ThreadTable[MaxThreads];  // Table of ordered thread pointers\n                              // to be executed or \"drawn\"\nstatic uint32_t ThreadGroupState[MaxThreadGroups];  // Control states for thread\n                                                    // groups. Each thread group\n                                                    // is a doubly linked list\nstatic uint32_t ThreadGroupCount[MaxThreadGroups];  // Current number of\n                                                    // threads in a group\nstatic Sc3VmThread*\n    ThreadGroupHeads[MaxThreadGroups];  // Pointers to thread group\n                                        // doubly linked list heads\nstatic Sc3VmThread*\n    ThreadGroupTails[MaxThreadGroups];  // Pointers to thread group\n                                        // doubly linked list tails\nstatic Sc3VmThread* NextFreeThreadCtx;  // Next free thread context in the\n                                        // thread pool\n\nstatic const InstructionProc* OpcodeTableSystem;\nstatic const InstructionProc* OpcodeTableUser1;\nstatic const InstructionProc* OpcodeTableGraph;\nstatic const InstructionProc* OpcodeTableGraph3D;\n\nstatic void CreateThreadExecTable();\nstatic void SortThreadExecTable();\nstatic void CreateThreadDrawTable();\nstatic void SortThreadDrawTable();\nstatic void DrawAllThreads();\n[[maybe_unused]] static void DestroyScriptThreads(uint32_t scriptBufferId);\nstatic void DestroyThreadGroup(uint32_t groupId);\n\nvoid Init() {\n  ImpLog(LogLevel::Info, LogChannel::VM,\n         \"Initializing SC3 virtual machine\\n**** Start apprication ****\\n\");\n\n  Profile::Vm::Configure();\n  Profile::ScriptInput::Configure();\n  Profile::ScriptVars::Configure();\n\n  switch (Profile::Vm::GameInstructionSet) {\n    case InstructionSet::RNE: {\n      OpcodeTableSystem = OpcodeTableSystem_RNE;\n      OpcodeTableGraph = OpcodeTableGraph_RNE;\n      OpcodeTableGraph3D = OpcodeTableGraph3D_RNE;\n      OpcodeTableUser1 = OpcodeTableUser1_RNE;\n      break;\n    }\n    case InstructionSet::Darling: {\n      OpcodeTableSystem = OpcodeTableSystem_Darling;\n      OpcodeTableGraph = OpcodeTableGraph_Darling;\n      OpcodeTableUser1 = OpcodeTableUser1_Darling;\n      break;\n    }\n    case InstructionSet::CHLCC: {\n      OpcodeTableSystem = OpcodeTableSystem_CHLCC;\n      OpcodeTableGraph = OpcodeTableGraph_CHLCC;\n      OpcodeTableUser1 = OpcodeTableUser1_CHLCC;\n      break;\n    }\n    case InstructionSet::MO6TW: {\n      OpcodeTableSystem = OpcodeTableSystem_MO6TW;\n      OpcodeTableGraph = OpcodeTableGraph_MO6TW;\n      OpcodeTableUser1 = OpcodeTableUser1_MO6TW;\n      break;\n    }\n    case InstructionSet::MO7: {\n      OpcodeTableSystem = OpcodeTableSystem_MO7;\n      OpcodeTableGraph = OpcodeTableGraph_MO7;\n      OpcodeTableUser1 = OpcodeTableUser1_MO7;\n      break;\n    }\n    case InstructionSet::Dash: {\n      OpcodeTableSystem = OpcodeTableSystem_Dash;\n      OpcodeTableGraph = OpcodeTableGraph_Dash;\n      OpcodeTableGraph3D = OpcodeTableGraph3D_Dash;\n      OpcodeTableUser1 = OpcodeTableUser1_Dash;\n      break;\n    }\n    case InstructionSet::CC: {\n      OpcodeTableSystem = OpcodeTableSystem_CC;\n      OpcodeTableGraph = OpcodeTableGraph_CC;\n      OpcodeTableUser1 = OpcodeTableUser1_CC;\n      break;\n    }\n    case InstructionSet::SGPS3: {\n      OpcodeTableSystem = OpcodeTableSystem_SGPS3;\n      OpcodeTableGraph = OpcodeTableGraph_SGPS3;\n      OpcodeTableUser1 = OpcodeTableUser1_SGPS3;\n      break;\n    }\n    case InstructionSet::MO8: {\n      OpcodeTableSystem = OpcodeTableSystem_MO8;\n      OpcodeTableGraph = OpcodeTableGraph_MO8;\n      OpcodeTableUser1 = OpcodeTableUser1_MO8;\n      break;\n    }\n    case InstructionSet::CHN: {\n      OpcodeTableSystem = OpcodeTableSystem_CHN;\n      OpcodeTableGraph = OpcodeTableGraph_CHN;\n      OpcodeTableUser1 = OpcodeTableUser1_CHN;\n      break;\n    }\n    default: {\n      ImpLog(LogLevel::Fatal, LogChannel::VM, \"Unsupported instruction set\\n\");\n      Window->Shutdown();\n      break;\n    }\n  }\n\n  for (int i = 0; i < MaxThreads - 1; i++) {\n    memset(&ThreadPool[i], 0, sizeof(Sc3VmThread));\n    ThreadPool[i].NextFreeContext = &ThreadPool[i + 1];\n    ThreadPool[i].Id = i;\n  }\n\n  NextFreeThreadCtx = ThreadPool;\n  memset(&ThreadPool[MaxThreads - 1], 0, sizeof(Sc3VmThread));\n  ThreadPool[MaxThreads - 1].Id = MaxThreads - 1;\n\n  for (int i = 0; i < MaxThreadGroups; i++) {\n    ThreadGroupState[i] = TF_Display;\n    ThreadGroupHeads[i] = NULL;\n    ThreadGroupTails[i] = NULL;\n  }\n\n  memset(&ThreadGroupCount, 0, MaxThreadGroups * 4);\n\n  bool res =\n      LoadScript(Profile::Vm::StartScriptBuffer, Profile::Vm::StartScript);\n  if (Profile::Vm::UseMsbStrings) {\n    res = LoadMsb(Profile::Vm::StartScriptBuffer,\n                  Profile::Vm::UseSeparateMsbArchive\n                      ? Profile::Vm::StartScript\n                      : Profile::Vm::StartScript - 1);\n  }\n  if (res) {\n    Sc3VmThread* startupThd = CreateThread(0);\n    startupThd->GroupId = 0;\n    startupThd->ScriptBufferId = Profile::Vm::StartScriptBuffer;\n    startupThd->IpOffset =\n        ScriptGetLabelAddress(Profile::Vm::StartScriptBuffer, 0);\n  }\n\n  ScrWork[2200] = 1;  // Global animation multiplier maybe?... Set in GameInit()\n}\n\nbool LoadScript(uint32_t bufferId, uint32_t scriptId) {\n  Io::FileMeta meta;\n  Io::VfsGetMeta(\"script\", scriptId, &meta);\n  ImpLogSlow(LogLevel::Debug, LogChannel::VM, \"Loading script \\\"{:s}\\\"\\n\",\n             meta.FileName);\n\n  void* file;\n  int64_t fileSize;\n  IoError err = Io::VfsSlurp(\"script\", scriptId, file, fileSize);\n  if (err != IoError_OK) {\n    ImpLog(LogLevel::Error, LogChannel::VM,\n           \"Could not read script file for {:d}\\n\", scriptId);\n    return false;\n  }\n  ScriptBuffers[bufferId] = std::span(static_cast<uint8_t*>(file), fileSize);\n  ScrWork[SW_SCRIPTNO0 + bufferId] = scriptId;\n  LoadedScriptMetas[bufferId] = meta;\n  return true;\n}\n\nbool LoadMsb(uint32_t bufferId, uint32_t fileId) {\n  Io::FileMeta meta;\n  std::string mountPoint =\n      Profile::Vm::UseSeparateMsbArchive ? \"mes\" : \"script\";\n  Io::VfsGetMeta(mountPoint, fileId, &meta);\n  ImpLogSlow(LogLevel::Debug, LogChannel::VM, \"Loading msb file \\\"{:s}\\\"\\n\",\n             meta.FileName);\n\n  void* file;\n  int64_t fileSize;\n  IoError err = Io::VfsSlurp(mountPoint, fileId, file, fileSize);\n  if (err != IoError_OK) {\n    ImpLog(LogLevel::Error, LogChannel::VM,\n           \"Could not read msb file for {:d}\\n\", fileId);\n    return false;\n  }\n  MsbBuffers[bufferId] = std::span(static_cast<uint8_t*>(file), fileSize);\n  return true;\n}\n\nSc3VmThread* CreateThread(uint32_t groupId) {\n  if (!NextFreeThreadCtx) return 0;\n  Sc3VmThread* thread = NextFreeThreadCtx;\n  NextFreeThreadCtx = NextFreeThreadCtx->NextFreeContext;\n  int id = thread->Id;\n  *thread = Sc3VmThread{};\n  thread->Id = id;\n  if (ThreadGroupHeads[groupId] == NULL) {\n    ThreadGroupHeads[groupId] = thread;\n    ThreadGroupTails[groupId] = thread;\n  } else {\n    Sc3VmThread* prevGroupTail = ThreadGroupTails[groupId];\n    thread->PreviousContext = prevGroupTail;\n    prevGroupTail->NextContext = thread;\n    ThreadGroupTails[groupId] = thread;\n  }\n  ++ThreadGroupCount[groupId];\n  return thread;\n}\n\n// This also destroys thread groups and threads that have the TF_Destroy flag\n// set before building the execution table\nstatic void CreateThreadExecTable() {\n  int tblIndex = 0;\n\n  for (int i = 0; i < MaxThreadGroups; i++) {\n    if (ThreadGroupState[i] & TF_Destroy) {\n      DestroyThreadGroup(i);\n      ThreadGroupState[i] ^= TF_Destroy;\n    } else if (!(ThreadGroupState[i] & TF_Pause)) {\n      Sc3VmThread* groupThread = ThreadGroupHeads[i];\n      if (groupThread == NULL) continue;\n      do {\n        if (groupThread->Flags & TF_Destroy) {\n          Sc3VmThread* next = groupThread->NextContext;\n          DestroyThread(groupThread);\n          groupThread = next;\n        } else if (!(groupThread->Flags & TF_Pause)) {\n          ThreadTable[tblIndex++] = groupThread;\n          groupThread = groupThread->NextContext;\n        } else {\n          groupThread = groupThread->NextContext;\n        }\n      } while (groupThread != NULL);\n    }\n  }\n  ThreadTable[tblIndex] = 0;\n}\n\nstatic void SortThreadExecTable() {\n  int i = 0;\n  while (ThreadTable[i]) {\n    int j = 0;\n    while (ThreadTable[j + i] && ThreadTable[j + 1]) {\n      if (ThreadTable[j]->ExecPriority > ThreadTable[j + 1]->ExecPriority) {\n        Sc3VmThread* temp = ThreadTable[j];\n        ThreadTable[j] = ThreadTable[j + 1];\n        ThreadTable[j + 1] = temp;\n      }\n      j++;\n    }\n    i++;\n  }\n}\n\nvoid Update(float dt) {\n  CreateThreadExecTable();\n  SortThreadExecTable();\n\n  int cnt = 0;\n  while (ThreadTable[cnt]) {\n    RunThread(ThreadTable[cnt++]);\n  }\n\n  cnt = 0;\n  while (ThreadTable[cnt]) {\n    if (ThreadTable[cnt]->Flags & TF_Destroy) {\n      DestroyThread(ThreadTable[cnt]);\n    }\n    cnt++;\n  }\n\n  DrawAllThreads();\n\n  if (+Profile::GameFeatures & +GameFeature::Scene3D) {\n    Interface::UpdateScene3D(dt);\n  } else {\n    Character2D::UpdateEyeMouth();\n  }\n}\n\nstatic void CreateThreadDrawTable() {\n  int tblIndex = 0;\n\n  for (int i = 0; i < MaxThreadGroups; i++) {\n    if (ThreadGroupState[i] & TF_Display) {\n      Sc3VmThread* groupThread = ThreadGroupHeads[i];\n      if (groupThread == NULL) continue;\n      do {\n        if (groupThread->Flags & TF_Display) {\n          ThreadTable[tblIndex++] = groupThread;\n        }\n        groupThread = groupThread->NextContext;\n      } while (groupThread != NULL);\n    }\n  }\n  ThreadTable[tblIndex] = 0;\n}\n\nstatic void SortThreadDrawTable() {\n  int i = 0;\n  while (ThreadTable[i]) {\n    int j = 0;\n    while (ThreadTable[j + i] && ThreadTable[j + 1]) {\n      if (ThreadTable[j]->DrawPriority > ThreadTable[j + 1]->DrawPriority) {\n        Sc3VmThread* temp = ThreadTable[j];\n        ThreadTable[j] = ThreadTable[j + 1];\n        ThreadTable[j + 1] = temp;\n      }\n      j++;\n    }\n    i++;\n  }\n}\n\nstatic void DrawAllThreads() {\n  CreateThreadDrawTable();\n  SortThreadDrawTable();\n\n  std::fill(std::begin(Game::DrawComponents), std::end(Game::DrawComponents),\n            Game::DrawComponentType::None);\n\n  int cnt = 0;\n  while (ThreadTable[cnt]) {\n    if (ThreadTable[cnt]->Flags & TF_Display) {\n      if (!magic_enum::enum_contains<Game::DrawComponentType>(\n              ThreadTable[cnt]->DrawType)) {\n        ImpLog(LogLevel::Warning, LogChannel::VM,\n               \"{} is not in the list of DrawTypes\\n\",\n               ThreadTable[cnt]->DrawType);\n      }\n      Game::DrawComponents[cnt] =\n          Game::DrawComponentType(ThreadTable[cnt]->DrawType);\n      if (Profile::Vm::RestartMaskUsesThreadAlpha &&\n          ThreadTable[cnt]->DrawType == +Game::DrawComponentType::Mask) {\n        ScrWork[SW_RESTARTMASK] = ThreadTable[cnt]->Alpha;\n      }\n    }\n    cnt++;\n  }\n}\n\nvoid DestroyThread(Sc3VmThread* thread) {\n  if (ThreadGroupHeads[thread->GroupId] == thread) {\n    ThreadGroupHeads[thread->GroupId] = thread->NextContext;\n  } else if (ThreadGroupTails[thread->GroupId] == thread) {\n    ThreadGroupTails[thread->GroupId] = thread->PreviousContext;\n  }\n  Sc3VmThread* previous = thread->PreviousContext;\n  Sc3VmThread* next = thread->NextContext;\n  if (next != 0) {\n    thread->NextContext->PreviousContext = previous;\n  }\n  if (previous != 0) {\n    thread->PreviousContext->NextContext = next;\n  }\n  --ThreadGroupCount[thread->GroupId];\n  int id = thread->Id;\n  memset(thread, 0, sizeof(Sc3VmThread));\n  thread->Id = id;\n  thread->NextFreeContext = NextFreeThreadCtx;\n  NextFreeThreadCtx = thread;\n  thread->PreviousContext = previous;\n  thread->NextContext = next;\n}\n\nvoid DestroyScriptThreads(uint32_t scriptBufferId) {\n  int cnt = 0;\n  while (ThreadTable[cnt]) {\n    if (ThreadTable[cnt]->ScriptBufferId == scriptBufferId) {\n      DestroyThread(ThreadTable[cnt]);\n    }\n    cnt++;\n  }\n}\n\nstatic void DestroyThreadGroup(uint32_t groupId) {\n  Sc3VmThread* groupThread = ThreadGroupHeads[groupId];\n  if (groupThread != NULL) {\n    do {\n      Sc3VmThread* next = groupThread->NextContext;\n      DestroyThread(groupThread);\n      groupThread = next;\n    } while (groupThread != NULL);\n  }\n  ThreadGroupHeads[groupId] = NULL;\n  ThreadGroupTails[groupId] = NULL;\n}\n\nvoid ControlThreadGroup(ThreadGroupControlType controlType, uint32_t groupId) {\n  switch (controlType) {\n    case TC_Destroy:\n      ThreadGroupState[groupId] |= TF_Destroy;\n      break;\n    case TC_Pause:\n      ThreadGroupState[groupId] |= TF_Pause;\n      break;\n    case TC_Start:\n      ThreadGroupState[groupId] =\n          (ThreadGroupState[groupId] | TF_Pause) ^ TF_Pause;\n      break;\n    case TC_Hide:\n      ThreadGroupState[groupId] =\n          (ThreadGroupState[groupId] | TF_Display) ^ TF_Display;\n      break;\n    case TC_Display:\n      ThreadGroupState[groupId] |= TF_Display;\n      break;\n    default:\n      break;\n  }\n}\n\nvoid RunThread(Sc3VmThread* thread) {\n  uint8_t* scrVal;\n  uint32_t opcodeGrp;\n  uint32_t opcode;\n  uint32_t opcodeGrp1;\n\n  ImpLog(LogLevel::Trace, LogChannel::VM, \"Running thread ID = {:d}\\n\",\n         thread->Id);\n\n#ifndef IMPACTO_DISABLE_IMGUI\n  if (DebugThreadId == thread->Id) {\n    if (DebuggerBreak && !DebuggerStepRequest && !DebuggerContinueRequest)\n      return;\n  }\n#endif\n\n  BlockCurrentScriptThread = 0;\n  do {\n    auto scriptIp = thread->IpOffset;\n#ifndef IMPACTO_DISABLE_IMGUI\n    if (!DebuggerStepRequest && !DebuggerContinueRequest) {\n      for (const auto& breakpoint : DebuggerBreakpoints) {\n        uint32_t currentScriptId = LoadedScriptMetas[thread->ScriptBufferId].Id;\n        if (currentScriptId == breakpoint.second.first &&\n            scriptIp == breakpoint.second.second) {\n          DebuggerBreak = true;\n          return;\n        }\n      }\n    }\n    if (DebugThreadId == thread->Id) {\n      DebuggerStepRequest = false;\n      if (DebuggerContinueRequest) {\n        DebuggerContinueRequest = false;\n        DebuggerBreak = false;\n      }\n    }\n#endif\n\n    scrVal = thread->GetIp();\n    opcodeGrp = *scrVal;\n    if ((uint8_t)opcodeGrp == 0xFE) {\n      thread->IpOffset += 1;\n      ExpressionEval(thread);\n    } else {\n      opcode = *(scrVal + 1);\n      opcodeGrp1 = opcodeGrp & 0x7F;\n\n      ImpLog(LogLevel::Trace, LogChannel::VM,\n             \"Address: {:#0x} Opcode: {:02x}:{:02x} ScriptBuffer: {:d}\\n\",\n             scriptIp, opcodeGrp1, opcode, thread->ScriptBufferId);\n\n      if (opcodeGrp1 == 0x10) {\n        OpcodeTableUser1[opcode](thread);\n      } else if (opcodeGrp1 == 0x02) {\n        OpcodeTableGraph3D[opcode](thread);\n      } else if (opcodeGrp1 == 0x01) {\n        OpcodeTableGraph[opcode](thread);\n      } else if (!opcodeGrp1) {\n        OpcodeTableSystem[opcode](thread);\n      } else {\n        ImpLog(LogLevel::Error, LogChannel::VM,\n               \"Thread CRASH! Unknown opcode. Attempting recovery. Address: \"\n               \"{:#0x} Opcode: {:02x}:{:02x} ScriptBuffer: {:d}\\n\",\n               scriptIp, opcodeGrp1, opcode, thread->ScriptBufferId);\n        if (thread->CallStackDepth) {\n          thread->CallStackDepth--;\n          uint32_t retBufferId =\n              thread->ReturnScriptBufferIds[thread->CallStackDepth];\n          thread->IpOffset = thread->ReturnAddresses[thread->CallStackDepth];\n          thread->ScriptBufferId = retBufferId;\n        } else {\n          ImpLog(LogLevel::Error, LogChannel::VM,\n                 \"Call stack empty, attempting instruction skip (will most \"\n                 \"likely result in a hang).\\n\");\n          while (*(thread->GetIp()) != 0xFE) {\n            thread->IpOffset++;\n          }\n        }\n      }\n    }\n#ifndef IMPACTO_DISABLE_IMGUI\n    if (DebugThreadId == thread->Id && (DebuggerBreak || DebuggerAlwaysBlock))\n      BlockCurrentScriptThread = 1;\n#endif\n  } while (!BlockCurrentScriptThread);\n}\n\nuint32_t ScriptGetLabelSize(uint32_t scriptBufferId, uint32_t labelNum) {\n  uint32_t labelAddressRel = ScriptGetLabelAddress(scriptBufferId, labelNum);\n\n  uint8_t* labelTableAdr = (uint8_t*)&ScriptBuffers[scriptBufferId][12];\n  uint8_t* nextLabelTableEntryAdr =\n      &labelTableAdr[(labelNum + 1) * sizeof(uint32_t)];\n  uint32_t nextLabelTableAdrRel =\n      SDL_SwapLE32(UnalignedRead<uint32_t>(nextLabelTableEntryAdr));\n  uint8_t* firstLabelAdr =\n      &ScriptBuffers[scriptBufferId][ScriptGetLabelAddress(scriptBufferId, 0)];\n\n  if (nextLabelTableEntryAdr == firstLabelAdr || nextLabelTableAdrRel == 0) {\n    uint32_t stringTableAdrRel = SDL_SwapLE32(\n        UnalignedRead<uint32_t>(&ScriptBuffers[scriptBufferId][4]));\n    return stringTableAdrRel - labelAddressRel;\n  } else {\n    return nextLabelTableAdrRel - labelAddressRel;\n  }\n}\n\nuint32_t ScriptGetLabelAddress(uint32_t scriptBufferId, uint32_t labelNum) {\n  uint8_t* labelTableAdr = (uint8_t*)&ScriptBuffers[scriptBufferId][12];\n  uint32_t labelAdrRel = SDL_SwapLE32(\n      UnalignedRead<uint32_t>(&labelTableAdr[labelNum * sizeof(uint32_t)]));\n  return labelAdrRel;\n}\n\nuint32_t ScriptGetStrAddress(uint32_t scriptBufferId, uint32_t mesNum) {\n  uint32_t stringTableAdrRel =\n      SDL_SwapLE32(UnalignedRead<uint32_t>(&ScriptBuffers[scriptBufferId][4]));\n  uint8_t* stringTableAdr =\n      (uint8_t*)&ScriptBuffers[scriptBufferId][stringTableAdrRel];\n  uint32_t stringAdrRel = SDL_SwapLE32(\n      UnalignedRead<uint32_t>(&stringTableAdr[mesNum * sizeof(uint32_t)]));\n  return stringAdrRel;\n}\n\nBufferOffsetContext ScriptGetTextTableStrAddress(uint32_t textTableId,\n                                                 uint32_t strNum) {\n  uint32_t scriptBufferId = TextTable[textTableId].scriptBufferId;\n  uint32_t stringTableAdrRel =\n      SDL_SwapLE32(UnalignedRead<uint32_t>(&ScriptBuffers[scriptBufferId][4]));\n  uint8_t* stringTableAdr =\n      (uint8_t*)&ScriptBuffers[scriptBufferId][stringTableAdrRel];\n\n  auto [textScrBufId, labelOffset] = TextTable[textTableId];\n  uint8_t* textTable = &ScriptBuffers[textScrBufId][labelOffset];\n  uint16_t mesNum =\n      UnalignedRead<uint16_t>(&textTable[strNum * sizeof(uint16_t)]);\n\n  uint32_t stringAdrRel = SDL_SwapLE32(\n      UnalignedRead<uint32_t>(&stringTableAdr[mesNum * sizeof(uint32_t)]));\n  return {scriptBufferId, stringAdrRel};\n}\n\nuint32_t ScriptGetRetAddress(uint32_t scriptBufferId, uint32_t retNum) {\n  uint32_t returnTableAdrRel =\n      SDL_SwapLE32(UnalignedRead<uint32_t>(&ScriptBuffers[scriptBufferId][8]));\n  uint8_t* returnTableAdr =\n      (uint8_t*)&ScriptBuffers[scriptBufferId][returnTableAdrRel];\n  uint32_t returnAdrRel = SDL_SwapLE32(\n      UnalignedRead<uint32_t>(&returnTableAdr[retNum * sizeof(uint32_t)]));\n  return returnAdrRel;\n}\n\nuint32_t MsbGetStrAddress(uint32_t msbBufferId, uint32_t mesNum) {\n  uint32_t languageCount =\n      SDL_SwapLE32(UnalignedRead<uint32_t>(&MsbBuffers[msbBufferId][4]));\n  uint32_t stringAreaStartRel =\n      SDL_SwapLE32(UnalignedRead<uint32_t>(&MsbBuffers[msbBufferId][12]));\n  uint8_t* tableAdrRel = (uint8_t*)&MsbBuffers[msbBufferId][16];\n\n  if (mesNum != 0) {\n    while (true) {\n      uint32_t id = SDL_SwapLE32(UnalignedRead<uint32_t>(\n          &tableAdrRel[(languageCount + 1) * sizeof(uint32_t)]));\n      tableAdrRel += (languageCount + 1) * sizeof(uint32_t);\n      if (id == mesNum) break;\n    }\n  }\n\n  uint32_t stringOffset =\n      SDL_SwapLE32(UnalignedRead<uint32_t>(&tableAdrRel[1 * sizeof(uint32_t)]));\n\n  return stringAreaStartRel + stringOffset;\n}\n\n}  // namespace Vm\n}  // namespace Impacto"
  },
  {
    "path": "src/vm/vm.h",
    "content": "#pragma once\n\n#include <span>\n#include \"thread.h\"\n#include \"../io/vfs.h\"\n#include <magic_enum/magic_enum.hpp>\n\n#define VmInstruction(name) void name(Sc3VmThread* thread)\n\nnamespace Impacto {\nnamespace Vm {\n\nenum class InstructionSet : int {\n  RNE,\n  Darling,\n  CHLCC,\n  MO6TW,\n  MO7,\n  Dash,\n  CC,\n  SGPS3,\n  MO8,\n  CHN,\n};\nusing InstructionProc = auto (*)(Sc3VmThread* thread) -> void;\n\nint constexpr MaxLoadedScripts = 16;\nint constexpr MaxThreads = 100;\nint constexpr MaxThreadGroups = 12;\n\nuint32_t ScriptGetLabelSize(uint32_t scriptBufferId, uint32_t labelNum);\nuint32_t ScriptGetLabelAddress(uint32_t scriptBufferId, uint32_t labelNum);\nuint32_t ScriptGetStrAddress(uint32_t scriptBufferId, uint32_t strNum);\n\nBufferOffsetContext ScriptGetTextTableStrAddress(uint32_t textTableId,\n                                                 uint32_t strNum);\nuint32_t ScriptGetRetAddress(uint32_t scriptBufferId, uint32_t retNum);\nuint32_t MsbGetStrAddress(uint32_t scriptBufferId, uint32_t mesNum);\n\nvoid Init();\nvoid Update(float dt);\n\nbool LoadScript(uint32_t bufferId, uint32_t scriptId);\nbool LoadMsb(uint32_t bufferId, uint32_t fileId);\n\nSc3VmThread* CreateThread(uint32_t groupId);\nvoid ControlThreadGroup(ThreadGroupControlType controlType, uint32_t groupId);\nvoid DestroyThread(Sc3VmThread* thread);\nvoid RunThread(Sc3VmThread* thread);\n\ninline std::span<uint8_t> ScriptBuffers[MaxLoadedScripts];\ninline std::span<uint8_t> MsbBuffers[MaxLoadedScripts];\n\ninline Io::FileMeta LoadedScriptMetas[MaxLoadedScripts];\n\ninline Sc3VmThread ThreadPool[MaxThreads];  // Main thread pool where all the\n                                            // thread objects are stored\n\ninline bool BlockCurrentScriptThread;\ninline uint32_t SwitchValue;  // Used in InstSwitch and InstCase\n\nstruct TextTableEntry {\n  uint8_t scriptBufferId;\n  uint32_t labelAdr;\n};\n\ninline TextTableEntry TextTable[16];\n\n#ifndef IMPACTO_DISABLE_IMGUI\ninline uint32_t DebugThreadId = std::numeric_limits<uint32_t>::max();\ninline bool DebuggerBreak = false;\ninline bool DebuggerStepRequest = false;\ninline bool DebuggerContinueRequest = false;\ninline bool DebuggerAlwaysBlock = false;\ninline std::map<int, std::pair<uint32_t, uint32_t>> DebuggerBreakpoints;\n#endif\n\n}  // namespace Vm\n}  // namespace Impacto"
  },
  {
    "path": "src/voicetable.cpp",
    "content": "#include \"voicetable.h\"\n\n#include \"io/io.h\"\n#include \"io/vfs.h\"\n#include \"log.h\"\n\nnamespace Impacto {\nbool VoiceTable::LoadSync(uint32_t id) {\n  Io::Stream* stream;\n  int64_t err = Io::VfsOpen(\"system\", id, &stream);\n  if (err != IoError_OK) return false;\n\n  VoiceFileCount = Io::ReadBE<uint16_t>(stream);\n  Io::ReadBE<uint16_t>(stream);\n\n  for (size_t i = 0; i < VoiceFileCount; ++i) {\n    uint16_t dataIndex = Io::ReadBE<uint16_t>(stream);\n    uint16_t voiceLengthSecTimes6 = Io::ReadLE<uint16_t>(stream);\n    VoiceMeta voiceMeta{dataIndex, voiceLengthSecTimes6};\n    TableOfContents[i] = voiceMeta;\n  }\n\n  [[maybe_unused]] const size_t endToc = 4 * VoiceFileCount + 4;\n  assert((size_t)stream->Position == endToc);\n\n  LipSyncData.resize(stream->Meta.Size - stream->Position);\n  stream->Read(LipSyncData.data(), LipSyncData.size());\n\n  delete stream;\n  return true;\n}\n\nvoid VoiceTable::UnloadSync() {\n  VoiceFileCount = 0;\n  TableOfContents.clear();\n  LipSyncData.resize(0);\n}\n\nvoid VoiceTable::MainThreadOnLoad(bool result) {}\n\n}  // namespace Impacto"
  },
  {
    "path": "src/voicetable.h",
    "content": "#pragma once\n\n#include <ankerl/unordered_dense.h>\n#include <unordered_map>\n#include <map>\n#include <vector>\n#include \"loadable.h\"\n#include \"io/stream.h\"\n\nnamespace Impacto {\nstruct VoiceMeta {\n  uint16_t DataIndex;\n  uint16_t VoiceLengthSecTimes6;\n};\n\nclass VoiceTable : public Loadable<VoiceTable, bool, uint32_t> {\n  friend class Loadable<VoiceTable, bool, uint32_t>;\n\n public:\n  uint8_t GetVoiceData(uint32_t id, size_t index) {\n    return LipSyncData[TableOfContents[id].DataIndex * 4 + index];\n  }\n\n protected:\n  bool LoadSync(uint32_t id);\n  void UnloadSync();\n  void MainThreadOnLoad(bool result);\n\n private:\n  size_t VoiceFileCount = 0;\n  std::map<size_t, VoiceMeta> TableOfContents;\n  std::vector<uint8_t> LipSyncData;\n};\n\ninline VoiceTable VoiceTableData;\n}  // namespace Impacto"
  },
  {
    "path": "src/workqueue.cpp",
    "content": "#include \"workqueue.h\"\n\n#include \"impacto.h\"\n#include <cassert>\n#include <vector>\n#include <utility>\n\n#if IMPACTO_HAVE_THREADS\n#include <thread>\n#if __SWITCH__\n#define __unix__\n#endif\n#include <blockingconcurrentqueue.h>\n#if __SWITCH__\n#undef __unix__\n#endif\n#endif\n\nnamespace Impacto {\nnamespace WorkQueue {\n\nstruct WorkItem {\n  void* Data;\n  WorkProc Perform;\n  WorkCompletionCallbackProc OnComplete;\n  void Handle();\n};\n\nstatic uint32_t WorkCompletedEventType = 0;\n\nstatic void InitEventType() {\n  assert(WorkCompletedEventType == 0);\n  WorkCompletedEventType = SDL_RegisterEvents(1);\n  assert(WorkCompletedEventType != 0);\n}\n\n#if IMPACTO_HAVE_THREADS\n\nstatic std::vector<std::thread> ThreadPool;\nstatic moodycamel::BlockingConcurrentQueue<WorkItem> QueueItems;\nstatic bool StopWorkers = false;\n\nstatic void WorkerThread() {\n  while (true) {\n    WorkItem item;\n    bool hasItem =\n        QueueItems.wait_dequeue_timed(item, std::chrono::milliseconds(5));\n    if (StopWorkers) {\n      return;\n    }\n    if (hasItem) {\n      item.Handle();\n    }\n  }\n}\n\nvoid Init() {\n  InitEventType();\n  const uint32_t numThreads =\n      std::min(static_cast<int>(std::thread::hardware_concurrency()), 4);\n  for (uint32_t i = 0; i < numThreads; ++i) {\n    ThreadPool.emplace_back(std::thread(WorkerThread));\n  }\n}\n\nvoid Push(void* data, WorkProc worker,\n          WorkCompletionCallbackProc completionCallback) {\n  WorkItem item;\n  item.Data = data;\n  item.Perform = worker;\n  item.OnComplete = completionCallback;\n  if (ThreadPool.empty())\n    item.Handle();\n  else\n    QueueItems.enqueue(std::move(item));\n}\nvoid StopWorkQueue() {\n  StopWorkers = true;\n  for (auto& thd : ThreadPool) {\n    thd.join();\n  }\n  ThreadPool.clear();\n}\n\n#else\n\n// If we don't have threads (i.e. on web), do each item right as it comes in for\n// now, no actual queue involved.\n\nvoid Init() { InitEventType(); }\nvoid Push(void* data, WorkProc worker,\n          WorkCompletionCallbackProc completionCallback) {\n  WorkItem item;\n  item.Data = data;\n  item.Perform = worker;\n  item.OnComplete = completionCallback;\n  item.Handle();\n}\nvoid StopWorkQueue() {}\n\n#endif\n\nvoid WorkItem::Handle() {\n  Perform(Data);\n  WorkItem* copy = new WorkItem(*this);\n  SDL_Event evt{};\n  evt.type = WorkCompletedEventType;\n  evt.user.data1 = copy;\n  SDL_PushEvent(&evt);\n}\n\nbool HandleEvent(SDL_Event* evt) {\n  if (evt->type != WorkCompletedEventType) return false;\n\n  auto item = static_cast<WorkItem*>(evt->user.data1);\n  item->OnComplete(item->Data);\n  delete item;\n  return true;\n}\n\n}  // namespace WorkQueue\n}  // namespace Impacto"
  },
  {
    "path": "src/workqueue.h",
    "content": "#pragma once\n\n#include <SDL.h>\n\nnamespace Impacto {\nnamespace WorkQueue {\nusing WorkProc = auto (*)(void* data) -> void;\nusing WorkCompletionCallbackProc = auto (*)(void* data) -> void;\n\nvoid Init();\n// Push work onto the background thread. worker(data) will be called on the\n// background thread in FIFO fashion. Completion will be posted onto SDL's event\n// queue.\nvoid Push(void* data, WorkProc worker,\n          WorkCompletionCallbackProc completionCallback);\n// Call this in the event loop to perform work completion callbacks on the main\n// thread at the start of every frame. Returns true if event was handled, false\n// if not.\nbool HandleEvent(SDL_Event* evt);\n\n// Stops all the worker threads.\nvoid StopWorkQueue();\n}  // namespace WorkQueue\n}  // namespace Impacto"
  },
  {
    "path": "triplets/arm64-android-ci.cmake",
    "content": "set(VCPKG_TARGET_ARCHITECTURE arm64)\nset(VCPKG_CRT_LINKAGE static)\nset(VCPKG_LIBRARY_LINKAGE static)\nset(VCPKG_CMAKE_SYSTEM_NAME Android)\nset(VCPKG_CMAKE_SYSTEM_VERSION 24)\nset(VCPKG_MAKE_BUILD_TRIPLET \"--host=aarch64-linux-android\")\nset(VCPKG_CMAKE_CONFIGURE_OPTIONS -DANDROID_ABI=arm64-v8a)\nset(VCPKG_BUILD_TYPE release)\n"
  },
  {
    "path": "triplets/arm64-osx-ci.cmake",
    "content": "set(VCPKG_TARGET_ARCHITECTURE arm64)\nset(VCPKG_CRT_LINKAGE dynamic)\nset(VCPKG_LIBRARY_LINKAGE dynamic)\n\nset(VCPKG_CMAKE_SYSTEM_NAME Darwin)\nset(VCPKG_OSX_ARCHITECTURES arm64)\nset(VCPKG_BUILD_TYPE release)\n\nset(VCPKG_FIXUP_MACHO_RPATH ON) "
  },
  {
    "path": "triplets/x64-linux-ci.cmake",
    "content": "set(VCPKG_TARGET_ARCHITECTURE x64)\nset(VCPKG_CRT_LINKAGE dynamic)\nset(VCPKG_LIBRARY_LINKAGE dynamic)\n\nset(VCPKG_CMAKE_SYSTEM_NAME Linux)\nset(VCPKG_BUILD_TYPE release)\n\nset(VCPKG_FIXUP_ELF_RPATH ON)"
  },
  {
    "path": "triplets/x64-osx-ci.cmake",
    "content": "set(VCPKG_TARGET_ARCHITECTURE x64)\nset(VCPKG_CRT_LINKAGE dynamic)\nset(VCPKG_LIBRARY_LINKAGE dynamic)\n\nset(VCPKG_CMAKE_SYSTEM_NAME Darwin)\nset(VCPKG_OSX_ARCHITECTURES x86_64)\nset(VCPKG_BUILD_TYPE release)\n\nset(VCPKG_FIXUP_MACHO_RPATH ON)"
  },
  {
    "path": "vcpkg-configuration.json",
    "content": "{\n  \"$schema\": \"https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg-configuration.schema.json\",\n  \"overlay-ports\": [\n    \"./portfiles\"\n  ],\n  \"overlay-triplets\": [\n    \"./triplets\"\n  ]\n}"
  },
  {
    "path": "vcpkg.json",
    "content": "{\n  \"$schema\": \"https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg.schema.json\",\n  \"builtin-baseline\": \"62159a45e18f3a9ac0548628dcaf74fcb60c6ff9\",\n  \"dependencies\": [\n    \"sdl2\",\n    {\n      \"name\": \"sdl2\",\n      \"default-features\": true,\n      \"features\": [\n        \"vulkan\"\n      ]\n    },\n    \"vulkan\",\n    \"openal-soft\",\n    \"libogg\",\n    \"libvorbis\",\n    \"zlib\",\n    {\n      \"name\": \"avcpp\",\n      \"default-features\": false\n    },\n    {\n      \"name\": \"ffmpeg\",\n      \"default-features\": false,\n      \"features\": [\n        \"avcodec\",\n        \"avformat\",\n        \"swresample\",\n        \"swscale\",\n        \"dav1d\",\n        \"zlib\"\n      ]\n    },\n    {\n      \"name\": \"ffmpeg\",\n      \"default-features\": false,\n      \"features\": [\n        \"vaapi\"\n      ],\n      \"platform\": \"linux\"\n    },\n    \"libwebp\",\n    \"fmt\",\n    \"libass\"\n  ],\n  \"overrides\": [\n    {\n      \"name\": \"ffmpeg\",\n      \"version\": \"7.1.2#5\"\n    },\n    {\n      \"name\": \"sdl2\",\n      \"version\": \"2.32.10\"\n    }\n  ]\n}"
  },
  {
    "path": "vendor/.clang-format",
    "content": "{\n    \"DisableFormat\": true,\n    \"SortIncludes\": \"Never\"\n}"
  },
  {
    "path": "vendor/clHCA/clHCA.c",
    "content": "/**\n * clHCA DECODER\n *\n * - Original decompilation and C++ decoder by nyaga\n *     https://github.com/Nyagamon/HCADecoder\n * - Ported to C by kode54\n *     https://gist.github.com/kode54/ce2bf799b445002e125f06ed833903c0\n * - Cleaned up by bnnm using Thealexbarney's VGAudio decoder as reference\n *     https://github.com/Thealexbarney/VGAudio\n */\n\n/* TODO:\n * - improve portability on types and float casts, sizeof(int) isn't necessarily sizeof(float)\n * - check \"packed_noise_level\" vs VGAudio (CriHcaPacking.UnpackFrameHeader), weird behaviour\n * - check \"delta scalefactors\" vs VGAudio (CriHcaPacking.DeltaDecode), may be setting wrong values on bad data\n * - check \"read intensity\" vs VGAudio (CriHcaPacking.ReadIntensity), skips intensities if first is 15\n * - simplify DCT4 code\n * - add extra validations: encoder_delay/padding < sample_count, bands/totals (max: 128?), track count==1, etc\n * - calling clHCA_clear multiple times will not deallocate \"comment\" correctly\n */\n//--------------------------------------------------\n// Includes\n//--------------------------------------------------\n#include \"clHCA.h\"\n#include <stddef.h>\n#include <stdlib.h>\n#include <memory.h>\n\n#define HCA_MASK  0x7F7F7F7F /* chunk obfuscation when the HCA is encrypted with key */\n#define HCA_SUBFRAMES_PER_FRAME  8\n#define HCA_SAMPLES_PER_SUBFRAME  128\n#define HCA_SAMPLES_PER_FRAME  (HCA_SUBFRAMES_PER_FRAME*HCA_SAMPLES_PER_SUBFRAME)\n#define HCA_MDCT_BITS  7 /* (1<<7) = 128 */\n\n#define HCA_MAX_CHANNELS  16 /* internal max? in practice only 8 can be encoded */\n\n#define HCA_ERROR_PARAMS        -1\n#define HCA_ERROR_HEADER        -2\n#define HCA_ERROR_CHECKSUM      -3\n#define HCA_ERROR_SYNC          -4\n#define HCA_ERROR_UNPACK        -5\n#define HCA_ERROR_BITREADER     -6\n\n//--------------------------------------------------\n// Decoder config/state\n//--------------------------------------------------\ntypedef enum { DISCRETE = 0, STEREO_PRIMARY = 1, STEREO_SECONDARY = 2 } channel_type_t;\ntypedef struct stChannel {\n    /* HCA channel config */\n    int type; /* discrete / stereo-primary / stereo-secondary */\n    unsigned int coded_scalefactor_count; /* scalefactors used (depending on channel type) */\n    unsigned char *hfr_scales; /* high frequency scales, pointing to higher scalefactors (simplification) */\n\n    /* subframe state */\n    unsigned char intensity[HCA_SUBFRAMES_PER_FRAME];       /* intensity indexes (value max: 15 / 4b) */\n    unsigned char scalefactors[HCA_SAMPLES_PER_SUBFRAME];   /* scale indexes (value max: 64 / 6b)*/\n    unsigned char resolution[HCA_SAMPLES_PER_SUBFRAME];     /* resolution indexes (value max: 15 / 4b) */\n\n    float gain[HCA_SAMPLES_PER_SUBFRAME];                   /* gain to apply to quantized spectral data */\n    float spectra[HCA_SAMPLES_PER_SUBFRAME];                /* resulting dequantized data */\n    float temp[HCA_SAMPLES_PER_SUBFRAME];                   /* temp for DCT-IV */\n    float dct[HCA_SAMPLES_PER_SUBFRAME];                    /* result of DCT-IV */\n    float imdct_previous[HCA_SAMPLES_PER_SUBFRAME];         /* IMDCT */\n\n    /* frame state */\n    float wave[HCA_SUBFRAMES_PER_FRAME][HCA_SAMPLES_PER_SUBFRAME];  /* resulting samples */\n} stChannel;\n\ntypedef struct clHCA {\n    /* header config */\n    unsigned int is_valid;\n    /* hca chunk */\n    unsigned int version;\n    unsigned int header_size;\n    /* fmt chunk */\n    unsigned int channels;\n    unsigned int sample_rate;\n    unsigned int frame_count;\n    unsigned int encoder_delay;\n    unsigned int encoder_padding;\n    /* comp/dec chunk */\n    unsigned int frame_size;\n    unsigned int min_resolution;\n    unsigned int max_resolution;\n    unsigned int track_count;\n    unsigned int channel_config;\n    unsigned int stereo_type;\n    unsigned int total_band_count;\n    unsigned int base_band_count;\n    unsigned int stereo_band_count;\n    unsigned int bands_per_hfr_group;\n    unsigned int reserved1;\n    unsigned int reserved2;\n    /* vbr chunk */\n    unsigned int vbr_max_frame_size;\n    unsigned int vbr_noise_Level;\n    /* ath chunk */\n    unsigned int ath_type;\n    /* loop chunk */\n    unsigned int loop_start_frame;\n    unsigned int loop_end_frame;\n    unsigned int loop_start_delay;\n    unsigned int loop_end_padding;\n    unsigned int loop_flag;\n    /* ciph chunk */\n    unsigned int ciph_type;\n    unsigned long long keycode;\n    /* rva chunk */\n    float rva_volume;\n    /* comm chunk */\n    unsigned int comment_len;\n    char *comment;\n\n    /* initial state */\n    unsigned int hfr_group_count;\n    unsigned char ath_curve[HCA_SAMPLES_PER_SUBFRAME];\n    unsigned char cipher_table[256];\n    /* variable state */\n    stChannel channel[HCA_MAX_CHANNELS];\n} clHCA;\n\ntypedef struct clData {\n    const unsigned char *data;\n    int size;\n    int bit;\n} clData; \n\n\n//--------------------------------------------------\n// Checksum\n//--------------------------------------------------\nstatic const unsigned short crc16_lookup_table[256] = {\n    0x0000,0x8005,0x800F,0x000A,0x801B,0x001E,0x0014,0x8011,0x8033,0x0036,0x003C,0x8039,0x0028,0x802D,0x8027,0x0022,\n    0x8063,0x0066,0x006C,0x8069,0x0078,0x807D,0x8077,0x0072,0x0050,0x8055,0x805F,0x005A,0x804B,0x004E,0x0044,0x8041,\n    0x80C3,0x00C6,0x00CC,0x80C9,0x00D8,0x80DD,0x80D7,0x00D2,0x00F0,0x80F5,0x80FF,0x00FA,0x80EB,0x00EE,0x00E4,0x80E1,\n    0x00A0,0x80A5,0x80AF,0x00AA,0x80BB,0x00BE,0x00B4,0x80B1,0x8093,0x0096,0x009C,0x8099,0x0088,0x808D,0x8087,0x0082,\n    0x8183,0x0186,0x018C,0x8189,0x0198,0x819D,0x8197,0x0192,0x01B0,0x81B5,0x81BF,0x01BA,0x81AB,0x01AE,0x01A4,0x81A1,\n    0x01E0,0x81E5,0x81EF,0x01EA,0x81FB,0x01FE,0x01F4,0x81F1,0x81D3,0x01D6,0x01DC,0x81D9,0x01C8,0x81CD,0x81C7,0x01C2,\n    0x0140,0x8145,0x814F,0x014A,0x815B,0x015E,0x0154,0x8151,0x8173,0x0176,0x017C,0x8179,0x0168,0x816D,0x8167,0x0162,\n    0x8123,0x0126,0x012C,0x8129,0x0138,0x813D,0x8137,0x0132,0x0110,0x8115,0x811F,0x011A,0x810B,0x010E,0x0104,0x8101,\n    0x8303,0x0306,0x030C,0x8309,0x0318,0x831D,0x8317,0x0312,0x0330,0x8335,0x833F,0x033A,0x832B,0x032E,0x0324,0x8321,\n    0x0360,0x8365,0x836F,0x036A,0x837B,0x037E,0x0374,0x8371,0x8353,0x0356,0x035C,0x8359,0x0348,0x834D,0x8347,0x0342,\n    0x03C0,0x83C5,0x83CF,0x03CA,0x83DB,0x03DE,0x03D4,0x83D1,0x83F3,0x03F6,0x03FC,0x83F9,0x03E8,0x83ED,0x83E7,0x03E2,\n    0x83A3,0x03A6,0x03AC,0x83A9,0x03B8,0x83BD,0x83B7,0x03B2,0x0390,0x8395,0x839F,0x039A,0x838B,0x038E,0x0384,0x8381,\n    0x0280,0x8285,0x828F,0x028A,0x829B,0x029E,0x0294,0x8291,0x82B3,0x02B6,0x02BC,0x82B9,0x02A8,0x82AD,0x82A7,0x02A2,\n    0x82E3,0x02E6,0x02EC,0x82E9,0x02F8,0x82FD,0x82F7,0x02F2,0x02D0,0x82D5,0x82DF,0x02DA,0x82CB,0x02CE,0x02C4,0x82C1,\n    0x8243,0x0246,0x024C,0x8249,0x0258,0x825D,0x8257,0x0252,0x0270,0x8275,0x827F,0x027A,0x826B,0x026E,0x0264,0x8261,\n    0x0220,0x8225,0x822F,0x022A,0x823B,0x023E,0x0234,0x8231,0x8213,0x0216,0x021C,0x8219,0x0208,0x820D,0x8207,0x0202,\n};\n\nstatic unsigned short crc16_checksum(const unsigned char *data, unsigned int size) {\n    unsigned int i;\n    unsigned short sum = 0;\n\n    /* HCA header/frames should always have checksum 0 (checksum(size-16b) = last 16b) */\n    for (i = 0; i < size; i++) {\n        sum = (sum << 8) ^ crc16_lookup_table[(sum >> 8) ^ data[i]];\n    }\n    return sum;\n}\n\n//--------------------------------------------------\n// Bitstream reader\n//--------------------------------------------------\nstatic void bitreader_init(clData *br, const void *data, int size) {\n    br->data = data;\n    br->size = size * 8;\n    br->bit = 0;\n}\n\nstatic unsigned int bitreader_peek(clData *br, int bitsize) {\n    const unsigned int bit = br->bit;\n    const unsigned int bit_rem = bit & 7;\n    const unsigned int size = br->size;\n    unsigned int v = 0;\n    unsigned int bit_offset, bit_left;\n\n    if (!(bit + bitsize <= size))\n        return v;\n\n    bit_offset = bitsize + bit_rem;\n    bit_left = size - bit;\n    if (bit_left >= 32 && bit_offset >= 25) {\n        static const unsigned int mask[8] = {\n                0xFFFFFFFF,0x7FFFFFFF,0x3FFFFFFF,0x1FFFFFFF,\n                0x0FFFFFFF,0x07FFFFFF,0x03FFFFFF,0x01FFFFFF\n        };\n        const unsigned char *data = &br->data[bit >> 3];\n        v = data[0];\n        v = (v << 8) | data[1];\n        v = (v << 8) | data[2];\n        v = (v << 8) | data[3];\n        v &= mask[bit_rem];\n        v >>= 32 - bit_rem - bitsize;\n    }\n    else if (bit_left >= 24 && bit_offset >= 17) {\n        static const unsigned int mask[8] = {\n                0xFFFFFF,0x7FFFFF,0x3FFFFF,0x1FFFFF,\n                0x0FFFFF,0x07FFFF,0x03FFFF,0x01FFFF\n        };\n        const unsigned char *data = &br->data[bit >> 3];\n        v = data[0];\n        v = (v << 8) | data[1];\n        v = (v << 8) | data[2];\n        v &= mask[bit_rem];\n        v >>= 24 - bit_rem - bitsize;\n    }\n    else if (bit_left >= 16 && bit_offset >= 9) {\n        static const unsigned int mask[8] = {\n                0xFFFF,0x7FFF,0x3FFF,0x1FFF,0x0FFF,0x07FF,0x03FF,0x01FF\n        };\n        const unsigned char *data = &br->data[bit >> 3];\n        v = data[0];\n        v = (v << 8) | data[1];\n        v &= mask[bit_rem];\n        v >>= 16 - bit_rem - bitsize;\n    }\n    else {\n        static const unsigned int mask[8] = {\n                0xFF,0x7F,0x3F,0x1F,0x0F,0x07,0x03,0x01\n        };\n        const unsigned char *data = &br->data[bit >> 3];\n        v = data[0];\n        v &= mask[bit_rem];\n        v >>= 8 - bit_rem - bitsize;\n    }\n    return v;\n}\n\nstatic unsigned int bitreader_read(clData *br, int bitsize) {\n    unsigned int v = bitreader_peek(br, bitsize);\n    br->bit += bitsize;\n    return v;\n}\n\nstatic void bitreader_skip(clData *br, int bitsize) {\n    br->bit += bitsize;\n}\n\n//--------------------------------------------------\n// API/Utilities\n//--------------------------------------------------\n\nint clHCA_isOurFile(const void *data, unsigned int size) {\n    clData br;\n    unsigned int header_size = 0;\n\n    if (!data || size < 0x08)\n        return HCA_ERROR_PARAMS;\n\n    bitreader_init(&br, data, 8);\n    if ((bitreader_peek(&br, 32) & HCA_MASK) == 0x48434100) {/*'HCA\\0'*/\n        bitreader_skip(&br, 32 + 16);\n        header_size = bitreader_read(&br, 16);\n    }\n\n    if (header_size == 0)\n        return HCA_ERROR_HEADER;\n    return header_size;\n}\n\nint clHCA_getInfo(clHCA *hca, clHCA_stInfo *info) {\n    if (!hca || !info || !hca->is_valid)\n        return HCA_ERROR_PARAMS;\n\n    info->version = hca->version;\n    info->headerSize = hca->header_size;\n    info->samplingRate = hca->sample_rate;\n    info->channelCount = hca->channels;\n    info->blockSize = hca->frame_size;\n    info->blockCount = hca->frame_count;\n    info->encoderDelay = hca->encoder_delay;\n    info->encoderPadding = hca->encoder_padding;\n    info->loopEnabled = hca->loop_flag;\n    info->loopStartBlock = hca->loop_start_frame;\n    info->loopEndBlock = hca->loop_end_frame;\n    info->loopStartDelay = hca->loop_start_delay;\n    info->loopEndPadding = hca->loop_end_padding;\n    info->samplesPerBlock = HCA_SAMPLES_PER_FRAME;\n    info->comment = hca->comment;\n    info->encryptionEnabled = hca->ciph_type == 56; /* keycode encryption */\n    return 0;\n}\n\nvoid clHCA_ReadSamples16(clHCA *hca, signed short *samples) {\n    const float scale = 32768.0f;\n    float f;\n    signed int s;\n    unsigned int i, j, k;\n\n    for (i = 0; i < HCA_SUBFRAMES_PER_FRAME; i++) {\n        for (j = 0; j < HCA_SAMPLES_PER_SUBFRAME; j++) {\n            for (k = 0; k < hca->channels; k++) {\n                f = hca->channel[k].wave[i][j];\n                //f = f * hca->rva_volume; /* rare, won't apply for now */\n                if (f > 1.0f) {\n                    f = 1.0f;\n                } else if (f < -1.0f) {\n                    f = -1.0f;\n                }\n                s = (signed int) (f * scale);\n                if ((unsigned) (s + 0x8000) & 0xFFFF0000)\n                    s = (s >> 31) ^ 0x7FFF;\n                *samples++ = (signed short) s;\n            }\n        }\n    }\n}\n\n\n//--------------------------------------------------\n// Allocation and creation\n//--------------------------------------------------\nstatic void clHCA_constructor(clHCA *hca) {\n    if (!hca)\n        return;\n    memset(hca, 0, sizeof(*hca));\n    hca->is_valid = 0;\n    hca->comment = 0;\n}\n\nstatic void clHCA_destructor(clHCA *hca) {\n    if (!hca)\n        return;\n    free(hca->comment);\n    hca->comment = 0;\n}\n\nint clHCA_sizeof() {\n    return sizeof(clHCA);\n}\n\nvoid clHCA_clear(clHCA *hca) {\n    clHCA_constructor(hca);\n}\n\nvoid clHCA_done(clHCA *hca) {\n    clHCA_destructor(hca);\n}\n\nclHCA * clHCA_new() {\n    clHCA *hca = (clHCA *) malloc(clHCA_sizeof());\n    if (hca) {\n        clHCA_constructor(hca);\n    }\n    return hca;\n}\n\nvoid clHCA_delete(clHCA *hca) {\n    clHCA_destructor(hca);\n    free(hca);\n}\n\n//--------------------------------------------------\n// ATH\n//--------------------------------------------------\n/* Base ATH (Absolute Threshold of Hearing) curve (for 41856hz).\n * May be a slight modification of the standard Painter & Spanias ATH curve formula. */\nstatic const unsigned char ath_base_curve[656] = {\n    0x78,0x5F,0x56,0x51,0x4E,0x4C,0x4B,0x49,0x48,0x48,0x47,0x46,0x46,0x45,0x45,0x45,\n    0x44,0x44,0x44,0x44,0x43,0x43,0x43,0x43,0x43,0x43,0x42,0x42,0x42,0x42,0x42,0x42,\n    0x42,0x42,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x40,0x40,0x40,0x40,\n    0x40,0x40,0x40,0x40,0x40,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,\n    0x3F,0x3F,0x3F,0x3E,0x3E,0x3E,0x3E,0x3E,0x3E,0x3D,0x3D,0x3D,0x3D,0x3D,0x3D,0x3D,\n    0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3B,0x3B,0x3B,0x3B,0x3B,0x3B,0x3B,0x3B,\n    0x3B,0x3B,0x3B,0x3B,0x3B,0x3B,0x3B,0x3B,0x3B,0x3B,0x3B,0x3B,0x3B,0x3B,0x3B,0x3B,\n    0x3B,0x3B,0x3B,0x3B,0x3B,0x3B,0x3B,0x3B,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,\n    0x3D,0x3D,0x3D,0x3D,0x3D,0x3D,0x3D,0x3D,0x3E,0x3E,0x3E,0x3E,0x3E,0x3E,0x3E,0x3F,\n    0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,\n    0x3F,0x3F,0x3F,0x3F,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,\n    0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x41,0x41,0x41,0x41,0x41,0x41,0x41,\n    0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,\n    0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x42,\n    0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x43,0x43,0x43,\n    0x43,0x43,0x43,0x43,0x43,0x43,0x43,0x43,0x43,0x43,0x43,0x43,0x43,0x43,0x44,0x44,\n    0x44,0x44,0x44,0x44,0x44,0x44,0x44,0x44,0x44,0x44,0x44,0x44,0x45,0x45,0x45,0x45,\n    0x45,0x45,0x45,0x45,0x45,0x45,0x45,0x45,0x46,0x46,0x46,0x46,0x46,0x46,0x46,0x46,\n    0x46,0x46,0x47,0x47,0x47,0x47,0x47,0x47,0x47,0x47,0x47,0x47,0x48,0x48,0x48,0x48,\n    0x48,0x48,0x48,0x48,0x49,0x49,0x49,0x49,0x49,0x49,0x49,0x49,0x4A,0x4A,0x4A,0x4A,\n    0x4A,0x4A,0x4A,0x4A,0x4B,0x4B,0x4B,0x4B,0x4B,0x4B,0x4B,0x4C,0x4C,0x4C,0x4C,0x4C,\n    0x4C,0x4D,0x4D,0x4D,0x4D,0x4D,0x4D,0x4E,0x4E,0x4E,0x4E,0x4E,0x4E,0x4F,0x4F,0x4F,\n    0x4F,0x4F,0x4F,0x50,0x50,0x50,0x50,0x50,0x51,0x51,0x51,0x51,0x51,0x52,0x52,0x52,\n    0x52,0x52,0x53,0x53,0x53,0x53,0x54,0x54,0x54,0x54,0x54,0x55,0x55,0x55,0x55,0x56,\n    0x56,0x56,0x56,0x57,0x57,0x57,0x57,0x57,0x58,0x58,0x58,0x59,0x59,0x59,0x59,0x5A,\n    0x5A,0x5A,0x5A,0x5B,0x5B,0x5B,0x5B,0x5C,0x5C,0x5C,0x5D,0x5D,0x5D,0x5D,0x5E,0x5E,\n    0x5E,0x5F,0x5F,0x5F,0x60,0x60,0x60,0x61,0x61,0x61,0x61,0x62,0x62,0x62,0x63,0x63,\n    0x63,0x64,0x64,0x64,0x65,0x65,0x66,0x66,0x66,0x67,0x67,0x67,0x68,0x68,0x68,0x69,\n    0x69,0x6A,0x6A,0x6A,0x6B,0x6B,0x6B,0x6C,0x6C,0x6D,0x6D,0x6D,0x6E,0x6E,0x6F,0x6F,\n    0x70,0x70,0x70,0x71,0x71,0x72,0x72,0x73,0x73,0x73,0x74,0x74,0x75,0x75,0x76,0x76,\n    0x77,0x77,0x78,0x78,0x78,0x79,0x79,0x7A,0x7A,0x7B,0x7B,0x7C,0x7C,0x7D,0x7D,0x7E,\n    0x7E,0x7F,0x7F,0x80,0x80,0x81,0x81,0x82,0x83,0x83,0x84,0x84,0x85,0x85,0x86,0x86,\n    0x87,0x88,0x88,0x89,0x89,0x8A,0x8A,0x8B,0x8C,0x8C,0x8D,0x8D,0x8E,0x8F,0x8F,0x90,\n    0x90,0x91,0x92,0x92,0x93,0x94,0x94,0x95,0x95,0x96,0x97,0x97,0x98,0x99,0x99,0x9A,\n    0x9B,0x9B,0x9C,0x9D,0x9D,0x9E,0x9F,0xA0,0xA0,0xA1,0xA2,0xA2,0xA3,0xA4,0xA5,0xA5,\n    0xA6,0xA7,0xA7,0xA8,0xA9,0xAA,0xAA,0xAB,0xAC,0xAD,0xAE,0xAE,0xAF,0xB0,0xB1,0xB1,\n    0xB2,0xB3,0xB4,0xB5,0xB6,0xB6,0xB7,0xB8,0xB9,0xBA,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF,\n    0xC0,0xC1,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xC9,0xCA,0xCB,0xCC,0xCD,\n    0xCE,0xCF,0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,\n    0xDE,0xDF,0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xED,0xEE,\n    0xEF,0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFF,0xFF,\n};\n\nstatic void ath_init0(unsigned char *ath_curve) {\n    /* disable curve */\n    memset(ath_curve, 0, sizeof(ath_curve[0]) * HCA_SAMPLES_PER_SUBFRAME);\n}\n\nstatic void ath_init1(unsigned char *ath_curve, unsigned int sample_rate) {\n    unsigned int i, index;\n    unsigned int acc = 0;\n\n    /* scale ATH curve depending on frequency */\n    for (i = 0; i < HCA_SAMPLES_PER_SUBFRAME; i++) {\n        acc += sample_rate;\n        index = acc >> 13;\n\n        if (index >= 654) {\n            memset(ath_curve+i, 0xFF, sizeof(ath_curve[0]) * (HCA_SAMPLES_PER_SUBFRAME - i));\n            break;\n        }\n        ath_curve[i] = ath_base_curve[index];\n    }\n}\n\nstatic int ath_init(unsigned char *ath_curve, int type, unsigned int sample_rate) {\n    switch (type) {\n    case 0:\n        ath_init0(ath_curve);\n        break;\n    case 1:\n        ath_init1(ath_curve, sample_rate);\n        break;\n    default:\n        return HCA_ERROR_HEADER;\n    }\n    return 0;\n}\n\n\n//--------------------------------------------------\n// Encryption\n//--------------------------------------------------\nstatic void cipher_decrypt(unsigned char *cipher_table, unsigned char *data, int size) {\n    unsigned int i;\n\n    for (i = 0; i < size; i++) {\n        data[i] = cipher_table[data[i]];\n    }\n}\n\nstatic void cipher_init0(unsigned char *cipher_table) {\n    unsigned int i;\n\n    /* no encryption */\n    for (i = 0; i < 256; i++) {\n        cipher_table[i] = i;\n    }\n}\n\nstatic void cipher_init1(unsigned char *cipher_table) {\n    const int mul = 13;\n    const int add = 11;\n    unsigned int i, v = 0;\n\n    /* keyless encryption (rare) */\n    for (i = 1; i < 256 - 1; i++) {\n        v = (v * mul + add) & 0xFF;\n        if (v == 0 || v == 0xFF)\n            v = (v * mul + add) & 0xFF;\n        cipher_table[i] = v;\n    }\n    cipher_table[0] = 0;\n    cipher_table[0xFF] = 0xFF;\n}\n\nstatic void cipher_init56_create_table(unsigned char *r, unsigned char key) {\n    const int mul = ((key & 1) << 3) | 5;\n    const int add = (key & 0xE) | 1;\n    unsigned int i;\n\n    key >>= 4;\n    for (i = 0; i < 16; i++) {\n        key = (key * mul + add) & 0xF;\n        r[i] = key;\n    }\n}\n\nstatic void cipher_init56(unsigned char *cipher_table, unsigned long long keycode) {\n    unsigned char kc[8];\n    unsigned char seed[16];\n    unsigned char base[256], base_r[16], base_c[16];\n    unsigned int r, c;\n\n    /* 56bit keycode encryption (given as a uint64_t number, but upper 8b aren't used) */\n\n    /* keycode = keycode - 1 */\n    if (keycode != 0)\n        keycode--;\n\n    /* init keycode table */\n    for (r = 0; r < (8-1); r++) {\n        kc[r] = keycode & 0xFF;\n        keycode = keycode >> 8;\n    }\n\n    /* init seed table */\n    seed[0x00] = kc[1];\n    seed[0x01] = kc[1] ^ kc[6];\n    seed[0x02] = kc[2] ^ kc[3];\n    seed[0x03] = kc[2];\n    seed[0x04] = kc[2] ^ kc[1];\n    seed[0x05] = kc[3] ^ kc[4];\n    seed[0x06] = kc[3];\n    seed[0x07] = kc[3] ^ kc[2];\n    seed[0x08] = kc[4] ^ kc[5];\n    seed[0x09] = kc[4];\n    seed[0x0A] = kc[4] ^ kc[3];\n    seed[0x0B] = kc[5] ^ kc[6];\n    seed[0x0C] = kc[5];\n    seed[0x0D] = kc[5] ^ kc[4];\n    seed[0x0E] = kc[6] ^ kc[1];\n    seed[0x0F] = kc[6];\n\n    /* init base table */\n    cipher_init56_create_table(base_r, kc[0]);\n    for (r = 0; r < 16; r++) {\n        unsigned char nb;\n        cipher_init56_create_table(base_c, seed[r]);\n        nb = base_r[r] << 4;\n        for (c = 0; c < 16; c++) {\n            base[r*16 + c] = nb | base_c[c]; /* combine nibbles */\n        }\n    }\n\n    /* final shuffle table */\n    {\n        unsigned int i;\n        unsigned int x = 0;\n        unsigned int pos = 1;\n\n        for (i = 0; i < 256; i++) {\n            x = (x + 17) & 0xFF;\n            if (base[x] != 0 && base[x] != 0xFF)\n                cipher_table[pos++] = base[x];\n        }\n        cipher_table[0] = 0;\n        cipher_table[0xFF] = 0xFF;\n    }\n}\n\nstatic int cipher_init(unsigned char *cipher_table, int type, unsigned long long keycode) {\n    if (type == 56 && !(keycode))\n        type = 0;\n\n    switch (type) {\n    case 0:\n        cipher_init0(cipher_table);\n        break;\n    case 1:\n        cipher_init1(cipher_table);\n        break;\n    case 56:\n        cipher_init56(cipher_table, keycode);\n        break;\n    default:\n        return HCA_ERROR_HEADER;\n    }\n    return 0;\n}\n\n//--------------------------------------------------\n// Parse\n//--------------------------------------------------\nstatic unsigned int header_ceil2(unsigned int a, unsigned int b) {\n    return (b > 0) ? (a / b + ((a % b) ? 1 : 0)) : 0;\n}\n\nint clHCA_DecodeHeader(clHCA *hca, const void *data, unsigned int size) {\n    clData br;\n    int res;\n\n    if (!hca || !data)\n        return HCA_ERROR_PARAMS;\n\n    hca->is_valid = 0;\n\n    if (size < 0x08)\n        return HCA_ERROR_PARAMS;\n\n    bitreader_init(&br, data, size);\n\n    /* read header chunks */\n\n    /* HCA base header */\n    if ((bitreader_peek(&br, 32) & HCA_MASK) == 0x48434100) { /* \"HCA\\0\" */\n        bitreader_skip(&br, 32);\n        hca->version = bitreader_read(&br, 16);\n        hca->header_size = bitreader_read(&br, 16);\n\n#if 0   // play unknown versions anyway (confirmed to exist: v1.1/v1.2/v1.3/v2.0)\n        if (hca->version != 0x0101 &&\n                hca->version != 0x0102 &&\n                hca->version != 0x0103 &&\n                hca->version != 0x0200)\n            return HCA_ERROR_HEADER;\n#endif\n        if (size < hca->header_size)\n            return HCA_ERROR_PARAMS;\n\n        if (crc16_checksum(data,hca->header_size))\n            return HCA_ERROR_CHECKSUM;\n\n        size -= 0x08;\n    }\n    else {\n        return HCA_ERROR_HEADER;\n    }\n\n    /* format info */\n    if (size >= 0x10 && (bitreader_peek(&br, 32) & HCA_MASK) == 0x666D7400) { /* \"fmt\\0\" */\n        bitreader_skip(&br, 32);\n        hca->channels = bitreader_read(&br, 8);\n        hca->sample_rate = bitreader_read(&br, 24);\n        hca->frame_count = bitreader_read(&br, 32);\n        hca->encoder_delay = bitreader_read(&br, 16);\n        hca->encoder_padding = bitreader_read(&br, 16);\n\n        if (!(hca->channels >= 1 && hca->channels <= HCA_MAX_CHANNELS))\n            return HCA_ERROR_HEADER;\n\n        if (hca->frame_count == 0)\n            return HCA_ERROR_HEADER;\n\n        if (!(hca->sample_rate >= 1 && hca->sample_rate <= 0x7FFFFF)) /* encoder max seems 48000 */\n            return HCA_ERROR_HEADER;\n\n        size -= 0x10;\n    }\n    else {\n        return HCA_ERROR_HEADER;\n    }\n\n    /* compression (v2.0) or decode (v1.x) info */\n    if (size >= 0x10 && (bitreader_peek(&br, 32) & HCA_MASK) == 0x636F6D70) { /* \"comp\" */\n        bitreader_skip(&br, 32);\n        hca->frame_size = bitreader_read(&br, 16);\n        hca->min_resolution = bitreader_read(&br, 8);\n        hca->max_resolution = bitreader_read(&br, 8);\n        hca->track_count = bitreader_read(&br, 8);\n        hca->channel_config = bitreader_read(&br, 8);\n        hca->total_band_count = bitreader_read(&br, 8);\n        hca->base_band_count = bitreader_read(&br, 8);\n        hca->stereo_band_count = bitreader_read(&br, 8);\n        hca->bands_per_hfr_group = bitreader_read(&br, 8);\n        hca->reserved1 = bitreader_read(&br, 8);\n        hca->reserved2 = bitreader_read(&br, 8);\n\n        size -= 0x10;\n    }\n    else if (size >= 0x0c && (bitreader_peek(&br, 32) & HCA_MASK) == 0x64656300) { /* \"dec\\0\" */\n        bitreader_skip(&br, 32);\n        hca->frame_size = bitreader_read(&br, 16);\n        hca->min_resolution = bitreader_read(&br, 8);\n        hca->max_resolution = bitreader_read(&br, 8);\n        hca->total_band_count = bitreader_read(&br, 8) + 1;\n        hca->base_band_count = bitreader_read(&br, 8) + 1;\n        hca->track_count = bitreader_read(&br, 4);\n        hca->channel_config = bitreader_read(&br, 4);\n        hca->stereo_type = bitreader_read(&br, 8);\n\n        if (hca->stereo_type == 0)\n            hca->base_band_count = hca->total_band_count;\n        hca->stereo_band_count = hca->total_band_count - hca->base_band_count;\n        hca->bands_per_hfr_group = 0;\n\n        size -= 0x0c;\n    }\n    else {\n        return HCA_ERROR_HEADER;\n    }\n\n    /* VBR (variable bit rate) info */\n    if (size >= 0x08 && (bitreader_peek(&br, 32) & HCA_MASK) == 0x76627200) { /* \"vbr\\0\" */\n        bitreader_skip(&br, 32);\n        hca->vbr_max_frame_size = bitreader_read(&br, 16);\n        hca->vbr_noise_Level = bitreader_read(&br, 16);\n\n        if (!(hca->frame_size == 0 && hca->vbr_max_frame_size > 8 && hca->vbr_max_frame_size <= 0x1FF))\n            return HCA_ERROR_HEADER;\n\n        size -= 0x08;\n    }\n    else {\n        /* removed in v2.0, probably unused in v1.x */\n        hca->vbr_max_frame_size = 0;\n        hca->vbr_noise_Level = 0;\n    }\n\n    /* ATH (Absolute Threshold of Hearing) info */\n    if (size >= 0x06 && (bitreader_peek(&br, 32) & HCA_MASK) == 0x61746800) { /* \"ath\\0\" */\n        bitreader_skip(&br, 32);\n        hca->ath_type = bitreader_read(&br, 16);\n    }\n    else {\n        /* removed in v2.0, default in v1.x (maybe only used in v1.1, as v1.2/v1.3 set ath_type = 0) */\n        hca->ath_type = (hca->version < 0x200) ? 1 : 0;\n    }\n\n    /* loop info */\n    if (size >= 0x10 && (bitreader_peek(&br, 32) & HCA_MASK) == 0x6C6F6F70) { /* \"loop\" */\n        bitreader_skip(&br, 32);\n        hca->loop_start_frame = bitreader_read(&br, 32);\n        hca->loop_end_frame = bitreader_read(&br, 32);\n        hca->loop_start_delay = bitreader_read(&br, 16);\n        hca->loop_end_padding = bitreader_read(&br, 16);\n\n        hca->loop_flag = 1;\n\n        if (!(hca->loop_start_frame >= 0 && hca->loop_start_frame <= hca->loop_end_frame\n                && hca->loop_end_frame < hca->frame_count))\n            return HCA_ERROR_HEADER;\n\n        size -= 0x10;\n    }\n    else {\n        hca->loop_start_frame = 0;\n        hca->loop_end_frame = 0;\n        hca->loop_start_delay = 0;\n        hca->loop_end_padding = 0;\n\n        hca->loop_flag = 0;\n    }\n\n    /* cipher/encryption info */\n    if (size >= 0x06 && (bitreader_peek(&br, 32) & HCA_MASK) == 0x63697068) { /* \"ciph\" */\n        bitreader_skip(&br, 32);\n        hca->ciph_type = bitreader_read(&br, 16);\n\n        if (!(hca->ciph_type == 0 || hca->ciph_type == 1 || hca->ciph_type == 56))\n            return HCA_ERROR_HEADER;\n\n        size -= 0x06;\n    }\n    else {\n        hca->ciph_type = 0;\n    }\n\n    /* RVA (relative volume adjustment) info */\n    if (size >= 0x08 && (bitreader_peek(&br, 32) & HCA_MASK) == 0x72766100) { /* \"rva\\0\" */\n        union {\n            unsigned int i;\n            float f;\n        } rva_volume_cast;\n        bitreader_skip(&br, 32);\n        rva_volume_cast.i = bitreader_read(&br, 32);\n        hca->rva_volume = rva_volume_cast.f;\n\n        size -= 0x08;\n    } else {\n        hca->rva_volume = 1;\n    }\n\n    /* comment */\n    if (size >= 0x05 && (bitreader_peek(&br, 32) & HCA_MASK) == 0x636F6D6D) {/* \"comm\" */\n        unsigned int i;\n        char *temp;\n        bitreader_skip(&br, 32);\n        hca->comment_len = bitreader_read(&br, 8);\n\n        if (hca->comment_len > size)\n            return HCA_ERROR_HEADER;\n\n        temp = realloc(hca->comment, hca->comment_len + 1);\n        if (!temp)\n            return HCA_ERROR_HEADER;\n        hca->comment = temp;\n        for (i = 0; i < hca->comment_len; ++i)\n            hca->comment[i] = bitreader_read(&br, 8);\n        hca->comment[i] = '\\0'; /* should be null terminated but make sure */\n\n        size -= 0x05 + hca->comment_len;\n    }\n    else {\n        hca->comment_len = 0;\n        hca->comment = NULL;\n    }\n\n    /* padding info */\n    if (size >= 0x04 && (bitreader_peek(&br, 32) & HCA_MASK) == 0x70616400) { /* \"pad\\0\" */\n        size -= (size - 0x02); /* fills up to header_size, sans checksum */\n    }\n\n    /* should be fully read, but allow as data buffer may be bigger than header_size */\n    //if (size != 0x02)\n    //    return HCA_ERROR_HEADER;\n\n\n    /* extra validations */\n    if (!(hca->frame_size >= 0x08 && hca->frame_size <= 0xFFFF)) /* actual max seems 0x155*channels */\n        return HCA_ERROR_HEADER; /* theoretically can be 0 if VBR (not seen) */\n\n    if (!(hca->min_resolution == 1 && hca->max_resolution == 15))\n        return HCA_ERROR_HEADER;\n\n\n    /* inits state */\n    if (hca->track_count == 0)\n        hca->track_count = 1; /* default to avoid division by zero */\n\n    hca->hfr_group_count = header_ceil2(\n            hca->total_band_count - hca->base_band_count - hca->stereo_band_count,\n            hca->bands_per_hfr_group);\n\n    res = ath_init(hca->ath_curve, hca->ath_type, hca->sample_rate);\n    if (res < 0)\n        return res;\n    res = cipher_init(hca->cipher_table, hca->ciph_type, hca->keycode);\n    if (res < 0)\n        return res;\n\n\n    /* init channels */\n    {\n        int channel_types[HCA_MAX_CHANNELS] = {0};\n        unsigned int i, channels_per_track;\n\n        channels_per_track = hca->channels / hca->track_count;\n        if (hca->stereo_band_count && channels_per_track > 1) {\n            int *ct = channel_types;\n            for (i = 0; i < hca->track_count; i++, ct += channels_per_track) {\n                switch (channels_per_track) {\n                case 2:\n                    ct[0] = STEREO_PRIMARY;\n                    ct[1] = STEREO_SECONDARY;\n                    break;\n                case 3:\n                    ct[0] = STEREO_PRIMARY;\n                    ct[1] = STEREO_SECONDARY;\n                    ct[2] = DISCRETE;\n                    break;\n                case 4:\n                    ct[0] = STEREO_PRIMARY;\n                    ct[1] = 2;\n                    if (hca->channel_config == 0) {\n                        ct[2] = STEREO_PRIMARY;\n                        ct[3] = STEREO_SECONDARY;\n                    } else {\n                        ct[2] = DISCRETE;\n                        ct[3] = DISCRETE;\n                    }\n                    break;\n                case 5:\n                    ct[0] = STEREO_PRIMARY;\n                    ct[1] = STEREO_SECONDARY;\n                    ct[2] = DISCRETE;\n                    if (hca->channel_config <= 2) {\n                        ct[3] = STEREO_PRIMARY;\n                        ct[4] = STEREO_SECONDARY;\n                    } else {\n                        ct[3] = DISCRETE;\n                        ct[4] = DISCRETE;\n                    }\n                    break;\n                case 6:\n                    ct[0] = STEREO_PRIMARY;\n                    ct[1] = STEREO_SECONDARY;\n                    ct[2] = DISCRETE;\n                    ct[3] = DISCRETE;\n                    ct[4] = STEREO_PRIMARY;\n                    ct[5] = STEREO_SECONDARY;\n                    break;\n                case 7:\n                    ct[0] = STEREO_PRIMARY;\n                    ct[1] = STEREO_SECONDARY;\n                    ct[2] = DISCRETE;\n                    ct[3] = DISCRETE;\n                    ct[4] = STEREO_PRIMARY;\n                    ct[5] = STEREO_SECONDARY;\n                    ct[6] = DISCRETE;\n                    break;\n                case 8:\n                    ct[0] = STEREO_PRIMARY;\n                    ct[1] = STEREO_SECONDARY;\n                    ct[2] = DISCRETE;\n                    ct[3] = DISCRETE;\n                    ct[4] = STEREO_PRIMARY;\n                    ct[5] = STEREO_SECONDARY;\n                    ct[6] = STEREO_PRIMARY;\n                    ct[7] = STEREO_SECONDARY;\n                    break;\n                }\n            }\n        }\n\n        memset(hca->channel, 0, sizeof(hca->channel));\n        for (i = 0; i < hca->channels; i++) {\n            hca->channel[i].type = channel_types[i];\n            hca->channel[i].coded_scalefactor_count = (channel_types[i] != 2) ?\n                    hca->base_band_count + hca->stereo_band_count :\n                    hca->base_band_count;\n            hca->channel[i].hfr_scales = &hca->channel[i].scalefactors[hca->base_band_count + hca->stereo_band_count];\n        }\n    }\n\n\n    /* clHCA is correctly initialized and decoder state reset\n     * (keycode is not changed between calls) */\n    hca->is_valid = 1;\n\n    return 0;\n}\n\nvoid clHCA_SetKey(clHCA *hca, unsigned long long keycode) {\n    if (!hca)\n        return;\n    hca->keycode = keycode;\n\n    /* May be called even if clHCA is not valid (header not parsed), as the\n     * key will be used during DecodeHeader ciph init. If header was already\n     * parsed reinitializes the decryption table using the new key. */\n    if (hca->is_valid) {\n        /* ignore error since it can't really fail */\n        cipher_init(hca->cipher_table, hca->ciph_type, hca->keycode);\n    }\n}\n\nint clHCA_TestBlock(clHCA *hca, void *data, unsigned int size) {\n    const float scale = 32768.0f;\n    unsigned int ch, sf, s;\n    int status;\n    int clips = 0, blanks = 0;\n\n\n    /* first blocks can be empty/silent, check all bytes but sync/crc */\n    {\n        int i;\n        int is_empty = 1;\n        const unsigned char *buf = data;\n\n        for (i = 2; i < size - 0x02; i++) {\n            if (buf[i] != 0) {\n                is_empty = 0;\n                break;\n            }\n        }\n\n        if (is_empty) {\n            return 0;\n        }\n    }\n\n    /* return if decode fails (happens often with wrong keys due to bad bitstream values) */\n    status = clHCA_DecodeBlock(hca, data, size);\n    if (status < 0)\n        return -1;\n\n    /* check decode results as bad keys may still get here */\n    for (ch = 0; ch < hca->channels; ch++) {\n        for (sf = 0; sf < HCA_SUBFRAMES_PER_FRAME; sf++) {\n            for (s = 0; s < HCA_SAMPLES_PER_SUBFRAME; s++) {\n                float fsample = hca->channel[ch].wave[sf][s];\n\n                if (fsample > 1.0f || fsample < -1.0f) { //improve?\n                    clips++;\n                }\n                else {\n                    signed int psample = (signed int) (fsample * scale);\n                    if (psample == 0 || psample == -1)\n                        blanks++;\n                }\n            }\n        }\n    }\n\n    /* the more clips the less likely block was correctly decrypted */\n    if (clips == 1)\n        clips++;\n    if (clips > 1)\n        return clips;\n    /* if block is silent result is not useful */\n    if (blanks == hca->channels * HCA_SUBFRAMES_PER_FRAME * HCA_SAMPLES_PER_SUBFRAME)\n        return 0;\n\n    /* block may be correct (but wrong keys can get this too and should test more blocks) */\n    return 1;\n}\n\nvoid clHCA_DecodeReset(clHCA * hca) {\n    unsigned int i;\n\n    if (!hca || !hca->is_valid)\n        return;\n\n    for (i = 0; i < hca->channels; i++) {\n        stChannel *ch = &hca->channel[i];\n\n        /* most values get overwritten during decode */\n        //memset(ch->intensity, 0, sizeof(ch->intensity[0]) * HCA_SUBFRAMES_PER_FRAME);\n        //memset(ch->scalefactors, 0, sizeof(ch->scalefactors[0]) * HCA_SAMPLES_PER_SUBFRAME);\n        //memset(ch->resolution, 0, sizeof(ch->resolution[0]) * HCA_SAMPLES_PER_SUBFRAME);\n        //memset(ch->gain, 0, sizeof(ch->gain[0]) * HCA_SAMPLES_PER_SUBFRAME);\n        //memset(ch->spectra, 0, sizeof(ch->spectra[0]) * HCA_SAMPLES_PER_SUBFRAME);\n        //memset(ch->temp, 0, sizeof(ch->temp[0]) * HCA_SAMPLES_PER_SUBFRAME);\n        //memset(ch->dct, 0, sizeof(ch->dct[0]) * HCA_SAMPLES_PER_SUBFRAME);\n        memset(ch->imdct_previous, 0, sizeof(ch->imdct_previous[0]) * HCA_SAMPLES_PER_SUBFRAME);\n        //memset(ch->wave, 0, sizeof(ch->wave[0][0]) * HCA_SUBFRAMES_PER_FRAME * HCA_SUBFRAMES_PER_FRAME);\n    }\n}\n\n//--------------------------------------------------\n// Decode\n//--------------------------------------------------\nstatic int decode1_unpack_channel(stChannel *ch, clData *br,\n        unsigned int hfr_group_count, unsigned int packed_noise_level, const unsigned char *ath_curve);\n\nstatic void decode2_dequantize_coefficients(stChannel *ch, clData *br);\n\nstatic void decode3_reconstruct_high_frequency(stChannel *ch,\n        unsigned int hfr_group_count, unsigned int bands_per_hfr_group,\n        unsigned int stereo_band_count, unsigned int base_band_count, unsigned int total_band_count);\n\nstatic void decode4_apply_intensity_stereo(stChannel *ch, int subframe,\n        unsigned int usable_band_count, unsigned int base_band_count, unsigned int stereo_band_count);\n\nstatic void decoder5_run_imdct(stChannel *ch, int subframe);\n\n\nint clHCA_DecodeBlock(clHCA *hca, void *data, unsigned int size) {\n    clData br;\n    unsigned short sync;\n    unsigned int subframe, ch;\n\n    if (!data || !hca || !hca->is_valid)\n        return HCA_ERROR_PARAMS;\n    if (size < hca->frame_size)\n        return HCA_ERROR_PARAMS;\n\n    bitreader_init(&br, data, hca->frame_size);\n\n    /* test sync (not encrypted) */\n    sync = bitreader_read(&br, 16);\n    if (sync != 0xFFFF)\n        return HCA_ERROR_SYNC;\n\n    if (crc16_checksum(data, hca->frame_size))\n        return HCA_ERROR_CHECKSUM;\n\n    cipher_decrypt(hca->cipher_table, data, hca->frame_size);\n\n\n    /* unpack frame values */\n    {\n        unsigned int frame_acceptable_noise_level = bitreader_read(&br, 9);\n        unsigned int frame_evaluation_boundary = bitreader_read(&br, 7);\n        unsigned int packed_noise_level = (frame_acceptable_noise_level << 8) - frame_evaluation_boundary;\n\n        for (ch = 0; ch < hca->channels; ch++) {\n            int unpack = decode1_unpack_channel(&hca->channel[ch], &br,\n                    hca->hfr_group_count, packed_noise_level, hca->ath_curve);\n            if (unpack < 0)\n                return unpack;\n        }\n    }\n\n    for (subframe = 0; subframe < HCA_SUBFRAMES_PER_FRAME; subframe++) {\n\n        /* unpack channel data and get dequantized spectra */\n        for (ch = 0; ch < hca->channels; ch++){\n            decode2_dequantize_coefficients(&hca->channel[ch], &br);\n        }\n\n        /* restore missing bands from spectra 1 */\n        for (ch = 0; ch < hca->channels; ch++) {\n            decode3_reconstruct_high_frequency(&hca->channel[ch],\n                    hca->hfr_group_count, hca->bands_per_hfr_group,\n                    hca->stereo_band_count, hca->base_band_count, hca->total_band_count);\n        }\n\n        /* restore missing bands from spectra 2 */\n        for (ch = 0; ch < hca->channels - 1; ch++) {\n            decode4_apply_intensity_stereo(&hca->channel[ch], subframe,\n                    hca->total_band_count, hca->base_band_count, hca->stereo_band_count);\n        }\n\n        /* apply imdct */\n        for (ch = 0; ch < hca->channels; ch++) {\n            decoder5_run_imdct(&hca->channel[ch], subframe);\n        }\n    }\n\n    /* should read all frame sans checksum at most */\n    if (br.bit > br.size - 16)\n        return HCA_ERROR_BITREADER;\n\n    return 0;\n}\n\n//--------------------------------------------------\n// Decode 1st step\n//--------------------------------------------------\nstatic const unsigned char decode1_scale_to_resolution_curve[64] = {\n    0x0E,0x0E,0x0E,0x0E,0x0E,0x0E,0x0D,0x0D,\n    0x0D,0x0D,0x0D,0x0D,0x0C,0x0C,0x0C,0x0C,\n    0x0C,0x0C,0x0B,0x0B,0x0B,0x0B,0x0B,0x0B,\n    0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x09,\n    0x09,0x09,0x09,0x09,0x09,0x08,0x08,0x08,\n    0x08,0x08,0x08,0x07,0x06,0x06,0x05,0x04,\n    0x04,0x04,0x03,0x03,0x03,0x02,0x02,0x02,\n    0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,\n    /* for v1.x indexes after 56 are different, but can't be used anyway */\n  //0x02,0x01,0x01,0x01,0x01,0x01,0x01,0x01,\n};\n\n/* scalefactor-to-scaling table, generated from sqrt(128) * (2^(53/128))^(scale_factor - 63) */\nstatic const unsigned int decode1_dequantizer_scaling_table_int[64] = {\n    0x342A8D26,0x34633F89,0x3497657D,0x34C9B9BE,0x35066491,0x353311C4,0x356E9910,0x359EF532,\n    0x35D3CCF1,0x360D1ADF,0x363C034A,0x367A83B3,0x36A6E595,0x36DE60F5,0x371426FF,0x3745672A,\n    0x37838359,0x37AF3B79,0x37E97C38,0x381B8D3A,0x384F4319,0x388A14D5,0x38B7FBF0,0x38F5257D,\n    0x3923520F,0x39599D16,0x3990FA4D,0x39C12C4D,0x3A00B1ED,0x3A2B7A3A,0x3A647B6D,0x3A9837F0,\n    0x3ACAD226,0x3B071F62,0x3B340AAF,0x3B6FE4BA,0x3B9FD228,0x3BD4F35B,0x3C0DDF04,0x3C3D08A4,\n    0x3C7BDFED,0x3CA7CD94,0x3CDF9613,0x3D14F4F0,0x3D467991,0x3D843A29,0x3DB02F0E,0x3DEAC0C7,\n    0x3E1C6573,0x3E506334,0x3E8AD4C6,0x3EB8FBAF,0x3EF67A41,0x3F243516,0x3F5ACB94,0x3F91C3D3,\n    0x3FC238D2,0x400164D2,0x402C6897,0x4065B907,0x40990B88,0x40CBEC15,0x4107DB35,0x413504F3,\n};\nstatic const float *decode1_dequantizer_scaling_table = (const float *)decode1_dequantizer_scaling_table_int;\n\nstatic const unsigned int decode1_quantizer_step_size_int[16] = {\n    0x00000000,0x3F2AAAAB,0x3ECCCCCD,0x3E924925,0x3E638E39,0x3E3A2E8C,0x3E1D89D9,0x3E088889,\n    0x3D842108,0x3D020821,0x3C810204,0x3C008081,0x3B804020,0x3B002008,0x3A801002,0x3A000801,\n};\nstatic const float *decode1_quantizer_step_size = (const float *)decode1_quantizer_step_size_int;\n\nstatic int decode1_unpack_channel(stChannel *ch, clData *br,\n        unsigned int hfr_group_count, unsigned int packed_noise_level, const unsigned char *ath_curve) {\n    unsigned int i;\n    const unsigned int csf_count = ch->coded_scalefactor_count;\n\n\n    /* read scalefactors */\n    {\n        /* scale indexes to normalize dequantized coefficients */\n        unsigned char scalefactor_delta_bits = bitreader_read(br, 3);\n        if (scalefactor_delta_bits >= 6) {\n            /* normal scalefactors */\n            for (i = 0; i < csf_count; i++) {\n                ch->scalefactors[i] = bitreader_read(br, 6);\n            }\n        }\n        else if (scalefactor_delta_bits > 0) {\n            /* delta scalefactors */\n            const unsigned char expected_delta = (1 << scalefactor_delta_bits) - 1;\n            const unsigned char extra_delta = expected_delta >> 1;\n            unsigned char scalefactor_prev = bitreader_read(br, 6);\n\n            ch->scalefactors[0] = scalefactor_prev;\n            for (i = 1; i < csf_count; i++) {\n                unsigned char delta = bitreader_read(br, scalefactor_delta_bits);\n\n                if (delta != expected_delta) {\n                    /* may happen with bad keycodes, scalefactors must be 6b indexes */\n                    int scalefactor_test = (int)scalefactor_prev + ((int)delta - (int)extra_delta);\n                    if (scalefactor_test < 0 || scalefactor_test > 64) {\n                        return HCA_ERROR_UNPACK;\n                    }\n\n                    scalefactor_prev += delta - extra_delta;\n                } else {\n                    scalefactor_prev = bitreader_read(br, 6);\n                }\n                ch->scalefactors[i] = scalefactor_prev;\n            }\n        }\n        else {\n            /* no scalefactors */\n            memset(ch->scalefactors, 0, sizeof(ch->scalefactors[0]) * HCA_SAMPLES_PER_SUBFRAME);\n        }\n    }\n\n    if (ch->type == STEREO_SECONDARY) {\n        /* read intensity */\n        unsigned char intensity_value = bitreader_peek(br, 4);\n\n        ch->intensity[0] = intensity_value;\n        if (intensity_value < 15) {\n            for (i = 0; i < HCA_SUBFRAMES_PER_FRAME; i++) {\n                ch->intensity[i] = bitreader_read(br, 4);\n            }\n        }\n        /* 15 may be an invalid value? */\n        //else {\n        //    return HCA_ERROR_INSENSITY;\n        //}\n    }\n    else {\n        /* read hfr scalefactors */\n        for (i = 0; i < hfr_group_count; i++) {\n            ch->hfr_scales[i] = bitreader_read(br, 6);\n        }\n    }\n\n    /* calculate resolutions */\n    {\n        /* resolution determines the range of values per encoded spectra,\n         * using codebooks for lower resolutions during dequantization */\n        for (i = 0; i < csf_count; i++) {\n            unsigned char new_resolution = 0;\n            unsigned char scalefactor = ch->scalefactors[i];\n            if (scalefactor > 0) {\n                int noise_level = ath_curve[i] + ((packed_noise_level + i) >> 8);\n                int curve_position = noise_level - ((5 * scalefactor) >> 1) + 1;\n\n                /* curve values can be simplified by clamping position to (0,58) and making\n                 * scale_table[0] = 15, table[58] = 1 (like VGAudio does) */\n                if (curve_position < 0)\n                    new_resolution = 15;\n                else if (curve_position >= 57)\n                    new_resolution = 1;\n                else\n                    new_resolution = decode1_scale_to_resolution_curve[curve_position];\n            }\n            ch->resolution[i] = new_resolution;\n        }\n        memset(&ch->resolution[csf_count], 0, sizeof(ch->resolution[0]) * (HCA_SAMPLES_PER_SUBFRAME - csf_count));\n    }\n\n    /* calculate gain */\n    {\n        /* get actual scales to dequantize */\n        for (i = 0; i < csf_count; i++) {\n            float scalefactor_scale = decode1_dequantizer_scaling_table[ ch->scalefactors[i] ];\n            float resolution_scale = decode1_quantizer_step_size[ ch->resolution[i] ];\n            ch->gain[i] = scalefactor_scale * resolution_scale;\n        }\n    }\n\n    return 0;\n}\n\n//--------------------------------------------------\n// Decode 2nd step\n//--------------------------------------------------\nstatic const unsigned char decode2_quantized_spectrum_max_bits[16] = {\n    0,2,3,3,4,4,4,4,5,6,7,8,9,10,11,12\n};\nstatic const unsigned char decode2_quantized_spectrum_bits[128] = {\n    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,\n    1,1,2,2,0,0,0,0,0,0,0,0,0,0,0,0,\n    2,2,2,2,2,2,3,3,0,0,0,0,0,0,0,0,\n    2,2,3,3,3,3,3,3,0,0,0,0,0,0,0,0,\n    3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,\n    3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,\n    3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,\n    3,3,4,4,4,4,4,4,4,4,4,4,4,4,4,4,\n};\nstatic const float decode2_quantized_spectrum_value[128] = {\n    +0,+0,+0,+0,+0,+0,+0,+0,+0,+0,+0,+0,+0,+0,+0,+0,\n    +0,+0,+1,-1,+0,+0,+0,+0,+0,+0,+0,+0,+0,+0,+0,+0,\n    +0,+0,+1,+1,-1,-1,+2,-2,+0,+0,+0,+0,+0,+0,+0,+0,\n    +0,+0,+1,-1,+2,-2,+3,-3,+0,+0,+0,+0,+0,+0,+0,+0,\n    +0,+0,+1,+1,-1,-1,+2,+2,-2,-2,+3,+3,-3,-3,+4,-4,\n    +0,+0,+1,+1,-1,-1,+2,+2,-2,-2,+3,-3,+4,-4,+5,-5,\n    +0,+0,+1,+1,-1,-1,+2,-2,+3,-3,+4,-4,+5,-5,+6,-6,\n    +0,+0,+1,-1,+2,-2,+3,-3,+4,-4,+5,-5,+6,-6,+7,-7,\n};\n\nstatic void decode2_dequantize_coefficients(stChannel *ch, clData *br) {\n    unsigned int i;\n    const unsigned int csf_count = ch->coded_scalefactor_count;\n\n\n    for (i = 0; i < csf_count; i++) {\n        float qc;\n        unsigned char resolution = ch->resolution[i];\n        unsigned char bits = decode2_quantized_spectrum_max_bits[resolution];\n        unsigned int code = bitreader_read(br, bits);\n\n        /* read spectral coefficients */\n        if (resolution < 8) {\n            /* use prefix codebooks for lower resolutions */\n            code += resolution << 4;\n            bitreader_skip(br, decode2_quantized_spectrum_bits[code] - bits);\n            qc = decode2_quantized_spectrum_value[code];\n        }\n        else {\n            /* parse values in sign-magnitude form (lowest bit = sign) */\n            int signed_code = (1 - ((code & 1) << 1)) * (code >> 1); /* move sign */\n            if (signed_code == 0)\n                bitreader_skip(br, -1); /* zero uses one less bit since it has no sign */\n            qc = (float)signed_code;\n        }\n\n        /* dequantize coef with gain */\n        ch->spectra[i] = ch->gain[i] * qc;\n    }\n\n    /* clean rest of spectra */\n    memset(&ch->spectra[csf_count], 0, sizeof(ch->spectra[0]) * (HCA_SAMPLES_PER_SUBFRAME - csf_count));\n}\n\n//--------------------------------------------------\n// Decode 3rd step\n//--------------------------------------------------\nstatic const unsigned int decode3_scale_conversion_table_int[128] = {\n    0x00000000,0x00000000,0x32A0B051,0x32D61B5E,0x330EA43A,0x333E0F68,0x337D3E0C,0x33A8B6D5,\n    0x33E0CCDF,0x3415C3FF,0x34478D75,0x3484F1F6,0x34B123F6,0x34EC0719,0x351D3EDA,0x355184DF,\n    0x358B95C2,0x35B9FCD2,0x35F7D0DF,0x36251958,0x365BFBB8,0x36928E72,0x36C346CD,0x370218AF,\n    0x372D583F,0x3766F85B,0x3799E046,0x37CD078C,0x3808980F,0x38360094,0x38728177,0x38A18FAF,\n    0x38D744FD,0x390F6A81,0x393F179A,0x397E9E11,0x39A9A15B,0x39E2055B,0x3A16942D,0x3A48A2D8,\n    0x3A85AAC3,0x3AB21A32,0x3AED4F30,0x3B1E196E,0x3B52A81E,0x3B8C57CA,0x3BBAFF5B,0x3BF9295A,\n    0x3C25FED7,0x3C5D2D82,0x3C935A2B,0x3CC4563F,0x3D02CD87,0x3D2E4934,0x3D68396A,0x3D9AB62B,\n    0x3DCE248C,0x3E0955EE,0x3E36FD92,0x3E73D290,0x3EA27043,0x3ED87039,0x3F1031DC,0x3F40213B,\n\n    0x3F800000,0x3FAA8D26,0x3FE33F89,0x4017657D,0x4049B9BE,0x40866491,0x40B311C4,0x40EE9910,\n    0x411EF532,0x4153CCF1,0x418D1ADF,0x41BC034A,0x41FA83B3,0x4226E595,0x425E60F5,0x429426FF,\n    0x42C5672A,0x43038359,0x432F3B79,0x43697C38,0x439B8D3A,0x43CF4319,0x440A14D5,0x4437FBF0,\n    0x4475257D,0x44A3520F,0x44D99D16,0x4510FA4D,0x45412C4D,0x4580B1ED,0x45AB7A3A,0x45E47B6D,\n    0x461837F0,0x464AD226,0x46871F62,0x46B40AAF,0x46EFE4BA,0x471FD228,0x4754F35B,0x478DDF04,\n    0x47BD08A4,0x47FBDFED,0x4827CD94,0x485F9613,0x4894F4F0,0x48C67991,0x49043A29,0x49302F0E,\n    0x496AC0C7,0x499C6573,0x49D06334,0x4A0AD4C6,0x4A38FBAF,0x4A767A41,0x4AA43516,0x4ADACB94,\n    0x4B11C3D3,0x4B4238D2,0x4B8164D2,0x4BAC6897,0x4BE5B907,0x4C190B88,0x4C4BEC15,0x00000000,\n};\nstatic const float *decode3_scale_conversion_table = (const float *)decode3_scale_conversion_table_int;\n\nstatic void decode3_reconstruct_high_frequency(stChannel *ch,\n        unsigned int hfr_group_count, unsigned int bands_per_hfr_group,\n        unsigned int stereo_band_count, unsigned int base_band_count, unsigned int total_band_count) {\n    if (ch->type == STEREO_SECONDARY)\n        return;\n    if (bands_per_hfr_group == 0) /* not in v1.x */\n        return;\n\n    {\n        unsigned int group, i;\n        unsigned int start_band = stereo_band_count + base_band_count;\n        unsigned int highband = start_band;\n        unsigned int lowband = start_band - 1;\n\n        for (group = 0; group < hfr_group_count; group++) {\n            for (i = 0; i < bands_per_hfr_group && highband < total_band_count; i++) {\n                unsigned int sc_index = ch->hfr_scales[group] - ch->scalefactors[lowband] + 64;\n                ch->spectra[highband] = decode3_scale_conversion_table[sc_index] * ch->spectra[lowband];\n                highband++;\n                lowband--;\n            }\n        }\n\n        ch->spectra[HCA_SAMPLES_PER_SUBFRAME - 1] = 0; /* last spectral coefficient should be 0 */\n    }\n}\n\n//--------------------------------------------------\n// Decode 4th step\n//--------------------------------------------------\nstatic const unsigned int decode4_intensity_ratio_table_int[80] = {\n    0x40000000,0x3FEDB6DB,0x3FDB6DB7,0x3FC92492,0x3FB6DB6E,0x3FA49249,0x3F924925,0x3F800000,\n    0x3F5B6DB7,0x3F36DB6E,0x3F124925,0x3EDB6DB7,0x3E924925,0x3E124925,0x00000000,0x00000000,\n    /* v2.0 seems to define indexes over 15, but intensity is packed in 4b thus unused */\n    0x00000000,0x32A0B051,0x32D61B5E,0x330EA43A,0x333E0F68,0x337D3E0C,0x33A8B6D5,0x33E0CCDF,\n    0x3415C3FF,0x34478D75,0x3484F1F6,0x34B123F6,0x34EC0719,0x351D3EDA,0x355184DF,0x358B95C2,\n    0x35B9FCD2,0x35F7D0DF,0x36251958,0x365BFBB8,0x36928E72,0x36C346CD,0x370218AF,0x372D583F,\n    0x3766F85B,0x3799E046,0x37CD078C,0x3808980F,0x38360094,0x38728177,0x38A18FAF,0x38D744FD,\n    0x390F6A81,0x393F179A,0x397E9E11,0x39A9A15B,0x39E2055B,0x3A16942D,0x3A48A2D8,0x3A85AAC3,\n    0x3AB21A32,0x3AED4F30,0x3B1E196E,0x3B52A81E,0x3B8C57CA,0x3BBAFF5B,0x3BF9295A,0x3C25FED7,\n    0x3C5D2D82,0x3C935A2B,0x3CC4563F,0x3D02CD87,0x3D2E4934,0x3D68396A,0x3D9AB62B,0x3DCE248C,\n    0x3E0955EE,0x3E36FD92,0x3E73D290,0x3EA27043,0x3ED87039,0x3F1031DC,0x3F40213B,0x00000000,\n};\nstatic const float *decode4_intensity_ratio_table = (const float *)decode4_intensity_ratio_table_int;\n\nstatic void decode4_apply_intensity_stereo(stChannel *ch_pair, int subframe,\n        unsigned int total_band_count, unsigned int base_band_count, unsigned int stereo_band_count) {\n    if (ch_pair[0].type != STEREO_PRIMARY)\n        return;\n    if (stereo_band_count == 0)\n        return;\n\n    {\n        float ratio_l = decode4_intensity_ratio_table[ ch_pair[1].intensity[subframe] ];\n        float ratio_r = ratio_l - 2.0f;\n        float *sp_l = ch_pair[0].spectra;\n        float *sp_r = ch_pair[1].spectra;\n        unsigned int band;\n\n        for (band = base_band_count; band < total_band_count; band++) {\n            sp_r[band] = sp_l[band] * ratio_r;\n            sp_l[band] = sp_l[band] * ratio_l;\n        }\n    }\n}\n\n//--------------------------------------------------\n// Decode 5th step\n//--------------------------------------------------\nstatic const unsigned int decode5_sin_tables_int[7][64] = {\n    {\n        0x3DA73D75,0x3DA73D75,0x3DA73D75,0x3DA73D75,0x3DA73D75,0x3DA73D75,0x3DA73D75,0x3DA73D75,\n        0x3DA73D75,0x3DA73D75,0x3DA73D75,0x3DA73D75,0x3DA73D75,0x3DA73D75,0x3DA73D75,0x3DA73D75,\n        0x3DA73D75,0x3DA73D75,0x3DA73D75,0x3DA73D75,0x3DA73D75,0x3DA73D75,0x3DA73D75,0x3DA73D75,\n        0x3DA73D75,0x3DA73D75,0x3DA73D75,0x3DA73D75,0x3DA73D75,0x3DA73D75,0x3DA73D75,0x3DA73D75,\n        0x3DA73D75,0x3DA73D75,0x3DA73D75,0x3DA73D75,0x3DA73D75,0x3DA73D75,0x3DA73D75,0x3DA73D75,\n        0x3DA73D75,0x3DA73D75,0x3DA73D75,0x3DA73D75,0x3DA73D75,0x3DA73D75,0x3DA73D75,0x3DA73D75,\n        0x3DA73D75,0x3DA73D75,0x3DA73D75,0x3DA73D75,0x3DA73D75,0x3DA73D75,0x3DA73D75,0x3DA73D75,\n        0x3DA73D75,0x3DA73D75,0x3DA73D75,0x3DA73D75,0x3DA73D75,0x3DA73D75,0x3DA73D75,0x3DA73D75,\n    },{\n        0x3F7B14BE,0x3F54DB31,0x3F7B14BE,0x3F54DB31,0x3F7B14BE,0x3F54DB31,0x3F7B14BE,0x3F54DB31,\n        0x3F7B14BE,0x3F54DB31,0x3F7B14BE,0x3F54DB31,0x3F7B14BE,0x3F54DB31,0x3F7B14BE,0x3F54DB31,\n        0x3F7B14BE,0x3F54DB31,0x3F7B14BE,0x3F54DB31,0x3F7B14BE,0x3F54DB31,0x3F7B14BE,0x3F54DB31,\n        0x3F7B14BE,0x3F54DB31,0x3F7B14BE,0x3F54DB31,0x3F7B14BE,0x3F54DB31,0x3F7B14BE,0x3F54DB31,\n        0x3F7B14BE,0x3F54DB31,0x3F7B14BE,0x3F54DB31,0x3F7B14BE,0x3F54DB31,0x3F7B14BE,0x3F54DB31,\n        0x3F7B14BE,0x3F54DB31,0x3F7B14BE,0x3F54DB31,0x3F7B14BE,0x3F54DB31,0x3F7B14BE,0x3F54DB31,\n        0x3F7B14BE,0x3F54DB31,0x3F7B14BE,0x3F54DB31,0x3F7B14BE,0x3F54DB31,0x3F7B14BE,0x3F54DB31,\n        0x3F7B14BE,0x3F54DB31,0x3F7B14BE,0x3F54DB31,0x3F7B14BE,0x3F54DB31,0x3F7B14BE,0x3F54DB31,\n    },{\n        0x3F7EC46D,0x3F74FA0B,0x3F61C598,0x3F45E403,0x3F7EC46D,0x3F74FA0B,0x3F61C598,0x3F45E403,\n        0x3F7EC46D,0x3F74FA0B,0x3F61C598,0x3F45E403,0x3F7EC46D,0x3F74FA0B,0x3F61C598,0x3F45E403,\n        0x3F7EC46D,0x3F74FA0B,0x3F61C598,0x3F45E403,0x3F7EC46D,0x3F74FA0B,0x3F61C598,0x3F45E403,\n        0x3F7EC46D,0x3F74FA0B,0x3F61C598,0x3F45E403,0x3F7EC46D,0x3F74FA0B,0x3F61C598,0x3F45E403,\n        0x3F7EC46D,0x3F74FA0B,0x3F61C598,0x3F45E403,0x3F7EC46D,0x3F74FA0B,0x3F61C598,0x3F45E403,\n        0x3F7EC46D,0x3F74FA0B,0x3F61C598,0x3F45E403,0x3F7EC46D,0x3F74FA0B,0x3F61C598,0x3F45E403,\n        0x3F7EC46D,0x3F74FA0B,0x3F61C598,0x3F45E403,0x3F7EC46D,0x3F74FA0B,0x3F61C598,0x3F45E403,\n        0x3F7EC46D,0x3F74FA0B,0x3F61C598,0x3F45E403,0x3F7EC46D,0x3F74FA0B,0x3F61C598,0x3F45E403,\n    },{\n        0x3F7FB10F,0x3F7D3AAC,0x3F7853F8,0x3F710908,0x3F676BD8,0x3F5B941A,0x3F4D9F02,0x3F3DAEF9,\n        0x3F7FB10F,0x3F7D3AAC,0x3F7853F8,0x3F710908,0x3F676BD8,0x3F5B941A,0x3F4D9F02,0x3F3DAEF9,\n        0x3F7FB10F,0x3F7D3AAC,0x3F7853F8,0x3F710908,0x3F676BD8,0x3F5B941A,0x3F4D9F02,0x3F3DAEF9,\n        0x3F7FB10F,0x3F7D3AAC,0x3F7853F8,0x3F710908,0x3F676BD8,0x3F5B941A,0x3F4D9F02,0x3F3DAEF9,\n        0x3F7FB10F,0x3F7D3AAC,0x3F7853F8,0x3F710908,0x3F676BD8,0x3F5B941A,0x3F4D9F02,0x3F3DAEF9,\n        0x3F7FB10F,0x3F7D3AAC,0x3F7853F8,0x3F710908,0x3F676BD8,0x3F5B941A,0x3F4D9F02,0x3F3DAEF9,\n        0x3F7FB10F,0x3F7D3AAC,0x3F7853F8,0x3F710908,0x3F676BD8,0x3F5B941A,0x3F4D9F02,0x3F3DAEF9,\n        0x3F7FB10F,0x3F7D3AAC,0x3F7853F8,0x3F710908,0x3F676BD8,0x3F5B941A,0x3F4D9F02,0x3F3DAEF9,\n    },{\n        0x3F7FEC43,0x3F7F4E6D,0x3F7E1324,0x3F7C3B28,0x3F79C79D,0x3F76BA07,0x3F731447,0x3F6ED89E,\n        0x3F6A09A7,0x3F64AA59,0x3F5EBE05,0x3F584853,0x3F514D3D,0x3F49D112,0x3F41D870,0x3F396842,\n        0x3F7FEC43,0x3F7F4E6D,0x3F7E1324,0x3F7C3B28,0x3F79C79D,0x3F76BA07,0x3F731447,0x3F6ED89E,\n        0x3F6A09A7,0x3F64AA59,0x3F5EBE05,0x3F584853,0x3F514D3D,0x3F49D112,0x3F41D870,0x3F396842,\n        0x3F7FEC43,0x3F7F4E6D,0x3F7E1324,0x3F7C3B28,0x3F79C79D,0x3F76BA07,0x3F731447,0x3F6ED89E,\n        0x3F6A09A7,0x3F64AA59,0x3F5EBE05,0x3F584853,0x3F514D3D,0x3F49D112,0x3F41D870,0x3F396842,\n        0x3F7FEC43,0x3F7F4E6D,0x3F7E1324,0x3F7C3B28,0x3F79C79D,0x3F76BA07,0x3F731447,0x3F6ED89E,\n        0x3F6A09A7,0x3F64AA59,0x3F5EBE05,0x3F584853,0x3F514D3D,0x3F49D112,0x3F41D870,0x3F396842,\n    },{\n        0x3F7FFB11,0x3F7FD397,0x3F7F84AB,0x3F7F0E58,0x3F7E70B0,0x3F7DABCC,0x3F7CBFC9,0x3F7BACCD,\n        0x3F7A7302,0x3F791298,0x3F778BC5,0x3F75DEC6,0x3F740BDD,0x3F721352,0x3F6FF573,0x3F6DB293,\n        0x3F6B4B0C,0x3F68BF3C,0x3F660F88,0x3F633C5A,0x3F604621,0x3F5D2D53,0x3F59F26A,0x3F5695E5,\n        0x3F531849,0x3F4F7A1F,0x3F4BBBF8,0x3F47DE65,0x3F43E200,0x3F3FC767,0x3F3B8F3B,0x3F373A23,\n        0x3F7FFB11,0x3F7FD397,0x3F7F84AB,0x3F7F0E58,0x3F7E70B0,0x3F7DABCC,0x3F7CBFC9,0x3F7BACCD,\n        0x3F7A7302,0x3F791298,0x3F778BC5,0x3F75DEC6,0x3F740BDD,0x3F721352,0x3F6FF573,0x3F6DB293,\n        0x3F6B4B0C,0x3F68BF3C,0x3F660F88,0x3F633C5A,0x3F604621,0x3F5D2D53,0x3F59F26A,0x3F5695E5,\n        0x3F531849,0x3F4F7A1F,0x3F4BBBF8,0x3F47DE65,0x3F43E200,0x3F3FC767,0x3F3B8F3B,0x3F373A23,\n    },{\n        0x3F7FFEC4,0x3F7FF4E6,0x3F7FE129,0x3F7FC38F,0x3F7F9C18,0x3F7F6AC7,0x3F7F2F9D,0x3F7EEA9D,\n        0x3F7E9BC9,0x3F7E4323,0x3F7DE0B1,0x3F7D7474,0x3F7CFE73,0x3F7C7EB0,0x3F7BF531,0x3F7B61FC,\n        0x3F7AC516,0x3F7A1E84,0x3F796E4E,0x3F78B47B,0x3F77F110,0x3F772417,0x3F764D97,0x3F756D97,\n        0x3F748422,0x3F73913F,0x3F7294F8,0x3F718F57,0x3F708066,0x3F6F6830,0x3F6E46BE,0x3F6D1C1D,\n        0x3F6BE858,0x3F6AAB7B,0x3F696591,0x3F6816A8,0x3F66BECC,0x3F655E0B,0x3F63F473,0x3F628210,\n        0x3F6106F2,0x3F5F8327,0x3F5DF6BE,0x3F5C61C7,0x3F5AC450,0x3F591E6A,0x3F577026,0x3F55B993,\n        0x3F53FAC3,0x3F5233C6,0x3F5064AF,0x3F4E8D90,0x3F4CAE79,0x3F4AC77F,0x3F48D8B3,0x3F46E22A,\n        0x3F44E3F5,0x3F42DE29,0x3F40D0DA,0x3F3EBC1B,0x3F3CA003,0x3F3A7CA4,0x3F385216,0x3F36206C,\n    }\n};\n\nstatic const unsigned int decode5_cos_tables_int[7][64]={\n    {\n        0xBD0A8BD4,0x3D0A8BD4,0x3D0A8BD4,0xBD0A8BD4,0x3D0A8BD4,0xBD0A8BD4,0xBD0A8BD4,0x3D0A8BD4,\n        0x3D0A8BD4,0xBD0A8BD4,0xBD0A8BD4,0x3D0A8BD4,0xBD0A8BD4,0x3D0A8BD4,0x3D0A8BD4,0xBD0A8BD4,\n        0x3D0A8BD4,0xBD0A8BD4,0xBD0A8BD4,0x3D0A8BD4,0xBD0A8BD4,0x3D0A8BD4,0x3D0A8BD4,0xBD0A8BD4,\n        0xBD0A8BD4,0x3D0A8BD4,0x3D0A8BD4,0xBD0A8BD4,0x3D0A8BD4,0xBD0A8BD4,0xBD0A8BD4,0x3D0A8BD4,\n        0x3D0A8BD4,0xBD0A8BD4,0xBD0A8BD4,0x3D0A8BD4,0xBD0A8BD4,0x3D0A8BD4,0x3D0A8BD4,0xBD0A8BD4,\n        0xBD0A8BD4,0x3D0A8BD4,0x3D0A8BD4,0xBD0A8BD4,0x3D0A8BD4,0xBD0A8BD4,0xBD0A8BD4,0x3D0A8BD4,\n        0xBD0A8BD4,0x3D0A8BD4,0x3D0A8BD4,0xBD0A8BD4,0x3D0A8BD4,0xBD0A8BD4,0xBD0A8BD4,0x3D0A8BD4,\n        0x3D0A8BD4,0xBD0A8BD4,0xBD0A8BD4,0x3D0A8BD4,0xBD0A8BD4,0x3D0A8BD4,0x3D0A8BD4,0xBD0A8BD4,\n    },{\n        0xBE47C5C2,0xBF0E39DA,0x3E47C5C2,0x3F0E39DA,0x3E47C5C2,0x3F0E39DA,0xBE47C5C2,0xBF0E39DA,\n        0x3E47C5C2,0x3F0E39DA,0xBE47C5C2,0xBF0E39DA,0xBE47C5C2,0xBF0E39DA,0x3E47C5C2,0x3F0E39DA,\n        0x3E47C5C2,0x3F0E39DA,0xBE47C5C2,0xBF0E39DA,0xBE47C5C2,0xBF0E39DA,0x3E47C5C2,0x3F0E39DA,\n        0xBE47C5C2,0xBF0E39DA,0x3E47C5C2,0x3F0E39DA,0x3E47C5C2,0x3F0E39DA,0xBE47C5C2,0xBF0E39DA,\n        0x3E47C5C2,0x3F0E39DA,0xBE47C5C2,0xBF0E39DA,0xBE47C5C2,0xBF0E39DA,0x3E47C5C2,0x3F0E39DA,\n        0xBE47C5C2,0xBF0E39DA,0x3E47C5C2,0x3F0E39DA,0x3E47C5C2,0x3F0E39DA,0xBE47C5C2,0xBF0E39DA,\n        0xBE47C5C2,0xBF0E39DA,0x3E47C5C2,0x3F0E39DA,0x3E47C5C2,0x3F0E39DA,0xBE47C5C2,0xBF0E39DA,\n        0x3E47C5C2,0x3F0E39DA,0xBE47C5C2,0xBF0E39DA,0xBE47C5C2,0xBF0E39DA,0x3E47C5C2,0x3F0E39DA,\n    },{\n        0xBDC8BD36,0xBE94A031,0xBEF15AEA,0xBF226799,0x3DC8BD36,0x3E94A031,0x3EF15AEA,0x3F226799,\n        0x3DC8BD36,0x3E94A031,0x3EF15AEA,0x3F226799,0xBDC8BD36,0xBE94A031,0xBEF15AEA,0xBF226799,\n        0x3DC8BD36,0x3E94A031,0x3EF15AEA,0x3F226799,0xBDC8BD36,0xBE94A031,0xBEF15AEA,0xBF226799,\n        0xBDC8BD36,0xBE94A031,0xBEF15AEA,0xBF226799,0x3DC8BD36,0x3E94A031,0x3EF15AEA,0x3F226799,\n        0x3DC8BD36,0x3E94A031,0x3EF15AEA,0x3F226799,0xBDC8BD36,0xBE94A031,0xBEF15AEA,0xBF226799,\n        0xBDC8BD36,0xBE94A031,0xBEF15AEA,0xBF226799,0x3DC8BD36,0x3E94A031,0x3EF15AEA,0x3F226799,\n        0xBDC8BD36,0xBE94A031,0xBEF15AEA,0xBF226799,0x3DC8BD36,0x3E94A031,0x3EF15AEA,0x3F226799,\n        0x3DC8BD36,0x3E94A031,0x3EF15AEA,0x3F226799,0xBDC8BD36,0xBE94A031,0xBEF15AEA,0xBF226799,\n    },{\n        0xBD48FB30,0xBE164083,0xBE78CFCC,0xBEAC7CD4,0xBEDAE880,0xBF039C3D,0xBF187FC0,0xBF2BEB4A,\n        0x3D48FB30,0x3E164083,0x3E78CFCC,0x3EAC7CD4,0x3EDAE880,0x3F039C3D,0x3F187FC0,0x3F2BEB4A,\n        0x3D48FB30,0x3E164083,0x3E78CFCC,0x3EAC7CD4,0x3EDAE880,0x3F039C3D,0x3F187FC0,0x3F2BEB4A,\n        0xBD48FB30,0xBE164083,0xBE78CFCC,0xBEAC7CD4,0xBEDAE880,0xBF039C3D,0xBF187FC0,0xBF2BEB4A,\n        0x3D48FB30,0x3E164083,0x3E78CFCC,0x3EAC7CD4,0x3EDAE880,0x3F039C3D,0x3F187FC0,0x3F2BEB4A,\n        0xBD48FB30,0xBE164083,0xBE78CFCC,0xBEAC7CD4,0xBEDAE880,0xBF039C3D,0xBF187FC0,0xBF2BEB4A,\n        0xBD48FB30,0xBE164083,0xBE78CFCC,0xBEAC7CD4,0xBEDAE880,0xBF039C3D,0xBF187FC0,0xBF2BEB4A,\n        0x3D48FB30,0x3E164083,0x3E78CFCC,0x3EAC7CD4,0x3EDAE880,0x3F039C3D,0x3F187FC0,0x3F2BEB4A,\n    },{\n        0xBCC90AB0,0xBD96A905,0xBDFAB273,0xBE2F10A2,0xBE605C13,0xBE888E93,0xBEA09AE5,0xBEB8442A,\n        0xBECF7BCA,0xBEE63375,0xBEFC5D27,0xBF08F59B,0xBF13682A,0xBF1D7FD1,0xBF273656,0xBF3085BB,\n        0x3CC90AB0,0x3D96A905,0x3DFAB273,0x3E2F10A2,0x3E605C13,0x3E888E93,0x3EA09AE5,0x3EB8442A,\n        0x3ECF7BCA,0x3EE63375,0x3EFC5D27,0x3F08F59B,0x3F13682A,0x3F1D7FD1,0x3F273656,0x3F3085BB,\n        0x3CC90AB0,0x3D96A905,0x3DFAB273,0x3E2F10A2,0x3E605C13,0x3E888E93,0x3EA09AE5,0x3EB8442A,\n        0x3ECF7BCA,0x3EE63375,0x3EFC5D27,0x3F08F59B,0x3F13682A,0x3F1D7FD1,0x3F273656,0x3F3085BB,\n        0xBCC90AB0,0xBD96A905,0xBDFAB273,0xBE2F10A2,0xBE605C13,0xBE888E93,0xBEA09AE5,0xBEB8442A,\n        0xBECF7BCA,0xBEE63375,0xBEFC5D27,0xBF08F59B,0xBF13682A,0xBF1D7FD1,0xBF273656,0xBF3085BB,\n    },{\n        0xBC490E90,0xBD16C32C,0xBD7B2B74,0xBDAFB680,0xBDE1BC2E,0xBE09CF86,0xBE22ABB6,0xBE3B6ECF,\n        0xBE541501,0xBE6C9A7F,0xBE827DC0,0xBE8E9A22,0xBE9AA086,0xBEA68F12,0xBEB263EF,0xBEBE1D4A,\n        0xBEC9B953,0xBED53641,0xBEE0924F,0xBEEBCBBB,0xBEF6E0CB,0xBF00E7E4,0xBF064B82,0xBF0B9A6B,\n        0xBF10D3CD,0xBF15F6D9,0xBF1B02C6,0xBF1FF6CB,0xBF24D225,0xBF299415,0xBF2E3BDE,0xBF32C8C9,\n        0x3C490E90,0x3D16C32C,0x3D7B2B74,0x3DAFB680,0x3DE1BC2E,0x3E09CF86,0x3E22ABB6,0x3E3B6ECF,\n        0x3E541501,0x3E6C9A7F,0x3E827DC0,0x3E8E9A22,0x3E9AA086,0x3EA68F12,0x3EB263EF,0x3EBE1D4A,\n        0x3EC9B953,0x3ED53641,0x3EE0924F,0x3EEBCBBB,0x3EF6E0CB,0x3F00E7E4,0x3F064B82,0x3F0B9A6B,\n        0x3F10D3CD,0x3F15F6D9,0x3F1B02C6,0x3F1FF6CB,0x3F24D225,0x3F299415,0x3F2E3BDE,0x3F32C8C9,\n    },{\n        0xBBC90F88,0xBC96C9B6,0xBCFB49BA,0xBD2FE007,0xBD621469,0xBD8A200A,0xBDA3308C,0xBDBC3AC3,\n        0xBDD53DB9,0xBDEE3876,0xBE039502,0xBE1008B7,0xBE1C76DE,0xBE28DEFC,0xBE354098,0xBE419B37,\n        0xBE4DEE60,0xBE5A3997,0xBE667C66,0xBE72B651,0xBE7EE6E1,0xBE8586CE,0xBE8B9507,0xBE919DDD,\n        0xBE97A117,0xBE9D9E78,0xBEA395C5,0xBEA986C4,0xBEAF713A,0xBEB554EC,0xBEBB31A0,0xBEC1071E,\n        0xBEC6D529,0xBECC9B8B,0xBED25A09,0xBED8106B,0xBEDDBE79,0xBEE363FA,0xBEE900B7,0xBEEE9479,\n        0xBEF41F07,0xBEF9A02D,0xBEFF17B2,0xBF0242B1,0xBF04F484,0xBF07A136,0xBF0A48AD,0xBF0CEAD0,\n        0xBF0F8784,0xBF121EB0,0xBF14B039,0xBF173C07,0xBF19C200,0xBF1C420C,0xBF1EBC12,0xBF212FF9,\n        0xBF239DA9,0xBF26050A,0xBF286605,0xBF2AC082,0xBF2D1469,0xBF2F61A5,0xBF31A81D,0xBF33E7BC,\n    }\n};\n\n/* HCA window function, close to a KBD window with an alpha of around 3.82 (similar to AAC/Vorbis) */\nstatic const unsigned int decode5_imdct_window_int[128] = {\n    0x3A3504F0,0x3B0183B8,0x3B70C538,0x3BBB9268,0x3C04A809,0x3C308200,0x3C61284C,0x3C8B3F17,\n    0x3CA83992,0x3CC77FBD,0x3CE91110,0x3D0677CD,0x3D198FC4,0x3D2DD35C,0x3D434643,0x3D59ECC1,\n    0x3D71CBA8,0x3D85741E,0x3D92A413,0x3DA078B4,0x3DAEF522,0x3DBE1C9E,0x3DCDF27B,0x3DDE7A1D,\n    0x3DEFB6ED,0x3E00D62B,0x3E0A2EDA,0x3E13E72A,0x3E1E00B1,0x3E287CF2,0x3E335D55,0x3E3EA321,\n    0x3E4A4F75,0x3E56633F,0x3E62DF37,0x3E6FC3D1,0x3E7D1138,0x3E8563A2,0x3E8C72B7,0x3E93B561,\n    0x3E9B2AEF,0x3EA2D26F,0x3EAAAAAB,0x3EB2B222,0x3EBAE706,0x3EC34737,0x3ECBD03D,0x3ED47F46,\n    0x3EDD5128,0x3EE6425C,0x3EEF4EFF,0x3EF872D7,0x3F00D4A9,0x3F0576CA,0x3F0A1D3B,0x3F0EC548,\n    0x3F136C25,0x3F180EF2,0x3F1CAAC2,0x3F213CA2,0x3F25C1A5,0x3F2A36E7,0x3F2E9998,0x3F32E705,\n\n    0xBF371C9E,0xBF3B37FE,0xBF3F36F2,0xBF431780,0xBF46D7E6,0xBF4A76A4,0xBF4DF27C,0xBF514A6F,\n    0xBF547DC5,0xBF578C03,0xBF5A74EE,0xBF5D3887,0xBF5FD707,0xBF6250DA,0xBF64A699,0xBF66D908,\n    0xBF68E90E,0xBF6AD7B1,0xBF6CA611,0xBF6E5562,0xBF6FE6E7,0xBF715BEF,0xBF72B5D1,0xBF73F5E6,\n    0xBF751D89,0xBF762E13,0xBF7728D7,0xBF780F20,0xBF78E234,0xBF79A34C,0xBF7A5397,0xBF7AF439,\n    0xBF7B8648,0xBF7C0ACE,0xBF7C82C8,0xBF7CEF26,0xBF7D50CB,0xBF7DA88E,0xBF7DF737,0xBF7E3D86,\n    0xBF7E7C2A,0xBF7EB3CC,0xBF7EE507,0xBF7F106C,0xBF7F3683,0xBF7F57CA,0xBF7F74B6,0xBF7F8DB6,\n    0xBF7FA32E,0xBF7FB57B,0xBF7FC4F6,0xBF7FD1ED,0xBF7FDCAD,0xBF7FE579,0xBF7FEC90,0xBF7FF22E,\n    0xBF7FF688,0xBF7FF9D0,0xBF7FFC32,0xBF7FFDDA,0xBF7FFEED,0xBF7FFF8F,0xBF7FFFDF,0xBF7FFFFC,\n};\nstatic const float *decode5_imdct_window = (const float *)decode5_imdct_window_int;\n\nstatic void decoder5_run_imdct(stChannel *ch, int subframe) {\n    const static unsigned int size = HCA_SAMPLES_PER_SUBFRAME;\n    const static unsigned int half = HCA_SAMPLES_PER_SUBFRAME / 2;\n    const static unsigned int mdct_bits = HCA_MDCT_BITS;\n\n\n    /* apply DCT-IV to dequantized spectra */\n    {\n        unsigned int i, j, k;\n        unsigned int count1a, count2a, count1b, count2b;\n        const float *temp1a, *temp1b;\n        float *temp2a, *temp2b;\n\n        /* this is all too crafty for me to simplify, see VGAudio (Mdct.Dct4) */\n\n        temp1a = ch->spectra;\n        temp2a = ch->temp;\n        count1a = 1;\n        count2a = half;\n        for (i = 0; i < mdct_bits; i++) {\n            float *swap;\n            float *d1 = &temp2a[0];\n            float *d2 = &temp2a[count2a];\n\n            for (j = 0; j < count1a; j++) {\n                for (k = 0; k < count2a; k++) {\n                    float a = *(temp1a++);\n                    float b = *(temp1a++);\n                    *(d1++) = b + a;\n                    *(d2++) = a - b;\n                }\n                d1 += count2a;\n                d2 += count2a;\n            }\n            swap = (float*) temp1a - HCA_SAMPLES_PER_SUBFRAME; /* move spectra/temp to beginning */\n            temp1a = temp2a;\n            temp2a = swap;\n\n            count1a = count1a << 1;\n            count2a = count2a >> 1;\n        }\n\n        temp1b = ch->temp;\n        temp2b = ch->spectra;\n        count1b = half;\n        count2b = 1;\n        for (i = 0; i < mdct_bits; i++) {\n            const float *sin_table = (const float *) decode5_sin_tables_int[i];\n            const float *cos_table = (const float *) decode5_cos_tables_int[i];\n            float *swap;\n            float *d1 = temp2b;\n            float *d2 = &temp2b[count2b * 2 - 1];\n            const float *s1 = &temp1b[0];\n            const float *s2 = &temp1b[count2b];\n\n            for (j = 0; j < count1b; j++) {\n                for (k = 0; k < count2b; k++) {\n                    float a = *(s1++);\n                    float b = *(s2++);\n                    float sin = *(sin_table++);\n                    float cos = *(cos_table++);\n                    *(d1++) = a * sin - b * cos;\n                    *(d2--) = a * cos + b * sin;\n                }\n                s1 += count2b;\n                s2 += count2b;\n                d1 += count2b;\n                d2 += count2b * 3;\n            }\n            swap = (float*) temp1b;\n            temp1b = temp2b;\n            temp2b = swap;\n\n            count1b = count1b >> 1;\n            count2b = count2b << 1;\n        }\n\n        /* copy dct */\n        /* (with the above optimization spectra is already modified, so this is redundant) */\n        for (i = 0; i < size; i++) {\n            ch->dct[i] = ch->spectra[i];\n        }\n    }\n\n    /* update output/imdct */\n    {\n        unsigned int i;\n\n        for (i = 0; i < half; i++) {\n            ch->wave[subframe][i] = decode5_imdct_window[i] * ch->dct[i + half] + ch->imdct_previous[i];\n            ch->wave[subframe][i + half] = decode5_imdct_window[i + half] * ch->dct[size - 1 - i] - ch->imdct_previous[i + half];\n            ch->imdct_previous[i] = decode5_imdct_window[size - 1 - i] * ch->dct[half - i - 1];\n            ch->imdct_previous[i + half] = decode5_imdct_window[half - i - 1] * ch->dct[i];\n        }\n#if 0\n        /* over-optimized IMDCT (for reference), barely noticeable even when decoding hundred of files */\n        const float *imdct_window = decode5_imdct_window;\n        const float *dct;\n        float *imdct_previous;\n        float *wave = ch->wave[subframe];\n\n        dct = &ch->dct[half];\n        imdct_previous = ch->imdct_previous;\n        for (i = 0; i < half; i++) {\n            *(wave++) = *(dct++) * *(imdct_window++) + *(imdct_previous++);\n        }\n        for (i = 0; i < half; i++) {\n            *(wave++) = *(imdct_window++) * *(--dct) - *(imdct_previous++);\n        }\n        /* implicit: imdct_window pointer is now at end */\n        dct = &ch->dct[half - 1];\n        imdct_previous = ch->imdct_previous;\n        for (i = 0; i < half; i++) {\n            *(imdct_previous++) = *(--imdct_window) * *(dct--);\n        }\n        for (i = 0; i < half; i++) {\n            *(imdct_previous++) = *(--imdct_window) * *(++dct) ;\n        }\n#endif\n    }\n}\n"
  },
  {
    "path": "vendor/clHCA/clHCA.h",
    "content": "#ifndef _clHCA_H\n#define _clHCA_H\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n\n/* Must pass at least 8 bytes of data to this function.\n * Returns <0 on non-match, or header size on success. */\nint clHCA_isOurFile(const void *data, unsigned int size);\n\n/* The opaque state structure. */\ntypedef struct clHCA clHCA;\n\n/* In case you wish to allocate and reset the structure on your own. */\nint clHCA_sizeof();\nvoid clHCA_clear(clHCA *);\nvoid clHCA_done(clHCA *);\n\n/* Or you could let the library allocate it. */\nclHCA * clHCA_new();\nvoid clHCA_delete(clHCA *);\n\n/* Parses the HCA header. Must be called before any decoding may be performed,\n * and size must be at least headerSize long. The recommended way is to detect\n * the header length with clHCA_isOurFile, then read data and call this.\n * May be called multiple times to reset decoder state.\n * Returns 0 on success, <0 on failure. */\nint clHCA_DecodeHeader(clHCA *, const void *data, unsigned int size);\n\ntypedef struct clHCA_stInfo {\n\tunsigned int version;\n\tunsigned int headerSize;\n\tunsigned int samplingRate;\n\tunsigned int channelCount;\n\tunsigned int blockSize;\n\tunsigned int blockCount;\n    unsigned int encoderDelay;      /* samples appended to the beginning */\n    unsigned int encoderPadding;    /* samples appended to the end */\n\tunsigned int loopEnabled;\n\tunsigned int loopStartBlock;\n\tunsigned int loopEndBlock;\n    unsigned int loopStartDelay;    /* samples in block before loop starts */\n    unsigned int loopEndPadding;    /* samples in block after loop ends */\n    unsigned int samplesPerBlock;   /* should be 1024 */\n\tconst char *comment;\n\tunsigned int encryptionEnabled; /* requires keycode */\n\n\t/* Derived sample formulas:\n\t * - sample count: blockCount*samplesPerBlock - encoderDelay - encoderPadding;\n     * - loop start sample = loopStartBlock*samplesPerBlock - encoderDelay + loopStartDelay\n     * - loop end sample = loopEndBlock*samplesPerBlock - encoderDelay + (samplesPerBlock - info.loopEndPadding)\n     */\n} clHCA_stInfo;\n\n/* Retrieves header information for decoding and playback (it's the caller's responsability\n * to apply looping, encoder delay/skip samples, etc). May be called after clHCA_DecodeHeader.\n * Returns 0 on success, <0 on failure. */\nint clHCA_getInfo(clHCA *, clHCA_stInfo *out);\n\n/* Decodes a single frame, from data after headerSize. Should be called after\n * clHCA_DecodeHeader and size must be at least blockSize long.\n * Data may be modified if encrypted.\n * Returns 0 on success, <0 on failure. */\nint clHCA_DecodeBlock(clHCA *, void *data, unsigned int size);\n\n/* Extracts signed and clipped 16 bit samples into sample buffer.\n * May be called after clHCA_DecodeBlock, and will return the same data until\n * next decode. Buffer must be at least (samplesPerBlock*channels) long. */\nvoid clHCA_ReadSamples16(clHCA *, signed short * outSamples);\n\n/* Sets a 64 bit encryption key, to properly decode blocks. This may be called\n * multiple times to change the key, before or after clHCA_DecodeHeader.\n * Key is ignored if the file is not encrypted. */\nvoid clHCA_SetKey(clHCA *, unsigned long long keycode);\n\n/* Tests a single frame for validity, mainly to test if current key is correct.\n * Returns <0 on incorrect block (wrong key), 0 on silent block (not useful to determine)\n * and >0 if block is correct (the closer to 1 the more likely).\n * Incorrect keys may give a few valid frames, so it's best to test a number of them\n * and select the key with scores closer to 1. */\nint clHCA_TestBlock(clHCA *hca, void *data, unsigned int size);\n\n/* Resets the internal decode state, used when restarting to decode the file from the beginning.\n * Without it there are minor differences, mainly useful when testing a new key. */\nvoid clHCA_DecodeReset(clHCA * hca);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif\n"
  },
  {
    "path": "vendor/glad/src/glad.c",
    "content": "/*\n\n    OpenGL, OpenGL ES loader generated by glad 0.1.27 on 10/02/18 19:25:43.\n\n    Language/Generator: C/C++\n    Specification: gl\n    APIs: gl=3.3, gles2=3.0\n    Profile: core\n    Extensions:\n        GL_3DFX_multisample,\n        GL_3DFX_tbuffer,\n        GL_3DFX_texture_compression_FXT1,\n        GL_AMD_blend_minmax_factor,\n        GL_AMD_compressed_3DC_texture,\n        GL_AMD_compressed_ATC_texture,\n        GL_AMD_conservative_depth,\n        GL_AMD_debug_output,\n        GL_AMD_depth_clamp_separate,\n        GL_AMD_draw_buffers_blend,\n        GL_AMD_framebuffer_multisample_advanced,\n        GL_AMD_framebuffer_sample_positions,\n        GL_AMD_gcn_shader,\n        GL_AMD_gpu_shader_half_float,\n        GL_AMD_gpu_shader_int16,\n        GL_AMD_gpu_shader_int64,\n        GL_AMD_interleaved_elements,\n        GL_AMD_multi_draw_indirect,\n        GL_AMD_name_gen_delete,\n        GL_AMD_occlusion_query_event,\n        GL_AMD_performance_monitor,\n        GL_AMD_pinned_memory,\n        GL_AMD_program_binary_Z400,\n        GL_AMD_query_buffer_object,\n        GL_AMD_sample_positions,\n        GL_AMD_seamless_cubemap_per_texture,\n        GL_AMD_shader_atomic_counter_ops,\n        GL_AMD_shader_ballot,\n        GL_AMD_shader_explicit_vertex_parameter,\n        GL_AMD_shader_gpu_shader_half_float_fetch,\n        GL_AMD_shader_image_load_store_lod,\n        GL_AMD_shader_stencil_export,\n        GL_AMD_shader_trinary_minmax,\n        GL_AMD_sparse_texture,\n        GL_AMD_stencil_operation_extended,\n        GL_AMD_texture_gather_bias_lod,\n        GL_AMD_texture_texture4,\n        GL_AMD_transform_feedback3_lines_triangles,\n        GL_AMD_transform_feedback4,\n        GL_AMD_vertex_shader_layer,\n        GL_AMD_vertex_shader_tessellator,\n        GL_AMD_vertex_shader_viewport_index,\n        GL_ANDROID_extension_pack_es31a,\n        GL_ANGLE_depth_texture,\n        GL_ANGLE_framebuffer_blit,\n        GL_ANGLE_framebuffer_multisample,\n        GL_ANGLE_instanced_arrays,\n        GL_ANGLE_pack_reverse_row_order,\n        GL_ANGLE_program_binary,\n        GL_ANGLE_texture_compression_dxt3,\n        GL_ANGLE_texture_compression_dxt5,\n        GL_ANGLE_texture_usage,\n        GL_ANGLE_translated_shader_source,\n        GL_APPLE_aux_depth_stencil,\n        GL_APPLE_client_storage,\n        GL_APPLE_clip_distance,\n        GL_APPLE_color_buffer_packed_float,\n        GL_APPLE_copy_texture_levels,\n        GL_APPLE_element_array,\n        GL_APPLE_fence,\n        GL_APPLE_float_pixels,\n        GL_APPLE_flush_buffer_range,\n        GL_APPLE_framebuffer_multisample,\n        GL_APPLE_object_purgeable,\n        GL_APPLE_rgb_422,\n        GL_APPLE_row_bytes,\n        GL_APPLE_specular_vector,\n        GL_APPLE_sync,\n        GL_APPLE_texture_format_BGRA8888,\n        GL_APPLE_texture_max_level,\n        GL_APPLE_texture_packed_float,\n        GL_APPLE_texture_range,\n        GL_APPLE_transform_hint,\n        GL_APPLE_vertex_array_object,\n        GL_APPLE_vertex_array_range,\n        GL_APPLE_vertex_program_evaluators,\n        GL_APPLE_ycbcr_422,\n        GL_ARB_ES2_compatibility,\n        GL_ARB_ES3_1_compatibility,\n        GL_ARB_ES3_2_compatibility,\n        GL_ARB_ES3_compatibility,\n        GL_ARB_arrays_of_arrays,\n        GL_ARB_base_instance,\n        GL_ARB_bindless_texture,\n        GL_ARB_blend_func_extended,\n        GL_ARB_buffer_storage,\n        GL_ARB_cl_event,\n        GL_ARB_clear_buffer_object,\n        GL_ARB_clear_texture,\n        GL_ARB_clip_control,\n        GL_ARB_color_buffer_float,\n        GL_ARB_compatibility,\n        GL_ARB_compressed_texture_pixel_storage,\n        GL_ARB_compute_shader,\n        GL_ARB_compute_variable_group_size,\n        GL_ARB_conditional_render_inverted,\n        GL_ARB_conservative_depth,\n        GL_ARB_copy_buffer,\n        GL_ARB_copy_image,\n        GL_ARB_cull_distance,\n        GL_ARB_debug_output,\n        GL_ARB_depth_buffer_float,\n        GL_ARB_depth_clamp,\n        GL_ARB_depth_texture,\n        GL_ARB_derivative_control,\n        GL_ARB_direct_state_access,\n        GL_ARB_draw_buffers,\n        GL_ARB_draw_buffers_blend,\n        GL_ARB_draw_elements_base_vertex,\n        GL_ARB_draw_indirect,\n        GL_ARB_draw_instanced,\n        GL_ARB_enhanced_layouts,\n        GL_ARB_explicit_attrib_location,\n        GL_ARB_explicit_uniform_location,\n        GL_ARB_fragment_coord_conventions,\n        GL_ARB_fragment_layer_viewport,\n        GL_ARB_fragment_program,\n        GL_ARB_fragment_program_shadow,\n        GL_ARB_fragment_shader,\n        GL_ARB_fragment_shader_interlock,\n        GL_ARB_framebuffer_no_attachments,\n        GL_ARB_framebuffer_object,\n        GL_ARB_framebuffer_sRGB,\n        GL_ARB_geometry_shader4,\n        GL_ARB_get_program_binary,\n        GL_ARB_get_texture_sub_image,\n        GL_ARB_gl_spirv,\n        GL_ARB_gpu_shader5,\n        GL_ARB_gpu_shader_fp64,\n        GL_ARB_gpu_shader_int64,\n        GL_ARB_half_float_pixel,\n        GL_ARB_half_float_vertex,\n        GL_ARB_imaging,\n        GL_ARB_indirect_parameters,\n        GL_ARB_instanced_arrays,\n        GL_ARB_internalformat_query,\n        GL_ARB_internalformat_query2,\n        GL_ARB_invalidate_subdata,\n        GL_ARB_map_buffer_alignment,\n        GL_ARB_map_buffer_range,\n        GL_ARB_matrix_palette,\n        GL_ARB_multi_bind,\n        GL_ARB_multi_draw_indirect,\n        GL_ARB_multisample,\n        GL_ARB_multitexture,\n        GL_ARB_occlusion_query,\n        GL_ARB_occlusion_query2,\n        GL_ARB_parallel_shader_compile,\n        GL_ARB_pipeline_statistics_query,\n        GL_ARB_pixel_buffer_object,\n        GL_ARB_point_parameters,\n        GL_ARB_point_sprite,\n        GL_ARB_polygon_offset_clamp,\n        GL_ARB_post_depth_coverage,\n        GL_ARB_program_interface_query,\n        GL_ARB_provoking_vertex,\n        GL_ARB_query_buffer_object,\n        GL_ARB_robust_buffer_access_behavior,\n        GL_ARB_robustness,\n        GL_ARB_robustness_isolation,\n        GL_ARB_sample_locations,\n        GL_ARB_sample_shading,\n        GL_ARB_sampler_objects,\n        GL_ARB_seamless_cube_map,\n        GL_ARB_seamless_cubemap_per_texture,\n        GL_ARB_separate_shader_objects,\n        GL_ARB_shader_atomic_counter_ops,\n        GL_ARB_shader_atomic_counters,\n        GL_ARB_shader_ballot,\n        GL_ARB_shader_bit_encoding,\n        GL_ARB_shader_clock,\n        GL_ARB_shader_draw_parameters,\n        GL_ARB_shader_group_vote,\n        GL_ARB_shader_image_load_store,\n        GL_ARB_shader_image_size,\n        GL_ARB_shader_objects,\n        GL_ARB_shader_precision,\n        GL_ARB_shader_stencil_export,\n        GL_ARB_shader_storage_buffer_object,\n        GL_ARB_shader_subroutine,\n        GL_ARB_shader_texture_image_samples,\n        GL_ARB_shader_texture_lod,\n        GL_ARB_shader_viewport_layer_array,\n        GL_ARB_shading_language_100,\n        GL_ARB_shading_language_420pack,\n        GL_ARB_shading_language_include,\n        GL_ARB_shading_language_packing,\n        GL_ARB_shadow,\n        GL_ARB_shadow_ambient,\n        GL_ARB_sparse_buffer,\n        GL_ARB_sparse_texture,\n        GL_ARB_sparse_texture2,\n        GL_ARB_sparse_texture_clamp,\n        GL_ARB_spirv_extensions,\n        GL_ARB_stencil_texturing,\n        GL_ARB_sync,\n        GL_ARB_tessellation_shader,\n        GL_ARB_texture_barrier,\n        GL_ARB_texture_border_clamp,\n        GL_ARB_texture_buffer_object,\n        GL_ARB_texture_buffer_object_rgb32,\n        GL_ARB_texture_buffer_range,\n        GL_ARB_texture_compression,\n        GL_ARB_texture_compression_bptc,\n        GL_ARB_texture_compression_rgtc,\n        GL_ARB_texture_cube_map,\n        GL_ARB_texture_cube_map_array,\n        GL_ARB_texture_env_add,\n        GL_ARB_texture_env_combine,\n        GL_ARB_texture_env_crossbar,\n        GL_ARB_texture_env_dot3,\n        GL_ARB_texture_filter_anisotropic,\n        GL_ARB_texture_filter_minmax,\n        GL_ARB_texture_float,\n        GL_ARB_texture_gather,\n        GL_ARB_texture_mirror_clamp_to_edge,\n        GL_ARB_texture_mirrored_repeat,\n        GL_ARB_texture_multisample,\n        GL_ARB_texture_non_power_of_two,\n        GL_ARB_texture_query_levels,\n        GL_ARB_texture_query_lod,\n        GL_ARB_texture_rectangle,\n        GL_ARB_texture_rg,\n        GL_ARB_texture_rgb10_a2ui,\n        GL_ARB_texture_stencil8,\n        GL_ARB_texture_storage,\n        GL_ARB_texture_storage_multisample,\n        GL_ARB_texture_swizzle,\n        GL_ARB_texture_view,\n        GL_ARB_timer_query,\n        GL_ARB_transform_feedback2,\n        GL_ARB_transform_feedback3,\n        GL_ARB_transform_feedback_instanced,\n        GL_ARB_transform_feedback_overflow_query,\n        GL_ARB_transpose_matrix,\n        GL_ARB_uniform_buffer_object,\n        GL_ARB_vertex_array_bgra,\n        GL_ARB_vertex_array_object,\n        GL_ARB_vertex_attrib_64bit,\n        GL_ARB_vertex_attrib_binding,\n        GL_ARB_vertex_blend,\n        GL_ARB_vertex_buffer_object,\n        GL_ARB_vertex_program,\n        GL_ARB_vertex_shader,\n        GL_ARB_vertex_type_10f_11f_11f_rev,\n        GL_ARB_vertex_type_2_10_10_10_rev,\n        GL_ARB_viewport_array,\n        GL_ARB_window_pos,\n        GL_ARM_mali_program_binary,\n        GL_ARM_mali_shader_binary,\n        GL_ARM_rgba8,\n        GL_ARM_shader_framebuffer_fetch,\n        GL_ARM_shader_framebuffer_fetch_depth_stencil,\n        GL_ATI_draw_buffers,\n        GL_ATI_element_array,\n        GL_ATI_envmap_bumpmap,\n        GL_ATI_fragment_shader,\n        GL_ATI_map_object_buffer,\n        GL_ATI_meminfo,\n        GL_ATI_pixel_format_float,\n        GL_ATI_pn_triangles,\n        GL_ATI_separate_stencil,\n        GL_ATI_text_fragment_shader,\n        GL_ATI_texture_env_combine3,\n        GL_ATI_texture_float,\n        GL_ATI_texture_mirror_once,\n        GL_ATI_vertex_array_object,\n        GL_ATI_vertex_attrib_array_object,\n        GL_ATI_vertex_streams,\n        GL_DMP_program_binary,\n        GL_DMP_shader_binary,\n        GL_EXT_422_pixels,\n        GL_EXT_EGL_image_array,\n        GL_EXT_EGL_image_storage,\n        GL_EXT_YUV_target,\n        GL_EXT_abgr,\n        GL_EXT_base_instance,\n        GL_EXT_bgra,\n        GL_EXT_bindable_uniform,\n        GL_EXT_blend_color,\n        GL_EXT_blend_equation_separate,\n        GL_EXT_blend_func_extended,\n        GL_EXT_blend_func_separate,\n        GL_EXT_blend_logic_op,\n        GL_EXT_blend_minmax,\n        GL_EXT_blend_subtract,\n        GL_EXT_buffer_storage,\n        GL_EXT_clear_texture,\n        GL_EXT_clip_control,\n        GL_EXT_clip_cull_distance,\n        GL_EXT_clip_volume_hint,\n        GL_EXT_cmyka,\n        GL_EXT_color_buffer_float,\n        GL_EXT_color_buffer_half_float,\n        GL_EXT_color_subtable,\n        GL_EXT_compiled_vertex_array,\n        GL_EXT_conservative_depth,\n        GL_EXT_convolution,\n        GL_EXT_coordinate_frame,\n        GL_EXT_copy_image,\n        GL_EXT_copy_texture,\n        GL_EXT_cull_vertex,\n        GL_EXT_debug_label,\n        GL_EXT_debug_marker,\n        GL_EXT_depth_bounds_test,\n        GL_EXT_direct_state_access,\n        GL_EXT_discard_framebuffer,\n        GL_EXT_disjoint_timer_query,\n        GL_EXT_draw_buffers,\n        GL_EXT_draw_buffers2,\n        GL_EXT_draw_buffers_indexed,\n        GL_EXT_draw_elements_base_vertex,\n        GL_EXT_draw_instanced,\n        GL_EXT_draw_range_elements,\n        GL_EXT_draw_transform_feedback,\n        GL_EXT_external_buffer,\n        GL_EXT_float_blend,\n        GL_EXT_fog_coord,\n        GL_EXT_framebuffer_blit,\n        GL_EXT_framebuffer_multisample,\n        GL_EXT_framebuffer_multisample_blit_scaled,\n        GL_EXT_framebuffer_object,\n        GL_EXT_framebuffer_sRGB,\n        GL_EXT_geometry_point_size,\n        GL_EXT_geometry_shader,\n        GL_EXT_geometry_shader4,\n        GL_EXT_gpu_program_parameters,\n        GL_EXT_gpu_shader4,\n        GL_EXT_gpu_shader5,\n        GL_EXT_histogram,\n        GL_EXT_index_array_formats,\n        GL_EXT_index_func,\n        GL_EXT_index_material,\n        GL_EXT_index_texture,\n        GL_EXT_instanced_arrays,\n        GL_EXT_light_texture,\n        GL_EXT_map_buffer_range,\n        GL_EXT_memory_object,\n        GL_EXT_memory_object_fd,\n        GL_EXT_memory_object_win32,\n        GL_EXT_misc_attribute,\n        GL_EXT_multi_draw_arrays,\n        GL_EXT_multi_draw_indirect,\n        GL_EXT_multisample,\n        GL_EXT_multisampled_compatibility,\n        GL_EXT_multisampled_render_to_texture,\n        GL_EXT_multiview_draw_buffers,\n        GL_EXT_occlusion_query_boolean,\n        GL_EXT_packed_depth_stencil,\n        GL_EXT_packed_float,\n        GL_EXT_packed_pixels,\n        GL_EXT_paletted_texture,\n        GL_EXT_pixel_buffer_object,\n        GL_EXT_pixel_transform,\n        GL_EXT_pixel_transform_color_table,\n        GL_EXT_point_parameters,\n        GL_EXT_polygon_offset,\n        GL_EXT_polygon_offset_clamp,\n        GL_EXT_post_depth_coverage,\n        GL_EXT_primitive_bounding_box,\n        GL_EXT_protected_textures,\n        GL_EXT_provoking_vertex,\n        GL_EXT_pvrtc_sRGB,\n        GL_EXT_raster_multisample,\n        GL_EXT_read_format_bgra,\n        GL_EXT_render_snorm,\n        GL_EXT_rescale_normal,\n        GL_EXT_robustness,\n        GL_EXT_sRGB,\n        GL_EXT_sRGB_write_control,\n        GL_EXT_secondary_color,\n        GL_EXT_semaphore,\n        GL_EXT_semaphore_fd,\n        GL_EXT_semaphore_win32,\n        GL_EXT_separate_shader_objects,\n        GL_EXT_separate_specular_color,\n        GL_EXT_shader_framebuffer_fetch,\n        GL_EXT_shader_framebuffer_fetch_non_coherent,\n        GL_EXT_shader_group_vote,\n        GL_EXT_shader_image_load_formatted,\n        GL_EXT_shader_image_load_store,\n        GL_EXT_shader_implicit_conversions,\n        GL_EXT_shader_integer_mix,\n        GL_EXT_shader_io_blocks,\n        GL_EXT_shader_non_constant_global_initializers,\n        GL_EXT_shader_pixel_local_storage,\n        GL_EXT_shader_pixel_local_storage2,\n        GL_EXT_shader_texture_lod,\n        GL_EXT_shadow_funcs,\n        GL_EXT_shadow_samplers,\n        GL_EXT_shared_texture_palette,\n        GL_EXT_sparse_texture,\n        GL_EXT_sparse_texture2,\n        GL_EXT_stencil_clear_tag,\n        GL_EXT_stencil_two_side,\n        GL_EXT_stencil_wrap,\n        GL_EXT_subtexture,\n        GL_EXT_tessellation_point_size,\n        GL_EXT_tessellation_shader,\n        GL_EXT_texture,\n        GL_EXT_texture3D,\n        GL_EXT_texture_array,\n        GL_EXT_texture_border_clamp,\n        GL_EXT_texture_buffer,\n        GL_EXT_texture_buffer_object,\n        GL_EXT_texture_compression_astc_decode_mode,\n        GL_EXT_texture_compression_bptc,\n        GL_EXT_texture_compression_dxt1,\n        GL_EXT_texture_compression_latc,\n        GL_EXT_texture_compression_rgtc,\n        GL_EXT_texture_compression_s3tc,\n        GL_EXT_texture_compression_s3tc_srgb,\n        GL_EXT_texture_cube_map,\n        GL_EXT_texture_cube_map_array,\n        GL_EXT_texture_env_add,\n        GL_EXT_texture_env_combine,\n        GL_EXT_texture_env_dot3,\n        GL_EXT_texture_filter_anisotropic,\n        GL_EXT_texture_filter_minmax,\n        GL_EXT_texture_format_BGRA8888,\n        GL_EXT_texture_format_sRGB_override,\n        GL_EXT_texture_integer,\n        GL_EXT_texture_lod_bias,\n        GL_EXT_texture_mirror_clamp,\n        GL_EXT_texture_mirror_clamp_to_edge,\n        GL_EXT_texture_norm16,\n        GL_EXT_texture_object,\n        GL_EXT_texture_perturb_normal,\n        GL_EXT_texture_rg,\n        GL_EXT_texture_sRGB,\n        GL_EXT_texture_sRGB_R8,\n        GL_EXT_texture_sRGB_RG8,\n        GL_EXT_texture_sRGB_decode,\n        GL_EXT_texture_shared_exponent,\n        GL_EXT_texture_snorm,\n        GL_EXT_texture_storage,\n        GL_EXT_texture_swizzle,\n        GL_EXT_texture_type_2_10_10_10_REV,\n        GL_EXT_texture_view,\n        GL_EXT_timer_query,\n        GL_EXT_transform_feedback,\n        GL_EXT_unpack_subimage,\n        GL_EXT_vertex_array,\n        GL_EXT_vertex_array_bgra,\n        GL_EXT_vertex_attrib_64bit,\n        GL_EXT_vertex_shader,\n        GL_EXT_vertex_weighting,\n        GL_EXT_win32_keyed_mutex,\n        GL_EXT_window_rectangles,\n        GL_EXT_x11_sync_object,\n        GL_FJ_shader_binary_GCCSO,\n        GL_GREMEDY_frame_terminator,\n        GL_GREMEDY_string_marker,\n        GL_HP_convolution_border_modes,\n        GL_HP_image_transform,\n        GL_HP_occlusion_test,\n        GL_HP_texture_lighting,\n        GL_IBM_cull_vertex,\n        GL_IBM_multimode_draw_arrays,\n        GL_IBM_rasterpos_clip,\n        GL_IBM_static_data,\n        GL_IBM_texture_mirrored_repeat,\n        GL_IBM_vertex_array_lists,\n        GL_IMG_bindless_texture,\n        GL_IMG_framebuffer_downsample,\n        GL_IMG_multisampled_render_to_texture,\n        GL_IMG_program_binary,\n        GL_IMG_read_format,\n        GL_IMG_shader_binary,\n        GL_IMG_texture_compression_pvrtc,\n        GL_IMG_texture_compression_pvrtc2,\n        GL_IMG_texture_filter_cubic,\n        GL_INGR_blend_func_separate,\n        GL_INGR_color_clamp,\n        GL_INGR_interlace_read,\n        GL_INTEL_blackhole_render,\n        GL_INTEL_conservative_rasterization,\n        GL_INTEL_fragment_shader_ordering,\n        GL_INTEL_framebuffer_CMAA,\n        GL_INTEL_map_texture,\n        GL_INTEL_parallel_arrays,\n        GL_INTEL_performance_query,\n        GL_KHR_blend_equation_advanced,\n        GL_KHR_blend_equation_advanced_coherent,\n        GL_KHR_context_flush_control,\n        GL_KHR_debug,\n        GL_KHR_no_error,\n        GL_KHR_parallel_shader_compile,\n        GL_KHR_robust_buffer_access_behavior,\n        GL_KHR_robustness,\n        GL_KHR_texture_compression_astc_hdr,\n        GL_KHR_texture_compression_astc_ldr,\n        GL_KHR_texture_compression_astc_sliced_3d,\n        GL_MESAX_texture_stack,\n        GL_MESA_framebuffer_flip_y,\n        GL_MESA_pack_invert,\n        GL_MESA_program_binary_formats,\n        GL_MESA_resize_buffers,\n        GL_MESA_shader_integer_functions,\n        GL_MESA_tile_raster_order,\n        GL_MESA_window_pos,\n        GL_MESA_ycbcr_texture,\n        GL_NVX_blend_equation_advanced_multi_draw_buffers,\n        GL_NVX_conditional_render,\n        GL_NVX_gpu_memory_info,\n        GL_NVX_linked_gpu_multicast,\n        GL_NV_alpha_to_coverage_dither_control,\n        GL_NV_bindless_multi_draw_indirect,\n        GL_NV_bindless_multi_draw_indirect_count,\n        GL_NV_bindless_texture,\n        GL_NV_blend_equation_advanced,\n        GL_NV_blend_equation_advanced_coherent,\n        GL_NV_blend_minmax_factor,\n        GL_NV_blend_square,\n        GL_NV_clip_space_w_scaling,\n        GL_NV_command_list,\n        GL_NV_compute_program5,\n        GL_NV_compute_shader_derivatives,\n        GL_NV_conditional_render,\n        GL_NV_conservative_raster,\n        GL_NV_conservative_raster_dilate,\n        GL_NV_conservative_raster_pre_snap,\n        GL_NV_conservative_raster_pre_snap_triangles,\n        GL_NV_conservative_raster_underestimation,\n        GL_NV_copy_buffer,\n        GL_NV_copy_depth_to_color,\n        GL_NV_copy_image,\n        GL_NV_coverage_sample,\n        GL_NV_deep_texture3D,\n        GL_NV_depth_buffer_float,\n        GL_NV_depth_clamp,\n        GL_NV_depth_nonlinear,\n        GL_NV_draw_buffers,\n        GL_NV_draw_instanced,\n        GL_NV_draw_texture,\n        GL_NV_draw_vulkan_image,\n        GL_NV_evaluators,\n        GL_NV_explicit_attrib_location,\n        GL_NV_explicit_multisample,\n        GL_NV_fbo_color_attachments,\n        GL_NV_fence,\n        GL_NV_fill_rectangle,\n        GL_NV_float_buffer,\n        GL_NV_fog_distance,\n        GL_NV_fragment_coverage_to_color,\n        GL_NV_fragment_program,\n        GL_NV_fragment_program2,\n        GL_NV_fragment_program4,\n        GL_NV_fragment_program_option,\n        GL_NV_fragment_shader_barycentric,\n        GL_NV_fragment_shader_interlock,\n        GL_NV_framebuffer_blit,\n        GL_NV_framebuffer_mixed_samples,\n        GL_NV_framebuffer_multisample,\n        GL_NV_framebuffer_multisample_coverage,\n        GL_NV_generate_mipmap_sRGB,\n        GL_NV_geometry_program4,\n        GL_NV_geometry_shader4,\n        GL_NV_geometry_shader_passthrough,\n        GL_NV_gpu_multicast,\n        GL_NV_gpu_program4,\n        GL_NV_gpu_program5,\n        GL_NV_gpu_program5_mem_extended,\n        GL_NV_gpu_shader5,\n        GL_NV_half_float,\n        GL_NV_image_formats,\n        GL_NV_instanced_arrays,\n        GL_NV_internalformat_sample_query,\n        GL_NV_light_max_exponent,\n        GL_NV_memory_attachment,\n        GL_NV_mesh_shader,\n        GL_NV_multisample_coverage,\n        GL_NV_multisample_filter_hint,\n        GL_NV_non_square_matrices,\n        GL_NV_occlusion_query,\n        GL_NV_packed_depth_stencil,\n        GL_NV_parameter_buffer_object,\n        GL_NV_parameter_buffer_object2,\n        GL_NV_path_rendering,\n        GL_NV_path_rendering_shared_edge,\n        GL_NV_pixel_buffer_object,\n        GL_NV_pixel_data_range,\n        GL_NV_point_sprite,\n        GL_NV_polygon_mode,\n        GL_NV_present_video,\n        GL_NV_primitive_restart,\n        GL_NV_query_resource,\n        GL_NV_query_resource_tag,\n        GL_NV_read_buffer,\n        GL_NV_read_buffer_front,\n        GL_NV_read_depth,\n        GL_NV_read_depth_stencil,\n        GL_NV_read_stencil,\n        GL_NV_register_combiners,\n        GL_NV_register_combiners2,\n        GL_NV_representative_fragment_test,\n        GL_NV_robustness_video_memory_purge,\n        GL_NV_sRGB_formats,\n        GL_NV_sample_locations,\n        GL_NV_sample_mask_override_coverage,\n        GL_NV_scissor_exclusive,\n        GL_NV_shader_atomic_counters,\n        GL_NV_shader_atomic_float,\n        GL_NV_shader_atomic_float64,\n        GL_NV_shader_atomic_fp16_vector,\n        GL_NV_shader_atomic_int64,\n        GL_NV_shader_buffer_load,\n        GL_NV_shader_buffer_store,\n        GL_NV_shader_noperspective_interpolation,\n        GL_NV_shader_storage_buffer_object,\n        GL_NV_shader_texture_footprint,\n        GL_NV_shader_thread_group,\n        GL_NV_shader_thread_shuffle,\n        GL_NV_shading_rate_image,\n        GL_NV_shadow_samplers_array,\n        GL_NV_shadow_samplers_cube,\n        GL_NV_stereo_view_rendering,\n        GL_NV_tessellation_program5,\n        GL_NV_texgen_emboss,\n        GL_NV_texgen_reflection,\n        GL_NV_texture_barrier,\n        GL_NV_texture_border_clamp,\n        GL_NV_texture_compression_s3tc_update,\n        GL_NV_texture_compression_vtc,\n        GL_NV_texture_env_combine4,\n        GL_NV_texture_expand_normal,\n        GL_NV_texture_multisample,\n        GL_NV_texture_npot_2D_mipmap,\n        GL_NV_texture_rectangle,\n        GL_NV_texture_rectangle_compressed,\n        GL_NV_texture_shader,\n        GL_NV_texture_shader2,\n        GL_NV_texture_shader3,\n        GL_NV_transform_feedback,\n        GL_NV_transform_feedback2,\n        GL_NV_uniform_buffer_unified_memory,\n        GL_NV_vdpau_interop,\n        GL_NV_vertex_array_range,\n        GL_NV_vertex_array_range2,\n        GL_NV_vertex_attrib_integer_64bit,\n        GL_NV_vertex_buffer_unified_memory,\n        GL_NV_vertex_program,\n        GL_NV_vertex_program1_1,\n        GL_NV_vertex_program2,\n        GL_NV_vertex_program2_option,\n        GL_NV_vertex_program3,\n        GL_NV_vertex_program4,\n        GL_NV_video_capture,\n        GL_NV_viewport_array,\n        GL_NV_viewport_array2,\n        GL_NV_viewport_swizzle,\n        GL_OES_EGL_image,\n        GL_OES_EGL_image_external,\n        GL_OES_EGL_image_external_essl3,\n        GL_OES_byte_coordinates,\n        GL_OES_compressed_ETC1_RGB8_sub_texture,\n        GL_OES_compressed_ETC1_RGB8_texture,\n        GL_OES_compressed_paletted_texture,\n        GL_OES_copy_image,\n        GL_OES_depth24,\n        GL_OES_depth32,\n        GL_OES_depth_texture,\n        GL_OES_draw_buffers_indexed,\n        GL_OES_draw_elements_base_vertex,\n        GL_OES_element_index_uint,\n        GL_OES_fbo_render_mipmap,\n        GL_OES_fixed_point,\n        GL_OES_fragment_precision_high,\n        GL_OES_geometry_point_size,\n        GL_OES_geometry_shader,\n        GL_OES_get_program_binary,\n        GL_OES_gpu_shader5,\n        GL_OES_mapbuffer,\n        GL_OES_packed_depth_stencil,\n        GL_OES_primitive_bounding_box,\n        GL_OES_query_matrix,\n        GL_OES_read_format,\n        GL_OES_required_internalformat,\n        GL_OES_rgb8_rgba8,\n        GL_OES_sample_shading,\n        GL_OES_sample_variables,\n        GL_OES_shader_image_atomic,\n        GL_OES_shader_io_blocks,\n        GL_OES_shader_multisample_interpolation,\n        GL_OES_single_precision,\n        GL_OES_standard_derivatives,\n        GL_OES_stencil1,\n        GL_OES_stencil4,\n        GL_OES_surfaceless_context,\n        GL_OES_tessellation_point_size,\n        GL_OES_tessellation_shader,\n        GL_OES_texture_3D,\n        GL_OES_texture_border_clamp,\n        GL_OES_texture_buffer,\n        GL_OES_texture_compression_astc,\n        GL_OES_texture_cube_map_array,\n        GL_OES_texture_float,\n        GL_OES_texture_float_linear,\n        GL_OES_texture_half_float,\n        GL_OES_texture_half_float_linear,\n        GL_OES_texture_npot,\n        GL_OES_texture_stencil8,\n        GL_OES_texture_storage_multisample_2d_array,\n        GL_OES_texture_view,\n        GL_OES_vertex_array_object,\n        GL_OES_vertex_half_float,\n        GL_OES_vertex_type_10_10_10_2,\n        GL_OES_viewport_array,\n        GL_OML_interlace,\n        GL_OML_resample,\n        GL_OML_subsample,\n        GL_OVR_multiview,\n        GL_OVR_multiview2,\n        GL_OVR_multiview_multisampled_render_to_texture,\n        GL_PGI_misc_hints,\n        GL_PGI_vertex_hints,\n        GL_QCOM_alpha_test,\n        GL_QCOM_binning_control,\n        GL_QCOM_driver_control,\n        GL_QCOM_extended_get,\n        GL_QCOM_extended_get2,\n        GL_QCOM_framebuffer_foveated,\n        GL_QCOM_perfmon_global_mode,\n        GL_QCOM_shader_framebuffer_fetch_noncoherent,\n        GL_QCOM_shader_framebuffer_fetch_rate,\n        GL_QCOM_texture_foveated,\n        GL_QCOM_texture_foveated_subsampled_layout,\n        GL_QCOM_tiled_rendering,\n        GL_QCOM_writeonly_rendering,\n        GL_REND_screen_coordinates,\n        GL_S3_s3tc,\n        GL_SGIS_detail_texture,\n        GL_SGIS_fog_function,\n        GL_SGIS_generate_mipmap,\n        GL_SGIS_multisample,\n        GL_SGIS_pixel_texture,\n        GL_SGIS_point_line_texgen,\n        GL_SGIS_point_parameters,\n        GL_SGIS_sharpen_texture,\n        GL_SGIS_texture4D,\n        GL_SGIS_texture_border_clamp,\n        GL_SGIS_texture_color_mask,\n        GL_SGIS_texture_edge_clamp,\n        GL_SGIS_texture_filter4,\n        GL_SGIS_texture_lod,\n        GL_SGIS_texture_select,\n        GL_SGIX_async,\n        GL_SGIX_async_histogram,\n        GL_SGIX_async_pixel,\n        GL_SGIX_blend_alpha_minmax,\n        GL_SGIX_calligraphic_fragment,\n        GL_SGIX_clipmap,\n        GL_SGIX_convolution_accuracy,\n        GL_SGIX_depth_pass_instrument,\n        GL_SGIX_depth_texture,\n        GL_SGIX_flush_raster,\n        GL_SGIX_fog_offset,\n        GL_SGIX_fragment_lighting,\n        GL_SGIX_framezoom,\n        GL_SGIX_igloo_interface,\n        GL_SGIX_instruments,\n        GL_SGIX_interlace,\n        GL_SGIX_ir_instrument1,\n        GL_SGIX_list_priority,\n        GL_SGIX_pixel_texture,\n        GL_SGIX_pixel_tiles,\n        GL_SGIX_polynomial_ffd,\n        GL_SGIX_reference_plane,\n        GL_SGIX_resample,\n        GL_SGIX_scalebias_hint,\n        GL_SGIX_shadow,\n        GL_SGIX_shadow_ambient,\n        GL_SGIX_sprite,\n        GL_SGIX_subsample,\n        GL_SGIX_tag_sample_buffer,\n        GL_SGIX_texture_add_env,\n        GL_SGIX_texture_coordinate_clamp,\n        GL_SGIX_texture_lod_bias,\n        GL_SGIX_texture_multi_buffer,\n        GL_SGIX_texture_scale_bias,\n        GL_SGIX_vertex_preclip,\n        GL_SGIX_ycrcb,\n        GL_SGIX_ycrcb_subsample,\n        GL_SGIX_ycrcba,\n        GL_SGI_color_matrix,\n        GL_SGI_color_table,\n        GL_SGI_texture_color_table,\n        GL_SUNX_constant_data,\n        GL_SUN_convolution_border_modes,\n        GL_SUN_global_alpha,\n        GL_SUN_mesh_array,\n        GL_SUN_slice_accum,\n        GL_SUN_triangle_list,\n        GL_SUN_vertex,\n        GL_VIV_shader_binary,\n        GL_WIN_phong_shading,\n        GL_WIN_specular_fog\n    Loader: True\n    Local files: False\n    Omit khrplatform: False\n\n    Commandline:\n        --profile=\"core\" --api=\"gl=3.3,gles2=3.0\" --generator=\"c\" --spec=\"gl\" --extensions=\"GL_3DFX_multisample,GL_3DFX_tbuffer,GL_3DFX_texture_compression_FXT1,GL_AMD_blend_minmax_factor,GL_AMD_compressed_3DC_texture,GL_AMD_compressed_ATC_texture,GL_AMD_conservative_depth,GL_AMD_debug_output,GL_AMD_depth_clamp_separate,GL_AMD_draw_buffers_blend,GL_AMD_framebuffer_multisample_advanced,GL_AMD_framebuffer_sample_positions,GL_AMD_gcn_shader,GL_AMD_gpu_shader_half_float,GL_AMD_gpu_shader_int16,GL_AMD_gpu_shader_int64,GL_AMD_interleaved_elements,GL_AMD_multi_draw_indirect,GL_AMD_name_gen_delete,GL_AMD_occlusion_query_event,GL_AMD_performance_monitor,GL_AMD_pinned_memory,GL_AMD_program_binary_Z400,GL_AMD_query_buffer_object,GL_AMD_sample_positions,GL_AMD_seamless_cubemap_per_texture,GL_AMD_shader_atomic_counter_ops,GL_AMD_shader_ballot,GL_AMD_shader_explicit_vertex_parameter,GL_AMD_shader_gpu_shader_half_float_fetch,GL_AMD_shader_image_load_store_lod,GL_AMD_shader_stencil_export,GL_AMD_shader_trinary_minmax,GL_AMD_sparse_texture,GL_AMD_stencil_operation_extended,GL_AMD_texture_gather_bias_lod,GL_AMD_texture_texture4,GL_AMD_transform_feedback3_lines_triangles,GL_AMD_transform_feedback4,GL_AMD_vertex_shader_layer,GL_AMD_vertex_shader_tessellator,GL_AMD_vertex_shader_viewport_index,GL_ANDROID_extension_pack_es31a,GL_ANGLE_depth_texture,GL_ANGLE_framebuffer_blit,GL_ANGLE_framebuffer_multisample,GL_ANGLE_instanced_arrays,GL_ANGLE_pack_reverse_row_order,GL_ANGLE_program_binary,GL_ANGLE_texture_compression_dxt3,GL_ANGLE_texture_compression_dxt5,GL_ANGLE_texture_usage,GL_ANGLE_translated_shader_source,GL_APPLE_aux_depth_stencil,GL_APPLE_client_storage,GL_APPLE_clip_distance,GL_APPLE_color_buffer_packed_float,GL_APPLE_copy_texture_levels,GL_APPLE_element_array,GL_APPLE_fence,GL_APPLE_float_pixels,GL_APPLE_flush_buffer_range,GL_APPLE_framebuffer_multisample,GL_APPLE_object_purgeable,GL_APPLE_rgb_422,GL_APPLE_row_bytes,GL_APPLE_specular_vector,GL_APPLE_sync,GL_APPLE_texture_format_BGRA8888,GL_APPLE_texture_max_level,GL_APPLE_texture_packed_float,GL_APPLE_texture_range,GL_APPLE_transform_hint,GL_APPLE_vertex_array_object,GL_APPLE_vertex_array_range,GL_APPLE_vertex_program_evaluators,GL_APPLE_ycbcr_422,GL_ARB_ES2_compatibility,GL_ARB_ES3_1_compatibility,GL_ARB_ES3_2_compatibility,GL_ARB_ES3_compatibility,GL_ARB_arrays_of_arrays,GL_ARB_base_instance,GL_ARB_bindless_texture,GL_ARB_blend_func_extended,GL_ARB_buffer_storage,GL_ARB_cl_event,GL_ARB_clear_buffer_object,GL_ARB_clear_texture,GL_ARB_clip_control,GL_ARB_color_buffer_float,GL_ARB_compatibility,GL_ARB_compressed_texture_pixel_storage,GL_ARB_compute_shader,GL_ARB_compute_variable_group_size,GL_ARB_conditional_render_inverted,GL_ARB_conservative_depth,GL_ARB_copy_buffer,GL_ARB_copy_image,GL_ARB_cull_distance,GL_ARB_debug_output,GL_ARB_depth_buffer_float,GL_ARB_depth_clamp,GL_ARB_depth_texture,GL_ARB_derivative_control,GL_ARB_direct_state_access,GL_ARB_draw_buffers,GL_ARB_draw_buffers_blend,GL_ARB_draw_elements_base_vertex,GL_ARB_draw_indirect,GL_ARB_draw_instanced,GL_ARB_enhanced_layouts,GL_ARB_explicit_attrib_location,GL_ARB_explicit_uniform_location,GL_ARB_fragment_coord_conventions,GL_ARB_fragment_layer_viewport,GL_ARB_fragment_program,GL_ARB_fragment_program_shadow,GL_ARB_fragment_shader,GL_ARB_fragment_shader_interlock,GL_ARB_framebuffer_no_attachments,GL_ARB_framebuffer_object,GL_ARB_framebuffer_sRGB,GL_ARB_geometry_shader4,GL_ARB_get_program_binary,GL_ARB_get_texture_sub_image,GL_ARB_gl_spirv,GL_ARB_gpu_shader5,GL_ARB_gpu_shader_fp64,GL_ARB_gpu_shader_int64,GL_ARB_half_float_pixel,GL_ARB_half_float_vertex,GL_ARB_imaging,GL_ARB_indirect_parameters,GL_ARB_instanced_arrays,GL_ARB_internalformat_query,GL_ARB_internalformat_query2,GL_ARB_invalidate_subdata,GL_ARB_map_buffer_alignment,GL_ARB_map_buffer_range,GL_ARB_matrix_palette,GL_ARB_multi_bind,GL_ARB_multi_draw_indirect,GL_ARB_multisample,GL_ARB_multitexture,GL_ARB_occlusion_query,GL_ARB_occlusion_query2,GL_ARB_parallel_shader_compile,GL_ARB_pipeline_statistics_query,GL_ARB_pixel_buffer_object,GL_ARB_point_parameters,GL_ARB_point_sprite,GL_ARB_polygon_offset_clamp,GL_ARB_post_depth_coverage,GL_ARB_program_interface_query,GL_ARB_provoking_vertex,GL_ARB_query_buffer_object,GL_ARB_robust_buffer_access_behavior,GL_ARB_robustness,GL_ARB_robustness_isolation,GL_ARB_sample_locations,GL_ARB_sample_shading,GL_ARB_sampler_objects,GL_ARB_seamless_cube_map,GL_ARB_seamless_cubemap_per_texture,GL_ARB_separate_shader_objects,GL_ARB_shader_atomic_counter_ops,GL_ARB_shader_atomic_counters,GL_ARB_shader_ballot,GL_ARB_shader_bit_encoding,GL_ARB_shader_clock,GL_ARB_shader_draw_parameters,GL_ARB_shader_group_vote,GL_ARB_shader_image_load_store,GL_ARB_shader_image_size,GL_ARB_shader_objects,GL_ARB_shader_precision,GL_ARB_shader_stencil_export,GL_ARB_shader_storage_buffer_object,GL_ARB_shader_subroutine,GL_ARB_shader_texture_image_samples,GL_ARB_shader_texture_lod,GL_ARB_shader_viewport_layer_array,GL_ARB_shading_language_100,GL_ARB_shading_language_420pack,GL_ARB_shading_language_include,GL_ARB_shading_language_packing,GL_ARB_shadow,GL_ARB_shadow_ambient,GL_ARB_sparse_buffer,GL_ARB_sparse_texture,GL_ARB_sparse_texture2,GL_ARB_sparse_texture_clamp,GL_ARB_spirv_extensions,GL_ARB_stencil_texturing,GL_ARB_sync,GL_ARB_tessellation_shader,GL_ARB_texture_barrier,GL_ARB_texture_border_clamp,GL_ARB_texture_buffer_object,GL_ARB_texture_buffer_object_rgb32,GL_ARB_texture_buffer_range,GL_ARB_texture_compression,GL_ARB_texture_compression_bptc,GL_ARB_texture_compression_rgtc,GL_ARB_texture_cube_map,GL_ARB_texture_cube_map_array,GL_ARB_texture_env_add,GL_ARB_texture_env_combine,GL_ARB_texture_env_crossbar,GL_ARB_texture_env_dot3,GL_ARB_texture_filter_anisotropic,GL_ARB_texture_filter_minmax,GL_ARB_texture_float,GL_ARB_texture_gather,GL_ARB_texture_mirror_clamp_to_edge,GL_ARB_texture_mirrored_repeat,GL_ARB_texture_multisample,GL_ARB_texture_non_power_of_two,GL_ARB_texture_query_levels,GL_ARB_texture_query_lod,GL_ARB_texture_rectangle,GL_ARB_texture_rg,GL_ARB_texture_rgb10_a2ui,GL_ARB_texture_stencil8,GL_ARB_texture_storage,GL_ARB_texture_storage_multisample,GL_ARB_texture_swizzle,GL_ARB_texture_view,GL_ARB_timer_query,GL_ARB_transform_feedback2,GL_ARB_transform_feedback3,GL_ARB_transform_feedback_instanced,GL_ARB_transform_feedback_overflow_query,GL_ARB_transpose_matrix,GL_ARB_uniform_buffer_object,GL_ARB_vertex_array_bgra,GL_ARB_vertex_array_object,GL_ARB_vertex_attrib_64bit,GL_ARB_vertex_attrib_binding,GL_ARB_vertex_blend,GL_ARB_vertex_buffer_object,GL_ARB_vertex_program,GL_ARB_vertex_shader,GL_ARB_vertex_type_10f_11f_11f_rev,GL_ARB_vertex_type_2_10_10_10_rev,GL_ARB_viewport_array,GL_ARB_window_pos,GL_ARM_mali_program_binary,GL_ARM_mali_shader_binary,GL_ARM_rgba8,GL_ARM_shader_framebuffer_fetch,GL_ARM_shader_framebuffer_fetch_depth_stencil,GL_ATI_draw_buffers,GL_ATI_element_array,GL_ATI_envmap_bumpmap,GL_ATI_fragment_shader,GL_ATI_map_object_buffer,GL_ATI_meminfo,GL_ATI_pixel_format_float,GL_ATI_pn_triangles,GL_ATI_separate_stencil,GL_ATI_text_fragment_shader,GL_ATI_texture_env_combine3,GL_ATI_texture_float,GL_ATI_texture_mirror_once,GL_ATI_vertex_array_object,GL_ATI_vertex_attrib_array_object,GL_ATI_vertex_streams,GL_DMP_program_binary,GL_DMP_shader_binary,GL_EXT_422_pixels,GL_EXT_EGL_image_array,GL_EXT_EGL_image_storage,GL_EXT_YUV_target,GL_EXT_abgr,GL_EXT_base_instance,GL_EXT_bgra,GL_EXT_bindable_uniform,GL_EXT_blend_color,GL_EXT_blend_equation_separate,GL_EXT_blend_func_extended,GL_EXT_blend_func_separate,GL_EXT_blend_logic_op,GL_EXT_blend_minmax,GL_EXT_blend_subtract,GL_EXT_buffer_storage,GL_EXT_clear_texture,GL_EXT_clip_control,GL_EXT_clip_cull_distance,GL_EXT_clip_volume_hint,GL_EXT_cmyka,GL_EXT_color_buffer_float,GL_EXT_color_buffer_half_float,GL_EXT_color_subtable,GL_EXT_compiled_vertex_array,GL_EXT_conservative_depth,GL_EXT_convolution,GL_EXT_coordinate_frame,GL_EXT_copy_image,GL_EXT_copy_texture,GL_EXT_cull_vertex,GL_EXT_debug_label,GL_EXT_debug_marker,GL_EXT_depth_bounds_test,GL_EXT_direct_state_access,GL_EXT_discard_framebuffer,GL_EXT_disjoint_timer_query,GL_EXT_draw_buffers,GL_EXT_draw_buffers2,GL_EXT_draw_buffers_indexed,GL_EXT_draw_elements_base_vertex,GL_EXT_draw_instanced,GL_EXT_draw_range_elements,GL_EXT_draw_transform_feedback,GL_EXT_external_buffer,GL_EXT_float_blend,GL_EXT_fog_coord,GL_EXT_framebuffer_blit,GL_EXT_framebuffer_multisample,GL_EXT_framebuffer_multisample_blit_scaled,GL_EXT_framebuffer_object,GL_EXT_framebuffer_sRGB,GL_EXT_geometry_point_size,GL_EXT_geometry_shader,GL_EXT_geometry_shader4,GL_EXT_gpu_program_parameters,GL_EXT_gpu_shader4,GL_EXT_gpu_shader5,GL_EXT_histogram,GL_EXT_index_array_formats,GL_EXT_index_func,GL_EXT_index_material,GL_EXT_index_texture,GL_EXT_instanced_arrays,GL_EXT_light_texture,GL_EXT_map_buffer_range,GL_EXT_memory_object,GL_EXT_memory_object_fd,GL_EXT_memory_object_win32,GL_EXT_misc_attribute,GL_EXT_multi_draw_arrays,GL_EXT_multi_draw_indirect,GL_EXT_multisample,GL_EXT_multisampled_compatibility,GL_EXT_multisampled_render_to_texture,GL_EXT_multiview_draw_buffers,GL_EXT_occlusion_query_boolean,GL_EXT_packed_depth_stencil,GL_EXT_packed_float,GL_EXT_packed_pixels,GL_EXT_paletted_texture,GL_EXT_pixel_buffer_object,GL_EXT_pixel_transform,GL_EXT_pixel_transform_color_table,GL_EXT_point_parameters,GL_EXT_polygon_offset,GL_EXT_polygon_offset_clamp,GL_EXT_post_depth_coverage,GL_EXT_primitive_bounding_box,GL_EXT_protected_textures,GL_EXT_provoking_vertex,GL_EXT_pvrtc_sRGB,GL_EXT_raster_multisample,GL_EXT_read_format_bgra,GL_EXT_render_snorm,GL_EXT_rescale_normal,GL_EXT_robustness,GL_EXT_sRGB,GL_EXT_sRGB_write_control,GL_EXT_secondary_color,GL_EXT_semaphore,GL_EXT_semaphore_fd,GL_EXT_semaphore_win32,GL_EXT_separate_shader_objects,GL_EXT_separate_specular_color,GL_EXT_shader_framebuffer_fetch,GL_EXT_shader_framebuffer_fetch_non_coherent,GL_EXT_shader_group_vote,GL_EXT_shader_image_load_formatted,GL_EXT_shader_image_load_store,GL_EXT_shader_implicit_conversions,GL_EXT_shader_integer_mix,GL_EXT_shader_io_blocks,GL_EXT_shader_non_constant_global_initializers,GL_EXT_shader_pixel_local_storage,GL_EXT_shader_pixel_local_storage2,GL_EXT_shader_texture_lod,GL_EXT_shadow_funcs,GL_EXT_shadow_samplers,GL_EXT_shared_texture_palette,GL_EXT_sparse_texture,GL_EXT_sparse_texture2,GL_EXT_stencil_clear_tag,GL_EXT_stencil_two_side,GL_EXT_stencil_wrap,GL_EXT_subtexture,GL_EXT_tessellation_point_size,GL_EXT_tessellation_shader,GL_EXT_texture,GL_EXT_texture3D,GL_EXT_texture_array,GL_EXT_texture_border_clamp,GL_EXT_texture_buffer,GL_EXT_texture_buffer_object,GL_EXT_texture_compression_astc_decode_mode,GL_EXT_texture_compression_bptc,GL_EXT_texture_compression_dxt1,GL_EXT_texture_compression_latc,GL_EXT_texture_compression_rgtc,GL_EXT_texture_compression_s3tc,GL_EXT_texture_compression_s3tc_srgb,GL_EXT_texture_cube_map,GL_EXT_texture_cube_map_array,GL_EXT_texture_env_add,GL_EXT_texture_env_combine,GL_EXT_texture_env_dot3,GL_EXT_texture_filter_anisotropic,GL_EXT_texture_filter_minmax,GL_EXT_texture_format_BGRA8888,GL_EXT_texture_format_sRGB_override,GL_EXT_texture_integer,GL_EXT_texture_lod_bias,GL_EXT_texture_mirror_clamp,GL_EXT_texture_mirror_clamp_to_edge,GL_EXT_texture_norm16,GL_EXT_texture_object,GL_EXT_texture_perturb_normal,GL_EXT_texture_rg,GL_EXT_texture_sRGB,GL_EXT_texture_sRGB_R8,GL_EXT_texture_sRGB_RG8,GL_EXT_texture_sRGB_decode,GL_EXT_texture_shared_exponent,GL_EXT_texture_snorm,GL_EXT_texture_storage,GL_EXT_texture_swizzle,GL_EXT_texture_type_2_10_10_10_REV,GL_EXT_texture_view,GL_EXT_timer_query,GL_EXT_transform_feedback,GL_EXT_unpack_subimage,GL_EXT_vertex_array,GL_EXT_vertex_array_bgra,GL_EXT_vertex_attrib_64bit,GL_EXT_vertex_shader,GL_EXT_vertex_weighting,GL_EXT_win32_keyed_mutex,GL_EXT_window_rectangles,GL_EXT_x11_sync_object,GL_FJ_shader_binary_GCCSO,GL_GREMEDY_frame_terminator,GL_GREMEDY_string_marker,GL_HP_convolution_border_modes,GL_HP_image_transform,GL_HP_occlusion_test,GL_HP_texture_lighting,GL_IBM_cull_vertex,GL_IBM_multimode_draw_arrays,GL_IBM_rasterpos_clip,GL_IBM_static_data,GL_IBM_texture_mirrored_repeat,GL_IBM_vertex_array_lists,GL_IMG_bindless_texture,GL_IMG_framebuffer_downsample,GL_IMG_multisampled_render_to_texture,GL_IMG_program_binary,GL_IMG_read_format,GL_IMG_shader_binary,GL_IMG_texture_compression_pvrtc,GL_IMG_texture_compression_pvrtc2,GL_IMG_texture_filter_cubic,GL_INGR_blend_func_separate,GL_INGR_color_clamp,GL_INGR_interlace_read,GL_INTEL_blackhole_render,GL_INTEL_conservative_rasterization,GL_INTEL_fragment_shader_ordering,GL_INTEL_framebuffer_CMAA,GL_INTEL_map_texture,GL_INTEL_parallel_arrays,GL_INTEL_performance_query,GL_KHR_blend_equation_advanced,GL_KHR_blend_equation_advanced_coherent,GL_KHR_context_flush_control,GL_KHR_debug,GL_KHR_no_error,GL_KHR_parallel_shader_compile,GL_KHR_robust_buffer_access_behavior,GL_KHR_robustness,GL_KHR_texture_compression_astc_hdr,GL_KHR_texture_compression_astc_ldr,GL_KHR_texture_compression_astc_sliced_3d,GL_MESAX_texture_stack,GL_MESA_framebuffer_flip_y,GL_MESA_pack_invert,GL_MESA_program_binary_formats,GL_MESA_resize_buffers,GL_MESA_shader_integer_functions,GL_MESA_tile_raster_order,GL_MESA_window_pos,GL_MESA_ycbcr_texture,GL_NVX_blend_equation_advanced_multi_draw_buffers,GL_NVX_conditional_render,GL_NVX_gpu_memory_info,GL_NVX_linked_gpu_multicast,GL_NV_alpha_to_coverage_dither_control,GL_NV_bindless_multi_draw_indirect,GL_NV_bindless_multi_draw_indirect_count,GL_NV_bindless_texture,GL_NV_blend_equation_advanced,GL_NV_blend_equation_advanced_coherent,GL_NV_blend_minmax_factor,GL_NV_blend_square,GL_NV_clip_space_w_scaling,GL_NV_command_list,GL_NV_compute_program5,GL_NV_compute_shader_derivatives,GL_NV_conditional_render,GL_NV_conservative_raster,GL_NV_conservative_raster_dilate,GL_NV_conservative_raster_pre_snap,GL_NV_conservative_raster_pre_snap_triangles,GL_NV_conservative_raster_underestimation,GL_NV_copy_buffer,GL_NV_copy_depth_to_color,GL_NV_copy_image,GL_NV_coverage_sample,GL_NV_deep_texture3D,GL_NV_depth_buffer_float,GL_NV_depth_clamp,GL_NV_depth_nonlinear,GL_NV_draw_buffers,GL_NV_draw_instanced,GL_NV_draw_texture,GL_NV_draw_vulkan_image,GL_NV_evaluators,GL_NV_explicit_attrib_location,GL_NV_explicit_multisample,GL_NV_fbo_color_attachments,GL_NV_fence,GL_NV_fill_rectangle,GL_NV_float_buffer,GL_NV_fog_distance,GL_NV_fragment_coverage_to_color,GL_NV_fragment_program,GL_NV_fragment_program2,GL_NV_fragment_program4,GL_NV_fragment_program_option,GL_NV_fragment_shader_barycentric,GL_NV_fragment_shader_interlock,GL_NV_framebuffer_blit,GL_NV_framebuffer_mixed_samples,GL_NV_framebuffer_multisample,GL_NV_framebuffer_multisample_coverage,GL_NV_generate_mipmap_sRGB,GL_NV_geometry_program4,GL_NV_geometry_shader4,GL_NV_geometry_shader_passthrough,GL_NV_gpu_multicast,GL_NV_gpu_program4,GL_NV_gpu_program5,GL_NV_gpu_program5_mem_extended,GL_NV_gpu_shader5,GL_NV_half_float,GL_NV_image_formats,GL_NV_instanced_arrays,GL_NV_internalformat_sample_query,GL_NV_light_max_exponent,GL_NV_memory_attachment,GL_NV_mesh_shader,GL_NV_multisample_coverage,GL_NV_multisample_filter_hint,GL_NV_non_square_matrices,GL_NV_occlusion_query,GL_NV_packed_depth_stencil,GL_NV_parameter_buffer_object,GL_NV_parameter_buffer_object2,GL_NV_path_rendering,GL_NV_path_rendering_shared_edge,GL_NV_pixel_buffer_object,GL_NV_pixel_data_range,GL_NV_point_sprite,GL_NV_polygon_mode,GL_NV_present_video,GL_NV_primitive_restart,GL_NV_query_resource,GL_NV_query_resource_tag,GL_NV_read_buffer,GL_NV_read_buffer_front,GL_NV_read_depth,GL_NV_read_depth_stencil,GL_NV_read_stencil,GL_NV_register_combiners,GL_NV_register_combiners2,GL_NV_representative_fragment_test,GL_NV_robustness_video_memory_purge,GL_NV_sRGB_formats,GL_NV_sample_locations,GL_NV_sample_mask_override_coverage,GL_NV_scissor_exclusive,GL_NV_shader_atomic_counters,GL_NV_shader_atomic_float,GL_NV_shader_atomic_float64,GL_NV_shader_atomic_fp16_vector,GL_NV_shader_atomic_int64,GL_NV_shader_buffer_load,GL_NV_shader_buffer_store,GL_NV_shader_noperspective_interpolation,GL_NV_shader_storage_buffer_object,GL_NV_shader_texture_footprint,GL_NV_shader_thread_group,GL_NV_shader_thread_shuffle,GL_NV_shading_rate_image,GL_NV_shadow_samplers_array,GL_NV_shadow_samplers_cube,GL_NV_stereo_view_rendering,GL_NV_tessellation_program5,GL_NV_texgen_emboss,GL_NV_texgen_reflection,GL_NV_texture_barrier,GL_NV_texture_border_clamp,GL_NV_texture_compression_s3tc_update,GL_NV_texture_compression_vtc,GL_NV_texture_env_combine4,GL_NV_texture_expand_normal,GL_NV_texture_multisample,GL_NV_texture_npot_2D_mipmap,GL_NV_texture_rectangle,GL_NV_texture_rectangle_compressed,GL_NV_texture_shader,GL_NV_texture_shader2,GL_NV_texture_shader3,GL_NV_transform_feedback,GL_NV_transform_feedback2,GL_NV_uniform_buffer_unified_memory,GL_NV_vdpau_interop,GL_NV_vertex_array_range,GL_NV_vertex_array_range2,GL_NV_vertex_attrib_integer_64bit,GL_NV_vertex_buffer_unified_memory,GL_NV_vertex_program,GL_NV_vertex_program1_1,GL_NV_vertex_program2,GL_NV_vertex_program2_option,GL_NV_vertex_program3,GL_NV_vertex_program4,GL_NV_video_capture,GL_NV_viewport_array,GL_NV_viewport_array2,GL_NV_viewport_swizzle,GL_OES_EGL_image,GL_OES_EGL_image_external,GL_OES_EGL_image_external_essl3,GL_OES_byte_coordinates,GL_OES_compressed_ETC1_RGB8_sub_texture,GL_OES_compressed_ETC1_RGB8_texture,GL_OES_compressed_paletted_texture,GL_OES_copy_image,GL_OES_depth24,GL_OES_depth32,GL_OES_depth_texture,GL_OES_draw_buffers_indexed,GL_OES_draw_elements_base_vertex,GL_OES_element_index_uint,GL_OES_fbo_render_mipmap,GL_OES_fixed_point,GL_OES_fragment_precision_high,GL_OES_geometry_point_size,GL_OES_geometry_shader,GL_OES_get_program_binary,GL_OES_gpu_shader5,GL_OES_mapbuffer,GL_OES_packed_depth_stencil,GL_OES_primitive_bounding_box,GL_OES_query_matrix,GL_OES_read_format,GL_OES_required_internalformat,GL_OES_rgb8_rgba8,GL_OES_sample_shading,GL_OES_sample_variables,GL_OES_shader_image_atomic,GL_OES_shader_io_blocks,GL_OES_shader_multisample_interpolation,GL_OES_single_precision,GL_OES_standard_derivatives,GL_OES_stencil1,GL_OES_stencil4,GL_OES_surfaceless_context,GL_OES_tessellation_point_size,GL_OES_tessellation_shader,GL_OES_texture_3D,GL_OES_texture_border_clamp,GL_OES_texture_buffer,GL_OES_texture_compression_astc,GL_OES_texture_cube_map_array,GL_OES_texture_float,GL_OES_texture_float_linear,GL_OES_texture_half_float,GL_OES_texture_half_float_linear,GL_OES_texture_npot,GL_OES_texture_stencil8,GL_OES_texture_storage_multisample_2d_array,GL_OES_texture_view,GL_OES_vertex_array_object,GL_OES_vertex_half_float,GL_OES_vertex_type_10_10_10_2,GL_OES_viewport_array,GL_OML_interlace,GL_OML_resample,GL_OML_subsample,GL_OVR_multiview,GL_OVR_multiview2,GL_OVR_multiview_multisampled_render_to_texture,GL_PGI_misc_hints,GL_PGI_vertex_hints,GL_QCOM_alpha_test,GL_QCOM_binning_control,GL_QCOM_driver_control,GL_QCOM_extended_get,GL_QCOM_extended_get2,GL_QCOM_framebuffer_foveated,GL_QCOM_perfmon_global_mode,GL_QCOM_shader_framebuffer_fetch_noncoherent,GL_QCOM_shader_framebuffer_fetch_rate,GL_QCOM_texture_foveated,GL_QCOM_texture_foveated_subsampled_layout,GL_QCOM_tiled_rendering,GL_QCOM_writeonly_rendering,GL_REND_screen_coordinates,GL_S3_s3tc,GL_SGIS_detail_texture,GL_SGIS_fog_function,GL_SGIS_generate_mipmap,GL_SGIS_multisample,GL_SGIS_pixel_texture,GL_SGIS_point_line_texgen,GL_SGIS_point_parameters,GL_SGIS_sharpen_texture,GL_SGIS_texture4D,GL_SGIS_texture_border_clamp,GL_SGIS_texture_color_mask,GL_SGIS_texture_edge_clamp,GL_SGIS_texture_filter4,GL_SGIS_texture_lod,GL_SGIS_texture_select,GL_SGIX_async,GL_SGIX_async_histogram,GL_SGIX_async_pixel,GL_SGIX_blend_alpha_minmax,GL_SGIX_calligraphic_fragment,GL_SGIX_clipmap,GL_SGIX_convolution_accuracy,GL_SGIX_depth_pass_instrument,GL_SGIX_depth_texture,GL_SGIX_flush_raster,GL_SGIX_fog_offset,GL_SGIX_fragment_lighting,GL_SGIX_framezoom,GL_SGIX_igloo_interface,GL_SGIX_instruments,GL_SGIX_interlace,GL_SGIX_ir_instrument1,GL_SGIX_list_priority,GL_SGIX_pixel_texture,GL_SGIX_pixel_tiles,GL_SGIX_polynomial_ffd,GL_SGIX_reference_plane,GL_SGIX_resample,GL_SGIX_scalebias_hint,GL_SGIX_shadow,GL_SGIX_shadow_ambient,GL_SGIX_sprite,GL_SGIX_subsample,GL_SGIX_tag_sample_buffer,GL_SGIX_texture_add_env,GL_SGIX_texture_coordinate_clamp,GL_SGIX_texture_lod_bias,GL_SGIX_texture_multi_buffer,GL_SGIX_texture_scale_bias,GL_SGIX_vertex_preclip,GL_SGIX_ycrcb,GL_SGIX_ycrcb_subsample,GL_SGIX_ycrcba,GL_SGI_color_matrix,GL_SGI_color_table,GL_SGI_texture_color_table,GL_SUNX_constant_data,GL_SUN_convolution_border_modes,GL_SUN_global_alpha,GL_SUN_mesh_array,GL_SUN_slice_accum,GL_SUN_triangle_list,GL_SUN_vertex,GL_VIV_shader_binary,GL_WIN_phong_shading,GL_WIN_specular_fog\"\n    Online:\n        Too many extensions\n*/\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <glad/glad.h>\n\n#if !defined(__SWITCH__)\nstatic void* get_proc(const char *namez);\n#endif\n\n#if defined(_WIN32) || defined(__CYGWIN__)\n#include <windows.h>\nstatic HMODULE libGL;\n\ntypedef void* (APIENTRYP PFNWGLGETPROCADDRESSPROC_PRIVATE)(const char*);\nstatic PFNWGLGETPROCADDRESSPROC_PRIVATE gladGetProcAddressPtr;\n\n#ifdef _MSC_VER\n#ifdef __has_include\n  #if __has_include(<winapifamily.h>)\n    #define HAVE_WINAPIFAMILY 1\n  #endif\n#elif _MSC_VER >= 1700 && !_USING_V110_SDK71_\n  #define HAVE_WINAPIFAMILY 1\n#endif\n#endif\n\n#ifdef HAVE_WINAPIFAMILY\n  #include <winapifamily.h>\n  #if !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) && WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP)\n    #define IS_UWP 1\n  #endif\n#endif\n\nstatic\nint open_gl(void) {\n#ifndef IS_UWP\n    libGL = LoadLibraryW(L\"opengl32.dll\");\n    if(libGL != NULL) {\n        void (* tmp)(void);\n        tmp = (void(*)(void)) GetProcAddress(libGL, \"wglGetProcAddress\");\n        gladGetProcAddressPtr = (PFNWGLGETPROCADDRESSPROC_PRIVATE) tmp;\n        return gladGetProcAddressPtr != NULL;\n    }\n#endif\n\n    return 0;\n}\n\nstatic\nvoid close_gl(void) {\n    if(libGL != NULL) {\n        FreeLibrary((HMODULE) libGL);\n        libGL = NULL;\n    }\n}\n#elif !defined(__SWITCH__)\n#include <dlfcn.h>\nstatic void* libGL;\n\n#if !defined(__APPLE__) && !defined(__HAIKU__)\ntypedef void* (APIENTRYP PFNGLXGETPROCADDRESSPROC_PRIVATE)(const char*);\nstatic PFNGLXGETPROCADDRESSPROC_PRIVATE gladGetProcAddressPtr;\n#endif\n\nstatic\nint open_gl(void) {\n#ifdef __APPLE__\n    static const char *NAMES[] = {\n        \"../Frameworks/OpenGL.framework/OpenGL\",\n        \"/Library/Frameworks/OpenGL.framework/OpenGL\",\n        \"/System/Library/Frameworks/OpenGL.framework/OpenGL\",\n        \"/System/Library/Frameworks/OpenGL.framework/Versions/Current/OpenGL\"\n    };\n#else\n    static const char *NAMES[] = {\"libGL.so.1\", \"libGL.so\"};\n#endif\n\n    unsigned int index = 0;\n    for(index = 0; index < (sizeof(NAMES) / sizeof(NAMES[0])); index++) {\n        libGL = dlopen(NAMES[index], RTLD_NOW | RTLD_GLOBAL);\n\n        if(libGL != NULL) {\n#if defined(__APPLE__) || defined(__HAIKU__)\n            return 1;\n#else\n            gladGetProcAddressPtr = (PFNGLXGETPROCADDRESSPROC_PRIVATE)dlsym(libGL,\n                \"glXGetProcAddressARB\");\n            return gladGetProcAddressPtr != NULL;\n#endif\n        }\n    }\n\n    return 0;\n}\n\nstatic\nvoid close_gl(void) {\n    if(libGL != NULL) {\n        dlclose(libGL);\n        libGL = NULL;\n    }\n}\n#endif\n\n#if !defined(__SWITCH__)\nstatic\nvoid* get_proc(const char *namez) {\n    void* result = NULL;\n    if(libGL == NULL) return NULL;\n\n#if !defined(__APPLE__) && !defined(__HAIKU__)\n    if(gladGetProcAddressPtr != NULL) {\n        result = gladGetProcAddressPtr(namez);\n    }\n#endif\n    if(result == NULL) {\n#if defined(_WIN32) || defined(__CYGWIN__)\n        result = (void*)GetProcAddress((HMODULE) libGL, namez);\n#else\n        result = dlsym(libGL, namez);\n#endif\n    }\n\n    return result;\n}\n#endif\n\n#if defined(__SWITCH__)\n#include <EGL/egl.h>\nint gladLoadGL(void) { return gladLoadGLLoader((GLADloadproc)eglGetProcAddress); }\n#else\nint gladLoadGL(void) {\n    int status = 0;\n\n    if(open_gl()) {\n        status = gladLoadGLLoader(&get_proc);\n        close_gl();\n    }\n\n    return status;\n}\n#endif\n\nstruct gladGLversionStruct GLVersion = { 0, 0 };\n\n#if defined(GL_ES_VERSION_3_0) || defined(GL_VERSION_3_0)\n#define _GLAD_IS_SOME_NEW_VERSION 1\n#endif\n\nstatic int max_loaded_major;\nstatic int max_loaded_minor;\n\nstatic const char *exts = NULL;\nstatic int num_exts_i = 0;\nstatic char **exts_i = NULL;\n\nstatic int get_exts(void) {\n#ifdef _GLAD_IS_SOME_NEW_VERSION\n    if(max_loaded_major < 3) {\n#endif\n        exts = (const char *)glGetString(GL_EXTENSIONS);\n#ifdef _GLAD_IS_SOME_NEW_VERSION\n    } else {\n        unsigned int index;\n\n        num_exts_i = 0;\n        glGetIntegerv(GL_NUM_EXTENSIONS, &num_exts_i);\n        if (num_exts_i > 0) {\n            exts_i = (char **)realloc((void *)exts_i, (size_t)num_exts_i * (sizeof *exts_i));\n        }\n\n        if (exts_i == NULL) {\n            return 0;\n        }\n\n        for(index = 0; index < (unsigned)num_exts_i; index++) {\n            const char *gl_str_tmp = (const char*)glGetStringi(GL_EXTENSIONS, index);\n            size_t len = strlen(gl_str_tmp);\n\n            char *local_str = (char*)malloc((len+1) * sizeof(char));\n            if(local_str != NULL) {\n                memcpy(local_str, gl_str_tmp, (len+1) * sizeof(char));\n            }\n            exts_i[index] = local_str;\n        }\n    }\n#endif\n    return 1;\n}\n\nstatic void free_exts(void) {\n    if (exts_i != NULL) {\n        int index;\n        for(index = 0; index < num_exts_i; index++) {\n            free((char *)exts_i[index]);\n        }\n        free((void *)exts_i);\n        exts_i = NULL;\n    }\n}\n\nstatic int has_ext(const char *ext) {\n#ifdef _GLAD_IS_SOME_NEW_VERSION\n    if(max_loaded_major < 3) {\n#endif\n        const char *extensions;\n        const char *loc;\n        const char *terminator;\n        extensions = exts;\n        if(extensions == NULL || ext == NULL) {\n            return 0;\n        }\n\n        while(1) {\n            loc = strstr(extensions, ext);\n            if(loc == NULL) {\n                return 0;\n            }\n\n            terminator = loc + strlen(ext);\n            if((loc == extensions || *(loc - 1) == ' ') &&\n                (*terminator == ' ' || *terminator == '\\0')) {\n                return 1;\n            }\n            extensions = terminator;\n        }\n#ifdef _GLAD_IS_SOME_NEW_VERSION\n    } else {\n        int index;\n        if(exts_i == NULL) return 0;\n        for(index = 0; index < num_exts_i; index++) {\n            const char *e = exts_i[index];\n\n            if(exts_i[index] != NULL && strcmp(e, ext) == 0) {\n                return 1;\n            }\n        }\n    }\n#endif\n\n    return 0;\n}\nint GLAD_GL_VERSION_1_0 = 0;\nint GLAD_GL_VERSION_1_1 = 0;\nint GLAD_GL_VERSION_1_2 = 0;\nint GLAD_GL_VERSION_1_3 = 0;\nint GLAD_GL_VERSION_1_4 = 0;\nint GLAD_GL_VERSION_1_5 = 0;\nint GLAD_GL_VERSION_2_0 = 0;\nint GLAD_GL_VERSION_2_1 = 0;\nint GLAD_GL_VERSION_3_0 = 0;\nint GLAD_GL_VERSION_3_1 = 0;\nint GLAD_GL_VERSION_3_2 = 0;\nint GLAD_GL_VERSION_3_3 = 0;\nint GLAD_GL_ES_VERSION_2_0 = 0;\nint GLAD_GL_ES_VERSION_3_0 = 0;\nPFNGLACTIVETEXTUREPROC glad_glActiveTexture = NULL;\nPFNGLATTACHSHADERPROC glad_glAttachShader = NULL;\nPFNGLBEGINCONDITIONALRENDERPROC glad_glBeginConditionalRender = NULL;\nPFNGLBEGINQUERYPROC glad_glBeginQuery = NULL;\nPFNGLBEGINTRANSFORMFEEDBACKPROC glad_glBeginTransformFeedback = NULL;\nPFNGLBINDATTRIBLOCATIONPROC glad_glBindAttribLocation = NULL;\nPFNGLBINDBUFFERPROC glad_glBindBuffer = NULL;\nPFNGLBINDBUFFERBASEPROC glad_glBindBufferBase = NULL;\nPFNGLBINDBUFFERRANGEPROC glad_glBindBufferRange = NULL;\nPFNGLBINDFRAGDATALOCATIONPROC glad_glBindFragDataLocation = NULL;\nPFNGLBINDFRAGDATALOCATIONINDEXEDPROC glad_glBindFragDataLocationIndexed = NULL;\nPFNGLBINDFRAMEBUFFERPROC glad_glBindFramebuffer = NULL;\nPFNGLBINDRENDERBUFFERPROC glad_glBindRenderbuffer = NULL;\nPFNGLBINDSAMPLERPROC glad_glBindSampler = NULL;\nPFNGLBINDTEXTUREPROC glad_glBindTexture = NULL;\nPFNGLBINDTRANSFORMFEEDBACKPROC glad_glBindTransformFeedback = NULL;\nPFNGLBINDVERTEXARRAYPROC glad_glBindVertexArray = NULL;\nPFNGLBLENDCOLORPROC glad_glBlendColor = NULL;\nPFNGLBLENDEQUATIONPROC glad_glBlendEquation = NULL;\nPFNGLBLENDEQUATIONSEPARATEPROC glad_glBlendEquationSeparate = NULL;\nPFNGLBLENDFUNCPROC glad_glBlendFunc = NULL;\nPFNGLBLENDFUNCSEPARATEPROC glad_glBlendFuncSeparate = NULL;\nPFNGLBLITFRAMEBUFFERPROC glad_glBlitFramebuffer = NULL;\nPFNGLBUFFERDATAPROC glad_glBufferData = NULL;\nPFNGLBUFFERSUBDATAPROC glad_glBufferSubData = NULL;\nPFNGLCHECKFRAMEBUFFERSTATUSPROC glad_glCheckFramebufferStatus = NULL;\nPFNGLCLAMPCOLORPROC glad_glClampColor = NULL;\nPFNGLCLEARPROC glad_glClear = NULL;\nPFNGLCLEARBUFFERFIPROC glad_glClearBufferfi = NULL;\nPFNGLCLEARBUFFERFVPROC glad_glClearBufferfv = NULL;\nPFNGLCLEARBUFFERIVPROC glad_glClearBufferiv = NULL;\nPFNGLCLEARBUFFERUIVPROC glad_glClearBufferuiv = NULL;\nPFNGLCLEARCOLORPROC glad_glClearColor = NULL;\nPFNGLCLEARDEPTHPROC glad_glClearDepth = NULL;\nPFNGLCLEARDEPTHFPROC glad_glClearDepthf = NULL;\nPFNGLCLEARSTENCILPROC glad_glClearStencil = NULL;\nPFNGLCLIENTWAITSYNCPROC glad_glClientWaitSync = NULL;\nPFNGLCOLORMASKPROC glad_glColorMask = NULL;\nPFNGLCOLORMASKIPROC glad_glColorMaski = NULL;\nPFNGLCOLORP3UIPROC glad_glColorP3ui = NULL;\nPFNGLCOLORP3UIVPROC glad_glColorP3uiv = NULL;\nPFNGLCOLORP4UIPROC glad_glColorP4ui = NULL;\nPFNGLCOLORP4UIVPROC glad_glColorP4uiv = NULL;\nPFNGLCOMPILESHADERPROC glad_glCompileShader = NULL;\nPFNGLCOMPRESSEDTEXIMAGE1DPROC glad_glCompressedTexImage1D = NULL;\nPFNGLCOMPRESSEDTEXIMAGE2DPROC glad_glCompressedTexImage2D = NULL;\nPFNGLCOMPRESSEDTEXIMAGE3DPROC glad_glCompressedTexImage3D = NULL;\nPFNGLCOMPRESSEDTEXSUBIMAGE1DPROC glad_glCompressedTexSubImage1D = NULL;\nPFNGLCOMPRESSEDTEXSUBIMAGE2DPROC glad_glCompressedTexSubImage2D = NULL;\nPFNGLCOMPRESSEDTEXSUBIMAGE3DPROC glad_glCompressedTexSubImage3D = NULL;\nPFNGLCOPYBUFFERSUBDATAPROC glad_glCopyBufferSubData = NULL;\nPFNGLCOPYTEXIMAGE1DPROC glad_glCopyTexImage1D = NULL;\nPFNGLCOPYTEXIMAGE2DPROC glad_glCopyTexImage2D = NULL;\nPFNGLCOPYTEXSUBIMAGE1DPROC glad_glCopyTexSubImage1D = NULL;\nPFNGLCOPYTEXSUBIMAGE2DPROC glad_glCopyTexSubImage2D = NULL;\nPFNGLCOPYTEXSUBIMAGE3DPROC glad_glCopyTexSubImage3D = NULL;\nPFNGLCREATEPROGRAMPROC glad_glCreateProgram = NULL;\nPFNGLCREATESHADERPROC glad_glCreateShader = NULL;\nPFNGLCULLFACEPROC glad_glCullFace = NULL;\nPFNGLDELETEBUFFERSPROC glad_glDeleteBuffers = NULL;\nPFNGLDELETEFRAMEBUFFERSPROC glad_glDeleteFramebuffers = NULL;\nPFNGLDELETEPROGRAMPROC glad_glDeleteProgram = NULL;\nPFNGLDELETEQUERIESPROC glad_glDeleteQueries = NULL;\nPFNGLDELETERENDERBUFFERSPROC glad_glDeleteRenderbuffers = NULL;\nPFNGLDELETESAMPLERSPROC glad_glDeleteSamplers = NULL;\nPFNGLDELETESHADERPROC glad_glDeleteShader = NULL;\nPFNGLDELETESYNCPROC glad_glDeleteSync = NULL;\nPFNGLDELETETEXTURESPROC glad_glDeleteTextures = NULL;\nPFNGLDELETETRANSFORMFEEDBACKSPROC glad_glDeleteTransformFeedbacks = NULL;\nPFNGLDELETEVERTEXARRAYSPROC glad_glDeleteVertexArrays = NULL;\nPFNGLDEPTHFUNCPROC glad_glDepthFunc = NULL;\nPFNGLDEPTHMASKPROC glad_glDepthMask = NULL;\nPFNGLDEPTHRANGEPROC glad_glDepthRange = NULL;\nPFNGLDEPTHRANGEFPROC glad_glDepthRangef = NULL;\nPFNGLDETACHSHADERPROC glad_glDetachShader = NULL;\nPFNGLDISABLEPROC glad_glDisable = NULL;\nPFNGLDISABLEVERTEXATTRIBARRAYPROC glad_glDisableVertexAttribArray = NULL;\nPFNGLDISABLEIPROC glad_glDisablei = NULL;\nPFNGLDRAWARRAYSPROC glad_glDrawArrays = NULL;\nPFNGLDRAWARRAYSINSTANCEDPROC glad_glDrawArraysInstanced = NULL;\nPFNGLDRAWBUFFERPROC glad_glDrawBuffer = NULL;\nPFNGLDRAWBUFFERSPROC glad_glDrawBuffers = NULL;\nPFNGLDRAWELEMENTSPROC glad_glDrawElements = NULL;\nPFNGLDRAWELEMENTSBASEVERTEXPROC glad_glDrawElementsBaseVertex = NULL;\nPFNGLDRAWELEMENTSINSTANCEDPROC glad_glDrawElementsInstanced = NULL;\nPFNGLDRAWELEMENTSINSTANCEDBASEVERTEXPROC glad_glDrawElementsInstancedBaseVertex = NULL;\nPFNGLDRAWRANGEELEMENTSPROC glad_glDrawRangeElements = NULL;\nPFNGLDRAWRANGEELEMENTSBASEVERTEXPROC glad_glDrawRangeElementsBaseVertex = NULL;\nPFNGLENABLEPROC glad_glEnable = NULL;\nPFNGLENABLEVERTEXATTRIBARRAYPROC glad_glEnableVertexAttribArray = NULL;\nPFNGLENABLEIPROC glad_glEnablei = NULL;\nPFNGLENDCONDITIONALRENDERPROC glad_glEndConditionalRender = NULL;\nPFNGLENDQUERYPROC glad_glEndQuery = NULL;\nPFNGLENDTRANSFORMFEEDBACKPROC glad_glEndTransformFeedback = NULL;\nPFNGLFENCESYNCPROC glad_glFenceSync = NULL;\nPFNGLFINISHPROC glad_glFinish = NULL;\nPFNGLFLUSHPROC glad_glFlush = NULL;\nPFNGLFLUSHMAPPEDBUFFERRANGEPROC glad_glFlushMappedBufferRange = NULL;\nPFNGLFRAMEBUFFERRENDERBUFFERPROC glad_glFramebufferRenderbuffer = NULL;\nPFNGLFRAMEBUFFERTEXTUREPROC glad_glFramebufferTexture = NULL;\nPFNGLFRAMEBUFFERTEXTURE1DPROC glad_glFramebufferTexture1D = NULL;\nPFNGLFRAMEBUFFERTEXTURE2DPROC glad_glFramebufferTexture2D = NULL;\nPFNGLFRAMEBUFFERTEXTURE3DPROC glad_glFramebufferTexture3D = NULL;\nPFNGLFRAMEBUFFERTEXTURELAYERPROC glad_glFramebufferTextureLayer = NULL;\nPFNGLFRONTFACEPROC glad_glFrontFace = NULL;\nPFNGLGENBUFFERSPROC glad_glGenBuffers = NULL;\nPFNGLGENFRAMEBUFFERSPROC glad_glGenFramebuffers = NULL;\nPFNGLGENQUERIESPROC glad_glGenQueries = NULL;\nPFNGLGENRENDERBUFFERSPROC glad_glGenRenderbuffers = NULL;\nPFNGLGENSAMPLERSPROC glad_glGenSamplers = NULL;\nPFNGLGENTEXTURESPROC glad_glGenTextures = NULL;\nPFNGLGENTRANSFORMFEEDBACKSPROC glad_glGenTransformFeedbacks = NULL;\nPFNGLGENVERTEXARRAYSPROC glad_glGenVertexArrays = NULL;\nPFNGLGENERATEMIPMAPPROC glad_glGenerateMipmap = NULL;\nPFNGLGETACTIVEATTRIBPROC glad_glGetActiveAttrib = NULL;\nPFNGLGETACTIVEUNIFORMPROC glad_glGetActiveUniform = NULL;\nPFNGLGETACTIVEUNIFORMBLOCKNAMEPROC glad_glGetActiveUniformBlockName = NULL;\nPFNGLGETACTIVEUNIFORMBLOCKIVPROC glad_glGetActiveUniformBlockiv = NULL;\nPFNGLGETACTIVEUNIFORMNAMEPROC glad_glGetActiveUniformName = NULL;\nPFNGLGETACTIVEUNIFORMSIVPROC glad_glGetActiveUniformsiv = NULL;\nPFNGLGETATTACHEDSHADERSPROC glad_glGetAttachedShaders = NULL;\nPFNGLGETATTRIBLOCATIONPROC glad_glGetAttribLocation = NULL;\nPFNGLGETBOOLEANI_VPROC glad_glGetBooleani_v = NULL;\nPFNGLGETBOOLEANVPROC glad_glGetBooleanv = NULL;\nPFNGLGETBUFFERPARAMETERI64VPROC glad_glGetBufferParameteri64v = NULL;\nPFNGLGETBUFFERPARAMETERIVPROC glad_glGetBufferParameteriv = NULL;\nPFNGLGETBUFFERPOINTERVPROC glad_glGetBufferPointerv = NULL;\nPFNGLGETBUFFERSUBDATAPROC glad_glGetBufferSubData = NULL;\nPFNGLGETCOMPRESSEDTEXIMAGEPROC glad_glGetCompressedTexImage = NULL;\nPFNGLGETDOUBLEVPROC glad_glGetDoublev = NULL;\nPFNGLGETERRORPROC glad_glGetError = NULL;\nPFNGLGETFLOATVPROC glad_glGetFloatv = NULL;\nPFNGLGETFRAGDATAINDEXPROC glad_glGetFragDataIndex = NULL;\nPFNGLGETFRAGDATALOCATIONPROC glad_glGetFragDataLocation = NULL;\nPFNGLGETFRAMEBUFFERATTACHMENTPARAMETERIVPROC glad_glGetFramebufferAttachmentParameteriv = NULL;\nPFNGLGETINTEGER64I_VPROC glad_glGetInteger64i_v = NULL;\nPFNGLGETINTEGER64VPROC glad_glGetInteger64v = NULL;\nPFNGLGETINTEGERI_VPROC glad_glGetIntegeri_v = NULL;\nPFNGLGETINTEGERVPROC glad_glGetIntegerv = NULL;\nPFNGLGETINTERNALFORMATIVPROC glad_glGetInternalformativ = NULL;\nPFNGLGETMULTISAMPLEFVPROC glad_glGetMultisamplefv = NULL;\nPFNGLGETPROGRAMBINARYPROC glad_glGetProgramBinary = NULL;\nPFNGLGETPROGRAMINFOLOGPROC glad_glGetProgramInfoLog = NULL;\nPFNGLGETPROGRAMIVPROC glad_glGetProgramiv = NULL;\nPFNGLGETQUERYOBJECTI64VPROC glad_glGetQueryObjecti64v = NULL;\nPFNGLGETQUERYOBJECTIVPROC glad_glGetQueryObjectiv = NULL;\nPFNGLGETQUERYOBJECTUI64VPROC glad_glGetQueryObjectui64v = NULL;\nPFNGLGETQUERYOBJECTUIVPROC glad_glGetQueryObjectuiv = NULL;\nPFNGLGETQUERYIVPROC glad_glGetQueryiv = NULL;\nPFNGLGETRENDERBUFFERPARAMETERIVPROC glad_glGetRenderbufferParameteriv = NULL;\nPFNGLGETSAMPLERPARAMETERIIVPROC glad_glGetSamplerParameterIiv = NULL;\nPFNGLGETSAMPLERPARAMETERIUIVPROC glad_glGetSamplerParameterIuiv = NULL;\nPFNGLGETSAMPLERPARAMETERFVPROC glad_glGetSamplerParameterfv = NULL;\nPFNGLGETSAMPLERPARAMETERIVPROC glad_glGetSamplerParameteriv = NULL;\nPFNGLGETSHADERINFOLOGPROC glad_glGetShaderInfoLog = NULL;\nPFNGLGETSHADERPRECISIONFORMATPROC glad_glGetShaderPrecisionFormat = NULL;\nPFNGLGETSHADERSOURCEPROC glad_glGetShaderSource = NULL;\nPFNGLGETSHADERIVPROC glad_glGetShaderiv = NULL;\nPFNGLGETSTRINGPROC glad_glGetString = NULL;\nPFNGLGETSTRINGIPROC glad_glGetStringi = NULL;\nPFNGLGETSYNCIVPROC glad_glGetSynciv = NULL;\nPFNGLGETTEXIMAGEPROC glad_glGetTexImage = NULL;\nPFNGLGETTEXLEVELPARAMETERFVPROC glad_glGetTexLevelParameterfv = NULL;\nPFNGLGETTEXLEVELPARAMETERIVPROC glad_glGetTexLevelParameteriv = NULL;\nPFNGLGETTEXPARAMETERIIVPROC glad_glGetTexParameterIiv = NULL;\nPFNGLGETTEXPARAMETERIUIVPROC glad_glGetTexParameterIuiv = NULL;\nPFNGLGETTEXPARAMETERFVPROC glad_glGetTexParameterfv = NULL;\nPFNGLGETTEXPARAMETERIVPROC glad_glGetTexParameteriv = NULL;\nPFNGLGETTRANSFORMFEEDBACKVARYINGPROC glad_glGetTransformFeedbackVarying = NULL;\nPFNGLGETUNIFORMBLOCKINDEXPROC glad_glGetUniformBlockIndex = NULL;\nPFNGLGETUNIFORMINDICESPROC glad_glGetUniformIndices = NULL;\nPFNGLGETUNIFORMLOCATIONPROC glad_glGetUniformLocation = NULL;\nPFNGLGETUNIFORMFVPROC glad_glGetUniformfv = NULL;\nPFNGLGETUNIFORMIVPROC glad_glGetUniformiv = NULL;\nPFNGLGETUNIFORMUIVPROC glad_glGetUniformuiv = NULL;\nPFNGLGETVERTEXATTRIBIIVPROC glad_glGetVertexAttribIiv = NULL;\nPFNGLGETVERTEXATTRIBIUIVPROC glad_glGetVertexAttribIuiv = NULL;\nPFNGLGETVERTEXATTRIBPOINTERVPROC glad_glGetVertexAttribPointerv = NULL;\nPFNGLGETVERTEXATTRIBDVPROC glad_glGetVertexAttribdv = NULL;\nPFNGLGETVERTEXATTRIBFVPROC glad_glGetVertexAttribfv = NULL;\nPFNGLGETVERTEXATTRIBIVPROC glad_glGetVertexAttribiv = NULL;\nPFNGLHINTPROC glad_glHint = NULL;\nPFNGLINVALIDATEFRAMEBUFFERPROC glad_glInvalidateFramebuffer = NULL;\nPFNGLINVALIDATESUBFRAMEBUFFERPROC glad_glInvalidateSubFramebuffer = NULL;\nPFNGLISBUFFERPROC glad_glIsBuffer = NULL;\nPFNGLISENABLEDPROC glad_glIsEnabled = NULL;\nPFNGLISENABLEDIPROC glad_glIsEnabledi = NULL;\nPFNGLISFRAMEBUFFERPROC glad_glIsFramebuffer = NULL;\nPFNGLISPROGRAMPROC glad_glIsProgram = NULL;\nPFNGLISQUERYPROC glad_glIsQuery = NULL;\nPFNGLISRENDERBUFFERPROC glad_glIsRenderbuffer = NULL;\nPFNGLISSAMPLERPROC glad_glIsSampler = NULL;\nPFNGLISSHADERPROC glad_glIsShader = NULL;\nPFNGLISSYNCPROC glad_glIsSync = NULL;\nPFNGLISTEXTUREPROC glad_glIsTexture = NULL;\nPFNGLISTRANSFORMFEEDBACKPROC glad_glIsTransformFeedback = NULL;\nPFNGLISVERTEXARRAYPROC glad_glIsVertexArray = NULL;\nPFNGLLINEWIDTHPROC glad_glLineWidth = NULL;\nPFNGLLINKPROGRAMPROC glad_glLinkProgram = NULL;\nPFNGLLOGICOPPROC glad_glLogicOp = NULL;\nPFNGLMAPBUFFERPROC glad_glMapBuffer = NULL;\nPFNGLMAPBUFFERRANGEPROC glad_glMapBufferRange = NULL;\nPFNGLMULTIDRAWARRAYSPROC glad_glMultiDrawArrays = NULL;\nPFNGLMULTIDRAWELEMENTSPROC glad_glMultiDrawElements = NULL;\nPFNGLMULTIDRAWELEMENTSBASEVERTEXPROC glad_glMultiDrawElementsBaseVertex = NULL;\nPFNGLMULTITEXCOORDP1UIPROC glad_glMultiTexCoordP1ui = NULL;\nPFNGLMULTITEXCOORDP1UIVPROC glad_glMultiTexCoordP1uiv = NULL;\nPFNGLMULTITEXCOORDP2UIPROC glad_glMultiTexCoordP2ui = NULL;\nPFNGLMULTITEXCOORDP2UIVPROC glad_glMultiTexCoordP2uiv = NULL;\nPFNGLMULTITEXCOORDP3UIPROC glad_glMultiTexCoordP3ui = NULL;\nPFNGLMULTITEXCOORDP3UIVPROC glad_glMultiTexCoordP3uiv = NULL;\nPFNGLMULTITEXCOORDP4UIPROC glad_glMultiTexCoordP4ui = NULL;\nPFNGLMULTITEXCOORDP4UIVPROC glad_glMultiTexCoordP4uiv = NULL;\nPFNGLNORMALP3UIPROC glad_glNormalP3ui = NULL;\nPFNGLNORMALP3UIVPROC glad_glNormalP3uiv = NULL;\nPFNGLPAUSETRANSFORMFEEDBACKPROC glad_glPauseTransformFeedback = NULL;\nPFNGLPIXELSTOREFPROC glad_glPixelStoref = NULL;\nPFNGLPIXELSTOREIPROC glad_glPixelStorei = NULL;\nPFNGLPOINTPARAMETERFPROC glad_glPointParameterf = NULL;\nPFNGLPOINTPARAMETERFVPROC glad_glPointParameterfv = NULL;\nPFNGLPOINTPARAMETERIPROC glad_glPointParameteri = NULL;\nPFNGLPOINTPARAMETERIVPROC glad_glPointParameteriv = NULL;\nPFNGLPOINTSIZEPROC glad_glPointSize = NULL;\nPFNGLPOLYGONMODEPROC glad_glPolygonMode = NULL;\nPFNGLPOLYGONOFFSETPROC glad_glPolygonOffset = NULL;\nPFNGLPRIMITIVERESTARTINDEXPROC glad_glPrimitiveRestartIndex = NULL;\nPFNGLPROGRAMBINARYPROC glad_glProgramBinary = NULL;\nPFNGLPROGRAMPARAMETERIPROC glad_glProgramParameteri = NULL;\nPFNGLPROVOKINGVERTEXPROC glad_glProvokingVertex = NULL;\nPFNGLQUERYCOUNTERPROC glad_glQueryCounter = NULL;\nPFNGLREADBUFFERPROC glad_glReadBuffer = NULL;\nPFNGLREADPIXELSPROC glad_glReadPixels = NULL;\nPFNGLRELEASESHADERCOMPILERPROC glad_glReleaseShaderCompiler = NULL;\nPFNGLRENDERBUFFERSTORAGEPROC glad_glRenderbufferStorage = NULL;\nPFNGLRENDERBUFFERSTORAGEMULTISAMPLEPROC glad_glRenderbufferStorageMultisample = NULL;\nPFNGLRESUMETRANSFORMFEEDBACKPROC glad_glResumeTransformFeedback = NULL;\nPFNGLSAMPLECOVERAGEPROC glad_glSampleCoverage = NULL;\nPFNGLSAMPLEMASKIPROC glad_glSampleMaski = NULL;\nPFNGLSAMPLERPARAMETERIIVPROC glad_glSamplerParameterIiv = NULL;\nPFNGLSAMPLERPARAMETERIUIVPROC glad_glSamplerParameterIuiv = NULL;\nPFNGLSAMPLERPARAMETERFPROC glad_glSamplerParameterf = NULL;\nPFNGLSAMPLERPARAMETERFVPROC glad_glSamplerParameterfv = NULL;\nPFNGLSAMPLERPARAMETERIPROC glad_glSamplerParameteri = NULL;\nPFNGLSAMPLERPARAMETERIVPROC glad_glSamplerParameteriv = NULL;\nPFNGLSCISSORPROC glad_glScissor = NULL;\nPFNGLSECONDARYCOLORP3UIPROC glad_glSecondaryColorP3ui = NULL;\nPFNGLSECONDARYCOLORP3UIVPROC glad_glSecondaryColorP3uiv = NULL;\nPFNGLSHADERBINARYPROC glad_glShaderBinary = NULL;\nPFNGLSHADERSOURCEPROC glad_glShaderSource = NULL;\nPFNGLSTENCILFUNCPROC glad_glStencilFunc = NULL;\nPFNGLSTENCILFUNCSEPARATEPROC glad_glStencilFuncSeparate = NULL;\nPFNGLSTENCILMASKPROC glad_glStencilMask = NULL;\nPFNGLSTENCILMASKSEPARATEPROC glad_glStencilMaskSeparate = NULL;\nPFNGLSTENCILOPPROC glad_glStencilOp = NULL;\nPFNGLSTENCILOPSEPARATEPROC glad_glStencilOpSeparate = NULL;\nPFNGLTEXBUFFERPROC glad_glTexBuffer = NULL;\nPFNGLTEXCOORDP1UIPROC glad_glTexCoordP1ui = NULL;\nPFNGLTEXCOORDP1UIVPROC glad_glTexCoordP1uiv = NULL;\nPFNGLTEXCOORDP2UIPROC glad_glTexCoordP2ui = NULL;\nPFNGLTEXCOORDP2UIVPROC glad_glTexCoordP2uiv = NULL;\nPFNGLTEXCOORDP3UIPROC glad_glTexCoordP3ui = NULL;\nPFNGLTEXCOORDP3UIVPROC glad_glTexCoordP3uiv = NULL;\nPFNGLTEXCOORDP4UIPROC glad_glTexCoordP4ui = NULL;\nPFNGLTEXCOORDP4UIVPROC glad_glTexCoordP4uiv = NULL;\nPFNGLTEXIMAGE1DPROC glad_glTexImage1D = NULL;\nPFNGLTEXIMAGE2DPROC glad_glTexImage2D = NULL;\nPFNGLTEXIMAGE2DMULTISAMPLEPROC glad_glTexImage2DMultisample = NULL;\nPFNGLTEXIMAGE3DPROC glad_glTexImage3D = NULL;\nPFNGLTEXIMAGE3DMULTISAMPLEPROC glad_glTexImage3DMultisample = NULL;\nPFNGLTEXPARAMETERIIVPROC glad_glTexParameterIiv = NULL;\nPFNGLTEXPARAMETERIUIVPROC glad_glTexParameterIuiv = NULL;\nPFNGLTEXPARAMETERFPROC glad_glTexParameterf = NULL;\nPFNGLTEXPARAMETERFVPROC glad_glTexParameterfv = NULL;\nPFNGLTEXPARAMETERIPROC glad_glTexParameteri = NULL;\nPFNGLTEXPARAMETERIVPROC glad_glTexParameteriv = NULL;\nPFNGLTEXSTORAGE2DPROC glad_glTexStorage2D = NULL;\nPFNGLTEXSTORAGE3DPROC glad_glTexStorage3D = NULL;\nPFNGLTEXSUBIMAGE1DPROC glad_glTexSubImage1D = NULL;\nPFNGLTEXSUBIMAGE2DPROC glad_glTexSubImage2D = NULL;\nPFNGLTEXSUBIMAGE3DPROC glad_glTexSubImage3D = NULL;\nPFNGLTRANSFORMFEEDBACKVARYINGSPROC glad_glTransformFeedbackVaryings = NULL;\nPFNGLUNIFORM1FPROC glad_glUniform1f = NULL;\nPFNGLUNIFORM1FVPROC glad_glUniform1fv = NULL;\nPFNGLUNIFORM1IPROC glad_glUniform1i = NULL;\nPFNGLUNIFORM1IVPROC glad_glUniform1iv = NULL;\nPFNGLUNIFORM1UIPROC glad_glUniform1ui = NULL;\nPFNGLUNIFORM1UIVPROC glad_glUniform1uiv = NULL;\nPFNGLUNIFORM2FPROC glad_glUniform2f = NULL;\nPFNGLUNIFORM2FVPROC glad_glUniform2fv = NULL;\nPFNGLUNIFORM2IPROC glad_glUniform2i = NULL;\nPFNGLUNIFORM2IVPROC glad_glUniform2iv = NULL;\nPFNGLUNIFORM2UIPROC glad_glUniform2ui = NULL;\nPFNGLUNIFORM2UIVPROC glad_glUniform2uiv = NULL;\nPFNGLUNIFORM3FPROC glad_glUniform3f = NULL;\nPFNGLUNIFORM3FVPROC glad_glUniform3fv = NULL;\nPFNGLUNIFORM3IPROC glad_glUniform3i = NULL;\nPFNGLUNIFORM3IVPROC glad_glUniform3iv = NULL;\nPFNGLUNIFORM3UIPROC glad_glUniform3ui = NULL;\nPFNGLUNIFORM3UIVPROC glad_glUniform3uiv = NULL;\nPFNGLUNIFORM4FPROC glad_glUniform4f = NULL;\nPFNGLUNIFORM4FVPROC glad_glUniform4fv = NULL;\nPFNGLUNIFORM4IPROC glad_glUniform4i = NULL;\nPFNGLUNIFORM4IVPROC glad_glUniform4iv = NULL;\nPFNGLUNIFORM4UIPROC glad_glUniform4ui = NULL;\nPFNGLUNIFORM4UIVPROC glad_glUniform4uiv = NULL;\nPFNGLUNIFORMBLOCKBINDINGPROC glad_glUniformBlockBinding = NULL;\nPFNGLUNIFORMMATRIX2FVPROC glad_glUniformMatrix2fv = NULL;\nPFNGLUNIFORMMATRIX2X3FVPROC glad_glUniformMatrix2x3fv = NULL;\nPFNGLUNIFORMMATRIX2X4FVPROC glad_glUniformMatrix2x4fv = NULL;\nPFNGLUNIFORMMATRIX3FVPROC glad_glUniformMatrix3fv = NULL;\nPFNGLUNIFORMMATRIX3X2FVPROC glad_glUniformMatrix3x2fv = NULL;\nPFNGLUNIFORMMATRIX3X4FVPROC glad_glUniformMatrix3x4fv = NULL;\nPFNGLUNIFORMMATRIX4FVPROC glad_glUniformMatrix4fv = NULL;\nPFNGLUNIFORMMATRIX4X2FVPROC glad_glUniformMatrix4x2fv = NULL;\nPFNGLUNIFORMMATRIX4X3FVPROC glad_glUniformMatrix4x3fv = NULL;\nPFNGLUNMAPBUFFERPROC glad_glUnmapBuffer = NULL;\nPFNGLUSEPROGRAMPROC glad_glUseProgram = NULL;\nPFNGLVALIDATEPROGRAMPROC glad_glValidateProgram = NULL;\nPFNGLVERTEXATTRIB1DPROC glad_glVertexAttrib1d = NULL;\nPFNGLVERTEXATTRIB1DVPROC glad_glVertexAttrib1dv = NULL;\nPFNGLVERTEXATTRIB1FPROC glad_glVertexAttrib1f = NULL;\nPFNGLVERTEXATTRIB1FVPROC glad_glVertexAttrib1fv = NULL;\nPFNGLVERTEXATTRIB1SPROC glad_glVertexAttrib1s = NULL;\nPFNGLVERTEXATTRIB1SVPROC glad_glVertexAttrib1sv = NULL;\nPFNGLVERTEXATTRIB2DPROC glad_glVertexAttrib2d = NULL;\nPFNGLVERTEXATTRIB2DVPROC glad_glVertexAttrib2dv = NULL;\nPFNGLVERTEXATTRIB2FPROC glad_glVertexAttrib2f = NULL;\nPFNGLVERTEXATTRIB2FVPROC glad_glVertexAttrib2fv = NULL;\nPFNGLVERTEXATTRIB2SPROC glad_glVertexAttrib2s = NULL;\nPFNGLVERTEXATTRIB2SVPROC glad_glVertexAttrib2sv = NULL;\nPFNGLVERTEXATTRIB3DPROC glad_glVertexAttrib3d = NULL;\nPFNGLVERTEXATTRIB3DVPROC glad_glVertexAttrib3dv = NULL;\nPFNGLVERTEXATTRIB3FPROC glad_glVertexAttrib3f = NULL;\nPFNGLVERTEXATTRIB3FVPROC glad_glVertexAttrib3fv = NULL;\nPFNGLVERTEXATTRIB3SPROC glad_glVertexAttrib3s = NULL;\nPFNGLVERTEXATTRIB3SVPROC glad_glVertexAttrib3sv = NULL;\nPFNGLVERTEXATTRIB4NBVPROC glad_glVertexAttrib4Nbv = NULL;\nPFNGLVERTEXATTRIB4NIVPROC glad_glVertexAttrib4Niv = NULL;\nPFNGLVERTEXATTRIB4NSVPROC glad_glVertexAttrib4Nsv = NULL;\nPFNGLVERTEXATTRIB4NUBPROC glad_glVertexAttrib4Nub = NULL;\nPFNGLVERTEXATTRIB4NUBVPROC glad_glVertexAttrib4Nubv = NULL;\nPFNGLVERTEXATTRIB4NUIVPROC glad_glVertexAttrib4Nuiv = NULL;\nPFNGLVERTEXATTRIB4NUSVPROC glad_glVertexAttrib4Nusv = NULL;\nPFNGLVERTEXATTRIB4BVPROC glad_glVertexAttrib4bv = NULL;\nPFNGLVERTEXATTRIB4DPROC glad_glVertexAttrib4d = NULL;\nPFNGLVERTEXATTRIB4DVPROC glad_glVertexAttrib4dv = NULL;\nPFNGLVERTEXATTRIB4FPROC glad_glVertexAttrib4f = NULL;\nPFNGLVERTEXATTRIB4FVPROC glad_glVertexAttrib4fv = NULL;\nPFNGLVERTEXATTRIB4IVPROC glad_glVertexAttrib4iv = NULL;\nPFNGLVERTEXATTRIB4SPROC glad_glVertexAttrib4s = NULL;\nPFNGLVERTEXATTRIB4SVPROC glad_glVertexAttrib4sv = NULL;\nPFNGLVERTEXATTRIB4UBVPROC glad_glVertexAttrib4ubv = NULL;\nPFNGLVERTEXATTRIB4UIVPROC glad_glVertexAttrib4uiv = NULL;\nPFNGLVERTEXATTRIB4USVPROC glad_glVertexAttrib4usv = NULL;\nPFNGLVERTEXATTRIBDIVISORPROC glad_glVertexAttribDivisor = NULL;\nPFNGLVERTEXATTRIBI1IPROC glad_glVertexAttribI1i = NULL;\nPFNGLVERTEXATTRIBI1IVPROC glad_glVertexAttribI1iv = NULL;\nPFNGLVERTEXATTRIBI1UIPROC glad_glVertexAttribI1ui = NULL;\nPFNGLVERTEXATTRIBI1UIVPROC glad_glVertexAttribI1uiv = NULL;\nPFNGLVERTEXATTRIBI2IPROC glad_glVertexAttribI2i = NULL;\nPFNGLVERTEXATTRIBI2IVPROC glad_glVertexAttribI2iv = NULL;\nPFNGLVERTEXATTRIBI2UIPROC glad_glVertexAttribI2ui = NULL;\nPFNGLVERTEXATTRIBI2UIVPROC glad_glVertexAttribI2uiv = NULL;\nPFNGLVERTEXATTRIBI3IPROC glad_glVertexAttribI3i = NULL;\nPFNGLVERTEXATTRIBI3IVPROC glad_glVertexAttribI3iv = NULL;\nPFNGLVERTEXATTRIBI3UIPROC glad_glVertexAttribI3ui = NULL;\nPFNGLVERTEXATTRIBI3UIVPROC glad_glVertexAttribI3uiv = NULL;\nPFNGLVERTEXATTRIBI4BVPROC glad_glVertexAttribI4bv = NULL;\nPFNGLVERTEXATTRIBI4IPROC glad_glVertexAttribI4i = NULL;\nPFNGLVERTEXATTRIBI4IVPROC glad_glVertexAttribI4iv = NULL;\nPFNGLVERTEXATTRIBI4SVPROC glad_glVertexAttribI4sv = NULL;\nPFNGLVERTEXATTRIBI4UBVPROC glad_glVertexAttribI4ubv = NULL;\nPFNGLVERTEXATTRIBI4UIPROC glad_glVertexAttribI4ui = NULL;\nPFNGLVERTEXATTRIBI4UIVPROC glad_glVertexAttribI4uiv = NULL;\nPFNGLVERTEXATTRIBI4USVPROC glad_glVertexAttribI4usv = NULL;\nPFNGLVERTEXATTRIBIPOINTERPROC glad_glVertexAttribIPointer = NULL;\nPFNGLVERTEXATTRIBP1UIPROC glad_glVertexAttribP1ui = NULL;\nPFNGLVERTEXATTRIBP1UIVPROC glad_glVertexAttribP1uiv = NULL;\nPFNGLVERTEXATTRIBP2UIPROC glad_glVertexAttribP2ui = NULL;\nPFNGLVERTEXATTRIBP2UIVPROC glad_glVertexAttribP2uiv = NULL;\nPFNGLVERTEXATTRIBP3UIPROC glad_glVertexAttribP3ui = NULL;\nPFNGLVERTEXATTRIBP3UIVPROC glad_glVertexAttribP3uiv = NULL;\nPFNGLVERTEXATTRIBP4UIPROC glad_glVertexAttribP4ui = NULL;\nPFNGLVERTEXATTRIBP4UIVPROC glad_glVertexAttribP4uiv = NULL;\nPFNGLVERTEXATTRIBPOINTERPROC glad_glVertexAttribPointer = NULL;\nPFNGLVERTEXP2UIPROC glad_glVertexP2ui = NULL;\nPFNGLVERTEXP2UIVPROC glad_glVertexP2uiv = NULL;\nPFNGLVERTEXP3UIPROC glad_glVertexP3ui = NULL;\nPFNGLVERTEXP3UIVPROC glad_glVertexP3uiv = NULL;\nPFNGLVERTEXP4UIPROC glad_glVertexP4ui = NULL;\nPFNGLVERTEXP4UIVPROC glad_glVertexP4uiv = NULL;\nPFNGLVIEWPORTPROC glad_glViewport = NULL;\nPFNGLWAITSYNCPROC glad_glWaitSync = NULL;\nint GLAD_GL_3DFX_multisample = 0;\nint GLAD_GL_3DFX_tbuffer = 0;\nint GLAD_GL_3DFX_texture_compression_FXT1 = 0;\nint GLAD_GL_AMD_blend_minmax_factor = 0;\nint GLAD_GL_AMD_compressed_3DC_texture = 0;\nint GLAD_GL_AMD_compressed_ATC_texture = 0;\nint GLAD_GL_AMD_conservative_depth = 0;\nint GLAD_GL_AMD_debug_output = 0;\nint GLAD_GL_AMD_depth_clamp_separate = 0;\nint GLAD_GL_AMD_draw_buffers_blend = 0;\nint GLAD_GL_AMD_framebuffer_multisample_advanced = 0;\nint GLAD_GL_AMD_framebuffer_sample_positions = 0;\nint GLAD_GL_AMD_gcn_shader = 0;\nint GLAD_GL_AMD_gpu_shader_half_float = 0;\nint GLAD_GL_AMD_gpu_shader_int16 = 0;\nint GLAD_GL_AMD_gpu_shader_int64 = 0;\nint GLAD_GL_AMD_interleaved_elements = 0;\nint GLAD_GL_AMD_multi_draw_indirect = 0;\nint GLAD_GL_AMD_name_gen_delete = 0;\nint GLAD_GL_AMD_occlusion_query_event = 0;\nint GLAD_GL_AMD_performance_monitor = 0;\nint GLAD_GL_AMD_pinned_memory = 0;\nint GLAD_GL_AMD_program_binary_Z400 = 0;\nint GLAD_GL_AMD_query_buffer_object = 0;\nint GLAD_GL_AMD_sample_positions = 0;\nint GLAD_GL_AMD_seamless_cubemap_per_texture = 0;\nint GLAD_GL_AMD_shader_atomic_counter_ops = 0;\nint GLAD_GL_AMD_shader_ballot = 0;\nint GLAD_GL_AMD_shader_explicit_vertex_parameter = 0;\nint GLAD_GL_AMD_shader_gpu_shader_half_float_fetch = 0;\nint GLAD_GL_AMD_shader_image_load_store_lod = 0;\nint GLAD_GL_AMD_shader_stencil_export = 0;\nint GLAD_GL_AMD_shader_trinary_minmax = 0;\nint GLAD_GL_AMD_sparse_texture = 0;\nint GLAD_GL_AMD_stencil_operation_extended = 0;\nint GLAD_GL_AMD_texture_gather_bias_lod = 0;\nint GLAD_GL_AMD_texture_texture4 = 0;\nint GLAD_GL_AMD_transform_feedback3_lines_triangles = 0;\nint GLAD_GL_AMD_transform_feedback4 = 0;\nint GLAD_GL_AMD_vertex_shader_layer = 0;\nint GLAD_GL_AMD_vertex_shader_tessellator = 0;\nint GLAD_GL_AMD_vertex_shader_viewport_index = 0;\nint GLAD_GL_ANDROID_extension_pack_es31a = 0;\nint GLAD_GL_ANGLE_depth_texture = 0;\nint GLAD_GL_ANGLE_framebuffer_blit = 0;\nint GLAD_GL_ANGLE_framebuffer_multisample = 0;\nint GLAD_GL_ANGLE_instanced_arrays = 0;\nint GLAD_GL_ANGLE_pack_reverse_row_order = 0;\nint GLAD_GL_ANGLE_program_binary = 0;\nint GLAD_GL_ANGLE_texture_compression_dxt3 = 0;\nint GLAD_GL_ANGLE_texture_compression_dxt5 = 0;\nint GLAD_GL_ANGLE_texture_usage = 0;\nint GLAD_GL_ANGLE_translated_shader_source = 0;\nint GLAD_GL_APPLE_aux_depth_stencil = 0;\nint GLAD_GL_APPLE_client_storage = 0;\nint GLAD_GL_APPLE_clip_distance = 0;\nint GLAD_GL_APPLE_color_buffer_packed_float = 0;\nint GLAD_GL_APPLE_copy_texture_levels = 0;\nint GLAD_GL_APPLE_element_array = 0;\nint GLAD_GL_APPLE_fence = 0;\nint GLAD_GL_APPLE_float_pixels = 0;\nint GLAD_GL_APPLE_flush_buffer_range = 0;\nint GLAD_GL_APPLE_framebuffer_multisample = 0;\nint GLAD_GL_APPLE_object_purgeable = 0;\nint GLAD_GL_APPLE_rgb_422 = 0;\nint GLAD_GL_APPLE_row_bytes = 0;\nint GLAD_GL_APPLE_specular_vector = 0;\nint GLAD_GL_APPLE_sync = 0;\nint GLAD_GL_APPLE_texture_format_BGRA8888 = 0;\nint GLAD_GL_APPLE_texture_max_level = 0;\nint GLAD_GL_APPLE_texture_packed_float = 0;\nint GLAD_GL_APPLE_texture_range = 0;\nint GLAD_GL_APPLE_transform_hint = 0;\nint GLAD_GL_APPLE_vertex_array_object = 0;\nint GLAD_GL_APPLE_vertex_array_range = 0;\nint GLAD_GL_APPLE_vertex_program_evaluators = 0;\nint GLAD_GL_APPLE_ycbcr_422 = 0;\nint GLAD_GL_ARB_ES2_compatibility = 0;\nint GLAD_GL_ARB_ES3_1_compatibility = 0;\nint GLAD_GL_ARB_ES3_2_compatibility = 0;\nint GLAD_GL_ARB_ES3_compatibility = 0;\nint GLAD_GL_ARB_arrays_of_arrays = 0;\nint GLAD_GL_ARB_base_instance = 0;\nint GLAD_GL_ARB_bindless_texture = 0;\nint GLAD_GL_ARB_blend_func_extended = 0;\nint GLAD_GL_ARB_buffer_storage = 0;\nint GLAD_GL_ARB_cl_event = 0;\nint GLAD_GL_ARB_clear_buffer_object = 0;\nint GLAD_GL_ARB_clear_texture = 0;\nint GLAD_GL_ARB_clip_control = 0;\nint GLAD_GL_ARB_color_buffer_float = 0;\nint GLAD_GL_ARB_compatibility = 0;\nint GLAD_GL_ARB_compressed_texture_pixel_storage = 0;\nint GLAD_GL_ARB_compute_shader = 0;\nint GLAD_GL_ARB_compute_variable_group_size = 0;\nint GLAD_GL_ARB_conditional_render_inverted = 0;\nint GLAD_GL_ARB_conservative_depth = 0;\nint GLAD_GL_ARB_copy_buffer = 0;\nint GLAD_GL_ARB_copy_image = 0;\nint GLAD_GL_ARB_cull_distance = 0;\nint GLAD_GL_ARB_debug_output = 0;\nint GLAD_GL_ARB_depth_buffer_float = 0;\nint GLAD_GL_ARB_depth_clamp = 0;\nint GLAD_GL_ARB_depth_texture = 0;\nint GLAD_GL_ARB_derivative_control = 0;\nint GLAD_GL_ARB_direct_state_access = 0;\nint GLAD_GL_ARB_draw_buffers = 0;\nint GLAD_GL_ARB_draw_buffers_blend = 0;\nint GLAD_GL_ARB_draw_elements_base_vertex = 0;\nint GLAD_GL_ARB_draw_indirect = 0;\nint GLAD_GL_ARB_draw_instanced = 0;\nint GLAD_GL_ARB_enhanced_layouts = 0;\nint GLAD_GL_ARB_explicit_attrib_location = 0;\nint GLAD_GL_ARB_explicit_uniform_location = 0;\nint GLAD_GL_ARB_fragment_coord_conventions = 0;\nint GLAD_GL_ARB_fragment_layer_viewport = 0;\nint GLAD_GL_ARB_fragment_program = 0;\nint GLAD_GL_ARB_fragment_program_shadow = 0;\nint GLAD_GL_ARB_fragment_shader = 0;\nint GLAD_GL_ARB_fragment_shader_interlock = 0;\nint GLAD_GL_ARB_framebuffer_no_attachments = 0;\nint GLAD_GL_ARB_framebuffer_object = 0;\nint GLAD_GL_ARB_framebuffer_sRGB = 0;\nint GLAD_GL_ARB_geometry_shader4 = 0;\nint GLAD_GL_ARB_get_program_binary = 0;\nint GLAD_GL_ARB_get_texture_sub_image = 0;\nint GLAD_GL_ARB_gl_spirv = 0;\nint GLAD_GL_ARB_gpu_shader5 = 0;\nint GLAD_GL_ARB_gpu_shader_fp64 = 0;\nint GLAD_GL_ARB_gpu_shader_int64 = 0;\nint GLAD_GL_ARB_half_float_pixel = 0;\nint GLAD_GL_ARB_half_float_vertex = 0;\nint GLAD_GL_ARB_imaging = 0;\nint GLAD_GL_ARB_indirect_parameters = 0;\nint GLAD_GL_ARB_instanced_arrays = 0;\nint GLAD_GL_ARB_internalformat_query = 0;\nint GLAD_GL_ARB_internalformat_query2 = 0;\nint GLAD_GL_ARB_invalidate_subdata = 0;\nint GLAD_GL_ARB_map_buffer_alignment = 0;\nint GLAD_GL_ARB_map_buffer_range = 0;\nint GLAD_GL_ARB_matrix_palette = 0;\nint GLAD_GL_ARB_multi_bind = 0;\nint GLAD_GL_ARB_multi_draw_indirect = 0;\nint GLAD_GL_ARB_multisample = 0;\nint GLAD_GL_ARB_multitexture = 0;\nint GLAD_GL_ARB_occlusion_query = 0;\nint GLAD_GL_ARB_occlusion_query2 = 0;\nint GLAD_GL_ARB_parallel_shader_compile = 0;\nint GLAD_GL_ARB_pipeline_statistics_query = 0;\nint GLAD_GL_ARB_pixel_buffer_object = 0;\nint GLAD_GL_ARB_point_parameters = 0;\nint GLAD_GL_ARB_point_sprite = 0;\nint GLAD_GL_ARB_polygon_offset_clamp = 0;\nint GLAD_GL_ARB_post_depth_coverage = 0;\nint GLAD_GL_ARB_program_interface_query = 0;\nint GLAD_GL_ARB_provoking_vertex = 0;\nint GLAD_GL_ARB_query_buffer_object = 0;\nint GLAD_GL_ARB_robust_buffer_access_behavior = 0;\nint GLAD_GL_ARB_robustness = 0;\nint GLAD_GL_ARB_robustness_isolation = 0;\nint GLAD_GL_ARB_sample_locations = 0;\nint GLAD_GL_ARB_sample_shading = 0;\nint GLAD_GL_ARB_sampler_objects = 0;\nint GLAD_GL_ARB_seamless_cube_map = 0;\nint GLAD_GL_ARB_seamless_cubemap_per_texture = 0;\nint GLAD_GL_ARB_separate_shader_objects = 0;\nint GLAD_GL_ARB_shader_atomic_counter_ops = 0;\nint GLAD_GL_ARB_shader_atomic_counters = 0;\nint GLAD_GL_ARB_shader_ballot = 0;\nint GLAD_GL_ARB_shader_bit_encoding = 0;\nint GLAD_GL_ARB_shader_clock = 0;\nint GLAD_GL_ARB_shader_draw_parameters = 0;\nint GLAD_GL_ARB_shader_group_vote = 0;\nint GLAD_GL_ARB_shader_image_load_store = 0;\nint GLAD_GL_ARB_shader_image_size = 0;\nint GLAD_GL_ARB_shader_objects = 0;\nint GLAD_GL_ARB_shader_precision = 0;\nint GLAD_GL_ARB_shader_stencil_export = 0;\nint GLAD_GL_ARB_shader_storage_buffer_object = 0;\nint GLAD_GL_ARB_shader_subroutine = 0;\nint GLAD_GL_ARB_shader_texture_image_samples = 0;\nint GLAD_GL_ARB_shader_texture_lod = 0;\nint GLAD_GL_ARB_shader_viewport_layer_array = 0;\nint GLAD_GL_ARB_shading_language_100 = 0;\nint GLAD_GL_ARB_shading_language_420pack = 0;\nint GLAD_GL_ARB_shading_language_include = 0;\nint GLAD_GL_ARB_shading_language_packing = 0;\nint GLAD_GL_ARB_shadow = 0;\nint GLAD_GL_ARB_shadow_ambient = 0;\nint GLAD_GL_ARB_sparse_buffer = 0;\nint GLAD_GL_ARB_sparse_texture = 0;\nint GLAD_GL_ARB_sparse_texture2 = 0;\nint GLAD_GL_ARB_sparse_texture_clamp = 0;\nint GLAD_GL_ARB_spirv_extensions = 0;\nint GLAD_GL_ARB_stencil_texturing = 0;\nint GLAD_GL_ARB_sync = 0;\nint GLAD_GL_ARB_tessellation_shader = 0;\nint GLAD_GL_ARB_texture_barrier = 0;\nint GLAD_GL_ARB_texture_border_clamp = 0;\nint GLAD_GL_ARB_texture_buffer_object = 0;\nint GLAD_GL_ARB_texture_buffer_object_rgb32 = 0;\nint GLAD_GL_ARB_texture_buffer_range = 0;\nint GLAD_GL_ARB_texture_compression = 0;\nint GLAD_GL_ARB_texture_compression_bptc = 0;\nint GLAD_GL_ARB_texture_compression_rgtc = 0;\nint GLAD_GL_ARB_texture_cube_map = 0;\nint GLAD_GL_ARB_texture_cube_map_array = 0;\nint GLAD_GL_ARB_texture_env_add = 0;\nint GLAD_GL_ARB_texture_env_combine = 0;\nint GLAD_GL_ARB_texture_env_crossbar = 0;\nint GLAD_GL_ARB_texture_env_dot3 = 0;\nint GLAD_GL_ARB_texture_filter_anisotropic = 0;\nint GLAD_GL_ARB_texture_filter_minmax = 0;\nint GLAD_GL_ARB_texture_float = 0;\nint GLAD_GL_ARB_texture_gather = 0;\nint GLAD_GL_ARB_texture_mirror_clamp_to_edge = 0;\nint GLAD_GL_ARB_texture_mirrored_repeat = 0;\nint GLAD_GL_ARB_texture_multisample = 0;\nint GLAD_GL_ARB_texture_non_power_of_two = 0;\nint GLAD_GL_ARB_texture_query_levels = 0;\nint GLAD_GL_ARB_texture_query_lod = 0;\nint GLAD_GL_ARB_texture_rectangle = 0;\nint GLAD_GL_ARB_texture_rg = 0;\nint GLAD_GL_ARB_texture_rgb10_a2ui = 0;\nint GLAD_GL_ARB_texture_stencil8 = 0;\nint GLAD_GL_ARB_texture_storage = 0;\nint GLAD_GL_ARB_texture_storage_multisample = 0;\nint GLAD_GL_ARB_texture_swizzle = 0;\nint GLAD_GL_ARB_texture_view = 0;\nint GLAD_GL_ARB_timer_query = 0;\nint GLAD_GL_ARB_transform_feedback2 = 0;\nint GLAD_GL_ARB_transform_feedback3 = 0;\nint GLAD_GL_ARB_transform_feedback_instanced = 0;\nint GLAD_GL_ARB_transform_feedback_overflow_query = 0;\nint GLAD_GL_ARB_transpose_matrix = 0;\nint GLAD_GL_ARB_uniform_buffer_object = 0;\nint GLAD_GL_ARB_vertex_array_bgra = 0;\nint GLAD_GL_ARB_vertex_array_object = 0;\nint GLAD_GL_ARB_vertex_attrib_64bit = 0;\nint GLAD_GL_ARB_vertex_attrib_binding = 0;\nint GLAD_GL_ARB_vertex_blend = 0;\nint GLAD_GL_ARB_vertex_buffer_object = 0;\nint GLAD_GL_ARB_vertex_program = 0;\nint GLAD_GL_ARB_vertex_shader = 0;\nint GLAD_GL_ARB_vertex_type_10f_11f_11f_rev = 0;\nint GLAD_GL_ARB_vertex_type_2_10_10_10_rev = 0;\nint GLAD_GL_ARB_viewport_array = 0;\nint GLAD_GL_ARB_window_pos = 0;\nint GLAD_GL_ARM_mali_program_binary = 0;\nint GLAD_GL_ARM_mali_shader_binary = 0;\nint GLAD_GL_ARM_rgba8 = 0;\nint GLAD_GL_ARM_shader_framebuffer_fetch = 0;\nint GLAD_GL_ARM_shader_framebuffer_fetch_depth_stencil = 0;\nint GLAD_GL_ATI_draw_buffers = 0;\nint GLAD_GL_ATI_element_array = 0;\nint GLAD_GL_ATI_envmap_bumpmap = 0;\nint GLAD_GL_ATI_fragment_shader = 0;\nint GLAD_GL_ATI_map_object_buffer = 0;\nint GLAD_GL_ATI_meminfo = 0;\nint GLAD_GL_ATI_pixel_format_float = 0;\nint GLAD_GL_ATI_pn_triangles = 0;\nint GLAD_GL_ATI_separate_stencil = 0;\nint GLAD_GL_ATI_text_fragment_shader = 0;\nint GLAD_GL_ATI_texture_env_combine3 = 0;\nint GLAD_GL_ATI_texture_float = 0;\nint GLAD_GL_ATI_texture_mirror_once = 0;\nint GLAD_GL_ATI_vertex_array_object = 0;\nint GLAD_GL_ATI_vertex_attrib_array_object = 0;\nint GLAD_GL_ATI_vertex_streams = 0;\nint GLAD_GL_DMP_program_binary = 0;\nint GLAD_GL_DMP_shader_binary = 0;\nint GLAD_GL_EXT_422_pixels = 0;\nint GLAD_GL_EXT_EGL_image_array = 0;\nint GLAD_GL_EXT_EGL_image_storage = 0;\nint GLAD_GL_EXT_YUV_target = 0;\nint GLAD_GL_EXT_abgr = 0;\nint GLAD_GL_EXT_base_instance = 0;\nint GLAD_GL_EXT_bgra = 0;\nint GLAD_GL_EXT_bindable_uniform = 0;\nint GLAD_GL_EXT_blend_color = 0;\nint GLAD_GL_EXT_blend_equation_separate = 0;\nint GLAD_GL_EXT_blend_func_extended = 0;\nint GLAD_GL_EXT_blend_func_separate = 0;\nint GLAD_GL_EXT_blend_logic_op = 0;\nint GLAD_GL_EXT_blend_minmax = 0;\nint GLAD_GL_EXT_blend_subtract = 0;\nint GLAD_GL_EXT_buffer_storage = 0;\nint GLAD_GL_EXT_clear_texture = 0;\nint GLAD_GL_EXT_clip_control = 0;\nint GLAD_GL_EXT_clip_cull_distance = 0;\nint GLAD_GL_EXT_clip_volume_hint = 0;\nint GLAD_GL_EXT_cmyka = 0;\nint GLAD_GL_EXT_color_buffer_float = 0;\nint GLAD_GL_EXT_color_buffer_half_float = 0;\nint GLAD_GL_EXT_color_subtable = 0;\nint GLAD_GL_EXT_compiled_vertex_array = 0;\nint GLAD_GL_EXT_conservative_depth = 0;\nint GLAD_GL_EXT_convolution = 0;\nint GLAD_GL_EXT_coordinate_frame = 0;\nint GLAD_GL_EXT_copy_image = 0;\nint GLAD_GL_EXT_copy_texture = 0;\nint GLAD_GL_EXT_cull_vertex = 0;\nint GLAD_GL_EXT_debug_label = 0;\nint GLAD_GL_EXT_debug_marker = 0;\nint GLAD_GL_EXT_depth_bounds_test = 0;\nint GLAD_GL_EXT_direct_state_access = 0;\nint GLAD_GL_EXT_discard_framebuffer = 0;\nint GLAD_GL_EXT_disjoint_timer_query = 0;\nint GLAD_GL_EXT_draw_buffers = 0;\nint GLAD_GL_EXT_draw_buffers2 = 0;\nint GLAD_GL_EXT_draw_buffers_indexed = 0;\nint GLAD_GL_EXT_draw_elements_base_vertex = 0;\nint GLAD_GL_EXT_draw_instanced = 0;\nint GLAD_GL_EXT_draw_range_elements = 0;\nint GLAD_GL_EXT_draw_transform_feedback = 0;\nint GLAD_GL_EXT_external_buffer = 0;\nint GLAD_GL_EXT_float_blend = 0;\nint GLAD_GL_EXT_fog_coord = 0;\nint GLAD_GL_EXT_framebuffer_blit = 0;\nint GLAD_GL_EXT_framebuffer_multisample = 0;\nint GLAD_GL_EXT_framebuffer_multisample_blit_scaled = 0;\nint GLAD_GL_EXT_framebuffer_object = 0;\nint GLAD_GL_EXT_framebuffer_sRGB = 0;\nint GLAD_GL_EXT_geometry_point_size = 0;\nint GLAD_GL_EXT_geometry_shader = 0;\nint GLAD_GL_EXT_geometry_shader4 = 0;\nint GLAD_GL_EXT_gpu_program_parameters = 0;\nint GLAD_GL_EXT_gpu_shader4 = 0;\nint GLAD_GL_EXT_gpu_shader5 = 0;\nint GLAD_GL_EXT_histogram = 0;\nint GLAD_GL_EXT_index_array_formats = 0;\nint GLAD_GL_EXT_index_func = 0;\nint GLAD_GL_EXT_index_material = 0;\nint GLAD_GL_EXT_index_texture = 0;\nint GLAD_GL_EXT_instanced_arrays = 0;\nint GLAD_GL_EXT_light_texture = 0;\nint GLAD_GL_EXT_map_buffer_range = 0;\nint GLAD_GL_EXT_memory_object = 0;\nint GLAD_GL_EXT_memory_object_fd = 0;\nint GLAD_GL_EXT_memory_object_win32 = 0;\nint GLAD_GL_EXT_misc_attribute = 0;\nint GLAD_GL_EXT_multi_draw_arrays = 0;\nint GLAD_GL_EXT_multi_draw_indirect = 0;\nint GLAD_GL_EXT_multisample = 0;\nint GLAD_GL_EXT_multisampled_compatibility = 0;\nint GLAD_GL_EXT_multisampled_render_to_texture = 0;\nint GLAD_GL_EXT_multiview_draw_buffers = 0;\nint GLAD_GL_EXT_occlusion_query_boolean = 0;\nint GLAD_GL_EXT_packed_depth_stencil = 0;\nint GLAD_GL_EXT_packed_float = 0;\nint GLAD_GL_EXT_packed_pixels = 0;\nint GLAD_GL_EXT_paletted_texture = 0;\nint GLAD_GL_EXT_pixel_buffer_object = 0;\nint GLAD_GL_EXT_pixel_transform = 0;\nint GLAD_GL_EXT_pixel_transform_color_table = 0;\nint GLAD_GL_EXT_point_parameters = 0;\nint GLAD_GL_EXT_polygon_offset = 0;\nint GLAD_GL_EXT_polygon_offset_clamp = 0;\nint GLAD_GL_EXT_post_depth_coverage = 0;\nint GLAD_GL_EXT_primitive_bounding_box = 0;\nint GLAD_GL_EXT_protected_textures = 0;\nint GLAD_GL_EXT_provoking_vertex = 0;\nint GLAD_GL_EXT_pvrtc_sRGB = 0;\nint GLAD_GL_EXT_raster_multisample = 0;\nint GLAD_GL_EXT_read_format_bgra = 0;\nint GLAD_GL_EXT_render_snorm = 0;\nint GLAD_GL_EXT_rescale_normal = 0;\nint GLAD_GL_EXT_robustness = 0;\nint GLAD_GL_EXT_sRGB = 0;\nint GLAD_GL_EXT_sRGB_write_control = 0;\nint GLAD_GL_EXT_secondary_color = 0;\nint GLAD_GL_EXT_semaphore = 0;\nint GLAD_GL_EXT_semaphore_fd = 0;\nint GLAD_GL_EXT_semaphore_win32 = 0;\nint GLAD_GL_EXT_separate_shader_objects = 0;\nint GLAD_GL_EXT_separate_specular_color = 0;\nint GLAD_GL_EXT_shader_framebuffer_fetch = 0;\nint GLAD_GL_EXT_shader_framebuffer_fetch_non_coherent = 0;\nint GLAD_GL_EXT_shader_group_vote = 0;\nint GLAD_GL_EXT_shader_image_load_formatted = 0;\nint GLAD_GL_EXT_shader_image_load_store = 0;\nint GLAD_GL_EXT_shader_implicit_conversions = 0;\nint GLAD_GL_EXT_shader_integer_mix = 0;\nint GLAD_GL_EXT_shader_io_blocks = 0;\nint GLAD_GL_EXT_shader_non_constant_global_initializers = 0;\nint GLAD_GL_EXT_shader_pixel_local_storage = 0;\nint GLAD_GL_EXT_shader_pixel_local_storage2 = 0;\nint GLAD_GL_EXT_shader_texture_lod = 0;\nint GLAD_GL_EXT_shadow_funcs = 0;\nint GLAD_GL_EXT_shadow_samplers = 0;\nint GLAD_GL_EXT_shared_texture_palette = 0;\nint GLAD_GL_EXT_sparse_texture = 0;\nint GLAD_GL_EXT_sparse_texture2 = 0;\nint GLAD_GL_EXT_stencil_clear_tag = 0;\nint GLAD_GL_EXT_stencil_two_side = 0;\nint GLAD_GL_EXT_stencil_wrap = 0;\nint GLAD_GL_EXT_subtexture = 0;\nint GLAD_GL_EXT_tessellation_point_size = 0;\nint GLAD_GL_EXT_tessellation_shader = 0;\nint GLAD_GL_EXT_texture = 0;\nint GLAD_GL_EXT_texture3D = 0;\nint GLAD_GL_EXT_texture_array = 0;\nint GLAD_GL_EXT_texture_border_clamp = 0;\nint GLAD_GL_EXT_texture_buffer = 0;\nint GLAD_GL_EXT_texture_buffer_object = 0;\nint GLAD_GL_EXT_texture_compression_astc_decode_mode = 0;\nint GLAD_GL_EXT_texture_compression_bptc = 0;\nint GLAD_GL_EXT_texture_compression_dxt1 = 0;\nint GLAD_GL_EXT_texture_compression_latc = 0;\nint GLAD_GL_EXT_texture_compression_rgtc = 0;\nint GLAD_GL_EXT_texture_compression_s3tc = 0;\nint GLAD_GL_EXT_texture_compression_s3tc_srgb = 0;\nint GLAD_GL_EXT_texture_cube_map = 0;\nint GLAD_GL_EXT_texture_cube_map_array = 0;\nint GLAD_GL_EXT_texture_env_add = 0;\nint GLAD_GL_EXT_texture_env_combine = 0;\nint GLAD_GL_EXT_texture_env_dot3 = 0;\nint GLAD_GL_EXT_texture_filter_anisotropic = 0;\nint GLAD_GL_EXT_texture_filter_minmax = 0;\nint GLAD_GL_EXT_texture_format_BGRA8888 = 0;\nint GLAD_GL_EXT_texture_format_sRGB_override = 0;\nint GLAD_GL_EXT_texture_integer = 0;\nint GLAD_GL_EXT_texture_lod_bias = 0;\nint GLAD_GL_EXT_texture_mirror_clamp = 0;\nint GLAD_GL_EXT_texture_mirror_clamp_to_edge = 0;\nint GLAD_GL_EXT_texture_norm16 = 0;\nint GLAD_GL_EXT_texture_object = 0;\nint GLAD_GL_EXT_texture_perturb_normal = 0;\nint GLAD_GL_EXT_texture_rg = 0;\nint GLAD_GL_EXT_texture_sRGB = 0;\nint GLAD_GL_EXT_texture_sRGB_R8 = 0;\nint GLAD_GL_EXT_texture_sRGB_RG8 = 0;\nint GLAD_GL_EXT_texture_sRGB_decode = 0;\nint GLAD_GL_EXT_texture_shared_exponent = 0;\nint GLAD_GL_EXT_texture_snorm = 0;\nint GLAD_GL_EXT_texture_storage = 0;\nint GLAD_GL_EXT_texture_swizzle = 0;\nint GLAD_GL_EXT_texture_type_2_10_10_10_REV = 0;\nint GLAD_GL_EXT_texture_view = 0;\nint GLAD_GL_EXT_timer_query = 0;\nint GLAD_GL_EXT_transform_feedback = 0;\nint GLAD_GL_EXT_unpack_subimage = 0;\nint GLAD_GL_EXT_vertex_array = 0;\nint GLAD_GL_EXT_vertex_array_bgra = 0;\nint GLAD_GL_EXT_vertex_attrib_64bit = 0;\nint GLAD_GL_EXT_vertex_shader = 0;\nint GLAD_GL_EXT_vertex_weighting = 0;\nint GLAD_GL_EXT_win32_keyed_mutex = 0;\nint GLAD_GL_EXT_window_rectangles = 0;\nint GLAD_GL_EXT_x11_sync_object = 0;\nint GLAD_GL_FJ_shader_binary_GCCSO = 0;\nint GLAD_GL_GREMEDY_frame_terminator = 0;\nint GLAD_GL_GREMEDY_string_marker = 0;\nint GLAD_GL_HP_convolution_border_modes = 0;\nint GLAD_GL_HP_image_transform = 0;\nint GLAD_GL_HP_occlusion_test = 0;\nint GLAD_GL_HP_texture_lighting = 0;\nint GLAD_GL_IBM_cull_vertex = 0;\nint GLAD_GL_IBM_multimode_draw_arrays = 0;\nint GLAD_GL_IBM_rasterpos_clip = 0;\nint GLAD_GL_IBM_static_data = 0;\nint GLAD_GL_IBM_texture_mirrored_repeat = 0;\nint GLAD_GL_IBM_vertex_array_lists = 0;\nint GLAD_GL_IMG_bindless_texture = 0;\nint GLAD_GL_IMG_framebuffer_downsample = 0;\nint GLAD_GL_IMG_multisampled_render_to_texture = 0;\nint GLAD_GL_IMG_program_binary = 0;\nint GLAD_GL_IMG_read_format = 0;\nint GLAD_GL_IMG_shader_binary = 0;\nint GLAD_GL_IMG_texture_compression_pvrtc = 0;\nint GLAD_GL_IMG_texture_compression_pvrtc2 = 0;\nint GLAD_GL_IMG_texture_filter_cubic = 0;\nint GLAD_GL_INGR_blend_func_separate = 0;\nint GLAD_GL_INGR_color_clamp = 0;\nint GLAD_GL_INGR_interlace_read = 0;\nint GLAD_GL_INTEL_blackhole_render = 0;\nint GLAD_GL_INTEL_conservative_rasterization = 0;\nint GLAD_GL_INTEL_fragment_shader_ordering = 0;\nint GLAD_GL_INTEL_framebuffer_CMAA = 0;\nint GLAD_GL_INTEL_map_texture = 0;\nint GLAD_GL_INTEL_parallel_arrays = 0;\nint GLAD_GL_INTEL_performance_query = 0;\nint GLAD_GL_KHR_blend_equation_advanced = 0;\nint GLAD_GL_KHR_blend_equation_advanced_coherent = 0;\nint GLAD_GL_KHR_context_flush_control = 0;\nint GLAD_GL_KHR_debug = 0;\nint GLAD_GL_KHR_no_error = 0;\nint GLAD_GL_KHR_parallel_shader_compile = 0;\nint GLAD_GL_KHR_robust_buffer_access_behavior = 0;\nint GLAD_GL_KHR_robustness = 0;\nint GLAD_GL_KHR_texture_compression_astc_hdr = 0;\nint GLAD_GL_KHR_texture_compression_astc_ldr = 0;\nint GLAD_GL_KHR_texture_compression_astc_sliced_3d = 0;\nint GLAD_GL_MESAX_texture_stack = 0;\nint GLAD_GL_MESA_framebuffer_flip_y = 0;\nint GLAD_GL_MESA_pack_invert = 0;\nint GLAD_GL_MESA_program_binary_formats = 0;\nint GLAD_GL_MESA_resize_buffers = 0;\nint GLAD_GL_MESA_shader_integer_functions = 0;\nint GLAD_GL_MESA_tile_raster_order = 0;\nint GLAD_GL_MESA_window_pos = 0;\nint GLAD_GL_MESA_ycbcr_texture = 0;\nint GLAD_GL_NVX_blend_equation_advanced_multi_draw_buffers = 0;\nint GLAD_GL_NVX_conditional_render = 0;\nint GLAD_GL_NVX_gpu_memory_info = 0;\nint GLAD_GL_NVX_linked_gpu_multicast = 0;\nint GLAD_GL_NV_alpha_to_coverage_dither_control = 0;\nint GLAD_GL_NV_bindless_multi_draw_indirect = 0;\nint GLAD_GL_NV_bindless_multi_draw_indirect_count = 0;\nint GLAD_GL_NV_bindless_texture = 0;\nint GLAD_GL_NV_blend_equation_advanced = 0;\nint GLAD_GL_NV_blend_equation_advanced_coherent = 0;\nint GLAD_GL_NV_blend_minmax_factor = 0;\nint GLAD_GL_NV_blend_square = 0;\nint GLAD_GL_NV_clip_space_w_scaling = 0;\nint GLAD_GL_NV_command_list = 0;\nint GLAD_GL_NV_compute_program5 = 0;\nint GLAD_GL_NV_compute_shader_derivatives = 0;\nint GLAD_GL_NV_conditional_render = 0;\nint GLAD_GL_NV_conservative_raster = 0;\nint GLAD_GL_NV_conservative_raster_dilate = 0;\nint GLAD_GL_NV_conservative_raster_pre_snap = 0;\nint GLAD_GL_NV_conservative_raster_pre_snap_triangles = 0;\nint GLAD_GL_NV_conservative_raster_underestimation = 0;\nint GLAD_GL_NV_copy_buffer = 0;\nint GLAD_GL_NV_copy_depth_to_color = 0;\nint GLAD_GL_NV_copy_image = 0;\nint GLAD_GL_NV_coverage_sample = 0;\nint GLAD_GL_NV_deep_texture3D = 0;\nint GLAD_GL_NV_depth_buffer_float = 0;\nint GLAD_GL_NV_depth_clamp = 0;\nint GLAD_GL_NV_depth_nonlinear = 0;\nint GLAD_GL_NV_draw_buffers = 0;\nint GLAD_GL_NV_draw_instanced = 0;\nint GLAD_GL_NV_draw_texture = 0;\nint GLAD_GL_NV_draw_vulkan_image = 0;\nint GLAD_GL_NV_evaluators = 0;\nint GLAD_GL_NV_explicit_attrib_location = 0;\nint GLAD_GL_NV_explicit_multisample = 0;\nint GLAD_GL_NV_fbo_color_attachments = 0;\nint GLAD_GL_NV_fence = 0;\nint GLAD_GL_NV_fill_rectangle = 0;\nint GLAD_GL_NV_float_buffer = 0;\nint GLAD_GL_NV_fog_distance = 0;\nint GLAD_GL_NV_fragment_coverage_to_color = 0;\nint GLAD_GL_NV_fragment_program = 0;\nint GLAD_GL_NV_fragment_program2 = 0;\nint GLAD_GL_NV_fragment_program4 = 0;\nint GLAD_GL_NV_fragment_program_option = 0;\nint GLAD_GL_NV_fragment_shader_barycentric = 0;\nint GLAD_GL_NV_fragment_shader_interlock = 0;\nint GLAD_GL_NV_framebuffer_blit = 0;\nint GLAD_GL_NV_framebuffer_mixed_samples = 0;\nint GLAD_GL_NV_framebuffer_multisample = 0;\nint GLAD_GL_NV_framebuffer_multisample_coverage = 0;\nint GLAD_GL_NV_generate_mipmap_sRGB = 0;\nint GLAD_GL_NV_geometry_program4 = 0;\nint GLAD_GL_NV_geometry_shader4 = 0;\nint GLAD_GL_NV_geometry_shader_passthrough = 0;\nint GLAD_GL_NV_gpu_multicast = 0;\nint GLAD_GL_NV_gpu_program4 = 0;\nint GLAD_GL_NV_gpu_program5 = 0;\nint GLAD_GL_NV_gpu_program5_mem_extended = 0;\nint GLAD_GL_NV_gpu_shader5 = 0;\nint GLAD_GL_NV_half_float = 0;\nint GLAD_GL_NV_image_formats = 0;\nint GLAD_GL_NV_instanced_arrays = 0;\nint GLAD_GL_NV_internalformat_sample_query = 0;\nint GLAD_GL_NV_light_max_exponent = 0;\nint GLAD_GL_NV_memory_attachment = 0;\nint GLAD_GL_NV_mesh_shader = 0;\nint GLAD_GL_NV_multisample_coverage = 0;\nint GLAD_GL_NV_multisample_filter_hint = 0;\nint GLAD_GL_NV_non_square_matrices = 0;\nint GLAD_GL_NV_occlusion_query = 0;\nint GLAD_GL_NV_packed_depth_stencil = 0;\nint GLAD_GL_NV_parameter_buffer_object = 0;\nint GLAD_GL_NV_parameter_buffer_object2 = 0;\nint GLAD_GL_NV_path_rendering = 0;\nint GLAD_GL_NV_path_rendering_shared_edge = 0;\nint GLAD_GL_NV_pixel_buffer_object = 0;\nint GLAD_GL_NV_pixel_data_range = 0;\nint GLAD_GL_NV_point_sprite = 0;\nint GLAD_GL_NV_polygon_mode = 0;\nint GLAD_GL_NV_present_video = 0;\nint GLAD_GL_NV_primitive_restart = 0;\nint GLAD_GL_NV_query_resource = 0;\nint GLAD_GL_NV_query_resource_tag = 0;\nint GLAD_GL_NV_read_buffer = 0;\nint GLAD_GL_NV_read_buffer_front = 0;\nint GLAD_GL_NV_read_depth = 0;\nint GLAD_GL_NV_read_depth_stencil = 0;\nint GLAD_GL_NV_read_stencil = 0;\nint GLAD_GL_NV_register_combiners = 0;\nint GLAD_GL_NV_register_combiners2 = 0;\nint GLAD_GL_NV_representative_fragment_test = 0;\nint GLAD_GL_NV_robustness_video_memory_purge = 0;\nint GLAD_GL_NV_sRGB_formats = 0;\nint GLAD_GL_NV_sample_locations = 0;\nint GLAD_GL_NV_sample_mask_override_coverage = 0;\nint GLAD_GL_NV_scissor_exclusive = 0;\nint GLAD_GL_NV_shader_atomic_counters = 0;\nint GLAD_GL_NV_shader_atomic_float = 0;\nint GLAD_GL_NV_shader_atomic_float64 = 0;\nint GLAD_GL_NV_shader_atomic_fp16_vector = 0;\nint GLAD_GL_NV_shader_atomic_int64 = 0;\nint GLAD_GL_NV_shader_buffer_load = 0;\nint GLAD_GL_NV_shader_buffer_store = 0;\nint GLAD_GL_NV_shader_noperspective_interpolation = 0;\nint GLAD_GL_NV_shader_storage_buffer_object = 0;\nint GLAD_GL_NV_shader_texture_footprint = 0;\nint GLAD_GL_NV_shader_thread_group = 0;\nint GLAD_GL_NV_shader_thread_shuffle = 0;\nint GLAD_GL_NV_shading_rate_image = 0;\nint GLAD_GL_NV_shadow_samplers_array = 0;\nint GLAD_GL_NV_shadow_samplers_cube = 0;\nint GLAD_GL_NV_stereo_view_rendering = 0;\nint GLAD_GL_NV_tessellation_program5 = 0;\nint GLAD_GL_NV_texgen_emboss = 0;\nint GLAD_GL_NV_texgen_reflection = 0;\nint GLAD_GL_NV_texture_barrier = 0;\nint GLAD_GL_NV_texture_border_clamp = 0;\nint GLAD_GL_NV_texture_compression_s3tc_update = 0;\nint GLAD_GL_NV_texture_compression_vtc = 0;\nint GLAD_GL_NV_texture_env_combine4 = 0;\nint GLAD_GL_NV_texture_expand_normal = 0;\nint GLAD_GL_NV_texture_multisample = 0;\nint GLAD_GL_NV_texture_npot_2D_mipmap = 0;\nint GLAD_GL_NV_texture_rectangle = 0;\nint GLAD_GL_NV_texture_rectangle_compressed = 0;\nint GLAD_GL_NV_texture_shader = 0;\nint GLAD_GL_NV_texture_shader2 = 0;\nint GLAD_GL_NV_texture_shader3 = 0;\nint GLAD_GL_NV_transform_feedback = 0;\nint GLAD_GL_NV_transform_feedback2 = 0;\nint GLAD_GL_NV_uniform_buffer_unified_memory = 0;\nint GLAD_GL_NV_vdpau_interop = 0;\nint GLAD_GL_NV_vertex_array_range = 0;\nint GLAD_GL_NV_vertex_array_range2 = 0;\nint GLAD_GL_NV_vertex_attrib_integer_64bit = 0;\nint GLAD_GL_NV_vertex_buffer_unified_memory = 0;\nint GLAD_GL_NV_vertex_program = 0;\nint GLAD_GL_NV_vertex_program1_1 = 0;\nint GLAD_GL_NV_vertex_program2 = 0;\nint GLAD_GL_NV_vertex_program2_option = 0;\nint GLAD_GL_NV_vertex_program3 = 0;\nint GLAD_GL_NV_vertex_program4 = 0;\nint GLAD_GL_NV_video_capture = 0;\nint GLAD_GL_NV_viewport_array = 0;\nint GLAD_GL_NV_viewport_array2 = 0;\nint GLAD_GL_NV_viewport_swizzle = 0;\nint GLAD_GL_OES_EGL_image = 0;\nint GLAD_GL_OES_EGL_image_external = 0;\nint GLAD_GL_OES_EGL_image_external_essl3 = 0;\nint GLAD_GL_OES_byte_coordinates = 0;\nint GLAD_GL_OES_compressed_ETC1_RGB8_sub_texture = 0;\nint GLAD_GL_OES_compressed_ETC1_RGB8_texture = 0;\nint GLAD_GL_OES_compressed_paletted_texture = 0;\nint GLAD_GL_OES_copy_image = 0;\nint GLAD_GL_OES_depth24 = 0;\nint GLAD_GL_OES_depth32 = 0;\nint GLAD_GL_OES_depth_texture = 0;\nint GLAD_GL_OES_draw_buffers_indexed = 0;\nint GLAD_GL_OES_draw_elements_base_vertex = 0;\nint GLAD_GL_OES_element_index_uint = 0;\nint GLAD_GL_OES_fbo_render_mipmap = 0;\nint GLAD_GL_OES_fixed_point = 0;\nint GLAD_GL_OES_fragment_precision_high = 0;\nint GLAD_GL_OES_geometry_point_size = 0;\nint GLAD_GL_OES_geometry_shader = 0;\nint GLAD_GL_OES_get_program_binary = 0;\nint GLAD_GL_OES_gpu_shader5 = 0;\nint GLAD_GL_OES_mapbuffer = 0;\nint GLAD_GL_OES_packed_depth_stencil = 0;\nint GLAD_GL_OES_primitive_bounding_box = 0;\nint GLAD_GL_OES_query_matrix = 0;\nint GLAD_GL_OES_read_format = 0;\nint GLAD_GL_OES_required_internalformat = 0;\nint GLAD_GL_OES_rgb8_rgba8 = 0;\nint GLAD_GL_OES_sample_shading = 0;\nint GLAD_GL_OES_sample_variables = 0;\nint GLAD_GL_OES_shader_image_atomic = 0;\nint GLAD_GL_OES_shader_io_blocks = 0;\nint GLAD_GL_OES_shader_multisample_interpolation = 0;\nint GLAD_GL_OES_single_precision = 0;\nint GLAD_GL_OES_standard_derivatives = 0;\nint GLAD_GL_OES_stencil1 = 0;\nint GLAD_GL_OES_stencil4 = 0;\nint GLAD_GL_OES_surfaceless_context = 0;\nint GLAD_GL_OES_tessellation_point_size = 0;\nint GLAD_GL_OES_tessellation_shader = 0;\nint GLAD_GL_OES_texture_3D = 0;\nint GLAD_GL_OES_texture_border_clamp = 0;\nint GLAD_GL_OES_texture_buffer = 0;\nint GLAD_GL_OES_texture_compression_astc = 0;\nint GLAD_GL_OES_texture_cube_map_array = 0;\nint GLAD_GL_OES_texture_float = 0;\nint GLAD_GL_OES_texture_float_linear = 0;\nint GLAD_GL_OES_texture_half_float = 0;\nint GLAD_GL_OES_texture_half_float_linear = 0;\nint GLAD_GL_OES_texture_npot = 0;\nint GLAD_GL_OES_texture_stencil8 = 0;\nint GLAD_GL_OES_texture_storage_multisample_2d_array = 0;\nint GLAD_GL_OES_texture_view = 0;\nint GLAD_GL_OES_vertex_array_object = 0;\nint GLAD_GL_OES_vertex_half_float = 0;\nint GLAD_GL_OES_vertex_type_10_10_10_2 = 0;\nint GLAD_GL_OES_viewport_array = 0;\nint GLAD_GL_OML_interlace = 0;\nint GLAD_GL_OML_resample = 0;\nint GLAD_GL_OML_subsample = 0;\nint GLAD_GL_OVR_multiview = 0;\nint GLAD_GL_OVR_multiview2 = 0;\nint GLAD_GL_OVR_multiview_multisampled_render_to_texture = 0;\nint GLAD_GL_PGI_misc_hints = 0;\nint GLAD_GL_PGI_vertex_hints = 0;\nint GLAD_GL_QCOM_alpha_test = 0;\nint GLAD_GL_QCOM_binning_control = 0;\nint GLAD_GL_QCOM_driver_control = 0;\nint GLAD_GL_QCOM_extended_get = 0;\nint GLAD_GL_QCOM_extended_get2 = 0;\nint GLAD_GL_QCOM_framebuffer_foveated = 0;\nint GLAD_GL_QCOM_perfmon_global_mode = 0;\nint GLAD_GL_QCOM_shader_framebuffer_fetch_noncoherent = 0;\nint GLAD_GL_QCOM_shader_framebuffer_fetch_rate = 0;\nint GLAD_GL_QCOM_texture_foveated = 0;\nint GLAD_GL_QCOM_texture_foveated_subsampled_layout = 0;\nint GLAD_GL_QCOM_tiled_rendering = 0;\nint GLAD_GL_QCOM_writeonly_rendering = 0;\nint GLAD_GL_REND_screen_coordinates = 0;\nint GLAD_GL_S3_s3tc = 0;\nint GLAD_GL_SGIS_detail_texture = 0;\nint GLAD_GL_SGIS_fog_function = 0;\nint GLAD_GL_SGIS_generate_mipmap = 0;\nint GLAD_GL_SGIS_multisample = 0;\nint GLAD_GL_SGIS_pixel_texture = 0;\nint GLAD_GL_SGIS_point_line_texgen = 0;\nint GLAD_GL_SGIS_point_parameters = 0;\nint GLAD_GL_SGIS_sharpen_texture = 0;\nint GLAD_GL_SGIS_texture4D = 0;\nint GLAD_GL_SGIS_texture_border_clamp = 0;\nint GLAD_GL_SGIS_texture_color_mask = 0;\nint GLAD_GL_SGIS_texture_edge_clamp = 0;\nint GLAD_GL_SGIS_texture_filter4 = 0;\nint GLAD_GL_SGIS_texture_lod = 0;\nint GLAD_GL_SGIS_texture_select = 0;\nint GLAD_GL_SGIX_async = 0;\nint GLAD_GL_SGIX_async_histogram = 0;\nint GLAD_GL_SGIX_async_pixel = 0;\nint GLAD_GL_SGIX_blend_alpha_minmax = 0;\nint GLAD_GL_SGIX_calligraphic_fragment = 0;\nint GLAD_GL_SGIX_clipmap = 0;\nint GLAD_GL_SGIX_convolution_accuracy = 0;\nint GLAD_GL_SGIX_depth_pass_instrument = 0;\nint GLAD_GL_SGIX_depth_texture = 0;\nint GLAD_GL_SGIX_flush_raster = 0;\nint GLAD_GL_SGIX_fog_offset = 0;\nint GLAD_GL_SGIX_fragment_lighting = 0;\nint GLAD_GL_SGIX_framezoom = 0;\nint GLAD_GL_SGIX_igloo_interface = 0;\nint GLAD_GL_SGIX_instruments = 0;\nint GLAD_GL_SGIX_interlace = 0;\nint GLAD_GL_SGIX_ir_instrument1 = 0;\nint GLAD_GL_SGIX_list_priority = 0;\nint GLAD_GL_SGIX_pixel_texture = 0;\nint GLAD_GL_SGIX_pixel_tiles = 0;\nint GLAD_GL_SGIX_polynomial_ffd = 0;\nint GLAD_GL_SGIX_reference_plane = 0;\nint GLAD_GL_SGIX_resample = 0;\nint GLAD_GL_SGIX_scalebias_hint = 0;\nint GLAD_GL_SGIX_shadow = 0;\nint GLAD_GL_SGIX_shadow_ambient = 0;\nint GLAD_GL_SGIX_sprite = 0;\nint GLAD_GL_SGIX_subsample = 0;\nint GLAD_GL_SGIX_tag_sample_buffer = 0;\nint GLAD_GL_SGIX_texture_add_env = 0;\nint GLAD_GL_SGIX_texture_coordinate_clamp = 0;\nint GLAD_GL_SGIX_texture_lod_bias = 0;\nint GLAD_GL_SGIX_texture_multi_buffer = 0;\nint GLAD_GL_SGIX_texture_scale_bias = 0;\nint GLAD_GL_SGIX_vertex_preclip = 0;\nint GLAD_GL_SGIX_ycrcb = 0;\nint GLAD_GL_SGIX_ycrcb_subsample = 0;\nint GLAD_GL_SGIX_ycrcba = 0;\nint GLAD_GL_SGI_color_matrix = 0;\nint GLAD_GL_SGI_color_table = 0;\nint GLAD_GL_SGI_texture_color_table = 0;\nint GLAD_GL_SUNX_constant_data = 0;\nint GLAD_GL_SUN_convolution_border_modes = 0;\nint GLAD_GL_SUN_global_alpha = 0;\nint GLAD_GL_SUN_mesh_array = 0;\nint GLAD_GL_SUN_slice_accum = 0;\nint GLAD_GL_SUN_triangle_list = 0;\nint GLAD_GL_SUN_vertex = 0;\nint GLAD_GL_VIV_shader_binary = 0;\nint GLAD_GL_WIN_phong_shading = 0;\nint GLAD_GL_WIN_specular_fog = 0;\nPFNGLTBUFFERMASK3DFXPROC glad_glTbufferMask3DFX = NULL;\nPFNGLDEBUGMESSAGEENABLEAMDPROC glad_glDebugMessageEnableAMD = NULL;\nPFNGLDEBUGMESSAGEINSERTAMDPROC glad_glDebugMessageInsertAMD = NULL;\nPFNGLDEBUGMESSAGECALLBACKAMDPROC glad_glDebugMessageCallbackAMD = NULL;\nPFNGLGETDEBUGMESSAGELOGAMDPROC glad_glGetDebugMessageLogAMD = NULL;\nPFNGLBLENDFUNCINDEXEDAMDPROC glad_glBlendFuncIndexedAMD = NULL;\nPFNGLBLENDFUNCSEPARATEINDEXEDAMDPROC glad_glBlendFuncSeparateIndexedAMD = NULL;\nPFNGLBLENDEQUATIONINDEXEDAMDPROC glad_glBlendEquationIndexedAMD = NULL;\nPFNGLBLENDEQUATIONSEPARATEINDEXEDAMDPROC glad_glBlendEquationSeparateIndexedAMD = NULL;\nPFNGLRENDERBUFFERSTORAGEMULTISAMPLEADVANCEDAMDPROC glad_glRenderbufferStorageMultisampleAdvancedAMD = NULL;\nPFNGLNAMEDRENDERBUFFERSTORAGEMULTISAMPLEADVANCEDAMDPROC glad_glNamedRenderbufferStorageMultisampleAdvancedAMD = NULL;\nPFNGLFRAMEBUFFERSAMPLEPOSITIONSFVAMDPROC glad_glFramebufferSamplePositionsfvAMD = NULL;\nPFNGLNAMEDFRAMEBUFFERSAMPLEPOSITIONSFVAMDPROC glad_glNamedFramebufferSamplePositionsfvAMD = NULL;\nPFNGLGETFRAMEBUFFERPARAMETERFVAMDPROC glad_glGetFramebufferParameterfvAMD = NULL;\nPFNGLGETNAMEDFRAMEBUFFERPARAMETERFVAMDPROC glad_glGetNamedFramebufferParameterfvAMD = NULL;\nPFNGLUNIFORM1I64NVPROC glad_glUniform1i64NV = NULL;\nPFNGLUNIFORM2I64NVPROC glad_glUniform2i64NV = NULL;\nPFNGLUNIFORM3I64NVPROC glad_glUniform3i64NV = NULL;\nPFNGLUNIFORM4I64NVPROC glad_glUniform4i64NV = NULL;\nPFNGLUNIFORM1I64VNVPROC glad_glUniform1i64vNV = NULL;\nPFNGLUNIFORM2I64VNVPROC glad_glUniform2i64vNV = NULL;\nPFNGLUNIFORM3I64VNVPROC glad_glUniform3i64vNV = NULL;\nPFNGLUNIFORM4I64VNVPROC glad_glUniform4i64vNV = NULL;\nPFNGLUNIFORM1UI64NVPROC glad_glUniform1ui64NV = NULL;\nPFNGLUNIFORM2UI64NVPROC glad_glUniform2ui64NV = NULL;\nPFNGLUNIFORM3UI64NVPROC glad_glUniform3ui64NV = NULL;\nPFNGLUNIFORM4UI64NVPROC glad_glUniform4ui64NV = NULL;\nPFNGLUNIFORM1UI64VNVPROC glad_glUniform1ui64vNV = NULL;\nPFNGLUNIFORM2UI64VNVPROC glad_glUniform2ui64vNV = NULL;\nPFNGLUNIFORM3UI64VNVPROC glad_glUniform3ui64vNV = NULL;\nPFNGLUNIFORM4UI64VNVPROC glad_glUniform4ui64vNV = NULL;\nPFNGLGETUNIFORMI64VNVPROC glad_glGetUniformi64vNV = NULL;\nPFNGLGETUNIFORMUI64VNVPROC glad_glGetUniformui64vNV = NULL;\nPFNGLPROGRAMUNIFORM1I64NVPROC glad_glProgramUniform1i64NV = NULL;\nPFNGLPROGRAMUNIFORM2I64NVPROC glad_glProgramUniform2i64NV = NULL;\nPFNGLPROGRAMUNIFORM3I64NVPROC glad_glProgramUniform3i64NV = NULL;\nPFNGLPROGRAMUNIFORM4I64NVPROC glad_glProgramUniform4i64NV = NULL;\nPFNGLPROGRAMUNIFORM1I64VNVPROC glad_glProgramUniform1i64vNV = NULL;\nPFNGLPROGRAMUNIFORM2I64VNVPROC glad_glProgramUniform2i64vNV = NULL;\nPFNGLPROGRAMUNIFORM3I64VNVPROC glad_glProgramUniform3i64vNV = NULL;\nPFNGLPROGRAMUNIFORM4I64VNVPROC glad_glProgramUniform4i64vNV = NULL;\nPFNGLPROGRAMUNIFORM1UI64NVPROC glad_glProgramUniform1ui64NV = NULL;\nPFNGLPROGRAMUNIFORM2UI64NVPROC glad_glProgramUniform2ui64NV = NULL;\nPFNGLPROGRAMUNIFORM3UI64NVPROC glad_glProgramUniform3ui64NV = NULL;\nPFNGLPROGRAMUNIFORM4UI64NVPROC glad_glProgramUniform4ui64NV = NULL;\nPFNGLPROGRAMUNIFORM1UI64VNVPROC glad_glProgramUniform1ui64vNV = NULL;\nPFNGLPROGRAMUNIFORM2UI64VNVPROC glad_glProgramUniform2ui64vNV = NULL;\nPFNGLPROGRAMUNIFORM3UI64VNVPROC glad_glProgramUniform3ui64vNV = NULL;\nPFNGLPROGRAMUNIFORM4UI64VNVPROC glad_glProgramUniform4ui64vNV = NULL;\nPFNGLVERTEXATTRIBPARAMETERIAMDPROC glad_glVertexAttribParameteriAMD = NULL;\nPFNGLMULTIDRAWARRAYSINDIRECTAMDPROC glad_glMultiDrawArraysIndirectAMD = NULL;\nPFNGLMULTIDRAWELEMENTSINDIRECTAMDPROC glad_glMultiDrawElementsIndirectAMD = NULL;\nPFNGLGENNAMESAMDPROC glad_glGenNamesAMD = NULL;\nPFNGLDELETENAMESAMDPROC glad_glDeleteNamesAMD = NULL;\nPFNGLISNAMEAMDPROC glad_glIsNameAMD = NULL;\nPFNGLQUERYOBJECTPARAMETERUIAMDPROC glad_glQueryObjectParameteruiAMD = NULL;\nPFNGLGETPERFMONITORGROUPSAMDPROC glad_glGetPerfMonitorGroupsAMD = NULL;\nPFNGLGETPERFMONITORCOUNTERSAMDPROC glad_glGetPerfMonitorCountersAMD = NULL;\nPFNGLGETPERFMONITORGROUPSTRINGAMDPROC glad_glGetPerfMonitorGroupStringAMD = NULL;\nPFNGLGETPERFMONITORCOUNTERSTRINGAMDPROC glad_glGetPerfMonitorCounterStringAMD = NULL;\nPFNGLGETPERFMONITORCOUNTERINFOAMDPROC glad_glGetPerfMonitorCounterInfoAMD = NULL;\nPFNGLGENPERFMONITORSAMDPROC glad_glGenPerfMonitorsAMD = NULL;\nPFNGLDELETEPERFMONITORSAMDPROC glad_glDeletePerfMonitorsAMD = NULL;\nPFNGLSELECTPERFMONITORCOUNTERSAMDPROC glad_glSelectPerfMonitorCountersAMD = NULL;\nPFNGLBEGINPERFMONITORAMDPROC glad_glBeginPerfMonitorAMD = NULL;\nPFNGLENDPERFMONITORAMDPROC glad_glEndPerfMonitorAMD = NULL;\nPFNGLGETPERFMONITORCOUNTERDATAAMDPROC glad_glGetPerfMonitorCounterDataAMD = NULL;\nPFNGLSETMULTISAMPLEFVAMDPROC glad_glSetMultisamplefvAMD = NULL;\nPFNGLTEXSTORAGESPARSEAMDPROC glad_glTexStorageSparseAMD = NULL;\nPFNGLTEXTURESTORAGESPARSEAMDPROC glad_glTextureStorageSparseAMD = NULL;\nPFNGLSTENCILOPVALUEAMDPROC glad_glStencilOpValueAMD = NULL;\nPFNGLTESSELLATIONFACTORAMDPROC glad_glTessellationFactorAMD = NULL;\nPFNGLTESSELLATIONMODEAMDPROC glad_glTessellationModeAMD = NULL;\nPFNGLELEMENTPOINTERAPPLEPROC glad_glElementPointerAPPLE = NULL;\nPFNGLDRAWELEMENTARRAYAPPLEPROC glad_glDrawElementArrayAPPLE = NULL;\nPFNGLDRAWRANGEELEMENTARRAYAPPLEPROC glad_glDrawRangeElementArrayAPPLE = NULL;\nPFNGLMULTIDRAWELEMENTARRAYAPPLEPROC glad_glMultiDrawElementArrayAPPLE = NULL;\nPFNGLMULTIDRAWRANGEELEMENTARRAYAPPLEPROC glad_glMultiDrawRangeElementArrayAPPLE = NULL;\nPFNGLGENFENCESAPPLEPROC glad_glGenFencesAPPLE = NULL;\nPFNGLDELETEFENCESAPPLEPROC glad_glDeleteFencesAPPLE = NULL;\nPFNGLSETFENCEAPPLEPROC glad_glSetFenceAPPLE = NULL;\nPFNGLISFENCEAPPLEPROC glad_glIsFenceAPPLE = NULL;\nPFNGLTESTFENCEAPPLEPROC glad_glTestFenceAPPLE = NULL;\nPFNGLFINISHFENCEAPPLEPROC glad_glFinishFenceAPPLE = NULL;\nPFNGLTESTOBJECTAPPLEPROC glad_glTestObjectAPPLE = NULL;\nPFNGLFINISHOBJECTAPPLEPROC glad_glFinishObjectAPPLE = NULL;\nPFNGLBUFFERPARAMETERIAPPLEPROC glad_glBufferParameteriAPPLE = NULL;\nPFNGLFLUSHMAPPEDBUFFERRANGEAPPLEPROC glad_glFlushMappedBufferRangeAPPLE = NULL;\nPFNGLOBJECTPURGEABLEAPPLEPROC glad_glObjectPurgeableAPPLE = NULL;\nPFNGLOBJECTUNPURGEABLEAPPLEPROC glad_glObjectUnpurgeableAPPLE = NULL;\nPFNGLGETOBJECTPARAMETERIVAPPLEPROC glad_glGetObjectParameterivAPPLE = NULL;\nPFNGLTEXTURERANGEAPPLEPROC glad_glTextureRangeAPPLE = NULL;\nPFNGLGETTEXPARAMETERPOINTERVAPPLEPROC glad_glGetTexParameterPointervAPPLE = NULL;\nPFNGLBINDVERTEXARRAYAPPLEPROC glad_glBindVertexArrayAPPLE = NULL;\nPFNGLDELETEVERTEXARRAYSAPPLEPROC glad_glDeleteVertexArraysAPPLE = NULL;\nPFNGLGENVERTEXARRAYSAPPLEPROC glad_glGenVertexArraysAPPLE = NULL;\nPFNGLISVERTEXARRAYAPPLEPROC glad_glIsVertexArrayAPPLE = NULL;\nPFNGLVERTEXARRAYRANGEAPPLEPROC glad_glVertexArrayRangeAPPLE = NULL;\nPFNGLFLUSHVERTEXARRAYRANGEAPPLEPROC glad_glFlushVertexArrayRangeAPPLE = NULL;\nPFNGLVERTEXARRAYPARAMETERIAPPLEPROC glad_glVertexArrayParameteriAPPLE = NULL;\nPFNGLENABLEVERTEXATTRIBAPPLEPROC glad_glEnableVertexAttribAPPLE = NULL;\nPFNGLDISABLEVERTEXATTRIBAPPLEPROC glad_glDisableVertexAttribAPPLE = NULL;\nPFNGLISVERTEXATTRIBENABLEDAPPLEPROC glad_glIsVertexAttribEnabledAPPLE = NULL;\nPFNGLMAPVERTEXATTRIB1DAPPLEPROC glad_glMapVertexAttrib1dAPPLE = NULL;\nPFNGLMAPVERTEXATTRIB1FAPPLEPROC glad_glMapVertexAttrib1fAPPLE = NULL;\nPFNGLMAPVERTEXATTRIB2DAPPLEPROC glad_glMapVertexAttrib2dAPPLE = NULL;\nPFNGLMAPVERTEXATTRIB2FAPPLEPROC glad_glMapVertexAttrib2fAPPLE = NULL;\nPFNGLMEMORYBARRIERBYREGIONPROC glad_glMemoryBarrierByRegion = NULL;\nPFNGLPRIMITIVEBOUNDINGBOXARBPROC glad_glPrimitiveBoundingBoxARB = NULL;\nPFNGLDRAWARRAYSINSTANCEDBASEINSTANCEPROC glad_glDrawArraysInstancedBaseInstance = NULL;\nPFNGLDRAWELEMENTSINSTANCEDBASEINSTANCEPROC glad_glDrawElementsInstancedBaseInstance = NULL;\nPFNGLDRAWELEMENTSINSTANCEDBASEVERTEXBASEINSTANCEPROC glad_glDrawElementsInstancedBaseVertexBaseInstance = NULL;\nPFNGLGETTEXTUREHANDLEARBPROC glad_glGetTextureHandleARB = NULL;\nPFNGLGETTEXTURESAMPLERHANDLEARBPROC glad_glGetTextureSamplerHandleARB = NULL;\nPFNGLMAKETEXTUREHANDLERESIDENTARBPROC glad_glMakeTextureHandleResidentARB = NULL;\nPFNGLMAKETEXTUREHANDLENONRESIDENTARBPROC glad_glMakeTextureHandleNonResidentARB = NULL;\nPFNGLGETIMAGEHANDLEARBPROC glad_glGetImageHandleARB = NULL;\nPFNGLMAKEIMAGEHANDLERESIDENTARBPROC glad_glMakeImageHandleResidentARB = NULL;\nPFNGLMAKEIMAGEHANDLENONRESIDENTARBPROC glad_glMakeImageHandleNonResidentARB = NULL;\nPFNGLUNIFORMHANDLEUI64ARBPROC glad_glUniformHandleui64ARB = NULL;\nPFNGLUNIFORMHANDLEUI64VARBPROC glad_glUniformHandleui64vARB = NULL;\nPFNGLPROGRAMUNIFORMHANDLEUI64ARBPROC glad_glProgramUniformHandleui64ARB = NULL;\nPFNGLPROGRAMUNIFORMHANDLEUI64VARBPROC glad_glProgramUniformHandleui64vARB = NULL;\nPFNGLISTEXTUREHANDLERESIDENTARBPROC glad_glIsTextureHandleResidentARB = NULL;\nPFNGLISIMAGEHANDLERESIDENTARBPROC glad_glIsImageHandleResidentARB = NULL;\nPFNGLVERTEXATTRIBL1UI64ARBPROC glad_glVertexAttribL1ui64ARB = NULL;\nPFNGLVERTEXATTRIBL1UI64VARBPROC glad_glVertexAttribL1ui64vARB = NULL;\nPFNGLGETVERTEXATTRIBLUI64VARBPROC glad_glGetVertexAttribLui64vARB = NULL;\nPFNGLBUFFERSTORAGEPROC glad_glBufferStorage = NULL;\nPFNGLCREATESYNCFROMCLEVENTARBPROC glad_glCreateSyncFromCLeventARB = NULL;\nPFNGLCLEARBUFFERDATAPROC glad_glClearBufferData = NULL;\nPFNGLCLEARBUFFERSUBDATAPROC glad_glClearBufferSubData = NULL;\nPFNGLCLEARTEXIMAGEPROC glad_glClearTexImage = NULL;\nPFNGLCLEARTEXSUBIMAGEPROC glad_glClearTexSubImage = NULL;\nPFNGLCLIPCONTROLPROC glad_glClipControl = NULL;\nPFNGLCLAMPCOLORARBPROC glad_glClampColorARB = NULL;\nPFNGLDISPATCHCOMPUTEPROC glad_glDispatchCompute = NULL;\nPFNGLDISPATCHCOMPUTEINDIRECTPROC glad_glDispatchComputeIndirect = NULL;\nPFNGLDISPATCHCOMPUTEGROUPSIZEARBPROC glad_glDispatchComputeGroupSizeARB = NULL;\nPFNGLCOPYIMAGESUBDATAPROC glad_glCopyImageSubData = NULL;\nPFNGLDEBUGMESSAGECONTROLARBPROC glad_glDebugMessageControlARB = NULL;\nPFNGLDEBUGMESSAGEINSERTARBPROC glad_glDebugMessageInsertARB = NULL;\nPFNGLDEBUGMESSAGECALLBACKARBPROC glad_glDebugMessageCallbackARB = NULL;\nPFNGLGETDEBUGMESSAGELOGARBPROC glad_glGetDebugMessageLogARB = NULL;\nPFNGLCREATETRANSFORMFEEDBACKSPROC glad_glCreateTransformFeedbacks = NULL;\nPFNGLTRANSFORMFEEDBACKBUFFERBASEPROC glad_glTransformFeedbackBufferBase = NULL;\nPFNGLTRANSFORMFEEDBACKBUFFERRANGEPROC glad_glTransformFeedbackBufferRange = NULL;\nPFNGLGETTRANSFORMFEEDBACKIVPROC glad_glGetTransformFeedbackiv = NULL;\nPFNGLGETTRANSFORMFEEDBACKI_VPROC glad_glGetTransformFeedbacki_v = NULL;\nPFNGLGETTRANSFORMFEEDBACKI64_VPROC glad_glGetTransformFeedbacki64_v = NULL;\nPFNGLCREATEBUFFERSPROC glad_glCreateBuffers = NULL;\nPFNGLNAMEDBUFFERSTORAGEPROC glad_glNamedBufferStorage = NULL;\nPFNGLNAMEDBUFFERDATAPROC glad_glNamedBufferData = NULL;\nPFNGLNAMEDBUFFERSUBDATAPROC glad_glNamedBufferSubData = NULL;\nPFNGLCOPYNAMEDBUFFERSUBDATAPROC glad_glCopyNamedBufferSubData = NULL;\nPFNGLCLEARNAMEDBUFFERDATAPROC glad_glClearNamedBufferData = NULL;\nPFNGLCLEARNAMEDBUFFERSUBDATAPROC glad_glClearNamedBufferSubData = NULL;\nPFNGLMAPNAMEDBUFFERPROC glad_glMapNamedBuffer = NULL;\nPFNGLMAPNAMEDBUFFERRANGEPROC glad_glMapNamedBufferRange = NULL;\nPFNGLUNMAPNAMEDBUFFERPROC glad_glUnmapNamedBuffer = NULL;\nPFNGLFLUSHMAPPEDNAMEDBUFFERRANGEPROC glad_glFlushMappedNamedBufferRange = NULL;\nPFNGLGETNAMEDBUFFERPARAMETERIVPROC glad_glGetNamedBufferParameteriv = NULL;\nPFNGLGETNAMEDBUFFERPARAMETERI64VPROC glad_glGetNamedBufferParameteri64v = NULL;\nPFNGLGETNAMEDBUFFERPOINTERVPROC glad_glGetNamedBufferPointerv = NULL;\nPFNGLGETNAMEDBUFFERSUBDATAPROC glad_glGetNamedBufferSubData = NULL;\nPFNGLCREATEFRAMEBUFFERSPROC glad_glCreateFramebuffers = NULL;\nPFNGLNAMEDFRAMEBUFFERRENDERBUFFERPROC glad_glNamedFramebufferRenderbuffer = NULL;\nPFNGLNAMEDFRAMEBUFFERPARAMETERIPROC glad_glNamedFramebufferParameteri = NULL;\nPFNGLNAMEDFRAMEBUFFERTEXTUREPROC glad_glNamedFramebufferTexture = NULL;\nPFNGLNAMEDFRAMEBUFFERTEXTURELAYERPROC glad_glNamedFramebufferTextureLayer = NULL;\nPFNGLNAMEDFRAMEBUFFERDRAWBUFFERPROC glad_glNamedFramebufferDrawBuffer = NULL;\nPFNGLNAMEDFRAMEBUFFERDRAWBUFFERSPROC glad_glNamedFramebufferDrawBuffers = NULL;\nPFNGLNAMEDFRAMEBUFFERREADBUFFERPROC glad_glNamedFramebufferReadBuffer = NULL;\nPFNGLINVALIDATENAMEDFRAMEBUFFERDATAPROC glad_glInvalidateNamedFramebufferData = NULL;\nPFNGLINVALIDATENAMEDFRAMEBUFFERSUBDATAPROC glad_glInvalidateNamedFramebufferSubData = NULL;\nPFNGLCLEARNAMEDFRAMEBUFFERIVPROC glad_glClearNamedFramebufferiv = NULL;\nPFNGLCLEARNAMEDFRAMEBUFFERUIVPROC glad_glClearNamedFramebufferuiv = NULL;\nPFNGLCLEARNAMEDFRAMEBUFFERFVPROC glad_glClearNamedFramebufferfv = NULL;\nPFNGLCLEARNAMEDFRAMEBUFFERFIPROC glad_glClearNamedFramebufferfi = NULL;\nPFNGLBLITNAMEDFRAMEBUFFERPROC glad_glBlitNamedFramebuffer = NULL;\nPFNGLCHECKNAMEDFRAMEBUFFERSTATUSPROC glad_glCheckNamedFramebufferStatus = NULL;\nPFNGLGETNAMEDFRAMEBUFFERPARAMETERIVPROC glad_glGetNamedFramebufferParameteriv = NULL;\nPFNGLGETNAMEDFRAMEBUFFERATTACHMENTPARAMETERIVPROC glad_glGetNamedFramebufferAttachmentParameteriv = NULL;\nPFNGLCREATERENDERBUFFERSPROC glad_glCreateRenderbuffers = NULL;\nPFNGLNAMEDRENDERBUFFERSTORAGEPROC glad_glNamedRenderbufferStorage = NULL;\nPFNGLNAMEDRENDERBUFFERSTORAGEMULTISAMPLEPROC glad_glNamedRenderbufferStorageMultisample = NULL;\nPFNGLGETNAMEDRENDERBUFFERPARAMETERIVPROC glad_glGetNamedRenderbufferParameteriv = NULL;\nPFNGLCREATETEXTURESPROC glad_glCreateTextures = NULL;\nPFNGLTEXTUREBUFFERPROC glad_glTextureBuffer = NULL;\nPFNGLTEXTUREBUFFERRANGEPROC glad_glTextureBufferRange = NULL;\nPFNGLTEXTURESTORAGE1DPROC glad_glTextureStorage1D = NULL;\nPFNGLTEXTURESTORAGE2DPROC glad_glTextureStorage2D = NULL;\nPFNGLTEXTURESTORAGE3DPROC glad_glTextureStorage3D = NULL;\nPFNGLTEXTURESTORAGE2DMULTISAMPLEPROC glad_glTextureStorage2DMultisample = NULL;\nPFNGLTEXTURESTORAGE3DMULTISAMPLEPROC glad_glTextureStorage3DMultisample = NULL;\nPFNGLTEXTURESUBIMAGE1DPROC glad_glTextureSubImage1D = NULL;\nPFNGLTEXTURESUBIMAGE2DPROC glad_glTextureSubImage2D = NULL;\nPFNGLTEXTURESUBIMAGE3DPROC glad_glTextureSubImage3D = NULL;\nPFNGLCOMPRESSEDTEXTURESUBIMAGE1DPROC glad_glCompressedTextureSubImage1D = NULL;\nPFNGLCOMPRESSEDTEXTURESUBIMAGE2DPROC glad_glCompressedTextureSubImage2D = NULL;\nPFNGLCOMPRESSEDTEXTURESUBIMAGE3DPROC glad_glCompressedTextureSubImage3D = NULL;\nPFNGLCOPYTEXTURESUBIMAGE1DPROC glad_glCopyTextureSubImage1D = NULL;\nPFNGLCOPYTEXTURESUBIMAGE2DPROC glad_glCopyTextureSubImage2D = NULL;\nPFNGLCOPYTEXTURESUBIMAGE3DPROC glad_glCopyTextureSubImage3D = NULL;\nPFNGLTEXTUREPARAMETERFPROC glad_glTextureParameterf = NULL;\nPFNGLTEXTUREPARAMETERFVPROC glad_glTextureParameterfv = NULL;\nPFNGLTEXTUREPARAMETERIPROC glad_glTextureParameteri = NULL;\nPFNGLTEXTUREPARAMETERIIVPROC glad_glTextureParameterIiv = NULL;\nPFNGLTEXTUREPARAMETERIUIVPROC glad_glTextureParameterIuiv = NULL;\nPFNGLTEXTUREPARAMETERIVPROC glad_glTextureParameteriv = NULL;\nPFNGLGENERATETEXTUREMIPMAPPROC glad_glGenerateTextureMipmap = NULL;\nPFNGLBINDTEXTUREUNITPROC glad_glBindTextureUnit = NULL;\nPFNGLGETTEXTUREIMAGEPROC glad_glGetTextureImage = NULL;\nPFNGLGETCOMPRESSEDTEXTUREIMAGEPROC glad_glGetCompressedTextureImage = NULL;\nPFNGLGETTEXTURELEVELPARAMETERFVPROC glad_glGetTextureLevelParameterfv = NULL;\nPFNGLGETTEXTURELEVELPARAMETERIVPROC glad_glGetTextureLevelParameteriv = NULL;\nPFNGLGETTEXTUREPARAMETERFVPROC glad_glGetTextureParameterfv = NULL;\nPFNGLGETTEXTUREPARAMETERIIVPROC glad_glGetTextureParameterIiv = NULL;\nPFNGLGETTEXTUREPARAMETERIUIVPROC glad_glGetTextureParameterIuiv = NULL;\nPFNGLGETTEXTUREPARAMETERIVPROC glad_glGetTextureParameteriv = NULL;\nPFNGLCREATEVERTEXARRAYSPROC glad_glCreateVertexArrays = NULL;\nPFNGLDISABLEVERTEXARRAYATTRIBPROC glad_glDisableVertexArrayAttrib = NULL;\nPFNGLENABLEVERTEXARRAYATTRIBPROC glad_glEnableVertexArrayAttrib = NULL;\nPFNGLVERTEXARRAYELEMENTBUFFERPROC glad_glVertexArrayElementBuffer = NULL;\nPFNGLVERTEXARRAYVERTEXBUFFERPROC glad_glVertexArrayVertexBuffer = NULL;\nPFNGLVERTEXARRAYVERTEXBUFFERSPROC glad_glVertexArrayVertexBuffers = NULL;\nPFNGLVERTEXARRAYATTRIBBINDINGPROC glad_glVertexArrayAttribBinding = NULL;\nPFNGLVERTEXARRAYATTRIBFORMATPROC glad_glVertexArrayAttribFormat = NULL;\nPFNGLVERTEXARRAYATTRIBIFORMATPROC glad_glVertexArrayAttribIFormat = NULL;\nPFNGLVERTEXARRAYATTRIBLFORMATPROC glad_glVertexArrayAttribLFormat = NULL;\nPFNGLVERTEXARRAYBINDINGDIVISORPROC glad_glVertexArrayBindingDivisor = NULL;\nPFNGLGETVERTEXARRAYIVPROC glad_glGetVertexArrayiv = NULL;\nPFNGLGETVERTEXARRAYINDEXEDIVPROC glad_glGetVertexArrayIndexediv = NULL;\nPFNGLGETVERTEXARRAYINDEXED64IVPROC glad_glGetVertexArrayIndexed64iv = NULL;\nPFNGLCREATESAMPLERSPROC glad_glCreateSamplers = NULL;\nPFNGLCREATEPROGRAMPIPELINESPROC glad_glCreateProgramPipelines = NULL;\nPFNGLCREATEQUERIESPROC glad_glCreateQueries = NULL;\nPFNGLGETQUERYBUFFEROBJECTI64VPROC glad_glGetQueryBufferObjecti64v = NULL;\nPFNGLGETQUERYBUFFEROBJECTIVPROC glad_glGetQueryBufferObjectiv = NULL;\nPFNGLGETQUERYBUFFEROBJECTUI64VPROC glad_glGetQueryBufferObjectui64v = NULL;\nPFNGLGETQUERYBUFFEROBJECTUIVPROC glad_glGetQueryBufferObjectuiv = NULL;\nPFNGLDRAWBUFFERSARBPROC glad_glDrawBuffersARB = NULL;\nPFNGLBLENDEQUATIONIARBPROC glad_glBlendEquationiARB = NULL;\nPFNGLBLENDEQUATIONSEPARATEIARBPROC glad_glBlendEquationSeparateiARB = NULL;\nPFNGLBLENDFUNCIARBPROC glad_glBlendFunciARB = NULL;\nPFNGLBLENDFUNCSEPARATEIARBPROC glad_glBlendFuncSeparateiARB = NULL;\nPFNGLDRAWARRAYSINDIRECTPROC glad_glDrawArraysIndirect = NULL;\nPFNGLDRAWELEMENTSINDIRECTPROC glad_glDrawElementsIndirect = NULL;\nPFNGLDRAWARRAYSINSTANCEDARBPROC glad_glDrawArraysInstancedARB = NULL;\nPFNGLDRAWELEMENTSINSTANCEDARBPROC glad_glDrawElementsInstancedARB = NULL;\nPFNGLPROGRAMSTRINGARBPROC glad_glProgramStringARB = NULL;\nPFNGLBINDPROGRAMARBPROC glad_glBindProgramARB = NULL;\nPFNGLDELETEPROGRAMSARBPROC glad_glDeleteProgramsARB = NULL;\nPFNGLGENPROGRAMSARBPROC glad_glGenProgramsARB = NULL;\nPFNGLPROGRAMENVPARAMETER4DARBPROC glad_glProgramEnvParameter4dARB = NULL;\nPFNGLPROGRAMENVPARAMETER4DVARBPROC glad_glProgramEnvParameter4dvARB = NULL;\nPFNGLPROGRAMENVPARAMETER4FARBPROC glad_glProgramEnvParameter4fARB = NULL;\nPFNGLPROGRAMENVPARAMETER4FVARBPROC glad_glProgramEnvParameter4fvARB = NULL;\nPFNGLPROGRAMLOCALPARAMETER4DARBPROC glad_glProgramLocalParameter4dARB = NULL;\nPFNGLPROGRAMLOCALPARAMETER4DVARBPROC glad_glProgramLocalParameter4dvARB = NULL;\nPFNGLPROGRAMLOCALPARAMETER4FARBPROC glad_glProgramLocalParameter4fARB = NULL;\nPFNGLPROGRAMLOCALPARAMETER4FVARBPROC glad_glProgramLocalParameter4fvARB = NULL;\nPFNGLGETPROGRAMENVPARAMETERDVARBPROC glad_glGetProgramEnvParameterdvARB = NULL;\nPFNGLGETPROGRAMENVPARAMETERFVARBPROC glad_glGetProgramEnvParameterfvARB = NULL;\nPFNGLGETPROGRAMLOCALPARAMETERDVARBPROC glad_glGetProgramLocalParameterdvARB = NULL;\nPFNGLGETPROGRAMLOCALPARAMETERFVARBPROC glad_glGetProgramLocalParameterfvARB = NULL;\nPFNGLGETPROGRAMIVARBPROC glad_glGetProgramivARB = NULL;\nPFNGLGETPROGRAMSTRINGARBPROC glad_glGetProgramStringARB = NULL;\nPFNGLISPROGRAMARBPROC glad_glIsProgramARB = NULL;\nPFNGLFRAMEBUFFERPARAMETERIPROC glad_glFramebufferParameteri = NULL;\nPFNGLGETFRAMEBUFFERPARAMETERIVPROC glad_glGetFramebufferParameteriv = NULL;\nPFNGLPROGRAMPARAMETERIARBPROC glad_glProgramParameteriARB = NULL;\nPFNGLFRAMEBUFFERTEXTUREARBPROC glad_glFramebufferTextureARB = NULL;\nPFNGLFRAMEBUFFERTEXTURELAYERARBPROC glad_glFramebufferTextureLayerARB = NULL;\nPFNGLFRAMEBUFFERTEXTUREFACEARBPROC glad_glFramebufferTextureFaceARB = NULL;\nPFNGLGETTEXTURESUBIMAGEPROC glad_glGetTextureSubImage = NULL;\nPFNGLGETCOMPRESSEDTEXTURESUBIMAGEPROC glad_glGetCompressedTextureSubImage = NULL;\nPFNGLSPECIALIZESHADERARBPROC glad_glSpecializeShaderARB = NULL;\nPFNGLUNIFORM1DPROC glad_glUniform1d = NULL;\nPFNGLUNIFORM2DPROC glad_glUniform2d = NULL;\nPFNGLUNIFORM3DPROC glad_glUniform3d = NULL;\nPFNGLUNIFORM4DPROC glad_glUniform4d = NULL;\nPFNGLUNIFORM1DVPROC glad_glUniform1dv = NULL;\nPFNGLUNIFORM2DVPROC glad_glUniform2dv = NULL;\nPFNGLUNIFORM3DVPROC glad_glUniform3dv = NULL;\nPFNGLUNIFORM4DVPROC glad_glUniform4dv = NULL;\nPFNGLUNIFORMMATRIX2DVPROC glad_glUniformMatrix2dv = NULL;\nPFNGLUNIFORMMATRIX3DVPROC glad_glUniformMatrix3dv = NULL;\nPFNGLUNIFORMMATRIX4DVPROC glad_glUniformMatrix4dv = NULL;\nPFNGLUNIFORMMATRIX2X3DVPROC glad_glUniformMatrix2x3dv = NULL;\nPFNGLUNIFORMMATRIX2X4DVPROC glad_glUniformMatrix2x4dv = NULL;\nPFNGLUNIFORMMATRIX3X2DVPROC glad_glUniformMatrix3x2dv = NULL;\nPFNGLUNIFORMMATRIX3X4DVPROC glad_glUniformMatrix3x4dv = NULL;\nPFNGLUNIFORMMATRIX4X2DVPROC glad_glUniformMatrix4x2dv = NULL;\nPFNGLUNIFORMMATRIX4X3DVPROC glad_glUniformMatrix4x3dv = NULL;\nPFNGLGETUNIFORMDVPROC glad_glGetUniformdv = NULL;\nPFNGLUNIFORM1I64ARBPROC glad_glUniform1i64ARB = NULL;\nPFNGLUNIFORM2I64ARBPROC glad_glUniform2i64ARB = NULL;\nPFNGLUNIFORM3I64ARBPROC glad_glUniform3i64ARB = NULL;\nPFNGLUNIFORM4I64ARBPROC glad_glUniform4i64ARB = NULL;\nPFNGLUNIFORM1I64VARBPROC glad_glUniform1i64vARB = NULL;\nPFNGLUNIFORM2I64VARBPROC glad_glUniform2i64vARB = NULL;\nPFNGLUNIFORM3I64VARBPROC glad_glUniform3i64vARB = NULL;\nPFNGLUNIFORM4I64VARBPROC glad_glUniform4i64vARB = NULL;\nPFNGLUNIFORM1UI64ARBPROC glad_glUniform1ui64ARB = NULL;\nPFNGLUNIFORM2UI64ARBPROC glad_glUniform2ui64ARB = NULL;\nPFNGLUNIFORM3UI64ARBPROC glad_glUniform3ui64ARB = NULL;\nPFNGLUNIFORM4UI64ARBPROC glad_glUniform4ui64ARB = NULL;\nPFNGLUNIFORM1UI64VARBPROC glad_glUniform1ui64vARB = NULL;\nPFNGLUNIFORM2UI64VARBPROC glad_glUniform2ui64vARB = NULL;\nPFNGLUNIFORM3UI64VARBPROC glad_glUniform3ui64vARB = NULL;\nPFNGLUNIFORM4UI64VARBPROC glad_glUniform4ui64vARB = NULL;\nPFNGLGETUNIFORMI64VARBPROC glad_glGetUniformi64vARB = NULL;\nPFNGLGETUNIFORMUI64VARBPROC glad_glGetUniformui64vARB = NULL;\nPFNGLGETNUNIFORMI64VARBPROC glad_glGetnUniformi64vARB = NULL;\nPFNGLGETNUNIFORMUI64VARBPROC glad_glGetnUniformui64vARB = NULL;\nPFNGLPROGRAMUNIFORM1I64ARBPROC glad_glProgramUniform1i64ARB = NULL;\nPFNGLPROGRAMUNIFORM2I64ARBPROC glad_glProgramUniform2i64ARB = NULL;\nPFNGLPROGRAMUNIFORM3I64ARBPROC glad_glProgramUniform3i64ARB = NULL;\nPFNGLPROGRAMUNIFORM4I64ARBPROC glad_glProgramUniform4i64ARB = NULL;\nPFNGLPROGRAMUNIFORM1I64VARBPROC glad_glProgramUniform1i64vARB = NULL;\nPFNGLPROGRAMUNIFORM2I64VARBPROC glad_glProgramUniform2i64vARB = NULL;\nPFNGLPROGRAMUNIFORM3I64VARBPROC glad_glProgramUniform3i64vARB = NULL;\nPFNGLPROGRAMUNIFORM4I64VARBPROC glad_glProgramUniform4i64vARB = NULL;\nPFNGLPROGRAMUNIFORM1UI64ARBPROC glad_glProgramUniform1ui64ARB = NULL;\nPFNGLPROGRAMUNIFORM2UI64ARBPROC glad_glProgramUniform2ui64ARB = NULL;\nPFNGLPROGRAMUNIFORM3UI64ARBPROC glad_glProgramUniform3ui64ARB = NULL;\nPFNGLPROGRAMUNIFORM4UI64ARBPROC glad_glProgramUniform4ui64ARB = NULL;\nPFNGLPROGRAMUNIFORM1UI64VARBPROC glad_glProgramUniform1ui64vARB = NULL;\nPFNGLPROGRAMUNIFORM2UI64VARBPROC glad_glProgramUniform2ui64vARB = NULL;\nPFNGLPROGRAMUNIFORM3UI64VARBPROC glad_glProgramUniform3ui64vARB = NULL;\nPFNGLPROGRAMUNIFORM4UI64VARBPROC glad_glProgramUniform4ui64vARB = NULL;\nPFNGLCOLORTABLEPROC glad_glColorTable = NULL;\nPFNGLCOLORTABLEPARAMETERFVPROC glad_glColorTableParameterfv = NULL;\nPFNGLCOLORTABLEPARAMETERIVPROC glad_glColorTableParameteriv = NULL;\nPFNGLCOPYCOLORTABLEPROC glad_glCopyColorTable = NULL;\nPFNGLGETCOLORTABLEPROC glad_glGetColorTable = NULL;\nPFNGLGETCOLORTABLEPARAMETERFVPROC glad_glGetColorTableParameterfv = NULL;\nPFNGLGETCOLORTABLEPARAMETERIVPROC glad_glGetColorTableParameteriv = NULL;\nPFNGLCOLORSUBTABLEPROC glad_glColorSubTable = NULL;\nPFNGLCOPYCOLORSUBTABLEPROC glad_glCopyColorSubTable = NULL;\nPFNGLCONVOLUTIONFILTER1DPROC glad_glConvolutionFilter1D = NULL;\nPFNGLCONVOLUTIONFILTER2DPROC glad_glConvolutionFilter2D = NULL;\nPFNGLCONVOLUTIONPARAMETERFPROC glad_glConvolutionParameterf = NULL;\nPFNGLCONVOLUTIONPARAMETERFVPROC glad_glConvolutionParameterfv = NULL;\nPFNGLCONVOLUTIONPARAMETERIPROC glad_glConvolutionParameteri = NULL;\nPFNGLCONVOLUTIONPARAMETERIVPROC glad_glConvolutionParameteriv = NULL;\nPFNGLCOPYCONVOLUTIONFILTER1DPROC glad_glCopyConvolutionFilter1D = NULL;\nPFNGLCOPYCONVOLUTIONFILTER2DPROC glad_glCopyConvolutionFilter2D = NULL;\nPFNGLGETCONVOLUTIONFILTERPROC glad_glGetConvolutionFilter = NULL;\nPFNGLGETCONVOLUTIONPARAMETERFVPROC glad_glGetConvolutionParameterfv = NULL;\nPFNGLGETCONVOLUTIONPARAMETERIVPROC glad_glGetConvolutionParameteriv = NULL;\nPFNGLGETSEPARABLEFILTERPROC glad_glGetSeparableFilter = NULL;\nPFNGLSEPARABLEFILTER2DPROC glad_glSeparableFilter2D = NULL;\nPFNGLGETHISTOGRAMPROC glad_glGetHistogram = NULL;\nPFNGLGETHISTOGRAMPARAMETERFVPROC glad_glGetHistogramParameterfv = NULL;\nPFNGLGETHISTOGRAMPARAMETERIVPROC glad_glGetHistogramParameteriv = NULL;\nPFNGLGETMINMAXPROC glad_glGetMinmax = NULL;\nPFNGLGETMINMAXPARAMETERFVPROC glad_glGetMinmaxParameterfv = NULL;\nPFNGLGETMINMAXPARAMETERIVPROC glad_glGetMinmaxParameteriv = NULL;\nPFNGLHISTOGRAMPROC glad_glHistogram = NULL;\nPFNGLMINMAXPROC glad_glMinmax = NULL;\nPFNGLRESETHISTOGRAMPROC glad_glResetHistogram = NULL;\nPFNGLRESETMINMAXPROC glad_glResetMinmax = NULL;\nPFNGLMULTIDRAWARRAYSINDIRECTCOUNTARBPROC glad_glMultiDrawArraysIndirectCountARB = NULL;\nPFNGLMULTIDRAWELEMENTSINDIRECTCOUNTARBPROC glad_glMultiDrawElementsIndirectCountARB = NULL;\nPFNGLVERTEXATTRIBDIVISORARBPROC glad_glVertexAttribDivisorARB = NULL;\nPFNGLGETINTERNALFORMATI64VPROC glad_glGetInternalformati64v = NULL;\nPFNGLINVALIDATETEXSUBIMAGEPROC glad_glInvalidateTexSubImage = NULL;\nPFNGLINVALIDATETEXIMAGEPROC glad_glInvalidateTexImage = NULL;\nPFNGLINVALIDATEBUFFERSUBDATAPROC glad_glInvalidateBufferSubData = NULL;\nPFNGLINVALIDATEBUFFERDATAPROC glad_glInvalidateBufferData = NULL;\nPFNGLCURRENTPALETTEMATRIXARBPROC glad_glCurrentPaletteMatrixARB = NULL;\nPFNGLMATRIXINDEXUBVARBPROC glad_glMatrixIndexubvARB = NULL;\nPFNGLMATRIXINDEXUSVARBPROC glad_glMatrixIndexusvARB = NULL;\nPFNGLMATRIXINDEXUIVARBPROC glad_glMatrixIndexuivARB = NULL;\nPFNGLMATRIXINDEXPOINTERARBPROC glad_glMatrixIndexPointerARB = NULL;\nPFNGLBINDBUFFERSBASEPROC glad_glBindBuffersBase = NULL;\nPFNGLBINDBUFFERSRANGEPROC glad_glBindBuffersRange = NULL;\nPFNGLBINDTEXTURESPROC glad_glBindTextures = NULL;\nPFNGLBINDSAMPLERSPROC glad_glBindSamplers = NULL;\nPFNGLBINDIMAGETEXTURESPROC glad_glBindImageTextures = NULL;\nPFNGLBINDVERTEXBUFFERSPROC glad_glBindVertexBuffers = NULL;\nPFNGLMULTIDRAWARRAYSINDIRECTPROC glad_glMultiDrawArraysIndirect = NULL;\nPFNGLMULTIDRAWELEMENTSINDIRECTPROC glad_glMultiDrawElementsIndirect = NULL;\nPFNGLSAMPLECOVERAGEARBPROC glad_glSampleCoverageARB = NULL;\nPFNGLACTIVETEXTUREARBPROC glad_glActiveTextureARB = NULL;\nPFNGLCLIENTACTIVETEXTUREARBPROC glad_glClientActiveTextureARB = NULL;\nPFNGLMULTITEXCOORD1DARBPROC glad_glMultiTexCoord1dARB = NULL;\nPFNGLMULTITEXCOORD1DVARBPROC glad_glMultiTexCoord1dvARB = NULL;\nPFNGLMULTITEXCOORD1FARBPROC glad_glMultiTexCoord1fARB = NULL;\nPFNGLMULTITEXCOORD1FVARBPROC glad_glMultiTexCoord1fvARB = NULL;\nPFNGLMULTITEXCOORD1IARBPROC glad_glMultiTexCoord1iARB = NULL;\nPFNGLMULTITEXCOORD1IVARBPROC glad_glMultiTexCoord1ivARB = NULL;\nPFNGLMULTITEXCOORD1SARBPROC glad_glMultiTexCoord1sARB = NULL;\nPFNGLMULTITEXCOORD1SVARBPROC glad_glMultiTexCoord1svARB = NULL;\nPFNGLMULTITEXCOORD2DARBPROC glad_glMultiTexCoord2dARB = NULL;\nPFNGLMULTITEXCOORD2DVARBPROC glad_glMultiTexCoord2dvARB = NULL;\nPFNGLMULTITEXCOORD2FARBPROC glad_glMultiTexCoord2fARB = NULL;\nPFNGLMULTITEXCOORD2FVARBPROC glad_glMultiTexCoord2fvARB = NULL;\nPFNGLMULTITEXCOORD2IARBPROC glad_glMultiTexCoord2iARB = NULL;\nPFNGLMULTITEXCOORD2IVARBPROC glad_glMultiTexCoord2ivARB = NULL;\nPFNGLMULTITEXCOORD2SARBPROC glad_glMultiTexCoord2sARB = NULL;\nPFNGLMULTITEXCOORD2SVARBPROC glad_glMultiTexCoord2svARB = NULL;\nPFNGLMULTITEXCOORD3DARBPROC glad_glMultiTexCoord3dARB = NULL;\nPFNGLMULTITEXCOORD3DVARBPROC glad_glMultiTexCoord3dvARB = NULL;\nPFNGLMULTITEXCOORD3FARBPROC glad_glMultiTexCoord3fARB = NULL;\nPFNGLMULTITEXCOORD3FVARBPROC glad_glMultiTexCoord3fvARB = NULL;\nPFNGLMULTITEXCOORD3IARBPROC glad_glMultiTexCoord3iARB = NULL;\nPFNGLMULTITEXCOORD3IVARBPROC glad_glMultiTexCoord3ivARB = NULL;\nPFNGLMULTITEXCOORD3SARBPROC glad_glMultiTexCoord3sARB = NULL;\nPFNGLMULTITEXCOORD3SVARBPROC glad_glMultiTexCoord3svARB = NULL;\nPFNGLMULTITEXCOORD4DARBPROC glad_glMultiTexCoord4dARB = NULL;\nPFNGLMULTITEXCOORD4DVARBPROC glad_glMultiTexCoord4dvARB = NULL;\nPFNGLMULTITEXCOORD4FARBPROC glad_glMultiTexCoord4fARB = NULL;\nPFNGLMULTITEXCOORD4FVARBPROC glad_glMultiTexCoord4fvARB = NULL;\nPFNGLMULTITEXCOORD4IARBPROC glad_glMultiTexCoord4iARB = NULL;\nPFNGLMULTITEXCOORD4IVARBPROC glad_glMultiTexCoord4ivARB = NULL;\nPFNGLMULTITEXCOORD4SARBPROC glad_glMultiTexCoord4sARB = NULL;\nPFNGLMULTITEXCOORD4SVARBPROC glad_glMultiTexCoord4svARB = NULL;\nPFNGLGENQUERIESARBPROC glad_glGenQueriesARB = NULL;\nPFNGLDELETEQUERIESARBPROC glad_glDeleteQueriesARB = NULL;\nPFNGLISQUERYARBPROC glad_glIsQueryARB = NULL;\nPFNGLBEGINQUERYARBPROC glad_glBeginQueryARB = NULL;\nPFNGLENDQUERYARBPROC glad_glEndQueryARB = NULL;\nPFNGLGETQUERYIVARBPROC glad_glGetQueryivARB = NULL;\nPFNGLGETQUERYOBJECTIVARBPROC glad_glGetQueryObjectivARB = NULL;\nPFNGLGETQUERYOBJECTUIVARBPROC glad_glGetQueryObjectuivARB = NULL;\nPFNGLMAXSHADERCOMPILERTHREADSARBPROC glad_glMaxShaderCompilerThreadsARB = NULL;\nPFNGLPOINTPARAMETERFARBPROC glad_glPointParameterfARB = NULL;\nPFNGLPOINTPARAMETERFVARBPROC glad_glPointParameterfvARB = NULL;\nPFNGLPOLYGONOFFSETCLAMPPROC glad_glPolygonOffsetClamp = NULL;\nPFNGLGETPROGRAMINTERFACEIVPROC glad_glGetProgramInterfaceiv = NULL;\nPFNGLGETPROGRAMRESOURCEINDEXPROC glad_glGetProgramResourceIndex = NULL;\nPFNGLGETPROGRAMRESOURCENAMEPROC glad_glGetProgramResourceName = NULL;\nPFNGLGETPROGRAMRESOURCEIVPROC glad_glGetProgramResourceiv = NULL;\nPFNGLGETPROGRAMRESOURCELOCATIONPROC glad_glGetProgramResourceLocation = NULL;\nPFNGLGETPROGRAMRESOURCELOCATIONINDEXPROC glad_glGetProgramResourceLocationIndex = NULL;\nPFNGLGETGRAPHICSRESETSTATUSARBPROC glad_glGetGraphicsResetStatusARB = NULL;\nPFNGLGETNTEXIMAGEARBPROC glad_glGetnTexImageARB = NULL;\nPFNGLREADNPIXELSARBPROC glad_glReadnPixelsARB = NULL;\nPFNGLGETNCOMPRESSEDTEXIMAGEARBPROC glad_glGetnCompressedTexImageARB = NULL;\nPFNGLGETNUNIFORMFVARBPROC glad_glGetnUniformfvARB = NULL;\nPFNGLGETNUNIFORMIVARBPROC glad_glGetnUniformivARB = NULL;\nPFNGLGETNUNIFORMUIVARBPROC glad_glGetnUniformuivARB = NULL;\nPFNGLGETNUNIFORMDVARBPROC glad_glGetnUniformdvARB = NULL;\nPFNGLGETNMAPDVARBPROC glad_glGetnMapdvARB = NULL;\nPFNGLGETNMAPFVARBPROC glad_glGetnMapfvARB = NULL;\nPFNGLGETNMAPIVARBPROC glad_glGetnMapivARB = NULL;\nPFNGLGETNPIXELMAPFVARBPROC glad_glGetnPixelMapfvARB = NULL;\nPFNGLGETNPIXELMAPUIVARBPROC glad_glGetnPixelMapuivARB = NULL;\nPFNGLGETNPIXELMAPUSVARBPROC glad_glGetnPixelMapusvARB = NULL;\nPFNGLGETNPOLYGONSTIPPLEARBPROC glad_glGetnPolygonStippleARB = NULL;\nPFNGLGETNCOLORTABLEARBPROC glad_glGetnColorTableARB = NULL;\nPFNGLGETNCONVOLUTIONFILTERARBPROC glad_glGetnConvolutionFilterARB = NULL;\nPFNGLGETNSEPARABLEFILTERARBPROC glad_glGetnSeparableFilterARB = NULL;\nPFNGLGETNHISTOGRAMARBPROC glad_glGetnHistogramARB = NULL;\nPFNGLGETNMINMAXARBPROC glad_glGetnMinmaxARB = NULL;\nPFNGLFRAMEBUFFERSAMPLELOCATIONSFVARBPROC glad_glFramebufferSampleLocationsfvARB = NULL;\nPFNGLNAMEDFRAMEBUFFERSAMPLELOCATIONSFVARBPROC glad_glNamedFramebufferSampleLocationsfvARB = NULL;\nPFNGLEVALUATEDEPTHVALUESARBPROC glad_glEvaluateDepthValuesARB = NULL;\nPFNGLMINSAMPLESHADINGARBPROC glad_glMinSampleShadingARB = NULL;\nPFNGLUSEPROGRAMSTAGESPROC glad_glUseProgramStages = NULL;\nPFNGLACTIVESHADERPROGRAMPROC glad_glActiveShaderProgram = NULL;\nPFNGLCREATESHADERPROGRAMVPROC glad_glCreateShaderProgramv = NULL;\nPFNGLBINDPROGRAMPIPELINEPROC glad_glBindProgramPipeline = NULL;\nPFNGLDELETEPROGRAMPIPELINESPROC glad_glDeleteProgramPipelines = NULL;\nPFNGLGENPROGRAMPIPELINESPROC glad_glGenProgramPipelines = NULL;\nPFNGLISPROGRAMPIPELINEPROC glad_glIsProgramPipeline = NULL;\nPFNGLGETPROGRAMPIPELINEIVPROC glad_glGetProgramPipelineiv = NULL;\nPFNGLPROGRAMUNIFORM1IPROC glad_glProgramUniform1i = NULL;\nPFNGLPROGRAMUNIFORM1IVPROC glad_glProgramUniform1iv = NULL;\nPFNGLPROGRAMUNIFORM1FPROC glad_glProgramUniform1f = NULL;\nPFNGLPROGRAMUNIFORM1FVPROC glad_glProgramUniform1fv = NULL;\nPFNGLPROGRAMUNIFORM1DPROC glad_glProgramUniform1d = NULL;\nPFNGLPROGRAMUNIFORM1DVPROC glad_glProgramUniform1dv = NULL;\nPFNGLPROGRAMUNIFORM1UIPROC glad_glProgramUniform1ui = NULL;\nPFNGLPROGRAMUNIFORM1UIVPROC glad_glProgramUniform1uiv = NULL;\nPFNGLPROGRAMUNIFORM2IPROC glad_glProgramUniform2i = NULL;\nPFNGLPROGRAMUNIFORM2IVPROC glad_glProgramUniform2iv = NULL;\nPFNGLPROGRAMUNIFORM2FPROC glad_glProgramUniform2f = NULL;\nPFNGLPROGRAMUNIFORM2FVPROC glad_glProgramUniform2fv = NULL;\nPFNGLPROGRAMUNIFORM2DPROC glad_glProgramUniform2d = NULL;\nPFNGLPROGRAMUNIFORM2DVPROC glad_glProgramUniform2dv = NULL;\nPFNGLPROGRAMUNIFORM2UIPROC glad_glProgramUniform2ui = NULL;\nPFNGLPROGRAMUNIFORM2UIVPROC glad_glProgramUniform2uiv = NULL;\nPFNGLPROGRAMUNIFORM3IPROC glad_glProgramUniform3i = NULL;\nPFNGLPROGRAMUNIFORM3IVPROC glad_glProgramUniform3iv = NULL;\nPFNGLPROGRAMUNIFORM3FPROC glad_glProgramUniform3f = NULL;\nPFNGLPROGRAMUNIFORM3FVPROC glad_glProgramUniform3fv = NULL;\nPFNGLPROGRAMUNIFORM3DPROC glad_glProgramUniform3d = NULL;\nPFNGLPROGRAMUNIFORM3DVPROC glad_glProgramUniform3dv = NULL;\nPFNGLPROGRAMUNIFORM3UIPROC glad_glProgramUniform3ui = NULL;\nPFNGLPROGRAMUNIFORM3UIVPROC glad_glProgramUniform3uiv = NULL;\nPFNGLPROGRAMUNIFORM4IPROC glad_glProgramUniform4i = NULL;\nPFNGLPROGRAMUNIFORM4IVPROC glad_glProgramUniform4iv = NULL;\nPFNGLPROGRAMUNIFORM4FPROC glad_glProgramUniform4f = NULL;\nPFNGLPROGRAMUNIFORM4FVPROC glad_glProgramUniform4fv = NULL;\nPFNGLPROGRAMUNIFORM4DPROC glad_glProgramUniform4d = NULL;\nPFNGLPROGRAMUNIFORM4DVPROC glad_glProgramUniform4dv = NULL;\nPFNGLPROGRAMUNIFORM4UIPROC glad_glProgramUniform4ui = NULL;\nPFNGLPROGRAMUNIFORM4UIVPROC glad_glProgramUniform4uiv = NULL;\nPFNGLPROGRAMUNIFORMMATRIX2FVPROC glad_glProgramUniformMatrix2fv = NULL;\nPFNGLPROGRAMUNIFORMMATRIX3FVPROC glad_glProgramUniformMatrix3fv = NULL;\nPFNGLPROGRAMUNIFORMMATRIX4FVPROC glad_glProgramUniformMatrix4fv = NULL;\nPFNGLPROGRAMUNIFORMMATRIX2DVPROC glad_glProgramUniformMatrix2dv = NULL;\nPFNGLPROGRAMUNIFORMMATRIX3DVPROC glad_glProgramUniformMatrix3dv = NULL;\nPFNGLPROGRAMUNIFORMMATRIX4DVPROC glad_glProgramUniformMatrix4dv = NULL;\nPFNGLPROGRAMUNIFORMMATRIX2X3FVPROC glad_glProgramUniformMatrix2x3fv = NULL;\nPFNGLPROGRAMUNIFORMMATRIX3X2FVPROC glad_glProgramUniformMatrix3x2fv = NULL;\nPFNGLPROGRAMUNIFORMMATRIX2X4FVPROC glad_glProgramUniformMatrix2x4fv = NULL;\nPFNGLPROGRAMUNIFORMMATRIX4X2FVPROC glad_glProgramUniformMatrix4x2fv = NULL;\nPFNGLPROGRAMUNIFORMMATRIX3X4FVPROC glad_glProgramUniformMatrix3x4fv = NULL;\nPFNGLPROGRAMUNIFORMMATRIX4X3FVPROC glad_glProgramUniformMatrix4x3fv = NULL;\nPFNGLPROGRAMUNIFORMMATRIX2X3DVPROC glad_glProgramUniformMatrix2x3dv = NULL;\nPFNGLPROGRAMUNIFORMMATRIX3X2DVPROC glad_glProgramUniformMatrix3x2dv = NULL;\nPFNGLPROGRAMUNIFORMMATRIX2X4DVPROC glad_glProgramUniformMatrix2x4dv = NULL;\nPFNGLPROGRAMUNIFORMMATRIX4X2DVPROC glad_glProgramUniformMatrix4x2dv = NULL;\nPFNGLPROGRAMUNIFORMMATRIX3X4DVPROC glad_glProgramUniformMatrix3x4dv = NULL;\nPFNGLPROGRAMUNIFORMMATRIX4X3DVPROC glad_glProgramUniformMatrix4x3dv = NULL;\nPFNGLVALIDATEPROGRAMPIPELINEPROC glad_glValidateProgramPipeline = NULL;\nPFNGLGETPROGRAMPIPELINEINFOLOGPROC glad_glGetProgramPipelineInfoLog = NULL;\nPFNGLGETACTIVEATOMICCOUNTERBUFFERIVPROC glad_glGetActiveAtomicCounterBufferiv = NULL;\nPFNGLBINDIMAGETEXTUREPROC glad_glBindImageTexture = NULL;\nPFNGLMEMORYBARRIERPROC glad_glMemoryBarrier = NULL;\nPFNGLDELETEOBJECTARBPROC glad_glDeleteObjectARB = NULL;\nPFNGLGETHANDLEARBPROC glad_glGetHandleARB = NULL;\nPFNGLDETACHOBJECTARBPROC glad_glDetachObjectARB = NULL;\nPFNGLCREATESHADEROBJECTARBPROC glad_glCreateShaderObjectARB = NULL;\nPFNGLSHADERSOURCEARBPROC glad_glShaderSourceARB = NULL;\nPFNGLCOMPILESHADERARBPROC glad_glCompileShaderARB = NULL;\nPFNGLCREATEPROGRAMOBJECTARBPROC glad_glCreateProgramObjectARB = NULL;\nPFNGLATTACHOBJECTARBPROC glad_glAttachObjectARB = NULL;\nPFNGLLINKPROGRAMARBPROC glad_glLinkProgramARB = NULL;\nPFNGLUSEPROGRAMOBJECTARBPROC glad_glUseProgramObjectARB = NULL;\nPFNGLVALIDATEPROGRAMARBPROC glad_glValidateProgramARB = NULL;\nPFNGLUNIFORM1FARBPROC glad_glUniform1fARB = NULL;\nPFNGLUNIFORM2FARBPROC glad_glUniform2fARB = NULL;\nPFNGLUNIFORM3FARBPROC glad_glUniform3fARB = NULL;\nPFNGLUNIFORM4FARBPROC glad_glUniform4fARB = NULL;\nPFNGLUNIFORM1IARBPROC glad_glUniform1iARB = NULL;\nPFNGLUNIFORM2IARBPROC glad_glUniform2iARB = NULL;\nPFNGLUNIFORM3IARBPROC glad_glUniform3iARB = NULL;\nPFNGLUNIFORM4IARBPROC glad_glUniform4iARB = NULL;\nPFNGLUNIFORM1FVARBPROC glad_glUniform1fvARB = NULL;\nPFNGLUNIFORM2FVARBPROC glad_glUniform2fvARB = NULL;\nPFNGLUNIFORM3FVARBPROC glad_glUniform3fvARB = NULL;\nPFNGLUNIFORM4FVARBPROC glad_glUniform4fvARB = NULL;\nPFNGLUNIFORM1IVARBPROC glad_glUniform1ivARB = NULL;\nPFNGLUNIFORM2IVARBPROC glad_glUniform2ivARB = NULL;\nPFNGLUNIFORM3IVARBPROC glad_glUniform3ivARB = NULL;\nPFNGLUNIFORM4IVARBPROC glad_glUniform4ivARB = NULL;\nPFNGLUNIFORMMATRIX2FVARBPROC glad_glUniformMatrix2fvARB = NULL;\nPFNGLUNIFORMMATRIX3FVARBPROC glad_glUniformMatrix3fvARB = NULL;\nPFNGLUNIFORMMATRIX4FVARBPROC glad_glUniformMatrix4fvARB = NULL;\nPFNGLGETOBJECTPARAMETERFVARBPROC glad_glGetObjectParameterfvARB = NULL;\nPFNGLGETOBJECTPARAMETERIVARBPROC glad_glGetObjectParameterivARB = NULL;\nPFNGLGETINFOLOGARBPROC glad_glGetInfoLogARB = NULL;\nPFNGLGETATTACHEDOBJECTSARBPROC glad_glGetAttachedObjectsARB = NULL;\nPFNGLGETUNIFORMLOCATIONARBPROC glad_glGetUniformLocationARB = NULL;\nPFNGLGETACTIVEUNIFORMARBPROC glad_glGetActiveUniformARB = NULL;\nPFNGLGETUNIFORMFVARBPROC glad_glGetUniformfvARB = NULL;\nPFNGLGETUNIFORMIVARBPROC glad_glGetUniformivARB = NULL;\nPFNGLGETSHADERSOURCEARBPROC glad_glGetShaderSourceARB = NULL;\nPFNGLSHADERSTORAGEBLOCKBINDINGPROC glad_glShaderStorageBlockBinding = NULL;\nPFNGLGETSUBROUTINEUNIFORMLOCATIONPROC glad_glGetSubroutineUniformLocation = NULL;\nPFNGLGETSUBROUTINEINDEXPROC glad_glGetSubroutineIndex = NULL;\nPFNGLGETACTIVESUBROUTINEUNIFORMIVPROC glad_glGetActiveSubroutineUniformiv = NULL;\nPFNGLGETACTIVESUBROUTINEUNIFORMNAMEPROC glad_glGetActiveSubroutineUniformName = NULL;\nPFNGLGETACTIVESUBROUTINENAMEPROC glad_glGetActiveSubroutineName = NULL;\nPFNGLUNIFORMSUBROUTINESUIVPROC glad_glUniformSubroutinesuiv = NULL;\nPFNGLGETUNIFORMSUBROUTINEUIVPROC glad_glGetUniformSubroutineuiv = NULL;\nPFNGLGETPROGRAMSTAGEIVPROC glad_glGetProgramStageiv = NULL;\nPFNGLNAMEDSTRINGARBPROC glad_glNamedStringARB = NULL;\nPFNGLDELETENAMEDSTRINGARBPROC glad_glDeleteNamedStringARB = NULL;\nPFNGLCOMPILESHADERINCLUDEARBPROC glad_glCompileShaderIncludeARB = NULL;\nPFNGLISNAMEDSTRINGARBPROC glad_glIsNamedStringARB = NULL;\nPFNGLGETNAMEDSTRINGARBPROC glad_glGetNamedStringARB = NULL;\nPFNGLGETNAMEDSTRINGIVARBPROC glad_glGetNamedStringivARB = NULL;\nPFNGLBUFFERPAGECOMMITMENTARBPROC glad_glBufferPageCommitmentARB = NULL;\nPFNGLNAMEDBUFFERPAGECOMMITMENTEXTPROC glad_glNamedBufferPageCommitmentEXT = NULL;\nPFNGLNAMEDBUFFERPAGECOMMITMENTARBPROC glad_glNamedBufferPageCommitmentARB = NULL;\nPFNGLTEXPAGECOMMITMENTARBPROC glad_glTexPageCommitmentARB = NULL;\nPFNGLPATCHPARAMETERIPROC glad_glPatchParameteri = NULL;\nPFNGLPATCHPARAMETERFVPROC glad_glPatchParameterfv = NULL;\nPFNGLTEXTUREBARRIERPROC glad_glTextureBarrier = NULL;\nPFNGLTEXBUFFERARBPROC glad_glTexBufferARB = NULL;\nPFNGLTEXBUFFERRANGEPROC glad_glTexBufferRange = NULL;\nPFNGLCOMPRESSEDTEXIMAGE3DARBPROC glad_glCompressedTexImage3DARB = NULL;\nPFNGLCOMPRESSEDTEXIMAGE2DARBPROC glad_glCompressedTexImage2DARB = NULL;\nPFNGLCOMPRESSEDTEXIMAGE1DARBPROC glad_glCompressedTexImage1DARB = NULL;\nPFNGLCOMPRESSEDTEXSUBIMAGE3DARBPROC glad_glCompressedTexSubImage3DARB = NULL;\nPFNGLCOMPRESSEDTEXSUBIMAGE2DARBPROC glad_glCompressedTexSubImage2DARB = NULL;\nPFNGLCOMPRESSEDTEXSUBIMAGE1DARBPROC glad_glCompressedTexSubImage1DARB = NULL;\nPFNGLGETCOMPRESSEDTEXIMAGEARBPROC glad_glGetCompressedTexImageARB = NULL;\nPFNGLTEXSTORAGE1DPROC glad_glTexStorage1D = NULL;\nPFNGLTEXSTORAGE2DMULTISAMPLEPROC glad_glTexStorage2DMultisample = NULL;\nPFNGLTEXSTORAGE3DMULTISAMPLEPROC glad_glTexStorage3DMultisample = NULL;\nPFNGLTEXTUREVIEWPROC glad_glTextureView = NULL;\nPFNGLDRAWTRANSFORMFEEDBACKPROC glad_glDrawTransformFeedback = NULL;\nPFNGLDRAWTRANSFORMFEEDBACKSTREAMPROC glad_glDrawTransformFeedbackStream = NULL;\nPFNGLBEGINQUERYINDEXEDPROC glad_glBeginQueryIndexed = NULL;\nPFNGLENDQUERYINDEXEDPROC glad_glEndQueryIndexed = NULL;\nPFNGLGETQUERYINDEXEDIVPROC glad_glGetQueryIndexediv = NULL;\nPFNGLDRAWTRANSFORMFEEDBACKINSTANCEDPROC glad_glDrawTransformFeedbackInstanced = NULL;\nPFNGLDRAWTRANSFORMFEEDBACKSTREAMINSTANCEDPROC glad_glDrawTransformFeedbackStreamInstanced = NULL;\nPFNGLLOADTRANSPOSEMATRIXFARBPROC glad_glLoadTransposeMatrixfARB = NULL;\nPFNGLLOADTRANSPOSEMATRIXDARBPROC glad_glLoadTransposeMatrixdARB = NULL;\nPFNGLMULTTRANSPOSEMATRIXFARBPROC glad_glMultTransposeMatrixfARB = NULL;\nPFNGLMULTTRANSPOSEMATRIXDARBPROC glad_glMultTransposeMatrixdARB = NULL;\nPFNGLVERTEXATTRIBL1DPROC glad_glVertexAttribL1d = NULL;\nPFNGLVERTEXATTRIBL2DPROC glad_glVertexAttribL2d = NULL;\nPFNGLVERTEXATTRIBL3DPROC glad_glVertexAttribL3d = NULL;\nPFNGLVERTEXATTRIBL4DPROC glad_glVertexAttribL4d = NULL;\nPFNGLVERTEXATTRIBL1DVPROC glad_glVertexAttribL1dv = NULL;\nPFNGLVERTEXATTRIBL2DVPROC glad_glVertexAttribL2dv = NULL;\nPFNGLVERTEXATTRIBL3DVPROC glad_glVertexAttribL3dv = NULL;\nPFNGLVERTEXATTRIBL4DVPROC glad_glVertexAttribL4dv = NULL;\nPFNGLVERTEXATTRIBLPOINTERPROC glad_glVertexAttribLPointer = NULL;\nPFNGLGETVERTEXATTRIBLDVPROC glad_glGetVertexAttribLdv = NULL;\nPFNGLBINDVERTEXBUFFERPROC glad_glBindVertexBuffer = NULL;\nPFNGLVERTEXATTRIBFORMATPROC glad_glVertexAttribFormat = NULL;\nPFNGLVERTEXATTRIBIFORMATPROC glad_glVertexAttribIFormat = NULL;\nPFNGLVERTEXATTRIBLFORMATPROC glad_glVertexAttribLFormat = NULL;\nPFNGLVERTEXATTRIBBINDINGPROC glad_glVertexAttribBinding = NULL;\nPFNGLVERTEXBINDINGDIVISORPROC glad_glVertexBindingDivisor = NULL;\nPFNGLWEIGHTBVARBPROC glad_glWeightbvARB = NULL;\nPFNGLWEIGHTSVARBPROC glad_glWeightsvARB = NULL;\nPFNGLWEIGHTIVARBPROC glad_glWeightivARB = NULL;\nPFNGLWEIGHTFVARBPROC glad_glWeightfvARB = NULL;\nPFNGLWEIGHTDVARBPROC glad_glWeightdvARB = NULL;\nPFNGLWEIGHTUBVARBPROC glad_glWeightubvARB = NULL;\nPFNGLWEIGHTUSVARBPROC glad_glWeightusvARB = NULL;\nPFNGLWEIGHTUIVARBPROC glad_glWeightuivARB = NULL;\nPFNGLWEIGHTPOINTERARBPROC glad_glWeightPointerARB = NULL;\nPFNGLVERTEXBLENDARBPROC glad_glVertexBlendARB = NULL;\nPFNGLBINDBUFFERARBPROC glad_glBindBufferARB = NULL;\nPFNGLDELETEBUFFERSARBPROC glad_glDeleteBuffersARB = NULL;\nPFNGLGENBUFFERSARBPROC glad_glGenBuffersARB = NULL;\nPFNGLISBUFFERARBPROC glad_glIsBufferARB = NULL;\nPFNGLBUFFERDATAARBPROC glad_glBufferDataARB = NULL;\nPFNGLBUFFERSUBDATAARBPROC glad_glBufferSubDataARB = NULL;\nPFNGLGETBUFFERSUBDATAARBPROC glad_glGetBufferSubDataARB = NULL;\nPFNGLMAPBUFFERARBPROC glad_glMapBufferARB = NULL;\nPFNGLUNMAPBUFFERARBPROC glad_glUnmapBufferARB = NULL;\nPFNGLGETBUFFERPARAMETERIVARBPROC glad_glGetBufferParameterivARB = NULL;\nPFNGLGETBUFFERPOINTERVARBPROC glad_glGetBufferPointervARB = NULL;\nPFNGLVERTEXATTRIB1DARBPROC glad_glVertexAttrib1dARB = NULL;\nPFNGLVERTEXATTRIB1DVARBPROC glad_glVertexAttrib1dvARB = NULL;\nPFNGLVERTEXATTRIB1FARBPROC glad_glVertexAttrib1fARB = NULL;\nPFNGLVERTEXATTRIB1FVARBPROC glad_glVertexAttrib1fvARB = NULL;\nPFNGLVERTEXATTRIB1SARBPROC glad_glVertexAttrib1sARB = NULL;\nPFNGLVERTEXATTRIB1SVARBPROC glad_glVertexAttrib1svARB = NULL;\nPFNGLVERTEXATTRIB2DARBPROC glad_glVertexAttrib2dARB = NULL;\nPFNGLVERTEXATTRIB2DVARBPROC glad_glVertexAttrib2dvARB = NULL;\nPFNGLVERTEXATTRIB2FARBPROC glad_glVertexAttrib2fARB = NULL;\nPFNGLVERTEXATTRIB2FVARBPROC glad_glVertexAttrib2fvARB = NULL;\nPFNGLVERTEXATTRIB2SARBPROC glad_glVertexAttrib2sARB = NULL;\nPFNGLVERTEXATTRIB2SVARBPROC glad_glVertexAttrib2svARB = NULL;\nPFNGLVERTEXATTRIB3DARBPROC glad_glVertexAttrib3dARB = NULL;\nPFNGLVERTEXATTRIB3DVARBPROC glad_glVertexAttrib3dvARB = NULL;\nPFNGLVERTEXATTRIB3FARBPROC glad_glVertexAttrib3fARB = NULL;\nPFNGLVERTEXATTRIB3FVARBPROC glad_glVertexAttrib3fvARB = NULL;\nPFNGLVERTEXATTRIB3SARBPROC glad_glVertexAttrib3sARB = NULL;\nPFNGLVERTEXATTRIB3SVARBPROC glad_glVertexAttrib3svARB = NULL;\nPFNGLVERTEXATTRIB4NBVARBPROC glad_glVertexAttrib4NbvARB = NULL;\nPFNGLVERTEXATTRIB4NIVARBPROC glad_glVertexAttrib4NivARB = NULL;\nPFNGLVERTEXATTRIB4NSVARBPROC glad_glVertexAttrib4NsvARB = NULL;\nPFNGLVERTEXATTRIB4NUBARBPROC glad_glVertexAttrib4NubARB = NULL;\nPFNGLVERTEXATTRIB4NUBVARBPROC glad_glVertexAttrib4NubvARB = NULL;\nPFNGLVERTEXATTRIB4NUIVARBPROC glad_glVertexAttrib4NuivARB = NULL;\nPFNGLVERTEXATTRIB4NUSVARBPROC glad_glVertexAttrib4NusvARB = NULL;\nPFNGLVERTEXATTRIB4BVARBPROC glad_glVertexAttrib4bvARB = NULL;\nPFNGLVERTEXATTRIB4DARBPROC glad_glVertexAttrib4dARB = NULL;\nPFNGLVERTEXATTRIB4DVARBPROC glad_glVertexAttrib4dvARB = NULL;\nPFNGLVERTEXATTRIB4FARBPROC glad_glVertexAttrib4fARB = NULL;\nPFNGLVERTEXATTRIB4FVARBPROC glad_glVertexAttrib4fvARB = NULL;\nPFNGLVERTEXATTRIB4IVARBPROC glad_glVertexAttrib4ivARB = NULL;\nPFNGLVERTEXATTRIB4SARBPROC glad_glVertexAttrib4sARB = NULL;\nPFNGLVERTEXATTRIB4SVARBPROC glad_glVertexAttrib4svARB = NULL;\nPFNGLVERTEXATTRIB4UBVARBPROC glad_glVertexAttrib4ubvARB = NULL;\nPFNGLVERTEXATTRIB4UIVARBPROC glad_glVertexAttrib4uivARB = NULL;\nPFNGLVERTEXATTRIB4USVARBPROC glad_glVertexAttrib4usvARB = NULL;\nPFNGLVERTEXATTRIBPOINTERARBPROC glad_glVertexAttribPointerARB = NULL;\nPFNGLENABLEVERTEXATTRIBARRAYARBPROC glad_glEnableVertexAttribArrayARB = NULL;\nPFNGLDISABLEVERTEXATTRIBARRAYARBPROC glad_glDisableVertexAttribArrayARB = NULL;\nPFNGLGETVERTEXATTRIBDVARBPROC glad_glGetVertexAttribdvARB = NULL;\nPFNGLGETVERTEXATTRIBFVARBPROC glad_glGetVertexAttribfvARB = NULL;\nPFNGLGETVERTEXATTRIBIVARBPROC glad_glGetVertexAttribivARB = NULL;\nPFNGLGETVERTEXATTRIBPOINTERVARBPROC glad_glGetVertexAttribPointervARB = NULL;\nPFNGLBINDATTRIBLOCATIONARBPROC glad_glBindAttribLocationARB = NULL;\nPFNGLGETACTIVEATTRIBARBPROC glad_glGetActiveAttribARB = NULL;\nPFNGLGETATTRIBLOCATIONARBPROC glad_glGetAttribLocationARB = NULL;\nPFNGLVIEWPORTARRAYVPROC glad_glViewportArrayv = NULL;\nPFNGLVIEWPORTINDEXEDFPROC glad_glViewportIndexedf = NULL;\nPFNGLVIEWPORTINDEXEDFVPROC glad_glViewportIndexedfv = NULL;\nPFNGLSCISSORARRAYVPROC glad_glScissorArrayv = NULL;\nPFNGLSCISSORINDEXEDPROC glad_glScissorIndexed = NULL;\nPFNGLSCISSORINDEXEDVPROC glad_glScissorIndexedv = NULL;\nPFNGLDEPTHRANGEARRAYVPROC glad_glDepthRangeArrayv = NULL;\nPFNGLDEPTHRANGEINDEXEDPROC glad_glDepthRangeIndexed = NULL;\nPFNGLGETFLOATI_VPROC glad_glGetFloati_v = NULL;\nPFNGLGETDOUBLEI_VPROC glad_glGetDoublei_v = NULL;\nPFNGLWINDOWPOS2DARBPROC glad_glWindowPos2dARB = NULL;\nPFNGLWINDOWPOS2DVARBPROC glad_glWindowPos2dvARB = NULL;\nPFNGLWINDOWPOS2FARBPROC glad_glWindowPos2fARB = NULL;\nPFNGLWINDOWPOS2FVARBPROC glad_glWindowPos2fvARB = NULL;\nPFNGLWINDOWPOS2IARBPROC glad_glWindowPos2iARB = NULL;\nPFNGLWINDOWPOS2IVARBPROC glad_glWindowPos2ivARB = NULL;\nPFNGLWINDOWPOS2SARBPROC glad_glWindowPos2sARB = NULL;\nPFNGLWINDOWPOS2SVARBPROC glad_glWindowPos2svARB = NULL;\nPFNGLWINDOWPOS3DARBPROC glad_glWindowPos3dARB = NULL;\nPFNGLWINDOWPOS3DVARBPROC glad_glWindowPos3dvARB = NULL;\nPFNGLWINDOWPOS3FARBPROC glad_glWindowPos3fARB = NULL;\nPFNGLWINDOWPOS3FVARBPROC glad_glWindowPos3fvARB = NULL;\nPFNGLWINDOWPOS3IARBPROC glad_glWindowPos3iARB = NULL;\nPFNGLWINDOWPOS3IVARBPROC glad_glWindowPos3ivARB = NULL;\nPFNGLWINDOWPOS3SARBPROC glad_glWindowPos3sARB = NULL;\nPFNGLWINDOWPOS3SVARBPROC glad_glWindowPos3svARB = NULL;\nPFNGLDRAWBUFFERSATIPROC glad_glDrawBuffersATI = NULL;\nPFNGLELEMENTPOINTERATIPROC glad_glElementPointerATI = NULL;\nPFNGLDRAWELEMENTARRAYATIPROC glad_glDrawElementArrayATI = NULL;\nPFNGLDRAWRANGEELEMENTARRAYATIPROC glad_glDrawRangeElementArrayATI = NULL;\nPFNGLTEXBUMPPARAMETERIVATIPROC glad_glTexBumpParameterivATI = NULL;\nPFNGLTEXBUMPPARAMETERFVATIPROC glad_glTexBumpParameterfvATI = NULL;\nPFNGLGETTEXBUMPPARAMETERIVATIPROC glad_glGetTexBumpParameterivATI = NULL;\nPFNGLGETTEXBUMPPARAMETERFVATIPROC glad_glGetTexBumpParameterfvATI = NULL;\nPFNGLGENFRAGMENTSHADERSATIPROC glad_glGenFragmentShadersATI = NULL;\nPFNGLBINDFRAGMENTSHADERATIPROC glad_glBindFragmentShaderATI = NULL;\nPFNGLDELETEFRAGMENTSHADERATIPROC glad_glDeleteFragmentShaderATI = NULL;\nPFNGLBEGINFRAGMENTSHADERATIPROC glad_glBeginFragmentShaderATI = NULL;\nPFNGLENDFRAGMENTSHADERATIPROC glad_glEndFragmentShaderATI = NULL;\nPFNGLPASSTEXCOORDATIPROC glad_glPassTexCoordATI = NULL;\nPFNGLSAMPLEMAPATIPROC glad_glSampleMapATI = NULL;\nPFNGLCOLORFRAGMENTOP1ATIPROC glad_glColorFragmentOp1ATI = NULL;\nPFNGLCOLORFRAGMENTOP2ATIPROC glad_glColorFragmentOp2ATI = NULL;\nPFNGLCOLORFRAGMENTOP3ATIPROC glad_glColorFragmentOp3ATI = NULL;\nPFNGLALPHAFRAGMENTOP1ATIPROC glad_glAlphaFragmentOp1ATI = NULL;\nPFNGLALPHAFRAGMENTOP2ATIPROC glad_glAlphaFragmentOp2ATI = NULL;\nPFNGLALPHAFRAGMENTOP3ATIPROC glad_glAlphaFragmentOp3ATI = NULL;\nPFNGLSETFRAGMENTSHADERCONSTANTATIPROC glad_glSetFragmentShaderConstantATI = NULL;\nPFNGLMAPOBJECTBUFFERATIPROC glad_glMapObjectBufferATI = NULL;\nPFNGLUNMAPOBJECTBUFFERATIPROC glad_glUnmapObjectBufferATI = NULL;\nPFNGLPNTRIANGLESIATIPROC glad_glPNTrianglesiATI = NULL;\nPFNGLPNTRIANGLESFATIPROC glad_glPNTrianglesfATI = NULL;\nPFNGLSTENCILOPSEPARATEATIPROC glad_glStencilOpSeparateATI = NULL;\nPFNGLSTENCILFUNCSEPARATEATIPROC glad_glStencilFuncSeparateATI = NULL;\nPFNGLNEWOBJECTBUFFERATIPROC glad_glNewObjectBufferATI = NULL;\nPFNGLISOBJECTBUFFERATIPROC glad_glIsObjectBufferATI = NULL;\nPFNGLUPDATEOBJECTBUFFERATIPROC glad_glUpdateObjectBufferATI = NULL;\nPFNGLGETOBJECTBUFFERFVATIPROC glad_glGetObjectBufferfvATI = NULL;\nPFNGLGETOBJECTBUFFERIVATIPROC glad_glGetObjectBufferivATI = NULL;\nPFNGLFREEOBJECTBUFFERATIPROC glad_glFreeObjectBufferATI = NULL;\nPFNGLARRAYOBJECTATIPROC glad_glArrayObjectATI = NULL;\nPFNGLGETARRAYOBJECTFVATIPROC glad_glGetArrayObjectfvATI = NULL;\nPFNGLGETARRAYOBJECTIVATIPROC glad_glGetArrayObjectivATI = NULL;\nPFNGLVARIANTARRAYOBJECTATIPROC glad_glVariantArrayObjectATI = NULL;\nPFNGLGETVARIANTARRAYOBJECTFVATIPROC glad_glGetVariantArrayObjectfvATI = NULL;\nPFNGLGETVARIANTARRAYOBJECTIVATIPROC glad_glGetVariantArrayObjectivATI = NULL;\nPFNGLVERTEXATTRIBARRAYOBJECTATIPROC glad_glVertexAttribArrayObjectATI = NULL;\nPFNGLGETVERTEXATTRIBARRAYOBJECTFVATIPROC glad_glGetVertexAttribArrayObjectfvATI = NULL;\nPFNGLGETVERTEXATTRIBARRAYOBJECTIVATIPROC glad_glGetVertexAttribArrayObjectivATI = NULL;\nPFNGLVERTEXSTREAM1SATIPROC glad_glVertexStream1sATI = NULL;\nPFNGLVERTEXSTREAM1SVATIPROC glad_glVertexStream1svATI = NULL;\nPFNGLVERTEXSTREAM1IATIPROC glad_glVertexStream1iATI = NULL;\nPFNGLVERTEXSTREAM1IVATIPROC glad_glVertexStream1ivATI = NULL;\nPFNGLVERTEXSTREAM1FATIPROC glad_glVertexStream1fATI = NULL;\nPFNGLVERTEXSTREAM1FVATIPROC glad_glVertexStream1fvATI = NULL;\nPFNGLVERTEXSTREAM1DATIPROC glad_glVertexStream1dATI = NULL;\nPFNGLVERTEXSTREAM1DVATIPROC glad_glVertexStream1dvATI = NULL;\nPFNGLVERTEXSTREAM2SATIPROC glad_glVertexStream2sATI = NULL;\nPFNGLVERTEXSTREAM2SVATIPROC glad_glVertexStream2svATI = NULL;\nPFNGLVERTEXSTREAM2IATIPROC glad_glVertexStream2iATI = NULL;\nPFNGLVERTEXSTREAM2IVATIPROC glad_glVertexStream2ivATI = NULL;\nPFNGLVERTEXSTREAM2FATIPROC glad_glVertexStream2fATI = NULL;\nPFNGLVERTEXSTREAM2FVATIPROC glad_glVertexStream2fvATI = NULL;\nPFNGLVERTEXSTREAM2DATIPROC glad_glVertexStream2dATI = NULL;\nPFNGLVERTEXSTREAM2DVATIPROC glad_glVertexStream2dvATI = NULL;\nPFNGLVERTEXSTREAM3SATIPROC glad_glVertexStream3sATI = NULL;\nPFNGLVERTEXSTREAM3SVATIPROC glad_glVertexStream3svATI = NULL;\nPFNGLVERTEXSTREAM3IATIPROC glad_glVertexStream3iATI = NULL;\nPFNGLVERTEXSTREAM3IVATIPROC glad_glVertexStream3ivATI = NULL;\nPFNGLVERTEXSTREAM3FATIPROC glad_glVertexStream3fATI = NULL;\nPFNGLVERTEXSTREAM3FVATIPROC glad_glVertexStream3fvATI = NULL;\nPFNGLVERTEXSTREAM3DATIPROC glad_glVertexStream3dATI = NULL;\nPFNGLVERTEXSTREAM3DVATIPROC glad_glVertexStream3dvATI = NULL;\nPFNGLVERTEXSTREAM4SATIPROC glad_glVertexStream4sATI = NULL;\nPFNGLVERTEXSTREAM4SVATIPROC glad_glVertexStream4svATI = NULL;\nPFNGLVERTEXSTREAM4IATIPROC glad_glVertexStream4iATI = NULL;\nPFNGLVERTEXSTREAM4IVATIPROC glad_glVertexStream4ivATI = NULL;\nPFNGLVERTEXSTREAM4FATIPROC glad_glVertexStream4fATI = NULL;\nPFNGLVERTEXSTREAM4FVATIPROC glad_glVertexStream4fvATI = NULL;\nPFNGLVERTEXSTREAM4DATIPROC glad_glVertexStream4dATI = NULL;\nPFNGLVERTEXSTREAM4DVATIPROC glad_glVertexStream4dvATI = NULL;\nPFNGLNORMALSTREAM3BATIPROC glad_glNormalStream3bATI = NULL;\nPFNGLNORMALSTREAM3BVATIPROC glad_glNormalStream3bvATI = NULL;\nPFNGLNORMALSTREAM3SATIPROC glad_glNormalStream3sATI = NULL;\nPFNGLNORMALSTREAM3SVATIPROC glad_glNormalStream3svATI = NULL;\nPFNGLNORMALSTREAM3IATIPROC glad_glNormalStream3iATI = NULL;\nPFNGLNORMALSTREAM3IVATIPROC glad_glNormalStream3ivATI = NULL;\nPFNGLNORMALSTREAM3FATIPROC glad_glNormalStream3fATI = NULL;\nPFNGLNORMALSTREAM3FVATIPROC glad_glNormalStream3fvATI = NULL;\nPFNGLNORMALSTREAM3DATIPROC glad_glNormalStream3dATI = NULL;\nPFNGLNORMALSTREAM3DVATIPROC glad_glNormalStream3dvATI = NULL;\nPFNGLCLIENTACTIVEVERTEXSTREAMATIPROC glad_glClientActiveVertexStreamATI = NULL;\nPFNGLVERTEXBLENDENVIATIPROC glad_glVertexBlendEnviATI = NULL;\nPFNGLVERTEXBLENDENVFATIPROC glad_glVertexBlendEnvfATI = NULL;\nPFNGLEGLIMAGETARGETTEXSTORAGEEXTPROC glad_glEGLImageTargetTexStorageEXT = NULL;\nPFNGLEGLIMAGETARGETTEXTURESTORAGEEXTPROC glad_glEGLImageTargetTextureStorageEXT = NULL;\nPFNGLUNIFORMBUFFEREXTPROC glad_glUniformBufferEXT = NULL;\nPFNGLGETUNIFORMBUFFERSIZEEXTPROC glad_glGetUniformBufferSizeEXT = NULL;\nPFNGLGETUNIFORMOFFSETEXTPROC glad_glGetUniformOffsetEXT = NULL;\nPFNGLBLENDCOLOREXTPROC glad_glBlendColorEXT = NULL;\nPFNGLBLENDEQUATIONSEPARATEEXTPROC glad_glBlendEquationSeparateEXT = NULL;\nPFNGLBLENDFUNCSEPARATEEXTPROC glad_glBlendFuncSeparateEXT = NULL;\nPFNGLBLENDEQUATIONEXTPROC glad_glBlendEquationEXT = NULL;\nPFNGLCOLORSUBTABLEEXTPROC glad_glColorSubTableEXT = NULL;\nPFNGLCOPYCOLORSUBTABLEEXTPROC glad_glCopyColorSubTableEXT = NULL;\nPFNGLLOCKARRAYSEXTPROC glad_glLockArraysEXT = NULL;\nPFNGLUNLOCKARRAYSEXTPROC glad_glUnlockArraysEXT = NULL;\nPFNGLCONVOLUTIONFILTER1DEXTPROC glad_glConvolutionFilter1DEXT = NULL;\nPFNGLCONVOLUTIONFILTER2DEXTPROC glad_glConvolutionFilter2DEXT = NULL;\nPFNGLCONVOLUTIONPARAMETERFEXTPROC glad_glConvolutionParameterfEXT = NULL;\nPFNGLCONVOLUTIONPARAMETERFVEXTPROC glad_glConvolutionParameterfvEXT = NULL;\nPFNGLCONVOLUTIONPARAMETERIEXTPROC glad_glConvolutionParameteriEXT = NULL;\nPFNGLCONVOLUTIONPARAMETERIVEXTPROC glad_glConvolutionParameterivEXT = NULL;\nPFNGLCOPYCONVOLUTIONFILTER1DEXTPROC glad_glCopyConvolutionFilter1DEXT = NULL;\nPFNGLCOPYCONVOLUTIONFILTER2DEXTPROC glad_glCopyConvolutionFilter2DEXT = NULL;\nPFNGLGETCONVOLUTIONFILTEREXTPROC glad_glGetConvolutionFilterEXT = NULL;\nPFNGLGETCONVOLUTIONPARAMETERFVEXTPROC glad_glGetConvolutionParameterfvEXT = NULL;\nPFNGLGETCONVOLUTIONPARAMETERIVEXTPROC glad_glGetConvolutionParameterivEXT = NULL;\nPFNGLGETSEPARABLEFILTEREXTPROC glad_glGetSeparableFilterEXT = NULL;\nPFNGLSEPARABLEFILTER2DEXTPROC glad_glSeparableFilter2DEXT = NULL;\nPFNGLTANGENT3BEXTPROC glad_glTangent3bEXT = NULL;\nPFNGLTANGENT3BVEXTPROC glad_glTangent3bvEXT = NULL;\nPFNGLTANGENT3DEXTPROC glad_glTangent3dEXT = NULL;\nPFNGLTANGENT3DVEXTPROC glad_glTangent3dvEXT = NULL;\nPFNGLTANGENT3FEXTPROC glad_glTangent3fEXT = NULL;\nPFNGLTANGENT3FVEXTPROC glad_glTangent3fvEXT = NULL;\nPFNGLTANGENT3IEXTPROC glad_glTangent3iEXT = NULL;\nPFNGLTANGENT3IVEXTPROC glad_glTangent3ivEXT = NULL;\nPFNGLTANGENT3SEXTPROC glad_glTangent3sEXT = NULL;\nPFNGLTANGENT3SVEXTPROC glad_glTangent3svEXT = NULL;\nPFNGLBINORMAL3BEXTPROC glad_glBinormal3bEXT = NULL;\nPFNGLBINORMAL3BVEXTPROC glad_glBinormal3bvEXT = NULL;\nPFNGLBINORMAL3DEXTPROC glad_glBinormal3dEXT = NULL;\nPFNGLBINORMAL3DVEXTPROC glad_glBinormal3dvEXT = NULL;\nPFNGLBINORMAL3FEXTPROC glad_glBinormal3fEXT = NULL;\nPFNGLBINORMAL3FVEXTPROC glad_glBinormal3fvEXT = NULL;\nPFNGLBINORMAL3IEXTPROC glad_glBinormal3iEXT = NULL;\nPFNGLBINORMAL3IVEXTPROC glad_glBinormal3ivEXT = NULL;\nPFNGLBINORMAL3SEXTPROC glad_glBinormal3sEXT = NULL;\nPFNGLBINORMAL3SVEXTPROC glad_glBinormal3svEXT = NULL;\nPFNGLTANGENTPOINTEREXTPROC glad_glTangentPointerEXT = NULL;\nPFNGLBINORMALPOINTEREXTPROC glad_glBinormalPointerEXT = NULL;\nPFNGLCOPYTEXIMAGE1DEXTPROC glad_glCopyTexImage1DEXT = NULL;\nPFNGLCOPYTEXIMAGE2DEXTPROC glad_glCopyTexImage2DEXT = NULL;\nPFNGLCOPYTEXSUBIMAGE1DEXTPROC glad_glCopyTexSubImage1DEXT = NULL;\nPFNGLCOPYTEXSUBIMAGE2DEXTPROC glad_glCopyTexSubImage2DEXT = NULL;\nPFNGLCOPYTEXSUBIMAGE3DEXTPROC glad_glCopyTexSubImage3DEXT = NULL;\nPFNGLCULLPARAMETERDVEXTPROC glad_glCullParameterdvEXT = NULL;\nPFNGLCULLPARAMETERFVEXTPROC glad_glCullParameterfvEXT = NULL;\nPFNGLLABELOBJECTEXTPROC glad_glLabelObjectEXT = NULL;\nPFNGLGETOBJECTLABELEXTPROC glad_glGetObjectLabelEXT = NULL;\nPFNGLINSERTEVENTMARKEREXTPROC glad_glInsertEventMarkerEXT = NULL;\nPFNGLPUSHGROUPMARKEREXTPROC glad_glPushGroupMarkerEXT = NULL;\nPFNGLPOPGROUPMARKEREXTPROC glad_glPopGroupMarkerEXT = NULL;\nPFNGLDEPTHBOUNDSEXTPROC glad_glDepthBoundsEXT = NULL;\nPFNGLMATRIXLOADFEXTPROC glad_glMatrixLoadfEXT = NULL;\nPFNGLMATRIXLOADDEXTPROC glad_glMatrixLoaddEXT = NULL;\nPFNGLMATRIXMULTFEXTPROC glad_glMatrixMultfEXT = NULL;\nPFNGLMATRIXMULTDEXTPROC glad_glMatrixMultdEXT = NULL;\nPFNGLMATRIXLOADIDENTITYEXTPROC glad_glMatrixLoadIdentityEXT = NULL;\nPFNGLMATRIXROTATEFEXTPROC glad_glMatrixRotatefEXT = NULL;\nPFNGLMATRIXROTATEDEXTPROC glad_glMatrixRotatedEXT = NULL;\nPFNGLMATRIXSCALEFEXTPROC glad_glMatrixScalefEXT = NULL;\nPFNGLMATRIXSCALEDEXTPROC glad_glMatrixScaledEXT = NULL;\nPFNGLMATRIXTRANSLATEFEXTPROC glad_glMatrixTranslatefEXT = NULL;\nPFNGLMATRIXTRANSLATEDEXTPROC glad_glMatrixTranslatedEXT = NULL;\nPFNGLMATRIXFRUSTUMEXTPROC glad_glMatrixFrustumEXT = NULL;\nPFNGLMATRIXORTHOEXTPROC glad_glMatrixOrthoEXT = NULL;\nPFNGLMATRIXPOPEXTPROC glad_glMatrixPopEXT = NULL;\nPFNGLMATRIXPUSHEXTPROC glad_glMatrixPushEXT = NULL;\nPFNGLCLIENTATTRIBDEFAULTEXTPROC glad_glClientAttribDefaultEXT = NULL;\nPFNGLPUSHCLIENTATTRIBDEFAULTEXTPROC glad_glPushClientAttribDefaultEXT = NULL;\nPFNGLTEXTUREPARAMETERFEXTPROC glad_glTextureParameterfEXT = NULL;\nPFNGLTEXTUREPARAMETERFVEXTPROC glad_glTextureParameterfvEXT = NULL;\nPFNGLTEXTUREPARAMETERIEXTPROC glad_glTextureParameteriEXT = NULL;\nPFNGLTEXTUREPARAMETERIVEXTPROC glad_glTextureParameterivEXT = NULL;\nPFNGLTEXTUREIMAGE1DEXTPROC glad_glTextureImage1DEXT = NULL;\nPFNGLTEXTUREIMAGE2DEXTPROC glad_glTextureImage2DEXT = NULL;\nPFNGLTEXTURESUBIMAGE1DEXTPROC glad_glTextureSubImage1DEXT = NULL;\nPFNGLTEXTURESUBIMAGE2DEXTPROC glad_glTextureSubImage2DEXT = NULL;\nPFNGLCOPYTEXTUREIMAGE1DEXTPROC glad_glCopyTextureImage1DEXT = NULL;\nPFNGLCOPYTEXTUREIMAGE2DEXTPROC glad_glCopyTextureImage2DEXT = NULL;\nPFNGLCOPYTEXTURESUBIMAGE1DEXTPROC glad_glCopyTextureSubImage1DEXT = NULL;\nPFNGLCOPYTEXTURESUBIMAGE2DEXTPROC glad_glCopyTextureSubImage2DEXT = NULL;\nPFNGLGETTEXTUREIMAGEEXTPROC glad_glGetTextureImageEXT = NULL;\nPFNGLGETTEXTUREPARAMETERFVEXTPROC glad_glGetTextureParameterfvEXT = NULL;\nPFNGLGETTEXTUREPARAMETERIVEXTPROC glad_glGetTextureParameterivEXT = NULL;\nPFNGLGETTEXTURELEVELPARAMETERFVEXTPROC glad_glGetTextureLevelParameterfvEXT = NULL;\nPFNGLGETTEXTURELEVELPARAMETERIVEXTPROC glad_glGetTextureLevelParameterivEXT = NULL;\nPFNGLTEXTUREIMAGE3DEXTPROC glad_glTextureImage3DEXT = NULL;\nPFNGLTEXTURESUBIMAGE3DEXTPROC glad_glTextureSubImage3DEXT = NULL;\nPFNGLCOPYTEXTURESUBIMAGE3DEXTPROC glad_glCopyTextureSubImage3DEXT = NULL;\nPFNGLBINDMULTITEXTUREEXTPROC glad_glBindMultiTextureEXT = NULL;\nPFNGLMULTITEXCOORDPOINTEREXTPROC glad_glMultiTexCoordPointerEXT = NULL;\nPFNGLMULTITEXENVFEXTPROC glad_glMultiTexEnvfEXT = NULL;\nPFNGLMULTITEXENVFVEXTPROC glad_glMultiTexEnvfvEXT = NULL;\nPFNGLMULTITEXENVIEXTPROC glad_glMultiTexEnviEXT = NULL;\nPFNGLMULTITEXENVIVEXTPROC glad_glMultiTexEnvivEXT = NULL;\nPFNGLMULTITEXGENDEXTPROC glad_glMultiTexGendEXT = NULL;\nPFNGLMULTITEXGENDVEXTPROC glad_glMultiTexGendvEXT = NULL;\nPFNGLMULTITEXGENFEXTPROC glad_glMultiTexGenfEXT = NULL;\nPFNGLMULTITEXGENFVEXTPROC glad_glMultiTexGenfvEXT = NULL;\nPFNGLMULTITEXGENIEXTPROC glad_glMultiTexGeniEXT = NULL;\nPFNGLMULTITEXGENIVEXTPROC glad_glMultiTexGenivEXT = NULL;\nPFNGLGETMULTITEXENVFVEXTPROC glad_glGetMultiTexEnvfvEXT = NULL;\nPFNGLGETMULTITEXENVIVEXTPROC glad_glGetMultiTexEnvivEXT = NULL;\nPFNGLGETMULTITEXGENDVEXTPROC glad_glGetMultiTexGendvEXT = NULL;\nPFNGLGETMULTITEXGENFVEXTPROC glad_glGetMultiTexGenfvEXT = NULL;\nPFNGLGETMULTITEXGENIVEXTPROC glad_glGetMultiTexGenivEXT = NULL;\nPFNGLMULTITEXPARAMETERIEXTPROC glad_glMultiTexParameteriEXT = NULL;\nPFNGLMULTITEXPARAMETERIVEXTPROC glad_glMultiTexParameterivEXT = NULL;\nPFNGLMULTITEXPARAMETERFEXTPROC glad_glMultiTexParameterfEXT = NULL;\nPFNGLMULTITEXPARAMETERFVEXTPROC glad_glMultiTexParameterfvEXT = NULL;\nPFNGLMULTITEXIMAGE1DEXTPROC glad_glMultiTexImage1DEXT = NULL;\nPFNGLMULTITEXIMAGE2DEXTPROC glad_glMultiTexImage2DEXT = NULL;\nPFNGLMULTITEXSUBIMAGE1DEXTPROC glad_glMultiTexSubImage1DEXT = NULL;\nPFNGLMULTITEXSUBIMAGE2DEXTPROC glad_glMultiTexSubImage2DEXT = NULL;\nPFNGLCOPYMULTITEXIMAGE1DEXTPROC glad_glCopyMultiTexImage1DEXT = NULL;\nPFNGLCOPYMULTITEXIMAGE2DEXTPROC glad_glCopyMultiTexImage2DEXT = NULL;\nPFNGLCOPYMULTITEXSUBIMAGE1DEXTPROC glad_glCopyMultiTexSubImage1DEXT = NULL;\nPFNGLCOPYMULTITEXSUBIMAGE2DEXTPROC glad_glCopyMultiTexSubImage2DEXT = NULL;\nPFNGLGETMULTITEXIMAGEEXTPROC glad_glGetMultiTexImageEXT = NULL;\nPFNGLGETMULTITEXPARAMETERFVEXTPROC glad_glGetMultiTexParameterfvEXT = NULL;\nPFNGLGETMULTITEXPARAMETERIVEXTPROC glad_glGetMultiTexParameterivEXT = NULL;\nPFNGLGETMULTITEXLEVELPARAMETERFVEXTPROC glad_glGetMultiTexLevelParameterfvEXT = NULL;\nPFNGLGETMULTITEXLEVELPARAMETERIVEXTPROC glad_glGetMultiTexLevelParameterivEXT = NULL;\nPFNGLMULTITEXIMAGE3DEXTPROC glad_glMultiTexImage3DEXT = NULL;\nPFNGLMULTITEXSUBIMAGE3DEXTPROC glad_glMultiTexSubImage3DEXT = NULL;\nPFNGLCOPYMULTITEXSUBIMAGE3DEXTPROC glad_glCopyMultiTexSubImage3DEXT = NULL;\nPFNGLENABLECLIENTSTATEINDEXEDEXTPROC glad_glEnableClientStateIndexedEXT = NULL;\nPFNGLDISABLECLIENTSTATEINDEXEDEXTPROC glad_glDisableClientStateIndexedEXT = NULL;\nPFNGLGETFLOATINDEXEDVEXTPROC glad_glGetFloatIndexedvEXT = NULL;\nPFNGLGETDOUBLEINDEXEDVEXTPROC glad_glGetDoubleIndexedvEXT = NULL;\nPFNGLGETPOINTERINDEXEDVEXTPROC glad_glGetPointerIndexedvEXT = NULL;\nPFNGLENABLEINDEXEDEXTPROC glad_glEnableIndexedEXT = NULL;\nPFNGLDISABLEINDEXEDEXTPROC glad_glDisableIndexedEXT = NULL;\nPFNGLISENABLEDINDEXEDEXTPROC glad_glIsEnabledIndexedEXT = NULL;\nPFNGLGETINTEGERINDEXEDVEXTPROC glad_glGetIntegerIndexedvEXT = NULL;\nPFNGLGETBOOLEANINDEXEDVEXTPROC glad_glGetBooleanIndexedvEXT = NULL;\nPFNGLCOMPRESSEDTEXTUREIMAGE3DEXTPROC glad_glCompressedTextureImage3DEXT = NULL;\nPFNGLCOMPRESSEDTEXTUREIMAGE2DEXTPROC glad_glCompressedTextureImage2DEXT = NULL;\nPFNGLCOMPRESSEDTEXTUREIMAGE1DEXTPROC glad_glCompressedTextureImage1DEXT = NULL;\nPFNGLCOMPRESSEDTEXTURESUBIMAGE3DEXTPROC glad_glCompressedTextureSubImage3DEXT = NULL;\nPFNGLCOMPRESSEDTEXTURESUBIMAGE2DEXTPROC glad_glCompressedTextureSubImage2DEXT = NULL;\nPFNGLCOMPRESSEDTEXTURESUBIMAGE1DEXTPROC glad_glCompressedTextureSubImage1DEXT = NULL;\nPFNGLGETCOMPRESSEDTEXTUREIMAGEEXTPROC glad_glGetCompressedTextureImageEXT = NULL;\nPFNGLCOMPRESSEDMULTITEXIMAGE3DEXTPROC glad_glCompressedMultiTexImage3DEXT = NULL;\nPFNGLCOMPRESSEDMULTITEXIMAGE2DEXTPROC glad_glCompressedMultiTexImage2DEXT = NULL;\nPFNGLCOMPRESSEDMULTITEXIMAGE1DEXTPROC glad_glCompressedMultiTexImage1DEXT = NULL;\nPFNGLCOMPRESSEDMULTITEXSUBIMAGE3DEXTPROC glad_glCompressedMultiTexSubImage3DEXT = NULL;\nPFNGLCOMPRESSEDMULTITEXSUBIMAGE2DEXTPROC glad_glCompressedMultiTexSubImage2DEXT = NULL;\nPFNGLCOMPRESSEDMULTITEXSUBIMAGE1DEXTPROC glad_glCompressedMultiTexSubImage1DEXT = NULL;\nPFNGLGETCOMPRESSEDMULTITEXIMAGEEXTPROC glad_glGetCompressedMultiTexImageEXT = NULL;\nPFNGLMATRIXLOADTRANSPOSEFEXTPROC glad_glMatrixLoadTransposefEXT = NULL;\nPFNGLMATRIXLOADTRANSPOSEDEXTPROC glad_glMatrixLoadTransposedEXT = NULL;\nPFNGLMATRIXMULTTRANSPOSEFEXTPROC glad_glMatrixMultTransposefEXT = NULL;\nPFNGLMATRIXMULTTRANSPOSEDEXTPROC glad_glMatrixMultTransposedEXT = NULL;\nPFNGLNAMEDBUFFERDATAEXTPROC glad_glNamedBufferDataEXT = NULL;\nPFNGLNAMEDBUFFERSUBDATAEXTPROC glad_glNamedBufferSubDataEXT = NULL;\nPFNGLMAPNAMEDBUFFEREXTPROC glad_glMapNamedBufferEXT = NULL;\nPFNGLUNMAPNAMEDBUFFEREXTPROC glad_glUnmapNamedBufferEXT = NULL;\nPFNGLGETNAMEDBUFFERPARAMETERIVEXTPROC glad_glGetNamedBufferParameterivEXT = NULL;\nPFNGLGETNAMEDBUFFERPOINTERVEXTPROC glad_glGetNamedBufferPointervEXT = NULL;\nPFNGLGETNAMEDBUFFERSUBDATAEXTPROC glad_glGetNamedBufferSubDataEXT = NULL;\nPFNGLPROGRAMUNIFORM1FEXTPROC glad_glProgramUniform1fEXT = NULL;\nPFNGLPROGRAMUNIFORM2FEXTPROC glad_glProgramUniform2fEXT = NULL;\nPFNGLPROGRAMUNIFORM3FEXTPROC glad_glProgramUniform3fEXT = NULL;\nPFNGLPROGRAMUNIFORM4FEXTPROC glad_glProgramUniform4fEXT = NULL;\nPFNGLPROGRAMUNIFORM1IEXTPROC glad_glProgramUniform1iEXT = NULL;\nPFNGLPROGRAMUNIFORM2IEXTPROC glad_glProgramUniform2iEXT = NULL;\nPFNGLPROGRAMUNIFORM3IEXTPROC glad_glProgramUniform3iEXT = NULL;\nPFNGLPROGRAMUNIFORM4IEXTPROC glad_glProgramUniform4iEXT = NULL;\nPFNGLPROGRAMUNIFORM1FVEXTPROC glad_glProgramUniform1fvEXT = NULL;\nPFNGLPROGRAMUNIFORM2FVEXTPROC glad_glProgramUniform2fvEXT = NULL;\nPFNGLPROGRAMUNIFORM3FVEXTPROC glad_glProgramUniform3fvEXT = NULL;\nPFNGLPROGRAMUNIFORM4FVEXTPROC glad_glProgramUniform4fvEXT = NULL;\nPFNGLPROGRAMUNIFORM1IVEXTPROC glad_glProgramUniform1ivEXT = NULL;\nPFNGLPROGRAMUNIFORM2IVEXTPROC glad_glProgramUniform2ivEXT = NULL;\nPFNGLPROGRAMUNIFORM3IVEXTPROC glad_glProgramUniform3ivEXT = NULL;\nPFNGLPROGRAMUNIFORM4IVEXTPROC glad_glProgramUniform4ivEXT = NULL;\nPFNGLPROGRAMUNIFORMMATRIX2FVEXTPROC glad_glProgramUniformMatrix2fvEXT = NULL;\nPFNGLPROGRAMUNIFORMMATRIX3FVEXTPROC glad_glProgramUniformMatrix3fvEXT = NULL;\nPFNGLPROGRAMUNIFORMMATRIX4FVEXTPROC glad_glProgramUniformMatrix4fvEXT = NULL;\nPFNGLPROGRAMUNIFORMMATRIX2X3FVEXTPROC glad_glProgramUniformMatrix2x3fvEXT = NULL;\nPFNGLPROGRAMUNIFORMMATRIX3X2FVEXTPROC glad_glProgramUniformMatrix3x2fvEXT = NULL;\nPFNGLPROGRAMUNIFORMMATRIX2X4FVEXTPROC glad_glProgramUniformMatrix2x4fvEXT = NULL;\nPFNGLPROGRAMUNIFORMMATRIX4X2FVEXTPROC glad_glProgramUniformMatrix4x2fvEXT = NULL;\nPFNGLPROGRAMUNIFORMMATRIX3X4FVEXTPROC glad_glProgramUniformMatrix3x4fvEXT = NULL;\nPFNGLPROGRAMUNIFORMMATRIX4X3FVEXTPROC glad_glProgramUniformMatrix4x3fvEXT = NULL;\nPFNGLTEXTUREBUFFEREXTPROC glad_glTextureBufferEXT = NULL;\nPFNGLMULTITEXBUFFEREXTPROC glad_glMultiTexBufferEXT = NULL;\nPFNGLTEXTUREPARAMETERIIVEXTPROC glad_glTextureParameterIivEXT = NULL;\nPFNGLTEXTUREPARAMETERIUIVEXTPROC glad_glTextureParameterIuivEXT = NULL;\nPFNGLGETTEXTUREPARAMETERIIVEXTPROC glad_glGetTextureParameterIivEXT = NULL;\nPFNGLGETTEXTUREPARAMETERIUIVEXTPROC glad_glGetTextureParameterIuivEXT = NULL;\nPFNGLMULTITEXPARAMETERIIVEXTPROC glad_glMultiTexParameterIivEXT = NULL;\nPFNGLMULTITEXPARAMETERIUIVEXTPROC glad_glMultiTexParameterIuivEXT = NULL;\nPFNGLGETMULTITEXPARAMETERIIVEXTPROC glad_glGetMultiTexParameterIivEXT = NULL;\nPFNGLGETMULTITEXPARAMETERIUIVEXTPROC glad_glGetMultiTexParameterIuivEXT = NULL;\nPFNGLPROGRAMUNIFORM1UIEXTPROC glad_glProgramUniform1uiEXT = NULL;\nPFNGLPROGRAMUNIFORM2UIEXTPROC glad_glProgramUniform2uiEXT = NULL;\nPFNGLPROGRAMUNIFORM3UIEXTPROC glad_glProgramUniform3uiEXT = NULL;\nPFNGLPROGRAMUNIFORM4UIEXTPROC glad_glProgramUniform4uiEXT = NULL;\nPFNGLPROGRAMUNIFORM1UIVEXTPROC glad_glProgramUniform1uivEXT = NULL;\nPFNGLPROGRAMUNIFORM2UIVEXTPROC glad_glProgramUniform2uivEXT = NULL;\nPFNGLPROGRAMUNIFORM3UIVEXTPROC glad_glProgramUniform3uivEXT = NULL;\nPFNGLPROGRAMUNIFORM4UIVEXTPROC glad_glProgramUniform4uivEXT = NULL;\nPFNGLNAMEDPROGRAMLOCALPARAMETERS4FVEXTPROC glad_glNamedProgramLocalParameters4fvEXT = NULL;\nPFNGLNAMEDPROGRAMLOCALPARAMETERI4IEXTPROC glad_glNamedProgramLocalParameterI4iEXT = NULL;\nPFNGLNAMEDPROGRAMLOCALPARAMETERI4IVEXTPROC glad_glNamedProgramLocalParameterI4ivEXT = NULL;\nPFNGLNAMEDPROGRAMLOCALPARAMETERSI4IVEXTPROC glad_glNamedProgramLocalParametersI4ivEXT = NULL;\nPFNGLNAMEDPROGRAMLOCALPARAMETERI4UIEXTPROC glad_glNamedProgramLocalParameterI4uiEXT = NULL;\nPFNGLNAMEDPROGRAMLOCALPARAMETERI4UIVEXTPROC glad_glNamedProgramLocalParameterI4uivEXT = NULL;\nPFNGLNAMEDPROGRAMLOCALPARAMETERSI4UIVEXTPROC glad_glNamedProgramLocalParametersI4uivEXT = NULL;\nPFNGLGETNAMEDPROGRAMLOCALPARAMETERIIVEXTPROC glad_glGetNamedProgramLocalParameterIivEXT = NULL;\nPFNGLGETNAMEDPROGRAMLOCALPARAMETERIUIVEXTPROC glad_glGetNamedProgramLocalParameterIuivEXT = NULL;\nPFNGLENABLECLIENTSTATEIEXTPROC glad_glEnableClientStateiEXT = NULL;\nPFNGLDISABLECLIENTSTATEIEXTPROC glad_glDisableClientStateiEXT = NULL;\nPFNGLGETFLOATI_VEXTPROC glad_glGetFloati_vEXT = NULL;\nPFNGLGETDOUBLEI_VEXTPROC glad_glGetDoublei_vEXT = NULL;\nPFNGLGETPOINTERI_VEXTPROC glad_glGetPointeri_vEXT = NULL;\nPFNGLNAMEDPROGRAMSTRINGEXTPROC glad_glNamedProgramStringEXT = NULL;\nPFNGLNAMEDPROGRAMLOCALPARAMETER4DEXTPROC glad_glNamedProgramLocalParameter4dEXT = NULL;\nPFNGLNAMEDPROGRAMLOCALPARAMETER4DVEXTPROC glad_glNamedProgramLocalParameter4dvEXT = NULL;\nPFNGLNAMEDPROGRAMLOCALPARAMETER4FEXTPROC glad_glNamedProgramLocalParameter4fEXT = NULL;\nPFNGLNAMEDPROGRAMLOCALPARAMETER4FVEXTPROC glad_glNamedProgramLocalParameter4fvEXT = NULL;\nPFNGLGETNAMEDPROGRAMLOCALPARAMETERDVEXTPROC glad_glGetNamedProgramLocalParameterdvEXT = NULL;\nPFNGLGETNAMEDPROGRAMLOCALPARAMETERFVEXTPROC glad_glGetNamedProgramLocalParameterfvEXT = NULL;\nPFNGLGETNAMEDPROGRAMIVEXTPROC glad_glGetNamedProgramivEXT = NULL;\nPFNGLGETNAMEDPROGRAMSTRINGEXTPROC glad_glGetNamedProgramStringEXT = NULL;\nPFNGLNAMEDRENDERBUFFERSTORAGEEXTPROC glad_glNamedRenderbufferStorageEXT = NULL;\nPFNGLGETNAMEDRENDERBUFFERPARAMETERIVEXTPROC glad_glGetNamedRenderbufferParameterivEXT = NULL;\nPFNGLNAMEDRENDERBUFFERSTORAGEMULTISAMPLEEXTPROC glad_glNamedRenderbufferStorageMultisampleEXT = NULL;\nPFNGLNAMEDRENDERBUFFERSTORAGEMULTISAMPLECOVERAGEEXTPROC glad_glNamedRenderbufferStorageMultisampleCoverageEXT = NULL;\nPFNGLCHECKNAMEDFRAMEBUFFERSTATUSEXTPROC glad_glCheckNamedFramebufferStatusEXT = NULL;\nPFNGLNAMEDFRAMEBUFFERTEXTURE1DEXTPROC glad_glNamedFramebufferTexture1DEXT = NULL;\nPFNGLNAMEDFRAMEBUFFERTEXTURE2DEXTPROC glad_glNamedFramebufferTexture2DEXT = NULL;\nPFNGLNAMEDFRAMEBUFFERTEXTURE3DEXTPROC glad_glNamedFramebufferTexture3DEXT = NULL;\nPFNGLNAMEDFRAMEBUFFERRENDERBUFFEREXTPROC glad_glNamedFramebufferRenderbufferEXT = NULL;\nPFNGLGETNAMEDFRAMEBUFFERATTACHMENTPARAMETERIVEXTPROC glad_glGetNamedFramebufferAttachmentParameterivEXT = NULL;\nPFNGLGENERATETEXTUREMIPMAPEXTPROC glad_glGenerateTextureMipmapEXT = NULL;\nPFNGLGENERATEMULTITEXMIPMAPEXTPROC glad_glGenerateMultiTexMipmapEXT = NULL;\nPFNGLFRAMEBUFFERDRAWBUFFEREXTPROC glad_glFramebufferDrawBufferEXT = NULL;\nPFNGLFRAMEBUFFERDRAWBUFFERSEXTPROC glad_glFramebufferDrawBuffersEXT = NULL;\nPFNGLFRAMEBUFFERREADBUFFEREXTPROC glad_glFramebufferReadBufferEXT = NULL;\nPFNGLGETFRAMEBUFFERPARAMETERIVEXTPROC glad_glGetFramebufferParameterivEXT = NULL;\nPFNGLNAMEDCOPYBUFFERSUBDATAEXTPROC glad_glNamedCopyBufferSubDataEXT = NULL;\nPFNGLNAMEDFRAMEBUFFERTEXTUREEXTPROC glad_glNamedFramebufferTextureEXT = NULL;\nPFNGLNAMEDFRAMEBUFFERTEXTURELAYEREXTPROC glad_glNamedFramebufferTextureLayerEXT = NULL;\nPFNGLNAMEDFRAMEBUFFERTEXTUREFACEEXTPROC glad_glNamedFramebufferTextureFaceEXT = NULL;\nPFNGLTEXTURERENDERBUFFEREXTPROC glad_glTextureRenderbufferEXT = NULL;\nPFNGLMULTITEXRENDERBUFFEREXTPROC glad_glMultiTexRenderbufferEXT = NULL;\nPFNGLVERTEXARRAYVERTEXOFFSETEXTPROC glad_glVertexArrayVertexOffsetEXT = NULL;\nPFNGLVERTEXARRAYCOLOROFFSETEXTPROC glad_glVertexArrayColorOffsetEXT = NULL;\nPFNGLVERTEXARRAYEDGEFLAGOFFSETEXTPROC glad_glVertexArrayEdgeFlagOffsetEXT = NULL;\nPFNGLVERTEXARRAYINDEXOFFSETEXTPROC glad_glVertexArrayIndexOffsetEXT = NULL;\nPFNGLVERTEXARRAYNORMALOFFSETEXTPROC glad_glVertexArrayNormalOffsetEXT = NULL;\nPFNGLVERTEXARRAYTEXCOORDOFFSETEXTPROC glad_glVertexArrayTexCoordOffsetEXT = NULL;\nPFNGLVERTEXARRAYMULTITEXCOORDOFFSETEXTPROC glad_glVertexArrayMultiTexCoordOffsetEXT = NULL;\nPFNGLVERTEXARRAYFOGCOORDOFFSETEXTPROC glad_glVertexArrayFogCoordOffsetEXT = NULL;\nPFNGLVERTEXARRAYSECONDARYCOLOROFFSETEXTPROC glad_glVertexArraySecondaryColorOffsetEXT = NULL;\nPFNGLVERTEXARRAYVERTEXATTRIBOFFSETEXTPROC glad_glVertexArrayVertexAttribOffsetEXT = NULL;\nPFNGLVERTEXARRAYVERTEXATTRIBIOFFSETEXTPROC glad_glVertexArrayVertexAttribIOffsetEXT = NULL;\nPFNGLENABLEVERTEXARRAYEXTPROC glad_glEnableVertexArrayEXT = NULL;\nPFNGLDISABLEVERTEXARRAYEXTPROC glad_glDisableVertexArrayEXT = NULL;\nPFNGLENABLEVERTEXARRAYATTRIBEXTPROC glad_glEnableVertexArrayAttribEXT = NULL;\nPFNGLDISABLEVERTEXARRAYATTRIBEXTPROC glad_glDisableVertexArrayAttribEXT = NULL;\nPFNGLGETVERTEXARRAYINTEGERVEXTPROC glad_glGetVertexArrayIntegervEXT = NULL;\nPFNGLGETVERTEXARRAYPOINTERVEXTPROC glad_glGetVertexArrayPointervEXT = NULL;\nPFNGLGETVERTEXARRAYINTEGERI_VEXTPROC glad_glGetVertexArrayIntegeri_vEXT = NULL;\nPFNGLGETVERTEXARRAYPOINTERI_VEXTPROC glad_glGetVertexArrayPointeri_vEXT = NULL;\nPFNGLMAPNAMEDBUFFERRANGEEXTPROC glad_glMapNamedBufferRangeEXT = NULL;\nPFNGLFLUSHMAPPEDNAMEDBUFFERRANGEEXTPROC glad_glFlushMappedNamedBufferRangeEXT = NULL;\nPFNGLNAMEDBUFFERSTORAGEEXTPROC glad_glNamedBufferStorageEXT = NULL;\nPFNGLCLEARNAMEDBUFFERDATAEXTPROC glad_glClearNamedBufferDataEXT = NULL;\nPFNGLCLEARNAMEDBUFFERSUBDATAEXTPROC glad_glClearNamedBufferSubDataEXT = NULL;\nPFNGLNAMEDFRAMEBUFFERPARAMETERIEXTPROC glad_glNamedFramebufferParameteriEXT = NULL;\nPFNGLGETNAMEDFRAMEBUFFERPARAMETERIVEXTPROC glad_glGetNamedFramebufferParameterivEXT = NULL;\nPFNGLPROGRAMUNIFORM1DEXTPROC glad_glProgramUniform1dEXT = NULL;\nPFNGLPROGRAMUNIFORM2DEXTPROC glad_glProgramUniform2dEXT = NULL;\nPFNGLPROGRAMUNIFORM3DEXTPROC glad_glProgramUniform3dEXT = NULL;\nPFNGLPROGRAMUNIFORM4DEXTPROC glad_glProgramUniform4dEXT = NULL;\nPFNGLPROGRAMUNIFORM1DVEXTPROC glad_glProgramUniform1dvEXT = NULL;\nPFNGLPROGRAMUNIFORM2DVEXTPROC glad_glProgramUniform2dvEXT = NULL;\nPFNGLPROGRAMUNIFORM3DVEXTPROC glad_glProgramUniform3dvEXT = NULL;\nPFNGLPROGRAMUNIFORM4DVEXTPROC glad_glProgramUniform4dvEXT = NULL;\nPFNGLPROGRAMUNIFORMMATRIX2DVEXTPROC glad_glProgramUniformMatrix2dvEXT = NULL;\nPFNGLPROGRAMUNIFORMMATRIX3DVEXTPROC glad_glProgramUniformMatrix3dvEXT = NULL;\nPFNGLPROGRAMUNIFORMMATRIX4DVEXTPROC glad_glProgramUniformMatrix4dvEXT = NULL;\nPFNGLPROGRAMUNIFORMMATRIX2X3DVEXTPROC glad_glProgramUniformMatrix2x3dvEXT = NULL;\nPFNGLPROGRAMUNIFORMMATRIX2X4DVEXTPROC glad_glProgramUniformMatrix2x4dvEXT = NULL;\nPFNGLPROGRAMUNIFORMMATRIX3X2DVEXTPROC glad_glProgramUniformMatrix3x2dvEXT = NULL;\nPFNGLPROGRAMUNIFORMMATRIX3X4DVEXTPROC glad_glProgramUniformMatrix3x4dvEXT = NULL;\nPFNGLPROGRAMUNIFORMMATRIX4X2DVEXTPROC glad_glProgramUniformMatrix4x2dvEXT = NULL;\nPFNGLPROGRAMUNIFORMMATRIX4X3DVEXTPROC glad_glProgramUniformMatrix4x3dvEXT = NULL;\nPFNGLTEXTUREBUFFERRANGEEXTPROC glad_glTextureBufferRangeEXT = NULL;\nPFNGLTEXTURESTORAGE1DEXTPROC glad_glTextureStorage1DEXT = NULL;\nPFNGLTEXTURESTORAGE2DEXTPROC glad_glTextureStorage2DEXT = NULL;\nPFNGLTEXTURESTORAGE3DEXTPROC glad_glTextureStorage3DEXT = NULL;\nPFNGLTEXTURESTORAGE2DMULTISAMPLEEXTPROC glad_glTextureStorage2DMultisampleEXT = NULL;\nPFNGLTEXTURESTORAGE3DMULTISAMPLEEXTPROC glad_glTextureStorage3DMultisampleEXT = NULL;\nPFNGLVERTEXARRAYBINDVERTEXBUFFEREXTPROC glad_glVertexArrayBindVertexBufferEXT = NULL;\nPFNGLVERTEXARRAYVERTEXATTRIBFORMATEXTPROC glad_glVertexArrayVertexAttribFormatEXT = NULL;\nPFNGLVERTEXARRAYVERTEXATTRIBIFORMATEXTPROC glad_glVertexArrayVertexAttribIFormatEXT = NULL;\nPFNGLVERTEXARRAYVERTEXATTRIBLFORMATEXTPROC glad_glVertexArrayVertexAttribLFormatEXT = NULL;\nPFNGLVERTEXARRAYVERTEXATTRIBBINDINGEXTPROC glad_glVertexArrayVertexAttribBindingEXT = NULL;\nPFNGLVERTEXARRAYVERTEXBINDINGDIVISOREXTPROC glad_glVertexArrayVertexBindingDivisorEXT = NULL;\nPFNGLVERTEXARRAYVERTEXATTRIBLOFFSETEXTPROC glad_glVertexArrayVertexAttribLOffsetEXT = NULL;\nPFNGLTEXTUREPAGECOMMITMENTEXTPROC glad_glTexturePageCommitmentEXT = NULL;\nPFNGLVERTEXARRAYVERTEXATTRIBDIVISOREXTPROC glad_glVertexArrayVertexAttribDivisorEXT = NULL;\nPFNGLCOLORMASKINDEXEDEXTPROC glad_glColorMaskIndexedEXT = NULL;\nPFNGLDRAWARRAYSINSTANCEDEXTPROC glad_glDrawArraysInstancedEXT = NULL;\nPFNGLDRAWELEMENTSINSTANCEDEXTPROC glad_glDrawElementsInstancedEXT = NULL;\nPFNGLDRAWRANGEELEMENTSEXTPROC glad_glDrawRangeElementsEXT = NULL;\nPFNGLBUFFERSTORAGEEXTERNALEXTPROC glad_glBufferStorageExternalEXT = NULL;\nPFNGLNAMEDBUFFERSTORAGEEXTERNALEXTPROC glad_glNamedBufferStorageExternalEXT = NULL;\nPFNGLFOGCOORDFEXTPROC glad_glFogCoordfEXT = NULL;\nPFNGLFOGCOORDFVEXTPROC glad_glFogCoordfvEXT = NULL;\nPFNGLFOGCOORDDEXTPROC glad_glFogCoorddEXT = NULL;\nPFNGLFOGCOORDDVEXTPROC glad_glFogCoorddvEXT = NULL;\nPFNGLFOGCOORDPOINTEREXTPROC glad_glFogCoordPointerEXT = NULL;\nPFNGLBLITFRAMEBUFFEREXTPROC glad_glBlitFramebufferEXT = NULL;\nPFNGLRENDERBUFFERSTORAGEMULTISAMPLEEXTPROC glad_glRenderbufferStorageMultisampleEXT = NULL;\nPFNGLISRENDERBUFFEREXTPROC glad_glIsRenderbufferEXT = NULL;\nPFNGLBINDRENDERBUFFEREXTPROC glad_glBindRenderbufferEXT = NULL;\nPFNGLDELETERENDERBUFFERSEXTPROC glad_glDeleteRenderbuffersEXT = NULL;\nPFNGLGENRENDERBUFFERSEXTPROC glad_glGenRenderbuffersEXT = NULL;\nPFNGLRENDERBUFFERSTORAGEEXTPROC glad_glRenderbufferStorageEXT = NULL;\nPFNGLGETRENDERBUFFERPARAMETERIVEXTPROC glad_glGetRenderbufferParameterivEXT = NULL;\nPFNGLISFRAMEBUFFEREXTPROC glad_glIsFramebufferEXT = NULL;\nPFNGLBINDFRAMEBUFFEREXTPROC glad_glBindFramebufferEXT = NULL;\nPFNGLDELETEFRAMEBUFFERSEXTPROC glad_glDeleteFramebuffersEXT = NULL;\nPFNGLGENFRAMEBUFFERSEXTPROC glad_glGenFramebuffersEXT = NULL;\nPFNGLCHECKFRAMEBUFFERSTATUSEXTPROC glad_glCheckFramebufferStatusEXT = NULL;\nPFNGLFRAMEBUFFERTEXTURE1DEXTPROC glad_glFramebufferTexture1DEXT = NULL;\nPFNGLFRAMEBUFFERTEXTURE2DEXTPROC glad_glFramebufferTexture2DEXT = NULL;\nPFNGLFRAMEBUFFERTEXTURE3DEXTPROC glad_glFramebufferTexture3DEXT = NULL;\nPFNGLFRAMEBUFFERRENDERBUFFEREXTPROC glad_glFramebufferRenderbufferEXT = NULL;\nPFNGLGETFRAMEBUFFERATTACHMENTPARAMETERIVEXTPROC glad_glGetFramebufferAttachmentParameterivEXT = NULL;\nPFNGLGENERATEMIPMAPEXTPROC glad_glGenerateMipmapEXT = NULL;\nPFNGLPROGRAMPARAMETERIEXTPROC glad_glProgramParameteriEXT = NULL;\nPFNGLPROGRAMENVPARAMETERS4FVEXTPROC glad_glProgramEnvParameters4fvEXT = NULL;\nPFNGLPROGRAMLOCALPARAMETERS4FVEXTPROC glad_glProgramLocalParameters4fvEXT = NULL;\nPFNGLGETUNIFORMUIVEXTPROC glad_glGetUniformuivEXT = NULL;\nPFNGLBINDFRAGDATALOCATIONEXTPROC glad_glBindFragDataLocationEXT = NULL;\nPFNGLGETFRAGDATALOCATIONEXTPROC glad_glGetFragDataLocationEXT = NULL;\nPFNGLUNIFORM1UIEXTPROC glad_glUniform1uiEXT = NULL;\nPFNGLUNIFORM2UIEXTPROC glad_glUniform2uiEXT = NULL;\nPFNGLUNIFORM3UIEXTPROC glad_glUniform3uiEXT = NULL;\nPFNGLUNIFORM4UIEXTPROC glad_glUniform4uiEXT = NULL;\nPFNGLUNIFORM1UIVEXTPROC glad_glUniform1uivEXT = NULL;\nPFNGLUNIFORM2UIVEXTPROC glad_glUniform2uivEXT = NULL;\nPFNGLUNIFORM3UIVEXTPROC glad_glUniform3uivEXT = NULL;\nPFNGLUNIFORM4UIVEXTPROC glad_glUniform4uivEXT = NULL;\nPFNGLGETHISTOGRAMEXTPROC glad_glGetHistogramEXT = NULL;\nPFNGLGETHISTOGRAMPARAMETERFVEXTPROC glad_glGetHistogramParameterfvEXT = NULL;\nPFNGLGETHISTOGRAMPARAMETERIVEXTPROC glad_glGetHistogramParameterivEXT = NULL;\nPFNGLGETMINMAXEXTPROC glad_glGetMinmaxEXT = NULL;\nPFNGLGETMINMAXPARAMETERFVEXTPROC glad_glGetMinmaxParameterfvEXT = NULL;\nPFNGLGETMINMAXPARAMETERIVEXTPROC glad_glGetMinmaxParameterivEXT = NULL;\nPFNGLHISTOGRAMEXTPROC glad_glHistogramEXT = NULL;\nPFNGLMINMAXEXTPROC glad_glMinmaxEXT = NULL;\nPFNGLRESETHISTOGRAMEXTPROC glad_glResetHistogramEXT = NULL;\nPFNGLRESETMINMAXEXTPROC glad_glResetMinmaxEXT = NULL;\nPFNGLINDEXFUNCEXTPROC glad_glIndexFuncEXT = NULL;\nPFNGLINDEXMATERIALEXTPROC glad_glIndexMaterialEXT = NULL;\nPFNGLAPPLYTEXTUREEXTPROC glad_glApplyTextureEXT = NULL;\nPFNGLTEXTURELIGHTEXTPROC glad_glTextureLightEXT = NULL;\nPFNGLTEXTUREMATERIALEXTPROC glad_glTextureMaterialEXT = NULL;\nPFNGLGETUNSIGNEDBYTEVEXTPROC glad_glGetUnsignedBytevEXT = NULL;\nPFNGLGETUNSIGNEDBYTEI_VEXTPROC glad_glGetUnsignedBytei_vEXT = NULL;\nPFNGLDELETEMEMORYOBJECTSEXTPROC glad_glDeleteMemoryObjectsEXT = NULL;\nPFNGLISMEMORYOBJECTEXTPROC glad_glIsMemoryObjectEXT = NULL;\nPFNGLCREATEMEMORYOBJECTSEXTPROC glad_glCreateMemoryObjectsEXT = NULL;\nPFNGLMEMORYOBJECTPARAMETERIVEXTPROC glad_glMemoryObjectParameterivEXT = NULL;\nPFNGLGETMEMORYOBJECTPARAMETERIVEXTPROC glad_glGetMemoryObjectParameterivEXT = NULL;\nPFNGLTEXSTORAGEMEM2DEXTPROC glad_glTexStorageMem2DEXT = NULL;\nPFNGLTEXSTORAGEMEM2DMULTISAMPLEEXTPROC glad_glTexStorageMem2DMultisampleEXT = NULL;\nPFNGLTEXSTORAGEMEM3DEXTPROC glad_glTexStorageMem3DEXT = NULL;\nPFNGLTEXSTORAGEMEM3DMULTISAMPLEEXTPROC glad_glTexStorageMem3DMultisampleEXT = NULL;\nPFNGLBUFFERSTORAGEMEMEXTPROC glad_glBufferStorageMemEXT = NULL;\nPFNGLTEXTURESTORAGEMEM2DEXTPROC glad_glTextureStorageMem2DEXT = NULL;\nPFNGLTEXTURESTORAGEMEM2DMULTISAMPLEEXTPROC glad_glTextureStorageMem2DMultisampleEXT = NULL;\nPFNGLTEXTURESTORAGEMEM3DEXTPROC glad_glTextureStorageMem3DEXT = NULL;\nPFNGLTEXTURESTORAGEMEM3DMULTISAMPLEEXTPROC glad_glTextureStorageMem3DMultisampleEXT = NULL;\nPFNGLNAMEDBUFFERSTORAGEMEMEXTPROC glad_glNamedBufferStorageMemEXT = NULL;\nPFNGLTEXSTORAGEMEM1DEXTPROC glad_glTexStorageMem1DEXT = NULL;\nPFNGLTEXTURESTORAGEMEM1DEXTPROC glad_glTextureStorageMem1DEXT = NULL;\nPFNGLIMPORTMEMORYFDEXTPROC glad_glImportMemoryFdEXT = NULL;\nPFNGLIMPORTMEMORYWIN32HANDLEEXTPROC glad_glImportMemoryWin32HandleEXT = NULL;\nPFNGLIMPORTMEMORYWIN32NAMEEXTPROC glad_glImportMemoryWin32NameEXT = NULL;\nPFNGLMULTIDRAWARRAYSEXTPROC glad_glMultiDrawArraysEXT = NULL;\nPFNGLMULTIDRAWELEMENTSEXTPROC glad_glMultiDrawElementsEXT = NULL;\nPFNGLSAMPLEMASKEXTPROC glad_glSampleMaskEXT = NULL;\nPFNGLSAMPLEPATTERNEXTPROC glad_glSamplePatternEXT = NULL;\nPFNGLCOLORTABLEEXTPROC glad_glColorTableEXT = NULL;\nPFNGLGETCOLORTABLEEXTPROC glad_glGetColorTableEXT = NULL;\nPFNGLGETCOLORTABLEPARAMETERIVEXTPROC glad_glGetColorTableParameterivEXT = NULL;\nPFNGLGETCOLORTABLEPARAMETERFVEXTPROC glad_glGetColorTableParameterfvEXT = NULL;\nPFNGLPIXELTRANSFORMPARAMETERIEXTPROC glad_glPixelTransformParameteriEXT = NULL;\nPFNGLPIXELTRANSFORMPARAMETERFEXTPROC glad_glPixelTransformParameterfEXT = NULL;\nPFNGLPIXELTRANSFORMPARAMETERIVEXTPROC glad_glPixelTransformParameterivEXT = NULL;\nPFNGLPIXELTRANSFORMPARAMETERFVEXTPROC glad_glPixelTransformParameterfvEXT = NULL;\nPFNGLGETPIXELTRANSFORMPARAMETERIVEXTPROC glad_glGetPixelTransformParameterivEXT = NULL;\nPFNGLGETPIXELTRANSFORMPARAMETERFVEXTPROC glad_glGetPixelTransformParameterfvEXT = NULL;\nPFNGLPOINTPARAMETERFEXTPROC glad_glPointParameterfEXT = NULL;\nPFNGLPOINTPARAMETERFVEXTPROC glad_glPointParameterfvEXT = NULL;\nPFNGLPOLYGONOFFSETEXTPROC glad_glPolygonOffsetEXT = NULL;\nPFNGLPOLYGONOFFSETCLAMPEXTPROC glad_glPolygonOffsetClampEXT = NULL;\nPFNGLPROVOKINGVERTEXEXTPROC glad_glProvokingVertexEXT = NULL;\nPFNGLRASTERSAMPLESEXTPROC glad_glRasterSamplesEXT = NULL;\nPFNGLSECONDARYCOLOR3BEXTPROC glad_glSecondaryColor3bEXT = NULL;\nPFNGLSECONDARYCOLOR3BVEXTPROC glad_glSecondaryColor3bvEXT = NULL;\nPFNGLSECONDARYCOLOR3DEXTPROC glad_glSecondaryColor3dEXT = NULL;\nPFNGLSECONDARYCOLOR3DVEXTPROC glad_glSecondaryColor3dvEXT = NULL;\nPFNGLSECONDARYCOLOR3FEXTPROC glad_glSecondaryColor3fEXT = NULL;\nPFNGLSECONDARYCOLOR3FVEXTPROC glad_glSecondaryColor3fvEXT = NULL;\nPFNGLSECONDARYCOLOR3IEXTPROC glad_glSecondaryColor3iEXT = NULL;\nPFNGLSECONDARYCOLOR3IVEXTPROC glad_glSecondaryColor3ivEXT = NULL;\nPFNGLSECONDARYCOLOR3SEXTPROC glad_glSecondaryColor3sEXT = NULL;\nPFNGLSECONDARYCOLOR3SVEXTPROC glad_glSecondaryColor3svEXT = NULL;\nPFNGLSECONDARYCOLOR3UBEXTPROC glad_glSecondaryColor3ubEXT = NULL;\nPFNGLSECONDARYCOLOR3UBVEXTPROC glad_glSecondaryColor3ubvEXT = NULL;\nPFNGLSECONDARYCOLOR3UIEXTPROC glad_glSecondaryColor3uiEXT = NULL;\nPFNGLSECONDARYCOLOR3UIVEXTPROC glad_glSecondaryColor3uivEXT = NULL;\nPFNGLSECONDARYCOLOR3USEXTPROC glad_glSecondaryColor3usEXT = NULL;\nPFNGLSECONDARYCOLOR3USVEXTPROC glad_glSecondaryColor3usvEXT = NULL;\nPFNGLSECONDARYCOLORPOINTEREXTPROC glad_glSecondaryColorPointerEXT = NULL;\nPFNGLGENSEMAPHORESEXTPROC glad_glGenSemaphoresEXT = NULL;\nPFNGLDELETESEMAPHORESEXTPROC glad_glDeleteSemaphoresEXT = NULL;\nPFNGLISSEMAPHOREEXTPROC glad_glIsSemaphoreEXT = NULL;\nPFNGLSEMAPHOREPARAMETERUI64VEXTPROC glad_glSemaphoreParameterui64vEXT = NULL;\nPFNGLGETSEMAPHOREPARAMETERUI64VEXTPROC glad_glGetSemaphoreParameterui64vEXT = NULL;\nPFNGLWAITSEMAPHOREEXTPROC glad_glWaitSemaphoreEXT = NULL;\nPFNGLSIGNALSEMAPHOREEXTPROC glad_glSignalSemaphoreEXT = NULL;\nPFNGLIMPORTSEMAPHOREFDEXTPROC glad_glImportSemaphoreFdEXT = NULL;\nPFNGLIMPORTSEMAPHOREWIN32HANDLEEXTPROC glad_glImportSemaphoreWin32HandleEXT = NULL;\nPFNGLIMPORTSEMAPHOREWIN32NAMEEXTPROC glad_glImportSemaphoreWin32NameEXT = NULL;\nPFNGLUSESHADERPROGRAMEXTPROC glad_glUseShaderProgramEXT = NULL;\nPFNGLACTIVEPROGRAMEXTPROC glad_glActiveProgramEXT = NULL;\nPFNGLCREATESHADERPROGRAMEXTPROC glad_glCreateShaderProgramEXT = NULL;\nPFNGLACTIVESHADERPROGRAMEXTPROC glad_glActiveShaderProgramEXT = NULL;\nPFNGLBINDPROGRAMPIPELINEEXTPROC glad_glBindProgramPipelineEXT = NULL;\nPFNGLCREATESHADERPROGRAMVEXTPROC glad_glCreateShaderProgramvEXT = NULL;\nPFNGLDELETEPROGRAMPIPELINESEXTPROC glad_glDeleteProgramPipelinesEXT = NULL;\nPFNGLGENPROGRAMPIPELINESEXTPROC glad_glGenProgramPipelinesEXT = NULL;\nPFNGLGETPROGRAMPIPELINEINFOLOGEXTPROC glad_glGetProgramPipelineInfoLogEXT = NULL;\nPFNGLGETPROGRAMPIPELINEIVEXTPROC glad_glGetProgramPipelineivEXT = NULL;\nPFNGLISPROGRAMPIPELINEEXTPROC glad_glIsProgramPipelineEXT = NULL;\nPFNGLUSEPROGRAMSTAGESEXTPROC glad_glUseProgramStagesEXT = NULL;\nPFNGLVALIDATEPROGRAMPIPELINEEXTPROC glad_glValidateProgramPipelineEXT = NULL;\nPFNGLFRAMEBUFFERFETCHBARRIEREXTPROC glad_glFramebufferFetchBarrierEXT = NULL;\nPFNGLBINDIMAGETEXTUREEXTPROC glad_glBindImageTextureEXT = NULL;\nPFNGLMEMORYBARRIEREXTPROC glad_glMemoryBarrierEXT = NULL;\nPFNGLSTENCILCLEARTAGEXTPROC glad_glStencilClearTagEXT = NULL;\nPFNGLACTIVESTENCILFACEEXTPROC glad_glActiveStencilFaceEXT = NULL;\nPFNGLTEXSUBIMAGE1DEXTPROC glad_glTexSubImage1DEXT = NULL;\nPFNGLTEXSUBIMAGE2DEXTPROC glad_glTexSubImage2DEXT = NULL;\nPFNGLTEXIMAGE3DEXTPROC glad_glTexImage3DEXT = NULL;\nPFNGLTEXSUBIMAGE3DEXTPROC glad_glTexSubImage3DEXT = NULL;\nPFNGLFRAMEBUFFERTEXTURELAYEREXTPROC glad_glFramebufferTextureLayerEXT = NULL;\nPFNGLTEXBUFFEREXTPROC glad_glTexBufferEXT = NULL;\nPFNGLTEXPARAMETERIIVEXTPROC glad_glTexParameterIivEXT = NULL;\nPFNGLTEXPARAMETERIUIVEXTPROC glad_glTexParameterIuivEXT = NULL;\nPFNGLGETTEXPARAMETERIIVEXTPROC glad_glGetTexParameterIivEXT = NULL;\nPFNGLGETTEXPARAMETERIUIVEXTPROC glad_glGetTexParameterIuivEXT = NULL;\nPFNGLCLEARCOLORIIEXTPROC glad_glClearColorIiEXT = NULL;\nPFNGLCLEARCOLORIUIEXTPROC glad_glClearColorIuiEXT = NULL;\nPFNGLARETEXTURESRESIDENTEXTPROC glad_glAreTexturesResidentEXT = NULL;\nPFNGLBINDTEXTUREEXTPROC glad_glBindTextureEXT = NULL;\nPFNGLDELETETEXTURESEXTPROC glad_glDeleteTexturesEXT = NULL;\nPFNGLGENTEXTURESEXTPROC glad_glGenTexturesEXT = NULL;\nPFNGLISTEXTUREEXTPROC glad_glIsTextureEXT = NULL;\nPFNGLPRIORITIZETEXTURESEXTPROC glad_glPrioritizeTexturesEXT = NULL;\nPFNGLTEXTURENORMALEXTPROC glad_glTextureNormalEXT = NULL;\nPFNGLGETQUERYOBJECTI64VEXTPROC glad_glGetQueryObjecti64vEXT = NULL;\nPFNGLGETQUERYOBJECTUI64VEXTPROC glad_glGetQueryObjectui64vEXT = NULL;\nPFNGLBEGINTRANSFORMFEEDBACKEXTPROC glad_glBeginTransformFeedbackEXT = NULL;\nPFNGLENDTRANSFORMFEEDBACKEXTPROC glad_glEndTransformFeedbackEXT = NULL;\nPFNGLBINDBUFFERRANGEEXTPROC glad_glBindBufferRangeEXT = NULL;\nPFNGLBINDBUFFEROFFSETEXTPROC glad_glBindBufferOffsetEXT = NULL;\nPFNGLBINDBUFFERBASEEXTPROC glad_glBindBufferBaseEXT = NULL;\nPFNGLTRANSFORMFEEDBACKVARYINGSEXTPROC glad_glTransformFeedbackVaryingsEXT = NULL;\nPFNGLGETTRANSFORMFEEDBACKVARYINGEXTPROC glad_glGetTransformFeedbackVaryingEXT = NULL;\nPFNGLARRAYELEMENTEXTPROC glad_glArrayElementEXT = NULL;\nPFNGLCOLORPOINTEREXTPROC glad_glColorPointerEXT = NULL;\nPFNGLDRAWARRAYSEXTPROC glad_glDrawArraysEXT = NULL;\nPFNGLEDGEFLAGPOINTEREXTPROC glad_glEdgeFlagPointerEXT = NULL;\nPFNGLGETPOINTERVEXTPROC glad_glGetPointervEXT = NULL;\nPFNGLINDEXPOINTEREXTPROC glad_glIndexPointerEXT = NULL;\nPFNGLNORMALPOINTEREXTPROC glad_glNormalPointerEXT = NULL;\nPFNGLTEXCOORDPOINTEREXTPROC glad_glTexCoordPointerEXT = NULL;\nPFNGLVERTEXPOINTEREXTPROC glad_glVertexPointerEXT = NULL;\nPFNGLVERTEXATTRIBL1DEXTPROC glad_glVertexAttribL1dEXT = NULL;\nPFNGLVERTEXATTRIBL2DEXTPROC glad_glVertexAttribL2dEXT = NULL;\nPFNGLVERTEXATTRIBL3DEXTPROC glad_glVertexAttribL3dEXT = NULL;\nPFNGLVERTEXATTRIBL4DEXTPROC glad_glVertexAttribL4dEXT = NULL;\nPFNGLVERTEXATTRIBL1DVEXTPROC glad_glVertexAttribL1dvEXT = NULL;\nPFNGLVERTEXATTRIBL2DVEXTPROC glad_glVertexAttribL2dvEXT = NULL;\nPFNGLVERTEXATTRIBL3DVEXTPROC glad_glVertexAttribL3dvEXT = NULL;\nPFNGLVERTEXATTRIBL4DVEXTPROC glad_glVertexAttribL4dvEXT = NULL;\nPFNGLVERTEXATTRIBLPOINTEREXTPROC glad_glVertexAttribLPointerEXT = NULL;\nPFNGLGETVERTEXATTRIBLDVEXTPROC glad_glGetVertexAttribLdvEXT = NULL;\nPFNGLBEGINVERTEXSHADEREXTPROC glad_glBeginVertexShaderEXT = NULL;\nPFNGLENDVERTEXSHADEREXTPROC glad_glEndVertexShaderEXT = NULL;\nPFNGLBINDVERTEXSHADEREXTPROC glad_glBindVertexShaderEXT = NULL;\nPFNGLGENVERTEXSHADERSEXTPROC glad_glGenVertexShadersEXT = NULL;\nPFNGLDELETEVERTEXSHADEREXTPROC glad_glDeleteVertexShaderEXT = NULL;\nPFNGLSHADEROP1EXTPROC glad_glShaderOp1EXT = NULL;\nPFNGLSHADEROP2EXTPROC glad_glShaderOp2EXT = NULL;\nPFNGLSHADEROP3EXTPROC glad_glShaderOp3EXT = NULL;\nPFNGLSWIZZLEEXTPROC glad_glSwizzleEXT = NULL;\nPFNGLWRITEMASKEXTPROC glad_glWriteMaskEXT = NULL;\nPFNGLINSERTCOMPONENTEXTPROC glad_glInsertComponentEXT = NULL;\nPFNGLEXTRACTCOMPONENTEXTPROC glad_glExtractComponentEXT = NULL;\nPFNGLGENSYMBOLSEXTPROC glad_glGenSymbolsEXT = NULL;\nPFNGLSETINVARIANTEXTPROC glad_glSetInvariantEXT = NULL;\nPFNGLSETLOCALCONSTANTEXTPROC glad_glSetLocalConstantEXT = NULL;\nPFNGLVARIANTBVEXTPROC glad_glVariantbvEXT = NULL;\nPFNGLVARIANTSVEXTPROC glad_glVariantsvEXT = NULL;\nPFNGLVARIANTIVEXTPROC glad_glVariantivEXT = NULL;\nPFNGLVARIANTFVEXTPROC glad_glVariantfvEXT = NULL;\nPFNGLVARIANTDVEXTPROC glad_glVariantdvEXT = NULL;\nPFNGLVARIANTUBVEXTPROC glad_glVariantubvEXT = NULL;\nPFNGLVARIANTUSVEXTPROC glad_glVariantusvEXT = NULL;\nPFNGLVARIANTUIVEXTPROC glad_glVariantuivEXT = NULL;\nPFNGLVARIANTPOINTEREXTPROC glad_glVariantPointerEXT = NULL;\nPFNGLENABLEVARIANTCLIENTSTATEEXTPROC glad_glEnableVariantClientStateEXT = NULL;\nPFNGLDISABLEVARIANTCLIENTSTATEEXTPROC glad_glDisableVariantClientStateEXT = NULL;\nPFNGLBINDLIGHTPARAMETEREXTPROC glad_glBindLightParameterEXT = NULL;\nPFNGLBINDMATERIALPARAMETEREXTPROC glad_glBindMaterialParameterEXT = NULL;\nPFNGLBINDTEXGENPARAMETEREXTPROC glad_glBindTexGenParameterEXT = NULL;\nPFNGLBINDTEXTUREUNITPARAMETEREXTPROC glad_glBindTextureUnitParameterEXT = NULL;\nPFNGLBINDPARAMETEREXTPROC glad_glBindParameterEXT = NULL;\nPFNGLISVARIANTENABLEDEXTPROC glad_glIsVariantEnabledEXT = NULL;\nPFNGLGETVARIANTBOOLEANVEXTPROC glad_glGetVariantBooleanvEXT = NULL;\nPFNGLGETVARIANTINTEGERVEXTPROC glad_glGetVariantIntegervEXT = NULL;\nPFNGLGETVARIANTFLOATVEXTPROC glad_glGetVariantFloatvEXT = NULL;\nPFNGLGETVARIANTPOINTERVEXTPROC glad_glGetVariantPointervEXT = NULL;\nPFNGLGETINVARIANTBOOLEANVEXTPROC glad_glGetInvariantBooleanvEXT = NULL;\nPFNGLGETINVARIANTINTEGERVEXTPROC glad_glGetInvariantIntegervEXT = NULL;\nPFNGLGETINVARIANTFLOATVEXTPROC glad_glGetInvariantFloatvEXT = NULL;\nPFNGLGETLOCALCONSTANTBOOLEANVEXTPROC glad_glGetLocalConstantBooleanvEXT = NULL;\nPFNGLGETLOCALCONSTANTINTEGERVEXTPROC glad_glGetLocalConstantIntegervEXT = NULL;\nPFNGLGETLOCALCONSTANTFLOATVEXTPROC glad_glGetLocalConstantFloatvEXT = NULL;\nPFNGLVERTEXWEIGHTFEXTPROC glad_glVertexWeightfEXT = NULL;\nPFNGLVERTEXWEIGHTFVEXTPROC glad_glVertexWeightfvEXT = NULL;\nPFNGLVERTEXWEIGHTPOINTEREXTPROC glad_glVertexWeightPointerEXT = NULL;\nPFNGLACQUIREKEYEDMUTEXWIN32EXTPROC glad_glAcquireKeyedMutexWin32EXT = NULL;\nPFNGLRELEASEKEYEDMUTEXWIN32EXTPROC glad_glReleaseKeyedMutexWin32EXT = NULL;\nPFNGLWINDOWRECTANGLESEXTPROC glad_glWindowRectanglesEXT = NULL;\nPFNGLIMPORTSYNCEXTPROC glad_glImportSyncEXT = NULL;\nPFNGLFRAMETERMINATORGREMEDYPROC glad_glFrameTerminatorGREMEDY = NULL;\nPFNGLSTRINGMARKERGREMEDYPROC glad_glStringMarkerGREMEDY = NULL;\nPFNGLIMAGETRANSFORMPARAMETERIHPPROC glad_glImageTransformParameteriHP = NULL;\nPFNGLIMAGETRANSFORMPARAMETERFHPPROC glad_glImageTransformParameterfHP = NULL;\nPFNGLIMAGETRANSFORMPARAMETERIVHPPROC glad_glImageTransformParameterivHP = NULL;\nPFNGLIMAGETRANSFORMPARAMETERFVHPPROC glad_glImageTransformParameterfvHP = NULL;\nPFNGLGETIMAGETRANSFORMPARAMETERIVHPPROC glad_glGetImageTransformParameterivHP = NULL;\nPFNGLGETIMAGETRANSFORMPARAMETERFVHPPROC glad_glGetImageTransformParameterfvHP = NULL;\nPFNGLMULTIMODEDRAWARRAYSIBMPROC glad_glMultiModeDrawArraysIBM = NULL;\nPFNGLMULTIMODEDRAWELEMENTSIBMPROC glad_glMultiModeDrawElementsIBM = NULL;\nPFNGLFLUSHSTATICDATAIBMPROC glad_glFlushStaticDataIBM = NULL;\nPFNGLCOLORPOINTERLISTIBMPROC glad_glColorPointerListIBM = NULL;\nPFNGLSECONDARYCOLORPOINTERLISTIBMPROC glad_glSecondaryColorPointerListIBM = NULL;\nPFNGLEDGEFLAGPOINTERLISTIBMPROC glad_glEdgeFlagPointerListIBM = NULL;\nPFNGLFOGCOORDPOINTERLISTIBMPROC glad_glFogCoordPointerListIBM = NULL;\nPFNGLINDEXPOINTERLISTIBMPROC glad_glIndexPointerListIBM = NULL;\nPFNGLNORMALPOINTERLISTIBMPROC glad_glNormalPointerListIBM = NULL;\nPFNGLTEXCOORDPOINTERLISTIBMPROC glad_glTexCoordPointerListIBM = NULL;\nPFNGLVERTEXPOINTERLISTIBMPROC glad_glVertexPointerListIBM = NULL;\nPFNGLBLENDFUNCSEPARATEINGRPROC glad_glBlendFuncSeparateINGR = NULL;\nPFNGLAPPLYFRAMEBUFFERATTACHMENTCMAAINTELPROC glad_glApplyFramebufferAttachmentCMAAINTEL = NULL;\nPFNGLSYNCTEXTUREINTELPROC glad_glSyncTextureINTEL = NULL;\nPFNGLUNMAPTEXTURE2DINTELPROC glad_glUnmapTexture2DINTEL = NULL;\nPFNGLMAPTEXTURE2DINTELPROC glad_glMapTexture2DINTEL = NULL;\nPFNGLVERTEXPOINTERVINTELPROC glad_glVertexPointervINTEL = NULL;\nPFNGLNORMALPOINTERVINTELPROC glad_glNormalPointervINTEL = NULL;\nPFNGLCOLORPOINTERVINTELPROC glad_glColorPointervINTEL = NULL;\nPFNGLTEXCOORDPOINTERVINTELPROC glad_glTexCoordPointervINTEL = NULL;\nPFNGLBEGINPERFQUERYINTELPROC glad_glBeginPerfQueryINTEL = NULL;\nPFNGLCREATEPERFQUERYINTELPROC glad_glCreatePerfQueryINTEL = NULL;\nPFNGLDELETEPERFQUERYINTELPROC glad_glDeletePerfQueryINTEL = NULL;\nPFNGLENDPERFQUERYINTELPROC glad_glEndPerfQueryINTEL = NULL;\nPFNGLGETFIRSTPERFQUERYIDINTELPROC glad_glGetFirstPerfQueryIdINTEL = NULL;\nPFNGLGETNEXTPERFQUERYIDINTELPROC glad_glGetNextPerfQueryIdINTEL = NULL;\nPFNGLGETPERFCOUNTERINFOINTELPROC glad_glGetPerfCounterInfoINTEL = NULL;\nPFNGLGETPERFQUERYDATAINTELPROC glad_glGetPerfQueryDataINTEL = NULL;\nPFNGLGETPERFQUERYIDBYNAMEINTELPROC glad_glGetPerfQueryIdByNameINTEL = NULL;\nPFNGLGETPERFQUERYINFOINTELPROC glad_glGetPerfQueryInfoINTEL = NULL;\nPFNGLBLENDBARRIERKHRPROC glad_glBlendBarrierKHR = NULL;\nPFNGLDEBUGMESSAGECONTROLPROC glad_glDebugMessageControl = NULL;\nPFNGLDEBUGMESSAGEINSERTPROC glad_glDebugMessageInsert = NULL;\nPFNGLDEBUGMESSAGECALLBACKPROC glad_glDebugMessageCallback = NULL;\nPFNGLGETDEBUGMESSAGELOGPROC glad_glGetDebugMessageLog = NULL;\nPFNGLPUSHDEBUGGROUPPROC glad_glPushDebugGroup = NULL;\nPFNGLPOPDEBUGGROUPPROC glad_glPopDebugGroup = NULL;\nPFNGLOBJECTLABELPROC glad_glObjectLabel = NULL;\nPFNGLGETOBJECTLABELPROC glad_glGetObjectLabel = NULL;\nPFNGLOBJECTPTRLABELPROC glad_glObjectPtrLabel = NULL;\nPFNGLGETOBJECTPTRLABELPROC glad_glGetObjectPtrLabel = NULL;\nPFNGLGETPOINTERVPROC glad_glGetPointerv = NULL;\nPFNGLDEBUGMESSAGECONTROLKHRPROC glad_glDebugMessageControlKHR = NULL;\nPFNGLDEBUGMESSAGEINSERTKHRPROC glad_glDebugMessageInsertKHR = NULL;\nPFNGLDEBUGMESSAGECALLBACKKHRPROC glad_glDebugMessageCallbackKHR = NULL;\nPFNGLGETDEBUGMESSAGELOGKHRPROC glad_glGetDebugMessageLogKHR = NULL;\nPFNGLPUSHDEBUGGROUPKHRPROC glad_glPushDebugGroupKHR = NULL;\nPFNGLPOPDEBUGGROUPKHRPROC glad_glPopDebugGroupKHR = NULL;\nPFNGLOBJECTLABELKHRPROC glad_glObjectLabelKHR = NULL;\nPFNGLGETOBJECTLABELKHRPROC glad_glGetObjectLabelKHR = NULL;\nPFNGLOBJECTPTRLABELKHRPROC glad_glObjectPtrLabelKHR = NULL;\nPFNGLGETOBJECTPTRLABELKHRPROC glad_glGetObjectPtrLabelKHR = NULL;\nPFNGLGETPOINTERVKHRPROC glad_glGetPointervKHR = NULL;\nPFNGLMAXSHADERCOMPILERTHREADSKHRPROC glad_glMaxShaderCompilerThreadsKHR = NULL;\nPFNGLGETGRAPHICSRESETSTATUSPROC glad_glGetGraphicsResetStatus = NULL;\nPFNGLREADNPIXELSPROC glad_glReadnPixels = NULL;\nPFNGLGETNUNIFORMFVPROC glad_glGetnUniformfv = NULL;\nPFNGLGETNUNIFORMIVPROC glad_glGetnUniformiv = NULL;\nPFNGLGETNUNIFORMUIVPROC glad_glGetnUniformuiv = NULL;\nPFNGLGETGRAPHICSRESETSTATUSKHRPROC glad_glGetGraphicsResetStatusKHR = NULL;\nPFNGLREADNPIXELSKHRPROC glad_glReadnPixelsKHR = NULL;\nPFNGLGETNUNIFORMFVKHRPROC glad_glGetnUniformfvKHR = NULL;\nPFNGLGETNUNIFORMIVKHRPROC glad_glGetnUniformivKHR = NULL;\nPFNGLGETNUNIFORMUIVKHRPROC glad_glGetnUniformuivKHR = NULL;\nPFNGLRESIZEBUFFERSMESAPROC glad_glResizeBuffersMESA = NULL;\nPFNGLWINDOWPOS2DMESAPROC glad_glWindowPos2dMESA = NULL;\nPFNGLWINDOWPOS2DVMESAPROC glad_glWindowPos2dvMESA = NULL;\nPFNGLWINDOWPOS2FMESAPROC glad_glWindowPos2fMESA = NULL;\nPFNGLWINDOWPOS2FVMESAPROC glad_glWindowPos2fvMESA = NULL;\nPFNGLWINDOWPOS2IMESAPROC glad_glWindowPos2iMESA = NULL;\nPFNGLWINDOWPOS2IVMESAPROC glad_glWindowPos2ivMESA = NULL;\nPFNGLWINDOWPOS2SMESAPROC glad_glWindowPos2sMESA = NULL;\nPFNGLWINDOWPOS2SVMESAPROC glad_glWindowPos2svMESA = NULL;\nPFNGLWINDOWPOS3DMESAPROC glad_glWindowPos3dMESA = NULL;\nPFNGLWINDOWPOS3DVMESAPROC glad_glWindowPos3dvMESA = NULL;\nPFNGLWINDOWPOS3FMESAPROC glad_glWindowPos3fMESA = NULL;\nPFNGLWINDOWPOS3FVMESAPROC glad_glWindowPos3fvMESA = NULL;\nPFNGLWINDOWPOS3IMESAPROC glad_glWindowPos3iMESA = NULL;\nPFNGLWINDOWPOS3IVMESAPROC glad_glWindowPos3ivMESA = NULL;\nPFNGLWINDOWPOS3SMESAPROC glad_glWindowPos3sMESA = NULL;\nPFNGLWINDOWPOS3SVMESAPROC glad_glWindowPos3svMESA = NULL;\nPFNGLWINDOWPOS4DMESAPROC glad_glWindowPos4dMESA = NULL;\nPFNGLWINDOWPOS4DVMESAPROC glad_glWindowPos4dvMESA = NULL;\nPFNGLWINDOWPOS4FMESAPROC glad_glWindowPos4fMESA = NULL;\nPFNGLWINDOWPOS4FVMESAPROC glad_glWindowPos4fvMESA = NULL;\nPFNGLWINDOWPOS4IMESAPROC glad_glWindowPos4iMESA = NULL;\nPFNGLWINDOWPOS4IVMESAPROC glad_glWindowPos4ivMESA = NULL;\nPFNGLWINDOWPOS4SMESAPROC glad_glWindowPos4sMESA = NULL;\nPFNGLWINDOWPOS4SVMESAPROC glad_glWindowPos4svMESA = NULL;\nPFNGLBEGINCONDITIONALRENDERNVXPROC glad_glBeginConditionalRenderNVX = NULL;\nPFNGLENDCONDITIONALRENDERNVXPROC glad_glEndConditionalRenderNVX = NULL;\nPFNGLLGPUNAMEDBUFFERSUBDATANVXPROC glad_glLGPUNamedBufferSubDataNVX = NULL;\nPFNGLLGPUCOPYIMAGESUBDATANVXPROC glad_glLGPUCopyImageSubDataNVX = NULL;\nPFNGLLGPUINTERLOCKNVXPROC glad_glLGPUInterlockNVX = NULL;\nPFNGLALPHATOCOVERAGEDITHERCONTROLNVPROC glad_glAlphaToCoverageDitherControlNV = NULL;\nPFNGLMULTIDRAWARRAYSINDIRECTBINDLESSNVPROC glad_glMultiDrawArraysIndirectBindlessNV = NULL;\nPFNGLMULTIDRAWELEMENTSINDIRECTBINDLESSNVPROC glad_glMultiDrawElementsIndirectBindlessNV = NULL;\nPFNGLMULTIDRAWARRAYSINDIRECTBINDLESSCOUNTNVPROC glad_glMultiDrawArraysIndirectBindlessCountNV = NULL;\nPFNGLMULTIDRAWELEMENTSINDIRECTBINDLESSCOUNTNVPROC glad_glMultiDrawElementsIndirectBindlessCountNV = NULL;\nPFNGLGETTEXTUREHANDLENVPROC glad_glGetTextureHandleNV = NULL;\nPFNGLGETTEXTURESAMPLERHANDLENVPROC glad_glGetTextureSamplerHandleNV = NULL;\nPFNGLMAKETEXTUREHANDLERESIDENTNVPROC glad_glMakeTextureHandleResidentNV = NULL;\nPFNGLMAKETEXTUREHANDLENONRESIDENTNVPROC glad_glMakeTextureHandleNonResidentNV = NULL;\nPFNGLGETIMAGEHANDLENVPROC glad_glGetImageHandleNV = NULL;\nPFNGLMAKEIMAGEHANDLERESIDENTNVPROC glad_glMakeImageHandleResidentNV = NULL;\nPFNGLMAKEIMAGEHANDLENONRESIDENTNVPROC glad_glMakeImageHandleNonResidentNV = NULL;\nPFNGLUNIFORMHANDLEUI64NVPROC glad_glUniformHandleui64NV = NULL;\nPFNGLUNIFORMHANDLEUI64VNVPROC glad_glUniformHandleui64vNV = NULL;\nPFNGLPROGRAMUNIFORMHANDLEUI64NVPROC glad_glProgramUniformHandleui64NV = NULL;\nPFNGLPROGRAMUNIFORMHANDLEUI64VNVPROC glad_glProgramUniformHandleui64vNV = NULL;\nPFNGLISTEXTUREHANDLERESIDENTNVPROC glad_glIsTextureHandleResidentNV = NULL;\nPFNGLISIMAGEHANDLERESIDENTNVPROC glad_glIsImageHandleResidentNV = NULL;\nPFNGLBLENDPARAMETERINVPROC glad_glBlendParameteriNV = NULL;\nPFNGLBLENDBARRIERNVPROC glad_glBlendBarrierNV = NULL;\nPFNGLVIEWPORTPOSITIONWSCALENVPROC glad_glViewportPositionWScaleNV = NULL;\nPFNGLCREATESTATESNVPROC glad_glCreateStatesNV = NULL;\nPFNGLDELETESTATESNVPROC glad_glDeleteStatesNV = NULL;\nPFNGLISSTATENVPROC glad_glIsStateNV = NULL;\nPFNGLSTATECAPTURENVPROC glad_glStateCaptureNV = NULL;\nPFNGLGETCOMMANDHEADERNVPROC glad_glGetCommandHeaderNV = NULL;\nPFNGLGETSTAGEINDEXNVPROC glad_glGetStageIndexNV = NULL;\nPFNGLDRAWCOMMANDSNVPROC glad_glDrawCommandsNV = NULL;\nPFNGLDRAWCOMMANDSADDRESSNVPROC glad_glDrawCommandsAddressNV = NULL;\nPFNGLDRAWCOMMANDSSTATESNVPROC glad_glDrawCommandsStatesNV = NULL;\nPFNGLDRAWCOMMANDSSTATESADDRESSNVPROC glad_glDrawCommandsStatesAddressNV = NULL;\nPFNGLCREATECOMMANDLISTSNVPROC glad_glCreateCommandListsNV = NULL;\nPFNGLDELETECOMMANDLISTSNVPROC glad_glDeleteCommandListsNV = NULL;\nPFNGLISCOMMANDLISTNVPROC glad_glIsCommandListNV = NULL;\nPFNGLLISTDRAWCOMMANDSSTATESCLIENTNVPROC glad_glListDrawCommandsStatesClientNV = NULL;\nPFNGLCOMMANDLISTSEGMENTSNVPROC glad_glCommandListSegmentsNV = NULL;\nPFNGLCOMPILECOMMANDLISTNVPROC glad_glCompileCommandListNV = NULL;\nPFNGLCALLCOMMANDLISTNVPROC glad_glCallCommandListNV = NULL;\nPFNGLBEGINCONDITIONALRENDERNVPROC glad_glBeginConditionalRenderNV = NULL;\nPFNGLENDCONDITIONALRENDERNVPROC glad_glEndConditionalRenderNV = NULL;\nPFNGLSUBPIXELPRECISIONBIASNVPROC glad_glSubpixelPrecisionBiasNV = NULL;\nPFNGLCONSERVATIVERASTERPARAMETERFNVPROC glad_glConservativeRasterParameterfNV = NULL;\nPFNGLCONSERVATIVERASTERPARAMETERINVPROC glad_glConservativeRasterParameteriNV = NULL;\nPFNGLCOPYIMAGESUBDATANVPROC glad_glCopyImageSubDataNV = NULL;\nPFNGLDEPTHRANGEDNVPROC glad_glDepthRangedNV = NULL;\nPFNGLCLEARDEPTHDNVPROC glad_glClearDepthdNV = NULL;\nPFNGLDEPTHBOUNDSDNVPROC glad_glDepthBoundsdNV = NULL;\nPFNGLDRAWTEXTURENVPROC glad_glDrawTextureNV = NULL;\nPFNGLDRAWVKIMAGENVPROC glad_glDrawVkImageNV = NULL;\nPFNGLGETVKPROCADDRNVPROC glad_glGetVkProcAddrNV = NULL;\nPFNGLWAITVKSEMAPHORENVPROC glad_glWaitVkSemaphoreNV = NULL;\nPFNGLSIGNALVKSEMAPHORENVPROC glad_glSignalVkSemaphoreNV = NULL;\nPFNGLSIGNALVKFENCENVPROC glad_glSignalVkFenceNV = NULL;\nPFNGLMAPCONTROLPOINTSNVPROC glad_glMapControlPointsNV = NULL;\nPFNGLMAPPARAMETERIVNVPROC glad_glMapParameterivNV = NULL;\nPFNGLMAPPARAMETERFVNVPROC glad_glMapParameterfvNV = NULL;\nPFNGLGETMAPCONTROLPOINTSNVPROC glad_glGetMapControlPointsNV = NULL;\nPFNGLGETMAPPARAMETERIVNVPROC glad_glGetMapParameterivNV = NULL;\nPFNGLGETMAPPARAMETERFVNVPROC glad_glGetMapParameterfvNV = NULL;\nPFNGLGETMAPATTRIBPARAMETERIVNVPROC glad_glGetMapAttribParameterivNV = NULL;\nPFNGLGETMAPATTRIBPARAMETERFVNVPROC glad_glGetMapAttribParameterfvNV = NULL;\nPFNGLEVALMAPSNVPROC glad_glEvalMapsNV = NULL;\nPFNGLGETMULTISAMPLEFVNVPROC glad_glGetMultisamplefvNV = NULL;\nPFNGLSAMPLEMASKINDEXEDNVPROC glad_glSampleMaskIndexedNV = NULL;\nPFNGLTEXRENDERBUFFERNVPROC glad_glTexRenderbufferNV = NULL;\nPFNGLDELETEFENCESNVPROC glad_glDeleteFencesNV = NULL;\nPFNGLGENFENCESNVPROC glad_glGenFencesNV = NULL;\nPFNGLISFENCENVPROC glad_glIsFenceNV = NULL;\nPFNGLTESTFENCENVPROC glad_glTestFenceNV = NULL;\nPFNGLGETFENCEIVNVPROC glad_glGetFenceivNV = NULL;\nPFNGLFINISHFENCENVPROC glad_glFinishFenceNV = NULL;\nPFNGLSETFENCENVPROC glad_glSetFenceNV = NULL;\nPFNGLFRAGMENTCOVERAGECOLORNVPROC glad_glFragmentCoverageColorNV = NULL;\nPFNGLPROGRAMNAMEDPARAMETER4FNVPROC glad_glProgramNamedParameter4fNV = NULL;\nPFNGLPROGRAMNAMEDPARAMETER4FVNVPROC glad_glProgramNamedParameter4fvNV = NULL;\nPFNGLPROGRAMNAMEDPARAMETER4DNVPROC glad_glProgramNamedParameter4dNV = NULL;\nPFNGLPROGRAMNAMEDPARAMETER4DVNVPROC glad_glProgramNamedParameter4dvNV = NULL;\nPFNGLGETPROGRAMNAMEDPARAMETERFVNVPROC glad_glGetProgramNamedParameterfvNV = NULL;\nPFNGLGETPROGRAMNAMEDPARAMETERDVNVPROC glad_glGetProgramNamedParameterdvNV = NULL;\nPFNGLCOVERAGEMODULATIONTABLENVPROC glad_glCoverageModulationTableNV = NULL;\nPFNGLGETCOVERAGEMODULATIONTABLENVPROC glad_glGetCoverageModulationTableNV = NULL;\nPFNGLCOVERAGEMODULATIONNVPROC glad_glCoverageModulationNV = NULL;\nPFNGLRENDERBUFFERSTORAGEMULTISAMPLECOVERAGENVPROC glad_glRenderbufferStorageMultisampleCoverageNV = NULL;\nPFNGLPROGRAMVERTEXLIMITNVPROC glad_glProgramVertexLimitNV = NULL;\nPFNGLFRAMEBUFFERTEXTUREEXTPROC glad_glFramebufferTextureEXT = NULL;\nPFNGLFRAMEBUFFERTEXTUREFACEEXTPROC glad_glFramebufferTextureFaceEXT = NULL;\nPFNGLRENDERGPUMASKNVPROC glad_glRenderGpuMaskNV = NULL;\nPFNGLMULTICASTBUFFERSUBDATANVPROC glad_glMulticastBufferSubDataNV = NULL;\nPFNGLMULTICASTCOPYBUFFERSUBDATANVPROC glad_glMulticastCopyBufferSubDataNV = NULL;\nPFNGLMULTICASTCOPYIMAGESUBDATANVPROC glad_glMulticastCopyImageSubDataNV = NULL;\nPFNGLMULTICASTBLITFRAMEBUFFERNVPROC glad_glMulticastBlitFramebufferNV = NULL;\nPFNGLMULTICASTFRAMEBUFFERSAMPLELOCATIONSFVNVPROC glad_glMulticastFramebufferSampleLocationsfvNV = NULL;\nPFNGLMULTICASTBARRIERNVPROC glad_glMulticastBarrierNV = NULL;\nPFNGLMULTICASTWAITSYNCNVPROC glad_glMulticastWaitSyncNV = NULL;\nPFNGLMULTICASTGETQUERYOBJECTIVNVPROC glad_glMulticastGetQueryObjectivNV = NULL;\nPFNGLMULTICASTGETQUERYOBJECTUIVNVPROC glad_glMulticastGetQueryObjectuivNV = NULL;\nPFNGLMULTICASTGETQUERYOBJECTI64VNVPROC glad_glMulticastGetQueryObjecti64vNV = NULL;\nPFNGLMULTICASTGETQUERYOBJECTUI64VNVPROC glad_glMulticastGetQueryObjectui64vNV = NULL;\nPFNGLPROGRAMLOCALPARAMETERI4INVPROC glad_glProgramLocalParameterI4iNV = NULL;\nPFNGLPROGRAMLOCALPARAMETERI4IVNVPROC glad_glProgramLocalParameterI4ivNV = NULL;\nPFNGLPROGRAMLOCALPARAMETERSI4IVNVPROC glad_glProgramLocalParametersI4ivNV = NULL;\nPFNGLPROGRAMLOCALPARAMETERI4UINVPROC glad_glProgramLocalParameterI4uiNV = NULL;\nPFNGLPROGRAMLOCALPARAMETERI4UIVNVPROC glad_glProgramLocalParameterI4uivNV = NULL;\nPFNGLPROGRAMLOCALPARAMETERSI4UIVNVPROC glad_glProgramLocalParametersI4uivNV = NULL;\nPFNGLPROGRAMENVPARAMETERI4INVPROC glad_glProgramEnvParameterI4iNV = NULL;\nPFNGLPROGRAMENVPARAMETERI4IVNVPROC glad_glProgramEnvParameterI4ivNV = NULL;\nPFNGLPROGRAMENVPARAMETERSI4IVNVPROC glad_glProgramEnvParametersI4ivNV = NULL;\nPFNGLPROGRAMENVPARAMETERI4UINVPROC glad_glProgramEnvParameterI4uiNV = NULL;\nPFNGLPROGRAMENVPARAMETERI4UIVNVPROC glad_glProgramEnvParameterI4uivNV = NULL;\nPFNGLPROGRAMENVPARAMETERSI4UIVNVPROC glad_glProgramEnvParametersI4uivNV = NULL;\nPFNGLGETPROGRAMLOCALPARAMETERIIVNVPROC glad_glGetProgramLocalParameterIivNV = NULL;\nPFNGLGETPROGRAMLOCALPARAMETERIUIVNVPROC glad_glGetProgramLocalParameterIuivNV = NULL;\nPFNGLGETPROGRAMENVPARAMETERIIVNVPROC glad_glGetProgramEnvParameterIivNV = NULL;\nPFNGLGETPROGRAMENVPARAMETERIUIVNVPROC glad_glGetProgramEnvParameterIuivNV = NULL;\nPFNGLPROGRAMSUBROUTINEPARAMETERSUIVNVPROC glad_glProgramSubroutineParametersuivNV = NULL;\nPFNGLGETPROGRAMSUBROUTINEPARAMETERUIVNVPROC glad_glGetProgramSubroutineParameteruivNV = NULL;\nPFNGLVERTEX2HNVPROC glad_glVertex2hNV = NULL;\nPFNGLVERTEX2HVNVPROC glad_glVertex2hvNV = NULL;\nPFNGLVERTEX3HNVPROC glad_glVertex3hNV = NULL;\nPFNGLVERTEX3HVNVPROC glad_glVertex3hvNV = NULL;\nPFNGLVERTEX4HNVPROC glad_glVertex4hNV = NULL;\nPFNGLVERTEX4HVNVPROC glad_glVertex4hvNV = NULL;\nPFNGLNORMAL3HNVPROC glad_glNormal3hNV = NULL;\nPFNGLNORMAL3HVNVPROC glad_glNormal3hvNV = NULL;\nPFNGLCOLOR3HNVPROC glad_glColor3hNV = NULL;\nPFNGLCOLOR3HVNVPROC glad_glColor3hvNV = NULL;\nPFNGLCOLOR4HNVPROC glad_glColor4hNV = NULL;\nPFNGLCOLOR4HVNVPROC glad_glColor4hvNV = NULL;\nPFNGLTEXCOORD1HNVPROC glad_glTexCoord1hNV = NULL;\nPFNGLTEXCOORD1HVNVPROC glad_glTexCoord1hvNV = NULL;\nPFNGLTEXCOORD2HNVPROC glad_glTexCoord2hNV = NULL;\nPFNGLTEXCOORD2HVNVPROC glad_glTexCoord2hvNV = NULL;\nPFNGLTEXCOORD3HNVPROC glad_glTexCoord3hNV = NULL;\nPFNGLTEXCOORD3HVNVPROC glad_glTexCoord3hvNV = NULL;\nPFNGLTEXCOORD4HNVPROC glad_glTexCoord4hNV = NULL;\nPFNGLTEXCOORD4HVNVPROC glad_glTexCoord4hvNV = NULL;\nPFNGLMULTITEXCOORD1HNVPROC glad_glMultiTexCoord1hNV = NULL;\nPFNGLMULTITEXCOORD1HVNVPROC glad_glMultiTexCoord1hvNV = NULL;\nPFNGLMULTITEXCOORD2HNVPROC glad_glMultiTexCoord2hNV = NULL;\nPFNGLMULTITEXCOORD2HVNVPROC glad_glMultiTexCoord2hvNV = NULL;\nPFNGLMULTITEXCOORD3HNVPROC glad_glMultiTexCoord3hNV = NULL;\nPFNGLMULTITEXCOORD3HVNVPROC glad_glMultiTexCoord3hvNV = NULL;\nPFNGLMULTITEXCOORD4HNVPROC glad_glMultiTexCoord4hNV = NULL;\nPFNGLMULTITEXCOORD4HVNVPROC glad_glMultiTexCoord4hvNV = NULL;\nPFNGLFOGCOORDHNVPROC glad_glFogCoordhNV = NULL;\nPFNGLFOGCOORDHVNVPROC glad_glFogCoordhvNV = NULL;\nPFNGLSECONDARYCOLOR3HNVPROC glad_glSecondaryColor3hNV = NULL;\nPFNGLSECONDARYCOLOR3HVNVPROC glad_glSecondaryColor3hvNV = NULL;\nPFNGLVERTEXWEIGHTHNVPROC glad_glVertexWeighthNV = NULL;\nPFNGLVERTEXWEIGHTHVNVPROC glad_glVertexWeighthvNV = NULL;\nPFNGLVERTEXATTRIB1HNVPROC glad_glVertexAttrib1hNV = NULL;\nPFNGLVERTEXATTRIB1HVNVPROC glad_glVertexAttrib1hvNV = NULL;\nPFNGLVERTEXATTRIB2HNVPROC glad_glVertexAttrib2hNV = NULL;\nPFNGLVERTEXATTRIB2HVNVPROC glad_glVertexAttrib2hvNV = NULL;\nPFNGLVERTEXATTRIB3HNVPROC glad_glVertexAttrib3hNV = NULL;\nPFNGLVERTEXATTRIB3HVNVPROC glad_glVertexAttrib3hvNV = NULL;\nPFNGLVERTEXATTRIB4HNVPROC glad_glVertexAttrib4hNV = NULL;\nPFNGLVERTEXATTRIB4HVNVPROC glad_glVertexAttrib4hvNV = NULL;\nPFNGLVERTEXATTRIBS1HVNVPROC glad_glVertexAttribs1hvNV = NULL;\nPFNGLVERTEXATTRIBS2HVNVPROC glad_glVertexAttribs2hvNV = NULL;\nPFNGLVERTEXATTRIBS3HVNVPROC glad_glVertexAttribs3hvNV = NULL;\nPFNGLVERTEXATTRIBS4HVNVPROC glad_glVertexAttribs4hvNV = NULL;\nPFNGLGETINTERNALFORMATSAMPLEIVNVPROC glad_glGetInternalformatSampleivNV = NULL;\nPFNGLGETMEMORYOBJECTDETACHEDRESOURCESUIVNVPROC glad_glGetMemoryObjectDetachedResourcesuivNV = NULL;\nPFNGLRESETMEMORYOBJECTPARAMETERNVPROC glad_glResetMemoryObjectParameterNV = NULL;\nPFNGLTEXATTACHMEMORYNVPROC glad_glTexAttachMemoryNV = NULL;\nPFNGLBUFFERATTACHMEMORYNVPROC glad_glBufferAttachMemoryNV = NULL;\nPFNGLTEXTUREATTACHMEMORYNVPROC glad_glTextureAttachMemoryNV = NULL;\nPFNGLNAMEDBUFFERATTACHMEMORYNVPROC glad_glNamedBufferAttachMemoryNV = NULL;\nPFNGLDRAWMESHTASKSNVPROC glad_glDrawMeshTasksNV = NULL;\nPFNGLDRAWMESHTASKSINDIRECTNVPROC glad_glDrawMeshTasksIndirectNV = NULL;\nPFNGLMULTIDRAWMESHTASKSINDIRECTNVPROC glad_glMultiDrawMeshTasksIndirectNV = NULL;\nPFNGLMULTIDRAWMESHTASKSINDIRECTCOUNTNVPROC glad_glMultiDrawMeshTasksIndirectCountNV = NULL;\nPFNGLGENOCCLUSIONQUERIESNVPROC glad_glGenOcclusionQueriesNV = NULL;\nPFNGLDELETEOCCLUSIONQUERIESNVPROC glad_glDeleteOcclusionQueriesNV = NULL;\nPFNGLISOCCLUSIONQUERYNVPROC glad_glIsOcclusionQueryNV = NULL;\nPFNGLBEGINOCCLUSIONQUERYNVPROC glad_glBeginOcclusionQueryNV = NULL;\nPFNGLENDOCCLUSIONQUERYNVPROC glad_glEndOcclusionQueryNV = NULL;\nPFNGLGETOCCLUSIONQUERYIVNVPROC glad_glGetOcclusionQueryivNV = NULL;\nPFNGLGETOCCLUSIONQUERYUIVNVPROC glad_glGetOcclusionQueryuivNV = NULL;\nPFNGLPROGRAMBUFFERPARAMETERSFVNVPROC glad_glProgramBufferParametersfvNV = NULL;\nPFNGLPROGRAMBUFFERPARAMETERSIIVNVPROC glad_glProgramBufferParametersIivNV = NULL;\nPFNGLPROGRAMBUFFERPARAMETERSIUIVNVPROC glad_glProgramBufferParametersIuivNV = NULL;\nPFNGLGENPATHSNVPROC glad_glGenPathsNV = NULL;\nPFNGLDELETEPATHSNVPROC glad_glDeletePathsNV = NULL;\nPFNGLISPATHNVPROC glad_glIsPathNV = NULL;\nPFNGLPATHCOMMANDSNVPROC glad_glPathCommandsNV = NULL;\nPFNGLPATHCOORDSNVPROC glad_glPathCoordsNV = NULL;\nPFNGLPATHSUBCOMMANDSNVPROC glad_glPathSubCommandsNV = NULL;\nPFNGLPATHSUBCOORDSNVPROC glad_glPathSubCoordsNV = NULL;\nPFNGLPATHSTRINGNVPROC glad_glPathStringNV = NULL;\nPFNGLPATHGLYPHSNVPROC glad_glPathGlyphsNV = NULL;\nPFNGLPATHGLYPHRANGENVPROC glad_glPathGlyphRangeNV = NULL;\nPFNGLWEIGHTPATHSNVPROC glad_glWeightPathsNV = NULL;\nPFNGLCOPYPATHNVPROC glad_glCopyPathNV = NULL;\nPFNGLINTERPOLATEPATHSNVPROC glad_glInterpolatePathsNV = NULL;\nPFNGLTRANSFORMPATHNVPROC glad_glTransformPathNV = NULL;\nPFNGLPATHPARAMETERIVNVPROC glad_glPathParameterivNV = NULL;\nPFNGLPATHPARAMETERINVPROC glad_glPathParameteriNV = NULL;\nPFNGLPATHPARAMETERFVNVPROC glad_glPathParameterfvNV = NULL;\nPFNGLPATHPARAMETERFNVPROC glad_glPathParameterfNV = NULL;\nPFNGLPATHDASHARRAYNVPROC glad_glPathDashArrayNV = NULL;\nPFNGLPATHSTENCILFUNCNVPROC glad_glPathStencilFuncNV = NULL;\nPFNGLPATHSTENCILDEPTHOFFSETNVPROC glad_glPathStencilDepthOffsetNV = NULL;\nPFNGLSTENCILFILLPATHNVPROC glad_glStencilFillPathNV = NULL;\nPFNGLSTENCILSTROKEPATHNVPROC glad_glStencilStrokePathNV = NULL;\nPFNGLSTENCILFILLPATHINSTANCEDNVPROC glad_glStencilFillPathInstancedNV = NULL;\nPFNGLSTENCILSTROKEPATHINSTANCEDNVPROC glad_glStencilStrokePathInstancedNV = NULL;\nPFNGLPATHCOVERDEPTHFUNCNVPROC glad_glPathCoverDepthFuncNV = NULL;\nPFNGLCOVERFILLPATHNVPROC glad_glCoverFillPathNV = NULL;\nPFNGLCOVERSTROKEPATHNVPROC glad_glCoverStrokePathNV = NULL;\nPFNGLCOVERFILLPATHINSTANCEDNVPROC glad_glCoverFillPathInstancedNV = NULL;\nPFNGLCOVERSTROKEPATHINSTANCEDNVPROC glad_glCoverStrokePathInstancedNV = NULL;\nPFNGLGETPATHPARAMETERIVNVPROC glad_glGetPathParameterivNV = NULL;\nPFNGLGETPATHPARAMETERFVNVPROC glad_glGetPathParameterfvNV = NULL;\nPFNGLGETPATHCOMMANDSNVPROC glad_glGetPathCommandsNV = NULL;\nPFNGLGETPATHCOORDSNVPROC glad_glGetPathCoordsNV = NULL;\nPFNGLGETPATHDASHARRAYNVPROC glad_glGetPathDashArrayNV = NULL;\nPFNGLGETPATHMETRICSNVPROC glad_glGetPathMetricsNV = NULL;\nPFNGLGETPATHMETRICRANGENVPROC glad_glGetPathMetricRangeNV = NULL;\nPFNGLGETPATHSPACINGNVPROC glad_glGetPathSpacingNV = NULL;\nPFNGLISPOINTINFILLPATHNVPROC glad_glIsPointInFillPathNV = NULL;\nPFNGLISPOINTINSTROKEPATHNVPROC glad_glIsPointInStrokePathNV = NULL;\nPFNGLGETPATHLENGTHNVPROC glad_glGetPathLengthNV = NULL;\nPFNGLPOINTALONGPATHNVPROC glad_glPointAlongPathNV = NULL;\nPFNGLMATRIXLOAD3X2FNVPROC glad_glMatrixLoad3x2fNV = NULL;\nPFNGLMATRIXLOAD3X3FNVPROC glad_glMatrixLoad3x3fNV = NULL;\nPFNGLMATRIXLOADTRANSPOSE3X3FNVPROC glad_glMatrixLoadTranspose3x3fNV = NULL;\nPFNGLMATRIXMULT3X2FNVPROC glad_glMatrixMult3x2fNV = NULL;\nPFNGLMATRIXMULT3X3FNVPROC glad_glMatrixMult3x3fNV = NULL;\nPFNGLMATRIXMULTTRANSPOSE3X3FNVPROC glad_glMatrixMultTranspose3x3fNV = NULL;\nPFNGLSTENCILTHENCOVERFILLPATHNVPROC glad_glStencilThenCoverFillPathNV = NULL;\nPFNGLSTENCILTHENCOVERSTROKEPATHNVPROC glad_glStencilThenCoverStrokePathNV = NULL;\nPFNGLSTENCILTHENCOVERFILLPATHINSTANCEDNVPROC glad_glStencilThenCoverFillPathInstancedNV = NULL;\nPFNGLSTENCILTHENCOVERSTROKEPATHINSTANCEDNVPROC glad_glStencilThenCoverStrokePathInstancedNV = NULL;\nPFNGLPATHGLYPHINDEXRANGENVPROC glad_glPathGlyphIndexRangeNV = NULL;\nPFNGLPATHGLYPHINDEXARRAYNVPROC glad_glPathGlyphIndexArrayNV = NULL;\nPFNGLPATHMEMORYGLYPHINDEXARRAYNVPROC glad_glPathMemoryGlyphIndexArrayNV = NULL;\nPFNGLPROGRAMPATHFRAGMENTINPUTGENNVPROC glad_glProgramPathFragmentInputGenNV = NULL;\nPFNGLGETPROGRAMRESOURCEFVNVPROC glad_glGetProgramResourcefvNV = NULL;\nPFNGLPATHCOLORGENNVPROC glad_glPathColorGenNV = NULL;\nPFNGLPATHTEXGENNVPROC glad_glPathTexGenNV = NULL;\nPFNGLPATHFOGGENNVPROC glad_glPathFogGenNV = NULL;\nPFNGLGETPATHCOLORGENIVNVPROC glad_glGetPathColorGenivNV = NULL;\nPFNGLGETPATHCOLORGENFVNVPROC glad_glGetPathColorGenfvNV = NULL;\nPFNGLGETPATHTEXGENIVNVPROC glad_glGetPathTexGenivNV = NULL;\nPFNGLGETPATHTEXGENFVNVPROC glad_glGetPathTexGenfvNV = NULL;\nPFNGLPIXELDATARANGENVPROC glad_glPixelDataRangeNV = NULL;\nPFNGLFLUSHPIXELDATARANGENVPROC glad_glFlushPixelDataRangeNV = NULL;\nPFNGLPOINTPARAMETERINVPROC glad_glPointParameteriNV = NULL;\nPFNGLPOINTPARAMETERIVNVPROC glad_glPointParameterivNV = NULL;\nPFNGLPRESENTFRAMEKEYEDNVPROC glad_glPresentFrameKeyedNV = NULL;\nPFNGLPRESENTFRAMEDUALFILLNVPROC glad_glPresentFrameDualFillNV = NULL;\nPFNGLGETVIDEOIVNVPROC glad_glGetVideoivNV = NULL;\nPFNGLGETVIDEOUIVNVPROC glad_glGetVideouivNV = NULL;\nPFNGLGETVIDEOI64VNVPROC glad_glGetVideoi64vNV = NULL;\nPFNGLGETVIDEOUI64VNVPROC glad_glGetVideoui64vNV = NULL;\nPFNGLPRIMITIVERESTARTNVPROC glad_glPrimitiveRestartNV = NULL;\nPFNGLPRIMITIVERESTARTINDEXNVPROC glad_glPrimitiveRestartIndexNV = NULL;\nPFNGLQUERYRESOURCENVPROC glad_glQueryResourceNV = NULL;\nPFNGLGENQUERYRESOURCETAGNVPROC glad_glGenQueryResourceTagNV = NULL;\nPFNGLDELETEQUERYRESOURCETAGNVPROC glad_glDeleteQueryResourceTagNV = NULL;\nPFNGLQUERYRESOURCETAGNVPROC glad_glQueryResourceTagNV = NULL;\nPFNGLCOMBINERPARAMETERFVNVPROC glad_glCombinerParameterfvNV = NULL;\nPFNGLCOMBINERPARAMETERFNVPROC glad_glCombinerParameterfNV = NULL;\nPFNGLCOMBINERPARAMETERIVNVPROC glad_glCombinerParameterivNV = NULL;\nPFNGLCOMBINERPARAMETERINVPROC glad_glCombinerParameteriNV = NULL;\nPFNGLCOMBINERINPUTNVPROC glad_glCombinerInputNV = NULL;\nPFNGLCOMBINEROUTPUTNVPROC glad_glCombinerOutputNV = NULL;\nPFNGLFINALCOMBINERINPUTNVPROC glad_glFinalCombinerInputNV = NULL;\nPFNGLGETCOMBINERINPUTPARAMETERFVNVPROC glad_glGetCombinerInputParameterfvNV = NULL;\nPFNGLGETCOMBINERINPUTPARAMETERIVNVPROC glad_glGetCombinerInputParameterivNV = NULL;\nPFNGLGETCOMBINEROUTPUTPARAMETERFVNVPROC glad_glGetCombinerOutputParameterfvNV = NULL;\nPFNGLGETCOMBINEROUTPUTPARAMETERIVNVPROC glad_glGetCombinerOutputParameterivNV = NULL;\nPFNGLGETFINALCOMBINERINPUTPARAMETERFVNVPROC glad_glGetFinalCombinerInputParameterfvNV = NULL;\nPFNGLGETFINALCOMBINERINPUTPARAMETERIVNVPROC glad_glGetFinalCombinerInputParameterivNV = NULL;\nPFNGLCOMBINERSTAGEPARAMETERFVNVPROC glad_glCombinerStageParameterfvNV = NULL;\nPFNGLGETCOMBINERSTAGEPARAMETERFVNVPROC glad_glGetCombinerStageParameterfvNV = NULL;\nPFNGLFRAMEBUFFERSAMPLELOCATIONSFVNVPROC glad_glFramebufferSampleLocationsfvNV = NULL;\nPFNGLNAMEDFRAMEBUFFERSAMPLELOCATIONSFVNVPROC glad_glNamedFramebufferSampleLocationsfvNV = NULL;\nPFNGLRESOLVEDEPTHVALUESNVPROC glad_glResolveDepthValuesNV = NULL;\nPFNGLSCISSOREXCLUSIVENVPROC glad_glScissorExclusiveNV = NULL;\nPFNGLSCISSOREXCLUSIVEARRAYVNVPROC glad_glScissorExclusiveArrayvNV = NULL;\nPFNGLMAKEBUFFERRESIDENTNVPROC glad_glMakeBufferResidentNV = NULL;\nPFNGLMAKEBUFFERNONRESIDENTNVPROC glad_glMakeBufferNonResidentNV = NULL;\nPFNGLISBUFFERRESIDENTNVPROC glad_glIsBufferResidentNV = NULL;\nPFNGLMAKENAMEDBUFFERRESIDENTNVPROC glad_glMakeNamedBufferResidentNV = NULL;\nPFNGLMAKENAMEDBUFFERNONRESIDENTNVPROC glad_glMakeNamedBufferNonResidentNV = NULL;\nPFNGLISNAMEDBUFFERRESIDENTNVPROC glad_glIsNamedBufferResidentNV = NULL;\nPFNGLGETBUFFERPARAMETERUI64VNVPROC glad_glGetBufferParameterui64vNV = NULL;\nPFNGLGETNAMEDBUFFERPARAMETERUI64VNVPROC glad_glGetNamedBufferParameterui64vNV = NULL;\nPFNGLGETINTEGERUI64VNVPROC glad_glGetIntegerui64vNV = NULL;\nPFNGLUNIFORMUI64NVPROC glad_glUniformui64NV = NULL;\nPFNGLUNIFORMUI64VNVPROC glad_glUniformui64vNV = NULL;\nPFNGLPROGRAMUNIFORMUI64NVPROC glad_glProgramUniformui64NV = NULL;\nPFNGLPROGRAMUNIFORMUI64VNVPROC glad_glProgramUniformui64vNV = NULL;\nPFNGLBINDSHADINGRATEIMAGENVPROC glad_glBindShadingRateImageNV = NULL;\nPFNGLGETSHADINGRATEIMAGEPALETTENVPROC glad_glGetShadingRateImagePaletteNV = NULL;\nPFNGLGETSHADINGRATESAMPLELOCATIONIVNVPROC glad_glGetShadingRateSampleLocationivNV = NULL;\nPFNGLSHADINGRATEIMAGEBARRIERNVPROC glad_glShadingRateImageBarrierNV = NULL;\nPFNGLSHADINGRATEIMAGEPALETTENVPROC glad_glShadingRateImagePaletteNV = NULL;\nPFNGLSHADINGRATESAMPLEORDERNVPROC glad_glShadingRateSampleOrderNV = NULL;\nPFNGLSHADINGRATESAMPLEORDERCUSTOMNVPROC glad_glShadingRateSampleOrderCustomNV = NULL;\nPFNGLTEXTUREBARRIERNVPROC glad_glTextureBarrierNV = NULL;\nPFNGLTEXIMAGE2DMULTISAMPLECOVERAGENVPROC glad_glTexImage2DMultisampleCoverageNV = NULL;\nPFNGLTEXIMAGE3DMULTISAMPLECOVERAGENVPROC glad_glTexImage3DMultisampleCoverageNV = NULL;\nPFNGLTEXTUREIMAGE2DMULTISAMPLENVPROC glad_glTextureImage2DMultisampleNV = NULL;\nPFNGLTEXTUREIMAGE3DMULTISAMPLENVPROC glad_glTextureImage3DMultisampleNV = NULL;\nPFNGLTEXTUREIMAGE2DMULTISAMPLECOVERAGENVPROC glad_glTextureImage2DMultisampleCoverageNV = NULL;\nPFNGLTEXTUREIMAGE3DMULTISAMPLECOVERAGENVPROC glad_glTextureImage3DMultisampleCoverageNV = NULL;\nPFNGLBEGINTRANSFORMFEEDBACKNVPROC glad_glBeginTransformFeedbackNV = NULL;\nPFNGLENDTRANSFORMFEEDBACKNVPROC glad_glEndTransformFeedbackNV = NULL;\nPFNGLTRANSFORMFEEDBACKATTRIBSNVPROC glad_glTransformFeedbackAttribsNV = NULL;\nPFNGLBINDBUFFERRANGENVPROC glad_glBindBufferRangeNV = NULL;\nPFNGLBINDBUFFEROFFSETNVPROC glad_glBindBufferOffsetNV = NULL;\nPFNGLBINDBUFFERBASENVPROC glad_glBindBufferBaseNV = NULL;\nPFNGLTRANSFORMFEEDBACKVARYINGSNVPROC glad_glTransformFeedbackVaryingsNV = NULL;\nPFNGLACTIVEVARYINGNVPROC glad_glActiveVaryingNV = NULL;\nPFNGLGETVARYINGLOCATIONNVPROC glad_glGetVaryingLocationNV = NULL;\nPFNGLGETACTIVEVARYINGNVPROC glad_glGetActiveVaryingNV = NULL;\nPFNGLGETTRANSFORMFEEDBACKVARYINGNVPROC glad_glGetTransformFeedbackVaryingNV = NULL;\nPFNGLTRANSFORMFEEDBACKSTREAMATTRIBSNVPROC glad_glTransformFeedbackStreamAttribsNV = NULL;\nPFNGLBINDTRANSFORMFEEDBACKNVPROC glad_glBindTransformFeedbackNV = NULL;\nPFNGLDELETETRANSFORMFEEDBACKSNVPROC glad_glDeleteTransformFeedbacksNV = NULL;\nPFNGLGENTRANSFORMFEEDBACKSNVPROC glad_glGenTransformFeedbacksNV = NULL;\nPFNGLISTRANSFORMFEEDBACKNVPROC glad_glIsTransformFeedbackNV = NULL;\nPFNGLPAUSETRANSFORMFEEDBACKNVPROC glad_glPauseTransformFeedbackNV = NULL;\nPFNGLRESUMETRANSFORMFEEDBACKNVPROC glad_glResumeTransformFeedbackNV = NULL;\nPFNGLDRAWTRANSFORMFEEDBACKNVPROC glad_glDrawTransformFeedbackNV = NULL;\nPFNGLVDPAUINITNVPROC glad_glVDPAUInitNV = NULL;\nPFNGLVDPAUFININVPROC glad_glVDPAUFiniNV = NULL;\nPFNGLVDPAUREGISTERVIDEOSURFACENVPROC glad_glVDPAURegisterVideoSurfaceNV = NULL;\nPFNGLVDPAUREGISTEROUTPUTSURFACENVPROC glad_glVDPAURegisterOutputSurfaceNV = NULL;\nPFNGLVDPAUISSURFACENVPROC glad_glVDPAUIsSurfaceNV = NULL;\nPFNGLVDPAUUNREGISTERSURFACENVPROC glad_glVDPAUUnregisterSurfaceNV = NULL;\nPFNGLVDPAUGETSURFACEIVNVPROC glad_glVDPAUGetSurfaceivNV = NULL;\nPFNGLVDPAUSURFACEACCESSNVPROC glad_glVDPAUSurfaceAccessNV = NULL;\nPFNGLVDPAUMAPSURFACESNVPROC glad_glVDPAUMapSurfacesNV = NULL;\nPFNGLVDPAUUNMAPSURFACESNVPROC glad_glVDPAUUnmapSurfacesNV = NULL;\nPFNGLFLUSHVERTEXARRAYRANGENVPROC glad_glFlushVertexArrayRangeNV = NULL;\nPFNGLVERTEXARRAYRANGENVPROC glad_glVertexArrayRangeNV = NULL;\nPFNGLVERTEXATTRIBL1I64NVPROC glad_glVertexAttribL1i64NV = NULL;\nPFNGLVERTEXATTRIBL2I64NVPROC glad_glVertexAttribL2i64NV = NULL;\nPFNGLVERTEXATTRIBL3I64NVPROC glad_glVertexAttribL3i64NV = NULL;\nPFNGLVERTEXATTRIBL4I64NVPROC glad_glVertexAttribL4i64NV = NULL;\nPFNGLVERTEXATTRIBL1I64VNVPROC glad_glVertexAttribL1i64vNV = NULL;\nPFNGLVERTEXATTRIBL2I64VNVPROC glad_glVertexAttribL2i64vNV = NULL;\nPFNGLVERTEXATTRIBL3I64VNVPROC glad_glVertexAttribL3i64vNV = NULL;\nPFNGLVERTEXATTRIBL4I64VNVPROC glad_glVertexAttribL4i64vNV = NULL;\nPFNGLVERTEXATTRIBL1UI64NVPROC glad_glVertexAttribL1ui64NV = NULL;\nPFNGLVERTEXATTRIBL2UI64NVPROC glad_glVertexAttribL2ui64NV = NULL;\nPFNGLVERTEXATTRIBL3UI64NVPROC glad_glVertexAttribL3ui64NV = NULL;\nPFNGLVERTEXATTRIBL4UI64NVPROC glad_glVertexAttribL4ui64NV = NULL;\nPFNGLVERTEXATTRIBL1UI64VNVPROC glad_glVertexAttribL1ui64vNV = NULL;\nPFNGLVERTEXATTRIBL2UI64VNVPROC glad_glVertexAttribL2ui64vNV = NULL;\nPFNGLVERTEXATTRIBL3UI64VNVPROC glad_glVertexAttribL3ui64vNV = NULL;\nPFNGLVERTEXATTRIBL4UI64VNVPROC glad_glVertexAttribL4ui64vNV = NULL;\nPFNGLGETVERTEXATTRIBLI64VNVPROC glad_glGetVertexAttribLi64vNV = NULL;\nPFNGLGETVERTEXATTRIBLUI64VNVPROC glad_glGetVertexAttribLui64vNV = NULL;\nPFNGLVERTEXATTRIBLFORMATNVPROC glad_glVertexAttribLFormatNV = NULL;\nPFNGLBUFFERADDRESSRANGENVPROC glad_glBufferAddressRangeNV = NULL;\nPFNGLVERTEXFORMATNVPROC glad_glVertexFormatNV = NULL;\nPFNGLNORMALFORMATNVPROC glad_glNormalFormatNV = NULL;\nPFNGLCOLORFORMATNVPROC glad_glColorFormatNV = NULL;\nPFNGLINDEXFORMATNVPROC glad_glIndexFormatNV = NULL;\nPFNGLTEXCOORDFORMATNVPROC glad_glTexCoordFormatNV = NULL;\nPFNGLEDGEFLAGFORMATNVPROC glad_glEdgeFlagFormatNV = NULL;\nPFNGLSECONDARYCOLORFORMATNVPROC glad_glSecondaryColorFormatNV = NULL;\nPFNGLFOGCOORDFORMATNVPROC glad_glFogCoordFormatNV = NULL;\nPFNGLVERTEXATTRIBFORMATNVPROC glad_glVertexAttribFormatNV = NULL;\nPFNGLVERTEXATTRIBIFORMATNVPROC glad_glVertexAttribIFormatNV = NULL;\nPFNGLGETINTEGERUI64I_VNVPROC glad_glGetIntegerui64i_vNV = NULL;\nPFNGLAREPROGRAMSRESIDENTNVPROC glad_glAreProgramsResidentNV = NULL;\nPFNGLBINDPROGRAMNVPROC glad_glBindProgramNV = NULL;\nPFNGLDELETEPROGRAMSNVPROC glad_glDeleteProgramsNV = NULL;\nPFNGLEXECUTEPROGRAMNVPROC glad_glExecuteProgramNV = NULL;\nPFNGLGENPROGRAMSNVPROC glad_glGenProgramsNV = NULL;\nPFNGLGETPROGRAMPARAMETERDVNVPROC glad_glGetProgramParameterdvNV = NULL;\nPFNGLGETPROGRAMPARAMETERFVNVPROC glad_glGetProgramParameterfvNV = NULL;\nPFNGLGETPROGRAMIVNVPROC glad_glGetProgramivNV = NULL;\nPFNGLGETPROGRAMSTRINGNVPROC glad_glGetProgramStringNV = NULL;\nPFNGLGETTRACKMATRIXIVNVPROC glad_glGetTrackMatrixivNV = NULL;\nPFNGLGETVERTEXATTRIBDVNVPROC glad_glGetVertexAttribdvNV = NULL;\nPFNGLGETVERTEXATTRIBFVNVPROC glad_glGetVertexAttribfvNV = NULL;\nPFNGLGETVERTEXATTRIBIVNVPROC glad_glGetVertexAttribivNV = NULL;\nPFNGLGETVERTEXATTRIBPOINTERVNVPROC glad_glGetVertexAttribPointervNV = NULL;\nPFNGLISPROGRAMNVPROC glad_glIsProgramNV = NULL;\nPFNGLLOADPROGRAMNVPROC glad_glLoadProgramNV = NULL;\nPFNGLPROGRAMPARAMETER4DNVPROC glad_glProgramParameter4dNV = NULL;\nPFNGLPROGRAMPARAMETER4DVNVPROC glad_glProgramParameter4dvNV = NULL;\nPFNGLPROGRAMPARAMETER4FNVPROC glad_glProgramParameter4fNV = NULL;\nPFNGLPROGRAMPARAMETER4FVNVPROC glad_glProgramParameter4fvNV = NULL;\nPFNGLPROGRAMPARAMETERS4DVNVPROC glad_glProgramParameters4dvNV = NULL;\nPFNGLPROGRAMPARAMETERS4FVNVPROC glad_glProgramParameters4fvNV = NULL;\nPFNGLREQUESTRESIDENTPROGRAMSNVPROC glad_glRequestResidentProgramsNV = NULL;\nPFNGLTRACKMATRIXNVPROC glad_glTrackMatrixNV = NULL;\nPFNGLVERTEXATTRIBPOINTERNVPROC glad_glVertexAttribPointerNV = NULL;\nPFNGLVERTEXATTRIB1DNVPROC glad_glVertexAttrib1dNV = NULL;\nPFNGLVERTEXATTRIB1DVNVPROC glad_glVertexAttrib1dvNV = NULL;\nPFNGLVERTEXATTRIB1FNVPROC glad_glVertexAttrib1fNV = NULL;\nPFNGLVERTEXATTRIB1FVNVPROC glad_glVertexAttrib1fvNV = NULL;\nPFNGLVERTEXATTRIB1SNVPROC glad_glVertexAttrib1sNV = NULL;\nPFNGLVERTEXATTRIB1SVNVPROC glad_glVertexAttrib1svNV = NULL;\nPFNGLVERTEXATTRIB2DNVPROC glad_glVertexAttrib2dNV = NULL;\nPFNGLVERTEXATTRIB2DVNVPROC glad_glVertexAttrib2dvNV = NULL;\nPFNGLVERTEXATTRIB2FNVPROC glad_glVertexAttrib2fNV = NULL;\nPFNGLVERTEXATTRIB2FVNVPROC glad_glVertexAttrib2fvNV = NULL;\nPFNGLVERTEXATTRIB2SNVPROC glad_glVertexAttrib2sNV = NULL;\nPFNGLVERTEXATTRIB2SVNVPROC glad_glVertexAttrib2svNV = NULL;\nPFNGLVERTEXATTRIB3DNVPROC glad_glVertexAttrib3dNV = NULL;\nPFNGLVERTEXATTRIB3DVNVPROC glad_glVertexAttrib3dvNV = NULL;\nPFNGLVERTEXATTRIB3FNVPROC glad_glVertexAttrib3fNV = NULL;\nPFNGLVERTEXATTRIB3FVNVPROC glad_glVertexAttrib3fvNV = NULL;\nPFNGLVERTEXATTRIB3SNVPROC glad_glVertexAttrib3sNV = NULL;\nPFNGLVERTEXATTRIB3SVNVPROC glad_glVertexAttrib3svNV = NULL;\nPFNGLVERTEXATTRIB4DNVPROC glad_glVertexAttrib4dNV = NULL;\nPFNGLVERTEXATTRIB4DVNVPROC glad_glVertexAttrib4dvNV = NULL;\nPFNGLVERTEXATTRIB4FNVPROC glad_glVertexAttrib4fNV = NULL;\nPFNGLVERTEXATTRIB4FVNVPROC glad_glVertexAttrib4fvNV = NULL;\nPFNGLVERTEXATTRIB4SNVPROC glad_glVertexAttrib4sNV = NULL;\nPFNGLVERTEXATTRIB4SVNVPROC glad_glVertexAttrib4svNV = NULL;\nPFNGLVERTEXATTRIB4UBNVPROC glad_glVertexAttrib4ubNV = NULL;\nPFNGLVERTEXATTRIB4UBVNVPROC glad_glVertexAttrib4ubvNV = NULL;\nPFNGLVERTEXATTRIBS1DVNVPROC glad_glVertexAttribs1dvNV = NULL;\nPFNGLVERTEXATTRIBS1FVNVPROC glad_glVertexAttribs1fvNV = NULL;\nPFNGLVERTEXATTRIBS1SVNVPROC glad_glVertexAttribs1svNV = NULL;\nPFNGLVERTEXATTRIBS2DVNVPROC glad_glVertexAttribs2dvNV = NULL;\nPFNGLVERTEXATTRIBS2FVNVPROC glad_glVertexAttribs2fvNV = NULL;\nPFNGLVERTEXATTRIBS2SVNVPROC glad_glVertexAttribs2svNV = NULL;\nPFNGLVERTEXATTRIBS3DVNVPROC glad_glVertexAttribs3dvNV = NULL;\nPFNGLVERTEXATTRIBS3FVNVPROC glad_glVertexAttribs3fvNV = NULL;\nPFNGLVERTEXATTRIBS3SVNVPROC glad_glVertexAttribs3svNV = NULL;\nPFNGLVERTEXATTRIBS4DVNVPROC glad_glVertexAttribs4dvNV = NULL;\nPFNGLVERTEXATTRIBS4FVNVPROC glad_glVertexAttribs4fvNV = NULL;\nPFNGLVERTEXATTRIBS4SVNVPROC glad_glVertexAttribs4svNV = NULL;\nPFNGLVERTEXATTRIBS4UBVNVPROC glad_glVertexAttribs4ubvNV = NULL;\nPFNGLVERTEXATTRIBI1IEXTPROC glad_glVertexAttribI1iEXT = NULL;\nPFNGLVERTEXATTRIBI2IEXTPROC glad_glVertexAttribI2iEXT = NULL;\nPFNGLVERTEXATTRIBI3IEXTPROC glad_glVertexAttribI3iEXT = NULL;\nPFNGLVERTEXATTRIBI4IEXTPROC glad_glVertexAttribI4iEXT = NULL;\nPFNGLVERTEXATTRIBI1UIEXTPROC glad_glVertexAttribI1uiEXT = NULL;\nPFNGLVERTEXATTRIBI2UIEXTPROC glad_glVertexAttribI2uiEXT = NULL;\nPFNGLVERTEXATTRIBI3UIEXTPROC glad_glVertexAttribI3uiEXT = NULL;\nPFNGLVERTEXATTRIBI4UIEXTPROC glad_glVertexAttribI4uiEXT = NULL;\nPFNGLVERTEXATTRIBI1IVEXTPROC glad_glVertexAttribI1ivEXT = NULL;\nPFNGLVERTEXATTRIBI2IVEXTPROC glad_glVertexAttribI2ivEXT = NULL;\nPFNGLVERTEXATTRIBI3IVEXTPROC glad_glVertexAttribI3ivEXT = NULL;\nPFNGLVERTEXATTRIBI4IVEXTPROC glad_glVertexAttribI4ivEXT = NULL;\nPFNGLVERTEXATTRIBI1UIVEXTPROC glad_glVertexAttribI1uivEXT = NULL;\nPFNGLVERTEXATTRIBI2UIVEXTPROC glad_glVertexAttribI2uivEXT = NULL;\nPFNGLVERTEXATTRIBI3UIVEXTPROC glad_glVertexAttribI3uivEXT = NULL;\nPFNGLVERTEXATTRIBI4UIVEXTPROC glad_glVertexAttribI4uivEXT = NULL;\nPFNGLVERTEXATTRIBI4BVEXTPROC glad_glVertexAttribI4bvEXT = NULL;\nPFNGLVERTEXATTRIBI4SVEXTPROC glad_glVertexAttribI4svEXT = NULL;\nPFNGLVERTEXATTRIBI4UBVEXTPROC glad_glVertexAttribI4ubvEXT = NULL;\nPFNGLVERTEXATTRIBI4USVEXTPROC glad_glVertexAttribI4usvEXT = NULL;\nPFNGLVERTEXATTRIBIPOINTEREXTPROC glad_glVertexAttribIPointerEXT = NULL;\nPFNGLGETVERTEXATTRIBIIVEXTPROC glad_glGetVertexAttribIivEXT = NULL;\nPFNGLGETVERTEXATTRIBIUIVEXTPROC glad_glGetVertexAttribIuivEXT = NULL;\nPFNGLBEGINVIDEOCAPTURENVPROC glad_glBeginVideoCaptureNV = NULL;\nPFNGLBINDVIDEOCAPTURESTREAMBUFFERNVPROC glad_glBindVideoCaptureStreamBufferNV = NULL;\nPFNGLBINDVIDEOCAPTURESTREAMTEXTURENVPROC glad_glBindVideoCaptureStreamTextureNV = NULL;\nPFNGLENDVIDEOCAPTURENVPROC glad_glEndVideoCaptureNV = NULL;\nPFNGLGETVIDEOCAPTUREIVNVPROC glad_glGetVideoCaptureivNV = NULL;\nPFNGLGETVIDEOCAPTURESTREAMIVNVPROC glad_glGetVideoCaptureStreamivNV = NULL;\nPFNGLGETVIDEOCAPTURESTREAMFVNVPROC glad_glGetVideoCaptureStreamfvNV = NULL;\nPFNGLGETVIDEOCAPTURESTREAMDVNVPROC glad_glGetVideoCaptureStreamdvNV = NULL;\nPFNGLVIDEOCAPTURENVPROC glad_glVideoCaptureNV = NULL;\nPFNGLVIDEOCAPTURESTREAMPARAMETERIVNVPROC glad_glVideoCaptureStreamParameterivNV = NULL;\nPFNGLVIDEOCAPTURESTREAMPARAMETERFVNVPROC glad_glVideoCaptureStreamParameterfvNV = NULL;\nPFNGLVIDEOCAPTURESTREAMPARAMETERDVNVPROC glad_glVideoCaptureStreamParameterdvNV = NULL;\nPFNGLVIEWPORTSWIZZLENVPROC glad_glViewportSwizzleNV = NULL;\nPFNGLMULTITEXCOORD1BOESPROC glad_glMultiTexCoord1bOES = NULL;\nPFNGLMULTITEXCOORD1BVOESPROC glad_glMultiTexCoord1bvOES = NULL;\nPFNGLMULTITEXCOORD2BOESPROC glad_glMultiTexCoord2bOES = NULL;\nPFNGLMULTITEXCOORD2BVOESPROC glad_glMultiTexCoord2bvOES = NULL;\nPFNGLMULTITEXCOORD3BOESPROC glad_glMultiTexCoord3bOES = NULL;\nPFNGLMULTITEXCOORD3BVOESPROC glad_glMultiTexCoord3bvOES = NULL;\nPFNGLMULTITEXCOORD4BOESPROC glad_glMultiTexCoord4bOES = NULL;\nPFNGLMULTITEXCOORD4BVOESPROC glad_glMultiTexCoord4bvOES = NULL;\nPFNGLTEXCOORD1BOESPROC glad_glTexCoord1bOES = NULL;\nPFNGLTEXCOORD1BVOESPROC glad_glTexCoord1bvOES = NULL;\nPFNGLTEXCOORD2BOESPROC glad_glTexCoord2bOES = NULL;\nPFNGLTEXCOORD2BVOESPROC glad_glTexCoord2bvOES = NULL;\nPFNGLTEXCOORD3BOESPROC glad_glTexCoord3bOES = NULL;\nPFNGLTEXCOORD3BVOESPROC glad_glTexCoord3bvOES = NULL;\nPFNGLTEXCOORD4BOESPROC glad_glTexCoord4bOES = NULL;\nPFNGLTEXCOORD4BVOESPROC glad_glTexCoord4bvOES = NULL;\nPFNGLVERTEX2BOESPROC glad_glVertex2bOES = NULL;\nPFNGLVERTEX2BVOESPROC glad_glVertex2bvOES = NULL;\nPFNGLVERTEX3BOESPROC glad_glVertex3bOES = NULL;\nPFNGLVERTEX3BVOESPROC glad_glVertex3bvOES = NULL;\nPFNGLVERTEX4BOESPROC glad_glVertex4bOES = NULL;\nPFNGLVERTEX4BVOESPROC glad_glVertex4bvOES = NULL;\nPFNGLALPHAFUNCXOESPROC glad_glAlphaFuncxOES = NULL;\nPFNGLCLEARCOLORXOESPROC glad_glClearColorxOES = NULL;\nPFNGLCLEARDEPTHXOESPROC glad_glClearDepthxOES = NULL;\nPFNGLCLIPPLANEXOESPROC glad_glClipPlanexOES = NULL;\nPFNGLCOLOR4XOESPROC glad_glColor4xOES = NULL;\nPFNGLDEPTHRANGEXOESPROC glad_glDepthRangexOES = NULL;\nPFNGLFOGXOESPROC glad_glFogxOES = NULL;\nPFNGLFOGXVOESPROC glad_glFogxvOES = NULL;\nPFNGLFRUSTUMXOESPROC glad_glFrustumxOES = NULL;\nPFNGLGETCLIPPLANEXOESPROC glad_glGetClipPlanexOES = NULL;\nPFNGLGETFIXEDVOESPROC glad_glGetFixedvOES = NULL;\nPFNGLGETTEXENVXVOESPROC glad_glGetTexEnvxvOES = NULL;\nPFNGLGETTEXPARAMETERXVOESPROC glad_glGetTexParameterxvOES = NULL;\nPFNGLLIGHTMODELXOESPROC glad_glLightModelxOES = NULL;\nPFNGLLIGHTMODELXVOESPROC glad_glLightModelxvOES = NULL;\nPFNGLLIGHTXOESPROC glad_glLightxOES = NULL;\nPFNGLLIGHTXVOESPROC glad_glLightxvOES = NULL;\nPFNGLLINEWIDTHXOESPROC glad_glLineWidthxOES = NULL;\nPFNGLLOADMATRIXXOESPROC glad_glLoadMatrixxOES = NULL;\nPFNGLMATERIALXOESPROC glad_glMaterialxOES = NULL;\nPFNGLMATERIALXVOESPROC glad_glMaterialxvOES = NULL;\nPFNGLMULTMATRIXXOESPROC glad_glMultMatrixxOES = NULL;\nPFNGLMULTITEXCOORD4XOESPROC glad_glMultiTexCoord4xOES = NULL;\nPFNGLNORMAL3XOESPROC glad_glNormal3xOES = NULL;\nPFNGLORTHOXOESPROC glad_glOrthoxOES = NULL;\nPFNGLPOINTPARAMETERXVOESPROC glad_glPointParameterxvOES = NULL;\nPFNGLPOINTSIZEXOESPROC glad_glPointSizexOES = NULL;\nPFNGLPOLYGONOFFSETXOESPROC glad_glPolygonOffsetxOES = NULL;\nPFNGLROTATEXOESPROC glad_glRotatexOES = NULL;\nPFNGLSCALEXOESPROC glad_glScalexOES = NULL;\nPFNGLTEXENVXOESPROC glad_glTexEnvxOES = NULL;\nPFNGLTEXENVXVOESPROC glad_glTexEnvxvOES = NULL;\nPFNGLTEXPARAMETERXOESPROC glad_glTexParameterxOES = NULL;\nPFNGLTEXPARAMETERXVOESPROC glad_glTexParameterxvOES = NULL;\nPFNGLTRANSLATEXOESPROC glad_glTranslatexOES = NULL;\nPFNGLGETLIGHTXVOESPROC glad_glGetLightxvOES = NULL;\nPFNGLGETMATERIALXVOESPROC glad_glGetMaterialxvOES = NULL;\nPFNGLPOINTPARAMETERXOESPROC glad_glPointParameterxOES = NULL;\nPFNGLSAMPLECOVERAGEXOESPROC glad_glSampleCoveragexOES = NULL;\nPFNGLACCUMXOESPROC glad_glAccumxOES = NULL;\nPFNGLBITMAPXOESPROC glad_glBitmapxOES = NULL;\nPFNGLBLENDCOLORXOESPROC glad_glBlendColorxOES = NULL;\nPFNGLCLEARACCUMXOESPROC glad_glClearAccumxOES = NULL;\nPFNGLCOLOR3XOESPROC glad_glColor3xOES = NULL;\nPFNGLCOLOR3XVOESPROC glad_glColor3xvOES = NULL;\nPFNGLCOLOR4XVOESPROC glad_glColor4xvOES = NULL;\nPFNGLCONVOLUTIONPARAMETERXOESPROC glad_glConvolutionParameterxOES = NULL;\nPFNGLCONVOLUTIONPARAMETERXVOESPROC glad_glConvolutionParameterxvOES = NULL;\nPFNGLEVALCOORD1XOESPROC glad_glEvalCoord1xOES = NULL;\nPFNGLEVALCOORD1XVOESPROC glad_glEvalCoord1xvOES = NULL;\nPFNGLEVALCOORD2XOESPROC glad_glEvalCoord2xOES = NULL;\nPFNGLEVALCOORD2XVOESPROC glad_glEvalCoord2xvOES = NULL;\nPFNGLFEEDBACKBUFFERXOESPROC glad_glFeedbackBufferxOES = NULL;\nPFNGLGETCONVOLUTIONPARAMETERXVOESPROC glad_glGetConvolutionParameterxvOES = NULL;\nPFNGLGETHISTOGRAMPARAMETERXVOESPROC glad_glGetHistogramParameterxvOES = NULL;\nPFNGLGETLIGHTXOESPROC glad_glGetLightxOES = NULL;\nPFNGLGETMAPXVOESPROC glad_glGetMapxvOES = NULL;\nPFNGLGETMATERIALXOESPROC glad_glGetMaterialxOES = NULL;\nPFNGLGETPIXELMAPXVPROC glad_glGetPixelMapxv = NULL;\nPFNGLGETTEXGENXVOESPROC glad_glGetTexGenxvOES = NULL;\nPFNGLGETTEXLEVELPARAMETERXVOESPROC glad_glGetTexLevelParameterxvOES = NULL;\nPFNGLINDEXXOESPROC glad_glIndexxOES = NULL;\nPFNGLINDEXXVOESPROC glad_glIndexxvOES = NULL;\nPFNGLLOADTRANSPOSEMATRIXXOESPROC glad_glLoadTransposeMatrixxOES = NULL;\nPFNGLMAP1XOESPROC glad_glMap1xOES = NULL;\nPFNGLMAP2XOESPROC glad_glMap2xOES = NULL;\nPFNGLMAPGRID1XOESPROC glad_glMapGrid1xOES = NULL;\nPFNGLMAPGRID2XOESPROC glad_glMapGrid2xOES = NULL;\nPFNGLMULTTRANSPOSEMATRIXXOESPROC glad_glMultTransposeMatrixxOES = NULL;\nPFNGLMULTITEXCOORD1XOESPROC glad_glMultiTexCoord1xOES = NULL;\nPFNGLMULTITEXCOORD1XVOESPROC glad_glMultiTexCoord1xvOES = NULL;\nPFNGLMULTITEXCOORD2XOESPROC glad_glMultiTexCoord2xOES = NULL;\nPFNGLMULTITEXCOORD2XVOESPROC glad_glMultiTexCoord2xvOES = NULL;\nPFNGLMULTITEXCOORD3XOESPROC glad_glMultiTexCoord3xOES = NULL;\nPFNGLMULTITEXCOORD3XVOESPROC glad_glMultiTexCoord3xvOES = NULL;\nPFNGLMULTITEXCOORD4XVOESPROC glad_glMultiTexCoord4xvOES = NULL;\nPFNGLNORMAL3XVOESPROC glad_glNormal3xvOES = NULL;\nPFNGLPASSTHROUGHXOESPROC glad_glPassThroughxOES = NULL;\nPFNGLPIXELMAPXPROC glad_glPixelMapx = NULL;\nPFNGLPIXELSTOREXPROC glad_glPixelStorex = NULL;\nPFNGLPIXELTRANSFERXOESPROC glad_glPixelTransferxOES = NULL;\nPFNGLPIXELZOOMXOESPROC glad_glPixelZoomxOES = NULL;\nPFNGLPRIORITIZETEXTURESXOESPROC glad_glPrioritizeTexturesxOES = NULL;\nPFNGLRASTERPOS2XOESPROC glad_glRasterPos2xOES = NULL;\nPFNGLRASTERPOS2XVOESPROC glad_glRasterPos2xvOES = NULL;\nPFNGLRASTERPOS3XOESPROC glad_glRasterPos3xOES = NULL;\nPFNGLRASTERPOS3XVOESPROC glad_glRasterPos3xvOES = NULL;\nPFNGLRASTERPOS4XOESPROC glad_glRasterPos4xOES = NULL;\nPFNGLRASTERPOS4XVOESPROC glad_glRasterPos4xvOES = NULL;\nPFNGLRECTXOESPROC glad_glRectxOES = NULL;\nPFNGLRECTXVOESPROC glad_glRectxvOES = NULL;\nPFNGLTEXCOORD1XOESPROC glad_glTexCoord1xOES = NULL;\nPFNGLTEXCOORD1XVOESPROC glad_glTexCoord1xvOES = NULL;\nPFNGLTEXCOORD2XOESPROC glad_glTexCoord2xOES = NULL;\nPFNGLTEXCOORD2XVOESPROC glad_glTexCoord2xvOES = NULL;\nPFNGLTEXCOORD3XOESPROC glad_glTexCoord3xOES = NULL;\nPFNGLTEXCOORD3XVOESPROC glad_glTexCoord3xvOES = NULL;\nPFNGLTEXCOORD4XOESPROC glad_glTexCoord4xOES = NULL;\nPFNGLTEXCOORD4XVOESPROC glad_glTexCoord4xvOES = NULL;\nPFNGLTEXGENXOESPROC glad_glTexGenxOES = NULL;\nPFNGLTEXGENXVOESPROC glad_glTexGenxvOES = NULL;\nPFNGLVERTEX2XOESPROC glad_glVertex2xOES = NULL;\nPFNGLVERTEX2XVOESPROC glad_glVertex2xvOES = NULL;\nPFNGLVERTEX3XOESPROC glad_glVertex3xOES = NULL;\nPFNGLVERTEX3XVOESPROC glad_glVertex3xvOES = NULL;\nPFNGLVERTEX4XOESPROC glad_glVertex4xOES = NULL;\nPFNGLVERTEX4XVOESPROC glad_glVertex4xvOES = NULL;\nPFNGLQUERYMATRIXXOESPROC glad_glQueryMatrixxOES = NULL;\nPFNGLCLEARDEPTHFOESPROC glad_glClearDepthfOES = NULL;\nPFNGLCLIPPLANEFOESPROC glad_glClipPlanefOES = NULL;\nPFNGLDEPTHRANGEFOESPROC glad_glDepthRangefOES = NULL;\nPFNGLFRUSTUMFOESPROC glad_glFrustumfOES = NULL;\nPFNGLGETCLIPPLANEFOESPROC glad_glGetClipPlanefOES = NULL;\nPFNGLORTHOFOESPROC glad_glOrthofOES = NULL;\nPFNGLFRAMEBUFFERTEXTUREMULTIVIEWOVRPROC glad_glFramebufferTextureMultiviewOVR = NULL;\nPFNGLHINTPGIPROC glad_glHintPGI = NULL;\nPFNGLDETAILTEXFUNCSGISPROC glad_glDetailTexFuncSGIS = NULL;\nPFNGLGETDETAILTEXFUNCSGISPROC glad_glGetDetailTexFuncSGIS = NULL;\nPFNGLFOGFUNCSGISPROC glad_glFogFuncSGIS = NULL;\nPFNGLGETFOGFUNCSGISPROC glad_glGetFogFuncSGIS = NULL;\nPFNGLSAMPLEMASKSGISPROC glad_glSampleMaskSGIS = NULL;\nPFNGLSAMPLEPATTERNSGISPROC glad_glSamplePatternSGIS = NULL;\nPFNGLPIXELTEXGENPARAMETERISGISPROC glad_glPixelTexGenParameteriSGIS = NULL;\nPFNGLPIXELTEXGENPARAMETERIVSGISPROC glad_glPixelTexGenParameterivSGIS = NULL;\nPFNGLPIXELTEXGENPARAMETERFSGISPROC glad_glPixelTexGenParameterfSGIS = NULL;\nPFNGLPIXELTEXGENPARAMETERFVSGISPROC glad_glPixelTexGenParameterfvSGIS = NULL;\nPFNGLGETPIXELTEXGENPARAMETERIVSGISPROC glad_glGetPixelTexGenParameterivSGIS = NULL;\nPFNGLGETPIXELTEXGENPARAMETERFVSGISPROC glad_glGetPixelTexGenParameterfvSGIS = NULL;\nPFNGLPOINTPARAMETERFSGISPROC glad_glPointParameterfSGIS = NULL;\nPFNGLPOINTPARAMETERFVSGISPROC glad_glPointParameterfvSGIS = NULL;\nPFNGLSHARPENTEXFUNCSGISPROC glad_glSharpenTexFuncSGIS = NULL;\nPFNGLGETSHARPENTEXFUNCSGISPROC glad_glGetSharpenTexFuncSGIS = NULL;\nPFNGLTEXIMAGE4DSGISPROC glad_glTexImage4DSGIS = NULL;\nPFNGLTEXSUBIMAGE4DSGISPROC glad_glTexSubImage4DSGIS = NULL;\nPFNGLTEXTURECOLORMASKSGISPROC glad_glTextureColorMaskSGIS = NULL;\nPFNGLGETTEXFILTERFUNCSGISPROC glad_glGetTexFilterFuncSGIS = NULL;\nPFNGLTEXFILTERFUNCSGISPROC glad_glTexFilterFuncSGIS = NULL;\nPFNGLASYNCMARKERSGIXPROC glad_glAsyncMarkerSGIX = NULL;\nPFNGLFINISHASYNCSGIXPROC glad_glFinishAsyncSGIX = NULL;\nPFNGLPOLLASYNCSGIXPROC glad_glPollAsyncSGIX = NULL;\nPFNGLGENASYNCMARKERSSGIXPROC glad_glGenAsyncMarkersSGIX = NULL;\nPFNGLDELETEASYNCMARKERSSGIXPROC glad_glDeleteAsyncMarkersSGIX = NULL;\nPFNGLISASYNCMARKERSGIXPROC glad_glIsAsyncMarkerSGIX = NULL;\nPFNGLFLUSHRASTERSGIXPROC glad_glFlushRasterSGIX = NULL;\nPFNGLFRAGMENTCOLORMATERIALSGIXPROC glad_glFragmentColorMaterialSGIX = NULL;\nPFNGLFRAGMENTLIGHTFSGIXPROC glad_glFragmentLightfSGIX = NULL;\nPFNGLFRAGMENTLIGHTFVSGIXPROC glad_glFragmentLightfvSGIX = NULL;\nPFNGLFRAGMENTLIGHTISGIXPROC glad_glFragmentLightiSGIX = NULL;\nPFNGLFRAGMENTLIGHTIVSGIXPROC glad_glFragmentLightivSGIX = NULL;\nPFNGLFRAGMENTLIGHTMODELFSGIXPROC glad_glFragmentLightModelfSGIX = NULL;\nPFNGLFRAGMENTLIGHTMODELFVSGIXPROC glad_glFragmentLightModelfvSGIX = NULL;\nPFNGLFRAGMENTLIGHTMODELISGIXPROC glad_glFragmentLightModeliSGIX = NULL;\nPFNGLFRAGMENTLIGHTMODELIVSGIXPROC glad_glFragmentLightModelivSGIX = NULL;\nPFNGLFRAGMENTMATERIALFSGIXPROC glad_glFragmentMaterialfSGIX = NULL;\nPFNGLFRAGMENTMATERIALFVSGIXPROC glad_glFragmentMaterialfvSGIX = NULL;\nPFNGLFRAGMENTMATERIALISGIXPROC glad_glFragmentMaterialiSGIX = NULL;\nPFNGLFRAGMENTMATERIALIVSGIXPROC glad_glFragmentMaterialivSGIX = NULL;\nPFNGLGETFRAGMENTLIGHTFVSGIXPROC glad_glGetFragmentLightfvSGIX = NULL;\nPFNGLGETFRAGMENTLIGHTIVSGIXPROC glad_glGetFragmentLightivSGIX = NULL;\nPFNGLGETFRAGMENTMATERIALFVSGIXPROC glad_glGetFragmentMaterialfvSGIX = NULL;\nPFNGLGETFRAGMENTMATERIALIVSGIXPROC glad_glGetFragmentMaterialivSGIX = NULL;\nPFNGLLIGHTENVISGIXPROC glad_glLightEnviSGIX = NULL;\nPFNGLFRAMEZOOMSGIXPROC glad_glFrameZoomSGIX = NULL;\nPFNGLIGLOOINTERFACESGIXPROC glad_glIglooInterfaceSGIX = NULL;\nPFNGLGETINSTRUMENTSSGIXPROC glad_glGetInstrumentsSGIX = NULL;\nPFNGLINSTRUMENTSBUFFERSGIXPROC glad_glInstrumentsBufferSGIX = NULL;\nPFNGLPOLLINSTRUMENTSSGIXPROC glad_glPollInstrumentsSGIX = NULL;\nPFNGLREADINSTRUMENTSSGIXPROC glad_glReadInstrumentsSGIX = NULL;\nPFNGLSTARTINSTRUMENTSSGIXPROC glad_glStartInstrumentsSGIX = NULL;\nPFNGLSTOPINSTRUMENTSSGIXPROC glad_glStopInstrumentsSGIX = NULL;\nPFNGLGETLISTPARAMETERFVSGIXPROC glad_glGetListParameterfvSGIX = NULL;\nPFNGLGETLISTPARAMETERIVSGIXPROC glad_glGetListParameterivSGIX = NULL;\nPFNGLLISTPARAMETERFSGIXPROC glad_glListParameterfSGIX = NULL;\nPFNGLLISTPARAMETERFVSGIXPROC glad_glListParameterfvSGIX = NULL;\nPFNGLLISTPARAMETERISGIXPROC glad_glListParameteriSGIX = NULL;\nPFNGLLISTPARAMETERIVSGIXPROC glad_glListParameterivSGIX = NULL;\nPFNGLPIXELTEXGENSGIXPROC glad_glPixelTexGenSGIX = NULL;\nPFNGLDEFORMATIONMAP3DSGIXPROC glad_glDeformationMap3dSGIX = NULL;\nPFNGLDEFORMATIONMAP3FSGIXPROC glad_glDeformationMap3fSGIX = NULL;\nPFNGLDEFORMSGIXPROC glad_glDeformSGIX = NULL;\nPFNGLLOADIDENTITYDEFORMATIONMAPSGIXPROC glad_glLoadIdentityDeformationMapSGIX = NULL;\nPFNGLREFERENCEPLANESGIXPROC glad_glReferencePlaneSGIX = NULL;\nPFNGLSPRITEPARAMETERFSGIXPROC glad_glSpriteParameterfSGIX = NULL;\nPFNGLSPRITEPARAMETERFVSGIXPROC glad_glSpriteParameterfvSGIX = NULL;\nPFNGLSPRITEPARAMETERISGIXPROC glad_glSpriteParameteriSGIX = NULL;\nPFNGLSPRITEPARAMETERIVSGIXPROC glad_glSpriteParameterivSGIX = NULL;\nPFNGLTAGSAMPLEBUFFERSGIXPROC glad_glTagSampleBufferSGIX = NULL;\nPFNGLCOLORTABLESGIPROC glad_glColorTableSGI = NULL;\nPFNGLCOLORTABLEPARAMETERFVSGIPROC glad_glColorTableParameterfvSGI = NULL;\nPFNGLCOLORTABLEPARAMETERIVSGIPROC glad_glColorTableParameterivSGI = NULL;\nPFNGLCOPYCOLORTABLESGIPROC glad_glCopyColorTableSGI = NULL;\nPFNGLGETCOLORTABLESGIPROC glad_glGetColorTableSGI = NULL;\nPFNGLGETCOLORTABLEPARAMETERFVSGIPROC glad_glGetColorTableParameterfvSGI = NULL;\nPFNGLGETCOLORTABLEPARAMETERIVSGIPROC glad_glGetColorTableParameterivSGI = NULL;\nPFNGLFINISHTEXTURESUNXPROC glad_glFinishTextureSUNX = NULL;\nPFNGLGLOBALALPHAFACTORBSUNPROC glad_glGlobalAlphaFactorbSUN = NULL;\nPFNGLGLOBALALPHAFACTORSSUNPROC glad_glGlobalAlphaFactorsSUN = NULL;\nPFNGLGLOBALALPHAFACTORISUNPROC glad_glGlobalAlphaFactoriSUN = NULL;\nPFNGLGLOBALALPHAFACTORFSUNPROC glad_glGlobalAlphaFactorfSUN = NULL;\nPFNGLGLOBALALPHAFACTORDSUNPROC glad_glGlobalAlphaFactordSUN = NULL;\nPFNGLGLOBALALPHAFACTORUBSUNPROC glad_glGlobalAlphaFactorubSUN = NULL;\nPFNGLGLOBALALPHAFACTORUSSUNPROC glad_glGlobalAlphaFactorusSUN = NULL;\nPFNGLGLOBALALPHAFACTORUISUNPROC glad_glGlobalAlphaFactoruiSUN = NULL;\nPFNGLDRAWMESHARRAYSSUNPROC glad_glDrawMeshArraysSUN = NULL;\nPFNGLREPLACEMENTCODEUISUNPROC glad_glReplacementCodeuiSUN = NULL;\nPFNGLREPLACEMENTCODEUSSUNPROC glad_glReplacementCodeusSUN = NULL;\nPFNGLREPLACEMENTCODEUBSUNPROC glad_glReplacementCodeubSUN = NULL;\nPFNGLREPLACEMENTCODEUIVSUNPROC glad_glReplacementCodeuivSUN = NULL;\nPFNGLREPLACEMENTCODEUSVSUNPROC glad_glReplacementCodeusvSUN = NULL;\nPFNGLREPLACEMENTCODEUBVSUNPROC glad_glReplacementCodeubvSUN = NULL;\nPFNGLREPLACEMENTCODEPOINTERSUNPROC glad_glReplacementCodePointerSUN = NULL;\nPFNGLCOLOR4UBVERTEX2FSUNPROC glad_glColor4ubVertex2fSUN = NULL;\nPFNGLCOLOR4UBVERTEX2FVSUNPROC glad_glColor4ubVertex2fvSUN = NULL;\nPFNGLCOLOR4UBVERTEX3FSUNPROC glad_glColor4ubVertex3fSUN = NULL;\nPFNGLCOLOR4UBVERTEX3FVSUNPROC glad_glColor4ubVertex3fvSUN = NULL;\nPFNGLCOLOR3FVERTEX3FSUNPROC glad_glColor3fVertex3fSUN = NULL;\nPFNGLCOLOR3FVERTEX3FVSUNPROC glad_glColor3fVertex3fvSUN = NULL;\nPFNGLNORMAL3FVERTEX3FSUNPROC glad_glNormal3fVertex3fSUN = NULL;\nPFNGLNORMAL3FVERTEX3FVSUNPROC glad_glNormal3fVertex3fvSUN = NULL;\nPFNGLCOLOR4FNORMAL3FVERTEX3FSUNPROC glad_glColor4fNormal3fVertex3fSUN = NULL;\nPFNGLCOLOR4FNORMAL3FVERTEX3FVSUNPROC glad_glColor4fNormal3fVertex3fvSUN = NULL;\nPFNGLTEXCOORD2FVERTEX3FSUNPROC glad_glTexCoord2fVertex3fSUN = NULL;\nPFNGLTEXCOORD2FVERTEX3FVSUNPROC glad_glTexCoord2fVertex3fvSUN = NULL;\nPFNGLTEXCOORD4FVERTEX4FSUNPROC glad_glTexCoord4fVertex4fSUN = NULL;\nPFNGLTEXCOORD4FVERTEX4FVSUNPROC glad_glTexCoord4fVertex4fvSUN = NULL;\nPFNGLTEXCOORD2FCOLOR4UBVERTEX3FSUNPROC glad_glTexCoord2fColor4ubVertex3fSUN = NULL;\nPFNGLTEXCOORD2FCOLOR4UBVERTEX3FVSUNPROC glad_glTexCoord2fColor4ubVertex3fvSUN = NULL;\nPFNGLTEXCOORD2FCOLOR3FVERTEX3FSUNPROC glad_glTexCoord2fColor3fVertex3fSUN = NULL;\nPFNGLTEXCOORD2FCOLOR3FVERTEX3FVSUNPROC glad_glTexCoord2fColor3fVertex3fvSUN = NULL;\nPFNGLTEXCOORD2FNORMAL3FVERTEX3FSUNPROC glad_glTexCoord2fNormal3fVertex3fSUN = NULL;\nPFNGLTEXCOORD2FNORMAL3FVERTEX3FVSUNPROC glad_glTexCoord2fNormal3fVertex3fvSUN = NULL;\nPFNGLTEXCOORD2FCOLOR4FNORMAL3FVERTEX3FSUNPROC glad_glTexCoord2fColor4fNormal3fVertex3fSUN = NULL;\nPFNGLTEXCOORD2FCOLOR4FNORMAL3FVERTEX3FVSUNPROC glad_glTexCoord2fColor4fNormal3fVertex3fvSUN = NULL;\nPFNGLTEXCOORD4FCOLOR4FNORMAL3FVERTEX4FSUNPROC glad_glTexCoord4fColor4fNormal3fVertex4fSUN = NULL;\nPFNGLTEXCOORD4FCOLOR4FNORMAL3FVERTEX4FVSUNPROC glad_glTexCoord4fColor4fNormal3fVertex4fvSUN = NULL;\nPFNGLREPLACEMENTCODEUIVERTEX3FSUNPROC glad_glReplacementCodeuiVertex3fSUN = NULL;\nPFNGLREPLACEMENTCODEUIVERTEX3FVSUNPROC glad_glReplacementCodeuiVertex3fvSUN = NULL;\nPFNGLREPLACEMENTCODEUICOLOR4UBVERTEX3FSUNPROC glad_glReplacementCodeuiColor4ubVertex3fSUN = NULL;\nPFNGLREPLACEMENTCODEUICOLOR4UBVERTEX3FVSUNPROC glad_glReplacementCodeuiColor4ubVertex3fvSUN = NULL;\nPFNGLREPLACEMENTCODEUICOLOR3FVERTEX3FSUNPROC glad_glReplacementCodeuiColor3fVertex3fSUN = NULL;\nPFNGLREPLACEMENTCODEUICOLOR3FVERTEX3FVSUNPROC glad_glReplacementCodeuiColor3fVertex3fvSUN = NULL;\nPFNGLREPLACEMENTCODEUINORMAL3FVERTEX3FSUNPROC glad_glReplacementCodeuiNormal3fVertex3fSUN = NULL;\nPFNGLREPLACEMENTCODEUINORMAL3FVERTEX3FVSUNPROC glad_glReplacementCodeuiNormal3fVertex3fvSUN = NULL;\nPFNGLREPLACEMENTCODEUICOLOR4FNORMAL3FVERTEX3FSUNPROC glad_glReplacementCodeuiColor4fNormal3fVertex3fSUN = NULL;\nPFNGLREPLACEMENTCODEUICOLOR4FNORMAL3FVERTEX3FVSUNPROC glad_glReplacementCodeuiColor4fNormal3fVertex3fvSUN = NULL;\nPFNGLREPLACEMENTCODEUITEXCOORD2FVERTEX3FSUNPROC glad_glReplacementCodeuiTexCoord2fVertex3fSUN = NULL;\nPFNGLREPLACEMENTCODEUITEXCOORD2FVERTEX3FVSUNPROC glad_glReplacementCodeuiTexCoord2fVertex3fvSUN = NULL;\nPFNGLREPLACEMENTCODEUITEXCOORD2FNORMAL3FVERTEX3FSUNPROC glad_glReplacementCodeuiTexCoord2fNormal3fVertex3fSUN = NULL;\nPFNGLREPLACEMENTCODEUITEXCOORD2FNORMAL3FVERTEX3FVSUNPROC glad_glReplacementCodeuiTexCoord2fNormal3fVertex3fvSUN = NULL;\nPFNGLREPLACEMENTCODEUITEXCOORD2FCOLOR4FNORMAL3FVERTEX3FSUNPROC glad_glReplacementCodeuiTexCoord2fColor4fNormal3fVertex3fSUN = NULL;\nPFNGLREPLACEMENTCODEUITEXCOORD2FCOLOR4FNORMAL3FVERTEX3FVSUNPROC glad_glReplacementCodeuiTexCoord2fColor4fNormal3fVertex3fvSUN = NULL;\nPFNGLBLITFRAMEBUFFERANGLEPROC glad_glBlitFramebufferANGLE = NULL;\nPFNGLRENDERBUFFERSTORAGEMULTISAMPLEANGLEPROC glad_glRenderbufferStorageMultisampleANGLE = NULL;\nPFNGLDRAWARRAYSINSTANCEDANGLEPROC glad_glDrawArraysInstancedANGLE = NULL;\nPFNGLDRAWELEMENTSINSTANCEDANGLEPROC glad_glDrawElementsInstancedANGLE = NULL;\nPFNGLVERTEXATTRIBDIVISORANGLEPROC glad_glVertexAttribDivisorANGLE = NULL;\nPFNGLGETTRANSLATEDSHADERSOURCEANGLEPROC glad_glGetTranslatedShaderSourceANGLE = NULL;\nPFNGLCOPYTEXTURELEVELSAPPLEPROC glad_glCopyTextureLevelsAPPLE = NULL;\nPFNGLRENDERBUFFERSTORAGEMULTISAMPLEAPPLEPROC glad_glRenderbufferStorageMultisampleAPPLE = NULL;\nPFNGLRESOLVEMULTISAMPLEFRAMEBUFFERAPPLEPROC glad_glResolveMultisampleFramebufferAPPLE = NULL;\nPFNGLFENCESYNCAPPLEPROC glad_glFenceSyncAPPLE = NULL;\nPFNGLISSYNCAPPLEPROC glad_glIsSyncAPPLE = NULL;\nPFNGLDELETESYNCAPPLEPROC glad_glDeleteSyncAPPLE = NULL;\nPFNGLCLIENTWAITSYNCAPPLEPROC glad_glClientWaitSyncAPPLE = NULL;\nPFNGLWAITSYNCAPPLEPROC glad_glWaitSyncAPPLE = NULL;\nPFNGLGETINTEGER64VAPPLEPROC glad_glGetInteger64vAPPLE = NULL;\nPFNGLGETSYNCIVAPPLEPROC glad_glGetSyncivAPPLE = NULL;\nPFNGLDRAWARRAYSINSTANCEDBASEINSTANCEEXTPROC glad_glDrawArraysInstancedBaseInstanceEXT = NULL;\nPFNGLDRAWELEMENTSINSTANCEDBASEINSTANCEEXTPROC glad_glDrawElementsInstancedBaseInstanceEXT = NULL;\nPFNGLDRAWELEMENTSINSTANCEDBASEVERTEXBASEINSTANCEEXTPROC glad_glDrawElementsInstancedBaseVertexBaseInstanceEXT = NULL;\nPFNGLBINDFRAGDATALOCATIONINDEXEDEXTPROC glad_glBindFragDataLocationIndexedEXT = NULL;\nPFNGLGETPROGRAMRESOURCELOCATIONINDEXEXTPROC glad_glGetProgramResourceLocationIndexEXT = NULL;\nPFNGLGETFRAGDATAINDEXEXTPROC glad_glGetFragDataIndexEXT = NULL;\nPFNGLBUFFERSTORAGEEXTPROC glad_glBufferStorageEXT = NULL;\nPFNGLCLEARTEXIMAGEEXTPROC glad_glClearTexImageEXT = NULL;\nPFNGLCLEARTEXSUBIMAGEEXTPROC glad_glClearTexSubImageEXT = NULL;\nPFNGLCLIPCONTROLEXTPROC glad_glClipControlEXT = NULL;\nPFNGLCOPYIMAGESUBDATAEXTPROC glad_glCopyImageSubDataEXT = NULL;\nPFNGLDISCARDFRAMEBUFFEREXTPROC glad_glDiscardFramebufferEXT = NULL;\nPFNGLGENQUERIESEXTPROC glad_glGenQueriesEXT = NULL;\nPFNGLDELETEQUERIESEXTPROC glad_glDeleteQueriesEXT = NULL;\nPFNGLISQUERYEXTPROC glad_glIsQueryEXT = NULL;\nPFNGLBEGINQUERYEXTPROC glad_glBeginQueryEXT = NULL;\nPFNGLENDQUERYEXTPROC glad_glEndQueryEXT = NULL;\nPFNGLQUERYCOUNTEREXTPROC glad_glQueryCounterEXT = NULL;\nPFNGLGETQUERYIVEXTPROC glad_glGetQueryivEXT = NULL;\nPFNGLGETQUERYOBJECTIVEXTPROC glad_glGetQueryObjectivEXT = NULL;\nPFNGLGETQUERYOBJECTUIVEXTPROC glad_glGetQueryObjectuivEXT = NULL;\nPFNGLDRAWBUFFERSEXTPROC glad_glDrawBuffersEXT = NULL;\nPFNGLENABLEIEXTPROC glad_glEnableiEXT = NULL;\nPFNGLDISABLEIEXTPROC glad_glDisableiEXT = NULL;\nPFNGLBLENDEQUATIONIEXTPROC glad_glBlendEquationiEXT = NULL;\nPFNGLBLENDEQUATIONSEPARATEIEXTPROC glad_glBlendEquationSeparateiEXT = NULL;\nPFNGLBLENDFUNCIEXTPROC glad_glBlendFunciEXT = NULL;\nPFNGLBLENDFUNCSEPARATEIEXTPROC glad_glBlendFuncSeparateiEXT = NULL;\nPFNGLCOLORMASKIEXTPROC glad_glColorMaskiEXT = NULL;\nPFNGLISENABLEDIEXTPROC glad_glIsEnablediEXT = NULL;\nPFNGLDRAWELEMENTSBASEVERTEXEXTPROC glad_glDrawElementsBaseVertexEXT = NULL;\nPFNGLDRAWRANGEELEMENTSBASEVERTEXEXTPROC glad_glDrawRangeElementsBaseVertexEXT = NULL;\nPFNGLDRAWELEMENTSINSTANCEDBASEVERTEXEXTPROC glad_glDrawElementsInstancedBaseVertexEXT = NULL;\nPFNGLMULTIDRAWELEMENTSBASEVERTEXEXTPROC glad_glMultiDrawElementsBaseVertexEXT = NULL;\nPFNGLDRAWTRANSFORMFEEDBACKEXTPROC glad_glDrawTransformFeedbackEXT = NULL;\nPFNGLDRAWTRANSFORMFEEDBACKINSTANCEDEXTPROC glad_glDrawTransformFeedbackInstancedEXT = NULL;\nPFNGLVERTEXATTRIBDIVISOREXTPROC glad_glVertexAttribDivisorEXT = NULL;\nPFNGLMAPBUFFERRANGEEXTPROC glad_glMapBufferRangeEXT = NULL;\nPFNGLFLUSHMAPPEDBUFFERRANGEEXTPROC glad_glFlushMappedBufferRangeEXT = NULL;\nPFNGLMULTIDRAWARRAYSINDIRECTEXTPROC glad_glMultiDrawArraysIndirectEXT = NULL;\nPFNGLMULTIDRAWELEMENTSINDIRECTEXTPROC glad_glMultiDrawElementsIndirectEXT = NULL;\nPFNGLFRAMEBUFFERTEXTURE2DMULTISAMPLEEXTPROC glad_glFramebufferTexture2DMultisampleEXT = NULL;\nPFNGLREADBUFFERINDEXEDEXTPROC glad_glReadBufferIndexedEXT = NULL;\nPFNGLDRAWBUFFERSINDEXEDEXTPROC glad_glDrawBuffersIndexedEXT = NULL;\nPFNGLGETINTEGERI_VEXTPROC glad_glGetIntegeri_vEXT = NULL;\nPFNGLPRIMITIVEBOUNDINGBOXEXTPROC glad_glPrimitiveBoundingBoxEXT = NULL;\nPFNGLGETGRAPHICSRESETSTATUSEXTPROC glad_glGetGraphicsResetStatusEXT = NULL;\nPFNGLREADNPIXELSEXTPROC glad_glReadnPixelsEXT = NULL;\nPFNGLGETNUNIFORMFVEXTPROC glad_glGetnUniformfvEXT = NULL;\nPFNGLGETNUNIFORMIVEXTPROC glad_glGetnUniformivEXT = NULL;\nPFNGLFRAMEBUFFERPIXELLOCALSTORAGESIZEEXTPROC glad_glFramebufferPixelLocalStorageSizeEXT = NULL;\nPFNGLGETFRAMEBUFFERPIXELLOCALSTORAGESIZEEXTPROC glad_glGetFramebufferPixelLocalStorageSizeEXT = NULL;\nPFNGLCLEARPIXELLOCALSTORAGEUIEXTPROC glad_glClearPixelLocalStorageuiEXT = NULL;\nPFNGLTEXPAGECOMMITMENTEXTPROC glad_glTexPageCommitmentEXT = NULL;\nPFNGLPATCHPARAMETERIEXTPROC glad_glPatchParameteriEXT = NULL;\nPFNGLSAMPLERPARAMETERIIVEXTPROC glad_glSamplerParameterIivEXT = NULL;\nPFNGLSAMPLERPARAMETERIUIVEXTPROC glad_glSamplerParameterIuivEXT = NULL;\nPFNGLGETSAMPLERPARAMETERIIVEXTPROC glad_glGetSamplerParameterIivEXT = NULL;\nPFNGLGETSAMPLERPARAMETERIUIVEXTPROC glad_glGetSamplerParameterIuivEXT = NULL;\nPFNGLTEXBUFFERRANGEEXTPROC glad_glTexBufferRangeEXT = NULL;\nPFNGLTEXSTORAGE1DEXTPROC glad_glTexStorage1DEXT = NULL;\nPFNGLTEXSTORAGE2DEXTPROC glad_glTexStorage2DEXT = NULL;\nPFNGLTEXSTORAGE3DEXTPROC glad_glTexStorage3DEXT = NULL;\nPFNGLTEXTUREVIEWEXTPROC glad_glTextureViewEXT = NULL;\nPFNGLGETTEXTUREHANDLEIMGPROC glad_glGetTextureHandleIMG = NULL;\nPFNGLGETTEXTURESAMPLERHANDLEIMGPROC glad_glGetTextureSamplerHandleIMG = NULL;\nPFNGLUNIFORMHANDLEUI64IMGPROC glad_glUniformHandleui64IMG = NULL;\nPFNGLUNIFORMHANDLEUI64VIMGPROC glad_glUniformHandleui64vIMG = NULL;\nPFNGLPROGRAMUNIFORMHANDLEUI64IMGPROC glad_glProgramUniformHandleui64IMG = NULL;\nPFNGLPROGRAMUNIFORMHANDLEUI64VIMGPROC glad_glProgramUniformHandleui64vIMG = NULL;\nPFNGLFRAMEBUFFERTEXTURE2DDOWNSAMPLEIMGPROC glad_glFramebufferTexture2DDownsampleIMG = NULL;\nPFNGLFRAMEBUFFERTEXTURELAYERDOWNSAMPLEIMGPROC glad_glFramebufferTextureLayerDownsampleIMG = NULL;\nPFNGLRENDERBUFFERSTORAGEMULTISAMPLEIMGPROC glad_glRenderbufferStorageMultisampleIMG = NULL;\nPFNGLFRAMEBUFFERTEXTURE2DMULTISAMPLEIMGPROC glad_glFramebufferTexture2DMultisampleIMG = NULL;\nPFNGLCOPYBUFFERSUBDATANVPROC glad_glCopyBufferSubDataNV = NULL;\nPFNGLCOVERAGEMASKNVPROC glad_glCoverageMaskNV = NULL;\nPFNGLCOVERAGEOPERATIONNVPROC glad_glCoverageOperationNV = NULL;\nPFNGLDRAWBUFFERSNVPROC glad_glDrawBuffersNV = NULL;\nPFNGLDRAWARRAYSINSTANCEDNVPROC glad_glDrawArraysInstancedNV = NULL;\nPFNGLDRAWELEMENTSINSTANCEDNVPROC glad_glDrawElementsInstancedNV = NULL;\nPFNGLBLITFRAMEBUFFERNVPROC glad_glBlitFramebufferNV = NULL;\nPFNGLRENDERBUFFERSTORAGEMULTISAMPLENVPROC glad_glRenderbufferStorageMultisampleNV = NULL;\nPFNGLVERTEXATTRIBDIVISORNVPROC glad_glVertexAttribDivisorNV = NULL;\nPFNGLUNIFORMMATRIX2X3FVNVPROC glad_glUniformMatrix2x3fvNV = NULL;\nPFNGLUNIFORMMATRIX3X2FVNVPROC glad_glUniformMatrix3x2fvNV = NULL;\nPFNGLUNIFORMMATRIX2X4FVNVPROC glad_glUniformMatrix2x4fvNV = NULL;\nPFNGLUNIFORMMATRIX4X2FVNVPROC glad_glUniformMatrix4x2fvNV = NULL;\nPFNGLUNIFORMMATRIX3X4FVNVPROC glad_glUniformMatrix3x4fvNV = NULL;\nPFNGLUNIFORMMATRIX4X3FVNVPROC glad_glUniformMatrix4x3fvNV = NULL;\nPFNGLPOLYGONMODENVPROC glad_glPolygonModeNV = NULL;\nPFNGLREADBUFFERNVPROC glad_glReadBufferNV = NULL;\nPFNGLVIEWPORTARRAYVNVPROC glad_glViewportArrayvNV = NULL;\nPFNGLVIEWPORTINDEXEDFNVPROC glad_glViewportIndexedfNV = NULL;\nPFNGLVIEWPORTINDEXEDFVNVPROC glad_glViewportIndexedfvNV = NULL;\nPFNGLSCISSORARRAYVNVPROC glad_glScissorArrayvNV = NULL;\nPFNGLSCISSORINDEXEDNVPROC glad_glScissorIndexedNV = NULL;\nPFNGLSCISSORINDEXEDVNVPROC glad_glScissorIndexedvNV = NULL;\nPFNGLDEPTHRANGEARRAYFVNVPROC glad_glDepthRangeArrayfvNV = NULL;\nPFNGLDEPTHRANGEINDEXEDFNVPROC glad_glDepthRangeIndexedfNV = NULL;\nPFNGLGETFLOATI_VNVPROC glad_glGetFloati_vNV = NULL;\nPFNGLENABLEINVPROC glad_glEnableiNV = NULL;\nPFNGLDISABLEINVPROC glad_glDisableiNV = NULL;\nPFNGLISENABLEDINVPROC glad_glIsEnablediNV = NULL;\nPFNGLEGLIMAGETARGETTEXTURE2DOESPROC glad_glEGLImageTargetTexture2DOES = NULL;\nPFNGLEGLIMAGETARGETRENDERBUFFERSTORAGEOESPROC glad_glEGLImageTargetRenderbufferStorageOES = NULL;\nPFNGLCOPYIMAGESUBDATAOESPROC glad_glCopyImageSubDataOES = NULL;\nPFNGLENABLEIOESPROC glad_glEnableiOES = NULL;\nPFNGLDISABLEIOESPROC glad_glDisableiOES = NULL;\nPFNGLBLENDEQUATIONIOESPROC glad_glBlendEquationiOES = NULL;\nPFNGLBLENDEQUATIONSEPARATEIOESPROC glad_glBlendEquationSeparateiOES = NULL;\nPFNGLBLENDFUNCIOESPROC glad_glBlendFunciOES = NULL;\nPFNGLBLENDFUNCSEPARATEIOESPROC glad_glBlendFuncSeparateiOES = NULL;\nPFNGLCOLORMASKIOESPROC glad_glColorMaskiOES = NULL;\nPFNGLISENABLEDIOESPROC glad_glIsEnablediOES = NULL;\nPFNGLDRAWELEMENTSBASEVERTEXOESPROC glad_glDrawElementsBaseVertexOES = NULL;\nPFNGLDRAWRANGEELEMENTSBASEVERTEXOESPROC glad_glDrawRangeElementsBaseVertexOES = NULL;\nPFNGLDRAWELEMENTSINSTANCEDBASEVERTEXOESPROC glad_glDrawElementsInstancedBaseVertexOES = NULL;\nPFNGLFRAMEBUFFERTEXTUREOESPROC glad_glFramebufferTextureOES = NULL;\nPFNGLGETPROGRAMBINARYOESPROC glad_glGetProgramBinaryOES = NULL;\nPFNGLPROGRAMBINARYOESPROC glad_glProgramBinaryOES = NULL;\nPFNGLMAPBUFFEROESPROC glad_glMapBufferOES = NULL;\nPFNGLUNMAPBUFFEROESPROC glad_glUnmapBufferOES = NULL;\nPFNGLGETBUFFERPOINTERVOESPROC glad_glGetBufferPointervOES = NULL;\nPFNGLPRIMITIVEBOUNDINGBOXOESPROC glad_glPrimitiveBoundingBoxOES = NULL;\nPFNGLMINSAMPLESHADINGOESPROC glad_glMinSampleShadingOES = NULL;\nPFNGLPATCHPARAMETERIOESPROC glad_glPatchParameteriOES = NULL;\nPFNGLTEXIMAGE3DOESPROC glad_glTexImage3DOES = NULL;\nPFNGLTEXSUBIMAGE3DOESPROC glad_glTexSubImage3DOES = NULL;\nPFNGLCOPYTEXSUBIMAGE3DOESPROC glad_glCopyTexSubImage3DOES = NULL;\nPFNGLCOMPRESSEDTEXIMAGE3DOESPROC glad_glCompressedTexImage3DOES = NULL;\nPFNGLCOMPRESSEDTEXSUBIMAGE3DOESPROC glad_glCompressedTexSubImage3DOES = NULL;\nPFNGLFRAMEBUFFERTEXTURE3DOESPROC glad_glFramebufferTexture3DOES = NULL;\nPFNGLTEXPARAMETERIIVOESPROC glad_glTexParameterIivOES = NULL;\nPFNGLTEXPARAMETERIUIVOESPROC glad_glTexParameterIuivOES = NULL;\nPFNGLGETTEXPARAMETERIIVOESPROC glad_glGetTexParameterIivOES = NULL;\nPFNGLGETTEXPARAMETERIUIVOESPROC glad_glGetTexParameterIuivOES = NULL;\nPFNGLSAMPLERPARAMETERIIVOESPROC glad_glSamplerParameterIivOES = NULL;\nPFNGLSAMPLERPARAMETERIUIVOESPROC glad_glSamplerParameterIuivOES = NULL;\nPFNGLGETSAMPLERPARAMETERIIVOESPROC glad_glGetSamplerParameterIivOES = NULL;\nPFNGLGETSAMPLERPARAMETERIUIVOESPROC glad_glGetSamplerParameterIuivOES = NULL;\nPFNGLTEXBUFFEROESPROC glad_glTexBufferOES = NULL;\nPFNGLTEXBUFFERRANGEOESPROC glad_glTexBufferRangeOES = NULL;\nPFNGLTEXSTORAGE3DMULTISAMPLEOESPROC glad_glTexStorage3DMultisampleOES = NULL;\nPFNGLTEXTUREVIEWOESPROC glad_glTextureViewOES = NULL;\nPFNGLBINDVERTEXARRAYOESPROC glad_glBindVertexArrayOES = NULL;\nPFNGLDELETEVERTEXARRAYSOESPROC glad_glDeleteVertexArraysOES = NULL;\nPFNGLGENVERTEXARRAYSOESPROC glad_glGenVertexArraysOES = NULL;\nPFNGLISVERTEXARRAYOESPROC glad_glIsVertexArrayOES = NULL;\nPFNGLVIEWPORTARRAYVOESPROC glad_glViewportArrayvOES = NULL;\nPFNGLVIEWPORTINDEXEDFOESPROC glad_glViewportIndexedfOES = NULL;\nPFNGLVIEWPORTINDEXEDFVOESPROC glad_glViewportIndexedfvOES = NULL;\nPFNGLSCISSORARRAYVOESPROC glad_glScissorArrayvOES = NULL;\nPFNGLSCISSORINDEXEDOESPROC glad_glScissorIndexedOES = NULL;\nPFNGLSCISSORINDEXEDVOESPROC glad_glScissorIndexedvOES = NULL;\nPFNGLDEPTHRANGEARRAYFVOESPROC glad_glDepthRangeArrayfvOES = NULL;\nPFNGLDEPTHRANGEINDEXEDFOESPROC glad_glDepthRangeIndexedfOES = NULL;\nPFNGLGETFLOATI_VOESPROC glad_glGetFloati_vOES = NULL;\nPFNGLFRAMEBUFFERTEXTUREMULTISAMPLEMULTIVIEWOVRPROC glad_glFramebufferTextureMultisampleMultiviewOVR = NULL;\nPFNGLALPHAFUNCQCOMPROC glad_glAlphaFuncQCOM = NULL;\nPFNGLGETDRIVERCONTROLSQCOMPROC glad_glGetDriverControlsQCOM = NULL;\nPFNGLGETDRIVERCONTROLSTRINGQCOMPROC glad_glGetDriverControlStringQCOM = NULL;\nPFNGLENABLEDRIVERCONTROLQCOMPROC glad_glEnableDriverControlQCOM = NULL;\nPFNGLDISABLEDRIVERCONTROLQCOMPROC glad_glDisableDriverControlQCOM = NULL;\nPFNGLEXTGETTEXTURESQCOMPROC glad_glExtGetTexturesQCOM = NULL;\nPFNGLEXTGETBUFFERSQCOMPROC glad_glExtGetBuffersQCOM = NULL;\nPFNGLEXTGETRENDERBUFFERSQCOMPROC glad_glExtGetRenderbuffersQCOM = NULL;\nPFNGLEXTGETFRAMEBUFFERSQCOMPROC glad_glExtGetFramebuffersQCOM = NULL;\nPFNGLEXTGETTEXLEVELPARAMETERIVQCOMPROC glad_glExtGetTexLevelParameterivQCOM = NULL;\nPFNGLEXTTEXOBJECTSTATEOVERRIDEIQCOMPROC glad_glExtTexObjectStateOverrideiQCOM = NULL;\nPFNGLEXTGETTEXSUBIMAGEQCOMPROC glad_glExtGetTexSubImageQCOM = NULL;\nPFNGLEXTGETBUFFERPOINTERVQCOMPROC glad_glExtGetBufferPointervQCOM = NULL;\nPFNGLEXTGETSHADERSQCOMPROC glad_glExtGetShadersQCOM = NULL;\nPFNGLEXTGETPROGRAMSQCOMPROC glad_glExtGetProgramsQCOM = NULL;\nPFNGLEXTISPROGRAMBINARYQCOMPROC glad_glExtIsProgramBinaryQCOM = NULL;\nPFNGLEXTGETPROGRAMBINARYSOURCEQCOMPROC glad_glExtGetProgramBinarySourceQCOM = NULL;\nPFNGLFRAMEBUFFERFOVEATIONCONFIGQCOMPROC glad_glFramebufferFoveationConfigQCOM = NULL;\nPFNGLFRAMEBUFFERFOVEATIONPARAMETERSQCOMPROC glad_glFramebufferFoveationParametersQCOM = NULL;\nPFNGLFRAMEBUFFERFETCHBARRIERQCOMPROC glad_glFramebufferFetchBarrierQCOM = NULL;\nPFNGLTEXTUREFOVEATIONPARAMETERSQCOMPROC glad_glTextureFoveationParametersQCOM = NULL;\nPFNGLSTARTTILINGQCOMPROC glad_glStartTilingQCOM = NULL;\nPFNGLENDTILINGQCOMPROC glad_glEndTilingQCOM = NULL;\nstatic void load_GL_VERSION_1_0(GLADloadproc load) {\n\tif(!GLAD_GL_VERSION_1_0) return;\n\tglad_glCullFace = (PFNGLCULLFACEPROC)load(\"glCullFace\");\n\tglad_glFrontFace = (PFNGLFRONTFACEPROC)load(\"glFrontFace\");\n\tglad_glHint = (PFNGLHINTPROC)load(\"glHint\");\n\tglad_glLineWidth = (PFNGLLINEWIDTHPROC)load(\"glLineWidth\");\n\tglad_glPointSize = (PFNGLPOINTSIZEPROC)load(\"glPointSize\");\n\tglad_glPolygonMode = (PFNGLPOLYGONMODEPROC)load(\"glPolygonMode\");\n\tglad_glScissor = (PFNGLSCISSORPROC)load(\"glScissor\");\n\tglad_glTexParameterf = (PFNGLTEXPARAMETERFPROC)load(\"glTexParameterf\");\n\tglad_glTexParameterfv = (PFNGLTEXPARAMETERFVPROC)load(\"glTexParameterfv\");\n\tglad_glTexParameteri = (PFNGLTEXPARAMETERIPROC)load(\"glTexParameteri\");\n\tglad_glTexParameteriv = (PFNGLTEXPARAMETERIVPROC)load(\"glTexParameteriv\");\n\tglad_glTexImage1D = (PFNGLTEXIMAGE1DPROC)load(\"glTexImage1D\");\n\tglad_glTexImage2D = (PFNGLTEXIMAGE2DPROC)load(\"glTexImage2D\");\n\tglad_glDrawBuffer = (PFNGLDRAWBUFFERPROC)load(\"glDrawBuffer\");\n\tglad_glClear = (PFNGLCLEARPROC)load(\"glClear\");\n\tglad_glClearColor = (PFNGLCLEARCOLORPROC)load(\"glClearColor\");\n\tglad_glClearStencil = (PFNGLCLEARSTENCILPROC)load(\"glClearStencil\");\n\tglad_glClearDepth = (PFNGLCLEARDEPTHPROC)load(\"glClearDepth\");\n\tglad_glStencilMask = (PFNGLSTENCILMASKPROC)load(\"glStencilMask\");\n\tglad_glColorMask = (PFNGLCOLORMASKPROC)load(\"glColorMask\");\n\tglad_glDepthMask = (PFNGLDEPTHMASKPROC)load(\"glDepthMask\");\n\tglad_glDisable = (PFNGLDISABLEPROC)load(\"glDisable\");\n\tglad_glEnable = (PFNGLENABLEPROC)load(\"glEnable\");\n\tglad_glFinish = (PFNGLFINISHPROC)load(\"glFinish\");\n\tglad_glFlush = (PFNGLFLUSHPROC)load(\"glFlush\");\n\tglad_glBlendFunc = (PFNGLBLENDFUNCPROC)load(\"glBlendFunc\");\n\tglad_glLogicOp = (PFNGLLOGICOPPROC)load(\"glLogicOp\");\n\tglad_glStencilFunc = (PFNGLSTENCILFUNCPROC)load(\"glStencilFunc\");\n\tglad_glStencilOp = (PFNGLSTENCILOPPROC)load(\"glStencilOp\");\n\tglad_glDepthFunc = (PFNGLDEPTHFUNCPROC)load(\"glDepthFunc\");\n\tglad_glPixelStoref = (PFNGLPIXELSTOREFPROC)load(\"glPixelStoref\");\n\tglad_glPixelStorei = (PFNGLPIXELSTOREIPROC)load(\"glPixelStorei\");\n\tglad_glReadBuffer = (PFNGLREADBUFFERPROC)load(\"glReadBuffer\");\n\tglad_glReadPixels = (PFNGLREADPIXELSPROC)load(\"glReadPixels\");\n\tglad_glGetBooleanv = (PFNGLGETBOOLEANVPROC)load(\"glGetBooleanv\");\n\tglad_glGetDoublev = (PFNGLGETDOUBLEVPROC)load(\"glGetDoublev\");\n\tglad_glGetError = (PFNGLGETERRORPROC)load(\"glGetError\");\n\tglad_glGetFloatv = (PFNGLGETFLOATVPROC)load(\"glGetFloatv\");\n\tglad_glGetIntegerv = (PFNGLGETINTEGERVPROC)load(\"glGetIntegerv\");\n\tglad_glGetString = (PFNGLGETSTRINGPROC)load(\"glGetString\");\n\tglad_glGetTexImage = (PFNGLGETTEXIMAGEPROC)load(\"glGetTexImage\");\n\tglad_glGetTexParameterfv = (PFNGLGETTEXPARAMETERFVPROC)load(\"glGetTexParameterfv\");\n\tglad_glGetTexParameteriv = (PFNGLGETTEXPARAMETERIVPROC)load(\"glGetTexParameteriv\");\n\tglad_glGetTexLevelParameterfv = (PFNGLGETTEXLEVELPARAMETERFVPROC)load(\"glGetTexLevelParameterfv\");\n\tglad_glGetTexLevelParameteriv = (PFNGLGETTEXLEVELPARAMETERIVPROC)load(\"glGetTexLevelParameteriv\");\n\tglad_glIsEnabled = (PFNGLISENABLEDPROC)load(\"glIsEnabled\");\n\tglad_glDepthRange = (PFNGLDEPTHRANGEPROC)load(\"glDepthRange\");\n\tglad_glViewport = (PFNGLVIEWPORTPROC)load(\"glViewport\");\n}\nstatic void load_GL_VERSION_1_1(GLADloadproc load) {\n\tif(!GLAD_GL_VERSION_1_1) return;\n\tglad_glDrawArrays = (PFNGLDRAWARRAYSPROC)load(\"glDrawArrays\");\n\tglad_glDrawElements = (PFNGLDRAWELEMENTSPROC)load(\"glDrawElements\");\n\tglad_glPolygonOffset = (PFNGLPOLYGONOFFSETPROC)load(\"glPolygonOffset\");\n\tglad_glCopyTexImage1D = (PFNGLCOPYTEXIMAGE1DPROC)load(\"glCopyTexImage1D\");\n\tglad_glCopyTexImage2D = (PFNGLCOPYTEXIMAGE2DPROC)load(\"glCopyTexImage2D\");\n\tglad_glCopyTexSubImage1D = (PFNGLCOPYTEXSUBIMAGE1DPROC)load(\"glCopyTexSubImage1D\");\n\tglad_glCopyTexSubImage2D = (PFNGLCOPYTEXSUBIMAGE2DPROC)load(\"glCopyTexSubImage2D\");\n\tglad_glTexSubImage1D = (PFNGLTEXSUBIMAGE1DPROC)load(\"glTexSubImage1D\");\n\tglad_glTexSubImage2D = (PFNGLTEXSUBIMAGE2DPROC)load(\"glTexSubImage2D\");\n\tglad_glBindTexture = (PFNGLBINDTEXTUREPROC)load(\"glBindTexture\");\n\tglad_glDeleteTextures = (PFNGLDELETETEXTURESPROC)load(\"glDeleteTextures\");\n\tglad_glGenTextures = (PFNGLGENTEXTURESPROC)load(\"glGenTextures\");\n\tglad_glIsTexture = (PFNGLISTEXTUREPROC)load(\"glIsTexture\");\n}\nstatic void load_GL_VERSION_1_2(GLADloadproc load) {\n\tif(!GLAD_GL_VERSION_1_2) return;\n\tglad_glDrawRangeElements = (PFNGLDRAWRANGEELEMENTSPROC)load(\"glDrawRangeElements\");\n\tglad_glTexImage3D = (PFNGLTEXIMAGE3DPROC)load(\"glTexImage3D\");\n\tglad_glTexSubImage3D = (PFNGLTEXSUBIMAGE3DPROC)load(\"glTexSubImage3D\");\n\tglad_glCopyTexSubImage3D = (PFNGLCOPYTEXSUBIMAGE3DPROC)load(\"glCopyTexSubImage3D\");\n}\nstatic void load_GL_VERSION_1_3(GLADloadproc load) {\n\tif(!GLAD_GL_VERSION_1_3) return;\n\tglad_glActiveTexture = (PFNGLACTIVETEXTUREPROC)load(\"glActiveTexture\");\n\tglad_glSampleCoverage = (PFNGLSAMPLECOVERAGEPROC)load(\"glSampleCoverage\");\n\tglad_glCompressedTexImage3D = (PFNGLCOMPRESSEDTEXIMAGE3DPROC)load(\"glCompressedTexImage3D\");\n\tglad_glCompressedTexImage2D = (PFNGLCOMPRESSEDTEXIMAGE2DPROC)load(\"glCompressedTexImage2D\");\n\tglad_glCompressedTexImage1D = (PFNGLCOMPRESSEDTEXIMAGE1DPROC)load(\"glCompressedTexImage1D\");\n\tglad_glCompressedTexSubImage3D = (PFNGLCOMPRESSEDTEXSUBIMAGE3DPROC)load(\"glCompressedTexSubImage3D\");\n\tglad_glCompressedTexSubImage2D = (PFNGLCOMPRESSEDTEXSUBIMAGE2DPROC)load(\"glCompressedTexSubImage2D\");\n\tglad_glCompressedTexSubImage1D = (PFNGLCOMPRESSEDTEXSUBIMAGE1DPROC)load(\"glCompressedTexSubImage1D\");\n\tglad_glGetCompressedTexImage = (PFNGLGETCOMPRESSEDTEXIMAGEPROC)load(\"glGetCompressedTexImage\");\n}\nstatic void load_GL_VERSION_1_4(GLADloadproc load) {\n\tif(!GLAD_GL_VERSION_1_4) return;\n\tglad_glBlendFuncSeparate = (PFNGLBLENDFUNCSEPARATEPROC)load(\"glBlendFuncSeparate\");\n\tglad_glMultiDrawArrays = (PFNGLMULTIDRAWARRAYSPROC)load(\"glMultiDrawArrays\");\n\tglad_glMultiDrawElements = (PFNGLMULTIDRAWELEMENTSPROC)load(\"glMultiDrawElements\");\n\tglad_glPointParameterf = (PFNGLPOINTPARAMETERFPROC)load(\"glPointParameterf\");\n\tglad_glPointParameterfv = (PFNGLPOINTPARAMETERFVPROC)load(\"glPointParameterfv\");\n\tglad_glPointParameteri = (PFNGLPOINTPARAMETERIPROC)load(\"glPointParameteri\");\n\tglad_glPointParameteriv = (PFNGLPOINTPARAMETERIVPROC)load(\"glPointParameteriv\");\n\tglad_glBlendColor = (PFNGLBLENDCOLORPROC)load(\"glBlendColor\");\n\tglad_glBlendEquation = (PFNGLBLENDEQUATIONPROC)load(\"glBlendEquation\");\n}\nstatic void load_GL_VERSION_1_5(GLADloadproc load) {\n\tif(!GLAD_GL_VERSION_1_5) return;\n\tglad_glGenQueries = (PFNGLGENQUERIESPROC)load(\"glGenQueries\");\n\tglad_glDeleteQueries = (PFNGLDELETEQUERIESPROC)load(\"glDeleteQueries\");\n\tglad_glIsQuery = (PFNGLISQUERYPROC)load(\"glIsQuery\");\n\tglad_glBeginQuery = (PFNGLBEGINQUERYPROC)load(\"glBeginQuery\");\n\tglad_glEndQuery = (PFNGLENDQUERYPROC)load(\"glEndQuery\");\n\tglad_glGetQueryiv = (PFNGLGETQUERYIVPROC)load(\"glGetQueryiv\");\n\tglad_glGetQueryObjectiv = (PFNGLGETQUERYOBJECTIVPROC)load(\"glGetQueryObjectiv\");\n\tglad_glGetQueryObjectuiv = (PFNGLGETQUERYOBJECTUIVPROC)load(\"glGetQueryObjectuiv\");\n\tglad_glBindBuffer = (PFNGLBINDBUFFERPROC)load(\"glBindBuffer\");\n\tglad_glDeleteBuffers = (PFNGLDELETEBUFFERSPROC)load(\"glDeleteBuffers\");\n\tglad_glGenBuffers = (PFNGLGENBUFFERSPROC)load(\"glGenBuffers\");\n\tglad_glIsBuffer = (PFNGLISBUFFERPROC)load(\"glIsBuffer\");\n\tglad_glBufferData = (PFNGLBUFFERDATAPROC)load(\"glBufferData\");\n\tglad_glBufferSubData = (PFNGLBUFFERSUBDATAPROC)load(\"glBufferSubData\");\n\tglad_glGetBufferSubData = (PFNGLGETBUFFERSUBDATAPROC)load(\"glGetBufferSubData\");\n\tglad_glMapBuffer = (PFNGLMAPBUFFERPROC)load(\"glMapBuffer\");\n\tglad_glUnmapBuffer = (PFNGLUNMAPBUFFERPROC)load(\"glUnmapBuffer\");\n\tglad_glGetBufferParameteriv = (PFNGLGETBUFFERPARAMETERIVPROC)load(\"glGetBufferParameteriv\");\n\tglad_glGetBufferPointerv = (PFNGLGETBUFFERPOINTERVPROC)load(\"glGetBufferPointerv\");\n}\nstatic void load_GL_VERSION_2_0(GLADloadproc load) {\n\tif(!GLAD_GL_VERSION_2_0) return;\n\tglad_glBlendEquationSeparate = (PFNGLBLENDEQUATIONSEPARATEPROC)load(\"glBlendEquationSeparate\");\n\tglad_glDrawBuffers = (PFNGLDRAWBUFFERSPROC)load(\"glDrawBuffers\");\n\tglad_glStencilOpSeparate = (PFNGLSTENCILOPSEPARATEPROC)load(\"glStencilOpSeparate\");\n\tglad_glStencilFuncSeparate = (PFNGLSTENCILFUNCSEPARATEPROC)load(\"glStencilFuncSeparate\");\n\tglad_glStencilMaskSeparate = (PFNGLSTENCILMASKSEPARATEPROC)load(\"glStencilMaskSeparate\");\n\tglad_glAttachShader = (PFNGLATTACHSHADERPROC)load(\"glAttachShader\");\n\tglad_glBindAttribLocation = (PFNGLBINDATTRIBLOCATIONPROC)load(\"glBindAttribLocation\");\n\tglad_glCompileShader = (PFNGLCOMPILESHADERPROC)load(\"glCompileShader\");\n\tglad_glCreateProgram = (PFNGLCREATEPROGRAMPROC)load(\"glCreateProgram\");\n\tglad_glCreateShader = (PFNGLCREATESHADERPROC)load(\"glCreateShader\");\n\tglad_glDeleteProgram = (PFNGLDELETEPROGRAMPROC)load(\"glDeleteProgram\");\n\tglad_glDeleteShader = (PFNGLDELETESHADERPROC)load(\"glDeleteShader\");\n\tglad_glDetachShader = (PFNGLDETACHSHADERPROC)load(\"glDetachShader\");\n\tglad_glDisableVertexAttribArray = (PFNGLDISABLEVERTEXATTRIBARRAYPROC)load(\"glDisableVertexAttribArray\");\n\tglad_glEnableVertexAttribArray = (PFNGLENABLEVERTEXATTRIBARRAYPROC)load(\"glEnableVertexAttribArray\");\n\tglad_glGetActiveAttrib = (PFNGLGETACTIVEATTRIBPROC)load(\"glGetActiveAttrib\");\n\tglad_glGetActiveUniform = (PFNGLGETACTIVEUNIFORMPROC)load(\"glGetActiveUniform\");\n\tglad_glGetAttachedShaders = (PFNGLGETATTACHEDSHADERSPROC)load(\"glGetAttachedShaders\");\n\tglad_glGetAttribLocation = (PFNGLGETATTRIBLOCATIONPROC)load(\"glGetAttribLocation\");\n\tglad_glGetProgramiv = (PFNGLGETPROGRAMIVPROC)load(\"glGetProgramiv\");\n\tglad_glGetProgramInfoLog = (PFNGLGETPROGRAMINFOLOGPROC)load(\"glGetProgramInfoLog\");\n\tglad_glGetShaderiv = (PFNGLGETSHADERIVPROC)load(\"glGetShaderiv\");\n\tglad_glGetShaderInfoLog = (PFNGLGETSHADERINFOLOGPROC)load(\"glGetShaderInfoLog\");\n\tglad_glGetShaderSource = (PFNGLGETSHADERSOURCEPROC)load(\"glGetShaderSource\");\n\tglad_glGetUniformLocation = (PFNGLGETUNIFORMLOCATIONPROC)load(\"glGetUniformLocation\");\n\tglad_glGetUniformfv = (PFNGLGETUNIFORMFVPROC)load(\"glGetUniformfv\");\n\tglad_glGetUniformiv = (PFNGLGETUNIFORMIVPROC)load(\"glGetUniformiv\");\n\tglad_glGetVertexAttribdv = (PFNGLGETVERTEXATTRIBDVPROC)load(\"glGetVertexAttribdv\");\n\tglad_glGetVertexAttribfv = (PFNGLGETVERTEXATTRIBFVPROC)load(\"glGetVertexAttribfv\");\n\tglad_glGetVertexAttribiv = (PFNGLGETVERTEXATTRIBIVPROC)load(\"glGetVertexAttribiv\");\n\tglad_glGetVertexAttribPointerv = (PFNGLGETVERTEXATTRIBPOINTERVPROC)load(\"glGetVertexAttribPointerv\");\n\tglad_glIsProgram = (PFNGLISPROGRAMPROC)load(\"glIsProgram\");\n\tglad_glIsShader = (PFNGLISSHADERPROC)load(\"glIsShader\");\n\tglad_glLinkProgram = (PFNGLLINKPROGRAMPROC)load(\"glLinkProgram\");\n\tglad_glShaderSource = (PFNGLSHADERSOURCEPROC)load(\"glShaderSource\");\n\tglad_glUseProgram = (PFNGLUSEPROGRAMPROC)load(\"glUseProgram\");\n\tglad_glUniform1f = (PFNGLUNIFORM1FPROC)load(\"glUniform1f\");\n\tglad_glUniform2f = (PFNGLUNIFORM2FPROC)load(\"glUniform2f\");\n\tglad_glUniform3f = (PFNGLUNIFORM3FPROC)load(\"glUniform3f\");\n\tglad_glUniform4f = (PFNGLUNIFORM4FPROC)load(\"glUniform4f\");\n\tglad_glUniform1i = (PFNGLUNIFORM1IPROC)load(\"glUniform1i\");\n\tglad_glUniform2i = (PFNGLUNIFORM2IPROC)load(\"glUniform2i\");\n\tglad_glUniform3i = (PFNGLUNIFORM3IPROC)load(\"glUniform3i\");\n\tglad_glUniform4i = (PFNGLUNIFORM4IPROC)load(\"glUniform4i\");\n\tglad_glUniform1fv = (PFNGLUNIFORM1FVPROC)load(\"glUniform1fv\");\n\tglad_glUniform2fv = (PFNGLUNIFORM2FVPROC)load(\"glUniform2fv\");\n\tglad_glUniform3fv = (PFNGLUNIFORM3FVPROC)load(\"glUniform3fv\");\n\tglad_glUniform4fv = (PFNGLUNIFORM4FVPROC)load(\"glUniform4fv\");\n\tglad_glUniform1iv = (PFNGLUNIFORM1IVPROC)load(\"glUniform1iv\");\n\tglad_glUniform2iv = (PFNGLUNIFORM2IVPROC)load(\"glUniform2iv\");\n\tglad_glUniform3iv = (PFNGLUNIFORM3IVPROC)load(\"glUniform3iv\");\n\tglad_glUniform4iv = (PFNGLUNIFORM4IVPROC)load(\"glUniform4iv\");\n\tglad_glUniformMatrix2fv = (PFNGLUNIFORMMATRIX2FVPROC)load(\"glUniformMatrix2fv\");\n\tglad_glUniformMatrix3fv = (PFNGLUNIFORMMATRIX3FVPROC)load(\"glUniformMatrix3fv\");\n\tglad_glUniformMatrix4fv = (PFNGLUNIFORMMATRIX4FVPROC)load(\"glUniformMatrix4fv\");\n\tglad_glValidateProgram = (PFNGLVALIDATEPROGRAMPROC)load(\"glValidateProgram\");\n\tglad_glVertexAttrib1d = (PFNGLVERTEXATTRIB1DPROC)load(\"glVertexAttrib1d\");\n\tglad_glVertexAttrib1dv = (PFNGLVERTEXATTRIB1DVPROC)load(\"glVertexAttrib1dv\");\n\tglad_glVertexAttrib1f = (PFNGLVERTEXATTRIB1FPROC)load(\"glVertexAttrib1f\");\n\tglad_glVertexAttrib1fv = (PFNGLVERTEXATTRIB1FVPROC)load(\"glVertexAttrib1fv\");\n\tglad_glVertexAttrib1s = (PFNGLVERTEXATTRIB1SPROC)load(\"glVertexAttrib1s\");\n\tglad_glVertexAttrib1sv = (PFNGLVERTEXATTRIB1SVPROC)load(\"glVertexAttrib1sv\");\n\tglad_glVertexAttrib2d = (PFNGLVERTEXATTRIB2DPROC)load(\"glVertexAttrib2d\");\n\tglad_glVertexAttrib2dv = (PFNGLVERTEXATTRIB2DVPROC)load(\"glVertexAttrib2dv\");\n\tglad_glVertexAttrib2f = (PFNGLVERTEXATTRIB2FPROC)load(\"glVertexAttrib2f\");\n\tglad_glVertexAttrib2fv = (PFNGLVERTEXATTRIB2FVPROC)load(\"glVertexAttrib2fv\");\n\tglad_glVertexAttrib2s = (PFNGLVERTEXATTRIB2SPROC)load(\"glVertexAttrib2s\");\n\tglad_glVertexAttrib2sv = (PFNGLVERTEXATTRIB2SVPROC)load(\"glVertexAttrib2sv\");\n\tglad_glVertexAttrib3d = (PFNGLVERTEXATTRIB3DPROC)load(\"glVertexAttrib3d\");\n\tglad_glVertexAttrib3dv = (PFNGLVERTEXATTRIB3DVPROC)load(\"glVertexAttrib3dv\");\n\tglad_glVertexAttrib3f = (PFNGLVERTEXATTRIB3FPROC)load(\"glVertexAttrib3f\");\n\tglad_glVertexAttrib3fv = (PFNGLVERTEXATTRIB3FVPROC)load(\"glVertexAttrib3fv\");\n\tglad_glVertexAttrib3s = (PFNGLVERTEXATTRIB3SPROC)load(\"glVertexAttrib3s\");\n\tglad_glVertexAttrib3sv = (PFNGLVERTEXATTRIB3SVPROC)load(\"glVertexAttrib3sv\");\n\tglad_glVertexAttrib4Nbv = (PFNGLVERTEXATTRIB4NBVPROC)load(\"glVertexAttrib4Nbv\");\n\tglad_glVertexAttrib4Niv = (PFNGLVERTEXATTRIB4NIVPROC)load(\"glVertexAttrib4Niv\");\n\tglad_glVertexAttrib4Nsv = (PFNGLVERTEXATTRIB4NSVPROC)load(\"glVertexAttrib4Nsv\");\n\tglad_glVertexAttrib4Nub = (PFNGLVERTEXATTRIB4NUBPROC)load(\"glVertexAttrib4Nub\");\n\tglad_glVertexAttrib4Nubv = (PFNGLVERTEXATTRIB4NUBVPROC)load(\"glVertexAttrib4Nubv\");\n\tglad_glVertexAttrib4Nuiv = (PFNGLVERTEXATTRIB4NUIVPROC)load(\"glVertexAttrib4Nuiv\");\n\tglad_glVertexAttrib4Nusv = (PFNGLVERTEXATTRIB4NUSVPROC)load(\"glVertexAttrib4Nusv\");\n\tglad_glVertexAttrib4bv = (PFNGLVERTEXATTRIB4BVPROC)load(\"glVertexAttrib4bv\");\n\tglad_glVertexAttrib4d = (PFNGLVERTEXATTRIB4DPROC)load(\"glVertexAttrib4d\");\n\tglad_glVertexAttrib4dv = (PFNGLVERTEXATTRIB4DVPROC)load(\"glVertexAttrib4dv\");\n\tglad_glVertexAttrib4f = (PFNGLVERTEXATTRIB4FPROC)load(\"glVertexAttrib4f\");\n\tglad_glVertexAttrib4fv = (PFNGLVERTEXATTRIB4FVPROC)load(\"glVertexAttrib4fv\");\n\tglad_glVertexAttrib4iv = (PFNGLVERTEXATTRIB4IVPROC)load(\"glVertexAttrib4iv\");\n\tglad_glVertexAttrib4s = (PFNGLVERTEXATTRIB4SPROC)load(\"glVertexAttrib4s\");\n\tglad_glVertexAttrib4sv = (PFNGLVERTEXATTRIB4SVPROC)load(\"glVertexAttrib4sv\");\n\tglad_glVertexAttrib4ubv = (PFNGLVERTEXATTRIB4UBVPROC)load(\"glVertexAttrib4ubv\");\n\tglad_glVertexAttrib4uiv = (PFNGLVERTEXATTRIB4UIVPROC)load(\"glVertexAttrib4uiv\");\n\tglad_glVertexAttrib4usv = (PFNGLVERTEXATTRIB4USVPROC)load(\"glVertexAttrib4usv\");\n\tglad_glVertexAttribPointer = (PFNGLVERTEXATTRIBPOINTERPROC)load(\"glVertexAttribPointer\");\n}\nstatic void load_GL_VERSION_2_1(GLADloadproc load) {\n\tif(!GLAD_GL_VERSION_2_1) return;\n\tglad_glUniformMatrix2x3fv = (PFNGLUNIFORMMATRIX2X3FVPROC)load(\"glUniformMatrix2x3fv\");\n\tglad_glUniformMatrix3x2fv = (PFNGLUNIFORMMATRIX3X2FVPROC)load(\"glUniformMatrix3x2fv\");\n\tglad_glUniformMatrix2x4fv = (PFNGLUNIFORMMATRIX2X4FVPROC)load(\"glUniformMatrix2x4fv\");\n\tglad_glUniformMatrix4x2fv = (PFNGLUNIFORMMATRIX4X2FVPROC)load(\"glUniformMatrix4x2fv\");\n\tglad_glUniformMatrix3x4fv = (PFNGLUNIFORMMATRIX3X4FVPROC)load(\"glUniformMatrix3x4fv\");\n\tglad_glUniformMatrix4x3fv = (PFNGLUNIFORMMATRIX4X3FVPROC)load(\"glUniformMatrix4x3fv\");\n}\nstatic void load_GL_VERSION_3_0(GLADloadproc load) {\n\tif(!GLAD_GL_VERSION_3_0) return;\n\tglad_glColorMaski = (PFNGLCOLORMASKIPROC)load(\"glColorMaski\");\n\tglad_glGetBooleani_v = (PFNGLGETBOOLEANI_VPROC)load(\"glGetBooleani_v\");\n\tglad_glGetIntegeri_v = (PFNGLGETINTEGERI_VPROC)load(\"glGetIntegeri_v\");\n\tglad_glEnablei = (PFNGLENABLEIPROC)load(\"glEnablei\");\n\tglad_glDisablei = (PFNGLDISABLEIPROC)load(\"glDisablei\");\n\tglad_glIsEnabledi = (PFNGLISENABLEDIPROC)load(\"glIsEnabledi\");\n\tglad_glBeginTransformFeedback = (PFNGLBEGINTRANSFORMFEEDBACKPROC)load(\"glBeginTransformFeedback\");\n\tglad_glEndTransformFeedback = (PFNGLENDTRANSFORMFEEDBACKPROC)load(\"glEndTransformFeedback\");\n\tglad_glBindBufferRange = (PFNGLBINDBUFFERRANGEPROC)load(\"glBindBufferRange\");\n\tglad_glBindBufferBase = (PFNGLBINDBUFFERBASEPROC)load(\"glBindBufferBase\");\n\tglad_glTransformFeedbackVaryings = (PFNGLTRANSFORMFEEDBACKVARYINGSPROC)load(\"glTransformFeedbackVaryings\");\n\tglad_glGetTransformFeedbackVarying = (PFNGLGETTRANSFORMFEEDBACKVARYINGPROC)load(\"glGetTransformFeedbackVarying\");\n\tglad_glClampColor = (PFNGLCLAMPCOLORPROC)load(\"glClampColor\");\n\tglad_glBeginConditionalRender = (PFNGLBEGINCONDITIONALRENDERPROC)load(\"glBeginConditionalRender\");\n\tglad_glEndConditionalRender = (PFNGLENDCONDITIONALRENDERPROC)load(\"glEndConditionalRender\");\n\tglad_glVertexAttribIPointer = (PFNGLVERTEXATTRIBIPOINTERPROC)load(\"glVertexAttribIPointer\");\n\tglad_glGetVertexAttribIiv = (PFNGLGETVERTEXATTRIBIIVPROC)load(\"glGetVertexAttribIiv\");\n\tglad_glGetVertexAttribIuiv = (PFNGLGETVERTEXATTRIBIUIVPROC)load(\"glGetVertexAttribIuiv\");\n\tglad_glVertexAttribI1i = (PFNGLVERTEXATTRIBI1IPROC)load(\"glVertexAttribI1i\");\n\tglad_glVertexAttribI2i = (PFNGLVERTEXATTRIBI2IPROC)load(\"glVertexAttribI2i\");\n\tglad_glVertexAttribI3i = (PFNGLVERTEXATTRIBI3IPROC)load(\"glVertexAttribI3i\");\n\tglad_glVertexAttribI4i = (PFNGLVERTEXATTRIBI4IPROC)load(\"glVertexAttribI4i\");\n\tglad_glVertexAttribI1ui = (PFNGLVERTEXATTRIBI1UIPROC)load(\"glVertexAttribI1ui\");\n\tglad_glVertexAttribI2ui = (PFNGLVERTEXATTRIBI2UIPROC)load(\"glVertexAttribI2ui\");\n\tglad_glVertexAttribI3ui = (PFNGLVERTEXATTRIBI3UIPROC)load(\"glVertexAttribI3ui\");\n\tglad_glVertexAttribI4ui = (PFNGLVERTEXATTRIBI4UIPROC)load(\"glVertexAttribI4ui\");\n\tglad_glVertexAttribI1iv = (PFNGLVERTEXATTRIBI1IVPROC)load(\"glVertexAttribI1iv\");\n\tglad_glVertexAttribI2iv = (PFNGLVERTEXATTRIBI2IVPROC)load(\"glVertexAttribI2iv\");\n\tglad_glVertexAttribI3iv = (PFNGLVERTEXATTRIBI3IVPROC)load(\"glVertexAttribI3iv\");\n\tglad_glVertexAttribI4iv = (PFNGLVERTEXATTRIBI4IVPROC)load(\"glVertexAttribI4iv\");\n\tglad_glVertexAttribI1uiv = (PFNGLVERTEXATTRIBI1UIVPROC)load(\"glVertexAttribI1uiv\");\n\tglad_glVertexAttribI2uiv = (PFNGLVERTEXATTRIBI2UIVPROC)load(\"glVertexAttribI2uiv\");\n\tglad_glVertexAttribI3uiv = (PFNGLVERTEXATTRIBI3UIVPROC)load(\"glVertexAttribI3uiv\");\n\tglad_glVertexAttribI4uiv = (PFNGLVERTEXATTRIBI4UIVPROC)load(\"glVertexAttribI4uiv\");\n\tglad_glVertexAttribI4bv = (PFNGLVERTEXATTRIBI4BVPROC)load(\"glVertexAttribI4bv\");\n\tglad_glVertexAttribI4sv = (PFNGLVERTEXATTRIBI4SVPROC)load(\"glVertexAttribI4sv\");\n\tglad_glVertexAttribI4ubv = (PFNGLVERTEXATTRIBI4UBVPROC)load(\"glVertexAttribI4ubv\");\n\tglad_glVertexAttribI4usv = (PFNGLVERTEXATTRIBI4USVPROC)load(\"glVertexAttribI4usv\");\n\tglad_glGetUniformuiv = (PFNGLGETUNIFORMUIVPROC)load(\"glGetUniformuiv\");\n\tglad_glBindFragDataLocation = (PFNGLBINDFRAGDATALOCATIONPROC)load(\"glBindFragDataLocation\");\n\tglad_glGetFragDataLocation = (PFNGLGETFRAGDATALOCATIONPROC)load(\"glGetFragDataLocation\");\n\tglad_glUniform1ui = (PFNGLUNIFORM1UIPROC)load(\"glUniform1ui\");\n\tglad_glUniform2ui = (PFNGLUNIFORM2UIPROC)load(\"glUniform2ui\");\n\tglad_glUniform3ui = (PFNGLUNIFORM3UIPROC)load(\"glUniform3ui\");\n\tglad_glUniform4ui = (PFNGLUNIFORM4UIPROC)load(\"glUniform4ui\");\n\tglad_glUniform1uiv = (PFNGLUNIFORM1UIVPROC)load(\"glUniform1uiv\");\n\tglad_glUniform2uiv = (PFNGLUNIFORM2UIVPROC)load(\"glUniform2uiv\");\n\tglad_glUniform3uiv = (PFNGLUNIFORM3UIVPROC)load(\"glUniform3uiv\");\n\tglad_glUniform4uiv = (PFNGLUNIFORM4UIVPROC)load(\"glUniform4uiv\");\n\tglad_glTexParameterIiv = (PFNGLTEXPARAMETERIIVPROC)load(\"glTexParameterIiv\");\n\tglad_glTexParameterIuiv = (PFNGLTEXPARAMETERIUIVPROC)load(\"glTexParameterIuiv\");\n\tglad_glGetTexParameterIiv = (PFNGLGETTEXPARAMETERIIVPROC)load(\"glGetTexParameterIiv\");\n\tglad_glGetTexParameterIuiv = (PFNGLGETTEXPARAMETERIUIVPROC)load(\"glGetTexParameterIuiv\");\n\tglad_glClearBufferiv = (PFNGLCLEARBUFFERIVPROC)load(\"glClearBufferiv\");\n\tglad_glClearBufferuiv = (PFNGLCLEARBUFFERUIVPROC)load(\"glClearBufferuiv\");\n\tglad_glClearBufferfv = (PFNGLCLEARBUFFERFVPROC)load(\"glClearBufferfv\");\n\tglad_glClearBufferfi = (PFNGLCLEARBUFFERFIPROC)load(\"glClearBufferfi\");\n\tglad_glGetStringi = (PFNGLGETSTRINGIPROC)load(\"glGetStringi\");\n\tglad_glIsRenderbuffer = (PFNGLISRENDERBUFFERPROC)load(\"glIsRenderbuffer\");\n\tglad_glBindRenderbuffer = (PFNGLBINDRENDERBUFFERPROC)load(\"glBindRenderbuffer\");\n\tglad_glDeleteRenderbuffers = (PFNGLDELETERENDERBUFFERSPROC)load(\"glDeleteRenderbuffers\");\n\tglad_glGenRenderbuffers = (PFNGLGENRENDERBUFFERSPROC)load(\"glGenRenderbuffers\");\n\tglad_glRenderbufferStorage = (PFNGLRENDERBUFFERSTORAGEPROC)load(\"glRenderbufferStorage\");\n\tglad_glGetRenderbufferParameteriv = (PFNGLGETRENDERBUFFERPARAMETERIVPROC)load(\"glGetRenderbufferParameteriv\");\n\tglad_glIsFramebuffer = (PFNGLISFRAMEBUFFERPROC)load(\"glIsFramebuffer\");\n\tglad_glBindFramebuffer = (PFNGLBINDFRAMEBUFFERPROC)load(\"glBindFramebuffer\");\n\tglad_glDeleteFramebuffers = (PFNGLDELETEFRAMEBUFFERSPROC)load(\"glDeleteFramebuffers\");\n\tglad_glGenFramebuffers = (PFNGLGENFRAMEBUFFERSPROC)load(\"glGenFramebuffers\");\n\tglad_glCheckFramebufferStatus = (PFNGLCHECKFRAMEBUFFERSTATUSPROC)load(\"glCheckFramebufferStatus\");\n\tglad_glFramebufferTexture1D = (PFNGLFRAMEBUFFERTEXTURE1DPROC)load(\"glFramebufferTexture1D\");\n\tglad_glFramebufferTexture2D = (PFNGLFRAMEBUFFERTEXTURE2DPROC)load(\"glFramebufferTexture2D\");\n\tglad_glFramebufferTexture3D = (PFNGLFRAMEBUFFERTEXTURE3DPROC)load(\"glFramebufferTexture3D\");\n\tglad_glFramebufferRenderbuffer = (PFNGLFRAMEBUFFERRENDERBUFFERPROC)load(\"glFramebufferRenderbuffer\");\n\tglad_glGetFramebufferAttachmentParameteriv = (PFNGLGETFRAMEBUFFERATTACHMENTPARAMETERIVPROC)load(\"glGetFramebufferAttachmentParameteriv\");\n\tglad_glGenerateMipmap = (PFNGLGENERATEMIPMAPPROC)load(\"glGenerateMipmap\");\n\tglad_glBlitFramebuffer = (PFNGLBLITFRAMEBUFFERPROC)load(\"glBlitFramebuffer\");\n\tglad_glRenderbufferStorageMultisample = (PFNGLRENDERBUFFERSTORAGEMULTISAMPLEPROC)load(\"glRenderbufferStorageMultisample\");\n\tglad_glFramebufferTextureLayer = (PFNGLFRAMEBUFFERTEXTURELAYERPROC)load(\"glFramebufferTextureLayer\");\n\tglad_glMapBufferRange = (PFNGLMAPBUFFERRANGEPROC)load(\"glMapBufferRange\");\n\tglad_glFlushMappedBufferRange = (PFNGLFLUSHMAPPEDBUFFERRANGEPROC)load(\"glFlushMappedBufferRange\");\n\tglad_glBindVertexArray = (PFNGLBINDVERTEXARRAYPROC)load(\"glBindVertexArray\");\n\tglad_glDeleteVertexArrays = (PFNGLDELETEVERTEXARRAYSPROC)load(\"glDeleteVertexArrays\");\n\tglad_glGenVertexArrays = (PFNGLGENVERTEXARRAYSPROC)load(\"glGenVertexArrays\");\n\tglad_glIsVertexArray = (PFNGLISVERTEXARRAYPROC)load(\"glIsVertexArray\");\n}\nstatic void load_GL_VERSION_3_1(GLADloadproc load) {\n\tif(!GLAD_GL_VERSION_3_1) return;\n\tglad_glDrawArraysInstanced = (PFNGLDRAWARRAYSINSTANCEDPROC)load(\"glDrawArraysInstanced\");\n\tglad_glDrawElementsInstanced = (PFNGLDRAWELEMENTSINSTANCEDPROC)load(\"glDrawElementsInstanced\");\n\tglad_glTexBuffer = (PFNGLTEXBUFFERPROC)load(\"glTexBuffer\");\n\tglad_glPrimitiveRestartIndex = (PFNGLPRIMITIVERESTARTINDEXPROC)load(\"glPrimitiveRestartIndex\");\n\tglad_glCopyBufferSubData = (PFNGLCOPYBUFFERSUBDATAPROC)load(\"glCopyBufferSubData\");\n\tglad_glGetUniformIndices = (PFNGLGETUNIFORMINDICESPROC)load(\"glGetUniformIndices\");\n\tglad_glGetActiveUniformsiv = (PFNGLGETACTIVEUNIFORMSIVPROC)load(\"glGetActiveUniformsiv\");\n\tglad_glGetActiveUniformName = (PFNGLGETACTIVEUNIFORMNAMEPROC)load(\"glGetActiveUniformName\");\n\tglad_glGetUniformBlockIndex = (PFNGLGETUNIFORMBLOCKINDEXPROC)load(\"glGetUniformBlockIndex\");\n\tglad_glGetActiveUniformBlockiv = (PFNGLGETACTIVEUNIFORMBLOCKIVPROC)load(\"glGetActiveUniformBlockiv\");\n\tglad_glGetActiveUniformBlockName = (PFNGLGETACTIVEUNIFORMBLOCKNAMEPROC)load(\"glGetActiveUniformBlockName\");\n\tglad_glUniformBlockBinding = (PFNGLUNIFORMBLOCKBINDINGPROC)load(\"glUniformBlockBinding\");\n\tglad_glBindBufferRange = (PFNGLBINDBUFFERRANGEPROC)load(\"glBindBufferRange\");\n\tglad_glBindBufferBase = (PFNGLBINDBUFFERBASEPROC)load(\"glBindBufferBase\");\n\tglad_glGetIntegeri_v = (PFNGLGETINTEGERI_VPROC)load(\"glGetIntegeri_v\");\n}\nstatic void load_GL_VERSION_3_2(GLADloadproc load) {\n\tif(!GLAD_GL_VERSION_3_2) return;\n\tglad_glDrawElementsBaseVertex = (PFNGLDRAWELEMENTSBASEVERTEXPROC)load(\"glDrawElementsBaseVertex\");\n\tglad_glDrawRangeElementsBaseVertex = (PFNGLDRAWRANGEELEMENTSBASEVERTEXPROC)load(\"glDrawRangeElementsBaseVertex\");\n\tglad_glDrawElementsInstancedBaseVertex = (PFNGLDRAWELEMENTSINSTANCEDBASEVERTEXPROC)load(\"glDrawElementsInstancedBaseVertex\");\n\tglad_glMultiDrawElementsBaseVertex = (PFNGLMULTIDRAWELEMENTSBASEVERTEXPROC)load(\"glMultiDrawElementsBaseVertex\");\n\tglad_glProvokingVertex = (PFNGLPROVOKINGVERTEXPROC)load(\"glProvokingVertex\");\n\tglad_glFenceSync = (PFNGLFENCESYNCPROC)load(\"glFenceSync\");\n\tglad_glIsSync = (PFNGLISSYNCPROC)load(\"glIsSync\");\n\tglad_glDeleteSync = (PFNGLDELETESYNCPROC)load(\"glDeleteSync\");\n\tglad_glClientWaitSync = (PFNGLCLIENTWAITSYNCPROC)load(\"glClientWaitSync\");\n\tglad_glWaitSync = (PFNGLWAITSYNCPROC)load(\"glWaitSync\");\n\tglad_glGetInteger64v = (PFNGLGETINTEGER64VPROC)load(\"glGetInteger64v\");\n\tglad_glGetSynciv = (PFNGLGETSYNCIVPROC)load(\"glGetSynciv\");\n\tglad_glGetInteger64i_v = (PFNGLGETINTEGER64I_VPROC)load(\"glGetInteger64i_v\");\n\tglad_glGetBufferParameteri64v = (PFNGLGETBUFFERPARAMETERI64VPROC)load(\"glGetBufferParameteri64v\");\n\tglad_glFramebufferTexture = (PFNGLFRAMEBUFFERTEXTUREPROC)load(\"glFramebufferTexture\");\n\tglad_glTexImage2DMultisample = (PFNGLTEXIMAGE2DMULTISAMPLEPROC)load(\"glTexImage2DMultisample\");\n\tglad_glTexImage3DMultisample = (PFNGLTEXIMAGE3DMULTISAMPLEPROC)load(\"glTexImage3DMultisample\");\n\tglad_glGetMultisamplefv = (PFNGLGETMULTISAMPLEFVPROC)load(\"glGetMultisamplefv\");\n\tglad_glSampleMaski = (PFNGLSAMPLEMASKIPROC)load(\"glSampleMaski\");\n}\nstatic void load_GL_VERSION_3_3(GLADloadproc load) {\n\tif(!GLAD_GL_VERSION_3_3) return;\n\tglad_glBindFragDataLocationIndexed = (PFNGLBINDFRAGDATALOCATIONINDEXEDPROC)load(\"glBindFragDataLocationIndexed\");\n\tglad_glGetFragDataIndex = (PFNGLGETFRAGDATAINDEXPROC)load(\"glGetFragDataIndex\");\n\tglad_glGenSamplers = (PFNGLGENSAMPLERSPROC)load(\"glGenSamplers\");\n\tglad_glDeleteSamplers = (PFNGLDELETESAMPLERSPROC)load(\"glDeleteSamplers\");\n\tglad_glIsSampler = (PFNGLISSAMPLERPROC)load(\"glIsSampler\");\n\tglad_glBindSampler = (PFNGLBINDSAMPLERPROC)load(\"glBindSampler\");\n\tglad_glSamplerParameteri = (PFNGLSAMPLERPARAMETERIPROC)load(\"glSamplerParameteri\");\n\tglad_glSamplerParameteriv = (PFNGLSAMPLERPARAMETERIVPROC)load(\"glSamplerParameteriv\");\n\tglad_glSamplerParameterf = (PFNGLSAMPLERPARAMETERFPROC)load(\"glSamplerParameterf\");\n\tglad_glSamplerParameterfv = (PFNGLSAMPLERPARAMETERFVPROC)load(\"glSamplerParameterfv\");\n\tglad_glSamplerParameterIiv = (PFNGLSAMPLERPARAMETERIIVPROC)load(\"glSamplerParameterIiv\");\n\tglad_glSamplerParameterIuiv = (PFNGLSAMPLERPARAMETERIUIVPROC)load(\"glSamplerParameterIuiv\");\n\tglad_glGetSamplerParameteriv = (PFNGLGETSAMPLERPARAMETERIVPROC)load(\"glGetSamplerParameteriv\");\n\tglad_glGetSamplerParameterIiv = (PFNGLGETSAMPLERPARAMETERIIVPROC)load(\"glGetSamplerParameterIiv\");\n\tglad_glGetSamplerParameterfv = (PFNGLGETSAMPLERPARAMETERFVPROC)load(\"glGetSamplerParameterfv\");\n\tglad_glGetSamplerParameterIuiv = (PFNGLGETSAMPLERPARAMETERIUIVPROC)load(\"glGetSamplerParameterIuiv\");\n\tglad_glQueryCounter = (PFNGLQUERYCOUNTERPROC)load(\"glQueryCounter\");\n\tglad_glGetQueryObjecti64v = (PFNGLGETQUERYOBJECTI64VPROC)load(\"glGetQueryObjecti64v\");\n\tglad_glGetQueryObjectui64v = (PFNGLGETQUERYOBJECTUI64VPROC)load(\"glGetQueryObjectui64v\");\n\tglad_glVertexAttribDivisor = (PFNGLVERTEXATTRIBDIVISORPROC)load(\"glVertexAttribDivisor\");\n\tglad_glVertexAttribP1ui = (PFNGLVERTEXATTRIBP1UIPROC)load(\"glVertexAttribP1ui\");\n\tglad_glVertexAttribP1uiv = (PFNGLVERTEXATTRIBP1UIVPROC)load(\"glVertexAttribP1uiv\");\n\tglad_glVertexAttribP2ui = (PFNGLVERTEXATTRIBP2UIPROC)load(\"glVertexAttribP2ui\");\n\tglad_glVertexAttribP2uiv = (PFNGLVERTEXATTRIBP2UIVPROC)load(\"glVertexAttribP2uiv\");\n\tglad_glVertexAttribP3ui = (PFNGLVERTEXATTRIBP3UIPROC)load(\"glVertexAttribP3ui\");\n\tglad_glVertexAttribP3uiv = (PFNGLVERTEXATTRIBP3UIVPROC)load(\"glVertexAttribP3uiv\");\n\tglad_glVertexAttribP4ui = (PFNGLVERTEXATTRIBP4UIPROC)load(\"glVertexAttribP4ui\");\n\tglad_glVertexAttribP4uiv = (PFNGLVERTEXATTRIBP4UIVPROC)load(\"glVertexAttribP4uiv\");\n\tglad_glVertexP2ui = (PFNGLVERTEXP2UIPROC)load(\"glVertexP2ui\");\n\tglad_glVertexP2uiv = (PFNGLVERTEXP2UIVPROC)load(\"glVertexP2uiv\");\n\tglad_glVertexP3ui = (PFNGLVERTEXP3UIPROC)load(\"glVertexP3ui\");\n\tglad_glVertexP3uiv = (PFNGLVERTEXP3UIVPROC)load(\"glVertexP3uiv\");\n\tglad_glVertexP4ui = (PFNGLVERTEXP4UIPROC)load(\"glVertexP4ui\");\n\tglad_glVertexP4uiv = (PFNGLVERTEXP4UIVPROC)load(\"glVertexP4uiv\");\n\tglad_glTexCoordP1ui = (PFNGLTEXCOORDP1UIPROC)load(\"glTexCoordP1ui\");\n\tglad_glTexCoordP1uiv = (PFNGLTEXCOORDP1UIVPROC)load(\"glTexCoordP1uiv\");\n\tglad_glTexCoordP2ui = (PFNGLTEXCOORDP2UIPROC)load(\"glTexCoordP2ui\");\n\tglad_glTexCoordP2uiv = (PFNGLTEXCOORDP2UIVPROC)load(\"glTexCoordP2uiv\");\n\tglad_glTexCoordP3ui = (PFNGLTEXCOORDP3UIPROC)load(\"glTexCoordP3ui\");\n\tglad_glTexCoordP3uiv = (PFNGLTEXCOORDP3UIVPROC)load(\"glTexCoordP3uiv\");\n\tglad_glTexCoordP4ui = (PFNGLTEXCOORDP4UIPROC)load(\"glTexCoordP4ui\");\n\tglad_glTexCoordP4uiv = (PFNGLTEXCOORDP4UIVPROC)load(\"glTexCoordP4uiv\");\n\tglad_glMultiTexCoordP1ui = (PFNGLMULTITEXCOORDP1UIPROC)load(\"glMultiTexCoordP1ui\");\n\tglad_glMultiTexCoordP1uiv = (PFNGLMULTITEXCOORDP1UIVPROC)load(\"glMultiTexCoordP1uiv\");\n\tglad_glMultiTexCoordP2ui = (PFNGLMULTITEXCOORDP2UIPROC)load(\"glMultiTexCoordP2ui\");\n\tglad_glMultiTexCoordP2uiv = (PFNGLMULTITEXCOORDP2UIVPROC)load(\"glMultiTexCoordP2uiv\");\n\tglad_glMultiTexCoordP3ui = (PFNGLMULTITEXCOORDP3UIPROC)load(\"glMultiTexCoordP3ui\");\n\tglad_glMultiTexCoordP3uiv = (PFNGLMULTITEXCOORDP3UIVPROC)load(\"glMultiTexCoordP3uiv\");\n\tglad_glMultiTexCoordP4ui = (PFNGLMULTITEXCOORDP4UIPROC)load(\"glMultiTexCoordP4ui\");\n\tglad_glMultiTexCoordP4uiv = (PFNGLMULTITEXCOORDP4UIVPROC)load(\"glMultiTexCoordP4uiv\");\n\tglad_glNormalP3ui = (PFNGLNORMALP3UIPROC)load(\"glNormalP3ui\");\n\tglad_glNormalP3uiv = (PFNGLNORMALP3UIVPROC)load(\"glNormalP3uiv\");\n\tglad_glColorP3ui = (PFNGLCOLORP3UIPROC)load(\"glColorP3ui\");\n\tglad_glColorP3uiv = (PFNGLCOLORP3UIVPROC)load(\"glColorP3uiv\");\n\tglad_glColorP4ui = (PFNGLCOLORP4UIPROC)load(\"glColorP4ui\");\n\tglad_glColorP4uiv = (PFNGLCOLORP4UIVPROC)load(\"glColorP4uiv\");\n\tglad_glSecondaryColorP3ui = (PFNGLSECONDARYCOLORP3UIPROC)load(\"glSecondaryColorP3ui\");\n\tglad_glSecondaryColorP3uiv = (PFNGLSECONDARYCOLORP3UIVPROC)load(\"glSecondaryColorP3uiv\");\n}\nstatic void load_GL_3DFX_tbuffer(GLADloadproc load) {\n\tif(!GLAD_GL_3DFX_tbuffer) return;\n\tglad_glTbufferMask3DFX = (PFNGLTBUFFERMASK3DFXPROC)load(\"glTbufferMask3DFX\");\n}\nstatic void load_GL_AMD_debug_output(GLADloadproc load) {\n\tif(!GLAD_GL_AMD_debug_output) return;\n\tglad_glDebugMessageEnableAMD = (PFNGLDEBUGMESSAGEENABLEAMDPROC)load(\"glDebugMessageEnableAMD\");\n\tglad_glDebugMessageInsertAMD = (PFNGLDEBUGMESSAGEINSERTAMDPROC)load(\"glDebugMessageInsertAMD\");\n\tglad_glDebugMessageCallbackAMD = (PFNGLDEBUGMESSAGECALLBACKAMDPROC)load(\"glDebugMessageCallbackAMD\");\n\tglad_glGetDebugMessageLogAMD = (PFNGLGETDEBUGMESSAGELOGAMDPROC)load(\"glGetDebugMessageLogAMD\");\n}\nstatic void load_GL_AMD_draw_buffers_blend(GLADloadproc load) {\n\tif(!GLAD_GL_AMD_draw_buffers_blend) return;\n\tglad_glBlendFuncIndexedAMD = (PFNGLBLENDFUNCINDEXEDAMDPROC)load(\"glBlendFuncIndexedAMD\");\n\tglad_glBlendFuncSeparateIndexedAMD = (PFNGLBLENDFUNCSEPARATEINDEXEDAMDPROC)load(\"glBlendFuncSeparateIndexedAMD\");\n\tglad_glBlendEquationIndexedAMD = (PFNGLBLENDEQUATIONINDEXEDAMDPROC)load(\"glBlendEquationIndexedAMD\");\n\tglad_glBlendEquationSeparateIndexedAMD = (PFNGLBLENDEQUATIONSEPARATEINDEXEDAMDPROC)load(\"glBlendEquationSeparateIndexedAMD\");\n}\nstatic void load_GL_AMD_framebuffer_multisample_advanced(GLADloadproc load) {\n\tif(!GLAD_GL_AMD_framebuffer_multisample_advanced) return;\n\tglad_glRenderbufferStorageMultisampleAdvancedAMD = (PFNGLRENDERBUFFERSTORAGEMULTISAMPLEADVANCEDAMDPROC)load(\"glRenderbufferStorageMultisampleAdvancedAMD\");\n\tglad_glNamedRenderbufferStorageMultisampleAdvancedAMD = (PFNGLNAMEDRENDERBUFFERSTORAGEMULTISAMPLEADVANCEDAMDPROC)load(\"glNamedRenderbufferStorageMultisampleAdvancedAMD\");\n}\nstatic void load_GL_AMD_framebuffer_sample_positions(GLADloadproc load) {\n\tif(!GLAD_GL_AMD_framebuffer_sample_positions) return;\n\tglad_glFramebufferSamplePositionsfvAMD = (PFNGLFRAMEBUFFERSAMPLEPOSITIONSFVAMDPROC)load(\"glFramebufferSamplePositionsfvAMD\");\n\tglad_glNamedFramebufferSamplePositionsfvAMD = (PFNGLNAMEDFRAMEBUFFERSAMPLEPOSITIONSFVAMDPROC)load(\"glNamedFramebufferSamplePositionsfvAMD\");\n\tglad_glGetFramebufferParameterfvAMD = (PFNGLGETFRAMEBUFFERPARAMETERFVAMDPROC)load(\"glGetFramebufferParameterfvAMD\");\n\tglad_glGetNamedFramebufferParameterfvAMD = (PFNGLGETNAMEDFRAMEBUFFERPARAMETERFVAMDPROC)load(\"glGetNamedFramebufferParameterfvAMD\");\n}\nstatic void load_GL_AMD_gpu_shader_int64(GLADloadproc load) {\n\tif(!GLAD_GL_AMD_gpu_shader_int64) return;\n\tglad_glUniform1i64NV = (PFNGLUNIFORM1I64NVPROC)load(\"glUniform1i64NV\");\n\tglad_glUniform2i64NV = (PFNGLUNIFORM2I64NVPROC)load(\"glUniform2i64NV\");\n\tglad_glUniform3i64NV = (PFNGLUNIFORM3I64NVPROC)load(\"glUniform3i64NV\");\n\tglad_glUniform4i64NV = (PFNGLUNIFORM4I64NVPROC)load(\"glUniform4i64NV\");\n\tglad_glUniform1i64vNV = (PFNGLUNIFORM1I64VNVPROC)load(\"glUniform1i64vNV\");\n\tglad_glUniform2i64vNV = (PFNGLUNIFORM2I64VNVPROC)load(\"glUniform2i64vNV\");\n\tglad_glUniform3i64vNV = (PFNGLUNIFORM3I64VNVPROC)load(\"glUniform3i64vNV\");\n\tglad_glUniform4i64vNV = (PFNGLUNIFORM4I64VNVPROC)load(\"glUniform4i64vNV\");\n\tglad_glUniform1ui64NV = (PFNGLUNIFORM1UI64NVPROC)load(\"glUniform1ui64NV\");\n\tglad_glUniform2ui64NV = (PFNGLUNIFORM2UI64NVPROC)load(\"glUniform2ui64NV\");\n\tglad_glUniform3ui64NV = (PFNGLUNIFORM3UI64NVPROC)load(\"glUniform3ui64NV\");\n\tglad_glUniform4ui64NV = (PFNGLUNIFORM4UI64NVPROC)load(\"glUniform4ui64NV\");\n\tglad_glUniform1ui64vNV = (PFNGLUNIFORM1UI64VNVPROC)load(\"glUniform1ui64vNV\");\n\tglad_glUniform2ui64vNV = (PFNGLUNIFORM2UI64VNVPROC)load(\"glUniform2ui64vNV\");\n\tglad_glUniform3ui64vNV = (PFNGLUNIFORM3UI64VNVPROC)load(\"glUniform3ui64vNV\");\n\tglad_glUniform4ui64vNV = (PFNGLUNIFORM4UI64VNVPROC)load(\"glUniform4ui64vNV\");\n\tglad_glGetUniformi64vNV = (PFNGLGETUNIFORMI64VNVPROC)load(\"glGetUniformi64vNV\");\n\tglad_glGetUniformui64vNV = (PFNGLGETUNIFORMUI64VNVPROC)load(\"glGetUniformui64vNV\");\n\tglad_glProgramUniform1i64NV = (PFNGLPROGRAMUNIFORM1I64NVPROC)load(\"glProgramUniform1i64NV\");\n\tglad_glProgramUniform2i64NV = (PFNGLPROGRAMUNIFORM2I64NVPROC)load(\"glProgramUniform2i64NV\");\n\tglad_glProgramUniform3i64NV = (PFNGLPROGRAMUNIFORM3I64NVPROC)load(\"glProgramUniform3i64NV\");\n\tglad_glProgramUniform4i64NV = (PFNGLPROGRAMUNIFORM4I64NVPROC)load(\"glProgramUniform4i64NV\");\n\tglad_glProgramUniform1i64vNV = (PFNGLPROGRAMUNIFORM1I64VNVPROC)load(\"glProgramUniform1i64vNV\");\n\tglad_glProgramUniform2i64vNV = (PFNGLPROGRAMUNIFORM2I64VNVPROC)load(\"glProgramUniform2i64vNV\");\n\tglad_glProgramUniform3i64vNV = (PFNGLPROGRAMUNIFORM3I64VNVPROC)load(\"glProgramUniform3i64vNV\");\n\tglad_glProgramUniform4i64vNV = (PFNGLPROGRAMUNIFORM4I64VNVPROC)load(\"glProgramUniform4i64vNV\");\n\tglad_glProgramUniform1ui64NV = (PFNGLPROGRAMUNIFORM1UI64NVPROC)load(\"glProgramUniform1ui64NV\");\n\tglad_glProgramUniform2ui64NV = (PFNGLPROGRAMUNIFORM2UI64NVPROC)load(\"glProgramUniform2ui64NV\");\n\tglad_glProgramUniform3ui64NV = (PFNGLPROGRAMUNIFORM3UI64NVPROC)load(\"glProgramUniform3ui64NV\");\n\tglad_glProgramUniform4ui64NV = (PFNGLPROGRAMUNIFORM4UI64NVPROC)load(\"glProgramUniform4ui64NV\");\n\tglad_glProgramUniform1ui64vNV = (PFNGLPROGRAMUNIFORM1UI64VNVPROC)load(\"glProgramUniform1ui64vNV\");\n\tglad_glProgramUniform2ui64vNV = (PFNGLPROGRAMUNIFORM2UI64VNVPROC)load(\"glProgramUniform2ui64vNV\");\n\tglad_glProgramUniform3ui64vNV = (PFNGLPROGRAMUNIFORM3UI64VNVPROC)load(\"glProgramUniform3ui64vNV\");\n\tglad_glProgramUniform4ui64vNV = (PFNGLPROGRAMUNIFORM4UI64VNVPROC)load(\"glProgramUniform4ui64vNV\");\n}\nstatic void load_GL_AMD_interleaved_elements(GLADloadproc load) {\n\tif(!GLAD_GL_AMD_interleaved_elements) return;\n\tglad_glVertexAttribParameteriAMD = (PFNGLVERTEXATTRIBPARAMETERIAMDPROC)load(\"glVertexAttribParameteriAMD\");\n}\nstatic void load_GL_AMD_multi_draw_indirect(GLADloadproc load) {\n\tif(!GLAD_GL_AMD_multi_draw_indirect) return;\n\tglad_glMultiDrawArraysIndirectAMD = (PFNGLMULTIDRAWARRAYSINDIRECTAMDPROC)load(\"glMultiDrawArraysIndirectAMD\");\n\tglad_glMultiDrawElementsIndirectAMD = (PFNGLMULTIDRAWELEMENTSINDIRECTAMDPROC)load(\"glMultiDrawElementsIndirectAMD\");\n}\nstatic void load_GL_AMD_name_gen_delete(GLADloadproc load) {\n\tif(!GLAD_GL_AMD_name_gen_delete) return;\n\tglad_glGenNamesAMD = (PFNGLGENNAMESAMDPROC)load(\"glGenNamesAMD\");\n\tglad_glDeleteNamesAMD = (PFNGLDELETENAMESAMDPROC)load(\"glDeleteNamesAMD\");\n\tglad_glIsNameAMD = (PFNGLISNAMEAMDPROC)load(\"glIsNameAMD\");\n}\nstatic void load_GL_AMD_occlusion_query_event(GLADloadproc load) {\n\tif(!GLAD_GL_AMD_occlusion_query_event) return;\n\tglad_glQueryObjectParameteruiAMD = (PFNGLQUERYOBJECTPARAMETERUIAMDPROC)load(\"glQueryObjectParameteruiAMD\");\n}\nstatic void load_GL_AMD_performance_monitor(GLADloadproc load) {\n\tif(!GLAD_GL_AMD_performance_monitor) return;\n\tglad_glGetPerfMonitorGroupsAMD = (PFNGLGETPERFMONITORGROUPSAMDPROC)load(\"glGetPerfMonitorGroupsAMD\");\n\tglad_glGetPerfMonitorCountersAMD = (PFNGLGETPERFMONITORCOUNTERSAMDPROC)load(\"glGetPerfMonitorCountersAMD\");\n\tglad_glGetPerfMonitorGroupStringAMD = (PFNGLGETPERFMONITORGROUPSTRINGAMDPROC)load(\"glGetPerfMonitorGroupStringAMD\");\n\tglad_glGetPerfMonitorCounterStringAMD = (PFNGLGETPERFMONITORCOUNTERSTRINGAMDPROC)load(\"glGetPerfMonitorCounterStringAMD\");\n\tglad_glGetPerfMonitorCounterInfoAMD = (PFNGLGETPERFMONITORCOUNTERINFOAMDPROC)load(\"glGetPerfMonitorCounterInfoAMD\");\n\tglad_glGenPerfMonitorsAMD = (PFNGLGENPERFMONITORSAMDPROC)load(\"glGenPerfMonitorsAMD\");\n\tglad_glDeletePerfMonitorsAMD = (PFNGLDELETEPERFMONITORSAMDPROC)load(\"glDeletePerfMonitorsAMD\");\n\tglad_glSelectPerfMonitorCountersAMD = (PFNGLSELECTPERFMONITORCOUNTERSAMDPROC)load(\"glSelectPerfMonitorCountersAMD\");\n\tglad_glBeginPerfMonitorAMD = (PFNGLBEGINPERFMONITORAMDPROC)load(\"glBeginPerfMonitorAMD\");\n\tglad_glEndPerfMonitorAMD = (PFNGLENDPERFMONITORAMDPROC)load(\"glEndPerfMonitorAMD\");\n\tglad_glGetPerfMonitorCounterDataAMD = (PFNGLGETPERFMONITORCOUNTERDATAAMDPROC)load(\"glGetPerfMonitorCounterDataAMD\");\n}\nstatic void load_GL_AMD_sample_positions(GLADloadproc load) {\n\tif(!GLAD_GL_AMD_sample_positions) return;\n\tglad_glSetMultisamplefvAMD = (PFNGLSETMULTISAMPLEFVAMDPROC)load(\"glSetMultisamplefvAMD\");\n}\nstatic void load_GL_AMD_sparse_texture(GLADloadproc load) {\n\tif(!GLAD_GL_AMD_sparse_texture) return;\n\tglad_glTexStorageSparseAMD = (PFNGLTEXSTORAGESPARSEAMDPROC)load(\"glTexStorageSparseAMD\");\n\tglad_glTextureStorageSparseAMD = (PFNGLTEXTURESTORAGESPARSEAMDPROC)load(\"glTextureStorageSparseAMD\");\n}\nstatic void load_GL_AMD_stencil_operation_extended(GLADloadproc load) {\n\tif(!GLAD_GL_AMD_stencil_operation_extended) return;\n\tglad_glStencilOpValueAMD = (PFNGLSTENCILOPVALUEAMDPROC)load(\"glStencilOpValueAMD\");\n}\nstatic void load_GL_AMD_vertex_shader_tessellator(GLADloadproc load) {\n\tif(!GLAD_GL_AMD_vertex_shader_tessellator) return;\n\tglad_glTessellationFactorAMD = (PFNGLTESSELLATIONFACTORAMDPROC)load(\"glTessellationFactorAMD\");\n\tglad_glTessellationModeAMD = (PFNGLTESSELLATIONMODEAMDPROC)load(\"glTessellationModeAMD\");\n}\nstatic void load_GL_APPLE_element_array(GLADloadproc load) {\n\tif(!GLAD_GL_APPLE_element_array) return;\n\tglad_glElementPointerAPPLE = (PFNGLELEMENTPOINTERAPPLEPROC)load(\"glElementPointerAPPLE\");\n\tglad_glDrawElementArrayAPPLE = (PFNGLDRAWELEMENTARRAYAPPLEPROC)load(\"glDrawElementArrayAPPLE\");\n\tglad_glDrawRangeElementArrayAPPLE = (PFNGLDRAWRANGEELEMENTARRAYAPPLEPROC)load(\"glDrawRangeElementArrayAPPLE\");\n\tglad_glMultiDrawElementArrayAPPLE = (PFNGLMULTIDRAWELEMENTARRAYAPPLEPROC)load(\"glMultiDrawElementArrayAPPLE\");\n\tglad_glMultiDrawRangeElementArrayAPPLE = (PFNGLMULTIDRAWRANGEELEMENTARRAYAPPLEPROC)load(\"glMultiDrawRangeElementArrayAPPLE\");\n}\nstatic void load_GL_APPLE_fence(GLADloadproc load) {\n\tif(!GLAD_GL_APPLE_fence) return;\n\tglad_glGenFencesAPPLE = (PFNGLGENFENCESAPPLEPROC)load(\"glGenFencesAPPLE\");\n\tglad_glDeleteFencesAPPLE = (PFNGLDELETEFENCESAPPLEPROC)load(\"glDeleteFencesAPPLE\");\n\tglad_glSetFenceAPPLE = (PFNGLSETFENCEAPPLEPROC)load(\"glSetFenceAPPLE\");\n\tglad_glIsFenceAPPLE = (PFNGLISFENCEAPPLEPROC)load(\"glIsFenceAPPLE\");\n\tglad_glTestFenceAPPLE = (PFNGLTESTFENCEAPPLEPROC)load(\"glTestFenceAPPLE\");\n\tglad_glFinishFenceAPPLE = (PFNGLFINISHFENCEAPPLEPROC)load(\"glFinishFenceAPPLE\");\n\tglad_glTestObjectAPPLE = (PFNGLTESTOBJECTAPPLEPROC)load(\"glTestObjectAPPLE\");\n\tglad_glFinishObjectAPPLE = (PFNGLFINISHOBJECTAPPLEPROC)load(\"glFinishObjectAPPLE\");\n}\nstatic void load_GL_APPLE_flush_buffer_range(GLADloadproc load) {\n\tif(!GLAD_GL_APPLE_flush_buffer_range) return;\n\tglad_glBufferParameteriAPPLE = (PFNGLBUFFERPARAMETERIAPPLEPROC)load(\"glBufferParameteriAPPLE\");\n\tglad_glFlushMappedBufferRangeAPPLE = (PFNGLFLUSHMAPPEDBUFFERRANGEAPPLEPROC)load(\"glFlushMappedBufferRangeAPPLE\");\n}\nstatic void load_GL_APPLE_object_purgeable(GLADloadproc load) {\n\tif(!GLAD_GL_APPLE_object_purgeable) return;\n\tglad_glObjectPurgeableAPPLE = (PFNGLOBJECTPURGEABLEAPPLEPROC)load(\"glObjectPurgeableAPPLE\");\n\tglad_glObjectUnpurgeableAPPLE = (PFNGLOBJECTUNPURGEABLEAPPLEPROC)load(\"glObjectUnpurgeableAPPLE\");\n\tglad_glGetObjectParameterivAPPLE = (PFNGLGETOBJECTPARAMETERIVAPPLEPROC)load(\"glGetObjectParameterivAPPLE\");\n}\nstatic void load_GL_APPLE_texture_range(GLADloadproc load) {\n\tif(!GLAD_GL_APPLE_texture_range) return;\n\tglad_glTextureRangeAPPLE = (PFNGLTEXTURERANGEAPPLEPROC)load(\"glTextureRangeAPPLE\");\n\tglad_glGetTexParameterPointervAPPLE = (PFNGLGETTEXPARAMETERPOINTERVAPPLEPROC)load(\"glGetTexParameterPointervAPPLE\");\n}\nstatic void load_GL_APPLE_vertex_array_object(GLADloadproc load) {\n\tif(!GLAD_GL_APPLE_vertex_array_object) return;\n\tglad_glBindVertexArrayAPPLE = (PFNGLBINDVERTEXARRAYAPPLEPROC)load(\"glBindVertexArrayAPPLE\");\n\tglad_glDeleteVertexArraysAPPLE = (PFNGLDELETEVERTEXARRAYSAPPLEPROC)load(\"glDeleteVertexArraysAPPLE\");\n\tglad_glGenVertexArraysAPPLE = (PFNGLGENVERTEXARRAYSAPPLEPROC)load(\"glGenVertexArraysAPPLE\");\n\tglad_glIsVertexArrayAPPLE = (PFNGLISVERTEXARRAYAPPLEPROC)load(\"glIsVertexArrayAPPLE\");\n}\nstatic void load_GL_APPLE_vertex_array_range(GLADloadproc load) {\n\tif(!GLAD_GL_APPLE_vertex_array_range) return;\n\tglad_glVertexArrayRangeAPPLE = (PFNGLVERTEXARRAYRANGEAPPLEPROC)load(\"glVertexArrayRangeAPPLE\");\n\tglad_glFlushVertexArrayRangeAPPLE = (PFNGLFLUSHVERTEXARRAYRANGEAPPLEPROC)load(\"glFlushVertexArrayRangeAPPLE\");\n\tglad_glVertexArrayParameteriAPPLE = (PFNGLVERTEXARRAYPARAMETERIAPPLEPROC)load(\"glVertexArrayParameteriAPPLE\");\n}\nstatic void load_GL_APPLE_vertex_program_evaluators(GLADloadproc load) {\n\tif(!GLAD_GL_APPLE_vertex_program_evaluators) return;\n\tglad_glEnableVertexAttribAPPLE = (PFNGLENABLEVERTEXATTRIBAPPLEPROC)load(\"glEnableVertexAttribAPPLE\");\n\tglad_glDisableVertexAttribAPPLE = (PFNGLDISABLEVERTEXATTRIBAPPLEPROC)load(\"glDisableVertexAttribAPPLE\");\n\tglad_glIsVertexAttribEnabledAPPLE = (PFNGLISVERTEXATTRIBENABLEDAPPLEPROC)load(\"glIsVertexAttribEnabledAPPLE\");\n\tglad_glMapVertexAttrib1dAPPLE = (PFNGLMAPVERTEXATTRIB1DAPPLEPROC)load(\"glMapVertexAttrib1dAPPLE\");\n\tglad_glMapVertexAttrib1fAPPLE = (PFNGLMAPVERTEXATTRIB1FAPPLEPROC)load(\"glMapVertexAttrib1fAPPLE\");\n\tglad_glMapVertexAttrib2dAPPLE = (PFNGLMAPVERTEXATTRIB2DAPPLEPROC)load(\"glMapVertexAttrib2dAPPLE\");\n\tglad_glMapVertexAttrib2fAPPLE = (PFNGLMAPVERTEXATTRIB2FAPPLEPROC)load(\"glMapVertexAttrib2fAPPLE\");\n}\nstatic void load_GL_ARB_ES2_compatibility(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_ES2_compatibility) return;\n\tglad_glReleaseShaderCompiler = (PFNGLRELEASESHADERCOMPILERPROC)load(\"glReleaseShaderCompiler\");\n\tglad_glShaderBinary = (PFNGLSHADERBINARYPROC)load(\"glShaderBinary\");\n\tglad_glGetShaderPrecisionFormat = (PFNGLGETSHADERPRECISIONFORMATPROC)load(\"glGetShaderPrecisionFormat\");\n\tglad_glDepthRangef = (PFNGLDEPTHRANGEFPROC)load(\"glDepthRangef\");\n\tglad_glClearDepthf = (PFNGLCLEARDEPTHFPROC)load(\"glClearDepthf\");\n}\nstatic void load_GL_ARB_ES3_1_compatibility(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_ES3_1_compatibility) return;\n\tglad_glMemoryBarrierByRegion = (PFNGLMEMORYBARRIERBYREGIONPROC)load(\"glMemoryBarrierByRegion\");\n}\nstatic void load_GL_ARB_ES3_2_compatibility(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_ES3_2_compatibility) return;\n\tglad_glPrimitiveBoundingBoxARB = (PFNGLPRIMITIVEBOUNDINGBOXARBPROC)load(\"glPrimitiveBoundingBoxARB\");\n}\nstatic void load_GL_ARB_base_instance(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_base_instance) return;\n\tglad_glDrawArraysInstancedBaseInstance = (PFNGLDRAWARRAYSINSTANCEDBASEINSTANCEPROC)load(\"glDrawArraysInstancedBaseInstance\");\n\tglad_glDrawElementsInstancedBaseInstance = (PFNGLDRAWELEMENTSINSTANCEDBASEINSTANCEPROC)load(\"glDrawElementsInstancedBaseInstance\");\n\tglad_glDrawElementsInstancedBaseVertexBaseInstance = (PFNGLDRAWELEMENTSINSTANCEDBASEVERTEXBASEINSTANCEPROC)load(\"glDrawElementsInstancedBaseVertexBaseInstance\");\n}\nstatic void load_GL_ARB_bindless_texture(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_bindless_texture) return;\n\tglad_glGetTextureHandleARB = (PFNGLGETTEXTUREHANDLEARBPROC)load(\"glGetTextureHandleARB\");\n\tglad_glGetTextureSamplerHandleARB = (PFNGLGETTEXTURESAMPLERHANDLEARBPROC)load(\"glGetTextureSamplerHandleARB\");\n\tglad_glMakeTextureHandleResidentARB = (PFNGLMAKETEXTUREHANDLERESIDENTARBPROC)load(\"glMakeTextureHandleResidentARB\");\n\tglad_glMakeTextureHandleNonResidentARB = (PFNGLMAKETEXTUREHANDLENONRESIDENTARBPROC)load(\"glMakeTextureHandleNonResidentARB\");\n\tglad_glGetImageHandleARB = (PFNGLGETIMAGEHANDLEARBPROC)load(\"glGetImageHandleARB\");\n\tglad_glMakeImageHandleResidentARB = (PFNGLMAKEIMAGEHANDLERESIDENTARBPROC)load(\"glMakeImageHandleResidentARB\");\n\tglad_glMakeImageHandleNonResidentARB = (PFNGLMAKEIMAGEHANDLENONRESIDENTARBPROC)load(\"glMakeImageHandleNonResidentARB\");\n\tglad_glUniformHandleui64ARB = (PFNGLUNIFORMHANDLEUI64ARBPROC)load(\"glUniformHandleui64ARB\");\n\tglad_glUniformHandleui64vARB = (PFNGLUNIFORMHANDLEUI64VARBPROC)load(\"glUniformHandleui64vARB\");\n\tglad_glProgramUniformHandleui64ARB = (PFNGLPROGRAMUNIFORMHANDLEUI64ARBPROC)load(\"glProgramUniformHandleui64ARB\");\n\tglad_glProgramUniformHandleui64vARB = (PFNGLPROGRAMUNIFORMHANDLEUI64VARBPROC)load(\"glProgramUniformHandleui64vARB\");\n\tglad_glIsTextureHandleResidentARB = (PFNGLISTEXTUREHANDLERESIDENTARBPROC)load(\"glIsTextureHandleResidentARB\");\n\tglad_glIsImageHandleResidentARB = (PFNGLISIMAGEHANDLERESIDENTARBPROC)load(\"glIsImageHandleResidentARB\");\n\tglad_glVertexAttribL1ui64ARB = (PFNGLVERTEXATTRIBL1UI64ARBPROC)load(\"glVertexAttribL1ui64ARB\");\n\tglad_glVertexAttribL1ui64vARB = (PFNGLVERTEXATTRIBL1UI64VARBPROC)load(\"glVertexAttribL1ui64vARB\");\n\tglad_glGetVertexAttribLui64vARB = (PFNGLGETVERTEXATTRIBLUI64VARBPROC)load(\"glGetVertexAttribLui64vARB\");\n}\nstatic void load_GL_ARB_blend_func_extended(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_blend_func_extended) return;\n\tglad_glBindFragDataLocationIndexed = (PFNGLBINDFRAGDATALOCATIONINDEXEDPROC)load(\"glBindFragDataLocationIndexed\");\n\tglad_glGetFragDataIndex = (PFNGLGETFRAGDATAINDEXPROC)load(\"glGetFragDataIndex\");\n}\nstatic void load_GL_ARB_buffer_storage(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_buffer_storage) return;\n\tglad_glBufferStorage = (PFNGLBUFFERSTORAGEPROC)load(\"glBufferStorage\");\n}\nstatic void load_GL_ARB_cl_event(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_cl_event) return;\n\tglad_glCreateSyncFromCLeventARB = (PFNGLCREATESYNCFROMCLEVENTARBPROC)load(\"glCreateSyncFromCLeventARB\");\n}\nstatic void load_GL_ARB_clear_buffer_object(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_clear_buffer_object) return;\n\tglad_glClearBufferData = (PFNGLCLEARBUFFERDATAPROC)load(\"glClearBufferData\");\n\tglad_glClearBufferSubData = (PFNGLCLEARBUFFERSUBDATAPROC)load(\"glClearBufferSubData\");\n}\nstatic void load_GL_ARB_clear_texture(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_clear_texture) return;\n\tglad_glClearTexImage = (PFNGLCLEARTEXIMAGEPROC)load(\"glClearTexImage\");\n\tglad_glClearTexSubImage = (PFNGLCLEARTEXSUBIMAGEPROC)load(\"glClearTexSubImage\");\n}\nstatic void load_GL_ARB_clip_control(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_clip_control) return;\n\tglad_glClipControl = (PFNGLCLIPCONTROLPROC)load(\"glClipControl\");\n}\nstatic void load_GL_ARB_color_buffer_float(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_color_buffer_float) return;\n\tglad_glClampColorARB = (PFNGLCLAMPCOLORARBPROC)load(\"glClampColorARB\");\n}\nstatic void load_GL_ARB_compute_shader(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_compute_shader) return;\n\tglad_glDispatchCompute = (PFNGLDISPATCHCOMPUTEPROC)load(\"glDispatchCompute\");\n\tglad_glDispatchComputeIndirect = (PFNGLDISPATCHCOMPUTEINDIRECTPROC)load(\"glDispatchComputeIndirect\");\n}\nstatic void load_GL_ARB_compute_variable_group_size(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_compute_variable_group_size) return;\n\tglad_glDispatchComputeGroupSizeARB = (PFNGLDISPATCHCOMPUTEGROUPSIZEARBPROC)load(\"glDispatchComputeGroupSizeARB\");\n}\nstatic void load_GL_ARB_copy_buffer(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_copy_buffer) return;\n\tglad_glCopyBufferSubData = (PFNGLCOPYBUFFERSUBDATAPROC)load(\"glCopyBufferSubData\");\n}\nstatic void load_GL_ARB_copy_image(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_copy_image) return;\n\tglad_glCopyImageSubData = (PFNGLCOPYIMAGESUBDATAPROC)load(\"glCopyImageSubData\");\n}\nstatic void load_GL_ARB_debug_output(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_debug_output) return;\n\tglad_glDebugMessageControlARB = (PFNGLDEBUGMESSAGECONTROLARBPROC)load(\"glDebugMessageControlARB\");\n\tglad_glDebugMessageInsertARB = (PFNGLDEBUGMESSAGEINSERTARBPROC)load(\"glDebugMessageInsertARB\");\n\tglad_glDebugMessageCallbackARB = (PFNGLDEBUGMESSAGECALLBACKARBPROC)load(\"glDebugMessageCallbackARB\");\n\tglad_glGetDebugMessageLogARB = (PFNGLGETDEBUGMESSAGELOGARBPROC)load(\"glGetDebugMessageLogARB\");\n}\nstatic void load_GL_ARB_direct_state_access(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_direct_state_access) return;\n\tglad_glCreateTransformFeedbacks = (PFNGLCREATETRANSFORMFEEDBACKSPROC)load(\"glCreateTransformFeedbacks\");\n\tglad_glTransformFeedbackBufferBase = (PFNGLTRANSFORMFEEDBACKBUFFERBASEPROC)load(\"glTransformFeedbackBufferBase\");\n\tglad_glTransformFeedbackBufferRange = (PFNGLTRANSFORMFEEDBACKBUFFERRANGEPROC)load(\"glTransformFeedbackBufferRange\");\n\tglad_glGetTransformFeedbackiv = (PFNGLGETTRANSFORMFEEDBACKIVPROC)load(\"glGetTransformFeedbackiv\");\n\tglad_glGetTransformFeedbacki_v = (PFNGLGETTRANSFORMFEEDBACKI_VPROC)load(\"glGetTransformFeedbacki_v\");\n\tglad_glGetTransformFeedbacki64_v = (PFNGLGETTRANSFORMFEEDBACKI64_VPROC)load(\"glGetTransformFeedbacki64_v\");\n\tglad_glCreateBuffers = (PFNGLCREATEBUFFERSPROC)load(\"glCreateBuffers\");\n\tglad_glNamedBufferStorage = (PFNGLNAMEDBUFFERSTORAGEPROC)load(\"glNamedBufferStorage\");\n\tglad_glNamedBufferData = (PFNGLNAMEDBUFFERDATAPROC)load(\"glNamedBufferData\");\n\tglad_glNamedBufferSubData = (PFNGLNAMEDBUFFERSUBDATAPROC)load(\"glNamedBufferSubData\");\n\tglad_glCopyNamedBufferSubData = (PFNGLCOPYNAMEDBUFFERSUBDATAPROC)load(\"glCopyNamedBufferSubData\");\n\tglad_glClearNamedBufferData = (PFNGLCLEARNAMEDBUFFERDATAPROC)load(\"glClearNamedBufferData\");\n\tglad_glClearNamedBufferSubData = (PFNGLCLEARNAMEDBUFFERSUBDATAPROC)load(\"glClearNamedBufferSubData\");\n\tglad_glMapNamedBuffer = (PFNGLMAPNAMEDBUFFERPROC)load(\"glMapNamedBuffer\");\n\tglad_glMapNamedBufferRange = (PFNGLMAPNAMEDBUFFERRANGEPROC)load(\"glMapNamedBufferRange\");\n\tglad_glUnmapNamedBuffer = (PFNGLUNMAPNAMEDBUFFERPROC)load(\"glUnmapNamedBuffer\");\n\tglad_glFlushMappedNamedBufferRange = (PFNGLFLUSHMAPPEDNAMEDBUFFERRANGEPROC)load(\"glFlushMappedNamedBufferRange\");\n\tglad_glGetNamedBufferParameteriv = (PFNGLGETNAMEDBUFFERPARAMETERIVPROC)load(\"glGetNamedBufferParameteriv\");\n\tglad_glGetNamedBufferParameteri64v = (PFNGLGETNAMEDBUFFERPARAMETERI64VPROC)load(\"glGetNamedBufferParameteri64v\");\n\tglad_glGetNamedBufferPointerv = (PFNGLGETNAMEDBUFFERPOINTERVPROC)load(\"glGetNamedBufferPointerv\");\n\tglad_glGetNamedBufferSubData = (PFNGLGETNAMEDBUFFERSUBDATAPROC)load(\"glGetNamedBufferSubData\");\n\tglad_glCreateFramebuffers = (PFNGLCREATEFRAMEBUFFERSPROC)load(\"glCreateFramebuffers\");\n\tglad_glNamedFramebufferRenderbuffer = (PFNGLNAMEDFRAMEBUFFERRENDERBUFFERPROC)load(\"glNamedFramebufferRenderbuffer\");\n\tglad_glNamedFramebufferParameteri = (PFNGLNAMEDFRAMEBUFFERPARAMETERIPROC)load(\"glNamedFramebufferParameteri\");\n\tglad_glNamedFramebufferTexture = (PFNGLNAMEDFRAMEBUFFERTEXTUREPROC)load(\"glNamedFramebufferTexture\");\n\tglad_glNamedFramebufferTextureLayer = (PFNGLNAMEDFRAMEBUFFERTEXTURELAYERPROC)load(\"glNamedFramebufferTextureLayer\");\n\tglad_glNamedFramebufferDrawBuffer = (PFNGLNAMEDFRAMEBUFFERDRAWBUFFERPROC)load(\"glNamedFramebufferDrawBuffer\");\n\tglad_glNamedFramebufferDrawBuffers = (PFNGLNAMEDFRAMEBUFFERDRAWBUFFERSPROC)load(\"glNamedFramebufferDrawBuffers\");\n\tglad_glNamedFramebufferReadBuffer = (PFNGLNAMEDFRAMEBUFFERREADBUFFERPROC)load(\"glNamedFramebufferReadBuffer\");\n\tglad_glInvalidateNamedFramebufferData = (PFNGLINVALIDATENAMEDFRAMEBUFFERDATAPROC)load(\"glInvalidateNamedFramebufferData\");\n\tglad_glInvalidateNamedFramebufferSubData = (PFNGLINVALIDATENAMEDFRAMEBUFFERSUBDATAPROC)load(\"glInvalidateNamedFramebufferSubData\");\n\tglad_glClearNamedFramebufferiv = (PFNGLCLEARNAMEDFRAMEBUFFERIVPROC)load(\"glClearNamedFramebufferiv\");\n\tglad_glClearNamedFramebufferuiv = (PFNGLCLEARNAMEDFRAMEBUFFERUIVPROC)load(\"glClearNamedFramebufferuiv\");\n\tglad_glClearNamedFramebufferfv = (PFNGLCLEARNAMEDFRAMEBUFFERFVPROC)load(\"glClearNamedFramebufferfv\");\n\tglad_glClearNamedFramebufferfi = (PFNGLCLEARNAMEDFRAMEBUFFERFIPROC)load(\"glClearNamedFramebufferfi\");\n\tglad_glBlitNamedFramebuffer = (PFNGLBLITNAMEDFRAMEBUFFERPROC)load(\"glBlitNamedFramebuffer\");\n\tglad_glCheckNamedFramebufferStatus = (PFNGLCHECKNAMEDFRAMEBUFFERSTATUSPROC)load(\"glCheckNamedFramebufferStatus\");\n\tglad_glGetNamedFramebufferParameteriv = (PFNGLGETNAMEDFRAMEBUFFERPARAMETERIVPROC)load(\"glGetNamedFramebufferParameteriv\");\n\tglad_glGetNamedFramebufferAttachmentParameteriv = (PFNGLGETNAMEDFRAMEBUFFERATTACHMENTPARAMETERIVPROC)load(\"glGetNamedFramebufferAttachmentParameteriv\");\n\tglad_glCreateRenderbuffers = (PFNGLCREATERENDERBUFFERSPROC)load(\"glCreateRenderbuffers\");\n\tglad_glNamedRenderbufferStorage = (PFNGLNAMEDRENDERBUFFERSTORAGEPROC)load(\"glNamedRenderbufferStorage\");\n\tglad_glNamedRenderbufferStorageMultisample = (PFNGLNAMEDRENDERBUFFERSTORAGEMULTISAMPLEPROC)load(\"glNamedRenderbufferStorageMultisample\");\n\tglad_glGetNamedRenderbufferParameteriv = (PFNGLGETNAMEDRENDERBUFFERPARAMETERIVPROC)load(\"glGetNamedRenderbufferParameteriv\");\n\tglad_glCreateTextures = (PFNGLCREATETEXTURESPROC)load(\"glCreateTextures\");\n\tglad_glTextureBuffer = (PFNGLTEXTUREBUFFERPROC)load(\"glTextureBuffer\");\n\tglad_glTextureBufferRange = (PFNGLTEXTUREBUFFERRANGEPROC)load(\"glTextureBufferRange\");\n\tglad_glTextureStorage1D = (PFNGLTEXTURESTORAGE1DPROC)load(\"glTextureStorage1D\");\n\tglad_glTextureStorage2D = (PFNGLTEXTURESTORAGE2DPROC)load(\"glTextureStorage2D\");\n\tglad_glTextureStorage3D = (PFNGLTEXTURESTORAGE3DPROC)load(\"glTextureStorage3D\");\n\tglad_glTextureStorage2DMultisample = (PFNGLTEXTURESTORAGE2DMULTISAMPLEPROC)load(\"glTextureStorage2DMultisample\");\n\tglad_glTextureStorage3DMultisample = (PFNGLTEXTURESTORAGE3DMULTISAMPLEPROC)load(\"glTextureStorage3DMultisample\");\n\tglad_glTextureSubImage1D = (PFNGLTEXTURESUBIMAGE1DPROC)load(\"glTextureSubImage1D\");\n\tglad_glTextureSubImage2D = (PFNGLTEXTURESUBIMAGE2DPROC)load(\"glTextureSubImage2D\");\n\tglad_glTextureSubImage3D = (PFNGLTEXTURESUBIMAGE3DPROC)load(\"glTextureSubImage3D\");\n\tglad_glCompressedTextureSubImage1D = (PFNGLCOMPRESSEDTEXTURESUBIMAGE1DPROC)load(\"glCompressedTextureSubImage1D\");\n\tglad_glCompressedTextureSubImage2D = (PFNGLCOMPRESSEDTEXTURESUBIMAGE2DPROC)load(\"glCompressedTextureSubImage2D\");\n\tglad_glCompressedTextureSubImage3D = (PFNGLCOMPRESSEDTEXTURESUBIMAGE3DPROC)load(\"glCompressedTextureSubImage3D\");\n\tglad_glCopyTextureSubImage1D = (PFNGLCOPYTEXTURESUBIMAGE1DPROC)load(\"glCopyTextureSubImage1D\");\n\tglad_glCopyTextureSubImage2D = (PFNGLCOPYTEXTURESUBIMAGE2DPROC)load(\"glCopyTextureSubImage2D\");\n\tglad_glCopyTextureSubImage3D = (PFNGLCOPYTEXTURESUBIMAGE3DPROC)load(\"glCopyTextureSubImage3D\");\n\tglad_glTextureParameterf = (PFNGLTEXTUREPARAMETERFPROC)load(\"glTextureParameterf\");\n\tglad_glTextureParameterfv = (PFNGLTEXTUREPARAMETERFVPROC)load(\"glTextureParameterfv\");\n\tglad_glTextureParameteri = (PFNGLTEXTUREPARAMETERIPROC)load(\"glTextureParameteri\");\n\tglad_glTextureParameterIiv = (PFNGLTEXTUREPARAMETERIIVPROC)load(\"glTextureParameterIiv\");\n\tglad_glTextureParameterIuiv = (PFNGLTEXTUREPARAMETERIUIVPROC)load(\"glTextureParameterIuiv\");\n\tglad_glTextureParameteriv = (PFNGLTEXTUREPARAMETERIVPROC)load(\"glTextureParameteriv\");\n\tglad_glGenerateTextureMipmap = (PFNGLGENERATETEXTUREMIPMAPPROC)load(\"glGenerateTextureMipmap\");\n\tglad_glBindTextureUnit = (PFNGLBINDTEXTUREUNITPROC)load(\"glBindTextureUnit\");\n\tglad_glGetTextureImage = (PFNGLGETTEXTUREIMAGEPROC)load(\"glGetTextureImage\");\n\tglad_glGetCompressedTextureImage = (PFNGLGETCOMPRESSEDTEXTUREIMAGEPROC)load(\"glGetCompressedTextureImage\");\n\tglad_glGetTextureLevelParameterfv = (PFNGLGETTEXTURELEVELPARAMETERFVPROC)load(\"glGetTextureLevelParameterfv\");\n\tglad_glGetTextureLevelParameteriv = (PFNGLGETTEXTURELEVELPARAMETERIVPROC)load(\"glGetTextureLevelParameteriv\");\n\tglad_glGetTextureParameterfv = (PFNGLGETTEXTUREPARAMETERFVPROC)load(\"glGetTextureParameterfv\");\n\tglad_glGetTextureParameterIiv = (PFNGLGETTEXTUREPARAMETERIIVPROC)load(\"glGetTextureParameterIiv\");\n\tglad_glGetTextureParameterIuiv = (PFNGLGETTEXTUREPARAMETERIUIVPROC)load(\"glGetTextureParameterIuiv\");\n\tglad_glGetTextureParameteriv = (PFNGLGETTEXTUREPARAMETERIVPROC)load(\"glGetTextureParameteriv\");\n\tglad_glCreateVertexArrays = (PFNGLCREATEVERTEXARRAYSPROC)load(\"glCreateVertexArrays\");\n\tglad_glDisableVertexArrayAttrib = (PFNGLDISABLEVERTEXARRAYATTRIBPROC)load(\"glDisableVertexArrayAttrib\");\n\tglad_glEnableVertexArrayAttrib = (PFNGLENABLEVERTEXARRAYATTRIBPROC)load(\"glEnableVertexArrayAttrib\");\n\tglad_glVertexArrayElementBuffer = (PFNGLVERTEXARRAYELEMENTBUFFERPROC)load(\"glVertexArrayElementBuffer\");\n\tglad_glVertexArrayVertexBuffer = (PFNGLVERTEXARRAYVERTEXBUFFERPROC)load(\"glVertexArrayVertexBuffer\");\n\tglad_glVertexArrayVertexBuffers = (PFNGLVERTEXARRAYVERTEXBUFFERSPROC)load(\"glVertexArrayVertexBuffers\");\n\tglad_glVertexArrayAttribBinding = (PFNGLVERTEXARRAYATTRIBBINDINGPROC)load(\"glVertexArrayAttribBinding\");\n\tglad_glVertexArrayAttribFormat = (PFNGLVERTEXARRAYATTRIBFORMATPROC)load(\"glVertexArrayAttribFormat\");\n\tglad_glVertexArrayAttribIFormat = (PFNGLVERTEXARRAYATTRIBIFORMATPROC)load(\"glVertexArrayAttribIFormat\");\n\tglad_glVertexArrayAttribLFormat = (PFNGLVERTEXARRAYATTRIBLFORMATPROC)load(\"glVertexArrayAttribLFormat\");\n\tglad_glVertexArrayBindingDivisor = (PFNGLVERTEXARRAYBINDINGDIVISORPROC)load(\"glVertexArrayBindingDivisor\");\n\tglad_glGetVertexArrayiv = (PFNGLGETVERTEXARRAYIVPROC)load(\"glGetVertexArrayiv\");\n\tglad_glGetVertexArrayIndexediv = (PFNGLGETVERTEXARRAYINDEXEDIVPROC)load(\"glGetVertexArrayIndexediv\");\n\tglad_glGetVertexArrayIndexed64iv = (PFNGLGETVERTEXARRAYINDEXED64IVPROC)load(\"glGetVertexArrayIndexed64iv\");\n\tglad_glCreateSamplers = (PFNGLCREATESAMPLERSPROC)load(\"glCreateSamplers\");\n\tglad_glCreateProgramPipelines = (PFNGLCREATEPROGRAMPIPELINESPROC)load(\"glCreateProgramPipelines\");\n\tglad_glCreateQueries = (PFNGLCREATEQUERIESPROC)load(\"glCreateQueries\");\n\tglad_glGetQueryBufferObjecti64v = (PFNGLGETQUERYBUFFEROBJECTI64VPROC)load(\"glGetQueryBufferObjecti64v\");\n\tglad_glGetQueryBufferObjectiv = (PFNGLGETQUERYBUFFEROBJECTIVPROC)load(\"glGetQueryBufferObjectiv\");\n\tglad_glGetQueryBufferObjectui64v = (PFNGLGETQUERYBUFFEROBJECTUI64VPROC)load(\"glGetQueryBufferObjectui64v\");\n\tglad_glGetQueryBufferObjectuiv = (PFNGLGETQUERYBUFFEROBJECTUIVPROC)load(\"glGetQueryBufferObjectuiv\");\n}\nstatic void load_GL_ARB_draw_buffers(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_draw_buffers) return;\n\tglad_glDrawBuffersARB = (PFNGLDRAWBUFFERSARBPROC)load(\"glDrawBuffersARB\");\n}\nstatic void load_GL_ARB_draw_buffers_blend(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_draw_buffers_blend) return;\n\tglad_glBlendEquationiARB = (PFNGLBLENDEQUATIONIARBPROC)load(\"glBlendEquationiARB\");\n\tglad_glBlendEquationSeparateiARB = (PFNGLBLENDEQUATIONSEPARATEIARBPROC)load(\"glBlendEquationSeparateiARB\");\n\tglad_glBlendFunciARB = (PFNGLBLENDFUNCIARBPROC)load(\"glBlendFunciARB\");\n\tglad_glBlendFuncSeparateiARB = (PFNGLBLENDFUNCSEPARATEIARBPROC)load(\"glBlendFuncSeparateiARB\");\n}\nstatic void load_GL_ARB_draw_elements_base_vertex(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_draw_elements_base_vertex) return;\n\tglad_glDrawElementsBaseVertex = (PFNGLDRAWELEMENTSBASEVERTEXPROC)load(\"glDrawElementsBaseVertex\");\n\tglad_glDrawRangeElementsBaseVertex = (PFNGLDRAWRANGEELEMENTSBASEVERTEXPROC)load(\"glDrawRangeElementsBaseVertex\");\n\tglad_glDrawElementsInstancedBaseVertex = (PFNGLDRAWELEMENTSINSTANCEDBASEVERTEXPROC)load(\"glDrawElementsInstancedBaseVertex\");\n\tglad_glMultiDrawElementsBaseVertex = (PFNGLMULTIDRAWELEMENTSBASEVERTEXPROC)load(\"glMultiDrawElementsBaseVertex\");\n}\nstatic void load_GL_ARB_draw_indirect(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_draw_indirect) return;\n\tglad_glDrawArraysIndirect = (PFNGLDRAWARRAYSINDIRECTPROC)load(\"glDrawArraysIndirect\");\n\tglad_glDrawElementsIndirect = (PFNGLDRAWELEMENTSINDIRECTPROC)load(\"glDrawElementsIndirect\");\n}\nstatic void load_GL_ARB_draw_instanced(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_draw_instanced) return;\n\tglad_glDrawArraysInstancedARB = (PFNGLDRAWARRAYSINSTANCEDARBPROC)load(\"glDrawArraysInstancedARB\");\n\tglad_glDrawElementsInstancedARB = (PFNGLDRAWELEMENTSINSTANCEDARBPROC)load(\"glDrawElementsInstancedARB\");\n}\nstatic void load_GL_ARB_fragment_program(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_fragment_program) return;\n\tglad_glProgramStringARB = (PFNGLPROGRAMSTRINGARBPROC)load(\"glProgramStringARB\");\n\tglad_glBindProgramARB = (PFNGLBINDPROGRAMARBPROC)load(\"glBindProgramARB\");\n\tglad_glDeleteProgramsARB = (PFNGLDELETEPROGRAMSARBPROC)load(\"glDeleteProgramsARB\");\n\tglad_glGenProgramsARB = (PFNGLGENPROGRAMSARBPROC)load(\"glGenProgramsARB\");\n\tglad_glProgramEnvParameter4dARB = (PFNGLPROGRAMENVPARAMETER4DARBPROC)load(\"glProgramEnvParameter4dARB\");\n\tglad_glProgramEnvParameter4dvARB = (PFNGLPROGRAMENVPARAMETER4DVARBPROC)load(\"glProgramEnvParameter4dvARB\");\n\tglad_glProgramEnvParameter4fARB = (PFNGLPROGRAMENVPARAMETER4FARBPROC)load(\"glProgramEnvParameter4fARB\");\n\tglad_glProgramEnvParameter4fvARB = (PFNGLPROGRAMENVPARAMETER4FVARBPROC)load(\"glProgramEnvParameter4fvARB\");\n\tglad_glProgramLocalParameter4dARB = (PFNGLPROGRAMLOCALPARAMETER4DARBPROC)load(\"glProgramLocalParameter4dARB\");\n\tglad_glProgramLocalParameter4dvARB = (PFNGLPROGRAMLOCALPARAMETER4DVARBPROC)load(\"glProgramLocalParameter4dvARB\");\n\tglad_glProgramLocalParameter4fARB = (PFNGLPROGRAMLOCALPARAMETER4FARBPROC)load(\"glProgramLocalParameter4fARB\");\n\tglad_glProgramLocalParameter4fvARB = (PFNGLPROGRAMLOCALPARAMETER4FVARBPROC)load(\"glProgramLocalParameter4fvARB\");\n\tglad_glGetProgramEnvParameterdvARB = (PFNGLGETPROGRAMENVPARAMETERDVARBPROC)load(\"glGetProgramEnvParameterdvARB\");\n\tglad_glGetProgramEnvParameterfvARB = (PFNGLGETPROGRAMENVPARAMETERFVARBPROC)load(\"glGetProgramEnvParameterfvARB\");\n\tglad_glGetProgramLocalParameterdvARB = (PFNGLGETPROGRAMLOCALPARAMETERDVARBPROC)load(\"glGetProgramLocalParameterdvARB\");\n\tglad_glGetProgramLocalParameterfvARB = (PFNGLGETPROGRAMLOCALPARAMETERFVARBPROC)load(\"glGetProgramLocalParameterfvARB\");\n\tglad_glGetProgramivARB = (PFNGLGETPROGRAMIVARBPROC)load(\"glGetProgramivARB\");\n\tglad_glGetProgramStringARB = (PFNGLGETPROGRAMSTRINGARBPROC)load(\"glGetProgramStringARB\");\n\tglad_glIsProgramARB = (PFNGLISPROGRAMARBPROC)load(\"glIsProgramARB\");\n}\nstatic void load_GL_ARB_framebuffer_no_attachments(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_framebuffer_no_attachments) return;\n\tglad_glFramebufferParameteri = (PFNGLFRAMEBUFFERPARAMETERIPROC)load(\"glFramebufferParameteri\");\n\tglad_glGetFramebufferParameteriv = (PFNGLGETFRAMEBUFFERPARAMETERIVPROC)load(\"glGetFramebufferParameteriv\");\n}\nstatic void load_GL_ARB_framebuffer_object(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_framebuffer_object) return;\n\tglad_glIsRenderbuffer = (PFNGLISRENDERBUFFERPROC)load(\"glIsRenderbuffer\");\n\tglad_glBindRenderbuffer = (PFNGLBINDRENDERBUFFERPROC)load(\"glBindRenderbuffer\");\n\tglad_glDeleteRenderbuffers = (PFNGLDELETERENDERBUFFERSPROC)load(\"glDeleteRenderbuffers\");\n\tglad_glGenRenderbuffers = (PFNGLGENRENDERBUFFERSPROC)load(\"glGenRenderbuffers\");\n\tglad_glRenderbufferStorage = (PFNGLRENDERBUFFERSTORAGEPROC)load(\"glRenderbufferStorage\");\n\tglad_glGetRenderbufferParameteriv = (PFNGLGETRENDERBUFFERPARAMETERIVPROC)load(\"glGetRenderbufferParameteriv\");\n\tglad_glIsFramebuffer = (PFNGLISFRAMEBUFFERPROC)load(\"glIsFramebuffer\");\n\tglad_glBindFramebuffer = (PFNGLBINDFRAMEBUFFERPROC)load(\"glBindFramebuffer\");\n\tglad_glDeleteFramebuffers = (PFNGLDELETEFRAMEBUFFERSPROC)load(\"glDeleteFramebuffers\");\n\tglad_glGenFramebuffers = (PFNGLGENFRAMEBUFFERSPROC)load(\"glGenFramebuffers\");\n\tglad_glCheckFramebufferStatus = (PFNGLCHECKFRAMEBUFFERSTATUSPROC)load(\"glCheckFramebufferStatus\");\n\tglad_glFramebufferTexture1D = (PFNGLFRAMEBUFFERTEXTURE1DPROC)load(\"glFramebufferTexture1D\");\n\tglad_glFramebufferTexture2D = (PFNGLFRAMEBUFFERTEXTURE2DPROC)load(\"glFramebufferTexture2D\");\n\tglad_glFramebufferTexture3D = (PFNGLFRAMEBUFFERTEXTURE3DPROC)load(\"glFramebufferTexture3D\");\n\tglad_glFramebufferRenderbuffer = (PFNGLFRAMEBUFFERRENDERBUFFERPROC)load(\"glFramebufferRenderbuffer\");\n\tglad_glGetFramebufferAttachmentParameteriv = (PFNGLGETFRAMEBUFFERATTACHMENTPARAMETERIVPROC)load(\"glGetFramebufferAttachmentParameteriv\");\n\tglad_glGenerateMipmap = (PFNGLGENERATEMIPMAPPROC)load(\"glGenerateMipmap\");\n\tglad_glBlitFramebuffer = (PFNGLBLITFRAMEBUFFERPROC)load(\"glBlitFramebuffer\");\n\tglad_glRenderbufferStorageMultisample = (PFNGLRENDERBUFFERSTORAGEMULTISAMPLEPROC)load(\"glRenderbufferStorageMultisample\");\n\tglad_glFramebufferTextureLayer = (PFNGLFRAMEBUFFERTEXTURELAYERPROC)load(\"glFramebufferTextureLayer\");\n}\nstatic void load_GL_ARB_geometry_shader4(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_geometry_shader4) return;\n\tglad_glProgramParameteriARB = (PFNGLPROGRAMPARAMETERIARBPROC)load(\"glProgramParameteriARB\");\n\tglad_glFramebufferTextureARB = (PFNGLFRAMEBUFFERTEXTUREARBPROC)load(\"glFramebufferTextureARB\");\n\tglad_glFramebufferTextureLayerARB = (PFNGLFRAMEBUFFERTEXTURELAYERARBPROC)load(\"glFramebufferTextureLayerARB\");\n\tglad_glFramebufferTextureFaceARB = (PFNGLFRAMEBUFFERTEXTUREFACEARBPROC)load(\"glFramebufferTextureFaceARB\");\n}\nstatic void load_GL_ARB_get_program_binary(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_get_program_binary) return;\n\tglad_glGetProgramBinary = (PFNGLGETPROGRAMBINARYPROC)load(\"glGetProgramBinary\");\n\tglad_glProgramBinary = (PFNGLPROGRAMBINARYPROC)load(\"glProgramBinary\");\n\tglad_glProgramParameteri = (PFNGLPROGRAMPARAMETERIPROC)load(\"glProgramParameteri\");\n}\nstatic void load_GL_ARB_get_texture_sub_image(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_get_texture_sub_image) return;\n\tglad_glGetTextureSubImage = (PFNGLGETTEXTURESUBIMAGEPROC)load(\"glGetTextureSubImage\");\n\tglad_glGetCompressedTextureSubImage = (PFNGLGETCOMPRESSEDTEXTURESUBIMAGEPROC)load(\"glGetCompressedTextureSubImage\");\n}\nstatic void load_GL_ARB_gl_spirv(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_gl_spirv) return;\n\tglad_glSpecializeShaderARB = (PFNGLSPECIALIZESHADERARBPROC)load(\"glSpecializeShaderARB\");\n}\nstatic void load_GL_ARB_gpu_shader_fp64(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_gpu_shader_fp64) return;\n\tglad_glUniform1d = (PFNGLUNIFORM1DPROC)load(\"glUniform1d\");\n\tglad_glUniform2d = (PFNGLUNIFORM2DPROC)load(\"glUniform2d\");\n\tglad_glUniform3d = (PFNGLUNIFORM3DPROC)load(\"glUniform3d\");\n\tglad_glUniform4d = (PFNGLUNIFORM4DPROC)load(\"glUniform4d\");\n\tglad_glUniform1dv = (PFNGLUNIFORM1DVPROC)load(\"glUniform1dv\");\n\tglad_glUniform2dv = (PFNGLUNIFORM2DVPROC)load(\"glUniform2dv\");\n\tglad_glUniform3dv = (PFNGLUNIFORM3DVPROC)load(\"glUniform3dv\");\n\tglad_glUniform4dv = (PFNGLUNIFORM4DVPROC)load(\"glUniform4dv\");\n\tglad_glUniformMatrix2dv = (PFNGLUNIFORMMATRIX2DVPROC)load(\"glUniformMatrix2dv\");\n\tglad_glUniformMatrix3dv = (PFNGLUNIFORMMATRIX3DVPROC)load(\"glUniformMatrix3dv\");\n\tglad_glUniformMatrix4dv = (PFNGLUNIFORMMATRIX4DVPROC)load(\"glUniformMatrix4dv\");\n\tglad_glUniformMatrix2x3dv = (PFNGLUNIFORMMATRIX2X3DVPROC)load(\"glUniformMatrix2x3dv\");\n\tglad_glUniformMatrix2x4dv = (PFNGLUNIFORMMATRIX2X4DVPROC)load(\"glUniformMatrix2x4dv\");\n\tglad_glUniformMatrix3x2dv = (PFNGLUNIFORMMATRIX3X2DVPROC)load(\"glUniformMatrix3x2dv\");\n\tglad_glUniformMatrix3x4dv = (PFNGLUNIFORMMATRIX3X4DVPROC)load(\"glUniformMatrix3x4dv\");\n\tglad_glUniformMatrix4x2dv = (PFNGLUNIFORMMATRIX4X2DVPROC)load(\"glUniformMatrix4x2dv\");\n\tglad_glUniformMatrix4x3dv = (PFNGLUNIFORMMATRIX4X3DVPROC)load(\"glUniformMatrix4x3dv\");\n\tglad_glGetUniformdv = (PFNGLGETUNIFORMDVPROC)load(\"glGetUniformdv\");\n}\nstatic void load_GL_ARB_gpu_shader_int64(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_gpu_shader_int64) return;\n\tglad_glUniform1i64ARB = (PFNGLUNIFORM1I64ARBPROC)load(\"glUniform1i64ARB\");\n\tglad_glUniform2i64ARB = (PFNGLUNIFORM2I64ARBPROC)load(\"glUniform2i64ARB\");\n\tglad_glUniform3i64ARB = (PFNGLUNIFORM3I64ARBPROC)load(\"glUniform3i64ARB\");\n\tglad_glUniform4i64ARB = (PFNGLUNIFORM4I64ARBPROC)load(\"glUniform4i64ARB\");\n\tglad_glUniform1i64vARB = (PFNGLUNIFORM1I64VARBPROC)load(\"glUniform1i64vARB\");\n\tglad_glUniform2i64vARB = (PFNGLUNIFORM2I64VARBPROC)load(\"glUniform2i64vARB\");\n\tglad_glUniform3i64vARB = (PFNGLUNIFORM3I64VARBPROC)load(\"glUniform3i64vARB\");\n\tglad_glUniform4i64vARB = (PFNGLUNIFORM4I64VARBPROC)load(\"glUniform4i64vARB\");\n\tglad_glUniform1ui64ARB = (PFNGLUNIFORM1UI64ARBPROC)load(\"glUniform1ui64ARB\");\n\tglad_glUniform2ui64ARB = (PFNGLUNIFORM2UI64ARBPROC)load(\"glUniform2ui64ARB\");\n\tglad_glUniform3ui64ARB = (PFNGLUNIFORM3UI64ARBPROC)load(\"glUniform3ui64ARB\");\n\tglad_glUniform4ui64ARB = (PFNGLUNIFORM4UI64ARBPROC)load(\"glUniform4ui64ARB\");\n\tglad_glUniform1ui64vARB = (PFNGLUNIFORM1UI64VARBPROC)load(\"glUniform1ui64vARB\");\n\tglad_glUniform2ui64vARB = (PFNGLUNIFORM2UI64VARBPROC)load(\"glUniform2ui64vARB\");\n\tglad_glUniform3ui64vARB = (PFNGLUNIFORM3UI64VARBPROC)load(\"glUniform3ui64vARB\");\n\tglad_glUniform4ui64vARB = (PFNGLUNIFORM4UI64VARBPROC)load(\"glUniform4ui64vARB\");\n\tglad_glGetUniformi64vARB = (PFNGLGETUNIFORMI64VARBPROC)load(\"glGetUniformi64vARB\");\n\tglad_glGetUniformui64vARB = (PFNGLGETUNIFORMUI64VARBPROC)load(\"glGetUniformui64vARB\");\n\tglad_glGetnUniformi64vARB = (PFNGLGETNUNIFORMI64VARBPROC)load(\"glGetnUniformi64vARB\");\n\tglad_glGetnUniformui64vARB = (PFNGLGETNUNIFORMUI64VARBPROC)load(\"glGetnUniformui64vARB\");\n\tglad_glProgramUniform1i64ARB = (PFNGLPROGRAMUNIFORM1I64ARBPROC)load(\"glProgramUniform1i64ARB\");\n\tglad_glProgramUniform2i64ARB = (PFNGLPROGRAMUNIFORM2I64ARBPROC)load(\"glProgramUniform2i64ARB\");\n\tglad_glProgramUniform3i64ARB = (PFNGLPROGRAMUNIFORM3I64ARBPROC)load(\"glProgramUniform3i64ARB\");\n\tglad_glProgramUniform4i64ARB = (PFNGLPROGRAMUNIFORM4I64ARBPROC)load(\"glProgramUniform4i64ARB\");\n\tglad_glProgramUniform1i64vARB = (PFNGLPROGRAMUNIFORM1I64VARBPROC)load(\"glProgramUniform1i64vARB\");\n\tglad_glProgramUniform2i64vARB = (PFNGLPROGRAMUNIFORM2I64VARBPROC)load(\"glProgramUniform2i64vARB\");\n\tglad_glProgramUniform3i64vARB = (PFNGLPROGRAMUNIFORM3I64VARBPROC)load(\"glProgramUniform3i64vARB\");\n\tglad_glProgramUniform4i64vARB = (PFNGLPROGRAMUNIFORM4I64VARBPROC)load(\"glProgramUniform4i64vARB\");\n\tglad_glProgramUniform1ui64ARB = (PFNGLPROGRAMUNIFORM1UI64ARBPROC)load(\"glProgramUniform1ui64ARB\");\n\tglad_glProgramUniform2ui64ARB = (PFNGLPROGRAMUNIFORM2UI64ARBPROC)load(\"glProgramUniform2ui64ARB\");\n\tglad_glProgramUniform3ui64ARB = (PFNGLPROGRAMUNIFORM3UI64ARBPROC)load(\"glProgramUniform3ui64ARB\");\n\tglad_glProgramUniform4ui64ARB = (PFNGLPROGRAMUNIFORM4UI64ARBPROC)load(\"glProgramUniform4ui64ARB\");\n\tglad_glProgramUniform1ui64vARB = (PFNGLPROGRAMUNIFORM1UI64VARBPROC)load(\"glProgramUniform1ui64vARB\");\n\tglad_glProgramUniform2ui64vARB = (PFNGLPROGRAMUNIFORM2UI64VARBPROC)load(\"glProgramUniform2ui64vARB\");\n\tglad_glProgramUniform3ui64vARB = (PFNGLPROGRAMUNIFORM3UI64VARBPROC)load(\"glProgramUniform3ui64vARB\");\n\tglad_glProgramUniform4ui64vARB = (PFNGLPROGRAMUNIFORM4UI64VARBPROC)load(\"glProgramUniform4ui64vARB\");\n}\nstatic void load_GL_ARB_imaging(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_imaging) return;\n\tglad_glBlendColor = (PFNGLBLENDCOLORPROC)load(\"glBlendColor\");\n\tglad_glBlendEquation = (PFNGLBLENDEQUATIONPROC)load(\"glBlendEquation\");\n\tglad_glColorTable = (PFNGLCOLORTABLEPROC)load(\"glColorTable\");\n\tglad_glColorTableParameterfv = (PFNGLCOLORTABLEPARAMETERFVPROC)load(\"glColorTableParameterfv\");\n\tglad_glColorTableParameteriv = (PFNGLCOLORTABLEPARAMETERIVPROC)load(\"glColorTableParameteriv\");\n\tglad_glCopyColorTable = (PFNGLCOPYCOLORTABLEPROC)load(\"glCopyColorTable\");\n\tglad_glGetColorTable = (PFNGLGETCOLORTABLEPROC)load(\"glGetColorTable\");\n\tglad_glGetColorTableParameterfv = (PFNGLGETCOLORTABLEPARAMETERFVPROC)load(\"glGetColorTableParameterfv\");\n\tglad_glGetColorTableParameteriv = (PFNGLGETCOLORTABLEPARAMETERIVPROC)load(\"glGetColorTableParameteriv\");\n\tglad_glColorSubTable = (PFNGLCOLORSUBTABLEPROC)load(\"glColorSubTable\");\n\tglad_glCopyColorSubTable = (PFNGLCOPYCOLORSUBTABLEPROC)load(\"glCopyColorSubTable\");\n\tglad_glConvolutionFilter1D = (PFNGLCONVOLUTIONFILTER1DPROC)load(\"glConvolutionFilter1D\");\n\tglad_glConvolutionFilter2D = (PFNGLCONVOLUTIONFILTER2DPROC)load(\"glConvolutionFilter2D\");\n\tglad_glConvolutionParameterf = (PFNGLCONVOLUTIONPARAMETERFPROC)load(\"glConvolutionParameterf\");\n\tglad_glConvolutionParameterfv = (PFNGLCONVOLUTIONPARAMETERFVPROC)load(\"glConvolutionParameterfv\");\n\tglad_glConvolutionParameteri = (PFNGLCONVOLUTIONPARAMETERIPROC)load(\"glConvolutionParameteri\");\n\tglad_glConvolutionParameteriv = (PFNGLCONVOLUTIONPARAMETERIVPROC)load(\"glConvolutionParameteriv\");\n\tglad_glCopyConvolutionFilter1D = (PFNGLCOPYCONVOLUTIONFILTER1DPROC)load(\"glCopyConvolutionFilter1D\");\n\tglad_glCopyConvolutionFilter2D = (PFNGLCOPYCONVOLUTIONFILTER2DPROC)load(\"glCopyConvolutionFilter2D\");\n\tglad_glGetConvolutionFilter = (PFNGLGETCONVOLUTIONFILTERPROC)load(\"glGetConvolutionFilter\");\n\tglad_glGetConvolutionParameterfv = (PFNGLGETCONVOLUTIONPARAMETERFVPROC)load(\"glGetConvolutionParameterfv\");\n\tglad_glGetConvolutionParameteriv = (PFNGLGETCONVOLUTIONPARAMETERIVPROC)load(\"glGetConvolutionParameteriv\");\n\tglad_glGetSeparableFilter = (PFNGLGETSEPARABLEFILTERPROC)load(\"glGetSeparableFilter\");\n\tglad_glSeparableFilter2D = (PFNGLSEPARABLEFILTER2DPROC)load(\"glSeparableFilter2D\");\n\tglad_glGetHistogram = (PFNGLGETHISTOGRAMPROC)load(\"glGetHistogram\");\n\tglad_glGetHistogramParameterfv = (PFNGLGETHISTOGRAMPARAMETERFVPROC)load(\"glGetHistogramParameterfv\");\n\tglad_glGetHistogramParameteriv = (PFNGLGETHISTOGRAMPARAMETERIVPROC)load(\"glGetHistogramParameteriv\");\n\tglad_glGetMinmax = (PFNGLGETMINMAXPROC)load(\"glGetMinmax\");\n\tglad_glGetMinmaxParameterfv = (PFNGLGETMINMAXPARAMETERFVPROC)load(\"glGetMinmaxParameterfv\");\n\tglad_glGetMinmaxParameteriv = (PFNGLGETMINMAXPARAMETERIVPROC)load(\"glGetMinmaxParameteriv\");\n\tglad_glHistogram = (PFNGLHISTOGRAMPROC)load(\"glHistogram\");\n\tglad_glMinmax = (PFNGLMINMAXPROC)load(\"glMinmax\");\n\tglad_glResetHistogram = (PFNGLRESETHISTOGRAMPROC)load(\"glResetHistogram\");\n\tglad_glResetMinmax = (PFNGLRESETMINMAXPROC)load(\"glResetMinmax\");\n}\nstatic void load_GL_ARB_indirect_parameters(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_indirect_parameters) return;\n\tglad_glMultiDrawArraysIndirectCountARB = (PFNGLMULTIDRAWARRAYSINDIRECTCOUNTARBPROC)load(\"glMultiDrawArraysIndirectCountARB\");\n\tglad_glMultiDrawElementsIndirectCountARB = (PFNGLMULTIDRAWELEMENTSINDIRECTCOUNTARBPROC)load(\"glMultiDrawElementsIndirectCountARB\");\n}\nstatic void load_GL_ARB_instanced_arrays(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_instanced_arrays) return;\n\tglad_glVertexAttribDivisorARB = (PFNGLVERTEXATTRIBDIVISORARBPROC)load(\"glVertexAttribDivisorARB\");\n}\nstatic void load_GL_ARB_internalformat_query(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_internalformat_query) return;\n\tglad_glGetInternalformativ = (PFNGLGETINTERNALFORMATIVPROC)load(\"glGetInternalformativ\");\n}\nstatic void load_GL_ARB_internalformat_query2(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_internalformat_query2) return;\n\tglad_glGetInternalformati64v = (PFNGLGETINTERNALFORMATI64VPROC)load(\"glGetInternalformati64v\");\n}\nstatic void load_GL_ARB_invalidate_subdata(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_invalidate_subdata) return;\n\tglad_glInvalidateTexSubImage = (PFNGLINVALIDATETEXSUBIMAGEPROC)load(\"glInvalidateTexSubImage\");\n\tglad_glInvalidateTexImage = (PFNGLINVALIDATETEXIMAGEPROC)load(\"glInvalidateTexImage\");\n\tglad_glInvalidateBufferSubData = (PFNGLINVALIDATEBUFFERSUBDATAPROC)load(\"glInvalidateBufferSubData\");\n\tglad_glInvalidateBufferData = (PFNGLINVALIDATEBUFFERDATAPROC)load(\"glInvalidateBufferData\");\n\tglad_glInvalidateFramebuffer = (PFNGLINVALIDATEFRAMEBUFFERPROC)load(\"glInvalidateFramebuffer\");\n\tglad_glInvalidateSubFramebuffer = (PFNGLINVALIDATESUBFRAMEBUFFERPROC)load(\"glInvalidateSubFramebuffer\");\n}\nstatic void load_GL_ARB_map_buffer_range(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_map_buffer_range) return;\n\tglad_glMapBufferRange = (PFNGLMAPBUFFERRANGEPROC)load(\"glMapBufferRange\");\n\tglad_glFlushMappedBufferRange = (PFNGLFLUSHMAPPEDBUFFERRANGEPROC)load(\"glFlushMappedBufferRange\");\n}\nstatic void load_GL_ARB_matrix_palette(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_matrix_palette) return;\n\tglad_glCurrentPaletteMatrixARB = (PFNGLCURRENTPALETTEMATRIXARBPROC)load(\"glCurrentPaletteMatrixARB\");\n\tglad_glMatrixIndexubvARB = (PFNGLMATRIXINDEXUBVARBPROC)load(\"glMatrixIndexubvARB\");\n\tglad_glMatrixIndexusvARB = (PFNGLMATRIXINDEXUSVARBPROC)load(\"glMatrixIndexusvARB\");\n\tglad_glMatrixIndexuivARB = (PFNGLMATRIXINDEXUIVARBPROC)load(\"glMatrixIndexuivARB\");\n\tglad_glMatrixIndexPointerARB = (PFNGLMATRIXINDEXPOINTERARBPROC)load(\"glMatrixIndexPointerARB\");\n}\nstatic void load_GL_ARB_multi_bind(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_multi_bind) return;\n\tglad_glBindBuffersBase = (PFNGLBINDBUFFERSBASEPROC)load(\"glBindBuffersBase\");\n\tglad_glBindBuffersRange = (PFNGLBINDBUFFERSRANGEPROC)load(\"glBindBuffersRange\");\n\tglad_glBindTextures = (PFNGLBINDTEXTURESPROC)load(\"glBindTextures\");\n\tglad_glBindSamplers = (PFNGLBINDSAMPLERSPROC)load(\"glBindSamplers\");\n\tglad_glBindImageTextures = (PFNGLBINDIMAGETEXTURESPROC)load(\"glBindImageTextures\");\n\tglad_glBindVertexBuffers = (PFNGLBINDVERTEXBUFFERSPROC)load(\"glBindVertexBuffers\");\n}\nstatic void load_GL_ARB_multi_draw_indirect(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_multi_draw_indirect) return;\n\tglad_glMultiDrawArraysIndirect = (PFNGLMULTIDRAWARRAYSINDIRECTPROC)load(\"glMultiDrawArraysIndirect\");\n\tglad_glMultiDrawElementsIndirect = (PFNGLMULTIDRAWELEMENTSINDIRECTPROC)load(\"glMultiDrawElementsIndirect\");\n}\nstatic void load_GL_ARB_multisample(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_multisample) return;\n\tglad_glSampleCoverageARB = (PFNGLSAMPLECOVERAGEARBPROC)load(\"glSampleCoverageARB\");\n}\nstatic void load_GL_ARB_multitexture(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_multitexture) return;\n\tglad_glActiveTextureARB = (PFNGLACTIVETEXTUREARBPROC)load(\"glActiveTextureARB\");\n\tglad_glClientActiveTextureARB = (PFNGLCLIENTACTIVETEXTUREARBPROC)load(\"glClientActiveTextureARB\");\n\tglad_glMultiTexCoord1dARB = (PFNGLMULTITEXCOORD1DARBPROC)load(\"glMultiTexCoord1dARB\");\n\tglad_glMultiTexCoord1dvARB = (PFNGLMULTITEXCOORD1DVARBPROC)load(\"glMultiTexCoord1dvARB\");\n\tglad_glMultiTexCoord1fARB = (PFNGLMULTITEXCOORD1FARBPROC)load(\"glMultiTexCoord1fARB\");\n\tglad_glMultiTexCoord1fvARB = (PFNGLMULTITEXCOORD1FVARBPROC)load(\"glMultiTexCoord1fvARB\");\n\tglad_glMultiTexCoord1iARB = (PFNGLMULTITEXCOORD1IARBPROC)load(\"glMultiTexCoord1iARB\");\n\tglad_glMultiTexCoord1ivARB = (PFNGLMULTITEXCOORD1IVARBPROC)load(\"glMultiTexCoord1ivARB\");\n\tglad_glMultiTexCoord1sARB = (PFNGLMULTITEXCOORD1SARBPROC)load(\"glMultiTexCoord1sARB\");\n\tglad_glMultiTexCoord1svARB = (PFNGLMULTITEXCOORD1SVARBPROC)load(\"glMultiTexCoord1svARB\");\n\tglad_glMultiTexCoord2dARB = (PFNGLMULTITEXCOORD2DARBPROC)load(\"glMultiTexCoord2dARB\");\n\tglad_glMultiTexCoord2dvARB = (PFNGLMULTITEXCOORD2DVARBPROC)load(\"glMultiTexCoord2dvARB\");\n\tglad_glMultiTexCoord2fARB = (PFNGLMULTITEXCOORD2FARBPROC)load(\"glMultiTexCoord2fARB\");\n\tglad_glMultiTexCoord2fvARB = (PFNGLMULTITEXCOORD2FVARBPROC)load(\"glMultiTexCoord2fvARB\");\n\tglad_glMultiTexCoord2iARB = (PFNGLMULTITEXCOORD2IARBPROC)load(\"glMultiTexCoord2iARB\");\n\tglad_glMultiTexCoord2ivARB = (PFNGLMULTITEXCOORD2IVARBPROC)load(\"glMultiTexCoord2ivARB\");\n\tglad_glMultiTexCoord2sARB = (PFNGLMULTITEXCOORD2SARBPROC)load(\"glMultiTexCoord2sARB\");\n\tglad_glMultiTexCoord2svARB = (PFNGLMULTITEXCOORD2SVARBPROC)load(\"glMultiTexCoord2svARB\");\n\tglad_glMultiTexCoord3dARB = (PFNGLMULTITEXCOORD3DARBPROC)load(\"glMultiTexCoord3dARB\");\n\tglad_glMultiTexCoord3dvARB = (PFNGLMULTITEXCOORD3DVARBPROC)load(\"glMultiTexCoord3dvARB\");\n\tglad_glMultiTexCoord3fARB = (PFNGLMULTITEXCOORD3FARBPROC)load(\"glMultiTexCoord3fARB\");\n\tglad_glMultiTexCoord3fvARB = (PFNGLMULTITEXCOORD3FVARBPROC)load(\"glMultiTexCoord3fvARB\");\n\tglad_glMultiTexCoord3iARB = (PFNGLMULTITEXCOORD3IARBPROC)load(\"glMultiTexCoord3iARB\");\n\tglad_glMultiTexCoord3ivARB = (PFNGLMULTITEXCOORD3IVARBPROC)load(\"glMultiTexCoord3ivARB\");\n\tglad_glMultiTexCoord3sARB = (PFNGLMULTITEXCOORD3SARBPROC)load(\"glMultiTexCoord3sARB\");\n\tglad_glMultiTexCoord3svARB = (PFNGLMULTITEXCOORD3SVARBPROC)load(\"glMultiTexCoord3svARB\");\n\tglad_glMultiTexCoord4dARB = (PFNGLMULTITEXCOORD4DARBPROC)load(\"glMultiTexCoord4dARB\");\n\tglad_glMultiTexCoord4dvARB = (PFNGLMULTITEXCOORD4DVARBPROC)load(\"glMultiTexCoord4dvARB\");\n\tglad_glMultiTexCoord4fARB = (PFNGLMULTITEXCOORD4FARBPROC)load(\"glMultiTexCoord4fARB\");\n\tglad_glMultiTexCoord4fvARB = (PFNGLMULTITEXCOORD4FVARBPROC)load(\"glMultiTexCoord4fvARB\");\n\tglad_glMultiTexCoord4iARB = (PFNGLMULTITEXCOORD4IARBPROC)load(\"glMultiTexCoord4iARB\");\n\tglad_glMultiTexCoord4ivARB = (PFNGLMULTITEXCOORD4IVARBPROC)load(\"glMultiTexCoord4ivARB\");\n\tglad_glMultiTexCoord4sARB = (PFNGLMULTITEXCOORD4SARBPROC)load(\"glMultiTexCoord4sARB\");\n\tglad_glMultiTexCoord4svARB = (PFNGLMULTITEXCOORD4SVARBPROC)load(\"glMultiTexCoord4svARB\");\n}\nstatic void load_GL_ARB_occlusion_query(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_occlusion_query) return;\n\tglad_glGenQueriesARB = (PFNGLGENQUERIESARBPROC)load(\"glGenQueriesARB\");\n\tglad_glDeleteQueriesARB = (PFNGLDELETEQUERIESARBPROC)load(\"glDeleteQueriesARB\");\n\tglad_glIsQueryARB = (PFNGLISQUERYARBPROC)load(\"glIsQueryARB\");\n\tglad_glBeginQueryARB = (PFNGLBEGINQUERYARBPROC)load(\"glBeginQueryARB\");\n\tglad_glEndQueryARB = (PFNGLENDQUERYARBPROC)load(\"glEndQueryARB\");\n\tglad_glGetQueryivARB = (PFNGLGETQUERYIVARBPROC)load(\"glGetQueryivARB\");\n\tglad_glGetQueryObjectivARB = (PFNGLGETQUERYOBJECTIVARBPROC)load(\"glGetQueryObjectivARB\");\n\tglad_glGetQueryObjectuivARB = (PFNGLGETQUERYOBJECTUIVARBPROC)load(\"glGetQueryObjectuivARB\");\n}\nstatic void load_GL_ARB_parallel_shader_compile(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_parallel_shader_compile) return;\n\tglad_glMaxShaderCompilerThreadsARB = (PFNGLMAXSHADERCOMPILERTHREADSARBPROC)load(\"glMaxShaderCompilerThreadsARB\");\n}\nstatic void load_GL_ARB_point_parameters(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_point_parameters) return;\n\tglad_glPointParameterfARB = (PFNGLPOINTPARAMETERFARBPROC)load(\"glPointParameterfARB\");\n\tglad_glPointParameterfvARB = (PFNGLPOINTPARAMETERFVARBPROC)load(\"glPointParameterfvARB\");\n}\nstatic void load_GL_ARB_polygon_offset_clamp(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_polygon_offset_clamp) return;\n\tglad_glPolygonOffsetClamp = (PFNGLPOLYGONOFFSETCLAMPPROC)load(\"glPolygonOffsetClamp\");\n}\nstatic void load_GL_ARB_program_interface_query(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_program_interface_query) return;\n\tglad_glGetProgramInterfaceiv = (PFNGLGETPROGRAMINTERFACEIVPROC)load(\"glGetProgramInterfaceiv\");\n\tglad_glGetProgramResourceIndex = (PFNGLGETPROGRAMRESOURCEINDEXPROC)load(\"glGetProgramResourceIndex\");\n\tglad_glGetProgramResourceName = (PFNGLGETPROGRAMRESOURCENAMEPROC)load(\"glGetProgramResourceName\");\n\tglad_glGetProgramResourceiv = (PFNGLGETPROGRAMRESOURCEIVPROC)load(\"glGetProgramResourceiv\");\n\tglad_glGetProgramResourceLocation = (PFNGLGETPROGRAMRESOURCELOCATIONPROC)load(\"glGetProgramResourceLocation\");\n\tglad_glGetProgramResourceLocationIndex = (PFNGLGETPROGRAMRESOURCELOCATIONINDEXPROC)load(\"glGetProgramResourceLocationIndex\");\n}\nstatic void load_GL_ARB_provoking_vertex(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_provoking_vertex) return;\n\tglad_glProvokingVertex = (PFNGLPROVOKINGVERTEXPROC)load(\"glProvokingVertex\");\n}\nstatic void load_GL_ARB_robustness(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_robustness) return;\n\tglad_glGetGraphicsResetStatusARB = (PFNGLGETGRAPHICSRESETSTATUSARBPROC)load(\"glGetGraphicsResetStatusARB\");\n\tglad_glGetnTexImageARB = (PFNGLGETNTEXIMAGEARBPROC)load(\"glGetnTexImageARB\");\n\tglad_glReadnPixelsARB = (PFNGLREADNPIXELSARBPROC)load(\"glReadnPixelsARB\");\n\tglad_glGetnCompressedTexImageARB = (PFNGLGETNCOMPRESSEDTEXIMAGEARBPROC)load(\"glGetnCompressedTexImageARB\");\n\tglad_glGetnUniformfvARB = (PFNGLGETNUNIFORMFVARBPROC)load(\"glGetnUniformfvARB\");\n\tglad_glGetnUniformivARB = (PFNGLGETNUNIFORMIVARBPROC)load(\"glGetnUniformivARB\");\n\tglad_glGetnUniformuivARB = (PFNGLGETNUNIFORMUIVARBPROC)load(\"glGetnUniformuivARB\");\n\tglad_glGetnUniformdvARB = (PFNGLGETNUNIFORMDVARBPROC)load(\"glGetnUniformdvARB\");\n\tglad_glGetnMapdvARB = (PFNGLGETNMAPDVARBPROC)load(\"glGetnMapdvARB\");\n\tglad_glGetnMapfvARB = (PFNGLGETNMAPFVARBPROC)load(\"glGetnMapfvARB\");\n\tglad_glGetnMapivARB = (PFNGLGETNMAPIVARBPROC)load(\"glGetnMapivARB\");\n\tglad_glGetnPixelMapfvARB = (PFNGLGETNPIXELMAPFVARBPROC)load(\"glGetnPixelMapfvARB\");\n\tglad_glGetnPixelMapuivARB = (PFNGLGETNPIXELMAPUIVARBPROC)load(\"glGetnPixelMapuivARB\");\n\tglad_glGetnPixelMapusvARB = (PFNGLGETNPIXELMAPUSVARBPROC)load(\"glGetnPixelMapusvARB\");\n\tglad_glGetnPolygonStippleARB = (PFNGLGETNPOLYGONSTIPPLEARBPROC)load(\"glGetnPolygonStippleARB\");\n\tglad_glGetnColorTableARB = (PFNGLGETNCOLORTABLEARBPROC)load(\"glGetnColorTableARB\");\n\tglad_glGetnConvolutionFilterARB = (PFNGLGETNCONVOLUTIONFILTERARBPROC)load(\"glGetnConvolutionFilterARB\");\n\tglad_glGetnSeparableFilterARB = (PFNGLGETNSEPARABLEFILTERARBPROC)load(\"glGetnSeparableFilterARB\");\n\tglad_glGetnHistogramARB = (PFNGLGETNHISTOGRAMARBPROC)load(\"glGetnHistogramARB\");\n\tglad_glGetnMinmaxARB = (PFNGLGETNMINMAXARBPROC)load(\"glGetnMinmaxARB\");\n}\nstatic void load_GL_ARB_sample_locations(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_sample_locations) return;\n\tglad_glFramebufferSampleLocationsfvARB = (PFNGLFRAMEBUFFERSAMPLELOCATIONSFVARBPROC)load(\"glFramebufferSampleLocationsfvARB\");\n\tglad_glNamedFramebufferSampleLocationsfvARB = (PFNGLNAMEDFRAMEBUFFERSAMPLELOCATIONSFVARBPROC)load(\"glNamedFramebufferSampleLocationsfvARB\");\n\tglad_glEvaluateDepthValuesARB = (PFNGLEVALUATEDEPTHVALUESARBPROC)load(\"glEvaluateDepthValuesARB\");\n}\nstatic void load_GL_ARB_sample_shading(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_sample_shading) return;\n\tglad_glMinSampleShadingARB = (PFNGLMINSAMPLESHADINGARBPROC)load(\"glMinSampleShadingARB\");\n}\nstatic void load_GL_ARB_sampler_objects(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_sampler_objects) return;\n\tglad_glGenSamplers = (PFNGLGENSAMPLERSPROC)load(\"glGenSamplers\");\n\tglad_glDeleteSamplers = (PFNGLDELETESAMPLERSPROC)load(\"glDeleteSamplers\");\n\tglad_glIsSampler = (PFNGLISSAMPLERPROC)load(\"glIsSampler\");\n\tglad_glBindSampler = (PFNGLBINDSAMPLERPROC)load(\"glBindSampler\");\n\tglad_glSamplerParameteri = (PFNGLSAMPLERPARAMETERIPROC)load(\"glSamplerParameteri\");\n\tglad_glSamplerParameteriv = (PFNGLSAMPLERPARAMETERIVPROC)load(\"glSamplerParameteriv\");\n\tglad_glSamplerParameterf = (PFNGLSAMPLERPARAMETERFPROC)load(\"glSamplerParameterf\");\n\tglad_glSamplerParameterfv = (PFNGLSAMPLERPARAMETERFVPROC)load(\"glSamplerParameterfv\");\n\tglad_glSamplerParameterIiv = (PFNGLSAMPLERPARAMETERIIVPROC)load(\"glSamplerParameterIiv\");\n\tglad_glSamplerParameterIuiv = (PFNGLSAMPLERPARAMETERIUIVPROC)load(\"glSamplerParameterIuiv\");\n\tglad_glGetSamplerParameteriv = (PFNGLGETSAMPLERPARAMETERIVPROC)load(\"glGetSamplerParameteriv\");\n\tglad_glGetSamplerParameterIiv = (PFNGLGETSAMPLERPARAMETERIIVPROC)load(\"glGetSamplerParameterIiv\");\n\tglad_glGetSamplerParameterfv = (PFNGLGETSAMPLERPARAMETERFVPROC)load(\"glGetSamplerParameterfv\");\n\tglad_glGetSamplerParameterIuiv = (PFNGLGETSAMPLERPARAMETERIUIVPROC)load(\"glGetSamplerParameterIuiv\");\n}\nstatic void load_GL_ARB_separate_shader_objects(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_separate_shader_objects) return;\n\tglad_glUseProgramStages = (PFNGLUSEPROGRAMSTAGESPROC)load(\"glUseProgramStages\");\n\tglad_glActiveShaderProgram = (PFNGLACTIVESHADERPROGRAMPROC)load(\"glActiveShaderProgram\");\n\tglad_glCreateShaderProgramv = (PFNGLCREATESHADERPROGRAMVPROC)load(\"glCreateShaderProgramv\");\n\tglad_glBindProgramPipeline = (PFNGLBINDPROGRAMPIPELINEPROC)load(\"glBindProgramPipeline\");\n\tglad_glDeleteProgramPipelines = (PFNGLDELETEPROGRAMPIPELINESPROC)load(\"glDeleteProgramPipelines\");\n\tglad_glGenProgramPipelines = (PFNGLGENPROGRAMPIPELINESPROC)load(\"glGenProgramPipelines\");\n\tglad_glIsProgramPipeline = (PFNGLISPROGRAMPIPELINEPROC)load(\"glIsProgramPipeline\");\n\tglad_glGetProgramPipelineiv = (PFNGLGETPROGRAMPIPELINEIVPROC)load(\"glGetProgramPipelineiv\");\n\tglad_glProgramParameteri = (PFNGLPROGRAMPARAMETERIPROC)load(\"glProgramParameteri\");\n\tglad_glProgramUniform1i = (PFNGLPROGRAMUNIFORM1IPROC)load(\"glProgramUniform1i\");\n\tglad_glProgramUniform1iv = (PFNGLPROGRAMUNIFORM1IVPROC)load(\"glProgramUniform1iv\");\n\tglad_glProgramUniform1f = (PFNGLPROGRAMUNIFORM1FPROC)load(\"glProgramUniform1f\");\n\tglad_glProgramUniform1fv = (PFNGLPROGRAMUNIFORM1FVPROC)load(\"glProgramUniform1fv\");\n\tglad_glProgramUniform1d = (PFNGLPROGRAMUNIFORM1DPROC)load(\"glProgramUniform1d\");\n\tglad_glProgramUniform1dv = (PFNGLPROGRAMUNIFORM1DVPROC)load(\"glProgramUniform1dv\");\n\tglad_glProgramUniform1ui = (PFNGLPROGRAMUNIFORM1UIPROC)load(\"glProgramUniform1ui\");\n\tglad_glProgramUniform1uiv = (PFNGLPROGRAMUNIFORM1UIVPROC)load(\"glProgramUniform1uiv\");\n\tglad_glProgramUniform2i = (PFNGLPROGRAMUNIFORM2IPROC)load(\"glProgramUniform2i\");\n\tglad_glProgramUniform2iv = (PFNGLPROGRAMUNIFORM2IVPROC)load(\"glProgramUniform2iv\");\n\tglad_glProgramUniform2f = (PFNGLPROGRAMUNIFORM2FPROC)load(\"glProgramUniform2f\");\n\tglad_glProgramUniform2fv = (PFNGLPROGRAMUNIFORM2FVPROC)load(\"glProgramUniform2fv\");\n\tglad_glProgramUniform2d = (PFNGLPROGRAMUNIFORM2DPROC)load(\"glProgramUniform2d\");\n\tglad_glProgramUniform2dv = (PFNGLPROGRAMUNIFORM2DVPROC)load(\"glProgramUniform2dv\");\n\tglad_glProgramUniform2ui = (PFNGLPROGRAMUNIFORM2UIPROC)load(\"glProgramUniform2ui\");\n\tglad_glProgramUniform2uiv = (PFNGLPROGRAMUNIFORM2UIVPROC)load(\"glProgramUniform2uiv\");\n\tglad_glProgramUniform3i = (PFNGLPROGRAMUNIFORM3IPROC)load(\"glProgramUniform3i\");\n\tglad_glProgramUniform3iv = (PFNGLPROGRAMUNIFORM3IVPROC)load(\"glProgramUniform3iv\");\n\tglad_glProgramUniform3f = (PFNGLPROGRAMUNIFORM3FPROC)load(\"glProgramUniform3f\");\n\tglad_glProgramUniform3fv = (PFNGLPROGRAMUNIFORM3FVPROC)load(\"glProgramUniform3fv\");\n\tglad_glProgramUniform3d = (PFNGLPROGRAMUNIFORM3DPROC)load(\"glProgramUniform3d\");\n\tglad_glProgramUniform3dv = (PFNGLPROGRAMUNIFORM3DVPROC)load(\"glProgramUniform3dv\");\n\tglad_glProgramUniform3ui = (PFNGLPROGRAMUNIFORM3UIPROC)load(\"glProgramUniform3ui\");\n\tglad_glProgramUniform3uiv = (PFNGLPROGRAMUNIFORM3UIVPROC)load(\"glProgramUniform3uiv\");\n\tglad_glProgramUniform4i = (PFNGLPROGRAMUNIFORM4IPROC)load(\"glProgramUniform4i\");\n\tglad_glProgramUniform4iv = (PFNGLPROGRAMUNIFORM4IVPROC)load(\"glProgramUniform4iv\");\n\tglad_glProgramUniform4f = (PFNGLPROGRAMUNIFORM4FPROC)load(\"glProgramUniform4f\");\n\tglad_glProgramUniform4fv = (PFNGLPROGRAMUNIFORM4FVPROC)load(\"glProgramUniform4fv\");\n\tglad_glProgramUniform4d = (PFNGLPROGRAMUNIFORM4DPROC)load(\"glProgramUniform4d\");\n\tglad_glProgramUniform4dv = (PFNGLPROGRAMUNIFORM4DVPROC)load(\"glProgramUniform4dv\");\n\tglad_glProgramUniform4ui = (PFNGLPROGRAMUNIFORM4UIPROC)load(\"glProgramUniform4ui\");\n\tglad_glProgramUniform4uiv = (PFNGLPROGRAMUNIFORM4UIVPROC)load(\"glProgramUniform4uiv\");\n\tglad_glProgramUniformMatrix2fv = (PFNGLPROGRAMUNIFORMMATRIX2FVPROC)load(\"glProgramUniformMatrix2fv\");\n\tglad_glProgramUniformMatrix3fv = (PFNGLPROGRAMUNIFORMMATRIX3FVPROC)load(\"glProgramUniformMatrix3fv\");\n\tglad_glProgramUniformMatrix4fv = (PFNGLPROGRAMUNIFORMMATRIX4FVPROC)load(\"glProgramUniformMatrix4fv\");\n\tglad_glProgramUniformMatrix2dv = (PFNGLPROGRAMUNIFORMMATRIX2DVPROC)load(\"glProgramUniformMatrix2dv\");\n\tglad_glProgramUniformMatrix3dv = (PFNGLPROGRAMUNIFORMMATRIX3DVPROC)load(\"glProgramUniformMatrix3dv\");\n\tglad_glProgramUniformMatrix4dv = (PFNGLPROGRAMUNIFORMMATRIX4DVPROC)load(\"glProgramUniformMatrix4dv\");\n\tglad_glProgramUniformMatrix2x3fv = (PFNGLPROGRAMUNIFORMMATRIX2X3FVPROC)load(\"glProgramUniformMatrix2x3fv\");\n\tglad_glProgramUniformMatrix3x2fv = (PFNGLPROGRAMUNIFORMMATRIX3X2FVPROC)load(\"glProgramUniformMatrix3x2fv\");\n\tglad_glProgramUniformMatrix2x4fv = (PFNGLPROGRAMUNIFORMMATRIX2X4FVPROC)load(\"glProgramUniformMatrix2x4fv\");\n\tglad_glProgramUniformMatrix4x2fv = (PFNGLPROGRAMUNIFORMMATRIX4X2FVPROC)load(\"glProgramUniformMatrix4x2fv\");\n\tglad_glProgramUniformMatrix3x4fv = (PFNGLPROGRAMUNIFORMMATRIX3X4FVPROC)load(\"glProgramUniformMatrix3x4fv\");\n\tglad_glProgramUniformMatrix4x3fv = (PFNGLPROGRAMUNIFORMMATRIX4X3FVPROC)load(\"glProgramUniformMatrix4x3fv\");\n\tglad_glProgramUniformMatrix2x3dv = (PFNGLPROGRAMUNIFORMMATRIX2X3DVPROC)load(\"glProgramUniformMatrix2x3dv\");\n\tglad_glProgramUniformMatrix3x2dv = (PFNGLPROGRAMUNIFORMMATRIX3X2DVPROC)load(\"glProgramUniformMatrix3x2dv\");\n\tglad_glProgramUniformMatrix2x4dv = (PFNGLPROGRAMUNIFORMMATRIX2X4DVPROC)load(\"glProgramUniformMatrix2x4dv\");\n\tglad_glProgramUniformMatrix4x2dv = (PFNGLPROGRAMUNIFORMMATRIX4X2DVPROC)load(\"glProgramUniformMatrix4x2dv\");\n\tglad_glProgramUniformMatrix3x4dv = (PFNGLPROGRAMUNIFORMMATRIX3X4DVPROC)load(\"glProgramUniformMatrix3x4dv\");\n\tglad_glProgramUniformMatrix4x3dv = (PFNGLPROGRAMUNIFORMMATRIX4X3DVPROC)load(\"glProgramUniformMatrix4x3dv\");\n\tglad_glValidateProgramPipeline = (PFNGLVALIDATEPROGRAMPIPELINEPROC)load(\"glValidateProgramPipeline\");\n\tglad_glGetProgramPipelineInfoLog = (PFNGLGETPROGRAMPIPELINEINFOLOGPROC)load(\"glGetProgramPipelineInfoLog\");\n}\nstatic void load_GL_ARB_shader_atomic_counters(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_shader_atomic_counters) return;\n\tglad_glGetActiveAtomicCounterBufferiv = (PFNGLGETACTIVEATOMICCOUNTERBUFFERIVPROC)load(\"glGetActiveAtomicCounterBufferiv\");\n}\nstatic void load_GL_ARB_shader_image_load_store(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_shader_image_load_store) return;\n\tglad_glBindImageTexture = (PFNGLBINDIMAGETEXTUREPROC)load(\"glBindImageTexture\");\n\tglad_glMemoryBarrier = (PFNGLMEMORYBARRIERPROC)load(\"glMemoryBarrier\");\n}\nstatic void load_GL_ARB_shader_objects(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_shader_objects) return;\n\tglad_glDeleteObjectARB = (PFNGLDELETEOBJECTARBPROC)load(\"glDeleteObjectARB\");\n\tglad_glGetHandleARB = (PFNGLGETHANDLEARBPROC)load(\"glGetHandleARB\");\n\tglad_glDetachObjectARB = (PFNGLDETACHOBJECTARBPROC)load(\"glDetachObjectARB\");\n\tglad_glCreateShaderObjectARB = (PFNGLCREATESHADEROBJECTARBPROC)load(\"glCreateShaderObjectARB\");\n\tglad_glShaderSourceARB = (PFNGLSHADERSOURCEARBPROC)load(\"glShaderSourceARB\");\n\tglad_glCompileShaderARB = (PFNGLCOMPILESHADERARBPROC)load(\"glCompileShaderARB\");\n\tglad_glCreateProgramObjectARB = (PFNGLCREATEPROGRAMOBJECTARBPROC)load(\"glCreateProgramObjectARB\");\n\tglad_glAttachObjectARB = (PFNGLATTACHOBJECTARBPROC)load(\"glAttachObjectARB\");\n\tglad_glLinkProgramARB = (PFNGLLINKPROGRAMARBPROC)load(\"glLinkProgramARB\");\n\tglad_glUseProgramObjectARB = (PFNGLUSEPROGRAMOBJECTARBPROC)load(\"glUseProgramObjectARB\");\n\tglad_glValidateProgramARB = (PFNGLVALIDATEPROGRAMARBPROC)load(\"glValidateProgramARB\");\n\tglad_glUniform1fARB = (PFNGLUNIFORM1FARBPROC)load(\"glUniform1fARB\");\n\tglad_glUniform2fARB = (PFNGLUNIFORM2FARBPROC)load(\"glUniform2fARB\");\n\tglad_glUniform3fARB = (PFNGLUNIFORM3FARBPROC)load(\"glUniform3fARB\");\n\tglad_glUniform4fARB = (PFNGLUNIFORM4FARBPROC)load(\"glUniform4fARB\");\n\tglad_glUniform1iARB = (PFNGLUNIFORM1IARBPROC)load(\"glUniform1iARB\");\n\tglad_glUniform2iARB = (PFNGLUNIFORM2IARBPROC)load(\"glUniform2iARB\");\n\tglad_glUniform3iARB = (PFNGLUNIFORM3IARBPROC)load(\"glUniform3iARB\");\n\tglad_glUniform4iARB = (PFNGLUNIFORM4IARBPROC)load(\"glUniform4iARB\");\n\tglad_glUniform1fvARB = (PFNGLUNIFORM1FVARBPROC)load(\"glUniform1fvARB\");\n\tglad_glUniform2fvARB = (PFNGLUNIFORM2FVARBPROC)load(\"glUniform2fvARB\");\n\tglad_glUniform3fvARB = (PFNGLUNIFORM3FVARBPROC)load(\"glUniform3fvARB\");\n\tglad_glUniform4fvARB = (PFNGLUNIFORM4FVARBPROC)load(\"glUniform4fvARB\");\n\tglad_glUniform1ivARB = (PFNGLUNIFORM1IVARBPROC)load(\"glUniform1ivARB\");\n\tglad_glUniform2ivARB = (PFNGLUNIFORM2IVARBPROC)load(\"glUniform2ivARB\");\n\tglad_glUniform3ivARB = (PFNGLUNIFORM3IVARBPROC)load(\"glUniform3ivARB\");\n\tglad_glUniform4ivARB = (PFNGLUNIFORM4IVARBPROC)load(\"glUniform4ivARB\");\n\tglad_glUniformMatrix2fvARB = (PFNGLUNIFORMMATRIX2FVARBPROC)load(\"glUniformMatrix2fvARB\");\n\tglad_glUniformMatrix3fvARB = (PFNGLUNIFORMMATRIX3FVARBPROC)load(\"glUniformMatrix3fvARB\");\n\tglad_glUniformMatrix4fvARB = (PFNGLUNIFORMMATRIX4FVARBPROC)load(\"glUniformMatrix4fvARB\");\n\tglad_glGetObjectParameterfvARB = (PFNGLGETOBJECTPARAMETERFVARBPROC)load(\"glGetObjectParameterfvARB\");\n\tglad_glGetObjectParameterivARB = (PFNGLGETOBJECTPARAMETERIVARBPROC)load(\"glGetObjectParameterivARB\");\n\tglad_glGetInfoLogARB = (PFNGLGETINFOLOGARBPROC)load(\"glGetInfoLogARB\");\n\tglad_glGetAttachedObjectsARB = (PFNGLGETATTACHEDOBJECTSARBPROC)load(\"glGetAttachedObjectsARB\");\n\tglad_glGetUniformLocationARB = (PFNGLGETUNIFORMLOCATIONARBPROC)load(\"glGetUniformLocationARB\");\n\tglad_glGetActiveUniformARB = (PFNGLGETACTIVEUNIFORMARBPROC)load(\"glGetActiveUniformARB\");\n\tglad_glGetUniformfvARB = (PFNGLGETUNIFORMFVARBPROC)load(\"glGetUniformfvARB\");\n\tglad_glGetUniformivARB = (PFNGLGETUNIFORMIVARBPROC)load(\"glGetUniformivARB\");\n\tglad_glGetShaderSourceARB = (PFNGLGETSHADERSOURCEARBPROC)load(\"glGetShaderSourceARB\");\n}\nstatic void load_GL_ARB_shader_storage_buffer_object(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_shader_storage_buffer_object) return;\n\tglad_glShaderStorageBlockBinding = (PFNGLSHADERSTORAGEBLOCKBINDINGPROC)load(\"glShaderStorageBlockBinding\");\n}\nstatic void load_GL_ARB_shader_subroutine(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_shader_subroutine) return;\n\tglad_glGetSubroutineUniformLocation = (PFNGLGETSUBROUTINEUNIFORMLOCATIONPROC)load(\"glGetSubroutineUniformLocation\");\n\tglad_glGetSubroutineIndex = (PFNGLGETSUBROUTINEINDEXPROC)load(\"glGetSubroutineIndex\");\n\tglad_glGetActiveSubroutineUniformiv = (PFNGLGETACTIVESUBROUTINEUNIFORMIVPROC)load(\"glGetActiveSubroutineUniformiv\");\n\tglad_glGetActiveSubroutineUniformName = (PFNGLGETACTIVESUBROUTINEUNIFORMNAMEPROC)load(\"glGetActiveSubroutineUniformName\");\n\tglad_glGetActiveSubroutineName = (PFNGLGETACTIVESUBROUTINENAMEPROC)load(\"glGetActiveSubroutineName\");\n\tglad_glUniformSubroutinesuiv = (PFNGLUNIFORMSUBROUTINESUIVPROC)load(\"glUniformSubroutinesuiv\");\n\tglad_glGetUniformSubroutineuiv = (PFNGLGETUNIFORMSUBROUTINEUIVPROC)load(\"glGetUniformSubroutineuiv\");\n\tglad_glGetProgramStageiv = (PFNGLGETPROGRAMSTAGEIVPROC)load(\"glGetProgramStageiv\");\n}\nstatic void load_GL_ARB_shading_language_include(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_shading_language_include) return;\n\tglad_glNamedStringARB = (PFNGLNAMEDSTRINGARBPROC)load(\"glNamedStringARB\");\n\tglad_glDeleteNamedStringARB = (PFNGLDELETENAMEDSTRINGARBPROC)load(\"glDeleteNamedStringARB\");\n\tglad_glCompileShaderIncludeARB = (PFNGLCOMPILESHADERINCLUDEARBPROC)load(\"glCompileShaderIncludeARB\");\n\tglad_glIsNamedStringARB = (PFNGLISNAMEDSTRINGARBPROC)load(\"glIsNamedStringARB\");\n\tglad_glGetNamedStringARB = (PFNGLGETNAMEDSTRINGARBPROC)load(\"glGetNamedStringARB\");\n\tglad_glGetNamedStringivARB = (PFNGLGETNAMEDSTRINGIVARBPROC)load(\"glGetNamedStringivARB\");\n}\nstatic void load_GL_ARB_sparse_buffer(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_sparse_buffer) return;\n\tglad_glBufferPageCommitmentARB = (PFNGLBUFFERPAGECOMMITMENTARBPROC)load(\"glBufferPageCommitmentARB\");\n\tglad_glNamedBufferPageCommitmentEXT = (PFNGLNAMEDBUFFERPAGECOMMITMENTEXTPROC)load(\"glNamedBufferPageCommitmentEXT\");\n\tglad_glNamedBufferPageCommitmentARB = (PFNGLNAMEDBUFFERPAGECOMMITMENTARBPROC)load(\"glNamedBufferPageCommitmentARB\");\n}\nstatic void load_GL_ARB_sparse_texture(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_sparse_texture) return;\n\tglad_glTexPageCommitmentARB = (PFNGLTEXPAGECOMMITMENTARBPROC)load(\"glTexPageCommitmentARB\");\n}\nstatic void load_GL_ARB_sync(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_sync) return;\n\tglad_glFenceSync = (PFNGLFENCESYNCPROC)load(\"glFenceSync\");\n\tglad_glIsSync = (PFNGLISSYNCPROC)load(\"glIsSync\");\n\tglad_glDeleteSync = (PFNGLDELETESYNCPROC)load(\"glDeleteSync\");\n\tglad_glClientWaitSync = (PFNGLCLIENTWAITSYNCPROC)load(\"glClientWaitSync\");\n\tglad_glWaitSync = (PFNGLWAITSYNCPROC)load(\"glWaitSync\");\n\tglad_glGetInteger64v = (PFNGLGETINTEGER64VPROC)load(\"glGetInteger64v\");\n\tglad_glGetSynciv = (PFNGLGETSYNCIVPROC)load(\"glGetSynciv\");\n}\nstatic void load_GL_ARB_tessellation_shader(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_tessellation_shader) return;\n\tglad_glPatchParameteri = (PFNGLPATCHPARAMETERIPROC)load(\"glPatchParameteri\");\n\tglad_glPatchParameterfv = (PFNGLPATCHPARAMETERFVPROC)load(\"glPatchParameterfv\");\n}\nstatic void load_GL_ARB_texture_barrier(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_texture_barrier) return;\n\tglad_glTextureBarrier = (PFNGLTEXTUREBARRIERPROC)load(\"glTextureBarrier\");\n}\nstatic void load_GL_ARB_texture_buffer_object(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_texture_buffer_object) return;\n\tglad_glTexBufferARB = (PFNGLTEXBUFFERARBPROC)load(\"glTexBufferARB\");\n}\nstatic void load_GL_ARB_texture_buffer_range(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_texture_buffer_range) return;\n\tglad_glTexBufferRange = (PFNGLTEXBUFFERRANGEPROC)load(\"glTexBufferRange\");\n}\nstatic void load_GL_ARB_texture_compression(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_texture_compression) return;\n\tglad_glCompressedTexImage3DARB = (PFNGLCOMPRESSEDTEXIMAGE3DARBPROC)load(\"glCompressedTexImage3DARB\");\n\tglad_glCompressedTexImage2DARB = (PFNGLCOMPRESSEDTEXIMAGE2DARBPROC)load(\"glCompressedTexImage2DARB\");\n\tglad_glCompressedTexImage1DARB = (PFNGLCOMPRESSEDTEXIMAGE1DARBPROC)load(\"glCompressedTexImage1DARB\");\n\tglad_glCompressedTexSubImage3DARB = (PFNGLCOMPRESSEDTEXSUBIMAGE3DARBPROC)load(\"glCompressedTexSubImage3DARB\");\n\tglad_glCompressedTexSubImage2DARB = (PFNGLCOMPRESSEDTEXSUBIMAGE2DARBPROC)load(\"glCompressedTexSubImage2DARB\");\n\tglad_glCompressedTexSubImage1DARB = (PFNGLCOMPRESSEDTEXSUBIMAGE1DARBPROC)load(\"glCompressedTexSubImage1DARB\");\n\tglad_glGetCompressedTexImageARB = (PFNGLGETCOMPRESSEDTEXIMAGEARBPROC)load(\"glGetCompressedTexImageARB\");\n}\nstatic void load_GL_ARB_texture_multisample(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_texture_multisample) return;\n\tglad_glTexImage2DMultisample = (PFNGLTEXIMAGE2DMULTISAMPLEPROC)load(\"glTexImage2DMultisample\");\n\tglad_glTexImage3DMultisample = (PFNGLTEXIMAGE3DMULTISAMPLEPROC)load(\"glTexImage3DMultisample\");\n\tglad_glGetMultisamplefv = (PFNGLGETMULTISAMPLEFVPROC)load(\"glGetMultisamplefv\");\n\tglad_glSampleMaski = (PFNGLSAMPLEMASKIPROC)load(\"glSampleMaski\");\n}\nstatic void load_GL_ARB_texture_storage(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_texture_storage) return;\n\tglad_glTexStorage1D = (PFNGLTEXSTORAGE1DPROC)load(\"glTexStorage1D\");\n\tglad_glTexStorage2D = (PFNGLTEXSTORAGE2DPROC)load(\"glTexStorage2D\");\n\tglad_glTexStorage3D = (PFNGLTEXSTORAGE3DPROC)load(\"glTexStorage3D\");\n}\nstatic void load_GL_ARB_texture_storage_multisample(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_texture_storage_multisample) return;\n\tglad_glTexStorage2DMultisample = (PFNGLTEXSTORAGE2DMULTISAMPLEPROC)load(\"glTexStorage2DMultisample\");\n\tglad_glTexStorage3DMultisample = (PFNGLTEXSTORAGE3DMULTISAMPLEPROC)load(\"glTexStorage3DMultisample\");\n}\nstatic void load_GL_ARB_texture_view(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_texture_view) return;\n\tglad_glTextureView = (PFNGLTEXTUREVIEWPROC)load(\"glTextureView\");\n}\nstatic void load_GL_ARB_timer_query(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_timer_query) return;\n\tglad_glQueryCounter = (PFNGLQUERYCOUNTERPROC)load(\"glQueryCounter\");\n\tglad_glGetQueryObjecti64v = (PFNGLGETQUERYOBJECTI64VPROC)load(\"glGetQueryObjecti64v\");\n\tglad_glGetQueryObjectui64v = (PFNGLGETQUERYOBJECTUI64VPROC)load(\"glGetQueryObjectui64v\");\n}\nstatic void load_GL_ARB_transform_feedback2(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_transform_feedback2) return;\n\tglad_glBindTransformFeedback = (PFNGLBINDTRANSFORMFEEDBACKPROC)load(\"glBindTransformFeedback\");\n\tglad_glDeleteTransformFeedbacks = (PFNGLDELETETRANSFORMFEEDBACKSPROC)load(\"glDeleteTransformFeedbacks\");\n\tglad_glGenTransformFeedbacks = (PFNGLGENTRANSFORMFEEDBACKSPROC)load(\"glGenTransformFeedbacks\");\n\tglad_glIsTransformFeedback = (PFNGLISTRANSFORMFEEDBACKPROC)load(\"glIsTransformFeedback\");\n\tglad_glPauseTransformFeedback = (PFNGLPAUSETRANSFORMFEEDBACKPROC)load(\"glPauseTransformFeedback\");\n\tglad_glResumeTransformFeedback = (PFNGLRESUMETRANSFORMFEEDBACKPROC)load(\"glResumeTransformFeedback\");\n\tglad_glDrawTransformFeedback = (PFNGLDRAWTRANSFORMFEEDBACKPROC)load(\"glDrawTransformFeedback\");\n}\nstatic void load_GL_ARB_transform_feedback3(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_transform_feedback3) return;\n\tglad_glDrawTransformFeedbackStream = (PFNGLDRAWTRANSFORMFEEDBACKSTREAMPROC)load(\"glDrawTransformFeedbackStream\");\n\tglad_glBeginQueryIndexed = (PFNGLBEGINQUERYINDEXEDPROC)load(\"glBeginQueryIndexed\");\n\tglad_glEndQueryIndexed = (PFNGLENDQUERYINDEXEDPROC)load(\"glEndQueryIndexed\");\n\tglad_glGetQueryIndexediv = (PFNGLGETQUERYINDEXEDIVPROC)load(\"glGetQueryIndexediv\");\n}\nstatic void load_GL_ARB_transform_feedback_instanced(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_transform_feedback_instanced) return;\n\tglad_glDrawTransformFeedbackInstanced = (PFNGLDRAWTRANSFORMFEEDBACKINSTANCEDPROC)load(\"glDrawTransformFeedbackInstanced\");\n\tglad_glDrawTransformFeedbackStreamInstanced = (PFNGLDRAWTRANSFORMFEEDBACKSTREAMINSTANCEDPROC)load(\"glDrawTransformFeedbackStreamInstanced\");\n}\nstatic void load_GL_ARB_transpose_matrix(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_transpose_matrix) return;\n\tglad_glLoadTransposeMatrixfARB = (PFNGLLOADTRANSPOSEMATRIXFARBPROC)load(\"glLoadTransposeMatrixfARB\");\n\tglad_glLoadTransposeMatrixdARB = (PFNGLLOADTRANSPOSEMATRIXDARBPROC)load(\"glLoadTransposeMatrixdARB\");\n\tglad_glMultTransposeMatrixfARB = (PFNGLMULTTRANSPOSEMATRIXFARBPROC)load(\"glMultTransposeMatrixfARB\");\n\tglad_glMultTransposeMatrixdARB = (PFNGLMULTTRANSPOSEMATRIXDARBPROC)load(\"glMultTransposeMatrixdARB\");\n}\nstatic void load_GL_ARB_uniform_buffer_object(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_uniform_buffer_object) return;\n\tglad_glGetUniformIndices = (PFNGLGETUNIFORMINDICESPROC)load(\"glGetUniformIndices\");\n\tglad_glGetActiveUniformsiv = (PFNGLGETACTIVEUNIFORMSIVPROC)load(\"glGetActiveUniformsiv\");\n\tglad_glGetActiveUniformName = (PFNGLGETACTIVEUNIFORMNAMEPROC)load(\"glGetActiveUniformName\");\n\tglad_glGetUniformBlockIndex = (PFNGLGETUNIFORMBLOCKINDEXPROC)load(\"glGetUniformBlockIndex\");\n\tglad_glGetActiveUniformBlockiv = (PFNGLGETACTIVEUNIFORMBLOCKIVPROC)load(\"glGetActiveUniformBlockiv\");\n\tglad_glGetActiveUniformBlockName = (PFNGLGETACTIVEUNIFORMBLOCKNAMEPROC)load(\"glGetActiveUniformBlockName\");\n\tglad_glUniformBlockBinding = (PFNGLUNIFORMBLOCKBINDINGPROC)load(\"glUniformBlockBinding\");\n\tglad_glBindBufferRange = (PFNGLBINDBUFFERRANGEPROC)load(\"glBindBufferRange\");\n\tglad_glBindBufferBase = (PFNGLBINDBUFFERBASEPROC)load(\"glBindBufferBase\");\n\tglad_glGetIntegeri_v = (PFNGLGETINTEGERI_VPROC)load(\"glGetIntegeri_v\");\n}\nstatic void load_GL_ARB_vertex_array_object(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_vertex_array_object) return;\n\tglad_glBindVertexArray = (PFNGLBINDVERTEXARRAYPROC)load(\"glBindVertexArray\");\n\tglad_glDeleteVertexArrays = (PFNGLDELETEVERTEXARRAYSPROC)load(\"glDeleteVertexArrays\");\n\tglad_glGenVertexArrays = (PFNGLGENVERTEXARRAYSPROC)load(\"glGenVertexArrays\");\n\tglad_glIsVertexArray = (PFNGLISVERTEXARRAYPROC)load(\"glIsVertexArray\");\n}\nstatic void load_GL_ARB_vertex_attrib_64bit(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_vertex_attrib_64bit) return;\n\tglad_glVertexAttribL1d = (PFNGLVERTEXATTRIBL1DPROC)load(\"glVertexAttribL1d\");\n\tglad_glVertexAttribL2d = (PFNGLVERTEXATTRIBL2DPROC)load(\"glVertexAttribL2d\");\n\tglad_glVertexAttribL3d = (PFNGLVERTEXATTRIBL3DPROC)load(\"glVertexAttribL3d\");\n\tglad_glVertexAttribL4d = (PFNGLVERTEXATTRIBL4DPROC)load(\"glVertexAttribL4d\");\n\tglad_glVertexAttribL1dv = (PFNGLVERTEXATTRIBL1DVPROC)load(\"glVertexAttribL1dv\");\n\tglad_glVertexAttribL2dv = (PFNGLVERTEXATTRIBL2DVPROC)load(\"glVertexAttribL2dv\");\n\tglad_glVertexAttribL3dv = (PFNGLVERTEXATTRIBL3DVPROC)load(\"glVertexAttribL3dv\");\n\tglad_glVertexAttribL4dv = (PFNGLVERTEXATTRIBL4DVPROC)load(\"glVertexAttribL4dv\");\n\tglad_glVertexAttribLPointer = (PFNGLVERTEXATTRIBLPOINTERPROC)load(\"glVertexAttribLPointer\");\n\tglad_glGetVertexAttribLdv = (PFNGLGETVERTEXATTRIBLDVPROC)load(\"glGetVertexAttribLdv\");\n}\nstatic void load_GL_ARB_vertex_attrib_binding(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_vertex_attrib_binding) return;\n\tglad_glBindVertexBuffer = (PFNGLBINDVERTEXBUFFERPROC)load(\"glBindVertexBuffer\");\n\tglad_glVertexAttribFormat = (PFNGLVERTEXATTRIBFORMATPROC)load(\"glVertexAttribFormat\");\n\tglad_glVertexAttribIFormat = (PFNGLVERTEXATTRIBIFORMATPROC)load(\"glVertexAttribIFormat\");\n\tglad_glVertexAttribLFormat = (PFNGLVERTEXATTRIBLFORMATPROC)load(\"glVertexAttribLFormat\");\n\tglad_glVertexAttribBinding = (PFNGLVERTEXATTRIBBINDINGPROC)load(\"glVertexAttribBinding\");\n\tglad_glVertexBindingDivisor = (PFNGLVERTEXBINDINGDIVISORPROC)load(\"glVertexBindingDivisor\");\n}\nstatic void load_GL_ARB_vertex_blend(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_vertex_blend) return;\n\tglad_glWeightbvARB = (PFNGLWEIGHTBVARBPROC)load(\"glWeightbvARB\");\n\tglad_glWeightsvARB = (PFNGLWEIGHTSVARBPROC)load(\"glWeightsvARB\");\n\tglad_glWeightivARB = (PFNGLWEIGHTIVARBPROC)load(\"glWeightivARB\");\n\tglad_glWeightfvARB = (PFNGLWEIGHTFVARBPROC)load(\"glWeightfvARB\");\n\tglad_glWeightdvARB = (PFNGLWEIGHTDVARBPROC)load(\"glWeightdvARB\");\n\tglad_glWeightubvARB = (PFNGLWEIGHTUBVARBPROC)load(\"glWeightubvARB\");\n\tglad_glWeightusvARB = (PFNGLWEIGHTUSVARBPROC)load(\"glWeightusvARB\");\n\tglad_glWeightuivARB = (PFNGLWEIGHTUIVARBPROC)load(\"glWeightuivARB\");\n\tglad_glWeightPointerARB = (PFNGLWEIGHTPOINTERARBPROC)load(\"glWeightPointerARB\");\n\tglad_glVertexBlendARB = (PFNGLVERTEXBLENDARBPROC)load(\"glVertexBlendARB\");\n}\nstatic void load_GL_ARB_vertex_buffer_object(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_vertex_buffer_object) return;\n\tglad_glBindBufferARB = (PFNGLBINDBUFFERARBPROC)load(\"glBindBufferARB\");\n\tglad_glDeleteBuffersARB = (PFNGLDELETEBUFFERSARBPROC)load(\"glDeleteBuffersARB\");\n\tglad_glGenBuffersARB = (PFNGLGENBUFFERSARBPROC)load(\"glGenBuffersARB\");\n\tglad_glIsBufferARB = (PFNGLISBUFFERARBPROC)load(\"glIsBufferARB\");\n\tglad_glBufferDataARB = (PFNGLBUFFERDATAARBPROC)load(\"glBufferDataARB\");\n\tglad_glBufferSubDataARB = (PFNGLBUFFERSUBDATAARBPROC)load(\"glBufferSubDataARB\");\n\tglad_glGetBufferSubDataARB = (PFNGLGETBUFFERSUBDATAARBPROC)load(\"glGetBufferSubDataARB\");\n\tglad_glMapBufferARB = (PFNGLMAPBUFFERARBPROC)load(\"glMapBufferARB\");\n\tglad_glUnmapBufferARB = (PFNGLUNMAPBUFFERARBPROC)load(\"glUnmapBufferARB\");\n\tglad_glGetBufferParameterivARB = (PFNGLGETBUFFERPARAMETERIVARBPROC)load(\"glGetBufferParameterivARB\");\n\tglad_glGetBufferPointervARB = (PFNGLGETBUFFERPOINTERVARBPROC)load(\"glGetBufferPointervARB\");\n}\nstatic void load_GL_ARB_vertex_program(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_vertex_program) return;\n\tglad_glVertexAttrib1dARB = (PFNGLVERTEXATTRIB1DARBPROC)load(\"glVertexAttrib1dARB\");\n\tglad_glVertexAttrib1dvARB = (PFNGLVERTEXATTRIB1DVARBPROC)load(\"glVertexAttrib1dvARB\");\n\tglad_glVertexAttrib1fARB = (PFNGLVERTEXATTRIB1FARBPROC)load(\"glVertexAttrib1fARB\");\n\tglad_glVertexAttrib1fvARB = (PFNGLVERTEXATTRIB1FVARBPROC)load(\"glVertexAttrib1fvARB\");\n\tglad_glVertexAttrib1sARB = (PFNGLVERTEXATTRIB1SARBPROC)load(\"glVertexAttrib1sARB\");\n\tglad_glVertexAttrib1svARB = (PFNGLVERTEXATTRIB1SVARBPROC)load(\"glVertexAttrib1svARB\");\n\tglad_glVertexAttrib2dARB = (PFNGLVERTEXATTRIB2DARBPROC)load(\"glVertexAttrib2dARB\");\n\tglad_glVertexAttrib2dvARB = (PFNGLVERTEXATTRIB2DVARBPROC)load(\"glVertexAttrib2dvARB\");\n\tglad_glVertexAttrib2fARB = (PFNGLVERTEXATTRIB2FARBPROC)load(\"glVertexAttrib2fARB\");\n\tglad_glVertexAttrib2fvARB = (PFNGLVERTEXATTRIB2FVARBPROC)load(\"glVertexAttrib2fvARB\");\n\tglad_glVertexAttrib2sARB = (PFNGLVERTEXATTRIB2SARBPROC)load(\"glVertexAttrib2sARB\");\n\tglad_glVertexAttrib2svARB = (PFNGLVERTEXATTRIB2SVARBPROC)load(\"glVertexAttrib2svARB\");\n\tglad_glVertexAttrib3dARB = (PFNGLVERTEXATTRIB3DARBPROC)load(\"glVertexAttrib3dARB\");\n\tglad_glVertexAttrib3dvARB = (PFNGLVERTEXATTRIB3DVARBPROC)load(\"glVertexAttrib3dvARB\");\n\tglad_glVertexAttrib3fARB = (PFNGLVERTEXATTRIB3FARBPROC)load(\"glVertexAttrib3fARB\");\n\tglad_glVertexAttrib3fvARB = (PFNGLVERTEXATTRIB3FVARBPROC)load(\"glVertexAttrib3fvARB\");\n\tglad_glVertexAttrib3sARB = (PFNGLVERTEXATTRIB3SARBPROC)load(\"glVertexAttrib3sARB\");\n\tglad_glVertexAttrib3svARB = (PFNGLVERTEXATTRIB3SVARBPROC)load(\"glVertexAttrib3svARB\");\n\tglad_glVertexAttrib4NbvARB = (PFNGLVERTEXATTRIB4NBVARBPROC)load(\"glVertexAttrib4NbvARB\");\n\tglad_glVertexAttrib4NivARB = (PFNGLVERTEXATTRIB4NIVARBPROC)load(\"glVertexAttrib4NivARB\");\n\tglad_glVertexAttrib4NsvARB = (PFNGLVERTEXATTRIB4NSVARBPROC)load(\"glVertexAttrib4NsvARB\");\n\tglad_glVertexAttrib4NubARB = (PFNGLVERTEXATTRIB4NUBARBPROC)load(\"glVertexAttrib4NubARB\");\n\tglad_glVertexAttrib4NubvARB = (PFNGLVERTEXATTRIB4NUBVARBPROC)load(\"glVertexAttrib4NubvARB\");\n\tglad_glVertexAttrib4NuivARB = (PFNGLVERTEXATTRIB4NUIVARBPROC)load(\"glVertexAttrib4NuivARB\");\n\tglad_glVertexAttrib4NusvARB = (PFNGLVERTEXATTRIB4NUSVARBPROC)load(\"glVertexAttrib4NusvARB\");\n\tglad_glVertexAttrib4bvARB = (PFNGLVERTEXATTRIB4BVARBPROC)load(\"glVertexAttrib4bvARB\");\n\tglad_glVertexAttrib4dARB = (PFNGLVERTEXATTRIB4DARBPROC)load(\"glVertexAttrib4dARB\");\n\tglad_glVertexAttrib4dvARB = (PFNGLVERTEXATTRIB4DVARBPROC)load(\"glVertexAttrib4dvARB\");\n\tglad_glVertexAttrib4fARB = (PFNGLVERTEXATTRIB4FARBPROC)load(\"glVertexAttrib4fARB\");\n\tglad_glVertexAttrib4fvARB = (PFNGLVERTEXATTRIB4FVARBPROC)load(\"glVertexAttrib4fvARB\");\n\tglad_glVertexAttrib4ivARB = (PFNGLVERTEXATTRIB4IVARBPROC)load(\"glVertexAttrib4ivARB\");\n\tglad_glVertexAttrib4sARB = (PFNGLVERTEXATTRIB4SARBPROC)load(\"glVertexAttrib4sARB\");\n\tglad_glVertexAttrib4svARB = (PFNGLVERTEXATTRIB4SVARBPROC)load(\"glVertexAttrib4svARB\");\n\tglad_glVertexAttrib4ubvARB = (PFNGLVERTEXATTRIB4UBVARBPROC)load(\"glVertexAttrib4ubvARB\");\n\tglad_glVertexAttrib4uivARB = (PFNGLVERTEXATTRIB4UIVARBPROC)load(\"glVertexAttrib4uivARB\");\n\tglad_glVertexAttrib4usvARB = (PFNGLVERTEXATTRIB4USVARBPROC)load(\"glVertexAttrib4usvARB\");\n\tglad_glVertexAttribPointerARB = (PFNGLVERTEXATTRIBPOINTERARBPROC)load(\"glVertexAttribPointerARB\");\n\tglad_glEnableVertexAttribArrayARB = (PFNGLENABLEVERTEXATTRIBARRAYARBPROC)load(\"glEnableVertexAttribArrayARB\");\n\tglad_glDisableVertexAttribArrayARB = (PFNGLDISABLEVERTEXATTRIBARRAYARBPROC)load(\"glDisableVertexAttribArrayARB\");\n\tglad_glProgramStringARB = (PFNGLPROGRAMSTRINGARBPROC)load(\"glProgramStringARB\");\n\tglad_glBindProgramARB = (PFNGLBINDPROGRAMARBPROC)load(\"glBindProgramARB\");\n\tglad_glDeleteProgramsARB = (PFNGLDELETEPROGRAMSARBPROC)load(\"glDeleteProgramsARB\");\n\tglad_glGenProgramsARB = (PFNGLGENPROGRAMSARBPROC)load(\"glGenProgramsARB\");\n\tglad_glProgramEnvParameter4dARB = (PFNGLPROGRAMENVPARAMETER4DARBPROC)load(\"glProgramEnvParameter4dARB\");\n\tglad_glProgramEnvParameter4dvARB = (PFNGLPROGRAMENVPARAMETER4DVARBPROC)load(\"glProgramEnvParameter4dvARB\");\n\tglad_glProgramEnvParameter4fARB = (PFNGLPROGRAMENVPARAMETER4FARBPROC)load(\"glProgramEnvParameter4fARB\");\n\tglad_glProgramEnvParameter4fvARB = (PFNGLPROGRAMENVPARAMETER4FVARBPROC)load(\"glProgramEnvParameter4fvARB\");\n\tglad_glProgramLocalParameter4dARB = (PFNGLPROGRAMLOCALPARAMETER4DARBPROC)load(\"glProgramLocalParameter4dARB\");\n\tglad_glProgramLocalParameter4dvARB = (PFNGLPROGRAMLOCALPARAMETER4DVARBPROC)load(\"glProgramLocalParameter4dvARB\");\n\tglad_glProgramLocalParameter4fARB = (PFNGLPROGRAMLOCALPARAMETER4FARBPROC)load(\"glProgramLocalParameter4fARB\");\n\tglad_glProgramLocalParameter4fvARB = (PFNGLPROGRAMLOCALPARAMETER4FVARBPROC)load(\"glProgramLocalParameter4fvARB\");\n\tglad_glGetProgramEnvParameterdvARB = (PFNGLGETPROGRAMENVPARAMETERDVARBPROC)load(\"glGetProgramEnvParameterdvARB\");\n\tglad_glGetProgramEnvParameterfvARB = (PFNGLGETPROGRAMENVPARAMETERFVARBPROC)load(\"glGetProgramEnvParameterfvARB\");\n\tglad_glGetProgramLocalParameterdvARB = (PFNGLGETPROGRAMLOCALPARAMETERDVARBPROC)load(\"glGetProgramLocalParameterdvARB\");\n\tglad_glGetProgramLocalParameterfvARB = (PFNGLGETPROGRAMLOCALPARAMETERFVARBPROC)load(\"glGetProgramLocalParameterfvARB\");\n\tglad_glGetProgramivARB = (PFNGLGETPROGRAMIVARBPROC)load(\"glGetProgramivARB\");\n\tglad_glGetProgramStringARB = (PFNGLGETPROGRAMSTRINGARBPROC)load(\"glGetProgramStringARB\");\n\tglad_glGetVertexAttribdvARB = (PFNGLGETVERTEXATTRIBDVARBPROC)load(\"glGetVertexAttribdvARB\");\n\tglad_glGetVertexAttribfvARB = (PFNGLGETVERTEXATTRIBFVARBPROC)load(\"glGetVertexAttribfvARB\");\n\tglad_glGetVertexAttribivARB = (PFNGLGETVERTEXATTRIBIVARBPROC)load(\"glGetVertexAttribivARB\");\n\tglad_glGetVertexAttribPointervARB = (PFNGLGETVERTEXATTRIBPOINTERVARBPROC)load(\"glGetVertexAttribPointervARB\");\n\tglad_glIsProgramARB = (PFNGLISPROGRAMARBPROC)load(\"glIsProgramARB\");\n}\nstatic void load_GL_ARB_vertex_shader(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_vertex_shader) return;\n\tglad_glVertexAttrib1fARB = (PFNGLVERTEXATTRIB1FARBPROC)load(\"glVertexAttrib1fARB\");\n\tglad_glVertexAttrib1sARB = (PFNGLVERTEXATTRIB1SARBPROC)load(\"glVertexAttrib1sARB\");\n\tglad_glVertexAttrib1dARB = (PFNGLVERTEXATTRIB1DARBPROC)load(\"glVertexAttrib1dARB\");\n\tglad_glVertexAttrib2fARB = (PFNGLVERTEXATTRIB2FARBPROC)load(\"glVertexAttrib2fARB\");\n\tglad_glVertexAttrib2sARB = (PFNGLVERTEXATTRIB2SARBPROC)load(\"glVertexAttrib2sARB\");\n\tglad_glVertexAttrib2dARB = (PFNGLVERTEXATTRIB2DARBPROC)load(\"glVertexAttrib2dARB\");\n\tglad_glVertexAttrib3fARB = (PFNGLVERTEXATTRIB3FARBPROC)load(\"glVertexAttrib3fARB\");\n\tglad_glVertexAttrib3sARB = (PFNGLVERTEXATTRIB3SARBPROC)load(\"glVertexAttrib3sARB\");\n\tglad_glVertexAttrib3dARB = (PFNGLVERTEXATTRIB3DARBPROC)load(\"glVertexAttrib3dARB\");\n\tglad_glVertexAttrib4fARB = (PFNGLVERTEXATTRIB4FARBPROC)load(\"glVertexAttrib4fARB\");\n\tglad_glVertexAttrib4sARB = (PFNGLVERTEXATTRIB4SARBPROC)load(\"glVertexAttrib4sARB\");\n\tglad_glVertexAttrib4dARB = (PFNGLVERTEXATTRIB4DARBPROC)load(\"glVertexAttrib4dARB\");\n\tglad_glVertexAttrib4NubARB = (PFNGLVERTEXATTRIB4NUBARBPROC)load(\"glVertexAttrib4NubARB\");\n\tglad_glVertexAttrib1fvARB = (PFNGLVERTEXATTRIB1FVARBPROC)load(\"glVertexAttrib1fvARB\");\n\tglad_glVertexAttrib1svARB = (PFNGLVERTEXATTRIB1SVARBPROC)load(\"glVertexAttrib1svARB\");\n\tglad_glVertexAttrib1dvARB = (PFNGLVERTEXATTRIB1DVARBPROC)load(\"glVertexAttrib1dvARB\");\n\tglad_glVertexAttrib2fvARB = (PFNGLVERTEXATTRIB2FVARBPROC)load(\"glVertexAttrib2fvARB\");\n\tglad_glVertexAttrib2svARB = (PFNGLVERTEXATTRIB2SVARBPROC)load(\"glVertexAttrib2svARB\");\n\tglad_glVertexAttrib2dvARB = (PFNGLVERTEXATTRIB2DVARBPROC)load(\"glVertexAttrib2dvARB\");\n\tglad_glVertexAttrib3fvARB = (PFNGLVERTEXATTRIB3FVARBPROC)load(\"glVertexAttrib3fvARB\");\n\tglad_glVertexAttrib3svARB = (PFNGLVERTEXATTRIB3SVARBPROC)load(\"glVertexAttrib3svARB\");\n\tglad_glVertexAttrib3dvARB = (PFNGLVERTEXATTRIB3DVARBPROC)load(\"glVertexAttrib3dvARB\");\n\tglad_glVertexAttrib4fvARB = (PFNGLVERTEXATTRIB4FVARBPROC)load(\"glVertexAttrib4fvARB\");\n\tglad_glVertexAttrib4svARB = (PFNGLVERTEXATTRIB4SVARBPROC)load(\"glVertexAttrib4svARB\");\n\tglad_glVertexAttrib4dvARB = (PFNGLVERTEXATTRIB4DVARBPROC)load(\"glVertexAttrib4dvARB\");\n\tglad_glVertexAttrib4ivARB = (PFNGLVERTEXATTRIB4IVARBPROC)load(\"glVertexAttrib4ivARB\");\n\tglad_glVertexAttrib4bvARB = (PFNGLVERTEXATTRIB4BVARBPROC)load(\"glVertexAttrib4bvARB\");\n\tglad_glVertexAttrib4ubvARB = (PFNGLVERTEXATTRIB4UBVARBPROC)load(\"glVertexAttrib4ubvARB\");\n\tglad_glVertexAttrib4usvARB = (PFNGLVERTEXATTRIB4USVARBPROC)load(\"glVertexAttrib4usvARB\");\n\tglad_glVertexAttrib4uivARB = (PFNGLVERTEXATTRIB4UIVARBPROC)load(\"glVertexAttrib4uivARB\");\n\tglad_glVertexAttrib4NbvARB = (PFNGLVERTEXATTRIB4NBVARBPROC)load(\"glVertexAttrib4NbvARB\");\n\tglad_glVertexAttrib4NsvARB = (PFNGLVERTEXATTRIB4NSVARBPROC)load(\"glVertexAttrib4NsvARB\");\n\tglad_glVertexAttrib4NivARB = (PFNGLVERTEXATTRIB4NIVARBPROC)load(\"glVertexAttrib4NivARB\");\n\tglad_glVertexAttrib4NubvARB = (PFNGLVERTEXATTRIB4NUBVARBPROC)load(\"glVertexAttrib4NubvARB\");\n\tglad_glVertexAttrib4NusvARB = (PFNGLVERTEXATTRIB4NUSVARBPROC)load(\"glVertexAttrib4NusvARB\");\n\tglad_glVertexAttrib4NuivARB = (PFNGLVERTEXATTRIB4NUIVARBPROC)load(\"glVertexAttrib4NuivARB\");\n\tglad_glVertexAttribPointerARB = (PFNGLVERTEXATTRIBPOINTERARBPROC)load(\"glVertexAttribPointerARB\");\n\tglad_glEnableVertexAttribArrayARB = (PFNGLENABLEVERTEXATTRIBARRAYARBPROC)load(\"glEnableVertexAttribArrayARB\");\n\tglad_glDisableVertexAttribArrayARB = (PFNGLDISABLEVERTEXATTRIBARRAYARBPROC)load(\"glDisableVertexAttribArrayARB\");\n\tglad_glBindAttribLocationARB = (PFNGLBINDATTRIBLOCATIONARBPROC)load(\"glBindAttribLocationARB\");\n\tglad_glGetActiveAttribARB = (PFNGLGETACTIVEATTRIBARBPROC)load(\"glGetActiveAttribARB\");\n\tglad_glGetAttribLocationARB = (PFNGLGETATTRIBLOCATIONARBPROC)load(\"glGetAttribLocationARB\");\n\tglad_glGetVertexAttribdvARB = (PFNGLGETVERTEXATTRIBDVARBPROC)load(\"glGetVertexAttribdvARB\");\n\tglad_glGetVertexAttribfvARB = (PFNGLGETVERTEXATTRIBFVARBPROC)load(\"glGetVertexAttribfvARB\");\n\tglad_glGetVertexAttribivARB = (PFNGLGETVERTEXATTRIBIVARBPROC)load(\"glGetVertexAttribivARB\");\n\tglad_glGetVertexAttribPointervARB = (PFNGLGETVERTEXATTRIBPOINTERVARBPROC)load(\"glGetVertexAttribPointervARB\");\n}\nstatic void load_GL_ARB_vertex_type_2_10_10_10_rev(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_vertex_type_2_10_10_10_rev) return;\n\tglad_glVertexAttribP1ui = (PFNGLVERTEXATTRIBP1UIPROC)load(\"glVertexAttribP1ui\");\n\tglad_glVertexAttribP1uiv = (PFNGLVERTEXATTRIBP1UIVPROC)load(\"glVertexAttribP1uiv\");\n\tglad_glVertexAttribP2ui = (PFNGLVERTEXATTRIBP2UIPROC)load(\"glVertexAttribP2ui\");\n\tglad_glVertexAttribP2uiv = (PFNGLVERTEXATTRIBP2UIVPROC)load(\"glVertexAttribP2uiv\");\n\tglad_glVertexAttribP3ui = (PFNGLVERTEXATTRIBP3UIPROC)load(\"glVertexAttribP3ui\");\n\tglad_glVertexAttribP3uiv = (PFNGLVERTEXATTRIBP3UIVPROC)load(\"glVertexAttribP3uiv\");\n\tglad_glVertexAttribP4ui = (PFNGLVERTEXATTRIBP4UIPROC)load(\"glVertexAttribP4ui\");\n\tglad_glVertexAttribP4uiv = (PFNGLVERTEXATTRIBP4UIVPROC)load(\"glVertexAttribP4uiv\");\n\tglad_glVertexP2ui = (PFNGLVERTEXP2UIPROC)load(\"glVertexP2ui\");\n\tglad_glVertexP2uiv = (PFNGLVERTEXP2UIVPROC)load(\"glVertexP2uiv\");\n\tglad_glVertexP3ui = (PFNGLVERTEXP3UIPROC)load(\"glVertexP3ui\");\n\tglad_glVertexP3uiv = (PFNGLVERTEXP3UIVPROC)load(\"glVertexP3uiv\");\n\tglad_glVertexP4ui = (PFNGLVERTEXP4UIPROC)load(\"glVertexP4ui\");\n\tglad_glVertexP4uiv = (PFNGLVERTEXP4UIVPROC)load(\"glVertexP4uiv\");\n\tglad_glTexCoordP1ui = (PFNGLTEXCOORDP1UIPROC)load(\"glTexCoordP1ui\");\n\tglad_glTexCoordP1uiv = (PFNGLTEXCOORDP1UIVPROC)load(\"glTexCoordP1uiv\");\n\tglad_glTexCoordP2ui = (PFNGLTEXCOORDP2UIPROC)load(\"glTexCoordP2ui\");\n\tglad_glTexCoordP2uiv = (PFNGLTEXCOORDP2UIVPROC)load(\"glTexCoordP2uiv\");\n\tglad_glTexCoordP3ui = (PFNGLTEXCOORDP3UIPROC)load(\"glTexCoordP3ui\");\n\tglad_glTexCoordP3uiv = (PFNGLTEXCOORDP3UIVPROC)load(\"glTexCoordP3uiv\");\n\tglad_glTexCoordP4ui = (PFNGLTEXCOORDP4UIPROC)load(\"glTexCoordP4ui\");\n\tglad_glTexCoordP4uiv = (PFNGLTEXCOORDP4UIVPROC)load(\"glTexCoordP4uiv\");\n\tglad_glMultiTexCoordP1ui = (PFNGLMULTITEXCOORDP1UIPROC)load(\"glMultiTexCoordP1ui\");\n\tglad_glMultiTexCoordP1uiv = (PFNGLMULTITEXCOORDP1UIVPROC)load(\"glMultiTexCoordP1uiv\");\n\tglad_glMultiTexCoordP2ui = (PFNGLMULTITEXCOORDP2UIPROC)load(\"glMultiTexCoordP2ui\");\n\tglad_glMultiTexCoordP2uiv = (PFNGLMULTITEXCOORDP2UIVPROC)load(\"glMultiTexCoordP2uiv\");\n\tglad_glMultiTexCoordP3ui = (PFNGLMULTITEXCOORDP3UIPROC)load(\"glMultiTexCoordP3ui\");\n\tglad_glMultiTexCoordP3uiv = (PFNGLMULTITEXCOORDP3UIVPROC)load(\"glMultiTexCoordP3uiv\");\n\tglad_glMultiTexCoordP4ui = (PFNGLMULTITEXCOORDP4UIPROC)load(\"glMultiTexCoordP4ui\");\n\tglad_glMultiTexCoordP4uiv = (PFNGLMULTITEXCOORDP4UIVPROC)load(\"glMultiTexCoordP4uiv\");\n\tglad_glNormalP3ui = (PFNGLNORMALP3UIPROC)load(\"glNormalP3ui\");\n\tglad_glNormalP3uiv = (PFNGLNORMALP3UIVPROC)load(\"glNormalP3uiv\");\n\tglad_glColorP3ui = (PFNGLCOLORP3UIPROC)load(\"glColorP3ui\");\n\tglad_glColorP3uiv = (PFNGLCOLORP3UIVPROC)load(\"glColorP3uiv\");\n\tglad_glColorP4ui = (PFNGLCOLORP4UIPROC)load(\"glColorP4ui\");\n\tglad_glColorP4uiv = (PFNGLCOLORP4UIVPROC)load(\"glColorP4uiv\");\n\tglad_glSecondaryColorP3ui = (PFNGLSECONDARYCOLORP3UIPROC)load(\"glSecondaryColorP3ui\");\n\tglad_glSecondaryColorP3uiv = (PFNGLSECONDARYCOLORP3UIVPROC)load(\"glSecondaryColorP3uiv\");\n}\nstatic void load_GL_ARB_viewport_array(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_viewport_array) return;\n\tglad_glViewportArrayv = (PFNGLVIEWPORTARRAYVPROC)load(\"glViewportArrayv\");\n\tglad_glViewportIndexedf = (PFNGLVIEWPORTINDEXEDFPROC)load(\"glViewportIndexedf\");\n\tglad_glViewportIndexedfv = (PFNGLVIEWPORTINDEXEDFVPROC)load(\"glViewportIndexedfv\");\n\tglad_glScissorArrayv = (PFNGLSCISSORARRAYVPROC)load(\"glScissorArrayv\");\n\tglad_glScissorIndexed = (PFNGLSCISSORINDEXEDPROC)load(\"glScissorIndexed\");\n\tglad_glScissorIndexedv = (PFNGLSCISSORINDEXEDVPROC)load(\"glScissorIndexedv\");\n\tglad_glDepthRangeArrayv = (PFNGLDEPTHRANGEARRAYVPROC)load(\"glDepthRangeArrayv\");\n\tglad_glDepthRangeIndexed = (PFNGLDEPTHRANGEINDEXEDPROC)load(\"glDepthRangeIndexed\");\n\tglad_glGetFloati_v = (PFNGLGETFLOATI_VPROC)load(\"glGetFloati_v\");\n\tglad_glGetDoublei_v = (PFNGLGETDOUBLEI_VPROC)load(\"glGetDoublei_v\");\n}\nstatic void load_GL_ARB_window_pos(GLADloadproc load) {\n\tif(!GLAD_GL_ARB_window_pos) return;\n\tglad_glWindowPos2dARB = (PFNGLWINDOWPOS2DARBPROC)load(\"glWindowPos2dARB\");\n\tglad_glWindowPos2dvARB = (PFNGLWINDOWPOS2DVARBPROC)load(\"glWindowPos2dvARB\");\n\tglad_glWindowPos2fARB = (PFNGLWINDOWPOS2FARBPROC)load(\"glWindowPos2fARB\");\n\tglad_glWindowPos2fvARB = (PFNGLWINDOWPOS2FVARBPROC)load(\"glWindowPos2fvARB\");\n\tglad_glWindowPos2iARB = (PFNGLWINDOWPOS2IARBPROC)load(\"glWindowPos2iARB\");\n\tglad_glWindowPos2ivARB = (PFNGLWINDOWPOS2IVARBPROC)load(\"glWindowPos2ivARB\");\n\tglad_glWindowPos2sARB = (PFNGLWINDOWPOS2SARBPROC)load(\"glWindowPos2sARB\");\n\tglad_glWindowPos2svARB = (PFNGLWINDOWPOS2SVARBPROC)load(\"glWindowPos2svARB\");\n\tglad_glWindowPos3dARB = (PFNGLWINDOWPOS3DARBPROC)load(\"glWindowPos3dARB\");\n\tglad_glWindowPos3dvARB = (PFNGLWINDOWPOS3DVARBPROC)load(\"glWindowPos3dvARB\");\n\tglad_glWindowPos3fARB = (PFNGLWINDOWPOS3FARBPROC)load(\"glWindowPos3fARB\");\n\tglad_glWindowPos3fvARB = (PFNGLWINDOWPOS3FVARBPROC)load(\"glWindowPos3fvARB\");\n\tglad_glWindowPos3iARB = (PFNGLWINDOWPOS3IARBPROC)load(\"glWindowPos3iARB\");\n\tglad_glWindowPos3ivARB = (PFNGLWINDOWPOS3IVARBPROC)load(\"glWindowPos3ivARB\");\n\tglad_glWindowPos3sARB = (PFNGLWINDOWPOS3SARBPROC)load(\"glWindowPos3sARB\");\n\tglad_glWindowPos3svARB = (PFNGLWINDOWPOS3SVARBPROC)load(\"glWindowPos3svARB\");\n}\nstatic void load_GL_ATI_draw_buffers(GLADloadproc load) {\n\tif(!GLAD_GL_ATI_draw_buffers) return;\n\tglad_glDrawBuffersATI = (PFNGLDRAWBUFFERSATIPROC)load(\"glDrawBuffersATI\");\n}\nstatic void load_GL_ATI_element_array(GLADloadproc load) {\n\tif(!GLAD_GL_ATI_element_array) return;\n\tglad_glElementPointerATI = (PFNGLELEMENTPOINTERATIPROC)load(\"glElementPointerATI\");\n\tglad_glDrawElementArrayATI = (PFNGLDRAWELEMENTARRAYATIPROC)load(\"glDrawElementArrayATI\");\n\tglad_glDrawRangeElementArrayATI = (PFNGLDRAWRANGEELEMENTARRAYATIPROC)load(\"glDrawRangeElementArrayATI\");\n}\nstatic void load_GL_ATI_envmap_bumpmap(GLADloadproc load) {\n\tif(!GLAD_GL_ATI_envmap_bumpmap) return;\n\tglad_glTexBumpParameterivATI = (PFNGLTEXBUMPPARAMETERIVATIPROC)load(\"glTexBumpParameterivATI\");\n\tglad_glTexBumpParameterfvATI = (PFNGLTEXBUMPPARAMETERFVATIPROC)load(\"glTexBumpParameterfvATI\");\n\tglad_glGetTexBumpParameterivATI = (PFNGLGETTEXBUMPPARAMETERIVATIPROC)load(\"glGetTexBumpParameterivATI\");\n\tglad_glGetTexBumpParameterfvATI = (PFNGLGETTEXBUMPPARAMETERFVATIPROC)load(\"glGetTexBumpParameterfvATI\");\n}\nstatic void load_GL_ATI_fragment_shader(GLADloadproc load) {\n\tif(!GLAD_GL_ATI_fragment_shader) return;\n\tglad_glGenFragmentShadersATI = (PFNGLGENFRAGMENTSHADERSATIPROC)load(\"glGenFragmentShadersATI\");\n\tglad_glBindFragmentShaderATI = (PFNGLBINDFRAGMENTSHADERATIPROC)load(\"glBindFragmentShaderATI\");\n\tglad_glDeleteFragmentShaderATI = (PFNGLDELETEFRAGMENTSHADERATIPROC)load(\"glDeleteFragmentShaderATI\");\n\tglad_glBeginFragmentShaderATI = (PFNGLBEGINFRAGMENTSHADERATIPROC)load(\"glBeginFragmentShaderATI\");\n\tglad_glEndFragmentShaderATI = (PFNGLENDFRAGMENTSHADERATIPROC)load(\"glEndFragmentShaderATI\");\n\tglad_glPassTexCoordATI = (PFNGLPASSTEXCOORDATIPROC)load(\"glPassTexCoordATI\");\n\tglad_glSampleMapATI = (PFNGLSAMPLEMAPATIPROC)load(\"glSampleMapATI\");\n\tglad_glColorFragmentOp1ATI = (PFNGLCOLORFRAGMENTOP1ATIPROC)load(\"glColorFragmentOp1ATI\");\n\tglad_glColorFragmentOp2ATI = (PFNGLCOLORFRAGMENTOP2ATIPROC)load(\"glColorFragmentOp2ATI\");\n\tglad_glColorFragmentOp3ATI = (PFNGLCOLORFRAGMENTOP3ATIPROC)load(\"glColorFragmentOp3ATI\");\n\tglad_glAlphaFragmentOp1ATI = (PFNGLALPHAFRAGMENTOP1ATIPROC)load(\"glAlphaFragmentOp1ATI\");\n\tglad_glAlphaFragmentOp2ATI = (PFNGLALPHAFRAGMENTOP2ATIPROC)load(\"glAlphaFragmentOp2ATI\");\n\tglad_glAlphaFragmentOp3ATI = (PFNGLALPHAFRAGMENTOP3ATIPROC)load(\"glAlphaFragmentOp3ATI\");\n\tglad_glSetFragmentShaderConstantATI = (PFNGLSETFRAGMENTSHADERCONSTANTATIPROC)load(\"glSetFragmentShaderConstantATI\");\n}\nstatic void load_GL_ATI_map_object_buffer(GLADloadproc load) {\n\tif(!GLAD_GL_ATI_map_object_buffer) return;\n\tglad_glMapObjectBufferATI = (PFNGLMAPOBJECTBUFFERATIPROC)load(\"glMapObjectBufferATI\");\n\tglad_glUnmapObjectBufferATI = (PFNGLUNMAPOBJECTBUFFERATIPROC)load(\"glUnmapObjectBufferATI\");\n}\nstatic void load_GL_ATI_pn_triangles(GLADloadproc load) {\n\tif(!GLAD_GL_ATI_pn_triangles) return;\n\tglad_glPNTrianglesiATI = (PFNGLPNTRIANGLESIATIPROC)load(\"glPNTrianglesiATI\");\n\tglad_glPNTrianglesfATI = (PFNGLPNTRIANGLESFATIPROC)load(\"glPNTrianglesfATI\");\n}\nstatic void load_GL_ATI_separate_stencil(GLADloadproc load) {\n\tif(!GLAD_GL_ATI_separate_stencil) return;\n\tglad_glStencilOpSeparateATI = (PFNGLSTENCILOPSEPARATEATIPROC)load(\"glStencilOpSeparateATI\");\n\tglad_glStencilFuncSeparateATI = (PFNGLSTENCILFUNCSEPARATEATIPROC)load(\"glStencilFuncSeparateATI\");\n}\nstatic void load_GL_ATI_vertex_array_object(GLADloadproc load) {\n\tif(!GLAD_GL_ATI_vertex_array_object) return;\n\tglad_glNewObjectBufferATI = (PFNGLNEWOBJECTBUFFERATIPROC)load(\"glNewObjectBufferATI\");\n\tglad_glIsObjectBufferATI = (PFNGLISOBJECTBUFFERATIPROC)load(\"glIsObjectBufferATI\");\n\tglad_glUpdateObjectBufferATI = (PFNGLUPDATEOBJECTBUFFERATIPROC)load(\"glUpdateObjectBufferATI\");\n\tglad_glGetObjectBufferfvATI = (PFNGLGETOBJECTBUFFERFVATIPROC)load(\"glGetObjectBufferfvATI\");\n\tglad_glGetObjectBufferivATI = (PFNGLGETOBJECTBUFFERIVATIPROC)load(\"glGetObjectBufferivATI\");\n\tglad_glFreeObjectBufferATI = (PFNGLFREEOBJECTBUFFERATIPROC)load(\"glFreeObjectBufferATI\");\n\tglad_glArrayObjectATI = (PFNGLARRAYOBJECTATIPROC)load(\"glArrayObjectATI\");\n\tglad_glGetArrayObjectfvATI = (PFNGLGETARRAYOBJECTFVATIPROC)load(\"glGetArrayObjectfvATI\");\n\tglad_glGetArrayObjectivATI = (PFNGLGETARRAYOBJECTIVATIPROC)load(\"glGetArrayObjectivATI\");\n\tglad_glVariantArrayObjectATI = (PFNGLVARIANTARRAYOBJECTATIPROC)load(\"glVariantArrayObjectATI\");\n\tglad_glGetVariantArrayObjectfvATI = (PFNGLGETVARIANTARRAYOBJECTFVATIPROC)load(\"glGetVariantArrayObjectfvATI\");\n\tglad_glGetVariantArrayObjectivATI = (PFNGLGETVARIANTARRAYOBJECTIVATIPROC)load(\"glGetVariantArrayObjectivATI\");\n}\nstatic void load_GL_ATI_vertex_attrib_array_object(GLADloadproc load) {\n\tif(!GLAD_GL_ATI_vertex_attrib_array_object) return;\n\tglad_glVertexAttribArrayObjectATI = (PFNGLVERTEXATTRIBARRAYOBJECTATIPROC)load(\"glVertexAttribArrayObjectATI\");\n\tglad_glGetVertexAttribArrayObjectfvATI = (PFNGLGETVERTEXATTRIBARRAYOBJECTFVATIPROC)load(\"glGetVertexAttribArrayObjectfvATI\");\n\tglad_glGetVertexAttribArrayObjectivATI = (PFNGLGETVERTEXATTRIBARRAYOBJECTIVATIPROC)load(\"glGetVertexAttribArrayObjectivATI\");\n}\nstatic void load_GL_ATI_vertex_streams(GLADloadproc load) {\n\tif(!GLAD_GL_ATI_vertex_streams) return;\n\tglad_glVertexStream1sATI = (PFNGLVERTEXSTREAM1SATIPROC)load(\"glVertexStream1sATI\");\n\tglad_glVertexStream1svATI = (PFNGLVERTEXSTREAM1SVATIPROC)load(\"glVertexStream1svATI\");\n\tglad_glVertexStream1iATI = (PFNGLVERTEXSTREAM1IATIPROC)load(\"glVertexStream1iATI\");\n\tglad_glVertexStream1ivATI = (PFNGLVERTEXSTREAM1IVATIPROC)load(\"glVertexStream1ivATI\");\n\tglad_glVertexStream1fATI = (PFNGLVERTEXSTREAM1FATIPROC)load(\"glVertexStream1fATI\");\n\tglad_glVertexStream1fvATI = (PFNGLVERTEXSTREAM1FVATIPROC)load(\"glVertexStream1fvATI\");\n\tglad_glVertexStream1dATI = (PFNGLVERTEXSTREAM1DATIPROC)load(\"glVertexStream1dATI\");\n\tglad_glVertexStream1dvATI = (PFNGLVERTEXSTREAM1DVATIPROC)load(\"glVertexStream1dvATI\");\n\tglad_glVertexStream2sATI = (PFNGLVERTEXSTREAM2SATIPROC)load(\"glVertexStream2sATI\");\n\tglad_glVertexStream2svATI = (PFNGLVERTEXSTREAM2SVATIPROC)load(\"glVertexStream2svATI\");\n\tglad_glVertexStream2iATI = (PFNGLVERTEXSTREAM2IATIPROC)load(\"glVertexStream2iATI\");\n\tglad_glVertexStream2ivATI = (PFNGLVERTEXSTREAM2IVATIPROC)load(\"glVertexStream2ivATI\");\n\tglad_glVertexStream2fATI = (PFNGLVERTEXSTREAM2FATIPROC)load(\"glVertexStream2fATI\");\n\tglad_glVertexStream2fvATI = (PFNGLVERTEXSTREAM2FVATIPROC)load(\"glVertexStream2fvATI\");\n\tglad_glVertexStream2dATI = (PFNGLVERTEXSTREAM2DATIPROC)load(\"glVertexStream2dATI\");\n\tglad_glVertexStream2dvATI = (PFNGLVERTEXSTREAM2DVATIPROC)load(\"glVertexStream2dvATI\");\n\tglad_glVertexStream3sATI = (PFNGLVERTEXSTREAM3SATIPROC)load(\"glVertexStream3sATI\");\n\tglad_glVertexStream3svATI = (PFNGLVERTEXSTREAM3SVATIPROC)load(\"glVertexStream3svATI\");\n\tglad_glVertexStream3iATI = (PFNGLVERTEXSTREAM3IATIPROC)load(\"glVertexStream3iATI\");\n\tglad_glVertexStream3ivATI = (PFNGLVERTEXSTREAM3IVATIPROC)load(\"glVertexStream3ivATI\");\n\tglad_glVertexStream3fATI = (PFNGLVERTEXSTREAM3FATIPROC)load(\"glVertexStream3fATI\");\n\tglad_glVertexStream3fvATI = (PFNGLVERTEXSTREAM3FVATIPROC)load(\"glVertexStream3fvATI\");\n\tglad_glVertexStream3dATI = (PFNGLVERTEXSTREAM3DATIPROC)load(\"glVertexStream3dATI\");\n\tglad_glVertexStream3dvATI = (PFNGLVERTEXSTREAM3DVATIPROC)load(\"glVertexStream3dvATI\");\n\tglad_glVertexStream4sATI = (PFNGLVERTEXSTREAM4SATIPROC)load(\"glVertexStream4sATI\");\n\tglad_glVertexStream4svATI = (PFNGLVERTEXSTREAM4SVATIPROC)load(\"glVertexStream4svATI\");\n\tglad_glVertexStream4iATI = (PFNGLVERTEXSTREAM4IATIPROC)load(\"glVertexStream4iATI\");\n\tglad_glVertexStream4ivATI = (PFNGLVERTEXSTREAM4IVATIPROC)load(\"glVertexStream4ivATI\");\n\tglad_glVertexStream4fATI = (PFNGLVERTEXSTREAM4FATIPROC)load(\"glVertexStream4fATI\");\n\tglad_glVertexStream4fvATI = (PFNGLVERTEXSTREAM4FVATIPROC)load(\"glVertexStream4fvATI\");\n\tglad_glVertexStream4dATI = (PFNGLVERTEXSTREAM4DATIPROC)load(\"glVertexStream4dATI\");\n\tglad_glVertexStream4dvATI = (PFNGLVERTEXSTREAM4DVATIPROC)load(\"glVertexStream4dvATI\");\n\tglad_glNormalStream3bATI = (PFNGLNORMALSTREAM3BATIPROC)load(\"glNormalStream3bATI\");\n\tglad_glNormalStream3bvATI = (PFNGLNORMALSTREAM3BVATIPROC)load(\"glNormalStream3bvATI\");\n\tglad_glNormalStream3sATI = (PFNGLNORMALSTREAM3SATIPROC)load(\"glNormalStream3sATI\");\n\tglad_glNormalStream3svATI = (PFNGLNORMALSTREAM3SVATIPROC)load(\"glNormalStream3svATI\");\n\tglad_glNormalStream3iATI = (PFNGLNORMALSTREAM3IATIPROC)load(\"glNormalStream3iATI\");\n\tglad_glNormalStream3ivATI = (PFNGLNORMALSTREAM3IVATIPROC)load(\"glNormalStream3ivATI\");\n\tglad_glNormalStream3fATI = (PFNGLNORMALSTREAM3FATIPROC)load(\"glNormalStream3fATI\");\n\tglad_glNormalStream3fvATI = (PFNGLNORMALSTREAM3FVATIPROC)load(\"glNormalStream3fvATI\");\n\tglad_glNormalStream3dATI = (PFNGLNORMALSTREAM3DATIPROC)load(\"glNormalStream3dATI\");\n\tglad_glNormalStream3dvATI = (PFNGLNORMALSTREAM3DVATIPROC)load(\"glNormalStream3dvATI\");\n\tglad_glClientActiveVertexStreamATI = (PFNGLCLIENTACTIVEVERTEXSTREAMATIPROC)load(\"glClientActiveVertexStreamATI\");\n\tglad_glVertexBlendEnviATI = (PFNGLVERTEXBLENDENVIATIPROC)load(\"glVertexBlendEnviATI\");\n\tglad_glVertexBlendEnvfATI = (PFNGLVERTEXBLENDENVFATIPROC)load(\"glVertexBlendEnvfATI\");\n}\nstatic void load_GL_EXT_EGL_image_storage(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_EGL_image_storage) return;\n\tglad_glEGLImageTargetTexStorageEXT = (PFNGLEGLIMAGETARGETTEXSTORAGEEXTPROC)load(\"glEGLImageTargetTexStorageEXT\");\n\tglad_glEGLImageTargetTextureStorageEXT = (PFNGLEGLIMAGETARGETTEXTURESTORAGEEXTPROC)load(\"glEGLImageTargetTextureStorageEXT\");\n}\nstatic void load_GL_EXT_bindable_uniform(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_bindable_uniform) return;\n\tglad_glUniformBufferEXT = (PFNGLUNIFORMBUFFEREXTPROC)load(\"glUniformBufferEXT\");\n\tglad_glGetUniformBufferSizeEXT = (PFNGLGETUNIFORMBUFFERSIZEEXTPROC)load(\"glGetUniformBufferSizeEXT\");\n\tglad_glGetUniformOffsetEXT = (PFNGLGETUNIFORMOFFSETEXTPROC)load(\"glGetUniformOffsetEXT\");\n}\nstatic void load_GL_EXT_blend_color(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_blend_color) return;\n\tglad_glBlendColorEXT = (PFNGLBLENDCOLOREXTPROC)load(\"glBlendColorEXT\");\n}\nstatic void load_GL_EXT_blend_equation_separate(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_blend_equation_separate) return;\n\tglad_glBlendEquationSeparateEXT = (PFNGLBLENDEQUATIONSEPARATEEXTPROC)load(\"glBlendEquationSeparateEXT\");\n}\nstatic void load_GL_EXT_blend_func_separate(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_blend_func_separate) return;\n\tglad_glBlendFuncSeparateEXT = (PFNGLBLENDFUNCSEPARATEEXTPROC)load(\"glBlendFuncSeparateEXT\");\n}\nstatic void load_GL_EXT_blend_minmax(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_blend_minmax) return;\n\tglad_glBlendEquationEXT = (PFNGLBLENDEQUATIONEXTPROC)load(\"glBlendEquationEXT\");\n}\nstatic void load_GL_EXT_color_subtable(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_color_subtable) return;\n\tglad_glColorSubTableEXT = (PFNGLCOLORSUBTABLEEXTPROC)load(\"glColorSubTableEXT\");\n\tglad_glCopyColorSubTableEXT = (PFNGLCOPYCOLORSUBTABLEEXTPROC)load(\"glCopyColorSubTableEXT\");\n}\nstatic void load_GL_EXT_compiled_vertex_array(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_compiled_vertex_array) return;\n\tglad_glLockArraysEXT = (PFNGLLOCKARRAYSEXTPROC)load(\"glLockArraysEXT\");\n\tglad_glUnlockArraysEXT = (PFNGLUNLOCKARRAYSEXTPROC)load(\"glUnlockArraysEXT\");\n}\nstatic void load_GL_EXT_convolution(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_convolution) return;\n\tglad_glConvolutionFilter1DEXT = (PFNGLCONVOLUTIONFILTER1DEXTPROC)load(\"glConvolutionFilter1DEXT\");\n\tglad_glConvolutionFilter2DEXT = (PFNGLCONVOLUTIONFILTER2DEXTPROC)load(\"glConvolutionFilter2DEXT\");\n\tglad_glConvolutionParameterfEXT = (PFNGLCONVOLUTIONPARAMETERFEXTPROC)load(\"glConvolutionParameterfEXT\");\n\tglad_glConvolutionParameterfvEXT = (PFNGLCONVOLUTIONPARAMETERFVEXTPROC)load(\"glConvolutionParameterfvEXT\");\n\tglad_glConvolutionParameteriEXT = (PFNGLCONVOLUTIONPARAMETERIEXTPROC)load(\"glConvolutionParameteriEXT\");\n\tglad_glConvolutionParameterivEXT = (PFNGLCONVOLUTIONPARAMETERIVEXTPROC)load(\"glConvolutionParameterivEXT\");\n\tglad_glCopyConvolutionFilter1DEXT = (PFNGLCOPYCONVOLUTIONFILTER1DEXTPROC)load(\"glCopyConvolutionFilter1DEXT\");\n\tglad_glCopyConvolutionFilter2DEXT = (PFNGLCOPYCONVOLUTIONFILTER2DEXTPROC)load(\"glCopyConvolutionFilter2DEXT\");\n\tglad_glGetConvolutionFilterEXT = (PFNGLGETCONVOLUTIONFILTEREXTPROC)load(\"glGetConvolutionFilterEXT\");\n\tglad_glGetConvolutionParameterfvEXT = (PFNGLGETCONVOLUTIONPARAMETERFVEXTPROC)load(\"glGetConvolutionParameterfvEXT\");\n\tglad_glGetConvolutionParameterivEXT = (PFNGLGETCONVOLUTIONPARAMETERIVEXTPROC)load(\"glGetConvolutionParameterivEXT\");\n\tglad_glGetSeparableFilterEXT = (PFNGLGETSEPARABLEFILTEREXTPROC)load(\"glGetSeparableFilterEXT\");\n\tglad_glSeparableFilter2DEXT = (PFNGLSEPARABLEFILTER2DEXTPROC)load(\"glSeparableFilter2DEXT\");\n}\nstatic void load_GL_EXT_coordinate_frame(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_coordinate_frame) return;\n\tglad_glTangent3bEXT = (PFNGLTANGENT3BEXTPROC)load(\"glTangent3bEXT\");\n\tglad_glTangent3bvEXT = (PFNGLTANGENT3BVEXTPROC)load(\"glTangent3bvEXT\");\n\tglad_glTangent3dEXT = (PFNGLTANGENT3DEXTPROC)load(\"glTangent3dEXT\");\n\tglad_glTangent3dvEXT = (PFNGLTANGENT3DVEXTPROC)load(\"glTangent3dvEXT\");\n\tglad_glTangent3fEXT = (PFNGLTANGENT3FEXTPROC)load(\"glTangent3fEXT\");\n\tglad_glTangent3fvEXT = (PFNGLTANGENT3FVEXTPROC)load(\"glTangent3fvEXT\");\n\tglad_glTangent3iEXT = (PFNGLTANGENT3IEXTPROC)load(\"glTangent3iEXT\");\n\tglad_glTangent3ivEXT = (PFNGLTANGENT3IVEXTPROC)load(\"glTangent3ivEXT\");\n\tglad_glTangent3sEXT = (PFNGLTANGENT3SEXTPROC)load(\"glTangent3sEXT\");\n\tglad_glTangent3svEXT = (PFNGLTANGENT3SVEXTPROC)load(\"glTangent3svEXT\");\n\tglad_glBinormal3bEXT = (PFNGLBINORMAL3BEXTPROC)load(\"glBinormal3bEXT\");\n\tglad_glBinormal3bvEXT = (PFNGLBINORMAL3BVEXTPROC)load(\"glBinormal3bvEXT\");\n\tglad_glBinormal3dEXT = (PFNGLBINORMAL3DEXTPROC)load(\"glBinormal3dEXT\");\n\tglad_glBinormal3dvEXT = (PFNGLBINORMAL3DVEXTPROC)load(\"glBinormal3dvEXT\");\n\tglad_glBinormal3fEXT = (PFNGLBINORMAL3FEXTPROC)load(\"glBinormal3fEXT\");\n\tglad_glBinormal3fvEXT = (PFNGLBINORMAL3FVEXTPROC)load(\"glBinormal3fvEXT\");\n\tglad_glBinormal3iEXT = (PFNGLBINORMAL3IEXTPROC)load(\"glBinormal3iEXT\");\n\tglad_glBinormal3ivEXT = (PFNGLBINORMAL3IVEXTPROC)load(\"glBinormal3ivEXT\");\n\tglad_glBinormal3sEXT = (PFNGLBINORMAL3SEXTPROC)load(\"glBinormal3sEXT\");\n\tglad_glBinormal3svEXT = (PFNGLBINORMAL3SVEXTPROC)load(\"glBinormal3svEXT\");\n\tglad_glTangentPointerEXT = (PFNGLTANGENTPOINTEREXTPROC)load(\"glTangentPointerEXT\");\n\tglad_glBinormalPointerEXT = (PFNGLBINORMALPOINTEREXTPROC)load(\"glBinormalPointerEXT\");\n}\nstatic void load_GL_EXT_copy_texture(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_copy_texture) return;\n\tglad_glCopyTexImage1DEXT = (PFNGLCOPYTEXIMAGE1DEXTPROC)load(\"glCopyTexImage1DEXT\");\n\tglad_glCopyTexImage2DEXT = (PFNGLCOPYTEXIMAGE2DEXTPROC)load(\"glCopyTexImage2DEXT\");\n\tglad_glCopyTexSubImage1DEXT = (PFNGLCOPYTEXSUBIMAGE1DEXTPROC)load(\"glCopyTexSubImage1DEXT\");\n\tglad_glCopyTexSubImage2DEXT = (PFNGLCOPYTEXSUBIMAGE2DEXTPROC)load(\"glCopyTexSubImage2DEXT\");\n\tglad_glCopyTexSubImage3DEXT = (PFNGLCOPYTEXSUBIMAGE3DEXTPROC)load(\"glCopyTexSubImage3DEXT\");\n}\nstatic void load_GL_EXT_cull_vertex(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_cull_vertex) return;\n\tglad_glCullParameterdvEXT = (PFNGLCULLPARAMETERDVEXTPROC)load(\"glCullParameterdvEXT\");\n\tglad_glCullParameterfvEXT = (PFNGLCULLPARAMETERFVEXTPROC)load(\"glCullParameterfvEXT\");\n}\nstatic void load_GL_EXT_debug_label(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_debug_label) return;\n\tglad_glLabelObjectEXT = (PFNGLLABELOBJECTEXTPROC)load(\"glLabelObjectEXT\");\n\tglad_glGetObjectLabelEXT = (PFNGLGETOBJECTLABELEXTPROC)load(\"glGetObjectLabelEXT\");\n}\nstatic void load_GL_EXT_debug_marker(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_debug_marker) return;\n\tglad_glInsertEventMarkerEXT = (PFNGLINSERTEVENTMARKEREXTPROC)load(\"glInsertEventMarkerEXT\");\n\tglad_glPushGroupMarkerEXT = (PFNGLPUSHGROUPMARKEREXTPROC)load(\"glPushGroupMarkerEXT\");\n\tglad_glPopGroupMarkerEXT = (PFNGLPOPGROUPMARKEREXTPROC)load(\"glPopGroupMarkerEXT\");\n}\nstatic void load_GL_EXT_depth_bounds_test(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_depth_bounds_test) return;\n\tglad_glDepthBoundsEXT = (PFNGLDEPTHBOUNDSEXTPROC)load(\"glDepthBoundsEXT\");\n}\nstatic void load_GL_EXT_direct_state_access(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_direct_state_access) return;\n\tglad_glMatrixLoadfEXT = (PFNGLMATRIXLOADFEXTPROC)load(\"glMatrixLoadfEXT\");\n\tglad_glMatrixLoaddEXT = (PFNGLMATRIXLOADDEXTPROC)load(\"glMatrixLoaddEXT\");\n\tglad_glMatrixMultfEXT = (PFNGLMATRIXMULTFEXTPROC)load(\"glMatrixMultfEXT\");\n\tglad_glMatrixMultdEXT = (PFNGLMATRIXMULTDEXTPROC)load(\"glMatrixMultdEXT\");\n\tglad_glMatrixLoadIdentityEXT = (PFNGLMATRIXLOADIDENTITYEXTPROC)load(\"glMatrixLoadIdentityEXT\");\n\tglad_glMatrixRotatefEXT = (PFNGLMATRIXROTATEFEXTPROC)load(\"glMatrixRotatefEXT\");\n\tglad_glMatrixRotatedEXT = (PFNGLMATRIXROTATEDEXTPROC)load(\"glMatrixRotatedEXT\");\n\tglad_glMatrixScalefEXT = (PFNGLMATRIXSCALEFEXTPROC)load(\"glMatrixScalefEXT\");\n\tglad_glMatrixScaledEXT = (PFNGLMATRIXSCALEDEXTPROC)load(\"glMatrixScaledEXT\");\n\tglad_glMatrixTranslatefEXT = (PFNGLMATRIXTRANSLATEFEXTPROC)load(\"glMatrixTranslatefEXT\");\n\tglad_glMatrixTranslatedEXT = (PFNGLMATRIXTRANSLATEDEXTPROC)load(\"glMatrixTranslatedEXT\");\n\tglad_glMatrixFrustumEXT = (PFNGLMATRIXFRUSTUMEXTPROC)load(\"glMatrixFrustumEXT\");\n\tglad_glMatrixOrthoEXT = (PFNGLMATRIXORTHOEXTPROC)load(\"glMatrixOrthoEXT\");\n\tglad_glMatrixPopEXT = (PFNGLMATRIXPOPEXTPROC)load(\"glMatrixPopEXT\");\n\tglad_glMatrixPushEXT = (PFNGLMATRIXPUSHEXTPROC)load(\"glMatrixPushEXT\");\n\tglad_glClientAttribDefaultEXT = (PFNGLCLIENTATTRIBDEFAULTEXTPROC)load(\"glClientAttribDefaultEXT\");\n\tglad_glPushClientAttribDefaultEXT = (PFNGLPUSHCLIENTATTRIBDEFAULTEXTPROC)load(\"glPushClientAttribDefaultEXT\");\n\tglad_glTextureParameterfEXT = (PFNGLTEXTUREPARAMETERFEXTPROC)load(\"glTextureParameterfEXT\");\n\tglad_glTextureParameterfvEXT = (PFNGLTEXTUREPARAMETERFVEXTPROC)load(\"glTextureParameterfvEXT\");\n\tglad_glTextureParameteriEXT = (PFNGLTEXTUREPARAMETERIEXTPROC)load(\"glTextureParameteriEXT\");\n\tglad_glTextureParameterivEXT = (PFNGLTEXTUREPARAMETERIVEXTPROC)load(\"glTextureParameterivEXT\");\n\tglad_glTextureImage1DEXT = (PFNGLTEXTUREIMAGE1DEXTPROC)load(\"glTextureImage1DEXT\");\n\tglad_glTextureImage2DEXT = (PFNGLTEXTUREIMAGE2DEXTPROC)load(\"glTextureImage2DEXT\");\n\tglad_glTextureSubImage1DEXT = (PFNGLTEXTURESUBIMAGE1DEXTPROC)load(\"glTextureSubImage1DEXT\");\n\tglad_glTextureSubImage2DEXT = (PFNGLTEXTURESUBIMAGE2DEXTPROC)load(\"glTextureSubImage2DEXT\");\n\tglad_glCopyTextureImage1DEXT = (PFNGLCOPYTEXTUREIMAGE1DEXTPROC)load(\"glCopyTextureImage1DEXT\");\n\tglad_glCopyTextureImage2DEXT = (PFNGLCOPYTEXTUREIMAGE2DEXTPROC)load(\"glCopyTextureImage2DEXT\");\n\tglad_glCopyTextureSubImage1DEXT = (PFNGLCOPYTEXTURESUBIMAGE1DEXTPROC)load(\"glCopyTextureSubImage1DEXT\");\n\tglad_glCopyTextureSubImage2DEXT = (PFNGLCOPYTEXTURESUBIMAGE2DEXTPROC)load(\"glCopyTextureSubImage2DEXT\");\n\tglad_glGetTextureImageEXT = (PFNGLGETTEXTUREIMAGEEXTPROC)load(\"glGetTextureImageEXT\");\n\tglad_glGetTextureParameterfvEXT = (PFNGLGETTEXTUREPARAMETERFVEXTPROC)load(\"glGetTextureParameterfvEXT\");\n\tglad_glGetTextureParameterivEXT = (PFNGLGETTEXTUREPARAMETERIVEXTPROC)load(\"glGetTextureParameterivEXT\");\n\tglad_glGetTextureLevelParameterfvEXT = (PFNGLGETTEXTURELEVELPARAMETERFVEXTPROC)load(\"glGetTextureLevelParameterfvEXT\");\n\tglad_glGetTextureLevelParameterivEXT = (PFNGLGETTEXTURELEVELPARAMETERIVEXTPROC)load(\"glGetTextureLevelParameterivEXT\");\n\tglad_glTextureImage3DEXT = (PFNGLTEXTUREIMAGE3DEXTPROC)load(\"glTextureImage3DEXT\");\n\tglad_glTextureSubImage3DEXT = (PFNGLTEXTURESUBIMAGE3DEXTPROC)load(\"glTextureSubImage3DEXT\");\n\tglad_glCopyTextureSubImage3DEXT = (PFNGLCOPYTEXTURESUBIMAGE3DEXTPROC)load(\"glCopyTextureSubImage3DEXT\");\n\tglad_glBindMultiTextureEXT = (PFNGLBINDMULTITEXTUREEXTPROC)load(\"glBindMultiTextureEXT\");\n\tglad_glMultiTexCoordPointerEXT = (PFNGLMULTITEXCOORDPOINTEREXTPROC)load(\"glMultiTexCoordPointerEXT\");\n\tglad_glMultiTexEnvfEXT = (PFNGLMULTITEXENVFEXTPROC)load(\"glMultiTexEnvfEXT\");\n\tglad_glMultiTexEnvfvEXT = (PFNGLMULTITEXENVFVEXTPROC)load(\"glMultiTexEnvfvEXT\");\n\tglad_glMultiTexEnviEXT = (PFNGLMULTITEXENVIEXTPROC)load(\"glMultiTexEnviEXT\");\n\tglad_glMultiTexEnvivEXT = (PFNGLMULTITEXENVIVEXTPROC)load(\"glMultiTexEnvivEXT\");\n\tglad_glMultiTexGendEXT = (PFNGLMULTITEXGENDEXTPROC)load(\"glMultiTexGendEXT\");\n\tglad_glMultiTexGendvEXT = (PFNGLMULTITEXGENDVEXTPROC)load(\"glMultiTexGendvEXT\");\n\tglad_glMultiTexGenfEXT = (PFNGLMULTITEXGENFEXTPROC)load(\"glMultiTexGenfEXT\");\n\tglad_glMultiTexGenfvEXT = (PFNGLMULTITEXGENFVEXTPROC)load(\"glMultiTexGenfvEXT\");\n\tglad_glMultiTexGeniEXT = (PFNGLMULTITEXGENIEXTPROC)load(\"glMultiTexGeniEXT\");\n\tglad_glMultiTexGenivEXT = (PFNGLMULTITEXGENIVEXTPROC)load(\"glMultiTexGenivEXT\");\n\tglad_glGetMultiTexEnvfvEXT = (PFNGLGETMULTITEXENVFVEXTPROC)load(\"glGetMultiTexEnvfvEXT\");\n\tglad_glGetMultiTexEnvivEXT = (PFNGLGETMULTITEXENVIVEXTPROC)load(\"glGetMultiTexEnvivEXT\");\n\tglad_glGetMultiTexGendvEXT = (PFNGLGETMULTITEXGENDVEXTPROC)load(\"glGetMultiTexGendvEXT\");\n\tglad_glGetMultiTexGenfvEXT = (PFNGLGETMULTITEXGENFVEXTPROC)load(\"glGetMultiTexGenfvEXT\");\n\tglad_glGetMultiTexGenivEXT = (PFNGLGETMULTITEXGENIVEXTPROC)load(\"glGetMultiTexGenivEXT\");\n\tglad_glMultiTexParameteriEXT = (PFNGLMULTITEXPARAMETERIEXTPROC)load(\"glMultiTexParameteriEXT\");\n\tglad_glMultiTexParameterivEXT = (PFNGLMULTITEXPARAMETERIVEXTPROC)load(\"glMultiTexParameterivEXT\");\n\tglad_glMultiTexParameterfEXT = (PFNGLMULTITEXPARAMETERFEXTPROC)load(\"glMultiTexParameterfEXT\");\n\tglad_glMultiTexParameterfvEXT = (PFNGLMULTITEXPARAMETERFVEXTPROC)load(\"glMultiTexParameterfvEXT\");\n\tglad_glMultiTexImage1DEXT = (PFNGLMULTITEXIMAGE1DEXTPROC)load(\"glMultiTexImage1DEXT\");\n\tglad_glMultiTexImage2DEXT = (PFNGLMULTITEXIMAGE2DEXTPROC)load(\"glMultiTexImage2DEXT\");\n\tglad_glMultiTexSubImage1DEXT = (PFNGLMULTITEXSUBIMAGE1DEXTPROC)load(\"glMultiTexSubImage1DEXT\");\n\tglad_glMultiTexSubImage2DEXT = (PFNGLMULTITEXSUBIMAGE2DEXTPROC)load(\"glMultiTexSubImage2DEXT\");\n\tglad_glCopyMultiTexImage1DEXT = (PFNGLCOPYMULTITEXIMAGE1DEXTPROC)load(\"glCopyMultiTexImage1DEXT\");\n\tglad_glCopyMultiTexImage2DEXT = (PFNGLCOPYMULTITEXIMAGE2DEXTPROC)load(\"glCopyMultiTexImage2DEXT\");\n\tglad_glCopyMultiTexSubImage1DEXT = (PFNGLCOPYMULTITEXSUBIMAGE1DEXTPROC)load(\"glCopyMultiTexSubImage1DEXT\");\n\tglad_glCopyMultiTexSubImage2DEXT = (PFNGLCOPYMULTITEXSUBIMAGE2DEXTPROC)load(\"glCopyMultiTexSubImage2DEXT\");\n\tglad_glGetMultiTexImageEXT = (PFNGLGETMULTITEXIMAGEEXTPROC)load(\"glGetMultiTexImageEXT\");\n\tglad_glGetMultiTexParameterfvEXT = (PFNGLGETMULTITEXPARAMETERFVEXTPROC)load(\"glGetMultiTexParameterfvEXT\");\n\tglad_glGetMultiTexParameterivEXT = (PFNGLGETMULTITEXPARAMETERIVEXTPROC)load(\"glGetMultiTexParameterivEXT\");\n\tglad_glGetMultiTexLevelParameterfvEXT = (PFNGLGETMULTITEXLEVELPARAMETERFVEXTPROC)load(\"glGetMultiTexLevelParameterfvEXT\");\n\tglad_glGetMultiTexLevelParameterivEXT = (PFNGLGETMULTITEXLEVELPARAMETERIVEXTPROC)load(\"glGetMultiTexLevelParameterivEXT\");\n\tglad_glMultiTexImage3DEXT = (PFNGLMULTITEXIMAGE3DEXTPROC)load(\"glMultiTexImage3DEXT\");\n\tglad_glMultiTexSubImage3DEXT = (PFNGLMULTITEXSUBIMAGE3DEXTPROC)load(\"glMultiTexSubImage3DEXT\");\n\tglad_glCopyMultiTexSubImage3DEXT = (PFNGLCOPYMULTITEXSUBIMAGE3DEXTPROC)load(\"glCopyMultiTexSubImage3DEXT\");\n\tglad_glEnableClientStateIndexedEXT = (PFNGLENABLECLIENTSTATEINDEXEDEXTPROC)load(\"glEnableClientStateIndexedEXT\");\n\tglad_glDisableClientStateIndexedEXT = (PFNGLDISABLECLIENTSTATEINDEXEDEXTPROC)load(\"glDisableClientStateIndexedEXT\");\n\tglad_glGetFloatIndexedvEXT = (PFNGLGETFLOATINDEXEDVEXTPROC)load(\"glGetFloatIndexedvEXT\");\n\tglad_glGetDoubleIndexedvEXT = (PFNGLGETDOUBLEINDEXEDVEXTPROC)load(\"glGetDoubleIndexedvEXT\");\n\tglad_glGetPointerIndexedvEXT = (PFNGLGETPOINTERINDEXEDVEXTPROC)load(\"glGetPointerIndexedvEXT\");\n\tglad_glEnableIndexedEXT = (PFNGLENABLEINDEXEDEXTPROC)load(\"glEnableIndexedEXT\");\n\tglad_glDisableIndexedEXT = (PFNGLDISABLEINDEXEDEXTPROC)load(\"glDisableIndexedEXT\");\n\tglad_glIsEnabledIndexedEXT = (PFNGLISENABLEDINDEXEDEXTPROC)load(\"glIsEnabledIndexedEXT\");\n\tglad_glGetIntegerIndexedvEXT = (PFNGLGETINTEGERINDEXEDVEXTPROC)load(\"glGetIntegerIndexedvEXT\");\n\tglad_glGetBooleanIndexedvEXT = (PFNGLGETBOOLEANINDEXEDVEXTPROC)load(\"glGetBooleanIndexedvEXT\");\n\tglad_glCompressedTextureImage3DEXT = (PFNGLCOMPRESSEDTEXTUREIMAGE3DEXTPROC)load(\"glCompressedTextureImage3DEXT\");\n\tglad_glCompressedTextureImage2DEXT = (PFNGLCOMPRESSEDTEXTUREIMAGE2DEXTPROC)load(\"glCompressedTextureImage2DEXT\");\n\tglad_glCompressedTextureImage1DEXT = (PFNGLCOMPRESSEDTEXTUREIMAGE1DEXTPROC)load(\"glCompressedTextureImage1DEXT\");\n\tglad_glCompressedTextureSubImage3DEXT = (PFNGLCOMPRESSEDTEXTURESUBIMAGE3DEXTPROC)load(\"glCompressedTextureSubImage3DEXT\");\n\tglad_glCompressedTextureSubImage2DEXT = (PFNGLCOMPRESSEDTEXTURESUBIMAGE2DEXTPROC)load(\"glCompressedTextureSubImage2DEXT\");\n\tglad_glCompressedTextureSubImage1DEXT = (PFNGLCOMPRESSEDTEXTURESUBIMAGE1DEXTPROC)load(\"glCompressedTextureSubImage1DEXT\");\n\tglad_glGetCompressedTextureImageEXT = (PFNGLGETCOMPRESSEDTEXTUREIMAGEEXTPROC)load(\"glGetCompressedTextureImageEXT\");\n\tglad_glCompressedMultiTexImage3DEXT = (PFNGLCOMPRESSEDMULTITEXIMAGE3DEXTPROC)load(\"glCompressedMultiTexImage3DEXT\");\n\tglad_glCompressedMultiTexImage2DEXT = (PFNGLCOMPRESSEDMULTITEXIMAGE2DEXTPROC)load(\"glCompressedMultiTexImage2DEXT\");\n\tglad_glCompressedMultiTexImage1DEXT = (PFNGLCOMPRESSEDMULTITEXIMAGE1DEXTPROC)load(\"glCompressedMultiTexImage1DEXT\");\n\tglad_glCompressedMultiTexSubImage3DEXT = (PFNGLCOMPRESSEDMULTITEXSUBIMAGE3DEXTPROC)load(\"glCompressedMultiTexSubImage3DEXT\");\n\tglad_glCompressedMultiTexSubImage2DEXT = (PFNGLCOMPRESSEDMULTITEXSUBIMAGE2DEXTPROC)load(\"glCompressedMultiTexSubImage2DEXT\");\n\tglad_glCompressedMultiTexSubImage1DEXT = (PFNGLCOMPRESSEDMULTITEXSUBIMAGE1DEXTPROC)load(\"glCompressedMultiTexSubImage1DEXT\");\n\tglad_glGetCompressedMultiTexImageEXT = (PFNGLGETCOMPRESSEDMULTITEXIMAGEEXTPROC)load(\"glGetCompressedMultiTexImageEXT\");\n\tglad_glMatrixLoadTransposefEXT = (PFNGLMATRIXLOADTRANSPOSEFEXTPROC)load(\"glMatrixLoadTransposefEXT\");\n\tglad_glMatrixLoadTransposedEXT = (PFNGLMATRIXLOADTRANSPOSEDEXTPROC)load(\"glMatrixLoadTransposedEXT\");\n\tglad_glMatrixMultTransposefEXT = (PFNGLMATRIXMULTTRANSPOSEFEXTPROC)load(\"glMatrixMultTransposefEXT\");\n\tglad_glMatrixMultTransposedEXT = (PFNGLMATRIXMULTTRANSPOSEDEXTPROC)load(\"glMatrixMultTransposedEXT\");\n\tglad_glNamedBufferDataEXT = (PFNGLNAMEDBUFFERDATAEXTPROC)load(\"glNamedBufferDataEXT\");\n\tglad_glNamedBufferSubDataEXT = (PFNGLNAMEDBUFFERSUBDATAEXTPROC)load(\"glNamedBufferSubDataEXT\");\n\tglad_glMapNamedBufferEXT = (PFNGLMAPNAMEDBUFFEREXTPROC)load(\"glMapNamedBufferEXT\");\n\tglad_glUnmapNamedBufferEXT = (PFNGLUNMAPNAMEDBUFFEREXTPROC)load(\"glUnmapNamedBufferEXT\");\n\tglad_glGetNamedBufferParameterivEXT = (PFNGLGETNAMEDBUFFERPARAMETERIVEXTPROC)load(\"glGetNamedBufferParameterivEXT\");\n\tglad_glGetNamedBufferPointervEXT = (PFNGLGETNAMEDBUFFERPOINTERVEXTPROC)load(\"glGetNamedBufferPointervEXT\");\n\tglad_glGetNamedBufferSubDataEXT = (PFNGLGETNAMEDBUFFERSUBDATAEXTPROC)load(\"glGetNamedBufferSubDataEXT\");\n\tglad_glProgramUniform1fEXT = (PFNGLPROGRAMUNIFORM1FEXTPROC)load(\"glProgramUniform1fEXT\");\n\tglad_glProgramUniform2fEXT = (PFNGLPROGRAMUNIFORM2FEXTPROC)load(\"glProgramUniform2fEXT\");\n\tglad_glProgramUniform3fEXT = (PFNGLPROGRAMUNIFORM3FEXTPROC)load(\"glProgramUniform3fEXT\");\n\tglad_glProgramUniform4fEXT = (PFNGLPROGRAMUNIFORM4FEXTPROC)load(\"glProgramUniform4fEXT\");\n\tglad_glProgramUniform1iEXT = (PFNGLPROGRAMUNIFORM1IEXTPROC)load(\"glProgramUniform1iEXT\");\n\tglad_glProgramUniform2iEXT = (PFNGLPROGRAMUNIFORM2IEXTPROC)load(\"glProgramUniform2iEXT\");\n\tglad_glProgramUniform3iEXT = (PFNGLPROGRAMUNIFORM3IEXTPROC)load(\"glProgramUniform3iEXT\");\n\tglad_glProgramUniform4iEXT = (PFNGLPROGRAMUNIFORM4IEXTPROC)load(\"glProgramUniform4iEXT\");\n\tglad_glProgramUniform1fvEXT = (PFNGLPROGRAMUNIFORM1FVEXTPROC)load(\"glProgramUniform1fvEXT\");\n\tglad_glProgramUniform2fvEXT = (PFNGLPROGRAMUNIFORM2FVEXTPROC)load(\"glProgramUniform2fvEXT\");\n\tglad_glProgramUniform3fvEXT = (PFNGLPROGRAMUNIFORM3FVEXTPROC)load(\"glProgramUniform3fvEXT\");\n\tglad_glProgramUniform4fvEXT = (PFNGLPROGRAMUNIFORM4FVEXTPROC)load(\"glProgramUniform4fvEXT\");\n\tglad_glProgramUniform1ivEXT = (PFNGLPROGRAMUNIFORM1IVEXTPROC)load(\"glProgramUniform1ivEXT\");\n\tglad_glProgramUniform2ivEXT = (PFNGLPROGRAMUNIFORM2IVEXTPROC)load(\"glProgramUniform2ivEXT\");\n\tglad_glProgramUniform3ivEXT = (PFNGLPROGRAMUNIFORM3IVEXTPROC)load(\"glProgramUniform3ivEXT\");\n\tglad_glProgramUniform4ivEXT = (PFNGLPROGRAMUNIFORM4IVEXTPROC)load(\"glProgramUniform4ivEXT\");\n\tglad_glProgramUniformMatrix2fvEXT = (PFNGLPROGRAMUNIFORMMATRIX2FVEXTPROC)load(\"glProgramUniformMatrix2fvEXT\");\n\tglad_glProgramUniformMatrix3fvEXT = (PFNGLPROGRAMUNIFORMMATRIX3FVEXTPROC)load(\"glProgramUniformMatrix3fvEXT\");\n\tglad_glProgramUniformMatrix4fvEXT = (PFNGLPROGRAMUNIFORMMATRIX4FVEXTPROC)load(\"glProgramUniformMatrix4fvEXT\");\n\tglad_glProgramUniformMatrix2x3fvEXT = (PFNGLPROGRAMUNIFORMMATRIX2X3FVEXTPROC)load(\"glProgramUniformMatrix2x3fvEXT\");\n\tglad_glProgramUniformMatrix3x2fvEXT = (PFNGLPROGRAMUNIFORMMATRIX3X2FVEXTPROC)load(\"glProgramUniformMatrix3x2fvEXT\");\n\tglad_glProgramUniformMatrix2x4fvEXT = (PFNGLPROGRAMUNIFORMMATRIX2X4FVEXTPROC)load(\"glProgramUniformMatrix2x4fvEXT\");\n\tglad_glProgramUniformMatrix4x2fvEXT = (PFNGLPROGRAMUNIFORMMATRIX4X2FVEXTPROC)load(\"glProgramUniformMatrix4x2fvEXT\");\n\tglad_glProgramUniformMatrix3x4fvEXT = (PFNGLPROGRAMUNIFORMMATRIX3X4FVEXTPROC)load(\"glProgramUniformMatrix3x4fvEXT\");\n\tglad_glProgramUniformMatrix4x3fvEXT = (PFNGLPROGRAMUNIFORMMATRIX4X3FVEXTPROC)load(\"glProgramUniformMatrix4x3fvEXT\");\n\tglad_glTextureBufferEXT = (PFNGLTEXTUREBUFFEREXTPROC)load(\"glTextureBufferEXT\");\n\tglad_glMultiTexBufferEXT = (PFNGLMULTITEXBUFFEREXTPROC)load(\"glMultiTexBufferEXT\");\n\tglad_glTextureParameterIivEXT = (PFNGLTEXTUREPARAMETERIIVEXTPROC)load(\"glTextureParameterIivEXT\");\n\tglad_glTextureParameterIuivEXT = (PFNGLTEXTUREPARAMETERIUIVEXTPROC)load(\"glTextureParameterIuivEXT\");\n\tglad_glGetTextureParameterIivEXT = (PFNGLGETTEXTUREPARAMETERIIVEXTPROC)load(\"glGetTextureParameterIivEXT\");\n\tglad_glGetTextureParameterIuivEXT = (PFNGLGETTEXTUREPARAMETERIUIVEXTPROC)load(\"glGetTextureParameterIuivEXT\");\n\tglad_glMultiTexParameterIivEXT = (PFNGLMULTITEXPARAMETERIIVEXTPROC)load(\"glMultiTexParameterIivEXT\");\n\tglad_glMultiTexParameterIuivEXT = (PFNGLMULTITEXPARAMETERIUIVEXTPROC)load(\"glMultiTexParameterIuivEXT\");\n\tglad_glGetMultiTexParameterIivEXT = (PFNGLGETMULTITEXPARAMETERIIVEXTPROC)load(\"glGetMultiTexParameterIivEXT\");\n\tglad_glGetMultiTexParameterIuivEXT = (PFNGLGETMULTITEXPARAMETERIUIVEXTPROC)load(\"glGetMultiTexParameterIuivEXT\");\n\tglad_glProgramUniform1uiEXT = (PFNGLPROGRAMUNIFORM1UIEXTPROC)load(\"glProgramUniform1uiEXT\");\n\tglad_glProgramUniform2uiEXT = (PFNGLPROGRAMUNIFORM2UIEXTPROC)load(\"glProgramUniform2uiEXT\");\n\tglad_glProgramUniform3uiEXT = (PFNGLPROGRAMUNIFORM3UIEXTPROC)load(\"glProgramUniform3uiEXT\");\n\tglad_glProgramUniform4uiEXT = (PFNGLPROGRAMUNIFORM4UIEXTPROC)load(\"glProgramUniform4uiEXT\");\n\tglad_glProgramUniform1uivEXT = (PFNGLPROGRAMUNIFORM1UIVEXTPROC)load(\"glProgramUniform1uivEXT\");\n\tglad_glProgramUniform2uivEXT = (PFNGLPROGRAMUNIFORM2UIVEXTPROC)load(\"glProgramUniform2uivEXT\");\n\tglad_glProgramUniform3uivEXT = (PFNGLPROGRAMUNIFORM3UIVEXTPROC)load(\"glProgramUniform3uivEXT\");\n\tglad_glProgramUniform4uivEXT = (PFNGLPROGRAMUNIFORM4UIVEXTPROC)load(\"glProgramUniform4uivEXT\");\n\tglad_glNamedProgramLocalParameters4fvEXT = (PFNGLNAMEDPROGRAMLOCALPARAMETERS4FVEXTPROC)load(\"glNamedProgramLocalParameters4fvEXT\");\n\tglad_glNamedProgramLocalParameterI4iEXT = (PFNGLNAMEDPROGRAMLOCALPARAMETERI4IEXTPROC)load(\"glNamedProgramLocalParameterI4iEXT\");\n\tglad_glNamedProgramLocalParameterI4ivEXT = (PFNGLNAMEDPROGRAMLOCALPARAMETERI4IVEXTPROC)load(\"glNamedProgramLocalParameterI4ivEXT\");\n\tglad_glNamedProgramLocalParametersI4ivEXT = (PFNGLNAMEDPROGRAMLOCALPARAMETERSI4IVEXTPROC)load(\"glNamedProgramLocalParametersI4ivEXT\");\n\tglad_glNamedProgramLocalParameterI4uiEXT = (PFNGLNAMEDPROGRAMLOCALPARAMETERI4UIEXTPROC)load(\"glNamedProgramLocalParameterI4uiEXT\");\n\tglad_glNamedProgramLocalParameterI4uivEXT = (PFNGLNAMEDPROGRAMLOCALPARAMETERI4UIVEXTPROC)load(\"glNamedProgramLocalParameterI4uivEXT\");\n\tglad_glNamedProgramLocalParametersI4uivEXT = (PFNGLNAMEDPROGRAMLOCALPARAMETERSI4UIVEXTPROC)load(\"glNamedProgramLocalParametersI4uivEXT\");\n\tglad_glGetNamedProgramLocalParameterIivEXT = (PFNGLGETNAMEDPROGRAMLOCALPARAMETERIIVEXTPROC)load(\"glGetNamedProgramLocalParameterIivEXT\");\n\tglad_glGetNamedProgramLocalParameterIuivEXT = (PFNGLGETNAMEDPROGRAMLOCALPARAMETERIUIVEXTPROC)load(\"glGetNamedProgramLocalParameterIuivEXT\");\n\tglad_glEnableClientStateiEXT = (PFNGLENABLECLIENTSTATEIEXTPROC)load(\"glEnableClientStateiEXT\");\n\tglad_glDisableClientStateiEXT = (PFNGLDISABLECLIENTSTATEIEXTPROC)load(\"glDisableClientStateiEXT\");\n\tglad_glGetFloati_vEXT = (PFNGLGETFLOATI_VEXTPROC)load(\"glGetFloati_vEXT\");\n\tglad_glGetDoublei_vEXT = (PFNGLGETDOUBLEI_VEXTPROC)load(\"glGetDoublei_vEXT\");\n\tglad_glGetPointeri_vEXT = (PFNGLGETPOINTERI_VEXTPROC)load(\"glGetPointeri_vEXT\");\n\tglad_glNamedProgramStringEXT = (PFNGLNAMEDPROGRAMSTRINGEXTPROC)load(\"glNamedProgramStringEXT\");\n\tglad_glNamedProgramLocalParameter4dEXT = (PFNGLNAMEDPROGRAMLOCALPARAMETER4DEXTPROC)load(\"glNamedProgramLocalParameter4dEXT\");\n\tglad_glNamedProgramLocalParameter4dvEXT = (PFNGLNAMEDPROGRAMLOCALPARAMETER4DVEXTPROC)load(\"glNamedProgramLocalParameter4dvEXT\");\n\tglad_glNamedProgramLocalParameter4fEXT = (PFNGLNAMEDPROGRAMLOCALPARAMETER4FEXTPROC)load(\"glNamedProgramLocalParameter4fEXT\");\n\tglad_glNamedProgramLocalParameter4fvEXT = (PFNGLNAMEDPROGRAMLOCALPARAMETER4FVEXTPROC)load(\"glNamedProgramLocalParameter4fvEXT\");\n\tglad_glGetNamedProgramLocalParameterdvEXT = (PFNGLGETNAMEDPROGRAMLOCALPARAMETERDVEXTPROC)load(\"glGetNamedProgramLocalParameterdvEXT\");\n\tglad_glGetNamedProgramLocalParameterfvEXT = (PFNGLGETNAMEDPROGRAMLOCALPARAMETERFVEXTPROC)load(\"glGetNamedProgramLocalParameterfvEXT\");\n\tglad_glGetNamedProgramivEXT = (PFNGLGETNAMEDPROGRAMIVEXTPROC)load(\"glGetNamedProgramivEXT\");\n\tglad_glGetNamedProgramStringEXT = (PFNGLGETNAMEDPROGRAMSTRINGEXTPROC)load(\"glGetNamedProgramStringEXT\");\n\tglad_glNamedRenderbufferStorageEXT = (PFNGLNAMEDRENDERBUFFERSTORAGEEXTPROC)load(\"glNamedRenderbufferStorageEXT\");\n\tglad_glGetNamedRenderbufferParameterivEXT = (PFNGLGETNAMEDRENDERBUFFERPARAMETERIVEXTPROC)load(\"glGetNamedRenderbufferParameterivEXT\");\n\tglad_glNamedRenderbufferStorageMultisampleEXT = (PFNGLNAMEDRENDERBUFFERSTORAGEMULTISAMPLEEXTPROC)load(\"glNamedRenderbufferStorageMultisampleEXT\");\n\tglad_glNamedRenderbufferStorageMultisampleCoverageEXT = (PFNGLNAMEDRENDERBUFFERSTORAGEMULTISAMPLECOVERAGEEXTPROC)load(\"glNamedRenderbufferStorageMultisampleCoverageEXT\");\n\tglad_glCheckNamedFramebufferStatusEXT = (PFNGLCHECKNAMEDFRAMEBUFFERSTATUSEXTPROC)load(\"glCheckNamedFramebufferStatusEXT\");\n\tglad_glNamedFramebufferTexture1DEXT = (PFNGLNAMEDFRAMEBUFFERTEXTURE1DEXTPROC)load(\"glNamedFramebufferTexture1DEXT\");\n\tglad_glNamedFramebufferTexture2DEXT = (PFNGLNAMEDFRAMEBUFFERTEXTURE2DEXTPROC)load(\"glNamedFramebufferTexture2DEXT\");\n\tglad_glNamedFramebufferTexture3DEXT = (PFNGLNAMEDFRAMEBUFFERTEXTURE3DEXTPROC)load(\"glNamedFramebufferTexture3DEXT\");\n\tglad_glNamedFramebufferRenderbufferEXT = (PFNGLNAMEDFRAMEBUFFERRENDERBUFFEREXTPROC)load(\"glNamedFramebufferRenderbufferEXT\");\n\tglad_glGetNamedFramebufferAttachmentParameterivEXT = (PFNGLGETNAMEDFRAMEBUFFERATTACHMENTPARAMETERIVEXTPROC)load(\"glGetNamedFramebufferAttachmentParameterivEXT\");\n\tglad_glGenerateTextureMipmapEXT = (PFNGLGENERATETEXTUREMIPMAPEXTPROC)load(\"glGenerateTextureMipmapEXT\");\n\tglad_glGenerateMultiTexMipmapEXT = (PFNGLGENERATEMULTITEXMIPMAPEXTPROC)load(\"glGenerateMultiTexMipmapEXT\");\n\tglad_glFramebufferDrawBufferEXT = (PFNGLFRAMEBUFFERDRAWBUFFEREXTPROC)load(\"glFramebufferDrawBufferEXT\");\n\tglad_glFramebufferDrawBuffersEXT = (PFNGLFRAMEBUFFERDRAWBUFFERSEXTPROC)load(\"glFramebufferDrawBuffersEXT\");\n\tglad_glFramebufferReadBufferEXT = (PFNGLFRAMEBUFFERREADBUFFEREXTPROC)load(\"glFramebufferReadBufferEXT\");\n\tglad_glGetFramebufferParameterivEXT = (PFNGLGETFRAMEBUFFERPARAMETERIVEXTPROC)load(\"glGetFramebufferParameterivEXT\");\n\tglad_glNamedCopyBufferSubDataEXT = (PFNGLNAMEDCOPYBUFFERSUBDATAEXTPROC)load(\"glNamedCopyBufferSubDataEXT\");\n\tglad_glNamedFramebufferTextureEXT = (PFNGLNAMEDFRAMEBUFFERTEXTUREEXTPROC)load(\"glNamedFramebufferTextureEXT\");\n\tglad_glNamedFramebufferTextureLayerEXT = (PFNGLNAMEDFRAMEBUFFERTEXTURELAYEREXTPROC)load(\"glNamedFramebufferTextureLayerEXT\");\n\tglad_glNamedFramebufferTextureFaceEXT = (PFNGLNAMEDFRAMEBUFFERTEXTUREFACEEXTPROC)load(\"glNamedFramebufferTextureFaceEXT\");\n\tglad_glTextureRenderbufferEXT = (PFNGLTEXTURERENDERBUFFEREXTPROC)load(\"glTextureRenderbufferEXT\");\n\tglad_glMultiTexRenderbufferEXT = (PFNGLMULTITEXRENDERBUFFEREXTPROC)load(\"glMultiTexRenderbufferEXT\");\n\tglad_glVertexArrayVertexOffsetEXT = (PFNGLVERTEXARRAYVERTEXOFFSETEXTPROC)load(\"glVertexArrayVertexOffsetEXT\");\n\tglad_glVertexArrayColorOffsetEXT = (PFNGLVERTEXARRAYCOLOROFFSETEXTPROC)load(\"glVertexArrayColorOffsetEXT\");\n\tglad_glVertexArrayEdgeFlagOffsetEXT = (PFNGLVERTEXARRAYEDGEFLAGOFFSETEXTPROC)load(\"glVertexArrayEdgeFlagOffsetEXT\");\n\tglad_glVertexArrayIndexOffsetEXT = (PFNGLVERTEXARRAYINDEXOFFSETEXTPROC)load(\"glVertexArrayIndexOffsetEXT\");\n\tglad_glVertexArrayNormalOffsetEXT = (PFNGLVERTEXARRAYNORMALOFFSETEXTPROC)load(\"glVertexArrayNormalOffsetEXT\");\n\tglad_glVertexArrayTexCoordOffsetEXT = (PFNGLVERTEXARRAYTEXCOORDOFFSETEXTPROC)load(\"glVertexArrayTexCoordOffsetEXT\");\n\tglad_glVertexArrayMultiTexCoordOffsetEXT = (PFNGLVERTEXARRAYMULTITEXCOORDOFFSETEXTPROC)load(\"glVertexArrayMultiTexCoordOffsetEXT\");\n\tglad_glVertexArrayFogCoordOffsetEXT = (PFNGLVERTEXARRAYFOGCOORDOFFSETEXTPROC)load(\"glVertexArrayFogCoordOffsetEXT\");\n\tglad_glVertexArraySecondaryColorOffsetEXT = (PFNGLVERTEXARRAYSECONDARYCOLOROFFSETEXTPROC)load(\"glVertexArraySecondaryColorOffsetEXT\");\n\tglad_glVertexArrayVertexAttribOffsetEXT = (PFNGLVERTEXARRAYVERTEXATTRIBOFFSETEXTPROC)load(\"glVertexArrayVertexAttribOffsetEXT\");\n\tglad_glVertexArrayVertexAttribIOffsetEXT = (PFNGLVERTEXARRAYVERTEXATTRIBIOFFSETEXTPROC)load(\"glVertexArrayVertexAttribIOffsetEXT\");\n\tglad_glEnableVertexArrayEXT = (PFNGLENABLEVERTEXARRAYEXTPROC)load(\"glEnableVertexArrayEXT\");\n\tglad_glDisableVertexArrayEXT = (PFNGLDISABLEVERTEXARRAYEXTPROC)load(\"glDisableVertexArrayEXT\");\n\tglad_glEnableVertexArrayAttribEXT = (PFNGLENABLEVERTEXARRAYATTRIBEXTPROC)load(\"glEnableVertexArrayAttribEXT\");\n\tglad_glDisableVertexArrayAttribEXT = (PFNGLDISABLEVERTEXARRAYATTRIBEXTPROC)load(\"glDisableVertexArrayAttribEXT\");\n\tglad_glGetVertexArrayIntegervEXT = (PFNGLGETVERTEXARRAYINTEGERVEXTPROC)load(\"glGetVertexArrayIntegervEXT\");\n\tglad_glGetVertexArrayPointervEXT = (PFNGLGETVERTEXARRAYPOINTERVEXTPROC)load(\"glGetVertexArrayPointervEXT\");\n\tglad_glGetVertexArrayIntegeri_vEXT = (PFNGLGETVERTEXARRAYINTEGERI_VEXTPROC)load(\"glGetVertexArrayIntegeri_vEXT\");\n\tglad_glGetVertexArrayPointeri_vEXT = (PFNGLGETVERTEXARRAYPOINTERI_VEXTPROC)load(\"glGetVertexArrayPointeri_vEXT\");\n\tglad_glMapNamedBufferRangeEXT = (PFNGLMAPNAMEDBUFFERRANGEEXTPROC)load(\"glMapNamedBufferRangeEXT\");\n\tglad_glFlushMappedNamedBufferRangeEXT = (PFNGLFLUSHMAPPEDNAMEDBUFFERRANGEEXTPROC)load(\"glFlushMappedNamedBufferRangeEXT\");\n\tglad_glNamedBufferStorageEXT = (PFNGLNAMEDBUFFERSTORAGEEXTPROC)load(\"glNamedBufferStorageEXT\");\n\tglad_glClearNamedBufferDataEXT = (PFNGLCLEARNAMEDBUFFERDATAEXTPROC)load(\"glClearNamedBufferDataEXT\");\n\tglad_glClearNamedBufferSubDataEXT = (PFNGLCLEARNAMEDBUFFERSUBDATAEXTPROC)load(\"glClearNamedBufferSubDataEXT\");\n\tglad_glNamedFramebufferParameteriEXT = (PFNGLNAMEDFRAMEBUFFERPARAMETERIEXTPROC)load(\"glNamedFramebufferParameteriEXT\");\n\tglad_glGetNamedFramebufferParameterivEXT = (PFNGLGETNAMEDFRAMEBUFFERPARAMETERIVEXTPROC)load(\"glGetNamedFramebufferParameterivEXT\");\n\tglad_glProgramUniform1dEXT = (PFNGLPROGRAMUNIFORM1DEXTPROC)load(\"glProgramUniform1dEXT\");\n\tglad_glProgramUniform2dEXT = (PFNGLPROGRAMUNIFORM2DEXTPROC)load(\"glProgramUniform2dEXT\");\n\tglad_glProgramUniform3dEXT = (PFNGLPROGRAMUNIFORM3DEXTPROC)load(\"glProgramUniform3dEXT\");\n\tglad_glProgramUniform4dEXT = (PFNGLPROGRAMUNIFORM4DEXTPROC)load(\"glProgramUniform4dEXT\");\n\tglad_glProgramUniform1dvEXT = (PFNGLPROGRAMUNIFORM1DVEXTPROC)load(\"glProgramUniform1dvEXT\");\n\tglad_glProgramUniform2dvEXT = (PFNGLPROGRAMUNIFORM2DVEXTPROC)load(\"glProgramUniform2dvEXT\");\n\tglad_glProgramUniform3dvEXT = (PFNGLPROGRAMUNIFORM3DVEXTPROC)load(\"glProgramUniform3dvEXT\");\n\tglad_glProgramUniform4dvEXT = (PFNGLPROGRAMUNIFORM4DVEXTPROC)load(\"glProgramUniform4dvEXT\");\n\tglad_glProgramUniformMatrix2dvEXT = (PFNGLPROGRAMUNIFORMMATRIX2DVEXTPROC)load(\"glProgramUniformMatrix2dvEXT\");\n\tglad_glProgramUniformMatrix3dvEXT = (PFNGLPROGRAMUNIFORMMATRIX3DVEXTPROC)load(\"glProgramUniformMatrix3dvEXT\");\n\tglad_glProgramUniformMatrix4dvEXT = (PFNGLPROGRAMUNIFORMMATRIX4DVEXTPROC)load(\"glProgramUniformMatrix4dvEXT\");\n\tglad_glProgramUniformMatrix2x3dvEXT = (PFNGLPROGRAMUNIFORMMATRIX2X3DVEXTPROC)load(\"glProgramUniformMatrix2x3dvEXT\");\n\tglad_glProgramUniformMatrix2x4dvEXT = (PFNGLPROGRAMUNIFORMMATRIX2X4DVEXTPROC)load(\"glProgramUniformMatrix2x4dvEXT\");\n\tglad_glProgramUniformMatrix3x2dvEXT = (PFNGLPROGRAMUNIFORMMATRIX3X2DVEXTPROC)load(\"glProgramUniformMatrix3x2dvEXT\");\n\tglad_glProgramUniformMatrix3x4dvEXT = (PFNGLPROGRAMUNIFORMMATRIX3X4DVEXTPROC)load(\"glProgramUniformMatrix3x4dvEXT\");\n\tglad_glProgramUniformMatrix4x2dvEXT = (PFNGLPROGRAMUNIFORMMATRIX4X2DVEXTPROC)load(\"glProgramUniformMatrix4x2dvEXT\");\n\tglad_glProgramUniformMatrix4x3dvEXT = (PFNGLPROGRAMUNIFORMMATRIX4X3DVEXTPROC)load(\"glProgramUniformMatrix4x3dvEXT\");\n\tglad_glTextureBufferRangeEXT = (PFNGLTEXTUREBUFFERRANGEEXTPROC)load(\"glTextureBufferRangeEXT\");\n\tglad_glTextureStorage1DEXT = (PFNGLTEXTURESTORAGE1DEXTPROC)load(\"glTextureStorage1DEXT\");\n\tglad_glTextureStorage2DEXT = (PFNGLTEXTURESTORAGE2DEXTPROC)load(\"glTextureStorage2DEXT\");\n\tglad_glTextureStorage3DEXT = (PFNGLTEXTURESTORAGE3DEXTPROC)load(\"glTextureStorage3DEXT\");\n\tglad_glTextureStorage2DMultisampleEXT = (PFNGLTEXTURESTORAGE2DMULTISAMPLEEXTPROC)load(\"glTextureStorage2DMultisampleEXT\");\n\tglad_glTextureStorage3DMultisampleEXT = (PFNGLTEXTURESTORAGE3DMULTISAMPLEEXTPROC)load(\"glTextureStorage3DMultisampleEXT\");\n\tglad_glVertexArrayBindVertexBufferEXT = (PFNGLVERTEXARRAYBINDVERTEXBUFFEREXTPROC)load(\"glVertexArrayBindVertexBufferEXT\");\n\tglad_glVertexArrayVertexAttribFormatEXT = (PFNGLVERTEXARRAYVERTEXATTRIBFORMATEXTPROC)load(\"glVertexArrayVertexAttribFormatEXT\");\n\tglad_glVertexArrayVertexAttribIFormatEXT = (PFNGLVERTEXARRAYVERTEXATTRIBIFORMATEXTPROC)load(\"glVertexArrayVertexAttribIFormatEXT\");\n\tglad_glVertexArrayVertexAttribLFormatEXT = (PFNGLVERTEXARRAYVERTEXATTRIBLFORMATEXTPROC)load(\"glVertexArrayVertexAttribLFormatEXT\");\n\tglad_glVertexArrayVertexAttribBindingEXT = (PFNGLVERTEXARRAYVERTEXATTRIBBINDINGEXTPROC)load(\"glVertexArrayVertexAttribBindingEXT\");\n\tglad_glVertexArrayVertexBindingDivisorEXT = (PFNGLVERTEXARRAYVERTEXBINDINGDIVISOREXTPROC)load(\"glVertexArrayVertexBindingDivisorEXT\");\n\tglad_glVertexArrayVertexAttribLOffsetEXT = (PFNGLVERTEXARRAYVERTEXATTRIBLOFFSETEXTPROC)load(\"glVertexArrayVertexAttribLOffsetEXT\");\n\tglad_glTexturePageCommitmentEXT = (PFNGLTEXTUREPAGECOMMITMENTEXTPROC)load(\"glTexturePageCommitmentEXT\");\n\tglad_glVertexArrayVertexAttribDivisorEXT = (PFNGLVERTEXARRAYVERTEXATTRIBDIVISOREXTPROC)load(\"glVertexArrayVertexAttribDivisorEXT\");\n}\nstatic void load_GL_EXT_draw_buffers2(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_draw_buffers2) return;\n\tglad_glColorMaskIndexedEXT = (PFNGLCOLORMASKINDEXEDEXTPROC)load(\"glColorMaskIndexedEXT\");\n\tglad_glGetBooleanIndexedvEXT = (PFNGLGETBOOLEANINDEXEDVEXTPROC)load(\"glGetBooleanIndexedvEXT\");\n\tglad_glGetIntegerIndexedvEXT = (PFNGLGETINTEGERINDEXEDVEXTPROC)load(\"glGetIntegerIndexedvEXT\");\n\tglad_glEnableIndexedEXT = (PFNGLENABLEINDEXEDEXTPROC)load(\"glEnableIndexedEXT\");\n\tglad_glDisableIndexedEXT = (PFNGLDISABLEINDEXEDEXTPROC)load(\"glDisableIndexedEXT\");\n\tglad_glIsEnabledIndexedEXT = (PFNGLISENABLEDINDEXEDEXTPROC)load(\"glIsEnabledIndexedEXT\");\n}\nstatic void load_GL_EXT_draw_instanced(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_draw_instanced) return;\n\tglad_glDrawArraysInstancedEXT = (PFNGLDRAWARRAYSINSTANCEDEXTPROC)load(\"glDrawArraysInstancedEXT\");\n\tglad_glDrawElementsInstancedEXT = (PFNGLDRAWELEMENTSINSTANCEDEXTPROC)load(\"glDrawElementsInstancedEXT\");\n}\nstatic void load_GL_EXT_draw_range_elements(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_draw_range_elements) return;\n\tglad_glDrawRangeElementsEXT = (PFNGLDRAWRANGEELEMENTSEXTPROC)load(\"glDrawRangeElementsEXT\");\n}\nstatic void load_GL_EXT_external_buffer(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_external_buffer) return;\n\tglad_glBufferStorageExternalEXT = (PFNGLBUFFERSTORAGEEXTERNALEXTPROC)load(\"glBufferStorageExternalEXT\");\n\tglad_glNamedBufferStorageExternalEXT = (PFNGLNAMEDBUFFERSTORAGEEXTERNALEXTPROC)load(\"glNamedBufferStorageExternalEXT\");\n}\nstatic void load_GL_EXT_fog_coord(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_fog_coord) return;\n\tglad_glFogCoordfEXT = (PFNGLFOGCOORDFEXTPROC)load(\"glFogCoordfEXT\");\n\tglad_glFogCoordfvEXT = (PFNGLFOGCOORDFVEXTPROC)load(\"glFogCoordfvEXT\");\n\tglad_glFogCoorddEXT = (PFNGLFOGCOORDDEXTPROC)load(\"glFogCoorddEXT\");\n\tglad_glFogCoorddvEXT = (PFNGLFOGCOORDDVEXTPROC)load(\"glFogCoorddvEXT\");\n\tglad_glFogCoordPointerEXT = (PFNGLFOGCOORDPOINTEREXTPROC)load(\"glFogCoordPointerEXT\");\n}\nstatic void load_GL_EXT_framebuffer_blit(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_framebuffer_blit) return;\n\tglad_glBlitFramebufferEXT = (PFNGLBLITFRAMEBUFFEREXTPROC)load(\"glBlitFramebufferEXT\");\n}\nstatic void load_GL_EXT_framebuffer_multisample(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_framebuffer_multisample) return;\n\tglad_glRenderbufferStorageMultisampleEXT = (PFNGLRENDERBUFFERSTORAGEMULTISAMPLEEXTPROC)load(\"glRenderbufferStorageMultisampleEXT\");\n}\nstatic void load_GL_EXT_framebuffer_object(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_framebuffer_object) return;\n\tglad_glIsRenderbufferEXT = (PFNGLISRENDERBUFFEREXTPROC)load(\"glIsRenderbufferEXT\");\n\tglad_glBindRenderbufferEXT = (PFNGLBINDRENDERBUFFEREXTPROC)load(\"glBindRenderbufferEXT\");\n\tglad_glDeleteRenderbuffersEXT = (PFNGLDELETERENDERBUFFERSEXTPROC)load(\"glDeleteRenderbuffersEXT\");\n\tglad_glGenRenderbuffersEXT = (PFNGLGENRENDERBUFFERSEXTPROC)load(\"glGenRenderbuffersEXT\");\n\tglad_glRenderbufferStorageEXT = (PFNGLRENDERBUFFERSTORAGEEXTPROC)load(\"glRenderbufferStorageEXT\");\n\tglad_glGetRenderbufferParameterivEXT = (PFNGLGETRENDERBUFFERPARAMETERIVEXTPROC)load(\"glGetRenderbufferParameterivEXT\");\n\tglad_glIsFramebufferEXT = (PFNGLISFRAMEBUFFEREXTPROC)load(\"glIsFramebufferEXT\");\n\tglad_glBindFramebufferEXT = (PFNGLBINDFRAMEBUFFEREXTPROC)load(\"glBindFramebufferEXT\");\n\tglad_glDeleteFramebuffersEXT = (PFNGLDELETEFRAMEBUFFERSEXTPROC)load(\"glDeleteFramebuffersEXT\");\n\tglad_glGenFramebuffersEXT = (PFNGLGENFRAMEBUFFERSEXTPROC)load(\"glGenFramebuffersEXT\");\n\tglad_glCheckFramebufferStatusEXT = (PFNGLCHECKFRAMEBUFFERSTATUSEXTPROC)load(\"glCheckFramebufferStatusEXT\");\n\tglad_glFramebufferTexture1DEXT = (PFNGLFRAMEBUFFERTEXTURE1DEXTPROC)load(\"glFramebufferTexture1DEXT\");\n\tglad_glFramebufferTexture2DEXT = (PFNGLFRAMEBUFFERTEXTURE2DEXTPROC)load(\"glFramebufferTexture2DEXT\");\n\tglad_glFramebufferTexture3DEXT = (PFNGLFRAMEBUFFERTEXTURE3DEXTPROC)load(\"glFramebufferTexture3DEXT\");\n\tglad_glFramebufferRenderbufferEXT = (PFNGLFRAMEBUFFERRENDERBUFFEREXTPROC)load(\"glFramebufferRenderbufferEXT\");\n\tglad_glGetFramebufferAttachmentParameterivEXT = (PFNGLGETFRAMEBUFFERATTACHMENTPARAMETERIVEXTPROC)load(\"glGetFramebufferAttachmentParameterivEXT\");\n\tglad_glGenerateMipmapEXT = (PFNGLGENERATEMIPMAPEXTPROC)load(\"glGenerateMipmapEXT\");\n}\nstatic void load_GL_EXT_geometry_shader4(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_geometry_shader4) return;\n\tglad_glProgramParameteriEXT = (PFNGLPROGRAMPARAMETERIEXTPROC)load(\"glProgramParameteriEXT\");\n}\nstatic void load_GL_EXT_gpu_program_parameters(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_gpu_program_parameters) return;\n\tglad_glProgramEnvParameters4fvEXT = (PFNGLPROGRAMENVPARAMETERS4FVEXTPROC)load(\"glProgramEnvParameters4fvEXT\");\n\tglad_glProgramLocalParameters4fvEXT = (PFNGLPROGRAMLOCALPARAMETERS4FVEXTPROC)load(\"glProgramLocalParameters4fvEXT\");\n}\nstatic void load_GL_EXT_gpu_shader4(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_gpu_shader4) return;\n\tglad_glGetUniformuivEXT = (PFNGLGETUNIFORMUIVEXTPROC)load(\"glGetUniformuivEXT\");\n\tglad_glBindFragDataLocationEXT = (PFNGLBINDFRAGDATALOCATIONEXTPROC)load(\"glBindFragDataLocationEXT\");\n\tglad_glGetFragDataLocationEXT = (PFNGLGETFRAGDATALOCATIONEXTPROC)load(\"glGetFragDataLocationEXT\");\n\tglad_glUniform1uiEXT = (PFNGLUNIFORM1UIEXTPROC)load(\"glUniform1uiEXT\");\n\tglad_glUniform2uiEXT = (PFNGLUNIFORM2UIEXTPROC)load(\"glUniform2uiEXT\");\n\tglad_glUniform3uiEXT = (PFNGLUNIFORM3UIEXTPROC)load(\"glUniform3uiEXT\");\n\tglad_glUniform4uiEXT = (PFNGLUNIFORM4UIEXTPROC)load(\"glUniform4uiEXT\");\n\tglad_glUniform1uivEXT = (PFNGLUNIFORM1UIVEXTPROC)load(\"glUniform1uivEXT\");\n\tglad_glUniform2uivEXT = (PFNGLUNIFORM2UIVEXTPROC)load(\"glUniform2uivEXT\");\n\tglad_glUniform3uivEXT = (PFNGLUNIFORM3UIVEXTPROC)load(\"glUniform3uivEXT\");\n\tglad_glUniform4uivEXT = (PFNGLUNIFORM4UIVEXTPROC)load(\"glUniform4uivEXT\");\n}\nstatic void load_GL_EXT_histogram(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_histogram) return;\n\tglad_glGetHistogramEXT = (PFNGLGETHISTOGRAMEXTPROC)load(\"glGetHistogramEXT\");\n\tglad_glGetHistogramParameterfvEXT = (PFNGLGETHISTOGRAMPARAMETERFVEXTPROC)load(\"glGetHistogramParameterfvEXT\");\n\tglad_glGetHistogramParameterivEXT = (PFNGLGETHISTOGRAMPARAMETERIVEXTPROC)load(\"glGetHistogramParameterivEXT\");\n\tglad_glGetMinmaxEXT = (PFNGLGETMINMAXEXTPROC)load(\"glGetMinmaxEXT\");\n\tglad_glGetMinmaxParameterfvEXT = (PFNGLGETMINMAXPARAMETERFVEXTPROC)load(\"glGetMinmaxParameterfvEXT\");\n\tglad_glGetMinmaxParameterivEXT = (PFNGLGETMINMAXPARAMETERIVEXTPROC)load(\"glGetMinmaxParameterivEXT\");\n\tglad_glHistogramEXT = (PFNGLHISTOGRAMEXTPROC)load(\"glHistogramEXT\");\n\tglad_glMinmaxEXT = (PFNGLMINMAXEXTPROC)load(\"glMinmaxEXT\");\n\tglad_glResetHistogramEXT = (PFNGLRESETHISTOGRAMEXTPROC)load(\"glResetHistogramEXT\");\n\tglad_glResetMinmaxEXT = (PFNGLRESETMINMAXEXTPROC)load(\"glResetMinmaxEXT\");\n}\nstatic void load_GL_EXT_index_func(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_index_func) return;\n\tglad_glIndexFuncEXT = (PFNGLINDEXFUNCEXTPROC)load(\"glIndexFuncEXT\");\n}\nstatic void load_GL_EXT_index_material(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_index_material) return;\n\tglad_glIndexMaterialEXT = (PFNGLINDEXMATERIALEXTPROC)load(\"glIndexMaterialEXT\");\n}\nstatic void load_GL_EXT_light_texture(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_light_texture) return;\n\tglad_glApplyTextureEXT = (PFNGLAPPLYTEXTUREEXTPROC)load(\"glApplyTextureEXT\");\n\tglad_glTextureLightEXT = (PFNGLTEXTURELIGHTEXTPROC)load(\"glTextureLightEXT\");\n\tglad_glTextureMaterialEXT = (PFNGLTEXTUREMATERIALEXTPROC)load(\"glTextureMaterialEXT\");\n}\nstatic void load_GL_EXT_memory_object(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_memory_object) return;\n\tglad_glGetUnsignedBytevEXT = (PFNGLGETUNSIGNEDBYTEVEXTPROC)load(\"glGetUnsignedBytevEXT\");\n\tglad_glGetUnsignedBytei_vEXT = (PFNGLGETUNSIGNEDBYTEI_VEXTPROC)load(\"glGetUnsignedBytei_vEXT\");\n\tglad_glDeleteMemoryObjectsEXT = (PFNGLDELETEMEMORYOBJECTSEXTPROC)load(\"glDeleteMemoryObjectsEXT\");\n\tglad_glIsMemoryObjectEXT = (PFNGLISMEMORYOBJECTEXTPROC)load(\"glIsMemoryObjectEXT\");\n\tglad_glCreateMemoryObjectsEXT = (PFNGLCREATEMEMORYOBJECTSEXTPROC)load(\"glCreateMemoryObjectsEXT\");\n\tglad_glMemoryObjectParameterivEXT = (PFNGLMEMORYOBJECTPARAMETERIVEXTPROC)load(\"glMemoryObjectParameterivEXT\");\n\tglad_glGetMemoryObjectParameterivEXT = (PFNGLGETMEMORYOBJECTPARAMETERIVEXTPROC)load(\"glGetMemoryObjectParameterivEXT\");\n\tglad_glTexStorageMem2DEXT = (PFNGLTEXSTORAGEMEM2DEXTPROC)load(\"glTexStorageMem2DEXT\");\n\tglad_glTexStorageMem2DMultisampleEXT = (PFNGLTEXSTORAGEMEM2DMULTISAMPLEEXTPROC)load(\"glTexStorageMem2DMultisampleEXT\");\n\tglad_glTexStorageMem3DEXT = (PFNGLTEXSTORAGEMEM3DEXTPROC)load(\"glTexStorageMem3DEXT\");\n\tglad_glTexStorageMem3DMultisampleEXT = (PFNGLTEXSTORAGEMEM3DMULTISAMPLEEXTPROC)load(\"glTexStorageMem3DMultisampleEXT\");\n\tglad_glBufferStorageMemEXT = (PFNGLBUFFERSTORAGEMEMEXTPROC)load(\"glBufferStorageMemEXT\");\n\tglad_glTextureStorageMem2DEXT = (PFNGLTEXTURESTORAGEMEM2DEXTPROC)load(\"glTextureStorageMem2DEXT\");\n\tglad_glTextureStorageMem2DMultisampleEXT = (PFNGLTEXTURESTORAGEMEM2DMULTISAMPLEEXTPROC)load(\"glTextureStorageMem2DMultisampleEXT\");\n\tglad_glTextureStorageMem3DEXT = (PFNGLTEXTURESTORAGEMEM3DEXTPROC)load(\"glTextureStorageMem3DEXT\");\n\tglad_glTextureStorageMem3DMultisampleEXT = (PFNGLTEXTURESTORAGEMEM3DMULTISAMPLEEXTPROC)load(\"glTextureStorageMem3DMultisampleEXT\");\n\tglad_glNamedBufferStorageMemEXT = (PFNGLNAMEDBUFFERSTORAGEMEMEXTPROC)load(\"glNamedBufferStorageMemEXT\");\n\tglad_glTexStorageMem1DEXT = (PFNGLTEXSTORAGEMEM1DEXTPROC)load(\"glTexStorageMem1DEXT\");\n\tglad_glTextureStorageMem1DEXT = (PFNGLTEXTURESTORAGEMEM1DEXTPROC)load(\"glTextureStorageMem1DEXT\");\n}\nstatic void load_GL_EXT_memory_object_fd(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_memory_object_fd) return;\n\tglad_glImportMemoryFdEXT = (PFNGLIMPORTMEMORYFDEXTPROC)load(\"glImportMemoryFdEXT\");\n}\nstatic void load_GL_EXT_memory_object_win32(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_memory_object_win32) return;\n\tglad_glImportMemoryWin32HandleEXT = (PFNGLIMPORTMEMORYWIN32HANDLEEXTPROC)load(\"glImportMemoryWin32HandleEXT\");\n\tglad_glImportMemoryWin32NameEXT = (PFNGLIMPORTMEMORYWIN32NAMEEXTPROC)load(\"glImportMemoryWin32NameEXT\");\n}\nstatic void load_GL_EXT_multi_draw_arrays(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_multi_draw_arrays) return;\n\tglad_glMultiDrawArraysEXT = (PFNGLMULTIDRAWARRAYSEXTPROC)load(\"glMultiDrawArraysEXT\");\n\tglad_glMultiDrawElementsEXT = (PFNGLMULTIDRAWELEMENTSEXTPROC)load(\"glMultiDrawElementsEXT\");\n}\nstatic void load_GL_EXT_multisample(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_multisample) return;\n\tglad_glSampleMaskEXT = (PFNGLSAMPLEMASKEXTPROC)load(\"glSampleMaskEXT\");\n\tglad_glSamplePatternEXT = (PFNGLSAMPLEPATTERNEXTPROC)load(\"glSamplePatternEXT\");\n}\nstatic void load_GL_EXT_paletted_texture(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_paletted_texture) return;\n\tglad_glColorTableEXT = (PFNGLCOLORTABLEEXTPROC)load(\"glColorTableEXT\");\n\tglad_glGetColorTableEXT = (PFNGLGETCOLORTABLEEXTPROC)load(\"glGetColorTableEXT\");\n\tglad_glGetColorTableParameterivEXT = (PFNGLGETCOLORTABLEPARAMETERIVEXTPROC)load(\"glGetColorTableParameterivEXT\");\n\tglad_glGetColorTableParameterfvEXT = (PFNGLGETCOLORTABLEPARAMETERFVEXTPROC)load(\"glGetColorTableParameterfvEXT\");\n}\nstatic void load_GL_EXT_pixel_transform(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_pixel_transform) return;\n\tglad_glPixelTransformParameteriEXT = (PFNGLPIXELTRANSFORMPARAMETERIEXTPROC)load(\"glPixelTransformParameteriEXT\");\n\tglad_glPixelTransformParameterfEXT = (PFNGLPIXELTRANSFORMPARAMETERFEXTPROC)load(\"glPixelTransformParameterfEXT\");\n\tglad_glPixelTransformParameterivEXT = (PFNGLPIXELTRANSFORMPARAMETERIVEXTPROC)load(\"glPixelTransformParameterivEXT\");\n\tglad_glPixelTransformParameterfvEXT = (PFNGLPIXELTRANSFORMPARAMETERFVEXTPROC)load(\"glPixelTransformParameterfvEXT\");\n\tglad_glGetPixelTransformParameterivEXT = (PFNGLGETPIXELTRANSFORMPARAMETERIVEXTPROC)load(\"glGetPixelTransformParameterivEXT\");\n\tglad_glGetPixelTransformParameterfvEXT = (PFNGLGETPIXELTRANSFORMPARAMETERFVEXTPROC)load(\"glGetPixelTransformParameterfvEXT\");\n}\nstatic void load_GL_EXT_point_parameters(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_point_parameters) return;\n\tglad_glPointParameterfEXT = (PFNGLPOINTPARAMETERFEXTPROC)load(\"glPointParameterfEXT\");\n\tglad_glPointParameterfvEXT = (PFNGLPOINTPARAMETERFVEXTPROC)load(\"glPointParameterfvEXT\");\n}\nstatic void load_GL_EXT_polygon_offset(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_polygon_offset) return;\n\tglad_glPolygonOffsetEXT = (PFNGLPOLYGONOFFSETEXTPROC)load(\"glPolygonOffsetEXT\");\n}\nstatic void load_GL_EXT_polygon_offset_clamp(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_polygon_offset_clamp) return;\n\tglad_glPolygonOffsetClampEXT = (PFNGLPOLYGONOFFSETCLAMPEXTPROC)load(\"glPolygonOffsetClampEXT\");\n}\nstatic void load_GL_EXT_provoking_vertex(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_provoking_vertex) return;\n\tglad_glProvokingVertexEXT = (PFNGLPROVOKINGVERTEXEXTPROC)load(\"glProvokingVertexEXT\");\n}\nstatic void load_GL_EXT_raster_multisample(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_raster_multisample) return;\n\tglad_glRasterSamplesEXT = (PFNGLRASTERSAMPLESEXTPROC)load(\"glRasterSamplesEXT\");\n}\nstatic void load_GL_EXT_secondary_color(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_secondary_color) return;\n\tglad_glSecondaryColor3bEXT = (PFNGLSECONDARYCOLOR3BEXTPROC)load(\"glSecondaryColor3bEXT\");\n\tglad_glSecondaryColor3bvEXT = (PFNGLSECONDARYCOLOR3BVEXTPROC)load(\"glSecondaryColor3bvEXT\");\n\tglad_glSecondaryColor3dEXT = (PFNGLSECONDARYCOLOR3DEXTPROC)load(\"glSecondaryColor3dEXT\");\n\tglad_glSecondaryColor3dvEXT = (PFNGLSECONDARYCOLOR3DVEXTPROC)load(\"glSecondaryColor3dvEXT\");\n\tglad_glSecondaryColor3fEXT = (PFNGLSECONDARYCOLOR3FEXTPROC)load(\"glSecondaryColor3fEXT\");\n\tglad_glSecondaryColor3fvEXT = (PFNGLSECONDARYCOLOR3FVEXTPROC)load(\"glSecondaryColor3fvEXT\");\n\tglad_glSecondaryColor3iEXT = (PFNGLSECONDARYCOLOR3IEXTPROC)load(\"glSecondaryColor3iEXT\");\n\tglad_glSecondaryColor3ivEXT = (PFNGLSECONDARYCOLOR3IVEXTPROC)load(\"glSecondaryColor3ivEXT\");\n\tglad_glSecondaryColor3sEXT = (PFNGLSECONDARYCOLOR3SEXTPROC)load(\"glSecondaryColor3sEXT\");\n\tglad_glSecondaryColor3svEXT = (PFNGLSECONDARYCOLOR3SVEXTPROC)load(\"glSecondaryColor3svEXT\");\n\tglad_glSecondaryColor3ubEXT = (PFNGLSECONDARYCOLOR3UBEXTPROC)load(\"glSecondaryColor3ubEXT\");\n\tglad_glSecondaryColor3ubvEXT = (PFNGLSECONDARYCOLOR3UBVEXTPROC)load(\"glSecondaryColor3ubvEXT\");\n\tglad_glSecondaryColor3uiEXT = (PFNGLSECONDARYCOLOR3UIEXTPROC)load(\"glSecondaryColor3uiEXT\");\n\tglad_glSecondaryColor3uivEXT = (PFNGLSECONDARYCOLOR3UIVEXTPROC)load(\"glSecondaryColor3uivEXT\");\n\tglad_glSecondaryColor3usEXT = (PFNGLSECONDARYCOLOR3USEXTPROC)load(\"glSecondaryColor3usEXT\");\n\tglad_glSecondaryColor3usvEXT = (PFNGLSECONDARYCOLOR3USVEXTPROC)load(\"glSecondaryColor3usvEXT\");\n\tglad_glSecondaryColorPointerEXT = (PFNGLSECONDARYCOLORPOINTEREXTPROC)load(\"glSecondaryColorPointerEXT\");\n}\nstatic void load_GL_EXT_semaphore(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_semaphore) return;\n\tglad_glGetUnsignedBytevEXT = (PFNGLGETUNSIGNEDBYTEVEXTPROC)load(\"glGetUnsignedBytevEXT\");\n\tglad_glGetUnsignedBytei_vEXT = (PFNGLGETUNSIGNEDBYTEI_VEXTPROC)load(\"glGetUnsignedBytei_vEXT\");\n\tglad_glGenSemaphoresEXT = (PFNGLGENSEMAPHORESEXTPROC)load(\"glGenSemaphoresEXT\");\n\tglad_glDeleteSemaphoresEXT = (PFNGLDELETESEMAPHORESEXTPROC)load(\"glDeleteSemaphoresEXT\");\n\tglad_glIsSemaphoreEXT = (PFNGLISSEMAPHOREEXTPROC)load(\"glIsSemaphoreEXT\");\n\tglad_glSemaphoreParameterui64vEXT = (PFNGLSEMAPHOREPARAMETERUI64VEXTPROC)load(\"glSemaphoreParameterui64vEXT\");\n\tglad_glGetSemaphoreParameterui64vEXT = (PFNGLGETSEMAPHOREPARAMETERUI64VEXTPROC)load(\"glGetSemaphoreParameterui64vEXT\");\n\tglad_glWaitSemaphoreEXT = (PFNGLWAITSEMAPHOREEXTPROC)load(\"glWaitSemaphoreEXT\");\n\tglad_glSignalSemaphoreEXT = (PFNGLSIGNALSEMAPHOREEXTPROC)load(\"glSignalSemaphoreEXT\");\n}\nstatic void load_GL_EXT_semaphore_fd(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_semaphore_fd) return;\n\tglad_glImportSemaphoreFdEXT = (PFNGLIMPORTSEMAPHOREFDEXTPROC)load(\"glImportSemaphoreFdEXT\");\n}\nstatic void load_GL_EXT_semaphore_win32(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_semaphore_win32) return;\n\tglad_glImportSemaphoreWin32HandleEXT = (PFNGLIMPORTSEMAPHOREWIN32HANDLEEXTPROC)load(\"glImportSemaphoreWin32HandleEXT\");\n\tglad_glImportSemaphoreWin32NameEXT = (PFNGLIMPORTSEMAPHOREWIN32NAMEEXTPROC)load(\"glImportSemaphoreWin32NameEXT\");\n}\nstatic void load_GL_EXT_separate_shader_objects(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_separate_shader_objects) return;\n\tglad_glUseShaderProgramEXT = (PFNGLUSESHADERPROGRAMEXTPROC)load(\"glUseShaderProgramEXT\");\n\tglad_glActiveProgramEXT = (PFNGLACTIVEPROGRAMEXTPROC)load(\"glActiveProgramEXT\");\n\tglad_glCreateShaderProgramEXT = (PFNGLCREATESHADERPROGRAMEXTPROC)load(\"glCreateShaderProgramEXT\");\n\tglad_glActiveShaderProgramEXT = (PFNGLACTIVESHADERPROGRAMEXTPROC)load(\"glActiveShaderProgramEXT\");\n\tglad_glBindProgramPipelineEXT = (PFNGLBINDPROGRAMPIPELINEEXTPROC)load(\"glBindProgramPipelineEXT\");\n\tglad_glCreateShaderProgramvEXT = (PFNGLCREATESHADERPROGRAMVEXTPROC)load(\"glCreateShaderProgramvEXT\");\n\tglad_glDeleteProgramPipelinesEXT = (PFNGLDELETEPROGRAMPIPELINESEXTPROC)load(\"glDeleteProgramPipelinesEXT\");\n\tglad_glGenProgramPipelinesEXT = (PFNGLGENPROGRAMPIPELINESEXTPROC)load(\"glGenProgramPipelinesEXT\");\n\tglad_glGetProgramPipelineInfoLogEXT = (PFNGLGETPROGRAMPIPELINEINFOLOGEXTPROC)load(\"glGetProgramPipelineInfoLogEXT\");\n\tglad_glGetProgramPipelineivEXT = (PFNGLGETPROGRAMPIPELINEIVEXTPROC)load(\"glGetProgramPipelineivEXT\");\n\tglad_glIsProgramPipelineEXT = (PFNGLISPROGRAMPIPELINEEXTPROC)load(\"glIsProgramPipelineEXT\");\n\tglad_glProgramParameteriEXT = (PFNGLPROGRAMPARAMETERIEXTPROC)load(\"glProgramParameteriEXT\");\n\tglad_glProgramUniform1fEXT = (PFNGLPROGRAMUNIFORM1FEXTPROC)load(\"glProgramUniform1fEXT\");\n\tglad_glProgramUniform1fvEXT = (PFNGLPROGRAMUNIFORM1FVEXTPROC)load(\"glProgramUniform1fvEXT\");\n\tglad_glProgramUniform1iEXT = (PFNGLPROGRAMUNIFORM1IEXTPROC)load(\"glProgramUniform1iEXT\");\n\tglad_glProgramUniform1ivEXT = (PFNGLPROGRAMUNIFORM1IVEXTPROC)load(\"glProgramUniform1ivEXT\");\n\tglad_glProgramUniform2fEXT = (PFNGLPROGRAMUNIFORM2FEXTPROC)load(\"glProgramUniform2fEXT\");\n\tglad_glProgramUniform2fvEXT = (PFNGLPROGRAMUNIFORM2FVEXTPROC)load(\"glProgramUniform2fvEXT\");\n\tglad_glProgramUniform2iEXT = (PFNGLPROGRAMUNIFORM2IEXTPROC)load(\"glProgramUniform2iEXT\");\n\tglad_glProgramUniform2ivEXT = (PFNGLPROGRAMUNIFORM2IVEXTPROC)load(\"glProgramUniform2ivEXT\");\n\tglad_glProgramUniform3fEXT = (PFNGLPROGRAMUNIFORM3FEXTPROC)load(\"glProgramUniform3fEXT\");\n\tglad_glProgramUniform3fvEXT = (PFNGLPROGRAMUNIFORM3FVEXTPROC)load(\"glProgramUniform3fvEXT\");\n\tglad_glProgramUniform3iEXT = (PFNGLPROGRAMUNIFORM3IEXTPROC)load(\"glProgramUniform3iEXT\");\n\tglad_glProgramUniform3ivEXT = (PFNGLPROGRAMUNIFORM3IVEXTPROC)load(\"glProgramUniform3ivEXT\");\n\tglad_glProgramUniform4fEXT = (PFNGLPROGRAMUNIFORM4FEXTPROC)load(\"glProgramUniform4fEXT\");\n\tglad_glProgramUniform4fvEXT = (PFNGLPROGRAMUNIFORM4FVEXTPROC)load(\"glProgramUniform4fvEXT\");\n\tglad_glProgramUniform4iEXT = (PFNGLPROGRAMUNIFORM4IEXTPROC)load(\"glProgramUniform4iEXT\");\n\tglad_glProgramUniform4ivEXT = (PFNGLPROGRAMUNIFORM4IVEXTPROC)load(\"glProgramUniform4ivEXT\");\n\tglad_glProgramUniformMatrix2fvEXT = (PFNGLPROGRAMUNIFORMMATRIX2FVEXTPROC)load(\"glProgramUniformMatrix2fvEXT\");\n\tglad_glProgramUniformMatrix3fvEXT = (PFNGLPROGRAMUNIFORMMATRIX3FVEXTPROC)load(\"glProgramUniformMatrix3fvEXT\");\n\tglad_glProgramUniformMatrix4fvEXT = (PFNGLPROGRAMUNIFORMMATRIX4FVEXTPROC)load(\"glProgramUniformMatrix4fvEXT\");\n\tglad_glUseProgramStagesEXT = (PFNGLUSEPROGRAMSTAGESEXTPROC)load(\"glUseProgramStagesEXT\");\n\tglad_glValidateProgramPipelineEXT = (PFNGLVALIDATEPROGRAMPIPELINEEXTPROC)load(\"glValidateProgramPipelineEXT\");\n\tglad_glProgramUniform1uiEXT = (PFNGLPROGRAMUNIFORM1UIEXTPROC)load(\"glProgramUniform1uiEXT\");\n\tglad_glProgramUniform2uiEXT = (PFNGLPROGRAMUNIFORM2UIEXTPROC)load(\"glProgramUniform2uiEXT\");\n\tglad_glProgramUniform3uiEXT = (PFNGLPROGRAMUNIFORM3UIEXTPROC)load(\"glProgramUniform3uiEXT\");\n\tglad_glProgramUniform4uiEXT = (PFNGLPROGRAMUNIFORM4UIEXTPROC)load(\"glProgramUniform4uiEXT\");\n\tglad_glProgramUniform1uivEXT = (PFNGLPROGRAMUNIFORM1UIVEXTPROC)load(\"glProgramUniform1uivEXT\");\n\tglad_glProgramUniform2uivEXT = (PFNGLPROGRAMUNIFORM2UIVEXTPROC)load(\"glProgramUniform2uivEXT\");\n\tglad_glProgramUniform3uivEXT = (PFNGLPROGRAMUNIFORM3UIVEXTPROC)load(\"glProgramUniform3uivEXT\");\n\tglad_glProgramUniform4uivEXT = (PFNGLPROGRAMUNIFORM4UIVEXTPROC)load(\"glProgramUniform4uivEXT\");\n\tglad_glProgramUniformMatrix4fvEXT = (PFNGLPROGRAMUNIFORMMATRIX4FVEXTPROC)load(\"glProgramUniformMatrix4fvEXT\");\n\tglad_glProgramUniformMatrix2x3fvEXT = (PFNGLPROGRAMUNIFORMMATRIX2X3FVEXTPROC)load(\"glProgramUniformMatrix2x3fvEXT\");\n\tglad_glProgramUniformMatrix3x2fvEXT = (PFNGLPROGRAMUNIFORMMATRIX3X2FVEXTPROC)load(\"glProgramUniformMatrix3x2fvEXT\");\n\tglad_glProgramUniformMatrix2x4fvEXT = (PFNGLPROGRAMUNIFORMMATRIX2X4FVEXTPROC)load(\"glProgramUniformMatrix2x4fvEXT\");\n\tglad_glProgramUniformMatrix4x2fvEXT = (PFNGLPROGRAMUNIFORMMATRIX4X2FVEXTPROC)load(\"glProgramUniformMatrix4x2fvEXT\");\n\tglad_glProgramUniformMatrix3x4fvEXT = (PFNGLPROGRAMUNIFORMMATRIX3X4FVEXTPROC)load(\"glProgramUniformMatrix3x4fvEXT\");\n\tglad_glProgramUniformMatrix4x3fvEXT = (PFNGLPROGRAMUNIFORMMATRIX4X3FVEXTPROC)load(\"glProgramUniformMatrix4x3fvEXT\");\n}\nstatic void load_GL_EXT_shader_framebuffer_fetch_non_coherent(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_shader_framebuffer_fetch_non_coherent) return;\n\tglad_glFramebufferFetchBarrierEXT = (PFNGLFRAMEBUFFERFETCHBARRIEREXTPROC)load(\"glFramebufferFetchBarrierEXT\");\n}\nstatic void load_GL_EXT_shader_image_load_store(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_shader_image_load_store) return;\n\tglad_glBindImageTextureEXT = (PFNGLBINDIMAGETEXTUREEXTPROC)load(\"glBindImageTextureEXT\");\n\tglad_glMemoryBarrierEXT = (PFNGLMEMORYBARRIEREXTPROC)load(\"glMemoryBarrierEXT\");\n}\nstatic void load_GL_EXT_stencil_clear_tag(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_stencil_clear_tag) return;\n\tglad_glStencilClearTagEXT = (PFNGLSTENCILCLEARTAGEXTPROC)load(\"glStencilClearTagEXT\");\n}\nstatic void load_GL_EXT_stencil_two_side(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_stencil_two_side) return;\n\tglad_glActiveStencilFaceEXT = (PFNGLACTIVESTENCILFACEEXTPROC)load(\"glActiveStencilFaceEXT\");\n}\nstatic void load_GL_EXT_subtexture(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_subtexture) return;\n\tglad_glTexSubImage1DEXT = (PFNGLTEXSUBIMAGE1DEXTPROC)load(\"glTexSubImage1DEXT\");\n\tglad_glTexSubImage2DEXT = (PFNGLTEXSUBIMAGE2DEXTPROC)load(\"glTexSubImage2DEXT\");\n}\nstatic void load_GL_EXT_texture3D(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_texture3D) return;\n\tglad_glTexImage3DEXT = (PFNGLTEXIMAGE3DEXTPROC)load(\"glTexImage3DEXT\");\n\tglad_glTexSubImage3DEXT = (PFNGLTEXSUBIMAGE3DEXTPROC)load(\"glTexSubImage3DEXT\");\n}\nstatic void load_GL_EXT_texture_array(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_texture_array) return;\n\tglad_glFramebufferTextureLayerEXT = (PFNGLFRAMEBUFFERTEXTURELAYEREXTPROC)load(\"glFramebufferTextureLayerEXT\");\n}\nstatic void load_GL_EXT_texture_buffer_object(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_texture_buffer_object) return;\n\tglad_glTexBufferEXT = (PFNGLTEXBUFFEREXTPROC)load(\"glTexBufferEXT\");\n}\nstatic void load_GL_EXT_texture_integer(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_texture_integer) return;\n\tglad_glTexParameterIivEXT = (PFNGLTEXPARAMETERIIVEXTPROC)load(\"glTexParameterIivEXT\");\n\tglad_glTexParameterIuivEXT = (PFNGLTEXPARAMETERIUIVEXTPROC)load(\"glTexParameterIuivEXT\");\n\tglad_glGetTexParameterIivEXT = (PFNGLGETTEXPARAMETERIIVEXTPROC)load(\"glGetTexParameterIivEXT\");\n\tglad_glGetTexParameterIuivEXT = (PFNGLGETTEXPARAMETERIUIVEXTPROC)load(\"glGetTexParameterIuivEXT\");\n\tglad_glClearColorIiEXT = (PFNGLCLEARCOLORIIEXTPROC)load(\"glClearColorIiEXT\");\n\tglad_glClearColorIuiEXT = (PFNGLCLEARCOLORIUIEXTPROC)load(\"glClearColorIuiEXT\");\n}\nstatic void load_GL_EXT_texture_object(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_texture_object) return;\n\tglad_glAreTexturesResidentEXT = (PFNGLARETEXTURESRESIDENTEXTPROC)load(\"glAreTexturesResidentEXT\");\n\tglad_glBindTextureEXT = (PFNGLBINDTEXTUREEXTPROC)load(\"glBindTextureEXT\");\n\tglad_glDeleteTexturesEXT = (PFNGLDELETETEXTURESEXTPROC)load(\"glDeleteTexturesEXT\");\n\tglad_glGenTexturesEXT = (PFNGLGENTEXTURESEXTPROC)load(\"glGenTexturesEXT\");\n\tglad_glIsTextureEXT = (PFNGLISTEXTUREEXTPROC)load(\"glIsTextureEXT\");\n\tglad_glPrioritizeTexturesEXT = (PFNGLPRIORITIZETEXTURESEXTPROC)load(\"glPrioritizeTexturesEXT\");\n}\nstatic void load_GL_EXT_texture_perturb_normal(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_texture_perturb_normal) return;\n\tglad_glTextureNormalEXT = (PFNGLTEXTURENORMALEXTPROC)load(\"glTextureNormalEXT\");\n}\nstatic void load_GL_EXT_timer_query(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_timer_query) return;\n\tglad_glGetQueryObjecti64vEXT = (PFNGLGETQUERYOBJECTI64VEXTPROC)load(\"glGetQueryObjecti64vEXT\");\n\tglad_glGetQueryObjectui64vEXT = (PFNGLGETQUERYOBJECTUI64VEXTPROC)load(\"glGetQueryObjectui64vEXT\");\n}\nstatic void load_GL_EXT_transform_feedback(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_transform_feedback) return;\n\tglad_glBeginTransformFeedbackEXT = (PFNGLBEGINTRANSFORMFEEDBACKEXTPROC)load(\"glBeginTransformFeedbackEXT\");\n\tglad_glEndTransformFeedbackEXT = (PFNGLENDTRANSFORMFEEDBACKEXTPROC)load(\"glEndTransformFeedbackEXT\");\n\tglad_glBindBufferRangeEXT = (PFNGLBINDBUFFERRANGEEXTPROC)load(\"glBindBufferRangeEXT\");\n\tglad_glBindBufferOffsetEXT = (PFNGLBINDBUFFEROFFSETEXTPROC)load(\"glBindBufferOffsetEXT\");\n\tglad_glBindBufferBaseEXT = (PFNGLBINDBUFFERBASEEXTPROC)load(\"glBindBufferBaseEXT\");\n\tglad_glTransformFeedbackVaryingsEXT = (PFNGLTRANSFORMFEEDBACKVARYINGSEXTPROC)load(\"glTransformFeedbackVaryingsEXT\");\n\tglad_glGetTransformFeedbackVaryingEXT = (PFNGLGETTRANSFORMFEEDBACKVARYINGEXTPROC)load(\"glGetTransformFeedbackVaryingEXT\");\n}\nstatic void load_GL_EXT_vertex_array(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_vertex_array) return;\n\tglad_glArrayElementEXT = (PFNGLARRAYELEMENTEXTPROC)load(\"glArrayElementEXT\");\n\tglad_glColorPointerEXT = (PFNGLCOLORPOINTEREXTPROC)load(\"glColorPointerEXT\");\n\tglad_glDrawArraysEXT = (PFNGLDRAWARRAYSEXTPROC)load(\"glDrawArraysEXT\");\n\tglad_glEdgeFlagPointerEXT = (PFNGLEDGEFLAGPOINTEREXTPROC)load(\"glEdgeFlagPointerEXT\");\n\tglad_glGetPointervEXT = (PFNGLGETPOINTERVEXTPROC)load(\"glGetPointervEXT\");\n\tglad_glIndexPointerEXT = (PFNGLINDEXPOINTEREXTPROC)load(\"glIndexPointerEXT\");\n\tglad_glNormalPointerEXT = (PFNGLNORMALPOINTEREXTPROC)load(\"glNormalPointerEXT\");\n\tglad_glTexCoordPointerEXT = (PFNGLTEXCOORDPOINTEREXTPROC)load(\"glTexCoordPointerEXT\");\n\tglad_glVertexPointerEXT = (PFNGLVERTEXPOINTEREXTPROC)load(\"glVertexPointerEXT\");\n}\nstatic void load_GL_EXT_vertex_attrib_64bit(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_vertex_attrib_64bit) return;\n\tglad_glVertexAttribL1dEXT = (PFNGLVERTEXATTRIBL1DEXTPROC)load(\"glVertexAttribL1dEXT\");\n\tglad_glVertexAttribL2dEXT = (PFNGLVERTEXATTRIBL2DEXTPROC)load(\"glVertexAttribL2dEXT\");\n\tglad_glVertexAttribL3dEXT = (PFNGLVERTEXATTRIBL3DEXTPROC)load(\"glVertexAttribL3dEXT\");\n\tglad_glVertexAttribL4dEXT = (PFNGLVERTEXATTRIBL4DEXTPROC)load(\"glVertexAttribL4dEXT\");\n\tglad_glVertexAttribL1dvEXT = (PFNGLVERTEXATTRIBL1DVEXTPROC)load(\"glVertexAttribL1dvEXT\");\n\tglad_glVertexAttribL2dvEXT = (PFNGLVERTEXATTRIBL2DVEXTPROC)load(\"glVertexAttribL2dvEXT\");\n\tglad_glVertexAttribL3dvEXT = (PFNGLVERTEXATTRIBL3DVEXTPROC)load(\"glVertexAttribL3dvEXT\");\n\tglad_glVertexAttribL4dvEXT = (PFNGLVERTEXATTRIBL4DVEXTPROC)load(\"glVertexAttribL4dvEXT\");\n\tglad_glVertexAttribLPointerEXT = (PFNGLVERTEXATTRIBLPOINTEREXTPROC)load(\"glVertexAttribLPointerEXT\");\n\tglad_glGetVertexAttribLdvEXT = (PFNGLGETVERTEXATTRIBLDVEXTPROC)load(\"glGetVertexAttribLdvEXT\");\n}\nstatic void load_GL_EXT_vertex_shader(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_vertex_shader) return;\n\tglad_glBeginVertexShaderEXT = (PFNGLBEGINVERTEXSHADEREXTPROC)load(\"glBeginVertexShaderEXT\");\n\tglad_glEndVertexShaderEXT = (PFNGLENDVERTEXSHADEREXTPROC)load(\"glEndVertexShaderEXT\");\n\tglad_glBindVertexShaderEXT = (PFNGLBINDVERTEXSHADEREXTPROC)load(\"glBindVertexShaderEXT\");\n\tglad_glGenVertexShadersEXT = (PFNGLGENVERTEXSHADERSEXTPROC)load(\"glGenVertexShadersEXT\");\n\tglad_glDeleteVertexShaderEXT = (PFNGLDELETEVERTEXSHADEREXTPROC)load(\"glDeleteVertexShaderEXT\");\n\tglad_glShaderOp1EXT = (PFNGLSHADEROP1EXTPROC)load(\"glShaderOp1EXT\");\n\tglad_glShaderOp2EXT = (PFNGLSHADEROP2EXTPROC)load(\"glShaderOp2EXT\");\n\tglad_glShaderOp3EXT = (PFNGLSHADEROP3EXTPROC)load(\"glShaderOp3EXT\");\n\tglad_glSwizzleEXT = (PFNGLSWIZZLEEXTPROC)load(\"glSwizzleEXT\");\n\tglad_glWriteMaskEXT = (PFNGLWRITEMASKEXTPROC)load(\"glWriteMaskEXT\");\n\tglad_glInsertComponentEXT = (PFNGLINSERTCOMPONENTEXTPROC)load(\"glInsertComponentEXT\");\n\tglad_glExtractComponentEXT = (PFNGLEXTRACTCOMPONENTEXTPROC)load(\"glExtractComponentEXT\");\n\tglad_glGenSymbolsEXT = (PFNGLGENSYMBOLSEXTPROC)load(\"glGenSymbolsEXT\");\n\tglad_glSetInvariantEXT = (PFNGLSETINVARIANTEXTPROC)load(\"glSetInvariantEXT\");\n\tglad_glSetLocalConstantEXT = (PFNGLSETLOCALCONSTANTEXTPROC)load(\"glSetLocalConstantEXT\");\n\tglad_glVariantbvEXT = (PFNGLVARIANTBVEXTPROC)load(\"glVariantbvEXT\");\n\tglad_glVariantsvEXT = (PFNGLVARIANTSVEXTPROC)load(\"glVariantsvEXT\");\n\tglad_glVariantivEXT = (PFNGLVARIANTIVEXTPROC)load(\"glVariantivEXT\");\n\tglad_glVariantfvEXT = (PFNGLVARIANTFVEXTPROC)load(\"glVariantfvEXT\");\n\tglad_glVariantdvEXT = (PFNGLVARIANTDVEXTPROC)load(\"glVariantdvEXT\");\n\tglad_glVariantubvEXT = (PFNGLVARIANTUBVEXTPROC)load(\"glVariantubvEXT\");\n\tglad_glVariantusvEXT = (PFNGLVARIANTUSVEXTPROC)load(\"glVariantusvEXT\");\n\tglad_glVariantuivEXT = (PFNGLVARIANTUIVEXTPROC)load(\"glVariantuivEXT\");\n\tglad_glVariantPointerEXT = (PFNGLVARIANTPOINTEREXTPROC)load(\"glVariantPointerEXT\");\n\tglad_glEnableVariantClientStateEXT = (PFNGLENABLEVARIANTCLIENTSTATEEXTPROC)load(\"glEnableVariantClientStateEXT\");\n\tglad_glDisableVariantClientStateEXT = (PFNGLDISABLEVARIANTCLIENTSTATEEXTPROC)load(\"glDisableVariantClientStateEXT\");\n\tglad_glBindLightParameterEXT = (PFNGLBINDLIGHTPARAMETEREXTPROC)load(\"glBindLightParameterEXT\");\n\tglad_glBindMaterialParameterEXT = (PFNGLBINDMATERIALPARAMETEREXTPROC)load(\"glBindMaterialParameterEXT\");\n\tglad_glBindTexGenParameterEXT = (PFNGLBINDTEXGENPARAMETEREXTPROC)load(\"glBindTexGenParameterEXT\");\n\tglad_glBindTextureUnitParameterEXT = (PFNGLBINDTEXTUREUNITPARAMETEREXTPROC)load(\"glBindTextureUnitParameterEXT\");\n\tglad_glBindParameterEXT = (PFNGLBINDPARAMETEREXTPROC)load(\"glBindParameterEXT\");\n\tglad_glIsVariantEnabledEXT = (PFNGLISVARIANTENABLEDEXTPROC)load(\"glIsVariantEnabledEXT\");\n\tglad_glGetVariantBooleanvEXT = (PFNGLGETVARIANTBOOLEANVEXTPROC)load(\"glGetVariantBooleanvEXT\");\n\tglad_glGetVariantIntegervEXT = (PFNGLGETVARIANTINTEGERVEXTPROC)load(\"glGetVariantIntegervEXT\");\n\tglad_glGetVariantFloatvEXT = (PFNGLGETVARIANTFLOATVEXTPROC)load(\"glGetVariantFloatvEXT\");\n\tglad_glGetVariantPointervEXT = (PFNGLGETVARIANTPOINTERVEXTPROC)load(\"glGetVariantPointervEXT\");\n\tglad_glGetInvariantBooleanvEXT = (PFNGLGETINVARIANTBOOLEANVEXTPROC)load(\"glGetInvariantBooleanvEXT\");\n\tglad_glGetInvariantIntegervEXT = (PFNGLGETINVARIANTINTEGERVEXTPROC)load(\"glGetInvariantIntegervEXT\");\n\tglad_glGetInvariantFloatvEXT = (PFNGLGETINVARIANTFLOATVEXTPROC)load(\"glGetInvariantFloatvEXT\");\n\tglad_glGetLocalConstantBooleanvEXT = (PFNGLGETLOCALCONSTANTBOOLEANVEXTPROC)load(\"glGetLocalConstantBooleanvEXT\");\n\tglad_glGetLocalConstantIntegervEXT = (PFNGLGETLOCALCONSTANTINTEGERVEXTPROC)load(\"glGetLocalConstantIntegervEXT\");\n\tglad_glGetLocalConstantFloatvEXT = (PFNGLGETLOCALCONSTANTFLOATVEXTPROC)load(\"glGetLocalConstantFloatvEXT\");\n}\nstatic void load_GL_EXT_vertex_weighting(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_vertex_weighting) return;\n\tglad_glVertexWeightfEXT = (PFNGLVERTEXWEIGHTFEXTPROC)load(\"glVertexWeightfEXT\");\n\tglad_glVertexWeightfvEXT = (PFNGLVERTEXWEIGHTFVEXTPROC)load(\"glVertexWeightfvEXT\");\n\tglad_glVertexWeightPointerEXT = (PFNGLVERTEXWEIGHTPOINTEREXTPROC)load(\"glVertexWeightPointerEXT\");\n}\nstatic void load_GL_EXT_win32_keyed_mutex(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_win32_keyed_mutex) return;\n\tglad_glAcquireKeyedMutexWin32EXT = (PFNGLACQUIREKEYEDMUTEXWIN32EXTPROC)load(\"glAcquireKeyedMutexWin32EXT\");\n\tglad_glReleaseKeyedMutexWin32EXT = (PFNGLRELEASEKEYEDMUTEXWIN32EXTPROC)load(\"glReleaseKeyedMutexWin32EXT\");\n}\nstatic void load_GL_EXT_window_rectangles(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_window_rectangles) return;\n\tglad_glWindowRectanglesEXT = (PFNGLWINDOWRECTANGLESEXTPROC)load(\"glWindowRectanglesEXT\");\n}\nstatic void load_GL_EXT_x11_sync_object(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_x11_sync_object) return;\n\tglad_glImportSyncEXT = (PFNGLIMPORTSYNCEXTPROC)load(\"glImportSyncEXT\");\n}\nstatic void load_GL_GREMEDY_frame_terminator(GLADloadproc load) {\n\tif(!GLAD_GL_GREMEDY_frame_terminator) return;\n\tglad_glFrameTerminatorGREMEDY = (PFNGLFRAMETERMINATORGREMEDYPROC)load(\"glFrameTerminatorGREMEDY\");\n}\nstatic void load_GL_GREMEDY_string_marker(GLADloadproc load) {\n\tif(!GLAD_GL_GREMEDY_string_marker) return;\n\tglad_glStringMarkerGREMEDY = (PFNGLSTRINGMARKERGREMEDYPROC)load(\"glStringMarkerGREMEDY\");\n}\nstatic void load_GL_HP_image_transform(GLADloadproc load) {\n\tif(!GLAD_GL_HP_image_transform) return;\n\tglad_glImageTransformParameteriHP = (PFNGLIMAGETRANSFORMPARAMETERIHPPROC)load(\"glImageTransformParameteriHP\");\n\tglad_glImageTransformParameterfHP = (PFNGLIMAGETRANSFORMPARAMETERFHPPROC)load(\"glImageTransformParameterfHP\");\n\tglad_glImageTransformParameterivHP = (PFNGLIMAGETRANSFORMPARAMETERIVHPPROC)load(\"glImageTransformParameterivHP\");\n\tglad_glImageTransformParameterfvHP = (PFNGLIMAGETRANSFORMPARAMETERFVHPPROC)load(\"glImageTransformParameterfvHP\");\n\tglad_glGetImageTransformParameterivHP = (PFNGLGETIMAGETRANSFORMPARAMETERIVHPPROC)load(\"glGetImageTransformParameterivHP\");\n\tglad_glGetImageTransformParameterfvHP = (PFNGLGETIMAGETRANSFORMPARAMETERFVHPPROC)load(\"glGetImageTransformParameterfvHP\");\n}\nstatic void load_GL_IBM_multimode_draw_arrays(GLADloadproc load) {\n\tif(!GLAD_GL_IBM_multimode_draw_arrays) return;\n\tglad_glMultiModeDrawArraysIBM = (PFNGLMULTIMODEDRAWARRAYSIBMPROC)load(\"glMultiModeDrawArraysIBM\");\n\tglad_glMultiModeDrawElementsIBM = (PFNGLMULTIMODEDRAWELEMENTSIBMPROC)load(\"glMultiModeDrawElementsIBM\");\n}\nstatic void load_GL_IBM_static_data(GLADloadproc load) {\n\tif(!GLAD_GL_IBM_static_data) return;\n\tglad_glFlushStaticDataIBM = (PFNGLFLUSHSTATICDATAIBMPROC)load(\"glFlushStaticDataIBM\");\n}\nstatic void load_GL_IBM_vertex_array_lists(GLADloadproc load) {\n\tif(!GLAD_GL_IBM_vertex_array_lists) return;\n\tglad_glColorPointerListIBM = (PFNGLCOLORPOINTERLISTIBMPROC)load(\"glColorPointerListIBM\");\n\tglad_glSecondaryColorPointerListIBM = (PFNGLSECONDARYCOLORPOINTERLISTIBMPROC)load(\"glSecondaryColorPointerListIBM\");\n\tglad_glEdgeFlagPointerListIBM = (PFNGLEDGEFLAGPOINTERLISTIBMPROC)load(\"glEdgeFlagPointerListIBM\");\n\tglad_glFogCoordPointerListIBM = (PFNGLFOGCOORDPOINTERLISTIBMPROC)load(\"glFogCoordPointerListIBM\");\n\tglad_glIndexPointerListIBM = (PFNGLINDEXPOINTERLISTIBMPROC)load(\"glIndexPointerListIBM\");\n\tglad_glNormalPointerListIBM = (PFNGLNORMALPOINTERLISTIBMPROC)load(\"glNormalPointerListIBM\");\n\tglad_glTexCoordPointerListIBM = (PFNGLTEXCOORDPOINTERLISTIBMPROC)load(\"glTexCoordPointerListIBM\");\n\tglad_glVertexPointerListIBM = (PFNGLVERTEXPOINTERLISTIBMPROC)load(\"glVertexPointerListIBM\");\n}\nstatic void load_GL_INGR_blend_func_separate(GLADloadproc load) {\n\tif(!GLAD_GL_INGR_blend_func_separate) return;\n\tglad_glBlendFuncSeparateINGR = (PFNGLBLENDFUNCSEPARATEINGRPROC)load(\"glBlendFuncSeparateINGR\");\n}\nstatic void load_GL_INTEL_framebuffer_CMAA(GLADloadproc load) {\n\tif(!GLAD_GL_INTEL_framebuffer_CMAA) return;\n\tglad_glApplyFramebufferAttachmentCMAAINTEL = (PFNGLAPPLYFRAMEBUFFERATTACHMENTCMAAINTELPROC)load(\"glApplyFramebufferAttachmentCMAAINTEL\");\n}\nstatic void load_GL_INTEL_map_texture(GLADloadproc load) {\n\tif(!GLAD_GL_INTEL_map_texture) return;\n\tglad_glSyncTextureINTEL = (PFNGLSYNCTEXTUREINTELPROC)load(\"glSyncTextureINTEL\");\n\tglad_glUnmapTexture2DINTEL = (PFNGLUNMAPTEXTURE2DINTELPROC)load(\"glUnmapTexture2DINTEL\");\n\tglad_glMapTexture2DINTEL = (PFNGLMAPTEXTURE2DINTELPROC)load(\"glMapTexture2DINTEL\");\n}\nstatic void load_GL_INTEL_parallel_arrays(GLADloadproc load) {\n\tif(!GLAD_GL_INTEL_parallel_arrays) return;\n\tglad_glVertexPointervINTEL = (PFNGLVERTEXPOINTERVINTELPROC)load(\"glVertexPointervINTEL\");\n\tglad_glNormalPointervINTEL = (PFNGLNORMALPOINTERVINTELPROC)load(\"glNormalPointervINTEL\");\n\tglad_glColorPointervINTEL = (PFNGLCOLORPOINTERVINTELPROC)load(\"glColorPointervINTEL\");\n\tglad_glTexCoordPointervINTEL = (PFNGLTEXCOORDPOINTERVINTELPROC)load(\"glTexCoordPointervINTEL\");\n}\nstatic void load_GL_INTEL_performance_query(GLADloadproc load) {\n\tif(!GLAD_GL_INTEL_performance_query) return;\n\tglad_glBeginPerfQueryINTEL = (PFNGLBEGINPERFQUERYINTELPROC)load(\"glBeginPerfQueryINTEL\");\n\tglad_glCreatePerfQueryINTEL = (PFNGLCREATEPERFQUERYINTELPROC)load(\"glCreatePerfQueryINTEL\");\n\tglad_glDeletePerfQueryINTEL = (PFNGLDELETEPERFQUERYINTELPROC)load(\"glDeletePerfQueryINTEL\");\n\tglad_glEndPerfQueryINTEL = (PFNGLENDPERFQUERYINTELPROC)load(\"glEndPerfQueryINTEL\");\n\tglad_glGetFirstPerfQueryIdINTEL = (PFNGLGETFIRSTPERFQUERYIDINTELPROC)load(\"glGetFirstPerfQueryIdINTEL\");\n\tglad_glGetNextPerfQueryIdINTEL = (PFNGLGETNEXTPERFQUERYIDINTELPROC)load(\"glGetNextPerfQueryIdINTEL\");\n\tglad_glGetPerfCounterInfoINTEL = (PFNGLGETPERFCOUNTERINFOINTELPROC)load(\"glGetPerfCounterInfoINTEL\");\n\tglad_glGetPerfQueryDataINTEL = (PFNGLGETPERFQUERYDATAINTELPROC)load(\"glGetPerfQueryDataINTEL\");\n\tglad_glGetPerfQueryIdByNameINTEL = (PFNGLGETPERFQUERYIDBYNAMEINTELPROC)load(\"glGetPerfQueryIdByNameINTEL\");\n\tglad_glGetPerfQueryInfoINTEL = (PFNGLGETPERFQUERYINFOINTELPROC)load(\"glGetPerfQueryInfoINTEL\");\n}\nstatic void load_GL_KHR_blend_equation_advanced(GLADloadproc load) {\n\tif(!GLAD_GL_KHR_blend_equation_advanced) return;\n\tglad_glBlendBarrierKHR = (PFNGLBLENDBARRIERKHRPROC)load(\"glBlendBarrierKHR\");\n}\nstatic void load_GL_KHR_debug(GLADloadproc load) {\n\tif(!GLAD_GL_KHR_debug) return;\n\tglad_glDebugMessageControl = (PFNGLDEBUGMESSAGECONTROLPROC)load(\"glDebugMessageControl\");\n\tglad_glDebugMessageInsert = (PFNGLDEBUGMESSAGEINSERTPROC)load(\"glDebugMessageInsert\");\n\tglad_glDebugMessageCallback = (PFNGLDEBUGMESSAGECALLBACKPROC)load(\"glDebugMessageCallback\");\n\tglad_glGetDebugMessageLog = (PFNGLGETDEBUGMESSAGELOGPROC)load(\"glGetDebugMessageLog\");\n\tglad_glPushDebugGroup = (PFNGLPUSHDEBUGGROUPPROC)load(\"glPushDebugGroup\");\n\tglad_glPopDebugGroup = (PFNGLPOPDEBUGGROUPPROC)load(\"glPopDebugGroup\");\n\tglad_glObjectLabel = (PFNGLOBJECTLABELPROC)load(\"glObjectLabel\");\n\tglad_glGetObjectLabel = (PFNGLGETOBJECTLABELPROC)load(\"glGetObjectLabel\");\n\tglad_glObjectPtrLabel = (PFNGLOBJECTPTRLABELPROC)load(\"glObjectPtrLabel\");\n\tglad_glGetObjectPtrLabel = (PFNGLGETOBJECTPTRLABELPROC)load(\"glGetObjectPtrLabel\");\n\tglad_glGetPointerv = (PFNGLGETPOINTERVPROC)load(\"glGetPointerv\");\n\tglad_glDebugMessageControlKHR = (PFNGLDEBUGMESSAGECONTROLKHRPROC)load(\"glDebugMessageControlKHR\");\n\tglad_glDebugMessageInsertKHR = (PFNGLDEBUGMESSAGEINSERTKHRPROC)load(\"glDebugMessageInsertKHR\");\n\tglad_glDebugMessageCallbackKHR = (PFNGLDEBUGMESSAGECALLBACKKHRPROC)load(\"glDebugMessageCallbackKHR\");\n\tglad_glGetDebugMessageLogKHR = (PFNGLGETDEBUGMESSAGELOGKHRPROC)load(\"glGetDebugMessageLogKHR\");\n\tglad_glPushDebugGroupKHR = (PFNGLPUSHDEBUGGROUPKHRPROC)load(\"glPushDebugGroupKHR\");\n\tglad_glPopDebugGroupKHR = (PFNGLPOPDEBUGGROUPKHRPROC)load(\"glPopDebugGroupKHR\");\n\tglad_glObjectLabelKHR = (PFNGLOBJECTLABELKHRPROC)load(\"glObjectLabelKHR\");\n\tglad_glGetObjectLabelKHR = (PFNGLGETOBJECTLABELKHRPROC)load(\"glGetObjectLabelKHR\");\n\tglad_glObjectPtrLabelKHR = (PFNGLOBJECTPTRLABELKHRPROC)load(\"glObjectPtrLabelKHR\");\n\tglad_glGetObjectPtrLabelKHR = (PFNGLGETOBJECTPTRLABELKHRPROC)load(\"glGetObjectPtrLabelKHR\");\n\tglad_glGetPointervKHR = (PFNGLGETPOINTERVKHRPROC)load(\"glGetPointervKHR\");\n}\nstatic void load_GL_KHR_parallel_shader_compile(GLADloadproc load) {\n\tif(!GLAD_GL_KHR_parallel_shader_compile) return;\n\tglad_glMaxShaderCompilerThreadsKHR = (PFNGLMAXSHADERCOMPILERTHREADSKHRPROC)load(\"glMaxShaderCompilerThreadsKHR\");\n}\nstatic void load_GL_KHR_robustness(GLADloadproc load) {\n\tif(!GLAD_GL_KHR_robustness) return;\n\tglad_glGetGraphicsResetStatus = (PFNGLGETGRAPHICSRESETSTATUSPROC)load(\"glGetGraphicsResetStatus\");\n\tglad_glReadnPixels = (PFNGLREADNPIXELSPROC)load(\"glReadnPixels\");\n\tglad_glGetnUniformfv = (PFNGLGETNUNIFORMFVPROC)load(\"glGetnUniformfv\");\n\tglad_glGetnUniformiv = (PFNGLGETNUNIFORMIVPROC)load(\"glGetnUniformiv\");\n\tglad_glGetnUniformuiv = (PFNGLGETNUNIFORMUIVPROC)load(\"glGetnUniformuiv\");\n\tglad_glGetGraphicsResetStatusKHR = (PFNGLGETGRAPHICSRESETSTATUSKHRPROC)load(\"glGetGraphicsResetStatusKHR\");\n\tglad_glReadnPixelsKHR = (PFNGLREADNPIXELSKHRPROC)load(\"glReadnPixelsKHR\");\n\tglad_glGetnUniformfvKHR = (PFNGLGETNUNIFORMFVKHRPROC)load(\"glGetnUniformfvKHR\");\n\tglad_glGetnUniformivKHR = (PFNGLGETNUNIFORMIVKHRPROC)load(\"glGetnUniformivKHR\");\n\tglad_glGetnUniformuivKHR = (PFNGLGETNUNIFORMUIVKHRPROC)load(\"glGetnUniformuivKHR\");\n}\nstatic void load_GL_MESA_resize_buffers(GLADloadproc load) {\n\tif(!GLAD_GL_MESA_resize_buffers) return;\n\tglad_glResizeBuffersMESA = (PFNGLRESIZEBUFFERSMESAPROC)load(\"glResizeBuffersMESA\");\n}\nstatic void load_GL_MESA_window_pos(GLADloadproc load) {\n\tif(!GLAD_GL_MESA_window_pos) return;\n\tglad_glWindowPos2dMESA = (PFNGLWINDOWPOS2DMESAPROC)load(\"glWindowPos2dMESA\");\n\tglad_glWindowPos2dvMESA = (PFNGLWINDOWPOS2DVMESAPROC)load(\"glWindowPos2dvMESA\");\n\tglad_glWindowPos2fMESA = (PFNGLWINDOWPOS2FMESAPROC)load(\"glWindowPos2fMESA\");\n\tglad_glWindowPos2fvMESA = (PFNGLWINDOWPOS2FVMESAPROC)load(\"glWindowPos2fvMESA\");\n\tglad_glWindowPos2iMESA = (PFNGLWINDOWPOS2IMESAPROC)load(\"glWindowPos2iMESA\");\n\tglad_glWindowPos2ivMESA = (PFNGLWINDOWPOS2IVMESAPROC)load(\"glWindowPos2ivMESA\");\n\tglad_glWindowPos2sMESA = (PFNGLWINDOWPOS2SMESAPROC)load(\"glWindowPos2sMESA\");\n\tglad_glWindowPos2svMESA = (PFNGLWINDOWPOS2SVMESAPROC)load(\"glWindowPos2svMESA\");\n\tglad_glWindowPos3dMESA = (PFNGLWINDOWPOS3DMESAPROC)load(\"glWindowPos3dMESA\");\n\tglad_glWindowPos3dvMESA = (PFNGLWINDOWPOS3DVMESAPROC)load(\"glWindowPos3dvMESA\");\n\tglad_glWindowPos3fMESA = (PFNGLWINDOWPOS3FMESAPROC)load(\"glWindowPos3fMESA\");\n\tglad_glWindowPos3fvMESA = (PFNGLWINDOWPOS3FVMESAPROC)load(\"glWindowPos3fvMESA\");\n\tglad_glWindowPos3iMESA = (PFNGLWINDOWPOS3IMESAPROC)load(\"glWindowPos3iMESA\");\n\tglad_glWindowPos3ivMESA = (PFNGLWINDOWPOS3IVMESAPROC)load(\"glWindowPos3ivMESA\");\n\tglad_glWindowPos3sMESA = (PFNGLWINDOWPOS3SMESAPROC)load(\"glWindowPos3sMESA\");\n\tglad_glWindowPos3svMESA = (PFNGLWINDOWPOS3SVMESAPROC)load(\"glWindowPos3svMESA\");\n\tglad_glWindowPos4dMESA = (PFNGLWINDOWPOS4DMESAPROC)load(\"glWindowPos4dMESA\");\n\tglad_glWindowPos4dvMESA = (PFNGLWINDOWPOS4DVMESAPROC)load(\"glWindowPos4dvMESA\");\n\tglad_glWindowPos4fMESA = (PFNGLWINDOWPOS4FMESAPROC)load(\"glWindowPos4fMESA\");\n\tglad_glWindowPos4fvMESA = (PFNGLWINDOWPOS4FVMESAPROC)load(\"glWindowPos4fvMESA\");\n\tglad_glWindowPos4iMESA = (PFNGLWINDOWPOS4IMESAPROC)load(\"glWindowPos4iMESA\");\n\tglad_glWindowPos4ivMESA = (PFNGLWINDOWPOS4IVMESAPROC)load(\"glWindowPos4ivMESA\");\n\tglad_glWindowPos4sMESA = (PFNGLWINDOWPOS4SMESAPROC)load(\"glWindowPos4sMESA\");\n\tglad_glWindowPos4svMESA = (PFNGLWINDOWPOS4SVMESAPROC)load(\"glWindowPos4svMESA\");\n}\nstatic void load_GL_NVX_conditional_render(GLADloadproc load) {\n\tif(!GLAD_GL_NVX_conditional_render) return;\n\tglad_glBeginConditionalRenderNVX = (PFNGLBEGINCONDITIONALRENDERNVXPROC)load(\"glBeginConditionalRenderNVX\");\n\tglad_glEndConditionalRenderNVX = (PFNGLENDCONDITIONALRENDERNVXPROC)load(\"glEndConditionalRenderNVX\");\n}\nstatic void load_GL_NVX_linked_gpu_multicast(GLADloadproc load) {\n\tif(!GLAD_GL_NVX_linked_gpu_multicast) return;\n\tglad_glLGPUNamedBufferSubDataNVX = (PFNGLLGPUNAMEDBUFFERSUBDATANVXPROC)load(\"glLGPUNamedBufferSubDataNVX\");\n\tglad_glLGPUCopyImageSubDataNVX = (PFNGLLGPUCOPYIMAGESUBDATANVXPROC)load(\"glLGPUCopyImageSubDataNVX\");\n\tglad_glLGPUInterlockNVX = (PFNGLLGPUINTERLOCKNVXPROC)load(\"glLGPUInterlockNVX\");\n}\nstatic void load_GL_NV_alpha_to_coverage_dither_control(GLADloadproc load) {\n\tif(!GLAD_GL_NV_alpha_to_coverage_dither_control) return;\n\tglad_glAlphaToCoverageDitherControlNV = (PFNGLALPHATOCOVERAGEDITHERCONTROLNVPROC)load(\"glAlphaToCoverageDitherControlNV\");\n}\nstatic void load_GL_NV_bindless_multi_draw_indirect(GLADloadproc load) {\n\tif(!GLAD_GL_NV_bindless_multi_draw_indirect) return;\n\tglad_glMultiDrawArraysIndirectBindlessNV = (PFNGLMULTIDRAWARRAYSINDIRECTBINDLESSNVPROC)load(\"glMultiDrawArraysIndirectBindlessNV\");\n\tglad_glMultiDrawElementsIndirectBindlessNV = (PFNGLMULTIDRAWELEMENTSINDIRECTBINDLESSNVPROC)load(\"glMultiDrawElementsIndirectBindlessNV\");\n}\nstatic void load_GL_NV_bindless_multi_draw_indirect_count(GLADloadproc load) {\n\tif(!GLAD_GL_NV_bindless_multi_draw_indirect_count) return;\n\tglad_glMultiDrawArraysIndirectBindlessCountNV = (PFNGLMULTIDRAWARRAYSINDIRECTBINDLESSCOUNTNVPROC)load(\"glMultiDrawArraysIndirectBindlessCountNV\");\n\tglad_glMultiDrawElementsIndirectBindlessCountNV = (PFNGLMULTIDRAWELEMENTSINDIRECTBINDLESSCOUNTNVPROC)load(\"glMultiDrawElementsIndirectBindlessCountNV\");\n}\nstatic void load_GL_NV_bindless_texture(GLADloadproc load) {\n\tif(!GLAD_GL_NV_bindless_texture) return;\n\tglad_glGetTextureHandleNV = (PFNGLGETTEXTUREHANDLENVPROC)load(\"glGetTextureHandleNV\");\n\tglad_glGetTextureSamplerHandleNV = (PFNGLGETTEXTURESAMPLERHANDLENVPROC)load(\"glGetTextureSamplerHandleNV\");\n\tglad_glMakeTextureHandleResidentNV = (PFNGLMAKETEXTUREHANDLERESIDENTNVPROC)load(\"glMakeTextureHandleResidentNV\");\n\tglad_glMakeTextureHandleNonResidentNV = (PFNGLMAKETEXTUREHANDLENONRESIDENTNVPROC)load(\"glMakeTextureHandleNonResidentNV\");\n\tglad_glGetImageHandleNV = (PFNGLGETIMAGEHANDLENVPROC)load(\"glGetImageHandleNV\");\n\tglad_glMakeImageHandleResidentNV = (PFNGLMAKEIMAGEHANDLERESIDENTNVPROC)load(\"glMakeImageHandleResidentNV\");\n\tglad_glMakeImageHandleNonResidentNV = (PFNGLMAKEIMAGEHANDLENONRESIDENTNVPROC)load(\"glMakeImageHandleNonResidentNV\");\n\tglad_glUniformHandleui64NV = (PFNGLUNIFORMHANDLEUI64NVPROC)load(\"glUniformHandleui64NV\");\n\tglad_glUniformHandleui64vNV = (PFNGLUNIFORMHANDLEUI64VNVPROC)load(\"glUniformHandleui64vNV\");\n\tglad_glProgramUniformHandleui64NV = (PFNGLPROGRAMUNIFORMHANDLEUI64NVPROC)load(\"glProgramUniformHandleui64NV\");\n\tglad_glProgramUniformHandleui64vNV = (PFNGLPROGRAMUNIFORMHANDLEUI64VNVPROC)load(\"glProgramUniformHandleui64vNV\");\n\tglad_glIsTextureHandleResidentNV = (PFNGLISTEXTUREHANDLERESIDENTNVPROC)load(\"glIsTextureHandleResidentNV\");\n\tglad_glIsImageHandleResidentNV = (PFNGLISIMAGEHANDLERESIDENTNVPROC)load(\"glIsImageHandleResidentNV\");\n}\nstatic void load_GL_NV_blend_equation_advanced(GLADloadproc load) {\n\tif(!GLAD_GL_NV_blend_equation_advanced) return;\n\tglad_glBlendParameteriNV = (PFNGLBLENDPARAMETERINVPROC)load(\"glBlendParameteriNV\");\n\tglad_glBlendBarrierNV = (PFNGLBLENDBARRIERNVPROC)load(\"glBlendBarrierNV\");\n}\nstatic void load_GL_NV_clip_space_w_scaling(GLADloadproc load) {\n\tif(!GLAD_GL_NV_clip_space_w_scaling) return;\n\tglad_glViewportPositionWScaleNV = (PFNGLVIEWPORTPOSITIONWSCALENVPROC)load(\"glViewportPositionWScaleNV\");\n}\nstatic void load_GL_NV_command_list(GLADloadproc load) {\n\tif(!GLAD_GL_NV_command_list) return;\n\tglad_glCreateStatesNV = (PFNGLCREATESTATESNVPROC)load(\"glCreateStatesNV\");\n\tglad_glDeleteStatesNV = (PFNGLDELETESTATESNVPROC)load(\"glDeleteStatesNV\");\n\tglad_glIsStateNV = (PFNGLISSTATENVPROC)load(\"glIsStateNV\");\n\tglad_glStateCaptureNV = (PFNGLSTATECAPTURENVPROC)load(\"glStateCaptureNV\");\n\tglad_glGetCommandHeaderNV = (PFNGLGETCOMMANDHEADERNVPROC)load(\"glGetCommandHeaderNV\");\n\tglad_glGetStageIndexNV = (PFNGLGETSTAGEINDEXNVPROC)load(\"glGetStageIndexNV\");\n\tglad_glDrawCommandsNV = (PFNGLDRAWCOMMANDSNVPROC)load(\"glDrawCommandsNV\");\n\tglad_glDrawCommandsAddressNV = (PFNGLDRAWCOMMANDSADDRESSNVPROC)load(\"glDrawCommandsAddressNV\");\n\tglad_glDrawCommandsStatesNV = (PFNGLDRAWCOMMANDSSTATESNVPROC)load(\"glDrawCommandsStatesNV\");\n\tglad_glDrawCommandsStatesAddressNV = (PFNGLDRAWCOMMANDSSTATESADDRESSNVPROC)load(\"glDrawCommandsStatesAddressNV\");\n\tglad_glCreateCommandListsNV = (PFNGLCREATECOMMANDLISTSNVPROC)load(\"glCreateCommandListsNV\");\n\tglad_glDeleteCommandListsNV = (PFNGLDELETECOMMANDLISTSNVPROC)load(\"glDeleteCommandListsNV\");\n\tglad_glIsCommandListNV = (PFNGLISCOMMANDLISTNVPROC)load(\"glIsCommandListNV\");\n\tglad_glListDrawCommandsStatesClientNV = (PFNGLLISTDRAWCOMMANDSSTATESCLIENTNVPROC)load(\"glListDrawCommandsStatesClientNV\");\n\tglad_glCommandListSegmentsNV = (PFNGLCOMMANDLISTSEGMENTSNVPROC)load(\"glCommandListSegmentsNV\");\n\tglad_glCompileCommandListNV = (PFNGLCOMPILECOMMANDLISTNVPROC)load(\"glCompileCommandListNV\");\n\tglad_glCallCommandListNV = (PFNGLCALLCOMMANDLISTNVPROC)load(\"glCallCommandListNV\");\n}\nstatic void load_GL_NV_conditional_render(GLADloadproc load) {\n\tif(!GLAD_GL_NV_conditional_render) return;\n\tglad_glBeginConditionalRenderNV = (PFNGLBEGINCONDITIONALRENDERNVPROC)load(\"glBeginConditionalRenderNV\");\n\tglad_glEndConditionalRenderNV = (PFNGLENDCONDITIONALRENDERNVPROC)load(\"glEndConditionalRenderNV\");\n}\nstatic void load_GL_NV_conservative_raster(GLADloadproc load) {\n\tif(!GLAD_GL_NV_conservative_raster) return;\n\tglad_glSubpixelPrecisionBiasNV = (PFNGLSUBPIXELPRECISIONBIASNVPROC)load(\"glSubpixelPrecisionBiasNV\");\n}\nstatic void load_GL_NV_conservative_raster_dilate(GLADloadproc load) {\n\tif(!GLAD_GL_NV_conservative_raster_dilate) return;\n\tglad_glConservativeRasterParameterfNV = (PFNGLCONSERVATIVERASTERPARAMETERFNVPROC)load(\"glConservativeRasterParameterfNV\");\n}\nstatic void load_GL_NV_conservative_raster_pre_snap_triangles(GLADloadproc load) {\n\tif(!GLAD_GL_NV_conservative_raster_pre_snap_triangles) return;\n\tglad_glConservativeRasterParameteriNV = (PFNGLCONSERVATIVERASTERPARAMETERINVPROC)load(\"glConservativeRasterParameteriNV\");\n}\nstatic void load_GL_NV_copy_image(GLADloadproc load) {\n\tif(!GLAD_GL_NV_copy_image) return;\n\tglad_glCopyImageSubDataNV = (PFNGLCOPYIMAGESUBDATANVPROC)load(\"glCopyImageSubDataNV\");\n}\nstatic void load_GL_NV_depth_buffer_float(GLADloadproc load) {\n\tif(!GLAD_GL_NV_depth_buffer_float) return;\n\tglad_glDepthRangedNV = (PFNGLDEPTHRANGEDNVPROC)load(\"glDepthRangedNV\");\n\tglad_glClearDepthdNV = (PFNGLCLEARDEPTHDNVPROC)load(\"glClearDepthdNV\");\n\tglad_glDepthBoundsdNV = (PFNGLDEPTHBOUNDSDNVPROC)load(\"glDepthBoundsdNV\");\n}\nstatic void load_GL_NV_draw_texture(GLADloadproc load) {\n\tif(!GLAD_GL_NV_draw_texture) return;\n\tglad_glDrawTextureNV = (PFNGLDRAWTEXTURENVPROC)load(\"glDrawTextureNV\");\n}\nstatic void load_GL_NV_draw_vulkan_image(GLADloadproc load) {\n\tif(!GLAD_GL_NV_draw_vulkan_image) return;\n\tglad_glDrawVkImageNV = (PFNGLDRAWVKIMAGENVPROC)load(\"glDrawVkImageNV\");\n\tglad_glGetVkProcAddrNV = (PFNGLGETVKPROCADDRNVPROC)load(\"glGetVkProcAddrNV\");\n\tglad_glWaitVkSemaphoreNV = (PFNGLWAITVKSEMAPHORENVPROC)load(\"glWaitVkSemaphoreNV\");\n\tglad_glSignalVkSemaphoreNV = (PFNGLSIGNALVKSEMAPHORENVPROC)load(\"glSignalVkSemaphoreNV\");\n\tglad_glSignalVkFenceNV = (PFNGLSIGNALVKFENCENVPROC)load(\"glSignalVkFenceNV\");\n}\nstatic void load_GL_NV_evaluators(GLADloadproc load) {\n\tif(!GLAD_GL_NV_evaluators) return;\n\tglad_glMapControlPointsNV = (PFNGLMAPCONTROLPOINTSNVPROC)load(\"glMapControlPointsNV\");\n\tglad_glMapParameterivNV = (PFNGLMAPPARAMETERIVNVPROC)load(\"glMapParameterivNV\");\n\tglad_glMapParameterfvNV = (PFNGLMAPPARAMETERFVNVPROC)load(\"glMapParameterfvNV\");\n\tglad_glGetMapControlPointsNV = (PFNGLGETMAPCONTROLPOINTSNVPROC)load(\"glGetMapControlPointsNV\");\n\tglad_glGetMapParameterivNV = (PFNGLGETMAPPARAMETERIVNVPROC)load(\"glGetMapParameterivNV\");\n\tglad_glGetMapParameterfvNV = (PFNGLGETMAPPARAMETERFVNVPROC)load(\"glGetMapParameterfvNV\");\n\tglad_glGetMapAttribParameterivNV = (PFNGLGETMAPATTRIBPARAMETERIVNVPROC)load(\"glGetMapAttribParameterivNV\");\n\tglad_glGetMapAttribParameterfvNV = (PFNGLGETMAPATTRIBPARAMETERFVNVPROC)load(\"glGetMapAttribParameterfvNV\");\n\tglad_glEvalMapsNV = (PFNGLEVALMAPSNVPROC)load(\"glEvalMapsNV\");\n}\nstatic void load_GL_NV_explicit_multisample(GLADloadproc load) {\n\tif(!GLAD_GL_NV_explicit_multisample) return;\n\tglad_glGetMultisamplefvNV = (PFNGLGETMULTISAMPLEFVNVPROC)load(\"glGetMultisamplefvNV\");\n\tglad_glSampleMaskIndexedNV = (PFNGLSAMPLEMASKINDEXEDNVPROC)load(\"glSampleMaskIndexedNV\");\n\tglad_glTexRenderbufferNV = (PFNGLTEXRENDERBUFFERNVPROC)load(\"glTexRenderbufferNV\");\n}\nstatic void load_GL_NV_fence(GLADloadproc load) {\n\tif(!GLAD_GL_NV_fence) return;\n\tglad_glDeleteFencesNV = (PFNGLDELETEFENCESNVPROC)load(\"glDeleteFencesNV\");\n\tglad_glGenFencesNV = (PFNGLGENFENCESNVPROC)load(\"glGenFencesNV\");\n\tglad_glIsFenceNV = (PFNGLISFENCENVPROC)load(\"glIsFenceNV\");\n\tglad_glTestFenceNV = (PFNGLTESTFENCENVPROC)load(\"glTestFenceNV\");\n\tglad_glGetFenceivNV = (PFNGLGETFENCEIVNVPROC)load(\"glGetFenceivNV\");\n\tglad_glFinishFenceNV = (PFNGLFINISHFENCENVPROC)load(\"glFinishFenceNV\");\n\tglad_glSetFenceNV = (PFNGLSETFENCENVPROC)load(\"glSetFenceNV\");\n}\nstatic void load_GL_NV_fragment_coverage_to_color(GLADloadproc load) {\n\tif(!GLAD_GL_NV_fragment_coverage_to_color) return;\n\tglad_glFragmentCoverageColorNV = (PFNGLFRAGMENTCOVERAGECOLORNVPROC)load(\"glFragmentCoverageColorNV\");\n}\nstatic void load_GL_NV_fragment_program(GLADloadproc load) {\n\tif(!GLAD_GL_NV_fragment_program) return;\n\tglad_glProgramNamedParameter4fNV = (PFNGLPROGRAMNAMEDPARAMETER4FNVPROC)load(\"glProgramNamedParameter4fNV\");\n\tglad_glProgramNamedParameter4fvNV = (PFNGLPROGRAMNAMEDPARAMETER4FVNVPROC)load(\"glProgramNamedParameter4fvNV\");\n\tglad_glProgramNamedParameter4dNV = (PFNGLPROGRAMNAMEDPARAMETER4DNVPROC)load(\"glProgramNamedParameter4dNV\");\n\tglad_glProgramNamedParameter4dvNV = (PFNGLPROGRAMNAMEDPARAMETER4DVNVPROC)load(\"glProgramNamedParameter4dvNV\");\n\tglad_glGetProgramNamedParameterfvNV = (PFNGLGETPROGRAMNAMEDPARAMETERFVNVPROC)load(\"glGetProgramNamedParameterfvNV\");\n\tglad_glGetProgramNamedParameterdvNV = (PFNGLGETPROGRAMNAMEDPARAMETERDVNVPROC)load(\"glGetProgramNamedParameterdvNV\");\n}\nstatic void load_GL_NV_framebuffer_mixed_samples(GLADloadproc load) {\n\tif(!GLAD_GL_NV_framebuffer_mixed_samples) return;\n\tglad_glRasterSamplesEXT = (PFNGLRASTERSAMPLESEXTPROC)load(\"glRasterSamplesEXT\");\n\tglad_glCoverageModulationTableNV = (PFNGLCOVERAGEMODULATIONTABLENVPROC)load(\"glCoverageModulationTableNV\");\n\tglad_glGetCoverageModulationTableNV = (PFNGLGETCOVERAGEMODULATIONTABLENVPROC)load(\"glGetCoverageModulationTableNV\");\n\tglad_glCoverageModulationNV = (PFNGLCOVERAGEMODULATIONNVPROC)load(\"glCoverageModulationNV\");\n}\nstatic void load_GL_NV_framebuffer_multisample_coverage(GLADloadproc load) {\n\tif(!GLAD_GL_NV_framebuffer_multisample_coverage) return;\n\tglad_glRenderbufferStorageMultisampleCoverageNV = (PFNGLRENDERBUFFERSTORAGEMULTISAMPLECOVERAGENVPROC)load(\"glRenderbufferStorageMultisampleCoverageNV\");\n}\nstatic void load_GL_NV_geometry_program4(GLADloadproc load) {\n\tif(!GLAD_GL_NV_geometry_program4) return;\n\tglad_glProgramVertexLimitNV = (PFNGLPROGRAMVERTEXLIMITNVPROC)load(\"glProgramVertexLimitNV\");\n\tglad_glFramebufferTextureEXT = (PFNGLFRAMEBUFFERTEXTUREEXTPROC)load(\"glFramebufferTextureEXT\");\n\tglad_glFramebufferTextureLayerEXT = (PFNGLFRAMEBUFFERTEXTURELAYEREXTPROC)load(\"glFramebufferTextureLayerEXT\");\n\tglad_glFramebufferTextureFaceEXT = (PFNGLFRAMEBUFFERTEXTUREFACEEXTPROC)load(\"glFramebufferTextureFaceEXT\");\n}\nstatic void load_GL_NV_gpu_multicast(GLADloadproc load) {\n\tif(!GLAD_GL_NV_gpu_multicast) return;\n\tglad_glRenderGpuMaskNV = (PFNGLRENDERGPUMASKNVPROC)load(\"glRenderGpuMaskNV\");\n\tglad_glMulticastBufferSubDataNV = (PFNGLMULTICASTBUFFERSUBDATANVPROC)load(\"glMulticastBufferSubDataNV\");\n\tglad_glMulticastCopyBufferSubDataNV = (PFNGLMULTICASTCOPYBUFFERSUBDATANVPROC)load(\"glMulticastCopyBufferSubDataNV\");\n\tglad_glMulticastCopyImageSubDataNV = (PFNGLMULTICASTCOPYIMAGESUBDATANVPROC)load(\"glMulticastCopyImageSubDataNV\");\n\tglad_glMulticastBlitFramebufferNV = (PFNGLMULTICASTBLITFRAMEBUFFERNVPROC)load(\"glMulticastBlitFramebufferNV\");\n\tglad_glMulticastFramebufferSampleLocationsfvNV = (PFNGLMULTICASTFRAMEBUFFERSAMPLELOCATIONSFVNVPROC)load(\"glMulticastFramebufferSampleLocationsfvNV\");\n\tglad_glMulticastBarrierNV = (PFNGLMULTICASTBARRIERNVPROC)load(\"glMulticastBarrierNV\");\n\tglad_glMulticastWaitSyncNV = (PFNGLMULTICASTWAITSYNCNVPROC)load(\"glMulticastWaitSyncNV\");\n\tglad_glMulticastGetQueryObjectivNV = (PFNGLMULTICASTGETQUERYOBJECTIVNVPROC)load(\"glMulticastGetQueryObjectivNV\");\n\tglad_glMulticastGetQueryObjectuivNV = (PFNGLMULTICASTGETQUERYOBJECTUIVNVPROC)load(\"glMulticastGetQueryObjectuivNV\");\n\tglad_glMulticastGetQueryObjecti64vNV = (PFNGLMULTICASTGETQUERYOBJECTI64VNVPROC)load(\"glMulticastGetQueryObjecti64vNV\");\n\tglad_glMulticastGetQueryObjectui64vNV = (PFNGLMULTICASTGETQUERYOBJECTUI64VNVPROC)load(\"glMulticastGetQueryObjectui64vNV\");\n}\nstatic void load_GL_NV_gpu_program4(GLADloadproc load) {\n\tif(!GLAD_GL_NV_gpu_program4) return;\n\tglad_glProgramLocalParameterI4iNV = (PFNGLPROGRAMLOCALPARAMETERI4INVPROC)load(\"glProgramLocalParameterI4iNV\");\n\tglad_glProgramLocalParameterI4ivNV = (PFNGLPROGRAMLOCALPARAMETERI4IVNVPROC)load(\"glProgramLocalParameterI4ivNV\");\n\tglad_glProgramLocalParametersI4ivNV = (PFNGLPROGRAMLOCALPARAMETERSI4IVNVPROC)load(\"glProgramLocalParametersI4ivNV\");\n\tglad_glProgramLocalParameterI4uiNV = (PFNGLPROGRAMLOCALPARAMETERI4UINVPROC)load(\"glProgramLocalParameterI4uiNV\");\n\tglad_glProgramLocalParameterI4uivNV = (PFNGLPROGRAMLOCALPARAMETERI4UIVNVPROC)load(\"glProgramLocalParameterI4uivNV\");\n\tglad_glProgramLocalParametersI4uivNV = (PFNGLPROGRAMLOCALPARAMETERSI4UIVNVPROC)load(\"glProgramLocalParametersI4uivNV\");\n\tglad_glProgramEnvParameterI4iNV = (PFNGLPROGRAMENVPARAMETERI4INVPROC)load(\"glProgramEnvParameterI4iNV\");\n\tglad_glProgramEnvParameterI4ivNV = (PFNGLPROGRAMENVPARAMETERI4IVNVPROC)load(\"glProgramEnvParameterI4ivNV\");\n\tglad_glProgramEnvParametersI4ivNV = (PFNGLPROGRAMENVPARAMETERSI4IVNVPROC)load(\"glProgramEnvParametersI4ivNV\");\n\tglad_glProgramEnvParameterI4uiNV = (PFNGLPROGRAMENVPARAMETERI4UINVPROC)load(\"glProgramEnvParameterI4uiNV\");\n\tglad_glProgramEnvParameterI4uivNV = (PFNGLPROGRAMENVPARAMETERI4UIVNVPROC)load(\"glProgramEnvParameterI4uivNV\");\n\tglad_glProgramEnvParametersI4uivNV = (PFNGLPROGRAMENVPARAMETERSI4UIVNVPROC)load(\"glProgramEnvParametersI4uivNV\");\n\tglad_glGetProgramLocalParameterIivNV = (PFNGLGETPROGRAMLOCALPARAMETERIIVNVPROC)load(\"glGetProgramLocalParameterIivNV\");\n\tglad_glGetProgramLocalParameterIuivNV = (PFNGLGETPROGRAMLOCALPARAMETERIUIVNVPROC)load(\"glGetProgramLocalParameterIuivNV\");\n\tglad_glGetProgramEnvParameterIivNV = (PFNGLGETPROGRAMENVPARAMETERIIVNVPROC)load(\"glGetProgramEnvParameterIivNV\");\n\tglad_glGetProgramEnvParameterIuivNV = (PFNGLGETPROGRAMENVPARAMETERIUIVNVPROC)load(\"glGetProgramEnvParameterIuivNV\");\n}\nstatic void load_GL_NV_gpu_program5(GLADloadproc load) {\n\tif(!GLAD_GL_NV_gpu_program5) return;\n\tglad_glProgramSubroutineParametersuivNV = (PFNGLPROGRAMSUBROUTINEPARAMETERSUIVNVPROC)load(\"glProgramSubroutineParametersuivNV\");\n\tglad_glGetProgramSubroutineParameteruivNV = (PFNGLGETPROGRAMSUBROUTINEPARAMETERUIVNVPROC)load(\"glGetProgramSubroutineParameteruivNV\");\n}\nstatic void load_GL_NV_gpu_shader5(GLADloadproc load) {\n\tif(!GLAD_GL_NV_gpu_shader5) return;\n\tglad_glUniform1i64NV = (PFNGLUNIFORM1I64NVPROC)load(\"glUniform1i64NV\");\n\tglad_glUniform2i64NV = (PFNGLUNIFORM2I64NVPROC)load(\"glUniform2i64NV\");\n\tglad_glUniform3i64NV = (PFNGLUNIFORM3I64NVPROC)load(\"glUniform3i64NV\");\n\tglad_glUniform4i64NV = (PFNGLUNIFORM4I64NVPROC)load(\"glUniform4i64NV\");\n\tglad_glUniform1i64vNV = (PFNGLUNIFORM1I64VNVPROC)load(\"glUniform1i64vNV\");\n\tglad_glUniform2i64vNV = (PFNGLUNIFORM2I64VNVPROC)load(\"glUniform2i64vNV\");\n\tglad_glUniform3i64vNV = (PFNGLUNIFORM3I64VNVPROC)load(\"glUniform3i64vNV\");\n\tglad_glUniform4i64vNV = (PFNGLUNIFORM4I64VNVPROC)load(\"glUniform4i64vNV\");\n\tglad_glUniform1ui64NV = (PFNGLUNIFORM1UI64NVPROC)load(\"glUniform1ui64NV\");\n\tglad_glUniform2ui64NV = (PFNGLUNIFORM2UI64NVPROC)load(\"glUniform2ui64NV\");\n\tglad_glUniform3ui64NV = (PFNGLUNIFORM3UI64NVPROC)load(\"glUniform3ui64NV\");\n\tglad_glUniform4ui64NV = (PFNGLUNIFORM4UI64NVPROC)load(\"glUniform4ui64NV\");\n\tglad_glUniform1ui64vNV = (PFNGLUNIFORM1UI64VNVPROC)load(\"glUniform1ui64vNV\");\n\tglad_glUniform2ui64vNV = (PFNGLUNIFORM2UI64VNVPROC)load(\"glUniform2ui64vNV\");\n\tglad_glUniform3ui64vNV = (PFNGLUNIFORM3UI64VNVPROC)load(\"glUniform3ui64vNV\");\n\tglad_glUniform4ui64vNV = (PFNGLUNIFORM4UI64VNVPROC)load(\"glUniform4ui64vNV\");\n\tglad_glGetUniformi64vNV = (PFNGLGETUNIFORMI64VNVPROC)load(\"glGetUniformi64vNV\");\n\tglad_glProgramUniform1i64NV = (PFNGLPROGRAMUNIFORM1I64NVPROC)load(\"glProgramUniform1i64NV\");\n\tglad_glProgramUniform2i64NV = (PFNGLPROGRAMUNIFORM2I64NVPROC)load(\"glProgramUniform2i64NV\");\n\tglad_glProgramUniform3i64NV = (PFNGLPROGRAMUNIFORM3I64NVPROC)load(\"glProgramUniform3i64NV\");\n\tglad_glProgramUniform4i64NV = (PFNGLPROGRAMUNIFORM4I64NVPROC)load(\"glProgramUniform4i64NV\");\n\tglad_glProgramUniform1i64vNV = (PFNGLPROGRAMUNIFORM1I64VNVPROC)load(\"glProgramUniform1i64vNV\");\n\tglad_glProgramUniform2i64vNV = (PFNGLPROGRAMUNIFORM2I64VNVPROC)load(\"glProgramUniform2i64vNV\");\n\tglad_glProgramUniform3i64vNV = (PFNGLPROGRAMUNIFORM3I64VNVPROC)load(\"glProgramUniform3i64vNV\");\n\tglad_glProgramUniform4i64vNV = (PFNGLPROGRAMUNIFORM4I64VNVPROC)load(\"glProgramUniform4i64vNV\");\n\tglad_glProgramUniform1ui64NV = (PFNGLPROGRAMUNIFORM1UI64NVPROC)load(\"glProgramUniform1ui64NV\");\n\tglad_glProgramUniform2ui64NV = (PFNGLPROGRAMUNIFORM2UI64NVPROC)load(\"glProgramUniform2ui64NV\");\n\tglad_glProgramUniform3ui64NV = (PFNGLPROGRAMUNIFORM3UI64NVPROC)load(\"glProgramUniform3ui64NV\");\n\tglad_glProgramUniform4ui64NV = (PFNGLPROGRAMUNIFORM4UI64NVPROC)load(\"glProgramUniform4ui64NV\");\n\tglad_glProgramUniform1ui64vNV = (PFNGLPROGRAMUNIFORM1UI64VNVPROC)load(\"glProgramUniform1ui64vNV\");\n\tglad_glProgramUniform2ui64vNV = (PFNGLPROGRAMUNIFORM2UI64VNVPROC)load(\"glProgramUniform2ui64vNV\");\n\tglad_glProgramUniform3ui64vNV = (PFNGLPROGRAMUNIFORM3UI64VNVPROC)load(\"glProgramUniform3ui64vNV\");\n\tglad_glProgramUniform4ui64vNV = (PFNGLPROGRAMUNIFORM4UI64VNVPROC)load(\"glProgramUniform4ui64vNV\");\n}\nstatic void load_GL_NV_half_float(GLADloadproc load) {\n\tif(!GLAD_GL_NV_half_float) return;\n\tglad_glVertex2hNV = (PFNGLVERTEX2HNVPROC)load(\"glVertex2hNV\");\n\tglad_glVertex2hvNV = (PFNGLVERTEX2HVNVPROC)load(\"glVertex2hvNV\");\n\tglad_glVertex3hNV = (PFNGLVERTEX3HNVPROC)load(\"glVertex3hNV\");\n\tglad_glVertex3hvNV = (PFNGLVERTEX3HVNVPROC)load(\"glVertex3hvNV\");\n\tglad_glVertex4hNV = (PFNGLVERTEX4HNVPROC)load(\"glVertex4hNV\");\n\tglad_glVertex4hvNV = (PFNGLVERTEX4HVNVPROC)load(\"glVertex4hvNV\");\n\tglad_glNormal3hNV = (PFNGLNORMAL3HNVPROC)load(\"glNormal3hNV\");\n\tglad_glNormal3hvNV = (PFNGLNORMAL3HVNVPROC)load(\"glNormal3hvNV\");\n\tglad_glColor3hNV = (PFNGLCOLOR3HNVPROC)load(\"glColor3hNV\");\n\tglad_glColor3hvNV = (PFNGLCOLOR3HVNVPROC)load(\"glColor3hvNV\");\n\tglad_glColor4hNV = (PFNGLCOLOR4HNVPROC)load(\"glColor4hNV\");\n\tglad_glColor4hvNV = (PFNGLCOLOR4HVNVPROC)load(\"glColor4hvNV\");\n\tglad_glTexCoord1hNV = (PFNGLTEXCOORD1HNVPROC)load(\"glTexCoord1hNV\");\n\tglad_glTexCoord1hvNV = (PFNGLTEXCOORD1HVNVPROC)load(\"glTexCoord1hvNV\");\n\tglad_glTexCoord2hNV = (PFNGLTEXCOORD2HNVPROC)load(\"glTexCoord2hNV\");\n\tglad_glTexCoord2hvNV = (PFNGLTEXCOORD2HVNVPROC)load(\"glTexCoord2hvNV\");\n\tglad_glTexCoord3hNV = (PFNGLTEXCOORD3HNVPROC)load(\"glTexCoord3hNV\");\n\tglad_glTexCoord3hvNV = (PFNGLTEXCOORD3HVNVPROC)load(\"glTexCoord3hvNV\");\n\tglad_glTexCoord4hNV = (PFNGLTEXCOORD4HNVPROC)load(\"glTexCoord4hNV\");\n\tglad_glTexCoord4hvNV = (PFNGLTEXCOORD4HVNVPROC)load(\"glTexCoord4hvNV\");\n\tglad_glMultiTexCoord1hNV = (PFNGLMULTITEXCOORD1HNVPROC)load(\"glMultiTexCoord1hNV\");\n\tglad_glMultiTexCoord1hvNV = (PFNGLMULTITEXCOORD1HVNVPROC)load(\"glMultiTexCoord1hvNV\");\n\tglad_glMultiTexCoord2hNV = (PFNGLMULTITEXCOORD2HNVPROC)load(\"glMultiTexCoord2hNV\");\n\tglad_glMultiTexCoord2hvNV = (PFNGLMULTITEXCOORD2HVNVPROC)load(\"glMultiTexCoord2hvNV\");\n\tglad_glMultiTexCoord3hNV = (PFNGLMULTITEXCOORD3HNVPROC)load(\"glMultiTexCoord3hNV\");\n\tglad_glMultiTexCoord3hvNV = (PFNGLMULTITEXCOORD3HVNVPROC)load(\"glMultiTexCoord3hvNV\");\n\tglad_glMultiTexCoord4hNV = (PFNGLMULTITEXCOORD4HNVPROC)load(\"glMultiTexCoord4hNV\");\n\tglad_glMultiTexCoord4hvNV = (PFNGLMULTITEXCOORD4HVNVPROC)load(\"glMultiTexCoord4hvNV\");\n\tglad_glFogCoordhNV = (PFNGLFOGCOORDHNVPROC)load(\"glFogCoordhNV\");\n\tglad_glFogCoordhvNV = (PFNGLFOGCOORDHVNVPROC)load(\"glFogCoordhvNV\");\n\tglad_glSecondaryColor3hNV = (PFNGLSECONDARYCOLOR3HNVPROC)load(\"glSecondaryColor3hNV\");\n\tglad_glSecondaryColor3hvNV = (PFNGLSECONDARYCOLOR3HVNVPROC)load(\"glSecondaryColor3hvNV\");\n\tglad_glVertexWeighthNV = (PFNGLVERTEXWEIGHTHNVPROC)load(\"glVertexWeighthNV\");\n\tglad_glVertexWeighthvNV = (PFNGLVERTEXWEIGHTHVNVPROC)load(\"glVertexWeighthvNV\");\n\tglad_glVertexAttrib1hNV = (PFNGLVERTEXATTRIB1HNVPROC)load(\"glVertexAttrib1hNV\");\n\tglad_glVertexAttrib1hvNV = (PFNGLVERTEXATTRIB1HVNVPROC)load(\"glVertexAttrib1hvNV\");\n\tglad_glVertexAttrib2hNV = (PFNGLVERTEXATTRIB2HNVPROC)load(\"glVertexAttrib2hNV\");\n\tglad_glVertexAttrib2hvNV = (PFNGLVERTEXATTRIB2HVNVPROC)load(\"glVertexAttrib2hvNV\");\n\tglad_glVertexAttrib3hNV = (PFNGLVERTEXATTRIB3HNVPROC)load(\"glVertexAttrib3hNV\");\n\tglad_glVertexAttrib3hvNV = (PFNGLVERTEXATTRIB3HVNVPROC)load(\"glVertexAttrib3hvNV\");\n\tglad_glVertexAttrib4hNV = (PFNGLVERTEXATTRIB4HNVPROC)load(\"glVertexAttrib4hNV\");\n\tglad_glVertexAttrib4hvNV = (PFNGLVERTEXATTRIB4HVNVPROC)load(\"glVertexAttrib4hvNV\");\n\tglad_glVertexAttribs1hvNV = (PFNGLVERTEXATTRIBS1HVNVPROC)load(\"glVertexAttribs1hvNV\");\n\tglad_glVertexAttribs2hvNV = (PFNGLVERTEXATTRIBS2HVNVPROC)load(\"glVertexAttribs2hvNV\");\n\tglad_glVertexAttribs3hvNV = (PFNGLVERTEXATTRIBS3HVNVPROC)load(\"glVertexAttribs3hvNV\");\n\tglad_glVertexAttribs4hvNV = (PFNGLVERTEXATTRIBS4HVNVPROC)load(\"glVertexAttribs4hvNV\");\n}\nstatic void load_GL_NV_internalformat_sample_query(GLADloadproc load) {\n\tif(!GLAD_GL_NV_internalformat_sample_query) return;\n\tglad_glGetInternalformatSampleivNV = (PFNGLGETINTERNALFORMATSAMPLEIVNVPROC)load(\"glGetInternalformatSampleivNV\");\n}\nstatic void load_GL_NV_memory_attachment(GLADloadproc load) {\n\tif(!GLAD_GL_NV_memory_attachment) return;\n\tglad_glGetMemoryObjectDetachedResourcesuivNV = (PFNGLGETMEMORYOBJECTDETACHEDRESOURCESUIVNVPROC)load(\"glGetMemoryObjectDetachedResourcesuivNV\");\n\tglad_glResetMemoryObjectParameterNV = (PFNGLRESETMEMORYOBJECTPARAMETERNVPROC)load(\"glResetMemoryObjectParameterNV\");\n\tglad_glTexAttachMemoryNV = (PFNGLTEXATTACHMEMORYNVPROC)load(\"glTexAttachMemoryNV\");\n\tglad_glBufferAttachMemoryNV = (PFNGLBUFFERATTACHMEMORYNVPROC)load(\"glBufferAttachMemoryNV\");\n\tglad_glTextureAttachMemoryNV = (PFNGLTEXTUREATTACHMEMORYNVPROC)load(\"glTextureAttachMemoryNV\");\n\tglad_glNamedBufferAttachMemoryNV = (PFNGLNAMEDBUFFERATTACHMEMORYNVPROC)load(\"glNamedBufferAttachMemoryNV\");\n}\nstatic void load_GL_NV_mesh_shader(GLADloadproc load) {\n\tif(!GLAD_GL_NV_mesh_shader) return;\n\tglad_glDrawMeshTasksNV = (PFNGLDRAWMESHTASKSNVPROC)load(\"glDrawMeshTasksNV\");\n\tglad_glDrawMeshTasksIndirectNV = (PFNGLDRAWMESHTASKSINDIRECTNVPROC)load(\"glDrawMeshTasksIndirectNV\");\n\tglad_glMultiDrawMeshTasksIndirectNV = (PFNGLMULTIDRAWMESHTASKSINDIRECTNVPROC)load(\"glMultiDrawMeshTasksIndirectNV\");\n\tglad_glMultiDrawMeshTasksIndirectCountNV = (PFNGLMULTIDRAWMESHTASKSINDIRECTCOUNTNVPROC)load(\"glMultiDrawMeshTasksIndirectCountNV\");\n}\nstatic void load_GL_NV_occlusion_query(GLADloadproc load) {\n\tif(!GLAD_GL_NV_occlusion_query) return;\n\tglad_glGenOcclusionQueriesNV = (PFNGLGENOCCLUSIONQUERIESNVPROC)load(\"glGenOcclusionQueriesNV\");\n\tglad_glDeleteOcclusionQueriesNV = (PFNGLDELETEOCCLUSIONQUERIESNVPROC)load(\"glDeleteOcclusionQueriesNV\");\n\tglad_glIsOcclusionQueryNV = (PFNGLISOCCLUSIONQUERYNVPROC)load(\"glIsOcclusionQueryNV\");\n\tglad_glBeginOcclusionQueryNV = (PFNGLBEGINOCCLUSIONQUERYNVPROC)load(\"glBeginOcclusionQueryNV\");\n\tglad_glEndOcclusionQueryNV = (PFNGLENDOCCLUSIONQUERYNVPROC)load(\"glEndOcclusionQueryNV\");\n\tglad_glGetOcclusionQueryivNV = (PFNGLGETOCCLUSIONQUERYIVNVPROC)load(\"glGetOcclusionQueryivNV\");\n\tglad_glGetOcclusionQueryuivNV = (PFNGLGETOCCLUSIONQUERYUIVNVPROC)load(\"glGetOcclusionQueryuivNV\");\n}\nstatic void load_GL_NV_parameter_buffer_object(GLADloadproc load) {\n\tif(!GLAD_GL_NV_parameter_buffer_object) return;\n\tglad_glProgramBufferParametersfvNV = (PFNGLPROGRAMBUFFERPARAMETERSFVNVPROC)load(\"glProgramBufferParametersfvNV\");\n\tglad_glProgramBufferParametersIivNV = (PFNGLPROGRAMBUFFERPARAMETERSIIVNVPROC)load(\"glProgramBufferParametersIivNV\");\n\tglad_glProgramBufferParametersIuivNV = (PFNGLPROGRAMBUFFERPARAMETERSIUIVNVPROC)load(\"glProgramBufferParametersIuivNV\");\n}\nstatic void load_GL_NV_path_rendering(GLADloadproc load) {\n\tif(!GLAD_GL_NV_path_rendering) return;\n\tglad_glGenPathsNV = (PFNGLGENPATHSNVPROC)load(\"glGenPathsNV\");\n\tglad_glDeletePathsNV = (PFNGLDELETEPATHSNVPROC)load(\"glDeletePathsNV\");\n\tglad_glIsPathNV = (PFNGLISPATHNVPROC)load(\"glIsPathNV\");\n\tglad_glPathCommandsNV = (PFNGLPATHCOMMANDSNVPROC)load(\"glPathCommandsNV\");\n\tglad_glPathCoordsNV = (PFNGLPATHCOORDSNVPROC)load(\"glPathCoordsNV\");\n\tglad_glPathSubCommandsNV = (PFNGLPATHSUBCOMMANDSNVPROC)load(\"glPathSubCommandsNV\");\n\tglad_glPathSubCoordsNV = (PFNGLPATHSUBCOORDSNVPROC)load(\"glPathSubCoordsNV\");\n\tglad_glPathStringNV = (PFNGLPATHSTRINGNVPROC)load(\"glPathStringNV\");\n\tglad_glPathGlyphsNV = (PFNGLPATHGLYPHSNVPROC)load(\"glPathGlyphsNV\");\n\tglad_glPathGlyphRangeNV = (PFNGLPATHGLYPHRANGENVPROC)load(\"glPathGlyphRangeNV\");\n\tglad_glWeightPathsNV = (PFNGLWEIGHTPATHSNVPROC)load(\"glWeightPathsNV\");\n\tglad_glCopyPathNV = (PFNGLCOPYPATHNVPROC)load(\"glCopyPathNV\");\n\tglad_glInterpolatePathsNV = (PFNGLINTERPOLATEPATHSNVPROC)load(\"glInterpolatePathsNV\");\n\tglad_glTransformPathNV = (PFNGLTRANSFORMPATHNVPROC)load(\"glTransformPathNV\");\n\tglad_glPathParameterivNV = (PFNGLPATHPARAMETERIVNVPROC)load(\"glPathParameterivNV\");\n\tglad_glPathParameteriNV = (PFNGLPATHPARAMETERINVPROC)load(\"glPathParameteriNV\");\n\tglad_glPathParameterfvNV = (PFNGLPATHPARAMETERFVNVPROC)load(\"glPathParameterfvNV\");\n\tglad_glPathParameterfNV = (PFNGLPATHPARAMETERFNVPROC)load(\"glPathParameterfNV\");\n\tglad_glPathDashArrayNV = (PFNGLPATHDASHARRAYNVPROC)load(\"glPathDashArrayNV\");\n\tglad_glPathStencilFuncNV = (PFNGLPATHSTENCILFUNCNVPROC)load(\"glPathStencilFuncNV\");\n\tglad_glPathStencilDepthOffsetNV = (PFNGLPATHSTENCILDEPTHOFFSETNVPROC)load(\"glPathStencilDepthOffsetNV\");\n\tglad_glStencilFillPathNV = (PFNGLSTENCILFILLPATHNVPROC)load(\"glStencilFillPathNV\");\n\tglad_glStencilStrokePathNV = (PFNGLSTENCILSTROKEPATHNVPROC)load(\"glStencilStrokePathNV\");\n\tglad_glStencilFillPathInstancedNV = (PFNGLSTENCILFILLPATHINSTANCEDNVPROC)load(\"glStencilFillPathInstancedNV\");\n\tglad_glStencilStrokePathInstancedNV = (PFNGLSTENCILSTROKEPATHINSTANCEDNVPROC)load(\"glStencilStrokePathInstancedNV\");\n\tglad_glPathCoverDepthFuncNV = (PFNGLPATHCOVERDEPTHFUNCNVPROC)load(\"glPathCoverDepthFuncNV\");\n\tglad_glCoverFillPathNV = (PFNGLCOVERFILLPATHNVPROC)load(\"glCoverFillPathNV\");\n\tglad_glCoverStrokePathNV = (PFNGLCOVERSTROKEPATHNVPROC)load(\"glCoverStrokePathNV\");\n\tglad_glCoverFillPathInstancedNV = (PFNGLCOVERFILLPATHINSTANCEDNVPROC)load(\"glCoverFillPathInstancedNV\");\n\tglad_glCoverStrokePathInstancedNV = (PFNGLCOVERSTROKEPATHINSTANCEDNVPROC)load(\"glCoverStrokePathInstancedNV\");\n\tglad_glGetPathParameterivNV = (PFNGLGETPATHPARAMETERIVNVPROC)load(\"glGetPathParameterivNV\");\n\tglad_glGetPathParameterfvNV = (PFNGLGETPATHPARAMETERFVNVPROC)load(\"glGetPathParameterfvNV\");\n\tglad_glGetPathCommandsNV = (PFNGLGETPATHCOMMANDSNVPROC)load(\"glGetPathCommandsNV\");\n\tglad_glGetPathCoordsNV = (PFNGLGETPATHCOORDSNVPROC)load(\"glGetPathCoordsNV\");\n\tglad_glGetPathDashArrayNV = (PFNGLGETPATHDASHARRAYNVPROC)load(\"glGetPathDashArrayNV\");\n\tglad_glGetPathMetricsNV = (PFNGLGETPATHMETRICSNVPROC)load(\"glGetPathMetricsNV\");\n\tglad_glGetPathMetricRangeNV = (PFNGLGETPATHMETRICRANGENVPROC)load(\"glGetPathMetricRangeNV\");\n\tglad_glGetPathSpacingNV = (PFNGLGETPATHSPACINGNVPROC)load(\"glGetPathSpacingNV\");\n\tglad_glIsPointInFillPathNV = (PFNGLISPOINTINFILLPATHNVPROC)load(\"glIsPointInFillPathNV\");\n\tglad_glIsPointInStrokePathNV = (PFNGLISPOINTINSTROKEPATHNVPROC)load(\"glIsPointInStrokePathNV\");\n\tglad_glGetPathLengthNV = (PFNGLGETPATHLENGTHNVPROC)load(\"glGetPathLengthNV\");\n\tglad_glPointAlongPathNV = (PFNGLPOINTALONGPATHNVPROC)load(\"glPointAlongPathNV\");\n\tglad_glMatrixLoad3x2fNV = (PFNGLMATRIXLOAD3X2FNVPROC)load(\"glMatrixLoad3x2fNV\");\n\tglad_glMatrixLoad3x3fNV = (PFNGLMATRIXLOAD3X3FNVPROC)load(\"glMatrixLoad3x3fNV\");\n\tglad_glMatrixLoadTranspose3x3fNV = (PFNGLMATRIXLOADTRANSPOSE3X3FNVPROC)load(\"glMatrixLoadTranspose3x3fNV\");\n\tglad_glMatrixMult3x2fNV = (PFNGLMATRIXMULT3X2FNVPROC)load(\"glMatrixMult3x2fNV\");\n\tglad_glMatrixMult3x3fNV = (PFNGLMATRIXMULT3X3FNVPROC)load(\"glMatrixMult3x3fNV\");\n\tglad_glMatrixMultTranspose3x3fNV = (PFNGLMATRIXMULTTRANSPOSE3X3FNVPROC)load(\"glMatrixMultTranspose3x3fNV\");\n\tglad_glStencilThenCoverFillPathNV = (PFNGLSTENCILTHENCOVERFILLPATHNVPROC)load(\"glStencilThenCoverFillPathNV\");\n\tglad_glStencilThenCoverStrokePathNV = (PFNGLSTENCILTHENCOVERSTROKEPATHNVPROC)load(\"glStencilThenCoverStrokePathNV\");\n\tglad_glStencilThenCoverFillPathInstancedNV = (PFNGLSTENCILTHENCOVERFILLPATHINSTANCEDNVPROC)load(\"glStencilThenCoverFillPathInstancedNV\");\n\tglad_glStencilThenCoverStrokePathInstancedNV = (PFNGLSTENCILTHENCOVERSTROKEPATHINSTANCEDNVPROC)load(\"glStencilThenCoverStrokePathInstancedNV\");\n\tglad_glPathGlyphIndexRangeNV = (PFNGLPATHGLYPHINDEXRANGENVPROC)load(\"glPathGlyphIndexRangeNV\");\n\tglad_glPathGlyphIndexArrayNV = (PFNGLPATHGLYPHINDEXARRAYNVPROC)load(\"glPathGlyphIndexArrayNV\");\n\tglad_glPathMemoryGlyphIndexArrayNV = (PFNGLPATHMEMORYGLYPHINDEXARRAYNVPROC)load(\"glPathMemoryGlyphIndexArrayNV\");\n\tglad_glProgramPathFragmentInputGenNV = (PFNGLPROGRAMPATHFRAGMENTINPUTGENNVPROC)load(\"glProgramPathFragmentInputGenNV\");\n\tglad_glGetProgramResourcefvNV = (PFNGLGETPROGRAMRESOURCEFVNVPROC)load(\"glGetProgramResourcefvNV\");\n\tglad_glPathColorGenNV = (PFNGLPATHCOLORGENNVPROC)load(\"glPathColorGenNV\");\n\tglad_glPathTexGenNV = (PFNGLPATHTEXGENNVPROC)load(\"glPathTexGenNV\");\n\tglad_glPathFogGenNV = (PFNGLPATHFOGGENNVPROC)load(\"glPathFogGenNV\");\n\tglad_glGetPathColorGenivNV = (PFNGLGETPATHCOLORGENIVNVPROC)load(\"glGetPathColorGenivNV\");\n\tglad_glGetPathColorGenfvNV = (PFNGLGETPATHCOLORGENFVNVPROC)load(\"glGetPathColorGenfvNV\");\n\tglad_glGetPathTexGenivNV = (PFNGLGETPATHTEXGENIVNVPROC)load(\"glGetPathTexGenivNV\");\n\tglad_glGetPathTexGenfvNV = (PFNGLGETPATHTEXGENFVNVPROC)load(\"glGetPathTexGenfvNV\");\n\tglad_glMatrixFrustumEXT = (PFNGLMATRIXFRUSTUMEXTPROC)load(\"glMatrixFrustumEXT\");\n\tglad_glMatrixLoadIdentityEXT = (PFNGLMATRIXLOADIDENTITYEXTPROC)load(\"glMatrixLoadIdentityEXT\");\n\tglad_glMatrixLoadTransposefEXT = (PFNGLMATRIXLOADTRANSPOSEFEXTPROC)load(\"glMatrixLoadTransposefEXT\");\n\tglad_glMatrixLoadTransposedEXT = (PFNGLMATRIXLOADTRANSPOSEDEXTPROC)load(\"glMatrixLoadTransposedEXT\");\n\tglad_glMatrixLoadfEXT = (PFNGLMATRIXLOADFEXTPROC)load(\"glMatrixLoadfEXT\");\n\tglad_glMatrixLoaddEXT = (PFNGLMATRIXLOADDEXTPROC)load(\"glMatrixLoaddEXT\");\n\tglad_glMatrixMultTransposefEXT = (PFNGLMATRIXMULTTRANSPOSEFEXTPROC)load(\"glMatrixMultTransposefEXT\");\n\tglad_glMatrixMultTransposedEXT = (PFNGLMATRIXMULTTRANSPOSEDEXTPROC)load(\"glMatrixMultTransposedEXT\");\n\tglad_glMatrixMultfEXT = (PFNGLMATRIXMULTFEXTPROC)load(\"glMatrixMultfEXT\");\n\tglad_glMatrixMultdEXT = (PFNGLMATRIXMULTDEXTPROC)load(\"glMatrixMultdEXT\");\n\tglad_glMatrixOrthoEXT = (PFNGLMATRIXORTHOEXTPROC)load(\"glMatrixOrthoEXT\");\n\tglad_glMatrixPopEXT = (PFNGLMATRIXPOPEXTPROC)load(\"glMatrixPopEXT\");\n\tglad_glMatrixPushEXT = (PFNGLMATRIXPUSHEXTPROC)load(\"glMatrixPushEXT\");\n\tglad_glMatrixRotatefEXT = (PFNGLMATRIXROTATEFEXTPROC)load(\"glMatrixRotatefEXT\");\n\tglad_glMatrixRotatedEXT = (PFNGLMATRIXROTATEDEXTPROC)load(\"glMatrixRotatedEXT\");\n\tglad_glMatrixScalefEXT = (PFNGLMATRIXSCALEFEXTPROC)load(\"glMatrixScalefEXT\");\n\tglad_glMatrixScaledEXT = (PFNGLMATRIXSCALEDEXTPROC)load(\"glMatrixScaledEXT\");\n\tglad_glMatrixTranslatefEXT = (PFNGLMATRIXTRANSLATEFEXTPROC)load(\"glMatrixTranslatefEXT\");\n\tglad_glMatrixTranslatedEXT = (PFNGLMATRIXTRANSLATEDEXTPROC)load(\"glMatrixTranslatedEXT\");\n}\nstatic void load_GL_NV_pixel_data_range(GLADloadproc load) {\n\tif(!GLAD_GL_NV_pixel_data_range) return;\n\tglad_glPixelDataRangeNV = (PFNGLPIXELDATARANGENVPROC)load(\"glPixelDataRangeNV\");\n\tglad_glFlushPixelDataRangeNV = (PFNGLFLUSHPIXELDATARANGENVPROC)load(\"glFlushPixelDataRangeNV\");\n}\nstatic void load_GL_NV_point_sprite(GLADloadproc load) {\n\tif(!GLAD_GL_NV_point_sprite) return;\n\tglad_glPointParameteriNV = (PFNGLPOINTPARAMETERINVPROC)load(\"glPointParameteriNV\");\n\tglad_glPointParameterivNV = (PFNGLPOINTPARAMETERIVNVPROC)load(\"glPointParameterivNV\");\n}\nstatic void load_GL_NV_present_video(GLADloadproc load) {\n\tif(!GLAD_GL_NV_present_video) return;\n\tglad_glPresentFrameKeyedNV = (PFNGLPRESENTFRAMEKEYEDNVPROC)load(\"glPresentFrameKeyedNV\");\n\tglad_glPresentFrameDualFillNV = (PFNGLPRESENTFRAMEDUALFILLNVPROC)load(\"glPresentFrameDualFillNV\");\n\tglad_glGetVideoivNV = (PFNGLGETVIDEOIVNVPROC)load(\"glGetVideoivNV\");\n\tglad_glGetVideouivNV = (PFNGLGETVIDEOUIVNVPROC)load(\"glGetVideouivNV\");\n\tglad_glGetVideoi64vNV = (PFNGLGETVIDEOI64VNVPROC)load(\"glGetVideoi64vNV\");\n\tglad_glGetVideoui64vNV = (PFNGLGETVIDEOUI64VNVPROC)load(\"glGetVideoui64vNV\");\n}\nstatic void load_GL_NV_primitive_restart(GLADloadproc load) {\n\tif(!GLAD_GL_NV_primitive_restart) return;\n\tglad_glPrimitiveRestartNV = (PFNGLPRIMITIVERESTARTNVPROC)load(\"glPrimitiveRestartNV\");\n\tglad_glPrimitiveRestartIndexNV = (PFNGLPRIMITIVERESTARTINDEXNVPROC)load(\"glPrimitiveRestartIndexNV\");\n}\nstatic void load_GL_NV_query_resource(GLADloadproc load) {\n\tif(!GLAD_GL_NV_query_resource) return;\n\tglad_glQueryResourceNV = (PFNGLQUERYRESOURCENVPROC)load(\"glQueryResourceNV\");\n}\nstatic void load_GL_NV_query_resource_tag(GLADloadproc load) {\n\tif(!GLAD_GL_NV_query_resource_tag) return;\n\tglad_glGenQueryResourceTagNV = (PFNGLGENQUERYRESOURCETAGNVPROC)load(\"glGenQueryResourceTagNV\");\n\tglad_glDeleteQueryResourceTagNV = (PFNGLDELETEQUERYRESOURCETAGNVPROC)load(\"glDeleteQueryResourceTagNV\");\n\tglad_glQueryResourceTagNV = (PFNGLQUERYRESOURCETAGNVPROC)load(\"glQueryResourceTagNV\");\n}\nstatic void load_GL_NV_register_combiners(GLADloadproc load) {\n\tif(!GLAD_GL_NV_register_combiners) return;\n\tglad_glCombinerParameterfvNV = (PFNGLCOMBINERPARAMETERFVNVPROC)load(\"glCombinerParameterfvNV\");\n\tglad_glCombinerParameterfNV = (PFNGLCOMBINERPARAMETERFNVPROC)load(\"glCombinerParameterfNV\");\n\tglad_glCombinerParameterivNV = (PFNGLCOMBINERPARAMETERIVNVPROC)load(\"glCombinerParameterivNV\");\n\tglad_glCombinerParameteriNV = (PFNGLCOMBINERPARAMETERINVPROC)load(\"glCombinerParameteriNV\");\n\tglad_glCombinerInputNV = (PFNGLCOMBINERINPUTNVPROC)load(\"glCombinerInputNV\");\n\tglad_glCombinerOutputNV = (PFNGLCOMBINEROUTPUTNVPROC)load(\"glCombinerOutputNV\");\n\tglad_glFinalCombinerInputNV = (PFNGLFINALCOMBINERINPUTNVPROC)load(\"glFinalCombinerInputNV\");\n\tglad_glGetCombinerInputParameterfvNV = (PFNGLGETCOMBINERINPUTPARAMETERFVNVPROC)load(\"glGetCombinerInputParameterfvNV\");\n\tglad_glGetCombinerInputParameterivNV = (PFNGLGETCOMBINERINPUTPARAMETERIVNVPROC)load(\"glGetCombinerInputParameterivNV\");\n\tglad_glGetCombinerOutputParameterfvNV = (PFNGLGETCOMBINEROUTPUTPARAMETERFVNVPROC)load(\"glGetCombinerOutputParameterfvNV\");\n\tglad_glGetCombinerOutputParameterivNV = (PFNGLGETCOMBINEROUTPUTPARAMETERIVNVPROC)load(\"glGetCombinerOutputParameterivNV\");\n\tglad_glGetFinalCombinerInputParameterfvNV = (PFNGLGETFINALCOMBINERINPUTPARAMETERFVNVPROC)load(\"glGetFinalCombinerInputParameterfvNV\");\n\tglad_glGetFinalCombinerInputParameterivNV = (PFNGLGETFINALCOMBINERINPUTPARAMETERIVNVPROC)load(\"glGetFinalCombinerInputParameterivNV\");\n}\nstatic void load_GL_NV_register_combiners2(GLADloadproc load) {\n\tif(!GLAD_GL_NV_register_combiners2) return;\n\tglad_glCombinerStageParameterfvNV = (PFNGLCOMBINERSTAGEPARAMETERFVNVPROC)load(\"glCombinerStageParameterfvNV\");\n\tglad_glGetCombinerStageParameterfvNV = (PFNGLGETCOMBINERSTAGEPARAMETERFVNVPROC)load(\"glGetCombinerStageParameterfvNV\");\n}\nstatic void load_GL_NV_sample_locations(GLADloadproc load) {\n\tif(!GLAD_GL_NV_sample_locations) return;\n\tglad_glFramebufferSampleLocationsfvNV = (PFNGLFRAMEBUFFERSAMPLELOCATIONSFVNVPROC)load(\"glFramebufferSampleLocationsfvNV\");\n\tglad_glNamedFramebufferSampleLocationsfvNV = (PFNGLNAMEDFRAMEBUFFERSAMPLELOCATIONSFVNVPROC)load(\"glNamedFramebufferSampleLocationsfvNV\");\n\tglad_glResolveDepthValuesNV = (PFNGLRESOLVEDEPTHVALUESNVPROC)load(\"glResolveDepthValuesNV\");\n}\nstatic void load_GL_NV_scissor_exclusive(GLADloadproc load) {\n\tif(!GLAD_GL_NV_scissor_exclusive) return;\n\tglad_glScissorExclusiveNV = (PFNGLSCISSOREXCLUSIVENVPROC)load(\"glScissorExclusiveNV\");\n\tglad_glScissorExclusiveArrayvNV = (PFNGLSCISSOREXCLUSIVEARRAYVNVPROC)load(\"glScissorExclusiveArrayvNV\");\n}\nstatic void load_GL_NV_shader_buffer_load(GLADloadproc load) {\n\tif(!GLAD_GL_NV_shader_buffer_load) return;\n\tglad_glMakeBufferResidentNV = (PFNGLMAKEBUFFERRESIDENTNVPROC)load(\"glMakeBufferResidentNV\");\n\tglad_glMakeBufferNonResidentNV = (PFNGLMAKEBUFFERNONRESIDENTNVPROC)load(\"glMakeBufferNonResidentNV\");\n\tglad_glIsBufferResidentNV = (PFNGLISBUFFERRESIDENTNVPROC)load(\"glIsBufferResidentNV\");\n\tglad_glMakeNamedBufferResidentNV = (PFNGLMAKENAMEDBUFFERRESIDENTNVPROC)load(\"glMakeNamedBufferResidentNV\");\n\tglad_glMakeNamedBufferNonResidentNV = (PFNGLMAKENAMEDBUFFERNONRESIDENTNVPROC)load(\"glMakeNamedBufferNonResidentNV\");\n\tglad_glIsNamedBufferResidentNV = (PFNGLISNAMEDBUFFERRESIDENTNVPROC)load(\"glIsNamedBufferResidentNV\");\n\tglad_glGetBufferParameterui64vNV = (PFNGLGETBUFFERPARAMETERUI64VNVPROC)load(\"glGetBufferParameterui64vNV\");\n\tglad_glGetNamedBufferParameterui64vNV = (PFNGLGETNAMEDBUFFERPARAMETERUI64VNVPROC)load(\"glGetNamedBufferParameterui64vNV\");\n\tglad_glGetIntegerui64vNV = (PFNGLGETINTEGERUI64VNVPROC)load(\"glGetIntegerui64vNV\");\n\tglad_glUniformui64NV = (PFNGLUNIFORMUI64NVPROC)load(\"glUniformui64NV\");\n\tglad_glUniformui64vNV = (PFNGLUNIFORMUI64VNVPROC)load(\"glUniformui64vNV\");\n\tglad_glGetUniformui64vNV = (PFNGLGETUNIFORMUI64VNVPROC)load(\"glGetUniformui64vNV\");\n\tglad_glProgramUniformui64NV = (PFNGLPROGRAMUNIFORMUI64NVPROC)load(\"glProgramUniformui64NV\");\n\tglad_glProgramUniformui64vNV = (PFNGLPROGRAMUNIFORMUI64VNVPROC)load(\"glProgramUniformui64vNV\");\n}\nstatic void load_GL_NV_shading_rate_image(GLADloadproc load) {\n\tif(!GLAD_GL_NV_shading_rate_image) return;\n\tglad_glBindShadingRateImageNV = (PFNGLBINDSHADINGRATEIMAGENVPROC)load(\"glBindShadingRateImageNV\");\n\tglad_glGetShadingRateImagePaletteNV = (PFNGLGETSHADINGRATEIMAGEPALETTENVPROC)load(\"glGetShadingRateImagePaletteNV\");\n\tglad_glGetShadingRateSampleLocationivNV = (PFNGLGETSHADINGRATESAMPLELOCATIONIVNVPROC)load(\"glGetShadingRateSampleLocationivNV\");\n\tglad_glShadingRateImageBarrierNV = (PFNGLSHADINGRATEIMAGEBARRIERNVPROC)load(\"glShadingRateImageBarrierNV\");\n\tglad_glShadingRateImageBarrierNV = (PFNGLSHADINGRATEIMAGEBARRIERNVPROC)load(\"glShadingRateImageBarrierNV\");\n\tglad_glShadingRateImagePaletteNV = (PFNGLSHADINGRATEIMAGEPALETTENVPROC)load(\"glShadingRateImagePaletteNV\");\n\tglad_glShadingRateSampleOrderNV = (PFNGLSHADINGRATESAMPLEORDERNVPROC)load(\"glShadingRateSampleOrderNV\");\n\tglad_glShadingRateSampleOrderCustomNV = (PFNGLSHADINGRATESAMPLEORDERCUSTOMNVPROC)load(\"glShadingRateSampleOrderCustomNV\");\n}\nstatic void load_GL_NV_texture_barrier(GLADloadproc load) {\n\tif(!GLAD_GL_NV_texture_barrier) return;\n\tglad_glTextureBarrierNV = (PFNGLTEXTUREBARRIERNVPROC)load(\"glTextureBarrierNV\");\n}\nstatic void load_GL_NV_texture_multisample(GLADloadproc load) {\n\tif(!GLAD_GL_NV_texture_multisample) return;\n\tglad_glTexImage2DMultisampleCoverageNV = (PFNGLTEXIMAGE2DMULTISAMPLECOVERAGENVPROC)load(\"glTexImage2DMultisampleCoverageNV\");\n\tglad_glTexImage3DMultisampleCoverageNV = (PFNGLTEXIMAGE3DMULTISAMPLECOVERAGENVPROC)load(\"glTexImage3DMultisampleCoverageNV\");\n\tglad_glTextureImage2DMultisampleNV = (PFNGLTEXTUREIMAGE2DMULTISAMPLENVPROC)load(\"glTextureImage2DMultisampleNV\");\n\tglad_glTextureImage3DMultisampleNV = (PFNGLTEXTUREIMAGE3DMULTISAMPLENVPROC)load(\"glTextureImage3DMultisampleNV\");\n\tglad_glTextureImage2DMultisampleCoverageNV = (PFNGLTEXTUREIMAGE2DMULTISAMPLECOVERAGENVPROC)load(\"glTextureImage2DMultisampleCoverageNV\");\n\tglad_glTextureImage3DMultisampleCoverageNV = (PFNGLTEXTUREIMAGE3DMULTISAMPLECOVERAGENVPROC)load(\"glTextureImage3DMultisampleCoverageNV\");\n}\nstatic void load_GL_NV_transform_feedback(GLADloadproc load) {\n\tif(!GLAD_GL_NV_transform_feedback) return;\n\tglad_glBeginTransformFeedbackNV = (PFNGLBEGINTRANSFORMFEEDBACKNVPROC)load(\"glBeginTransformFeedbackNV\");\n\tglad_glEndTransformFeedbackNV = (PFNGLENDTRANSFORMFEEDBACKNVPROC)load(\"glEndTransformFeedbackNV\");\n\tglad_glTransformFeedbackAttribsNV = (PFNGLTRANSFORMFEEDBACKATTRIBSNVPROC)load(\"glTransformFeedbackAttribsNV\");\n\tglad_glBindBufferRangeNV = (PFNGLBINDBUFFERRANGENVPROC)load(\"glBindBufferRangeNV\");\n\tglad_glBindBufferOffsetNV = (PFNGLBINDBUFFEROFFSETNVPROC)load(\"glBindBufferOffsetNV\");\n\tglad_glBindBufferBaseNV = (PFNGLBINDBUFFERBASENVPROC)load(\"glBindBufferBaseNV\");\n\tglad_glTransformFeedbackVaryingsNV = (PFNGLTRANSFORMFEEDBACKVARYINGSNVPROC)load(\"glTransformFeedbackVaryingsNV\");\n\tglad_glActiveVaryingNV = (PFNGLACTIVEVARYINGNVPROC)load(\"glActiveVaryingNV\");\n\tglad_glGetVaryingLocationNV = (PFNGLGETVARYINGLOCATIONNVPROC)load(\"glGetVaryingLocationNV\");\n\tglad_glGetActiveVaryingNV = (PFNGLGETACTIVEVARYINGNVPROC)load(\"glGetActiveVaryingNV\");\n\tglad_glGetTransformFeedbackVaryingNV = (PFNGLGETTRANSFORMFEEDBACKVARYINGNVPROC)load(\"glGetTransformFeedbackVaryingNV\");\n\tglad_glTransformFeedbackStreamAttribsNV = (PFNGLTRANSFORMFEEDBACKSTREAMATTRIBSNVPROC)load(\"glTransformFeedbackStreamAttribsNV\");\n}\nstatic void load_GL_NV_transform_feedback2(GLADloadproc load) {\n\tif(!GLAD_GL_NV_transform_feedback2) return;\n\tglad_glBindTransformFeedbackNV = (PFNGLBINDTRANSFORMFEEDBACKNVPROC)load(\"glBindTransformFeedbackNV\");\n\tglad_glDeleteTransformFeedbacksNV = (PFNGLDELETETRANSFORMFEEDBACKSNVPROC)load(\"glDeleteTransformFeedbacksNV\");\n\tglad_glGenTransformFeedbacksNV = (PFNGLGENTRANSFORMFEEDBACKSNVPROC)load(\"glGenTransformFeedbacksNV\");\n\tglad_glIsTransformFeedbackNV = (PFNGLISTRANSFORMFEEDBACKNVPROC)load(\"glIsTransformFeedbackNV\");\n\tglad_glPauseTransformFeedbackNV = (PFNGLPAUSETRANSFORMFEEDBACKNVPROC)load(\"glPauseTransformFeedbackNV\");\n\tglad_glResumeTransformFeedbackNV = (PFNGLRESUMETRANSFORMFEEDBACKNVPROC)load(\"glResumeTransformFeedbackNV\");\n\tglad_glDrawTransformFeedbackNV = (PFNGLDRAWTRANSFORMFEEDBACKNVPROC)load(\"glDrawTransformFeedbackNV\");\n}\nstatic void load_GL_NV_vdpau_interop(GLADloadproc load) {\n\tif(!GLAD_GL_NV_vdpau_interop) return;\n\tglad_glVDPAUInitNV = (PFNGLVDPAUINITNVPROC)load(\"glVDPAUInitNV\");\n\tglad_glVDPAUFiniNV = (PFNGLVDPAUFININVPROC)load(\"glVDPAUFiniNV\");\n\tglad_glVDPAURegisterVideoSurfaceNV = (PFNGLVDPAUREGISTERVIDEOSURFACENVPROC)load(\"glVDPAURegisterVideoSurfaceNV\");\n\tglad_glVDPAURegisterOutputSurfaceNV = (PFNGLVDPAUREGISTEROUTPUTSURFACENVPROC)load(\"glVDPAURegisterOutputSurfaceNV\");\n\tglad_glVDPAUIsSurfaceNV = (PFNGLVDPAUISSURFACENVPROC)load(\"glVDPAUIsSurfaceNV\");\n\tglad_glVDPAUUnregisterSurfaceNV = (PFNGLVDPAUUNREGISTERSURFACENVPROC)load(\"glVDPAUUnregisterSurfaceNV\");\n\tglad_glVDPAUGetSurfaceivNV = (PFNGLVDPAUGETSURFACEIVNVPROC)load(\"glVDPAUGetSurfaceivNV\");\n\tglad_glVDPAUSurfaceAccessNV = (PFNGLVDPAUSURFACEACCESSNVPROC)load(\"glVDPAUSurfaceAccessNV\");\n\tglad_glVDPAUMapSurfacesNV = (PFNGLVDPAUMAPSURFACESNVPROC)load(\"glVDPAUMapSurfacesNV\");\n\tglad_glVDPAUUnmapSurfacesNV = (PFNGLVDPAUUNMAPSURFACESNVPROC)load(\"glVDPAUUnmapSurfacesNV\");\n}\nstatic void load_GL_NV_vertex_array_range(GLADloadproc load) {\n\tif(!GLAD_GL_NV_vertex_array_range) return;\n\tglad_glFlushVertexArrayRangeNV = (PFNGLFLUSHVERTEXARRAYRANGENVPROC)load(\"glFlushVertexArrayRangeNV\");\n\tglad_glVertexArrayRangeNV = (PFNGLVERTEXARRAYRANGENVPROC)load(\"glVertexArrayRangeNV\");\n}\nstatic void load_GL_NV_vertex_attrib_integer_64bit(GLADloadproc load) {\n\tif(!GLAD_GL_NV_vertex_attrib_integer_64bit) return;\n\tglad_glVertexAttribL1i64NV = (PFNGLVERTEXATTRIBL1I64NVPROC)load(\"glVertexAttribL1i64NV\");\n\tglad_glVertexAttribL2i64NV = (PFNGLVERTEXATTRIBL2I64NVPROC)load(\"glVertexAttribL2i64NV\");\n\tglad_glVertexAttribL3i64NV = (PFNGLVERTEXATTRIBL3I64NVPROC)load(\"glVertexAttribL3i64NV\");\n\tglad_glVertexAttribL4i64NV = (PFNGLVERTEXATTRIBL4I64NVPROC)load(\"glVertexAttribL4i64NV\");\n\tglad_glVertexAttribL1i64vNV = (PFNGLVERTEXATTRIBL1I64VNVPROC)load(\"glVertexAttribL1i64vNV\");\n\tglad_glVertexAttribL2i64vNV = (PFNGLVERTEXATTRIBL2I64VNVPROC)load(\"glVertexAttribL2i64vNV\");\n\tglad_glVertexAttribL3i64vNV = (PFNGLVERTEXATTRIBL3I64VNVPROC)load(\"glVertexAttribL3i64vNV\");\n\tglad_glVertexAttribL4i64vNV = (PFNGLVERTEXATTRIBL4I64VNVPROC)load(\"glVertexAttribL4i64vNV\");\n\tglad_glVertexAttribL1ui64NV = (PFNGLVERTEXATTRIBL1UI64NVPROC)load(\"glVertexAttribL1ui64NV\");\n\tglad_glVertexAttribL2ui64NV = (PFNGLVERTEXATTRIBL2UI64NVPROC)load(\"glVertexAttribL2ui64NV\");\n\tglad_glVertexAttribL3ui64NV = (PFNGLVERTEXATTRIBL3UI64NVPROC)load(\"glVertexAttribL3ui64NV\");\n\tglad_glVertexAttribL4ui64NV = (PFNGLVERTEXATTRIBL4UI64NVPROC)load(\"glVertexAttribL4ui64NV\");\n\tglad_glVertexAttribL1ui64vNV = (PFNGLVERTEXATTRIBL1UI64VNVPROC)load(\"glVertexAttribL1ui64vNV\");\n\tglad_glVertexAttribL2ui64vNV = (PFNGLVERTEXATTRIBL2UI64VNVPROC)load(\"glVertexAttribL2ui64vNV\");\n\tglad_glVertexAttribL3ui64vNV = (PFNGLVERTEXATTRIBL3UI64VNVPROC)load(\"glVertexAttribL3ui64vNV\");\n\tglad_glVertexAttribL4ui64vNV = (PFNGLVERTEXATTRIBL4UI64VNVPROC)load(\"glVertexAttribL4ui64vNV\");\n\tglad_glGetVertexAttribLi64vNV = (PFNGLGETVERTEXATTRIBLI64VNVPROC)load(\"glGetVertexAttribLi64vNV\");\n\tglad_glGetVertexAttribLui64vNV = (PFNGLGETVERTEXATTRIBLUI64VNVPROC)load(\"glGetVertexAttribLui64vNV\");\n\tglad_glVertexAttribLFormatNV = (PFNGLVERTEXATTRIBLFORMATNVPROC)load(\"glVertexAttribLFormatNV\");\n}\nstatic void load_GL_NV_vertex_buffer_unified_memory(GLADloadproc load) {\n\tif(!GLAD_GL_NV_vertex_buffer_unified_memory) return;\n\tglad_glBufferAddressRangeNV = (PFNGLBUFFERADDRESSRANGENVPROC)load(\"glBufferAddressRangeNV\");\n\tglad_glVertexFormatNV = (PFNGLVERTEXFORMATNVPROC)load(\"glVertexFormatNV\");\n\tglad_glNormalFormatNV = (PFNGLNORMALFORMATNVPROC)load(\"glNormalFormatNV\");\n\tglad_glColorFormatNV = (PFNGLCOLORFORMATNVPROC)load(\"glColorFormatNV\");\n\tglad_glIndexFormatNV = (PFNGLINDEXFORMATNVPROC)load(\"glIndexFormatNV\");\n\tglad_glTexCoordFormatNV = (PFNGLTEXCOORDFORMATNVPROC)load(\"glTexCoordFormatNV\");\n\tglad_glEdgeFlagFormatNV = (PFNGLEDGEFLAGFORMATNVPROC)load(\"glEdgeFlagFormatNV\");\n\tglad_glSecondaryColorFormatNV = (PFNGLSECONDARYCOLORFORMATNVPROC)load(\"glSecondaryColorFormatNV\");\n\tglad_glFogCoordFormatNV = (PFNGLFOGCOORDFORMATNVPROC)load(\"glFogCoordFormatNV\");\n\tglad_glVertexAttribFormatNV = (PFNGLVERTEXATTRIBFORMATNVPROC)load(\"glVertexAttribFormatNV\");\n\tglad_glVertexAttribIFormatNV = (PFNGLVERTEXATTRIBIFORMATNVPROC)load(\"glVertexAttribIFormatNV\");\n\tglad_glGetIntegerui64i_vNV = (PFNGLGETINTEGERUI64I_VNVPROC)load(\"glGetIntegerui64i_vNV\");\n}\nstatic void load_GL_NV_vertex_program(GLADloadproc load) {\n\tif(!GLAD_GL_NV_vertex_program) return;\n\tglad_glAreProgramsResidentNV = (PFNGLAREPROGRAMSRESIDENTNVPROC)load(\"glAreProgramsResidentNV\");\n\tglad_glBindProgramNV = (PFNGLBINDPROGRAMNVPROC)load(\"glBindProgramNV\");\n\tglad_glDeleteProgramsNV = (PFNGLDELETEPROGRAMSNVPROC)load(\"glDeleteProgramsNV\");\n\tglad_glExecuteProgramNV = (PFNGLEXECUTEPROGRAMNVPROC)load(\"glExecuteProgramNV\");\n\tglad_glGenProgramsNV = (PFNGLGENPROGRAMSNVPROC)load(\"glGenProgramsNV\");\n\tglad_glGetProgramParameterdvNV = (PFNGLGETPROGRAMPARAMETERDVNVPROC)load(\"glGetProgramParameterdvNV\");\n\tglad_glGetProgramParameterfvNV = (PFNGLGETPROGRAMPARAMETERFVNVPROC)load(\"glGetProgramParameterfvNV\");\n\tglad_glGetProgramivNV = (PFNGLGETPROGRAMIVNVPROC)load(\"glGetProgramivNV\");\n\tglad_glGetProgramStringNV = (PFNGLGETPROGRAMSTRINGNVPROC)load(\"glGetProgramStringNV\");\n\tglad_glGetTrackMatrixivNV = (PFNGLGETTRACKMATRIXIVNVPROC)load(\"glGetTrackMatrixivNV\");\n\tglad_glGetVertexAttribdvNV = (PFNGLGETVERTEXATTRIBDVNVPROC)load(\"glGetVertexAttribdvNV\");\n\tglad_glGetVertexAttribfvNV = (PFNGLGETVERTEXATTRIBFVNVPROC)load(\"glGetVertexAttribfvNV\");\n\tglad_glGetVertexAttribivNV = (PFNGLGETVERTEXATTRIBIVNVPROC)load(\"glGetVertexAttribivNV\");\n\tglad_glGetVertexAttribPointervNV = (PFNGLGETVERTEXATTRIBPOINTERVNVPROC)load(\"glGetVertexAttribPointervNV\");\n\tglad_glIsProgramNV = (PFNGLISPROGRAMNVPROC)load(\"glIsProgramNV\");\n\tglad_glLoadProgramNV = (PFNGLLOADPROGRAMNVPROC)load(\"glLoadProgramNV\");\n\tglad_glProgramParameter4dNV = (PFNGLPROGRAMPARAMETER4DNVPROC)load(\"glProgramParameter4dNV\");\n\tglad_glProgramParameter4dvNV = (PFNGLPROGRAMPARAMETER4DVNVPROC)load(\"glProgramParameter4dvNV\");\n\tglad_glProgramParameter4fNV = (PFNGLPROGRAMPARAMETER4FNVPROC)load(\"glProgramParameter4fNV\");\n\tglad_glProgramParameter4fvNV = (PFNGLPROGRAMPARAMETER4FVNVPROC)load(\"glProgramParameter4fvNV\");\n\tglad_glProgramParameters4dvNV = (PFNGLPROGRAMPARAMETERS4DVNVPROC)load(\"glProgramParameters4dvNV\");\n\tglad_glProgramParameters4fvNV = (PFNGLPROGRAMPARAMETERS4FVNVPROC)load(\"glProgramParameters4fvNV\");\n\tglad_glRequestResidentProgramsNV = (PFNGLREQUESTRESIDENTPROGRAMSNVPROC)load(\"glRequestResidentProgramsNV\");\n\tglad_glTrackMatrixNV = (PFNGLTRACKMATRIXNVPROC)load(\"glTrackMatrixNV\");\n\tglad_glVertexAttribPointerNV = (PFNGLVERTEXATTRIBPOINTERNVPROC)load(\"glVertexAttribPointerNV\");\n\tglad_glVertexAttrib1dNV = (PFNGLVERTEXATTRIB1DNVPROC)load(\"glVertexAttrib1dNV\");\n\tglad_glVertexAttrib1dvNV = (PFNGLVERTEXATTRIB1DVNVPROC)load(\"glVertexAttrib1dvNV\");\n\tglad_glVertexAttrib1fNV = (PFNGLVERTEXATTRIB1FNVPROC)load(\"glVertexAttrib1fNV\");\n\tglad_glVertexAttrib1fvNV = (PFNGLVERTEXATTRIB1FVNVPROC)load(\"glVertexAttrib1fvNV\");\n\tglad_glVertexAttrib1sNV = (PFNGLVERTEXATTRIB1SNVPROC)load(\"glVertexAttrib1sNV\");\n\tglad_glVertexAttrib1svNV = (PFNGLVERTEXATTRIB1SVNVPROC)load(\"glVertexAttrib1svNV\");\n\tglad_glVertexAttrib2dNV = (PFNGLVERTEXATTRIB2DNVPROC)load(\"glVertexAttrib2dNV\");\n\tglad_glVertexAttrib2dvNV = (PFNGLVERTEXATTRIB2DVNVPROC)load(\"glVertexAttrib2dvNV\");\n\tglad_glVertexAttrib2fNV = (PFNGLVERTEXATTRIB2FNVPROC)load(\"glVertexAttrib2fNV\");\n\tglad_glVertexAttrib2fvNV = (PFNGLVERTEXATTRIB2FVNVPROC)load(\"glVertexAttrib2fvNV\");\n\tglad_glVertexAttrib2sNV = (PFNGLVERTEXATTRIB2SNVPROC)load(\"glVertexAttrib2sNV\");\n\tglad_glVertexAttrib2svNV = (PFNGLVERTEXATTRIB2SVNVPROC)load(\"glVertexAttrib2svNV\");\n\tglad_glVertexAttrib3dNV = (PFNGLVERTEXATTRIB3DNVPROC)load(\"glVertexAttrib3dNV\");\n\tglad_glVertexAttrib3dvNV = (PFNGLVERTEXATTRIB3DVNVPROC)load(\"glVertexAttrib3dvNV\");\n\tglad_glVertexAttrib3fNV = (PFNGLVERTEXATTRIB3FNVPROC)load(\"glVertexAttrib3fNV\");\n\tglad_glVertexAttrib3fvNV = (PFNGLVERTEXATTRIB3FVNVPROC)load(\"glVertexAttrib3fvNV\");\n\tglad_glVertexAttrib3sNV = (PFNGLVERTEXATTRIB3SNVPROC)load(\"glVertexAttrib3sNV\");\n\tglad_glVertexAttrib3svNV = (PFNGLVERTEXATTRIB3SVNVPROC)load(\"glVertexAttrib3svNV\");\n\tglad_glVertexAttrib4dNV = (PFNGLVERTEXATTRIB4DNVPROC)load(\"glVertexAttrib4dNV\");\n\tglad_glVertexAttrib4dvNV = (PFNGLVERTEXATTRIB4DVNVPROC)load(\"glVertexAttrib4dvNV\");\n\tglad_glVertexAttrib4fNV = (PFNGLVERTEXATTRIB4FNVPROC)load(\"glVertexAttrib4fNV\");\n\tglad_glVertexAttrib4fvNV = (PFNGLVERTEXATTRIB4FVNVPROC)load(\"glVertexAttrib4fvNV\");\n\tglad_glVertexAttrib4sNV = (PFNGLVERTEXATTRIB4SNVPROC)load(\"glVertexAttrib4sNV\");\n\tglad_glVertexAttrib4svNV = (PFNGLVERTEXATTRIB4SVNVPROC)load(\"glVertexAttrib4svNV\");\n\tglad_glVertexAttrib4ubNV = (PFNGLVERTEXATTRIB4UBNVPROC)load(\"glVertexAttrib4ubNV\");\n\tglad_glVertexAttrib4ubvNV = (PFNGLVERTEXATTRIB4UBVNVPROC)load(\"glVertexAttrib4ubvNV\");\n\tglad_glVertexAttribs1dvNV = (PFNGLVERTEXATTRIBS1DVNVPROC)load(\"glVertexAttribs1dvNV\");\n\tglad_glVertexAttribs1fvNV = (PFNGLVERTEXATTRIBS1FVNVPROC)load(\"glVertexAttribs1fvNV\");\n\tglad_glVertexAttribs1svNV = (PFNGLVERTEXATTRIBS1SVNVPROC)load(\"glVertexAttribs1svNV\");\n\tglad_glVertexAttribs2dvNV = (PFNGLVERTEXATTRIBS2DVNVPROC)load(\"glVertexAttribs2dvNV\");\n\tglad_glVertexAttribs2fvNV = (PFNGLVERTEXATTRIBS2FVNVPROC)load(\"glVertexAttribs2fvNV\");\n\tglad_glVertexAttribs2svNV = (PFNGLVERTEXATTRIBS2SVNVPROC)load(\"glVertexAttribs2svNV\");\n\tglad_glVertexAttribs3dvNV = (PFNGLVERTEXATTRIBS3DVNVPROC)load(\"glVertexAttribs3dvNV\");\n\tglad_glVertexAttribs3fvNV = (PFNGLVERTEXATTRIBS3FVNVPROC)load(\"glVertexAttribs3fvNV\");\n\tglad_glVertexAttribs3svNV = (PFNGLVERTEXATTRIBS3SVNVPROC)load(\"glVertexAttribs3svNV\");\n\tglad_glVertexAttribs4dvNV = (PFNGLVERTEXATTRIBS4DVNVPROC)load(\"glVertexAttribs4dvNV\");\n\tglad_glVertexAttribs4fvNV = (PFNGLVERTEXATTRIBS4FVNVPROC)load(\"glVertexAttribs4fvNV\");\n\tglad_glVertexAttribs4svNV = (PFNGLVERTEXATTRIBS4SVNVPROC)load(\"glVertexAttribs4svNV\");\n\tglad_glVertexAttribs4ubvNV = (PFNGLVERTEXATTRIBS4UBVNVPROC)load(\"glVertexAttribs4ubvNV\");\n}\nstatic void load_GL_NV_vertex_program4(GLADloadproc load) {\n\tif(!GLAD_GL_NV_vertex_program4) return;\n\tglad_glVertexAttribI1iEXT = (PFNGLVERTEXATTRIBI1IEXTPROC)load(\"glVertexAttribI1iEXT\");\n\tglad_glVertexAttribI2iEXT = (PFNGLVERTEXATTRIBI2IEXTPROC)load(\"glVertexAttribI2iEXT\");\n\tglad_glVertexAttribI3iEXT = (PFNGLVERTEXATTRIBI3IEXTPROC)load(\"glVertexAttribI3iEXT\");\n\tglad_glVertexAttribI4iEXT = (PFNGLVERTEXATTRIBI4IEXTPROC)load(\"glVertexAttribI4iEXT\");\n\tglad_glVertexAttribI1uiEXT = (PFNGLVERTEXATTRIBI1UIEXTPROC)load(\"glVertexAttribI1uiEXT\");\n\tglad_glVertexAttribI2uiEXT = (PFNGLVERTEXATTRIBI2UIEXTPROC)load(\"glVertexAttribI2uiEXT\");\n\tglad_glVertexAttribI3uiEXT = (PFNGLVERTEXATTRIBI3UIEXTPROC)load(\"glVertexAttribI3uiEXT\");\n\tglad_glVertexAttribI4uiEXT = (PFNGLVERTEXATTRIBI4UIEXTPROC)load(\"glVertexAttribI4uiEXT\");\n\tglad_glVertexAttribI1ivEXT = (PFNGLVERTEXATTRIBI1IVEXTPROC)load(\"glVertexAttribI1ivEXT\");\n\tglad_glVertexAttribI2ivEXT = (PFNGLVERTEXATTRIBI2IVEXTPROC)load(\"glVertexAttribI2ivEXT\");\n\tglad_glVertexAttribI3ivEXT = (PFNGLVERTEXATTRIBI3IVEXTPROC)load(\"glVertexAttribI3ivEXT\");\n\tglad_glVertexAttribI4ivEXT = (PFNGLVERTEXATTRIBI4IVEXTPROC)load(\"glVertexAttribI4ivEXT\");\n\tglad_glVertexAttribI1uivEXT = (PFNGLVERTEXATTRIBI1UIVEXTPROC)load(\"glVertexAttribI1uivEXT\");\n\tglad_glVertexAttribI2uivEXT = (PFNGLVERTEXATTRIBI2UIVEXTPROC)load(\"glVertexAttribI2uivEXT\");\n\tglad_glVertexAttribI3uivEXT = (PFNGLVERTEXATTRIBI3UIVEXTPROC)load(\"glVertexAttribI3uivEXT\");\n\tglad_glVertexAttribI4uivEXT = (PFNGLVERTEXATTRIBI4UIVEXTPROC)load(\"glVertexAttribI4uivEXT\");\n\tglad_glVertexAttribI4bvEXT = (PFNGLVERTEXATTRIBI4BVEXTPROC)load(\"glVertexAttribI4bvEXT\");\n\tglad_glVertexAttribI4svEXT = (PFNGLVERTEXATTRIBI4SVEXTPROC)load(\"glVertexAttribI4svEXT\");\n\tglad_glVertexAttribI4ubvEXT = (PFNGLVERTEXATTRIBI4UBVEXTPROC)load(\"glVertexAttribI4ubvEXT\");\n\tglad_glVertexAttribI4usvEXT = (PFNGLVERTEXATTRIBI4USVEXTPROC)load(\"glVertexAttribI4usvEXT\");\n\tglad_glVertexAttribIPointerEXT = (PFNGLVERTEXATTRIBIPOINTEREXTPROC)load(\"glVertexAttribIPointerEXT\");\n\tglad_glGetVertexAttribIivEXT = (PFNGLGETVERTEXATTRIBIIVEXTPROC)load(\"glGetVertexAttribIivEXT\");\n\tglad_glGetVertexAttribIuivEXT = (PFNGLGETVERTEXATTRIBIUIVEXTPROC)load(\"glGetVertexAttribIuivEXT\");\n}\nstatic void load_GL_NV_video_capture(GLADloadproc load) {\n\tif(!GLAD_GL_NV_video_capture) return;\n\tglad_glBeginVideoCaptureNV = (PFNGLBEGINVIDEOCAPTURENVPROC)load(\"glBeginVideoCaptureNV\");\n\tglad_glBindVideoCaptureStreamBufferNV = (PFNGLBINDVIDEOCAPTURESTREAMBUFFERNVPROC)load(\"glBindVideoCaptureStreamBufferNV\");\n\tglad_glBindVideoCaptureStreamTextureNV = (PFNGLBINDVIDEOCAPTURESTREAMTEXTURENVPROC)load(\"glBindVideoCaptureStreamTextureNV\");\n\tglad_glEndVideoCaptureNV = (PFNGLENDVIDEOCAPTURENVPROC)load(\"glEndVideoCaptureNV\");\n\tglad_glGetVideoCaptureivNV = (PFNGLGETVIDEOCAPTUREIVNVPROC)load(\"glGetVideoCaptureivNV\");\n\tglad_glGetVideoCaptureStreamivNV = (PFNGLGETVIDEOCAPTURESTREAMIVNVPROC)load(\"glGetVideoCaptureStreamivNV\");\n\tglad_glGetVideoCaptureStreamfvNV = (PFNGLGETVIDEOCAPTURESTREAMFVNVPROC)load(\"glGetVideoCaptureStreamfvNV\");\n\tglad_glGetVideoCaptureStreamdvNV = (PFNGLGETVIDEOCAPTURESTREAMDVNVPROC)load(\"glGetVideoCaptureStreamdvNV\");\n\tglad_glVideoCaptureNV = (PFNGLVIDEOCAPTURENVPROC)load(\"glVideoCaptureNV\");\n\tglad_glVideoCaptureStreamParameterivNV = (PFNGLVIDEOCAPTURESTREAMPARAMETERIVNVPROC)load(\"glVideoCaptureStreamParameterivNV\");\n\tglad_glVideoCaptureStreamParameterfvNV = (PFNGLVIDEOCAPTURESTREAMPARAMETERFVNVPROC)load(\"glVideoCaptureStreamParameterfvNV\");\n\tglad_glVideoCaptureStreamParameterdvNV = (PFNGLVIDEOCAPTURESTREAMPARAMETERDVNVPROC)load(\"glVideoCaptureStreamParameterdvNV\");\n}\nstatic void load_GL_NV_viewport_swizzle(GLADloadproc load) {\n\tif(!GLAD_GL_NV_viewport_swizzle) return;\n\tglad_glViewportSwizzleNV = (PFNGLVIEWPORTSWIZZLENVPROC)load(\"glViewportSwizzleNV\");\n}\nstatic void load_GL_OES_byte_coordinates(GLADloadproc load) {\n\tif(!GLAD_GL_OES_byte_coordinates) return;\n\tglad_glMultiTexCoord1bOES = (PFNGLMULTITEXCOORD1BOESPROC)load(\"glMultiTexCoord1bOES\");\n\tglad_glMultiTexCoord1bvOES = (PFNGLMULTITEXCOORD1BVOESPROC)load(\"glMultiTexCoord1bvOES\");\n\tglad_glMultiTexCoord2bOES = (PFNGLMULTITEXCOORD2BOESPROC)load(\"glMultiTexCoord2bOES\");\n\tglad_glMultiTexCoord2bvOES = (PFNGLMULTITEXCOORD2BVOESPROC)load(\"glMultiTexCoord2bvOES\");\n\tglad_glMultiTexCoord3bOES = (PFNGLMULTITEXCOORD3BOESPROC)load(\"glMultiTexCoord3bOES\");\n\tglad_glMultiTexCoord3bvOES = (PFNGLMULTITEXCOORD3BVOESPROC)load(\"glMultiTexCoord3bvOES\");\n\tglad_glMultiTexCoord4bOES = (PFNGLMULTITEXCOORD4BOESPROC)load(\"glMultiTexCoord4bOES\");\n\tglad_glMultiTexCoord4bvOES = (PFNGLMULTITEXCOORD4BVOESPROC)load(\"glMultiTexCoord4bvOES\");\n\tglad_glTexCoord1bOES = (PFNGLTEXCOORD1BOESPROC)load(\"glTexCoord1bOES\");\n\tglad_glTexCoord1bvOES = (PFNGLTEXCOORD1BVOESPROC)load(\"glTexCoord1bvOES\");\n\tglad_glTexCoord2bOES = (PFNGLTEXCOORD2BOESPROC)load(\"glTexCoord2bOES\");\n\tglad_glTexCoord2bvOES = (PFNGLTEXCOORD2BVOESPROC)load(\"glTexCoord2bvOES\");\n\tglad_glTexCoord3bOES = (PFNGLTEXCOORD3BOESPROC)load(\"glTexCoord3bOES\");\n\tglad_glTexCoord3bvOES = (PFNGLTEXCOORD3BVOESPROC)load(\"glTexCoord3bvOES\");\n\tglad_glTexCoord4bOES = (PFNGLTEXCOORD4BOESPROC)load(\"glTexCoord4bOES\");\n\tglad_glTexCoord4bvOES = (PFNGLTEXCOORD4BVOESPROC)load(\"glTexCoord4bvOES\");\n\tglad_glVertex2bOES = (PFNGLVERTEX2BOESPROC)load(\"glVertex2bOES\");\n\tglad_glVertex2bvOES = (PFNGLVERTEX2BVOESPROC)load(\"glVertex2bvOES\");\n\tglad_glVertex3bOES = (PFNGLVERTEX3BOESPROC)load(\"glVertex3bOES\");\n\tglad_glVertex3bvOES = (PFNGLVERTEX3BVOESPROC)load(\"glVertex3bvOES\");\n\tglad_glVertex4bOES = (PFNGLVERTEX4BOESPROC)load(\"glVertex4bOES\");\n\tglad_glVertex4bvOES = (PFNGLVERTEX4BVOESPROC)load(\"glVertex4bvOES\");\n}\nstatic void load_GL_OES_fixed_point(GLADloadproc load) {\n\tif(!GLAD_GL_OES_fixed_point) return;\n\tglad_glAlphaFuncxOES = (PFNGLALPHAFUNCXOESPROC)load(\"glAlphaFuncxOES\");\n\tglad_glClearColorxOES = (PFNGLCLEARCOLORXOESPROC)load(\"glClearColorxOES\");\n\tglad_glClearDepthxOES = (PFNGLCLEARDEPTHXOESPROC)load(\"glClearDepthxOES\");\n\tglad_glClipPlanexOES = (PFNGLCLIPPLANEXOESPROC)load(\"glClipPlanexOES\");\n\tglad_glColor4xOES = (PFNGLCOLOR4XOESPROC)load(\"glColor4xOES\");\n\tglad_glDepthRangexOES = (PFNGLDEPTHRANGEXOESPROC)load(\"glDepthRangexOES\");\n\tglad_glFogxOES = (PFNGLFOGXOESPROC)load(\"glFogxOES\");\n\tglad_glFogxvOES = (PFNGLFOGXVOESPROC)load(\"glFogxvOES\");\n\tglad_glFrustumxOES = (PFNGLFRUSTUMXOESPROC)load(\"glFrustumxOES\");\n\tglad_glGetClipPlanexOES = (PFNGLGETCLIPPLANEXOESPROC)load(\"glGetClipPlanexOES\");\n\tglad_glGetFixedvOES = (PFNGLGETFIXEDVOESPROC)load(\"glGetFixedvOES\");\n\tglad_glGetTexEnvxvOES = (PFNGLGETTEXENVXVOESPROC)load(\"glGetTexEnvxvOES\");\n\tglad_glGetTexParameterxvOES = (PFNGLGETTEXPARAMETERXVOESPROC)load(\"glGetTexParameterxvOES\");\n\tglad_glLightModelxOES = (PFNGLLIGHTMODELXOESPROC)load(\"glLightModelxOES\");\n\tglad_glLightModelxvOES = (PFNGLLIGHTMODELXVOESPROC)load(\"glLightModelxvOES\");\n\tglad_glLightxOES = (PFNGLLIGHTXOESPROC)load(\"glLightxOES\");\n\tglad_glLightxvOES = (PFNGLLIGHTXVOESPROC)load(\"glLightxvOES\");\n\tglad_glLineWidthxOES = (PFNGLLINEWIDTHXOESPROC)load(\"glLineWidthxOES\");\n\tglad_glLoadMatrixxOES = (PFNGLLOADMATRIXXOESPROC)load(\"glLoadMatrixxOES\");\n\tglad_glMaterialxOES = (PFNGLMATERIALXOESPROC)load(\"glMaterialxOES\");\n\tglad_glMaterialxvOES = (PFNGLMATERIALXVOESPROC)load(\"glMaterialxvOES\");\n\tglad_glMultMatrixxOES = (PFNGLMULTMATRIXXOESPROC)load(\"glMultMatrixxOES\");\n\tglad_glMultiTexCoord4xOES = (PFNGLMULTITEXCOORD4XOESPROC)load(\"glMultiTexCoord4xOES\");\n\tglad_glNormal3xOES = (PFNGLNORMAL3XOESPROC)load(\"glNormal3xOES\");\n\tglad_glOrthoxOES = (PFNGLORTHOXOESPROC)load(\"glOrthoxOES\");\n\tglad_glPointParameterxvOES = (PFNGLPOINTPARAMETERXVOESPROC)load(\"glPointParameterxvOES\");\n\tglad_glPointSizexOES = (PFNGLPOINTSIZEXOESPROC)load(\"glPointSizexOES\");\n\tglad_glPolygonOffsetxOES = (PFNGLPOLYGONOFFSETXOESPROC)load(\"glPolygonOffsetxOES\");\n\tglad_glRotatexOES = (PFNGLROTATEXOESPROC)load(\"glRotatexOES\");\n\tglad_glScalexOES = (PFNGLSCALEXOESPROC)load(\"glScalexOES\");\n\tglad_glTexEnvxOES = (PFNGLTEXENVXOESPROC)load(\"glTexEnvxOES\");\n\tglad_glTexEnvxvOES = (PFNGLTEXENVXVOESPROC)load(\"glTexEnvxvOES\");\n\tglad_glTexParameterxOES = (PFNGLTEXPARAMETERXOESPROC)load(\"glTexParameterxOES\");\n\tglad_glTexParameterxvOES = (PFNGLTEXPARAMETERXVOESPROC)load(\"glTexParameterxvOES\");\n\tglad_glTranslatexOES = (PFNGLTRANSLATEXOESPROC)load(\"glTranslatexOES\");\n\tglad_glGetLightxvOES = (PFNGLGETLIGHTXVOESPROC)load(\"glGetLightxvOES\");\n\tglad_glGetMaterialxvOES = (PFNGLGETMATERIALXVOESPROC)load(\"glGetMaterialxvOES\");\n\tglad_glPointParameterxOES = (PFNGLPOINTPARAMETERXOESPROC)load(\"glPointParameterxOES\");\n\tglad_glSampleCoveragexOES = (PFNGLSAMPLECOVERAGEXOESPROC)load(\"glSampleCoveragexOES\");\n\tglad_glAccumxOES = (PFNGLACCUMXOESPROC)load(\"glAccumxOES\");\n\tglad_glBitmapxOES = (PFNGLBITMAPXOESPROC)load(\"glBitmapxOES\");\n\tglad_glBlendColorxOES = (PFNGLBLENDCOLORXOESPROC)load(\"glBlendColorxOES\");\n\tglad_glClearAccumxOES = (PFNGLCLEARACCUMXOESPROC)load(\"glClearAccumxOES\");\n\tglad_glColor3xOES = (PFNGLCOLOR3XOESPROC)load(\"glColor3xOES\");\n\tglad_glColor3xvOES = (PFNGLCOLOR3XVOESPROC)load(\"glColor3xvOES\");\n\tglad_glColor4xvOES = (PFNGLCOLOR4XVOESPROC)load(\"glColor4xvOES\");\n\tglad_glConvolutionParameterxOES = (PFNGLCONVOLUTIONPARAMETERXOESPROC)load(\"glConvolutionParameterxOES\");\n\tglad_glConvolutionParameterxvOES = (PFNGLCONVOLUTIONPARAMETERXVOESPROC)load(\"glConvolutionParameterxvOES\");\n\tglad_glEvalCoord1xOES = (PFNGLEVALCOORD1XOESPROC)load(\"glEvalCoord1xOES\");\n\tglad_glEvalCoord1xvOES = (PFNGLEVALCOORD1XVOESPROC)load(\"glEvalCoord1xvOES\");\n\tglad_glEvalCoord2xOES = (PFNGLEVALCOORD2XOESPROC)load(\"glEvalCoord2xOES\");\n\tglad_glEvalCoord2xvOES = (PFNGLEVALCOORD2XVOESPROC)load(\"glEvalCoord2xvOES\");\n\tglad_glFeedbackBufferxOES = (PFNGLFEEDBACKBUFFERXOESPROC)load(\"glFeedbackBufferxOES\");\n\tglad_glGetConvolutionParameterxvOES = (PFNGLGETCONVOLUTIONPARAMETERXVOESPROC)load(\"glGetConvolutionParameterxvOES\");\n\tglad_glGetHistogramParameterxvOES = (PFNGLGETHISTOGRAMPARAMETERXVOESPROC)load(\"glGetHistogramParameterxvOES\");\n\tglad_glGetLightxOES = (PFNGLGETLIGHTXOESPROC)load(\"glGetLightxOES\");\n\tglad_glGetMapxvOES = (PFNGLGETMAPXVOESPROC)load(\"glGetMapxvOES\");\n\tglad_glGetMaterialxOES = (PFNGLGETMATERIALXOESPROC)load(\"glGetMaterialxOES\");\n\tglad_glGetPixelMapxv = (PFNGLGETPIXELMAPXVPROC)load(\"glGetPixelMapxv\");\n\tglad_glGetTexGenxvOES = (PFNGLGETTEXGENXVOESPROC)load(\"glGetTexGenxvOES\");\n\tglad_glGetTexLevelParameterxvOES = (PFNGLGETTEXLEVELPARAMETERXVOESPROC)load(\"glGetTexLevelParameterxvOES\");\n\tglad_glIndexxOES = (PFNGLINDEXXOESPROC)load(\"glIndexxOES\");\n\tglad_glIndexxvOES = (PFNGLINDEXXVOESPROC)load(\"glIndexxvOES\");\n\tglad_glLoadTransposeMatrixxOES = (PFNGLLOADTRANSPOSEMATRIXXOESPROC)load(\"glLoadTransposeMatrixxOES\");\n\tglad_glMap1xOES = (PFNGLMAP1XOESPROC)load(\"glMap1xOES\");\n\tglad_glMap2xOES = (PFNGLMAP2XOESPROC)load(\"glMap2xOES\");\n\tglad_glMapGrid1xOES = (PFNGLMAPGRID1XOESPROC)load(\"glMapGrid1xOES\");\n\tglad_glMapGrid2xOES = (PFNGLMAPGRID2XOESPROC)load(\"glMapGrid2xOES\");\n\tglad_glMultTransposeMatrixxOES = (PFNGLMULTTRANSPOSEMATRIXXOESPROC)load(\"glMultTransposeMatrixxOES\");\n\tglad_glMultiTexCoord1xOES = (PFNGLMULTITEXCOORD1XOESPROC)load(\"glMultiTexCoord1xOES\");\n\tglad_glMultiTexCoord1xvOES = (PFNGLMULTITEXCOORD1XVOESPROC)load(\"glMultiTexCoord1xvOES\");\n\tglad_glMultiTexCoord2xOES = (PFNGLMULTITEXCOORD2XOESPROC)load(\"glMultiTexCoord2xOES\");\n\tglad_glMultiTexCoord2xvOES = (PFNGLMULTITEXCOORD2XVOESPROC)load(\"glMultiTexCoord2xvOES\");\n\tglad_glMultiTexCoord3xOES = (PFNGLMULTITEXCOORD3XOESPROC)load(\"glMultiTexCoord3xOES\");\n\tglad_glMultiTexCoord3xvOES = (PFNGLMULTITEXCOORD3XVOESPROC)load(\"glMultiTexCoord3xvOES\");\n\tglad_glMultiTexCoord4xvOES = (PFNGLMULTITEXCOORD4XVOESPROC)load(\"glMultiTexCoord4xvOES\");\n\tglad_glNormal3xvOES = (PFNGLNORMAL3XVOESPROC)load(\"glNormal3xvOES\");\n\tglad_glPassThroughxOES = (PFNGLPASSTHROUGHXOESPROC)load(\"glPassThroughxOES\");\n\tglad_glPixelMapx = (PFNGLPIXELMAPXPROC)load(\"glPixelMapx\");\n\tglad_glPixelStorex = (PFNGLPIXELSTOREXPROC)load(\"glPixelStorex\");\n\tglad_glPixelTransferxOES = (PFNGLPIXELTRANSFERXOESPROC)load(\"glPixelTransferxOES\");\n\tglad_glPixelZoomxOES = (PFNGLPIXELZOOMXOESPROC)load(\"glPixelZoomxOES\");\n\tglad_glPrioritizeTexturesxOES = (PFNGLPRIORITIZETEXTURESXOESPROC)load(\"glPrioritizeTexturesxOES\");\n\tglad_glRasterPos2xOES = (PFNGLRASTERPOS2XOESPROC)load(\"glRasterPos2xOES\");\n\tglad_glRasterPos2xvOES = (PFNGLRASTERPOS2XVOESPROC)load(\"glRasterPos2xvOES\");\n\tglad_glRasterPos3xOES = (PFNGLRASTERPOS3XOESPROC)load(\"glRasterPos3xOES\");\n\tglad_glRasterPos3xvOES = (PFNGLRASTERPOS3XVOESPROC)load(\"glRasterPos3xvOES\");\n\tglad_glRasterPos4xOES = (PFNGLRASTERPOS4XOESPROC)load(\"glRasterPos4xOES\");\n\tglad_glRasterPos4xvOES = (PFNGLRASTERPOS4XVOESPROC)load(\"glRasterPos4xvOES\");\n\tglad_glRectxOES = (PFNGLRECTXOESPROC)load(\"glRectxOES\");\n\tglad_glRectxvOES = (PFNGLRECTXVOESPROC)load(\"glRectxvOES\");\n\tglad_glTexCoord1xOES = (PFNGLTEXCOORD1XOESPROC)load(\"glTexCoord1xOES\");\n\tglad_glTexCoord1xvOES = (PFNGLTEXCOORD1XVOESPROC)load(\"glTexCoord1xvOES\");\n\tglad_glTexCoord2xOES = (PFNGLTEXCOORD2XOESPROC)load(\"glTexCoord2xOES\");\n\tglad_glTexCoord2xvOES = (PFNGLTEXCOORD2XVOESPROC)load(\"glTexCoord2xvOES\");\n\tglad_glTexCoord3xOES = (PFNGLTEXCOORD3XOESPROC)load(\"glTexCoord3xOES\");\n\tglad_glTexCoord3xvOES = (PFNGLTEXCOORD3XVOESPROC)load(\"glTexCoord3xvOES\");\n\tglad_glTexCoord4xOES = (PFNGLTEXCOORD4XOESPROC)load(\"glTexCoord4xOES\");\n\tglad_glTexCoord4xvOES = (PFNGLTEXCOORD4XVOESPROC)load(\"glTexCoord4xvOES\");\n\tglad_glTexGenxOES = (PFNGLTEXGENXOESPROC)load(\"glTexGenxOES\");\n\tglad_glTexGenxvOES = (PFNGLTEXGENXVOESPROC)load(\"glTexGenxvOES\");\n\tglad_glVertex2xOES = (PFNGLVERTEX2XOESPROC)load(\"glVertex2xOES\");\n\tglad_glVertex2xvOES = (PFNGLVERTEX2XVOESPROC)load(\"glVertex2xvOES\");\n\tglad_glVertex3xOES = (PFNGLVERTEX3XOESPROC)load(\"glVertex3xOES\");\n\tglad_glVertex3xvOES = (PFNGLVERTEX3XVOESPROC)load(\"glVertex3xvOES\");\n\tglad_glVertex4xOES = (PFNGLVERTEX4XOESPROC)load(\"glVertex4xOES\");\n\tglad_glVertex4xvOES = (PFNGLVERTEX4XVOESPROC)load(\"glVertex4xvOES\");\n}\nstatic void load_GL_OES_query_matrix(GLADloadproc load) {\n\tif(!GLAD_GL_OES_query_matrix) return;\n\tglad_glQueryMatrixxOES = (PFNGLQUERYMATRIXXOESPROC)load(\"glQueryMatrixxOES\");\n}\nstatic void load_GL_OES_single_precision(GLADloadproc load) {\n\tif(!GLAD_GL_OES_single_precision) return;\n\tglad_glClearDepthfOES = (PFNGLCLEARDEPTHFOESPROC)load(\"glClearDepthfOES\");\n\tglad_glClipPlanefOES = (PFNGLCLIPPLANEFOESPROC)load(\"glClipPlanefOES\");\n\tglad_glDepthRangefOES = (PFNGLDEPTHRANGEFOESPROC)load(\"glDepthRangefOES\");\n\tglad_glFrustumfOES = (PFNGLFRUSTUMFOESPROC)load(\"glFrustumfOES\");\n\tglad_glGetClipPlanefOES = (PFNGLGETCLIPPLANEFOESPROC)load(\"glGetClipPlanefOES\");\n\tglad_glOrthofOES = (PFNGLORTHOFOESPROC)load(\"glOrthofOES\");\n}\nstatic void load_GL_OVR_multiview(GLADloadproc load) {\n\tif(!GLAD_GL_OVR_multiview) return;\n\tglad_glFramebufferTextureMultiviewOVR = (PFNGLFRAMEBUFFERTEXTUREMULTIVIEWOVRPROC)load(\"glFramebufferTextureMultiviewOVR\");\n}\nstatic void load_GL_PGI_misc_hints(GLADloadproc load) {\n\tif(!GLAD_GL_PGI_misc_hints) return;\n\tglad_glHintPGI = (PFNGLHINTPGIPROC)load(\"glHintPGI\");\n}\nstatic void load_GL_SGIS_detail_texture(GLADloadproc load) {\n\tif(!GLAD_GL_SGIS_detail_texture) return;\n\tglad_glDetailTexFuncSGIS = (PFNGLDETAILTEXFUNCSGISPROC)load(\"glDetailTexFuncSGIS\");\n\tglad_glGetDetailTexFuncSGIS = (PFNGLGETDETAILTEXFUNCSGISPROC)load(\"glGetDetailTexFuncSGIS\");\n}\nstatic void load_GL_SGIS_fog_function(GLADloadproc load) {\n\tif(!GLAD_GL_SGIS_fog_function) return;\n\tglad_glFogFuncSGIS = (PFNGLFOGFUNCSGISPROC)load(\"glFogFuncSGIS\");\n\tglad_glGetFogFuncSGIS = (PFNGLGETFOGFUNCSGISPROC)load(\"glGetFogFuncSGIS\");\n}\nstatic void load_GL_SGIS_multisample(GLADloadproc load) {\n\tif(!GLAD_GL_SGIS_multisample) return;\n\tglad_glSampleMaskSGIS = (PFNGLSAMPLEMASKSGISPROC)load(\"glSampleMaskSGIS\");\n\tglad_glSamplePatternSGIS = (PFNGLSAMPLEPATTERNSGISPROC)load(\"glSamplePatternSGIS\");\n}\nstatic void load_GL_SGIS_pixel_texture(GLADloadproc load) {\n\tif(!GLAD_GL_SGIS_pixel_texture) return;\n\tglad_glPixelTexGenParameteriSGIS = (PFNGLPIXELTEXGENPARAMETERISGISPROC)load(\"glPixelTexGenParameteriSGIS\");\n\tglad_glPixelTexGenParameterivSGIS = (PFNGLPIXELTEXGENPARAMETERIVSGISPROC)load(\"glPixelTexGenParameterivSGIS\");\n\tglad_glPixelTexGenParameterfSGIS = (PFNGLPIXELTEXGENPARAMETERFSGISPROC)load(\"glPixelTexGenParameterfSGIS\");\n\tglad_glPixelTexGenParameterfvSGIS = (PFNGLPIXELTEXGENPARAMETERFVSGISPROC)load(\"glPixelTexGenParameterfvSGIS\");\n\tglad_glGetPixelTexGenParameterivSGIS = (PFNGLGETPIXELTEXGENPARAMETERIVSGISPROC)load(\"glGetPixelTexGenParameterivSGIS\");\n\tglad_glGetPixelTexGenParameterfvSGIS = (PFNGLGETPIXELTEXGENPARAMETERFVSGISPROC)load(\"glGetPixelTexGenParameterfvSGIS\");\n}\nstatic void load_GL_SGIS_point_parameters(GLADloadproc load) {\n\tif(!GLAD_GL_SGIS_point_parameters) return;\n\tglad_glPointParameterfSGIS = (PFNGLPOINTPARAMETERFSGISPROC)load(\"glPointParameterfSGIS\");\n\tglad_glPointParameterfvSGIS = (PFNGLPOINTPARAMETERFVSGISPROC)load(\"glPointParameterfvSGIS\");\n}\nstatic void load_GL_SGIS_sharpen_texture(GLADloadproc load) {\n\tif(!GLAD_GL_SGIS_sharpen_texture) return;\n\tglad_glSharpenTexFuncSGIS = (PFNGLSHARPENTEXFUNCSGISPROC)load(\"glSharpenTexFuncSGIS\");\n\tglad_glGetSharpenTexFuncSGIS = (PFNGLGETSHARPENTEXFUNCSGISPROC)load(\"glGetSharpenTexFuncSGIS\");\n}\nstatic void load_GL_SGIS_texture4D(GLADloadproc load) {\n\tif(!GLAD_GL_SGIS_texture4D) return;\n\tglad_glTexImage4DSGIS = (PFNGLTEXIMAGE4DSGISPROC)load(\"glTexImage4DSGIS\");\n\tglad_glTexSubImage4DSGIS = (PFNGLTEXSUBIMAGE4DSGISPROC)load(\"glTexSubImage4DSGIS\");\n}\nstatic void load_GL_SGIS_texture_color_mask(GLADloadproc load) {\n\tif(!GLAD_GL_SGIS_texture_color_mask) return;\n\tglad_glTextureColorMaskSGIS = (PFNGLTEXTURECOLORMASKSGISPROC)load(\"glTextureColorMaskSGIS\");\n}\nstatic void load_GL_SGIS_texture_filter4(GLADloadproc load) {\n\tif(!GLAD_GL_SGIS_texture_filter4) return;\n\tglad_glGetTexFilterFuncSGIS = (PFNGLGETTEXFILTERFUNCSGISPROC)load(\"glGetTexFilterFuncSGIS\");\n\tglad_glTexFilterFuncSGIS = (PFNGLTEXFILTERFUNCSGISPROC)load(\"glTexFilterFuncSGIS\");\n}\nstatic void load_GL_SGIX_async(GLADloadproc load) {\n\tif(!GLAD_GL_SGIX_async) return;\n\tglad_glAsyncMarkerSGIX = (PFNGLASYNCMARKERSGIXPROC)load(\"glAsyncMarkerSGIX\");\n\tglad_glFinishAsyncSGIX = (PFNGLFINISHASYNCSGIXPROC)load(\"glFinishAsyncSGIX\");\n\tglad_glPollAsyncSGIX = (PFNGLPOLLASYNCSGIXPROC)load(\"glPollAsyncSGIX\");\n\tglad_glGenAsyncMarkersSGIX = (PFNGLGENASYNCMARKERSSGIXPROC)load(\"glGenAsyncMarkersSGIX\");\n\tglad_glDeleteAsyncMarkersSGIX = (PFNGLDELETEASYNCMARKERSSGIXPROC)load(\"glDeleteAsyncMarkersSGIX\");\n\tglad_glIsAsyncMarkerSGIX = (PFNGLISASYNCMARKERSGIXPROC)load(\"glIsAsyncMarkerSGIX\");\n}\nstatic void load_GL_SGIX_flush_raster(GLADloadproc load) {\n\tif(!GLAD_GL_SGIX_flush_raster) return;\n\tglad_glFlushRasterSGIX = (PFNGLFLUSHRASTERSGIXPROC)load(\"glFlushRasterSGIX\");\n}\nstatic void load_GL_SGIX_fragment_lighting(GLADloadproc load) {\n\tif(!GLAD_GL_SGIX_fragment_lighting) return;\n\tglad_glFragmentColorMaterialSGIX = (PFNGLFRAGMENTCOLORMATERIALSGIXPROC)load(\"glFragmentColorMaterialSGIX\");\n\tglad_glFragmentLightfSGIX = (PFNGLFRAGMENTLIGHTFSGIXPROC)load(\"glFragmentLightfSGIX\");\n\tglad_glFragmentLightfvSGIX = (PFNGLFRAGMENTLIGHTFVSGIXPROC)load(\"glFragmentLightfvSGIX\");\n\tglad_glFragmentLightiSGIX = (PFNGLFRAGMENTLIGHTISGIXPROC)load(\"glFragmentLightiSGIX\");\n\tglad_glFragmentLightivSGIX = (PFNGLFRAGMENTLIGHTIVSGIXPROC)load(\"glFragmentLightivSGIX\");\n\tglad_glFragmentLightModelfSGIX = (PFNGLFRAGMENTLIGHTMODELFSGIXPROC)load(\"glFragmentLightModelfSGIX\");\n\tglad_glFragmentLightModelfvSGIX = (PFNGLFRAGMENTLIGHTMODELFVSGIXPROC)load(\"glFragmentLightModelfvSGIX\");\n\tglad_glFragmentLightModeliSGIX = (PFNGLFRAGMENTLIGHTMODELISGIXPROC)load(\"glFragmentLightModeliSGIX\");\n\tglad_glFragmentLightModelivSGIX = (PFNGLFRAGMENTLIGHTMODELIVSGIXPROC)load(\"glFragmentLightModelivSGIX\");\n\tglad_glFragmentMaterialfSGIX = (PFNGLFRAGMENTMATERIALFSGIXPROC)load(\"glFragmentMaterialfSGIX\");\n\tglad_glFragmentMaterialfvSGIX = (PFNGLFRAGMENTMATERIALFVSGIXPROC)load(\"glFragmentMaterialfvSGIX\");\n\tglad_glFragmentMaterialiSGIX = (PFNGLFRAGMENTMATERIALISGIXPROC)load(\"glFragmentMaterialiSGIX\");\n\tglad_glFragmentMaterialivSGIX = (PFNGLFRAGMENTMATERIALIVSGIXPROC)load(\"glFragmentMaterialivSGIX\");\n\tglad_glGetFragmentLightfvSGIX = (PFNGLGETFRAGMENTLIGHTFVSGIXPROC)load(\"glGetFragmentLightfvSGIX\");\n\tglad_glGetFragmentLightivSGIX = (PFNGLGETFRAGMENTLIGHTIVSGIXPROC)load(\"glGetFragmentLightivSGIX\");\n\tglad_glGetFragmentMaterialfvSGIX = (PFNGLGETFRAGMENTMATERIALFVSGIXPROC)load(\"glGetFragmentMaterialfvSGIX\");\n\tglad_glGetFragmentMaterialivSGIX = (PFNGLGETFRAGMENTMATERIALIVSGIXPROC)load(\"glGetFragmentMaterialivSGIX\");\n\tglad_glLightEnviSGIX = (PFNGLLIGHTENVISGIXPROC)load(\"glLightEnviSGIX\");\n}\nstatic void load_GL_SGIX_framezoom(GLADloadproc load) {\n\tif(!GLAD_GL_SGIX_framezoom) return;\n\tglad_glFrameZoomSGIX = (PFNGLFRAMEZOOMSGIXPROC)load(\"glFrameZoomSGIX\");\n}\nstatic void load_GL_SGIX_igloo_interface(GLADloadproc load) {\n\tif(!GLAD_GL_SGIX_igloo_interface) return;\n\tglad_glIglooInterfaceSGIX = (PFNGLIGLOOINTERFACESGIXPROC)load(\"glIglooInterfaceSGIX\");\n}\nstatic void load_GL_SGIX_instruments(GLADloadproc load) {\n\tif(!GLAD_GL_SGIX_instruments) return;\n\tglad_glGetInstrumentsSGIX = (PFNGLGETINSTRUMENTSSGIXPROC)load(\"glGetInstrumentsSGIX\");\n\tglad_glInstrumentsBufferSGIX = (PFNGLINSTRUMENTSBUFFERSGIXPROC)load(\"glInstrumentsBufferSGIX\");\n\tglad_glPollInstrumentsSGIX = (PFNGLPOLLINSTRUMENTSSGIXPROC)load(\"glPollInstrumentsSGIX\");\n\tglad_glReadInstrumentsSGIX = (PFNGLREADINSTRUMENTSSGIXPROC)load(\"glReadInstrumentsSGIX\");\n\tglad_glStartInstrumentsSGIX = (PFNGLSTARTINSTRUMENTSSGIXPROC)load(\"glStartInstrumentsSGIX\");\n\tglad_glStopInstrumentsSGIX = (PFNGLSTOPINSTRUMENTSSGIXPROC)load(\"glStopInstrumentsSGIX\");\n}\nstatic void load_GL_SGIX_list_priority(GLADloadproc load) {\n\tif(!GLAD_GL_SGIX_list_priority) return;\n\tglad_glGetListParameterfvSGIX = (PFNGLGETLISTPARAMETERFVSGIXPROC)load(\"glGetListParameterfvSGIX\");\n\tglad_glGetListParameterivSGIX = (PFNGLGETLISTPARAMETERIVSGIXPROC)load(\"glGetListParameterivSGIX\");\n\tglad_glListParameterfSGIX = (PFNGLLISTPARAMETERFSGIXPROC)load(\"glListParameterfSGIX\");\n\tglad_glListParameterfvSGIX = (PFNGLLISTPARAMETERFVSGIXPROC)load(\"glListParameterfvSGIX\");\n\tglad_glListParameteriSGIX = (PFNGLLISTPARAMETERISGIXPROC)load(\"glListParameteriSGIX\");\n\tglad_glListParameterivSGIX = (PFNGLLISTPARAMETERIVSGIXPROC)load(\"glListParameterivSGIX\");\n}\nstatic void load_GL_SGIX_pixel_texture(GLADloadproc load) {\n\tif(!GLAD_GL_SGIX_pixel_texture) return;\n\tglad_glPixelTexGenSGIX = (PFNGLPIXELTEXGENSGIXPROC)load(\"glPixelTexGenSGIX\");\n}\nstatic void load_GL_SGIX_polynomial_ffd(GLADloadproc load) {\n\tif(!GLAD_GL_SGIX_polynomial_ffd) return;\n\tglad_glDeformationMap3dSGIX = (PFNGLDEFORMATIONMAP3DSGIXPROC)load(\"glDeformationMap3dSGIX\");\n\tglad_glDeformationMap3fSGIX = (PFNGLDEFORMATIONMAP3FSGIXPROC)load(\"glDeformationMap3fSGIX\");\n\tglad_glDeformSGIX = (PFNGLDEFORMSGIXPROC)load(\"glDeformSGIX\");\n\tglad_glLoadIdentityDeformationMapSGIX = (PFNGLLOADIDENTITYDEFORMATIONMAPSGIXPROC)load(\"glLoadIdentityDeformationMapSGIX\");\n}\nstatic void load_GL_SGIX_reference_plane(GLADloadproc load) {\n\tif(!GLAD_GL_SGIX_reference_plane) return;\n\tglad_glReferencePlaneSGIX = (PFNGLREFERENCEPLANESGIXPROC)load(\"glReferencePlaneSGIX\");\n}\nstatic void load_GL_SGIX_sprite(GLADloadproc load) {\n\tif(!GLAD_GL_SGIX_sprite) return;\n\tglad_glSpriteParameterfSGIX = (PFNGLSPRITEPARAMETERFSGIXPROC)load(\"glSpriteParameterfSGIX\");\n\tglad_glSpriteParameterfvSGIX = (PFNGLSPRITEPARAMETERFVSGIXPROC)load(\"glSpriteParameterfvSGIX\");\n\tglad_glSpriteParameteriSGIX = (PFNGLSPRITEPARAMETERISGIXPROC)load(\"glSpriteParameteriSGIX\");\n\tglad_glSpriteParameterivSGIX = (PFNGLSPRITEPARAMETERIVSGIXPROC)load(\"glSpriteParameterivSGIX\");\n}\nstatic void load_GL_SGIX_tag_sample_buffer(GLADloadproc load) {\n\tif(!GLAD_GL_SGIX_tag_sample_buffer) return;\n\tglad_glTagSampleBufferSGIX = (PFNGLTAGSAMPLEBUFFERSGIXPROC)load(\"glTagSampleBufferSGIX\");\n}\nstatic void load_GL_SGI_color_table(GLADloadproc load) {\n\tif(!GLAD_GL_SGI_color_table) return;\n\tglad_glColorTableSGI = (PFNGLCOLORTABLESGIPROC)load(\"glColorTableSGI\");\n\tglad_glColorTableParameterfvSGI = (PFNGLCOLORTABLEPARAMETERFVSGIPROC)load(\"glColorTableParameterfvSGI\");\n\tglad_glColorTableParameterivSGI = (PFNGLCOLORTABLEPARAMETERIVSGIPROC)load(\"glColorTableParameterivSGI\");\n\tglad_glCopyColorTableSGI = (PFNGLCOPYCOLORTABLESGIPROC)load(\"glCopyColorTableSGI\");\n\tglad_glGetColorTableSGI = (PFNGLGETCOLORTABLESGIPROC)load(\"glGetColorTableSGI\");\n\tglad_glGetColorTableParameterfvSGI = (PFNGLGETCOLORTABLEPARAMETERFVSGIPROC)load(\"glGetColorTableParameterfvSGI\");\n\tglad_glGetColorTableParameterivSGI = (PFNGLGETCOLORTABLEPARAMETERIVSGIPROC)load(\"glGetColorTableParameterivSGI\");\n}\nstatic void load_GL_SUNX_constant_data(GLADloadproc load) {\n\tif(!GLAD_GL_SUNX_constant_data) return;\n\tglad_glFinishTextureSUNX = (PFNGLFINISHTEXTURESUNXPROC)load(\"glFinishTextureSUNX\");\n}\nstatic void load_GL_SUN_global_alpha(GLADloadproc load) {\n\tif(!GLAD_GL_SUN_global_alpha) return;\n\tglad_glGlobalAlphaFactorbSUN = (PFNGLGLOBALALPHAFACTORBSUNPROC)load(\"glGlobalAlphaFactorbSUN\");\n\tglad_glGlobalAlphaFactorsSUN = (PFNGLGLOBALALPHAFACTORSSUNPROC)load(\"glGlobalAlphaFactorsSUN\");\n\tglad_glGlobalAlphaFactoriSUN = (PFNGLGLOBALALPHAFACTORISUNPROC)load(\"glGlobalAlphaFactoriSUN\");\n\tglad_glGlobalAlphaFactorfSUN = (PFNGLGLOBALALPHAFACTORFSUNPROC)load(\"glGlobalAlphaFactorfSUN\");\n\tglad_glGlobalAlphaFactordSUN = (PFNGLGLOBALALPHAFACTORDSUNPROC)load(\"glGlobalAlphaFactordSUN\");\n\tglad_glGlobalAlphaFactorubSUN = (PFNGLGLOBALALPHAFACTORUBSUNPROC)load(\"glGlobalAlphaFactorubSUN\");\n\tglad_glGlobalAlphaFactorusSUN = (PFNGLGLOBALALPHAFACTORUSSUNPROC)load(\"glGlobalAlphaFactorusSUN\");\n\tglad_glGlobalAlphaFactoruiSUN = (PFNGLGLOBALALPHAFACTORUISUNPROC)load(\"glGlobalAlphaFactoruiSUN\");\n}\nstatic void load_GL_SUN_mesh_array(GLADloadproc load) {\n\tif(!GLAD_GL_SUN_mesh_array) return;\n\tglad_glDrawMeshArraysSUN = (PFNGLDRAWMESHARRAYSSUNPROC)load(\"glDrawMeshArraysSUN\");\n}\nstatic void load_GL_SUN_triangle_list(GLADloadproc load) {\n\tif(!GLAD_GL_SUN_triangle_list) return;\n\tglad_glReplacementCodeuiSUN = (PFNGLREPLACEMENTCODEUISUNPROC)load(\"glReplacementCodeuiSUN\");\n\tglad_glReplacementCodeusSUN = (PFNGLREPLACEMENTCODEUSSUNPROC)load(\"glReplacementCodeusSUN\");\n\tglad_glReplacementCodeubSUN = (PFNGLREPLACEMENTCODEUBSUNPROC)load(\"glReplacementCodeubSUN\");\n\tglad_glReplacementCodeuivSUN = (PFNGLREPLACEMENTCODEUIVSUNPROC)load(\"glReplacementCodeuivSUN\");\n\tglad_glReplacementCodeusvSUN = (PFNGLREPLACEMENTCODEUSVSUNPROC)load(\"glReplacementCodeusvSUN\");\n\tglad_glReplacementCodeubvSUN = (PFNGLREPLACEMENTCODEUBVSUNPROC)load(\"glReplacementCodeubvSUN\");\n\tglad_glReplacementCodePointerSUN = (PFNGLREPLACEMENTCODEPOINTERSUNPROC)load(\"glReplacementCodePointerSUN\");\n}\nstatic void load_GL_SUN_vertex(GLADloadproc load) {\n\tif(!GLAD_GL_SUN_vertex) return;\n\tglad_glColor4ubVertex2fSUN = (PFNGLCOLOR4UBVERTEX2FSUNPROC)load(\"glColor4ubVertex2fSUN\");\n\tglad_glColor4ubVertex2fvSUN = (PFNGLCOLOR4UBVERTEX2FVSUNPROC)load(\"glColor4ubVertex2fvSUN\");\n\tglad_glColor4ubVertex3fSUN = (PFNGLCOLOR4UBVERTEX3FSUNPROC)load(\"glColor4ubVertex3fSUN\");\n\tglad_glColor4ubVertex3fvSUN = (PFNGLCOLOR4UBVERTEX3FVSUNPROC)load(\"glColor4ubVertex3fvSUN\");\n\tglad_glColor3fVertex3fSUN = (PFNGLCOLOR3FVERTEX3FSUNPROC)load(\"glColor3fVertex3fSUN\");\n\tglad_glColor3fVertex3fvSUN = (PFNGLCOLOR3FVERTEX3FVSUNPROC)load(\"glColor3fVertex3fvSUN\");\n\tglad_glNormal3fVertex3fSUN = (PFNGLNORMAL3FVERTEX3FSUNPROC)load(\"glNormal3fVertex3fSUN\");\n\tglad_glNormal3fVertex3fvSUN = (PFNGLNORMAL3FVERTEX3FVSUNPROC)load(\"glNormal3fVertex3fvSUN\");\n\tglad_glColor4fNormal3fVertex3fSUN = (PFNGLCOLOR4FNORMAL3FVERTEX3FSUNPROC)load(\"glColor4fNormal3fVertex3fSUN\");\n\tglad_glColor4fNormal3fVertex3fvSUN = (PFNGLCOLOR4FNORMAL3FVERTEX3FVSUNPROC)load(\"glColor4fNormal3fVertex3fvSUN\");\n\tglad_glTexCoord2fVertex3fSUN = (PFNGLTEXCOORD2FVERTEX3FSUNPROC)load(\"glTexCoord2fVertex3fSUN\");\n\tglad_glTexCoord2fVertex3fvSUN = (PFNGLTEXCOORD2FVERTEX3FVSUNPROC)load(\"glTexCoord2fVertex3fvSUN\");\n\tglad_glTexCoord4fVertex4fSUN = (PFNGLTEXCOORD4FVERTEX4FSUNPROC)load(\"glTexCoord4fVertex4fSUN\");\n\tglad_glTexCoord4fVertex4fvSUN = (PFNGLTEXCOORD4FVERTEX4FVSUNPROC)load(\"glTexCoord4fVertex4fvSUN\");\n\tglad_glTexCoord2fColor4ubVertex3fSUN = (PFNGLTEXCOORD2FCOLOR4UBVERTEX3FSUNPROC)load(\"glTexCoord2fColor4ubVertex3fSUN\");\n\tglad_glTexCoord2fColor4ubVertex3fvSUN = (PFNGLTEXCOORD2FCOLOR4UBVERTEX3FVSUNPROC)load(\"glTexCoord2fColor4ubVertex3fvSUN\");\n\tglad_glTexCoord2fColor3fVertex3fSUN = (PFNGLTEXCOORD2FCOLOR3FVERTEX3FSUNPROC)load(\"glTexCoord2fColor3fVertex3fSUN\");\n\tglad_glTexCoord2fColor3fVertex3fvSUN = (PFNGLTEXCOORD2FCOLOR3FVERTEX3FVSUNPROC)load(\"glTexCoord2fColor3fVertex3fvSUN\");\n\tglad_glTexCoord2fNormal3fVertex3fSUN = (PFNGLTEXCOORD2FNORMAL3FVERTEX3FSUNPROC)load(\"glTexCoord2fNormal3fVertex3fSUN\");\n\tglad_glTexCoord2fNormal3fVertex3fvSUN = (PFNGLTEXCOORD2FNORMAL3FVERTEX3FVSUNPROC)load(\"glTexCoord2fNormal3fVertex3fvSUN\");\n\tglad_glTexCoord2fColor4fNormal3fVertex3fSUN = (PFNGLTEXCOORD2FCOLOR4FNORMAL3FVERTEX3FSUNPROC)load(\"glTexCoord2fColor4fNormal3fVertex3fSUN\");\n\tglad_glTexCoord2fColor4fNormal3fVertex3fvSUN = (PFNGLTEXCOORD2FCOLOR4FNORMAL3FVERTEX3FVSUNPROC)load(\"glTexCoord2fColor4fNormal3fVertex3fvSUN\");\n\tglad_glTexCoord4fColor4fNormal3fVertex4fSUN = (PFNGLTEXCOORD4FCOLOR4FNORMAL3FVERTEX4FSUNPROC)load(\"glTexCoord4fColor4fNormal3fVertex4fSUN\");\n\tglad_glTexCoord4fColor4fNormal3fVertex4fvSUN = (PFNGLTEXCOORD4FCOLOR4FNORMAL3FVERTEX4FVSUNPROC)load(\"glTexCoord4fColor4fNormal3fVertex4fvSUN\");\n\tglad_glReplacementCodeuiVertex3fSUN = (PFNGLREPLACEMENTCODEUIVERTEX3FSUNPROC)load(\"glReplacementCodeuiVertex3fSUN\");\n\tglad_glReplacementCodeuiVertex3fvSUN = (PFNGLREPLACEMENTCODEUIVERTEX3FVSUNPROC)load(\"glReplacementCodeuiVertex3fvSUN\");\n\tglad_glReplacementCodeuiColor4ubVertex3fSUN = (PFNGLREPLACEMENTCODEUICOLOR4UBVERTEX3FSUNPROC)load(\"glReplacementCodeuiColor4ubVertex3fSUN\");\n\tglad_glReplacementCodeuiColor4ubVertex3fvSUN = (PFNGLREPLACEMENTCODEUICOLOR4UBVERTEX3FVSUNPROC)load(\"glReplacementCodeuiColor4ubVertex3fvSUN\");\n\tglad_glReplacementCodeuiColor3fVertex3fSUN = (PFNGLREPLACEMENTCODEUICOLOR3FVERTEX3FSUNPROC)load(\"glReplacementCodeuiColor3fVertex3fSUN\");\n\tglad_glReplacementCodeuiColor3fVertex3fvSUN = (PFNGLREPLACEMENTCODEUICOLOR3FVERTEX3FVSUNPROC)load(\"glReplacementCodeuiColor3fVertex3fvSUN\");\n\tglad_glReplacementCodeuiNormal3fVertex3fSUN = (PFNGLREPLACEMENTCODEUINORMAL3FVERTEX3FSUNPROC)load(\"glReplacementCodeuiNormal3fVertex3fSUN\");\n\tglad_glReplacementCodeuiNormal3fVertex3fvSUN = (PFNGLREPLACEMENTCODEUINORMAL3FVERTEX3FVSUNPROC)load(\"glReplacementCodeuiNormal3fVertex3fvSUN\");\n\tglad_glReplacementCodeuiColor4fNormal3fVertex3fSUN = (PFNGLREPLACEMENTCODEUICOLOR4FNORMAL3FVERTEX3FSUNPROC)load(\"glReplacementCodeuiColor4fNormal3fVertex3fSUN\");\n\tglad_glReplacementCodeuiColor4fNormal3fVertex3fvSUN = (PFNGLREPLACEMENTCODEUICOLOR4FNORMAL3FVERTEX3FVSUNPROC)load(\"glReplacementCodeuiColor4fNormal3fVertex3fvSUN\");\n\tglad_glReplacementCodeuiTexCoord2fVertex3fSUN = (PFNGLREPLACEMENTCODEUITEXCOORD2FVERTEX3FSUNPROC)load(\"glReplacementCodeuiTexCoord2fVertex3fSUN\");\n\tglad_glReplacementCodeuiTexCoord2fVertex3fvSUN = (PFNGLREPLACEMENTCODEUITEXCOORD2FVERTEX3FVSUNPROC)load(\"glReplacementCodeuiTexCoord2fVertex3fvSUN\");\n\tglad_glReplacementCodeuiTexCoord2fNormal3fVertex3fSUN = (PFNGLREPLACEMENTCODEUITEXCOORD2FNORMAL3FVERTEX3FSUNPROC)load(\"glReplacementCodeuiTexCoord2fNormal3fVertex3fSUN\");\n\tglad_glReplacementCodeuiTexCoord2fNormal3fVertex3fvSUN = (PFNGLREPLACEMENTCODEUITEXCOORD2FNORMAL3FVERTEX3FVSUNPROC)load(\"glReplacementCodeuiTexCoord2fNormal3fVertex3fvSUN\");\n\tglad_glReplacementCodeuiTexCoord2fColor4fNormal3fVertex3fSUN = (PFNGLREPLACEMENTCODEUITEXCOORD2FCOLOR4FNORMAL3FVERTEX3FSUNPROC)load(\"glReplacementCodeuiTexCoord2fColor4fNormal3fVertex3fSUN\");\n\tglad_glReplacementCodeuiTexCoord2fColor4fNormal3fVertex3fvSUN = (PFNGLREPLACEMENTCODEUITEXCOORD2FCOLOR4FNORMAL3FVERTEX3FVSUNPROC)load(\"glReplacementCodeuiTexCoord2fColor4fNormal3fVertex3fvSUN\");\n}\nstatic int find_extensionsGL(void) {\n\tif (!get_exts()) return 0;\n\tGLAD_GL_3DFX_multisample = has_ext(\"GL_3DFX_multisample\");\n\tGLAD_GL_3DFX_tbuffer = has_ext(\"GL_3DFX_tbuffer\");\n\tGLAD_GL_3DFX_texture_compression_FXT1 = has_ext(\"GL_3DFX_texture_compression_FXT1\");\n\tGLAD_GL_AMD_blend_minmax_factor = has_ext(\"GL_AMD_blend_minmax_factor\");\n\tGLAD_GL_AMD_conservative_depth = has_ext(\"GL_AMD_conservative_depth\");\n\tGLAD_GL_AMD_debug_output = has_ext(\"GL_AMD_debug_output\");\n\tGLAD_GL_AMD_depth_clamp_separate = has_ext(\"GL_AMD_depth_clamp_separate\");\n\tGLAD_GL_AMD_draw_buffers_blend = has_ext(\"GL_AMD_draw_buffers_blend\");\n\tGLAD_GL_AMD_framebuffer_multisample_advanced = has_ext(\"GL_AMD_framebuffer_multisample_advanced\");\n\tGLAD_GL_AMD_framebuffer_sample_positions = has_ext(\"GL_AMD_framebuffer_sample_positions\");\n\tGLAD_GL_AMD_gcn_shader = has_ext(\"GL_AMD_gcn_shader\");\n\tGLAD_GL_AMD_gpu_shader_half_float = has_ext(\"GL_AMD_gpu_shader_half_float\");\n\tGLAD_GL_AMD_gpu_shader_int16 = has_ext(\"GL_AMD_gpu_shader_int16\");\n\tGLAD_GL_AMD_gpu_shader_int64 = has_ext(\"GL_AMD_gpu_shader_int64\");\n\tGLAD_GL_AMD_interleaved_elements = has_ext(\"GL_AMD_interleaved_elements\");\n\tGLAD_GL_AMD_multi_draw_indirect = has_ext(\"GL_AMD_multi_draw_indirect\");\n\tGLAD_GL_AMD_name_gen_delete = has_ext(\"GL_AMD_name_gen_delete\");\n\tGLAD_GL_AMD_occlusion_query_event = has_ext(\"GL_AMD_occlusion_query_event\");\n\tGLAD_GL_AMD_performance_monitor = has_ext(\"GL_AMD_performance_monitor\");\n\tGLAD_GL_AMD_pinned_memory = has_ext(\"GL_AMD_pinned_memory\");\n\tGLAD_GL_AMD_query_buffer_object = has_ext(\"GL_AMD_query_buffer_object\");\n\tGLAD_GL_AMD_sample_positions = has_ext(\"GL_AMD_sample_positions\");\n\tGLAD_GL_AMD_seamless_cubemap_per_texture = has_ext(\"GL_AMD_seamless_cubemap_per_texture\");\n\tGLAD_GL_AMD_shader_atomic_counter_ops = has_ext(\"GL_AMD_shader_atomic_counter_ops\");\n\tGLAD_GL_AMD_shader_ballot = has_ext(\"GL_AMD_shader_ballot\");\n\tGLAD_GL_AMD_shader_explicit_vertex_parameter = has_ext(\"GL_AMD_shader_explicit_vertex_parameter\");\n\tGLAD_GL_AMD_shader_gpu_shader_half_float_fetch = has_ext(\"GL_AMD_shader_gpu_shader_half_float_fetch\");\n\tGLAD_GL_AMD_shader_image_load_store_lod = has_ext(\"GL_AMD_shader_image_load_store_lod\");\n\tGLAD_GL_AMD_shader_stencil_export = has_ext(\"GL_AMD_shader_stencil_export\");\n\tGLAD_GL_AMD_shader_trinary_minmax = has_ext(\"GL_AMD_shader_trinary_minmax\");\n\tGLAD_GL_AMD_sparse_texture = has_ext(\"GL_AMD_sparse_texture\");\n\tGLAD_GL_AMD_stencil_operation_extended = has_ext(\"GL_AMD_stencil_operation_extended\");\n\tGLAD_GL_AMD_texture_gather_bias_lod = has_ext(\"GL_AMD_texture_gather_bias_lod\");\n\tGLAD_GL_AMD_texture_texture4 = has_ext(\"GL_AMD_texture_texture4\");\n\tGLAD_GL_AMD_transform_feedback3_lines_triangles = has_ext(\"GL_AMD_transform_feedback3_lines_triangles\");\n\tGLAD_GL_AMD_transform_feedback4 = has_ext(\"GL_AMD_transform_feedback4\");\n\tGLAD_GL_AMD_vertex_shader_layer = has_ext(\"GL_AMD_vertex_shader_layer\");\n\tGLAD_GL_AMD_vertex_shader_tessellator = has_ext(\"GL_AMD_vertex_shader_tessellator\");\n\tGLAD_GL_AMD_vertex_shader_viewport_index = has_ext(\"GL_AMD_vertex_shader_viewport_index\");\n\tGLAD_GL_APPLE_aux_depth_stencil = has_ext(\"GL_APPLE_aux_depth_stencil\");\n\tGLAD_GL_APPLE_client_storage = has_ext(\"GL_APPLE_client_storage\");\n\tGLAD_GL_APPLE_element_array = has_ext(\"GL_APPLE_element_array\");\n\tGLAD_GL_APPLE_fence = has_ext(\"GL_APPLE_fence\");\n\tGLAD_GL_APPLE_float_pixels = has_ext(\"GL_APPLE_float_pixels\");\n\tGLAD_GL_APPLE_flush_buffer_range = has_ext(\"GL_APPLE_flush_buffer_range\");\n\tGLAD_GL_APPLE_object_purgeable = has_ext(\"GL_APPLE_object_purgeable\");\n\tGLAD_GL_APPLE_rgb_422 = has_ext(\"GL_APPLE_rgb_422\");\n\tGLAD_GL_APPLE_row_bytes = has_ext(\"GL_APPLE_row_bytes\");\n\tGLAD_GL_APPLE_specular_vector = has_ext(\"GL_APPLE_specular_vector\");\n\tGLAD_GL_APPLE_texture_range = has_ext(\"GL_APPLE_texture_range\");\n\tGLAD_GL_APPLE_transform_hint = has_ext(\"GL_APPLE_transform_hint\");\n\tGLAD_GL_APPLE_vertex_array_object = has_ext(\"GL_APPLE_vertex_array_object\");\n\tGLAD_GL_APPLE_vertex_array_range = has_ext(\"GL_APPLE_vertex_array_range\");\n\tGLAD_GL_APPLE_vertex_program_evaluators = has_ext(\"GL_APPLE_vertex_program_evaluators\");\n\tGLAD_GL_APPLE_ycbcr_422 = has_ext(\"GL_APPLE_ycbcr_422\");\n\tGLAD_GL_ARB_ES2_compatibility = has_ext(\"GL_ARB_ES2_compatibility\");\n\tGLAD_GL_ARB_ES3_1_compatibility = has_ext(\"GL_ARB_ES3_1_compatibility\");\n\tGLAD_GL_ARB_ES3_2_compatibility = has_ext(\"GL_ARB_ES3_2_compatibility\");\n\tGLAD_GL_ARB_ES3_compatibility = has_ext(\"GL_ARB_ES3_compatibility\");\n\tGLAD_GL_ARB_arrays_of_arrays = has_ext(\"GL_ARB_arrays_of_arrays\");\n\tGLAD_GL_ARB_base_instance = has_ext(\"GL_ARB_base_instance\");\n\tGLAD_GL_ARB_bindless_texture = has_ext(\"GL_ARB_bindless_texture\");\n\tGLAD_GL_ARB_blend_func_extended = has_ext(\"GL_ARB_blend_func_extended\");\n\tGLAD_GL_ARB_buffer_storage = has_ext(\"GL_ARB_buffer_storage\");\n\tGLAD_GL_ARB_cl_event = has_ext(\"GL_ARB_cl_event\");\n\tGLAD_GL_ARB_clear_buffer_object = has_ext(\"GL_ARB_clear_buffer_object\");\n\tGLAD_GL_ARB_clear_texture = has_ext(\"GL_ARB_clear_texture\");\n\tGLAD_GL_ARB_clip_control = has_ext(\"GL_ARB_clip_control\");\n\tGLAD_GL_ARB_color_buffer_float = has_ext(\"GL_ARB_color_buffer_float\");\n\tGLAD_GL_ARB_compatibility = has_ext(\"GL_ARB_compatibility\");\n\tGLAD_GL_ARB_compressed_texture_pixel_storage = has_ext(\"GL_ARB_compressed_texture_pixel_storage\");\n\tGLAD_GL_ARB_compute_shader = has_ext(\"GL_ARB_compute_shader\");\n\tGLAD_GL_ARB_compute_variable_group_size = has_ext(\"GL_ARB_compute_variable_group_size\");\n\tGLAD_GL_ARB_conditional_render_inverted = has_ext(\"GL_ARB_conditional_render_inverted\");\n\tGLAD_GL_ARB_conservative_depth = has_ext(\"GL_ARB_conservative_depth\");\n\tGLAD_GL_ARB_copy_buffer = has_ext(\"GL_ARB_copy_buffer\");\n\tGLAD_GL_ARB_copy_image = has_ext(\"GL_ARB_copy_image\");\n\tGLAD_GL_ARB_cull_distance = has_ext(\"GL_ARB_cull_distance\");\n\tGLAD_GL_ARB_debug_output = has_ext(\"GL_ARB_debug_output\");\n\tGLAD_GL_ARB_depth_buffer_float = has_ext(\"GL_ARB_depth_buffer_float\");\n\tGLAD_GL_ARB_depth_clamp = has_ext(\"GL_ARB_depth_clamp\");\n\tGLAD_GL_ARB_depth_texture = has_ext(\"GL_ARB_depth_texture\");\n\tGLAD_GL_ARB_derivative_control = has_ext(\"GL_ARB_derivative_control\");\n\tGLAD_GL_ARB_direct_state_access = has_ext(\"GL_ARB_direct_state_access\");\n\tGLAD_GL_ARB_draw_buffers = has_ext(\"GL_ARB_draw_buffers\");\n\tGLAD_GL_ARB_draw_buffers_blend = has_ext(\"GL_ARB_draw_buffers_blend\");\n\tGLAD_GL_ARB_draw_elements_base_vertex = has_ext(\"GL_ARB_draw_elements_base_vertex\");\n\tGLAD_GL_ARB_draw_indirect = has_ext(\"GL_ARB_draw_indirect\");\n\tGLAD_GL_ARB_draw_instanced = has_ext(\"GL_ARB_draw_instanced\");\n\tGLAD_GL_ARB_enhanced_layouts = has_ext(\"GL_ARB_enhanced_layouts\");\n\tGLAD_GL_ARB_explicit_attrib_location = has_ext(\"GL_ARB_explicit_attrib_location\");\n\tGLAD_GL_ARB_explicit_uniform_location = has_ext(\"GL_ARB_explicit_uniform_location\");\n\tGLAD_GL_ARB_fragment_coord_conventions = has_ext(\"GL_ARB_fragment_coord_conventions\");\n\tGLAD_GL_ARB_fragment_layer_viewport = has_ext(\"GL_ARB_fragment_layer_viewport\");\n\tGLAD_GL_ARB_fragment_program = has_ext(\"GL_ARB_fragment_program\");\n\tGLAD_GL_ARB_fragment_program_shadow = has_ext(\"GL_ARB_fragment_program_shadow\");\n\tGLAD_GL_ARB_fragment_shader = has_ext(\"GL_ARB_fragment_shader\");\n\tGLAD_GL_ARB_fragment_shader_interlock = has_ext(\"GL_ARB_fragment_shader_interlock\");\n\tGLAD_GL_ARB_framebuffer_no_attachments = has_ext(\"GL_ARB_framebuffer_no_attachments\");\n\tGLAD_GL_ARB_framebuffer_object = has_ext(\"GL_ARB_framebuffer_object\");\n\tGLAD_GL_ARB_framebuffer_sRGB = has_ext(\"GL_ARB_framebuffer_sRGB\");\n\tGLAD_GL_ARB_geometry_shader4 = has_ext(\"GL_ARB_geometry_shader4\");\n\tGLAD_GL_ARB_get_program_binary = has_ext(\"GL_ARB_get_program_binary\");\n\tGLAD_GL_ARB_get_texture_sub_image = has_ext(\"GL_ARB_get_texture_sub_image\");\n\tGLAD_GL_ARB_gl_spirv = has_ext(\"GL_ARB_gl_spirv\");\n\tGLAD_GL_ARB_gpu_shader5 = has_ext(\"GL_ARB_gpu_shader5\");\n\tGLAD_GL_ARB_gpu_shader_fp64 = has_ext(\"GL_ARB_gpu_shader_fp64\");\n\tGLAD_GL_ARB_gpu_shader_int64 = has_ext(\"GL_ARB_gpu_shader_int64\");\n\tGLAD_GL_ARB_half_float_pixel = has_ext(\"GL_ARB_half_float_pixel\");\n\tGLAD_GL_ARB_half_float_vertex = has_ext(\"GL_ARB_half_float_vertex\");\n\tGLAD_GL_ARB_imaging = has_ext(\"GL_ARB_imaging\");\n\tGLAD_GL_ARB_indirect_parameters = has_ext(\"GL_ARB_indirect_parameters\");\n\tGLAD_GL_ARB_instanced_arrays = has_ext(\"GL_ARB_instanced_arrays\");\n\tGLAD_GL_ARB_internalformat_query = has_ext(\"GL_ARB_internalformat_query\");\n\tGLAD_GL_ARB_internalformat_query2 = has_ext(\"GL_ARB_internalformat_query2\");\n\tGLAD_GL_ARB_invalidate_subdata = has_ext(\"GL_ARB_invalidate_subdata\");\n\tGLAD_GL_ARB_map_buffer_alignment = has_ext(\"GL_ARB_map_buffer_alignment\");\n\tGLAD_GL_ARB_map_buffer_range = has_ext(\"GL_ARB_map_buffer_range\");\n\tGLAD_GL_ARB_matrix_palette = has_ext(\"GL_ARB_matrix_palette\");\n\tGLAD_GL_ARB_multi_bind = has_ext(\"GL_ARB_multi_bind\");\n\tGLAD_GL_ARB_multi_draw_indirect = has_ext(\"GL_ARB_multi_draw_indirect\");\n\tGLAD_GL_ARB_multisample = has_ext(\"GL_ARB_multisample\");\n\tGLAD_GL_ARB_multitexture = has_ext(\"GL_ARB_multitexture\");\n\tGLAD_GL_ARB_occlusion_query = has_ext(\"GL_ARB_occlusion_query\");\n\tGLAD_GL_ARB_occlusion_query2 = has_ext(\"GL_ARB_occlusion_query2\");\n\tGLAD_GL_ARB_parallel_shader_compile = has_ext(\"GL_ARB_parallel_shader_compile\");\n\tGLAD_GL_ARB_pipeline_statistics_query = has_ext(\"GL_ARB_pipeline_statistics_query\");\n\tGLAD_GL_ARB_pixel_buffer_object = has_ext(\"GL_ARB_pixel_buffer_object\");\n\tGLAD_GL_ARB_point_parameters = has_ext(\"GL_ARB_point_parameters\");\n\tGLAD_GL_ARB_point_sprite = has_ext(\"GL_ARB_point_sprite\");\n\tGLAD_GL_ARB_polygon_offset_clamp = has_ext(\"GL_ARB_polygon_offset_clamp\");\n\tGLAD_GL_ARB_post_depth_coverage = has_ext(\"GL_ARB_post_depth_coverage\");\n\tGLAD_GL_ARB_program_interface_query = has_ext(\"GL_ARB_program_interface_query\");\n\tGLAD_GL_ARB_provoking_vertex = has_ext(\"GL_ARB_provoking_vertex\");\n\tGLAD_GL_ARB_query_buffer_object = has_ext(\"GL_ARB_query_buffer_object\");\n\tGLAD_GL_ARB_robust_buffer_access_behavior = has_ext(\"GL_ARB_robust_buffer_access_behavior\");\n\tGLAD_GL_ARB_robustness = has_ext(\"GL_ARB_robustness\");\n\tGLAD_GL_ARB_robustness_isolation = has_ext(\"GL_ARB_robustness_isolation\");\n\tGLAD_GL_ARB_sample_locations = has_ext(\"GL_ARB_sample_locations\");\n\tGLAD_GL_ARB_sample_shading = has_ext(\"GL_ARB_sample_shading\");\n\tGLAD_GL_ARB_sampler_objects = has_ext(\"GL_ARB_sampler_objects\");\n\tGLAD_GL_ARB_seamless_cube_map = has_ext(\"GL_ARB_seamless_cube_map\");\n\tGLAD_GL_ARB_seamless_cubemap_per_texture = has_ext(\"GL_ARB_seamless_cubemap_per_texture\");\n\tGLAD_GL_ARB_separate_shader_objects = has_ext(\"GL_ARB_separate_shader_objects\");\n\tGLAD_GL_ARB_shader_atomic_counter_ops = has_ext(\"GL_ARB_shader_atomic_counter_ops\");\n\tGLAD_GL_ARB_shader_atomic_counters = has_ext(\"GL_ARB_shader_atomic_counters\");\n\tGLAD_GL_ARB_shader_ballot = has_ext(\"GL_ARB_shader_ballot\");\n\tGLAD_GL_ARB_shader_bit_encoding = has_ext(\"GL_ARB_shader_bit_encoding\");\n\tGLAD_GL_ARB_shader_clock = has_ext(\"GL_ARB_shader_clock\");\n\tGLAD_GL_ARB_shader_draw_parameters = has_ext(\"GL_ARB_shader_draw_parameters\");\n\tGLAD_GL_ARB_shader_group_vote = has_ext(\"GL_ARB_shader_group_vote\");\n\tGLAD_GL_ARB_shader_image_load_store = has_ext(\"GL_ARB_shader_image_load_store\");\n\tGLAD_GL_ARB_shader_image_size = has_ext(\"GL_ARB_shader_image_size\");\n\tGLAD_GL_ARB_shader_objects = has_ext(\"GL_ARB_shader_objects\");\n\tGLAD_GL_ARB_shader_precision = has_ext(\"GL_ARB_shader_precision\");\n\tGLAD_GL_ARB_shader_stencil_export = has_ext(\"GL_ARB_shader_stencil_export\");\n\tGLAD_GL_ARB_shader_storage_buffer_object = has_ext(\"GL_ARB_shader_storage_buffer_object\");\n\tGLAD_GL_ARB_shader_subroutine = has_ext(\"GL_ARB_shader_subroutine\");\n\tGLAD_GL_ARB_shader_texture_image_samples = has_ext(\"GL_ARB_shader_texture_image_samples\");\n\tGLAD_GL_ARB_shader_texture_lod = has_ext(\"GL_ARB_shader_texture_lod\");\n\tGLAD_GL_ARB_shader_viewport_layer_array = has_ext(\"GL_ARB_shader_viewport_layer_array\");\n\tGLAD_GL_ARB_shading_language_100 = has_ext(\"GL_ARB_shading_language_100\");\n\tGLAD_GL_ARB_shading_language_420pack = has_ext(\"GL_ARB_shading_language_420pack\");\n\tGLAD_GL_ARB_shading_language_include = has_ext(\"GL_ARB_shading_language_include\");\n\tGLAD_GL_ARB_shading_language_packing = has_ext(\"GL_ARB_shading_language_packing\");\n\tGLAD_GL_ARB_shadow = has_ext(\"GL_ARB_shadow\");\n\tGLAD_GL_ARB_shadow_ambient = has_ext(\"GL_ARB_shadow_ambient\");\n\tGLAD_GL_ARB_sparse_buffer = has_ext(\"GL_ARB_sparse_buffer\");\n\tGLAD_GL_ARB_sparse_texture = has_ext(\"GL_ARB_sparse_texture\");\n\tGLAD_GL_ARB_sparse_texture2 = has_ext(\"GL_ARB_sparse_texture2\");\n\tGLAD_GL_ARB_sparse_texture_clamp = has_ext(\"GL_ARB_sparse_texture_clamp\");\n\tGLAD_GL_ARB_spirv_extensions = has_ext(\"GL_ARB_spirv_extensions\");\n\tGLAD_GL_ARB_stencil_texturing = has_ext(\"GL_ARB_stencil_texturing\");\n\tGLAD_GL_ARB_sync = has_ext(\"GL_ARB_sync\");\n\tGLAD_GL_ARB_tessellation_shader = has_ext(\"GL_ARB_tessellation_shader\");\n\tGLAD_GL_ARB_texture_barrier = has_ext(\"GL_ARB_texture_barrier\");\n\tGLAD_GL_ARB_texture_border_clamp = has_ext(\"GL_ARB_texture_border_clamp\");\n\tGLAD_GL_ARB_texture_buffer_object = has_ext(\"GL_ARB_texture_buffer_object\");\n\tGLAD_GL_ARB_texture_buffer_object_rgb32 = has_ext(\"GL_ARB_texture_buffer_object_rgb32\");\n\tGLAD_GL_ARB_texture_buffer_range = has_ext(\"GL_ARB_texture_buffer_range\");\n\tGLAD_GL_ARB_texture_compression = has_ext(\"GL_ARB_texture_compression\");\n\tGLAD_GL_ARB_texture_compression_bptc = has_ext(\"GL_ARB_texture_compression_bptc\");\n\tGLAD_GL_ARB_texture_compression_rgtc = has_ext(\"GL_ARB_texture_compression_rgtc\");\n\tGLAD_GL_ARB_texture_cube_map = has_ext(\"GL_ARB_texture_cube_map\");\n\tGLAD_GL_ARB_texture_cube_map_array = has_ext(\"GL_ARB_texture_cube_map_array\");\n\tGLAD_GL_ARB_texture_env_add = has_ext(\"GL_ARB_texture_env_add\");\n\tGLAD_GL_ARB_texture_env_combine = has_ext(\"GL_ARB_texture_env_combine\");\n\tGLAD_GL_ARB_texture_env_crossbar = has_ext(\"GL_ARB_texture_env_crossbar\");\n\tGLAD_GL_ARB_texture_env_dot3 = has_ext(\"GL_ARB_texture_env_dot3\");\n\tGLAD_GL_ARB_texture_filter_anisotropic = has_ext(\"GL_ARB_texture_filter_anisotropic\");\n\tGLAD_GL_ARB_texture_filter_minmax = has_ext(\"GL_ARB_texture_filter_minmax\");\n\tGLAD_GL_ARB_texture_float = has_ext(\"GL_ARB_texture_float\");\n\tGLAD_GL_ARB_texture_gather = has_ext(\"GL_ARB_texture_gather\");\n\tGLAD_GL_ARB_texture_mirror_clamp_to_edge = has_ext(\"GL_ARB_texture_mirror_clamp_to_edge\");\n\tGLAD_GL_ARB_texture_mirrored_repeat = has_ext(\"GL_ARB_texture_mirrored_repeat\");\n\tGLAD_GL_ARB_texture_multisample = has_ext(\"GL_ARB_texture_multisample\");\n\tGLAD_GL_ARB_texture_non_power_of_two = has_ext(\"GL_ARB_texture_non_power_of_two\");\n\tGLAD_GL_ARB_texture_query_levels = has_ext(\"GL_ARB_texture_query_levels\");\n\tGLAD_GL_ARB_texture_query_lod = has_ext(\"GL_ARB_texture_query_lod\");\n\tGLAD_GL_ARB_texture_rectangle = has_ext(\"GL_ARB_texture_rectangle\");\n\tGLAD_GL_ARB_texture_rg = has_ext(\"GL_ARB_texture_rg\");\n\tGLAD_GL_ARB_texture_rgb10_a2ui = has_ext(\"GL_ARB_texture_rgb10_a2ui\");\n\tGLAD_GL_ARB_texture_stencil8 = has_ext(\"GL_ARB_texture_stencil8\");\n\tGLAD_GL_ARB_texture_storage = has_ext(\"GL_ARB_texture_storage\");\n\tGLAD_GL_ARB_texture_storage_multisample = has_ext(\"GL_ARB_texture_storage_multisample\");\n\tGLAD_GL_ARB_texture_swizzle = has_ext(\"GL_ARB_texture_swizzle\");\n\tGLAD_GL_ARB_texture_view = has_ext(\"GL_ARB_texture_view\");\n\tGLAD_GL_ARB_timer_query = has_ext(\"GL_ARB_timer_query\");\n\tGLAD_GL_ARB_transform_feedback2 = has_ext(\"GL_ARB_transform_feedback2\");\n\tGLAD_GL_ARB_transform_feedback3 = has_ext(\"GL_ARB_transform_feedback3\");\n\tGLAD_GL_ARB_transform_feedback_instanced = has_ext(\"GL_ARB_transform_feedback_instanced\");\n\tGLAD_GL_ARB_transform_feedback_overflow_query = has_ext(\"GL_ARB_transform_feedback_overflow_query\");\n\tGLAD_GL_ARB_transpose_matrix = has_ext(\"GL_ARB_transpose_matrix\");\n\tGLAD_GL_ARB_uniform_buffer_object = has_ext(\"GL_ARB_uniform_buffer_object\");\n\tGLAD_GL_ARB_vertex_array_bgra = has_ext(\"GL_ARB_vertex_array_bgra\");\n\tGLAD_GL_ARB_vertex_array_object = has_ext(\"GL_ARB_vertex_array_object\");\n\tGLAD_GL_ARB_vertex_attrib_64bit = has_ext(\"GL_ARB_vertex_attrib_64bit\");\n\tGLAD_GL_ARB_vertex_attrib_binding = has_ext(\"GL_ARB_vertex_attrib_binding\");\n\tGLAD_GL_ARB_vertex_blend = has_ext(\"GL_ARB_vertex_blend\");\n\tGLAD_GL_ARB_vertex_buffer_object = has_ext(\"GL_ARB_vertex_buffer_object\");\n\tGLAD_GL_ARB_vertex_program = has_ext(\"GL_ARB_vertex_program\");\n\tGLAD_GL_ARB_vertex_shader = has_ext(\"GL_ARB_vertex_shader\");\n\tGLAD_GL_ARB_vertex_type_10f_11f_11f_rev = has_ext(\"GL_ARB_vertex_type_10f_11f_11f_rev\");\n\tGLAD_GL_ARB_vertex_type_2_10_10_10_rev = has_ext(\"GL_ARB_vertex_type_2_10_10_10_rev\");\n\tGLAD_GL_ARB_viewport_array = has_ext(\"GL_ARB_viewport_array\");\n\tGLAD_GL_ARB_window_pos = has_ext(\"GL_ARB_window_pos\");\n\tGLAD_GL_ATI_draw_buffers = has_ext(\"GL_ATI_draw_buffers\");\n\tGLAD_GL_ATI_element_array = has_ext(\"GL_ATI_element_array\");\n\tGLAD_GL_ATI_envmap_bumpmap = has_ext(\"GL_ATI_envmap_bumpmap\");\n\tGLAD_GL_ATI_fragment_shader = has_ext(\"GL_ATI_fragment_shader\");\n\tGLAD_GL_ATI_map_object_buffer = has_ext(\"GL_ATI_map_object_buffer\");\n\tGLAD_GL_ATI_meminfo = has_ext(\"GL_ATI_meminfo\");\n\tGLAD_GL_ATI_pixel_format_float = has_ext(\"GL_ATI_pixel_format_float\");\n\tGLAD_GL_ATI_pn_triangles = has_ext(\"GL_ATI_pn_triangles\");\n\tGLAD_GL_ATI_separate_stencil = has_ext(\"GL_ATI_separate_stencil\");\n\tGLAD_GL_ATI_text_fragment_shader = has_ext(\"GL_ATI_text_fragment_shader\");\n\tGLAD_GL_ATI_texture_env_combine3 = has_ext(\"GL_ATI_texture_env_combine3\");\n\tGLAD_GL_ATI_texture_float = has_ext(\"GL_ATI_texture_float\");\n\tGLAD_GL_ATI_texture_mirror_once = has_ext(\"GL_ATI_texture_mirror_once\");\n\tGLAD_GL_ATI_vertex_array_object = has_ext(\"GL_ATI_vertex_array_object\");\n\tGLAD_GL_ATI_vertex_attrib_array_object = has_ext(\"GL_ATI_vertex_attrib_array_object\");\n\tGLAD_GL_ATI_vertex_streams = has_ext(\"GL_ATI_vertex_streams\");\n\tGLAD_GL_EXT_422_pixels = has_ext(\"GL_EXT_422_pixels\");\n\tGLAD_GL_EXT_EGL_image_storage = has_ext(\"GL_EXT_EGL_image_storage\");\n\tGLAD_GL_EXT_abgr = has_ext(\"GL_EXT_abgr\");\n\tGLAD_GL_EXT_bgra = has_ext(\"GL_EXT_bgra\");\n\tGLAD_GL_EXT_bindable_uniform = has_ext(\"GL_EXT_bindable_uniform\");\n\tGLAD_GL_EXT_blend_color = has_ext(\"GL_EXT_blend_color\");\n\tGLAD_GL_EXT_blend_equation_separate = has_ext(\"GL_EXT_blend_equation_separate\");\n\tGLAD_GL_EXT_blend_func_separate = has_ext(\"GL_EXT_blend_func_separate\");\n\tGLAD_GL_EXT_blend_logic_op = has_ext(\"GL_EXT_blend_logic_op\");\n\tGLAD_GL_EXT_blend_minmax = has_ext(\"GL_EXT_blend_minmax\");\n\tGLAD_GL_EXT_blend_subtract = has_ext(\"GL_EXT_blend_subtract\");\n\tGLAD_GL_EXT_clip_volume_hint = has_ext(\"GL_EXT_clip_volume_hint\");\n\tGLAD_GL_EXT_cmyka = has_ext(\"GL_EXT_cmyka\");\n\tGLAD_GL_EXT_color_subtable = has_ext(\"GL_EXT_color_subtable\");\n\tGLAD_GL_EXT_compiled_vertex_array = has_ext(\"GL_EXT_compiled_vertex_array\");\n\tGLAD_GL_EXT_convolution = has_ext(\"GL_EXT_convolution\");\n\tGLAD_GL_EXT_coordinate_frame = has_ext(\"GL_EXT_coordinate_frame\");\n\tGLAD_GL_EXT_copy_texture = has_ext(\"GL_EXT_copy_texture\");\n\tGLAD_GL_EXT_cull_vertex = has_ext(\"GL_EXT_cull_vertex\");\n\tGLAD_GL_EXT_debug_label = has_ext(\"GL_EXT_debug_label\");\n\tGLAD_GL_EXT_debug_marker = has_ext(\"GL_EXT_debug_marker\");\n\tGLAD_GL_EXT_depth_bounds_test = has_ext(\"GL_EXT_depth_bounds_test\");\n\tGLAD_GL_EXT_direct_state_access = has_ext(\"GL_EXT_direct_state_access\");\n\tGLAD_GL_EXT_draw_buffers2 = has_ext(\"GL_EXT_draw_buffers2\");\n\tGLAD_GL_EXT_draw_instanced = has_ext(\"GL_EXT_draw_instanced\");\n\tGLAD_GL_EXT_draw_range_elements = has_ext(\"GL_EXT_draw_range_elements\");\n\tGLAD_GL_EXT_external_buffer = has_ext(\"GL_EXT_external_buffer\");\n\tGLAD_GL_EXT_fog_coord = has_ext(\"GL_EXT_fog_coord\");\n\tGLAD_GL_EXT_framebuffer_blit = has_ext(\"GL_EXT_framebuffer_blit\");\n\tGLAD_GL_EXT_framebuffer_multisample = has_ext(\"GL_EXT_framebuffer_multisample\");\n\tGLAD_GL_EXT_framebuffer_multisample_blit_scaled = has_ext(\"GL_EXT_framebuffer_multisample_blit_scaled\");\n\tGLAD_GL_EXT_framebuffer_object = has_ext(\"GL_EXT_framebuffer_object\");\n\tGLAD_GL_EXT_framebuffer_sRGB = has_ext(\"GL_EXT_framebuffer_sRGB\");\n\tGLAD_GL_EXT_geometry_shader4 = has_ext(\"GL_EXT_geometry_shader4\");\n\tGLAD_GL_EXT_gpu_program_parameters = has_ext(\"GL_EXT_gpu_program_parameters\");\n\tGLAD_GL_EXT_gpu_shader4 = has_ext(\"GL_EXT_gpu_shader4\");\n\tGLAD_GL_EXT_histogram = has_ext(\"GL_EXT_histogram\");\n\tGLAD_GL_EXT_index_array_formats = has_ext(\"GL_EXT_index_array_formats\");\n\tGLAD_GL_EXT_index_func = has_ext(\"GL_EXT_index_func\");\n\tGLAD_GL_EXT_index_material = has_ext(\"GL_EXT_index_material\");\n\tGLAD_GL_EXT_index_texture = has_ext(\"GL_EXT_index_texture\");\n\tGLAD_GL_EXT_light_texture = has_ext(\"GL_EXT_light_texture\");\n\tGLAD_GL_EXT_memory_object = has_ext(\"GL_EXT_memory_object\");\n\tGLAD_GL_EXT_memory_object_fd = has_ext(\"GL_EXT_memory_object_fd\");\n\tGLAD_GL_EXT_memory_object_win32 = has_ext(\"GL_EXT_memory_object_win32\");\n\tGLAD_GL_EXT_misc_attribute = has_ext(\"GL_EXT_misc_attribute\");\n\tGLAD_GL_EXT_multi_draw_arrays = has_ext(\"GL_EXT_multi_draw_arrays\");\n\tGLAD_GL_EXT_multisample = has_ext(\"GL_EXT_multisample\");\n\tGLAD_GL_EXT_packed_depth_stencil = has_ext(\"GL_EXT_packed_depth_stencil\");\n\tGLAD_GL_EXT_packed_float = has_ext(\"GL_EXT_packed_float\");\n\tGLAD_GL_EXT_packed_pixels = has_ext(\"GL_EXT_packed_pixels\");\n\tGLAD_GL_EXT_paletted_texture = has_ext(\"GL_EXT_paletted_texture\");\n\tGLAD_GL_EXT_pixel_buffer_object = has_ext(\"GL_EXT_pixel_buffer_object\");\n\tGLAD_GL_EXT_pixel_transform = has_ext(\"GL_EXT_pixel_transform\");\n\tGLAD_GL_EXT_pixel_transform_color_table = has_ext(\"GL_EXT_pixel_transform_color_table\");\n\tGLAD_GL_EXT_point_parameters = has_ext(\"GL_EXT_point_parameters\");\n\tGLAD_GL_EXT_polygon_offset = has_ext(\"GL_EXT_polygon_offset\");\n\tGLAD_GL_EXT_polygon_offset_clamp = has_ext(\"GL_EXT_polygon_offset_clamp\");\n\tGLAD_GL_EXT_post_depth_coverage = has_ext(\"GL_EXT_post_depth_coverage\");\n\tGLAD_GL_EXT_provoking_vertex = has_ext(\"GL_EXT_provoking_vertex\");\n\tGLAD_GL_EXT_raster_multisample = has_ext(\"GL_EXT_raster_multisample\");\n\tGLAD_GL_EXT_rescale_normal = has_ext(\"GL_EXT_rescale_normal\");\n\tGLAD_GL_EXT_secondary_color = has_ext(\"GL_EXT_secondary_color\");\n\tGLAD_GL_EXT_semaphore = has_ext(\"GL_EXT_semaphore\");\n\tGLAD_GL_EXT_semaphore_fd = has_ext(\"GL_EXT_semaphore_fd\");\n\tGLAD_GL_EXT_semaphore_win32 = has_ext(\"GL_EXT_semaphore_win32\");\n\tGLAD_GL_EXT_separate_shader_objects = has_ext(\"GL_EXT_separate_shader_objects\");\n\tGLAD_GL_EXT_separate_specular_color = has_ext(\"GL_EXT_separate_specular_color\");\n\tGLAD_GL_EXT_shader_framebuffer_fetch = has_ext(\"GL_EXT_shader_framebuffer_fetch\");\n\tGLAD_GL_EXT_shader_framebuffer_fetch_non_coherent = has_ext(\"GL_EXT_shader_framebuffer_fetch_non_coherent\");\n\tGLAD_GL_EXT_shader_image_load_formatted = has_ext(\"GL_EXT_shader_image_load_formatted\");\n\tGLAD_GL_EXT_shader_image_load_store = has_ext(\"GL_EXT_shader_image_load_store\");\n\tGLAD_GL_EXT_shader_integer_mix = has_ext(\"GL_EXT_shader_integer_mix\");\n\tGLAD_GL_EXT_shadow_funcs = has_ext(\"GL_EXT_shadow_funcs\");\n\tGLAD_GL_EXT_shared_texture_palette = has_ext(\"GL_EXT_shared_texture_palette\");\n\tGLAD_GL_EXT_sparse_texture2 = has_ext(\"GL_EXT_sparse_texture2\");\n\tGLAD_GL_EXT_stencil_clear_tag = has_ext(\"GL_EXT_stencil_clear_tag\");\n\tGLAD_GL_EXT_stencil_two_side = has_ext(\"GL_EXT_stencil_two_side\");\n\tGLAD_GL_EXT_stencil_wrap = has_ext(\"GL_EXT_stencil_wrap\");\n\tGLAD_GL_EXT_subtexture = has_ext(\"GL_EXT_subtexture\");\n\tGLAD_GL_EXT_texture = has_ext(\"GL_EXT_texture\");\n\tGLAD_GL_EXT_texture3D = has_ext(\"GL_EXT_texture3D\");\n\tGLAD_GL_EXT_texture_array = has_ext(\"GL_EXT_texture_array\");\n\tGLAD_GL_EXT_texture_buffer_object = has_ext(\"GL_EXT_texture_buffer_object\");\n\tGLAD_GL_EXT_texture_compression_latc = has_ext(\"GL_EXT_texture_compression_latc\");\n\tGLAD_GL_EXT_texture_compression_rgtc = has_ext(\"GL_EXT_texture_compression_rgtc\");\n\tGLAD_GL_EXT_texture_compression_s3tc = has_ext(\"GL_EXT_texture_compression_s3tc\");\n\tGLAD_GL_EXT_texture_cube_map = has_ext(\"GL_EXT_texture_cube_map\");\n\tGLAD_GL_EXT_texture_env_add = has_ext(\"GL_EXT_texture_env_add\");\n\tGLAD_GL_EXT_texture_env_combine = has_ext(\"GL_EXT_texture_env_combine\");\n\tGLAD_GL_EXT_texture_env_dot3 = has_ext(\"GL_EXT_texture_env_dot3\");\n\tGLAD_GL_EXT_texture_filter_anisotropic = has_ext(\"GL_EXT_texture_filter_anisotropic\");\n\tGLAD_GL_EXT_texture_filter_minmax = has_ext(\"GL_EXT_texture_filter_minmax\");\n\tGLAD_GL_EXT_texture_integer = has_ext(\"GL_EXT_texture_integer\");\n\tGLAD_GL_EXT_texture_lod_bias = has_ext(\"GL_EXT_texture_lod_bias\");\n\tGLAD_GL_EXT_texture_mirror_clamp = has_ext(\"GL_EXT_texture_mirror_clamp\");\n\tGLAD_GL_EXT_texture_object = has_ext(\"GL_EXT_texture_object\");\n\tGLAD_GL_EXT_texture_perturb_normal = has_ext(\"GL_EXT_texture_perturb_normal\");\n\tGLAD_GL_EXT_texture_sRGB = has_ext(\"GL_EXT_texture_sRGB\");\n\tGLAD_GL_EXT_texture_sRGB_decode = has_ext(\"GL_EXT_texture_sRGB_decode\");\n\tGLAD_GL_EXT_texture_shared_exponent = has_ext(\"GL_EXT_texture_shared_exponent\");\n\tGLAD_GL_EXT_texture_snorm = has_ext(\"GL_EXT_texture_snorm\");\n\tGLAD_GL_EXT_texture_swizzle = has_ext(\"GL_EXT_texture_swizzle\");\n\tGLAD_GL_EXT_timer_query = has_ext(\"GL_EXT_timer_query\");\n\tGLAD_GL_EXT_transform_feedback = has_ext(\"GL_EXT_transform_feedback\");\n\tGLAD_GL_EXT_vertex_array = has_ext(\"GL_EXT_vertex_array\");\n\tGLAD_GL_EXT_vertex_array_bgra = has_ext(\"GL_EXT_vertex_array_bgra\");\n\tGLAD_GL_EXT_vertex_attrib_64bit = has_ext(\"GL_EXT_vertex_attrib_64bit\");\n\tGLAD_GL_EXT_vertex_shader = has_ext(\"GL_EXT_vertex_shader\");\n\tGLAD_GL_EXT_vertex_weighting = has_ext(\"GL_EXT_vertex_weighting\");\n\tGLAD_GL_EXT_win32_keyed_mutex = has_ext(\"GL_EXT_win32_keyed_mutex\");\n\tGLAD_GL_EXT_window_rectangles = has_ext(\"GL_EXT_window_rectangles\");\n\tGLAD_GL_EXT_x11_sync_object = has_ext(\"GL_EXT_x11_sync_object\");\n\tGLAD_GL_GREMEDY_frame_terminator = has_ext(\"GL_GREMEDY_frame_terminator\");\n\tGLAD_GL_GREMEDY_string_marker = has_ext(\"GL_GREMEDY_string_marker\");\n\tGLAD_GL_HP_convolution_border_modes = has_ext(\"GL_HP_convolution_border_modes\");\n\tGLAD_GL_HP_image_transform = has_ext(\"GL_HP_image_transform\");\n\tGLAD_GL_HP_occlusion_test = has_ext(\"GL_HP_occlusion_test\");\n\tGLAD_GL_HP_texture_lighting = has_ext(\"GL_HP_texture_lighting\");\n\tGLAD_GL_IBM_cull_vertex = has_ext(\"GL_IBM_cull_vertex\");\n\tGLAD_GL_IBM_multimode_draw_arrays = has_ext(\"GL_IBM_multimode_draw_arrays\");\n\tGLAD_GL_IBM_rasterpos_clip = has_ext(\"GL_IBM_rasterpos_clip\");\n\tGLAD_GL_IBM_static_data = has_ext(\"GL_IBM_static_data\");\n\tGLAD_GL_IBM_texture_mirrored_repeat = has_ext(\"GL_IBM_texture_mirrored_repeat\");\n\tGLAD_GL_IBM_vertex_array_lists = has_ext(\"GL_IBM_vertex_array_lists\");\n\tGLAD_GL_INGR_blend_func_separate = has_ext(\"GL_INGR_blend_func_separate\");\n\tGLAD_GL_INGR_color_clamp = has_ext(\"GL_INGR_color_clamp\");\n\tGLAD_GL_INGR_interlace_read = has_ext(\"GL_INGR_interlace_read\");\n\tGLAD_GL_INTEL_blackhole_render = has_ext(\"GL_INTEL_blackhole_render\");\n\tGLAD_GL_INTEL_conservative_rasterization = has_ext(\"GL_INTEL_conservative_rasterization\");\n\tGLAD_GL_INTEL_fragment_shader_ordering = has_ext(\"GL_INTEL_fragment_shader_ordering\");\n\tGLAD_GL_INTEL_framebuffer_CMAA = has_ext(\"GL_INTEL_framebuffer_CMAA\");\n\tGLAD_GL_INTEL_map_texture = has_ext(\"GL_INTEL_map_texture\");\n\tGLAD_GL_INTEL_parallel_arrays = has_ext(\"GL_INTEL_parallel_arrays\");\n\tGLAD_GL_INTEL_performance_query = has_ext(\"GL_INTEL_performance_query\");\n\tGLAD_GL_KHR_blend_equation_advanced = has_ext(\"GL_KHR_blend_equation_advanced\");\n\tGLAD_GL_KHR_blend_equation_advanced_coherent = has_ext(\"GL_KHR_blend_equation_advanced_coherent\");\n\tGLAD_GL_KHR_context_flush_control = has_ext(\"GL_KHR_context_flush_control\");\n\tGLAD_GL_KHR_debug = has_ext(\"GL_KHR_debug\");\n\tGLAD_GL_KHR_no_error = has_ext(\"GL_KHR_no_error\");\n\tGLAD_GL_KHR_parallel_shader_compile = has_ext(\"GL_KHR_parallel_shader_compile\");\n\tGLAD_GL_KHR_robust_buffer_access_behavior = has_ext(\"GL_KHR_robust_buffer_access_behavior\");\n\tGLAD_GL_KHR_robustness = has_ext(\"GL_KHR_robustness\");\n\tGLAD_GL_KHR_texture_compression_astc_hdr = has_ext(\"GL_KHR_texture_compression_astc_hdr\");\n\tGLAD_GL_KHR_texture_compression_astc_ldr = has_ext(\"GL_KHR_texture_compression_astc_ldr\");\n\tGLAD_GL_KHR_texture_compression_astc_sliced_3d = has_ext(\"GL_KHR_texture_compression_astc_sliced_3d\");\n\tGLAD_GL_MESAX_texture_stack = has_ext(\"GL_MESAX_texture_stack\");\n\tGLAD_GL_MESA_pack_invert = has_ext(\"GL_MESA_pack_invert\");\n\tGLAD_GL_MESA_program_binary_formats = has_ext(\"GL_MESA_program_binary_formats\");\n\tGLAD_GL_MESA_resize_buffers = has_ext(\"GL_MESA_resize_buffers\");\n\tGLAD_GL_MESA_shader_integer_functions = has_ext(\"GL_MESA_shader_integer_functions\");\n\tGLAD_GL_MESA_tile_raster_order = has_ext(\"GL_MESA_tile_raster_order\");\n\tGLAD_GL_MESA_window_pos = has_ext(\"GL_MESA_window_pos\");\n\tGLAD_GL_MESA_ycbcr_texture = has_ext(\"GL_MESA_ycbcr_texture\");\n\tGLAD_GL_NVX_blend_equation_advanced_multi_draw_buffers = has_ext(\"GL_NVX_blend_equation_advanced_multi_draw_buffers\");\n\tGLAD_GL_NVX_conditional_render = has_ext(\"GL_NVX_conditional_render\");\n\tGLAD_GL_NVX_gpu_memory_info = has_ext(\"GL_NVX_gpu_memory_info\");\n\tGLAD_GL_NVX_linked_gpu_multicast = has_ext(\"GL_NVX_linked_gpu_multicast\");\n\tGLAD_GL_NV_alpha_to_coverage_dither_control = has_ext(\"GL_NV_alpha_to_coverage_dither_control\");\n\tGLAD_GL_NV_bindless_multi_draw_indirect = has_ext(\"GL_NV_bindless_multi_draw_indirect\");\n\tGLAD_GL_NV_bindless_multi_draw_indirect_count = has_ext(\"GL_NV_bindless_multi_draw_indirect_count\");\n\tGLAD_GL_NV_bindless_texture = has_ext(\"GL_NV_bindless_texture\");\n\tGLAD_GL_NV_blend_equation_advanced = has_ext(\"GL_NV_blend_equation_advanced\");\n\tGLAD_GL_NV_blend_equation_advanced_coherent = has_ext(\"GL_NV_blend_equation_advanced_coherent\");\n\tGLAD_GL_NV_blend_minmax_factor = has_ext(\"GL_NV_blend_minmax_factor\");\n\tGLAD_GL_NV_blend_square = has_ext(\"GL_NV_blend_square\");\n\tGLAD_GL_NV_clip_space_w_scaling = has_ext(\"GL_NV_clip_space_w_scaling\");\n\tGLAD_GL_NV_command_list = has_ext(\"GL_NV_command_list\");\n\tGLAD_GL_NV_compute_program5 = has_ext(\"GL_NV_compute_program5\");\n\tGLAD_GL_NV_compute_shader_derivatives = has_ext(\"GL_NV_compute_shader_derivatives\");\n\tGLAD_GL_NV_conditional_render = has_ext(\"GL_NV_conditional_render\");\n\tGLAD_GL_NV_conservative_raster = has_ext(\"GL_NV_conservative_raster\");\n\tGLAD_GL_NV_conservative_raster_dilate = has_ext(\"GL_NV_conservative_raster_dilate\");\n\tGLAD_GL_NV_conservative_raster_pre_snap = has_ext(\"GL_NV_conservative_raster_pre_snap\");\n\tGLAD_GL_NV_conservative_raster_pre_snap_triangles = has_ext(\"GL_NV_conservative_raster_pre_snap_triangles\");\n\tGLAD_GL_NV_conservative_raster_underestimation = has_ext(\"GL_NV_conservative_raster_underestimation\");\n\tGLAD_GL_NV_copy_depth_to_color = has_ext(\"GL_NV_copy_depth_to_color\");\n\tGLAD_GL_NV_copy_image = has_ext(\"GL_NV_copy_image\");\n\tGLAD_GL_NV_deep_texture3D = has_ext(\"GL_NV_deep_texture3D\");\n\tGLAD_GL_NV_depth_buffer_float = has_ext(\"GL_NV_depth_buffer_float\");\n\tGLAD_GL_NV_depth_clamp = has_ext(\"GL_NV_depth_clamp\");\n\tGLAD_GL_NV_draw_texture = has_ext(\"GL_NV_draw_texture\");\n\tGLAD_GL_NV_draw_vulkan_image = has_ext(\"GL_NV_draw_vulkan_image\");\n\tGLAD_GL_NV_evaluators = has_ext(\"GL_NV_evaluators\");\n\tGLAD_GL_NV_explicit_multisample = has_ext(\"GL_NV_explicit_multisample\");\n\tGLAD_GL_NV_fence = has_ext(\"GL_NV_fence\");\n\tGLAD_GL_NV_fill_rectangle = has_ext(\"GL_NV_fill_rectangle\");\n\tGLAD_GL_NV_float_buffer = has_ext(\"GL_NV_float_buffer\");\n\tGLAD_GL_NV_fog_distance = has_ext(\"GL_NV_fog_distance\");\n\tGLAD_GL_NV_fragment_coverage_to_color = has_ext(\"GL_NV_fragment_coverage_to_color\");\n\tGLAD_GL_NV_fragment_program = has_ext(\"GL_NV_fragment_program\");\n\tGLAD_GL_NV_fragment_program2 = has_ext(\"GL_NV_fragment_program2\");\n\tGLAD_GL_NV_fragment_program4 = has_ext(\"GL_NV_fragment_program4\");\n\tGLAD_GL_NV_fragment_program_option = has_ext(\"GL_NV_fragment_program_option\");\n\tGLAD_GL_NV_fragment_shader_barycentric = has_ext(\"GL_NV_fragment_shader_barycentric\");\n\tGLAD_GL_NV_fragment_shader_interlock = has_ext(\"GL_NV_fragment_shader_interlock\");\n\tGLAD_GL_NV_framebuffer_mixed_samples = has_ext(\"GL_NV_framebuffer_mixed_samples\");\n\tGLAD_GL_NV_framebuffer_multisample_coverage = has_ext(\"GL_NV_framebuffer_multisample_coverage\");\n\tGLAD_GL_NV_geometry_program4 = has_ext(\"GL_NV_geometry_program4\");\n\tGLAD_GL_NV_geometry_shader4 = has_ext(\"GL_NV_geometry_shader4\");\n\tGLAD_GL_NV_geometry_shader_passthrough = has_ext(\"GL_NV_geometry_shader_passthrough\");\n\tGLAD_GL_NV_gpu_multicast = has_ext(\"GL_NV_gpu_multicast\");\n\tGLAD_GL_NV_gpu_program4 = has_ext(\"GL_NV_gpu_program4\");\n\tGLAD_GL_NV_gpu_program5 = has_ext(\"GL_NV_gpu_program5\");\n\tGLAD_GL_NV_gpu_program5_mem_extended = has_ext(\"GL_NV_gpu_program5_mem_extended\");\n\tGLAD_GL_NV_gpu_shader5 = has_ext(\"GL_NV_gpu_shader5\");\n\tGLAD_GL_NV_half_float = has_ext(\"GL_NV_half_float\");\n\tGLAD_GL_NV_internalformat_sample_query = has_ext(\"GL_NV_internalformat_sample_query\");\n\tGLAD_GL_NV_light_max_exponent = has_ext(\"GL_NV_light_max_exponent\");\n\tGLAD_GL_NV_memory_attachment = has_ext(\"GL_NV_memory_attachment\");\n\tGLAD_GL_NV_mesh_shader = has_ext(\"GL_NV_mesh_shader\");\n\tGLAD_GL_NV_multisample_coverage = has_ext(\"GL_NV_multisample_coverage\");\n\tGLAD_GL_NV_multisample_filter_hint = has_ext(\"GL_NV_multisample_filter_hint\");\n\tGLAD_GL_NV_occlusion_query = has_ext(\"GL_NV_occlusion_query\");\n\tGLAD_GL_NV_packed_depth_stencil = has_ext(\"GL_NV_packed_depth_stencil\");\n\tGLAD_GL_NV_parameter_buffer_object = has_ext(\"GL_NV_parameter_buffer_object\");\n\tGLAD_GL_NV_parameter_buffer_object2 = has_ext(\"GL_NV_parameter_buffer_object2\");\n\tGLAD_GL_NV_path_rendering = has_ext(\"GL_NV_path_rendering\");\n\tGLAD_GL_NV_path_rendering_shared_edge = has_ext(\"GL_NV_path_rendering_shared_edge\");\n\tGLAD_GL_NV_pixel_data_range = has_ext(\"GL_NV_pixel_data_range\");\n\tGLAD_GL_NV_point_sprite = has_ext(\"GL_NV_point_sprite\");\n\tGLAD_GL_NV_present_video = has_ext(\"GL_NV_present_video\");\n\tGLAD_GL_NV_primitive_restart = has_ext(\"GL_NV_primitive_restart\");\n\tGLAD_GL_NV_query_resource = has_ext(\"GL_NV_query_resource\");\n\tGLAD_GL_NV_query_resource_tag = has_ext(\"GL_NV_query_resource_tag\");\n\tGLAD_GL_NV_register_combiners = has_ext(\"GL_NV_register_combiners\");\n\tGLAD_GL_NV_register_combiners2 = has_ext(\"GL_NV_register_combiners2\");\n\tGLAD_GL_NV_representative_fragment_test = has_ext(\"GL_NV_representative_fragment_test\");\n\tGLAD_GL_NV_robustness_video_memory_purge = has_ext(\"GL_NV_robustness_video_memory_purge\");\n\tGLAD_GL_NV_sample_locations = has_ext(\"GL_NV_sample_locations\");\n\tGLAD_GL_NV_sample_mask_override_coverage = has_ext(\"GL_NV_sample_mask_override_coverage\");\n\tGLAD_GL_NV_scissor_exclusive = has_ext(\"GL_NV_scissor_exclusive\");\n\tGLAD_GL_NV_shader_atomic_counters = has_ext(\"GL_NV_shader_atomic_counters\");\n\tGLAD_GL_NV_shader_atomic_float = has_ext(\"GL_NV_shader_atomic_float\");\n\tGLAD_GL_NV_shader_atomic_float64 = has_ext(\"GL_NV_shader_atomic_float64\");\n\tGLAD_GL_NV_shader_atomic_fp16_vector = has_ext(\"GL_NV_shader_atomic_fp16_vector\");\n\tGLAD_GL_NV_shader_atomic_int64 = has_ext(\"GL_NV_shader_atomic_int64\");\n\tGLAD_GL_NV_shader_buffer_load = has_ext(\"GL_NV_shader_buffer_load\");\n\tGLAD_GL_NV_shader_buffer_store = has_ext(\"GL_NV_shader_buffer_store\");\n\tGLAD_GL_NV_shader_storage_buffer_object = has_ext(\"GL_NV_shader_storage_buffer_object\");\n\tGLAD_GL_NV_shader_texture_footprint = has_ext(\"GL_NV_shader_texture_footprint\");\n\tGLAD_GL_NV_shader_thread_group = has_ext(\"GL_NV_shader_thread_group\");\n\tGLAD_GL_NV_shader_thread_shuffle = has_ext(\"GL_NV_shader_thread_shuffle\");\n\tGLAD_GL_NV_shading_rate_image = has_ext(\"GL_NV_shading_rate_image\");\n\tGLAD_GL_NV_stereo_view_rendering = has_ext(\"GL_NV_stereo_view_rendering\");\n\tGLAD_GL_NV_tessellation_program5 = has_ext(\"GL_NV_tessellation_program5\");\n\tGLAD_GL_NV_texgen_emboss = has_ext(\"GL_NV_texgen_emboss\");\n\tGLAD_GL_NV_texgen_reflection = has_ext(\"GL_NV_texgen_reflection\");\n\tGLAD_GL_NV_texture_barrier = has_ext(\"GL_NV_texture_barrier\");\n\tGLAD_GL_NV_texture_compression_vtc = has_ext(\"GL_NV_texture_compression_vtc\");\n\tGLAD_GL_NV_texture_env_combine4 = has_ext(\"GL_NV_texture_env_combine4\");\n\tGLAD_GL_NV_texture_expand_normal = has_ext(\"GL_NV_texture_expand_normal\");\n\tGLAD_GL_NV_texture_multisample = has_ext(\"GL_NV_texture_multisample\");\n\tGLAD_GL_NV_texture_rectangle = has_ext(\"GL_NV_texture_rectangle\");\n\tGLAD_GL_NV_texture_rectangle_compressed = has_ext(\"GL_NV_texture_rectangle_compressed\");\n\tGLAD_GL_NV_texture_shader = has_ext(\"GL_NV_texture_shader\");\n\tGLAD_GL_NV_texture_shader2 = has_ext(\"GL_NV_texture_shader2\");\n\tGLAD_GL_NV_texture_shader3 = has_ext(\"GL_NV_texture_shader3\");\n\tGLAD_GL_NV_transform_feedback = has_ext(\"GL_NV_transform_feedback\");\n\tGLAD_GL_NV_transform_feedback2 = has_ext(\"GL_NV_transform_feedback2\");\n\tGLAD_GL_NV_uniform_buffer_unified_memory = has_ext(\"GL_NV_uniform_buffer_unified_memory\");\n\tGLAD_GL_NV_vdpau_interop = has_ext(\"GL_NV_vdpau_interop\");\n\tGLAD_GL_NV_vertex_array_range = has_ext(\"GL_NV_vertex_array_range\");\n\tGLAD_GL_NV_vertex_array_range2 = has_ext(\"GL_NV_vertex_array_range2\");\n\tGLAD_GL_NV_vertex_attrib_integer_64bit = has_ext(\"GL_NV_vertex_attrib_integer_64bit\");\n\tGLAD_GL_NV_vertex_buffer_unified_memory = has_ext(\"GL_NV_vertex_buffer_unified_memory\");\n\tGLAD_GL_NV_vertex_program = has_ext(\"GL_NV_vertex_program\");\n\tGLAD_GL_NV_vertex_program1_1 = has_ext(\"GL_NV_vertex_program1_1\");\n\tGLAD_GL_NV_vertex_program2 = has_ext(\"GL_NV_vertex_program2\");\n\tGLAD_GL_NV_vertex_program2_option = has_ext(\"GL_NV_vertex_program2_option\");\n\tGLAD_GL_NV_vertex_program3 = has_ext(\"GL_NV_vertex_program3\");\n\tGLAD_GL_NV_vertex_program4 = has_ext(\"GL_NV_vertex_program4\");\n\tGLAD_GL_NV_video_capture = has_ext(\"GL_NV_video_capture\");\n\tGLAD_GL_NV_viewport_array2 = has_ext(\"GL_NV_viewport_array2\");\n\tGLAD_GL_NV_viewport_swizzle = has_ext(\"GL_NV_viewport_swizzle\");\n\tGLAD_GL_OES_byte_coordinates = has_ext(\"GL_OES_byte_coordinates\");\n\tGLAD_GL_OES_compressed_paletted_texture = has_ext(\"GL_OES_compressed_paletted_texture\");\n\tGLAD_GL_OES_fixed_point = has_ext(\"GL_OES_fixed_point\");\n\tGLAD_GL_OES_query_matrix = has_ext(\"GL_OES_query_matrix\");\n\tGLAD_GL_OES_read_format = has_ext(\"GL_OES_read_format\");\n\tGLAD_GL_OES_single_precision = has_ext(\"GL_OES_single_precision\");\n\tGLAD_GL_OML_interlace = has_ext(\"GL_OML_interlace\");\n\tGLAD_GL_OML_resample = has_ext(\"GL_OML_resample\");\n\tGLAD_GL_OML_subsample = has_ext(\"GL_OML_subsample\");\n\tGLAD_GL_OVR_multiview = has_ext(\"GL_OVR_multiview\");\n\tGLAD_GL_OVR_multiview2 = has_ext(\"GL_OVR_multiview2\");\n\tGLAD_GL_PGI_misc_hints = has_ext(\"GL_PGI_misc_hints\");\n\tGLAD_GL_PGI_vertex_hints = has_ext(\"GL_PGI_vertex_hints\");\n\tGLAD_GL_REND_screen_coordinates = has_ext(\"GL_REND_screen_coordinates\");\n\tGLAD_GL_S3_s3tc = has_ext(\"GL_S3_s3tc\");\n\tGLAD_GL_SGIS_detail_texture = has_ext(\"GL_SGIS_detail_texture\");\n\tGLAD_GL_SGIS_fog_function = has_ext(\"GL_SGIS_fog_function\");\n\tGLAD_GL_SGIS_generate_mipmap = has_ext(\"GL_SGIS_generate_mipmap\");\n\tGLAD_GL_SGIS_multisample = has_ext(\"GL_SGIS_multisample\");\n\tGLAD_GL_SGIS_pixel_texture = has_ext(\"GL_SGIS_pixel_texture\");\n\tGLAD_GL_SGIS_point_line_texgen = has_ext(\"GL_SGIS_point_line_texgen\");\n\tGLAD_GL_SGIS_point_parameters = has_ext(\"GL_SGIS_point_parameters\");\n\tGLAD_GL_SGIS_sharpen_texture = has_ext(\"GL_SGIS_sharpen_texture\");\n\tGLAD_GL_SGIS_texture4D = has_ext(\"GL_SGIS_texture4D\");\n\tGLAD_GL_SGIS_texture_border_clamp = has_ext(\"GL_SGIS_texture_border_clamp\");\n\tGLAD_GL_SGIS_texture_color_mask = has_ext(\"GL_SGIS_texture_color_mask\");\n\tGLAD_GL_SGIS_texture_edge_clamp = has_ext(\"GL_SGIS_texture_edge_clamp\");\n\tGLAD_GL_SGIS_texture_filter4 = has_ext(\"GL_SGIS_texture_filter4\");\n\tGLAD_GL_SGIS_texture_lod = has_ext(\"GL_SGIS_texture_lod\");\n\tGLAD_GL_SGIS_texture_select = has_ext(\"GL_SGIS_texture_select\");\n\tGLAD_GL_SGIX_async = has_ext(\"GL_SGIX_async\");\n\tGLAD_GL_SGIX_async_histogram = has_ext(\"GL_SGIX_async_histogram\");\n\tGLAD_GL_SGIX_async_pixel = has_ext(\"GL_SGIX_async_pixel\");\n\tGLAD_GL_SGIX_blend_alpha_minmax = has_ext(\"GL_SGIX_blend_alpha_minmax\");\n\tGLAD_GL_SGIX_calligraphic_fragment = has_ext(\"GL_SGIX_calligraphic_fragment\");\n\tGLAD_GL_SGIX_clipmap = has_ext(\"GL_SGIX_clipmap\");\n\tGLAD_GL_SGIX_convolution_accuracy = has_ext(\"GL_SGIX_convolution_accuracy\");\n\tGLAD_GL_SGIX_depth_pass_instrument = has_ext(\"GL_SGIX_depth_pass_instrument\");\n\tGLAD_GL_SGIX_depth_texture = has_ext(\"GL_SGIX_depth_texture\");\n\tGLAD_GL_SGIX_flush_raster = has_ext(\"GL_SGIX_flush_raster\");\n\tGLAD_GL_SGIX_fog_offset = has_ext(\"GL_SGIX_fog_offset\");\n\tGLAD_GL_SGIX_fragment_lighting = has_ext(\"GL_SGIX_fragment_lighting\");\n\tGLAD_GL_SGIX_framezoom = has_ext(\"GL_SGIX_framezoom\");\n\tGLAD_GL_SGIX_igloo_interface = has_ext(\"GL_SGIX_igloo_interface\");\n\tGLAD_GL_SGIX_instruments = has_ext(\"GL_SGIX_instruments\");\n\tGLAD_GL_SGIX_interlace = has_ext(\"GL_SGIX_interlace\");\n\tGLAD_GL_SGIX_ir_instrument1 = has_ext(\"GL_SGIX_ir_instrument1\");\n\tGLAD_GL_SGIX_list_priority = has_ext(\"GL_SGIX_list_priority\");\n\tGLAD_GL_SGIX_pixel_texture = has_ext(\"GL_SGIX_pixel_texture\");\n\tGLAD_GL_SGIX_pixel_tiles = has_ext(\"GL_SGIX_pixel_tiles\");\n\tGLAD_GL_SGIX_polynomial_ffd = has_ext(\"GL_SGIX_polynomial_ffd\");\n\tGLAD_GL_SGIX_reference_plane = has_ext(\"GL_SGIX_reference_plane\");\n\tGLAD_GL_SGIX_resample = has_ext(\"GL_SGIX_resample\");\n\tGLAD_GL_SGIX_scalebias_hint = has_ext(\"GL_SGIX_scalebias_hint\");\n\tGLAD_GL_SGIX_shadow = has_ext(\"GL_SGIX_shadow\");\n\tGLAD_GL_SGIX_shadow_ambient = has_ext(\"GL_SGIX_shadow_ambient\");\n\tGLAD_GL_SGIX_sprite = has_ext(\"GL_SGIX_sprite\");\n\tGLAD_GL_SGIX_subsample = has_ext(\"GL_SGIX_subsample\");\n\tGLAD_GL_SGIX_tag_sample_buffer = has_ext(\"GL_SGIX_tag_sample_buffer\");\n\tGLAD_GL_SGIX_texture_add_env = has_ext(\"GL_SGIX_texture_add_env\");\n\tGLAD_GL_SGIX_texture_coordinate_clamp = has_ext(\"GL_SGIX_texture_coordinate_clamp\");\n\tGLAD_GL_SGIX_texture_lod_bias = has_ext(\"GL_SGIX_texture_lod_bias\");\n\tGLAD_GL_SGIX_texture_multi_buffer = has_ext(\"GL_SGIX_texture_multi_buffer\");\n\tGLAD_GL_SGIX_texture_scale_bias = has_ext(\"GL_SGIX_texture_scale_bias\");\n\tGLAD_GL_SGIX_vertex_preclip = has_ext(\"GL_SGIX_vertex_preclip\");\n\tGLAD_GL_SGIX_ycrcb = has_ext(\"GL_SGIX_ycrcb\");\n\tGLAD_GL_SGIX_ycrcb_subsample = has_ext(\"GL_SGIX_ycrcb_subsample\");\n\tGLAD_GL_SGIX_ycrcba = has_ext(\"GL_SGIX_ycrcba\");\n\tGLAD_GL_SGI_color_matrix = has_ext(\"GL_SGI_color_matrix\");\n\tGLAD_GL_SGI_color_table = has_ext(\"GL_SGI_color_table\");\n\tGLAD_GL_SGI_texture_color_table = has_ext(\"GL_SGI_texture_color_table\");\n\tGLAD_GL_SUNX_constant_data = has_ext(\"GL_SUNX_constant_data\");\n\tGLAD_GL_SUN_convolution_border_modes = has_ext(\"GL_SUN_convolution_border_modes\");\n\tGLAD_GL_SUN_global_alpha = has_ext(\"GL_SUN_global_alpha\");\n\tGLAD_GL_SUN_mesh_array = has_ext(\"GL_SUN_mesh_array\");\n\tGLAD_GL_SUN_slice_accum = has_ext(\"GL_SUN_slice_accum\");\n\tGLAD_GL_SUN_triangle_list = has_ext(\"GL_SUN_triangle_list\");\n\tGLAD_GL_SUN_vertex = has_ext(\"GL_SUN_vertex\");\n\tGLAD_GL_WIN_phong_shading = has_ext(\"GL_WIN_phong_shading\");\n\tGLAD_GL_WIN_specular_fog = has_ext(\"GL_WIN_specular_fog\");\n\tfree_exts();\n\treturn 1;\n}\n\nstatic void find_coreGL(void) {\n\n    /* Thank you @elmindreda\n     * https://github.com/elmindreda/greg/blob/master/templates/greg.c.in#L176\n     * https://github.com/glfw/glfw/blob/master/src/context.c#L36\n     */\n    int i, major, minor;\n\n    const char* version;\n    const char* prefixes[] = {\n        \"OpenGL ES-CM \",\n        \"OpenGL ES-CL \",\n        \"OpenGL ES \",\n        NULL\n    };\n\n    version = (const char*) glGetString(GL_VERSION);\n    if (!version) return;\n\n    for (i = 0;  prefixes[i];  i++) {\n        const size_t length = strlen(prefixes[i]);\n        if (strncmp(version, prefixes[i], length) == 0) {\n            version += length;\n            break;\n        }\n    }\n\n/* PR #18 */\n#ifdef _MSC_VER\n    sscanf_s(version, \"%d.%d\", &major, &minor);\n#else\n    sscanf(version, \"%d.%d\", &major, &minor);\n#endif\n\n    GLVersion.major = major; GLVersion.minor = minor;\n    max_loaded_major = major; max_loaded_minor = minor;\n\tGLAD_GL_VERSION_1_0 = (major == 1 && minor >= 0) || major > 1;\n\tGLAD_GL_VERSION_1_1 = (major == 1 && minor >= 1) || major > 1;\n\tGLAD_GL_VERSION_1_2 = (major == 1 && minor >= 2) || major > 1;\n\tGLAD_GL_VERSION_1_3 = (major == 1 && minor >= 3) || major > 1;\n\tGLAD_GL_VERSION_1_4 = (major == 1 && minor >= 4) || major > 1;\n\tGLAD_GL_VERSION_1_5 = (major == 1 && minor >= 5) || major > 1;\n\tGLAD_GL_VERSION_2_0 = (major == 2 && minor >= 0) || major > 2;\n\tGLAD_GL_VERSION_2_1 = (major == 2 && minor >= 1) || major > 2;\n\tGLAD_GL_VERSION_3_0 = (major == 3 && minor >= 0) || major > 3;\n\tGLAD_GL_VERSION_3_1 = (major == 3 && minor >= 1) || major > 3;\n\tGLAD_GL_VERSION_3_2 = (major == 3 && minor >= 2) || major > 3;\n\tGLAD_GL_VERSION_3_3 = (major == 3 && minor >= 3) || major > 3;\n\tif (GLVersion.major > 3 || (GLVersion.major >= 3 && GLVersion.minor >= 3)) {\n\t\tmax_loaded_major = 3;\n\t\tmax_loaded_minor = 3;\n\t}\n}\n\nint gladLoadGLLoader(GLADloadproc load) {\n\tGLVersion.major = 0; GLVersion.minor = 0;\n\tglGetString = (PFNGLGETSTRINGPROC)load(\"glGetString\");\n\tif(glGetString == NULL) return 0;\n\tif(glGetString(GL_VERSION) == NULL) return 0;\n\tfind_coreGL();\n\tload_GL_VERSION_1_0(load);\n\tload_GL_VERSION_1_1(load);\n\tload_GL_VERSION_1_2(load);\n\tload_GL_VERSION_1_3(load);\n\tload_GL_VERSION_1_4(load);\n\tload_GL_VERSION_1_5(load);\n\tload_GL_VERSION_2_0(load);\n\tload_GL_VERSION_2_1(load);\n\tload_GL_VERSION_3_0(load);\n\tload_GL_VERSION_3_1(load);\n\tload_GL_VERSION_3_2(load);\n\tload_GL_VERSION_3_3(load);\n\n\tif (!find_extensionsGL()) return 0;\n\tload_GL_3DFX_tbuffer(load);\n\tload_GL_AMD_debug_output(load);\n\tload_GL_AMD_draw_buffers_blend(load);\n\tload_GL_AMD_framebuffer_multisample_advanced(load);\n\tload_GL_AMD_framebuffer_sample_positions(load);\n\tload_GL_AMD_gpu_shader_int64(load);\n\tload_GL_AMD_interleaved_elements(load);\n\tload_GL_AMD_multi_draw_indirect(load);\n\tload_GL_AMD_name_gen_delete(load);\n\tload_GL_AMD_occlusion_query_event(load);\n\tload_GL_AMD_performance_monitor(load);\n\tload_GL_AMD_sample_positions(load);\n\tload_GL_AMD_sparse_texture(load);\n\tload_GL_AMD_stencil_operation_extended(load);\n\tload_GL_AMD_vertex_shader_tessellator(load);\n\tload_GL_APPLE_element_array(load);\n\tload_GL_APPLE_fence(load);\n\tload_GL_APPLE_flush_buffer_range(load);\n\tload_GL_APPLE_object_purgeable(load);\n\tload_GL_APPLE_texture_range(load);\n\tload_GL_APPLE_vertex_array_object(load);\n\tload_GL_APPLE_vertex_array_range(load);\n\tload_GL_APPLE_vertex_program_evaluators(load);\n\tload_GL_ARB_ES2_compatibility(load);\n\tload_GL_ARB_ES3_1_compatibility(load);\n\tload_GL_ARB_ES3_2_compatibility(load);\n\tload_GL_ARB_base_instance(load);\n\tload_GL_ARB_bindless_texture(load);\n\tload_GL_ARB_blend_func_extended(load);\n\tload_GL_ARB_buffer_storage(load);\n\tload_GL_ARB_cl_event(load);\n\tload_GL_ARB_clear_buffer_object(load);\n\tload_GL_ARB_clear_texture(load);\n\tload_GL_ARB_clip_control(load);\n\tload_GL_ARB_color_buffer_float(load);\n\tload_GL_ARB_compute_shader(load);\n\tload_GL_ARB_compute_variable_group_size(load);\n\tload_GL_ARB_copy_buffer(load);\n\tload_GL_ARB_copy_image(load);\n\tload_GL_ARB_debug_output(load);\n\tload_GL_ARB_direct_state_access(load);\n\tload_GL_ARB_draw_buffers(load);\n\tload_GL_ARB_draw_buffers_blend(load);\n\tload_GL_ARB_draw_elements_base_vertex(load);\n\tload_GL_ARB_draw_indirect(load);\n\tload_GL_ARB_draw_instanced(load);\n\tload_GL_ARB_fragment_program(load);\n\tload_GL_ARB_framebuffer_no_attachments(load);\n\tload_GL_ARB_framebuffer_object(load);\n\tload_GL_ARB_geometry_shader4(load);\n\tload_GL_ARB_get_program_binary(load);\n\tload_GL_ARB_get_texture_sub_image(load);\n\tload_GL_ARB_gl_spirv(load);\n\tload_GL_ARB_gpu_shader_fp64(load);\n\tload_GL_ARB_gpu_shader_int64(load);\n\tload_GL_ARB_imaging(load);\n\tload_GL_ARB_indirect_parameters(load);\n\tload_GL_ARB_instanced_arrays(load);\n\tload_GL_ARB_internalformat_query(load);\n\tload_GL_ARB_internalformat_query2(load);\n\tload_GL_ARB_invalidate_subdata(load);\n\tload_GL_ARB_map_buffer_range(load);\n\tload_GL_ARB_matrix_palette(load);\n\tload_GL_ARB_multi_bind(load);\n\tload_GL_ARB_multi_draw_indirect(load);\n\tload_GL_ARB_multisample(load);\n\tload_GL_ARB_multitexture(load);\n\tload_GL_ARB_occlusion_query(load);\n\tload_GL_ARB_parallel_shader_compile(load);\n\tload_GL_ARB_point_parameters(load);\n\tload_GL_ARB_polygon_offset_clamp(load);\n\tload_GL_ARB_program_interface_query(load);\n\tload_GL_ARB_provoking_vertex(load);\n\tload_GL_ARB_robustness(load);\n\tload_GL_ARB_sample_locations(load);\n\tload_GL_ARB_sample_shading(load);\n\tload_GL_ARB_sampler_objects(load);\n\tload_GL_ARB_separate_shader_objects(load);\n\tload_GL_ARB_shader_atomic_counters(load);\n\tload_GL_ARB_shader_image_load_store(load);\n\tload_GL_ARB_shader_objects(load);\n\tload_GL_ARB_shader_storage_buffer_object(load);\n\tload_GL_ARB_shader_subroutine(load);\n\tload_GL_ARB_shading_language_include(load);\n\tload_GL_ARB_sparse_buffer(load);\n\tload_GL_ARB_sparse_texture(load);\n\tload_GL_ARB_sync(load);\n\tload_GL_ARB_tessellation_shader(load);\n\tload_GL_ARB_texture_barrier(load);\n\tload_GL_ARB_texture_buffer_object(load);\n\tload_GL_ARB_texture_buffer_range(load);\n\tload_GL_ARB_texture_compression(load);\n\tload_GL_ARB_texture_multisample(load);\n\tload_GL_ARB_texture_storage(load);\n\tload_GL_ARB_texture_storage_multisample(load);\n\tload_GL_ARB_texture_view(load);\n\tload_GL_ARB_timer_query(load);\n\tload_GL_ARB_transform_feedback2(load);\n\tload_GL_ARB_transform_feedback3(load);\n\tload_GL_ARB_transform_feedback_instanced(load);\n\tload_GL_ARB_transpose_matrix(load);\n\tload_GL_ARB_uniform_buffer_object(load);\n\tload_GL_ARB_vertex_array_object(load);\n\tload_GL_ARB_vertex_attrib_64bit(load);\n\tload_GL_ARB_vertex_attrib_binding(load);\n\tload_GL_ARB_vertex_blend(load);\n\tload_GL_ARB_vertex_buffer_object(load);\n\tload_GL_ARB_vertex_program(load);\n\tload_GL_ARB_vertex_shader(load);\n\tload_GL_ARB_vertex_type_2_10_10_10_rev(load);\n\tload_GL_ARB_viewport_array(load);\n\tload_GL_ARB_window_pos(load);\n\tload_GL_ATI_draw_buffers(load);\n\tload_GL_ATI_element_array(load);\n\tload_GL_ATI_envmap_bumpmap(load);\n\tload_GL_ATI_fragment_shader(load);\n\tload_GL_ATI_map_object_buffer(load);\n\tload_GL_ATI_pn_triangles(load);\n\tload_GL_ATI_separate_stencil(load);\n\tload_GL_ATI_vertex_array_object(load);\n\tload_GL_ATI_vertex_attrib_array_object(load);\n\tload_GL_ATI_vertex_streams(load);\n\tload_GL_EXT_EGL_image_storage(load);\n\tload_GL_EXT_bindable_uniform(load);\n\tload_GL_EXT_blend_color(load);\n\tload_GL_EXT_blend_equation_separate(load);\n\tload_GL_EXT_blend_func_separate(load);\n\tload_GL_EXT_blend_minmax(load);\n\tload_GL_EXT_color_subtable(load);\n\tload_GL_EXT_compiled_vertex_array(load);\n\tload_GL_EXT_convolution(load);\n\tload_GL_EXT_coordinate_frame(load);\n\tload_GL_EXT_copy_texture(load);\n\tload_GL_EXT_cull_vertex(load);\n\tload_GL_EXT_debug_label(load);\n\tload_GL_EXT_debug_marker(load);\n\tload_GL_EXT_depth_bounds_test(load);\n\tload_GL_EXT_direct_state_access(load);\n\tload_GL_EXT_draw_buffers2(load);\n\tload_GL_EXT_draw_instanced(load);\n\tload_GL_EXT_draw_range_elements(load);\n\tload_GL_EXT_external_buffer(load);\n\tload_GL_EXT_fog_coord(load);\n\tload_GL_EXT_framebuffer_blit(load);\n\tload_GL_EXT_framebuffer_multisample(load);\n\tload_GL_EXT_framebuffer_object(load);\n\tload_GL_EXT_geometry_shader4(load);\n\tload_GL_EXT_gpu_program_parameters(load);\n\tload_GL_EXT_gpu_shader4(load);\n\tload_GL_EXT_histogram(load);\n\tload_GL_EXT_index_func(load);\n\tload_GL_EXT_index_material(load);\n\tload_GL_EXT_light_texture(load);\n\tload_GL_EXT_memory_object(load);\n\tload_GL_EXT_memory_object_fd(load);\n\tload_GL_EXT_memory_object_win32(load);\n\tload_GL_EXT_multi_draw_arrays(load);\n\tload_GL_EXT_multisample(load);\n\tload_GL_EXT_paletted_texture(load);\n\tload_GL_EXT_pixel_transform(load);\n\tload_GL_EXT_point_parameters(load);\n\tload_GL_EXT_polygon_offset(load);\n\tload_GL_EXT_polygon_offset_clamp(load);\n\tload_GL_EXT_provoking_vertex(load);\n\tload_GL_EXT_raster_multisample(load);\n\tload_GL_EXT_secondary_color(load);\n\tload_GL_EXT_semaphore(load);\n\tload_GL_EXT_semaphore_fd(load);\n\tload_GL_EXT_semaphore_win32(load);\n\tload_GL_EXT_separate_shader_objects(load);\n\tload_GL_EXT_shader_framebuffer_fetch_non_coherent(load);\n\tload_GL_EXT_shader_image_load_store(load);\n\tload_GL_EXT_stencil_clear_tag(load);\n\tload_GL_EXT_stencil_two_side(load);\n\tload_GL_EXT_subtexture(load);\n\tload_GL_EXT_texture3D(load);\n\tload_GL_EXT_texture_array(load);\n\tload_GL_EXT_texture_buffer_object(load);\n\tload_GL_EXT_texture_integer(load);\n\tload_GL_EXT_texture_object(load);\n\tload_GL_EXT_texture_perturb_normal(load);\n\tload_GL_EXT_timer_query(load);\n\tload_GL_EXT_transform_feedback(load);\n\tload_GL_EXT_vertex_array(load);\n\tload_GL_EXT_vertex_attrib_64bit(load);\n\tload_GL_EXT_vertex_shader(load);\n\tload_GL_EXT_vertex_weighting(load);\n\tload_GL_EXT_win32_keyed_mutex(load);\n\tload_GL_EXT_window_rectangles(load);\n\tload_GL_EXT_x11_sync_object(load);\n\tload_GL_GREMEDY_frame_terminator(load);\n\tload_GL_GREMEDY_string_marker(load);\n\tload_GL_HP_image_transform(load);\n\tload_GL_IBM_multimode_draw_arrays(load);\n\tload_GL_IBM_static_data(load);\n\tload_GL_IBM_vertex_array_lists(load);\n\tload_GL_INGR_blend_func_separate(load);\n\tload_GL_INTEL_framebuffer_CMAA(load);\n\tload_GL_INTEL_map_texture(load);\n\tload_GL_INTEL_parallel_arrays(load);\n\tload_GL_INTEL_performance_query(load);\n\tload_GL_KHR_blend_equation_advanced(load);\n\tload_GL_KHR_debug(load);\n\tload_GL_KHR_parallel_shader_compile(load);\n\tload_GL_KHR_robustness(load);\n\tload_GL_MESA_resize_buffers(load);\n\tload_GL_MESA_window_pos(load);\n\tload_GL_NVX_conditional_render(load);\n\tload_GL_NVX_linked_gpu_multicast(load);\n\tload_GL_NV_alpha_to_coverage_dither_control(load);\n\tload_GL_NV_bindless_multi_draw_indirect(load);\n\tload_GL_NV_bindless_multi_draw_indirect_count(load);\n\tload_GL_NV_bindless_texture(load);\n\tload_GL_NV_blend_equation_advanced(load);\n\tload_GL_NV_clip_space_w_scaling(load);\n\tload_GL_NV_command_list(load);\n\tload_GL_NV_conditional_render(load);\n\tload_GL_NV_conservative_raster(load);\n\tload_GL_NV_conservative_raster_dilate(load);\n\tload_GL_NV_conservative_raster_pre_snap_triangles(load);\n\tload_GL_NV_copy_image(load);\n\tload_GL_NV_depth_buffer_float(load);\n\tload_GL_NV_draw_texture(load);\n\tload_GL_NV_draw_vulkan_image(load);\n\tload_GL_NV_evaluators(load);\n\tload_GL_NV_explicit_multisample(load);\n\tload_GL_NV_fence(load);\n\tload_GL_NV_fragment_coverage_to_color(load);\n\tload_GL_NV_fragment_program(load);\n\tload_GL_NV_framebuffer_mixed_samples(load);\n\tload_GL_NV_framebuffer_multisample_coverage(load);\n\tload_GL_NV_geometry_program4(load);\n\tload_GL_NV_gpu_multicast(load);\n\tload_GL_NV_gpu_program4(load);\n\tload_GL_NV_gpu_program5(load);\n\tload_GL_NV_gpu_shader5(load);\n\tload_GL_NV_half_float(load);\n\tload_GL_NV_internalformat_sample_query(load);\n\tload_GL_NV_memory_attachment(load);\n\tload_GL_NV_mesh_shader(load);\n\tload_GL_NV_occlusion_query(load);\n\tload_GL_NV_parameter_buffer_object(load);\n\tload_GL_NV_path_rendering(load);\n\tload_GL_NV_pixel_data_range(load);\n\tload_GL_NV_point_sprite(load);\n\tload_GL_NV_present_video(load);\n\tload_GL_NV_primitive_restart(load);\n\tload_GL_NV_query_resource(load);\n\tload_GL_NV_query_resource_tag(load);\n\tload_GL_NV_register_combiners(load);\n\tload_GL_NV_register_combiners2(load);\n\tload_GL_NV_sample_locations(load);\n\tload_GL_NV_scissor_exclusive(load);\n\tload_GL_NV_shader_buffer_load(load);\n\tload_GL_NV_shading_rate_image(load);\n\tload_GL_NV_texture_barrier(load);\n\tload_GL_NV_texture_multisample(load);\n\tload_GL_NV_transform_feedback(load);\n\tload_GL_NV_transform_feedback2(load);\n\tload_GL_NV_vdpau_interop(load);\n\tload_GL_NV_vertex_array_range(load);\n\tload_GL_NV_vertex_attrib_integer_64bit(load);\n\tload_GL_NV_vertex_buffer_unified_memory(load);\n\tload_GL_NV_vertex_program(load);\n\tload_GL_NV_vertex_program4(load);\n\tload_GL_NV_video_capture(load);\n\tload_GL_NV_viewport_swizzle(load);\n\tload_GL_OES_byte_coordinates(load);\n\tload_GL_OES_fixed_point(load);\n\tload_GL_OES_query_matrix(load);\n\tload_GL_OES_single_precision(load);\n\tload_GL_OVR_multiview(load);\n\tload_GL_PGI_misc_hints(load);\n\tload_GL_SGIS_detail_texture(load);\n\tload_GL_SGIS_fog_function(load);\n\tload_GL_SGIS_multisample(load);\n\tload_GL_SGIS_pixel_texture(load);\n\tload_GL_SGIS_point_parameters(load);\n\tload_GL_SGIS_sharpen_texture(load);\n\tload_GL_SGIS_texture4D(load);\n\tload_GL_SGIS_texture_color_mask(load);\n\tload_GL_SGIS_texture_filter4(load);\n\tload_GL_SGIX_async(load);\n\tload_GL_SGIX_flush_raster(load);\n\tload_GL_SGIX_fragment_lighting(load);\n\tload_GL_SGIX_framezoom(load);\n\tload_GL_SGIX_igloo_interface(load);\n\tload_GL_SGIX_instruments(load);\n\tload_GL_SGIX_list_priority(load);\n\tload_GL_SGIX_pixel_texture(load);\n\tload_GL_SGIX_polynomial_ffd(load);\n\tload_GL_SGIX_reference_plane(load);\n\tload_GL_SGIX_sprite(load);\n\tload_GL_SGIX_tag_sample_buffer(load);\n\tload_GL_SGI_color_table(load);\n\tload_GL_SUNX_constant_data(load);\n\tload_GL_SUN_global_alpha(load);\n\tload_GL_SUN_mesh_array(load);\n\tload_GL_SUN_triangle_list(load);\n\tload_GL_SUN_vertex(load);\n\treturn GLVersion.major != 0 || GLVersion.minor != 0;\n}\n\nstatic void load_GL_ES_VERSION_2_0(GLADloadproc load) {\n\tif(!GLAD_GL_ES_VERSION_2_0) return;\n\tglad_glActiveTexture = (PFNGLACTIVETEXTUREPROC)load(\"glActiveTexture\");\n\tglad_glAttachShader = (PFNGLATTACHSHADERPROC)load(\"glAttachShader\");\n\tglad_glBindAttribLocation = (PFNGLBINDATTRIBLOCATIONPROC)load(\"glBindAttribLocation\");\n\tglad_glBindBuffer = (PFNGLBINDBUFFERPROC)load(\"glBindBuffer\");\n\tglad_glBindFramebuffer = (PFNGLBINDFRAMEBUFFERPROC)load(\"glBindFramebuffer\");\n\tglad_glBindRenderbuffer = (PFNGLBINDRENDERBUFFERPROC)load(\"glBindRenderbuffer\");\n\tglad_glBindTexture = (PFNGLBINDTEXTUREPROC)load(\"glBindTexture\");\n\tglad_glBlendColor = (PFNGLBLENDCOLORPROC)load(\"glBlendColor\");\n\tglad_glBlendEquation = (PFNGLBLENDEQUATIONPROC)load(\"glBlendEquation\");\n\tglad_glBlendEquationSeparate = (PFNGLBLENDEQUATIONSEPARATEPROC)load(\"glBlendEquationSeparate\");\n\tglad_glBlendFunc = (PFNGLBLENDFUNCPROC)load(\"glBlendFunc\");\n\tglad_glBlendFuncSeparate = (PFNGLBLENDFUNCSEPARATEPROC)load(\"glBlendFuncSeparate\");\n\tglad_glBufferData = (PFNGLBUFFERDATAPROC)load(\"glBufferData\");\n\tglad_glBufferSubData = (PFNGLBUFFERSUBDATAPROC)load(\"glBufferSubData\");\n\tglad_glCheckFramebufferStatus = (PFNGLCHECKFRAMEBUFFERSTATUSPROC)load(\"glCheckFramebufferStatus\");\n\tglad_glClear = (PFNGLCLEARPROC)load(\"glClear\");\n\tglad_glClearColor = (PFNGLCLEARCOLORPROC)load(\"glClearColor\");\n\tglad_glClearDepthf = (PFNGLCLEARDEPTHFPROC)load(\"glClearDepthf\");\n\tglad_glClearStencil = (PFNGLCLEARSTENCILPROC)load(\"glClearStencil\");\n\tglad_glColorMask = (PFNGLCOLORMASKPROC)load(\"glColorMask\");\n\tglad_glCompileShader = (PFNGLCOMPILESHADERPROC)load(\"glCompileShader\");\n\tglad_glCompressedTexImage2D = (PFNGLCOMPRESSEDTEXIMAGE2DPROC)load(\"glCompressedTexImage2D\");\n\tglad_glCompressedTexSubImage2D = (PFNGLCOMPRESSEDTEXSUBIMAGE2DPROC)load(\"glCompressedTexSubImage2D\");\n\tglad_glCopyTexImage2D = (PFNGLCOPYTEXIMAGE2DPROC)load(\"glCopyTexImage2D\");\n\tglad_glCopyTexSubImage2D = (PFNGLCOPYTEXSUBIMAGE2DPROC)load(\"glCopyTexSubImage2D\");\n\tglad_glCreateProgram = (PFNGLCREATEPROGRAMPROC)load(\"glCreateProgram\");\n\tglad_glCreateShader = (PFNGLCREATESHADERPROC)load(\"glCreateShader\");\n\tglad_glCullFace = (PFNGLCULLFACEPROC)load(\"glCullFace\");\n\tglad_glDeleteBuffers = (PFNGLDELETEBUFFERSPROC)load(\"glDeleteBuffers\");\n\tglad_glDeleteFramebuffers = (PFNGLDELETEFRAMEBUFFERSPROC)load(\"glDeleteFramebuffers\");\n\tglad_glDeleteProgram = (PFNGLDELETEPROGRAMPROC)load(\"glDeleteProgram\");\n\tglad_glDeleteRenderbuffers = (PFNGLDELETERENDERBUFFERSPROC)load(\"glDeleteRenderbuffers\");\n\tglad_glDeleteShader = (PFNGLDELETESHADERPROC)load(\"glDeleteShader\");\n\tglad_glDeleteTextures = (PFNGLDELETETEXTURESPROC)load(\"glDeleteTextures\");\n\tglad_glDepthFunc = (PFNGLDEPTHFUNCPROC)load(\"glDepthFunc\");\n\tglad_glDepthMask = (PFNGLDEPTHMASKPROC)load(\"glDepthMask\");\n\tglad_glDepthRangef = (PFNGLDEPTHRANGEFPROC)load(\"glDepthRangef\");\n\tglad_glDetachShader = (PFNGLDETACHSHADERPROC)load(\"glDetachShader\");\n\tglad_glDisable = (PFNGLDISABLEPROC)load(\"glDisable\");\n\tglad_glDisableVertexAttribArray = (PFNGLDISABLEVERTEXATTRIBARRAYPROC)load(\"glDisableVertexAttribArray\");\n\tglad_glDrawArrays = (PFNGLDRAWARRAYSPROC)load(\"glDrawArrays\");\n\tglad_glDrawElements = (PFNGLDRAWELEMENTSPROC)load(\"glDrawElements\");\n\tglad_glEnable = (PFNGLENABLEPROC)load(\"glEnable\");\n\tglad_glEnableVertexAttribArray = (PFNGLENABLEVERTEXATTRIBARRAYPROC)load(\"glEnableVertexAttribArray\");\n\tglad_glFinish = (PFNGLFINISHPROC)load(\"glFinish\");\n\tglad_glFlush = (PFNGLFLUSHPROC)load(\"glFlush\");\n\tglad_glFramebufferRenderbuffer = (PFNGLFRAMEBUFFERRENDERBUFFERPROC)load(\"glFramebufferRenderbuffer\");\n\tglad_glFramebufferTexture2D = (PFNGLFRAMEBUFFERTEXTURE2DPROC)load(\"glFramebufferTexture2D\");\n\tglad_glFrontFace = (PFNGLFRONTFACEPROC)load(\"glFrontFace\");\n\tglad_glGenBuffers = (PFNGLGENBUFFERSPROC)load(\"glGenBuffers\");\n\tglad_glGenerateMipmap = (PFNGLGENERATEMIPMAPPROC)load(\"glGenerateMipmap\");\n\tglad_glGenFramebuffers = (PFNGLGENFRAMEBUFFERSPROC)load(\"glGenFramebuffers\");\n\tglad_glGenRenderbuffers = (PFNGLGENRENDERBUFFERSPROC)load(\"glGenRenderbuffers\");\n\tglad_glGenTextures = (PFNGLGENTEXTURESPROC)load(\"glGenTextures\");\n\tglad_glGetActiveAttrib = (PFNGLGETACTIVEATTRIBPROC)load(\"glGetActiveAttrib\");\n\tglad_glGetActiveUniform = (PFNGLGETACTIVEUNIFORMPROC)load(\"glGetActiveUniform\");\n\tglad_glGetAttachedShaders = (PFNGLGETATTACHEDSHADERSPROC)load(\"glGetAttachedShaders\");\n\tglad_glGetAttribLocation = (PFNGLGETATTRIBLOCATIONPROC)load(\"glGetAttribLocation\");\n\tglad_glGetBooleanv = (PFNGLGETBOOLEANVPROC)load(\"glGetBooleanv\");\n\tglad_glGetBufferParameteriv = (PFNGLGETBUFFERPARAMETERIVPROC)load(\"glGetBufferParameteriv\");\n\tglad_glGetError = (PFNGLGETERRORPROC)load(\"glGetError\");\n\tglad_glGetFloatv = (PFNGLGETFLOATVPROC)load(\"glGetFloatv\");\n\tglad_glGetFramebufferAttachmentParameteriv = (PFNGLGETFRAMEBUFFERATTACHMENTPARAMETERIVPROC)load(\"glGetFramebufferAttachmentParameteriv\");\n\tglad_glGetIntegerv = (PFNGLGETINTEGERVPROC)load(\"glGetIntegerv\");\n\tglad_glGetProgramiv = (PFNGLGETPROGRAMIVPROC)load(\"glGetProgramiv\");\n\tglad_glGetProgramInfoLog = (PFNGLGETPROGRAMINFOLOGPROC)load(\"glGetProgramInfoLog\");\n\tglad_glGetRenderbufferParameteriv = (PFNGLGETRENDERBUFFERPARAMETERIVPROC)load(\"glGetRenderbufferParameteriv\");\n\tglad_glGetShaderiv = (PFNGLGETSHADERIVPROC)load(\"glGetShaderiv\");\n\tglad_glGetShaderInfoLog = (PFNGLGETSHADERINFOLOGPROC)load(\"glGetShaderInfoLog\");\n\tglad_glGetShaderPrecisionFormat = (PFNGLGETSHADERPRECISIONFORMATPROC)load(\"glGetShaderPrecisionFormat\");\n\tglad_glGetShaderSource = (PFNGLGETSHADERSOURCEPROC)load(\"glGetShaderSource\");\n\tglad_glGetString = (PFNGLGETSTRINGPROC)load(\"glGetString\");\n\tglad_glGetTexParameterfv = (PFNGLGETTEXPARAMETERFVPROC)load(\"glGetTexParameterfv\");\n\tglad_glGetTexParameteriv = (PFNGLGETTEXPARAMETERIVPROC)load(\"glGetTexParameteriv\");\n\tglad_glGetUniformfv = (PFNGLGETUNIFORMFVPROC)load(\"glGetUniformfv\");\n\tglad_glGetUniformiv = (PFNGLGETUNIFORMIVPROC)load(\"glGetUniformiv\");\n\tglad_glGetUniformLocation = (PFNGLGETUNIFORMLOCATIONPROC)load(\"glGetUniformLocation\");\n\tglad_glGetVertexAttribfv = (PFNGLGETVERTEXATTRIBFVPROC)load(\"glGetVertexAttribfv\");\n\tglad_glGetVertexAttribiv = (PFNGLGETVERTEXATTRIBIVPROC)load(\"glGetVertexAttribiv\");\n\tglad_glGetVertexAttribPointerv = (PFNGLGETVERTEXATTRIBPOINTERVPROC)load(\"glGetVertexAttribPointerv\");\n\tglad_glHint = (PFNGLHINTPROC)load(\"glHint\");\n\tglad_glIsBuffer = (PFNGLISBUFFERPROC)load(\"glIsBuffer\");\n\tglad_glIsEnabled = (PFNGLISENABLEDPROC)load(\"glIsEnabled\");\n\tglad_glIsFramebuffer = (PFNGLISFRAMEBUFFERPROC)load(\"glIsFramebuffer\");\n\tglad_glIsProgram = (PFNGLISPROGRAMPROC)load(\"glIsProgram\");\n\tglad_glIsRenderbuffer = (PFNGLISRENDERBUFFERPROC)load(\"glIsRenderbuffer\");\n\tglad_glIsShader = (PFNGLISSHADERPROC)load(\"glIsShader\");\n\tglad_glIsTexture = (PFNGLISTEXTUREPROC)load(\"glIsTexture\");\n\tglad_glLineWidth = (PFNGLLINEWIDTHPROC)load(\"glLineWidth\");\n\tglad_glLinkProgram = (PFNGLLINKPROGRAMPROC)load(\"glLinkProgram\");\n\tglad_glPixelStorei = (PFNGLPIXELSTOREIPROC)load(\"glPixelStorei\");\n\tglad_glPolygonOffset = (PFNGLPOLYGONOFFSETPROC)load(\"glPolygonOffset\");\n\tglad_glReadPixels = (PFNGLREADPIXELSPROC)load(\"glReadPixels\");\n\tglad_glReleaseShaderCompiler = (PFNGLRELEASESHADERCOMPILERPROC)load(\"glReleaseShaderCompiler\");\n\tglad_glRenderbufferStorage = (PFNGLRENDERBUFFERSTORAGEPROC)load(\"glRenderbufferStorage\");\n\tglad_glSampleCoverage = (PFNGLSAMPLECOVERAGEPROC)load(\"glSampleCoverage\");\n\tglad_glScissor = (PFNGLSCISSORPROC)load(\"glScissor\");\n\tglad_glShaderBinary = (PFNGLSHADERBINARYPROC)load(\"glShaderBinary\");\n\tglad_glShaderSource = (PFNGLSHADERSOURCEPROC)load(\"glShaderSource\");\n\tglad_glStencilFunc = (PFNGLSTENCILFUNCPROC)load(\"glStencilFunc\");\n\tglad_glStencilFuncSeparate = (PFNGLSTENCILFUNCSEPARATEPROC)load(\"glStencilFuncSeparate\");\n\tglad_glStencilMask = (PFNGLSTENCILMASKPROC)load(\"glStencilMask\");\n\tglad_glStencilMaskSeparate = (PFNGLSTENCILMASKSEPARATEPROC)load(\"glStencilMaskSeparate\");\n\tglad_glStencilOp = (PFNGLSTENCILOPPROC)load(\"glStencilOp\");\n\tglad_glStencilOpSeparate = (PFNGLSTENCILOPSEPARATEPROC)load(\"glStencilOpSeparate\");\n\tglad_glTexImage2D = (PFNGLTEXIMAGE2DPROC)load(\"glTexImage2D\");\n\tglad_glTexParameterf = (PFNGLTEXPARAMETERFPROC)load(\"glTexParameterf\");\n\tglad_glTexParameterfv = (PFNGLTEXPARAMETERFVPROC)load(\"glTexParameterfv\");\n\tglad_glTexParameteri = (PFNGLTEXPARAMETERIPROC)load(\"glTexParameteri\");\n\tglad_glTexParameteriv = (PFNGLTEXPARAMETERIVPROC)load(\"glTexParameteriv\");\n\tglad_glTexSubImage2D = (PFNGLTEXSUBIMAGE2DPROC)load(\"glTexSubImage2D\");\n\tglad_glUniform1f = (PFNGLUNIFORM1FPROC)load(\"glUniform1f\");\n\tglad_glUniform1fv = (PFNGLUNIFORM1FVPROC)load(\"glUniform1fv\");\n\tglad_glUniform1i = (PFNGLUNIFORM1IPROC)load(\"glUniform1i\");\n\tglad_glUniform1iv = (PFNGLUNIFORM1IVPROC)load(\"glUniform1iv\");\n\tglad_glUniform2f = (PFNGLUNIFORM2FPROC)load(\"glUniform2f\");\n\tglad_glUniform2fv = (PFNGLUNIFORM2FVPROC)load(\"glUniform2fv\");\n\tglad_glUniform2i = (PFNGLUNIFORM2IPROC)load(\"glUniform2i\");\n\tglad_glUniform2iv = (PFNGLUNIFORM2IVPROC)load(\"glUniform2iv\");\n\tglad_glUniform3f = (PFNGLUNIFORM3FPROC)load(\"glUniform3f\");\n\tglad_glUniform3fv = (PFNGLUNIFORM3FVPROC)load(\"glUniform3fv\");\n\tglad_glUniform3i = (PFNGLUNIFORM3IPROC)load(\"glUniform3i\");\n\tglad_glUniform3iv = (PFNGLUNIFORM3IVPROC)load(\"glUniform3iv\");\n\tglad_glUniform4f = (PFNGLUNIFORM4FPROC)load(\"glUniform4f\");\n\tglad_glUniform4fv = (PFNGLUNIFORM4FVPROC)load(\"glUniform4fv\");\n\tglad_glUniform4i = (PFNGLUNIFORM4IPROC)load(\"glUniform4i\");\n\tglad_glUniform4iv = (PFNGLUNIFORM4IVPROC)load(\"glUniform4iv\");\n\tglad_glUniformMatrix2fv = (PFNGLUNIFORMMATRIX2FVPROC)load(\"glUniformMatrix2fv\");\n\tglad_glUniformMatrix3fv = (PFNGLUNIFORMMATRIX3FVPROC)load(\"glUniformMatrix3fv\");\n\tglad_glUniformMatrix4fv = (PFNGLUNIFORMMATRIX4FVPROC)load(\"glUniformMatrix4fv\");\n\tglad_glUseProgram = (PFNGLUSEPROGRAMPROC)load(\"glUseProgram\");\n\tglad_glValidateProgram = (PFNGLVALIDATEPROGRAMPROC)load(\"glValidateProgram\");\n\tglad_glVertexAttrib1f = (PFNGLVERTEXATTRIB1FPROC)load(\"glVertexAttrib1f\");\n\tglad_glVertexAttrib1fv = (PFNGLVERTEXATTRIB1FVPROC)load(\"glVertexAttrib1fv\");\n\tglad_glVertexAttrib2f = (PFNGLVERTEXATTRIB2FPROC)load(\"glVertexAttrib2f\");\n\tglad_glVertexAttrib2fv = (PFNGLVERTEXATTRIB2FVPROC)load(\"glVertexAttrib2fv\");\n\tglad_glVertexAttrib3f = (PFNGLVERTEXATTRIB3FPROC)load(\"glVertexAttrib3f\");\n\tglad_glVertexAttrib3fv = (PFNGLVERTEXATTRIB3FVPROC)load(\"glVertexAttrib3fv\");\n\tglad_glVertexAttrib4f = (PFNGLVERTEXATTRIB4FPROC)load(\"glVertexAttrib4f\");\n\tglad_glVertexAttrib4fv = (PFNGLVERTEXATTRIB4FVPROC)load(\"glVertexAttrib4fv\");\n\tglad_glVertexAttribPointer = (PFNGLVERTEXATTRIBPOINTERPROC)load(\"glVertexAttribPointer\");\n\tglad_glViewport = (PFNGLVIEWPORTPROC)load(\"glViewport\");\n}\nstatic void load_GL_ES_VERSION_3_0(GLADloadproc load) {\n\tif(!GLAD_GL_ES_VERSION_3_0) return;\n\tglad_glReadBuffer = (PFNGLREADBUFFERPROC)load(\"glReadBuffer\");\n\tglad_glDrawRangeElements = (PFNGLDRAWRANGEELEMENTSPROC)load(\"glDrawRangeElements\");\n\tglad_glTexImage3D = (PFNGLTEXIMAGE3DPROC)load(\"glTexImage3D\");\n\tglad_glTexSubImage3D = (PFNGLTEXSUBIMAGE3DPROC)load(\"glTexSubImage3D\");\n\tglad_glCopyTexSubImage3D = (PFNGLCOPYTEXSUBIMAGE3DPROC)load(\"glCopyTexSubImage3D\");\n\tglad_glCompressedTexImage3D = (PFNGLCOMPRESSEDTEXIMAGE3DPROC)load(\"glCompressedTexImage3D\");\n\tglad_glCompressedTexSubImage3D = (PFNGLCOMPRESSEDTEXSUBIMAGE3DPROC)load(\"glCompressedTexSubImage3D\");\n\tglad_glGenQueries = (PFNGLGENQUERIESPROC)load(\"glGenQueries\");\n\tglad_glDeleteQueries = (PFNGLDELETEQUERIESPROC)load(\"glDeleteQueries\");\n\tglad_glIsQuery = (PFNGLISQUERYPROC)load(\"glIsQuery\");\n\tglad_glBeginQuery = (PFNGLBEGINQUERYPROC)load(\"glBeginQuery\");\n\tglad_glEndQuery = (PFNGLENDQUERYPROC)load(\"glEndQuery\");\n\tglad_glGetQueryiv = (PFNGLGETQUERYIVPROC)load(\"glGetQueryiv\");\n\tglad_glGetQueryObjectuiv = (PFNGLGETQUERYOBJECTUIVPROC)load(\"glGetQueryObjectuiv\");\n\tglad_glUnmapBuffer = (PFNGLUNMAPBUFFERPROC)load(\"glUnmapBuffer\");\n\tglad_glGetBufferPointerv = (PFNGLGETBUFFERPOINTERVPROC)load(\"glGetBufferPointerv\");\n\tglad_glDrawBuffers = (PFNGLDRAWBUFFERSPROC)load(\"glDrawBuffers\");\n\tglad_glUniformMatrix2x3fv = (PFNGLUNIFORMMATRIX2X3FVPROC)load(\"glUniformMatrix2x3fv\");\n\tglad_glUniformMatrix3x2fv = (PFNGLUNIFORMMATRIX3X2FVPROC)load(\"glUniformMatrix3x2fv\");\n\tglad_glUniformMatrix2x4fv = (PFNGLUNIFORMMATRIX2X4FVPROC)load(\"glUniformMatrix2x4fv\");\n\tglad_glUniformMatrix4x2fv = (PFNGLUNIFORMMATRIX4X2FVPROC)load(\"glUniformMatrix4x2fv\");\n\tglad_glUniformMatrix3x4fv = (PFNGLUNIFORMMATRIX3X4FVPROC)load(\"glUniformMatrix3x4fv\");\n\tglad_glUniformMatrix4x3fv = (PFNGLUNIFORMMATRIX4X3FVPROC)load(\"glUniformMatrix4x3fv\");\n\tglad_glBlitFramebuffer = (PFNGLBLITFRAMEBUFFERPROC)load(\"glBlitFramebuffer\");\n\tglad_glRenderbufferStorageMultisample = (PFNGLRENDERBUFFERSTORAGEMULTISAMPLEPROC)load(\"glRenderbufferStorageMultisample\");\n\tglad_glFramebufferTextureLayer = (PFNGLFRAMEBUFFERTEXTURELAYERPROC)load(\"glFramebufferTextureLayer\");\n\tglad_glMapBufferRange = (PFNGLMAPBUFFERRANGEPROC)load(\"glMapBufferRange\");\n\tglad_glFlushMappedBufferRange = (PFNGLFLUSHMAPPEDBUFFERRANGEPROC)load(\"glFlushMappedBufferRange\");\n\tglad_glBindVertexArray = (PFNGLBINDVERTEXARRAYPROC)load(\"glBindVertexArray\");\n\tglad_glDeleteVertexArrays = (PFNGLDELETEVERTEXARRAYSPROC)load(\"glDeleteVertexArrays\");\n\tglad_glGenVertexArrays = (PFNGLGENVERTEXARRAYSPROC)load(\"glGenVertexArrays\");\n\tglad_glIsVertexArray = (PFNGLISVERTEXARRAYPROC)load(\"glIsVertexArray\");\n\tglad_glGetIntegeri_v = (PFNGLGETINTEGERI_VPROC)load(\"glGetIntegeri_v\");\n\tglad_glBeginTransformFeedback = (PFNGLBEGINTRANSFORMFEEDBACKPROC)load(\"glBeginTransformFeedback\");\n\tglad_glEndTransformFeedback = (PFNGLENDTRANSFORMFEEDBACKPROC)load(\"glEndTransformFeedback\");\n\tglad_glBindBufferRange = (PFNGLBINDBUFFERRANGEPROC)load(\"glBindBufferRange\");\n\tglad_glBindBufferBase = (PFNGLBINDBUFFERBASEPROC)load(\"glBindBufferBase\");\n\tglad_glTransformFeedbackVaryings = (PFNGLTRANSFORMFEEDBACKVARYINGSPROC)load(\"glTransformFeedbackVaryings\");\n\tglad_glGetTransformFeedbackVarying = (PFNGLGETTRANSFORMFEEDBACKVARYINGPROC)load(\"glGetTransformFeedbackVarying\");\n\tglad_glVertexAttribIPointer = (PFNGLVERTEXATTRIBIPOINTERPROC)load(\"glVertexAttribIPointer\");\n\tglad_glGetVertexAttribIiv = (PFNGLGETVERTEXATTRIBIIVPROC)load(\"glGetVertexAttribIiv\");\n\tglad_glGetVertexAttribIuiv = (PFNGLGETVERTEXATTRIBIUIVPROC)load(\"glGetVertexAttribIuiv\");\n\tglad_glVertexAttribI4i = (PFNGLVERTEXATTRIBI4IPROC)load(\"glVertexAttribI4i\");\n\tglad_glVertexAttribI4ui = (PFNGLVERTEXATTRIBI4UIPROC)load(\"glVertexAttribI4ui\");\n\tglad_glVertexAttribI4iv = (PFNGLVERTEXATTRIBI4IVPROC)load(\"glVertexAttribI4iv\");\n\tglad_glVertexAttribI4uiv = (PFNGLVERTEXATTRIBI4UIVPROC)load(\"glVertexAttribI4uiv\");\n\tglad_glGetUniformuiv = (PFNGLGETUNIFORMUIVPROC)load(\"glGetUniformuiv\");\n\tglad_glGetFragDataLocation = (PFNGLGETFRAGDATALOCATIONPROC)load(\"glGetFragDataLocation\");\n\tglad_glUniform1ui = (PFNGLUNIFORM1UIPROC)load(\"glUniform1ui\");\n\tglad_glUniform2ui = (PFNGLUNIFORM2UIPROC)load(\"glUniform2ui\");\n\tglad_glUniform3ui = (PFNGLUNIFORM3UIPROC)load(\"glUniform3ui\");\n\tglad_glUniform4ui = (PFNGLUNIFORM4UIPROC)load(\"glUniform4ui\");\n\tglad_glUniform1uiv = (PFNGLUNIFORM1UIVPROC)load(\"glUniform1uiv\");\n\tglad_glUniform2uiv = (PFNGLUNIFORM2UIVPROC)load(\"glUniform2uiv\");\n\tglad_glUniform3uiv = (PFNGLUNIFORM3UIVPROC)load(\"glUniform3uiv\");\n\tglad_glUniform4uiv = (PFNGLUNIFORM4UIVPROC)load(\"glUniform4uiv\");\n\tglad_glClearBufferiv = (PFNGLCLEARBUFFERIVPROC)load(\"glClearBufferiv\");\n\tglad_glClearBufferuiv = (PFNGLCLEARBUFFERUIVPROC)load(\"glClearBufferuiv\");\n\tglad_glClearBufferfv = (PFNGLCLEARBUFFERFVPROC)load(\"glClearBufferfv\");\n\tglad_glClearBufferfi = (PFNGLCLEARBUFFERFIPROC)load(\"glClearBufferfi\");\n\tglad_glGetStringi = (PFNGLGETSTRINGIPROC)load(\"glGetStringi\");\n\tglad_glCopyBufferSubData = (PFNGLCOPYBUFFERSUBDATAPROC)load(\"glCopyBufferSubData\");\n\tglad_glGetUniformIndices = (PFNGLGETUNIFORMINDICESPROC)load(\"glGetUniformIndices\");\n\tglad_glGetActiveUniformsiv = (PFNGLGETACTIVEUNIFORMSIVPROC)load(\"glGetActiveUniformsiv\");\n\tglad_glGetUniformBlockIndex = (PFNGLGETUNIFORMBLOCKINDEXPROC)load(\"glGetUniformBlockIndex\");\n\tglad_glGetActiveUniformBlockiv = (PFNGLGETACTIVEUNIFORMBLOCKIVPROC)load(\"glGetActiveUniformBlockiv\");\n\tglad_glGetActiveUniformBlockName = (PFNGLGETACTIVEUNIFORMBLOCKNAMEPROC)load(\"glGetActiveUniformBlockName\");\n\tglad_glUniformBlockBinding = (PFNGLUNIFORMBLOCKBINDINGPROC)load(\"glUniformBlockBinding\");\n\tglad_glDrawArraysInstanced = (PFNGLDRAWARRAYSINSTANCEDPROC)load(\"glDrawArraysInstanced\");\n\tglad_glDrawElementsInstanced = (PFNGLDRAWELEMENTSINSTANCEDPROC)load(\"glDrawElementsInstanced\");\n\tglad_glFenceSync = (PFNGLFENCESYNCPROC)load(\"glFenceSync\");\n\tglad_glIsSync = (PFNGLISSYNCPROC)load(\"glIsSync\");\n\tglad_glDeleteSync = (PFNGLDELETESYNCPROC)load(\"glDeleteSync\");\n\tglad_glClientWaitSync = (PFNGLCLIENTWAITSYNCPROC)load(\"glClientWaitSync\");\n\tglad_glWaitSync = (PFNGLWAITSYNCPROC)load(\"glWaitSync\");\n\tglad_glGetInteger64v = (PFNGLGETINTEGER64VPROC)load(\"glGetInteger64v\");\n\tglad_glGetSynciv = (PFNGLGETSYNCIVPROC)load(\"glGetSynciv\");\n\tglad_glGetInteger64i_v = (PFNGLGETINTEGER64I_VPROC)load(\"glGetInteger64i_v\");\n\tglad_glGetBufferParameteri64v = (PFNGLGETBUFFERPARAMETERI64VPROC)load(\"glGetBufferParameteri64v\");\n\tglad_glGenSamplers = (PFNGLGENSAMPLERSPROC)load(\"glGenSamplers\");\n\tglad_glDeleteSamplers = (PFNGLDELETESAMPLERSPROC)load(\"glDeleteSamplers\");\n\tglad_glIsSampler = (PFNGLISSAMPLERPROC)load(\"glIsSampler\");\n\tglad_glBindSampler = (PFNGLBINDSAMPLERPROC)load(\"glBindSampler\");\n\tglad_glSamplerParameteri = (PFNGLSAMPLERPARAMETERIPROC)load(\"glSamplerParameteri\");\n\tglad_glSamplerParameteriv = (PFNGLSAMPLERPARAMETERIVPROC)load(\"glSamplerParameteriv\");\n\tglad_glSamplerParameterf = (PFNGLSAMPLERPARAMETERFPROC)load(\"glSamplerParameterf\");\n\tglad_glSamplerParameterfv = (PFNGLSAMPLERPARAMETERFVPROC)load(\"glSamplerParameterfv\");\n\tglad_glGetSamplerParameteriv = (PFNGLGETSAMPLERPARAMETERIVPROC)load(\"glGetSamplerParameteriv\");\n\tglad_glGetSamplerParameterfv = (PFNGLGETSAMPLERPARAMETERFVPROC)load(\"glGetSamplerParameterfv\");\n\tglad_glVertexAttribDivisor = (PFNGLVERTEXATTRIBDIVISORPROC)load(\"glVertexAttribDivisor\");\n\tglad_glBindTransformFeedback = (PFNGLBINDTRANSFORMFEEDBACKPROC)load(\"glBindTransformFeedback\");\n\tglad_glDeleteTransformFeedbacks = (PFNGLDELETETRANSFORMFEEDBACKSPROC)load(\"glDeleteTransformFeedbacks\");\n\tglad_glGenTransformFeedbacks = (PFNGLGENTRANSFORMFEEDBACKSPROC)load(\"glGenTransformFeedbacks\");\n\tglad_glIsTransformFeedback = (PFNGLISTRANSFORMFEEDBACKPROC)load(\"glIsTransformFeedback\");\n\tglad_glPauseTransformFeedback = (PFNGLPAUSETRANSFORMFEEDBACKPROC)load(\"glPauseTransformFeedback\");\n\tglad_glResumeTransformFeedback = (PFNGLRESUMETRANSFORMFEEDBACKPROC)load(\"glResumeTransformFeedback\");\n\tglad_glGetProgramBinary = (PFNGLGETPROGRAMBINARYPROC)load(\"glGetProgramBinary\");\n\tglad_glProgramBinary = (PFNGLPROGRAMBINARYPROC)load(\"glProgramBinary\");\n\tglad_glProgramParameteri = (PFNGLPROGRAMPARAMETERIPROC)load(\"glProgramParameteri\");\n\tglad_glInvalidateFramebuffer = (PFNGLINVALIDATEFRAMEBUFFERPROC)load(\"glInvalidateFramebuffer\");\n\tglad_glInvalidateSubFramebuffer = (PFNGLINVALIDATESUBFRAMEBUFFERPROC)load(\"glInvalidateSubFramebuffer\");\n\tglad_glTexStorage2D = (PFNGLTEXSTORAGE2DPROC)load(\"glTexStorage2D\");\n\tglad_glTexStorage3D = (PFNGLTEXSTORAGE3DPROC)load(\"glTexStorage3D\");\n\tglad_glGetInternalformativ = (PFNGLGETINTERNALFORMATIVPROC)load(\"glGetInternalformativ\");\n}\nstatic void load_GL_ANGLE_framebuffer_blit(GLADloadproc load) {\n\tif(!GLAD_GL_ANGLE_framebuffer_blit) return;\n\tglad_glBlitFramebufferANGLE = (PFNGLBLITFRAMEBUFFERANGLEPROC)load(\"glBlitFramebufferANGLE\");\n}\nstatic void load_GL_ANGLE_framebuffer_multisample(GLADloadproc load) {\n\tif(!GLAD_GL_ANGLE_framebuffer_multisample) return;\n\tglad_glRenderbufferStorageMultisampleANGLE = (PFNGLRENDERBUFFERSTORAGEMULTISAMPLEANGLEPROC)load(\"glRenderbufferStorageMultisampleANGLE\");\n}\nstatic void load_GL_ANGLE_instanced_arrays(GLADloadproc load) {\n\tif(!GLAD_GL_ANGLE_instanced_arrays) return;\n\tglad_glDrawArraysInstancedANGLE = (PFNGLDRAWARRAYSINSTANCEDANGLEPROC)load(\"glDrawArraysInstancedANGLE\");\n\tglad_glDrawElementsInstancedANGLE = (PFNGLDRAWELEMENTSINSTANCEDANGLEPROC)load(\"glDrawElementsInstancedANGLE\");\n\tglad_glVertexAttribDivisorANGLE = (PFNGLVERTEXATTRIBDIVISORANGLEPROC)load(\"glVertexAttribDivisorANGLE\");\n}\nstatic void load_GL_ANGLE_translated_shader_source(GLADloadproc load) {\n\tif(!GLAD_GL_ANGLE_translated_shader_source) return;\n\tglad_glGetTranslatedShaderSourceANGLE = (PFNGLGETTRANSLATEDSHADERSOURCEANGLEPROC)load(\"glGetTranslatedShaderSourceANGLE\");\n}\nstatic void load_GL_APPLE_copy_texture_levels(GLADloadproc load) {\n\tif(!GLAD_GL_APPLE_copy_texture_levels) return;\n\tglad_glCopyTextureLevelsAPPLE = (PFNGLCOPYTEXTURELEVELSAPPLEPROC)load(\"glCopyTextureLevelsAPPLE\");\n}\nstatic void load_GL_APPLE_framebuffer_multisample(GLADloadproc load) {\n\tif(!GLAD_GL_APPLE_framebuffer_multisample) return;\n\tglad_glRenderbufferStorageMultisampleAPPLE = (PFNGLRENDERBUFFERSTORAGEMULTISAMPLEAPPLEPROC)load(\"glRenderbufferStorageMultisampleAPPLE\");\n\tglad_glResolveMultisampleFramebufferAPPLE = (PFNGLRESOLVEMULTISAMPLEFRAMEBUFFERAPPLEPROC)load(\"glResolveMultisampleFramebufferAPPLE\");\n}\nstatic void load_GL_APPLE_sync(GLADloadproc load) {\n\tif(!GLAD_GL_APPLE_sync) return;\n\tglad_glFenceSyncAPPLE = (PFNGLFENCESYNCAPPLEPROC)load(\"glFenceSyncAPPLE\");\n\tglad_glIsSyncAPPLE = (PFNGLISSYNCAPPLEPROC)load(\"glIsSyncAPPLE\");\n\tglad_glDeleteSyncAPPLE = (PFNGLDELETESYNCAPPLEPROC)load(\"glDeleteSyncAPPLE\");\n\tglad_glClientWaitSyncAPPLE = (PFNGLCLIENTWAITSYNCAPPLEPROC)load(\"glClientWaitSyncAPPLE\");\n\tglad_glWaitSyncAPPLE = (PFNGLWAITSYNCAPPLEPROC)load(\"glWaitSyncAPPLE\");\n\tglad_glGetInteger64vAPPLE = (PFNGLGETINTEGER64VAPPLEPROC)load(\"glGetInteger64vAPPLE\");\n\tglad_glGetSyncivAPPLE = (PFNGLGETSYNCIVAPPLEPROC)load(\"glGetSyncivAPPLE\");\n}\nstatic void load_GL_EXT_base_instance(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_base_instance) return;\n\tglad_glDrawArraysInstancedBaseInstanceEXT = (PFNGLDRAWARRAYSINSTANCEDBASEINSTANCEEXTPROC)load(\"glDrawArraysInstancedBaseInstanceEXT\");\n\tglad_glDrawElementsInstancedBaseInstanceEXT = (PFNGLDRAWELEMENTSINSTANCEDBASEINSTANCEEXTPROC)load(\"glDrawElementsInstancedBaseInstanceEXT\");\n\tglad_glDrawElementsInstancedBaseVertexBaseInstanceEXT = (PFNGLDRAWELEMENTSINSTANCEDBASEVERTEXBASEINSTANCEEXTPROC)load(\"glDrawElementsInstancedBaseVertexBaseInstanceEXT\");\n}\nstatic void load_GL_EXT_blend_func_extended(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_blend_func_extended) return;\n\tglad_glBindFragDataLocationIndexedEXT = (PFNGLBINDFRAGDATALOCATIONINDEXEDEXTPROC)load(\"glBindFragDataLocationIndexedEXT\");\n\tglad_glBindFragDataLocationEXT = (PFNGLBINDFRAGDATALOCATIONEXTPROC)load(\"glBindFragDataLocationEXT\");\n\tglad_glGetProgramResourceLocationIndexEXT = (PFNGLGETPROGRAMRESOURCELOCATIONINDEXEXTPROC)load(\"glGetProgramResourceLocationIndexEXT\");\n\tglad_glGetFragDataIndexEXT = (PFNGLGETFRAGDATAINDEXEXTPROC)load(\"glGetFragDataIndexEXT\");\n}\nstatic void load_GL_EXT_buffer_storage(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_buffer_storage) return;\n\tglad_glBufferStorageEXT = (PFNGLBUFFERSTORAGEEXTPROC)load(\"glBufferStorageEXT\");\n}\nstatic void load_GL_EXT_clear_texture(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_clear_texture) return;\n\tglad_glClearTexImageEXT = (PFNGLCLEARTEXIMAGEEXTPROC)load(\"glClearTexImageEXT\");\n\tglad_glClearTexSubImageEXT = (PFNGLCLEARTEXSUBIMAGEEXTPROC)load(\"glClearTexSubImageEXT\");\n}\nstatic void load_GL_EXT_clip_control(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_clip_control) return;\n\tglad_glClipControlEXT = (PFNGLCLIPCONTROLEXTPROC)load(\"glClipControlEXT\");\n}\nstatic void load_GL_EXT_copy_image(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_copy_image) return;\n\tglad_glCopyImageSubDataEXT = (PFNGLCOPYIMAGESUBDATAEXTPROC)load(\"glCopyImageSubDataEXT\");\n}\nstatic void load_GL_EXT_discard_framebuffer(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_discard_framebuffer) return;\n\tglad_glDiscardFramebufferEXT = (PFNGLDISCARDFRAMEBUFFEREXTPROC)load(\"glDiscardFramebufferEXT\");\n}\nstatic void load_GL_EXT_disjoint_timer_query(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_disjoint_timer_query) return;\n\tglad_glGenQueriesEXT = (PFNGLGENQUERIESEXTPROC)load(\"glGenQueriesEXT\");\n\tglad_glDeleteQueriesEXT = (PFNGLDELETEQUERIESEXTPROC)load(\"glDeleteQueriesEXT\");\n\tglad_glIsQueryEXT = (PFNGLISQUERYEXTPROC)load(\"glIsQueryEXT\");\n\tglad_glBeginQueryEXT = (PFNGLBEGINQUERYEXTPROC)load(\"glBeginQueryEXT\");\n\tglad_glEndQueryEXT = (PFNGLENDQUERYEXTPROC)load(\"glEndQueryEXT\");\n\tglad_glQueryCounterEXT = (PFNGLQUERYCOUNTEREXTPROC)load(\"glQueryCounterEXT\");\n\tglad_glGetQueryivEXT = (PFNGLGETQUERYIVEXTPROC)load(\"glGetQueryivEXT\");\n\tglad_glGetQueryObjectivEXT = (PFNGLGETQUERYOBJECTIVEXTPROC)load(\"glGetQueryObjectivEXT\");\n\tglad_glGetQueryObjectuivEXT = (PFNGLGETQUERYOBJECTUIVEXTPROC)load(\"glGetQueryObjectuivEXT\");\n\tglad_glGetQueryObjecti64vEXT = (PFNGLGETQUERYOBJECTI64VEXTPROC)load(\"glGetQueryObjecti64vEXT\");\n\tglad_glGetQueryObjectui64vEXT = (PFNGLGETQUERYOBJECTUI64VEXTPROC)load(\"glGetQueryObjectui64vEXT\");\n}\nstatic void load_GL_EXT_draw_buffers(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_draw_buffers) return;\n\tglad_glDrawBuffersEXT = (PFNGLDRAWBUFFERSEXTPROC)load(\"glDrawBuffersEXT\");\n}\nstatic void load_GL_EXT_draw_buffers_indexed(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_draw_buffers_indexed) return;\n\tglad_glEnableiEXT = (PFNGLENABLEIEXTPROC)load(\"glEnableiEXT\");\n\tglad_glDisableiEXT = (PFNGLDISABLEIEXTPROC)load(\"glDisableiEXT\");\n\tglad_glBlendEquationiEXT = (PFNGLBLENDEQUATIONIEXTPROC)load(\"glBlendEquationiEXT\");\n\tglad_glBlendEquationSeparateiEXT = (PFNGLBLENDEQUATIONSEPARATEIEXTPROC)load(\"glBlendEquationSeparateiEXT\");\n\tglad_glBlendFunciEXT = (PFNGLBLENDFUNCIEXTPROC)load(\"glBlendFunciEXT\");\n\tglad_glBlendFuncSeparateiEXT = (PFNGLBLENDFUNCSEPARATEIEXTPROC)load(\"glBlendFuncSeparateiEXT\");\n\tglad_glColorMaskiEXT = (PFNGLCOLORMASKIEXTPROC)load(\"glColorMaskiEXT\");\n\tglad_glIsEnablediEXT = (PFNGLISENABLEDIEXTPROC)load(\"glIsEnablediEXT\");\n}\nstatic void load_GL_EXT_draw_elements_base_vertex(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_draw_elements_base_vertex) return;\n\tglad_glDrawElementsBaseVertexEXT = (PFNGLDRAWELEMENTSBASEVERTEXEXTPROC)load(\"glDrawElementsBaseVertexEXT\");\n\tglad_glDrawRangeElementsBaseVertexEXT = (PFNGLDRAWRANGEELEMENTSBASEVERTEXEXTPROC)load(\"glDrawRangeElementsBaseVertexEXT\");\n\tglad_glDrawElementsInstancedBaseVertexEXT = (PFNGLDRAWELEMENTSINSTANCEDBASEVERTEXEXTPROC)load(\"glDrawElementsInstancedBaseVertexEXT\");\n\tglad_glMultiDrawElementsBaseVertexEXT = (PFNGLMULTIDRAWELEMENTSBASEVERTEXEXTPROC)load(\"glMultiDrawElementsBaseVertexEXT\");\n}\nstatic void load_GL_EXT_draw_transform_feedback(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_draw_transform_feedback) return;\n\tglad_glDrawTransformFeedbackEXT = (PFNGLDRAWTRANSFORMFEEDBACKEXTPROC)load(\"glDrawTransformFeedbackEXT\");\n\tglad_glDrawTransformFeedbackInstancedEXT = (PFNGLDRAWTRANSFORMFEEDBACKINSTANCEDEXTPROC)load(\"glDrawTransformFeedbackInstancedEXT\");\n}\nstatic void load_GL_EXT_geometry_shader(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_geometry_shader) return;\n\tglad_glFramebufferTextureEXT = (PFNGLFRAMEBUFFERTEXTUREEXTPROC)load(\"glFramebufferTextureEXT\");\n}\nstatic void load_GL_EXT_instanced_arrays(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_instanced_arrays) return;\n\tglad_glDrawArraysInstancedEXT = (PFNGLDRAWARRAYSINSTANCEDEXTPROC)load(\"glDrawArraysInstancedEXT\");\n\tglad_glDrawElementsInstancedEXT = (PFNGLDRAWELEMENTSINSTANCEDEXTPROC)load(\"glDrawElementsInstancedEXT\");\n\tglad_glVertexAttribDivisorEXT = (PFNGLVERTEXATTRIBDIVISOREXTPROC)load(\"glVertexAttribDivisorEXT\");\n}\nstatic void load_GL_EXT_map_buffer_range(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_map_buffer_range) return;\n\tglad_glMapBufferRangeEXT = (PFNGLMAPBUFFERRANGEEXTPROC)load(\"glMapBufferRangeEXT\");\n\tglad_glFlushMappedBufferRangeEXT = (PFNGLFLUSHMAPPEDBUFFERRANGEEXTPROC)load(\"glFlushMappedBufferRangeEXT\");\n}\nstatic void load_GL_EXT_multi_draw_indirect(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_multi_draw_indirect) return;\n\tglad_glMultiDrawArraysIndirectEXT = (PFNGLMULTIDRAWARRAYSINDIRECTEXTPROC)load(\"glMultiDrawArraysIndirectEXT\");\n\tglad_glMultiDrawElementsIndirectEXT = (PFNGLMULTIDRAWELEMENTSINDIRECTEXTPROC)load(\"glMultiDrawElementsIndirectEXT\");\n}\nstatic void load_GL_EXT_multisampled_render_to_texture(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_multisampled_render_to_texture) return;\n\tglad_glRenderbufferStorageMultisampleEXT = (PFNGLRENDERBUFFERSTORAGEMULTISAMPLEEXTPROC)load(\"glRenderbufferStorageMultisampleEXT\");\n\tglad_glFramebufferTexture2DMultisampleEXT = (PFNGLFRAMEBUFFERTEXTURE2DMULTISAMPLEEXTPROC)load(\"glFramebufferTexture2DMultisampleEXT\");\n}\nstatic void load_GL_EXT_multiview_draw_buffers(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_multiview_draw_buffers) return;\n\tglad_glReadBufferIndexedEXT = (PFNGLREADBUFFERINDEXEDEXTPROC)load(\"glReadBufferIndexedEXT\");\n\tglad_glDrawBuffersIndexedEXT = (PFNGLDRAWBUFFERSINDEXEDEXTPROC)load(\"glDrawBuffersIndexedEXT\");\n\tglad_glGetIntegeri_vEXT = (PFNGLGETINTEGERI_VEXTPROC)load(\"glGetIntegeri_vEXT\");\n}\nstatic void load_GL_EXT_occlusion_query_boolean(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_occlusion_query_boolean) return;\n\tglad_glGenQueriesEXT = (PFNGLGENQUERIESEXTPROC)load(\"glGenQueriesEXT\");\n\tglad_glDeleteQueriesEXT = (PFNGLDELETEQUERIESEXTPROC)load(\"glDeleteQueriesEXT\");\n\tglad_glIsQueryEXT = (PFNGLISQUERYEXTPROC)load(\"glIsQueryEXT\");\n\tglad_glBeginQueryEXT = (PFNGLBEGINQUERYEXTPROC)load(\"glBeginQueryEXT\");\n\tglad_glEndQueryEXT = (PFNGLENDQUERYEXTPROC)load(\"glEndQueryEXT\");\n\tglad_glGetQueryivEXT = (PFNGLGETQUERYIVEXTPROC)load(\"glGetQueryivEXT\");\n\tglad_glGetQueryObjectuivEXT = (PFNGLGETQUERYOBJECTUIVEXTPROC)load(\"glGetQueryObjectuivEXT\");\n}\nstatic void load_GL_EXT_primitive_bounding_box(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_primitive_bounding_box) return;\n\tglad_glPrimitiveBoundingBoxEXT = (PFNGLPRIMITIVEBOUNDINGBOXEXTPROC)load(\"glPrimitiveBoundingBoxEXT\");\n}\nstatic void load_GL_EXT_robustness(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_robustness) return;\n\tglad_glGetGraphicsResetStatusEXT = (PFNGLGETGRAPHICSRESETSTATUSEXTPROC)load(\"glGetGraphicsResetStatusEXT\");\n\tglad_glReadnPixelsEXT = (PFNGLREADNPIXELSEXTPROC)load(\"glReadnPixelsEXT\");\n\tglad_glGetnUniformfvEXT = (PFNGLGETNUNIFORMFVEXTPROC)load(\"glGetnUniformfvEXT\");\n\tglad_glGetnUniformivEXT = (PFNGLGETNUNIFORMIVEXTPROC)load(\"glGetnUniformivEXT\");\n}\nstatic void load_GL_EXT_shader_pixel_local_storage2(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_shader_pixel_local_storage2) return;\n\tglad_glFramebufferPixelLocalStorageSizeEXT = (PFNGLFRAMEBUFFERPIXELLOCALSTORAGESIZEEXTPROC)load(\"glFramebufferPixelLocalStorageSizeEXT\");\n\tglad_glGetFramebufferPixelLocalStorageSizeEXT = (PFNGLGETFRAMEBUFFERPIXELLOCALSTORAGESIZEEXTPROC)load(\"glGetFramebufferPixelLocalStorageSizeEXT\");\n\tglad_glClearPixelLocalStorageuiEXT = (PFNGLCLEARPIXELLOCALSTORAGEUIEXTPROC)load(\"glClearPixelLocalStorageuiEXT\");\n}\nstatic void load_GL_EXT_sparse_texture(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_sparse_texture) return;\n\tglad_glTexPageCommitmentEXT = (PFNGLTEXPAGECOMMITMENTEXTPROC)load(\"glTexPageCommitmentEXT\");\n}\nstatic void load_GL_EXT_tessellation_shader(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_tessellation_shader) return;\n\tglad_glPatchParameteriEXT = (PFNGLPATCHPARAMETERIEXTPROC)load(\"glPatchParameteriEXT\");\n}\nstatic void load_GL_EXT_texture_border_clamp(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_texture_border_clamp) return;\n\tglad_glTexParameterIivEXT = (PFNGLTEXPARAMETERIIVEXTPROC)load(\"glTexParameterIivEXT\");\n\tglad_glTexParameterIuivEXT = (PFNGLTEXPARAMETERIUIVEXTPROC)load(\"glTexParameterIuivEXT\");\n\tglad_glGetTexParameterIivEXT = (PFNGLGETTEXPARAMETERIIVEXTPROC)load(\"glGetTexParameterIivEXT\");\n\tglad_glGetTexParameterIuivEXT = (PFNGLGETTEXPARAMETERIUIVEXTPROC)load(\"glGetTexParameterIuivEXT\");\n\tglad_glSamplerParameterIivEXT = (PFNGLSAMPLERPARAMETERIIVEXTPROC)load(\"glSamplerParameterIivEXT\");\n\tglad_glSamplerParameterIuivEXT = (PFNGLSAMPLERPARAMETERIUIVEXTPROC)load(\"glSamplerParameterIuivEXT\");\n\tglad_glGetSamplerParameterIivEXT = (PFNGLGETSAMPLERPARAMETERIIVEXTPROC)load(\"glGetSamplerParameterIivEXT\");\n\tglad_glGetSamplerParameterIuivEXT = (PFNGLGETSAMPLERPARAMETERIUIVEXTPROC)load(\"glGetSamplerParameterIuivEXT\");\n}\nstatic void load_GL_EXT_texture_buffer(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_texture_buffer) return;\n\tglad_glTexBufferEXT = (PFNGLTEXBUFFEREXTPROC)load(\"glTexBufferEXT\");\n\tglad_glTexBufferRangeEXT = (PFNGLTEXBUFFERRANGEEXTPROC)load(\"glTexBufferRangeEXT\");\n}\nstatic void load_GL_EXT_texture_storage(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_texture_storage) return;\n\tglad_glTexStorage1DEXT = (PFNGLTEXSTORAGE1DEXTPROC)load(\"glTexStorage1DEXT\");\n\tglad_glTexStorage2DEXT = (PFNGLTEXSTORAGE2DEXTPROC)load(\"glTexStorage2DEXT\");\n\tglad_glTexStorage3DEXT = (PFNGLTEXSTORAGE3DEXTPROC)load(\"glTexStorage3DEXT\");\n\tglad_glTextureStorage1DEXT = (PFNGLTEXTURESTORAGE1DEXTPROC)load(\"glTextureStorage1DEXT\");\n\tglad_glTextureStorage2DEXT = (PFNGLTEXTURESTORAGE2DEXTPROC)load(\"glTextureStorage2DEXT\");\n\tglad_glTextureStorage3DEXT = (PFNGLTEXTURESTORAGE3DEXTPROC)load(\"glTextureStorage3DEXT\");\n}\nstatic void load_GL_EXT_texture_view(GLADloadproc load) {\n\tif(!GLAD_GL_EXT_texture_view) return;\n\tglad_glTextureViewEXT = (PFNGLTEXTUREVIEWEXTPROC)load(\"glTextureViewEXT\");\n}\nstatic void load_GL_IMG_bindless_texture(GLADloadproc load) {\n\tif(!GLAD_GL_IMG_bindless_texture) return;\n\tglad_glGetTextureHandleIMG = (PFNGLGETTEXTUREHANDLEIMGPROC)load(\"glGetTextureHandleIMG\");\n\tglad_glGetTextureSamplerHandleIMG = (PFNGLGETTEXTURESAMPLERHANDLEIMGPROC)load(\"glGetTextureSamplerHandleIMG\");\n\tglad_glUniformHandleui64IMG = (PFNGLUNIFORMHANDLEUI64IMGPROC)load(\"glUniformHandleui64IMG\");\n\tglad_glUniformHandleui64vIMG = (PFNGLUNIFORMHANDLEUI64VIMGPROC)load(\"glUniformHandleui64vIMG\");\n\tglad_glProgramUniformHandleui64IMG = (PFNGLPROGRAMUNIFORMHANDLEUI64IMGPROC)load(\"glProgramUniformHandleui64IMG\");\n\tglad_glProgramUniformHandleui64vIMG = (PFNGLPROGRAMUNIFORMHANDLEUI64VIMGPROC)load(\"glProgramUniformHandleui64vIMG\");\n}\nstatic void load_GL_IMG_framebuffer_downsample(GLADloadproc load) {\n\tif(!GLAD_GL_IMG_framebuffer_downsample) return;\n\tglad_glFramebufferTexture2DDownsampleIMG = (PFNGLFRAMEBUFFERTEXTURE2DDOWNSAMPLEIMGPROC)load(\"glFramebufferTexture2DDownsampleIMG\");\n\tglad_glFramebufferTextureLayerDownsampleIMG = (PFNGLFRAMEBUFFERTEXTURELAYERDOWNSAMPLEIMGPROC)load(\"glFramebufferTextureLayerDownsampleIMG\");\n}\nstatic void load_GL_IMG_multisampled_render_to_texture(GLADloadproc load) {\n\tif(!GLAD_GL_IMG_multisampled_render_to_texture) return;\n\tglad_glRenderbufferStorageMultisampleIMG = (PFNGLRENDERBUFFERSTORAGEMULTISAMPLEIMGPROC)load(\"glRenderbufferStorageMultisampleIMG\");\n\tglad_glFramebufferTexture2DMultisampleIMG = (PFNGLFRAMEBUFFERTEXTURE2DMULTISAMPLEIMGPROC)load(\"glFramebufferTexture2DMultisampleIMG\");\n}\nstatic void load_GL_NV_copy_buffer(GLADloadproc load) {\n\tif(!GLAD_GL_NV_copy_buffer) return;\n\tglad_glCopyBufferSubDataNV = (PFNGLCOPYBUFFERSUBDATANVPROC)load(\"glCopyBufferSubDataNV\");\n}\nstatic void load_GL_NV_coverage_sample(GLADloadproc load) {\n\tif(!GLAD_GL_NV_coverage_sample) return;\n\tglad_glCoverageMaskNV = (PFNGLCOVERAGEMASKNVPROC)load(\"glCoverageMaskNV\");\n\tglad_glCoverageOperationNV = (PFNGLCOVERAGEOPERATIONNVPROC)load(\"glCoverageOperationNV\");\n}\nstatic void load_GL_NV_draw_buffers(GLADloadproc load) {\n\tif(!GLAD_GL_NV_draw_buffers) return;\n\tglad_glDrawBuffersNV = (PFNGLDRAWBUFFERSNVPROC)load(\"glDrawBuffersNV\");\n}\nstatic void load_GL_NV_draw_instanced(GLADloadproc load) {\n\tif(!GLAD_GL_NV_draw_instanced) return;\n\tglad_glDrawArraysInstancedNV = (PFNGLDRAWARRAYSINSTANCEDNVPROC)load(\"glDrawArraysInstancedNV\");\n\tglad_glDrawElementsInstancedNV = (PFNGLDRAWELEMENTSINSTANCEDNVPROC)load(\"glDrawElementsInstancedNV\");\n}\nstatic void load_GL_NV_framebuffer_blit(GLADloadproc load) {\n\tif(!GLAD_GL_NV_framebuffer_blit) return;\n\tglad_glBlitFramebufferNV = (PFNGLBLITFRAMEBUFFERNVPROC)load(\"glBlitFramebufferNV\");\n}\nstatic void load_GL_NV_framebuffer_multisample(GLADloadproc load) {\n\tif(!GLAD_GL_NV_framebuffer_multisample) return;\n\tglad_glRenderbufferStorageMultisampleNV = (PFNGLRENDERBUFFERSTORAGEMULTISAMPLENVPROC)load(\"glRenderbufferStorageMultisampleNV\");\n}\nstatic void load_GL_NV_instanced_arrays(GLADloadproc load) {\n\tif(!GLAD_GL_NV_instanced_arrays) return;\n\tglad_glVertexAttribDivisorNV = (PFNGLVERTEXATTRIBDIVISORNVPROC)load(\"glVertexAttribDivisorNV\");\n}\nstatic void load_GL_NV_non_square_matrices(GLADloadproc load) {\n\tif(!GLAD_GL_NV_non_square_matrices) return;\n\tglad_glUniformMatrix2x3fvNV = (PFNGLUNIFORMMATRIX2X3FVNVPROC)load(\"glUniformMatrix2x3fvNV\");\n\tglad_glUniformMatrix3x2fvNV = (PFNGLUNIFORMMATRIX3X2FVNVPROC)load(\"glUniformMatrix3x2fvNV\");\n\tglad_glUniformMatrix2x4fvNV = (PFNGLUNIFORMMATRIX2X4FVNVPROC)load(\"glUniformMatrix2x4fvNV\");\n\tglad_glUniformMatrix4x2fvNV = (PFNGLUNIFORMMATRIX4X2FVNVPROC)load(\"glUniformMatrix4x2fvNV\");\n\tglad_glUniformMatrix3x4fvNV = (PFNGLUNIFORMMATRIX3X4FVNVPROC)load(\"glUniformMatrix3x4fvNV\");\n\tglad_glUniformMatrix4x3fvNV = (PFNGLUNIFORMMATRIX4X3FVNVPROC)load(\"glUniformMatrix4x3fvNV\");\n}\nstatic void load_GL_NV_polygon_mode(GLADloadproc load) {\n\tif(!GLAD_GL_NV_polygon_mode) return;\n\tglad_glPolygonModeNV = (PFNGLPOLYGONMODENVPROC)load(\"glPolygonModeNV\");\n}\nstatic void load_GL_NV_read_buffer(GLADloadproc load) {\n\tif(!GLAD_GL_NV_read_buffer) return;\n\tglad_glReadBufferNV = (PFNGLREADBUFFERNVPROC)load(\"glReadBufferNV\");\n}\nstatic void load_GL_NV_viewport_array(GLADloadproc load) {\n\tif(!GLAD_GL_NV_viewport_array) return;\n\tglad_glViewportArrayvNV = (PFNGLVIEWPORTARRAYVNVPROC)load(\"glViewportArrayvNV\");\n\tglad_glViewportIndexedfNV = (PFNGLVIEWPORTINDEXEDFNVPROC)load(\"glViewportIndexedfNV\");\n\tglad_glViewportIndexedfvNV = (PFNGLVIEWPORTINDEXEDFVNVPROC)load(\"glViewportIndexedfvNV\");\n\tglad_glScissorArrayvNV = (PFNGLSCISSORARRAYVNVPROC)load(\"glScissorArrayvNV\");\n\tglad_glScissorIndexedNV = (PFNGLSCISSORINDEXEDNVPROC)load(\"glScissorIndexedNV\");\n\tglad_glScissorIndexedvNV = (PFNGLSCISSORINDEXEDVNVPROC)load(\"glScissorIndexedvNV\");\n\tglad_glDepthRangeArrayfvNV = (PFNGLDEPTHRANGEARRAYFVNVPROC)load(\"glDepthRangeArrayfvNV\");\n\tglad_glDepthRangeIndexedfNV = (PFNGLDEPTHRANGEINDEXEDFNVPROC)load(\"glDepthRangeIndexedfNV\");\n\tglad_glGetFloati_vNV = (PFNGLGETFLOATI_VNVPROC)load(\"glGetFloati_vNV\");\n\tglad_glEnableiNV = (PFNGLENABLEINVPROC)load(\"glEnableiNV\");\n\tglad_glDisableiNV = (PFNGLDISABLEINVPROC)load(\"glDisableiNV\");\n\tglad_glIsEnablediNV = (PFNGLISENABLEDINVPROC)load(\"glIsEnablediNV\");\n}\nstatic void load_GL_OES_EGL_image(GLADloadproc load) {\n\tif(!GLAD_GL_OES_EGL_image) return;\n\tglad_glEGLImageTargetTexture2DOES = (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)load(\"glEGLImageTargetTexture2DOES\");\n\tglad_glEGLImageTargetRenderbufferStorageOES = (PFNGLEGLIMAGETARGETRENDERBUFFERSTORAGEOESPROC)load(\"glEGLImageTargetRenderbufferStorageOES\");\n}\nstatic void load_GL_OES_copy_image(GLADloadproc load) {\n\tif(!GLAD_GL_OES_copy_image) return;\n\tglad_glCopyImageSubDataOES = (PFNGLCOPYIMAGESUBDATAOESPROC)load(\"glCopyImageSubDataOES\");\n}\nstatic void load_GL_OES_draw_buffers_indexed(GLADloadproc load) {\n\tif(!GLAD_GL_OES_draw_buffers_indexed) return;\n\tglad_glEnableiOES = (PFNGLENABLEIOESPROC)load(\"glEnableiOES\");\n\tglad_glDisableiOES = (PFNGLDISABLEIOESPROC)load(\"glDisableiOES\");\n\tglad_glBlendEquationiOES = (PFNGLBLENDEQUATIONIOESPROC)load(\"glBlendEquationiOES\");\n\tglad_glBlendEquationSeparateiOES = (PFNGLBLENDEQUATIONSEPARATEIOESPROC)load(\"glBlendEquationSeparateiOES\");\n\tglad_glBlendFunciOES = (PFNGLBLENDFUNCIOESPROC)load(\"glBlendFunciOES\");\n\tglad_glBlendFuncSeparateiOES = (PFNGLBLENDFUNCSEPARATEIOESPROC)load(\"glBlendFuncSeparateiOES\");\n\tglad_glColorMaskiOES = (PFNGLCOLORMASKIOESPROC)load(\"glColorMaskiOES\");\n\tglad_glIsEnablediOES = (PFNGLISENABLEDIOESPROC)load(\"glIsEnablediOES\");\n}\nstatic void load_GL_OES_draw_elements_base_vertex(GLADloadproc load) {\n\tif(!GLAD_GL_OES_draw_elements_base_vertex) return;\n\tglad_glDrawElementsBaseVertexOES = (PFNGLDRAWELEMENTSBASEVERTEXOESPROC)load(\"glDrawElementsBaseVertexOES\");\n\tglad_glDrawRangeElementsBaseVertexOES = (PFNGLDRAWRANGEELEMENTSBASEVERTEXOESPROC)load(\"glDrawRangeElementsBaseVertexOES\");\n\tglad_glDrawElementsInstancedBaseVertexOES = (PFNGLDRAWELEMENTSINSTANCEDBASEVERTEXOESPROC)load(\"glDrawElementsInstancedBaseVertexOES\");\n\tglad_glMultiDrawElementsBaseVertexEXT = (PFNGLMULTIDRAWELEMENTSBASEVERTEXEXTPROC)load(\"glMultiDrawElementsBaseVertexEXT\");\n}\nstatic void load_GL_OES_geometry_shader(GLADloadproc load) {\n\tif(!GLAD_GL_OES_geometry_shader) return;\n\tglad_glFramebufferTextureOES = (PFNGLFRAMEBUFFERTEXTUREOESPROC)load(\"glFramebufferTextureOES\");\n}\nstatic void load_GL_OES_get_program_binary(GLADloadproc load) {\n\tif(!GLAD_GL_OES_get_program_binary) return;\n\tglad_glGetProgramBinaryOES = (PFNGLGETPROGRAMBINARYOESPROC)load(\"glGetProgramBinaryOES\");\n\tglad_glProgramBinaryOES = (PFNGLPROGRAMBINARYOESPROC)load(\"glProgramBinaryOES\");\n}\nstatic void load_GL_OES_mapbuffer(GLADloadproc load) {\n\tif(!GLAD_GL_OES_mapbuffer) return;\n\tglad_glMapBufferOES = (PFNGLMAPBUFFEROESPROC)load(\"glMapBufferOES\");\n\tglad_glUnmapBufferOES = (PFNGLUNMAPBUFFEROESPROC)load(\"glUnmapBufferOES\");\n\tglad_glGetBufferPointervOES = (PFNGLGETBUFFERPOINTERVOESPROC)load(\"glGetBufferPointervOES\");\n}\nstatic void load_GL_OES_primitive_bounding_box(GLADloadproc load) {\n\tif(!GLAD_GL_OES_primitive_bounding_box) return;\n\tglad_glPrimitiveBoundingBoxOES = (PFNGLPRIMITIVEBOUNDINGBOXOESPROC)load(\"glPrimitiveBoundingBoxOES\");\n}\nstatic void load_GL_OES_sample_shading(GLADloadproc load) {\n\tif(!GLAD_GL_OES_sample_shading) return;\n\tglad_glMinSampleShadingOES = (PFNGLMINSAMPLESHADINGOESPROC)load(\"glMinSampleShadingOES\");\n}\nstatic void load_GL_OES_tessellation_shader(GLADloadproc load) {\n\tif(!GLAD_GL_OES_tessellation_shader) return;\n\tglad_glPatchParameteriOES = (PFNGLPATCHPARAMETERIOESPROC)load(\"glPatchParameteriOES\");\n}\nstatic void load_GL_OES_texture_3D(GLADloadproc load) {\n\tif(!GLAD_GL_OES_texture_3D) return;\n\tglad_glTexImage3DOES = (PFNGLTEXIMAGE3DOESPROC)load(\"glTexImage3DOES\");\n\tglad_glTexSubImage3DOES = (PFNGLTEXSUBIMAGE3DOESPROC)load(\"glTexSubImage3DOES\");\n\tglad_glCopyTexSubImage3DOES = (PFNGLCOPYTEXSUBIMAGE3DOESPROC)load(\"glCopyTexSubImage3DOES\");\n\tglad_glCompressedTexImage3DOES = (PFNGLCOMPRESSEDTEXIMAGE3DOESPROC)load(\"glCompressedTexImage3DOES\");\n\tglad_glCompressedTexSubImage3DOES = (PFNGLCOMPRESSEDTEXSUBIMAGE3DOESPROC)load(\"glCompressedTexSubImage3DOES\");\n\tglad_glFramebufferTexture3DOES = (PFNGLFRAMEBUFFERTEXTURE3DOESPROC)load(\"glFramebufferTexture3DOES\");\n}\nstatic void load_GL_OES_texture_border_clamp(GLADloadproc load) {\n\tif(!GLAD_GL_OES_texture_border_clamp) return;\n\tglad_glTexParameterIivOES = (PFNGLTEXPARAMETERIIVOESPROC)load(\"glTexParameterIivOES\");\n\tglad_glTexParameterIuivOES = (PFNGLTEXPARAMETERIUIVOESPROC)load(\"glTexParameterIuivOES\");\n\tglad_glGetTexParameterIivOES = (PFNGLGETTEXPARAMETERIIVOESPROC)load(\"glGetTexParameterIivOES\");\n\tglad_glGetTexParameterIuivOES = (PFNGLGETTEXPARAMETERIUIVOESPROC)load(\"glGetTexParameterIuivOES\");\n\tglad_glSamplerParameterIivOES = (PFNGLSAMPLERPARAMETERIIVOESPROC)load(\"glSamplerParameterIivOES\");\n\tglad_glSamplerParameterIuivOES = (PFNGLSAMPLERPARAMETERIUIVOESPROC)load(\"glSamplerParameterIuivOES\");\n\tglad_glGetSamplerParameterIivOES = (PFNGLGETSAMPLERPARAMETERIIVOESPROC)load(\"glGetSamplerParameterIivOES\");\n\tglad_glGetSamplerParameterIuivOES = (PFNGLGETSAMPLERPARAMETERIUIVOESPROC)load(\"glGetSamplerParameterIuivOES\");\n}\nstatic void load_GL_OES_texture_buffer(GLADloadproc load) {\n\tif(!GLAD_GL_OES_texture_buffer) return;\n\tglad_glTexBufferOES = (PFNGLTEXBUFFEROESPROC)load(\"glTexBufferOES\");\n\tglad_glTexBufferRangeOES = (PFNGLTEXBUFFERRANGEOESPROC)load(\"glTexBufferRangeOES\");\n}\nstatic void load_GL_OES_texture_storage_multisample_2d_array(GLADloadproc load) {\n\tif(!GLAD_GL_OES_texture_storage_multisample_2d_array) return;\n\tglad_glTexStorage3DMultisampleOES = (PFNGLTEXSTORAGE3DMULTISAMPLEOESPROC)load(\"glTexStorage3DMultisampleOES\");\n}\nstatic void load_GL_OES_texture_view(GLADloadproc load) {\n\tif(!GLAD_GL_OES_texture_view) return;\n\tglad_glTextureViewOES = (PFNGLTEXTUREVIEWOESPROC)load(\"glTextureViewOES\");\n}\nstatic void load_GL_OES_vertex_array_object(GLADloadproc load) {\n\tif(!GLAD_GL_OES_vertex_array_object) return;\n\tglad_glBindVertexArrayOES = (PFNGLBINDVERTEXARRAYOESPROC)load(\"glBindVertexArrayOES\");\n\tglad_glDeleteVertexArraysOES = (PFNGLDELETEVERTEXARRAYSOESPROC)load(\"glDeleteVertexArraysOES\");\n\tglad_glGenVertexArraysOES = (PFNGLGENVERTEXARRAYSOESPROC)load(\"glGenVertexArraysOES\");\n\tglad_glIsVertexArrayOES = (PFNGLISVERTEXARRAYOESPROC)load(\"glIsVertexArrayOES\");\n}\nstatic void load_GL_OES_viewport_array(GLADloadproc load) {\n\tif(!GLAD_GL_OES_viewport_array) return;\n\tglad_glViewportArrayvOES = (PFNGLVIEWPORTARRAYVOESPROC)load(\"glViewportArrayvOES\");\n\tglad_glViewportIndexedfOES = (PFNGLVIEWPORTINDEXEDFOESPROC)load(\"glViewportIndexedfOES\");\n\tglad_glViewportIndexedfvOES = (PFNGLVIEWPORTINDEXEDFVOESPROC)load(\"glViewportIndexedfvOES\");\n\tglad_glScissorArrayvOES = (PFNGLSCISSORARRAYVOESPROC)load(\"glScissorArrayvOES\");\n\tglad_glScissorIndexedOES = (PFNGLSCISSORINDEXEDOESPROC)load(\"glScissorIndexedOES\");\n\tglad_glScissorIndexedvOES = (PFNGLSCISSORINDEXEDVOESPROC)load(\"glScissorIndexedvOES\");\n\tglad_glDepthRangeArrayfvOES = (PFNGLDEPTHRANGEARRAYFVOESPROC)load(\"glDepthRangeArrayfvOES\");\n\tglad_glDepthRangeIndexedfOES = (PFNGLDEPTHRANGEINDEXEDFOESPROC)load(\"glDepthRangeIndexedfOES\");\n\tglad_glGetFloati_vOES = (PFNGLGETFLOATI_VOESPROC)load(\"glGetFloati_vOES\");\n\tglad_glEnableiOES = (PFNGLENABLEIOESPROC)load(\"glEnableiOES\");\n\tglad_glDisableiOES = (PFNGLDISABLEIOESPROC)load(\"glDisableiOES\");\n\tglad_glIsEnablediOES = (PFNGLISENABLEDIOESPROC)load(\"glIsEnablediOES\");\n}\nstatic void load_GL_OVR_multiview_multisampled_render_to_texture(GLADloadproc load) {\n\tif(!GLAD_GL_OVR_multiview_multisampled_render_to_texture) return;\n\tglad_glFramebufferTextureMultisampleMultiviewOVR = (PFNGLFRAMEBUFFERTEXTUREMULTISAMPLEMULTIVIEWOVRPROC)load(\"glFramebufferTextureMultisampleMultiviewOVR\");\n}\nstatic void load_GL_QCOM_alpha_test(GLADloadproc load) {\n\tif(!GLAD_GL_QCOM_alpha_test) return;\n\tglad_glAlphaFuncQCOM = (PFNGLALPHAFUNCQCOMPROC)load(\"glAlphaFuncQCOM\");\n}\nstatic void load_GL_QCOM_driver_control(GLADloadproc load) {\n\tif(!GLAD_GL_QCOM_driver_control) return;\n\tglad_glGetDriverControlsQCOM = (PFNGLGETDRIVERCONTROLSQCOMPROC)load(\"glGetDriverControlsQCOM\");\n\tglad_glGetDriverControlStringQCOM = (PFNGLGETDRIVERCONTROLSTRINGQCOMPROC)load(\"glGetDriverControlStringQCOM\");\n\tglad_glEnableDriverControlQCOM = (PFNGLENABLEDRIVERCONTROLQCOMPROC)load(\"glEnableDriverControlQCOM\");\n\tglad_glDisableDriverControlQCOM = (PFNGLDISABLEDRIVERCONTROLQCOMPROC)load(\"glDisableDriverControlQCOM\");\n}\nstatic void load_GL_QCOM_extended_get(GLADloadproc load) {\n\tif(!GLAD_GL_QCOM_extended_get) return;\n\tglad_glExtGetTexturesQCOM = (PFNGLEXTGETTEXTURESQCOMPROC)load(\"glExtGetTexturesQCOM\");\n\tglad_glExtGetBuffersQCOM = (PFNGLEXTGETBUFFERSQCOMPROC)load(\"glExtGetBuffersQCOM\");\n\tglad_glExtGetRenderbuffersQCOM = (PFNGLEXTGETRENDERBUFFERSQCOMPROC)load(\"glExtGetRenderbuffersQCOM\");\n\tglad_glExtGetFramebuffersQCOM = (PFNGLEXTGETFRAMEBUFFERSQCOMPROC)load(\"glExtGetFramebuffersQCOM\");\n\tglad_glExtGetTexLevelParameterivQCOM = (PFNGLEXTGETTEXLEVELPARAMETERIVQCOMPROC)load(\"glExtGetTexLevelParameterivQCOM\");\n\tglad_glExtTexObjectStateOverrideiQCOM = (PFNGLEXTTEXOBJECTSTATEOVERRIDEIQCOMPROC)load(\"glExtTexObjectStateOverrideiQCOM\");\n\tglad_glExtGetTexSubImageQCOM = (PFNGLEXTGETTEXSUBIMAGEQCOMPROC)load(\"glExtGetTexSubImageQCOM\");\n\tglad_glExtGetBufferPointervQCOM = (PFNGLEXTGETBUFFERPOINTERVQCOMPROC)load(\"glExtGetBufferPointervQCOM\");\n}\nstatic void load_GL_QCOM_extended_get2(GLADloadproc load) {\n\tif(!GLAD_GL_QCOM_extended_get2) return;\n\tglad_glExtGetShadersQCOM = (PFNGLEXTGETSHADERSQCOMPROC)load(\"glExtGetShadersQCOM\");\n\tglad_glExtGetProgramsQCOM = (PFNGLEXTGETPROGRAMSQCOMPROC)load(\"glExtGetProgramsQCOM\");\n\tglad_glExtIsProgramBinaryQCOM = (PFNGLEXTISPROGRAMBINARYQCOMPROC)load(\"glExtIsProgramBinaryQCOM\");\n\tglad_glExtGetProgramBinarySourceQCOM = (PFNGLEXTGETPROGRAMBINARYSOURCEQCOMPROC)load(\"glExtGetProgramBinarySourceQCOM\");\n}\nstatic void load_GL_QCOM_framebuffer_foveated(GLADloadproc load) {\n\tif(!GLAD_GL_QCOM_framebuffer_foveated) return;\n\tglad_glFramebufferFoveationConfigQCOM = (PFNGLFRAMEBUFFERFOVEATIONCONFIGQCOMPROC)load(\"glFramebufferFoveationConfigQCOM\");\n\tglad_glFramebufferFoveationParametersQCOM = (PFNGLFRAMEBUFFERFOVEATIONPARAMETERSQCOMPROC)load(\"glFramebufferFoveationParametersQCOM\");\n}\nstatic void load_GL_QCOM_shader_framebuffer_fetch_noncoherent(GLADloadproc load) {\n\tif(!GLAD_GL_QCOM_shader_framebuffer_fetch_noncoherent) return;\n\tglad_glFramebufferFetchBarrierQCOM = (PFNGLFRAMEBUFFERFETCHBARRIERQCOMPROC)load(\"glFramebufferFetchBarrierQCOM\");\n}\nstatic void load_GL_QCOM_texture_foveated(GLADloadproc load) {\n\tif(!GLAD_GL_QCOM_texture_foveated) return;\n\tglad_glTextureFoveationParametersQCOM = (PFNGLTEXTUREFOVEATIONPARAMETERSQCOMPROC)load(\"glTextureFoveationParametersQCOM\");\n}\nstatic void load_GL_QCOM_tiled_rendering(GLADloadproc load) {\n\tif(!GLAD_GL_QCOM_tiled_rendering) return;\n\tglad_glStartTilingQCOM = (PFNGLSTARTTILINGQCOMPROC)load(\"glStartTilingQCOM\");\n\tglad_glEndTilingQCOM = (PFNGLENDTILINGQCOMPROC)load(\"glEndTilingQCOM\");\n}\nstatic int find_extensionsGLES2(void) {\n\tif (!get_exts()) return 0;\n\tGLAD_GL_AMD_compressed_3DC_texture = has_ext(\"GL_AMD_compressed_3DC_texture\");\n\tGLAD_GL_AMD_compressed_ATC_texture = has_ext(\"GL_AMD_compressed_ATC_texture\");\n\tGLAD_GL_AMD_framebuffer_multisample_advanced = has_ext(\"GL_AMD_framebuffer_multisample_advanced\");\n\tGLAD_GL_AMD_performance_monitor = has_ext(\"GL_AMD_performance_monitor\");\n\tGLAD_GL_AMD_program_binary_Z400 = has_ext(\"GL_AMD_program_binary_Z400\");\n\tGLAD_GL_ANDROID_extension_pack_es31a = has_ext(\"GL_ANDROID_extension_pack_es31a\");\n\tGLAD_GL_ANGLE_depth_texture = has_ext(\"GL_ANGLE_depth_texture\");\n\tGLAD_GL_ANGLE_framebuffer_blit = has_ext(\"GL_ANGLE_framebuffer_blit\");\n\tGLAD_GL_ANGLE_framebuffer_multisample = has_ext(\"GL_ANGLE_framebuffer_multisample\");\n\tGLAD_GL_ANGLE_instanced_arrays = has_ext(\"GL_ANGLE_instanced_arrays\");\n\tGLAD_GL_ANGLE_pack_reverse_row_order = has_ext(\"GL_ANGLE_pack_reverse_row_order\");\n\tGLAD_GL_ANGLE_program_binary = has_ext(\"GL_ANGLE_program_binary\");\n\tGLAD_GL_ANGLE_texture_compression_dxt3 = has_ext(\"GL_ANGLE_texture_compression_dxt3\");\n\tGLAD_GL_ANGLE_texture_compression_dxt5 = has_ext(\"GL_ANGLE_texture_compression_dxt5\");\n\tGLAD_GL_ANGLE_texture_usage = has_ext(\"GL_ANGLE_texture_usage\");\n\tGLAD_GL_ANGLE_translated_shader_source = has_ext(\"GL_ANGLE_translated_shader_source\");\n\tGLAD_GL_APPLE_clip_distance = has_ext(\"GL_APPLE_clip_distance\");\n\tGLAD_GL_APPLE_color_buffer_packed_float = has_ext(\"GL_APPLE_color_buffer_packed_float\");\n\tGLAD_GL_APPLE_copy_texture_levels = has_ext(\"GL_APPLE_copy_texture_levels\");\n\tGLAD_GL_APPLE_framebuffer_multisample = has_ext(\"GL_APPLE_framebuffer_multisample\");\n\tGLAD_GL_APPLE_rgb_422 = has_ext(\"GL_APPLE_rgb_422\");\n\tGLAD_GL_APPLE_sync = has_ext(\"GL_APPLE_sync\");\n\tGLAD_GL_APPLE_texture_format_BGRA8888 = has_ext(\"GL_APPLE_texture_format_BGRA8888\");\n\tGLAD_GL_APPLE_texture_max_level = has_ext(\"GL_APPLE_texture_max_level\");\n\tGLAD_GL_APPLE_texture_packed_float = has_ext(\"GL_APPLE_texture_packed_float\");\n\tGLAD_GL_ARM_mali_program_binary = has_ext(\"GL_ARM_mali_program_binary\");\n\tGLAD_GL_ARM_mali_shader_binary = has_ext(\"GL_ARM_mali_shader_binary\");\n\tGLAD_GL_ARM_rgba8 = has_ext(\"GL_ARM_rgba8\");\n\tGLAD_GL_ARM_shader_framebuffer_fetch = has_ext(\"GL_ARM_shader_framebuffer_fetch\");\n\tGLAD_GL_ARM_shader_framebuffer_fetch_depth_stencil = has_ext(\"GL_ARM_shader_framebuffer_fetch_depth_stencil\");\n\tGLAD_GL_DMP_program_binary = has_ext(\"GL_DMP_program_binary\");\n\tGLAD_GL_DMP_shader_binary = has_ext(\"GL_DMP_shader_binary\");\n\tGLAD_GL_EXT_EGL_image_array = has_ext(\"GL_EXT_EGL_image_array\");\n\tGLAD_GL_EXT_EGL_image_storage = has_ext(\"GL_EXT_EGL_image_storage\");\n\tGLAD_GL_EXT_YUV_target = has_ext(\"GL_EXT_YUV_target\");\n\tGLAD_GL_EXT_base_instance = has_ext(\"GL_EXT_base_instance\");\n\tGLAD_GL_EXT_blend_func_extended = has_ext(\"GL_EXT_blend_func_extended\");\n\tGLAD_GL_EXT_blend_minmax = has_ext(\"GL_EXT_blend_minmax\");\n\tGLAD_GL_EXT_buffer_storage = has_ext(\"GL_EXT_buffer_storage\");\n\tGLAD_GL_EXT_clear_texture = has_ext(\"GL_EXT_clear_texture\");\n\tGLAD_GL_EXT_clip_control = has_ext(\"GL_EXT_clip_control\");\n\tGLAD_GL_EXT_clip_cull_distance = has_ext(\"GL_EXT_clip_cull_distance\");\n\tGLAD_GL_EXT_color_buffer_float = has_ext(\"GL_EXT_color_buffer_float\");\n\tGLAD_GL_EXT_color_buffer_half_float = has_ext(\"GL_EXT_color_buffer_half_float\");\n\tGLAD_GL_EXT_conservative_depth = has_ext(\"GL_EXT_conservative_depth\");\n\tGLAD_GL_EXT_copy_image = has_ext(\"GL_EXT_copy_image\");\n\tGLAD_GL_EXT_debug_label = has_ext(\"GL_EXT_debug_label\");\n\tGLAD_GL_EXT_debug_marker = has_ext(\"GL_EXT_debug_marker\");\n\tGLAD_GL_EXT_discard_framebuffer = has_ext(\"GL_EXT_discard_framebuffer\");\n\tGLAD_GL_EXT_disjoint_timer_query = has_ext(\"GL_EXT_disjoint_timer_query\");\n\tGLAD_GL_EXT_draw_buffers = has_ext(\"GL_EXT_draw_buffers\");\n\tGLAD_GL_EXT_draw_buffers_indexed = has_ext(\"GL_EXT_draw_buffers_indexed\");\n\tGLAD_GL_EXT_draw_elements_base_vertex = has_ext(\"GL_EXT_draw_elements_base_vertex\");\n\tGLAD_GL_EXT_draw_instanced = has_ext(\"GL_EXT_draw_instanced\");\n\tGLAD_GL_EXT_draw_transform_feedback = has_ext(\"GL_EXT_draw_transform_feedback\");\n\tGLAD_GL_EXT_external_buffer = has_ext(\"GL_EXT_external_buffer\");\n\tGLAD_GL_EXT_float_blend = has_ext(\"GL_EXT_float_blend\");\n\tGLAD_GL_EXT_geometry_point_size = has_ext(\"GL_EXT_geometry_point_size\");\n\tGLAD_GL_EXT_geometry_shader = has_ext(\"GL_EXT_geometry_shader\");\n\tGLAD_GL_EXT_gpu_shader5 = has_ext(\"GL_EXT_gpu_shader5\");\n\tGLAD_GL_EXT_instanced_arrays = has_ext(\"GL_EXT_instanced_arrays\");\n\tGLAD_GL_EXT_map_buffer_range = has_ext(\"GL_EXT_map_buffer_range\");\n\tGLAD_GL_EXT_memory_object = has_ext(\"GL_EXT_memory_object\");\n\tGLAD_GL_EXT_memory_object_fd = has_ext(\"GL_EXT_memory_object_fd\");\n\tGLAD_GL_EXT_memory_object_win32 = has_ext(\"GL_EXT_memory_object_win32\");\n\tGLAD_GL_EXT_multi_draw_arrays = has_ext(\"GL_EXT_multi_draw_arrays\");\n\tGLAD_GL_EXT_multi_draw_indirect = has_ext(\"GL_EXT_multi_draw_indirect\");\n\tGLAD_GL_EXT_multisampled_compatibility = has_ext(\"GL_EXT_multisampled_compatibility\");\n\tGLAD_GL_EXT_multisampled_render_to_texture = has_ext(\"GL_EXT_multisampled_render_to_texture\");\n\tGLAD_GL_EXT_multiview_draw_buffers = has_ext(\"GL_EXT_multiview_draw_buffers\");\n\tGLAD_GL_EXT_occlusion_query_boolean = has_ext(\"GL_EXT_occlusion_query_boolean\");\n\tGLAD_GL_EXT_polygon_offset_clamp = has_ext(\"GL_EXT_polygon_offset_clamp\");\n\tGLAD_GL_EXT_post_depth_coverage = has_ext(\"GL_EXT_post_depth_coverage\");\n\tGLAD_GL_EXT_primitive_bounding_box = has_ext(\"GL_EXT_primitive_bounding_box\");\n\tGLAD_GL_EXT_protected_textures = has_ext(\"GL_EXT_protected_textures\");\n\tGLAD_GL_EXT_pvrtc_sRGB = has_ext(\"GL_EXT_pvrtc_sRGB\");\n\tGLAD_GL_EXT_raster_multisample = has_ext(\"GL_EXT_raster_multisample\");\n\tGLAD_GL_EXT_read_format_bgra = has_ext(\"GL_EXT_read_format_bgra\");\n\tGLAD_GL_EXT_render_snorm = has_ext(\"GL_EXT_render_snorm\");\n\tGLAD_GL_EXT_robustness = has_ext(\"GL_EXT_robustness\");\n\tGLAD_GL_EXT_sRGB = has_ext(\"GL_EXT_sRGB\");\n\tGLAD_GL_EXT_sRGB_write_control = has_ext(\"GL_EXT_sRGB_write_control\");\n\tGLAD_GL_EXT_semaphore = has_ext(\"GL_EXT_semaphore\");\n\tGLAD_GL_EXT_semaphore_fd = has_ext(\"GL_EXT_semaphore_fd\");\n\tGLAD_GL_EXT_semaphore_win32 = has_ext(\"GL_EXT_semaphore_win32\");\n\tGLAD_GL_EXT_separate_shader_objects = has_ext(\"GL_EXT_separate_shader_objects\");\n\tGLAD_GL_EXT_shader_framebuffer_fetch = has_ext(\"GL_EXT_shader_framebuffer_fetch\");\n\tGLAD_GL_EXT_shader_framebuffer_fetch_non_coherent = has_ext(\"GL_EXT_shader_framebuffer_fetch_non_coherent\");\n\tGLAD_GL_EXT_shader_group_vote = has_ext(\"GL_EXT_shader_group_vote\");\n\tGLAD_GL_EXT_shader_implicit_conversions = has_ext(\"GL_EXT_shader_implicit_conversions\");\n\tGLAD_GL_EXT_shader_integer_mix = has_ext(\"GL_EXT_shader_integer_mix\");\n\tGLAD_GL_EXT_shader_io_blocks = has_ext(\"GL_EXT_shader_io_blocks\");\n\tGLAD_GL_EXT_shader_non_constant_global_initializers = has_ext(\"GL_EXT_shader_non_constant_global_initializers\");\n\tGLAD_GL_EXT_shader_pixel_local_storage = has_ext(\"GL_EXT_shader_pixel_local_storage\");\n\tGLAD_GL_EXT_shader_pixel_local_storage2 = has_ext(\"GL_EXT_shader_pixel_local_storage2\");\n\tGLAD_GL_EXT_shader_texture_lod = has_ext(\"GL_EXT_shader_texture_lod\");\n\tGLAD_GL_EXT_shadow_samplers = has_ext(\"GL_EXT_shadow_samplers\");\n\tGLAD_GL_EXT_sparse_texture = has_ext(\"GL_EXT_sparse_texture\");\n\tGLAD_GL_EXT_sparse_texture2 = has_ext(\"GL_EXT_sparse_texture2\");\n\tGLAD_GL_EXT_tessellation_point_size = has_ext(\"GL_EXT_tessellation_point_size\");\n\tGLAD_GL_EXT_tessellation_shader = has_ext(\"GL_EXT_tessellation_shader\");\n\tGLAD_GL_EXT_texture_border_clamp = has_ext(\"GL_EXT_texture_border_clamp\");\n\tGLAD_GL_EXT_texture_buffer = has_ext(\"GL_EXT_texture_buffer\");\n\tGLAD_GL_EXT_texture_compression_astc_decode_mode = has_ext(\"GL_EXT_texture_compression_astc_decode_mode\");\n\tGLAD_GL_EXT_texture_compression_bptc = has_ext(\"GL_EXT_texture_compression_bptc\");\n\tGLAD_GL_EXT_texture_compression_dxt1 = has_ext(\"GL_EXT_texture_compression_dxt1\");\n\tGLAD_GL_EXT_texture_compression_rgtc = has_ext(\"GL_EXT_texture_compression_rgtc\");\n\tGLAD_GL_EXT_texture_compression_s3tc = has_ext(\"GL_EXT_texture_compression_s3tc\");\n\tGLAD_GL_EXT_texture_compression_s3tc_srgb = has_ext(\"GL_EXT_texture_compression_s3tc_srgb\");\n\tGLAD_GL_EXT_texture_cube_map_array = has_ext(\"GL_EXT_texture_cube_map_array\");\n\tGLAD_GL_EXT_texture_filter_anisotropic = has_ext(\"GL_EXT_texture_filter_anisotropic\");\n\tGLAD_GL_EXT_texture_filter_minmax = has_ext(\"GL_EXT_texture_filter_minmax\");\n\tGLAD_GL_EXT_texture_format_BGRA8888 = has_ext(\"GL_EXT_texture_format_BGRA8888\");\n\tGLAD_GL_EXT_texture_format_sRGB_override = has_ext(\"GL_EXT_texture_format_sRGB_override\");\n\tGLAD_GL_EXT_texture_mirror_clamp_to_edge = has_ext(\"GL_EXT_texture_mirror_clamp_to_edge\");\n\tGLAD_GL_EXT_texture_norm16 = has_ext(\"GL_EXT_texture_norm16\");\n\tGLAD_GL_EXT_texture_rg = has_ext(\"GL_EXT_texture_rg\");\n\tGLAD_GL_EXT_texture_sRGB_R8 = has_ext(\"GL_EXT_texture_sRGB_R8\");\n\tGLAD_GL_EXT_texture_sRGB_RG8 = has_ext(\"GL_EXT_texture_sRGB_RG8\");\n\tGLAD_GL_EXT_texture_sRGB_decode = has_ext(\"GL_EXT_texture_sRGB_decode\");\n\tGLAD_GL_EXT_texture_storage = has_ext(\"GL_EXT_texture_storage\");\n\tGLAD_GL_EXT_texture_type_2_10_10_10_REV = has_ext(\"GL_EXT_texture_type_2_10_10_10_REV\");\n\tGLAD_GL_EXT_texture_view = has_ext(\"GL_EXT_texture_view\");\n\tGLAD_GL_EXT_unpack_subimage = has_ext(\"GL_EXT_unpack_subimage\");\n\tGLAD_GL_EXT_win32_keyed_mutex = has_ext(\"GL_EXT_win32_keyed_mutex\");\n\tGLAD_GL_EXT_window_rectangles = has_ext(\"GL_EXT_window_rectangles\");\n\tGLAD_GL_FJ_shader_binary_GCCSO = has_ext(\"GL_FJ_shader_binary_GCCSO\");\n\tGLAD_GL_IMG_bindless_texture = has_ext(\"GL_IMG_bindless_texture\");\n\tGLAD_GL_IMG_framebuffer_downsample = has_ext(\"GL_IMG_framebuffer_downsample\");\n\tGLAD_GL_IMG_multisampled_render_to_texture = has_ext(\"GL_IMG_multisampled_render_to_texture\");\n\tGLAD_GL_IMG_program_binary = has_ext(\"GL_IMG_program_binary\");\n\tGLAD_GL_IMG_read_format = has_ext(\"GL_IMG_read_format\");\n\tGLAD_GL_IMG_shader_binary = has_ext(\"GL_IMG_shader_binary\");\n\tGLAD_GL_IMG_texture_compression_pvrtc = has_ext(\"GL_IMG_texture_compression_pvrtc\");\n\tGLAD_GL_IMG_texture_compression_pvrtc2 = has_ext(\"GL_IMG_texture_compression_pvrtc2\");\n\tGLAD_GL_IMG_texture_filter_cubic = has_ext(\"GL_IMG_texture_filter_cubic\");\n\tGLAD_GL_INTEL_blackhole_render = has_ext(\"GL_INTEL_blackhole_render\");\n\tGLAD_GL_INTEL_conservative_rasterization = has_ext(\"GL_INTEL_conservative_rasterization\");\n\tGLAD_GL_INTEL_framebuffer_CMAA = has_ext(\"GL_INTEL_framebuffer_CMAA\");\n\tGLAD_GL_INTEL_performance_query = has_ext(\"GL_INTEL_performance_query\");\n\tGLAD_GL_KHR_blend_equation_advanced = has_ext(\"GL_KHR_blend_equation_advanced\");\n\tGLAD_GL_KHR_blend_equation_advanced_coherent = has_ext(\"GL_KHR_blend_equation_advanced_coherent\");\n\tGLAD_GL_KHR_context_flush_control = has_ext(\"GL_KHR_context_flush_control\");\n\tGLAD_GL_KHR_debug = has_ext(\"GL_KHR_debug\");\n\tGLAD_GL_KHR_no_error = has_ext(\"GL_KHR_no_error\");\n\tGLAD_GL_KHR_parallel_shader_compile = has_ext(\"GL_KHR_parallel_shader_compile\");\n\tGLAD_GL_KHR_robust_buffer_access_behavior = has_ext(\"GL_KHR_robust_buffer_access_behavior\");\n\tGLAD_GL_KHR_robustness = has_ext(\"GL_KHR_robustness\");\n\tGLAD_GL_KHR_texture_compression_astc_hdr = has_ext(\"GL_KHR_texture_compression_astc_hdr\");\n\tGLAD_GL_KHR_texture_compression_astc_ldr = has_ext(\"GL_KHR_texture_compression_astc_ldr\");\n\tGLAD_GL_KHR_texture_compression_astc_sliced_3d = has_ext(\"GL_KHR_texture_compression_astc_sliced_3d\");\n\tGLAD_GL_MESA_framebuffer_flip_y = has_ext(\"GL_MESA_framebuffer_flip_y\");\n\tGLAD_GL_MESA_program_binary_formats = has_ext(\"GL_MESA_program_binary_formats\");\n\tGLAD_GL_MESA_shader_integer_functions = has_ext(\"GL_MESA_shader_integer_functions\");\n\tGLAD_GL_NVX_blend_equation_advanced_multi_draw_buffers = has_ext(\"GL_NVX_blend_equation_advanced_multi_draw_buffers\");\n\tGLAD_GL_NV_bindless_texture = has_ext(\"GL_NV_bindless_texture\");\n\tGLAD_GL_NV_blend_equation_advanced = has_ext(\"GL_NV_blend_equation_advanced\");\n\tGLAD_GL_NV_blend_equation_advanced_coherent = has_ext(\"GL_NV_blend_equation_advanced_coherent\");\n\tGLAD_GL_NV_blend_minmax_factor = has_ext(\"GL_NV_blend_minmax_factor\");\n\tGLAD_GL_NV_clip_space_w_scaling = has_ext(\"GL_NV_clip_space_w_scaling\");\n\tGLAD_GL_NV_conditional_render = has_ext(\"GL_NV_conditional_render\");\n\tGLAD_GL_NV_conservative_raster = has_ext(\"GL_NV_conservative_raster\");\n\tGLAD_GL_NV_conservative_raster_pre_snap = has_ext(\"GL_NV_conservative_raster_pre_snap\");\n\tGLAD_GL_NV_conservative_raster_pre_snap_triangles = has_ext(\"GL_NV_conservative_raster_pre_snap_triangles\");\n\tGLAD_GL_NV_copy_buffer = has_ext(\"GL_NV_copy_buffer\");\n\tGLAD_GL_NV_coverage_sample = has_ext(\"GL_NV_coverage_sample\");\n\tGLAD_GL_NV_depth_nonlinear = has_ext(\"GL_NV_depth_nonlinear\");\n\tGLAD_GL_NV_draw_buffers = has_ext(\"GL_NV_draw_buffers\");\n\tGLAD_GL_NV_draw_instanced = has_ext(\"GL_NV_draw_instanced\");\n\tGLAD_GL_NV_draw_vulkan_image = has_ext(\"GL_NV_draw_vulkan_image\");\n\tGLAD_GL_NV_explicit_attrib_location = has_ext(\"GL_NV_explicit_attrib_location\");\n\tGLAD_GL_NV_fbo_color_attachments = has_ext(\"GL_NV_fbo_color_attachments\");\n\tGLAD_GL_NV_fence = has_ext(\"GL_NV_fence\");\n\tGLAD_GL_NV_fill_rectangle = has_ext(\"GL_NV_fill_rectangle\");\n\tGLAD_GL_NV_fragment_coverage_to_color = has_ext(\"GL_NV_fragment_coverage_to_color\");\n\tGLAD_GL_NV_fragment_shader_interlock = has_ext(\"GL_NV_fragment_shader_interlock\");\n\tGLAD_GL_NV_framebuffer_blit = has_ext(\"GL_NV_framebuffer_blit\");\n\tGLAD_GL_NV_framebuffer_mixed_samples = has_ext(\"GL_NV_framebuffer_mixed_samples\");\n\tGLAD_GL_NV_framebuffer_multisample = has_ext(\"GL_NV_framebuffer_multisample\");\n\tGLAD_GL_NV_generate_mipmap_sRGB = has_ext(\"GL_NV_generate_mipmap_sRGB\");\n\tGLAD_GL_NV_geometry_shader_passthrough = has_ext(\"GL_NV_geometry_shader_passthrough\");\n\tGLAD_GL_NV_gpu_shader5 = has_ext(\"GL_NV_gpu_shader5\");\n\tGLAD_GL_NV_image_formats = has_ext(\"GL_NV_image_formats\");\n\tGLAD_GL_NV_instanced_arrays = has_ext(\"GL_NV_instanced_arrays\");\n\tGLAD_GL_NV_internalformat_sample_query = has_ext(\"GL_NV_internalformat_sample_query\");\n\tGLAD_GL_NV_memory_attachment = has_ext(\"GL_NV_memory_attachment\");\n\tGLAD_GL_NV_non_square_matrices = has_ext(\"GL_NV_non_square_matrices\");\n\tGLAD_GL_NV_path_rendering = has_ext(\"GL_NV_path_rendering\");\n\tGLAD_GL_NV_path_rendering_shared_edge = has_ext(\"GL_NV_path_rendering_shared_edge\");\n\tGLAD_GL_NV_pixel_buffer_object = has_ext(\"GL_NV_pixel_buffer_object\");\n\tGLAD_GL_NV_polygon_mode = has_ext(\"GL_NV_polygon_mode\");\n\tGLAD_GL_NV_read_buffer = has_ext(\"GL_NV_read_buffer\");\n\tGLAD_GL_NV_read_buffer_front = has_ext(\"GL_NV_read_buffer_front\");\n\tGLAD_GL_NV_read_depth = has_ext(\"GL_NV_read_depth\");\n\tGLAD_GL_NV_read_depth_stencil = has_ext(\"GL_NV_read_depth_stencil\");\n\tGLAD_GL_NV_read_stencil = has_ext(\"GL_NV_read_stencil\");\n\tGLAD_GL_NV_sRGB_formats = has_ext(\"GL_NV_sRGB_formats\");\n\tGLAD_GL_NV_sample_locations = has_ext(\"GL_NV_sample_locations\");\n\tGLAD_GL_NV_sample_mask_override_coverage = has_ext(\"GL_NV_sample_mask_override_coverage\");\n\tGLAD_GL_NV_shader_atomic_fp16_vector = has_ext(\"GL_NV_shader_atomic_fp16_vector\");\n\tGLAD_GL_NV_shader_noperspective_interpolation = has_ext(\"GL_NV_shader_noperspective_interpolation\");\n\tGLAD_GL_NV_shadow_samplers_array = has_ext(\"GL_NV_shadow_samplers_array\");\n\tGLAD_GL_NV_shadow_samplers_cube = has_ext(\"GL_NV_shadow_samplers_cube\");\n\tGLAD_GL_NV_stereo_view_rendering = has_ext(\"GL_NV_stereo_view_rendering\");\n\tGLAD_GL_NV_texture_border_clamp = has_ext(\"GL_NV_texture_border_clamp\");\n\tGLAD_GL_NV_texture_compression_s3tc_update = has_ext(\"GL_NV_texture_compression_s3tc_update\");\n\tGLAD_GL_NV_texture_npot_2D_mipmap = has_ext(\"GL_NV_texture_npot_2D_mipmap\");\n\tGLAD_GL_NV_viewport_array = has_ext(\"GL_NV_viewport_array\");\n\tGLAD_GL_NV_viewport_array2 = has_ext(\"GL_NV_viewport_array2\");\n\tGLAD_GL_NV_viewport_swizzle = has_ext(\"GL_NV_viewport_swizzle\");\n\tGLAD_GL_OES_EGL_image = has_ext(\"GL_OES_EGL_image\");\n\tGLAD_GL_OES_EGL_image_external = has_ext(\"GL_OES_EGL_image_external\");\n\tGLAD_GL_OES_EGL_image_external_essl3 = has_ext(\"GL_OES_EGL_image_external_essl3\");\n\tGLAD_GL_OES_compressed_ETC1_RGB8_sub_texture = has_ext(\"GL_OES_compressed_ETC1_RGB8_sub_texture\");\n\tGLAD_GL_OES_compressed_ETC1_RGB8_texture = has_ext(\"GL_OES_compressed_ETC1_RGB8_texture\");\n\tGLAD_GL_OES_compressed_paletted_texture = has_ext(\"GL_OES_compressed_paletted_texture\");\n\tGLAD_GL_OES_copy_image = has_ext(\"GL_OES_copy_image\");\n\tGLAD_GL_OES_depth24 = has_ext(\"GL_OES_depth24\");\n\tGLAD_GL_OES_depth32 = has_ext(\"GL_OES_depth32\");\n\tGLAD_GL_OES_depth_texture = has_ext(\"GL_OES_depth_texture\");\n\tGLAD_GL_OES_draw_buffers_indexed = has_ext(\"GL_OES_draw_buffers_indexed\");\n\tGLAD_GL_OES_draw_elements_base_vertex = has_ext(\"GL_OES_draw_elements_base_vertex\");\n\tGLAD_GL_OES_element_index_uint = has_ext(\"GL_OES_element_index_uint\");\n\tGLAD_GL_OES_fbo_render_mipmap = has_ext(\"GL_OES_fbo_render_mipmap\");\n\tGLAD_GL_OES_fragment_precision_high = has_ext(\"GL_OES_fragment_precision_high\");\n\tGLAD_GL_OES_geometry_point_size = has_ext(\"GL_OES_geometry_point_size\");\n\tGLAD_GL_OES_geometry_shader = has_ext(\"GL_OES_geometry_shader\");\n\tGLAD_GL_OES_get_program_binary = has_ext(\"GL_OES_get_program_binary\");\n\tGLAD_GL_OES_gpu_shader5 = has_ext(\"GL_OES_gpu_shader5\");\n\tGLAD_GL_OES_mapbuffer = has_ext(\"GL_OES_mapbuffer\");\n\tGLAD_GL_OES_packed_depth_stencil = has_ext(\"GL_OES_packed_depth_stencil\");\n\tGLAD_GL_OES_primitive_bounding_box = has_ext(\"GL_OES_primitive_bounding_box\");\n\tGLAD_GL_OES_required_internalformat = has_ext(\"GL_OES_required_internalformat\");\n\tGLAD_GL_OES_rgb8_rgba8 = has_ext(\"GL_OES_rgb8_rgba8\");\n\tGLAD_GL_OES_sample_shading = has_ext(\"GL_OES_sample_shading\");\n\tGLAD_GL_OES_sample_variables = has_ext(\"GL_OES_sample_variables\");\n\tGLAD_GL_OES_shader_image_atomic = has_ext(\"GL_OES_shader_image_atomic\");\n\tGLAD_GL_OES_shader_io_blocks = has_ext(\"GL_OES_shader_io_blocks\");\n\tGLAD_GL_OES_shader_multisample_interpolation = has_ext(\"GL_OES_shader_multisample_interpolation\");\n\tGLAD_GL_OES_standard_derivatives = has_ext(\"GL_OES_standard_derivatives\");\n\tGLAD_GL_OES_stencil1 = has_ext(\"GL_OES_stencil1\");\n\tGLAD_GL_OES_stencil4 = has_ext(\"GL_OES_stencil4\");\n\tGLAD_GL_OES_surfaceless_context = has_ext(\"GL_OES_surfaceless_context\");\n\tGLAD_GL_OES_tessellation_point_size = has_ext(\"GL_OES_tessellation_point_size\");\n\tGLAD_GL_OES_tessellation_shader = has_ext(\"GL_OES_tessellation_shader\");\n\tGLAD_GL_OES_texture_3D = has_ext(\"GL_OES_texture_3D\");\n\tGLAD_GL_OES_texture_border_clamp = has_ext(\"GL_OES_texture_border_clamp\");\n\tGLAD_GL_OES_texture_buffer = has_ext(\"GL_OES_texture_buffer\");\n\tGLAD_GL_OES_texture_compression_astc = has_ext(\"GL_OES_texture_compression_astc\");\n\tGLAD_GL_OES_texture_cube_map_array = has_ext(\"GL_OES_texture_cube_map_array\");\n\tGLAD_GL_OES_texture_float = has_ext(\"GL_OES_texture_float\");\n\tGLAD_GL_OES_texture_float_linear = has_ext(\"GL_OES_texture_float_linear\");\n\tGLAD_GL_OES_texture_half_float = has_ext(\"GL_OES_texture_half_float\");\n\tGLAD_GL_OES_texture_half_float_linear = has_ext(\"GL_OES_texture_half_float_linear\");\n\tGLAD_GL_OES_texture_npot = has_ext(\"GL_OES_texture_npot\");\n\tGLAD_GL_OES_texture_stencil8 = has_ext(\"GL_OES_texture_stencil8\");\n\tGLAD_GL_OES_texture_storage_multisample_2d_array = has_ext(\"GL_OES_texture_storage_multisample_2d_array\");\n\tGLAD_GL_OES_texture_view = has_ext(\"GL_OES_texture_view\");\n\tGLAD_GL_OES_vertex_array_object = has_ext(\"GL_OES_vertex_array_object\");\n\tGLAD_GL_OES_vertex_half_float = has_ext(\"GL_OES_vertex_half_float\");\n\tGLAD_GL_OES_vertex_type_10_10_10_2 = has_ext(\"GL_OES_vertex_type_10_10_10_2\");\n\tGLAD_GL_OES_viewport_array = has_ext(\"GL_OES_viewport_array\");\n\tGLAD_GL_OVR_multiview = has_ext(\"GL_OVR_multiview\");\n\tGLAD_GL_OVR_multiview2 = has_ext(\"GL_OVR_multiview2\");\n\tGLAD_GL_OVR_multiview_multisampled_render_to_texture = has_ext(\"GL_OVR_multiview_multisampled_render_to_texture\");\n\tGLAD_GL_QCOM_alpha_test = has_ext(\"GL_QCOM_alpha_test\");\n\tGLAD_GL_QCOM_binning_control = has_ext(\"GL_QCOM_binning_control\");\n\tGLAD_GL_QCOM_driver_control = has_ext(\"GL_QCOM_driver_control\");\n\tGLAD_GL_QCOM_extended_get = has_ext(\"GL_QCOM_extended_get\");\n\tGLAD_GL_QCOM_extended_get2 = has_ext(\"GL_QCOM_extended_get2\");\n\tGLAD_GL_QCOM_framebuffer_foveated = has_ext(\"GL_QCOM_framebuffer_foveated\");\n\tGLAD_GL_QCOM_perfmon_global_mode = has_ext(\"GL_QCOM_perfmon_global_mode\");\n\tGLAD_GL_QCOM_shader_framebuffer_fetch_noncoherent = has_ext(\"GL_QCOM_shader_framebuffer_fetch_noncoherent\");\n\tGLAD_GL_QCOM_shader_framebuffer_fetch_rate = has_ext(\"GL_QCOM_shader_framebuffer_fetch_rate\");\n\tGLAD_GL_QCOM_texture_foveated = has_ext(\"GL_QCOM_texture_foveated\");\n\tGLAD_GL_QCOM_texture_foveated_subsampled_layout = has_ext(\"GL_QCOM_texture_foveated_subsampled_layout\");\n\tGLAD_GL_QCOM_tiled_rendering = has_ext(\"GL_QCOM_tiled_rendering\");\n\tGLAD_GL_QCOM_writeonly_rendering = has_ext(\"GL_QCOM_writeonly_rendering\");\n\tGLAD_GL_VIV_shader_binary = has_ext(\"GL_VIV_shader_binary\");\n\tfree_exts();\n\treturn 1;\n}\n\nstatic void find_coreGLES2(void) {\n\n    /* Thank you @elmindreda\n     * https://github.com/elmindreda/greg/blob/master/templates/greg.c.in#L176\n     * https://github.com/glfw/glfw/blob/master/src/context.c#L36\n     */\n    int i, major, minor;\n\n    const char* version;\n    const char* prefixes[] = {\n        \"OpenGL ES-CM \",\n        \"OpenGL ES-CL \",\n        \"OpenGL ES \",\n        NULL\n    };\n\n    version = (const char*) glGetString(GL_VERSION);\n    if (!version) return;\n\n    for (i = 0;  prefixes[i];  i++) {\n        const size_t length = strlen(prefixes[i]);\n        if (strncmp(version, prefixes[i], length) == 0) {\n            version += length;\n            break;\n        }\n    }\n\n/* PR #18 */\n#ifdef _MSC_VER\n    sscanf_s(version, \"%d.%d\", &major, &minor);\n#else\n    sscanf(version, \"%d.%d\", &major, &minor);\n#endif\n\n    GLVersion.major = major; GLVersion.minor = minor;\n    max_loaded_major = major; max_loaded_minor = minor;\n\tGLAD_GL_ES_VERSION_2_0 = (major == 2 && minor >= 0) || major > 2;\n\tGLAD_GL_ES_VERSION_3_0 = (major == 3 && minor >= 0) || major > 3;\n\tif (GLVersion.major > 3 || (GLVersion.major >= 3 && GLVersion.minor >= 0)) {\n\t\tmax_loaded_major = 3;\n\t\tmax_loaded_minor = 0;\n\t}\n}\n\nint gladLoadGLES2Loader(GLADloadproc load) {\n\tGLVersion.major = 0; GLVersion.minor = 0;\n\tglGetString = (PFNGLGETSTRINGPROC)load(\"glGetString\");\n\tif(glGetString == NULL) return 0;\n\tif(glGetString(GL_VERSION) == NULL) return 0;\n\tfind_coreGLES2();\n\tload_GL_ES_VERSION_2_0(load);\n\tload_GL_ES_VERSION_3_0(load);\n\n\tif (!find_extensionsGLES2()) return 0;\n\tload_GL_AMD_framebuffer_multisample_advanced(load);\n\tload_GL_AMD_performance_monitor(load);\n\tload_GL_ANGLE_framebuffer_blit(load);\n\tload_GL_ANGLE_framebuffer_multisample(load);\n\tload_GL_ANGLE_instanced_arrays(load);\n\tload_GL_ANGLE_translated_shader_source(load);\n\tload_GL_APPLE_copy_texture_levels(load);\n\tload_GL_APPLE_framebuffer_multisample(load);\n\tload_GL_APPLE_sync(load);\n\tload_GL_EXT_EGL_image_storage(load);\n\tload_GL_EXT_base_instance(load);\n\tload_GL_EXT_blend_func_extended(load);\n\tload_GL_EXT_blend_minmax(load);\n\tload_GL_EXT_buffer_storage(load);\n\tload_GL_EXT_clear_texture(load);\n\tload_GL_EXT_clip_control(load);\n\tload_GL_EXT_copy_image(load);\n\tload_GL_EXT_debug_label(load);\n\tload_GL_EXT_debug_marker(load);\n\tload_GL_EXT_discard_framebuffer(load);\n\tload_GL_EXT_disjoint_timer_query(load);\n\tload_GL_EXT_draw_buffers(load);\n\tload_GL_EXT_draw_buffers_indexed(load);\n\tload_GL_EXT_draw_elements_base_vertex(load);\n\tload_GL_EXT_draw_instanced(load);\n\tload_GL_EXT_draw_transform_feedback(load);\n\tload_GL_EXT_external_buffer(load);\n\tload_GL_EXT_geometry_shader(load);\n\tload_GL_EXT_instanced_arrays(load);\n\tload_GL_EXT_map_buffer_range(load);\n\tload_GL_EXT_memory_object(load);\n\tload_GL_EXT_memory_object_fd(load);\n\tload_GL_EXT_memory_object_win32(load);\n\tload_GL_EXT_multi_draw_arrays(load);\n\tload_GL_EXT_multi_draw_indirect(load);\n\tload_GL_EXT_multisampled_render_to_texture(load);\n\tload_GL_EXT_multiview_draw_buffers(load);\n\tload_GL_EXT_occlusion_query_boolean(load);\n\tload_GL_EXT_polygon_offset_clamp(load);\n\tload_GL_EXT_primitive_bounding_box(load);\n\tload_GL_EXT_raster_multisample(load);\n\tload_GL_EXT_robustness(load);\n\tload_GL_EXT_semaphore(load);\n\tload_GL_EXT_semaphore_fd(load);\n\tload_GL_EXT_semaphore_win32(load);\n\tload_GL_EXT_separate_shader_objects(load);\n\tload_GL_EXT_shader_framebuffer_fetch_non_coherent(load);\n\tload_GL_EXT_shader_pixel_local_storage2(load);\n\tload_GL_EXT_sparse_texture(load);\n\tload_GL_EXT_tessellation_shader(load);\n\tload_GL_EXT_texture_border_clamp(load);\n\tload_GL_EXT_texture_buffer(load);\n\tload_GL_EXT_texture_storage(load);\n\tload_GL_EXT_texture_view(load);\n\tload_GL_EXT_win32_keyed_mutex(load);\n\tload_GL_EXT_window_rectangles(load);\n\tload_GL_IMG_bindless_texture(load);\n\tload_GL_IMG_framebuffer_downsample(load);\n\tload_GL_IMG_multisampled_render_to_texture(load);\n\tload_GL_INTEL_framebuffer_CMAA(load);\n\tload_GL_INTEL_performance_query(load);\n\tload_GL_KHR_blend_equation_advanced(load);\n\tload_GL_KHR_debug(load);\n\tload_GL_KHR_parallel_shader_compile(load);\n\tload_GL_KHR_robustness(load);\n\tload_GL_NV_bindless_texture(load);\n\tload_GL_NV_blend_equation_advanced(load);\n\tload_GL_NV_clip_space_w_scaling(load);\n\tload_GL_NV_conditional_render(load);\n\tload_GL_NV_conservative_raster(load);\n\tload_GL_NV_conservative_raster_pre_snap_triangles(load);\n\tload_GL_NV_copy_buffer(load);\n\tload_GL_NV_coverage_sample(load);\n\tload_GL_NV_draw_buffers(load);\n\tload_GL_NV_draw_instanced(load);\n\tload_GL_NV_draw_vulkan_image(load);\n\tload_GL_NV_fence(load);\n\tload_GL_NV_fragment_coverage_to_color(load);\n\tload_GL_NV_framebuffer_blit(load);\n\tload_GL_NV_framebuffer_mixed_samples(load);\n\tload_GL_NV_framebuffer_multisample(load);\n\tload_GL_NV_gpu_shader5(load);\n\tload_GL_NV_instanced_arrays(load);\n\tload_GL_NV_internalformat_sample_query(load);\n\tload_GL_NV_memory_attachment(load);\n\tload_GL_NV_non_square_matrices(load);\n\tload_GL_NV_path_rendering(load);\n\tload_GL_NV_polygon_mode(load);\n\tload_GL_NV_read_buffer(load);\n\tload_GL_NV_sample_locations(load);\n\tload_GL_NV_viewport_array(load);\n\tload_GL_NV_viewport_swizzle(load);\n\tload_GL_OES_EGL_image(load);\n\tload_GL_OES_copy_image(load);\n\tload_GL_OES_draw_buffers_indexed(load);\n\tload_GL_OES_draw_elements_base_vertex(load);\n\tload_GL_OES_geometry_shader(load);\n\tload_GL_OES_get_program_binary(load);\n\tload_GL_OES_mapbuffer(load);\n\tload_GL_OES_primitive_bounding_box(load);\n\tload_GL_OES_sample_shading(load);\n\tload_GL_OES_tessellation_shader(load);\n\tload_GL_OES_texture_3D(load);\n\tload_GL_OES_texture_border_clamp(load);\n\tload_GL_OES_texture_buffer(load);\n\tload_GL_OES_texture_storage_multisample_2d_array(load);\n\tload_GL_OES_texture_view(load);\n\tload_GL_OES_vertex_array_object(load);\n\tload_GL_OES_viewport_array(load);\n\tload_GL_OVR_multiview(load);\n\tload_GL_OVR_multiview_multisampled_render_to_texture(load);\n\tload_GL_QCOM_alpha_test(load);\n\tload_GL_QCOM_driver_control(load);\n\tload_GL_QCOM_extended_get(load);\n\tload_GL_QCOM_extended_get2(load);\n\tload_GL_QCOM_framebuffer_foveated(load);\n\tload_GL_QCOM_shader_framebuffer_fetch_noncoherent(load);\n\tload_GL_QCOM_texture_foveated(load);\n\tload_GL_QCOM_tiled_rendering(load);\n\treturn GLVersion.major != 0 || GLVersion.minor != 0;\n}\n\n"
  },
  {
    "path": "vendor/imgui_custom/backends/imgui_impl_opengl3.cpp",
    "content": "// dear imgui: Renderer Backend for modern OpenGL with shaders / programmatic pipeline\n// - Desktop GL: 2.x 3.x 4.x\n// - Embedded GL: ES 2.0 (WebGL 1.0), ES 3.0 (WebGL 2.0)\n// This needs to be used along with a Platform Backend (e.g. GLFW, SDL, Win32, custom..)\n\n// Implemented features:\n//  [X] Renderer: User texture binding. Use 'GLuint' OpenGL texture identifier as void*/ImTextureID. Read the FAQ about ImTextureID!\n//  [X] Renderer: Large meshes support (64k+ vertices) with 16-bit indices (Desktop OpenGL only).\n//  [X] Renderer: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'.\n\n// About WebGL/ES:\n// - You need to '#define IMGUI_IMPL_OPENGL_ES2' or '#define IMGUI_IMPL_OPENGL_ES3' to use WebGL or OpenGL ES.\n// - This is done automatically on iOS, Android and Emscripten targets.\n// - For other targets, the define needs to be visible from the imgui_impl_opengl3.cpp compilation unit. If unsure, define globally or in imconfig.h.\n\n// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.\n// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.\n// Learn about Dear ImGui:\n// - FAQ                  https://dearimgui.com/faq\n// - Getting Started      https://dearimgui.com/getting-started\n// - Documentation        https://dearimgui.com/docs (same as your local docs/ folder).\n// - Introduction, links and more at the top of imgui.cpp\n\n// CHANGELOG\n// (minor and older changes stripped away, please see git history for details)\n//  2024-XX-XX: Platform: Added support for multiple windows via the ImGuiPlatformIO interface.\n//  2024-01-09: OpenGL: Update GL3W based imgui_impl_opengl3_loader.h to load \"libGL.so\" and variants, fixing regression on distros missing a symlink.\n//  2023-11-08: OpenGL: Update GL3W based imgui_impl_opengl3_loader.h to load \"libGL.so\" instead of \"libGL.so.1\", accommodating for NetBSD systems having only \"libGL.so.3\" available. (#6983)\n//  2023-10-05: OpenGL: Rename symbols in our internal loader so that LTO compilation with another copy of gl3w is possible. (#6875, #6668, #4445)\n//  2023-06-20: OpenGL: Fixed erroneous use glGetIntegerv(GL_CONTEXT_PROFILE_MASK) on contexts lower than 3.2. (#6539, #6333)\n//  2023-05-09: OpenGL: Support for glBindSampler() backup/restore on ES3. (#6375)\n//  2023-04-18: OpenGL: Restore front and back polygon mode separately when supported by context. (#6333)\n//  2023-03-23: OpenGL: Properly restoring \"no shader program bound\" if it was the case prior to running the rendering function. (#6267, #6220, #6224)\n//  2023-03-15: OpenGL: Fixed GL loader crash when GL_VERSION returns NULL. (#6154, #4445, #3530)\n//  2023-03-06: OpenGL: Fixed restoration of a potentially deleted OpenGL program, by calling glIsProgram(). (#6220, #6224)\n//  2022-11-09: OpenGL: Reverted use of glBufferSubData(), too many corruptions issues + old issues seemingly can't be reproed with Intel drivers nowadays (revert 2021-12-15 and 2022-05-23 changes).\n//  2022-10-11: Using 'nullptr' instead of 'NULL' as per our switch to C++11.\n//  2022-09-27: OpenGL: Added ability to '#define IMGUI_IMPL_OPENGL_DEBUG'.\n//  2022-05-23: OpenGL: Reworking 2021-12-15 \"Using buffer orphaning\" so it only happens on Intel GPU, seems to cause problems otherwise. (#4468, #4825, #4832, #5127).\n//  2022-05-13: OpenGL: Fixed state corruption on OpenGL ES 2.0 due to not preserving GL_ELEMENT_ARRAY_BUFFER_BINDING and vertex attribute states.\n//  2021-12-15: OpenGL: Using buffer orphaning + glBufferSubData(), seems to fix leaks with multi-viewports with some Intel HD drivers.\n//  2021-08-23: OpenGL: Fixed ES 3.0 shader (\"#version 300 es\") use normal precision floats to avoid wobbly rendering at HD resolutions.\n//  2021-08-19: OpenGL: Embed and use our own minimal GL loader (imgui_impl_opengl3_loader.h), removing requirement and support for third-party loader.\n//  2021-06-29: Reorganized backend to pull data from a single structure to facilitate usage with multiple-contexts (all g_XXXX access changed to bd->XXXX).\n//  2021-06-25: OpenGL: Use OES_vertex_array extension on Emscripten + backup/restore current state.\n//  2021-06-21: OpenGL: Destroy individual vertex/fragment shader objects right after they are linked into the main shader.\n//  2021-05-24: OpenGL: Access GL_CLIP_ORIGIN when \"GL_ARB_clip_control\" extension is detected, inside of just OpenGL 4.5 version.\n//  2021-05-19: OpenGL: Replaced direct access to ImDrawCmd::TextureId with a call to ImDrawCmd::GetTexID(). (will become a requirement)\n//  2021-04-06: OpenGL: Don't try to read GL_CLIP_ORIGIN unless we're OpenGL 4.5 or greater.\n//  2021-02-18: OpenGL: Change blending equation to preserve alpha in output buffer.\n//  2021-01-03: OpenGL: Backup, setup and restore GL_STENCIL_TEST state.\n//  2020-10-23: OpenGL: Backup, setup and restore GL_PRIMITIVE_RESTART state.\n//  2020-10-15: OpenGL: Use glGetString(GL_VERSION) instead of glGetIntegerv(GL_MAJOR_VERSION, ...) when the later returns zero (e.g. Desktop GL 2.x)\n//  2020-09-17: OpenGL: Fix to avoid compiling/calling glBindSampler() on ES or pre 3.3 context which have the defines set by a loader.\n//  2020-07-10: OpenGL: Added support for glad2 OpenGL loader.\n//  2020-05-08: OpenGL: Made default GLSL version 150 (instead of 130) on OSX.\n//  2020-04-21: OpenGL: Fixed handling of glClipControl(GL_UPPER_LEFT) by inverting projection matrix.\n//  2020-04-12: OpenGL: Fixed context version check mistakenly testing for 4.0+ instead of 3.2+ to enable ImGuiBackendFlags_RendererHasVtxOffset.\n//  2020-03-24: OpenGL: Added support for glbinding 2.x OpenGL loader.\n//  2020-01-07: OpenGL: Added support for glbinding 3.x OpenGL loader.\n//  2019-10-25: OpenGL: Using a combination of GL define and runtime GL version to decide whether to use glDrawElementsBaseVertex(). Fix building with pre-3.2 GL loaders.\n//  2019-09-22: OpenGL: Detect default GL loader using __has_include compiler facility.\n//  2019-09-16: OpenGL: Tweak initialization code to allow application calling ImGui_ImplOpenGL3_CreateFontsTexture() before the first NewFrame() call.\n//  2019-05-29: OpenGL: Desktop GL only: Added support for large mesh (64K+ vertices), enable ImGuiBackendFlags_RendererHasVtxOffset flag.\n//  2019-04-30: OpenGL: Added support for special ImDrawCallback_ResetRenderState callback to reset render state.\n//  2019-03-29: OpenGL: Not calling glBindBuffer more than necessary in the render loop.\n//  2019-03-15: OpenGL: Added a GL call + comments in ImGui_ImplOpenGL3_Init() to detect uninitialized GL function loaders early.\n//  2019-03-03: OpenGL: Fix support for ES 2.0 (WebGL 1.0).\n//  2019-02-20: OpenGL: Fix for OSX not supporting OpenGL 4.5, we don't try to read GL_CLIP_ORIGIN even if defined by the headers/loader.\n//  2019-02-11: OpenGL: Projecting clipping rectangles correctly using draw_data->FramebufferScale to allow multi-viewports for retina display.\n//  2019-02-01: OpenGL: Using GLSL 410 shaders for any version over 410 (e.g. 430, 450).\n//  2018-11-30: Misc: Setting up io.BackendRendererName so it can be displayed in the About Window.\n//  2018-11-13: OpenGL: Support for GL 4.5's glClipControl(GL_UPPER_LEFT) / GL_CLIP_ORIGIN.\n//  2018-08-29: OpenGL: Added support for more OpenGL loaders: glew and glad, with comments indicative that any loader can be used.\n//  2018-08-09: OpenGL: Default to OpenGL ES 3 on iOS and Android. GLSL version default to \"#version 300 ES\".\n//  2018-07-30: OpenGL: Support for GLSL 300 ES and 410 core. Fixes for Emscripten compilation.\n//  2018-07-10: OpenGL: Support for more GLSL versions (based on the GLSL version string). Added error output when shaders fail to compile/link.\n//  2018-06-08: Misc: Extracted imgui_impl_opengl3.cpp/.h away from the old combined GLFW/SDL+OpenGL3 examples.\n//  2018-06-08: OpenGL: Use draw_data->DisplayPos and draw_data->DisplaySize to setup projection matrix and clipping rectangle.\n//  2018-05-25: OpenGL: Removed unnecessary backup/restore of GL_ELEMENT_ARRAY_BUFFER_BINDING since this is part of the VAO state.\n//  2018-05-14: OpenGL: Making the call to glBindSampler() optional so 3.2 context won't fail if the function is a nullptr pointer.\n//  2018-03-06: OpenGL: Added const char* glsl_version parameter to ImGui_ImplOpenGL3_Init() so user can override the GLSL version e.g. \"#version 150\".\n//  2018-02-23: OpenGL: Create the VAO in the render function so the setup can more easily be used with multiple shared GL context.\n//  2018-02-16: Misc: Obsoleted the io.RenderDrawListsFn callback and exposed ImGui_ImplSdlGL3_RenderDrawData() in the .h file so you can call it yourself.\n//  2018-01-07: OpenGL: Changed GLSL shader version from 330 to 150.\n//  2017-09-01: OpenGL: Save and restore current bound sampler. Save and restore current polygon mode.\n//  2017-05-01: OpenGL: Fixed save and restore of current blend func state.\n//  2017-05-01: OpenGL: Fixed save and restore of current GL_ACTIVE_TEXTURE.\n//  2016-09-05: OpenGL: Fixed save and restore of current scissor rectangle.\n//  2016-07-29: OpenGL: Explicitly setting GL_UNPACK_ROW_LENGTH to reduce issues because SDL changes it. (#752)\n\n//----------------------------------------\n// OpenGL    GLSL      GLSL\n// version   version   string\n//----------------------------------------\n//  2.0       110       \"#version 110\"\n//  2.1       120       \"#version 120\"\n//  3.0       130       \"#version 130\"\n//  3.1       140       \"#version 140\"\n//  3.2       150       \"#version 150\"\n//  3.3       330       \"#version 330 core\"\n//  4.0       400       \"#version 400 core\"\n//  4.1       410       \"#version 410 core\"\n//  4.2       420       \"#version 410 core\"\n//  4.3       430       \"#version 430 core\"\n//  ES 2.0    100       \"#version 100\"      = WebGL 1.0\n//  ES 3.0    300       \"#version 300 es\"   = WebGL 2.0\n//----------------------------------------\n\n#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)\n#define _CRT_SECURE_NO_WARNINGS\n#endif\n\n#include \"imgui.h\"\n#ifndef IMGUI_DISABLE\n#include \"imgui_impl_opengl3.h\"\n#include <stdio.h>\n#include <stdint.h>     // intptr_t\n#if defined(__APPLE__)\n#include <TargetConditionals.h>\n#endif\n\n// Clang/GCC warnings with -Weverything\n#if defined(__clang__)\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wold-style-cast\"         // warning: use of old-style cast\n#pragma clang diagnostic ignored \"-Wsign-conversion\"        // warning: implicit conversion changes signedness\n#pragma clang diagnostic ignored \"-Wunused-macros\"          // warning: macro is not used\n#pragma clang diagnostic ignored \"-Wnonportable-system-include-path\"\n#pragma clang diagnostic ignored \"-Wcast-function-type\"     // warning: cast between incompatible function types (for loader)\n#endif\n#if defined(__GNUC__)\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wpragmas\"                  // warning: unknown option after '#pragma GCC diagnostic' kind\n#pragma GCC diagnostic ignored \"-Wunknown-warning-option\"   // warning: unknown warning group 'xxx'\n#pragma GCC diagnostic ignored \"-Wcast-function-type\"       // warning: cast between incompatible function types (for loader)\n#endif\n\n// GL includes\n#if defined(IMGUI_IMPL_OPENGL_ES2)\n#if (defined(__APPLE__) && (TARGET_OS_IOS || TARGET_OS_TV))\n#include <OpenGLES/ES2/gl.h>    // Use GL ES 2\n#else\n#include <GLES2/gl2.h>          // Use GL ES 2\n#endif\n#if defined(__EMSCRIPTEN__)\n#ifndef GL_GLEXT_PROTOTYPES\n#define GL_GLEXT_PROTOTYPES\n#endif\n#include <GLES2/gl2ext.h>\n#endif\n#elif defined(IMGUI_IMPL_OPENGL_ES3)\n#if (defined(__APPLE__) && (TARGET_OS_IOS || TARGET_OS_TV))\n#include <OpenGLES/ES3/gl.h>    // Use GL ES 3\n#else\n#include <GLES3/gl3.h>          // Use GL ES 3\n#endif\n#elif !defined(IMGUI_IMPL_OPENGL_LOADER_CUSTOM)\n// Modern desktop OpenGL doesn't have a standard portable header file to load OpenGL function pointers.\n// Helper libraries are often used for this purpose! Here we are using our own minimal custom loader based on gl3w.\n// In the rest of your app/engine, you can use another loader of your choice (gl3w, glew, glad, glbinding, glext, glLoadGen, etc.).\n// If you happen to be developing a new feature for this backend (imgui_impl_opengl3.cpp):\n// - You may need to regenerate imgui_impl_opengl3_loader.h to add new symbols. See https://github.com/dearimgui/gl3w_stripped\n// - You can temporarily use an unstripped version. See https://github.com/dearimgui/gl3w_stripped/releases\n// Changes to this backend using new APIs should be accompanied by a regenerated stripped loader version.\n#define IMGL3W_IMPL\n#include \"imgui_impl_opengl3_loader.h\"\n#endif\n\n// Vertex arrays are not supported on ES2/WebGL1 unless Emscripten which uses an extension\n#ifndef IMGUI_IMPL_OPENGL_ES2\n#define IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY\n#elif defined(__EMSCRIPTEN__)\n#define IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY\n#define glBindVertexArray       glBindVertexArrayOES\n#define glGenVertexArrays       glGenVertexArraysOES\n#define glDeleteVertexArrays    glDeleteVertexArraysOES\n#define GL_VERTEX_ARRAY_BINDING GL_VERTEX_ARRAY_BINDING_OES\n#endif\n\n// Desktop GL 2.0+ has extension and glPolygonMode() which GL ES and WebGL don't have..\n#if !defined(IMGUI_IMPL_OPENGL_ES2) && !defined(IMGUI_IMPL_OPENGL_ES3)\n#define IMGUI_IMPL_OPENGL_HAS_EXTENSIONS        // has glGetIntegerv(GL_NUM_EXTENSIONS)\n#define IMGUI_IMPL_OPENGL_HAS_POLYGON_MODE      // has glPolygonMode()\n#endif\n\n// Desktop GL 2.1+ and GL ES 3.0+ have glBindBuffer() with GL_PIXEL_UNPACK_BUFFER target.\n#if !defined(IMGUI_IMPL_OPENGL_ES2)\n#define IMGUI_IMPL_OPENGL_MAY_HAVE_BIND_BUFFER_PIXEL_UNPACK\n#endif\n\n// Desktop GL 3.1+ has GL_PRIMITIVE_RESTART state\n#if !defined(IMGUI_IMPL_OPENGL_ES2) && !defined(IMGUI_IMPL_OPENGL_ES3) && defined(GL_VERSION_3_1)\n#define IMGUI_IMPL_OPENGL_MAY_HAVE_PRIMITIVE_RESTART\n#endif\n\n// Desktop GL 3.2+ has glDrawElementsBaseVertex() which GL ES and WebGL don't have.\n#if !defined(IMGUI_IMPL_OPENGL_ES2) && !defined(IMGUI_IMPL_OPENGL_ES3) && defined(GL_VERSION_3_2)\n#define IMGUI_IMPL_OPENGL_MAY_HAVE_VTX_OFFSET\n#endif\n\n// Desktop GL 3.3+ and GL ES 3.0+ have glBindSampler()\n#if !defined(IMGUI_IMPL_OPENGL_ES2) && (defined(IMGUI_IMPL_OPENGL_ES3) || defined(GL_VERSION_3_3))\n#define IMGUI_IMPL_OPENGL_MAY_HAVE_BIND_SAMPLER\n#endif\n\n// [Debugging]\n//#define IMGUI_IMPL_OPENGL_DEBUG\n#ifdef IMGUI_IMPL_OPENGL_DEBUG\n#include <stdio.h>\n#define GL_CALL(_CALL)      do { _CALL; GLenum gl_err = glGetError(); if (gl_err != 0) fprintf(stderr, \"GL error 0x%x returned from '%s'.\\n\", gl_err, #_CALL); } while (0)  // Call with error check\n#else\n#define GL_CALL(_CALL)      _CALL   // Call without error check\n#endif\n\n// OpenGL Data\nstruct ImGui_ImplOpenGL3_Data\n{\n    GLuint          GlVersion;               // Extracted at runtime using GL_MAJOR_VERSION, GL_MINOR_VERSION queries (e.g. 320 for GL 3.2)\n    char            GlslVersionString[32];   // Specified by user or detected based on compile time GL settings.\n    bool            GlProfileIsES2;\n    bool            GlProfileIsES3;\n    bool            GlProfileIsCompat;\n    GLint           GlProfileMask;\n    GLuint          FontTexture;\n    GLuint          ShaderHandle;\n    GLint           AttribLocationTex;       // Uniforms location\n    GLint           AttribLocationProjMtx;\n    GLuint          AttribLocationVtxPos;    // Vertex attributes location\n    GLuint          AttribLocationVtxUV;\n    GLuint          AttribLocationVtxColor;\n    unsigned int    VboHandle, ElementsHandle;\n    GLsizeiptr      VertexBufferSize;\n    GLsizeiptr      IndexBufferSize;\n    bool            HasClipOrigin;\n    bool            UseBufferSubData;\n\n    ImGui_ImplOpenGL3_Data() { memset((void*)this, 0, sizeof(*this)); }\n};\n\n// Backend data stored in io.BackendRendererUserData to allow support for multiple Dear ImGui contexts\n// It is STRONGLY preferred that you use docking branch with multi-viewports (== single Dear ImGui context + multiple windows) instead of multiple Dear ImGui contexts.\nstatic ImGui_ImplOpenGL3_Data* ImGui_ImplOpenGL3_GetBackendData()\n{\n    return ImGui::GetCurrentContext() ? (ImGui_ImplOpenGL3_Data*)ImGui::GetIO().BackendRendererUserData : nullptr;\n}\n\n// Forward Declarations\nstatic void ImGui_ImplOpenGL3_InitPlatformInterface();\nstatic void ImGui_ImplOpenGL3_ShutdownPlatformInterface();\n\n// OpenGL vertex attribute state (for ES 1.0 and ES 2.0 only)\n#ifndef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY\nstruct ImGui_ImplOpenGL3_VtxAttribState\n{\n    GLint   Enabled, Size, Type, Normalized, Stride;\n    GLvoid* Ptr;\n\n    void GetState(GLint index)\n    {\n        glGetVertexAttribiv(index, GL_VERTEX_ATTRIB_ARRAY_ENABLED, &Enabled);\n        glGetVertexAttribiv(index, GL_VERTEX_ATTRIB_ARRAY_SIZE, &Size);\n        glGetVertexAttribiv(index, GL_VERTEX_ATTRIB_ARRAY_TYPE, &Type);\n        glGetVertexAttribiv(index, GL_VERTEX_ATTRIB_ARRAY_NORMALIZED, &Normalized);\n        glGetVertexAttribiv(index, GL_VERTEX_ATTRIB_ARRAY_STRIDE, &Stride);\n        glGetVertexAttribPointerv(index, GL_VERTEX_ATTRIB_ARRAY_POINTER, &Ptr);\n    }\n    void SetState(GLint index)\n    {\n        glVertexAttribPointer(index, Size, Type, (GLboolean)Normalized, Stride, Ptr);\n        if (Enabled) glEnableVertexAttribArray(index); else glDisableVertexAttribArray(index);\n    }\n};\n#endif\n\n// Functions\nbool    ImGui_ImplOpenGL3_Init(const char* glsl_version)\n{\n    ImGuiIO& io = ImGui::GetIO();\n    IM_ASSERT(io.BackendRendererUserData == nullptr && \"Already initialized a renderer backend!\");\n\n    // Initialize our loader\n#if !defined(IMGUI_IMPL_OPENGL_ES2) && !defined(IMGUI_IMPL_OPENGL_ES3) && !defined(IMGUI_IMPL_OPENGL_LOADER_CUSTOM)\n    if (imgl3wInit() != 0)\n    {\n        fprintf(stderr, \"Failed to initialize OpenGL loader!\\n\");\n        return false;\n    }\n#endif\n\n    // Setup backend capabilities flags\n    ImGui_ImplOpenGL3_Data* bd = IM_NEW(ImGui_ImplOpenGL3_Data)();\n    io.BackendRendererUserData = (void*)bd;\n    io.BackendRendererName = \"imgui_impl_opengl3\";\n\n    // Query for GL version (e.g. 320 for GL 3.2)\n#if defined(IMGUI_IMPL_OPENGL_ES2)\n    // GLES 2\n    bd->GlVersion = 200;\n    bd->GlProfileIsES2 = true;\n#else\n    // Desktop or GLES 3\n    GLint major = 0;\n    GLint minor = 0;\n    glGetIntegerv(GL_MAJOR_VERSION, &major);\n    glGetIntegerv(GL_MINOR_VERSION, &minor);\n    if (major == 0 && minor == 0)\n    {\n        // Query GL_VERSION in desktop GL 2.x, the string will start with \"<major>.<minor>\"\n        const char* gl_version = (const char*)glGetString(GL_VERSION);\n        sscanf(gl_version, \"%d.%d\", &major, &minor);\n    }\n    bd->GlVersion = (GLuint)(major * 100 + minor * 10);\n#if defined(GL_CONTEXT_PROFILE_MASK)\n    if (bd->GlVersion >= 320)\n        glGetIntegerv(GL_CONTEXT_PROFILE_MASK, &bd->GlProfileMask);\n    bd->GlProfileIsCompat = (bd->GlProfileMask & GL_CONTEXT_COMPATIBILITY_PROFILE_BIT) != 0;\n#endif\n\n#if defined(IMGUI_IMPL_OPENGL_ES3)\n    bd->GlProfileIsES3 = true;\n#endif\n\n    bd->UseBufferSubData = false;\n    /*\n    // Query vendor to enable glBufferSubData kludge\n#ifdef _WIN32\n    if (const char* vendor = (const char*)glGetString(GL_VENDOR))\n        if (strncmp(vendor, \"Intel\", 5) == 0)\n            bd->UseBufferSubData = true;\n#endif\n    */\n#endif\n\n#ifdef IMGUI_IMPL_OPENGL_DEBUG\n    printf(\"GlVersion = %d\\nGlProfileIsCompat = %d\\nGlProfileMask = 0x%X\\nGlProfileIsES2 = %d, GlProfileIsES3 = %d\\nGL_VENDOR = '%s'\\nGL_RENDERER = '%s'\\n\", bd->GlVersion, bd->GlProfileIsCompat, bd->GlProfileMask, bd->GlProfileIsES2, bd->GlProfileIsES3, (const char*)glGetString(GL_VENDOR), (const char*)glGetString(GL_RENDERER)); // [DEBUG]\n#endif\n\n#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_VTX_OFFSET\n    if (bd->GlVersion >= 320)\n        io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset;  // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes.\n#endif\n    io.BackendFlags |= ImGuiBackendFlags_RendererHasViewports;  // We can create multi-viewports on the Renderer side (optional)\n\n    // Store GLSL version string so we can refer to it later in case we recreate shaders.\n    // Note: GLSL version is NOT the same as GL version. Leave this to nullptr if unsure.\n    if (glsl_version == nullptr)\n    {\n#if defined(IMGUI_IMPL_OPENGL_ES2)\n        glsl_version = \"#version 100\";\n#elif defined(IMGUI_IMPL_OPENGL_ES3)\n        glsl_version = \"#version 300 es\";\n#elif defined(__APPLE__)\n        glsl_version = \"#version 150\";\n#else\n        glsl_version = \"#version 130\";\n#endif\n    }\n    IM_ASSERT((int)strlen(glsl_version) + 2 < IM_ARRAYSIZE(bd->GlslVersionString));\n    strcpy(bd->GlslVersionString, glsl_version);\n    strcat(bd->GlslVersionString, \"\\n\");\n\n    // Make an arbitrary GL call (we don't actually need the result)\n    // IF YOU GET A CRASH HERE: it probably means the OpenGL function loader didn't do its job. Let us know!\n    GLint current_texture;\n    glGetIntegerv(GL_TEXTURE_BINDING_2D, &current_texture);\n\n    // Detect extensions we support\n    bd->HasClipOrigin = (bd->GlVersion >= 450);\n#ifdef IMGUI_IMPL_OPENGL_HAS_EXTENSIONS\n    GLint num_extensions = 0;\n    glGetIntegerv(GL_NUM_EXTENSIONS, &num_extensions);\n    for (GLint i = 0; i < num_extensions; i++)\n    {\n        const char* extension = (const char*)glGetStringi(GL_EXTENSIONS, i);\n        if (extension != nullptr && strcmp(extension, \"GL_ARB_clip_control\") == 0)\n            bd->HasClipOrigin = true;\n    }\n#endif\n\n    if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable)\n        ImGui_ImplOpenGL3_InitPlatformInterface();\n\n    return true;\n}\n\nvoid    ImGui_ImplOpenGL3_Shutdown()\n{\n    ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData();\n    IM_ASSERT(bd != nullptr && \"No renderer backend to shutdown, or already shutdown?\");\n    ImGuiIO& io = ImGui::GetIO();\n\n    ImGui_ImplOpenGL3_ShutdownPlatformInterface();\n    ImGui_ImplOpenGL3_DestroyDeviceObjects();\n    io.BackendRendererName = nullptr;\n    io.BackendRendererUserData = nullptr;\n    io.BackendFlags &= ~(ImGuiBackendFlags_RendererHasVtxOffset | ImGuiBackendFlags_RendererHasViewports);\n    IM_DELETE(bd);\n}\n\nvoid    ImGui_ImplOpenGL3_NewFrame()\n{\n    ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData();\n    IM_ASSERT(bd != nullptr && \"Did you call ImGui_ImplOpenGL3_Init()?\");\n\n    if (!bd->ShaderHandle)\n        ImGui_ImplOpenGL3_CreateDeviceObjects();\n}\n\nstatic void ImGui_ImplOpenGL3_SetupRenderState(ImDrawData* draw_data, int fb_width, int fb_height, GLuint vertex_array_object)\n{\n    ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData();\n\n    // Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled, polygon fill\n    glEnable(GL_BLEND);\n    glBlendEquation(GL_FUNC_ADD);\n    glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);\n    glDisable(GL_CULL_FACE);\n    glDisable(GL_DEPTH_TEST);\n    glDisable(GL_STENCIL_TEST);\n    glEnable(GL_SCISSOR_TEST);\n#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_PRIMITIVE_RESTART\n    if (bd->GlVersion >= 310)\n        glDisable(GL_PRIMITIVE_RESTART);\n#endif\n#ifdef IMGUI_IMPL_OPENGL_HAS_POLYGON_MODE\n    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);\n#endif\n\n    // Support for GL 4.5 rarely used glClipControl(GL_UPPER_LEFT)\n#if defined(GL_CLIP_ORIGIN)\n    bool clip_origin_lower_left = true;\n    if (bd->HasClipOrigin)\n    {\n        GLenum current_clip_origin = 0; glGetIntegerv(GL_CLIP_ORIGIN, (GLint*)&current_clip_origin);\n        if (current_clip_origin == GL_UPPER_LEFT)\n            clip_origin_lower_left = false;\n    }\n#endif\n\n    // Setup viewport, orthographic projection matrix\n    // Our visible imgui space lies from draw_data->DisplayPos (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single viewport apps.\n    GL_CALL(glViewport(0, 0, (GLsizei)fb_width, (GLsizei)fb_height));\n    float L = draw_data->DisplayPos.x;\n    float R = draw_data->DisplayPos.x + draw_data->DisplaySize.x;\n    float T = draw_data->DisplayPos.y;\n    float B = draw_data->DisplayPos.y + draw_data->DisplaySize.y;\n#if defined(GL_CLIP_ORIGIN)\n    if (!clip_origin_lower_left) { float tmp = T; T = B; B = tmp; } // Swap top and bottom if origin is upper left\n#endif\n    const float ortho_projection[4][4] =\n    {\n        { 2.0f/(R-L),   0.0f,         0.0f,   0.0f },\n        { 0.0f,         2.0f/(T-B),   0.0f,   0.0f },\n        { 0.0f,         0.0f,        -1.0f,   0.0f },\n        { (R+L)/(L-R),  (T+B)/(B-T),  0.0f,   1.0f },\n    };\n    glUseProgram(bd->ShaderHandle);\n    glUniform1i(bd->AttribLocationTex, 0);\n    glUniformMatrix4fv(bd->AttribLocationProjMtx, 1, GL_FALSE, &ortho_projection[0][0]);\n\n#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_BIND_SAMPLER\n    if (bd->GlVersion >= 330 || bd->GlProfileIsES3)\n        glBindSampler(0, 0); // We use combined texture/sampler state. Applications using GL 3.3 and GL ES 3.0 may set that otherwise.\n#endif\n\n    (void)vertex_array_object;\n#ifdef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY\n    glBindVertexArray(vertex_array_object);\n#endif\n\n    // Bind vertex/index buffers and setup attributes for ImDrawVert\n    GL_CALL(glBindBuffer(GL_ARRAY_BUFFER, bd->VboHandle));\n    GL_CALL(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bd->ElementsHandle));\n    GL_CALL(glEnableVertexAttribArray(bd->AttribLocationVtxPos));\n    GL_CALL(glEnableVertexAttribArray(bd->AttribLocationVtxUV));\n    GL_CALL(glEnableVertexAttribArray(bd->AttribLocationVtxColor));\n    GL_CALL(glVertexAttribPointer(bd->AttribLocationVtxPos,   2, GL_FLOAT,         GL_FALSE, sizeof(ImDrawVert), (GLvoid*)offsetof(ImDrawVert, pos)));\n    GL_CALL(glVertexAttribPointer(bd->AttribLocationVtxUV,    2, GL_FLOAT,         GL_FALSE, sizeof(ImDrawVert), (GLvoid*)offsetof(ImDrawVert, uv)));\n    GL_CALL(glVertexAttribPointer(bd->AttribLocationVtxColor, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(ImDrawVert), (GLvoid*)offsetof(ImDrawVert, col)));\n}\n\n// OpenGL3 Render function.\n// Note that this implementation is little overcomplicated because we are saving/setting up/restoring every OpenGL state explicitly.\n// This is in order to be able to run within an OpenGL engine that doesn't do so.\nvoid    ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data)\n{\n    // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates)\n    int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x);\n    int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y);\n    if (fb_width <= 0 || fb_height <= 0)\n        return;\n\n    ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData();\n\n    // Backup GL state\n    GLenum last_active_texture; glGetIntegerv(GL_ACTIVE_TEXTURE, (GLint*)&last_active_texture);\n    glActiveTexture(GL_TEXTURE0);\n    GLuint last_program; glGetIntegerv(GL_CURRENT_PROGRAM, (GLint*)&last_program);\n    GLuint last_texture; glGetIntegerv(GL_TEXTURE_BINDING_2D, (GLint*)&last_texture);\n#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_BIND_SAMPLER\n    GLuint last_sampler; if (bd->GlVersion >= 330 || bd->GlProfileIsES3) { glGetIntegerv(GL_SAMPLER_BINDING, (GLint*)&last_sampler); } else { last_sampler = 0; }\n#endif\n    GLuint last_array_buffer; glGetIntegerv(GL_ARRAY_BUFFER_BINDING, (GLint*)&last_array_buffer);\n#ifndef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY\n    // This is part of VAO on OpenGL 3.0+ and OpenGL ES 3.0+.\n    GLint last_element_array_buffer; glGetIntegerv(GL_ELEMENT_ARRAY_BUFFER_BINDING, &last_element_array_buffer);\n    ImGui_ImplOpenGL3_VtxAttribState last_vtx_attrib_state_pos; last_vtx_attrib_state_pos.GetState(bd->AttribLocationVtxPos);\n    ImGui_ImplOpenGL3_VtxAttribState last_vtx_attrib_state_uv; last_vtx_attrib_state_uv.GetState(bd->AttribLocationVtxUV);\n    ImGui_ImplOpenGL3_VtxAttribState last_vtx_attrib_state_color; last_vtx_attrib_state_color.GetState(bd->AttribLocationVtxColor);\n#endif\n#ifdef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY\n    GLuint last_vertex_array_object; glGetIntegerv(GL_VERTEX_ARRAY_BINDING, (GLint*)&last_vertex_array_object);\n#endif\n#ifdef IMGUI_IMPL_OPENGL_HAS_POLYGON_MODE\n    GLint last_polygon_mode[2]; glGetIntegerv(GL_POLYGON_MODE, last_polygon_mode);\n#endif\n    GLint last_viewport[4]; glGetIntegerv(GL_VIEWPORT, last_viewport);\n    GLint last_scissor_box[4]; glGetIntegerv(GL_SCISSOR_BOX, last_scissor_box);\n    GLenum last_blend_src_rgb; glGetIntegerv(GL_BLEND_SRC_RGB, (GLint*)&last_blend_src_rgb);\n    GLenum last_blend_dst_rgb; glGetIntegerv(GL_BLEND_DST_RGB, (GLint*)&last_blend_dst_rgb);\n    GLenum last_blend_src_alpha; glGetIntegerv(GL_BLEND_SRC_ALPHA, (GLint*)&last_blend_src_alpha);\n    GLenum last_blend_dst_alpha; glGetIntegerv(GL_BLEND_DST_ALPHA, (GLint*)&last_blend_dst_alpha);\n    GLenum last_blend_equation_rgb; glGetIntegerv(GL_BLEND_EQUATION_RGB, (GLint*)&last_blend_equation_rgb);\n    GLenum last_blend_equation_alpha; glGetIntegerv(GL_BLEND_EQUATION_ALPHA, (GLint*)&last_blend_equation_alpha);\n    GLboolean last_enable_blend = glIsEnabled(GL_BLEND);\n    GLboolean last_enable_cull_face = glIsEnabled(GL_CULL_FACE);\n    GLboolean last_enable_depth_test = glIsEnabled(GL_DEPTH_TEST);\n    GLboolean last_enable_stencil_test = glIsEnabled(GL_STENCIL_TEST);\n    GLboolean last_enable_scissor_test = glIsEnabled(GL_SCISSOR_TEST);\n#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_PRIMITIVE_RESTART\n    GLboolean last_enable_primitive_restart = (bd->GlVersion >= 310) ? glIsEnabled(GL_PRIMITIVE_RESTART) : GL_FALSE;\n#endif\n\n    // Setup desired GL state\n    // Recreate the VAO every time (this is to easily allow multiple GL contexts to be rendered to. VAO are not shared among GL contexts)\n    // The renderer would actually work without any VAO bound, but then our VertexAttrib calls would overwrite the default one currently bound.\n    GLuint vertex_array_object = 0;\n#ifdef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY\n    GL_CALL(glGenVertexArrays(1, &vertex_array_object));\n#endif\n    ImGui_ImplOpenGL3_SetupRenderState(draw_data, fb_width, fb_height, vertex_array_object);\n\n    // Will project scissor/clipping rectangles into framebuffer space\n    ImVec2 clip_off = draw_data->DisplayPos;         // (0,0) unless using multi-viewports\n    ImVec2 clip_scale = draw_data->FramebufferScale; // (1,1) unless using retina display which are often (2,2)\n\n    // Render command lists\n    for (int n = 0; n < draw_data->CmdListsCount; n++)\n    {\n        const ImDrawList* cmd_list = draw_data->CmdLists[n];\n\n        // Upload vertex/index buffers\n        // - OpenGL drivers are in a very sorry state nowadays....\n        //   During 2021 we attempted to switch from glBufferData() to orphaning+glBufferSubData() following reports\n        //   of leaks on Intel GPU when using multi-viewports on Windows.\n        // - After this we kept hearing of various display corruptions issues. We started disabling on non-Intel GPU, but issues still got reported on Intel.\n        // - We are now back to using exclusively glBufferData(). So bd->UseBufferSubData IS ALWAYS FALSE in this code.\n        //   We are keeping the old code path for a while in case people finding new issues may want to test the bd->UseBufferSubData path.\n        // - See https://github.com/ocornut/imgui/issues/4468 and please report any corruption issues.\n        const GLsizeiptr vtx_buffer_size = (GLsizeiptr)cmd_list->VtxBuffer.Size * (int)sizeof(ImDrawVert);\n        const GLsizeiptr idx_buffer_size = (GLsizeiptr)cmd_list->IdxBuffer.Size * (int)sizeof(ImDrawIdx);\n        if (bd->UseBufferSubData)\n        {\n            if (bd->VertexBufferSize < vtx_buffer_size)\n            {\n                bd->VertexBufferSize = vtx_buffer_size;\n                GL_CALL(glBufferData(GL_ARRAY_BUFFER, bd->VertexBufferSize, nullptr, GL_STREAM_DRAW));\n            }\n            if (bd->IndexBufferSize < idx_buffer_size)\n            {\n                bd->IndexBufferSize = idx_buffer_size;\n                GL_CALL(glBufferData(GL_ELEMENT_ARRAY_BUFFER, bd->IndexBufferSize, nullptr, GL_STREAM_DRAW));\n            }\n            GL_CALL(glBufferSubData(GL_ARRAY_BUFFER, 0, vtx_buffer_size, (const GLvoid*)cmd_list->VtxBuffer.Data));\n            GL_CALL(glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, idx_buffer_size, (const GLvoid*)cmd_list->IdxBuffer.Data));\n        }\n        else\n        {\n            GL_CALL(glBufferData(GL_ARRAY_BUFFER, vtx_buffer_size, (const GLvoid*)cmd_list->VtxBuffer.Data, GL_STREAM_DRAW));\n            GL_CALL(glBufferData(GL_ELEMENT_ARRAY_BUFFER, idx_buffer_size, (const GLvoid*)cmd_list->IdxBuffer.Data, GL_STREAM_DRAW));\n        }\n\n        for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++)\n        {\n            const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i];\n            if (pcmd->UserCallback != nullptr)\n            {\n                // User callback, registered via ImDrawList::AddCallback()\n                // (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.)\n                if (pcmd->UserCallback == ImDrawCallback_ResetRenderState)\n                    ImGui_ImplOpenGL3_SetupRenderState(draw_data, fb_width, fb_height, vertex_array_object);\n                else\n                    pcmd->UserCallback(cmd_list, pcmd);\n            }\n            else\n            {\n                // Project scissor/clipping rectangles into framebuffer space\n                ImVec2 clip_min((pcmd->ClipRect.x - clip_off.x) * clip_scale.x, (pcmd->ClipRect.y - clip_off.y) * clip_scale.y);\n                ImVec2 clip_max((pcmd->ClipRect.z - clip_off.x) * clip_scale.x, (pcmd->ClipRect.w - clip_off.y) * clip_scale.y);\n                if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y)\n                    continue;\n\n                // Apply scissor/clipping rectangle (Y is inverted in OpenGL)\n                GL_CALL(glScissor((int)clip_min.x, (int)((float)fb_height - clip_max.y), (int)(clip_max.x - clip_min.x), (int)(clip_max.y - clip_min.y)));\n\n                // Bind texture, Draw\n                GL_CALL(glBindTexture(GL_TEXTURE_2D, (GLuint)(intptr_t)pcmd->GetTexID()));\n#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_VTX_OFFSET\n                if (bd->GlVersion >= 320)\n                    GL_CALL(glDrawElementsBaseVertex(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, (void*)(intptr_t)(pcmd->IdxOffset * sizeof(ImDrawIdx)), (GLint)pcmd->VtxOffset));\n                else\n#endif\n                GL_CALL(glDrawElements(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, (void*)(intptr_t)(pcmd->IdxOffset * sizeof(ImDrawIdx))));\n            }\n        }\n    }\n\n    // Destroy the temporary VAO\n#ifdef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY\n    GL_CALL(glDeleteVertexArrays(1, &vertex_array_object));\n#endif\n\n    // Restore modified GL state\n    // This \"glIsProgram()\" check is required because if the program is \"pending deletion\" at the time of binding backup, it will have been deleted by now and will cause an OpenGL error. See #6220.\n    if (last_program == 0 || glIsProgram(last_program)) glUseProgram(last_program);\n    glBindTexture(GL_TEXTURE_2D, last_texture);\n#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_BIND_SAMPLER\n    if (bd->GlVersion >= 330 || bd->GlProfileIsES3)\n        glBindSampler(0, last_sampler);\n#endif\n    glActiveTexture(last_active_texture);\n#ifdef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY\n    glBindVertexArray(last_vertex_array_object);\n#endif\n    glBindBuffer(GL_ARRAY_BUFFER, last_array_buffer);\n#ifndef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY\n    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, last_element_array_buffer);\n    last_vtx_attrib_state_pos.SetState(bd->AttribLocationVtxPos);\n    last_vtx_attrib_state_uv.SetState(bd->AttribLocationVtxUV);\n    last_vtx_attrib_state_color.SetState(bd->AttribLocationVtxColor);\n#endif\n    glBlendEquationSeparate(last_blend_equation_rgb, last_blend_equation_alpha);\n    glBlendFuncSeparate(last_blend_src_rgb, last_blend_dst_rgb, last_blend_src_alpha, last_blend_dst_alpha);\n    if (last_enable_blend) glEnable(GL_BLEND); else glDisable(GL_BLEND);\n    if (last_enable_cull_face) glEnable(GL_CULL_FACE); else glDisable(GL_CULL_FACE);\n    if (last_enable_depth_test) glEnable(GL_DEPTH_TEST); else glDisable(GL_DEPTH_TEST);\n    if (last_enable_stencil_test) glEnable(GL_STENCIL_TEST); else glDisable(GL_STENCIL_TEST);\n    if (last_enable_scissor_test) glEnable(GL_SCISSOR_TEST); else glDisable(GL_SCISSOR_TEST);\n#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_PRIMITIVE_RESTART\n    if (bd->GlVersion >= 310) { if (last_enable_primitive_restart) glEnable(GL_PRIMITIVE_RESTART); else glDisable(GL_PRIMITIVE_RESTART); }\n#endif\n\n#ifdef IMGUI_IMPL_OPENGL_HAS_POLYGON_MODE\n    // Desktop OpenGL 3.0 and OpenGL 3.1 had separate polygon draw modes for front-facing and back-facing faces of polygons\n    if (bd->GlVersion <= 310 || bd->GlProfileIsCompat)\n    {\n        glPolygonMode(GL_FRONT, (GLenum)last_polygon_mode[0]);\n        glPolygonMode(GL_BACK, (GLenum)last_polygon_mode[1]);\n    }\n    else\n    {\n        glPolygonMode(GL_FRONT_AND_BACK, (GLenum)last_polygon_mode[0]);\n    }\n#endif // IMGUI_IMPL_OPENGL_HAS_POLYGON_MODE\n\n    glViewport(last_viewport[0], last_viewport[1], (GLsizei)last_viewport[2], (GLsizei)last_viewport[3]);\n    glScissor(last_scissor_box[0], last_scissor_box[1], (GLsizei)last_scissor_box[2], (GLsizei)last_scissor_box[3]);\n    (void)bd; // Not all compilation paths use this\n}\n\nbool ImGui_ImplOpenGL3_CreateFontsTexture()\n{\n    ImGuiIO& io = ImGui::GetIO();\n    ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData();\n\n    // Build texture atlas\n    unsigned char* pixels;\n    int width, height;\n    io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height);   // Load as RGBA 32-bit (75% of the memory is wasted, but default font is so small) because it is more likely to be compatible with user's existing shaders. If your ImTextureId represent a higher-level concept than just a GL texture id, consider calling GetTexDataAsAlpha8() instead to save on GPU memory.\n\n    // Upload texture to graphics system\n    // (Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling)\n    GLint last_texture;\n    GL_CALL(glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture));\n    GL_CALL(glGenTextures(1, &bd->FontTexture));\n    GL_CALL(glBindTexture(GL_TEXTURE_2D, bd->FontTexture));\n    GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));\n    GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));\n#ifdef GL_UNPACK_ROW_LENGTH // Not on WebGL/ES\n    GL_CALL(glPixelStorei(GL_UNPACK_ROW_LENGTH, 0));\n#endif\n    GL_CALL(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels));\n\n    // Store our identifier\n    io.Fonts->SetTexID((ImTextureID)(intptr_t)bd->FontTexture);\n\n    // Restore state\n    GL_CALL(glBindTexture(GL_TEXTURE_2D, last_texture));\n\n    return true;\n}\n\nvoid ImGui_ImplOpenGL3_DestroyFontsTexture()\n{\n    ImGuiIO& io = ImGui::GetIO();\n    ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData();\n    if (bd->FontTexture)\n    {\n        glDeleteTextures(1, &bd->FontTexture);\n        io.Fonts->SetTexID(0);\n        bd->FontTexture = 0;\n    }\n}\n\n// If you get an error please report on github. You may try different GL context version or GLSL version. See GL<>GLSL version table at the top of this file.\nstatic bool CheckShader(GLuint handle, const char* desc)\n{\n    ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData();\n    GLint status = 0, log_length = 0;\n    glGetShaderiv(handle, GL_COMPILE_STATUS, &status);\n    glGetShaderiv(handle, GL_INFO_LOG_LENGTH, &log_length);\n    if ((GLboolean)status == GL_FALSE)\n        fprintf(stderr, \"ERROR: ImGui_ImplOpenGL3_CreateDeviceObjects: failed to compile %s! With GLSL: %s\\n\", desc, bd->GlslVersionString);\n    if (log_length > 1)\n    {\n        ImVector<char> buf;\n        buf.resize((int)(log_length + 1));\n        glGetShaderInfoLog(handle, log_length, nullptr, (GLchar*)buf.begin());\n        fprintf(stderr, \"%s\\n\", buf.begin());\n    }\n    return (GLboolean)status == GL_TRUE;\n}\n\n// If you get an error please report on GitHub. You may try different GL context version or GLSL version.\nstatic bool CheckProgram(GLuint handle, const char* desc)\n{\n    ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData();\n    GLint status = 0, log_length = 0;\n    glGetProgramiv(handle, GL_LINK_STATUS, &status);\n    glGetProgramiv(handle, GL_INFO_LOG_LENGTH, &log_length);\n    if ((GLboolean)status == GL_FALSE)\n        fprintf(stderr, \"ERROR: ImGui_ImplOpenGL3_CreateDeviceObjects: failed to link %s! With GLSL %s\\n\", desc, bd->GlslVersionString);\n    if (log_length > 1)\n    {\n        ImVector<char> buf;\n        buf.resize((int)(log_length + 1));\n        glGetProgramInfoLog(handle, log_length, nullptr, (GLchar*)buf.begin());\n        fprintf(stderr, \"%s\\n\", buf.begin());\n    }\n    return (GLboolean)status == GL_TRUE;\n}\n\nbool    ImGui_ImplOpenGL3_CreateDeviceObjects()\n{\n    ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData();\n\n    // Backup GL state\n    GLint last_texture, last_array_buffer;\n    glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture);\n    glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &last_array_buffer);\n#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_BIND_BUFFER_PIXEL_UNPACK\n    GLint last_pixel_unpack_buffer;\n    if (bd->GlVersion >= 210) { glGetIntegerv(GL_PIXEL_UNPACK_BUFFER_BINDING, &last_pixel_unpack_buffer); glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); }\n#endif\n#ifdef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY\n    GLint last_vertex_array;\n    glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &last_vertex_array);\n#endif\n\n    // Parse GLSL version string\n    int glsl_version = 130;\n    sscanf(bd->GlslVersionString, \"#version %d\", &glsl_version);\n\n    const GLchar* vertex_shader_glsl_120 =\n        \"uniform mat4 ProjMtx;\\n\"\n        \"attribute vec2 Position;\\n\"\n        \"attribute vec2 UV;\\n\"\n        \"attribute vec4 Color;\\n\"\n        \"varying vec2 Frag_UV;\\n\"\n        \"varying vec4 Frag_Color;\\n\"\n        \"void main()\\n\"\n        \"{\\n\"\n        \"    Frag_UV = UV;\\n\"\n        \"    Frag_Color = Color;\\n\"\n        \"    gl_Position = ProjMtx * vec4(Position.xy,0,1);\\n\"\n        \"}\\n\";\n\n    const GLchar* vertex_shader_glsl_130 =\n        \"uniform mat4 ProjMtx;\\n\"\n        \"in vec2 Position;\\n\"\n        \"in vec2 UV;\\n\"\n        \"in vec4 Color;\\n\"\n        \"out vec2 Frag_UV;\\n\"\n        \"out vec4 Frag_Color;\\n\"\n        \"void main()\\n\"\n        \"{\\n\"\n        \"    Frag_UV = UV;\\n\"\n        \"    Frag_Color = Color;\\n\"\n        \"    gl_Position = ProjMtx * vec4(Position.xy,0,1);\\n\"\n        \"}\\n\";\n\n    const GLchar* vertex_shader_glsl_300_es =\n        \"precision highp float;\\n\"\n        \"layout (location = 0) in vec2 Position;\\n\"\n        \"layout (location = 1) in vec2 UV;\\n\"\n        \"layout (location = 2) in vec4 Color;\\n\"\n        \"uniform mat4 ProjMtx;\\n\"\n        \"out vec2 Frag_UV;\\n\"\n        \"out vec4 Frag_Color;\\n\"\n        \"void main()\\n\"\n        \"{\\n\"\n        \"    Frag_UV = UV;\\n\"\n        \"    Frag_Color = Color;\\n\"\n        \"    gl_Position = ProjMtx * vec4(Position.xy,0,1);\\n\"\n        \"}\\n\";\n\n    const GLchar* vertex_shader_glsl_410_core =\n        \"layout (location = 0) in vec2 Position;\\n\"\n        \"layout (location = 1) in vec2 UV;\\n\"\n        \"layout (location = 2) in vec4 Color;\\n\"\n        \"uniform mat4 ProjMtx;\\n\"\n        \"out vec2 Frag_UV;\\n\"\n        \"out vec4 Frag_Color;\\n\"\n        \"void main()\\n\"\n        \"{\\n\"\n        \"    Frag_UV = UV;\\n\"\n        \"    Frag_Color = Color;\\n\"\n        \"    gl_Position = ProjMtx * vec4(Position.xy,0,1);\\n\"\n        \"}\\n\";\n\n    const GLchar* fragment_shader_glsl_120 =\n        \"#ifdef GL_ES\\n\"\n        \"    precision mediump float;\\n\"\n        \"#endif\\n\"\n        \"uniform sampler2D Texture;\\n\"\n        \"varying vec2 Frag_UV;\\n\"\n        \"varying vec4 Frag_Color;\\n\"\n        \"void main()\\n\"\n        \"{\\n\"\n        \"    gl_FragColor = Frag_Color * texture2D(Texture, Frag_UV.st);\\n\"\n        \"}\\n\";\n\n    const GLchar* fragment_shader_glsl_130 =\n        \"uniform sampler2D Texture;\\n\"\n        \"in vec2 Frag_UV;\\n\"\n        \"in vec4 Frag_Color;\\n\"\n        \"out vec4 Out_Color;\\n\"\n        \"void main()\\n\"\n        \"{\\n\"\n        \"    Out_Color = Frag_Color * texture(Texture, Frag_UV.st);\\n\"\n        \"}\\n\";\n\n    const GLchar* fragment_shader_glsl_300_es =\n        \"precision mediump float;\\n\"\n        \"uniform sampler2D Texture;\\n\"\n        \"in vec2 Frag_UV;\\n\"\n        \"in vec4 Frag_Color;\\n\"\n        \"layout (location = 0) out vec4 Out_Color;\\n\"\n        \"void main()\\n\"\n        \"{\\n\"\n        \"    Out_Color = Frag_Color * texture(Texture, Frag_UV.st);\\n\"\n        \"}\\n\";\n\n    const GLchar* fragment_shader_glsl_410_core =\n        \"in vec2 Frag_UV;\\n\"\n        \"in vec4 Frag_Color;\\n\"\n        \"uniform sampler2D Texture;\\n\"\n        \"layout (location = 0) out vec4 Out_Color;\\n\"\n        \"void main()\\n\"\n        \"{\\n\"\n        \"    Out_Color = Frag_Color * texture(Texture, Frag_UV.st);\\n\"\n        \"}\\n\";\n\n    // Select shaders matching our GLSL versions\n    const GLchar* vertex_shader = nullptr;\n    const GLchar* fragment_shader = nullptr;\n    if (glsl_version < 130)\n    {\n        vertex_shader = vertex_shader_glsl_120;\n        fragment_shader = fragment_shader_glsl_120;\n    }\n    else if (glsl_version >= 410)\n    {\n        vertex_shader = vertex_shader_glsl_410_core;\n        fragment_shader = fragment_shader_glsl_410_core;\n    }\n    else if (glsl_version == 300)\n    {\n        vertex_shader = vertex_shader_glsl_300_es;\n        fragment_shader = fragment_shader_glsl_300_es;\n    }\n    else\n    {\n        vertex_shader = vertex_shader_glsl_130;\n        fragment_shader = fragment_shader_glsl_130;\n    }\n\n    // Create shaders\n    const GLchar* vertex_shader_with_version[2] = { bd->GlslVersionString, vertex_shader };\n    GLuint vert_handle = glCreateShader(GL_VERTEX_SHADER);\n    glShaderSource(vert_handle, 2, vertex_shader_with_version, nullptr);\n    glCompileShader(vert_handle);\n    CheckShader(vert_handle, \"vertex shader\");\n\n    const GLchar* fragment_shader_with_version[2] = { bd->GlslVersionString, fragment_shader };\n    GLuint frag_handle = glCreateShader(GL_FRAGMENT_SHADER);\n    glShaderSource(frag_handle, 2, fragment_shader_with_version, nullptr);\n    glCompileShader(frag_handle);\n    CheckShader(frag_handle, \"fragment shader\");\n\n    // Link\n    bd->ShaderHandle = glCreateProgram();\n    glAttachShader(bd->ShaderHandle, vert_handle);\n    glAttachShader(bd->ShaderHandle, frag_handle);\n    glLinkProgram(bd->ShaderHandle);\n    CheckProgram(bd->ShaderHandle, \"shader program\");\n\n    glDetachShader(bd->ShaderHandle, vert_handle);\n    glDetachShader(bd->ShaderHandle, frag_handle);\n    glDeleteShader(vert_handle);\n    glDeleteShader(frag_handle);\n\n    bd->AttribLocationTex = glGetUniformLocation(bd->ShaderHandle, \"Texture\");\n    bd->AttribLocationProjMtx = glGetUniformLocation(bd->ShaderHandle, \"ProjMtx\");\n    bd->AttribLocationVtxPos = (GLuint)glGetAttribLocation(bd->ShaderHandle, \"Position\");\n    bd->AttribLocationVtxUV = (GLuint)glGetAttribLocation(bd->ShaderHandle, \"UV\");\n    bd->AttribLocationVtxColor = (GLuint)glGetAttribLocation(bd->ShaderHandle, \"Color\");\n\n    // Create buffers\n    glGenBuffers(1, &bd->VboHandle);\n    glGenBuffers(1, &bd->ElementsHandle);\n\n    ImGui_ImplOpenGL3_CreateFontsTexture();\n\n    // Restore modified GL state\n    glBindTexture(GL_TEXTURE_2D, last_texture);\n    glBindBuffer(GL_ARRAY_BUFFER, last_array_buffer);\n#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_BIND_BUFFER_PIXEL_UNPACK\n    if (bd->GlVersion >= 210) { glBindBuffer(GL_PIXEL_UNPACK_BUFFER, last_pixel_unpack_buffer); }\n#endif\n#ifdef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY\n    glBindVertexArray(last_vertex_array);\n#endif\n\n    return true;\n}\n\nvoid    ImGui_ImplOpenGL3_DestroyDeviceObjects()\n{\n    ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData();\n    if (bd->VboHandle)      { glDeleteBuffers(1, &bd->VboHandle); bd->VboHandle = 0; }\n    if (bd->ElementsHandle) { glDeleteBuffers(1, &bd->ElementsHandle); bd->ElementsHandle = 0; }\n    if (bd->ShaderHandle)   { glDeleteProgram(bd->ShaderHandle); bd->ShaderHandle = 0; }\n    ImGui_ImplOpenGL3_DestroyFontsTexture();\n}\n\n//--------------------------------------------------------------------------------------------------------\n// MULTI-VIEWPORT / PLATFORM INTERFACE SUPPORT\n// This is an _advanced_ and _optional_ feature, allowing the backend to create and handle multiple viewports simultaneously.\n// If you are new to dear imgui or creating a new binding for dear imgui, it is recommended that you completely ignore this section first..\n//--------------------------------------------------------------------------------------------------------\n\nstatic void ImGui_ImplOpenGL3_RenderWindow(ImGuiViewport* viewport, void*)\n{\n    if (!(viewport->Flags & ImGuiViewportFlags_NoRendererClear))\n    {\n        ImVec4 clear_color = ImVec4(0.0f, 0.0f, 0.0f, 1.0f);\n        glClearColor(clear_color.x, clear_color.y, clear_color.z, clear_color.w);\n        glClear(GL_COLOR_BUFFER_BIT);\n    }\n    ImGui_ImplOpenGL3_RenderDrawData(viewport->DrawData);\n}\n\nstatic void ImGui_ImplOpenGL3_InitPlatformInterface()\n{\n    ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();\n    platform_io.Renderer_RenderWindow = ImGui_ImplOpenGL3_RenderWindow;\n}\n\nstatic void ImGui_ImplOpenGL3_ShutdownPlatformInterface()\n{\n    ImGui::DestroyPlatformWindows();\n}\n\n//-----------------------------------------------------------------------------\n\n#if defined(__GNUC__)\n#pragma GCC diagnostic pop\n#endif\n#if defined(__clang__)\n#pragma clang diagnostic pop\n#endif\n\n#endif // #ifndef IMGUI_DISABLE\n"
  },
  {
    "path": "vendor/imgui_custom/backends/imgui_impl_opengl3.h",
    "content": "// dear imgui: Renderer Backend for modern OpenGL with shaders / programmatic pipeline\n// - Desktop GL: 2.x 3.x 4.x\n// - Embedded GL: ES 2.0 (WebGL 1.0), ES 3.0 (WebGL 2.0)\n// This needs to be used along with a Platform Backend (e.g. GLFW, SDL, Win32, custom..)\n\n// Implemented features:\n//  [X] Renderer: User texture binding. Use 'GLuint' OpenGL texture identifier as void*/ImTextureID. Read the FAQ about ImTextureID!\n//  [X] Renderer: Large meshes support (64k+ vertices) with 16-bit indices (Desktop OpenGL only).\n//  [X] Renderer: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'.\n\n// About WebGL/ES:\n// - You need to '#define IMGUI_IMPL_OPENGL_ES2' or '#define IMGUI_IMPL_OPENGL_ES3' to use WebGL or OpenGL ES.\n// - This is done automatically on iOS, Android and Emscripten targets.\n// - For other targets, the define needs to be visible from the imgui_impl_opengl3.cpp compilation unit. If unsure, define globally or in imconfig.h.\n\n// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.\n// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.\n// Learn about Dear ImGui:\n// - FAQ                  https://dearimgui.com/faq\n// - Getting Started      https://dearimgui.com/getting-started\n// - Documentation        https://dearimgui.com/docs (same as your local docs/ folder).\n// - Introduction, links and more at the top of imgui.cpp\n\n// About GLSL version:\n//  The 'glsl_version' initialization parameter should be nullptr (default) or a \"#version XXX\" string.\n//  On computer platform the GLSL version default to \"#version 130\". On OpenGL ES 3 platform it defaults to \"#version 300 es\"\n//  Only override if your GL version doesn't handle this GLSL version. See GLSL version table at the top of imgui_impl_opengl3.cpp.\n\n#pragma once\n#include \"imgui.h\"      // IMGUI_IMPL_API\n#ifndef IMGUI_DISABLE\n\n// Backend API\nIMGUI_IMPL_API bool     ImGui_ImplOpenGL3_Init(const char* glsl_version = nullptr);\nIMGUI_IMPL_API void     ImGui_ImplOpenGL3_Shutdown();\nIMGUI_IMPL_API void     ImGui_ImplOpenGL3_NewFrame();\nIMGUI_IMPL_API void     ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data);\n\n// (Optional) Called by Init/NewFrame/Shutdown\nIMGUI_IMPL_API bool     ImGui_ImplOpenGL3_CreateFontsTexture();\nIMGUI_IMPL_API void     ImGui_ImplOpenGL3_DestroyFontsTexture();\nIMGUI_IMPL_API bool     ImGui_ImplOpenGL3_CreateDeviceObjects();\nIMGUI_IMPL_API void     ImGui_ImplOpenGL3_DestroyDeviceObjects();\n\n// Specific OpenGL ES versions\n//#define IMGUI_IMPL_OPENGL_ES2     // Auto-detected on Emscripten\n//#define IMGUI_IMPL_OPENGL_ES3     // Auto-detected on iOS/Android\n\n// You can explicitly select GLES2 or GLES3 API by using one of the '#define IMGUI_IMPL_OPENGL_LOADER_XXX' in imconfig.h or compiler command-line.\n#if !defined(IMGUI_IMPL_OPENGL_ES2) \\\n && !defined(IMGUI_IMPL_OPENGL_ES3)\n\n// Try to detect GLES on matching platforms\n#if defined(__APPLE__)\n#include <TargetConditionals.h>\n#endif\n#if (defined(__APPLE__) && (TARGET_OS_IOS || TARGET_OS_TV)) || (defined(__ANDROID__))\n#define IMGUI_IMPL_OPENGL_ES3               // iOS, Android  -> GL ES 3, \"#version 300 es\"\n#elif defined(__EMSCRIPTEN__) || defined(__amigaos4__)\n#define IMGUI_IMPL_OPENGL_ES2               // Emscripten    -> GL ES 2, \"#version 100\"\n#else\n// Otherwise imgui_impl_opengl3_loader.h will be used.\n#endif\n\n#endif\n\n#endif // #ifndef IMGUI_DISABLE\n"
  },
  {
    "path": "vendor/imgui_custom/backends/imgui_impl_opengl3_loader.h",
    "content": "//-----------------------------------------------------------------------------\n// About imgui_impl_opengl3_loader.h:\n//\n// We embed our own OpenGL loader to not require user to provide their own or to have to use ours,\n// which proved to be endless problems for users.\n// Our loader is custom-generated, based on gl3w but automatically filtered to only include\n// enums/functions that we use in our imgui_impl_opengl3.cpp source file in order to be small.\n//\n// YOU SHOULD NOT NEED TO INCLUDE/USE THIS DIRECTLY. THIS IS USED BY imgui_impl_opengl3.cpp ONLY.\n// THE REST OF YOUR APP SHOULD USE A DIFFERENT GL LOADER: ANY GL LOADER OF YOUR CHOICE.\n//\n// IF YOU GET BUILD ERRORS IN THIS FILE (commonly macro redefinitions or function redefinitions):\n// IT LIKELY MEANS THAT YOU ARE BUILDING 'imgui_impl_opengl3.cpp' OR INCUDING 'imgui_impl_opengl3_loader.h'\n// IN THE SAME COMPILATION UNIT AS ONE OF YOUR FILE WHICH IS USING A THIRD-PARTY OPENGL LOADER.\n// (e.g. COULD HAPPEN IF YOU ARE DOING A UNITY/JUMBO BUILD, OR INCLUDING .CPP FILES FROM OTHERS)\n// YOU SHOULD NOT BUILD BOTH IN THE SAME COMPILATION UNIT.\n// BUT IF YOU REALLY WANT TO, you can '#define IMGUI_IMPL_OPENGL_LOADER_CUSTOM' and imgui_impl_opengl3.cpp\n// WILL NOT BE USING OUR LOADER, AND INSTEAD EXPECT ANOTHER/YOUR LOADER TO BE AVAILABLE IN THE COMPILATION UNIT.\n//\n// Regenerate with:\n//   python gl3w_gen.py --output ../imgui/backends/imgui_impl_opengl3_loader.h --ref ../imgui/backends/imgui_impl_opengl3.cpp ./extra_symbols.txt\n//\n// More info:\n//   https://github.com/dearimgui/gl3w_stripped\n//   https://github.com/ocornut/imgui/issues/4445\n//-----------------------------------------------------------------------------\n\n/*\n * This file was generated with gl3w_gen.py, part of imgl3w\n * (hosted at https://github.com/dearimgui/gl3w_stripped)\n *\n * This is free and unencumbered software released into the public domain.\n *\n * Anyone is free to copy, modify, publish, use, compile, sell, or\n * distribute this software, either in source code form or as a compiled\n * binary, for any purpose, commercial or non-commercial, and by any\n * means.\n *\n * In jurisdictions that recognize copyright laws, the author or authors\n * of this software dedicate any and all copyright interest in the\n * software to the public domain. We make this dedication for the benefit\n * of the public at large and to the detriment of our heirs and\n * successors. We intend this dedication to be an overt act of\n * relinquishment in perpetuity of all present and future rights to this\n * software under copyright law.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\n * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR\n * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,\n * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\n * OTHER DEALINGS IN THE SOFTWARE.\n */\n\n#ifndef __gl3w_h_\n#define __gl3w_h_\n\n#if defined(__SWITCH__)\n#include <EGL/egl.h>\n#endif\n\n// Adapted from KHR/khrplatform.h to avoid including entire file.\n#ifndef __khrplatform_h_\ntypedef          float         khronos_float_t;\ntypedef signed   char          khronos_int8_t;\ntypedef unsigned char          khronos_uint8_t;\ntypedef signed   short int     khronos_int16_t;\ntypedef unsigned short int     khronos_uint16_t;\n#ifdef _WIN64\ntypedef signed   long long int khronos_intptr_t;\ntypedef signed   long long int khronos_ssize_t;\n#else\ntypedef signed   long  int     khronos_intptr_t;\ntypedef signed   long  int     khronos_ssize_t;\n#endif\n\n#if defined(_MSC_VER) && !defined(__clang__)\ntypedef signed   __int64       khronos_int64_t;\ntypedef unsigned __int64       khronos_uint64_t;\n#elif (defined(__clang__) || defined(__GNUC__)) && (__cplusplus < 201100)\n#include <stdint.h>\ntypedef          int64_t       khronos_int64_t;\ntypedef          uint64_t      khronos_uint64_t;\n#else\ntypedef signed   long long     khronos_int64_t;\ntypedef unsigned long long     khronos_uint64_t;\n#endif\n#endif  // __khrplatform_h_\n\n#ifndef __gl_glcorearb_h_\n#define __gl_glcorearb_h_ 1\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n/*\n** Copyright 2013-2020 The Khronos Group Inc.\n** SPDX-License-Identifier: MIT\n**\n** This header is generated from the Khronos OpenGL / OpenGL ES XML\n** API Registry. The current version of the Registry, generator scripts\n** used to make the header, and the header can be found at\n**   https://github.com/KhronosGroup/OpenGL-Registry\n*/\n#if defined(_WIN32) && !defined(APIENTRY) && !defined(__CYGWIN__) && !defined(__SCITECH_SNAP__)\n#ifndef WIN32_LEAN_AND_MEAN\n#define WIN32_LEAN_AND_MEAN 1\n#endif\n#include <windows.h>\n#endif\n#ifndef APIENTRY\n#define APIENTRY\n#endif\n#ifndef APIENTRYP\n#define APIENTRYP APIENTRY *\n#endif\n#ifndef GLAPI\n#define GLAPI extern\n#endif\n/* glcorearb.h is for use with OpenGL core profile implementations.\n** It should should be placed in the same directory as gl.h and\n** included as <GL/glcorearb.h>.\n**\n** glcorearb.h includes only APIs in the latest OpenGL core profile\n** implementation together with APIs in newer ARB extensions which\n** can be supported by the core profile. It does not, and never will\n** include functionality removed from the core profile, such as\n** fixed-function vertex and fragment processing.\n**\n** Do not #include both <GL/glcorearb.h> and either of <GL/gl.h> or\n** <GL/glext.h> in the same source file.\n*/\n/* Generated C header for:\n * API: gl\n * Profile: core\n * Versions considered: .*\n * Versions emitted: .*\n * Default extensions included: glcore\n * Additional extensions included: _nomatch_^\n * Extensions removed: _nomatch_^\n */\n#ifndef GL_VERSION_1_0\ntypedef void GLvoid;\ntypedef unsigned int GLenum;\n\ntypedef khronos_float_t GLfloat;\ntypedef int GLint;\ntypedef int GLsizei;\ntypedef unsigned int GLbitfield;\ntypedef double GLdouble;\ntypedef unsigned int GLuint;\ntypedef unsigned char GLboolean;\ntypedef khronos_uint8_t GLubyte;\n#define GL_COLOR_BUFFER_BIT               0x00004000\n#define GL_FALSE                          0\n#define GL_TRUE                           1\n#define GL_TRIANGLES                      0x0004\n#define GL_ONE                            1\n#define GL_SRC_ALPHA                      0x0302\n#define GL_ONE_MINUS_SRC_ALPHA            0x0303\n#define GL_FRONT                          0x0404\n#define GL_BACK                           0x0405\n#define GL_FRONT_AND_BACK                 0x0408\n#define GL_POLYGON_MODE                   0x0B40\n#define GL_CULL_FACE                      0x0B44\n#define GL_DEPTH_TEST                     0x0B71\n#define GL_STENCIL_TEST                   0x0B90\n#define GL_VIEWPORT                       0x0BA2\n#define GL_BLEND                          0x0BE2\n#define GL_SCISSOR_BOX                    0x0C10\n#define GL_SCISSOR_TEST                   0x0C11\n#define GL_UNPACK_ROW_LENGTH              0x0CF2\n#define GL_PACK_ALIGNMENT                 0x0D05\n#define GL_TEXTURE_2D                     0x0DE1\n#define GL_UNSIGNED_BYTE                  0x1401\n#define GL_UNSIGNED_SHORT                 0x1403\n#define GL_UNSIGNED_INT                   0x1405\n#define GL_FLOAT                          0x1406\n#define GL_RGBA                           0x1908\n#define GL_FILL                           0x1B02\n#define GL_VENDOR                         0x1F00\n#define GL_RENDERER                       0x1F01\n#define GL_VERSION                        0x1F02\n#define GL_EXTENSIONS                     0x1F03\n#define GL_LINEAR                         0x2601\n#define GL_TEXTURE_MAG_FILTER             0x2800\n#define GL_TEXTURE_MIN_FILTER             0x2801\ntypedef void (APIENTRYP PFNGLPOLYGONMODEPROC) (GLenum face, GLenum mode);\ntypedef void (APIENTRYP PFNGLSCISSORPROC) (GLint x, GLint y, GLsizei width, GLsizei height);\ntypedef void (APIENTRYP PFNGLTEXPARAMETERIPROC) (GLenum target, GLenum pname, GLint param);\ntypedef void (APIENTRYP PFNGLTEXIMAGE2DPROC) (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void *pixels);\ntypedef void (APIENTRYP PFNGLCLEARPROC) (GLbitfield mask);\ntypedef void (APIENTRYP PFNGLCLEARCOLORPROC) (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha);\ntypedef void (APIENTRYP PFNGLDISABLEPROC) (GLenum cap);\ntypedef void (APIENTRYP PFNGLENABLEPROC) (GLenum cap);\ntypedef void (APIENTRYP PFNGLFLUSHPROC) (void);\ntypedef void (APIENTRYP PFNGLPIXELSTOREIPROC) (GLenum pname, GLint param);\ntypedef void (APIENTRYP PFNGLREADPIXELSPROC) (GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void *pixels);\ntypedef GLenum (APIENTRYP PFNGLGETERRORPROC) (void);\ntypedef void (APIENTRYP PFNGLGETINTEGERVPROC) (GLenum pname, GLint *data);\ntypedef const GLubyte *(APIENTRYP PFNGLGETSTRINGPROC) (GLenum name);\ntypedef GLboolean (APIENTRYP PFNGLISENABLEDPROC) (GLenum cap);\ntypedef void (APIENTRYP PFNGLVIEWPORTPROC) (GLint x, GLint y, GLsizei width, GLsizei height);\n#ifdef GL_GLEXT_PROTOTYPES\nGLAPI void APIENTRY glPolygonMode (GLenum face, GLenum mode);\nGLAPI void APIENTRY glScissor (GLint x, GLint y, GLsizei width, GLsizei height);\nGLAPI void APIENTRY glTexParameteri (GLenum target, GLenum pname, GLint param);\nGLAPI void APIENTRY glTexImage2D (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void *pixels);\nGLAPI void APIENTRY glClear (GLbitfield mask);\nGLAPI void APIENTRY glClearColor (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha);\nGLAPI void APIENTRY glDisable (GLenum cap);\nGLAPI void APIENTRY glEnable (GLenum cap);\nGLAPI void APIENTRY glFlush (void);\nGLAPI void APIENTRY glPixelStorei (GLenum pname, GLint param);\nGLAPI void APIENTRY glReadPixels (GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void *pixels);\nGLAPI GLenum APIENTRY glGetError (void);\nGLAPI void APIENTRY glGetIntegerv (GLenum pname, GLint *data);\nGLAPI const GLubyte *APIENTRY glGetString (GLenum name);\nGLAPI GLboolean APIENTRY glIsEnabled (GLenum cap);\nGLAPI void APIENTRY glViewport (GLint x, GLint y, GLsizei width, GLsizei height);\n#endif\n#endif /* GL_VERSION_1_0 */\n#ifndef GL_VERSION_1_1\ntypedef khronos_float_t GLclampf;\ntypedef double GLclampd;\n#define GL_TEXTURE_BINDING_2D             0x8069\ntypedef void (APIENTRYP PFNGLDRAWELEMENTSPROC) (GLenum mode, GLsizei count, GLenum type, const void *indices);\ntypedef void (APIENTRYP PFNGLBINDTEXTUREPROC) (GLenum target, GLuint texture);\ntypedef void (APIENTRYP PFNGLDELETETEXTURESPROC) (GLsizei n, const GLuint *textures);\ntypedef void (APIENTRYP PFNGLGENTEXTURESPROC) (GLsizei n, GLuint *textures);\n#ifdef GL_GLEXT_PROTOTYPES\nGLAPI void APIENTRY glDrawElements (GLenum mode, GLsizei count, GLenum type, const void *indices);\nGLAPI void APIENTRY glBindTexture (GLenum target, GLuint texture);\nGLAPI void APIENTRY glDeleteTextures (GLsizei n, const GLuint *textures);\nGLAPI void APIENTRY glGenTextures (GLsizei n, GLuint *textures);\n#endif\n#endif /* GL_VERSION_1_1 */\n#ifndef GL_VERSION_1_3\n#define GL_TEXTURE0                       0x84C0\n#define GL_ACTIVE_TEXTURE                 0x84E0\ntypedef void (APIENTRYP PFNGLACTIVETEXTUREPROC) (GLenum texture);\n#ifdef GL_GLEXT_PROTOTYPES\nGLAPI void APIENTRY glActiveTexture (GLenum texture);\n#endif\n#endif /* GL_VERSION_1_3 */\n#ifndef GL_VERSION_1_4\n#define GL_BLEND_DST_RGB                  0x80C8\n#define GL_BLEND_SRC_RGB                  0x80C9\n#define GL_BLEND_DST_ALPHA                0x80CA\n#define GL_BLEND_SRC_ALPHA                0x80CB\n#define GL_FUNC_ADD                       0x8006\ntypedef void (APIENTRYP PFNGLBLENDFUNCSEPARATEPROC) (GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha);\ntypedef void (APIENTRYP PFNGLBLENDEQUATIONPROC) (GLenum mode);\n#ifdef GL_GLEXT_PROTOTYPES\nGLAPI void APIENTRY glBlendFuncSeparate (GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha);\nGLAPI void APIENTRY glBlendEquation (GLenum mode);\n#endif\n#endif /* GL_VERSION_1_4 */\n#ifndef GL_VERSION_1_5\ntypedef khronos_ssize_t GLsizeiptr;\ntypedef khronos_intptr_t GLintptr;\n#define GL_ARRAY_BUFFER                   0x8892\n#define GL_ELEMENT_ARRAY_BUFFER           0x8893\n#define GL_ARRAY_BUFFER_BINDING           0x8894\n#define GL_ELEMENT_ARRAY_BUFFER_BINDING   0x8895\n#define GL_STREAM_DRAW                    0x88E0\n#define GL_PIXEL_UNPACK_BUFFER            0x88EC\n#define GL_PIXEL_UNPACK_BUFFER_BINDING    0x88EF\ntypedef void (APIENTRYP PFNGLBINDBUFFERPROC) (GLenum target, GLuint buffer);\ntypedef void (APIENTRYP PFNGLDELETEBUFFERSPROC) (GLsizei n, const GLuint *buffers);\ntypedef void (APIENTRYP PFNGLGENBUFFERSPROC) (GLsizei n, GLuint *buffers);\ntypedef void (APIENTRYP PFNGLBUFFERDATAPROC) (GLenum target, GLsizeiptr size, const void *data, GLenum usage);\ntypedef void (APIENTRYP PFNGLBUFFERSUBDATAPROC) (GLenum target, GLintptr offset, GLsizeiptr size, const void *data);\n#ifdef GL_GLEXT_PROTOTYPES\nGLAPI void APIENTRY glBindBuffer (GLenum target, GLuint buffer);\nGLAPI void APIENTRY glDeleteBuffers (GLsizei n, const GLuint *buffers);\nGLAPI void APIENTRY glGenBuffers (GLsizei n, GLuint *buffers);\nGLAPI void APIENTRY glBufferData (GLenum target, GLsizeiptr size, const void *data, GLenum usage);\nGLAPI void APIENTRY glBufferSubData (GLenum target, GLintptr offset, GLsizeiptr size, const void *data);\n#endif\n#endif /* GL_VERSION_1_5 */\n#ifndef GL_VERSION_2_0\ntypedef char GLchar;\ntypedef khronos_int16_t GLshort;\ntypedef khronos_int8_t GLbyte;\ntypedef khronos_uint16_t GLushort;\n#define GL_BLEND_EQUATION_RGB             0x8009\n#define GL_VERTEX_ATTRIB_ARRAY_ENABLED    0x8622\n#define GL_VERTEX_ATTRIB_ARRAY_SIZE       0x8623\n#define GL_VERTEX_ATTRIB_ARRAY_STRIDE     0x8624\n#define GL_VERTEX_ATTRIB_ARRAY_TYPE       0x8625\n#define GL_VERTEX_ATTRIB_ARRAY_POINTER    0x8645\n#define GL_BLEND_EQUATION_ALPHA           0x883D\n#define GL_VERTEX_ATTRIB_ARRAY_NORMALIZED 0x886A\n#define GL_FRAGMENT_SHADER                0x8B30\n#define GL_VERTEX_SHADER                  0x8B31\n#define GL_COMPILE_STATUS                 0x8B81\n#define GL_LINK_STATUS                    0x8B82\n#define GL_INFO_LOG_LENGTH                0x8B84\n#define GL_CURRENT_PROGRAM                0x8B8D\n#define GL_UPPER_LEFT                     0x8CA2\ntypedef void (APIENTRYP PFNGLBLENDEQUATIONSEPARATEPROC) (GLenum modeRGB, GLenum modeAlpha);\ntypedef void (APIENTRYP PFNGLATTACHSHADERPROC) (GLuint program, GLuint shader);\ntypedef void (APIENTRYP PFNGLCOMPILESHADERPROC) (GLuint shader);\ntypedef GLuint (APIENTRYP PFNGLCREATEPROGRAMPROC) (void);\ntypedef GLuint (APIENTRYP PFNGLCREATESHADERPROC) (GLenum type);\ntypedef void (APIENTRYP PFNGLDELETEPROGRAMPROC) (GLuint program);\ntypedef void (APIENTRYP PFNGLDELETESHADERPROC) (GLuint shader);\ntypedef void (APIENTRYP PFNGLDETACHSHADERPROC) (GLuint program, GLuint shader);\ntypedef void (APIENTRYP PFNGLDISABLEVERTEXATTRIBARRAYPROC) (GLuint index);\ntypedef void (APIENTRYP PFNGLENABLEVERTEXATTRIBARRAYPROC) (GLuint index);\ntypedef GLint (APIENTRYP PFNGLGETATTRIBLOCATIONPROC) (GLuint program, const GLchar *name);\ntypedef void (APIENTRYP PFNGLGETPROGRAMIVPROC) (GLuint program, GLenum pname, GLint *params);\ntypedef void (APIENTRYP PFNGLGETPROGRAMINFOLOGPROC) (GLuint program, GLsizei bufSize, GLsizei *length, GLchar *infoLog);\ntypedef void (APIENTRYP PFNGLGETSHADERIVPROC) (GLuint shader, GLenum pname, GLint *params);\ntypedef void (APIENTRYP PFNGLGETSHADERINFOLOGPROC) (GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *infoLog);\ntypedef GLint (APIENTRYP PFNGLGETUNIFORMLOCATIONPROC) (GLuint program, const GLchar *name);\ntypedef void (APIENTRYP PFNGLGETVERTEXATTRIBIVPROC) (GLuint index, GLenum pname, GLint *params);\ntypedef void (APIENTRYP PFNGLGETVERTEXATTRIBPOINTERVPROC) (GLuint index, GLenum pname, void **pointer);\ntypedef GLboolean (APIENTRYP PFNGLISPROGRAMPROC) (GLuint program);\ntypedef void (APIENTRYP PFNGLLINKPROGRAMPROC) (GLuint program);\ntypedef void (APIENTRYP PFNGLSHADERSOURCEPROC) (GLuint shader, GLsizei count, const GLchar *const*string, const GLint *length);\ntypedef void (APIENTRYP PFNGLUSEPROGRAMPROC) (GLuint program);\ntypedef void (APIENTRYP PFNGLUNIFORM1IPROC) (GLint location, GLint v0);\ntypedef void (APIENTRYP PFNGLUNIFORMMATRIX4FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);\ntypedef void (APIENTRYP PFNGLVERTEXATTRIBPOINTERPROC) (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *pointer);\n#ifdef GL_GLEXT_PROTOTYPES\nGLAPI void APIENTRY glBlendEquationSeparate (GLenum modeRGB, GLenum modeAlpha);\nGLAPI void APIENTRY glAttachShader (GLuint program, GLuint shader);\nGLAPI void APIENTRY glCompileShader (GLuint shader);\nGLAPI GLuint APIENTRY glCreateProgram (void);\nGLAPI GLuint APIENTRY glCreateShader (GLenum type);\nGLAPI void APIENTRY glDeleteProgram (GLuint program);\nGLAPI void APIENTRY glDeleteShader (GLuint shader);\nGLAPI void APIENTRY glDetachShader (GLuint program, GLuint shader);\nGLAPI void APIENTRY glDisableVertexAttribArray (GLuint index);\nGLAPI void APIENTRY glEnableVertexAttribArray (GLuint index);\nGLAPI GLint APIENTRY glGetAttribLocation (GLuint program, const GLchar *name);\nGLAPI void APIENTRY glGetProgramiv (GLuint program, GLenum pname, GLint *params);\nGLAPI void APIENTRY glGetProgramInfoLog (GLuint program, GLsizei bufSize, GLsizei *length, GLchar *infoLog);\nGLAPI void APIENTRY glGetShaderiv (GLuint shader, GLenum pname, GLint *params);\nGLAPI void APIENTRY glGetShaderInfoLog (GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *infoLog);\nGLAPI GLint APIENTRY glGetUniformLocation (GLuint program, const GLchar *name);\nGLAPI void APIENTRY glGetVertexAttribiv (GLuint index, GLenum pname, GLint *params);\nGLAPI void APIENTRY glGetVertexAttribPointerv (GLuint index, GLenum pname, void **pointer);\nGLAPI GLboolean APIENTRY glIsProgram (GLuint program);\nGLAPI void APIENTRY glLinkProgram (GLuint program);\nGLAPI void APIENTRY glShaderSource (GLuint shader, GLsizei count, const GLchar *const*string, const GLint *length);\nGLAPI void APIENTRY glUseProgram (GLuint program);\nGLAPI void APIENTRY glUniform1i (GLint location, GLint v0);\nGLAPI void APIENTRY glUniformMatrix4fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);\nGLAPI void APIENTRY glVertexAttribPointer (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *pointer);\n#endif\n#endif /* GL_VERSION_2_0 */\n#ifndef GL_VERSION_3_0\ntypedef khronos_uint16_t GLhalf;\n#define GL_MAJOR_VERSION                  0x821B\n#define GL_MINOR_VERSION                  0x821C\n#define GL_NUM_EXTENSIONS                 0x821D\n#define GL_FRAMEBUFFER_SRGB               0x8DB9\n#define GL_VERTEX_ARRAY_BINDING           0x85B5\ntypedef void (APIENTRYP PFNGLGETBOOLEANI_VPROC) (GLenum target, GLuint index, GLboolean *data);\ntypedef void (APIENTRYP PFNGLGETINTEGERI_VPROC) (GLenum target, GLuint index, GLint *data);\ntypedef const GLubyte *(APIENTRYP PFNGLGETSTRINGIPROC) (GLenum name, GLuint index);\ntypedef void (APIENTRYP PFNGLBINDVERTEXARRAYPROC) (GLuint array);\ntypedef void (APIENTRYP PFNGLDELETEVERTEXARRAYSPROC) (GLsizei n, const GLuint *arrays);\ntypedef void (APIENTRYP PFNGLGENVERTEXARRAYSPROC) (GLsizei n, GLuint *arrays);\n#ifdef GL_GLEXT_PROTOTYPES\nGLAPI const GLubyte *APIENTRY glGetStringi (GLenum name, GLuint index);\nGLAPI void APIENTRY glBindVertexArray (GLuint array);\nGLAPI void APIENTRY glDeleteVertexArrays (GLsizei n, const GLuint *arrays);\nGLAPI void APIENTRY glGenVertexArrays (GLsizei n, GLuint *arrays);\n#endif\n#endif /* GL_VERSION_3_0 */\n#ifndef GL_VERSION_3_1\n#define GL_VERSION_3_1 1\n#define GL_PRIMITIVE_RESTART              0x8F9D\n#endif /* GL_VERSION_3_1 */\n#ifndef GL_VERSION_3_2\n#define GL_VERSION_3_2 1\ntypedef struct __GLsync *GLsync;\ntypedef khronos_uint64_t GLuint64;\ntypedef khronos_int64_t GLint64;\n#define GL_CONTEXT_COMPATIBILITY_PROFILE_BIT 0x00000002\n#define GL_CONTEXT_PROFILE_MASK           0x9126\ntypedef void (APIENTRYP PFNGLDRAWELEMENTSBASEVERTEXPROC) (GLenum mode, GLsizei count, GLenum type, const void *indices, GLint basevertex);\ntypedef void (APIENTRYP PFNGLGETINTEGER64I_VPROC) (GLenum target, GLuint index, GLint64 *data);\n#ifdef GL_GLEXT_PROTOTYPES\nGLAPI void APIENTRY glDrawElementsBaseVertex (GLenum mode, GLsizei count, GLenum type, const void *indices, GLint basevertex);\n#endif\n#endif /* GL_VERSION_3_2 */\n#ifndef GL_VERSION_3_3\n#define GL_VERSION_3_3 1\n#define GL_SAMPLER_BINDING                0x8919\ntypedef void (APIENTRYP PFNGLBINDSAMPLERPROC) (GLuint unit, GLuint sampler);\n#ifdef GL_GLEXT_PROTOTYPES\nGLAPI void APIENTRY glBindSampler (GLuint unit, GLuint sampler);\n#endif\n#endif /* GL_VERSION_3_3 */\n#ifndef GL_VERSION_4_1\ntypedef void (APIENTRYP PFNGLGETFLOATI_VPROC) (GLenum target, GLuint index, GLfloat *data);\ntypedef void (APIENTRYP PFNGLGETDOUBLEI_VPROC) (GLenum target, GLuint index, GLdouble *data);\n#endif /* GL_VERSION_4_1 */\n#ifndef GL_VERSION_4_3\ntypedef void (APIENTRY  *GLDEBUGPROC)(GLenum source,GLenum type,GLuint id,GLenum severity,GLsizei length,const GLchar *message,const void *userParam);\n#endif /* GL_VERSION_4_3 */\n#ifndef GL_VERSION_4_5\n#define GL_CLIP_ORIGIN                    0x935C\ntypedef void (APIENTRYP PFNGLGETTRANSFORMFEEDBACKI_VPROC) (GLuint xfb, GLenum pname, GLuint index, GLint *param);\ntypedef void (APIENTRYP PFNGLGETTRANSFORMFEEDBACKI64_VPROC) (GLuint xfb, GLenum pname, GLuint index, GLint64 *param);\n#endif /* GL_VERSION_4_5 */\n#ifndef GL_ARB_bindless_texture\ntypedef khronos_uint64_t GLuint64EXT;\n#endif /* GL_ARB_bindless_texture */\n#ifndef GL_ARB_cl_event\nstruct _cl_context;\nstruct _cl_event;\n#endif /* GL_ARB_cl_event */\n#ifndef GL_ARB_clip_control\n#define GL_ARB_clip_control 1\n#endif /* GL_ARB_clip_control */\n#ifndef GL_ARB_debug_output\ntypedef void (APIENTRY  *GLDEBUGPROCARB)(GLenum source,GLenum type,GLuint id,GLenum severity,GLsizei length,const GLchar *message,const void *userParam);\n#endif /* GL_ARB_debug_output */\n#ifndef GL_EXT_EGL_image_storage\ntypedef void *GLeglImageOES;\n#endif /* GL_EXT_EGL_image_storage */\n#ifndef GL_EXT_direct_state_access\ntypedef void (APIENTRYP PFNGLGETFLOATI_VEXTPROC) (GLenum pname, GLuint index, GLfloat *params);\ntypedef void (APIENTRYP PFNGLGETDOUBLEI_VEXTPROC) (GLenum pname, GLuint index, GLdouble *params);\ntypedef void (APIENTRYP PFNGLGETPOINTERI_VEXTPROC) (GLenum pname, GLuint index, void **params);\ntypedef void (APIENTRYP PFNGLGETVERTEXARRAYINTEGERI_VEXTPROC) (GLuint vaobj, GLuint index, GLenum pname, GLint *param);\ntypedef void (APIENTRYP PFNGLGETVERTEXARRAYPOINTERI_VEXTPROC) (GLuint vaobj, GLuint index, GLenum pname, void **param);\n#endif /* GL_EXT_direct_state_access */\n#ifndef GL_NV_draw_vulkan_image\ntypedef void (APIENTRY  *GLVULKANPROCNV)(void);\n#endif /* GL_NV_draw_vulkan_image */\n#ifndef GL_NV_gpu_shader5\ntypedef khronos_int64_t GLint64EXT;\n#endif /* GL_NV_gpu_shader5 */\n#ifndef GL_NV_vertex_buffer_unified_memory\ntypedef void (APIENTRYP PFNGLGETINTEGERUI64I_VNVPROC) (GLenum value, GLuint index, GLuint64EXT *result);\n#endif /* GL_NV_vertex_buffer_unified_memory */\n#ifdef __cplusplus\n}\n#endif\n#endif\n\n#ifndef GL3W_API\n#define GL3W_API\n#endif\n\n#ifndef __gl_h_\n#define __gl_h_\n#endif\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n#define GL3W_OK 0\n#define GL3W_ERROR_INIT -1\n#define GL3W_ERROR_LIBRARY_OPEN -2\n#define GL3W_ERROR_OPENGL_VERSION -3\n\ntypedef void (*GL3WglProc)(void);\ntypedef GL3WglProc (*GL3WGetProcAddressProc)(const char *proc);\n\n/* gl3w api */\nGL3W_API int imgl3wInit(void);\nGL3W_API int imgl3wInit2(GL3WGetProcAddressProc proc);\nGL3W_API int imgl3wIsSupported(int major, int minor);\nGL3W_API GL3WglProc imgl3wGetProcAddress(const char *proc);\n\n/* gl3w internal state */\nunion ImGL3WProcs {\n    GL3WglProc ptr[59];\n    struct {\n        PFNGLACTIVETEXTUREPROC            ActiveTexture;\n        PFNGLATTACHSHADERPROC             AttachShader;\n        PFNGLBINDBUFFERPROC               BindBuffer;\n        PFNGLBINDSAMPLERPROC              BindSampler;\n        PFNGLBINDTEXTUREPROC              BindTexture;\n        PFNGLBINDVERTEXARRAYPROC          BindVertexArray;\n        PFNGLBLENDEQUATIONPROC            BlendEquation;\n        PFNGLBLENDEQUATIONSEPARATEPROC    BlendEquationSeparate;\n        PFNGLBLENDFUNCSEPARATEPROC        BlendFuncSeparate;\n        PFNGLBUFFERDATAPROC               BufferData;\n        PFNGLBUFFERSUBDATAPROC            BufferSubData;\n        PFNGLCLEARPROC                    Clear;\n        PFNGLCLEARCOLORPROC               ClearColor;\n        PFNGLCOMPILESHADERPROC            CompileShader;\n        PFNGLCREATEPROGRAMPROC            CreateProgram;\n        PFNGLCREATESHADERPROC             CreateShader;\n        PFNGLDELETEBUFFERSPROC            DeleteBuffers;\n        PFNGLDELETEPROGRAMPROC            DeleteProgram;\n        PFNGLDELETESHADERPROC             DeleteShader;\n        PFNGLDELETETEXTURESPROC           DeleteTextures;\n        PFNGLDELETEVERTEXARRAYSPROC       DeleteVertexArrays;\n        PFNGLDETACHSHADERPROC             DetachShader;\n        PFNGLDISABLEPROC                  Disable;\n        PFNGLDISABLEVERTEXATTRIBARRAYPROC DisableVertexAttribArray;\n        PFNGLDRAWELEMENTSPROC             DrawElements;\n        PFNGLDRAWELEMENTSBASEVERTEXPROC   DrawElementsBaseVertex;\n        PFNGLENABLEPROC                   Enable;\n        PFNGLENABLEVERTEXATTRIBARRAYPROC  EnableVertexAttribArray;\n        PFNGLFLUSHPROC                    Flush;\n        PFNGLGENBUFFERSPROC               GenBuffers;\n        PFNGLGENTEXTURESPROC              GenTextures;\n        PFNGLGENVERTEXARRAYSPROC          GenVertexArrays;\n        PFNGLGETATTRIBLOCATIONPROC        GetAttribLocation;\n        PFNGLGETERRORPROC                 GetError;\n        PFNGLGETINTEGERVPROC              GetIntegerv;\n        PFNGLGETPROGRAMINFOLOGPROC        GetProgramInfoLog;\n        PFNGLGETPROGRAMIVPROC             GetProgramiv;\n        PFNGLGETSHADERINFOLOGPROC         GetShaderInfoLog;\n        PFNGLGETSHADERIVPROC              GetShaderiv;\n        PFNGLGETSTRINGPROC                GetString;\n        PFNGLGETSTRINGIPROC               GetStringi;\n        PFNGLGETUNIFORMLOCATIONPROC       GetUniformLocation;\n        PFNGLGETVERTEXATTRIBPOINTERVPROC  GetVertexAttribPointerv;\n        PFNGLGETVERTEXATTRIBIVPROC        GetVertexAttribiv;\n        PFNGLISENABLEDPROC                IsEnabled;\n        PFNGLISPROGRAMPROC                IsProgram;\n        PFNGLLINKPROGRAMPROC              LinkProgram;\n        PFNGLPIXELSTOREIPROC              PixelStorei;\n        PFNGLPOLYGONMODEPROC              PolygonMode;\n        PFNGLREADPIXELSPROC               ReadPixels;\n        PFNGLSCISSORPROC                  Scissor;\n        PFNGLSHADERSOURCEPROC             ShaderSource;\n        PFNGLTEXIMAGE2DPROC               TexImage2D;\n        PFNGLTEXPARAMETERIPROC            TexParameteri;\n        PFNGLUNIFORM1IPROC                Uniform1i;\n        PFNGLUNIFORMMATRIX4FVPROC         UniformMatrix4fv;\n        PFNGLUSEPROGRAMPROC               UseProgram;\n        PFNGLVERTEXATTRIBPOINTERPROC      VertexAttribPointer;\n        PFNGLVIEWPORTPROC                 Viewport;\n    } gl;\n};\n\nGL3W_API extern union ImGL3WProcs imgl3wProcs;\n\n/* OpenGL functions */\n#define glActiveTexture                   imgl3wProcs.gl.ActiveTexture\n#define glAttachShader                    imgl3wProcs.gl.AttachShader\n#define glBindBuffer                      imgl3wProcs.gl.BindBuffer\n#define glBindSampler                     imgl3wProcs.gl.BindSampler\n#define glBindTexture                     imgl3wProcs.gl.BindTexture\n#define glBindVertexArray                 imgl3wProcs.gl.BindVertexArray\n#define glBlendEquation                   imgl3wProcs.gl.BlendEquation\n#define glBlendEquationSeparate           imgl3wProcs.gl.BlendEquationSeparate\n#define glBlendFuncSeparate               imgl3wProcs.gl.BlendFuncSeparate\n#define glBufferData                      imgl3wProcs.gl.BufferData\n#define glBufferSubData                   imgl3wProcs.gl.BufferSubData\n#define glClear                           imgl3wProcs.gl.Clear\n#define glClearColor                      imgl3wProcs.gl.ClearColor\n#define glCompileShader                   imgl3wProcs.gl.CompileShader\n#define glCreateProgram                   imgl3wProcs.gl.CreateProgram\n#define glCreateShader                    imgl3wProcs.gl.CreateShader\n#define glDeleteBuffers                   imgl3wProcs.gl.DeleteBuffers\n#define glDeleteProgram                   imgl3wProcs.gl.DeleteProgram\n#define glDeleteShader                    imgl3wProcs.gl.DeleteShader\n#define glDeleteTextures                  imgl3wProcs.gl.DeleteTextures\n#define glDeleteVertexArrays              imgl3wProcs.gl.DeleteVertexArrays\n#define glDetachShader                    imgl3wProcs.gl.DetachShader\n#define glDisable                         imgl3wProcs.gl.Disable\n#define glDisableVertexAttribArray        imgl3wProcs.gl.DisableVertexAttribArray\n#define glDrawElements                    imgl3wProcs.gl.DrawElements\n#define glDrawElementsBaseVertex          imgl3wProcs.gl.DrawElementsBaseVertex\n#define glEnable                          imgl3wProcs.gl.Enable\n#define glEnableVertexAttribArray         imgl3wProcs.gl.EnableVertexAttribArray\n#define glFlush                           imgl3wProcs.gl.Flush\n#define glGenBuffers                      imgl3wProcs.gl.GenBuffers\n#define glGenTextures                     imgl3wProcs.gl.GenTextures\n#define glGenVertexArrays                 imgl3wProcs.gl.GenVertexArrays\n#define glGetAttribLocation               imgl3wProcs.gl.GetAttribLocation\n#define glGetError                        imgl3wProcs.gl.GetError\n#define glGetIntegerv                     imgl3wProcs.gl.GetIntegerv\n#define glGetProgramInfoLog               imgl3wProcs.gl.GetProgramInfoLog\n#define glGetProgramiv                    imgl3wProcs.gl.GetProgramiv\n#define glGetShaderInfoLog                imgl3wProcs.gl.GetShaderInfoLog\n#define glGetShaderiv                     imgl3wProcs.gl.GetShaderiv\n#define glGetString                       imgl3wProcs.gl.GetString\n#define glGetStringi                      imgl3wProcs.gl.GetStringi\n#define glGetUniformLocation              imgl3wProcs.gl.GetUniformLocation\n#define glGetVertexAttribPointerv         imgl3wProcs.gl.GetVertexAttribPointerv\n#define glGetVertexAttribiv               imgl3wProcs.gl.GetVertexAttribiv\n#define glIsEnabled                       imgl3wProcs.gl.IsEnabled\n#define glIsProgram                       imgl3wProcs.gl.IsProgram\n#define glLinkProgram                     imgl3wProcs.gl.LinkProgram\n#define glPixelStorei                     imgl3wProcs.gl.PixelStorei\n#define glPolygonMode                     imgl3wProcs.gl.PolygonMode\n#define glReadPixels                      imgl3wProcs.gl.ReadPixels\n#define glScissor                         imgl3wProcs.gl.Scissor\n#define glShaderSource                    imgl3wProcs.gl.ShaderSource\n#define glTexImage2D                      imgl3wProcs.gl.TexImage2D\n#define glTexParameteri                   imgl3wProcs.gl.TexParameteri\n#define glUniform1i                       imgl3wProcs.gl.Uniform1i\n#define glUniformMatrix4fv                imgl3wProcs.gl.UniformMatrix4fv\n#define glUseProgram                      imgl3wProcs.gl.UseProgram\n#define glVertexAttribPointer             imgl3wProcs.gl.VertexAttribPointer\n#define glViewport                        imgl3wProcs.gl.Viewport\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif\n\n#ifdef IMGL3W_IMPL\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n#include <stdlib.h>\n\n#define GL3W_ARRAY_SIZE(x)  (sizeof(x) / sizeof((x)[0]))\n\n#if defined(_WIN32)\n#ifndef WIN32_LEAN_AND_MEAN\n#define WIN32_LEAN_AND_MEAN 1\n#endif\n#include <windows.h>\n\nstatic HMODULE libgl;\ntypedef PROC(__stdcall* GL3WglGetProcAddr)(LPCSTR);\nstatic GL3WglGetProcAddr wgl_get_proc_address;\n\nstatic int open_libgl(void)\n{\n    libgl = LoadLibraryA(\"opengl32.dll\");\n    if (!libgl)\n        return GL3W_ERROR_LIBRARY_OPEN;\n    wgl_get_proc_address = (GL3WglGetProcAddr)GetProcAddress(libgl, \"wglGetProcAddress\");\n    return GL3W_OK;\n}\n\nstatic void close_libgl(void) { FreeLibrary(libgl); }\nstatic GL3WglProc get_proc(const char *proc)\n{\n    GL3WglProc res;\n    res = (GL3WglProc)wgl_get_proc_address(proc);\n    if (!res)\n        res = (GL3WglProc)GetProcAddress(libgl, proc);\n    return res;\n}\n#elif defined(__APPLE__)\n#include <dlfcn.h>\n\nstatic void *libgl;\nstatic int open_libgl(void)\n{\n    libgl = dlopen(\"/System/Library/Frameworks/OpenGL.framework/OpenGL\", RTLD_LAZY | RTLD_LOCAL);\n    if (!libgl)\n        return GL3W_ERROR_LIBRARY_OPEN;\n    return GL3W_OK;\n}\n\nstatic void close_libgl(void) { dlclose(libgl); }\n\nstatic GL3WglProc get_proc(const char *proc)\n{\n    GL3WglProc res;\n    *(void **)(&res) = dlsym(libgl, proc);\n    return res;\n}\n#elif defined(__SWITCH__)\n\nstatic GL3WglProc (*glx_get_proc_address)(const GLubyte *);\n\nstatic int open_libgl(void)\n{\n    *(void **)(&glx_get_proc_address) = (void *)&eglGetProcAddress;\n    return GL3W_OK;\n}\n\nstatic void close_libgl(void) {}\n\nstatic GL3WglProc get_proc(const char *proc)\n{\n    GL3WglProc res;\n    res = glx_get_proc_address((const GLubyte *)proc);\n    return res;\n}\n#else\n#include <dlfcn.h>\n\nstatic void *libgl;\nstatic GL3WglProc (*glx_get_proc_address)(const GLubyte *);\n\nstatic int open_libgl(void)\n{\n    // While most systems use libGL.so.1, NetBSD seems to use that libGL.so.3. See https://github.com/ocornut/imgui/issues/6983\n    libgl = dlopen(\"libGL.so\", RTLD_LAZY | RTLD_LOCAL);\n    if (!libgl)\n        libgl = dlopen(\"libGL.so.1\", RTLD_LAZY | RTLD_LOCAL);\n    if (!libgl)\n        libgl = dlopen(\"libGL.so.3\", RTLD_LAZY | RTLD_LOCAL);\n    if (!libgl)\n        return GL3W_ERROR_LIBRARY_OPEN;\n    *(void **)(&glx_get_proc_address) = dlsym(libgl, \"glXGetProcAddressARB\");\n    return GL3W_OK;\n}\n\nstatic void close_libgl(void) { dlclose(libgl); }\n\nstatic GL3WglProc get_proc(const char *proc)\n{\n    GL3WglProc res;\n    res = glx_get_proc_address((const GLubyte *)proc);\n    if (!res)\n        *(void **)(&res) = dlsym(libgl, proc);\n    return res;\n}\n#endif\n\nstatic struct { int major, minor; } version;\n\nstatic int parse_version(void)\n{\n    if (!glGetIntegerv)\n        return GL3W_ERROR_INIT;\n    glGetIntegerv(GL_MAJOR_VERSION, &version.major);\n    glGetIntegerv(GL_MINOR_VERSION, &version.minor);\n    if (version.major == 0 && version.minor == 0)\n    {\n        // Query GL_VERSION in desktop GL 2.x, the string will start with \"<major>.<minor>\"\n        if (const char* gl_version = (const char*)glGetString(GL_VERSION))\n            sscanf(gl_version, \"%d.%d\", &version.major, &version.minor);\n    }\n    if (version.major < 2)\n        return GL3W_ERROR_OPENGL_VERSION;\n    return GL3W_OK;\n}\n\nstatic void load_procs(GL3WGetProcAddressProc proc);\n\nint imgl3wInit(void)\n{\n    int res = open_libgl();\n    if (res)\n        return res;\n    atexit(close_libgl);\n    return imgl3wInit2(get_proc);\n}\n\nint imgl3wInit2(GL3WGetProcAddressProc proc)\n{\n    load_procs(proc);\n    return parse_version();\n}\n\nint imgl3wIsSupported(int major, int minor)\n{\n    if (major < 2)\n        return 0;\n    if (version.major == major)\n        return version.minor >= minor;\n    return version.major >= major;\n}\n\nGL3WglProc imgl3wGetProcAddress(const char *proc) { return get_proc(proc); }\n\nstatic const char *proc_names[] = {\n    \"glActiveTexture\",\n    \"glAttachShader\",\n    \"glBindBuffer\",\n    \"glBindSampler\",\n    \"glBindTexture\",\n    \"glBindVertexArray\",\n    \"glBlendEquation\",\n    \"glBlendEquationSeparate\",\n    \"glBlendFuncSeparate\",\n    \"glBufferData\",\n    \"glBufferSubData\",\n    \"glClear\",\n    \"glClearColor\",\n    \"glCompileShader\",\n    \"glCreateProgram\",\n    \"glCreateShader\",\n    \"glDeleteBuffers\",\n    \"glDeleteProgram\",\n    \"glDeleteShader\",\n    \"glDeleteTextures\",\n    \"glDeleteVertexArrays\",\n    \"glDetachShader\",\n    \"glDisable\",\n    \"glDisableVertexAttribArray\",\n    \"glDrawElements\",\n    \"glDrawElementsBaseVertex\",\n    \"glEnable\",\n    \"glEnableVertexAttribArray\",\n    \"glFlush\",\n    \"glGenBuffers\",\n    \"glGenTextures\",\n    \"glGenVertexArrays\",\n    \"glGetAttribLocation\",\n    \"glGetError\",\n    \"glGetIntegerv\",\n    \"glGetProgramInfoLog\",\n    \"glGetProgramiv\",\n    \"glGetShaderInfoLog\",\n    \"glGetShaderiv\",\n    \"glGetString\",\n    \"glGetStringi\",\n    \"glGetUniformLocation\",\n    \"glGetVertexAttribPointerv\",\n    \"glGetVertexAttribiv\",\n    \"glIsEnabled\",\n    \"glIsProgram\",\n    \"glLinkProgram\",\n    \"glPixelStorei\",\n    \"glPolygonMode\",\n    \"glReadPixels\",\n    \"glScissor\",\n    \"glShaderSource\",\n    \"glTexImage2D\",\n    \"glTexParameteri\",\n    \"glUniform1i\",\n    \"glUniformMatrix4fv\",\n    \"glUseProgram\",\n    \"glVertexAttribPointer\",\n    \"glViewport\",\n};\n\nGL3W_API union ImGL3WProcs imgl3wProcs;\n\nstatic void load_procs(GL3WGetProcAddressProc proc)\n{\n    size_t i;\n    for (i = 0; i < GL3W_ARRAY_SIZE(proc_names); i++)\n        imgl3wProcs.ptr[i] = proc(proc_names[i]);\n}\n\n#ifdef __cplusplus\n}\n#endif\n#endif\n"
  },
  {
    "path": "vendor/minilua/minilua.h",
    "content": "/*\n  minilua.h -- Lua in a single header\n  Project URL: https://github.com/edubart/minilua\n\n  This is Lua 5.4.6 contained in a single header to be bundled in C/C++ applications with ease.\n  Lua is a powerful, efficient, lightweight, embeddable scripting language.\n\n  Do the following in *one* C file to create the implementation:\n    #define LUA_IMPL\n\n  By default it detects the system platform to use, however you could explicitly define one.\n\n  Note that almost no modification was made in the Lua implementation code,\n  thus there are some C variable names that may collide with your code,\n  therefore it is best to declare the Lua implementation in dedicated C file.\n\n  Optionally provide the following defines:\n    LUA_MAKE_LUA     - implement the Lua command line REPL\n\n  LICENSE\n    MIT License, same as Lua, see end of file.\n*/\n\n/* detect system platform */\n#if !defined(LUA_USE_WINDOWS) && !defined(LUA_USE_LINUX) && !defined(LUA_USE_MACOSX) && !defined(LUA_USE_POSIX) && !defined(LUA_USE_C89) && !defined(LUA_USE_IOS)\n#if defined(_WIN32)\n#define LUA_USE_WINDOWS\n#elif defined(__linux__)\n#define LUA_USE_LINUX\n#elif defined(__APPLE__)\n#define LUA_USE_MACOSX\n#elif defined(__SWITCH__) || defined(PLATFORM_DREAMCAST)\n#define LUA_USE_C89\n#else /* probably a POSIX system */\n#define LUA_USE_POSIX\n#define LUA_USE_DLOPEN\n#endif\n#endif\n\n#ifdef LUA_IMPL\n#define LUA_CORE\n/*\n** $Id: lprefix.h $\n** Definitions for Lua code that must come before any other header file\n** See Copyright Notice in lua.h\n*/\n\n#ifndef lprefix_h\n#define lprefix_h\n\n\n/*\n** Allows POSIX/XSI stuff\n*/\n#if !defined(LUA_USE_C89)\t/* { */\n\n#if !defined(_XOPEN_SOURCE)\n#define _XOPEN_SOURCE           600\n#elif _XOPEN_SOURCE == 0\n#undef _XOPEN_SOURCE  /* use -D_XOPEN_SOURCE=0 to undefine it */\n#endif\n\n/*\n** Allows manipulation of large files in gcc and some other compilers\n*/\n#if !defined(LUA_32BITS) && !defined(_FILE_OFFSET_BITS)\n#define _LARGEFILE_SOURCE       1\n#define _FILE_OFFSET_BITS       64\n#endif\n\n#endif\t\t\t\t/* } */\n\n\n/*\n** Windows stuff\n*/\n#if defined(_WIN32)\t/* { */\n\n#if !defined(_CRT_SECURE_NO_WARNINGS)\n#define _CRT_SECURE_NO_WARNINGS  /* avoid warnings about ISO C functions */\n#endif\n\n#endif\t\t\t/* } */\n\n#endif\n\n#endif /* LUA_IMPL */\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n/*\n** $Id: luaconf.h $\n** Configuration file for Lua\n** See Copyright Notice in lua.h\n*/\n\n\n#ifndef luaconf_h\n#define luaconf_h\n\n#include <limits.h>\n#include <stddef.h>\n\n\n/*\n** ===================================================================\n** General Configuration File for Lua\n**\n** Some definitions here can be changed externally, through the compiler\n** (e.g., with '-D' options): They are commented out or protected\n** by '#if !defined' guards. However, several other definitions\n** should be changed directly here, either because they affect the\n** Lua ABI (by making the changes here, you ensure that all software\n** connected to Lua, such as C libraries, will be compiled with the same\n** configuration); or because they are seldom changed.\n**\n** Search for \"@@\" to find all configurable definitions.\n** ===================================================================\n*/\n\n\n/*\n** {====================================================================\n** System Configuration: macros to adapt (if needed) Lua to some\n** particular platform, for instance restricting it to C89.\n** =====================================================================\n*/\n\n/*\n@@ LUA_USE_C89 controls the use of non-ISO-C89 features.\n** Define it if you want Lua to avoid the use of a few C99 features\n** or Windows-specific features on Windows.\n*/\n/* #define LUA_USE_C89 */\n\n\n/*\n** By default, Lua on Windows use (some) specific Windows features\n*/\n#if !defined(LUA_USE_C89) && defined(_WIN32) && !defined(_WIN32_WCE)\n#define LUA_USE_WINDOWS  /* enable goodies for regular Windows */\n#endif\n\n\n#if defined(LUA_USE_WINDOWS)\n#define LUA_DL_DLL\t/* enable support for DLL */\n#define LUA_USE_C89\t/* broadly, Windows is C89 */\n#endif\n\n\n#if defined(LUA_USE_LINUX)\n#define LUA_USE_POSIX\n#define LUA_USE_DLOPEN\t\t/* needs an extra library: -ldl */\n#endif\n\n\n#if defined(LUA_USE_MACOSX)\n#define LUA_USE_POSIX\n#define LUA_USE_DLOPEN\t\t/* MacOS does not need -ldl */\n#endif\n\n\n#if defined(LUA_USE_IOS)\n#define LUA_USE_POSIX\n#define LUA_USE_DLOPEN\n#endif\n\n\n/*\n@@ LUAI_IS32INT is true iff 'int' has (at least) 32 bits.\n*/\n#define LUAI_IS32INT\t((UINT_MAX >> 30) >= 3)\n\n/* }================================================================== */\n\n\n\n/*\n** {==================================================================\n** Configuration for Number types. These options should not be\n** set externally, because any other code connected to Lua must\n** use the same configuration.\n** ===================================================================\n*/\n\n/*\n@@ LUA_INT_TYPE defines the type for Lua integers.\n@@ LUA_FLOAT_TYPE defines the type for Lua floats.\n** Lua should work fine with any mix of these options supported\n** by your C compiler. The usual configurations are 64-bit integers\n** and 'double' (the default), 32-bit integers and 'float' (for\n** restricted platforms), and 'long'/'double' (for C compilers not\n** compliant with C99, which may not have support for 'long long').\n*/\n\n/* predefined options for LUA_INT_TYPE */\n#define LUA_INT_INT\t\t1\n#define LUA_INT_LONG\t\t2\n#define LUA_INT_LONGLONG\t3\n\n/* predefined options for LUA_FLOAT_TYPE */\n#define LUA_FLOAT_FLOAT\t\t1\n#define LUA_FLOAT_DOUBLE\t2\n#define LUA_FLOAT_LONGDOUBLE\t3\n\n\n/* Default configuration ('long long' and 'double', for 64-bit Lua) */\n#define LUA_INT_DEFAULT\t\tLUA_INT_LONGLONG\n#define LUA_FLOAT_DEFAULT\tLUA_FLOAT_DOUBLE\n\n\n/*\n@@ LUA_32BITS enables Lua with 32-bit integers and 32-bit floats.\n*/\n#ifdef PLATFORM_DREAMCAST\n#define LUA_32BITS\t1\n#else\n#define LUA_32BITS\t0\n#endif\n\n\n/*\n@@ LUA_C89_NUMBERS ensures that Lua uses the largest types available for\n** C89 ('long' and 'double'); Windows always has '__int64', so it does\n** not need to use this case.\n*/\n#if defined(LUA_USE_C89) && !defined(LUA_USE_WINDOWS)\n#define LUA_C89_NUMBERS\t\t1\n#else\n#define LUA_C89_NUMBERS\t\t0\n#endif\n\n\n#if LUA_32BITS\t\t/* { */\n/*\n** 32-bit integers and 'float'\n*/\n#if LUAI_IS32INT  /* use 'int' if big enough */\n#define LUA_INT_TYPE\tLUA_INT_INT\n#else  /* otherwise use 'long' */\n#define LUA_INT_TYPE\tLUA_INT_LONG\n#endif\n#define LUA_FLOAT_TYPE\tLUA_FLOAT_FLOAT\n\n#elif LUA_C89_NUMBERS\t/* }{ */\n/*\n** largest types available for C89 ('long' and 'double')\n*/\n#define LUA_INT_TYPE\tLUA_INT_LONG\n#define LUA_FLOAT_TYPE\tLUA_FLOAT_DOUBLE\n\n#else\t\t/* }{ */\n/* use defaults */\n\n#define LUA_INT_TYPE\tLUA_INT_DEFAULT\n#define LUA_FLOAT_TYPE\tLUA_FLOAT_DEFAULT\n\n#endif\t\t\t\t/* } */\n\n\n/* }================================================================== */\n\n\n\n/*\n** {==================================================================\n** Configuration for Paths.\n** ===================================================================\n*/\n\n/*\n** LUA_PATH_SEP is the character that separates templates in a path.\n** LUA_PATH_MARK is the string that marks the substitution points in a\n** template.\n** LUA_EXEC_DIR in a Windows path is replaced by the executable's\n** directory.\n*/\n#define LUA_PATH_SEP            \";\"\n#define LUA_PATH_MARK           \"?\"\n#define LUA_EXEC_DIR            \"!\"\n\n\n/*\n@@ LUA_PATH_DEFAULT is the default path that Lua uses to look for\n** Lua libraries.\n@@ LUA_CPATH_DEFAULT is the default path that Lua uses to look for\n** C libraries.\n** CHANGE them if your machine has a non-conventional directory\n** hierarchy or if you want to install your libraries in\n** non-conventional directories.\n*/\n\n#define LUA_VDIR\tLUA_VERSION_MAJOR \".\" LUA_VERSION_MINOR\n#if defined(_WIN32)\t/* { */\n/*\n** In Windows, any exclamation mark ('!') in the path is replaced by the\n** path of the directory of the executable file of the current process.\n*/\n#define LUA_LDIR\t\"!\\\\lua\\\\\"\n#define LUA_CDIR\t\"!\\\\\"\n#define LUA_SHRDIR\t\"!\\\\..\\\\share\\\\lua\\\\\" LUA_VDIR \"\\\\\"\n\n#if !defined(LUA_PATH_DEFAULT)\n#define LUA_PATH_DEFAULT  \\\n\t\tLUA_LDIR\"?.lua;\"  LUA_LDIR\"?\\\\init.lua;\" \\\n\t\tLUA_CDIR\"?.lua;\"  LUA_CDIR\"?\\\\init.lua;\" \\\n\t\tLUA_SHRDIR\"?.lua;\" LUA_SHRDIR\"?\\\\init.lua;\" \\\n\t\t\".\\\\?.lua;\" \".\\\\?\\\\init.lua\"\n#endif\n\n#if !defined(LUA_CPATH_DEFAULT)\n#define LUA_CPATH_DEFAULT \\\n\t\tLUA_CDIR\"?.dll;\" \\\n\t\tLUA_CDIR\"..\\\\lib\\\\lua\\\\\" LUA_VDIR \"\\\\?.dll;\" \\\n\t\tLUA_CDIR\"loadall.dll;\" \".\\\\?.dll\"\n#endif\n\n#else\t\t\t/* }{ */\n\n#define LUA_ROOT\t\"/usr/local/\"\n#define LUA_LDIR\tLUA_ROOT \"share/lua/\" LUA_VDIR \"/\"\n#define LUA_CDIR\tLUA_ROOT \"lib/lua/\" LUA_VDIR \"/\"\n\n#if !defined(LUA_PATH_DEFAULT)\n#define LUA_PATH_DEFAULT  \\\n\t\tLUA_LDIR\"?.lua;\"  LUA_LDIR\"?/init.lua;\" \\\n\t\tLUA_CDIR\"?.lua;\"  LUA_CDIR\"?/init.lua;\" \\\n\t\t\"./?.lua;\" \"./?/init.lua\"\n#endif\n\n#if !defined(LUA_CPATH_DEFAULT)\n#define LUA_CPATH_DEFAULT \\\n\t\tLUA_CDIR\"?.so;\" LUA_CDIR\"loadall.so;\" \"./?.so\"\n#endif\n\n#endif\t\t\t/* } */\n\n\n/*\n@@ LUA_DIRSEP is the directory separator (for submodules).\n** CHANGE it if your machine does not use \"/\" as the directory separator\n** and is not Windows. (On Windows Lua automatically uses \"\\\".)\n*/\n#if !defined(LUA_DIRSEP)\n\n#if defined(_WIN32)\n#define LUA_DIRSEP\t\"\\\\\"\n#else\n#define LUA_DIRSEP\t\"/\"\n#endif\n\n#endif\n\n/* }================================================================== */\n\n\n/*\n** {==================================================================\n** Marks for exported symbols in the C code\n** ===================================================================\n*/\n\n/*\n@@ LUA_API is a mark for all core API functions.\n@@ LUALIB_API is a mark for all auxiliary library functions.\n@@ LUAMOD_API is a mark for all standard library opening functions.\n** CHANGE them if you need to define those functions in some special way.\n** For instance, if you want to create one Windows DLL with the core and\n** the libraries, you may want to use the following definition (define\n** LUA_BUILD_AS_DLL to get it).\n*/\n#if defined(LUA_BUILD_AS_DLL)\t/* { */\n\n#if defined(LUA_CORE) || defined(LUA_LIB)\t/* { */\n#define LUA_API __declspec(dllexport)\n#else\t\t\t\t\t\t/* }{ */\n#define LUA_API __declspec(dllimport)\n#endif\t\t\t\t\t\t/* } */\n\n#else\t\t\t\t/* }{ */\n\n#define LUA_API\t\textern\n\n#endif\t\t\t\t/* } */\n\n\n/*\n** More often than not the libs go together with the core.\n*/\n#define LUALIB_API\tLUA_API\n#define LUAMOD_API\tLUA_API\n\n\n/*\n@@ LUAI_FUNC is a mark for all extern functions that are not to be\n** exported to outside modules.\n@@ LUAI_DDEF and LUAI_DDEC are marks for all extern (const) variables,\n** none of which to be exported to outside modules (LUAI_DDEF for\n** definitions and LUAI_DDEC for declarations).\n** CHANGE them if you need to mark them in some special way. Elf/gcc\n** (versions 3.2 and later) mark them as \"hidden\" to optimize access\n** when Lua is compiled as a shared library. Not all elf targets support\n** this attribute. Unfortunately, gcc does not offer a way to check\n** whether the target offers that support, and those without support\n** give a warning about it. To avoid these warnings, change to the\n** default definition.\n*/\n#if defined(__GNUC__) && ((__GNUC__*100 + __GNUC_MINOR__) >= 302) && \\\n    defined(__ELF__)\t\t/* { */\n#define LUAI_FUNC\t__attribute__((visibility(\"internal\"))) extern\n#else\t\t\t\t/* }{ */\n#define LUAI_FUNC\textern\n#endif\t\t\t\t/* } */\n\n#define LUAI_DDEC(dec)\tLUAI_FUNC dec\n#define LUAI_DDEF\t/* empty */\n\n/* }================================================================== */\n\n\n/*\n** {==================================================================\n** Compatibility with previous versions\n** ===================================================================\n*/\n\n/*\n@@ LUA_COMPAT_5_3 controls other macros for compatibility with Lua 5.3.\n** You can define it to get all options, or change specific options\n** to fit your specific needs.\n*/\n#if defined(LUA_COMPAT_5_3)\t/* { */\n\n/*\n@@ LUA_COMPAT_MATHLIB controls the presence of several deprecated\n** functions in the mathematical library.\n** (These functions were already officially removed in 5.3;\n** nevertheless they are still available here.)\n*/\n#define LUA_COMPAT_MATHLIB\n\n/*\n@@ LUA_COMPAT_APIINTCASTS controls the presence of macros for\n** manipulating other integer types (lua_pushunsigned, lua_tounsigned,\n** luaL_checkint, luaL_checklong, etc.)\n** (These macros were also officially removed in 5.3, but they are still\n** available here.)\n*/\n#define LUA_COMPAT_APIINTCASTS\n\n\n/*\n@@ LUA_COMPAT_LT_LE controls the emulation of the '__le' metamethod\n** using '__lt'.\n*/\n#define LUA_COMPAT_LT_LE\n\n\n/*\n@@ The following macros supply trivial compatibility for some\n** changes in the API. The macros themselves document how to\n** change your code to avoid using them.\n** (Once more, these macros were officially removed in 5.3, but they are\n** still available here.)\n*/\n#define lua_strlen(L,i)\t\tlua_rawlen(L, (i))\n\n#define lua_objlen(L,i)\t\tlua_rawlen(L, (i))\n\n#define lua_equal(L,idx1,idx2)\t\tlua_compare(L,(idx1),(idx2),LUA_OPEQ)\n#define lua_lessthan(L,idx1,idx2)\tlua_compare(L,(idx1),(idx2),LUA_OPLT)\n\n#endif\t\t\t\t/* } */\n\n/* }================================================================== */\n\n\n\n/*\n** {==================================================================\n** Configuration for Numbers (low-level part).\n** Change these definitions if no predefined LUA_FLOAT_* / LUA_INT_*\n** satisfy your needs.\n** ===================================================================\n*/\n\n/*\n@@ LUAI_UACNUMBER is the result of a 'default argument promotion'\n@@ over a floating number.\n@@ l_floatatt(x) corrects float attribute 'x' to the proper float type\n** by prefixing it with one of FLT/DBL/LDBL.\n@@ LUA_NUMBER_FRMLEN is the length modifier for writing floats.\n@@ LUA_NUMBER_FMT is the format for writing floats.\n@@ lua_number2str converts a float to a string.\n@@ l_mathop allows the addition of an 'l' or 'f' to all math operations.\n@@ l_floor takes the floor of a float.\n@@ lua_str2number converts a decimal numeral to a number.\n*/\n\n\n/* The following definitions are good for most cases here */\n\n#define l_floor(x)\t\t(l_mathop(floor)(x))\n\n#define lua_number2str(s,sz,n)  \\\n\tl_sprintf((s), sz, LUA_NUMBER_FMT, (LUAI_UACNUMBER)(n))\n\n/*\n@@ lua_numbertointeger converts a float number with an integral value\n** to an integer, or returns 0 if float is not within the range of\n** a lua_Integer.  (The range comparisons are tricky because of\n** rounding. The tests here assume a two-complement representation,\n** where MININTEGER always has an exact representation as a float;\n** MAXINTEGER may not have one, and therefore its conversion to float\n** may have an ill-defined value.)\n*/\n#define lua_numbertointeger(n,p) \\\n  ((n) >= (LUA_NUMBER)(LUA_MININTEGER) && \\\n   (n) < -(LUA_NUMBER)(LUA_MININTEGER) && \\\n      (*(p) = (LUA_INTEGER)(n), 1))\n\n\n/* now the variable definitions */\n\n#if LUA_FLOAT_TYPE == LUA_FLOAT_FLOAT\t\t/* { single float */\n\n#define LUA_NUMBER\tfloat\n\n#define l_floatatt(n)\t\t(FLT_##n)\n\n#define LUAI_UACNUMBER\tdouble\n\n#define LUA_NUMBER_FRMLEN\t\"\"\n#define LUA_NUMBER_FMT\t\t\"%.7g\"\n\n#define l_mathop(op)\t\top##f\n\n#define lua_str2number(s,p)\tstrtof((s), (p))\n\n\n#elif LUA_FLOAT_TYPE == LUA_FLOAT_LONGDOUBLE\t/* }{ long double */\n\n#define LUA_NUMBER\tlong double\n\n#define l_floatatt(n)\t\t(LDBL_##n)\n\n#define LUAI_UACNUMBER\tlong double\n\n#define LUA_NUMBER_FRMLEN\t\"L\"\n#define LUA_NUMBER_FMT\t\t\"%.19Lg\"\n\n#define l_mathop(op)\t\top##l\n\n#define lua_str2number(s,p)\tstrtold((s), (p))\n\n#elif LUA_FLOAT_TYPE == LUA_FLOAT_DOUBLE\t/* }{ double */\n\n#define LUA_NUMBER\tdouble\n\n#define l_floatatt(n)\t\t(DBL_##n)\n\n#define LUAI_UACNUMBER\tdouble\n\n#define LUA_NUMBER_FRMLEN\t\"\"\n#define LUA_NUMBER_FMT\t\t\"%.14g\"\n\n#define l_mathop(op)\t\top\n\n#define lua_str2number(s,p)\tstrtod((s), (p))\n\n#else\t\t\t\t\t\t/* }{ */\n\n#error \"numeric float type not defined\"\n\n#endif\t\t\t\t\t/* } */\n\n\n\n/*\n@@ LUA_UNSIGNED is the unsigned version of LUA_INTEGER.\n@@ LUAI_UACINT is the result of a 'default argument promotion'\n@@ over a LUA_INTEGER.\n@@ LUA_INTEGER_FRMLEN is the length modifier for reading/writing integers.\n@@ LUA_INTEGER_FMT is the format for writing integers.\n@@ LUA_MAXINTEGER is the maximum value for a LUA_INTEGER.\n@@ LUA_MININTEGER is the minimum value for a LUA_INTEGER.\n@@ LUA_MAXUNSIGNED is the maximum value for a LUA_UNSIGNED.\n@@ lua_integer2str converts an integer to a string.\n*/\n\n\n/* The following definitions are good for most cases here */\n\n#define LUA_INTEGER_FMT\t\t\"%\" LUA_INTEGER_FRMLEN \"d\"\n\n#define LUAI_UACINT\t\tLUA_INTEGER\n\n#define lua_integer2str(s,sz,n)  \\\n\tl_sprintf((s), sz, LUA_INTEGER_FMT, (LUAI_UACINT)(n))\n\n/*\n** use LUAI_UACINT here to avoid problems with promotions (which\n** can turn a comparison between unsigneds into a signed comparison)\n*/\n#define LUA_UNSIGNED\t\tunsigned LUAI_UACINT\n\n\n/* now the variable definitions */\n\n#if LUA_INT_TYPE == LUA_INT_INT\t\t/* { int */\n\n#define LUA_INTEGER\t\tint\n#define LUA_INTEGER_FRMLEN\t\"\"\n\n#define LUA_MAXINTEGER\t\tINT_MAX\n#define LUA_MININTEGER\t\tINT_MIN\n\n#define LUA_MAXUNSIGNED\t\tUINT_MAX\n\n#elif LUA_INT_TYPE == LUA_INT_LONG\t/* }{ long */\n\n#define LUA_INTEGER\t\tlong\n#define LUA_INTEGER_FRMLEN\t\"l\"\n\n#define LUA_MAXINTEGER\t\tLONG_MAX\n#define LUA_MININTEGER\t\tLONG_MIN\n\n#define LUA_MAXUNSIGNED\t\tULONG_MAX\n\n#elif LUA_INT_TYPE == LUA_INT_LONGLONG\t/* }{ long long */\n\n/* use presence of macro LLONG_MAX as proxy for C99 compliance */\n#if defined(LLONG_MAX)\t\t/* { */\n/* use ISO C99 stuff */\n\n#define LUA_INTEGER\t\tlong long\n#define LUA_INTEGER_FRMLEN\t\"ll\"\n\n#define LUA_MAXINTEGER\t\tLLONG_MAX\n#define LUA_MININTEGER\t\tLLONG_MIN\n\n#define LUA_MAXUNSIGNED\t\tULLONG_MAX\n\n#elif defined(LUA_USE_WINDOWS) /* }{ */\n/* in Windows, can use specific Windows types */\n\n#define LUA_INTEGER\t\t__int64\n#define LUA_INTEGER_FRMLEN\t\"I64\"\n\n#define LUA_MAXINTEGER\t\t_I64_MAX\n#define LUA_MININTEGER\t\t_I64_MIN\n\n#define LUA_MAXUNSIGNED\t\t_UI64_MAX\n\n#else\t\t\t\t/* }{ */\n\n#error \"Compiler does not support 'long long'. Use option '-DLUA_32BITS' \\\n  or '-DLUA_C89_NUMBERS' (see file 'luaconf.h' for details)\"\n\n#endif\t\t\t\t/* } */\n\n#else\t\t\t\t/* }{ */\n\n#error \"numeric integer type not defined\"\n\n#endif\t\t\t\t/* } */\n\n/* }================================================================== */\n\n\n/*\n** {==================================================================\n** Dependencies with C99 and other C details\n** ===================================================================\n*/\n\n/*\n@@ l_sprintf is equivalent to 'snprintf' or 'sprintf' in C89.\n** (All uses in Lua have only one format item.)\n*/\n#if !defined(LUA_USE_C89)\n#define l_sprintf(s,sz,f,i)\tsnprintf(s,sz,f,i)\n#else\n#define l_sprintf(s,sz,f,i)\t((void)(sz), sprintf(s,f,i))\n#endif\n\n\n/*\n@@ lua_strx2number converts a hexadecimal numeral to a number.\n** In C99, 'strtod' does that conversion. Otherwise, you can\n** leave 'lua_strx2number' undefined and Lua will provide its own\n** implementation.\n*/\n#if !defined(LUA_USE_C89)\n#define lua_strx2number(s,p)\t\tlua_str2number(s,p)\n#endif\n\n\n/*\n@@ lua_pointer2str converts a pointer to a readable string in a\n** non-specified way.\n*/\n#define lua_pointer2str(buff,sz,p)\tl_sprintf(buff,sz,\"%p\",p)\n\n\n/*\n@@ lua_number2strx converts a float to a hexadecimal numeral.\n** In C99, 'sprintf' (with format specifiers '%a'/'%A') does that.\n** Otherwise, you can leave 'lua_number2strx' undefined and Lua will\n** provide its own implementation.\n*/\n#if !defined(LUA_USE_C89)\n#define lua_number2strx(L,b,sz,f,n)  \\\n\t((void)L, l_sprintf(b,sz,f,(LUAI_UACNUMBER)(n)))\n#endif\n\n\n/*\n** 'strtof' and 'opf' variants for math functions are not valid in\n** C89. Otherwise, the macro 'HUGE_VALF' is a good proxy for testing the\n** availability of these variants. ('math.h' is already included in\n** all files that use these macros.)\n*/\n#if defined(LUA_USE_C89) || (defined(HUGE_VAL) && !defined(HUGE_VALF))\n#undef l_mathop  /* variants not available */\n#undef lua_str2number\n#define l_mathop(op)\t\t(lua_Number)op  /* no variant */\n#define lua_str2number(s,p)\t((lua_Number)strtod((s), (p)))\n#endif\n\n\n/*\n@@ LUA_KCONTEXT is the type of the context ('ctx') for continuation\n** functions.  It must be a numerical type; Lua will use 'intptr_t' if\n** available, otherwise it will use 'ptrdiff_t' (the nearest thing to\n** 'intptr_t' in C89)\n*/\n#define LUA_KCONTEXT\tptrdiff_t\n\n#if !defined(LUA_USE_C89) && defined(__STDC_VERSION__) && \\\n    __STDC_VERSION__ >= 199901L\n#include <stdint.h>\n#if defined(INTPTR_MAX)  /* even in C99 this type is optional */\n#undef LUA_KCONTEXT\n#define LUA_KCONTEXT\tintptr_t\n#endif\n#endif\n\n\n/*\n@@ lua_getlocaledecpoint gets the locale \"radix character\" (decimal point).\n** Change that if you do not want to use C locales. (Code using this\n** macro must include the header 'locale.h'.)\n*/\n#if !defined(lua_getlocaledecpoint)\n#define lua_getlocaledecpoint()\t\t(localeconv()->decimal_point[0])\n#endif\n\n\n/*\n** macros to improve jump prediction, used mostly for error handling\n** and debug facilities. (Some macros in the Lua API use these macros.\n** Define LUA_NOBUILTIN if you do not want '__builtin_expect' in your\n** code.)\n*/\n#if !defined(luai_likely)\n\n#if defined(__GNUC__) && !defined(LUA_NOBUILTIN)\n#define luai_likely(x)\t\t(__builtin_expect(((x) != 0), 1))\n#define luai_unlikely(x)\t(__builtin_expect(((x) != 0), 0))\n#else\n#define luai_likely(x)\t\t(x)\n#define luai_unlikely(x)\t(x)\n#endif\n\n#endif\n\n\n#if defined(LUA_CORE) || defined(LUA_LIB)\n/* shorter names for Lua's own use */\n#define l_likely(x)\tluai_likely(x)\n#define l_unlikely(x)\tluai_unlikely(x)\n#endif\n\n\n\n/* }================================================================== */\n\n\n/*\n** {==================================================================\n** Language Variations\n** =====================================================================\n*/\n\n/*\n@@ LUA_NOCVTN2S/LUA_NOCVTS2N control how Lua performs some\n** coercions. Define LUA_NOCVTN2S to turn off automatic coercion from\n** numbers to strings. Define LUA_NOCVTS2N to turn off automatic\n** coercion from strings to numbers.\n*/\n/* #define LUA_NOCVTN2S */\n/* #define LUA_NOCVTS2N */\n\n\n/*\n@@ LUA_USE_APICHECK turns on several consistency checks on the C API.\n** Define it as a help when debugging C code.\n*/\n#if defined(LUA_USE_APICHECK)\n#include <assert.h>\n#define luai_apicheck(l,e)\tassert(e)\n#endif\n\n/* }================================================================== */\n\n\n/*\n** {==================================================================\n** Macros that affect the API and must be stable (that is, must be the\n** same when you compile Lua and when you compile code that links to\n** Lua).\n** =====================================================================\n*/\n\n/*\n@@ LUAI_MAXSTACK limits the size of the Lua stack.\n** CHANGE it if you need a different limit. This limit is arbitrary;\n** its only purpose is to stop Lua from consuming unlimited stack\n** space (and to reserve some numbers for pseudo-indices).\n** (It must fit into max(size_t)/32 and max(int)/2.)\n*/\n#if LUAI_IS32INT\n#define LUAI_MAXSTACK\t\t1000000\n#else\n#define LUAI_MAXSTACK\t\t15000\n#endif\n\n\n/*\n@@ LUA_EXTRASPACE defines the size of a raw memory area associated with\n** a Lua state with very fast access.\n** CHANGE it if you need a different size.\n*/\n#define LUA_EXTRASPACE\t\t(sizeof(void *))\n\n\n/*\n@@ LUA_IDSIZE gives the maximum size for the description of the source\n** of a function in debug information.\n** CHANGE it if you want a different size.\n*/\n#define LUA_IDSIZE\t60\n\n\n/*\n@@ LUAL_BUFFERSIZE is the initial buffer size used by the lauxlib\n** buffer system.\n*/\n#define LUAL_BUFFERSIZE   ((int)(16 * sizeof(void*) * sizeof(lua_Number)))\n\n\n/*\n@@ LUAI_MAXALIGN defines fields that, when used in a union, ensure\n** maximum alignment for the other items in that union.\n*/\n#define LUAI_MAXALIGN  lua_Number n; double u; void *s; lua_Integer i; long l\n\n/* }================================================================== */\n\n\n\n\n\n/* =================================================================== */\n\n/*\n** Local configuration. You can use this space to add your redefinitions\n** without modifying the main part of the file.\n*/\n\n\n\n\n\n#endif\n\n/*\n** $Id: lua.h $\n** Lua - A Scripting Language\n** Lua.org, PUC-Rio, Brazil (http://www.lua.org)\n** See Copyright Notice at the end of this file\n*/\n\n\n#ifndef lua_h\n#define lua_h\n\n#include <stdarg.h>\n#include <stddef.h>\n\n\n/*#include \"luaconf.h\"*/\n\n\n#define LUA_VERSION_MAJOR\t\"5\"\n#define LUA_VERSION_MINOR\t\"4\"\n#define LUA_VERSION_RELEASE\t\"6\"\n\n#define LUA_VERSION_NUM\t\t\t504\n#define LUA_VERSION_RELEASE_NUM\t\t(LUA_VERSION_NUM * 100 + 6)\n\n#define LUA_VERSION\t\"Lua \" LUA_VERSION_MAJOR \".\" LUA_VERSION_MINOR\n#define LUA_RELEASE\tLUA_VERSION \".\" LUA_VERSION_RELEASE\n#define LUA_COPYRIGHT\tLUA_RELEASE \"  Copyright (C) 1994-2023 Lua.org, PUC-Rio\"\n#define LUA_AUTHORS\t\"R. Ierusalimschy, L. H. de Figueiredo, W. Celes\"\n\n\n/* mark for precompiled code ('<esc>Lua') */\n#define LUA_SIGNATURE\t\"\\x1bLua\"\n\n/* option for multiple returns in 'lua_pcall' and 'lua_call' */\n#define LUA_MULTRET\t(-1)\n\n\n/*\n** Pseudo-indices\n** (-LUAI_MAXSTACK is the minimum valid index; we keep some free empty\n** space after that to help overflow detection)\n*/\n#define LUA_REGISTRYINDEX\t(-LUAI_MAXSTACK - 1000)\n#define lua_upvalueindex(i)\t(LUA_REGISTRYINDEX - (i))\n\n\n/* thread status */\n#define LUA_OK\t\t0\n#define LUA_YIELD\t1\n#define LUA_ERRRUN\t2\n#define LUA_ERRSYNTAX\t3\n#define LUA_ERRMEM\t4\n#define LUA_ERRERR\t5\n\n\ntypedef struct lua_State lua_State;\n\n\n/*\n** basic types\n*/\n#define LUA_TNONE\t\t(-1)\n\n#define LUA_TNIL\t\t0\n#define LUA_TBOOLEAN\t\t1\n#define LUA_TLIGHTUSERDATA\t2\n#define LUA_TNUMBER\t\t3\n#define LUA_TSTRING\t\t4\n#define LUA_TTABLE\t\t5\n#define LUA_TFUNCTION\t\t6\n#define LUA_TUSERDATA\t\t7\n#define LUA_TTHREAD\t\t8\n\n#define LUA_NUMTYPES\t\t9\n\n\n\n/* minimum Lua stack available to a C function */\n#define LUA_MINSTACK\t20\n\n\n/* predefined values in the registry */\n#define LUA_RIDX_MAINTHREAD\t1\n#define LUA_RIDX_GLOBALS\t2\n#define LUA_RIDX_LAST\t\tLUA_RIDX_GLOBALS\n\n\n/* type of numbers in Lua */\ntypedef LUA_NUMBER lua_Number;\n\n\n/* type for integer functions */\ntypedef LUA_INTEGER lua_Integer;\n\n/* unsigned integer type */\ntypedef LUA_UNSIGNED lua_Unsigned;\n\n/* type for continuation-function contexts */\ntypedef LUA_KCONTEXT lua_KContext;\n\n\n/*\n** Type for C functions registered with Lua\n*/\ntypedef int (*lua_CFunction) (lua_State *L);\n\n/*\n** Type for continuation functions\n*/\ntypedef int (*lua_KFunction) (lua_State *L, int status, lua_KContext ctx);\n\n\n/*\n** Type for functions that read/write blocks when loading/dumping Lua chunks\n*/\ntypedef const char * (*lua_Reader) (lua_State *L, void *ud, size_t *sz);\n\ntypedef int (*lua_Writer) (lua_State *L, const void *p, size_t sz, void *ud);\n\n\n/*\n** Type for memory-allocation functions\n*/\ntypedef void * (*lua_Alloc) (void *ud, void *ptr, size_t osize, size_t nsize);\n\n\n/*\n** Type for warning functions\n*/\ntypedef void (*lua_WarnFunction) (void *ud, const char *msg, int tocont);\n\n\n/*\n** Type used by the debug API to collect debug information\n*/\ntypedef struct lua_Debug lua_Debug;\n\n\n/*\n** Functions to be called by the debugger in specific events\n*/\ntypedef void (*lua_Hook) (lua_State *L, lua_Debug *ar);\n\n\n/*\n** generic extra include file\n*/\n#if defined(LUA_USER_H)\n#include LUA_USER_H\n#endif\n\n\n/*\n** RCS ident string\n*/\nextern const char lua_ident[];\n\n\n/*\n** state manipulation\n*/\nLUA_API lua_State *(lua_newstate) (lua_Alloc f, void *ud);\nLUA_API void       (lua_close) (lua_State *L);\nLUA_API lua_State *(lua_newthread) (lua_State *L);\nLUA_API int        (lua_closethread) (lua_State *L, lua_State *from);\nLUA_API int        (lua_resetthread) (lua_State *L);  /* Deprecated! */\n\nLUA_API lua_CFunction (lua_atpanic) (lua_State *L, lua_CFunction panicf);\n\n\nLUA_API lua_Number (lua_version) (lua_State *L);\n\n\n/*\n** basic stack manipulation\n*/\nLUA_API int   (lua_absindex) (lua_State *L, int idx);\nLUA_API int   (lua_gettop) (lua_State *L);\nLUA_API void  (lua_settop) (lua_State *L, int idx);\nLUA_API void  (lua_pushvalue) (lua_State *L, int idx);\nLUA_API void  (lua_rotate) (lua_State *L, int idx, int n);\nLUA_API void  (lua_copy) (lua_State *L, int fromidx, int toidx);\nLUA_API int   (lua_checkstack) (lua_State *L, int n);\n\nLUA_API void  (lua_xmove) (lua_State *from, lua_State *to, int n);\n\n\n/*\n** access functions (stack -> C)\n*/\n\nLUA_API int             (lua_isnumber) (lua_State *L, int idx);\nLUA_API int             (lua_isstring) (lua_State *L, int idx);\nLUA_API int             (lua_iscfunction) (lua_State *L, int idx);\nLUA_API int             (lua_isinteger) (lua_State *L, int idx);\nLUA_API int             (lua_isuserdata) (lua_State *L, int idx);\nLUA_API int             (lua_type) (lua_State *L, int idx);\nLUA_API const char     *(lua_typename) (lua_State *L, int tp);\n\nLUA_API lua_Number      (lua_tonumberx) (lua_State *L, int idx, int *isnum);\nLUA_API lua_Integer     (lua_tointegerx) (lua_State *L, int idx, int *isnum);\nLUA_API int             (lua_toboolean) (lua_State *L, int idx);\nLUA_API const char     *(lua_tolstring) (lua_State *L, int idx, size_t *len);\nLUA_API lua_Unsigned    (lua_rawlen) (lua_State *L, int idx);\nLUA_API lua_CFunction   (lua_tocfunction) (lua_State *L, int idx);\nLUA_API void\t       *(lua_touserdata) (lua_State *L, int idx);\nLUA_API lua_State      *(lua_tothread) (lua_State *L, int idx);\nLUA_API const void     *(lua_topointer) (lua_State *L, int idx);\n\n\n/*\n** Comparison and arithmetic functions\n*/\n\n#define LUA_OPADD\t0\t/* ORDER TM, ORDER OP */\n#define LUA_OPSUB\t1\n#define LUA_OPMUL\t2\n#define LUA_OPMOD\t3\n#define LUA_OPPOW\t4\n#define LUA_OPDIV\t5\n#define LUA_OPIDIV\t6\n#define LUA_OPBAND\t7\n#define LUA_OPBOR\t8\n#define LUA_OPBXOR\t9\n#define LUA_OPSHL\t10\n#define LUA_OPSHR\t11\n#define LUA_OPUNM\t12\n#define LUA_OPBNOT\t13\n\nLUA_API void  (lua_arith) (lua_State *L, int op);\n\n#define LUA_OPEQ\t0\n#define LUA_OPLT\t1\n#define LUA_OPLE\t2\n\nLUA_API int   (lua_rawequal) (lua_State *L, int idx1, int idx2);\nLUA_API int   (lua_compare) (lua_State *L, int idx1, int idx2, int op);\n\n\n/*\n** push functions (C -> stack)\n*/\nLUA_API void        (lua_pushnil) (lua_State *L);\nLUA_API void        (lua_pushnumber) (lua_State *L, lua_Number n);\nLUA_API void        (lua_pushinteger) (lua_State *L, lua_Integer n);\nLUA_API const char *(lua_pushlstring) (lua_State *L, const char *s, size_t len);\nLUA_API const char *(lua_pushstring) (lua_State *L, const char *s);\nLUA_API const char *(lua_pushvfstring) (lua_State *L, const char *fmt,\n                                                      va_list argp);\nLUA_API const char *(lua_pushfstring) (lua_State *L, const char *fmt, ...);\nLUA_API void  (lua_pushcclosure) (lua_State *L, lua_CFunction fn, int n);\nLUA_API void  (lua_pushboolean) (lua_State *L, int b);\nLUA_API void  (lua_pushlightuserdata) (lua_State *L, void *p);\nLUA_API int   (lua_pushthread) (lua_State *L);\n\n\n/*\n** get functions (Lua -> stack)\n*/\nLUA_API int (lua_getglobal) (lua_State *L, const char *name);\nLUA_API int (lua_gettable) (lua_State *L, int idx);\nLUA_API int (lua_getfield) (lua_State *L, int idx, const char *k);\nLUA_API int (lua_geti) (lua_State *L, int idx, lua_Integer n);\nLUA_API int (lua_rawget) (lua_State *L, int idx);\nLUA_API int (lua_rawgeti) (lua_State *L, int idx, lua_Integer n);\nLUA_API int (lua_rawgetp) (lua_State *L, int idx, const void *p);\n\nLUA_API void  (lua_createtable) (lua_State *L, int narr, int nrec);\nLUA_API void *(lua_newuserdatauv) (lua_State *L, size_t sz, int nuvalue);\nLUA_API int   (lua_getmetatable) (lua_State *L, int objindex);\nLUA_API int  (lua_getiuservalue) (lua_State *L, int idx, int n);\n\n\n/*\n** set functions (stack -> Lua)\n*/\nLUA_API void  (lua_setglobal) (lua_State *L, const char *name);\nLUA_API void  (lua_settable) (lua_State *L, int idx);\nLUA_API void  (lua_setfield) (lua_State *L, int idx, const char *k);\nLUA_API void  (lua_seti) (lua_State *L, int idx, lua_Integer n);\nLUA_API void  (lua_rawset) (lua_State *L, int idx);\nLUA_API void  (lua_rawseti) (lua_State *L, int idx, lua_Integer n);\nLUA_API void  (lua_rawsetp) (lua_State *L, int idx, const void *p);\nLUA_API int   (lua_setmetatable) (lua_State *L, int objindex);\nLUA_API int   (lua_setiuservalue) (lua_State *L, int idx, int n);\n\n\n/*\n** 'load' and 'call' functions (load and run Lua code)\n*/\nLUA_API void  (lua_callk) (lua_State *L, int nargs, int nresults,\n                           lua_KContext ctx, lua_KFunction k);\n#define lua_call(L,n,r)\t\tlua_callk(L, (n), (r), 0, NULL)\n\nLUA_API int   (lua_pcallk) (lua_State *L, int nargs, int nresults, int errfunc,\n                            lua_KContext ctx, lua_KFunction k);\n#define lua_pcall(L,n,r,f)\tlua_pcallk(L, (n), (r), (f), 0, NULL)\n\nLUA_API int   (lua_load) (lua_State *L, lua_Reader reader, void *dt,\n                          const char *chunkname, const char *mode);\n\nLUA_API int (lua_dump) (lua_State *L, lua_Writer writer, void *data, int strip);\n\n\n/*\n** coroutine functions\n*/\nLUA_API int  (lua_yieldk)     (lua_State *L, int nresults, lua_KContext ctx,\n                               lua_KFunction k);\nLUA_API int  (lua_resume)     (lua_State *L, lua_State *from, int narg,\n                               int *nres);\nLUA_API int  (lua_status)     (lua_State *L);\nLUA_API int (lua_isyieldable) (lua_State *L);\n\n#define lua_yield(L,n)\t\tlua_yieldk(L, (n), 0, NULL)\n\n\n/*\n** Warning-related functions\n*/\nLUA_API void (lua_setwarnf) (lua_State *L, lua_WarnFunction f, void *ud);\nLUA_API void (lua_warning)  (lua_State *L, const char *msg, int tocont);\n\n\n/*\n** garbage-collection function and options\n*/\n\n#define LUA_GCSTOP\t\t0\n#define LUA_GCRESTART\t\t1\n#define LUA_GCCOLLECT\t\t2\n#define LUA_GCCOUNT\t\t3\n#define LUA_GCCOUNTB\t\t4\n#define LUA_GCSTEP\t\t5\n#define LUA_GCSETPAUSE\t\t6\n#define LUA_GCSETSTEPMUL\t7\n#define LUA_GCISRUNNING\t\t9\n#define LUA_GCGEN\t\t10\n#define LUA_GCINC\t\t11\n\nLUA_API int (lua_gc) (lua_State *L, int what, ...);\n\n\n/*\n** miscellaneous functions\n*/\n\nLUA_API int   (lua_error) (lua_State *L);\n\nLUA_API int   (lua_next) (lua_State *L, int idx);\n\nLUA_API void  (lua_concat) (lua_State *L, int n);\nLUA_API void  (lua_len)    (lua_State *L, int idx);\n\nLUA_API size_t   (lua_stringtonumber) (lua_State *L, const char *s);\n\nLUA_API lua_Alloc (lua_getallocf) (lua_State *L, void **ud);\nLUA_API void      (lua_setallocf) (lua_State *L, lua_Alloc f, void *ud);\n\nLUA_API void (lua_toclose) (lua_State *L, int idx);\nLUA_API void (lua_closeslot) (lua_State *L, int idx);\n\n\n/*\n** {==============================================================\n** some useful macros\n** ===============================================================\n*/\n\n#define lua_getextraspace(L)\t((void *)((char *)(L) - LUA_EXTRASPACE))\n\n#define lua_tonumber(L,i)\tlua_tonumberx(L,(i),NULL)\n#define lua_tointeger(L,i)\tlua_tointegerx(L,(i),NULL)\n\n#define lua_pop(L,n)\t\tlua_settop(L, -(n)-1)\n\n#define lua_newtable(L)\t\tlua_createtable(L, 0, 0)\n\n#define lua_register(L,n,f) (lua_pushcfunction(L, (f)), lua_setglobal(L, (n)))\n\n#define lua_pushcfunction(L,f)\tlua_pushcclosure(L, (f), 0)\n\n#define lua_isfunction(L,n)\t(lua_type(L, (n)) == LUA_TFUNCTION)\n#define lua_istable(L,n)\t(lua_type(L, (n)) == LUA_TTABLE)\n#define lua_islightuserdata(L,n)\t(lua_type(L, (n)) == LUA_TLIGHTUSERDATA)\n#define lua_isnil(L,n)\t\t(lua_type(L, (n)) == LUA_TNIL)\n#define lua_isboolean(L,n)\t(lua_type(L, (n)) == LUA_TBOOLEAN)\n#define lua_isthread(L,n)\t(lua_type(L, (n)) == LUA_TTHREAD)\n#define lua_isnone(L,n)\t\t(lua_type(L, (n)) == LUA_TNONE)\n#define lua_isnoneornil(L, n)\t(lua_type(L, (n)) <= 0)\n\n#define lua_pushliteral(L, s)\tlua_pushstring(L, \"\" s)\n\n#define lua_pushglobaltable(L)  \\\n\t((void)lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS))\n\n#define lua_tostring(L,i)\tlua_tolstring(L, (i), NULL)\n\n\n#define lua_insert(L,idx)\tlua_rotate(L, (idx), 1)\n\n#define lua_remove(L,idx)\t(lua_rotate(L, (idx), -1), lua_pop(L, 1))\n\n#define lua_replace(L,idx)\t(lua_copy(L, -1, (idx)), lua_pop(L, 1))\n\n/* }============================================================== */\n\n\n/*\n** {==============================================================\n** compatibility macros\n** ===============================================================\n*/\n#if defined(LUA_COMPAT_APIINTCASTS)\n\n#define lua_pushunsigned(L,n)\tlua_pushinteger(L, (lua_Integer)(n))\n#define lua_tounsignedx(L,i,is)\t((lua_Unsigned)lua_tointegerx(L,i,is))\n#define lua_tounsigned(L,i)\tlua_tounsignedx(L,(i),NULL)\n\n#endif\n\n#define lua_newuserdata(L,s)\tlua_newuserdatauv(L,s,1)\n#define lua_getuservalue(L,idx)\tlua_getiuservalue(L,idx,1)\n#define lua_setuservalue(L,idx)\tlua_setiuservalue(L,idx,1)\n\n#define LUA_NUMTAGS\t\tLUA_NUMTYPES\n\n/* }============================================================== */\n\n/*\n** {======================================================================\n** Debug API\n** =======================================================================\n*/\n\n\n/*\n** Event codes\n*/\n#define LUA_HOOKCALL\t0\n#define LUA_HOOKRET\t1\n#define LUA_HOOKLINE\t2\n#define LUA_HOOKCOUNT\t3\n#define LUA_HOOKTAILCALL 4\n\n\n/*\n** Event masks\n*/\n#define LUA_MASKCALL\t(1 << LUA_HOOKCALL)\n#define LUA_MASKRET\t(1 << LUA_HOOKRET)\n#define LUA_MASKLINE\t(1 << LUA_HOOKLINE)\n#define LUA_MASKCOUNT\t(1 << LUA_HOOKCOUNT)\n\n\nLUA_API int (lua_getstack) (lua_State *L, int level, lua_Debug *ar);\nLUA_API int (lua_getinfo) (lua_State *L, const char *what, lua_Debug *ar);\nLUA_API const char *(lua_getlocal) (lua_State *L, const lua_Debug *ar, int n);\nLUA_API const char *(lua_setlocal) (lua_State *L, const lua_Debug *ar, int n);\nLUA_API const char *(lua_getupvalue) (lua_State *L, int funcindex, int n);\nLUA_API const char *(lua_setupvalue) (lua_State *L, int funcindex, int n);\n\nLUA_API void *(lua_upvalueid) (lua_State *L, int fidx, int n);\nLUA_API void  (lua_upvaluejoin) (lua_State *L, int fidx1, int n1,\n                                               int fidx2, int n2);\n\nLUA_API void (lua_sethook) (lua_State *L, lua_Hook func, int mask, int count);\nLUA_API lua_Hook (lua_gethook) (lua_State *L);\nLUA_API int (lua_gethookmask) (lua_State *L);\nLUA_API int (lua_gethookcount) (lua_State *L);\n\nLUA_API int (lua_setcstacklimit) (lua_State *L, unsigned int limit);\n\nstruct lua_Debug {\n  int event;\n  const char *name;\t/* (n) */\n  const char *namewhat;\t/* (n) 'global', 'local', 'field', 'method' */\n  const char *what;\t/* (S) 'Lua', 'C', 'main', 'tail' */\n  const char *source;\t/* (S) */\n  size_t srclen;\t/* (S) */\n  int currentline;\t/* (l) */\n  int linedefined;\t/* (S) */\n  int lastlinedefined;\t/* (S) */\n  unsigned char nups;\t/* (u) number of upvalues */\n  unsigned char nparams;/* (u) number of parameters */\n  char isvararg;        /* (u) */\n  char istailcall;\t/* (t) */\n  unsigned short ftransfer;   /* (r) index of first value transferred */\n  unsigned short ntransfer;   /* (r) number of transferred values */\n  char short_src[LUA_IDSIZE]; /* (S) */\n  /* private part */\n  struct CallInfo *i_ci;  /* active function */\n};\n\n/* }====================================================================== */\n\n\n/******************************************************************************\n* Copyright (C) 1994-2023 Lua.org, PUC-Rio.\n*\n* Permission is hereby granted, free of charge, to any person obtaining\n* a copy of this software and associated documentation files (the\n* \"Software\"), to deal in the Software without restriction, including\n* without limitation the rights to use, copy, modify, merge, publish,\n* distribute, sublicense, and/or sell copies of the Software, and to\n* permit persons to whom the Software is furnished to do so, subject to\n* the following conditions:\n*\n* The above copyright notice and this permission notice shall be\n* included in all copies or substantial portions of the Software.\n*\n* THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\n* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\n* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\n* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n******************************************************************************/\n\n\n#endif\n/*\n** $Id: lauxlib.h $\n** Auxiliary functions for building Lua libraries\n** See Copyright Notice in lua.h\n*/\n\n\n#ifndef lauxlib_h\n#define lauxlib_h\n\n\n#include <stddef.h>\n#include <stdio.h>\n\n/*#include \"luaconf.h\"*/\n/*#include \"lua.h\"*/\n\n\n/* global table */\n#define LUA_GNAME\t\"_G\"\n\n\ntypedef struct luaL_Buffer luaL_Buffer;\n\n\n/* extra error code for 'luaL_loadfilex' */\n#define LUA_ERRFILE     (LUA_ERRERR+1)\n\n\n/* key, in the registry, for table of loaded modules */\n#define LUA_LOADED_TABLE\t\"_LOADED\"\n\n\n/* key, in the registry, for table of preloaded loaders */\n#define LUA_PRELOAD_TABLE\t\"_PRELOAD\"\n\n\ntypedef struct luaL_Reg {\n  const char *name;\n  lua_CFunction func;\n} luaL_Reg;\n\n\n#define LUAL_NUMSIZES\t(sizeof(lua_Integer)*16 + sizeof(lua_Number))\n\nLUALIB_API void (luaL_checkversion_) (lua_State *L, lua_Number ver, size_t sz);\n#define luaL_checkversion(L)  \\\n\t  luaL_checkversion_(L, LUA_VERSION_NUM, LUAL_NUMSIZES)\n\nLUALIB_API int (luaL_getmetafield) (lua_State *L, int obj, const char *e);\nLUALIB_API int (luaL_callmeta) (lua_State *L, int obj, const char *e);\nLUALIB_API const char *(luaL_tolstring) (lua_State *L, int idx, size_t *len);\nLUALIB_API int (luaL_argerror) (lua_State *L, int arg, const char *extramsg);\nLUALIB_API int (luaL_typeerror) (lua_State *L, int arg, const char *tname);\nLUALIB_API const char *(luaL_checklstring) (lua_State *L, int arg,\n                                                          size_t *l);\nLUALIB_API const char *(luaL_optlstring) (lua_State *L, int arg,\n                                          const char *def, size_t *l);\nLUALIB_API lua_Number (luaL_checknumber) (lua_State *L, int arg);\nLUALIB_API lua_Number (luaL_optnumber) (lua_State *L, int arg, lua_Number def);\n\nLUALIB_API lua_Integer (luaL_checkinteger) (lua_State *L, int arg);\nLUALIB_API lua_Integer (luaL_optinteger) (lua_State *L, int arg,\n                                          lua_Integer def);\n\nLUALIB_API void (luaL_checkstack) (lua_State *L, int sz, const char *msg);\nLUALIB_API void (luaL_checktype) (lua_State *L, int arg, int t);\nLUALIB_API void (luaL_checkany) (lua_State *L, int arg);\n\nLUALIB_API int   (luaL_newmetatable) (lua_State *L, const char *tname);\nLUALIB_API void  (luaL_setmetatable) (lua_State *L, const char *tname);\nLUALIB_API void *(luaL_testudata) (lua_State *L, int ud, const char *tname);\nLUALIB_API void *(luaL_checkudata) (lua_State *L, int ud, const char *tname);\n\nLUALIB_API void (luaL_where) (lua_State *L, int lvl);\nLUALIB_API int (luaL_error) (lua_State *L, const char *fmt, ...);\n\nLUALIB_API int (luaL_checkoption) (lua_State *L, int arg, const char *def,\n                                   const char *const lst[]);\n\nLUALIB_API int (luaL_fileresult) (lua_State *L, int stat, const char *fname);\nLUALIB_API int (luaL_execresult) (lua_State *L, int stat);\n\n\n/* predefined references */\n#define LUA_NOREF       (-2)\n#define LUA_REFNIL      (-1)\n\nLUALIB_API int (luaL_ref) (lua_State *L, int t);\nLUALIB_API void (luaL_unref) (lua_State *L, int t, int ref);\n\nLUALIB_API int (luaL_loadfilex) (lua_State *L, const char *filename,\n                                               const char *mode);\n\n#define luaL_loadfile(L,f)\tluaL_loadfilex(L,f,NULL)\n\nLUALIB_API int (luaL_loadbufferx) (lua_State *L, const char *buff, size_t sz,\n                                   const char *name, const char *mode);\nLUALIB_API int (luaL_loadstring) (lua_State *L, const char *s);\n\nLUALIB_API lua_State *(luaL_newstate) (void);\n\nLUALIB_API lua_Integer (luaL_len) (lua_State *L, int idx);\n\nLUALIB_API void (luaL_addgsub) (luaL_Buffer *b, const char *s,\n                                     const char *p, const char *r);\nLUALIB_API const char *(luaL_gsub) (lua_State *L, const char *s,\n                                    const char *p, const char *r);\n\nLUALIB_API void (luaL_setfuncs) (lua_State *L, const luaL_Reg *l, int nup);\n\nLUALIB_API int (luaL_getsubtable) (lua_State *L, int idx, const char *fname);\n\nLUALIB_API void (luaL_traceback) (lua_State *L, lua_State *L1,\n                                  const char *msg, int level);\n\nLUALIB_API void (luaL_requiref) (lua_State *L, const char *modname,\n                                 lua_CFunction openf, int glb);\n\n/*\n** ===============================================================\n** some useful macros\n** ===============================================================\n*/\n\n\n#define luaL_newlibtable(L,l)\t\\\n  lua_createtable(L, 0, sizeof(l)/sizeof((l)[0]) - 1)\n\n#define luaL_newlib(L,l)  \\\n  (luaL_checkversion(L), luaL_newlibtable(L,l), luaL_setfuncs(L,l,0))\n\n#define luaL_argcheck(L, cond,arg,extramsg)\t\\\n\t((void)(luai_likely(cond) || luaL_argerror(L, (arg), (extramsg))))\n\n#define luaL_argexpected(L,cond,arg,tname)\t\\\n\t((void)(luai_likely(cond) || luaL_typeerror(L, (arg), (tname))))\n\n#define luaL_checkstring(L,n)\t(luaL_checklstring(L, (n), NULL))\n#define luaL_optstring(L,n,d)\t(luaL_optlstring(L, (n), (d), NULL))\n\n#define luaL_typename(L,i)\tlua_typename(L, lua_type(L,(i)))\n\n#define luaL_dofile(L, fn) \\\n\t(luaL_loadfile(L, fn) || lua_pcall(L, 0, LUA_MULTRET, 0))\n\n#define luaL_dostring(L, s) \\\n\t(luaL_loadstring(L, s) || lua_pcall(L, 0, LUA_MULTRET, 0))\n\n#define luaL_getmetatable(L,n)\t(lua_getfield(L, LUA_REGISTRYINDEX, (n)))\n\n#define luaL_opt(L,f,n,d)\t(lua_isnoneornil(L,(n)) ? (d) : f(L,(n)))\n\n#define luaL_loadbuffer(L,s,sz,n)\tluaL_loadbufferx(L,s,sz,n,NULL)\n\n\n/*\n** Perform arithmetic operations on lua_Integer values with wrap-around\n** semantics, as the Lua core does.\n*/\n#define luaL_intop(op,v1,v2)  \\\n\t((lua_Integer)((lua_Unsigned)(v1) op (lua_Unsigned)(v2)))\n\n\n/* push the value used to represent failure/error */\n#define luaL_pushfail(L)\tlua_pushnil(L)\n\n\n/*\n** Internal assertions for in-house debugging\n*/\n#if !defined(lua_assert)\n\n#if defined LUAI_ASSERT\n  #include <assert.h>\n  #define lua_assert(c)\t\tassert(c)\n#else\n  #define lua_assert(c)\t\t((void)0)\n#endif\n\n#endif\n\n\n\n/*\n** {======================================================\n** Generic Buffer manipulation\n** =======================================================\n*/\n\nstruct luaL_Buffer {\n  char *b;  /* buffer address */\n  size_t size;  /* buffer size */\n  size_t n;  /* number of characters in buffer */\n  lua_State *L;\n  union {\n    LUAI_MAXALIGN;  /* ensure maximum alignment for buffer */\n    char b[LUAL_BUFFERSIZE];  /* initial buffer */\n  } init;\n};\n\n\n#define luaL_bufflen(bf)\t((bf)->n)\n#define luaL_buffaddr(bf)\t((bf)->b)\n\n\n#define luaL_addchar(B,c) \\\n  ((void)((B)->n < (B)->size || luaL_prepbuffsize((B), 1)), \\\n   ((B)->b[(B)->n++] = (c)))\n\n#define luaL_addsize(B,s)\t((B)->n += (s))\n\n#define luaL_buffsub(B,s)\t((B)->n -= (s))\n\nLUALIB_API void (luaL_buffinit) (lua_State *L, luaL_Buffer *B);\nLUALIB_API char *(luaL_prepbuffsize) (luaL_Buffer *B, size_t sz);\nLUALIB_API void (luaL_addlstring) (luaL_Buffer *B, const char *s, size_t l);\nLUALIB_API void (luaL_addstring) (luaL_Buffer *B, const char *s);\nLUALIB_API void (luaL_addvalue) (luaL_Buffer *B);\nLUALIB_API void (luaL_pushresult) (luaL_Buffer *B);\nLUALIB_API void (luaL_pushresultsize) (luaL_Buffer *B, size_t sz);\nLUALIB_API char *(luaL_buffinitsize) (lua_State *L, luaL_Buffer *B, size_t sz);\n\n#define luaL_prepbuffer(B)\tluaL_prepbuffsize(B, LUAL_BUFFERSIZE)\n\n/* }====================================================== */\n\n\n\n/*\n** {======================================================\n** File handles for IO library\n** =======================================================\n*/\n\n/*\n** A file handle is a userdata with metatable 'LUA_FILEHANDLE' and\n** initial structure 'luaL_Stream' (it may contain other fields\n** after that initial structure).\n*/\n\n#define LUA_FILEHANDLE          \"FILE*\"\n\n\ntypedef struct luaL_Stream {\n  FILE *f;  /* stream (NULL for incompletely created streams) */\n  lua_CFunction closef;  /* to close stream (NULL for closed streams) */\n} luaL_Stream;\n\n/* }====================================================== */\n\n/*\n** {==================================================================\n** \"Abstraction Layer\" for basic report of messages and errors\n** ===================================================================\n*/\n\n/* print a string */\n#if !defined(lua_writestring)\n#define lua_writestring(s,l)   fwrite((s), sizeof(char), (l), stdout)\n#endif\n\n/* print a newline and flush the output */\n#if !defined(lua_writeline)\n#define lua_writeline()        (lua_writestring(\"\\n\", 1), fflush(stdout))\n#endif\n\n/* print an error message */\n#if !defined(lua_writestringerror)\n#define lua_writestringerror(s,p) \\\n        (fprintf(stderr, (s), (p)), fflush(stderr))\n#endif\n\n/* }================================================================== */\n\n\n/*\n** {============================================================\n** Compatibility with deprecated conversions\n** =============================================================\n*/\n#if defined(LUA_COMPAT_APIINTCASTS)\n\n#define luaL_checkunsigned(L,a)\t((lua_Unsigned)luaL_checkinteger(L,a))\n#define luaL_optunsigned(L,a,d)\t\\\n\t((lua_Unsigned)luaL_optinteger(L,a,(lua_Integer)(d)))\n\n#define luaL_checkint(L,n)\t((int)luaL_checkinteger(L, (n)))\n#define luaL_optint(L,n,d)\t((int)luaL_optinteger(L, (n), (d)))\n\n#define luaL_checklong(L,n)\t((long)luaL_checkinteger(L, (n)))\n#define luaL_optlong(L,n,d)\t((long)luaL_optinteger(L, (n), (d)))\n\n#endif\n/* }============================================================ */\n\n\n\n#endif\n\n\n/*\n** $Id: lualib.h $\n** Lua standard libraries\n** See Copyright Notice in lua.h\n*/\n\n\n#ifndef lualib_h\n#define lualib_h\n\n/*#include \"lua.h\"*/\n\n\n/* version suffix for environment variable names */\n#define LUA_VERSUFFIX          \"_\" LUA_VERSION_MAJOR \"_\" LUA_VERSION_MINOR\n\n\nLUAMOD_API int (luaopen_base) (lua_State *L);\n\n#define LUA_COLIBNAME\t\"coroutine\"\nLUAMOD_API int (luaopen_coroutine) (lua_State *L);\n\n#define LUA_TABLIBNAME\t\"table\"\nLUAMOD_API int (luaopen_table) (lua_State *L);\n\n#define LUA_IOLIBNAME\t\"io\"\nLUAMOD_API int (luaopen_io) (lua_State *L);\n\n#define LUA_OSLIBNAME\t\"os\"\nLUAMOD_API int (luaopen_os) (lua_State *L);\n\n#define LUA_STRLIBNAME\t\"string\"\nLUAMOD_API int (luaopen_string) (lua_State *L);\n\n#define LUA_UTF8LIBNAME\t\"utf8\"\nLUAMOD_API int (luaopen_utf8) (lua_State *L);\n\n#define LUA_MATHLIBNAME\t\"math\"\nLUAMOD_API int (luaopen_math) (lua_State *L);\n\n#define LUA_DBLIBNAME\t\"debug\"\nLUAMOD_API int (luaopen_debug) (lua_State *L);\n\n#define LUA_LOADLIBNAME\t\"package\"\nLUAMOD_API int (luaopen_package) (lua_State *L);\n\n\n/* open all previous libraries */\nLUALIB_API void (luaL_openlibs) (lua_State *L);\n\n\n#endif\n#ifdef LUA_IMPL\ntypedef struct CallInfo CallInfo;\n/*\n** $Id: llimits.h $\n** Limits, basic types, and some other 'installation-dependent' definitions\n** See Copyright Notice in lua.h\n*/\n\n#ifndef llimits_h\n#define llimits_h\n\n\n#include <limits.h>\n#include <stddef.h>\n\n\n/*#include \"lua.h\"*/\n\n\n/*\n** 'lu_mem' and 'l_mem' are unsigned/signed integers big enough to count\n** the total memory used by Lua (in bytes). Usually, 'size_t' and\n** 'ptrdiff_t' should work, but we use 'long' for 16-bit machines.\n*/\n#if defined(LUAI_MEM)\t\t/* { external definitions? */\ntypedef LUAI_UMEM lu_mem;\ntypedef LUAI_MEM l_mem;\n#elif LUAI_IS32INT\t/* }{ */\ntypedef size_t lu_mem;\ntypedef ptrdiff_t l_mem;\n#else  /* 16-bit ints */\t/* }{ */\ntypedef unsigned long lu_mem;\ntypedef long l_mem;\n#endif\t\t\t\t/* } */\n\n\n/* chars used as small naturals (so that 'char' is reserved for characters) */\ntypedef unsigned char lu_byte;\ntypedef signed char ls_byte;\n\n\n/* maximum value for size_t */\n#define MAX_SIZET\t((size_t)(~(size_t)0))\n\n/* maximum size visible for Lua (must be representable in a lua_Integer) */\n#define MAX_SIZE\t(sizeof(size_t) < sizeof(lua_Integer) ? MAX_SIZET \\\n                          : (size_t)(LUA_MAXINTEGER))\n\n\n#define MAX_LUMEM\t((lu_mem)(~(lu_mem)0))\n\n#define MAX_LMEM\t((l_mem)(MAX_LUMEM >> 1))\n\n\n#define MAX_INT\t\tINT_MAX  /* maximum value of an int */\n\n\n/*\n** floor of the log2 of the maximum signed value for integral type 't'.\n** (That is, maximum 'n' such that '2^n' fits in the given signed type.)\n*/\n#define log2maxs(t)\t(sizeof(t) * 8 - 2)\n\n\n/*\n** test whether an unsigned value is a power of 2 (or zero)\n*/\n#define ispow2(x)\t(((x) & ((x) - 1)) == 0)\n\n\n/* number of chars of a literal string without the ending \\0 */\n#define LL(x)   (sizeof(x)/sizeof(char) - 1)\n\n\n/*\n** conversion of pointer to unsigned integer: this is for hashing only;\n** there is no problem if the integer cannot hold the whole pointer\n** value. (In strict ISO C this may cause undefined behavior, but no\n** actual machine seems to bother.)\n*/\n#if !defined(LUA_USE_C89) && defined(__STDC_VERSION__) && \\\n    __STDC_VERSION__ >= 199901L\n#include <stdint.h>\n#if defined(UINTPTR_MAX)  /* even in C99 this type is optional */\n#define L_P2I\tuintptr_t\n#else  /* no 'intptr'? */\n#define L_P2I\tuintmax_t  /* use the largest available integer */\n#endif\n#else  /* C89 option */\n#define L_P2I\tsize_t\n#endif\n\n#define point2uint(p)\t((unsigned int)((L_P2I)(p) & UINT_MAX))\n\n\n\n/* types of 'usual argument conversions' for lua_Number and lua_Integer */\ntypedef LUAI_UACNUMBER l_uacNumber;\ntypedef LUAI_UACINT l_uacInt;\n\n\n/*\n** Internal assertions for in-house debugging\n*/\n#if defined LUAI_ASSERT\n#undef NDEBUG\n#include <assert.h>\n#define lua_assert(c)           assert(c)\n#endif\n\n#if defined(lua_assert)\n#define check_exp(c,e)\t\t(lua_assert(c), (e))\n/* to avoid problems with conditions too long */\n#define lua_longassert(c)\t((c) ? (void)0 : lua_assert(0))\n#else\n#define lua_assert(c)\t\t((void)0)\n#define check_exp(c,e)\t\t(e)\n#define lua_longassert(c)\t((void)0)\n#endif\n\n/*\n** assertion for checking API calls\n*/\n#if !defined(luai_apicheck)\n#define luai_apicheck(l,e)\t((void)l, lua_assert(e))\n#endif\n\n#define api_check(l,e,msg)\tluai_apicheck(l,(e) && msg)\n\n\n/* macro to avoid warnings about unused variables */\n#if !defined(UNUSED)\n#define UNUSED(x)\t((void)(x))\n#endif\n\n\n/* type casts (a macro highlights casts in the code) */\n#define cast(t, exp)\t((t)(exp))\n\n#define cast_void(i)\tcast(void, (i))\n#define cast_voidp(i)\tcast(void *, (i))\n#define cast_num(i)\tcast(lua_Number, (i))\n#define cast_int(i)\tcast(int, (i))\n#define cast_uint(i)\tcast(unsigned int, (i))\n#define cast_byte(i)\tcast(lu_byte, (i))\n#define cast_uchar(i)\tcast(unsigned char, (i))\n#define cast_char(i)\tcast(char, (i))\n#define cast_charp(i)\tcast(char *, (i))\n#define cast_sizet(i)\tcast(size_t, (i))\n\n\n/* cast a signed lua_Integer to lua_Unsigned */\n#if !defined(l_castS2U)\n#define l_castS2U(i)\t((lua_Unsigned)(i))\n#endif\n\n/*\n** cast a lua_Unsigned to a signed lua_Integer; this cast is\n** not strict ISO C, but two-complement architectures should\n** work fine.\n*/\n#if !defined(l_castU2S)\n#define l_castU2S(i)\t((lua_Integer)(i))\n#endif\n\n\n/*\n** non-return type\n*/\n#if !defined(l_noret)\n\n#if defined(__GNUC__)\n#define l_noret\t\tvoid __attribute__((noreturn))\n#elif defined(_MSC_VER) && _MSC_VER >= 1200\n#define l_noret\t\tvoid __declspec(noreturn)\n#else\n#define l_noret\t\tvoid\n#endif\n\n#endif\n\n\n/*\n** Inline functions\n*/\n#if !defined(LUA_USE_C89)\n#define l_inline\tinline\n#elif defined(__GNUC__)\n#define l_inline\t__inline__\n#else\n#define l_inline\t/* empty */\n#endif\n\n#define l_sinline\tstatic l_inline\n\n\n/*\n** type for virtual-machine instructions;\n** must be an unsigned with (at least) 4 bytes (see details in lopcodes.h)\n*/\n#if LUAI_IS32INT\ntypedef unsigned int l_uint32;\n#else\ntypedef unsigned long l_uint32;\n#endif\n\ntypedef l_uint32 Instruction;\n\n\n\n/*\n** Maximum length for short strings, that is, strings that are\n** internalized. (Cannot be smaller than reserved words or tags for\n** metamethods, as these strings must be internalized;\n** #(\"function\") = 8, #(\"__newindex\") = 10.)\n*/\n#if !defined(LUAI_MAXSHORTLEN)\n#define LUAI_MAXSHORTLEN\t40\n#endif\n\n\n/*\n** Initial size for the string table (must be power of 2).\n** The Lua core alone registers ~50 strings (reserved words +\n** metaevent keys + a few others). Libraries would typically add\n** a few dozens more.\n*/\n#if !defined(MINSTRTABSIZE)\n#define MINSTRTABSIZE\t128\n#endif\n\n\n/*\n** Size of cache for strings in the API. 'N' is the number of\n** sets (better be a prime) and \"M\" is the size of each set (M == 1\n** makes a direct cache.)\n*/\n#if !defined(STRCACHE_N)\n#define STRCACHE_N\t\t53\n#define STRCACHE_M\t\t2\n#endif\n\n\n/* minimum size for string buffer */\n#if !defined(LUA_MINBUFFER)\n#define LUA_MINBUFFER\t32\n#endif\n\n\n/*\n** Maximum depth for nested C calls, syntactical nested non-terminals,\n** and other features implemented through recursion in C. (Value must\n** fit in a 16-bit unsigned integer. It must also be compatible with\n** the size of the C stack.)\n*/\n#if !defined(LUAI_MAXCCALLS)\n#define LUAI_MAXCCALLS\t\t200\n#endif\n\n\n/*\n** macros that are executed whenever program enters the Lua core\n** ('lua_lock') and leaves the core ('lua_unlock')\n*/\n#if !defined(lua_lock)\n#define lua_lock(L)\t((void) 0)\n#define lua_unlock(L)\t((void) 0)\n#endif\n\n/*\n** macro executed during Lua functions at points where the\n** function can yield.\n*/\n#if !defined(luai_threadyield)\n#define luai_threadyield(L)\t{lua_unlock(L); lua_lock(L);}\n#endif\n\n\n/*\n** these macros allow user-specific actions when a thread is\n** created/deleted/resumed/yielded.\n*/\n#if !defined(luai_userstateopen)\n#define luai_userstateopen(L)\t\t((void)L)\n#endif\n\n#if !defined(luai_userstateclose)\n#define luai_userstateclose(L)\t\t((void)L)\n#endif\n\n#if !defined(luai_userstatethread)\n#define luai_userstatethread(L,L1)\t((void)L)\n#endif\n\n#if !defined(luai_userstatefree)\n#define luai_userstatefree(L,L1)\t((void)L)\n#endif\n\n#if !defined(luai_userstateresume)\n#define luai_userstateresume(L,n)\t((void)L)\n#endif\n\n#if !defined(luai_userstateyield)\n#define luai_userstateyield(L,n)\t((void)L)\n#endif\n\n\n\n/*\n** The luai_num* macros define the primitive operations over numbers.\n*/\n\n/* floor division (defined as 'floor(a/b)') */\n#if !defined(luai_numidiv)\n#define luai_numidiv(L,a,b)     ((void)L, l_floor(luai_numdiv(L,a,b)))\n#endif\n\n/* float division */\n#if !defined(luai_numdiv)\n#define luai_numdiv(L,a,b)      ((a)/(b))\n#endif\n\n/*\n** modulo: defined as 'a - floor(a/b)*b'; the direct computation\n** using this definition has several problems with rounding errors,\n** so it is better to use 'fmod'. 'fmod' gives the result of\n** 'a - trunc(a/b)*b', and therefore must be corrected when\n** 'trunc(a/b) ~= floor(a/b)'. That happens when the division has a\n** non-integer negative result: non-integer result is equivalent to\n** a non-zero remainder 'm'; negative result is equivalent to 'a' and\n** 'b' with different signs, or 'm' and 'b' with different signs\n** (as the result 'm' of 'fmod' has the same sign of 'a').\n*/\n#if !defined(luai_nummod)\n#define luai_nummod(L,a,b,m)  \\\n  { (void)L; (m) = l_mathop(fmod)(a,b); \\\n    if (((m) > 0) ? (b) < 0 : ((m) < 0 && (b) > 0)) (m) += (b); }\n#endif\n\n/* exponentiation */\n#if !defined(luai_numpow)\n#define luai_numpow(L,a,b)  \\\n  ((void)L, (b == 2) ? (a)*(a) : l_mathop(pow)(a,b))\n#endif\n\n/* the others are quite standard operations */\n#if !defined(luai_numadd)\n#define luai_numadd(L,a,b)      ((a)+(b))\n#define luai_numsub(L,a,b)      ((a)-(b))\n#define luai_nummul(L,a,b)      ((a)*(b))\n#define luai_numunm(L,a)        (-(a))\n#define luai_numeq(a,b)         ((a)==(b))\n#define luai_numlt(a,b)         ((a)<(b))\n#define luai_numle(a,b)         ((a)<=(b))\n#define luai_numgt(a,b)         ((a)>(b))\n#define luai_numge(a,b)         ((a)>=(b))\n#define luai_numisnan(a)        (!luai_numeq((a), (a)))\n#endif\n\n\n\n\n\n/*\n** macro to control inclusion of some hard tests on stack reallocation\n*/\n#if !defined(HARDSTACKTESTS)\n#define condmovestack(L,pre,pos)\t((void)0)\n#else\n/* realloc stack keeping its size */\n#define condmovestack(L,pre,pos)  \\\n  { int sz_ = stacksize(L); pre; luaD_reallocstack((L), sz_, 0); pos; }\n#endif\n\n#if !defined(HARDMEMTESTS)\n#define condchangemem(L,pre,pos)\t((void)0)\n#else\n#define condchangemem(L,pre,pos)  \\\n\t{ if (gcrunning(G(L))) { pre; luaC_fullgc(L, 0); pos; } }\n#endif\n\n#endif\n/*\n** $Id: lobject.h $\n** Type definitions for Lua objects\n** See Copyright Notice in lua.h\n*/\n\n\n#ifndef lobject_h\n#define lobject_h\n\n\n#include <stdarg.h>\n\n\n/*#include \"llimits.h\"*/\n/*#include \"lua.h\"*/\n\n\n/*\n** Extra types for collectable non-values\n*/\n#define LUA_TUPVAL\tLUA_NUMTYPES  /* upvalues */\n#define LUA_TPROTO\t(LUA_NUMTYPES+1)  /* function prototypes */\n#define LUA_TDEADKEY\t(LUA_NUMTYPES+2)  /* removed keys in tables */\n\n\n\n/*\n** number of all possible types (including LUA_TNONE but excluding DEADKEY)\n*/\n#define LUA_TOTALTYPES\t\t(LUA_TPROTO + 2)\n\n\n/*\n** tags for Tagged Values have the following use of bits:\n** bits 0-3: actual tag (a LUA_T* constant)\n** bits 4-5: variant bits\n** bit 6: whether value is collectable\n*/\n\n/* add variant bits to a type */\n#define makevariant(t,v)\t((t) | ((v) << 4))\n\n\n\n/*\n** Union of all Lua values\n*/\ntypedef union Value {\n  struct GCObject *gc;    /* collectable objects */\n  void *p;         /* light userdata */\n  lua_CFunction f; /* light C functions */\n  lua_Integer i;   /* integer numbers */\n  lua_Number n;    /* float numbers */\n  /* not used, but may avoid warnings for uninitialized value */\n  lu_byte ub;\n} Value;\n\n\n/*\n** Tagged Values. This is the basic representation of values in Lua:\n** an actual value plus a tag with its type.\n*/\n\n#define TValuefields\tValue value_; lu_byte tt_\n\ntypedef struct TValue {\n  TValuefields;\n} TValue;\n\n\n#define val_(o)\t\t((o)->value_)\n#define valraw(o)\t(val_(o))\n\n\n/* raw type tag of a TValue */\n#define rawtt(o)\t((o)->tt_)\n\n/* tag with no variants (bits 0-3) */\n#define novariant(t)\t((t) & 0x0F)\n\n/* type tag of a TValue (bits 0-3 for tags + variant bits 4-5) */\n#define withvariant(t)\t((t) & 0x3F)\n#define ttypetag(o)\twithvariant(rawtt(o))\n\n/* type of a TValue */\n#define ttype(o)\t(novariant(rawtt(o)))\n\n\n/* Macros to test type */\n#define checktag(o,t)\t\t(rawtt(o) == (t))\n#define checktype(o,t)\t\t(ttype(o) == (t))\n\n\n/* Macros for internal tests */\n\n/* collectable object has the same tag as the original value */\n#define righttt(obj)\t\t(ttypetag(obj) == gcvalue(obj)->tt)\n\n/*\n** Any value being manipulated by the program either is non\n** collectable, or the collectable object has the right tag\n** and it is not dead. The option 'L == NULL' allows other\n** macros using this one to be used where L is not available.\n*/\n#define checkliveness(L,obj) \\\n\t((void)L, lua_longassert(!iscollectable(obj) || \\\n\t\t(righttt(obj) && (L == NULL || !isdead(G(L),gcvalue(obj))))))\n\n\n/* Macros to set values */\n\n/* set a value's tag */\n#define settt_(o,t)\t((o)->tt_=(t))\n\n\n/* main macro to copy values (from 'obj2' to 'obj1') */\n#define setobj(L,obj1,obj2) \\\n\t{ TValue *io1=(obj1); const TValue *io2=(obj2); \\\n          io1->value_ = io2->value_; settt_(io1, io2->tt_); \\\n\t  checkliveness(L,io1); lua_assert(!isnonstrictnil(io1)); }\n\n/*\n** Different types of assignments, according to source and destination.\n** (They are mostly equal now, but may be different in the future.)\n*/\n\n/* from stack to stack */\n#define setobjs2s(L,o1,o2)\tsetobj(L,s2v(o1),s2v(o2))\n/* to stack (not from same stack) */\n#define setobj2s(L,o1,o2)\tsetobj(L,s2v(o1),o2)\n/* from table to same table */\n#define setobjt2t\tsetobj\n/* to new object */\n#define setobj2n\tsetobj\n/* to table */\n#define setobj2t\tsetobj\n\n\n/*\n** Entries in a Lua stack. Field 'tbclist' forms a list of all\n** to-be-closed variables active in this stack. Dummy entries are\n** used when the distance between two tbc variables does not fit\n** in an unsigned short. They are represented by delta==0, and\n** their real delta is always the maximum value that fits in\n** that field.\n*/\ntypedef union StackValue {\n  TValue val;\n  struct {\n    TValuefields;\n    unsigned short delta;\n  } tbclist;\n} StackValue;\n\n\n/* index to stack elements */\ntypedef StackValue *StkId;\n\n\n/*\n** When reallocating the stack, change all pointers to the stack into\n** proper offsets.\n*/\ntypedef union {\n  StkId p;  /* actual pointer */\n  ptrdiff_t offset;  /* used while the stack is being reallocated */\n} StkIdRel;\n\n\n/* convert a 'StackValue' to a 'TValue' */\n#define s2v(o)\t(&(o)->val)\n\n\n\n/*\n** {==================================================================\n** Nil\n** ===================================================================\n*/\n\n/* Standard nil */\n#define LUA_VNIL\tmakevariant(LUA_TNIL, 0)\n\n/* Empty slot (which might be different from a slot containing nil) */\n#define LUA_VEMPTY\tmakevariant(LUA_TNIL, 1)\n\n/* Value returned for a key not found in a table (absent key) */\n#define LUA_VABSTKEY\tmakevariant(LUA_TNIL, 2)\n\n\n/* macro to test for (any kind of) nil */\n#define ttisnil(v)\t\tchecktype((v), LUA_TNIL)\n\n\n/* macro to test for a standard nil */\n#define ttisstrictnil(o)\tchecktag((o), LUA_VNIL)\n\n\n#define setnilvalue(obj) settt_(obj, LUA_VNIL)\n\n\n#define isabstkey(v)\t\tchecktag((v), LUA_VABSTKEY)\n\n\n/*\n** macro to detect non-standard nils (used only in assertions)\n*/\n#define isnonstrictnil(v)\t(ttisnil(v) && !ttisstrictnil(v))\n\n\n/*\n** By default, entries with any kind of nil are considered empty.\n** (In any definition, values associated with absent keys must also\n** be accepted as empty.)\n*/\n#define isempty(v)\t\tttisnil(v)\n\n\n/* macro defining a value corresponding to an absent key */\n#define ABSTKEYCONSTANT\t\t{NULL}, LUA_VABSTKEY\n\n\n/* mark an entry as empty */\n#define setempty(v)\t\tsettt_(v, LUA_VEMPTY)\n\n\n\n/* }================================================================== */\n\n\n/*\n** {==================================================================\n** Booleans\n** ===================================================================\n*/\n\n\n#define LUA_VFALSE\tmakevariant(LUA_TBOOLEAN, 0)\n#define LUA_VTRUE\tmakevariant(LUA_TBOOLEAN, 1)\n\n#define ttisboolean(o)\t\tchecktype((o), LUA_TBOOLEAN)\n#define ttisfalse(o)\t\tchecktag((o), LUA_VFALSE)\n#define ttistrue(o)\t\tchecktag((o), LUA_VTRUE)\n\n\n#define l_isfalse(o)\t(ttisfalse(o) || ttisnil(o))\n\n\n#define setbfvalue(obj)\t\tsettt_(obj, LUA_VFALSE)\n#define setbtvalue(obj)\t\tsettt_(obj, LUA_VTRUE)\n\n/* }================================================================== */\n\n\n/*\n** {==================================================================\n** Threads\n** ===================================================================\n*/\n\n#define LUA_VTHREAD\t\tmakevariant(LUA_TTHREAD, 0)\n\n#define ttisthread(o)\t\tchecktag((o), ctb(LUA_VTHREAD))\n\n#define thvalue(o)\tcheck_exp(ttisthread(o), gco2th(val_(o).gc))\n\n#define setthvalue(L,obj,x) \\\n  { TValue *io = (obj); lua_State *x_ = (x); \\\n    val_(io).gc = obj2gco(x_); settt_(io, ctb(LUA_VTHREAD)); \\\n    checkliveness(L,io); }\n\n#define setthvalue2s(L,o,t)\tsetthvalue(L,s2v(o),t)\n\n/* }================================================================== */\n\n\n/*\n** {==================================================================\n** Collectable Objects\n** ===================================================================\n*/\n\n/*\n** Common Header for all collectable objects (in macro form, to be\n** included in other objects)\n*/\n#define CommonHeader\tstruct GCObject *next; lu_byte tt; lu_byte marked\n\n\n/* Common type for all collectable objects */\ntypedef struct GCObject {\n  CommonHeader;\n} GCObject;\n\n\n/* Bit mark for collectable types */\n#define BIT_ISCOLLECTABLE\t(1 << 6)\n\n#define iscollectable(o)\t(rawtt(o) & BIT_ISCOLLECTABLE)\n\n/* mark a tag as collectable */\n#define ctb(t)\t\t\t((t) | BIT_ISCOLLECTABLE)\n\n#define gcvalue(o)\tcheck_exp(iscollectable(o), val_(o).gc)\n\n#define gcvalueraw(v)\t((v).gc)\n\n#define setgcovalue(L,obj,x) \\\n  { TValue *io = (obj); GCObject *i_g=(x); \\\n    val_(io).gc = i_g; settt_(io, ctb(i_g->tt)); }\n\n/* }================================================================== */\n\n\n/*\n** {==================================================================\n** Numbers\n** ===================================================================\n*/\n\n/* Variant tags for numbers */\n#define LUA_VNUMINT\tmakevariant(LUA_TNUMBER, 0)  /* integer numbers */\n#define LUA_VNUMFLT\tmakevariant(LUA_TNUMBER, 1)  /* float numbers */\n\n#define ttisnumber(o)\t\tchecktype((o), LUA_TNUMBER)\n#define ttisfloat(o)\t\tchecktag((o), LUA_VNUMFLT)\n#define ttisinteger(o)\t\tchecktag((o), LUA_VNUMINT)\n\n#define nvalue(o)\tcheck_exp(ttisnumber(o), \\\n\t(ttisinteger(o) ? cast_num(ivalue(o)) : fltvalue(o)))\n#define fltvalue(o)\tcheck_exp(ttisfloat(o), val_(o).n)\n#define ivalue(o)\tcheck_exp(ttisinteger(o), val_(o).i)\n\n#define fltvalueraw(v)\t((v).n)\n#define ivalueraw(v)\t((v).i)\n\n#define setfltvalue(obj,x) \\\n  { TValue *io=(obj); val_(io).n=(x); settt_(io, LUA_VNUMFLT); }\n\n#define chgfltvalue(obj,x) \\\n  { TValue *io=(obj); lua_assert(ttisfloat(io)); val_(io).n=(x); }\n\n#define setivalue(obj,x) \\\n  { TValue *io=(obj); val_(io).i=(x); settt_(io, LUA_VNUMINT); }\n\n#define chgivalue(obj,x) \\\n  { TValue *io=(obj); lua_assert(ttisinteger(io)); val_(io).i=(x); }\n\n/* }================================================================== */\n\n\n/*\n** {==================================================================\n** Strings\n** ===================================================================\n*/\n\n/* Variant tags for strings */\n#define LUA_VSHRSTR\tmakevariant(LUA_TSTRING, 0)  /* short strings */\n#define LUA_VLNGSTR\tmakevariant(LUA_TSTRING, 1)  /* long strings */\n\n#define ttisstring(o)\t\tchecktype((o), LUA_TSTRING)\n#define ttisshrstring(o)\tchecktag((o), ctb(LUA_VSHRSTR))\n#define ttislngstring(o)\tchecktag((o), ctb(LUA_VLNGSTR))\n\n#define tsvalueraw(v)\t(gco2ts((v).gc))\n\n#define tsvalue(o)\tcheck_exp(ttisstring(o), gco2ts(val_(o).gc))\n\n#define setsvalue(L,obj,x) \\\n  { TValue *io = (obj); TString *x_ = (x); \\\n    val_(io).gc = obj2gco(x_); settt_(io, ctb(x_->tt)); \\\n    checkliveness(L,io); }\n\n/* set a string to the stack */\n#define setsvalue2s(L,o,s)\tsetsvalue(L,s2v(o),s)\n\n/* set a string to a new object */\n#define setsvalue2n\tsetsvalue\n\n\n/*\n** Header for a string value.\n*/\ntypedef struct TString {\n  CommonHeader;\n  lu_byte extra;  /* reserved words for short strings; \"has hash\" for longs */\n  lu_byte shrlen;  /* length for short strings */\n  unsigned int hash;\n  union {\n    size_t lnglen;  /* length for long strings */\n    struct TString *hnext;  /* linked list for hash table */\n  } u;\n  char contents[1];\n} TString;\n\n\n\n/*\n** Get the actual string (array of bytes) from a 'TString'.\n*/\n#define getstr(ts)  ((ts)->contents)\n\n\n/* get the actual string (array of bytes) from a Lua value */\n#define svalue(o)       getstr(tsvalue(o))\n\n/* get string length from 'TString *s' */\n#define tsslen(s)\t((s)->tt == LUA_VSHRSTR ? (s)->shrlen : (s)->u.lnglen)\n\n/* get string length from 'TValue *o' */\n#define vslen(o)\ttsslen(tsvalue(o))\n\n/* }================================================================== */\n\n\n/*\n** {==================================================================\n** Userdata\n** ===================================================================\n*/\n\n\n/*\n** Light userdata should be a variant of userdata, but for compatibility\n** reasons they are also different types.\n*/\n#define LUA_VLIGHTUSERDATA\tmakevariant(LUA_TLIGHTUSERDATA, 0)\n\n#define LUA_VUSERDATA\t\tmakevariant(LUA_TUSERDATA, 0)\n\n#define ttislightuserdata(o)\tchecktag((o), LUA_VLIGHTUSERDATA)\n#define ttisfulluserdata(o)\tchecktag((o), ctb(LUA_VUSERDATA))\n\n#define pvalue(o)\tcheck_exp(ttislightuserdata(o), val_(o).p)\n#define uvalue(o)\tcheck_exp(ttisfulluserdata(o), gco2u(val_(o).gc))\n\n#define pvalueraw(v)\t((v).p)\n\n#define setpvalue(obj,x) \\\n  { TValue *io=(obj); val_(io).p=(x); settt_(io, LUA_VLIGHTUSERDATA); }\n\n#define setuvalue(L,obj,x) \\\n  { TValue *io = (obj); Udata *x_ = (x); \\\n    val_(io).gc = obj2gco(x_); settt_(io, ctb(LUA_VUSERDATA)); \\\n    checkliveness(L,io); }\n\n\n/* Ensures that addresses after this type are always fully aligned. */\ntypedef union UValue {\n  TValue uv;\n  LUAI_MAXALIGN;  /* ensures maximum alignment for udata bytes */\n} UValue;\n\n\n/*\n** Header for userdata with user values;\n** memory area follows the end of this structure.\n*/\ntypedef struct Udata {\n  CommonHeader;\n  unsigned short nuvalue;  /* number of user values */\n  size_t len;  /* number of bytes */\n  struct Table *metatable;\n  GCObject *gclist;\n  UValue uv[1];  /* user values */\n} Udata;\n\n\n/*\n** Header for userdata with no user values. These userdata do not need\n** to be gray during GC, and therefore do not need a 'gclist' field.\n** To simplify, the code always use 'Udata' for both kinds of userdata,\n** making sure it never accesses 'gclist' on userdata with no user values.\n** This structure here is used only to compute the correct size for\n** this representation. (The 'bindata' field in its end ensures correct\n** alignment for binary data following this header.)\n*/\ntypedef struct Udata0 {\n  CommonHeader;\n  unsigned short nuvalue;  /* number of user values */\n  size_t len;  /* number of bytes */\n  struct Table *metatable;\n  union {LUAI_MAXALIGN;} bindata;\n} Udata0;\n\n\n/* compute the offset of the memory area of a userdata */\n#define udatamemoffset(nuv) \\\n\t((nuv) == 0 ? offsetof(Udata0, bindata)  \\\n                    : offsetof(Udata, uv) + (sizeof(UValue) * (nuv)))\n\n/* get the address of the memory block inside 'Udata' */\n#define getudatamem(u)\t(cast_charp(u) + udatamemoffset((u)->nuvalue))\n\n/* compute the size of a userdata */\n#define sizeudata(nuv,nb)\t(udatamemoffset(nuv) + (nb))\n\n/* }================================================================== */\n\n\n/*\n** {==================================================================\n** Prototypes\n** ===================================================================\n*/\n\n#define LUA_VPROTO\tmakevariant(LUA_TPROTO, 0)\n\n\n/*\n** Description of an upvalue for function prototypes\n*/\ntypedef struct Upvaldesc {\n  TString *name;  /* upvalue name (for debug information) */\n  lu_byte instack;  /* whether it is in stack (register) */\n  lu_byte idx;  /* index of upvalue (in stack or in outer function's list) */\n  lu_byte kind;  /* kind of corresponding variable */\n} Upvaldesc;\n\n\n/*\n** Description of a local variable for function prototypes\n** (used for debug information)\n*/\ntypedef struct LocVar {\n  TString *varname;\n  int startpc;  /* first point where variable is active */\n  int endpc;    /* first point where variable is dead */\n} LocVar;\n\n\n/*\n** Associates the absolute line source for a given instruction ('pc').\n** The array 'lineinfo' gives, for each instruction, the difference in\n** lines from the previous instruction. When that difference does not\n** fit into a byte, Lua saves the absolute line for that instruction.\n** (Lua also saves the absolute line periodically, to speed up the\n** computation of a line number: we can use binary search in the\n** absolute-line array, but we must traverse the 'lineinfo' array\n** linearly to compute a line.)\n*/\ntypedef struct AbsLineInfo {\n  int pc;\n  int line;\n} AbsLineInfo;\n\n/*\n** Function Prototypes\n*/\ntypedef struct Proto {\n  CommonHeader;\n  lu_byte numparams;  /* number of fixed (named) parameters */\n  lu_byte is_vararg;\n  lu_byte maxstacksize;  /* number of registers needed by this function */\n  int sizeupvalues;  /* size of 'upvalues' */\n  int sizek;  /* size of 'k' */\n  int sizecode;\n  int sizelineinfo;\n  int sizep;  /* size of 'p' */\n  int sizelocvars;\n  int sizeabslineinfo;  /* size of 'abslineinfo' */\n  int linedefined;  /* debug information  */\n  int lastlinedefined;  /* debug information  */\n  TValue *k;  /* constants used by the function */\n  Instruction *code;  /* opcodes */\n  struct Proto **p;  /* functions defined inside the function */\n  Upvaldesc *upvalues;  /* upvalue information */\n  ls_byte *lineinfo;  /* information about source lines (debug information) */\n  AbsLineInfo *abslineinfo;  /* idem */\n  LocVar *locvars;  /* information about local variables (debug information) */\n  TString  *source;  /* used for debug information */\n  GCObject *gclist;\n} Proto;\n\n/* }================================================================== */\n\n\n/*\n** {==================================================================\n** Functions\n** ===================================================================\n*/\n\n#define LUA_VUPVAL\tmakevariant(LUA_TUPVAL, 0)\n\n\n/* Variant tags for functions */\n#define LUA_VLCL\tmakevariant(LUA_TFUNCTION, 0)  /* Lua closure */\n#define LUA_VLCF\tmakevariant(LUA_TFUNCTION, 1)  /* light C function */\n#define LUA_VCCL\tmakevariant(LUA_TFUNCTION, 2)  /* C closure */\n\n#define ttisfunction(o)\t\tchecktype(o, LUA_TFUNCTION)\n#define ttisLclosure(o)\t\tchecktag((o), ctb(LUA_VLCL))\n#define ttislcf(o)\t\tchecktag((o), LUA_VLCF)\n#define ttisCclosure(o)\t\tchecktag((o), ctb(LUA_VCCL))\n#define ttisclosure(o)         (ttisLclosure(o) || ttisCclosure(o))\n\n\n#define isLfunction(o)\tttisLclosure(o)\n\n#define clvalue(o)\tcheck_exp(ttisclosure(o), gco2cl(val_(o).gc))\n#define clLvalue(o)\tcheck_exp(ttisLclosure(o), gco2lcl(val_(o).gc))\n#define fvalue(o)\tcheck_exp(ttislcf(o), val_(o).f)\n#define clCvalue(o)\tcheck_exp(ttisCclosure(o), gco2ccl(val_(o).gc))\n\n#define fvalueraw(v)\t((v).f)\n\n#define setclLvalue(L,obj,x) \\\n  { TValue *io = (obj); LClosure *x_ = (x); \\\n    val_(io).gc = obj2gco(x_); settt_(io, ctb(LUA_VLCL)); \\\n    checkliveness(L,io); }\n\n#define setclLvalue2s(L,o,cl)\tsetclLvalue(L,s2v(o),cl)\n\n#define setfvalue(obj,x) \\\n  { TValue *io=(obj); val_(io).f=(x); settt_(io, LUA_VLCF); }\n\n#define setclCvalue(L,obj,x) \\\n  { TValue *io = (obj); CClosure *x_ = (x); \\\n    val_(io).gc = obj2gco(x_); settt_(io, ctb(LUA_VCCL)); \\\n    checkliveness(L,io); }\n\n\n/*\n** Upvalues for Lua closures\n*/\ntypedef struct UpVal {\n  CommonHeader;\n  union {\n    TValue *p;  /* points to stack or to its own value */\n    ptrdiff_t offset;  /* used while the stack is being reallocated */\n  } v;\n  union {\n    struct {  /* (when open) */\n      struct UpVal *next;  /* linked list */\n      struct UpVal **previous;\n    } open;\n    TValue value;  /* the value (when closed) */\n  } u;\n} UpVal;\n\n\n\n#define ClosureHeader \\\n\tCommonHeader; lu_byte nupvalues; GCObject *gclist\n\ntypedef struct CClosure {\n  ClosureHeader;\n  lua_CFunction f;\n  TValue upvalue[1];  /* list of upvalues */\n} CClosure;\n\n\ntypedef struct LClosure {\n  ClosureHeader;\n  struct Proto *p;\n  UpVal *upvals[1];  /* list of upvalues */\n} LClosure;\n\n\ntypedef union Closure {\n  CClosure c;\n  LClosure l;\n} Closure;\n\n\n#define getproto(o)\t(clLvalue(o)->p)\n\n/* }================================================================== */\n\n\n/*\n** {==================================================================\n** Tables\n** ===================================================================\n*/\n\n#define LUA_VTABLE\tmakevariant(LUA_TTABLE, 0)\n\n#define ttistable(o)\t\tchecktag((o), ctb(LUA_VTABLE))\n\n#define hvalue(o)\tcheck_exp(ttistable(o), gco2t(val_(o).gc))\n\n#define sethvalue(L,obj,x) \\\n  { TValue *io = (obj); Table *x_ = (x); \\\n    val_(io).gc = obj2gco(x_); settt_(io, ctb(LUA_VTABLE)); \\\n    checkliveness(L,io); }\n\n#define sethvalue2s(L,o,h)\tsethvalue(L,s2v(o),h)\n\n\n/*\n** Nodes for Hash tables: A pack of two TValue's (key-value pairs)\n** plus a 'next' field to link colliding entries. The distribution\n** of the key's fields ('key_tt' and 'key_val') not forming a proper\n** 'TValue' allows for a smaller size for 'Node' both in 4-byte\n** and 8-byte alignments.\n*/\ntypedef union Node {\n  struct NodeKey {\n    TValuefields;  /* fields for value */\n    lu_byte key_tt;  /* key type */\n    int next;  /* for chaining */\n    Value key_val;  /* key value */\n  } u;\n  TValue i_val;  /* direct access to node's value as a proper 'TValue' */\n} Node;\n\n\n/* copy a value into a key */\n#define setnodekey(L,node,obj) \\\n\t{ Node *n_=(node); const TValue *io_=(obj); \\\n\t  n_->u.key_val = io_->value_; n_->u.key_tt = io_->tt_; \\\n\t  checkliveness(L,io_); }\n\n\n/* copy a value from a key */\n#define getnodekey(L,obj,node) \\\n\t{ TValue *io_=(obj); const Node *n_=(node); \\\n\t  io_->value_ = n_->u.key_val; io_->tt_ = n_->u.key_tt; \\\n\t  checkliveness(L,io_); }\n\n\n/*\n** About 'alimit': if 'isrealasize(t)' is true, then 'alimit' is the\n** real size of 'array'. Otherwise, the real size of 'array' is the\n** smallest power of two not smaller than 'alimit' (or zero iff 'alimit'\n** is zero); 'alimit' is then used as a hint for #t.\n*/\n\n#define BITRAS\t\t(1 << 7)\n#define isrealasize(t)\t\t(!((t)->flags & BITRAS))\n#define setrealasize(t)\t\t((t)->flags &= cast_byte(~BITRAS))\n#define setnorealasize(t)\t((t)->flags |= BITRAS)\n\n\ntypedef struct Table {\n  CommonHeader;\n  lu_byte flags;  /* 1<<p means tagmethod(p) is not present */\n  lu_byte lsizenode;  /* log2 of size of 'node' array */\n  unsigned int alimit;  /* \"limit\" of 'array' array */\n  TValue *array;  /* array part */\n  Node *node;\n  Node *lastfree;  /* any free position is before this position */\n  struct Table *metatable;\n  GCObject *gclist;\n} Table;\n\n\n/*\n** Macros to manipulate keys inserted in nodes\n*/\n#define keytt(node)\t\t((node)->u.key_tt)\n#define keyval(node)\t\t((node)->u.key_val)\n\n#define keyisnil(node)\t\t(keytt(node) == LUA_TNIL)\n#define keyisinteger(node)\t(keytt(node) == LUA_VNUMINT)\n#define keyival(node)\t\t(keyval(node).i)\n#define keyisshrstr(node)\t(keytt(node) == ctb(LUA_VSHRSTR))\n#define keystrval(node)\t\t(gco2ts(keyval(node).gc))\n\n#define setnilkey(node)\t\t(keytt(node) = LUA_TNIL)\n\n#define keyiscollectable(n)\t(keytt(n) & BIT_ISCOLLECTABLE)\n\n#define gckey(n)\t(keyval(n).gc)\n#define gckeyN(n)\t(keyiscollectable(n) ? gckey(n) : NULL)\n\n\n/*\n** Dead keys in tables have the tag DEADKEY but keep their original\n** gcvalue. This distinguishes them from regular keys but allows them to\n** be found when searched in a special way. ('next' needs that to find\n** keys removed from a table during a traversal.)\n*/\n#define setdeadkey(node)\t(keytt(node) = LUA_TDEADKEY)\n#define keyisdead(node)\t\t(keytt(node) == LUA_TDEADKEY)\n\n/* }================================================================== */\n\n\n\n/*\n** 'module' operation for hashing (size is always a power of 2)\n*/\n#define lmod(s,size) \\\n\t(check_exp((size&(size-1))==0, (cast_int((s) & ((size)-1)))))\n\n\n#define twoto(x)\t(1<<(x))\n#define sizenode(t)\t(twoto((t)->lsizenode))\n\n\n/* size of buffer for 'luaO_utf8esc' function */\n#define UTF8BUFFSZ\t8\n\nLUAI_FUNC int luaO_utf8esc (char *buff, unsigned long x);\nLUAI_FUNC int luaO_ceillog2 (unsigned int x);\nLUAI_FUNC int luaO_rawarith (lua_State *L, int op, const TValue *p1,\n                             const TValue *p2, TValue *res);\nLUAI_FUNC void luaO_arith (lua_State *L, int op, const TValue *p1,\n                           const TValue *p2, StkId res);\nLUAI_FUNC size_t luaO_str2num (const char *s, TValue *o);\nLUAI_FUNC int luaO_hexavalue (int c);\nLUAI_FUNC void luaO_tostring (lua_State *L, TValue *obj);\nLUAI_FUNC const char *luaO_pushvfstring (lua_State *L, const char *fmt,\n                                                       va_list argp);\nLUAI_FUNC const char *luaO_pushfstring (lua_State *L, const char *fmt, ...);\nLUAI_FUNC void luaO_chunkid (char *out, const char *source, size_t srclen);\n\n\n#endif\n\n/*\n** $Id: lmem.h $\n** Interface to Memory Manager\n** See Copyright Notice in lua.h\n*/\n\n#ifndef lmem_h\n#define lmem_h\n\n\n#include <stddef.h>\n\n/*#include \"llimits.h\"*/\n/*#include \"lua.h\"*/\n\n\n#define luaM_error(L)\tluaD_throw(L, LUA_ERRMEM)\n\n\n/*\n** This macro tests whether it is safe to multiply 'n' by the size of\n** type 't' without overflows. Because 'e' is always constant, it avoids\n** the runtime division MAX_SIZET/(e).\n** (The macro is somewhat complex to avoid warnings:  The 'sizeof'\n** comparison avoids a runtime comparison when overflow cannot occur.\n** The compiler should be able to optimize the real test by itself, but\n** when it does it, it may give a warning about \"comparison is always\n** false due to limited range of data type\"; the +1 tricks the compiler,\n** avoiding this warning but also this optimization.)\n*/\n#define luaM_testsize(n,e)  \\\n\t(sizeof(n) >= sizeof(size_t) && cast_sizet((n)) + 1 > MAX_SIZET/(e))\n\n#define luaM_checksize(L,n,e)  \\\n\t(luaM_testsize(n,e) ? luaM_toobig(L) : cast_void(0))\n\n\n/*\n** Computes the minimum between 'n' and 'MAX_SIZET/sizeof(t)', so that\n** the result is not larger than 'n' and cannot overflow a 'size_t'\n** when multiplied by the size of type 't'. (Assumes that 'n' is an\n** 'int' or 'unsigned int' and that 'int' is not larger than 'size_t'.)\n*/\n#define luaM_limitN(n,t)  \\\n  ((cast_sizet(n) <= MAX_SIZET/sizeof(t)) ? (n) :  \\\n     cast_uint((MAX_SIZET/sizeof(t))))\n\n\n/*\n** Arrays of chars do not need any test\n*/\n#define luaM_reallocvchar(L,b,on,n)  \\\n  cast_charp(luaM_saferealloc_(L, (b), (on)*sizeof(char), (n)*sizeof(char)))\n\n#define luaM_freemem(L, b, s)\tluaM_free_(L, (b), (s))\n#define luaM_free(L, b)\t\tluaM_free_(L, (b), sizeof(*(b)))\n#define luaM_freearray(L, b, n)   luaM_free_(L, (b), (n)*sizeof(*(b)))\n\n#define luaM_new(L,t)\t\tcast(t*, luaM_malloc_(L, sizeof(t), 0))\n#define luaM_newvector(L,n,t)\tcast(t*, luaM_malloc_(L, (n)*sizeof(t), 0))\n#define luaM_newvectorchecked(L,n,t) \\\n  (luaM_checksize(L,n,sizeof(t)), luaM_newvector(L,n,t))\n\n#define luaM_newobject(L,tag,s)\tluaM_malloc_(L, (s), tag)\n\n#define luaM_growvector(L,v,nelems,size,t,limit,e) \\\n\t((v)=cast(t *, luaM_growaux_(L,v,nelems,&(size),sizeof(t), \\\n                         luaM_limitN(limit,t),e)))\n\n#define luaM_reallocvector(L, v,oldn,n,t) \\\n   (cast(t *, luaM_realloc_(L, v, cast_sizet(oldn) * sizeof(t), \\\n                                  cast_sizet(n) * sizeof(t))))\n\n#define luaM_shrinkvector(L,v,size,fs,t) \\\n   ((v)=cast(t *, luaM_shrinkvector_(L, v, &(size), fs, sizeof(t))))\n\nLUAI_FUNC l_noret luaM_toobig (lua_State *L);\n\n/* not to be called directly */\nLUAI_FUNC void *luaM_realloc_ (lua_State *L, void *block, size_t oldsize,\n                                                          size_t size);\nLUAI_FUNC void *luaM_saferealloc_ (lua_State *L, void *block, size_t oldsize,\n                                                              size_t size);\nLUAI_FUNC void luaM_free_ (lua_State *L, void *block, size_t osize);\nLUAI_FUNC void *luaM_growaux_ (lua_State *L, void *block, int nelems,\n                               int *size, int size_elem, int limit,\n                               const char *what);\nLUAI_FUNC void *luaM_shrinkvector_ (lua_State *L, void *block, int *nelem,\n                                    int final_n, int size_elem);\nLUAI_FUNC void *luaM_malloc_ (lua_State *L, size_t size, int tag);\n\n#endif\n\n/*\n** $Id: ltm.h $\n** Tag methods\n** See Copyright Notice in lua.h\n*/\n\n#ifndef ltm_h\n#define ltm_h\n\n\n/*#include \"lobject.h\"*/\n/*#include \"lstate.h\"*/\n\n\n/*\n* WARNING: if you change the order of this enumeration,\n* grep \"ORDER TM\" and \"ORDER OP\"\n*/\ntypedef enum {\n  TM_INDEX,\n  TM_NEWINDEX,\n  TM_GC,\n  TM_MODE,\n  TM_LEN,\n  TM_EQ,  /* last tag method with fast access */\n  TM_ADD,\n  TM_SUB,\n  TM_MUL,\n  TM_MOD,\n  TM_POW,\n  TM_DIV,\n  TM_IDIV,\n  TM_BAND,\n  TM_BOR,\n  TM_BXOR,\n  TM_SHL,\n  TM_SHR,\n  TM_UNM,\n  TM_BNOT,\n  TM_LT,\n  TM_LE,\n  TM_CONCAT,\n  TM_CALL,\n  TM_CLOSE,\n  TM_N\t\t/* number of elements in the enum */\n} TMS;\n\n\n/*\n** Mask with 1 in all fast-access methods. A 1 in any of these bits\n** in the flag of a (meta)table means the metatable does not have the\n** corresponding metamethod field. (Bit 7 of the flag is used for\n** 'isrealasize'.)\n*/\n#define maskflags\t(~(~0u << (TM_EQ + 1)))\n\n\n/*\n** Test whether there is no tagmethod.\n** (Because tagmethods use raw accesses, the result may be an \"empty\" nil.)\n*/\n#define notm(tm)\tttisnil(tm)\n\n\n#define gfasttm(g,et,e) ((et) == NULL ? NULL : \\\n  ((et)->flags & (1u<<(e))) ? NULL : luaT_gettm(et, e, (g)->tmname[e]))\n\n#define fasttm(l,et,e)\tgfasttm(G(l), et, e)\n\n#define ttypename(x)\tluaT_typenames_[(x) + 1]\n\nLUAI_DDEC(const char *const luaT_typenames_[LUA_TOTALTYPES];)\n\n\nLUAI_FUNC const char *luaT_objtypename (lua_State *L, const TValue *o);\n\nLUAI_FUNC const TValue *luaT_gettm (Table *events, TMS event, TString *ename);\nLUAI_FUNC const TValue *luaT_gettmbyobj (lua_State *L, const TValue *o,\n                                                       TMS event);\nLUAI_FUNC void luaT_init (lua_State *L);\n\nLUAI_FUNC void luaT_callTM (lua_State *L, const TValue *f, const TValue *p1,\n                            const TValue *p2, const TValue *p3);\nLUAI_FUNC void luaT_callTMres (lua_State *L, const TValue *f,\n                            const TValue *p1, const TValue *p2, StkId p3);\nLUAI_FUNC void luaT_trybinTM (lua_State *L, const TValue *p1, const TValue *p2,\n                              StkId res, TMS event);\nLUAI_FUNC void luaT_tryconcatTM (lua_State *L);\nLUAI_FUNC void luaT_trybinassocTM (lua_State *L, const TValue *p1,\n       const TValue *p2, int inv, StkId res, TMS event);\nLUAI_FUNC void luaT_trybiniTM (lua_State *L, const TValue *p1, lua_Integer i2,\n                               int inv, StkId res, TMS event);\nLUAI_FUNC int luaT_callorderTM (lua_State *L, const TValue *p1,\n                                const TValue *p2, TMS event);\nLUAI_FUNC int luaT_callorderiTM (lua_State *L, const TValue *p1, int v2,\n                                 int inv, int isfloat, TMS event);\n\nLUAI_FUNC void luaT_adjustvarargs (lua_State *L, int nfixparams,\n                                   CallInfo *ci, const Proto *p);\nLUAI_FUNC void luaT_getvarargs (lua_State *L, CallInfo *ci,\n                                              StkId where, int wanted);\n\n\n#endif\n/*\n** $Id: lstate.h $\n** Global State\n** See Copyright Notice in lua.h\n*/\n\n#ifndef lstate_h\n#define lstate_h\n\n/*#include \"lua.h\"*/\n\n\n/* Some header files included here need this definition */\ntypedef struct CallInfo CallInfo;\n\n\n/*#include \"lobject.h\"*/\n/*#include \"ltm.h\"*/\n/*#include \"lzio.h\"*/\n\n\n/*\n** Some notes about garbage-collected objects: All objects in Lua must\n** be kept somehow accessible until being freed, so all objects always\n** belong to one (and only one) of these lists, using field 'next' of\n** the 'CommonHeader' for the link:\n**\n** 'allgc': all objects not marked for finalization;\n** 'finobj': all objects marked for finalization;\n** 'tobefnz': all objects ready to be finalized;\n** 'fixedgc': all objects that are not to be collected (currently\n** only small strings, such as reserved words).\n**\n** For the generational collector, some of these lists have marks for\n** generations. Each mark points to the first element in the list for\n** that particular generation; that generation goes until the next mark.\n**\n** 'allgc' -> 'survival': new objects;\n** 'survival' -> 'old': objects that survived one collection;\n** 'old1' -> 'reallyold': objects that became old in last collection;\n** 'reallyold' -> NULL: objects old for more than one cycle.\n**\n** 'finobj' -> 'finobjsur': new objects marked for finalization;\n** 'finobjsur' -> 'finobjold1': survived   \"\"\"\";\n** 'finobjold1' -> 'finobjrold': just old  \"\"\"\";\n** 'finobjrold' -> NULL: really old       \"\"\"\".\n**\n** All lists can contain elements older than their main ages, due\n** to 'luaC_checkfinalizer' and 'udata2finalize', which move\n** objects between the normal lists and the \"marked for finalization\"\n** lists. Moreover, barriers can age young objects in young lists as\n** OLD0, which then become OLD1. However, a list never contains\n** elements younger than their main ages.\n**\n** The generational collector also uses a pointer 'firstold1', which\n** points to the first OLD1 object in the list. It is used to optimize\n** 'markold'. (Potentially OLD1 objects can be anywhere between 'allgc'\n** and 'reallyold', but often the list has no OLD1 objects or they are\n** after 'old1'.) Note the difference between it and 'old1':\n** 'firstold1': no OLD1 objects before this point; there can be all\n**   ages after it.\n** 'old1': no objects younger than OLD1 after this point.\n*/\n\n/*\n** Moreover, there is another set of lists that control gray objects.\n** These lists are linked by fields 'gclist'. (All objects that\n** can become gray have such a field. The field is not the same\n** in all objects, but it always has this name.)  Any gray object\n** must belong to one of these lists, and all objects in these lists\n** must be gray (with two exceptions explained below):\n**\n** 'gray': regular gray objects, still waiting to be visited.\n** 'grayagain': objects that must be revisited at the atomic phase.\n**   That includes\n**   - black objects got in a write barrier;\n**   - all kinds of weak tables during propagation phase;\n**   - all threads.\n** 'weak': tables with weak values to be cleared;\n** 'ephemeron': ephemeron tables with white->white entries;\n** 'allweak': tables with weak keys and/or weak values to be cleared.\n**\n** The exceptions to that \"gray rule\" are:\n** - TOUCHED2 objects in generational mode stay in a gray list (because\n** they must be visited again at the end of the cycle), but they are\n** marked black because assignments to them must activate barriers (to\n** move them back to TOUCHED1).\n** - Open upvales are kept gray to avoid barriers, but they stay out\n** of gray lists. (They don't even have a 'gclist' field.)\n*/\n\n\n\n/*\n** About 'nCcalls':  This count has two parts: the lower 16 bits counts\n** the number of recursive invocations in the C stack; the higher\n** 16 bits counts the number of non-yieldable calls in the stack.\n** (They are together so that we can change and save both with one\n** instruction.)\n*/\n\n\n/* true if this thread does not have non-yieldable calls in the stack */\n#define yieldable(L)\t\t(((L)->nCcalls & 0xffff0000) == 0)\n\n/* real number of C calls */\n#define getCcalls(L)\t((L)->nCcalls & 0xffff)\n\n\n/* Increment the number of non-yieldable calls */\n#define incnny(L)\t((L)->nCcalls += 0x10000)\n\n/* Decrement the number of non-yieldable calls */\n#define decnny(L)\t((L)->nCcalls -= 0x10000)\n\n/* Non-yieldable call increment */\n#define nyci\t(0x10000 | 1)\n\n\n\n\nstruct lua_longjmp;  /* defined in ldo.c */\n\n\n/*\n** Atomic type (relative to signals) to better ensure that 'lua_sethook'\n** is thread safe\n*/\n#if !defined(l_signalT)\n#include <signal.h>\n#define l_signalT\tsig_atomic_t\n#endif\n\n\n/*\n** Extra stack space to handle TM calls and some other extras. This\n** space is not included in 'stack_last'. It is used only to avoid stack\n** checks, either because the element will be promptly popped or because\n** there will be a stack check soon after the push. Function frames\n** never use this extra space, so it does not need to be kept clean.\n*/\n#define EXTRA_STACK   5\n\n\n#define BASIC_STACK_SIZE        (2*LUA_MINSTACK)\n\n#define stacksize(th)\tcast_int((th)->stack_last.p - (th)->stack.p)\n\n\n/* kinds of Garbage Collection */\n#define KGC_INC\t\t0\t/* incremental gc */\n#define KGC_GEN\t\t1\t/* generational gc */\n\n\ntypedef struct stringtable {\n  TString **hash;\n  int nuse;  /* number of elements */\n  int size;\n} stringtable;\n\n\n/*\n** Information about a call.\n** About union 'u':\n** - field 'l' is used only for Lua functions;\n** - field 'c' is used only for C functions.\n** About union 'u2':\n** - field 'funcidx' is used only by C functions while doing a\n** protected call;\n** - field 'nyield' is used only while a function is \"doing\" an\n** yield (from the yield until the next resume);\n** - field 'nres' is used only while closing tbc variables when\n** returning from a function;\n** - field 'transferinfo' is used only during call/returnhooks,\n** before the function starts or after it ends.\n*/\nstruct CallInfo {\n  StkIdRel func;  /* function index in the stack */\n  StkIdRel\ttop;  /* top for this function */\n  struct CallInfo *previous, *next;  /* dynamic call link */\n  union {\n    struct {  /* only for Lua functions */\n      const Instruction *savedpc;\n      volatile l_signalT trap;\n      int nextraargs;  /* # of extra arguments in vararg functions */\n    } l;\n    struct {  /* only for C functions */\n      lua_KFunction k;  /* continuation in case of yields */\n      ptrdiff_t old_errfunc;\n      lua_KContext ctx;  /* context info. in case of yields */\n    } c;\n  } u;\n  union {\n    int funcidx;  /* called-function index */\n    int nyield;  /* number of values yielded */\n    int nres;  /* number of values returned */\n    struct {  /* info about transferred values (for call/return hooks) */\n      unsigned short ftransfer;  /* offset of first value transferred */\n      unsigned short ntransfer;  /* number of values transferred */\n    } transferinfo;\n  } u2;\n  short nresults;  /* expected number of results from this function */\n  unsigned short callstatus;\n};\n\n\n/*\n** Bits in CallInfo status\n*/\n#define CIST_OAH\t(1<<0)\t/* original value of 'allowhook' */\n#define CIST_C\t\t(1<<1)\t/* call is running a C function */\n#define CIST_FRESH\t(1<<2)\t/* call is on a fresh \"luaV_execute\" frame */\n#define CIST_HOOKED\t(1<<3)\t/* call is running a debug hook */\n#define CIST_YPCALL\t(1<<4)\t/* doing a yieldable protected call */\n#define CIST_TAIL\t(1<<5)\t/* call was tail called */\n#define CIST_HOOKYIELD\t(1<<6)\t/* last hook called yielded */\n#define CIST_FIN\t(1<<7)\t/* function \"called\" a finalizer */\n#define CIST_TRAN\t(1<<8)\t/* 'ci' has transfer information */\n#define CIST_CLSRET\t(1<<9)  /* function is closing tbc variables */\n/* Bits 10-12 are used for CIST_RECST (see below) */\n#define CIST_RECST\t10\n#if defined(LUA_COMPAT_LT_LE)\n#define CIST_LEQ\t(1<<13)  /* using __lt for __le */\n#endif\n\n\n/*\n** Field CIST_RECST stores the \"recover status\", used to keep the error\n** status while closing to-be-closed variables in coroutines, so that\n** Lua can correctly resume after an yield from a __close method called\n** because of an error.  (Three bits are enough for error status.)\n*/\n#define getcistrecst(ci)     (((ci)->callstatus >> CIST_RECST) & 7)\n#define setcistrecst(ci,st)  \\\n  check_exp(((st) & 7) == (st),   /* status must fit in three bits */  \\\n            ((ci)->callstatus = ((ci)->callstatus & ~(7 << CIST_RECST))  \\\n                                                  | ((st) << CIST_RECST)))\n\n\n/* active function is a Lua function */\n#define isLua(ci)\t(!((ci)->callstatus & CIST_C))\n\n/* call is running Lua code (not a hook) */\n#define isLuacode(ci)\t(!((ci)->callstatus & (CIST_C | CIST_HOOKED)))\n\n/* assume that CIST_OAH has offset 0 and that 'v' is strictly 0/1 */\n#define setoah(st,v)\t((st) = ((st) & ~CIST_OAH) | (v))\n#define getoah(st)\t((st) & CIST_OAH)\n\n\n/*\n** 'global state', shared by all threads of this state\n*/\ntypedef struct global_State {\n  lua_Alloc frealloc;  /* function to reallocate memory */\n  void *ud;         /* auxiliary data to 'frealloc' */\n  l_mem totalbytes;  /* number of bytes currently allocated - GCdebt */\n  l_mem GCdebt;  /* bytes allocated not yet compensated by the collector */\n  lu_mem GCestimate;  /* an estimate of the non-garbage memory in use */\n  lu_mem lastatomic;  /* see function 'genstep' in file 'lgc.c' */\n  stringtable strt;  /* hash table for strings */\n  TValue l_registry;\n  TValue nilvalue;  /* a nil value */\n  unsigned int seed;  /* randomized seed for hashes */\n  lu_byte currentwhite;\n  lu_byte gcstate;  /* state of garbage collector */\n  lu_byte gckind;  /* kind of GC running */\n  lu_byte gcstopem;  /* stops emergency collections */\n  lu_byte genminormul;  /* control for minor generational collections */\n  lu_byte genmajormul;  /* control for major generational collections */\n  lu_byte gcstp;  /* control whether GC is running */\n  lu_byte gcemergency;  /* true if this is an emergency collection */\n  lu_byte gcpause;  /* size of pause between successive GCs */\n  lu_byte gcstepmul;  /* GC \"speed\" */\n  lu_byte gcstepsize;  /* (log2 of) GC granularity */\n  GCObject *allgc;  /* list of all collectable objects */\n  GCObject **sweepgc;  /* current position of sweep in list */\n  GCObject *finobj;  /* list of collectable objects with finalizers */\n  GCObject *gray;  /* list of gray objects */\n  GCObject *grayagain;  /* list of objects to be traversed atomically */\n  GCObject *weak;  /* list of tables with weak values */\n  GCObject *ephemeron;  /* list of ephemeron tables (weak keys) */\n  GCObject *allweak;  /* list of all-weak tables */\n  GCObject *tobefnz;  /* list of userdata to be GC */\n  GCObject *fixedgc;  /* list of objects not to be collected */\n  /* fields for generational collector */\n  GCObject *survival;  /* start of objects that survived one GC cycle */\n  GCObject *old1;  /* start of old1 objects */\n  GCObject *reallyold;  /* objects more than one cycle old (\"really old\") */\n  GCObject *firstold1;  /* first OLD1 object in the list (if any) */\n  GCObject *finobjsur;  /* list of survival objects with finalizers */\n  GCObject *finobjold1;  /* list of old1 objects with finalizers */\n  GCObject *finobjrold;  /* list of really old objects with finalizers */\n  struct lua_State *twups;  /* list of threads with open upvalues */\n  lua_CFunction panic;  /* to be called in unprotected errors */\n  struct lua_State *mainthread;\n  TString *memerrmsg;  /* message for memory-allocation errors */\n  TString *tmname[TM_N];  /* array with tag-method names */\n  struct Table *mt[LUA_NUMTYPES];  /* metatables for basic types */\n  TString *strcache[STRCACHE_N][STRCACHE_M];  /* cache for strings in API */\n  lua_WarnFunction warnf;  /* warning function */\n  void *ud_warn;         /* auxiliary data to 'warnf' */\n} global_State;\n\n\n/*\n** 'per thread' state\n*/\nstruct lua_State {\n  CommonHeader;\n  lu_byte status;\n  lu_byte allowhook;\n  unsigned short nci;  /* number of items in 'ci' list */\n  StkIdRel top;  /* first free slot in the stack */\n  global_State *l_G;\n  CallInfo *ci;  /* call info for current function */\n  StkIdRel stack_last;  /* end of stack (last element + 1) */\n  StkIdRel stack;  /* stack base */\n  UpVal *openupval;  /* list of open upvalues in this stack */\n  StkIdRel tbclist;  /* list of to-be-closed variables */\n  GCObject *gclist;\n  struct lua_State *twups;  /* list of threads with open upvalues */\n  struct lua_longjmp *errorJmp;  /* current error recover point */\n  CallInfo base_ci;  /* CallInfo for first level (C calling Lua) */\n  volatile lua_Hook hook;\n  ptrdiff_t errfunc;  /* current error handling function (stack index) */\n  l_uint32 nCcalls;  /* number of nested (non-yieldable | C)  calls */\n  int oldpc;  /* last pc traced */\n  int basehookcount;\n  int hookcount;\n  volatile l_signalT hookmask;\n};\n\n\n#define G(L)\t(L->l_G)\n\n/*\n** 'g->nilvalue' being a nil value flags that the state was completely\n** build.\n*/\n#define completestate(g)\tttisnil(&g->nilvalue)\n\n\n/*\n** Union of all collectable objects (only for conversions)\n** ISO C99, 6.5.2.3 p.5:\n** \"if a union contains several structures that share a common initial\n** sequence [...], and if the union object currently contains one\n** of these structures, it is permitted to inspect the common initial\n** part of any of them anywhere that a declaration of the complete type\n** of the union is visible.\"\n*/\nunion GCUnion {\n  GCObject gc;  /* common header */\n  struct TString ts;\n  struct Udata u;\n  union Closure cl;\n  struct Table h;\n  struct Proto p;\n  struct lua_State th;  /* thread */\n  struct UpVal upv;\n};\n\n\n/*\n** ISO C99, 6.7.2.1 p.14:\n** \"A pointer to a union object, suitably converted, points to each of\n** its members [...], and vice versa.\"\n*/\n#define cast_u(o)\tcast(union GCUnion *, (o))\n\n/* macros to convert a GCObject into a specific value */\n#define gco2ts(o)  \\\n\tcheck_exp(novariant((o)->tt) == LUA_TSTRING, &((cast_u(o))->ts))\n#define gco2u(o)  check_exp((o)->tt == LUA_VUSERDATA, &((cast_u(o))->u))\n#define gco2lcl(o)  check_exp((o)->tt == LUA_VLCL, &((cast_u(o))->cl.l))\n#define gco2ccl(o)  check_exp((o)->tt == LUA_VCCL, &((cast_u(o))->cl.c))\n#define gco2cl(o)  \\\n\tcheck_exp(novariant((o)->tt) == LUA_TFUNCTION, &((cast_u(o))->cl))\n#define gco2t(o)  check_exp((o)->tt == LUA_VTABLE, &((cast_u(o))->h))\n#define gco2p(o)  check_exp((o)->tt == LUA_VPROTO, &((cast_u(o))->p))\n#define gco2th(o)  check_exp((o)->tt == LUA_VTHREAD, &((cast_u(o))->th))\n#define gco2upv(o)\tcheck_exp((o)->tt == LUA_VUPVAL, &((cast_u(o))->upv))\n\n\n/*\n** macro to convert a Lua object into a GCObject\n** (The access to 'tt' tries to ensure that 'v' is actually a Lua object.)\n*/\n#define obj2gco(v)\tcheck_exp((v)->tt >= LUA_TSTRING, &(cast_u(v)->gc))\n\n\n/* actual number of total bytes allocated */\n#define gettotalbytes(g)\tcast(lu_mem, (g)->totalbytes + (g)->GCdebt)\n\nLUAI_FUNC void luaE_setdebt (global_State *g, l_mem debt);\nLUAI_FUNC void luaE_freethread (lua_State *L, lua_State *L1);\nLUAI_FUNC CallInfo *luaE_extendCI (lua_State *L);\nLUAI_FUNC void luaE_freeCI (lua_State *L);\nLUAI_FUNC void luaE_shrinkCI (lua_State *L);\nLUAI_FUNC void luaE_checkcstack (lua_State *L);\nLUAI_FUNC void luaE_incCstack (lua_State *L);\nLUAI_FUNC void luaE_warning (lua_State *L, const char *msg, int tocont);\nLUAI_FUNC void luaE_warnerror (lua_State *L, const char *where);\nLUAI_FUNC int luaE_resetthread (lua_State *L, int status);\n\n\n#endif\n\n/*\n** $Id: lzio.h $\n** Buffered streams\n** See Copyright Notice in lua.h\n*/\n\n\n#ifndef lzio_h\n#define lzio_h\n\n/*#include \"lua.h\"*/\n\n/*#include \"lmem.h\"*/\n\n\n#define EOZ\t(-1)\t\t\t/* end of stream */\n\ntypedef struct Zio ZIO;\n\n#define zgetc(z)  (((z)->n--)>0 ?  cast_uchar(*(z)->p++) : luaZ_fill(z))\n\n\ntypedef struct Mbuffer {\n  char *buffer;\n  size_t n;\n  size_t buffsize;\n} Mbuffer;\n\n#define luaZ_initbuffer(L, buff) ((buff)->buffer = NULL, (buff)->buffsize = 0)\n\n#define luaZ_buffer(buff)\t((buff)->buffer)\n#define luaZ_sizebuffer(buff)\t((buff)->buffsize)\n#define luaZ_bufflen(buff)\t((buff)->n)\n\n#define luaZ_buffremove(buff,i)\t((buff)->n -= (i))\n#define luaZ_resetbuffer(buff) ((buff)->n = 0)\n\n\n#define luaZ_resizebuffer(L, buff, size) \\\n\t((buff)->buffer = luaM_reallocvchar(L, (buff)->buffer, \\\n\t\t\t\t(buff)->buffsize, size), \\\n\t(buff)->buffsize = size)\n\n#define luaZ_freebuffer(L, buff)\tluaZ_resizebuffer(L, buff, 0)\n\n\nLUAI_FUNC void luaZ_init (lua_State *L, ZIO *z, lua_Reader reader,\n                                        void *data);\nLUAI_FUNC size_t luaZ_read (ZIO* z, void *b, size_t n);\t/* read next n bytes */\n\n\n\n/* --------- Private Part ------------------ */\n\nstruct Zio {\n  size_t n;\t\t\t/* bytes still unread */\n  const char *p;\t\t/* current position in buffer */\n  lua_Reader reader;\t\t/* reader function */\n  void *data;\t\t\t/* additional data */\n  lua_State *L;\t\t\t/* Lua state (for reader) */\n};\n\n\nLUAI_FUNC int luaZ_fill (ZIO *z);\n\n#endif\n/*\n** $Id: lopcodes.h $\n** Opcodes for Lua virtual machine\n** See Copyright Notice in lua.h\n*/\n\n#ifndef lopcodes_h\n#define lopcodes_h\n\n/*#include \"llimits.h\"*/\n\n\n/*===========================================================================\n  We assume that instructions are unsigned 32-bit integers.\n  All instructions have an opcode in the first 7 bits.\n  Instructions can have the following formats:\n\n        3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0\n        1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0\niABC          C(8)     |      B(8)     |k|     A(8)      |   Op(7)     |\niABx                Bx(17)               |     A(8)      |   Op(7)     |\niAsBx              sBx (signed)(17)      |     A(8)      |   Op(7)     |\niAx                           Ax(25)                     |   Op(7)     |\nisJ                           sJ (signed)(25)            |   Op(7)     |\n\n  A signed argument is represented in excess K: the represented value is\n  the written unsigned value minus K, where K is half the maximum for the\n  corresponding unsigned argument.\n===========================================================================*/\n\n\nenum OpMode {iABC, iABx, iAsBx, iAx, isJ};  /* basic instruction formats */\n\n\n/*\n** size and position of opcode arguments.\n*/\n#define SIZE_C\t\t8\n#define SIZE_B\t\t8\n#define SIZE_Bx\t\t(SIZE_C + SIZE_B + 1)\n#define SIZE_A\t\t8\n#define SIZE_Ax\t\t(SIZE_Bx + SIZE_A)\n#define SIZE_sJ\t\t(SIZE_Bx + SIZE_A)\n\n#define SIZE_OP\t\t7\n\n#define POS_OP\t\t0\n\n#define POS_A\t\t(POS_OP + SIZE_OP)\n#define POS_k\t\t(POS_A + SIZE_A)\n#define POS_B\t\t(POS_k + 1)\n#define POS_C\t\t(POS_B + SIZE_B)\n\n#define POS_Bx\t\tPOS_k\n\n#define POS_Ax\t\tPOS_A\n\n#define POS_sJ\t\tPOS_A\n\n\n/*\n** limits for opcode arguments.\n** we use (signed) 'int' to manipulate most arguments,\n** so they must fit in ints.\n*/\n\n/* Check whether type 'int' has at least 'b' bits ('b' < 32) */\n#define L_INTHASBITS(b)\t\t((UINT_MAX >> ((b) - 1)) >= 1)\n\n\n#if L_INTHASBITS(SIZE_Bx)\n#define MAXARG_Bx\t((1<<SIZE_Bx)-1)\n#else\n#define MAXARG_Bx\tMAX_INT\n#endif\n\n#define OFFSET_sBx\t(MAXARG_Bx>>1)         /* 'sBx' is signed */\n\n\n#if L_INTHASBITS(SIZE_Ax)\n#define MAXARG_Ax\t((1<<SIZE_Ax)-1)\n#else\n#define MAXARG_Ax\tMAX_INT\n#endif\n\n#if L_INTHASBITS(SIZE_sJ)\n#define MAXARG_sJ\t((1 << SIZE_sJ) - 1)\n#else\n#define MAXARG_sJ\tMAX_INT\n#endif\n\n#define OFFSET_sJ\t(MAXARG_sJ >> 1)\n\n\n#define MAXARG_A\t((1<<SIZE_A)-1)\n#define MAXARG_B\t((1<<SIZE_B)-1)\n#define MAXARG_C\t((1<<SIZE_C)-1)\n#define OFFSET_sC\t(MAXARG_C >> 1)\n\n#define int2sC(i)\t((i) + OFFSET_sC)\n#define sC2int(i)\t((i) - OFFSET_sC)\n\n\n/* creates a mask with 'n' 1 bits at position 'p' */\n#define MASK1(n,p)\t((~((~(Instruction)0)<<(n)))<<(p))\n\n/* creates a mask with 'n' 0 bits at position 'p' */\n#define MASK0(n,p)\t(~MASK1(n,p))\n\n/*\n** the following macros help to manipulate instructions\n*/\n\n#define GET_OPCODE(i)\t(cast(OpCode, ((i)>>POS_OP) & MASK1(SIZE_OP,0)))\n#define SET_OPCODE(i,o)\t((i) = (((i)&MASK0(SIZE_OP,POS_OP)) | \\\n\t\t((cast(Instruction, o)<<POS_OP)&MASK1(SIZE_OP,POS_OP))))\n\n#define checkopm(i,m)\t(getOpMode(GET_OPCODE(i)) == m)\n\n\n#define getarg(i,pos,size)\t(cast_int(((i)>>(pos)) & MASK1(size,0)))\n#define setarg(i,v,pos,size)\t((i) = (((i)&MASK0(size,pos)) | \\\n                ((cast(Instruction, v)<<pos)&MASK1(size,pos))))\n\n#define GETARG_A(i)\tgetarg(i, POS_A, SIZE_A)\n#define SETARG_A(i,v)\tsetarg(i, v, POS_A, SIZE_A)\n\n#define GETARG_B(i)\tcheck_exp(checkopm(i, iABC), getarg(i, POS_B, SIZE_B))\n#define GETARG_sB(i)\tsC2int(GETARG_B(i))\n#define SETARG_B(i,v)\tsetarg(i, v, POS_B, SIZE_B)\n\n#define GETARG_C(i)\tcheck_exp(checkopm(i, iABC), getarg(i, POS_C, SIZE_C))\n#define GETARG_sC(i)\tsC2int(GETARG_C(i))\n#define SETARG_C(i,v)\tsetarg(i, v, POS_C, SIZE_C)\n\n#define TESTARG_k(i)\tcheck_exp(checkopm(i, iABC), (cast_int(((i) & (1u << POS_k)))))\n#define GETARG_k(i)\tcheck_exp(checkopm(i, iABC), getarg(i, POS_k, 1))\n#define SETARG_k(i,v)\tsetarg(i, v, POS_k, 1)\n\n#define GETARG_Bx(i)\tcheck_exp(checkopm(i, iABx), getarg(i, POS_Bx, SIZE_Bx))\n#define SETARG_Bx(i,v)\tsetarg(i, v, POS_Bx, SIZE_Bx)\n\n#define GETARG_Ax(i)\tcheck_exp(checkopm(i, iAx), getarg(i, POS_Ax, SIZE_Ax))\n#define SETARG_Ax(i,v)\tsetarg(i, v, POS_Ax, SIZE_Ax)\n\n#define GETARG_sBx(i)  \\\n\tcheck_exp(checkopm(i, iAsBx), getarg(i, POS_Bx, SIZE_Bx) - OFFSET_sBx)\n#define SETARG_sBx(i,b)\tSETARG_Bx((i),cast_uint((b)+OFFSET_sBx))\n\n#define GETARG_sJ(i)  \\\n\tcheck_exp(checkopm(i, isJ), getarg(i, POS_sJ, SIZE_sJ) - OFFSET_sJ)\n#define SETARG_sJ(i,j) \\\n\tsetarg(i, cast_uint((j)+OFFSET_sJ), POS_sJ, SIZE_sJ)\n\n\n#define CREATE_ABCk(o,a,b,c,k)\t((cast(Instruction, o)<<POS_OP) \\\n\t\t\t| (cast(Instruction, a)<<POS_A) \\\n\t\t\t| (cast(Instruction, b)<<POS_B) \\\n\t\t\t| (cast(Instruction, c)<<POS_C) \\\n\t\t\t| (cast(Instruction, k)<<POS_k))\n\n#define CREATE_ABx(o,a,bc)\t((cast(Instruction, o)<<POS_OP) \\\n\t\t\t| (cast(Instruction, a)<<POS_A) \\\n\t\t\t| (cast(Instruction, bc)<<POS_Bx))\n\n#define CREATE_Ax(o,a)\t\t((cast(Instruction, o)<<POS_OP) \\\n\t\t\t| (cast(Instruction, a)<<POS_Ax))\n\n#define CREATE_sJ(o,j,k)\t((cast(Instruction, o) << POS_OP) \\\n\t\t\t| (cast(Instruction, j) << POS_sJ) \\\n\t\t\t| (cast(Instruction, k) << POS_k))\n\n\n#if !defined(MAXINDEXRK)  /* (for debugging only) */\n#define MAXINDEXRK\tMAXARG_B\n#endif\n\n\n/*\n** invalid register that fits in 8 bits\n*/\n#define NO_REG\t\tMAXARG_A\n\n\n/*\n** R[x] - register\n** K[x] - constant (in constant table)\n** RK(x) == if k(i) then K[x] else R[x]\n*/\n\n\n/*\n** Grep \"ORDER OP\" if you change these enums. Opcodes marked with a (*)\n** has extra descriptions in the notes after the enumeration.\n*/\n\ntypedef enum {\n/*----------------------------------------------------------------------\n  name\t\targs\tdescription\n------------------------------------------------------------------------*/\nOP_MOVE,/*\tA B\tR[A] := R[B]\t\t\t\t\t*/\nOP_LOADI,/*\tA sBx\tR[A] := sBx\t\t\t\t\t*/\nOP_LOADF,/*\tA sBx\tR[A] := (lua_Number)sBx\t\t\t\t*/\nOP_LOADK,/*\tA Bx\tR[A] := K[Bx]\t\t\t\t\t*/\nOP_LOADKX,/*\tA\tR[A] := K[extra arg]\t\t\t\t*/\nOP_LOADFALSE,/*\tA\tR[A] := false\t\t\t\t\t*/\nOP_LFALSESKIP,/*A\tR[A] := false; pc++\t(*)\t\t\t*/\nOP_LOADTRUE,/*\tA\tR[A] := true\t\t\t\t\t*/\nOP_LOADNIL,/*\tA B\tR[A], R[A+1], ..., R[A+B] := nil\t\t*/\nOP_GETUPVAL,/*\tA B\tR[A] := UpValue[B]\t\t\t\t*/\nOP_SETUPVAL,/*\tA B\tUpValue[B] := R[A]\t\t\t\t*/\n\nOP_GETTABUP,/*\tA B C\tR[A] := UpValue[B][K[C]:string]\t\t\t*/\nOP_GETTABLE,/*\tA B C\tR[A] := R[B][R[C]]\t\t\t\t*/\nOP_GETI,/*\tA B C\tR[A] := R[B][C]\t\t\t\t\t*/\nOP_GETFIELD,/*\tA B C\tR[A] := R[B][K[C]:string]\t\t\t*/\n\nOP_SETTABUP,/*\tA B C\tUpValue[A][K[B]:string] := RK(C)\t\t*/\nOP_SETTABLE,/*\tA B C\tR[A][R[B]] := RK(C)\t\t\t\t*/\nOP_SETI,/*\tA B C\tR[A][B] := RK(C)\t\t\t\t*/\nOP_SETFIELD,/*\tA B C\tR[A][K[B]:string] := RK(C)\t\t\t*/\n\nOP_NEWTABLE,/*\tA B C k\tR[A] := {}\t\t\t\t\t*/\n\nOP_SELF,/*\tA B C\tR[A+1] := R[B]; R[A] := R[B][RK(C):string]\t*/\n\nOP_ADDI,/*\tA B sC\tR[A] := R[B] + sC\t\t\t\t*/\n\nOP_ADDK,/*\tA B C\tR[A] := R[B] + K[C]:number\t\t\t*/\nOP_SUBK,/*\tA B C\tR[A] := R[B] - K[C]:number\t\t\t*/\nOP_MULK,/*\tA B C\tR[A] := R[B] * K[C]:number\t\t\t*/\nOP_MODK,/*\tA B C\tR[A] := R[B] % K[C]:number\t\t\t*/\nOP_POWK,/*\tA B C\tR[A] := R[B] ^ K[C]:number\t\t\t*/\nOP_DIVK,/*\tA B C\tR[A] := R[B] / K[C]:number\t\t\t*/\nOP_IDIVK,/*\tA B C\tR[A] := R[B] // K[C]:number\t\t\t*/\n\nOP_BANDK,/*\tA B C\tR[A] := R[B] & K[C]:integer\t\t\t*/\nOP_BORK,/*\tA B C\tR[A] := R[B] | K[C]:integer\t\t\t*/\nOP_BXORK,/*\tA B C\tR[A] := R[B] ~ K[C]:integer\t\t\t*/\n\nOP_SHRI,/*\tA B sC\tR[A] := R[B] >> sC\t\t\t\t*/\nOP_SHLI,/*\tA B sC\tR[A] := sC << R[B]\t\t\t\t*/\n\nOP_ADD,/*\tA B C\tR[A] := R[B] + R[C]\t\t\t\t*/\nOP_SUB,/*\tA B C\tR[A] := R[B] - R[C]\t\t\t\t*/\nOP_MUL,/*\tA B C\tR[A] := R[B] * R[C]\t\t\t\t*/\nOP_MOD,/*\tA B C\tR[A] := R[B] % R[C]\t\t\t\t*/\nOP_POW,/*\tA B C\tR[A] := R[B] ^ R[C]\t\t\t\t*/\nOP_DIV,/*\tA B C\tR[A] := R[B] / R[C]\t\t\t\t*/\nOP_IDIV,/*\tA B C\tR[A] := R[B] // R[C]\t\t\t\t*/\n\nOP_BAND,/*\tA B C\tR[A] := R[B] & R[C]\t\t\t\t*/\nOP_BOR,/*\tA B C\tR[A] := R[B] | R[C]\t\t\t\t*/\nOP_BXOR,/*\tA B C\tR[A] := R[B] ~ R[C]\t\t\t\t*/\nOP_SHL,/*\tA B C\tR[A] := R[B] << R[C]\t\t\t\t*/\nOP_SHR,/*\tA B C\tR[A] := R[B] >> R[C]\t\t\t\t*/\n\nOP_MMBIN,/*\tA B C\tcall C metamethod over R[A] and R[B]\t(*)\t*/\nOP_MMBINI,/*\tA sB C k\tcall C metamethod over R[A] and sB\t*/\nOP_MMBINK,/*\tA B C k\t\tcall C metamethod over R[A] and K[B]\t*/\n\nOP_UNM,/*\tA B\tR[A] := -R[B]\t\t\t\t\t*/\nOP_BNOT,/*\tA B\tR[A] := ~R[B]\t\t\t\t\t*/\nOP_NOT,/*\tA B\tR[A] := not R[B]\t\t\t\t*/\nOP_LEN,/*\tA B\tR[A] := #R[B] (length operator)\t\t\t*/\n\nOP_CONCAT,/*\tA B\tR[A] := R[A].. ... ..R[A + B - 1]\t\t*/\n\nOP_CLOSE,/*\tA\tclose all upvalues >= R[A]\t\t\t*/\nOP_TBC,/*\tA\tmark variable A \"to be closed\"\t\t\t*/\nOP_JMP,/*\tsJ\tpc += sJ\t\t\t\t\t*/\nOP_EQ,/*\tA B k\tif ((R[A] == R[B]) ~= k) then pc++\t\t*/\nOP_LT,/*\tA B k\tif ((R[A] <  R[B]) ~= k) then pc++\t\t*/\nOP_LE,/*\tA B k\tif ((R[A] <= R[B]) ~= k) then pc++\t\t*/\n\nOP_EQK,/*\tA B k\tif ((R[A] == K[B]) ~= k) then pc++\t\t*/\nOP_EQI,/*\tA sB k\tif ((R[A] == sB) ~= k) then pc++\t\t*/\nOP_LTI,/*\tA sB k\tif ((R[A] < sB) ~= k) then pc++\t\t\t*/\nOP_LEI,/*\tA sB k\tif ((R[A] <= sB) ~= k) then pc++\t\t*/\nOP_GTI,/*\tA sB k\tif ((R[A] > sB) ~= k) then pc++\t\t\t*/\nOP_GEI,/*\tA sB k\tif ((R[A] >= sB) ~= k) then pc++\t\t*/\n\nOP_TEST,/*\tA k\tif (not R[A] == k) then pc++\t\t\t*/\nOP_TESTSET,/*\tA B k\tif (not R[B] == k) then pc++ else R[A] := R[B] (*) */\n\nOP_CALL,/*\tA B C\tR[A], ... ,R[A+C-2] := R[A](R[A+1], ... ,R[A+B-1]) */\nOP_TAILCALL,/*\tA B C k\treturn R[A](R[A+1], ... ,R[A+B-1])\t\t*/\n\nOP_RETURN,/*\tA B C k\treturn R[A], ... ,R[A+B-2]\t(see note)\t*/\nOP_RETURN0,/*\t\treturn\t\t\t\t\t\t*/\nOP_RETURN1,/*\tA\treturn R[A]\t\t\t\t\t*/\n\nOP_FORLOOP,/*\tA Bx\tupdate counters; if loop continues then pc-=Bx; */\nOP_FORPREP,/*\tA Bx\t<check values and prepare counters>;\n                        if not to run then pc+=Bx+1;\t\t\t*/\n\nOP_TFORPREP,/*\tA Bx\tcreate upvalue for R[A + 3]; pc+=Bx\t\t*/\nOP_TFORCALL,/*\tA C\tR[A+4], ... ,R[A+3+C] := R[A](R[A+1], R[A+2]);\t*/\nOP_TFORLOOP,/*\tA Bx\tif R[A+2] ~= nil then { R[A]=R[A+2]; pc -= Bx }\t*/\n\nOP_SETLIST,/*\tA B C k\tR[A][C+i] := R[A+i], 1 <= i <= B\t\t*/\n\nOP_CLOSURE,/*\tA Bx\tR[A] := closure(KPROTO[Bx])\t\t\t*/\n\nOP_VARARG,/*\tA C\tR[A], R[A+1], ..., R[A+C-2] = vararg\t\t*/\n\nOP_VARARGPREP,/*A\t(adjust vararg parameters)\t\t\t*/\n\nOP_EXTRAARG/*\tAx\textra (larger) argument for previous opcode\t*/\n} OpCode;\n\n\n#define NUM_OPCODES\t((int)(OP_EXTRAARG) + 1)\n\n\n\n/*===========================================================================\n  Notes:\n\n  (*) Opcode OP_LFALSESKIP is used to convert a condition to a boolean\n  value, in a code equivalent to (not cond ? false : true).  (It\n  produces false and skips the next instruction producing true.)\n\n  (*) Opcodes OP_MMBIN and variants follow each arithmetic and\n  bitwise opcode. If the operation succeeds, it skips this next\n  opcode. Otherwise, this opcode calls the corresponding metamethod.\n\n  (*) Opcode OP_TESTSET is used in short-circuit expressions that need\n  both to jump and to produce a value, such as (a = b or c).\n\n  (*) In OP_CALL, if (B == 0) then B = top - A. If (C == 0), then\n  'top' is set to last_result+1, so next open instruction (OP_CALL,\n  OP_RETURN*, OP_SETLIST) may use 'top'.\n\n  (*) In OP_VARARG, if (C == 0) then use actual number of varargs and\n  set top (like in OP_CALL with C == 0).\n\n  (*) In OP_RETURN, if (B == 0) then return up to 'top'.\n\n  (*) In OP_LOADKX and OP_NEWTABLE, the next instruction is always\n  OP_EXTRAARG.\n\n  (*) In OP_SETLIST, if (B == 0) then real B = 'top'; if k, then\n  real C = EXTRAARG _ C (the bits of EXTRAARG concatenated with the\n  bits of C).\n\n  (*) In OP_NEWTABLE, B is log2 of the hash size (which is always a\n  power of 2) plus 1, or zero for size zero. If not k, the array size\n  is C. Otherwise, the array size is EXTRAARG _ C.\n\n  (*) For comparisons, k specifies what condition the test should accept\n  (true or false).\n\n  (*) In OP_MMBINI/OP_MMBINK, k means the arguments were flipped\n   (the constant is the first operand).\n\n  (*) All 'skips' (pc++) assume that next instruction is a jump.\n\n  (*) In instructions OP_RETURN/OP_TAILCALL, 'k' specifies that the\n  function builds upvalues, which may need to be closed. C > 0 means\n  the function is vararg, so that its 'func' must be corrected before\n  returning; in this case, (C - 1) is its number of fixed parameters.\n\n  (*) In comparisons with an immediate operand, C signals whether the\n  original operand was a float. (It must be corrected in case of\n  metamethods.)\n\n===========================================================================*/\n\n\n/*\n** masks for instruction properties. The format is:\n** bits 0-2: op mode\n** bit 3: instruction set register A\n** bit 4: operator is a test (next instruction must be a jump)\n** bit 5: instruction uses 'L->top' set by previous instruction (when B == 0)\n** bit 6: instruction sets 'L->top' for next instruction (when C == 0)\n** bit 7: instruction is an MM instruction (call a metamethod)\n*/\n\nLUAI_DDEC(const lu_byte luaP_opmodes[NUM_OPCODES];)\n\n#define getOpMode(m)\t(cast(enum OpMode, luaP_opmodes[m] & 7))\n#define testAMode(m)\t(luaP_opmodes[m] & (1 << 3))\n#define testTMode(m)\t(luaP_opmodes[m] & (1 << 4))\n#define testITMode(m)\t(luaP_opmodes[m] & (1 << 5))\n#define testOTMode(m)\t(luaP_opmodes[m] & (1 << 6))\n#define testMMMode(m)\t(luaP_opmodes[m] & (1 << 7))\n\n/* \"out top\" (set top for next instruction) */\n#define isOT(i)  \\\n\t((testOTMode(GET_OPCODE(i)) && GETARG_C(i) == 0) || \\\n          GET_OPCODE(i) == OP_TAILCALL)\n\n/* \"in top\" (uses top from previous instruction) */\n#define isIT(i)\t\t(testITMode(GET_OPCODE(i)) && GETARG_B(i) == 0)\n\n#define opmode(mm,ot,it,t,a,m)  \\\n    (((mm) << 7) | ((ot) << 6) | ((it) << 5) | ((t) << 4) | ((a) << 3) | (m))\n\n\n/* number of list items to accumulate before a SETLIST instruction */\n#define LFIELDS_PER_FLUSH\t50\n\n#endif\n/*\n** $Id: ldebug.h $\n** Auxiliary functions from Debug Interface module\n** See Copyright Notice in lua.h\n*/\n\n#ifndef ldebug_h\n#define ldebug_h\n\n\n/*#include \"lstate.h\"*/\n\n\n#define pcRel(pc, p)\t(cast_int((pc) - (p)->code) - 1)\n\n\n/* Active Lua function (given call info) */\n#define ci_func(ci)\t\t(clLvalue(s2v((ci)->func.p)))\n\n\n#define resethookcount(L)\t(L->hookcount = L->basehookcount)\n\n/*\n** mark for entries in 'lineinfo' array that has absolute information in\n** 'abslineinfo' array\n*/\n#define ABSLINEINFO\t(-0x80)\n\n\n/*\n** MAXimum number of successive Instructions WiTHout ABSolute line\n** information. (A power of two allows fast divisions.)\n*/\n#if !defined(MAXIWTHABS)\n#define MAXIWTHABS\t128\n#endif\n\n\nLUAI_FUNC int luaG_getfuncline (const Proto *f, int pc);\nLUAI_FUNC const char *luaG_findlocal (lua_State *L, CallInfo *ci, int n,\n                                                    StkId *pos);\nLUAI_FUNC l_noret luaG_typeerror (lua_State *L, const TValue *o,\n                                                const char *opname);\nLUAI_FUNC l_noret luaG_callerror (lua_State *L, const TValue *o);\nLUAI_FUNC l_noret luaG_forerror (lua_State *L, const TValue *o,\n                                               const char *what);\nLUAI_FUNC l_noret luaG_concaterror (lua_State *L, const TValue *p1,\n                                                  const TValue *p2);\nLUAI_FUNC l_noret luaG_opinterror (lua_State *L, const TValue *p1,\n                                                 const TValue *p2,\n                                                 const char *msg);\nLUAI_FUNC l_noret luaG_tointerror (lua_State *L, const TValue *p1,\n                                                 const TValue *p2);\nLUAI_FUNC l_noret luaG_ordererror (lua_State *L, const TValue *p1,\n                                                 const TValue *p2);\nLUAI_FUNC l_noret luaG_runerror (lua_State *L, const char *fmt, ...);\nLUAI_FUNC const char *luaG_addinfo (lua_State *L, const char *msg,\n                                                  TString *src, int line);\nLUAI_FUNC l_noret luaG_errormsg (lua_State *L);\nLUAI_FUNC int luaG_traceexec (lua_State *L, const Instruction *pc);\n\n\n#endif\n/*\n** $Id: ldo.h $\n** Stack and Call structure of Lua\n** See Copyright Notice in lua.h\n*/\n\n#ifndef ldo_h\n#define ldo_h\n\n\n/*#include \"llimits.h\"*/\n/*#include \"lobject.h\"*/\n/*#include \"lstate.h\"*/\n/*#include \"lzio.h\"*/\n\n\n/*\n** Macro to check stack size and grow stack if needed.  Parameters\n** 'pre'/'pos' allow the macro to preserve a pointer into the\n** stack across reallocations, doing the work only when needed.\n** It also allows the running of one GC step when the stack is\n** reallocated.\n** 'condmovestack' is used in heavy tests to force a stack reallocation\n** at every check.\n*/\n#define luaD_checkstackaux(L,n,pre,pos)  \\\n\tif (l_unlikely(L->stack_last.p - L->top.p <= (n))) \\\n\t  { pre; luaD_growstack(L, n, 1); pos; } \\\n        else { condmovestack(L,pre,pos); }\n\n/* In general, 'pre'/'pos' are empty (nothing to save) */\n#define luaD_checkstack(L,n)\tluaD_checkstackaux(L,n,(void)0,(void)0)\n\n\n\n#define savestack(L,pt)\t\t(cast_charp(pt) - cast_charp(L->stack.p))\n#define restorestack(L,n)\tcast(StkId, cast_charp(L->stack.p) + (n))\n\n\n/* macro to check stack size, preserving 'p' */\n#define checkstackp(L,n,p)  \\\n  luaD_checkstackaux(L, n, \\\n    ptrdiff_t t__ = savestack(L, p),  /* save 'p' */ \\\n    p = restorestack(L, t__))  /* 'pos' part: restore 'p' */\n\n\n/* macro to check stack size and GC, preserving 'p' */\n#define checkstackGCp(L,n,p)  \\\n  luaD_checkstackaux(L, n, \\\n    ptrdiff_t t__ = savestack(L, p);  /* save 'p' */ \\\n    luaC_checkGC(L),  /* stack grow uses memory */ \\\n    p = restorestack(L, t__))  /* 'pos' part: restore 'p' */\n\n\n/* macro to check stack size and GC */\n#define checkstackGC(L,fsize)  \\\n\tluaD_checkstackaux(L, (fsize), luaC_checkGC(L), (void)0)\n\n\n/* type of protected functions, to be ran by 'runprotected' */\ntypedef void (*Pfunc) (lua_State *L, void *ud);\n\nLUAI_FUNC void luaD_seterrorobj (lua_State *L, int errcode, StkId oldtop);\nLUAI_FUNC int luaD_protectedparser (lua_State *L, ZIO *z, const char *name,\n                                                  const char *mode);\nLUAI_FUNC void luaD_hook (lua_State *L, int event, int line,\n                                        int fTransfer, int nTransfer);\nLUAI_FUNC void luaD_hookcall (lua_State *L, CallInfo *ci);\nLUAI_FUNC int luaD_pretailcall (lua_State *L, CallInfo *ci, StkId func,\n                                              int narg1, int delta);\nLUAI_FUNC CallInfo *luaD_precall (lua_State *L, StkId func, int nResults);\nLUAI_FUNC void luaD_call (lua_State *L, StkId func, int nResults);\nLUAI_FUNC void luaD_callnoyield (lua_State *L, StkId func, int nResults);\nLUAI_FUNC StkId luaD_tryfuncTM (lua_State *L, StkId func);\nLUAI_FUNC int luaD_closeprotected (lua_State *L, ptrdiff_t level, int status);\nLUAI_FUNC int luaD_pcall (lua_State *L, Pfunc func, void *u,\n                                        ptrdiff_t oldtop, ptrdiff_t ef);\nLUAI_FUNC void luaD_poscall (lua_State *L, CallInfo *ci, int nres);\nLUAI_FUNC int luaD_reallocstack (lua_State *L, int newsize, int raiseerror);\nLUAI_FUNC int luaD_growstack (lua_State *L, int n, int raiseerror);\nLUAI_FUNC void luaD_shrinkstack (lua_State *L);\nLUAI_FUNC void luaD_inctop (lua_State *L);\n\nLUAI_FUNC l_noret luaD_throw (lua_State *L, int errcode);\nLUAI_FUNC int luaD_rawrunprotected (lua_State *L, Pfunc f, void *ud);\n\n#endif\n\n/*\n** $Id: lgc.h $\n** Garbage Collector\n** See Copyright Notice in lua.h\n*/\n\n#ifndef lgc_h\n#define lgc_h\n\n\n/*#include \"lobject.h\"*/\n/*#include \"lstate.h\"*/\n\n/*\n** Collectable objects may have one of three colors: white, which means\n** the object is not marked; gray, which means the object is marked, but\n** its references may be not marked; and black, which means that the\n** object and all its references are marked.  The main invariant of the\n** garbage collector, while marking objects, is that a black object can\n** never point to a white one. Moreover, any gray object must be in a\n** \"gray list\" (gray, grayagain, weak, allweak, ephemeron) so that it\n** can be visited again before finishing the collection cycle. (Open\n** upvalues are an exception to this rule.)  These lists have no meaning\n** when the invariant is not being enforced (e.g., sweep phase).\n*/\n\n\n/*\n** Possible states of the Garbage Collector\n*/\n#define GCSpropagate\t0\n#define GCSenteratomic\t1\n#define GCSatomic\t2\n#define GCSswpallgc\t3\n#define GCSswpfinobj\t4\n#define GCSswptobefnz\t5\n#define GCSswpend\t6\n#define GCScallfin\t7\n#define GCSpause\t8\n\n\n#define issweepphase(g)  \\\n\t(GCSswpallgc <= (g)->gcstate && (g)->gcstate <= GCSswpend)\n\n\n/*\n** macro to tell when main invariant (white objects cannot point to black\n** ones) must be kept. During a collection, the sweep\n** phase may break the invariant, as objects turned white may point to\n** still-black objects. The invariant is restored when sweep ends and\n** all objects are white again.\n*/\n\n#define keepinvariant(g)\t((g)->gcstate <= GCSatomic)\n\n\n/*\n** some useful bit tricks\n*/\n#define resetbits(x,m)\t\t((x) &= cast_byte(~(m)))\n#define setbits(x,m)\t\t((x) |= (m))\n#define testbits(x,m)\t\t((x) & (m))\n#define bitmask(b)\t\t(1<<(b))\n#define bit2mask(b1,b2)\t\t(bitmask(b1) | bitmask(b2))\n#define l_setbit(x,b)\t\tsetbits(x, bitmask(b))\n#define resetbit(x,b)\t\tresetbits(x, bitmask(b))\n#define testbit(x,b)\t\ttestbits(x, bitmask(b))\n\n\n/*\n** Layout for bit use in 'marked' field. First three bits are\n** used for object \"age\" in generational mode. Last bit is used\n** by tests.\n*/\n#define WHITE0BIT\t3  /* object is white (type 0) */\n#define WHITE1BIT\t4  /* object is white (type 1) */\n#define BLACKBIT\t5  /* object is black */\n#define FINALIZEDBIT\t6  /* object has been marked for finalization */\n\n#define TESTBIT\t\t7\n\n\n\n#define WHITEBITS\tbit2mask(WHITE0BIT, WHITE1BIT)\n\n\n#define iswhite(x)      testbits((x)->marked, WHITEBITS)\n#define isblack(x)      testbit((x)->marked, BLACKBIT)\n#define isgray(x)  /* neither white nor black */  \\\n\t(!testbits((x)->marked, WHITEBITS | bitmask(BLACKBIT)))\n\n#define tofinalize(x)\ttestbit((x)->marked, FINALIZEDBIT)\n\n#define otherwhite(g)\t((g)->currentwhite ^ WHITEBITS)\n#define isdeadm(ow,m)\t((m) & (ow))\n#define isdead(g,v)\tisdeadm(otherwhite(g), (v)->marked)\n\n#define changewhite(x)\t((x)->marked ^= WHITEBITS)\n#define nw2black(x)  \\\n\tcheck_exp(!iswhite(x), l_setbit((x)->marked, BLACKBIT))\n\n#define luaC_white(g)\tcast_byte((g)->currentwhite & WHITEBITS)\n\n\n/* object age in generational mode */\n#define G_NEW\t\t0\t/* created in current cycle */\n#define G_SURVIVAL\t1\t/* created in previous cycle */\n#define G_OLD0\t\t2\t/* marked old by frw. barrier in this cycle */\n#define G_OLD1\t\t3\t/* first full cycle as old */\n#define G_OLD\t\t4\t/* really old object (not to be visited) */\n#define G_TOUCHED1\t5\t/* old object touched this cycle */\n#define G_TOUCHED2\t6\t/* old object touched in previous cycle */\n\n#define AGEBITS\t\t7  /* all age bits (111) */\n\n#define getage(o)\t((o)->marked & AGEBITS)\n#define setage(o,a)  ((o)->marked = cast_byte(((o)->marked & (~AGEBITS)) | a))\n#define isold(o)\t(getage(o) > G_SURVIVAL)\n\n#define changeage(o,f,t)  \\\n\tcheck_exp(getage(o) == (f), (o)->marked ^= ((f)^(t)))\n\n\n/* Default Values for GC parameters */\n#define LUAI_GENMAJORMUL         100\n#define LUAI_GENMINORMUL         20\n\n/* wait memory to double before starting new cycle */\n#define LUAI_GCPAUSE    200\n\n/*\n** some gc parameters are stored divided by 4 to allow a maximum value\n** up to 1023 in a 'lu_byte'.\n*/\n#define getgcparam(p)\t((p) * 4)\n#define setgcparam(p,v)\t((p) = (v) / 4)\n\n#define LUAI_GCMUL      100\n\n/* how much to allocate before next GC step (log2) */\n#define LUAI_GCSTEPSIZE 13      /* 8 KB */\n\n\n/*\n** Check whether the declared GC mode is generational. While in\n** generational mode, the collector can go temporarily to incremental\n** mode to improve performance. This is signaled by 'g->lastatomic != 0'.\n*/\n#define isdecGCmodegen(g)\t(g->gckind == KGC_GEN || g->lastatomic != 0)\n\n\n/*\n** Control when GC is running:\n*/\n#define GCSTPUSR\t1  /* bit true when GC stopped by user */\n#define GCSTPGC\t\t2  /* bit true when GC stopped by itself */\n#define GCSTPCLS\t4  /* bit true when closing Lua state */\n#define gcrunning(g)\t((g)->gcstp == 0)\n\n\n/*\n** Does one step of collection when debt becomes positive. 'pre'/'pos'\n** allows some adjustments to be done only when needed. macro\n** 'condchangemem' is used only for heavy tests (forcing a full\n** GC cycle on every opportunity)\n*/\n#define luaC_condGC(L,pre,pos) \\\n\t{ if (G(L)->GCdebt > 0) { pre; luaC_step(L); pos;}; \\\n\t  condchangemem(L,pre,pos); }\n\n/* more often than not, 'pre'/'pos' are empty */\n#define luaC_checkGC(L)\t\tluaC_condGC(L,(void)0,(void)0)\n\n\n#define luaC_objbarrier(L,p,o) (  \\\n\t(isblack(p) && iswhite(o)) ? \\\n\tluaC_barrier_(L,obj2gco(p),obj2gco(o)) : cast_void(0))\n\n#define luaC_barrier(L,p,v) (  \\\n\tiscollectable(v) ? luaC_objbarrier(L,p,gcvalue(v)) : cast_void(0))\n\n#define luaC_objbarrierback(L,p,o) (  \\\n\t(isblack(p) && iswhite(o)) ? luaC_barrierback_(L,p) : cast_void(0))\n\n#define luaC_barrierback(L,p,v) (  \\\n\tiscollectable(v) ? luaC_objbarrierback(L, p, gcvalue(v)) : cast_void(0))\n\nLUAI_FUNC void luaC_fix (lua_State *L, GCObject *o);\nLUAI_FUNC void luaC_freeallobjects (lua_State *L);\nLUAI_FUNC void luaC_step (lua_State *L);\nLUAI_FUNC void luaC_runtilstate (lua_State *L, int statesmask);\nLUAI_FUNC void luaC_fullgc (lua_State *L, int isemergency);\nLUAI_FUNC GCObject *luaC_newobj (lua_State *L, int tt, size_t sz);\nLUAI_FUNC GCObject *luaC_newobjdt (lua_State *L, int tt, size_t sz,\n                                                 size_t offset);\nLUAI_FUNC void luaC_barrier_ (lua_State *L, GCObject *o, GCObject *v);\nLUAI_FUNC void luaC_barrierback_ (lua_State *L, GCObject *o);\nLUAI_FUNC void luaC_checkfinalizer (lua_State *L, GCObject *o, Table *mt);\nLUAI_FUNC void luaC_changemode (lua_State *L, int newmode);\n\n\n#endif\n/*\n** $Id: lfunc.h $\n** Auxiliary functions to manipulate prototypes and closures\n** See Copyright Notice in lua.h\n*/\n\n#ifndef lfunc_h\n#define lfunc_h\n\n\n/*#include \"lobject.h\"*/\n\n\n#define sizeCclosure(n)\t(cast_int(offsetof(CClosure, upvalue)) + \\\n                         cast_int(sizeof(TValue)) * (n))\n\n#define sizeLclosure(n)\t(cast_int(offsetof(LClosure, upvals)) + \\\n                         cast_int(sizeof(TValue *)) * (n))\n\n\n/* test whether thread is in 'twups' list */\n#define isintwups(L)\t(L->twups != L)\n\n\n/*\n** maximum number of upvalues in a closure (both C and Lua). (Value\n** must fit in a VM register.)\n*/\n#define MAXUPVAL\t255\n\n\n#define upisopen(up)\t((up)->v.p != &(up)->u.value)\n\n\n#define uplevel(up)\tcheck_exp(upisopen(up), cast(StkId, (up)->v.p))\n\n\n/*\n** maximum number of misses before giving up the cache of closures\n** in prototypes\n*/\n#define MAXMISS\t\t10\n\n\n\n/* special status to close upvalues preserving the top of the stack */\n#define CLOSEKTOP\t(-1)\n\n\nLUAI_FUNC Proto *luaF_newproto (lua_State *L);\nLUAI_FUNC CClosure *luaF_newCclosure (lua_State *L, int nupvals);\nLUAI_FUNC LClosure *luaF_newLclosure (lua_State *L, int nupvals);\nLUAI_FUNC void luaF_initupvals (lua_State *L, LClosure *cl);\nLUAI_FUNC UpVal *luaF_findupval (lua_State *L, StkId level);\nLUAI_FUNC void luaF_newtbcupval (lua_State *L, StkId level);\nLUAI_FUNC void luaF_closeupval (lua_State *L, StkId level);\nLUAI_FUNC StkId luaF_close (lua_State *L, StkId level, int status, int yy);\nLUAI_FUNC void luaF_unlinkupval (UpVal *uv);\nLUAI_FUNC void luaF_freeproto (lua_State *L, Proto *f);\nLUAI_FUNC const char *luaF_getlocalname (const Proto *func, int local_number,\n                                         int pc);\n\n\n#endif\n/*\n** $Id: lstring.h $\n** String table (keep all strings handled by Lua)\n** See Copyright Notice in lua.h\n*/\n\n#ifndef lstring_h\n#define lstring_h\n\n/*#include \"lgc.h\"*/\n/*#include \"lobject.h\"*/\n/*#include \"lstate.h\"*/\n\n\n/*\n** Memory-allocation error message must be preallocated (it cannot\n** be created after memory is exhausted)\n*/\n#define MEMERRMSG       \"not enough memory\"\n\n\n/*\n** Size of a TString: Size of the header plus space for the string\n** itself (including final '\\0').\n*/\n#define sizelstring(l)  (offsetof(TString, contents) + ((l) + 1) * sizeof(char))\n\n#define luaS_newliteral(L, s)\t(luaS_newlstr(L, \"\" s, \\\n                                 (sizeof(s)/sizeof(char))-1))\n\n\n/*\n** test whether a string is a reserved word\n*/\n#define isreserved(s)\t((s)->tt == LUA_VSHRSTR && (s)->extra > 0)\n\n\n/*\n** equality for short strings, which are always internalized\n*/\n#define eqshrstr(a,b)\tcheck_exp((a)->tt == LUA_VSHRSTR, (a) == (b))\n\n\nLUAI_FUNC unsigned int luaS_hash (const char *str, size_t l, unsigned int seed);\nLUAI_FUNC unsigned int luaS_hashlongstr (TString *ts);\nLUAI_FUNC int luaS_eqlngstr (TString *a, TString *b);\nLUAI_FUNC void luaS_resize (lua_State *L, int newsize);\nLUAI_FUNC void luaS_clearcache (global_State *g);\nLUAI_FUNC void luaS_init (lua_State *L);\nLUAI_FUNC void luaS_remove (lua_State *L, TString *ts);\nLUAI_FUNC Udata *luaS_newudata (lua_State *L, size_t s, int nuvalue);\nLUAI_FUNC TString *luaS_newlstr (lua_State *L, const char *str, size_t l);\nLUAI_FUNC TString *luaS_new (lua_State *L, const char *str);\nLUAI_FUNC TString *luaS_createlngstrobj (lua_State *L, size_t l);\n\n\n#endif\n/*\n** $Id: lundump.h $\n** load precompiled Lua chunks\n** See Copyright Notice in lua.h\n*/\n\n#ifndef lundump_h\n#define lundump_h\n\n/*#include \"llimits.h\"*/\n/*#include \"lobject.h\"*/\n/*#include \"lzio.h\"*/\n\n\n/* data to catch conversion errors */\n#define LUAC_DATA\t\"\\x19\\x93\\r\\n\\x1a\\n\"\n\n#define LUAC_INT\t0x5678\n#define LUAC_NUM\tcast_num(370.5)\n\n/*\n** Encode major-minor version in one byte, one nibble for each\n*/\n#define MYINT(s)\t(s[0]-'0')  /* assume one-digit numerals */\n#define LUAC_VERSION\t(MYINT(LUA_VERSION_MAJOR)*16+MYINT(LUA_VERSION_MINOR))\n\n#define LUAC_FORMAT\t0\t/* this is the official format */\n\n/* load one chunk; from lundump.c */\nLUAI_FUNC LClosure* luaU_undump (lua_State* L, ZIO* Z, const char* name);\n\n/* dump one chunk; from ldump.c */\nLUAI_FUNC int luaU_dump (lua_State* L, const Proto* f, lua_Writer w,\n                         void* data, int strip);\n\n#endif\n/*\n** $Id: lapi.h $\n** Auxiliary functions from Lua API\n** See Copyright Notice in lua.h\n*/\n\n#ifndef lapi_h\n#define lapi_h\n\n\n/*#include \"llimits.h\"*/\n/*#include \"lstate.h\"*/\n\n\n/* Increments 'L->top.p', checking for stack overflows */\n#define api_incr_top(L)\t{L->top.p++; \\\n\t\t\t api_check(L, L->top.p <= L->ci->top.p, \\\n\t\t\t\t\t\"stack overflow\");}\n\n\n/*\n** If a call returns too many multiple returns, the callee may not have\n** stack space to accommodate all results. In this case, this macro\n** increases its stack space ('L->ci->top.p').\n*/\n#define adjustresults(L,nres) \\\n    { if ((nres) <= LUA_MULTRET && L->ci->top.p < L->top.p) \\\n\tL->ci->top.p = L->top.p; }\n\n\n/* Ensure the stack has at least 'n' elements */\n#define api_checknelems(L,n) \\\n\tapi_check(L, (n) < (L->top.p - L->ci->func.p), \\\n\t\t\t  \"not enough elements in the stack\")\n\n\n/*\n** To reduce the overhead of returning from C functions, the presence of\n** to-be-closed variables in these functions is coded in the CallInfo's\n** field 'nresults', in a way that functions with no to-be-closed variables\n** with zero, one, or \"all\" wanted results have no overhead. Functions\n** with other number of wanted results, as well as functions with\n** variables to be closed, have an extra check.\n*/\n\n#define hastocloseCfunc(n)\t((n) < LUA_MULTRET)\n\n/* Map [-1, inf) (range of 'nresults') into (-inf, -2] */\n#define codeNresults(n)\t\t(-(n) - 3)\n#define decodeNresults(n)\t(-(n) - 3)\n\n#endif\n/*\n** $Id: llex.h $\n** Lexical Analyzer\n** See Copyright Notice in lua.h\n*/\n\n#ifndef llex_h\n#define llex_h\n\n#include <limits.h>\n\n/*#include \"lobject.h\"*/\n/*#include \"lzio.h\"*/\n\n\n/*\n** Single-char tokens (terminal symbols) are represented by their own\n** numeric code. Other tokens start at the following value.\n*/\n#define FIRST_RESERVED\t(UCHAR_MAX + 1)\n\n\n#if !defined(LUA_ENV)\n#define LUA_ENV\t\t\"_ENV\"\n#endif\n\n\n/*\n* WARNING: if you change the order of this enumeration,\n* grep \"ORDER RESERVED\"\n*/\nenum RESERVED {\n  /* terminal symbols denoted by reserved words */\n  TK_AND = FIRST_RESERVED, TK_BREAK,\n  TK_DO, TK_ELSE, TK_ELSEIF, TK_END, TK_FALSE, TK_FOR, TK_FUNCTION,\n  TK_GOTO, TK_IF, TK_IN, TK_LOCAL, TK_NIL, TK_NOT, TK_OR, TK_REPEAT,\n  TK_RETURN, TK_THEN, TK_TRUE, TK_UNTIL, TK_WHILE,\n  /* other terminal symbols */\n  TK_IDIV, TK_CONCAT, TK_DOTS, TK_EQ, TK_GE, TK_LE, TK_NE,\n  TK_SHL, TK_SHR,\n  TK_DBCOLON, TK_EOS,\n  TK_FLT, TK_INT, TK_NAME, TK_STRING\n};\n\n/* number of reserved words */\n#define NUM_RESERVED\t(cast_int(TK_WHILE-FIRST_RESERVED + 1))\n\n\ntypedef union {\n  lua_Number r;\n  lua_Integer i;\n  TString *ts;\n} SemInfo;  /* semantics information */\n\n\ntypedef struct Token {\n  int token;\n  SemInfo seminfo;\n} Token;\n\n\n/* state of the lexer plus state of the parser when shared by all\n   functions */\ntypedef struct LexState {\n  int current;  /* current character (charint) */\n  int linenumber;  /* input line counter */\n  int lastline;  /* line of last token 'consumed' */\n  Token t;  /* current token */\n  Token lookahead;  /* look ahead token */\n  struct FuncState *fs;  /* current function (parser) */\n  struct lua_State *L;\n  ZIO *z;  /* input stream */\n  Mbuffer *buff;  /* buffer for tokens */\n  Table *h;  /* to avoid collection/reuse strings */\n  struct Dyndata *dyd;  /* dynamic structures used by the parser */\n  TString *source;  /* current source name */\n  TString *envn;  /* environment variable name */\n} LexState;\n\n\nLUAI_FUNC void luaX_init (lua_State *L);\nLUAI_FUNC void luaX_setinput (lua_State *L, LexState *ls, ZIO *z,\n                              TString *source, int firstchar);\nLUAI_FUNC TString *luaX_newstring (LexState *ls, const char *str, size_t l);\nLUAI_FUNC void luaX_next (LexState *ls);\nLUAI_FUNC int luaX_lookahead (LexState *ls);\nLUAI_FUNC l_noret luaX_syntaxerror (LexState *ls, const char *s);\nLUAI_FUNC const char *luaX_token2str (LexState *ls, int token);\n\n\n#endif\n/*\n** $Id: ltable.h $\n** Lua tables (hash)\n** See Copyright Notice in lua.h\n*/\n\n#ifndef ltable_h\n#define ltable_h\n\n/*#include \"lobject.h\"*/\n\n\n#define gnode(t,i)\t(&(t)->node[i])\n#define gval(n)\t\t(&(n)->i_val)\n#define gnext(n)\t((n)->u.next)\n\n\n/*\n** Clear all bits of fast-access metamethods, which means that the table\n** may have any of these metamethods. (First access that fails after the\n** clearing will set the bit again.)\n*/\n#define invalidateTMcache(t)\t((t)->flags &= ~maskflags)\n\n\n/* true when 't' is using 'dummynode' as its hash part */\n#define isdummy(t)\t\t((t)->lastfree == NULL)\n\n\n/* allocated size for hash nodes */\n#define allocsizenode(t)\t(isdummy(t) ? 0 : sizenode(t))\n\n\n/* returns the Node, given the value of a table entry */\n#define nodefromval(v)\tcast(Node *, (v))\n\n\nLUAI_FUNC const TValue *luaH_getint (Table *t, lua_Integer key);\nLUAI_FUNC void luaH_setint (lua_State *L, Table *t, lua_Integer key,\n                                                    TValue *value);\nLUAI_FUNC const TValue *luaH_getshortstr (Table *t, TString *key);\nLUAI_FUNC const TValue *luaH_getstr (Table *t, TString *key);\nLUAI_FUNC const TValue *luaH_get (Table *t, const TValue *key);\nLUAI_FUNC void luaH_newkey (lua_State *L, Table *t, const TValue *key,\n                                                    TValue *value);\nLUAI_FUNC void luaH_set (lua_State *L, Table *t, const TValue *key,\n                                                 TValue *value);\nLUAI_FUNC void luaH_finishset (lua_State *L, Table *t, const TValue *key,\n                                       const TValue *slot, TValue *value);\nLUAI_FUNC Table *luaH_new (lua_State *L);\nLUAI_FUNC void luaH_resize (lua_State *L, Table *t, unsigned int nasize,\n                                                    unsigned int nhsize);\nLUAI_FUNC void luaH_resizearray (lua_State *L, Table *t, unsigned int nasize);\nLUAI_FUNC void luaH_free (lua_State *L, Table *t);\nLUAI_FUNC int luaH_next (lua_State *L, Table *t, StkId key);\nLUAI_FUNC lua_Unsigned luaH_getn (Table *t);\nLUAI_FUNC unsigned int luaH_realasize (const Table *t);\n\n\n#if defined(LUA_DEBUG)\nLUAI_FUNC Node *luaH_mainposition (const Table *t, const TValue *key);\n#endif\n\n\n#endif\n/*\n** $Id: lparser.h $\n** Lua Parser\n** See Copyright Notice in lua.h\n*/\n\n#ifndef lparser_h\n#define lparser_h\n\n/*#include \"llimits.h\"*/\n/*#include \"lobject.h\"*/\n/*#include \"lzio.h\"*/\n\n\n/*\n** Expression and variable descriptor.\n** Code generation for variables and expressions can be delayed to allow\n** optimizations; An 'expdesc' structure describes a potentially-delayed\n** variable/expression. It has a description of its \"main\" value plus a\n** list of conditional jumps that can also produce its value (generated\n** by short-circuit operators 'and'/'or').\n*/\n\n/* kinds of variables/expressions */\ntypedef enum {\n  VVOID,  /* when 'expdesc' describes the last expression of a list,\n             this kind means an empty list (so, no expression) */\n  VNIL,  /* constant nil */\n  VTRUE,  /* constant true */\n  VFALSE,  /* constant false */\n  VK,  /* constant in 'k'; info = index of constant in 'k' */\n  VKFLT,  /* floating constant; nval = numerical float value */\n  VKINT,  /* integer constant; ival = numerical integer value */\n  VKSTR,  /* string constant; strval = TString address;\n             (string is fixed by the lexer) */\n  VNONRELOC,  /* expression has its value in a fixed register;\n                 info = result register */\n  VLOCAL,  /* local variable; var.ridx = register index;\n              var.vidx = relative index in 'actvar.arr'  */\n  VUPVAL,  /* upvalue variable; info = index of upvalue in 'upvalues' */\n  VCONST,  /* compile-time <const> variable;\n              info = absolute index in 'actvar.arr'  */\n  VINDEXED,  /* indexed variable;\n                ind.t = table register;\n                ind.idx = key's R index */\n  VINDEXUP,  /* indexed upvalue;\n                ind.t = table upvalue;\n                ind.idx = key's K index */\n  VINDEXI, /* indexed variable with constant integer;\n                ind.t = table register;\n                ind.idx = key's value */\n  VINDEXSTR, /* indexed variable with literal string;\n                ind.t = table register;\n                ind.idx = key's K index */\n  VJMP,  /* expression is a test/comparison;\n            info = pc of corresponding jump instruction */\n  VRELOC,  /* expression can put result in any register;\n              info = instruction pc */\n  VCALL,  /* expression is a function call; info = instruction pc */\n  VVARARG  /* vararg expression; info = instruction pc */\n} expkind;\n\n\n#define vkisvar(k)\t(VLOCAL <= (k) && (k) <= VINDEXSTR)\n#define vkisindexed(k)\t(VINDEXED <= (k) && (k) <= VINDEXSTR)\n\n\ntypedef struct expdesc {\n  expkind k;\n  union {\n    lua_Integer ival;    /* for VKINT */\n    lua_Number nval;  /* for VKFLT */\n    TString *strval;  /* for VKSTR */\n    int info;  /* for generic use */\n    struct {  /* for indexed variables */\n      short idx;  /* index (R or \"long\" K) */\n      lu_byte t;  /* table (register or upvalue) */\n    } ind;\n    struct {  /* for local variables */\n      lu_byte ridx;  /* register holding the variable */\n      unsigned short vidx;  /* compiler index (in 'actvar.arr')  */\n    } var;\n  } u;\n  int t;  /* patch list of 'exit when true' */\n  int f;  /* patch list of 'exit when false' */\n} expdesc;\n\n\n/* kinds of variables */\n#define VDKREG\t\t0   /* regular */\n#define RDKCONST\t1   /* constant */\n#define RDKTOCLOSE\t2   /* to-be-closed */\n#define RDKCTC\t\t3   /* compile-time constant */\n\n/* description of an active local variable */\ntypedef union Vardesc {\n  struct {\n    TValuefields;  /* constant value (if it is a compile-time constant) */\n    lu_byte kind;\n    lu_byte ridx;  /* register holding the variable */\n    short pidx;  /* index of the variable in the Proto's 'locvars' array */\n    TString *name;  /* variable name */\n  } vd;\n  TValue k;  /* constant value (if any) */\n} Vardesc;\n\n\n\n/* description of pending goto statements and label statements */\ntypedef struct Labeldesc {\n  TString *name;  /* label identifier */\n  int pc;  /* position in code */\n  int line;  /* line where it appeared */\n  lu_byte nactvar;  /* number of active variables in that position */\n  lu_byte close;  /* goto that escapes upvalues */\n} Labeldesc;\n\n\n/* list of labels or gotos */\ntypedef struct Labellist {\n  Labeldesc *arr;  /* array */\n  int n;  /* number of entries in use */\n  int size;  /* array size */\n} Labellist;\n\n\n/* dynamic structures used by the parser */\ntypedef struct Dyndata {\n  struct {  /* list of all active local variables */\n    Vardesc *arr;\n    int n;\n    int size;\n  } actvar;\n  Labellist gt;  /* list of pending gotos */\n  Labellist label;   /* list of active labels */\n} Dyndata;\n\n\n/* control of blocks */\nstruct BlockCnt;  /* defined in lparser.c */\n\n\n/* state needed to generate code for a given function */\ntypedef struct FuncState {\n  Proto *f;  /* current function header */\n  struct FuncState *prev;  /* enclosing function */\n  struct LexState *ls;  /* lexical state */\n  struct BlockCnt *bl;  /* chain of current blocks */\n  int pc;  /* next position to code (equivalent to 'ncode') */\n  int lasttarget;   /* 'label' of last 'jump label' */\n  int previousline;  /* last line that was saved in 'lineinfo' */\n  int nk;  /* number of elements in 'k' */\n  int np;  /* number of elements in 'p' */\n  int nabslineinfo;  /* number of elements in 'abslineinfo' */\n  int firstlocal;  /* index of first local var (in Dyndata array) */\n  int firstlabel;  /* index of first label (in 'dyd->label->arr') */\n  short ndebugvars;  /* number of elements in 'f->locvars' */\n  lu_byte nactvar;  /* number of active local variables */\n  lu_byte nups;  /* number of upvalues */\n  lu_byte freereg;  /* first free register */\n  lu_byte iwthabs;  /* instructions issued since last absolute line info */\n  lu_byte needclose;  /* function needs to close upvalues when returning */\n} FuncState;\n\n\nLUAI_FUNC int luaY_nvarstack (FuncState *fs);\nLUAI_FUNC LClosure *luaY_parser (lua_State *L, ZIO *z, Mbuffer *buff,\n                                 Dyndata *dyd, const char *name, int firstchar);\n\n\n#endif\n/*\n** $Id: lcode.h $\n** Code generator for Lua\n** See Copyright Notice in lua.h\n*/\n\n#ifndef lcode_h\n#define lcode_h\n\n/*#include \"llex.h\"*/\n/*#include \"lobject.h\"*/\n/*#include \"lopcodes.h\"*/\n/*#include \"lparser.h\"*/\n\n\n/*\n** Marks the end of a patch list. It is an invalid value both as an absolute\n** address, and as a list link (would link an element to itself).\n*/\n#define NO_JUMP (-1)\n\n\n/*\n** grep \"ORDER OPR\" if you change these enums  (ORDER OP)\n*/\ntypedef enum BinOpr {\n  /* arithmetic operators */\n  OPR_ADD, OPR_SUB, OPR_MUL, OPR_MOD, OPR_POW,\n  OPR_DIV, OPR_IDIV,\n  /* bitwise operators */\n  OPR_BAND, OPR_BOR, OPR_BXOR,\n  OPR_SHL, OPR_SHR,\n  /* string operator */\n  OPR_CONCAT,\n  /* comparison operators */\n  OPR_EQ, OPR_LT, OPR_LE,\n  OPR_NE, OPR_GT, OPR_GE,\n  /* logical operators */\n  OPR_AND, OPR_OR,\n  OPR_NOBINOPR\n} BinOpr;\n\n\n/* true if operation is foldable (that is, it is arithmetic or bitwise) */\n#define foldbinop(op)\t((op) <= OPR_SHR)\n\n\n#define luaK_codeABC(fs,o,a,b,c)\tluaK_codeABCk(fs,o,a,b,c,0)\n\n\ntypedef enum UnOpr { OPR_MINUS, OPR_BNOT, OPR_NOT, OPR_LEN, OPR_NOUNOPR } UnOpr;\n\n\n/* get (pointer to) instruction of given 'expdesc' */\n#define getinstruction(fs,e)\t((fs)->f->code[(e)->u.info])\n\n\n#define luaK_setmultret(fs,e)\tluaK_setreturns(fs, e, LUA_MULTRET)\n\n#define luaK_jumpto(fs,t)\tluaK_patchlist(fs, luaK_jump(fs), t)\n\nLUAI_FUNC int luaK_code (FuncState *fs, Instruction i);\nLUAI_FUNC int luaK_codeABx (FuncState *fs, OpCode o, int A, unsigned int Bx);\nLUAI_FUNC int luaK_codeAsBx (FuncState *fs, OpCode o, int A, int Bx);\nLUAI_FUNC int luaK_codeABCk (FuncState *fs, OpCode o, int A,\n                                            int B, int C, int k);\nLUAI_FUNC int luaK_isKint (expdesc *e);\nLUAI_FUNC int luaK_exp2const (FuncState *fs, const expdesc *e, TValue *v);\nLUAI_FUNC void luaK_fixline (FuncState *fs, int line);\nLUAI_FUNC void luaK_nil (FuncState *fs, int from, int n);\nLUAI_FUNC void luaK_reserveregs (FuncState *fs, int n);\nLUAI_FUNC void luaK_checkstack (FuncState *fs, int n);\nLUAI_FUNC void luaK_int (FuncState *fs, int reg, lua_Integer n);\nLUAI_FUNC void luaK_dischargevars (FuncState *fs, expdesc *e);\nLUAI_FUNC int luaK_exp2anyreg (FuncState *fs, expdesc *e);\nLUAI_FUNC void luaK_exp2anyregup (FuncState *fs, expdesc *e);\nLUAI_FUNC void luaK_exp2nextreg (FuncState *fs, expdesc *e);\nLUAI_FUNC void luaK_exp2val (FuncState *fs, expdesc *e);\nLUAI_FUNC int luaK_exp2RK (FuncState *fs, expdesc *e);\nLUAI_FUNC void luaK_self (FuncState *fs, expdesc *e, expdesc *key);\nLUAI_FUNC void luaK_indexed (FuncState *fs, expdesc *t, expdesc *k);\nLUAI_FUNC void luaK_goiftrue (FuncState *fs, expdesc *e);\nLUAI_FUNC void luaK_goiffalse (FuncState *fs, expdesc *e);\nLUAI_FUNC void luaK_storevar (FuncState *fs, expdesc *var, expdesc *e);\nLUAI_FUNC void luaK_setreturns (FuncState *fs, expdesc *e, int nresults);\nLUAI_FUNC void luaK_setoneret (FuncState *fs, expdesc *e);\nLUAI_FUNC int luaK_jump (FuncState *fs);\nLUAI_FUNC void luaK_ret (FuncState *fs, int first, int nret);\nLUAI_FUNC void luaK_patchlist (FuncState *fs, int list, int target);\nLUAI_FUNC void luaK_patchtohere (FuncState *fs, int list);\nLUAI_FUNC void luaK_concat (FuncState *fs, int *l1, int l2);\nLUAI_FUNC int luaK_getlabel (FuncState *fs);\nLUAI_FUNC void luaK_prefix (FuncState *fs, UnOpr op, expdesc *v, int line);\nLUAI_FUNC void luaK_infix (FuncState *fs, BinOpr op, expdesc *v);\nLUAI_FUNC void luaK_posfix (FuncState *fs, BinOpr op, expdesc *v1,\n                            expdesc *v2, int line);\nLUAI_FUNC void luaK_settablesize (FuncState *fs, int pc,\n                                  int ra, int asize, int hsize);\nLUAI_FUNC void luaK_setlist (FuncState *fs, int base, int nelems, int tostore);\nLUAI_FUNC void luaK_finish (FuncState *fs);\nLUAI_FUNC l_noret luaK_semerror (LexState *ls, const char *msg);\n\n\n#endif\n/*\n** $Id: lvm.h $\n** Lua virtual machine\n** See Copyright Notice in lua.h\n*/\n\n#ifndef lvm_h\n#define lvm_h\n\n\n/*#include \"ldo.h\"*/\n/*#include \"lobject.h\"*/\n/*#include \"ltm.h\"*/\n\n\n#if !defined(LUA_NOCVTN2S)\n#define cvt2str(o)\tttisnumber(o)\n#else\n#define cvt2str(o)\t0\t/* no conversion from numbers to strings */\n#endif\n\n\n#if !defined(LUA_NOCVTS2N)\n#define cvt2num(o)\tttisstring(o)\n#else\n#define cvt2num(o)\t0\t/* no conversion from strings to numbers */\n#endif\n\n\n/*\n** You can define LUA_FLOORN2I if you want to convert floats to integers\n** by flooring them (instead of raising an error if they are not\n** integral values)\n*/\n#if !defined(LUA_FLOORN2I)\n#define LUA_FLOORN2I\t\tF2Ieq\n#endif\n\n\n/*\n** Rounding modes for float->integer coercion\n */\ntypedef enum {\n  F2Ieq,     /* no rounding; accepts only integral values */\n  F2Ifloor,  /* takes the floor of the number */\n  F2Iceil    /* takes the ceil of the number */\n} F2Imod;\n\n\n/* convert an object to a float (including string coercion) */\n#define tonumber(o,n) \\\n\t(ttisfloat(o) ? (*(n) = fltvalue(o), 1) : luaV_tonumber_(o,n))\n\n\n/* convert an object to a float (without string coercion) */\n#define tonumberns(o,n) \\\n\t(ttisfloat(o) ? ((n) = fltvalue(o), 1) : \\\n\t(ttisinteger(o) ? ((n) = cast_num(ivalue(o)), 1) : 0))\n\n\n/* convert an object to an integer (including string coercion) */\n#define tointeger(o,i) \\\n  (l_likely(ttisinteger(o)) ? (*(i) = ivalue(o), 1) \\\n                          : luaV_tointeger(o,i,LUA_FLOORN2I))\n\n\n/* convert an object to an integer (without string coercion) */\n#define tointegerns(o,i) \\\n  (l_likely(ttisinteger(o)) ? (*(i) = ivalue(o), 1) \\\n                          : luaV_tointegerns(o,i,LUA_FLOORN2I))\n\n\n#define intop(op,v1,v2) l_castU2S(l_castS2U(v1) op l_castS2U(v2))\n\n#define luaV_rawequalobj(t1,t2)\t\tluaV_equalobj(NULL,t1,t2)\n\n\n/*\n** fast track for 'gettable': if 't' is a table and 't[k]' is present,\n** return 1 with 'slot' pointing to 't[k]' (position of final result).\n** Otherwise, return 0 (meaning it will have to check metamethod)\n** with 'slot' pointing to an empty 't[k]' (if 't' is a table) or NULL\n** (otherwise). 'f' is the raw get function to use.\n*/\n#define luaV_fastget(L,t,k,slot,f) \\\n  (!ttistable(t)  \\\n   ? (slot = NULL, 0)  /* not a table; 'slot' is NULL and result is 0 */  \\\n   : (slot = f(hvalue(t), k),  /* else, do raw access */  \\\n      !isempty(slot)))  /* result not empty? */\n\n\n/*\n** Special case of 'luaV_fastget' for integers, inlining the fast case\n** of 'luaH_getint'.\n*/\n#define luaV_fastgeti(L,t,k,slot) \\\n  (!ttistable(t)  \\\n   ? (slot = NULL, 0)  /* not a table; 'slot' is NULL and result is 0 */  \\\n   : (slot = (l_castS2U(k) - 1u < hvalue(t)->alimit) \\\n              ? &hvalue(t)->array[k - 1] : luaH_getint(hvalue(t), k), \\\n      !isempty(slot)))  /* result not empty? */\n\n\n/*\n** Finish a fast set operation (when fast get succeeds). In that case,\n** 'slot' points to the place to put the value.\n*/\n#define luaV_finishfastset(L,t,slot,v) \\\n    { setobj2t(L, cast(TValue *,slot), v); \\\n      luaC_barrierback(L, gcvalue(t), v); }\n\n\n/*\n** Shift right is the same as shift left with a negative 'y'\n*/\n#define luaV_shiftr(x,y)\tluaV_shiftl(x,intop(-, 0, y))\n\n\n\nLUAI_FUNC int luaV_equalobj (lua_State *L, const TValue *t1, const TValue *t2);\nLUAI_FUNC int luaV_lessthan (lua_State *L, const TValue *l, const TValue *r);\nLUAI_FUNC int luaV_lessequal (lua_State *L, const TValue *l, const TValue *r);\nLUAI_FUNC int luaV_tonumber_ (const TValue *obj, lua_Number *n);\nLUAI_FUNC int luaV_tointeger (const TValue *obj, lua_Integer *p, F2Imod mode);\nLUAI_FUNC int luaV_tointegerns (const TValue *obj, lua_Integer *p,\n                                F2Imod mode);\nLUAI_FUNC int luaV_flttointeger (lua_Number n, lua_Integer *p, F2Imod mode);\nLUAI_FUNC void luaV_finishget (lua_State *L, const TValue *t, TValue *key,\n                               StkId val, const TValue *slot);\nLUAI_FUNC void luaV_finishset (lua_State *L, const TValue *t, TValue *key,\n                               TValue *val, const TValue *slot);\nLUAI_FUNC void luaV_finishOp (lua_State *L);\nLUAI_FUNC void luaV_execute (lua_State *L, CallInfo *ci);\nLUAI_FUNC void luaV_concat (lua_State *L, int total);\nLUAI_FUNC lua_Integer luaV_idiv (lua_State *L, lua_Integer x, lua_Integer y);\nLUAI_FUNC lua_Integer luaV_mod (lua_State *L, lua_Integer x, lua_Integer y);\nLUAI_FUNC lua_Number luaV_modf (lua_State *L, lua_Number x, lua_Number y);\nLUAI_FUNC lua_Integer luaV_shiftl (lua_Integer x, lua_Integer y);\nLUAI_FUNC void luaV_objlen (lua_State *L, StkId ra, const TValue *rb);\n\n#endif\n/*\n** $Id: lctype.h $\n** 'ctype' functions for Lua\n** See Copyright Notice in lua.h\n*/\n\n#ifndef lctype_h\n#define lctype_h\n\n/*#include \"lua.h\"*/\n\n\n/*\n** WARNING: the functions defined here do not necessarily correspond\n** to the similar functions in the standard C ctype.h. They are\n** optimized for the specific needs of Lua.\n*/\n\n#if !defined(LUA_USE_CTYPE)\n\n#if 'A' == 65 && '0' == 48\n/* ASCII case: can use its own tables; faster and fixed */\n#define LUA_USE_CTYPE\t0\n#else\n/* must use standard C ctype */\n#define LUA_USE_CTYPE\t1\n#endif\n\n#endif\n\n\n#if !LUA_USE_CTYPE\t/* { */\n\n#include <limits.h>\n\n/*#include \"llimits.h\"*/\n\n\n#define ALPHABIT\t0\n#define DIGITBIT\t1\n#define PRINTBIT\t2\n#define SPACEBIT\t3\n#define XDIGITBIT\t4\n\n\n#define MASK(B)\t\t(1 << (B))\n\n\n/*\n** add 1 to char to allow index -1 (EOZ)\n*/\n#define testprop(c,p)\t(luai_ctype_[(c)+1] & (p))\n\n/*\n** 'lalpha' (Lua alphabetic) and 'lalnum' (Lua alphanumeric) both include '_'\n*/\n#define lislalpha(c)\ttestprop(c, MASK(ALPHABIT))\n#define lislalnum(c)\ttestprop(c, (MASK(ALPHABIT) | MASK(DIGITBIT)))\n#define lisdigit(c)\ttestprop(c, MASK(DIGITBIT))\n#define lisspace(c)\ttestprop(c, MASK(SPACEBIT))\n#define lisprint(c)\ttestprop(c, MASK(PRINTBIT))\n#define lisxdigit(c)\ttestprop(c, MASK(XDIGITBIT))\n\n\n/*\n** In ASCII, this 'ltolower' is correct for alphabetic characters and\n** for '.'. That is enough for Lua needs. ('check_exp' ensures that\n** the character either is an upper-case letter or is unchanged by\n** the transformation, which holds for lower-case letters and '.'.)\n*/\n#define ltolower(c)  \\\n  check_exp(('A' <= (c) && (c) <= 'Z') || (c) == ((c) | ('A' ^ 'a')),  \\\n            (c) | ('A' ^ 'a'))\n\n\n/* one entry for each character and for -1 (EOZ) */\nLUAI_DDEC(const lu_byte luai_ctype_[UCHAR_MAX + 2];)\n\n\n#else\t\t\t/* }{ */\n\n/*\n** use standard C ctypes\n*/\n\n#include <ctype.h>\n\n\n#define lislalpha(c)\t(isalpha(c) || (c) == '_')\n#define lislalnum(c)\t(isalnum(c) || (c) == '_')\n#define lisdigit(c)\t(isdigit(c))\n#define lisspace(c)\t(isspace(c))\n#define lisprint(c)\t(isprint(c))\n#define lisxdigit(c)\t(isxdigit(c))\n\n#define ltolower(c)\t(tolower(c))\n\n#endif\t\t\t/* } */\n\n#endif\n\n/*\n** $Id: lzio.c $\n** Buffered streams\n** See Copyright Notice in lua.h\n*/\n\n#define lzio_c\n#define LUA_CORE\n\n/*#include \"lprefix.h\"*/\n\n\n#include <string.h>\n\n/*#include \"lua.h\"*/\n\n/*#include \"llimits.h\"*/\n/*#include \"lmem.h\"*/\n/*#include \"lstate.h\"*/\n/*#include \"lzio.h\"*/\n\n\nint luaZ_fill (ZIO *z) {\n  size_t size;\n  lua_State *L = z->L;\n  const char *buff;\n  lua_unlock(L);\n  buff = z->reader(L, z->data, &size);\n  lua_lock(L);\n  if (buff == NULL || size == 0)\n    return EOZ;\n  z->n = size - 1;  /* discount char being returned */\n  z->p = buff;\n  return cast_uchar(*(z->p++));\n}\n\n\nvoid luaZ_init (lua_State *L, ZIO *z, lua_Reader reader, void *data) {\n  z->L = L;\n  z->reader = reader;\n  z->data = data;\n  z->n = 0;\n  z->p = NULL;\n}\n\n\n/* --------------------------------------------------------------- read --- */\nsize_t luaZ_read (ZIO *z, void *b, size_t n) {\n  while (n) {\n    size_t m;\n    if (z->n == 0) {  /* no bytes in buffer? */\n      if (luaZ_fill(z) == EOZ)  /* try to read more */\n        return n;  /* no more input; return number of missing bytes */\n      else {\n        z->n++;  /* luaZ_fill consumed first byte; put it back */\n        z->p--;\n      }\n    }\n    m = (n <= z->n) ? n : z->n;  /* min. between n and z->n */\n    memcpy(b, z->p, m);\n    z->n -= m;\n    z->p += m;\n    b = (char *)b + m;\n    n -= m;\n  }\n  return 0;\n}\n\n/*\n** $Id: lctype.c $\n** 'ctype' functions for Lua\n** See Copyright Notice in lua.h\n*/\n\n#define lctype_c\n#define LUA_CORE\n\n/*#include \"lprefix.h\"*/\n\n\n/*#include \"lctype.h\"*/\n\n#if !LUA_USE_CTYPE\t/* { */\n\n#include <limits.h>\n\n\n#if defined (LUA_UCID)\t\t/* accept UniCode IDentifiers? */\n/* consider all non-ascii codepoints to be alphabetic */\n#define NONA\t\t0x01\n#else\n#define NONA\t\t0x00\t/* default */\n#endif\n\n\nLUAI_DDEF const lu_byte luai_ctype_[UCHAR_MAX + 2] = {\n  0x00,  /* EOZ */\n  0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00,\t/* 0. */\n  0x00,  0x08,  0x08,  0x08,  0x08,  0x08,  0x00,  0x00,\n  0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00,\t/* 1. */\n  0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00,\n  0x0c,  0x04,  0x04,  0x04,  0x04,  0x04,  0x04,  0x04,\t/* 2. */\n  0x04,  0x04,  0x04,  0x04,  0x04,  0x04,  0x04,  0x04,\n  0x16,  0x16,  0x16,  0x16,  0x16,  0x16,  0x16,  0x16,\t/* 3. */\n  0x16,  0x16,  0x04,  0x04,  0x04,  0x04,  0x04,  0x04,\n  0x04,  0x15,  0x15,  0x15,  0x15,  0x15,  0x15,  0x05,\t/* 4. */\n  0x05,  0x05,  0x05,  0x05,  0x05,  0x05,  0x05,  0x05,\n  0x05,  0x05,  0x05,  0x05,  0x05,  0x05,  0x05,  0x05,\t/* 5. */\n  0x05,  0x05,  0x05,  0x04,  0x04,  0x04,  0x04,  0x05,\n  0x04,  0x15,  0x15,  0x15,  0x15,  0x15,  0x15,  0x05,\t/* 6. */\n  0x05,  0x05,  0x05,  0x05,  0x05,  0x05,  0x05,  0x05,\n  0x05,  0x05,  0x05,  0x05,  0x05,  0x05,  0x05,  0x05,\t/* 7. */\n  0x05,  0x05,  0x05,  0x04,  0x04,  0x04,  0x04,  0x00,\n  NONA,  NONA,  NONA,  NONA,  NONA,  NONA,  NONA,  NONA,\t/* 8. */\n  NONA,  NONA,  NONA,  NONA,  NONA,  NONA,  NONA,  NONA,\n  NONA,  NONA,  NONA,  NONA,  NONA,  NONA,  NONA,  NONA,\t/* 9. */\n  NONA,  NONA,  NONA,  NONA,  NONA,  NONA,  NONA,  NONA,\n  NONA,  NONA,  NONA,  NONA,  NONA,  NONA,  NONA,  NONA,\t/* a. */\n  NONA,  NONA,  NONA,  NONA,  NONA,  NONA,  NONA,  NONA,\n  NONA,  NONA,  NONA,  NONA,  NONA,  NONA,  NONA,  NONA,\t/* b. */\n  NONA,  NONA,  NONA,  NONA,  NONA,  NONA,  NONA,  NONA,\n  0x00,  0x00,  NONA,  NONA,  NONA,  NONA,  NONA,  NONA,\t/* c. */\n  NONA,  NONA,  NONA,  NONA,  NONA,  NONA,  NONA,  NONA,\n  NONA,  NONA,  NONA,  NONA,  NONA,  NONA,  NONA,  NONA,\t/* d. */\n  NONA,  NONA,  NONA,  NONA,  NONA,  NONA,  NONA,  NONA,\n  NONA,  NONA,  NONA,  NONA,  NONA,  NONA,  NONA,  NONA,\t/* e. */\n  NONA,  NONA,  NONA,  NONA,  NONA,  NONA,  NONA,  NONA,\n  NONA,  NONA,  NONA,  NONA,  NONA,  0x00,  0x00,  0x00,\t/* f. */\n  0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00\n};\n\n#endif\t\t\t/* } */\n/*\n** $Id: lopcodes.c $\n** Opcodes for Lua virtual machine\n** See Copyright Notice in lua.h\n*/\n\n#define lopcodes_c\n#define LUA_CORE\n\n/*#include \"lprefix.h\"*/\n\n\n/*#include \"lopcodes.h\"*/\n\n\n/* ORDER OP */\n\nLUAI_DDEF const lu_byte luaP_opmodes[NUM_OPCODES] = {\n/*       MM OT IT T  A  mode\t\t   opcode  */\n  opmode(0, 0, 0, 0, 1, iABC)\t\t/* OP_MOVE */\n ,opmode(0, 0, 0, 0, 1, iAsBx)\t\t/* OP_LOADI */\n ,opmode(0, 0, 0, 0, 1, iAsBx)\t\t/* OP_LOADF */\n ,opmode(0, 0, 0, 0, 1, iABx)\t\t/* OP_LOADK */\n ,opmode(0, 0, 0, 0, 1, iABx)\t\t/* OP_LOADKX */\n ,opmode(0, 0, 0, 0, 1, iABC)\t\t/* OP_LOADFALSE */\n ,opmode(0, 0, 0, 0, 1, iABC)\t\t/* OP_LFALSESKIP */\n ,opmode(0, 0, 0, 0, 1, iABC)\t\t/* OP_LOADTRUE */\n ,opmode(0, 0, 0, 0, 1, iABC)\t\t/* OP_LOADNIL */\n ,opmode(0, 0, 0, 0, 1, iABC)\t\t/* OP_GETUPVAL */\n ,opmode(0, 0, 0, 0, 0, iABC)\t\t/* OP_SETUPVAL */\n ,opmode(0, 0, 0, 0, 1, iABC)\t\t/* OP_GETTABUP */\n ,opmode(0, 0, 0, 0, 1, iABC)\t\t/* OP_GETTABLE */\n ,opmode(0, 0, 0, 0, 1, iABC)\t\t/* OP_GETI */\n ,opmode(0, 0, 0, 0, 1, iABC)\t\t/* OP_GETFIELD */\n ,opmode(0, 0, 0, 0, 0, iABC)\t\t/* OP_SETTABUP */\n ,opmode(0, 0, 0, 0, 0, iABC)\t\t/* OP_SETTABLE */\n ,opmode(0, 0, 0, 0, 0, iABC)\t\t/* OP_SETI */\n ,opmode(0, 0, 0, 0, 0, iABC)\t\t/* OP_SETFIELD */\n ,opmode(0, 0, 0, 0, 1, iABC)\t\t/* OP_NEWTABLE */\n ,opmode(0, 0, 0, 0, 1, iABC)\t\t/* OP_SELF */\n ,opmode(0, 0, 0, 0, 1, iABC)\t\t/* OP_ADDI */\n ,opmode(0, 0, 0, 0, 1, iABC)\t\t/* OP_ADDK */\n ,opmode(0, 0, 0, 0, 1, iABC)\t\t/* OP_SUBK */\n ,opmode(0, 0, 0, 0, 1, iABC)\t\t/* OP_MULK */\n ,opmode(0, 0, 0, 0, 1, iABC)\t\t/* OP_MODK */\n ,opmode(0, 0, 0, 0, 1, iABC)\t\t/* OP_POWK */\n ,opmode(0, 0, 0, 0, 1, iABC)\t\t/* OP_DIVK */\n ,opmode(0, 0, 0, 0, 1, iABC)\t\t/* OP_IDIVK */\n ,opmode(0, 0, 0, 0, 1, iABC)\t\t/* OP_BANDK */\n ,opmode(0, 0, 0, 0, 1, iABC)\t\t/* OP_BORK */\n ,opmode(0, 0, 0, 0, 1, iABC)\t\t/* OP_BXORK */\n ,opmode(0, 0, 0, 0, 1, iABC)\t\t/* OP_SHRI */\n ,opmode(0, 0, 0, 0, 1, iABC)\t\t/* OP_SHLI */\n ,opmode(0, 0, 0, 0, 1, iABC)\t\t/* OP_ADD */\n ,opmode(0, 0, 0, 0, 1, iABC)\t\t/* OP_SUB */\n ,opmode(0, 0, 0, 0, 1, iABC)\t\t/* OP_MUL */\n ,opmode(0, 0, 0, 0, 1, iABC)\t\t/* OP_MOD */\n ,opmode(0, 0, 0, 0, 1, iABC)\t\t/* OP_POW */\n ,opmode(0, 0, 0, 0, 1, iABC)\t\t/* OP_DIV */\n ,opmode(0, 0, 0, 0, 1, iABC)\t\t/* OP_IDIV */\n ,opmode(0, 0, 0, 0, 1, iABC)\t\t/* OP_BAND */\n ,opmode(0, 0, 0, 0, 1, iABC)\t\t/* OP_BOR */\n ,opmode(0, 0, 0, 0, 1, iABC)\t\t/* OP_BXOR */\n ,opmode(0, 0, 0, 0, 1, iABC)\t\t/* OP_SHL */\n ,opmode(0, 0, 0, 0, 1, iABC)\t\t/* OP_SHR */\n ,opmode(1, 0, 0, 0, 0, iABC)\t\t/* OP_MMBIN */\n ,opmode(1, 0, 0, 0, 0, iABC)\t\t/* OP_MMBINI*/\n ,opmode(1, 0, 0, 0, 0, iABC)\t\t/* OP_MMBINK*/\n ,opmode(0, 0, 0, 0, 1, iABC)\t\t/* OP_UNM */\n ,opmode(0, 0, 0, 0, 1, iABC)\t\t/* OP_BNOT */\n ,opmode(0, 0, 0, 0, 1, iABC)\t\t/* OP_NOT */\n ,opmode(0, 0, 0, 0, 1, iABC)\t\t/* OP_LEN */\n ,opmode(0, 0, 0, 0, 1, iABC)\t\t/* OP_CONCAT */\n ,opmode(0, 0, 0, 0, 0, iABC)\t\t/* OP_CLOSE */\n ,opmode(0, 0, 0, 0, 0, iABC)\t\t/* OP_TBC */\n ,opmode(0, 0, 0, 0, 0, isJ)\t\t/* OP_JMP */\n ,opmode(0, 0, 0, 1, 0, iABC)\t\t/* OP_EQ */\n ,opmode(0, 0, 0, 1, 0, iABC)\t\t/* OP_LT */\n ,opmode(0, 0, 0, 1, 0, iABC)\t\t/* OP_LE */\n ,opmode(0, 0, 0, 1, 0, iABC)\t\t/* OP_EQK */\n ,opmode(0, 0, 0, 1, 0, iABC)\t\t/* OP_EQI */\n ,opmode(0, 0, 0, 1, 0, iABC)\t\t/* OP_LTI */\n ,opmode(0, 0, 0, 1, 0, iABC)\t\t/* OP_LEI */\n ,opmode(0, 0, 0, 1, 0, iABC)\t\t/* OP_GTI */\n ,opmode(0, 0, 0, 1, 0, iABC)\t\t/* OP_GEI */\n ,opmode(0, 0, 0, 1, 0, iABC)\t\t/* OP_TEST */\n ,opmode(0, 0, 0, 1, 1, iABC)\t\t/* OP_TESTSET */\n ,opmode(0, 1, 1, 0, 1, iABC)\t\t/* OP_CALL */\n ,opmode(0, 1, 1, 0, 1, iABC)\t\t/* OP_TAILCALL */\n ,opmode(0, 0, 1, 0, 0, iABC)\t\t/* OP_RETURN */\n ,opmode(0, 0, 0, 0, 0, iABC)\t\t/* OP_RETURN0 */\n ,opmode(0, 0, 0, 0, 0, iABC)\t\t/* OP_RETURN1 */\n ,opmode(0, 0, 0, 0, 1, iABx)\t\t/* OP_FORLOOP */\n ,opmode(0, 0, 0, 0, 1, iABx)\t\t/* OP_FORPREP */\n ,opmode(0, 0, 0, 0, 0, iABx)\t\t/* OP_TFORPREP */\n ,opmode(0, 0, 0, 0, 0, iABC)\t\t/* OP_TFORCALL */\n ,opmode(0, 0, 0, 0, 1, iABx)\t\t/* OP_TFORLOOP */\n ,opmode(0, 0, 1, 0, 0, iABC)\t\t/* OP_SETLIST */\n ,opmode(0, 0, 0, 0, 1, iABx)\t\t/* OP_CLOSURE */\n ,opmode(0, 1, 0, 0, 1, iABC)\t\t/* OP_VARARG */\n ,opmode(0, 0, 1, 0, 1, iABC)\t\t/* OP_VARARGPREP */\n ,opmode(0, 0, 0, 0, 0, iAx)\t\t/* OP_EXTRAARG */\n};\n\n/*\n** $Id: lmem.c $\n** Interface to Memory Manager\n** See Copyright Notice in lua.h\n*/\n\n#define lmem_c\n#define LUA_CORE\n\n/*#include \"lprefix.h\"*/\n\n\n#include <stddef.h>\n\n/*#include \"lua.h\"*/\n\n/*#include \"ldebug.h\"*/\n/*#include \"ldo.h\"*/\n/*#include \"lgc.h\"*/\n/*#include \"lmem.h\"*/\n/*#include \"lobject.h\"*/\n/*#include \"lstate.h\"*/\n\n\n\n/*\n** About the realloc function:\n** void *frealloc (void *ud, void *ptr, size_t osize, size_t nsize);\n** ('osize' is the old size, 'nsize' is the new size)\n**\n** - frealloc(ud, p, x, 0) frees the block 'p' and returns NULL.\n** Particularly, frealloc(ud, NULL, 0, 0) does nothing,\n** which is equivalent to free(NULL) in ISO C.\n**\n** - frealloc(ud, NULL, x, s) creates a new block of size 's'\n** (no matter 'x'). Returns NULL if it cannot create the new block.\n**\n** - otherwise, frealloc(ud, b, x, y) reallocates the block 'b' from\n** size 'x' to size 'y'. Returns NULL if it cannot reallocate the\n** block to the new size.\n*/\n\n\n/*\n** Macro to call the allocation function.\n*/\n#define callfrealloc(g,block,os,ns)    ((*g->frealloc)(g->ud, block, os, ns))\n\n\n/*\n** When an allocation fails, it will try again after an emergency\n** collection, except when it cannot run a collection.  The GC should\n** not be called while the state is not fully built, as the collector\n** is not yet fully initialized. Also, it should not be called when\n** 'gcstopem' is true, because then the interpreter is in the middle of\n** a collection step.\n*/\n#define cantryagain(g)\t(completestate(g) && !g->gcstopem)\n\n\n\n\n#if defined(EMERGENCYGCTESTS)\n/*\n** First allocation will fail except when freeing a block (frees never\n** fail) and when it cannot try again; this fail will trigger 'tryagain'\n** and a full GC cycle at every allocation.\n*/\nstatic void *firsttry (global_State *g, void *block, size_t os, size_t ns) {\n  if (ns > 0 && cantryagain(g))\n    return NULL;  /* fail */\n  else  /* normal allocation */\n    return callfrealloc(g, block, os, ns);\n}\n#else\n#define firsttry(g,block,os,ns)    callfrealloc(g, block, os, ns)\n#endif\n\n\n\n\n\n/*\n** {==================================================================\n** Functions to allocate/deallocate arrays for the Parser\n** ===================================================================\n*/\n\n/*\n** Minimum size for arrays during parsing, to avoid overhead of\n** reallocating to size 1, then 2, and then 4. All these arrays\n** will be reallocated to exact sizes or erased when parsing ends.\n*/\n#define MINSIZEARRAY\t4\n\n\nvoid *luaM_growaux_ (lua_State *L, void *block, int nelems, int *psize,\n                     int size_elems, int limit, const char *what) {\n  void *newblock;\n  int size = *psize;\n  if (nelems + 1 <= size)  /* does one extra element still fit? */\n    return block;  /* nothing to be done */\n  if (size >= limit / 2) {  /* cannot double it? */\n    if (l_unlikely(size >= limit))  /* cannot grow even a little? */\n      luaG_runerror(L, \"too many %s (limit is %d)\", what, limit);\n    size = limit;  /* still have at least one free place */\n  }\n  else {\n    size *= 2;\n    if (size < MINSIZEARRAY)\n      size = MINSIZEARRAY;  /* minimum size */\n  }\n  lua_assert(nelems + 1 <= size && size <= limit);\n  /* 'limit' ensures that multiplication will not overflow */\n  newblock = luaM_saferealloc_(L, block, cast_sizet(*psize) * size_elems,\n                                         cast_sizet(size) * size_elems);\n  *psize = size;  /* update only when everything else is OK */\n  return newblock;\n}\n\n\n/*\n** In prototypes, the size of the array is also its number of\n** elements (to save memory). So, if it cannot shrink an array\n** to its number of elements, the only option is to raise an\n** error.\n*/\nvoid *luaM_shrinkvector_ (lua_State *L, void *block, int *size,\n                          int final_n, int size_elem) {\n  void *newblock;\n  size_t oldsize = cast_sizet((*size) * size_elem);\n  size_t newsize = cast_sizet(final_n * size_elem);\n  lua_assert(newsize <= oldsize);\n  newblock = luaM_saferealloc_(L, block, oldsize, newsize);\n  *size = final_n;\n  return newblock;\n}\n\n/* }================================================================== */\n\n\nl_noret luaM_toobig (lua_State *L) {\n  luaG_runerror(L, \"memory allocation error: block too big\");\n}\n\n\n/*\n** Free memory\n*/\nvoid luaM_free_ (lua_State *L, void *block, size_t osize) {\n  global_State *g = G(L);\n  lua_assert((osize == 0) == (block == NULL));\n  callfrealloc(g, block, osize, 0);\n  g->GCdebt -= osize;\n}\n\n\n/*\n** In case of allocation fail, this function will do an emergency\n** collection to free some memory and then try the allocation again.\n*/\nstatic void *tryagain (lua_State *L, void *block,\n                       size_t osize, size_t nsize) {\n  global_State *g = G(L);\n  if (cantryagain(g)) {\n    luaC_fullgc(L, 1);  /* try to free some memory... */\n    return callfrealloc(g, block, osize, nsize);  /* try again */\n  }\n  else return NULL;  /* cannot run an emergency collection */\n}\n\n\n/*\n** Generic allocation routine.\n*/\nvoid *luaM_realloc_ (lua_State *L, void *block, size_t osize, size_t nsize) {\n  void *newblock;\n  global_State *g = G(L);\n  lua_assert((osize == 0) == (block == NULL));\n  newblock = firsttry(g, block, osize, nsize);\n  if (l_unlikely(newblock == NULL && nsize > 0)) {\n    newblock = tryagain(L, block, osize, nsize);\n    if (newblock == NULL)  /* still no memory? */\n      return NULL;  /* do not update 'GCdebt' */\n  }\n  lua_assert((nsize == 0) == (newblock == NULL));\n  g->GCdebt = (g->GCdebt + nsize) - osize;\n  return newblock;\n}\n\n\nvoid *luaM_saferealloc_ (lua_State *L, void *block, size_t osize,\n                                                    size_t nsize) {\n  void *newblock = luaM_realloc_(L, block, osize, nsize);\n  if (l_unlikely(newblock == NULL && nsize > 0))  /* allocation failed? */\n    luaM_error(L);\n  return newblock;\n}\n\n\nvoid *luaM_malloc_ (lua_State *L, size_t size, int tag) {\n  if (size == 0)\n    return NULL;  /* that's all */\n  else {\n    global_State *g = G(L);\n    void *newblock = firsttry(g, NULL, tag, size);\n    if (l_unlikely(newblock == NULL)) {\n      newblock = tryagain(L, NULL, tag, size);\n      if (newblock == NULL)\n        luaM_error(L);\n    }\n    g->GCdebt += size;\n    return newblock;\n  }\n}\n/*\n** $Id: lundump.c $\n** load precompiled Lua chunks\n** See Copyright Notice in lua.h\n*/\n\n#define lundump_c\n#define LUA_CORE\n\n/*#include \"lprefix.h\"*/\n\n\n#include <limits.h>\n#include <string.h>\n\n/*#include \"lua.h\"*/\n\n/*#include \"ldebug.h\"*/\n/*#include \"ldo.h\"*/\n/*#include \"lfunc.h\"*/\n/*#include \"lmem.h\"*/\n/*#include \"lobject.h\"*/\n/*#include \"lstring.h\"*/\n/*#include \"lundump.h\"*/\n/*#include \"lzio.h\"*/\n\n\n#if !defined(luai_verifycode)\n#define luai_verifycode(L,f)  /* empty */\n#endif\n\n\ntypedef struct {\n  lua_State *L;\n  ZIO *Z;\n  const char *name;\n} LoadState;\n\n\nstatic l_noret error (LoadState *S, const char *why) {\n  luaO_pushfstring(S->L, \"%s: bad binary format (%s)\", S->name, why);\n  luaD_throw(S->L, LUA_ERRSYNTAX);\n}\n\n\n/*\n** All high-level loads go through loadVector; you can change it to\n** adapt to the endianness of the input\n*/\n#define loadVector(S,b,n)\tloadBlock(S,b,(n)*sizeof((b)[0]))\n\nstatic void loadBlock (LoadState *S, void *b, size_t size) {\n  if (luaZ_read(S->Z, b, size) != 0)\n    error(S, \"truncated chunk\");\n}\n\n\n#define loadVar(S,x)\t\tloadVector(S,&x,1)\n\n\nstatic lu_byte loadByte (LoadState *S) {\n  int b = zgetc(S->Z);\n  if (b == EOZ)\n    error(S, \"truncated chunk\");\n  return cast_byte(b);\n}\n\n\nstatic size_t loadUnsigned (LoadState *S, size_t limit) {\n  size_t x = 0;\n  int b;\n  limit >>= 7;\n  do {\n    b = loadByte(S);\n    if (x >= limit)\n      error(S, \"integer overflow\");\n    x = (x << 7) | (b & 0x7f);\n  } while ((b & 0x80) == 0);\n  return x;\n}\n\n\nstatic size_t loadSize (LoadState *S) {\n  return loadUnsigned(S, ~(size_t)0);\n}\n\n\nstatic int loadInt (LoadState *S) {\n  return cast_int(loadUnsigned(S, INT_MAX));\n}\n\n\nstatic lua_Number loadNumber (LoadState *S) {\n  lua_Number x;\n  loadVar(S, x);\n  return x;\n}\n\n\nstatic lua_Integer loadInteger (LoadState *S) {\n  lua_Integer x;\n  loadVar(S, x);\n  return x;\n}\n\n\n/*\n** Load a nullable string into prototype 'p'.\n*/\nstatic TString *loadStringN (LoadState *S, Proto *p) {\n  lua_State *L = S->L;\n  TString *ts;\n  size_t size = loadSize(S);\n  if (size == 0)  /* no string? */\n    return NULL;\n  else if (--size <= LUAI_MAXSHORTLEN) {  /* short string? */\n    char buff[LUAI_MAXSHORTLEN];\n    loadVector(S, buff, size);  /* load string into buffer */\n    ts = luaS_newlstr(L, buff, size);  /* create string */\n  }\n  else {  /* long string */\n    ts = luaS_createlngstrobj(L, size);  /* create string */\n    setsvalue2s(L, L->top.p, ts);  /* anchor it ('loadVector' can GC) */\n    luaD_inctop(L);\n    loadVector(S, getstr(ts), size);  /* load directly in final place */\n    L->top.p--;  /* pop string */\n  }\n  luaC_objbarrier(L, p, ts);\n  return ts;\n}\n\n\n/*\n** Load a non-nullable string into prototype 'p'.\n*/\nstatic TString *loadString (LoadState *S, Proto *p) {\n  TString *st = loadStringN(S, p);\n  if (st == NULL)\n    error(S, \"bad format for constant string\");\n  return st;\n}\n\n\nstatic void loadCode (LoadState *S, Proto *f) {\n  int n = loadInt(S);\n  f->code = luaM_newvectorchecked(S->L, n, Instruction);\n  f->sizecode = n;\n  loadVector(S, f->code, n);\n}\n\n\nstatic void loadFunction(LoadState *S, Proto *f, TString *psource);\n\n\nstatic void loadConstants (LoadState *S, Proto *f) {\n  int i;\n  int n = loadInt(S);\n  f->k = luaM_newvectorchecked(S->L, n, TValue);\n  f->sizek = n;\n  for (i = 0; i < n; i++)\n    setnilvalue(&f->k[i]);\n  for (i = 0; i < n; i++) {\n    TValue *o = &f->k[i];\n    int t = loadByte(S);\n    switch (t) {\n      case LUA_VNIL:\n        setnilvalue(o);\n        break;\n      case LUA_VFALSE:\n        setbfvalue(o);\n        break;\n      case LUA_VTRUE:\n        setbtvalue(o);\n        break;\n      case LUA_VNUMFLT:\n        setfltvalue(o, loadNumber(S));\n        break;\n      case LUA_VNUMINT:\n        setivalue(o, loadInteger(S));\n        break;\n      case LUA_VSHRSTR:\n      case LUA_VLNGSTR:\n        setsvalue2n(S->L, o, loadString(S, f));\n        break;\n      default: lua_assert(0);\n    }\n  }\n}\n\n\nstatic void loadProtos (LoadState *S, Proto *f) {\n  int i;\n  int n = loadInt(S);\n  f->p = luaM_newvectorchecked(S->L, n, Proto *);\n  f->sizep = n;\n  for (i = 0; i < n; i++)\n    f->p[i] = NULL;\n  for (i = 0; i < n; i++) {\n    f->p[i] = luaF_newproto(S->L);\n    luaC_objbarrier(S->L, f, f->p[i]);\n    loadFunction(S, f->p[i], f->source);\n  }\n}\n\n\n/*\n** Load the upvalues for a function. The names must be filled first,\n** because the filling of the other fields can raise read errors and\n** the creation of the error message can call an emergency collection;\n** in that case all prototypes must be consistent for the GC.\n*/\nstatic void loadUpvalues (LoadState *S, Proto *f) {\n  int i, n;\n  n = loadInt(S);\n  f->upvalues = luaM_newvectorchecked(S->L, n, Upvaldesc);\n  f->sizeupvalues = n;\n  for (i = 0; i < n; i++)  /* make array valid for GC */\n    f->upvalues[i].name = NULL;\n  for (i = 0; i < n; i++) {  /* following calls can raise errors */\n    f->upvalues[i].instack = loadByte(S);\n    f->upvalues[i].idx = loadByte(S);\n    f->upvalues[i].kind = loadByte(S);\n  }\n}\n\n\nstatic void loadDebug (LoadState *S, Proto *f) {\n  int i, n;\n  n = loadInt(S);\n  f->lineinfo = luaM_newvectorchecked(S->L, n, ls_byte);\n  f->sizelineinfo = n;\n  loadVector(S, f->lineinfo, n);\n  n = loadInt(S);\n  f->abslineinfo = luaM_newvectorchecked(S->L, n, AbsLineInfo);\n  f->sizeabslineinfo = n;\n  for (i = 0; i < n; i++) {\n    f->abslineinfo[i].pc = loadInt(S);\n    f->abslineinfo[i].line = loadInt(S);\n  }\n  n = loadInt(S);\n  f->locvars = luaM_newvectorchecked(S->L, n, LocVar);\n  f->sizelocvars = n;\n  for (i = 0; i < n; i++)\n    f->locvars[i].varname = NULL;\n  for (i = 0; i < n; i++) {\n    f->locvars[i].varname = loadStringN(S, f);\n    f->locvars[i].startpc = loadInt(S);\n    f->locvars[i].endpc = loadInt(S);\n  }\n  n = loadInt(S);\n  if (n != 0)  /* does it have debug information? */\n    n = f->sizeupvalues;  /* must be this many */\n  for (i = 0; i < n; i++)\n    f->upvalues[i].name = loadStringN(S, f);\n}\n\n\nstatic void loadFunction (LoadState *S, Proto *f, TString *psource) {\n  f->source = loadStringN(S, f);\n  if (f->source == NULL)  /* no source in dump? */\n    f->source = psource;  /* reuse parent's source */\n  f->linedefined = loadInt(S);\n  f->lastlinedefined = loadInt(S);\n  f->numparams = loadByte(S);\n  f->is_vararg = loadByte(S);\n  f->maxstacksize = loadByte(S);\n  loadCode(S, f);\n  loadConstants(S, f);\n  loadUpvalues(S, f);\n  loadProtos(S, f);\n  loadDebug(S, f);\n}\n\n\nstatic void checkliteral (LoadState *S, const char *s, const char *msg) {\n  char buff[sizeof(LUA_SIGNATURE) + sizeof(LUAC_DATA)]; /* larger than both */\n  size_t len = strlen(s);\n  loadVector(S, buff, len);\n  if (memcmp(s, buff, len) != 0)\n    error(S, msg);\n}\n\n\nstatic void fchecksize (LoadState *S, size_t size, const char *tname) {\n  if (loadByte(S) != size)\n    error(S, luaO_pushfstring(S->L, \"%s size mismatch\", tname));\n}\n\n\n#define checksize(S,t)\tfchecksize(S,sizeof(t),#t)\n\nstatic void checkHeader (LoadState *S) {\n  /* skip 1st char (already read and checked) */\n  checkliteral(S, &LUA_SIGNATURE[1], \"not a binary chunk\");\n  if (loadByte(S) != LUAC_VERSION)\n    error(S, \"version mismatch\");\n  if (loadByte(S) != LUAC_FORMAT)\n    error(S, \"format mismatch\");\n  checkliteral(S, LUAC_DATA, \"corrupted chunk\");\n  checksize(S, Instruction);\n  checksize(S, lua_Integer);\n  checksize(S, lua_Number);\n  if (loadInteger(S) != LUAC_INT)\n    error(S, \"integer format mismatch\");\n  if (loadNumber(S) != LUAC_NUM)\n    error(S, \"float format mismatch\");\n}\n\n\n/*\n** Load precompiled chunk.\n*/\nLClosure *luaU_undump(lua_State *L, ZIO *Z, const char *name) {\n  LoadState S;\n  LClosure *cl;\n  if (*name == '@' || *name == '=')\n    S.name = name + 1;\n  else if (*name == LUA_SIGNATURE[0])\n    S.name = \"binary string\";\n  else\n    S.name = name;\n  S.L = L;\n  S.Z = Z;\n  checkHeader(&S);\n  cl = luaF_newLclosure(L, loadByte(&S));\n  setclLvalue2s(L, L->top.p, cl);\n  luaD_inctop(L);\n  cl->p = luaF_newproto(L);\n  luaC_objbarrier(L, cl, cl->p);\n  loadFunction(&S, cl->p, NULL);\n  lua_assert(cl->nupvalues == cl->p->sizeupvalues);\n  luai_verifycode(L, cl->p);\n  return cl;\n}\n\n/*\n** $Id: ldump.c $\n** save precompiled Lua chunks\n** See Copyright Notice in lua.h\n*/\n\n#define ldump_c\n#define LUA_CORE\n\n/*#include \"lprefix.h\"*/\n\n\n#include <limits.h>\n#include <stddef.h>\n\n/*#include \"lua.h\"*/\n\n/*#include \"lobject.h\"*/\n/*#include \"lstate.h\"*/\n/*#include \"lundump.h\"*/\n\n\ntypedef struct {\n  lua_State *L;\n  lua_Writer writer;\n  void *data;\n  int strip;\n  int status;\n} DumpState;\n\n\n/*\n** All high-level dumps go through dumpVector; you can change it to\n** change the endianness of the result\n*/\n#define dumpVector(D,v,n)\tdumpBlock(D,v,(n)*sizeof((v)[0]))\n\n#define dumpLiteral(D, s)\tdumpBlock(D,s,sizeof(s) - sizeof(char))\n\n\nstatic void dumpBlock (DumpState *D, const void *b, size_t size) {\n  if (D->status == 0 && size > 0) {\n    lua_unlock(D->L);\n    D->status = (*D->writer)(D->L, b, size, D->data);\n    lua_lock(D->L);\n  }\n}\n\n\n#define dumpVar(D,x)\t\tdumpVector(D,&x,1)\n\n\nstatic void dumpByte (DumpState *D, int y) {\n  lu_byte x = (lu_byte)y;\n  dumpVar(D, x);\n}\n\n\n/*\n** 'dumpSize' buffer size: each byte can store up to 7 bits. (The \"+6\"\n** rounds up the division.)\n*/\n#define DIBS    ((sizeof(size_t) * CHAR_BIT + 6) / 7)\n\nstatic void dumpSize (DumpState *D, size_t x) {\n  lu_byte buff[DIBS];\n  int n = 0;\n  do {\n    buff[DIBS - (++n)] = x & 0x7f;  /* fill buffer in reverse order */\n    x >>= 7;\n  } while (x != 0);\n  buff[DIBS - 1] |= 0x80;  /* mark last byte */\n  dumpVector(D, buff + DIBS - n, n);\n}\n\n\nstatic void dumpInt (DumpState *D, int x) {\n  dumpSize(D, x);\n}\n\n\nstatic void dumpNumber (DumpState *D, lua_Number x) {\n  dumpVar(D, x);\n}\n\n\nstatic void dumpInteger (DumpState *D, lua_Integer x) {\n  dumpVar(D, x);\n}\n\n\nstatic void dumpString (DumpState *D, const TString *s) {\n  if (s == NULL)\n    dumpSize(D, 0);\n  else {\n    size_t size = tsslen(s);\n    const char *str = getstr(s);\n    dumpSize(D, size + 1);\n    dumpVector(D, str, size);\n  }\n}\n\n\nstatic void dumpCode (DumpState *D, const Proto *f) {\n  dumpInt(D, f->sizecode);\n  dumpVector(D, f->code, f->sizecode);\n}\n\n\nstatic void dumpFunction(DumpState *D, const Proto *f, TString *psource);\n\nstatic void dumpConstants (DumpState *D, const Proto *f) {\n  int i;\n  int n = f->sizek;\n  dumpInt(D, n);\n  for (i = 0; i < n; i++) {\n    const TValue *o = &f->k[i];\n    int tt = ttypetag(o);\n    dumpByte(D, tt);\n    switch (tt) {\n      case LUA_VNUMFLT:\n        dumpNumber(D, fltvalue(o));\n        break;\n      case LUA_VNUMINT:\n        dumpInteger(D, ivalue(o));\n        break;\n      case LUA_VSHRSTR:\n      case LUA_VLNGSTR:\n        dumpString(D, tsvalue(o));\n        break;\n      default:\n        lua_assert(tt == LUA_VNIL || tt == LUA_VFALSE || tt == LUA_VTRUE);\n    }\n  }\n}\n\n\nstatic void dumpProtos (DumpState *D, const Proto *f) {\n  int i;\n  int n = f->sizep;\n  dumpInt(D, n);\n  for (i = 0; i < n; i++)\n    dumpFunction(D, f->p[i], f->source);\n}\n\n\nstatic void dumpUpvalues (DumpState *D, const Proto *f) {\n  int i, n = f->sizeupvalues;\n  dumpInt(D, n);\n  for (i = 0; i < n; i++) {\n    dumpByte(D, f->upvalues[i].instack);\n    dumpByte(D, f->upvalues[i].idx);\n    dumpByte(D, f->upvalues[i].kind);\n  }\n}\n\n\nstatic void dumpDebug (DumpState *D, const Proto *f) {\n  int i, n;\n  n = (D->strip) ? 0 : f->sizelineinfo;\n  dumpInt(D, n);\n  dumpVector(D, f->lineinfo, n);\n  n = (D->strip) ? 0 : f->sizeabslineinfo;\n  dumpInt(D, n);\n  for (i = 0; i < n; i++) {\n    dumpInt(D, f->abslineinfo[i].pc);\n    dumpInt(D, f->abslineinfo[i].line);\n  }\n  n = (D->strip) ? 0 : f->sizelocvars;\n  dumpInt(D, n);\n  for (i = 0; i < n; i++) {\n    dumpString(D, f->locvars[i].varname);\n    dumpInt(D, f->locvars[i].startpc);\n    dumpInt(D, f->locvars[i].endpc);\n  }\n  n = (D->strip) ? 0 : f->sizeupvalues;\n  dumpInt(D, n);\n  for (i = 0; i < n; i++)\n    dumpString(D, f->upvalues[i].name);\n}\n\n\nstatic void dumpFunction (DumpState *D, const Proto *f, TString *psource) {\n  if (D->strip || f->source == psource)\n    dumpString(D, NULL);  /* no debug info or same source as its parent */\n  else\n    dumpString(D, f->source);\n  dumpInt(D, f->linedefined);\n  dumpInt(D, f->lastlinedefined);\n  dumpByte(D, f->numparams);\n  dumpByte(D, f->is_vararg);\n  dumpByte(D, f->maxstacksize);\n  dumpCode(D, f);\n  dumpConstants(D, f);\n  dumpUpvalues(D, f);\n  dumpProtos(D, f);\n  dumpDebug(D, f);\n}\n\n\nstatic void dumpHeader (DumpState *D) {\n  dumpLiteral(D, LUA_SIGNATURE);\n  dumpByte(D, LUAC_VERSION);\n  dumpByte(D, LUAC_FORMAT);\n  dumpLiteral(D, LUAC_DATA);\n  dumpByte(D, sizeof(Instruction));\n  dumpByte(D, sizeof(lua_Integer));\n  dumpByte(D, sizeof(lua_Number));\n  dumpInteger(D, LUAC_INT);\n  dumpNumber(D, LUAC_NUM);\n}\n\n\n/*\n** dump Lua function as precompiled chunk\n*/\nint luaU_dump(lua_State *L, const Proto *f, lua_Writer w, void *data,\n              int strip) {\n  DumpState D;\n  D.L = L;\n  D.writer = w;\n  D.data = data;\n  D.strip = strip;\n  D.status = 0;\n  dumpHeader(&D);\n  dumpByte(&D, f->sizeupvalues);\n  dumpFunction(&D, f, NULL);\n  return D.status;\n}\n\n/*\n** $Id: lstate.c $\n** Global State\n** See Copyright Notice in lua.h\n*/\n\n#define lstate_c\n#define LUA_CORE\n\n/*#include \"lprefix.h\"*/\n\n\n#include <stddef.h>\n#include <string.h>\n\n/*#include \"lua.h\"*/\n\n/*#include \"lapi.h\"*/\n/*#include \"ldebug.h\"*/\n/*#include \"ldo.h\"*/\n/*#include \"lfunc.h\"*/\n/*#include \"lgc.h\"*/\n/*#include \"llex.h\"*/\n/*#include \"lmem.h\"*/\n/*#include \"lstate.h\"*/\n/*#include \"lstring.h\"*/\n/*#include \"ltable.h\"*/\n/*#include \"ltm.h\"*/\n\n\n\n/*\n** thread state + extra space\n*/\ntypedef struct LX {\n  lu_byte extra_[LUA_EXTRASPACE];\n  lua_State l;\n} LX;\n\n\n/*\n** Main thread combines a thread state and the global state\n*/\ntypedef struct LG {\n  LX l;\n  global_State g;\n} LG;\n\n\n\n#define fromstate(L)\t(cast(LX *, cast(lu_byte *, (L)) - offsetof(LX, l)))\n\n\n/*\n** A macro to create a \"random\" seed when a state is created;\n** the seed is used to randomize string hashes.\n*/\n#if !defined(luai_makeseed)\n\n#include <time.h>\n\n/*\n** Compute an initial seed with some level of randomness.\n** Rely on Address Space Layout Randomization (if present) and\n** current time.\n*/\n#define addbuff(b,p,e) \\\n  { size_t t = cast_sizet(e); \\\n    memcpy(b + p, &t, sizeof(t)); p += sizeof(t); }\n\nstatic unsigned int luai_makeseed (lua_State *L) {\n  char buff[3 * sizeof(size_t)];\n  unsigned int h = cast_uint(time(NULL));\n  int p = 0;\n  addbuff(buff, p, L);  /* heap variable */\n  addbuff(buff, p, &h);  /* local variable */\n  addbuff(buff, p, &lua_newstate);  /* public function */\n  lua_assert(p == sizeof(buff));\n  return luaS_hash(buff, p, h);\n}\n\n#endif\n\n\n/*\n** set GCdebt to a new value keeping the value (totalbytes + GCdebt)\n** invariant (and avoiding underflows in 'totalbytes')\n*/\nvoid luaE_setdebt (global_State *g, l_mem debt) {\n  l_mem tb = gettotalbytes(g);\n  lua_assert(tb > 0);\n  if (debt < tb - MAX_LMEM)\n    debt = tb - MAX_LMEM;  /* will make 'totalbytes == MAX_LMEM' */\n  g->totalbytes = tb - debt;\n  g->GCdebt = debt;\n}\n\n\nLUA_API int lua_setcstacklimit (lua_State *L, unsigned int limit) {\n  UNUSED(L); UNUSED(limit);\n  return LUAI_MAXCCALLS;  /* warning?? */\n}\n\n\nCallInfo *luaE_extendCI (lua_State *L) {\n  CallInfo *ci;\n  lua_assert(L->ci->next == NULL);\n  ci = luaM_new(L, CallInfo);\n  lua_assert(L->ci->next == NULL);\n  L->ci->next = ci;\n  ci->previous = L->ci;\n  ci->next = NULL;\n  ci->u.l.trap = 0;\n  L->nci++;\n  return ci;\n}\n\n\n/*\n** free all CallInfo structures not in use by a thread\n*/\nvoid luaE_freeCI (lua_State *L) {\n  CallInfo *ci = L->ci;\n  CallInfo *next = ci->next;\n  ci->next = NULL;\n  while ((ci = next) != NULL) {\n    next = ci->next;\n    luaM_free(L, ci);\n    L->nci--;\n  }\n}\n\n\n/*\n** free half of the CallInfo structures not in use by a thread,\n** keeping the first one.\n*/\nvoid luaE_shrinkCI (lua_State *L) {\n  CallInfo *ci = L->ci->next;  /* first free CallInfo */\n  CallInfo *next;\n  if (ci == NULL)\n    return;  /* no extra elements */\n  while ((next = ci->next) != NULL) {  /* two extra elements? */\n    CallInfo *next2 = next->next;  /* next's next */\n    ci->next = next2;  /* remove next from the list */\n    L->nci--;\n    luaM_free(L, next);  /* free next */\n    if (next2 == NULL)\n      break;  /* no more elements */\n    else {\n      next2->previous = ci;\n      ci = next2;  /* continue */\n    }\n  }\n}\n\n\n/*\n** Called when 'getCcalls(L)' larger or equal to LUAI_MAXCCALLS.\n** If equal, raises an overflow error. If value is larger than\n** LUAI_MAXCCALLS (which means it is handling an overflow) but\n** not much larger, does not report an error (to allow overflow\n** handling to work).\n*/\nvoid luaE_checkcstack (lua_State *L) {\n  if (getCcalls(L) == LUAI_MAXCCALLS)\n    luaG_runerror(L, \"C stack overflow\");\n  else if (getCcalls(L) >= (LUAI_MAXCCALLS / 10 * 11))\n    luaD_throw(L, LUA_ERRERR);  /* error while handling stack error */\n}\n\n\nLUAI_FUNC void luaE_incCstack (lua_State *L) {\n  L->nCcalls++;\n  if (l_unlikely(getCcalls(L) >= LUAI_MAXCCALLS))\n    luaE_checkcstack(L);\n}\n\n\nstatic void stack_init (lua_State *L1, lua_State *L) {\n  int i; CallInfo *ci;\n  /* initialize stack array */\n  L1->stack.p = luaM_newvector(L, BASIC_STACK_SIZE + EXTRA_STACK, StackValue);\n  L1->tbclist.p = L1->stack.p;\n  for (i = 0; i < BASIC_STACK_SIZE + EXTRA_STACK; i++)\n    setnilvalue(s2v(L1->stack.p + i));  /* erase new stack */\n  L1->top.p = L1->stack.p;\n  L1->stack_last.p = L1->stack.p + BASIC_STACK_SIZE;\n  /* initialize first ci */\n  ci = &L1->base_ci;\n  ci->next = ci->previous = NULL;\n  ci->callstatus = CIST_C;\n  ci->func.p = L1->top.p;\n  ci->u.c.k = NULL;\n  ci->nresults = 0;\n  setnilvalue(s2v(L1->top.p));  /* 'function' entry for this 'ci' */\n  L1->top.p++;\n  ci->top.p = L1->top.p + LUA_MINSTACK;\n  L1->ci = ci;\n}\n\n\nstatic void freestack (lua_State *L) {\n  if (L->stack.p == NULL)\n    return;  /* stack not completely built yet */\n  L->ci = &L->base_ci;  /* free the entire 'ci' list */\n  luaE_freeCI(L);\n  lua_assert(L->nci == 0);\n  luaM_freearray(L, L->stack.p, stacksize(L) + EXTRA_STACK);  /* free stack */\n}\n\n\n/*\n** Create registry table and its predefined values\n*/\nstatic void init_registry (lua_State *L, global_State *g) {\n  /* create registry */\n  Table *registry = luaH_new(L);\n  sethvalue(L, &g->l_registry, registry);\n  luaH_resize(L, registry, LUA_RIDX_LAST, 0);\n  /* registry[LUA_RIDX_MAINTHREAD] = L */\n  setthvalue(L, &registry->array[LUA_RIDX_MAINTHREAD - 1], L);\n  /* registry[LUA_RIDX_GLOBALS] = new table (table of globals) */\n  sethvalue(L, &registry->array[LUA_RIDX_GLOBALS - 1], luaH_new(L));\n}\n\n\n/*\n** open parts of the state that may cause memory-allocation errors.\n*/\nstatic void f_luaopen (lua_State *L, void *ud) {\n  global_State *g = G(L);\n  UNUSED(ud);\n  stack_init(L, L);  /* init stack */\n  init_registry(L, g);\n  luaS_init(L);\n  luaT_init(L);\n  luaX_init(L);\n  g->gcstp = 0;  /* allow gc */\n  setnilvalue(&g->nilvalue);  /* now state is complete */\n  luai_userstateopen(L);\n}\n\n\n/*\n** preinitialize a thread with consistent values without allocating\n** any memory (to avoid errors)\n*/\nstatic void preinit_thread (lua_State *L, global_State *g) {\n  G(L) = g;\n  L->stack.p = NULL;\n  L->ci = NULL;\n  L->nci = 0;\n  L->twups = L;  /* thread has no upvalues */\n  L->nCcalls = 0;\n  L->errorJmp = NULL;\n  L->hook = NULL;\n  L->hookmask = 0;\n  L->basehookcount = 0;\n  L->allowhook = 1;\n  resethookcount(L);\n  L->openupval = NULL;\n  L->status = LUA_OK;\n  L->errfunc = 0;\n  L->oldpc = 0;\n}\n\n\nstatic void close_state (lua_State *L) {\n  global_State *g = G(L);\n  if (!completestate(g))  /* closing a partially built state? */\n    luaC_freeallobjects(L);  /* just collect its objects */\n  else {  /* closing a fully built state */\n    L->ci = &L->base_ci;  /* unwind CallInfo list */\n    luaD_closeprotected(L, 1, LUA_OK);  /* close all upvalues */\n    luaC_freeallobjects(L);  /* collect all objects */\n    luai_userstateclose(L);\n  }\n  luaM_freearray(L, G(L)->strt.hash, G(L)->strt.size);\n  freestack(L);\n  lua_assert(gettotalbytes(g) == sizeof(LG));\n  (*g->frealloc)(g->ud, fromstate(L), sizeof(LG), 0);  /* free main block */\n}\n\n\nLUA_API lua_State *lua_newthread (lua_State *L) {\n  global_State *g = G(L);\n  GCObject *o;\n  lua_State *L1;\n  lua_lock(L);\n  luaC_checkGC(L);\n  /* create new thread */\n  o = luaC_newobjdt(L, LUA_TTHREAD, sizeof(LX), offsetof(LX, l));\n  L1 = gco2th(o);\n  /* anchor it on L stack */\n  setthvalue2s(L, L->top.p, L1);\n  api_incr_top(L);\n  preinit_thread(L1, g);\n  L1->hookmask = L->hookmask;\n  L1->basehookcount = L->basehookcount;\n  L1->hook = L->hook;\n  resethookcount(L1);\n  /* initialize L1 extra space */\n  memcpy(lua_getextraspace(L1), lua_getextraspace(g->mainthread),\n         LUA_EXTRASPACE);\n  luai_userstatethread(L, L1);\n  stack_init(L1, L);  /* init stack */\n  lua_unlock(L);\n  return L1;\n}\n\n\nvoid luaE_freethread (lua_State *L, lua_State *L1) {\n  LX *l = fromstate(L1);\n  luaF_closeupval(L1, L1->stack.p);  /* close all upvalues */\n  lua_assert(L1->openupval == NULL);\n  luai_userstatefree(L, L1);\n  freestack(L1);\n  luaM_free(L, l);\n}\n\n\nint luaE_resetthread (lua_State *L, int status) {\n  CallInfo *ci = L->ci = &L->base_ci;  /* unwind CallInfo list */\n  setnilvalue(s2v(L->stack.p));  /* 'function' entry for basic 'ci' */\n  ci->func.p = L->stack.p;\n  ci->callstatus = CIST_C;\n  if (status == LUA_YIELD)\n    status = LUA_OK;\n  L->status = LUA_OK;  /* so it can run __close metamethods */\n  status = luaD_closeprotected(L, 1, status);\n  if (status != LUA_OK)  /* errors? */\n    luaD_seterrorobj(L, status, L->stack.p + 1);\n  else\n    L->top.p = L->stack.p + 1;\n  ci->top.p = L->top.p + LUA_MINSTACK;\n  luaD_reallocstack(L, cast_int(ci->top.p - L->stack.p), 0);\n  return status;\n}\n\n\nLUA_API int lua_closethread (lua_State *L, lua_State *from) {\n  int status;\n  lua_lock(L);\n  L->nCcalls = (from) ? getCcalls(from) : 0;\n  status = luaE_resetthread(L, L->status);\n  lua_unlock(L);\n  return status;\n}\n\n\n/*\n** Deprecated! Use 'lua_closethread' instead.\n*/\nLUA_API int lua_resetthread (lua_State *L) {\n  return lua_closethread(L, NULL);\n}\n\n\nLUA_API lua_State *lua_newstate (lua_Alloc f, void *ud) {\n  int i;\n  lua_State *L;\n  global_State *g;\n  LG *l = cast(LG *, (*f)(ud, NULL, LUA_TTHREAD, sizeof(LG)));\n  if (l == NULL) return NULL;\n  L = &l->l.l;\n  g = &l->g;\n  L->tt = LUA_VTHREAD;\n  g->currentwhite = bitmask(WHITE0BIT);\n  L->marked = luaC_white(g);\n  preinit_thread(L, g);\n  g->allgc = obj2gco(L);  /* by now, only object is the main thread */\n  L->next = NULL;\n  incnny(L);  /* main thread is always non yieldable */\n  g->frealloc = f;\n  g->ud = ud;\n  g->warnf = NULL;\n  g->ud_warn = NULL;\n  g->mainthread = L;\n  g->seed = luai_makeseed(L);\n  g->gcstp = GCSTPGC;  /* no GC while building state */\n  g->strt.size = g->strt.nuse = 0;\n  g->strt.hash = NULL;\n  setnilvalue(&g->l_registry);\n  g->panic = NULL;\n  g->gcstate = GCSpause;\n  g->gckind = KGC_INC;\n  g->gcstopem = 0;\n  g->gcemergency = 0;\n  g->finobj = g->tobefnz = g->fixedgc = NULL;\n  g->firstold1 = g->survival = g->old1 = g->reallyold = NULL;\n  g->finobjsur = g->finobjold1 = g->finobjrold = NULL;\n  g->sweepgc = NULL;\n  g->gray = g->grayagain = NULL;\n  g->weak = g->ephemeron = g->allweak = NULL;\n  g->twups = NULL;\n  g->totalbytes = sizeof(LG);\n  g->GCdebt = 0;\n  g->lastatomic = 0;\n  setivalue(&g->nilvalue, 0);  /* to signal that state is not yet built */\n  setgcparam(g->gcpause, LUAI_GCPAUSE);\n  setgcparam(g->gcstepmul, LUAI_GCMUL);\n  g->gcstepsize = LUAI_GCSTEPSIZE;\n  setgcparam(g->genmajormul, LUAI_GENMAJORMUL);\n  g->genminormul = LUAI_GENMINORMUL;\n  for (i=0; i < LUA_NUMTAGS; i++) g->mt[i] = NULL;\n  if (luaD_rawrunprotected(L, f_luaopen, NULL) != LUA_OK) {\n    /* memory allocation error: free partial state */\n    close_state(L);\n    L = NULL;\n  }\n  return L;\n}\n\n\nLUA_API void lua_close (lua_State *L) {\n  lua_lock(L);\n  L = G(L)->mainthread;  /* only the main thread can be closed */\n  close_state(L);\n}\n\n\nvoid luaE_warning (lua_State *L, const char *msg, int tocont) {\n  lua_WarnFunction wf = G(L)->warnf;\n  if (wf != NULL)\n    wf(G(L)->ud_warn, msg, tocont);\n}\n\n\n/*\n** Generate a warning from an error message\n*/\nvoid luaE_warnerror (lua_State *L, const char *where) {\n  TValue *errobj = s2v(L->top.p - 1);  /* error object */\n  const char *msg = (ttisstring(errobj))\n                  ? svalue(errobj)\n                  : \"error object is not a string\";\n  /* produce warning \"error in %s (%s)\" (where, msg) */\n  luaE_warning(L, \"error in \", 1);\n  luaE_warning(L, where, 1);\n  luaE_warning(L, \" (\", 1);\n  luaE_warning(L, msg, 1);\n  luaE_warning(L, \")\", 0);\n}\n\n/*\n** $Id: lgc.c $\n** Garbage Collector\n** See Copyright Notice in lua.h\n*/\n\n#define lgc_c\n#define LUA_CORE\n\n/*#include \"lprefix.h\"*/\n\n#include <stdio.h>\n#include <string.h>\n\n\n/*#include \"lua.h\"*/\n\n/*#include \"ldebug.h\"*/\n/*#include \"ldo.h\"*/\n/*#include \"lfunc.h\"*/\n/*#include \"lgc.h\"*/\n/*#include \"lmem.h\"*/\n/*#include \"lobject.h\"*/\n/*#include \"lstate.h\"*/\n/*#include \"lstring.h\"*/\n/*#include \"ltable.h\"*/\n/*#include \"ltm.h\"*/\n\n\n/*\n** Maximum number of elements to sweep in each single step.\n** (Large enough to dissipate fixed overheads but small enough\n** to allow small steps for the collector.)\n*/\n#define GCSWEEPMAX\t100\n\n/*\n** Maximum number of finalizers to call in each single step.\n*/\n#define GCFINMAX\t10\n\n\n/*\n** Cost of calling one finalizer.\n*/\n#define GCFINALIZECOST\t50\n\n\n/*\n** The equivalent, in bytes, of one unit of \"work\" (visiting a slot,\n** sweeping an object, etc.)\n*/\n#define WORK2MEM\tsizeof(TValue)\n\n\n/*\n** macro to adjust 'pause': 'pause' is actually used like\n** 'pause / PAUSEADJ' (value chosen by tests)\n*/\n#define PAUSEADJ\t\t100\n\n\n/* mask with all color bits */\n#define maskcolors\t(bitmask(BLACKBIT) | WHITEBITS)\n\n/* mask with all GC bits */\n#define maskgcbits      (maskcolors | AGEBITS)\n\n\n/* macro to erase all color bits then set only the current white bit */\n#define makewhite(g,x)\t\\\n  (x->marked = cast_byte((x->marked & ~maskcolors) | luaC_white(g)))\n\n/* make an object gray (neither white nor black) */\n#define set2gray(x)\tresetbits(x->marked, maskcolors)\n\n\n/* make an object black (coming from any color) */\n#define set2black(x)  \\\n  (x->marked = cast_byte((x->marked & ~WHITEBITS) | bitmask(BLACKBIT)))\n\n\n#define valiswhite(x)   (iscollectable(x) && iswhite(gcvalue(x)))\n\n#define keyiswhite(n)   (keyiscollectable(n) && iswhite(gckey(n)))\n\n\n/*\n** Protected access to objects in values\n*/\n#define gcvalueN(o)     (iscollectable(o) ? gcvalue(o) : NULL)\n\n\n#define markvalue(g,o) { checkliveness(g->mainthread,o); \\\n  if (valiswhite(o)) reallymarkobject(g,gcvalue(o)); }\n\n#define markkey(g, n)\t{ if keyiswhite(n) reallymarkobject(g,gckey(n)); }\n\n#define markobject(g,t)\t{ if (iswhite(t)) reallymarkobject(g, obj2gco(t)); }\n\n/*\n** mark an object that can be NULL (either because it is really optional,\n** or it was stripped as debug info, or inside an uncompleted structure)\n*/\n#define markobjectN(g,t)\t{ if (t) markobject(g,t); }\n\nstatic void reallymarkobject (global_State *g, GCObject *o);\nstatic lu_mem atomic (lua_State *L);\nstatic void entersweep (lua_State *L);\n\n\n/*\n** {======================================================\n** Generic functions\n** =======================================================\n*/\n\n\n/*\n** one after last element in a hash array\n*/\n#define gnodelast(h)\tgnode(h, cast_sizet(sizenode(h)))\n\n\nstatic GCObject **getgclist (GCObject *o) {\n  switch (o->tt) {\n    case LUA_VTABLE: return &gco2t(o)->gclist;\n    case LUA_VLCL: return &gco2lcl(o)->gclist;\n    case LUA_VCCL: return &gco2ccl(o)->gclist;\n    case LUA_VTHREAD: return &gco2th(o)->gclist;\n    case LUA_VPROTO: return &gco2p(o)->gclist;\n    case LUA_VUSERDATA: {\n      Udata *u = gco2u(o);\n      lua_assert(u->nuvalue > 0);\n      return &u->gclist;\n    }\n    default: lua_assert(0); return 0;\n  }\n}\n\n\n/*\n** Link a collectable object 'o' with a known type into the list 'p'.\n** (Must be a macro to access the 'gclist' field in different types.)\n*/\n#define linkgclist(o,p)\tlinkgclist_(obj2gco(o), &(o)->gclist, &(p))\n\nstatic void linkgclist_ (GCObject *o, GCObject **pnext, GCObject **list) {\n  lua_assert(!isgray(o));  /* cannot be in a gray list */\n  *pnext = *list;\n  *list = o;\n  set2gray(o);  /* now it is */\n}\n\n\n/*\n** Link a generic collectable object 'o' into the list 'p'.\n*/\n#define linkobjgclist(o,p) linkgclist_(obj2gco(o), getgclist(o), &(p))\n\n\n\n/*\n** Clear keys for empty entries in tables. If entry is empty, mark its\n** entry as dead. This allows the collection of the key, but keeps its\n** entry in the table: its removal could break a chain and could break\n** a table traversal.  Other places never manipulate dead keys, because\n** its associated empty value is enough to signal that the entry is\n** logically empty.\n*/\nstatic void clearkey (Node *n) {\n  lua_assert(isempty(gval(n)));\n  if (keyiscollectable(n))\n    setdeadkey(n);  /* unused key; remove it */\n}\n\n\n/*\n** tells whether a key or value can be cleared from a weak\n** table. Non-collectable objects are never removed from weak\n** tables. Strings behave as 'values', so are never removed too. for\n** other objects: if really collected, cannot keep them; for objects\n** being finalized, keep them in keys, but not in values\n*/\nstatic int iscleared (global_State *g, const GCObject *o) {\n  if (o == NULL) return 0;  /* non-collectable value */\n  else if (novariant(o->tt) == LUA_TSTRING) {\n    markobject(g, o);  /* strings are 'values', so are never weak */\n    return 0;\n  }\n  else return iswhite(o);\n}\n\n\n/*\n** Barrier that moves collector forward, that is, marks the white object\n** 'v' being pointed by the black object 'o'.  In the generational\n** mode, 'v' must also become old, if 'o' is old; however, it cannot\n** be changed directly to OLD, because it may still point to non-old\n** objects. So, it is marked as OLD0. In the next cycle it will become\n** OLD1, and in the next it will finally become OLD (regular old). By\n** then, any object it points to will also be old.  If called in the\n** incremental sweep phase, it clears the black object to white (sweep\n** it) to avoid other barrier calls for this same object. (That cannot\n** be done is generational mode, as its sweep does not distinguish\n** whites from deads.)\n*/\nvoid luaC_barrier_ (lua_State *L, GCObject *o, GCObject *v) {\n  global_State *g = G(L);\n  lua_assert(isblack(o) && iswhite(v) && !isdead(g, v) && !isdead(g, o));\n  if (keepinvariant(g)) {  /* must keep invariant? */\n    reallymarkobject(g, v);  /* restore invariant */\n    if (isold(o)) {\n      lua_assert(!isold(v));  /* white object could not be old */\n      setage(v, G_OLD0);  /* restore generational invariant */\n    }\n  }\n  else {  /* sweep phase */\n    lua_assert(issweepphase(g));\n    if (g->gckind == KGC_INC)  /* incremental mode? */\n      makewhite(g, o);  /* mark 'o' as white to avoid other barriers */\n  }\n}\n\n\n/*\n** barrier that moves collector backward, that is, mark the black object\n** pointing to a white object as gray again.\n*/\nvoid luaC_barrierback_ (lua_State *L, GCObject *o) {\n  global_State *g = G(L);\n  lua_assert(isblack(o) && !isdead(g, o));\n  lua_assert((g->gckind == KGC_GEN) == (isold(o) && getage(o) != G_TOUCHED1));\n  if (getage(o) == G_TOUCHED2)  /* already in gray list? */\n    set2gray(o);  /* make it gray to become touched1 */\n  else  /* link it in 'grayagain' and paint it gray */\n    linkobjgclist(o, g->grayagain);\n  if (isold(o))  /* generational mode? */\n    setage(o, G_TOUCHED1);  /* touched in current cycle */\n}\n\n\nvoid luaC_fix (lua_State *L, GCObject *o) {\n  global_State *g = G(L);\n  lua_assert(g->allgc == o);  /* object must be 1st in 'allgc' list! */\n  set2gray(o);  /* they will be gray forever */\n  setage(o, G_OLD);  /* and old forever */\n  g->allgc = o->next;  /* remove object from 'allgc' list */\n  o->next = g->fixedgc;  /* link it to 'fixedgc' list */\n  g->fixedgc = o;\n}\n\n\n/*\n** create a new collectable object (with given type, size, and offset)\n** and link it to 'allgc' list.\n*/\nGCObject *luaC_newobjdt (lua_State *L, int tt, size_t sz, size_t offset) {\n  global_State *g = G(L);\n  char *p = cast_charp(luaM_newobject(L, novariant(tt), sz));\n  GCObject *o = cast(GCObject *, p + offset);\n  o->marked = luaC_white(g);\n  o->tt = tt;\n  o->next = g->allgc;\n  g->allgc = o;\n  return o;\n}\n\n\nGCObject *luaC_newobj (lua_State *L, int tt, size_t sz) {\n  return luaC_newobjdt(L, tt, sz, 0);\n}\n\n/* }====================================================== */\n\n\n\n/*\n** {======================================================\n** Mark functions\n** =======================================================\n*/\n\n\n/*\n** Mark an object.  Userdata with no user values, strings, and closed\n** upvalues are visited and turned black here.  Open upvalues are\n** already indirectly linked through their respective threads in the\n** 'twups' list, so they don't go to the gray list; nevertheless, they\n** are kept gray to avoid barriers, as their values will be revisited\n** by the thread or by 'remarkupvals'.  Other objects are added to the\n** gray list to be visited (and turned black) later.  Both userdata and\n** upvalues can call this function recursively, but this recursion goes\n** for at most two levels: An upvalue cannot refer to another upvalue\n** (only closures can), and a userdata's metatable must be a table.\n*/\nstatic void reallymarkobject (global_State *g, GCObject *o) {\n  switch (o->tt) {\n    case LUA_VSHRSTR:\n    case LUA_VLNGSTR: {\n      set2black(o);  /* nothing to visit */\n      break;\n    }\n    case LUA_VUPVAL: {\n      UpVal *uv = gco2upv(o);\n      if (upisopen(uv))\n        set2gray(uv);  /* open upvalues are kept gray */\n      else\n        set2black(uv);  /* closed upvalues are visited here */\n      markvalue(g, uv->v.p);  /* mark its content */\n      break;\n    }\n    case LUA_VUSERDATA: {\n      Udata *u = gco2u(o);\n      if (u->nuvalue == 0) {  /* no user values? */\n        markobjectN(g, u->metatable);  /* mark its metatable */\n        set2black(u);  /* nothing else to mark */\n        break;\n      }\n      /* else... */\n    }  /* FALLTHROUGH */\n    case LUA_VLCL: case LUA_VCCL: case LUA_VTABLE:\n    case LUA_VTHREAD: case LUA_VPROTO: {\n      linkobjgclist(o, g->gray);  /* to be visited later */\n      break;\n    }\n    default: lua_assert(0); break;\n  }\n}\n\n\n/*\n** mark metamethods for basic types\n*/\nstatic void markmt (global_State *g) {\n  int i;\n  for (i=0; i < LUA_NUMTAGS; i++)\n    markobjectN(g, g->mt[i]);\n}\n\n\n/*\n** mark all objects in list of being-finalized\n*/\nstatic lu_mem markbeingfnz (global_State *g) {\n  GCObject *o;\n  lu_mem count = 0;\n  for (o = g->tobefnz; o != NULL; o = o->next) {\n    count++;\n    markobject(g, o);\n  }\n  return count;\n}\n\n\n/*\n** For each non-marked thread, simulates a barrier between each open\n** upvalue and its value. (If the thread is collected, the value will be\n** assigned to the upvalue, but then it can be too late for the barrier\n** to act. The \"barrier\" does not need to check colors: A non-marked\n** thread must be young; upvalues cannot be older than their threads; so\n** any visited upvalue must be young too.) Also removes the thread from\n** the list, as it was already visited. Removes also threads with no\n** upvalues, as they have nothing to be checked. (If the thread gets an\n** upvalue later, it will be linked in the list again.)\n*/\nstatic int remarkupvals (global_State *g) {\n  lua_State *thread;\n  lua_State **p = &g->twups;\n  int work = 0;  /* estimate of how much work was done here */\n  while ((thread = *p) != NULL) {\n    work++;\n    if (!iswhite(thread) && thread->openupval != NULL)\n      p = &thread->twups;  /* keep marked thread with upvalues in the list */\n    else {  /* thread is not marked or without upvalues */\n      UpVal *uv;\n      lua_assert(!isold(thread) || thread->openupval == NULL);\n      *p = thread->twups;  /* remove thread from the list */\n      thread->twups = thread;  /* mark that it is out of list */\n      for (uv = thread->openupval; uv != NULL; uv = uv->u.open.next) {\n        lua_assert(getage(uv) <= getage(thread));\n        work++;\n        if (!iswhite(uv)) {  /* upvalue already visited? */\n          lua_assert(upisopen(uv) && isgray(uv));\n          markvalue(g, uv->v.p);  /* mark its value */\n        }\n      }\n    }\n  }\n  return work;\n}\n\n\nstatic void cleargraylists (global_State *g) {\n  g->gray = g->grayagain = NULL;\n  g->weak = g->allweak = g->ephemeron = NULL;\n}\n\n\n/*\n** mark root set and reset all gray lists, to start a new collection\n*/\nstatic void restartcollection (global_State *g) {\n  cleargraylists(g);\n  markobject(g, g->mainthread);\n  markvalue(g, &g->l_registry);\n  markmt(g);\n  markbeingfnz(g);  /* mark any finalizing object left from previous cycle */\n}\n\n/* }====================================================== */\n\n\n/*\n** {======================================================\n** Traverse functions\n** =======================================================\n*/\n\n\n/*\n** Check whether object 'o' should be kept in the 'grayagain' list for\n** post-processing by 'correctgraylist'. (It could put all old objects\n** in the list and leave all the work to 'correctgraylist', but it is\n** more efficient to avoid adding elements that will be removed.) Only\n** TOUCHED1 objects need to be in the list. TOUCHED2 doesn't need to go\n** back to a gray list, but then it must become OLD. (That is what\n** 'correctgraylist' does when it finds a TOUCHED2 object.)\n*/\nstatic void genlink (global_State *g, GCObject *o) {\n  lua_assert(isblack(o));\n  if (getage(o) == G_TOUCHED1) {  /* touched in this cycle? */\n    linkobjgclist(o, g->grayagain);  /* link it back in 'grayagain' */\n  }  /* everything else do not need to be linked back */\n  else if (getage(o) == G_TOUCHED2)\n    changeage(o, G_TOUCHED2, G_OLD);  /* advance age */\n}\n\n\n/*\n** Traverse a table with weak values and link it to proper list. During\n** propagate phase, keep it in 'grayagain' list, to be revisited in the\n** atomic phase. In the atomic phase, if table has any white value,\n** put it in 'weak' list, to be cleared.\n*/\nstatic void traverseweakvalue (global_State *g, Table *h) {\n  Node *n, *limit = gnodelast(h);\n  /* if there is array part, assume it may have white values (it is not\n     worth traversing it now just to check) */\n  int hasclears = (h->alimit > 0);\n  for (n = gnode(h, 0); n < limit; n++) {  /* traverse hash part */\n    if (isempty(gval(n)))  /* entry is empty? */\n      clearkey(n);  /* clear its key */\n    else {\n      lua_assert(!keyisnil(n));\n      markkey(g, n);\n      if (!hasclears && iscleared(g, gcvalueN(gval(n))))  /* a white value? */\n        hasclears = 1;  /* table will have to be cleared */\n    }\n  }\n  if (g->gcstate == GCSatomic && hasclears)\n    linkgclist(h, g->weak);  /* has to be cleared later */\n  else\n    linkgclist(h, g->grayagain);  /* must retraverse it in atomic phase */\n}\n\n\n/*\n** Traverse an ephemeron table and link it to proper list. Returns true\n** iff any object was marked during this traversal (which implies that\n** convergence has to continue). During propagation phase, keep table\n** in 'grayagain' list, to be visited again in the atomic phase. In\n** the atomic phase, if table has any white->white entry, it has to\n** be revisited during ephemeron convergence (as that key may turn\n** black). Otherwise, if it has any white key, table has to be cleared\n** (in the atomic phase). In generational mode, some tables\n** must be kept in some gray list for post-processing; this is done\n** by 'genlink'.\n*/\nstatic int traverseephemeron (global_State *g, Table *h, int inv) {\n  int marked = 0;  /* true if an object is marked in this traversal */\n  int hasclears = 0;  /* true if table has white keys */\n  int hasww = 0;  /* true if table has entry \"white-key -> white-value\" */\n  unsigned int i;\n  unsigned int asize = luaH_realasize(h);\n  unsigned int nsize = sizenode(h);\n  /* traverse array part */\n  for (i = 0; i < asize; i++) {\n    if (valiswhite(&h->array[i])) {\n      marked = 1;\n      reallymarkobject(g, gcvalue(&h->array[i]));\n    }\n  }\n  /* traverse hash part; if 'inv', traverse descending\n     (see 'convergeephemerons') */\n  for (i = 0; i < nsize; i++) {\n    Node *n = inv ? gnode(h, nsize - 1 - i) : gnode(h, i);\n    if (isempty(gval(n)))  /* entry is empty? */\n      clearkey(n);  /* clear its key */\n    else if (iscleared(g, gckeyN(n))) {  /* key is not marked (yet)? */\n      hasclears = 1;  /* table must be cleared */\n      if (valiswhite(gval(n)))  /* value not marked yet? */\n        hasww = 1;  /* white-white entry */\n    }\n    else if (valiswhite(gval(n))) {  /* value not marked yet? */\n      marked = 1;\n      reallymarkobject(g, gcvalue(gval(n)));  /* mark it now */\n    }\n  }\n  /* link table into proper list */\n  if (g->gcstate == GCSpropagate)\n    linkgclist(h, g->grayagain);  /* must retraverse it in atomic phase */\n  else if (hasww)  /* table has white->white entries? */\n    linkgclist(h, g->ephemeron);  /* have to propagate again */\n  else if (hasclears)  /* table has white keys? */\n    linkgclist(h, g->allweak);  /* may have to clean white keys */\n  else\n    genlink(g, obj2gco(h));  /* check whether collector still needs to see it */\n  return marked;\n}\n\n\nstatic void traversestrongtable (global_State *g, Table *h) {\n  Node *n, *limit = gnodelast(h);\n  unsigned int i;\n  unsigned int asize = luaH_realasize(h);\n  for (i = 0; i < asize; i++)  /* traverse array part */\n    markvalue(g, &h->array[i]);\n  for (n = gnode(h, 0); n < limit; n++) {  /* traverse hash part */\n    if (isempty(gval(n)))  /* entry is empty? */\n      clearkey(n);  /* clear its key */\n    else {\n      lua_assert(!keyisnil(n));\n      markkey(g, n);\n      markvalue(g, gval(n));\n    }\n  }\n  genlink(g, obj2gco(h));\n}\n\n\nstatic lu_mem traversetable (global_State *g, Table *h) {\n  const char *weakkey, *weakvalue;\n  const TValue *mode = gfasttm(g, h->metatable, TM_MODE);\n  markobjectN(g, h->metatable);\n  if (mode && ttisstring(mode) &&  /* is there a weak mode? */\n      (cast_void(weakkey = strchr(svalue(mode), 'k')),\n       cast_void(weakvalue = strchr(svalue(mode), 'v')),\n       (weakkey || weakvalue))) {  /* is really weak? */\n    if (!weakkey)  /* strong keys? */\n      traverseweakvalue(g, h);\n    else if (!weakvalue)  /* strong values? */\n      traverseephemeron(g, h, 0);\n    else  /* all weak */\n      linkgclist(h, g->allweak);  /* nothing to traverse now */\n  }\n  else  /* not weak */\n    traversestrongtable(g, h);\n  return 1 + h->alimit + 2 * allocsizenode(h);\n}\n\n\nstatic int traverseudata (global_State *g, Udata *u) {\n  int i;\n  markobjectN(g, u->metatable);  /* mark its metatable */\n  for (i = 0; i < u->nuvalue; i++)\n    markvalue(g, &u->uv[i].uv);\n  genlink(g, obj2gco(u));\n  return 1 + u->nuvalue;\n}\n\n\n/*\n** Traverse a prototype. (While a prototype is being build, its\n** arrays can be larger than needed; the extra slots are filled with\n** NULL, so the use of 'markobjectN')\n*/\nstatic int traverseproto (global_State *g, Proto *f) {\n  int i;\n  markobjectN(g, f->source);\n  for (i = 0; i < f->sizek; i++)  /* mark literals */\n    markvalue(g, &f->k[i]);\n  for (i = 0; i < f->sizeupvalues; i++)  /* mark upvalue names */\n    markobjectN(g, f->upvalues[i].name);\n  for (i = 0; i < f->sizep; i++)  /* mark nested protos */\n    markobjectN(g, f->p[i]);\n  for (i = 0; i < f->sizelocvars; i++)  /* mark local-variable names */\n    markobjectN(g, f->locvars[i].varname);\n  return 1 + f->sizek + f->sizeupvalues + f->sizep + f->sizelocvars;\n}\n\n\nstatic int traverseCclosure (global_State *g, CClosure *cl) {\n  int i;\n  for (i = 0; i < cl->nupvalues; i++)  /* mark its upvalues */\n    markvalue(g, &cl->upvalue[i]);\n  return 1 + cl->nupvalues;\n}\n\n/*\n** Traverse a Lua closure, marking its prototype and its upvalues.\n** (Both can be NULL while closure is being created.)\n*/\nstatic int traverseLclosure (global_State *g, LClosure *cl) {\n  int i;\n  markobjectN(g, cl->p);  /* mark its prototype */\n  for (i = 0; i < cl->nupvalues; i++) {  /* visit its upvalues */\n    UpVal *uv = cl->upvals[i];\n    markobjectN(g, uv);  /* mark upvalue */\n  }\n  return 1 + cl->nupvalues;\n}\n\n\n/*\n** Traverse a thread, marking the elements in the stack up to its top\n** and cleaning the rest of the stack in the final traversal. That\n** ensures that the entire stack have valid (non-dead) objects.\n** Threads have no barriers. In gen. mode, old threads must be visited\n** at every cycle, because they might point to young objects.  In inc.\n** mode, the thread can still be modified before the end of the cycle,\n** and therefore it must be visited again in the atomic phase. To ensure\n** these visits, threads must return to a gray list if they are not new\n** (which can only happen in generational mode) or if the traverse is in\n** the propagate phase (which can only happen in incremental mode).\n*/\nstatic int traversethread (global_State *g, lua_State *th) {\n  UpVal *uv;\n  StkId o = th->stack.p;\n  if (isold(th) || g->gcstate == GCSpropagate)\n    linkgclist(th, g->grayagain);  /* insert into 'grayagain' list */\n  if (o == NULL)\n    return 1;  /* stack not completely built yet */\n  lua_assert(g->gcstate == GCSatomic ||\n             th->openupval == NULL || isintwups(th));\n  for (; o < th->top.p; o++)  /* mark live elements in the stack */\n    markvalue(g, s2v(o));\n  for (uv = th->openupval; uv != NULL; uv = uv->u.open.next)\n    markobject(g, uv);  /* open upvalues cannot be collected */\n  if (g->gcstate == GCSatomic) {  /* final traversal? */\n    for (; o < th->stack_last.p + EXTRA_STACK; o++)\n      setnilvalue(s2v(o));  /* clear dead stack slice */\n    /* 'remarkupvals' may have removed thread from 'twups' list */\n    if (!isintwups(th) && th->openupval != NULL) {\n      th->twups = g->twups;  /* link it back to the list */\n      g->twups = th;\n    }\n  }\n  else if (!g->gcemergency)\n    luaD_shrinkstack(th); /* do not change stack in emergency cycle */\n  return 1 + stacksize(th);\n}\n\n\n/*\n** traverse one gray object, turning it to black.\n*/\nstatic lu_mem propagatemark (global_State *g) {\n  GCObject *o = g->gray;\n  nw2black(o);\n  g->gray = *getgclist(o);  /* remove from 'gray' list */\n  switch (o->tt) {\n    case LUA_VTABLE: return traversetable(g, gco2t(o));\n    case LUA_VUSERDATA: return traverseudata(g, gco2u(o));\n    case LUA_VLCL: return traverseLclosure(g, gco2lcl(o));\n    case LUA_VCCL: return traverseCclosure(g, gco2ccl(o));\n    case LUA_VPROTO: return traverseproto(g, gco2p(o));\n    case LUA_VTHREAD: return traversethread(g, gco2th(o));\n    default: lua_assert(0); return 0;\n  }\n}\n\n\nstatic lu_mem propagateall (global_State *g) {\n  lu_mem tot = 0;\n  while (g->gray)\n    tot += propagatemark(g);\n  return tot;\n}\n\n\n/*\n** Traverse all ephemeron tables propagating marks from keys to values.\n** Repeat until it converges, that is, nothing new is marked. 'dir'\n** inverts the direction of the traversals, trying to speed up\n** convergence on chains in the same table.\n**\n*/\nstatic void convergeephemerons (global_State *g) {\n  int changed;\n  int dir = 0;\n  do {\n    GCObject *w;\n    GCObject *next = g->ephemeron;  /* get ephemeron list */\n    g->ephemeron = NULL;  /* tables may return to this list when traversed */\n    changed = 0;\n    while ((w = next) != NULL) {  /* for each ephemeron table */\n      Table *h = gco2t(w);\n      next = h->gclist;  /* list is rebuilt during loop */\n      nw2black(h);  /* out of the list (for now) */\n      if (traverseephemeron(g, h, dir)) {  /* marked some value? */\n        propagateall(g);  /* propagate changes */\n        changed = 1;  /* will have to revisit all ephemeron tables */\n      }\n    }\n    dir = !dir;  /* invert direction next time */\n  } while (changed);  /* repeat until no more changes */\n}\n\n/* }====================================================== */\n\n\n/*\n** {======================================================\n** Sweep Functions\n** =======================================================\n*/\n\n\n/*\n** clear entries with unmarked keys from all weaktables in list 'l'\n*/\nstatic void clearbykeys (global_State *g, GCObject *l) {\n  for (; l; l = gco2t(l)->gclist) {\n    Table *h = gco2t(l);\n    Node *limit = gnodelast(h);\n    Node *n;\n    for (n = gnode(h, 0); n < limit; n++) {\n      if (iscleared(g, gckeyN(n)))  /* unmarked key? */\n        setempty(gval(n));  /* remove entry */\n      if (isempty(gval(n)))  /* is entry empty? */\n        clearkey(n);  /* clear its key */\n    }\n  }\n}\n\n\n/*\n** clear entries with unmarked values from all weaktables in list 'l' up\n** to element 'f'\n*/\nstatic void clearbyvalues (global_State *g, GCObject *l, GCObject *f) {\n  for (; l != f; l = gco2t(l)->gclist) {\n    Table *h = gco2t(l);\n    Node *n, *limit = gnodelast(h);\n    unsigned int i;\n    unsigned int asize = luaH_realasize(h);\n    for (i = 0; i < asize; i++) {\n      TValue *o = &h->array[i];\n      if (iscleared(g, gcvalueN(o)))  /* value was collected? */\n        setempty(o);  /* remove entry */\n    }\n    for (n = gnode(h, 0); n < limit; n++) {\n      if (iscleared(g, gcvalueN(gval(n))))  /* unmarked value? */\n        setempty(gval(n));  /* remove entry */\n      if (isempty(gval(n)))  /* is entry empty? */\n        clearkey(n);  /* clear its key */\n    }\n  }\n}\n\n\nstatic void freeupval (lua_State *L, UpVal *uv) {\n  if (upisopen(uv))\n    luaF_unlinkupval(uv);\n  luaM_free(L, uv);\n}\n\n\nstatic void freeobj (lua_State *L, GCObject *o) {\n  switch (o->tt) {\n    case LUA_VPROTO:\n      luaF_freeproto(L, gco2p(o));\n      break;\n    case LUA_VUPVAL:\n      freeupval(L, gco2upv(o));\n      break;\n    case LUA_VLCL: {\n      LClosure *cl = gco2lcl(o);\n      luaM_freemem(L, cl, sizeLclosure(cl->nupvalues));\n      break;\n    }\n    case LUA_VCCL: {\n      CClosure *cl = gco2ccl(o);\n      luaM_freemem(L, cl, sizeCclosure(cl->nupvalues));\n      break;\n    }\n    case LUA_VTABLE:\n      luaH_free(L, gco2t(o));\n      break;\n    case LUA_VTHREAD:\n      luaE_freethread(L, gco2th(o));\n      break;\n    case LUA_VUSERDATA: {\n      Udata *u = gco2u(o);\n      luaM_freemem(L, o, sizeudata(u->nuvalue, u->len));\n      break;\n    }\n    case LUA_VSHRSTR: {\n      TString *ts = gco2ts(o);\n      luaS_remove(L, ts);  /* remove it from hash table */\n      luaM_freemem(L, ts, sizelstring(ts->shrlen));\n      break;\n    }\n    case LUA_VLNGSTR: {\n      TString *ts = gco2ts(o);\n      luaM_freemem(L, ts, sizelstring(ts->u.lnglen));\n      break;\n    }\n    default: lua_assert(0);\n  }\n}\n\n\n/*\n** sweep at most 'countin' elements from a list of GCObjects erasing dead\n** objects, where a dead object is one marked with the old (non current)\n** white; change all non-dead objects back to white, preparing for next\n** collection cycle. Return where to continue the traversal or NULL if\n** list is finished. ('*countout' gets the number of elements traversed.)\n*/\nstatic GCObject **sweeplist (lua_State *L, GCObject **p, int countin,\n                             int *countout) {\n  global_State *g = G(L);\n  int ow = otherwhite(g);\n  int i;\n  int white = luaC_white(g);  /* current white */\n  for (i = 0; *p != NULL && i < countin; i++) {\n    GCObject *curr = *p;\n    int marked = curr->marked;\n    if (isdeadm(ow, marked)) {  /* is 'curr' dead? */\n      *p = curr->next;  /* remove 'curr' from list */\n      freeobj(L, curr);  /* erase 'curr' */\n    }\n    else {  /* change mark to 'white' */\n      curr->marked = cast_byte((marked & ~maskgcbits) | white);\n      p = &curr->next;  /* go to next element */\n    }\n  }\n  if (countout)\n    *countout = i;  /* number of elements traversed */\n  return (*p == NULL) ? NULL : p;\n}\n\n\n/*\n** sweep a list until a live object (or end of list)\n*/\nstatic GCObject **sweeptolive (lua_State *L, GCObject **p) {\n  GCObject **old = p;\n  do {\n    p = sweeplist(L, p, 1, NULL);\n  } while (p == old);\n  return p;\n}\n\n/* }====================================================== */\n\n\n/*\n** {======================================================\n** Finalization\n** =======================================================\n*/\n\n/*\n** If possible, shrink string table.\n*/\nstatic void checkSizes (lua_State *L, global_State *g) {\n  if (!g->gcemergency) {\n    if (g->strt.nuse < g->strt.size / 4) {  /* string table too big? */\n      l_mem olddebt = g->GCdebt;\n      luaS_resize(L, g->strt.size / 2);\n      g->GCestimate += g->GCdebt - olddebt;  /* correct estimate */\n    }\n  }\n}\n\n\n/*\n** Get the next udata to be finalized from the 'tobefnz' list, and\n** link it back into the 'allgc' list.\n*/\nstatic GCObject *udata2finalize (global_State *g) {\n  GCObject *o = g->tobefnz;  /* get first element */\n  lua_assert(tofinalize(o));\n  g->tobefnz = o->next;  /* remove it from 'tobefnz' list */\n  o->next = g->allgc;  /* return it to 'allgc' list */\n  g->allgc = o;\n  resetbit(o->marked, FINALIZEDBIT);  /* object is \"normal\" again */\n  if (issweepphase(g))\n    makewhite(g, o);  /* \"sweep\" object */\n  else if (getage(o) == G_OLD1)\n    g->firstold1 = o;  /* it is the first OLD1 object in the list */\n  return o;\n}\n\n\nstatic void dothecall (lua_State *L, void *ud) {\n  UNUSED(ud);\n  luaD_callnoyield(L, L->top.p - 2, 0);\n}\n\n\nstatic void GCTM (lua_State *L) {\n  global_State *g = G(L);\n  const TValue *tm;\n  TValue v;\n  lua_assert(!g->gcemergency);\n  setgcovalue(L, &v, udata2finalize(g));\n  tm = luaT_gettmbyobj(L, &v, TM_GC);\n  if (!notm(tm)) {  /* is there a finalizer? */\n    int status;\n    lu_byte oldah = L->allowhook;\n    int oldgcstp  = g->gcstp;\n    g->gcstp |= GCSTPGC;  /* avoid GC steps */\n    L->allowhook = 0;  /* stop debug hooks during GC metamethod */\n    setobj2s(L, L->top.p++, tm);  /* push finalizer... */\n    setobj2s(L, L->top.p++, &v);  /* ... and its argument */\n    L->ci->callstatus |= CIST_FIN;  /* will run a finalizer */\n    status = luaD_pcall(L, dothecall, NULL, savestack(L, L->top.p - 2), 0);\n    L->ci->callstatus &= ~CIST_FIN;  /* not running a finalizer anymore */\n    L->allowhook = oldah;  /* restore hooks */\n    g->gcstp = oldgcstp;  /* restore state */\n    if (l_unlikely(status != LUA_OK)) {  /* error while running __gc? */\n      luaE_warnerror(L, \"__gc\");\n      L->top.p--;  /* pops error object */\n    }\n  }\n}\n\n\n/*\n** Call a few finalizers\n*/\nstatic int runafewfinalizers (lua_State *L, int n) {\n  global_State *g = G(L);\n  int i;\n  for (i = 0; i < n && g->tobefnz; i++)\n    GCTM(L);  /* call one finalizer */\n  return i;\n}\n\n\n/*\n** call all pending finalizers\n*/\nstatic void callallpendingfinalizers (lua_State *L) {\n  global_State *g = G(L);\n  while (g->tobefnz)\n    GCTM(L);\n}\n\n\n/*\n** find last 'next' field in list 'p' list (to add elements in its end)\n*/\nstatic GCObject **findlast (GCObject **p) {\n  while (*p != NULL)\n    p = &(*p)->next;\n  return p;\n}\n\n\n/*\n** Move all unreachable objects (or 'all' objects) that need\n** finalization from list 'finobj' to list 'tobefnz' (to be finalized).\n** (Note that objects after 'finobjold1' cannot be white, so they\n** don't need to be traversed. In incremental mode, 'finobjold1' is NULL,\n** so the whole list is traversed.)\n*/\nstatic void separatetobefnz (global_State *g, int all) {\n  GCObject *curr;\n  GCObject **p = &g->finobj;\n  GCObject **lastnext = findlast(&g->tobefnz);\n  while ((curr = *p) != g->finobjold1) {  /* traverse all finalizable objects */\n    lua_assert(tofinalize(curr));\n    if (!(iswhite(curr) || all))  /* not being collected? */\n      p = &curr->next;  /* don't bother with it */\n    else {\n      if (curr == g->finobjsur)  /* removing 'finobjsur'? */\n        g->finobjsur = curr->next;  /* correct it */\n      *p = curr->next;  /* remove 'curr' from 'finobj' list */\n      curr->next = *lastnext;  /* link at the end of 'tobefnz' list */\n      *lastnext = curr;\n      lastnext = &curr->next;\n    }\n  }\n}\n\n\n/*\n** If pointer 'p' points to 'o', move it to the next element.\n*/\nstatic void checkpointer (GCObject **p, GCObject *o) {\n  if (o == *p)\n    *p = o->next;\n}\n\n\n/*\n** Correct pointers to objects inside 'allgc' list when\n** object 'o' is being removed from the list.\n*/\nstatic void correctpointers (global_State *g, GCObject *o) {\n  checkpointer(&g->survival, o);\n  checkpointer(&g->old1, o);\n  checkpointer(&g->reallyold, o);\n  checkpointer(&g->firstold1, o);\n}\n\n\n/*\n** if object 'o' has a finalizer, remove it from 'allgc' list (must\n** search the list to find it) and link it in 'finobj' list.\n*/\nvoid luaC_checkfinalizer (lua_State *L, GCObject *o, Table *mt) {\n  global_State *g = G(L);\n  if (tofinalize(o) ||                 /* obj. is already marked... */\n      gfasttm(g, mt, TM_GC) == NULL ||    /* or has no finalizer... */\n      (g->gcstp & GCSTPCLS))                   /* or closing state? */\n    return;  /* nothing to be done */\n  else {  /* move 'o' to 'finobj' list */\n    GCObject **p;\n    if (issweepphase(g)) {\n      makewhite(g, o);  /* \"sweep\" object 'o' */\n      if (g->sweepgc == &o->next)  /* should not remove 'sweepgc' object */\n        g->sweepgc = sweeptolive(L, g->sweepgc);  /* change 'sweepgc' */\n    }\n    else\n      correctpointers(g, o);\n    /* search for pointer pointing to 'o' */\n    for (p = &g->allgc; *p != o; p = &(*p)->next) { /* empty */ }\n    *p = o->next;  /* remove 'o' from 'allgc' list */\n    o->next = g->finobj;  /* link it in 'finobj' list */\n    g->finobj = o;\n    l_setbit(o->marked, FINALIZEDBIT);  /* mark it as such */\n  }\n}\n\n/* }====================================================== */\n\n\n/*\n** {======================================================\n** Generational Collector\n** =======================================================\n*/\n\n\n/*\n** Set the \"time\" to wait before starting a new GC cycle; cycle will\n** start when memory use hits the threshold of ('estimate' * pause /\n** PAUSEADJ). (Division by 'estimate' should be OK: it cannot be zero,\n** because Lua cannot even start with less than PAUSEADJ bytes).\n*/\nstatic void setpause (global_State *g) {\n  l_mem threshold, debt;\n  int pause = getgcparam(g->gcpause);\n  l_mem estimate = g->GCestimate / PAUSEADJ;  /* adjust 'estimate' */\n  lua_assert(estimate > 0);\n  threshold = (pause < MAX_LMEM / estimate)  /* overflow? */\n            ? estimate * pause  /* no overflow */\n            : MAX_LMEM;  /* overflow; truncate to maximum */\n  debt = gettotalbytes(g) - threshold;\n  if (debt > 0) debt = 0;\n  luaE_setdebt(g, debt);\n}\n\n\n/*\n** Sweep a list of objects to enter generational mode.  Deletes dead\n** objects and turns the non dead to old. All non-dead threads---which\n** are now old---must be in a gray list. Everything else is not in a\n** gray list. Open upvalues are also kept gray.\n*/\nstatic void sweep2old (lua_State *L, GCObject **p) {\n  GCObject *curr;\n  global_State *g = G(L);\n  while ((curr = *p) != NULL) {\n    if (iswhite(curr)) {  /* is 'curr' dead? */\n      lua_assert(isdead(g, curr));\n      *p = curr->next;  /* remove 'curr' from list */\n      freeobj(L, curr);  /* erase 'curr' */\n    }\n    else {  /* all surviving objects become old */\n      setage(curr, G_OLD);\n      if (curr->tt == LUA_VTHREAD) {  /* threads must be watched */\n        lua_State *th = gco2th(curr);\n        linkgclist(th, g->grayagain);  /* insert into 'grayagain' list */\n      }\n      else if (curr->tt == LUA_VUPVAL && upisopen(gco2upv(curr)))\n        set2gray(curr);  /* open upvalues are always gray */\n      else  /* everything else is black */\n        nw2black(curr);\n      p = &curr->next;  /* go to next element */\n    }\n  }\n}\n\n\n/*\n** Sweep for generational mode. Delete dead objects. (Because the\n** collection is not incremental, there are no \"new white\" objects\n** during the sweep. So, any white object must be dead.) For\n** non-dead objects, advance their ages and clear the color of\n** new objects. (Old objects keep their colors.)\n** The ages of G_TOUCHED1 and G_TOUCHED2 objects cannot be advanced\n** here, because these old-generation objects are usually not swept\n** here.  They will all be advanced in 'correctgraylist'. That function\n** will also remove objects turned white here from any gray list.\n*/\nstatic GCObject **sweepgen (lua_State *L, global_State *g, GCObject **p,\n                            GCObject *limit, GCObject **pfirstold1) {\n  static const lu_byte nextage[] = {\n    G_SURVIVAL,  /* from G_NEW */\n    G_OLD1,      /* from G_SURVIVAL */\n    G_OLD1,      /* from G_OLD0 */\n    G_OLD,       /* from G_OLD1 */\n    G_OLD,       /* from G_OLD (do not change) */\n    G_TOUCHED1,  /* from G_TOUCHED1 (do not change) */\n    G_TOUCHED2   /* from G_TOUCHED2 (do not change) */\n  };\n  int white = luaC_white(g);\n  GCObject *curr;\n  while ((curr = *p) != limit) {\n    if (iswhite(curr)) {  /* is 'curr' dead? */\n      lua_assert(!isold(curr) && isdead(g, curr));\n      *p = curr->next;  /* remove 'curr' from list */\n      freeobj(L, curr);  /* erase 'curr' */\n    }\n    else {  /* correct mark and age */\n      if (getage(curr) == G_NEW) {  /* new objects go back to white */\n        int marked = curr->marked & ~maskgcbits;  /* erase GC bits */\n        curr->marked = cast_byte(marked | G_SURVIVAL | white);\n      }\n      else {  /* all other objects will be old, and so keep their color */\n        setage(curr, nextage[getage(curr)]);\n        if (getage(curr) == G_OLD1 && *pfirstold1 == NULL)\n          *pfirstold1 = curr;  /* first OLD1 object in the list */\n      }\n      p = &curr->next;  /* go to next element */\n    }\n  }\n  return p;\n}\n\n\n/*\n** Traverse a list making all its elements white and clearing their\n** age. In incremental mode, all objects are 'new' all the time,\n** except for fixed strings (which are always old).\n*/\nstatic void whitelist (global_State *g, GCObject *p) {\n  int white = luaC_white(g);\n  for (; p != NULL; p = p->next)\n    p->marked = cast_byte((p->marked & ~maskgcbits) | white);\n}\n\n\n/*\n** Correct a list of gray objects. Return pointer to where rest of the\n** list should be linked.\n** Because this correction is done after sweeping, young objects might\n** be turned white and still be in the list. They are only removed.\n** 'TOUCHED1' objects are advanced to 'TOUCHED2' and remain on the list;\n** Non-white threads also remain on the list; 'TOUCHED2' objects become\n** regular old; they and anything else are removed from the list.\n*/\nstatic GCObject **correctgraylist (GCObject **p) {\n  GCObject *curr;\n  while ((curr = *p) != NULL) {\n    GCObject **next = getgclist(curr);\n    if (iswhite(curr))\n      goto remove;  /* remove all white objects */\n    else if (getage(curr) == G_TOUCHED1) {  /* touched in this cycle? */\n      lua_assert(isgray(curr));\n      nw2black(curr);  /* make it black, for next barrier */\n      changeage(curr, G_TOUCHED1, G_TOUCHED2);\n      goto remain;  /* keep it in the list and go to next element */\n    }\n    else if (curr->tt == LUA_VTHREAD) {\n      lua_assert(isgray(curr));\n      goto remain;  /* keep non-white threads on the list */\n    }\n    else {  /* everything else is removed */\n      lua_assert(isold(curr));  /* young objects should be white here */\n      if (getage(curr) == G_TOUCHED2)  /* advance from TOUCHED2... */\n        changeage(curr, G_TOUCHED2, G_OLD);  /* ... to OLD */\n      nw2black(curr);  /* make object black (to be removed) */\n      goto remove;\n    }\n    remove: *p = *next; continue;\n    remain: p = next; continue;\n  }\n  return p;\n}\n\n\n/*\n** Correct all gray lists, coalescing them into 'grayagain'.\n*/\nstatic void correctgraylists (global_State *g) {\n  GCObject **list = correctgraylist(&g->grayagain);\n  *list = g->weak; g->weak = NULL;\n  list = correctgraylist(list);\n  *list = g->allweak; g->allweak = NULL;\n  list = correctgraylist(list);\n  *list = g->ephemeron; g->ephemeron = NULL;\n  correctgraylist(list);\n}\n\n\n/*\n** Mark black 'OLD1' objects when starting a new young collection.\n** Gray objects are already in some gray list, and so will be visited\n** in the atomic step.\n*/\nstatic void markold (global_State *g, GCObject *from, GCObject *to) {\n  GCObject *p;\n  for (p = from; p != to; p = p->next) {\n    if (getage(p) == G_OLD1) {\n      lua_assert(!iswhite(p));\n      changeage(p, G_OLD1, G_OLD);  /* now they are old */\n      if (isblack(p))\n        reallymarkobject(g, p);\n    }\n  }\n}\n\n\n/*\n** Finish a young-generation collection.\n*/\nstatic void finishgencycle (lua_State *L, global_State *g) {\n  correctgraylists(g);\n  checkSizes(L, g);\n  g->gcstate = GCSpropagate;  /* skip restart */\n  if (!g->gcemergency)\n    callallpendingfinalizers(L);\n}\n\n\n/*\n** Does a young collection. First, mark 'OLD1' objects. Then does the\n** atomic step. Then, sweep all lists and advance pointers. Finally,\n** finish the collection.\n*/\nstatic void youngcollection (lua_State *L, global_State *g) {\n  GCObject **psurvival;  /* to point to first non-dead survival object */\n  GCObject *dummy;  /* dummy out parameter to 'sweepgen' */\n  lua_assert(g->gcstate == GCSpropagate);\n  if (g->firstold1) {  /* are there regular OLD1 objects? */\n    markold(g, g->firstold1, g->reallyold);  /* mark them */\n    g->firstold1 = NULL;  /* no more OLD1 objects (for now) */\n  }\n  markold(g, g->finobj, g->finobjrold);\n  markold(g, g->tobefnz, NULL);\n  atomic(L);\n\n  /* sweep nursery and get a pointer to its last live element */\n  g->gcstate = GCSswpallgc;\n  psurvival = sweepgen(L, g, &g->allgc, g->survival, &g->firstold1);\n  /* sweep 'survival' */\n  sweepgen(L, g, psurvival, g->old1, &g->firstold1);\n  g->reallyold = g->old1;\n  g->old1 = *psurvival;  /* 'survival' survivals are old now */\n  g->survival = g->allgc;  /* all news are survivals */\n\n  /* repeat for 'finobj' lists */\n  dummy = NULL;  /* no 'firstold1' optimization for 'finobj' lists */\n  psurvival = sweepgen(L, g, &g->finobj, g->finobjsur, &dummy);\n  /* sweep 'survival' */\n  sweepgen(L, g, psurvival, g->finobjold1, &dummy);\n  g->finobjrold = g->finobjold1;\n  g->finobjold1 = *psurvival;  /* 'survival' survivals are old now */\n  g->finobjsur = g->finobj;  /* all news are survivals */\n\n  sweepgen(L, g, &g->tobefnz, NULL, &dummy);\n  finishgencycle(L, g);\n}\n\n\n/*\n** Clears all gray lists, sweeps objects, and prepare sublists to enter\n** generational mode. The sweeps remove dead objects and turn all\n** surviving objects to old. Threads go back to 'grayagain'; everything\n** else is turned black (not in any gray list).\n*/\nstatic void atomic2gen (lua_State *L, global_State *g) {\n  cleargraylists(g);\n  /* sweep all elements making them old */\n  g->gcstate = GCSswpallgc;\n  sweep2old(L, &g->allgc);\n  /* everything alive now is old */\n  g->reallyold = g->old1 = g->survival = g->allgc;\n  g->firstold1 = NULL;  /* there are no OLD1 objects anywhere */\n\n  /* repeat for 'finobj' lists */\n  sweep2old(L, &g->finobj);\n  g->finobjrold = g->finobjold1 = g->finobjsur = g->finobj;\n\n  sweep2old(L, &g->tobefnz);\n\n  g->gckind = KGC_GEN;\n  g->lastatomic = 0;\n  g->GCestimate = gettotalbytes(g);  /* base for memory control */\n  finishgencycle(L, g);\n}\n\n\n/*\n** Set debt for the next minor collection, which will happen when\n** memory grows 'genminormul'%.\n*/\nstatic void setminordebt (global_State *g) {\n  luaE_setdebt(g, -(cast(l_mem, (gettotalbytes(g) / 100)) * g->genminormul));\n}\n\n\n/*\n** Enter generational mode. Must go until the end of an atomic cycle\n** to ensure that all objects are correctly marked and weak tables\n** are cleared. Then, turn all objects into old and finishes the\n** collection.\n*/\nstatic lu_mem entergen (lua_State *L, global_State *g) {\n  lu_mem numobjs;\n  luaC_runtilstate(L, bitmask(GCSpause));  /* prepare to start a new cycle */\n  luaC_runtilstate(L, bitmask(GCSpropagate));  /* start new cycle */\n  numobjs = atomic(L);  /* propagates all and then do the atomic stuff */\n  atomic2gen(L, g);\n  setminordebt(g);  /* set debt assuming next cycle will be minor */\n  return numobjs;\n}\n\n\n/*\n** Enter incremental mode. Turn all objects white, make all\n** intermediate lists point to NULL (to avoid invalid pointers),\n** and go to the pause state.\n*/\nstatic void enterinc (global_State *g) {\n  whitelist(g, g->allgc);\n  g->reallyold = g->old1 = g->survival = NULL;\n  whitelist(g, g->finobj);\n  whitelist(g, g->tobefnz);\n  g->finobjrold = g->finobjold1 = g->finobjsur = NULL;\n  g->gcstate = GCSpause;\n  g->gckind = KGC_INC;\n  g->lastatomic = 0;\n}\n\n\n/*\n** Change collector mode to 'newmode'.\n*/\nvoid luaC_changemode (lua_State *L, int newmode) {\n  global_State *g = G(L);\n  if (newmode != g->gckind) {\n    if (newmode == KGC_GEN)  /* entering generational mode? */\n      entergen(L, g);\n    else\n      enterinc(g);  /* entering incremental mode */\n  }\n  g->lastatomic = 0;\n}\n\n\n/*\n** Does a full collection in generational mode.\n*/\nstatic lu_mem fullgen (lua_State *L, global_State *g) {\n  enterinc(g);\n  return entergen(L, g);\n}\n\n\n/*\n** Does a major collection after last collection was a \"bad collection\".\n**\n** When the program is building a big structure, it allocates lots of\n** memory but generates very little garbage. In those scenarios,\n** the generational mode just wastes time doing small collections, and\n** major collections are frequently what we call a \"bad collection\", a\n** collection that frees too few objects. To avoid the cost of switching\n** between generational mode and the incremental mode needed for full\n** (major) collections, the collector tries to stay in incremental mode\n** after a bad collection, and to switch back to generational mode only\n** after a \"good\" collection (one that traverses less than 9/8 objects\n** of the previous one).\n** The collector must choose whether to stay in incremental mode or to\n** switch back to generational mode before sweeping. At this point, it\n** does not know the real memory in use, so it cannot use memory to\n** decide whether to return to generational mode. Instead, it uses the\n** number of objects traversed (returned by 'atomic') as a proxy. The\n** field 'g->lastatomic' keeps this count from the last collection.\n** ('g->lastatomic != 0' also means that the last collection was bad.)\n*/\nstatic void stepgenfull (lua_State *L, global_State *g) {\n  lu_mem newatomic;  /* count of traversed objects */\n  lu_mem lastatomic = g->lastatomic;  /* count from last collection */\n  if (g->gckind == KGC_GEN)  /* still in generational mode? */\n    enterinc(g);  /* enter incremental mode */\n  luaC_runtilstate(L, bitmask(GCSpropagate));  /* start new cycle */\n  newatomic = atomic(L);  /* mark everybody */\n  if (newatomic < lastatomic + (lastatomic >> 3)) {  /* good collection? */\n    atomic2gen(L, g);  /* return to generational mode */\n    setminordebt(g);\n  }\n  else {  /* another bad collection; stay in incremental mode */\n    g->GCestimate = gettotalbytes(g);  /* first estimate */;\n    entersweep(L);\n    luaC_runtilstate(L, bitmask(GCSpause));  /* finish collection */\n    setpause(g);\n    g->lastatomic = newatomic;\n  }\n}\n\n\n/*\n** Does a generational \"step\".\n** Usually, this means doing a minor collection and setting the debt to\n** make another collection when memory grows 'genminormul'% larger.\n**\n** However, there are exceptions.  If memory grows 'genmajormul'%\n** larger than it was at the end of the last major collection (kept\n** in 'g->GCestimate'), the function does a major collection. At the\n** end, it checks whether the major collection was able to free a\n** decent amount of memory (at least half the growth in memory since\n** previous major collection). If so, the collector keeps its state,\n** and the next collection will probably be minor again. Otherwise,\n** we have what we call a \"bad collection\". In that case, set the field\n** 'g->lastatomic' to signal that fact, so that the next collection will\n** go to 'stepgenfull'.\n**\n** 'GCdebt <= 0' means an explicit call to GC step with \"size\" zero;\n** in that case, do a minor collection.\n*/\nstatic void genstep (lua_State *L, global_State *g) {\n  if (g->lastatomic != 0)  /* last collection was a bad one? */\n    stepgenfull(L, g);  /* do a full step */\n  else {\n    lu_mem majorbase = g->GCestimate;  /* memory after last major collection */\n    lu_mem majorinc = (majorbase / 100) * getgcparam(g->genmajormul);\n    if (g->GCdebt > 0 && gettotalbytes(g) > majorbase + majorinc) {\n      lu_mem numobjs = fullgen(L, g);  /* do a major collection */\n      if (gettotalbytes(g) < majorbase + (majorinc / 2)) {\n        /* collected at least half of memory growth since last major\n           collection; keep doing minor collections. */\n        lua_assert(g->lastatomic == 0);\n      }\n      else {  /* bad collection */\n        g->lastatomic = numobjs;  /* signal that last collection was bad */\n        setpause(g);  /* do a long wait for next (major) collection */\n      }\n    }\n    else {  /* regular case; do a minor collection */\n      youngcollection(L, g);\n      setminordebt(g);\n      g->GCestimate = majorbase;  /* preserve base value */\n    }\n  }\n  lua_assert(isdecGCmodegen(g));\n}\n\n/* }====================================================== */\n\n\n/*\n** {======================================================\n** GC control\n** =======================================================\n*/\n\n\n/*\n** Enter first sweep phase.\n** The call to 'sweeptolive' makes the pointer point to an object\n** inside the list (instead of to the header), so that the real sweep do\n** not need to skip objects created between \"now\" and the start of the\n** real sweep.\n*/\nstatic void entersweep (lua_State *L) {\n  global_State *g = G(L);\n  g->gcstate = GCSswpallgc;\n  lua_assert(g->sweepgc == NULL);\n  g->sweepgc = sweeptolive(L, &g->allgc);\n}\n\n\n/*\n** Delete all objects in list 'p' until (but not including) object\n** 'limit'.\n*/\nstatic void deletelist (lua_State *L, GCObject *p, GCObject *limit) {\n  while (p != limit) {\n    GCObject *next = p->next;\n    freeobj(L, p);\n    p = next;\n  }\n}\n\n\n/*\n** Call all finalizers of the objects in the given Lua state, and\n** then free all objects, except for the main thread.\n*/\nvoid luaC_freeallobjects (lua_State *L) {\n  global_State *g = G(L);\n  g->gcstp = GCSTPCLS;  /* no extra finalizers after here */\n  luaC_changemode(L, KGC_INC);\n  separatetobefnz(g, 1);  /* separate all objects with finalizers */\n  lua_assert(g->finobj == NULL);\n  callallpendingfinalizers(L);\n  deletelist(L, g->allgc, obj2gco(g->mainthread));\n  lua_assert(g->finobj == NULL);  /* no new finalizers */\n  deletelist(L, g->fixedgc, NULL);  /* collect fixed objects */\n  lua_assert(g->strt.nuse == 0);\n}\n\n\nstatic lu_mem atomic (lua_State *L) {\n  global_State *g = G(L);\n  lu_mem work = 0;\n  GCObject *origweak, *origall;\n  GCObject *grayagain = g->grayagain;  /* save original list */\n  g->grayagain = NULL;\n  lua_assert(g->ephemeron == NULL && g->weak == NULL);\n  lua_assert(!iswhite(g->mainthread));\n  g->gcstate = GCSatomic;\n  markobject(g, L);  /* mark running thread */\n  /* registry and global metatables may be changed by API */\n  markvalue(g, &g->l_registry);\n  markmt(g);  /* mark global metatables */\n  work += propagateall(g);  /* empties 'gray' list */\n  /* remark occasional upvalues of (maybe) dead threads */\n  work += remarkupvals(g);\n  work += propagateall(g);  /* propagate changes */\n  g->gray = grayagain;\n  work += propagateall(g);  /* traverse 'grayagain' list */\n  convergeephemerons(g);\n  /* at this point, all strongly accessible objects are marked. */\n  /* Clear values from weak tables, before checking finalizers */\n  clearbyvalues(g, g->weak, NULL);\n  clearbyvalues(g, g->allweak, NULL);\n  origweak = g->weak; origall = g->allweak;\n  separatetobefnz(g, 0);  /* separate objects to be finalized */\n  work += markbeingfnz(g);  /* mark objects that will be finalized */\n  work += propagateall(g);  /* remark, to propagate 'resurrection' */\n  convergeephemerons(g);\n  /* at this point, all resurrected objects are marked. */\n  /* remove dead objects from weak tables */\n  clearbykeys(g, g->ephemeron);  /* clear keys from all ephemeron tables */\n  clearbykeys(g, g->allweak);  /* clear keys from all 'allweak' tables */\n  /* clear values from resurrected weak tables */\n  clearbyvalues(g, g->weak, origweak);\n  clearbyvalues(g, g->allweak, origall);\n  luaS_clearcache(g);\n  g->currentwhite = cast_byte(otherwhite(g));  /* flip current white */\n  lua_assert(g->gray == NULL);\n  return work;  /* estimate of slots marked by 'atomic' */\n}\n\n\nstatic int sweepstep (lua_State *L, global_State *g,\n                      int nextstate, GCObject **nextlist) {\n  if (g->sweepgc) {\n    l_mem olddebt = g->GCdebt;\n    int count;\n    g->sweepgc = sweeplist(L, g->sweepgc, GCSWEEPMAX, &count);\n    g->GCestimate += g->GCdebt - olddebt;  /* update estimate */\n    return count;\n  }\n  else {  /* enter next state */\n    g->gcstate = nextstate;\n    g->sweepgc = nextlist;\n    return 0;  /* no work done */\n  }\n}\n\n\nstatic lu_mem singlestep (lua_State *L) {\n  global_State *g = G(L);\n  lu_mem work;\n  lua_assert(!g->gcstopem);  /* collector is not reentrant */\n  g->gcstopem = 1;  /* no emergency collections while collecting */\n  switch (g->gcstate) {\n    case GCSpause: {\n      restartcollection(g);\n      g->gcstate = GCSpropagate;\n      work = 1;\n      break;\n    }\n    case GCSpropagate: {\n      if (g->gray == NULL) {  /* no more gray objects? */\n        g->gcstate = GCSenteratomic;  /* finish propagate phase */\n        work = 0;\n      }\n      else\n        work = propagatemark(g);  /* traverse one gray object */\n      break;\n    }\n    case GCSenteratomic: {\n      work = atomic(L);  /* work is what was traversed by 'atomic' */\n      entersweep(L);\n      g->GCestimate = gettotalbytes(g);  /* first estimate */;\n      break;\n    }\n    case GCSswpallgc: {  /* sweep \"regular\" objects */\n      work = sweepstep(L, g, GCSswpfinobj, &g->finobj);\n      break;\n    }\n    case GCSswpfinobj: {  /* sweep objects with finalizers */\n      work = sweepstep(L, g, GCSswptobefnz, &g->tobefnz);\n      break;\n    }\n    case GCSswptobefnz: {  /* sweep objects to be finalized */\n      work = sweepstep(L, g, GCSswpend, NULL);\n      break;\n    }\n    case GCSswpend: {  /* finish sweeps */\n      checkSizes(L, g);\n      g->gcstate = GCScallfin;\n      work = 0;\n      break;\n    }\n    case GCScallfin: {  /* call remaining finalizers */\n      if (g->tobefnz && !g->gcemergency) {\n        g->gcstopem = 0;  /* ok collections during finalizers */\n        work = runafewfinalizers(L, GCFINMAX) * GCFINALIZECOST;\n      }\n      else {  /* emergency mode or no more finalizers */\n        g->gcstate = GCSpause;  /* finish collection */\n        work = 0;\n      }\n      break;\n    }\n    default: lua_assert(0); return 0;\n  }\n  g->gcstopem = 0;\n  return work;\n}\n\n\n/*\n** advances the garbage collector until it reaches a state allowed\n** by 'statemask'\n*/\nvoid luaC_runtilstate (lua_State *L, int statesmask) {\n  global_State *g = G(L);\n  while (!testbit(statesmask, g->gcstate))\n    singlestep(L);\n}\n\n\n\n/*\n** Performs a basic incremental step. The debt and step size are\n** converted from bytes to \"units of work\"; then the function loops\n** running single steps until adding that many units of work or\n** finishing a cycle (pause state). Finally, it sets the debt that\n** controls when next step will be performed.\n*/\nstatic void incstep (lua_State *L, global_State *g) {\n  int stepmul = (getgcparam(g->gcstepmul) | 1);  /* avoid division by 0 */\n  l_mem debt = (g->GCdebt / WORK2MEM) * stepmul;\n  l_mem stepsize = (g->gcstepsize <= log2maxs(l_mem))\n                 ? ((cast(l_mem, 1) << g->gcstepsize) / WORK2MEM) * stepmul\n                 : MAX_LMEM;  /* overflow; keep maximum value */\n  do {  /* repeat until pause or enough \"credit\" (negative debt) */\n    lu_mem work = singlestep(L);  /* perform one single step */\n    debt -= work;\n  } while (debt > -stepsize && g->gcstate != GCSpause);\n  if (g->gcstate == GCSpause)\n    setpause(g);  /* pause until next cycle */\n  else {\n    debt = (debt / stepmul) * WORK2MEM;  /* convert 'work units' to bytes */\n    luaE_setdebt(g, debt);\n  }\n}\n\n/*\n** Performs a basic GC step if collector is running. (If collector is\n** not running, set a reasonable debt to avoid it being called at\n** every single check.)\n*/\nvoid luaC_step (lua_State *L) {\n  global_State *g = G(L);\n  if (!gcrunning(g))  /* not running? */\n    luaE_setdebt(g, -2000);\n  else {\n    if(isdecGCmodegen(g))\n      genstep(L, g);\n    else\n      incstep(L, g);\n  }\n}\n\n\n/*\n** Perform a full collection in incremental mode.\n** Before running the collection, check 'keepinvariant'; if it is true,\n** there may be some objects marked as black, so the collector has\n** to sweep all objects to turn them back to white (as white has not\n** changed, nothing will be collected).\n*/\nstatic void fullinc (lua_State *L, global_State *g) {\n  if (keepinvariant(g))  /* black objects? */\n    entersweep(L); /* sweep everything to turn them back to white */\n  /* finish any pending sweep phase to start a new cycle */\n  luaC_runtilstate(L, bitmask(GCSpause));\n  luaC_runtilstate(L, bitmask(GCScallfin));  /* run up to finalizers */\n  /* estimate must be correct after a full GC cycle */\n  lua_assert(g->GCestimate == gettotalbytes(g));\n  luaC_runtilstate(L, bitmask(GCSpause));  /* finish collection */\n  setpause(g);\n}\n\n\n/*\n** Performs a full GC cycle; if 'isemergency', set a flag to avoid\n** some operations which could change the interpreter state in some\n** unexpected ways (running finalizers and shrinking some structures).\n*/\nvoid luaC_fullgc (lua_State *L, int isemergency) {\n  global_State *g = G(L);\n  lua_assert(!g->gcemergency);\n  g->gcemergency = isemergency;  /* set flag */\n  if (g->gckind == KGC_INC)\n    fullinc(L, g);\n  else\n    fullgen(L, g);\n  g->gcemergency = 0;\n}\n\n/* }====================================================== */\n\n\n/*\n** $Id: llex.c $\n** Lexical Analyzer\n** See Copyright Notice in lua.h\n*/\n\n#define llex_c\n#define LUA_CORE\n\n/*#include \"lprefix.h\"*/\n\n\n#include <locale.h>\n#include <string.h>\n\n/*#include \"lua.h\"*/\n\n/*#include \"lctype.h\"*/\n/*#include \"ldebug.h\"*/\n/*#include \"ldo.h\"*/\n/*#include \"lgc.h\"*/\n/*#include \"llex.h\"*/\n/*#include \"lobject.h\"*/\n/*#include \"lparser.h\"*/\n/*#include \"lstate.h\"*/\n/*#include \"lstring.h\"*/\n/*#include \"ltable.h\"*/\n/*#include \"lzio.h\"*/\n\n\n\n#define next(ls)\t(ls->current = zgetc(ls->z))\n\n\n\n#define currIsNewline(ls)\t(ls->current == '\\n' || ls->current == '\\r')\n\n\n/* ORDER RESERVED */\nstatic const char *const luaX_tokens [] = {\n    \"and\", \"break\", \"do\", \"else\", \"elseif\",\n    \"end\", \"false\", \"for\", \"function\", \"goto\", \"if\",\n    \"in\", \"local\", \"nil\", \"not\", \"or\", \"repeat\",\n    \"return\", \"then\", \"true\", \"until\", \"while\",\n    \"//\", \"..\", \"...\", \"==\", \">=\", \"<=\", \"~=\",\n    \"<<\", \">>\", \"::\", \"<eof>\",\n    \"<number>\", \"<integer>\", \"<name>\", \"<string>\"\n};\n\n\n#define save_and_next(ls) (save(ls, ls->current), next(ls))\n\n\nstatic l_noret lexerror (LexState *ls, const char *msg, int token);\n\n\nstatic void save (LexState *ls, int c) {\n  Mbuffer *b = ls->buff;\n  if (luaZ_bufflen(b) + 1 > luaZ_sizebuffer(b)) {\n    size_t newsize;\n    if (luaZ_sizebuffer(b) >= MAX_SIZE/2)\n      lexerror(ls, \"lexical element too long\", 0);\n    newsize = luaZ_sizebuffer(b) * 2;\n    luaZ_resizebuffer(ls->L, b, newsize);\n  }\n  b->buffer[luaZ_bufflen(b)++] = cast_char(c);\n}\n\n\nvoid luaX_init (lua_State *L) {\n  int i;\n  TString *e = luaS_newliteral(L, LUA_ENV);  /* create env name */\n  luaC_fix(L, obj2gco(e));  /* never collect this name */\n  for (i=0; i<NUM_RESERVED; i++) {\n    TString *ts = luaS_new(L, luaX_tokens[i]);\n    luaC_fix(L, obj2gco(ts));  /* reserved words are never collected */\n    ts->extra = cast_byte(i+1);  /* reserved word */\n  }\n}\n\n\nconst char *luaX_token2str (LexState *ls, int token) {\n  if (token < FIRST_RESERVED) {  /* single-byte symbols? */\n    if (lisprint(token))\n      return luaO_pushfstring(ls->L, \"'%c'\", token);\n    else  /* control character */\n      return luaO_pushfstring(ls->L, \"'<\\\\%d>'\", token);\n  }\n  else {\n    const char *s = luaX_tokens[token - FIRST_RESERVED];\n    if (token < TK_EOS)  /* fixed format (symbols and reserved words)? */\n      return luaO_pushfstring(ls->L, \"'%s'\", s);\n    else  /* names, strings, and numerals */\n      return s;\n  }\n}\n\n\nstatic const char *txtToken (LexState *ls, int token) {\n  switch (token) {\n    case TK_NAME: case TK_STRING:\n    case TK_FLT: case TK_INT:\n      save(ls, '\\0');\n      return luaO_pushfstring(ls->L, \"'%s'\", luaZ_buffer(ls->buff));\n    default:\n      return luaX_token2str(ls, token);\n  }\n}\n\n\nstatic l_noret lexerror (LexState *ls, const char *msg, int token) {\n  msg = luaG_addinfo(ls->L, msg, ls->source, ls->linenumber);\n  if (token)\n    luaO_pushfstring(ls->L, \"%s near %s\", msg, txtToken(ls, token));\n  luaD_throw(ls->L, LUA_ERRSYNTAX);\n}\n\n\nl_noret luaX_syntaxerror (LexState *ls, const char *msg) {\n  lexerror(ls, msg, ls->t.token);\n}\n\n\n/*\n** Creates a new string and anchors it in scanner's table so that it\n** will not be collected until the end of the compilation; by that time\n** it should be anchored somewhere. It also internalizes long strings,\n** ensuring there is only one copy of each unique string.  The table\n** here is used as a set: the string enters as the key, while its value\n** is irrelevant. We use the string itself as the value only because it\n** is a TValue readily available. Later, the code generation can change\n** this value.\n*/\nTString *luaX_newstring (LexState *ls, const char *str, size_t l) {\n  lua_State *L = ls->L;\n  TString *ts = luaS_newlstr(L, str, l);  /* create new string */\n  const TValue *o = luaH_getstr(ls->h, ts);\n  if (!ttisnil(o))  /* string already present? */\n    ts = keystrval(nodefromval(o));  /* get saved copy */\n  else {  /* not in use yet */\n    TValue *stv = s2v(L->top.p++);  /* reserve stack space for string */\n    setsvalue(L, stv, ts);  /* temporarily anchor the string */\n    luaH_finishset(L, ls->h, stv, o, stv);  /* t[string] = string */\n    /* table is not a metatable, so it does not need to invalidate cache */\n    luaC_checkGC(L);\n    L->top.p--;  /* remove string from stack */\n  }\n  return ts;\n}\n\n\n/*\n** increment line number and skips newline sequence (any of\n** \\n, \\r, \\n\\r, or \\r\\n)\n*/\nstatic void inclinenumber (LexState *ls) {\n  int old = ls->current;\n  lua_assert(currIsNewline(ls));\n  next(ls);  /* skip '\\n' or '\\r' */\n  if (currIsNewline(ls) && ls->current != old)\n    next(ls);  /* skip '\\n\\r' or '\\r\\n' */\n  if (++ls->linenumber >= MAX_INT)\n    lexerror(ls, \"chunk has too many lines\", 0);\n}\n\n\nvoid luaX_setinput (lua_State *L, LexState *ls, ZIO *z, TString *source,\n                    int firstchar) {\n  ls->t.token = 0;\n  ls->L = L;\n  ls->current = firstchar;\n  ls->lookahead.token = TK_EOS;  /* no look-ahead token */\n  ls->z = z;\n  ls->fs = NULL;\n  ls->linenumber = 1;\n  ls->lastline = 1;\n  ls->source = source;\n  ls->envn = luaS_newliteral(L, LUA_ENV);  /* get env name */\n  luaZ_resizebuffer(ls->L, ls->buff, LUA_MINBUFFER);  /* initialize buffer */\n}\n\n\n\n/*\n** =======================================================\n** LEXICAL ANALYZER\n** =======================================================\n*/\n\n\nstatic int check_next1 (LexState *ls, int c) {\n  if (ls->current == c) {\n    next(ls);\n    return 1;\n  }\n  else return 0;\n}\n\n\n/*\n** Check whether current char is in set 'set' (with two chars) and\n** saves it\n*/\nstatic int check_next2 (LexState *ls, const char *set) {\n  lua_assert(set[2] == '\\0');\n  if (ls->current == set[0] || ls->current == set[1]) {\n    save_and_next(ls);\n    return 1;\n  }\n  else return 0;\n}\n\n\n/* LUA_NUMBER */\n/*\n** This function is quite liberal in what it accepts, as 'luaO_str2num'\n** will reject ill-formed numerals. Roughly, it accepts the following\n** pattern:\n**\n**   %d(%x|%.|([Ee][+-]?))* | 0[Xx](%x|%.|([Pp][+-]?))*\n**\n** The only tricky part is to accept [+-] only after a valid exponent\n** mark, to avoid reading '3-4' or '0xe+1' as a single number.\n**\n** The caller might have already read an initial dot.\n*/\nstatic int read_numeral (LexState *ls, SemInfo *seminfo) {\n  TValue obj;\n  const char *expo = \"Ee\";\n  int first = ls->current;\n  lua_assert(lisdigit(ls->current));\n  save_and_next(ls);\n  if (first == '0' && check_next2(ls, \"xX\"))  /* hexadecimal? */\n    expo = \"Pp\";\n  for (;;) {\n    if (check_next2(ls, expo))  /* exponent mark? */\n      check_next2(ls, \"-+\");  /* optional exponent sign */\n    else if (lisxdigit(ls->current) || ls->current == '.')  /* '%x|%.' */\n      save_and_next(ls);\n    else break;\n  }\n  if (lislalpha(ls->current))  /* is numeral touching a letter? */\n    save_and_next(ls);  /* force an error */\n  save(ls, '\\0');\n  if (luaO_str2num(luaZ_buffer(ls->buff), &obj) == 0)  /* format error? */\n    lexerror(ls, \"malformed number\", TK_FLT);\n  if (ttisinteger(&obj)) {\n    seminfo->i = ivalue(&obj);\n    return TK_INT;\n  }\n  else {\n    lua_assert(ttisfloat(&obj));\n    seminfo->r = fltvalue(&obj);\n    return TK_FLT;\n  }\n}\n\n\n/*\n** read a sequence '[=*[' or ']=*]', leaving the last bracket. If\n** sequence is well formed, return its number of '='s + 2; otherwise,\n** return 1 if it is a single bracket (no '='s and no 2nd bracket);\n** otherwise (an unfinished '[==...') return 0.\n*/\nstatic size_t skip_sep (LexState *ls) {\n  size_t count = 0;\n  int s = ls->current;\n  lua_assert(s == '[' || s == ']');\n  save_and_next(ls);\n  while (ls->current == '=') {\n    save_and_next(ls);\n    count++;\n  }\n  return (ls->current == s) ? count + 2\n         : (count == 0) ? 1\n         : 0;\n}\n\n\nstatic void read_long_string (LexState *ls, SemInfo *seminfo, size_t sep) {\n  int line = ls->linenumber;  /* initial line (for error message) */\n  save_and_next(ls);  /* skip 2nd '[' */\n  if (currIsNewline(ls))  /* string starts with a newline? */\n    inclinenumber(ls);  /* skip it */\n  for (;;) {\n    switch (ls->current) {\n      case EOZ: {  /* error */\n        const char *what = (seminfo ? \"string\" : \"comment\");\n        const char *msg = luaO_pushfstring(ls->L,\n                     \"unfinished long %s (starting at line %d)\", what, line);\n        lexerror(ls, msg, TK_EOS);\n        break;  /* to avoid warnings */\n      }\n      case ']': {\n        if (skip_sep(ls) == sep) {\n          save_and_next(ls);  /* skip 2nd ']' */\n          goto endloop;\n        }\n        break;\n      }\n      case '\\n': case '\\r': {\n        save(ls, '\\n');\n        inclinenumber(ls);\n        if (!seminfo) luaZ_resetbuffer(ls->buff);  /* avoid wasting space */\n        break;\n      }\n      default: {\n        if (seminfo) save_and_next(ls);\n        else next(ls);\n      }\n    }\n  } endloop:\n  if (seminfo)\n    seminfo->ts = luaX_newstring(ls, luaZ_buffer(ls->buff) + sep,\n                                     luaZ_bufflen(ls->buff) - 2 * sep);\n}\n\n\nstatic void esccheck (LexState *ls, int c, const char *msg) {\n  if (!c) {\n    if (ls->current != EOZ)\n      save_and_next(ls);  /* add current to buffer for error message */\n    lexerror(ls, msg, TK_STRING);\n  }\n}\n\n\nstatic int gethexa (LexState *ls) {\n  save_and_next(ls);\n  esccheck (ls, lisxdigit(ls->current), \"hexadecimal digit expected\");\n  return luaO_hexavalue(ls->current);\n}\n\n\nstatic int readhexaesc (LexState *ls) {\n  int r = gethexa(ls);\n  r = (r << 4) + gethexa(ls);\n  luaZ_buffremove(ls->buff, 2);  /* remove saved chars from buffer */\n  return r;\n}\n\n\nstatic unsigned long readutf8esc (LexState *ls) {\n  unsigned long r;\n  int i = 4;  /* chars to be removed: '\\', 'u', '{', and first digit */\n  save_and_next(ls);  /* skip 'u' */\n  esccheck(ls, ls->current == '{', \"missing '{'\");\n  r = gethexa(ls);  /* must have at least one digit */\n  while (cast_void(save_and_next(ls)), lisxdigit(ls->current)) {\n    i++;\n    esccheck(ls, r <= (0x7FFFFFFFu >> 4), \"UTF-8 value too large\");\n    r = (r << 4) + luaO_hexavalue(ls->current);\n  }\n  esccheck(ls, ls->current == '}', \"missing '}'\");\n  next(ls);  /* skip '}' */\n  luaZ_buffremove(ls->buff, i);  /* remove saved chars from buffer */\n  return r;\n}\n\n\nstatic void utf8esc (LexState *ls) {\n  char buff[UTF8BUFFSZ];\n  int n = luaO_utf8esc(buff, readutf8esc(ls));\n  for (; n > 0; n--)  /* add 'buff' to string */\n    save(ls, buff[UTF8BUFFSZ - n]);\n}\n\n\nstatic int readdecesc (LexState *ls) {\n  int i;\n  int r = 0;  /* result accumulator */\n  for (i = 0; i < 3 && lisdigit(ls->current); i++) {  /* read up to 3 digits */\n    r = 10*r + ls->current - '0';\n    save_and_next(ls);\n  }\n  esccheck(ls, r <= UCHAR_MAX, \"decimal escape too large\");\n  luaZ_buffremove(ls->buff, i);  /* remove read digits from buffer */\n  return r;\n}\n\n\nstatic void read_string (LexState *ls, int del, SemInfo *seminfo) {\n  save_and_next(ls);  /* keep delimiter (for error messages) */\n  while (ls->current != del) {\n    switch (ls->current) {\n      case EOZ:\n        lexerror(ls, \"unfinished string\", TK_EOS);\n        break;  /* to avoid warnings */\n      case '\\n':\n      case '\\r':\n        lexerror(ls, \"unfinished string\", TK_STRING);\n        break;  /* to avoid warnings */\n      case '\\\\': {  /* escape sequences */\n        int c;  /* final character to be saved */\n        save_and_next(ls);  /* keep '\\\\' for error messages */\n        switch (ls->current) {\n          case 'a': c = '\\a'; goto read_save;\n          case 'b': c = '\\b'; goto read_save;\n          case 'f': c = '\\f'; goto read_save;\n          case 'n': c = '\\n'; goto read_save;\n          case 'r': c = '\\r'; goto read_save;\n          case 't': c = '\\t'; goto read_save;\n          case 'v': c = '\\v'; goto read_save;\n          case 'x': c = readhexaesc(ls); goto read_save;\n          case 'u': utf8esc(ls);  goto no_save;\n          case '\\n': case '\\r':\n            inclinenumber(ls); c = '\\n'; goto only_save;\n          case '\\\\': case '\\\"': case '\\'':\n            c = ls->current; goto read_save;\n          case EOZ: goto no_save;  /* will raise an error next loop */\n          case 'z': {  /* zap following span of spaces */\n            luaZ_buffremove(ls->buff, 1);  /* remove '\\\\' */\n            next(ls);  /* skip the 'z' */\n            while (lisspace(ls->current)) {\n              if (currIsNewline(ls)) inclinenumber(ls);\n              else next(ls);\n            }\n            goto no_save;\n          }\n          default: {\n            esccheck(ls, lisdigit(ls->current), \"invalid escape sequence\");\n            c = readdecesc(ls);  /* digital escape '\\ddd' */\n            goto only_save;\n          }\n        }\n       read_save:\n         next(ls);\n         /* go through */\n       only_save:\n         luaZ_buffremove(ls->buff, 1);  /* remove '\\\\' */\n         save(ls, c);\n         /* go through */\n       no_save: break;\n      }\n      default:\n        save_and_next(ls);\n    }\n  }\n  save_and_next(ls);  /* skip delimiter */\n  seminfo->ts = luaX_newstring(ls, luaZ_buffer(ls->buff) + 1,\n                                   luaZ_bufflen(ls->buff) - 2);\n}\n\n\nstatic int llex (LexState *ls, SemInfo *seminfo) {\n  luaZ_resetbuffer(ls->buff);\n  for (;;) {\n    switch (ls->current) {\n      case '\\n': case '\\r': {  /* line breaks */\n        inclinenumber(ls);\n        break;\n      }\n      case ' ': case '\\f': case '\\t': case '\\v': {  /* spaces */\n        next(ls);\n        break;\n      }\n      case '-': {  /* '-' or '--' (comment) */\n        next(ls);\n        if (ls->current != '-') return '-';\n        /* else is a comment */\n        next(ls);\n        if (ls->current == '[') {  /* long comment? */\n          size_t sep = skip_sep(ls);\n          luaZ_resetbuffer(ls->buff);  /* 'skip_sep' may dirty the buffer */\n          if (sep >= 2) {\n            read_long_string(ls, NULL, sep);  /* skip long comment */\n            luaZ_resetbuffer(ls->buff);  /* previous call may dirty the buff. */\n            break;\n          }\n        }\n        /* else short comment */\n        while (!currIsNewline(ls) && ls->current != EOZ)\n          next(ls);  /* skip until end of line (or end of file) */\n        break;\n      }\n      case '[': {  /* long string or simply '[' */\n        size_t sep = skip_sep(ls);\n        if (sep >= 2) {\n          read_long_string(ls, seminfo, sep);\n          return TK_STRING;\n        }\n        else if (sep == 0)  /* '[=...' missing second bracket? */\n          lexerror(ls, \"invalid long string delimiter\", TK_STRING);\n        return '[';\n      }\n      case '=': {\n        next(ls);\n        if (check_next1(ls, '=')) return TK_EQ;  /* '==' */\n        else return '=';\n      }\n      case '<': {\n        next(ls);\n        if (check_next1(ls, '=')) return TK_LE;  /* '<=' */\n        else if (check_next1(ls, '<')) return TK_SHL;  /* '<<' */\n        else return '<';\n      }\n      case '>': {\n        next(ls);\n        if (check_next1(ls, '=')) return TK_GE;  /* '>=' */\n        else if (check_next1(ls, '>')) return TK_SHR;  /* '>>' */\n        else return '>';\n      }\n      case '/': {\n        next(ls);\n        if (check_next1(ls, '/')) return TK_IDIV;  /* '//' */\n        else return '/';\n      }\n      case '~': {\n        next(ls);\n        if (check_next1(ls, '=')) return TK_NE;  /* '~=' */\n        else return '~';\n      }\n      case ':': {\n        next(ls);\n        if (check_next1(ls, ':')) return TK_DBCOLON;  /* '::' */\n        else return ':';\n      }\n      case '\"': case '\\'': {  /* short literal strings */\n        read_string(ls, ls->current, seminfo);\n        return TK_STRING;\n      }\n      case '.': {  /* '.', '..', '...', or number */\n        save_and_next(ls);\n        if (check_next1(ls, '.')) {\n          if (check_next1(ls, '.'))\n            return TK_DOTS;   /* '...' */\n          else return TK_CONCAT;   /* '..' */\n        }\n        else if (!lisdigit(ls->current)) return '.';\n        else return read_numeral(ls, seminfo);\n      }\n      case '0': case '1': case '2': case '3': case '4':\n      case '5': case '6': case '7': case '8': case '9': {\n        return read_numeral(ls, seminfo);\n      }\n      case EOZ: {\n        return TK_EOS;\n      }\n      default: {\n        if (lislalpha(ls->current)) {  /* identifier or reserved word? */\n          TString *ts;\n          do {\n            save_and_next(ls);\n          } while (lislalnum(ls->current));\n          ts = luaX_newstring(ls, luaZ_buffer(ls->buff),\n                                  luaZ_bufflen(ls->buff));\n          seminfo->ts = ts;\n          if (isreserved(ts))  /* reserved word? */\n            return ts->extra - 1 + FIRST_RESERVED;\n          else {\n            return TK_NAME;\n          }\n        }\n        else {  /* single-char tokens ('+', '*', '%', '{', '}', ...) */\n          int c = ls->current;\n          next(ls);\n          return c;\n        }\n      }\n    }\n  }\n}\n\n\nvoid luaX_next (LexState *ls) {\n  ls->lastline = ls->linenumber;\n  if (ls->lookahead.token != TK_EOS) {  /* is there a look-ahead token? */\n    ls->t = ls->lookahead;  /* use this one */\n    ls->lookahead.token = TK_EOS;  /* and discharge it */\n  }\n  else\n    ls->t.token = llex(ls, &ls->t.seminfo);  /* read next token */\n}\n\n\nint luaX_lookahead (LexState *ls) {\n  lua_assert(ls->lookahead.token == TK_EOS);\n  ls->lookahead.token = llex(ls, &ls->lookahead.seminfo);\n  return ls->lookahead.token;\n}\n\n/*\n** $Id: lcode.c $\n** Code generator for Lua\n** See Copyright Notice in lua.h\n*/\n\n#define lcode_c\n#define LUA_CORE\n\n/*#include \"lprefix.h\"*/\n\n\n#include <float.h>\n#include <limits.h>\n#include <math.h>\n#include <stdlib.h>\n\n/*#include \"lua.h\"*/\n\n/*#include \"lcode.h\"*/\n/*#include \"ldebug.h\"*/\n/*#include \"ldo.h\"*/\n/*#include \"lgc.h\"*/\n/*#include \"llex.h\"*/\n/*#include \"lmem.h\"*/\n/*#include \"lobject.h\"*/\n/*#include \"lopcodes.h\"*/\n/*#include \"lparser.h\"*/\n/*#include \"lstring.h\"*/\n/*#include \"ltable.h\"*/\n/*#include \"lvm.h\"*/\n\n\n/* Maximum number of registers in a Lua function (must fit in 8 bits) */\n#define MAXREGS\t\t255\n\n\n#define hasjumps(e)\t((e)->t != (e)->f)\n\n\nstatic int codesJ (FuncState *fs, OpCode o, int sj, int k);\n\n\n\n/* semantic error */\nl_noret luaK_semerror (LexState *ls, const char *msg) {\n  ls->t.token = 0;  /* remove \"near <token>\" from final message */\n  luaX_syntaxerror(ls, msg);\n}\n\n\n/*\n** If expression is a numeric constant, fills 'v' with its value\n** and returns 1. Otherwise, returns 0.\n*/\nstatic int tonumeral (const expdesc *e, TValue *v) {\n  if (hasjumps(e))\n    return 0;  /* not a numeral */\n  switch (e->k) {\n    case VKINT:\n      if (v) setivalue(v, e->u.ival);\n      return 1;\n    case VKFLT:\n      if (v) setfltvalue(v, e->u.nval);\n      return 1;\n    default: return 0;\n  }\n}\n\n\n/*\n** Get the constant value from a constant expression\n*/\nstatic TValue *const2val (FuncState *fs, const expdesc *e) {\n  lua_assert(e->k == VCONST);\n  return &fs->ls->dyd->actvar.arr[e->u.info].k;\n}\n\n\n/*\n** If expression is a constant, fills 'v' with its value\n** and returns 1. Otherwise, returns 0.\n*/\nint luaK_exp2const (FuncState *fs, const expdesc *e, TValue *v) {\n  if (hasjumps(e))\n    return 0;  /* not a constant */\n  switch (e->k) {\n    case VFALSE:\n      setbfvalue(v);\n      return 1;\n    case VTRUE:\n      setbtvalue(v);\n      return 1;\n    case VNIL:\n      setnilvalue(v);\n      return 1;\n    case VKSTR: {\n      setsvalue(fs->ls->L, v, e->u.strval);\n      return 1;\n    }\n    case VCONST: {\n      setobj(fs->ls->L, v, const2val(fs, e));\n      return 1;\n    }\n    default: return tonumeral(e, v);\n  }\n}\n\n\n/*\n** Return the previous instruction of the current code. If there\n** may be a jump target between the current instruction and the\n** previous one, return an invalid instruction (to avoid wrong\n** optimizations).\n*/\nstatic Instruction *previousinstruction (FuncState *fs) {\n  static const Instruction invalidinstruction = ~(Instruction)0;\n  if (fs->pc > fs->lasttarget)\n    return &fs->f->code[fs->pc - 1];  /* previous instruction */\n  else\n    return cast(Instruction*, &invalidinstruction);\n}\n\n\n/*\n** Create a OP_LOADNIL instruction, but try to optimize: if the previous\n** instruction is also OP_LOADNIL and ranges are compatible, adjust\n** range of previous instruction instead of emitting a new one. (For\n** instance, 'local a; local b' will generate a single opcode.)\n*/\nvoid luaK_nil (FuncState *fs, int from, int n) {\n  int l = from + n - 1;  /* last register to set nil */\n  Instruction *previous = previousinstruction(fs);\n  if (GET_OPCODE(*previous) == OP_LOADNIL) {  /* previous is LOADNIL? */\n    int pfrom = GETARG_A(*previous);  /* get previous range */\n    int pl = pfrom + GETARG_B(*previous);\n    if ((pfrom <= from && from <= pl + 1) ||\n        (from <= pfrom && pfrom <= l + 1)) {  /* can connect both? */\n      if (pfrom < from) from = pfrom;  /* from = min(from, pfrom) */\n      if (pl > l) l = pl;  /* l = max(l, pl) */\n      SETARG_A(*previous, from);\n      SETARG_B(*previous, l - from);\n      return;\n    }  /* else go through */\n  }\n  luaK_codeABC(fs, OP_LOADNIL, from, n - 1, 0);  /* else no optimization */\n}\n\n\n/*\n** Gets the destination address of a jump instruction. Used to traverse\n** a list of jumps.\n*/\nstatic int getjump (FuncState *fs, int pc) {\n  int offset = GETARG_sJ(fs->f->code[pc]);\n  if (offset == NO_JUMP)  /* point to itself represents end of list */\n    return NO_JUMP;  /* end of list */\n  else\n    return (pc+1)+offset;  /* turn offset into absolute position */\n}\n\n\n/*\n** Fix jump instruction at position 'pc' to jump to 'dest'.\n** (Jump addresses are relative in Lua)\n*/\nstatic void fixjump (FuncState *fs, int pc, int dest) {\n  Instruction *jmp = &fs->f->code[pc];\n  int offset = dest - (pc + 1);\n  lua_assert(dest != NO_JUMP);\n  if (!(-OFFSET_sJ <= offset && offset <= MAXARG_sJ - OFFSET_sJ))\n    luaX_syntaxerror(fs->ls, \"control structure too long\");\n  lua_assert(GET_OPCODE(*jmp) == OP_JMP);\n  SETARG_sJ(*jmp, offset);\n}\n\n\n/*\n** Concatenate jump-list 'l2' into jump-list 'l1'\n*/\nvoid luaK_concat (FuncState *fs, int *l1, int l2) {\n  if (l2 == NO_JUMP) return;  /* nothing to concatenate? */\n  else if (*l1 == NO_JUMP)  /* no original list? */\n    *l1 = l2;  /* 'l1' points to 'l2' */\n  else {\n    int list = *l1;\n    int next;\n    while ((next = getjump(fs, list)) != NO_JUMP)  /* find last element */\n      list = next;\n    fixjump(fs, list, l2);  /* last element links to 'l2' */\n  }\n}\n\n\n/*\n** Create a jump instruction and return its position, so its destination\n** can be fixed later (with 'fixjump').\n*/\nint luaK_jump (FuncState *fs) {\n  return codesJ(fs, OP_JMP, NO_JUMP, 0);\n}\n\n\n/*\n** Code a 'return' instruction\n*/\nvoid luaK_ret (FuncState *fs, int first, int nret) {\n  OpCode op;\n  switch (nret) {\n    case 0: op = OP_RETURN0; break;\n    case 1: op = OP_RETURN1; break;\n    default: op = OP_RETURN; break;\n  }\n  luaK_codeABC(fs, op, first, nret + 1, 0);\n}\n\n\n/*\n** Code a \"conditional jump\", that is, a test or comparison opcode\n** followed by a jump. Return jump position.\n*/\nstatic int condjump (FuncState *fs, OpCode op, int A, int B, int C, int k) {\n  luaK_codeABCk(fs, op, A, B, C, k);\n  return luaK_jump(fs);\n}\n\n\n/*\n** returns current 'pc' and marks it as a jump target (to avoid wrong\n** optimizations with consecutive instructions not in the same basic block).\n*/\nint luaK_getlabel (FuncState *fs) {\n  fs->lasttarget = fs->pc;\n  return fs->pc;\n}\n\n\n/*\n** Returns the position of the instruction \"controlling\" a given\n** jump (that is, its condition), or the jump itself if it is\n** unconditional.\n*/\nstatic Instruction *getjumpcontrol (FuncState *fs, int pc) {\n  Instruction *pi = &fs->f->code[pc];\n  if (pc >= 1 && testTMode(GET_OPCODE(*(pi-1))))\n    return pi-1;\n  else\n    return pi;\n}\n\n\n/*\n** Patch destination register for a TESTSET instruction.\n** If instruction in position 'node' is not a TESTSET, return 0 (\"fails\").\n** Otherwise, if 'reg' is not 'NO_REG', set it as the destination\n** register. Otherwise, change instruction to a simple 'TEST' (produces\n** no register value)\n*/\nstatic int patchtestreg (FuncState *fs, int node, int reg) {\n  Instruction *i = getjumpcontrol(fs, node);\n  if (GET_OPCODE(*i) != OP_TESTSET)\n    return 0;  /* cannot patch other instructions */\n  if (reg != NO_REG && reg != GETARG_B(*i))\n    SETARG_A(*i, reg);\n  else {\n     /* no register to put value or register already has the value;\n        change instruction to simple test */\n    *i = CREATE_ABCk(OP_TEST, GETARG_B(*i), 0, 0, GETARG_k(*i));\n  }\n  return 1;\n}\n\n\n/*\n** Traverse a list of tests ensuring no one produces a value\n*/\nstatic void removevalues (FuncState *fs, int list) {\n  for (; list != NO_JUMP; list = getjump(fs, list))\n      patchtestreg(fs, list, NO_REG);\n}\n\n\n/*\n** Traverse a list of tests, patching their destination address and\n** registers: tests producing values jump to 'vtarget' (and put their\n** values in 'reg'), other tests jump to 'dtarget'.\n*/\nstatic void patchlistaux (FuncState *fs, int list, int vtarget, int reg,\n                          int dtarget) {\n  while (list != NO_JUMP) {\n    int next = getjump(fs, list);\n    if (patchtestreg(fs, list, reg))\n      fixjump(fs, list, vtarget);\n    else\n      fixjump(fs, list, dtarget);  /* jump to default target */\n    list = next;\n  }\n}\n\n\n/*\n** Path all jumps in 'list' to jump to 'target'.\n** (The assert means that we cannot fix a jump to a forward address\n** because we only know addresses once code is generated.)\n*/\nvoid luaK_patchlist (FuncState *fs, int list, int target) {\n  lua_assert(target <= fs->pc);\n  patchlistaux(fs, list, target, NO_REG, target);\n}\n\n\nvoid luaK_patchtohere (FuncState *fs, int list) {\n  int hr = luaK_getlabel(fs);  /* mark \"here\" as a jump target */\n  luaK_patchlist(fs, list, hr);\n}\n\n\n/* limit for difference between lines in relative line info. */\n#define LIMLINEDIFF\t0x80\n\n\n/*\n** Save line info for a new instruction. If difference from last line\n** does not fit in a byte, of after that many instructions, save a new\n** absolute line info; (in that case, the special value 'ABSLINEINFO'\n** in 'lineinfo' signals the existence of this absolute information.)\n** Otherwise, store the difference from last line in 'lineinfo'.\n*/\nstatic void savelineinfo (FuncState *fs, Proto *f, int line) {\n  int linedif = line - fs->previousline;\n  int pc = fs->pc - 1;  /* last instruction coded */\n  if (abs(linedif) >= LIMLINEDIFF || fs->iwthabs++ >= MAXIWTHABS) {\n    luaM_growvector(fs->ls->L, f->abslineinfo, fs->nabslineinfo,\n                    f->sizeabslineinfo, AbsLineInfo, MAX_INT, \"lines\");\n    f->abslineinfo[fs->nabslineinfo].pc = pc;\n    f->abslineinfo[fs->nabslineinfo++].line = line;\n    linedif = ABSLINEINFO;  /* signal that there is absolute information */\n    fs->iwthabs = 1;  /* restart counter */\n  }\n  luaM_growvector(fs->ls->L, f->lineinfo, pc, f->sizelineinfo, ls_byte,\n                  MAX_INT, \"opcodes\");\n  f->lineinfo[pc] = linedif;\n  fs->previousline = line;  /* last line saved */\n}\n\n\n/*\n** Remove line information from the last instruction.\n** If line information for that instruction is absolute, set 'iwthabs'\n** above its max to force the new (replacing) instruction to have\n** absolute line info, too.\n*/\nstatic void removelastlineinfo (FuncState *fs) {\n  Proto *f = fs->f;\n  int pc = fs->pc - 1;  /* last instruction coded */\n  if (f->lineinfo[pc] != ABSLINEINFO) {  /* relative line info? */\n    fs->previousline -= f->lineinfo[pc];  /* correct last line saved */\n    fs->iwthabs--;  /* undo previous increment */\n  }\n  else {  /* absolute line information */\n    lua_assert(f->abslineinfo[fs->nabslineinfo - 1].pc == pc);\n    fs->nabslineinfo--;  /* remove it */\n    fs->iwthabs = MAXIWTHABS + 1;  /* force next line info to be absolute */\n  }\n}\n\n\n/*\n** Remove the last instruction created, correcting line information\n** accordingly.\n*/\nstatic void removelastinstruction (FuncState *fs) {\n  removelastlineinfo(fs);\n  fs->pc--;\n}\n\n\n/*\n** Emit instruction 'i', checking for array sizes and saving also its\n** line information. Return 'i' position.\n*/\nint luaK_code (FuncState *fs, Instruction i) {\n  Proto *f = fs->f;\n  /* put new instruction in code array */\n  luaM_growvector(fs->ls->L, f->code, fs->pc, f->sizecode, Instruction,\n                  MAX_INT, \"opcodes\");\n  f->code[fs->pc++] = i;\n  savelineinfo(fs, f, fs->ls->lastline);\n  return fs->pc - 1;  /* index of new instruction */\n}\n\n\n/*\n** Format and emit an 'iABC' instruction. (Assertions check consistency\n** of parameters versus opcode.)\n*/\nint luaK_codeABCk (FuncState *fs, OpCode o, int a, int b, int c, int k) {\n  lua_assert(getOpMode(o) == iABC);\n  lua_assert(a <= MAXARG_A && b <= MAXARG_B &&\n             c <= MAXARG_C && (k & ~1) == 0);\n  return luaK_code(fs, CREATE_ABCk(o, a, b, c, k));\n}\n\n\n/*\n** Format and emit an 'iABx' instruction.\n*/\nint luaK_codeABx (FuncState *fs, OpCode o, int a, unsigned int bc) {\n  lua_assert(getOpMode(o) == iABx);\n  lua_assert(a <= MAXARG_A && bc <= MAXARG_Bx);\n  return luaK_code(fs, CREATE_ABx(o, a, bc));\n}\n\n\n/*\n** Format and emit an 'iAsBx' instruction.\n*/\nint luaK_codeAsBx (FuncState *fs, OpCode o, int a, int bc) {\n  unsigned int b = bc + OFFSET_sBx;\n  lua_assert(getOpMode(o) == iAsBx);\n  lua_assert(a <= MAXARG_A && b <= MAXARG_Bx);\n  return luaK_code(fs, CREATE_ABx(o, a, b));\n}\n\n\n/*\n** Format and emit an 'isJ' instruction.\n*/\nstatic int codesJ (FuncState *fs, OpCode o, int sj, int k) {\n  unsigned int j = sj + OFFSET_sJ;\n  lua_assert(getOpMode(o) == isJ);\n  lua_assert(j <= MAXARG_sJ && (k & ~1) == 0);\n  return luaK_code(fs, CREATE_sJ(o, j, k));\n}\n\n\n/*\n** Emit an \"extra argument\" instruction (format 'iAx')\n*/\nstatic int codeextraarg (FuncState *fs, int a) {\n  lua_assert(a <= MAXARG_Ax);\n  return luaK_code(fs, CREATE_Ax(OP_EXTRAARG, a));\n}\n\n\n/*\n** Emit a \"load constant\" instruction, using either 'OP_LOADK'\n** (if constant index 'k' fits in 18 bits) or an 'OP_LOADKX'\n** instruction with \"extra argument\".\n*/\nstatic int luaK_codek (FuncState *fs, int reg, int k) {\n  if (k <= MAXARG_Bx)\n    return luaK_codeABx(fs, OP_LOADK, reg, k);\n  else {\n    int p = luaK_codeABx(fs, OP_LOADKX, reg, 0);\n    codeextraarg(fs, k);\n    return p;\n  }\n}\n\n\n/*\n** Check register-stack level, keeping track of its maximum size\n** in field 'maxstacksize'\n*/\nvoid luaK_checkstack (FuncState *fs, int n) {\n  int newstack = fs->freereg + n;\n  if (newstack > fs->f->maxstacksize) {\n    if (newstack >= MAXREGS)\n      luaX_syntaxerror(fs->ls,\n        \"function or expression needs too many registers\");\n    fs->f->maxstacksize = cast_byte(newstack);\n  }\n}\n\n\n/*\n** Reserve 'n' registers in register stack\n*/\nvoid luaK_reserveregs (FuncState *fs, int n) {\n  luaK_checkstack(fs, n);\n  fs->freereg += n;\n}\n\n\n/*\n** Free register 'reg', if it is neither a constant index nor\n** a local variable.\n)\n*/\nstatic void freereg (FuncState *fs, int reg) {\n  if (reg >= luaY_nvarstack(fs)) {\n    fs->freereg--;\n    lua_assert(reg == fs->freereg);\n  }\n}\n\n\n/*\n** Free two registers in proper order\n*/\nstatic void freeregs (FuncState *fs, int r1, int r2) {\n  if (r1 > r2) {\n    freereg(fs, r1);\n    freereg(fs, r2);\n  }\n  else {\n    freereg(fs, r2);\n    freereg(fs, r1);\n  }\n}\n\n\n/*\n** Free register used by expression 'e' (if any)\n*/\nstatic void freeexp (FuncState *fs, expdesc *e) {\n  if (e->k == VNONRELOC)\n    freereg(fs, e->u.info);\n}\n\n\n/*\n** Free registers used by expressions 'e1' and 'e2' (if any) in proper\n** order.\n*/\nstatic void freeexps (FuncState *fs, expdesc *e1, expdesc *e2) {\n  int r1 = (e1->k == VNONRELOC) ? e1->u.info : -1;\n  int r2 = (e2->k == VNONRELOC) ? e2->u.info : -1;\n  freeregs(fs, r1, r2);\n}\n\n\n/*\n** Add constant 'v' to prototype's list of constants (field 'k').\n** Use scanner's table to cache position of constants in constant list\n** and try to reuse constants. Because some values should not be used\n** as keys (nil cannot be a key, integer keys can collapse with float\n** keys), the caller must provide a useful 'key' for indexing the cache.\n** Note that all functions share the same table, so entering or exiting\n** a function can make some indices wrong.\n*/\nstatic int addk (FuncState *fs, TValue *key, TValue *v) {\n  TValue val;\n  lua_State *L = fs->ls->L;\n  Proto *f = fs->f;\n  const TValue *idx = luaH_get(fs->ls->h, key);  /* query scanner table */\n  int k, oldsize;\n  if (ttisinteger(idx)) {  /* is there an index there? */\n    k = cast_int(ivalue(idx));\n    /* correct value? (warning: must distinguish floats from integers!) */\n    if (k < fs->nk && ttypetag(&f->k[k]) == ttypetag(v) &&\n                      luaV_rawequalobj(&f->k[k], v))\n      return k;  /* reuse index */\n  }\n  /* constant not found; create a new entry */\n  oldsize = f->sizek;\n  k = fs->nk;\n  /* numerical value does not need GC barrier;\n     table has no metatable, so it does not need to invalidate cache */\n  setivalue(&val, k);\n  luaH_finishset(L, fs->ls->h, key, idx, &val);\n  luaM_growvector(L, f->k, k, f->sizek, TValue, MAXARG_Ax, \"constants\");\n  while (oldsize < f->sizek) setnilvalue(&f->k[oldsize++]);\n  setobj(L, &f->k[k], v);\n  fs->nk++;\n  luaC_barrier(L, f, v);\n  return k;\n}\n\n\n/*\n** Add a string to list of constants and return its index.\n*/\nstatic int stringK (FuncState *fs, TString *s) {\n  TValue o;\n  setsvalue(fs->ls->L, &o, s);\n  return addk(fs, &o, &o);  /* use string itself as key */\n}\n\n\n/*\n** Add an integer to list of constants and return its index.\n*/\nstatic int luaK_intK (FuncState *fs, lua_Integer n) {\n  TValue o;\n  setivalue(&o, n);\n  return addk(fs, &o, &o);  /* use integer itself as key */\n}\n\n/*\n** Add a float to list of constants and return its index. Floats\n** with integral values need a different key, to avoid collision\n** with actual integers. To that, we add to the number its smaller\n** power-of-two fraction that is still significant in its scale.\n** For doubles, that would be 1/2^52.\n** (This method is not bulletproof: there may be another float\n** with that value, and for floats larger than 2^53 the result is\n** still an integer. At worst, this only wastes an entry with\n** a duplicate.)\n*/\nstatic int luaK_numberK (FuncState *fs, lua_Number r) {\n  TValue o;\n  lua_Integer ik;\n  setfltvalue(&o, r);\n  if (!luaV_flttointeger(r, &ik, F2Ieq))  /* not an integral value? */\n    return addk(fs, &o, &o);  /* use number itself as key */\n  else {  /* must build an alternative key */\n    const int nbm = l_floatatt(MANT_DIG);\n    const lua_Number q = l_mathop(ldexp)(l_mathop(1.0), -nbm + 1);\n    const lua_Number k = (ik == 0) ? q : r + r*q;  /* new key */\n    TValue kv;\n    setfltvalue(&kv, k);\n    /* result is not an integral value, unless value is too large */\n    lua_assert(!luaV_flttointeger(k, &ik, F2Ieq) ||\n                l_mathop(fabs)(r) >= l_mathop(1e6));\n    return addk(fs, &kv, &o);\n  }\n}\n\n\n/*\n** Add a false to list of constants and return its index.\n*/\nstatic int boolF (FuncState *fs) {\n  TValue o;\n  setbfvalue(&o);\n  return addk(fs, &o, &o);  /* use boolean itself as key */\n}\n\n\n/*\n** Add a true to list of constants and return its index.\n*/\nstatic int boolT (FuncState *fs) {\n  TValue o;\n  setbtvalue(&o);\n  return addk(fs, &o, &o);  /* use boolean itself as key */\n}\n\n\n/*\n** Add nil to list of constants and return its index.\n*/\nstatic int nilK (FuncState *fs) {\n  TValue k, v;\n  setnilvalue(&v);\n  /* cannot use nil as key; instead use table itself to represent nil */\n  sethvalue(fs->ls->L, &k, fs->ls->h);\n  return addk(fs, &k, &v);\n}\n\n\n/*\n** Check whether 'i' can be stored in an 'sC' operand. Equivalent to\n** (0 <= int2sC(i) && int2sC(i) <= MAXARG_C) but without risk of\n** overflows in the hidden addition inside 'int2sC'.\n*/\nstatic int fitsC (lua_Integer i) {\n  return (l_castS2U(i) + OFFSET_sC <= cast_uint(MAXARG_C));\n}\n\n\n/*\n** Check whether 'i' can be stored in an 'sBx' operand.\n*/\nstatic int fitsBx (lua_Integer i) {\n  return (-OFFSET_sBx <= i && i <= MAXARG_Bx - OFFSET_sBx);\n}\n\n\nvoid luaK_int (FuncState *fs, int reg, lua_Integer i) {\n  if (fitsBx(i))\n    luaK_codeAsBx(fs, OP_LOADI, reg, cast_int(i));\n  else\n    luaK_codek(fs, reg, luaK_intK(fs, i));\n}\n\n\nstatic void luaK_float (FuncState *fs, int reg, lua_Number f) {\n  lua_Integer fi;\n  if (luaV_flttointeger(f, &fi, F2Ieq) && fitsBx(fi))\n    luaK_codeAsBx(fs, OP_LOADF, reg, cast_int(fi));\n  else\n    luaK_codek(fs, reg, luaK_numberK(fs, f));\n}\n\n\n/*\n** Convert a constant in 'v' into an expression description 'e'\n*/\nstatic void const2exp (TValue *v, expdesc *e) {\n  switch (ttypetag(v)) {\n    case LUA_VNUMINT:\n      e->k = VKINT; e->u.ival = ivalue(v);\n      break;\n    case LUA_VNUMFLT:\n      e->k = VKFLT; e->u.nval = fltvalue(v);\n      break;\n    case LUA_VFALSE:\n      e->k = VFALSE;\n      break;\n    case LUA_VTRUE:\n      e->k = VTRUE;\n      break;\n    case LUA_VNIL:\n      e->k = VNIL;\n      break;\n    case LUA_VSHRSTR:  case LUA_VLNGSTR:\n      e->k = VKSTR; e->u.strval = tsvalue(v);\n      break;\n    default: lua_assert(0);\n  }\n}\n\n\n/*\n** Fix an expression to return the number of results 'nresults'.\n** 'e' must be a multi-ret expression (function call or vararg).\n*/\nvoid luaK_setreturns (FuncState *fs, expdesc *e, int nresults) {\n  Instruction *pc = &getinstruction(fs, e);\n  if (e->k == VCALL)  /* expression is an open function call? */\n    SETARG_C(*pc, nresults + 1);\n  else {\n    lua_assert(e->k == VVARARG);\n    SETARG_C(*pc, nresults + 1);\n    SETARG_A(*pc, fs->freereg);\n    luaK_reserveregs(fs, 1);\n  }\n}\n\n\n/*\n** Convert a VKSTR to a VK\n*/\nstatic void str2K (FuncState *fs, expdesc *e) {\n  lua_assert(e->k == VKSTR);\n  e->u.info = stringK(fs, e->u.strval);\n  e->k = VK;\n}\n\n\n/*\n** Fix an expression to return one result.\n** If expression is not a multi-ret expression (function call or\n** vararg), it already returns one result, so nothing needs to be done.\n** Function calls become VNONRELOC expressions (as its result comes\n** fixed in the base register of the call), while vararg expressions\n** become VRELOC (as OP_VARARG puts its results where it wants).\n** (Calls are created returning one result, so that does not need\n** to be fixed.)\n*/\nvoid luaK_setoneret (FuncState *fs, expdesc *e) {\n  if (e->k == VCALL) {  /* expression is an open function call? */\n    /* already returns 1 value */\n    lua_assert(GETARG_C(getinstruction(fs, e)) == 2);\n    e->k = VNONRELOC;  /* result has fixed position */\n    e->u.info = GETARG_A(getinstruction(fs, e));\n  }\n  else if (e->k == VVARARG) {\n    SETARG_C(getinstruction(fs, e), 2);\n    e->k = VRELOC;  /* can relocate its simple result */\n  }\n}\n\n\n/*\n** Ensure that expression 'e' is not a variable (nor a <const>).\n** (Expression still may have jump lists.)\n*/\nvoid luaK_dischargevars (FuncState *fs, expdesc *e) {\n  switch (e->k) {\n    case VCONST: {\n      const2exp(const2val(fs, e), e);\n      break;\n    }\n    case VLOCAL: {  /* already in a register */\n      e->u.info = e->u.var.ridx;\n      e->k = VNONRELOC;  /* becomes a non-relocatable value */\n      break;\n    }\n    case VUPVAL: {  /* move value to some (pending) register */\n      e->u.info = luaK_codeABC(fs, OP_GETUPVAL, 0, e->u.info, 0);\n      e->k = VRELOC;\n      break;\n    }\n    case VINDEXUP: {\n      e->u.info = luaK_codeABC(fs, OP_GETTABUP, 0, e->u.ind.t, e->u.ind.idx);\n      e->k = VRELOC;\n      break;\n    }\n    case VINDEXI: {\n      freereg(fs, e->u.ind.t);\n      e->u.info = luaK_codeABC(fs, OP_GETI, 0, e->u.ind.t, e->u.ind.idx);\n      e->k = VRELOC;\n      break;\n    }\n    case VINDEXSTR: {\n      freereg(fs, e->u.ind.t);\n      e->u.info = luaK_codeABC(fs, OP_GETFIELD, 0, e->u.ind.t, e->u.ind.idx);\n      e->k = VRELOC;\n      break;\n    }\n    case VINDEXED: {\n      freeregs(fs, e->u.ind.t, e->u.ind.idx);\n      e->u.info = luaK_codeABC(fs, OP_GETTABLE, 0, e->u.ind.t, e->u.ind.idx);\n      e->k = VRELOC;\n      break;\n    }\n    case VVARARG: case VCALL: {\n      luaK_setoneret(fs, e);\n      break;\n    }\n    default: break;  /* there is one value available (somewhere) */\n  }\n}\n\n\n/*\n** Ensure expression value is in register 'reg', making 'e' a\n** non-relocatable expression.\n** (Expression still may have jump lists.)\n*/\nstatic void discharge2reg (FuncState *fs, expdesc *e, int reg) {\n  luaK_dischargevars(fs, e);\n  switch (e->k) {\n    case VNIL: {\n      luaK_nil(fs, reg, 1);\n      break;\n    }\n    case VFALSE: {\n      luaK_codeABC(fs, OP_LOADFALSE, reg, 0, 0);\n      break;\n    }\n    case VTRUE: {\n      luaK_codeABC(fs, OP_LOADTRUE, reg, 0, 0);\n      break;\n    }\n    case VKSTR: {\n      str2K(fs, e);\n    }  /* FALLTHROUGH */\n    case VK: {\n      luaK_codek(fs, reg, e->u.info);\n      break;\n    }\n    case VKFLT: {\n      luaK_float(fs, reg, e->u.nval);\n      break;\n    }\n    case VKINT: {\n      luaK_int(fs, reg, e->u.ival);\n      break;\n    }\n    case VRELOC: {\n      Instruction *pc = &getinstruction(fs, e);\n      SETARG_A(*pc, reg);  /* instruction will put result in 'reg' */\n      break;\n    }\n    case VNONRELOC: {\n      if (reg != e->u.info)\n        luaK_codeABC(fs, OP_MOVE, reg, e->u.info, 0);\n      break;\n    }\n    default: {\n      lua_assert(e->k == VJMP);\n      return;  /* nothing to do... */\n    }\n  }\n  e->u.info = reg;\n  e->k = VNONRELOC;\n}\n\n\n/*\n** Ensure expression value is in a register, making 'e' a\n** non-relocatable expression.\n** (Expression still may have jump lists.)\n*/\nstatic void discharge2anyreg (FuncState *fs, expdesc *e) {\n  if (e->k != VNONRELOC) {  /* no fixed register yet? */\n    luaK_reserveregs(fs, 1);  /* get a register */\n    discharge2reg(fs, e, fs->freereg-1);  /* put value there */\n  }\n}\n\n\nstatic int code_loadbool (FuncState *fs, int A, OpCode op) {\n  luaK_getlabel(fs);  /* those instructions may be jump targets */\n  return luaK_codeABC(fs, op, A, 0, 0);\n}\n\n\n/*\n** check whether list has any jump that do not produce a value\n** or produce an inverted value\n*/\nstatic int need_value (FuncState *fs, int list) {\n  for (; list != NO_JUMP; list = getjump(fs, list)) {\n    Instruction i = *getjumpcontrol(fs, list);\n    if (GET_OPCODE(i) != OP_TESTSET) return 1;\n  }\n  return 0;  /* not found */\n}\n\n\n/*\n** Ensures final expression result (which includes results from its\n** jump lists) is in register 'reg'.\n** If expression has jumps, need to patch these jumps either to\n** its final position or to \"load\" instructions (for those tests\n** that do not produce values).\n*/\nstatic void exp2reg (FuncState *fs, expdesc *e, int reg) {\n  discharge2reg(fs, e, reg);\n  if (e->k == VJMP)  /* expression itself is a test? */\n    luaK_concat(fs, &e->t, e->u.info);  /* put this jump in 't' list */\n  if (hasjumps(e)) {\n    int final;  /* position after whole expression */\n    int p_f = NO_JUMP;  /* position of an eventual LOAD false */\n    int p_t = NO_JUMP;  /* position of an eventual LOAD true */\n    if (need_value(fs, e->t) || need_value(fs, e->f)) {\n      int fj = (e->k == VJMP) ? NO_JUMP : luaK_jump(fs);\n      p_f = code_loadbool(fs, reg, OP_LFALSESKIP);  /* skip next inst. */\n      p_t = code_loadbool(fs, reg, OP_LOADTRUE);\n      /* jump around these booleans if 'e' is not a test */\n      luaK_patchtohere(fs, fj);\n    }\n    final = luaK_getlabel(fs);\n    patchlistaux(fs, e->f, final, reg, p_f);\n    patchlistaux(fs, e->t, final, reg, p_t);\n  }\n  e->f = e->t = NO_JUMP;\n  e->u.info = reg;\n  e->k = VNONRELOC;\n}\n\n\n/*\n** Ensures final expression result is in next available register.\n*/\nvoid luaK_exp2nextreg (FuncState *fs, expdesc *e) {\n  luaK_dischargevars(fs, e);\n  freeexp(fs, e);\n  luaK_reserveregs(fs, 1);\n  exp2reg(fs, e, fs->freereg - 1);\n}\n\n\n/*\n** Ensures final expression result is in some (any) register\n** and return that register.\n*/\nint luaK_exp2anyreg (FuncState *fs, expdesc *e) {\n  luaK_dischargevars(fs, e);\n  if (e->k == VNONRELOC) {  /* expression already has a register? */\n    if (!hasjumps(e))  /* no jumps? */\n      return e->u.info;  /* result is already in a register */\n    if (e->u.info >= luaY_nvarstack(fs)) {  /* reg. is not a local? */\n      exp2reg(fs, e, e->u.info);  /* put final result in it */\n      return e->u.info;\n    }\n    /* else expression has jumps and cannot change its register\n       to hold the jump values, because it is a local variable.\n       Go through to the default case. */\n  }\n  luaK_exp2nextreg(fs, e);  /* default: use next available register */\n  return e->u.info;\n}\n\n\n/*\n** Ensures final expression result is either in a register\n** or in an upvalue.\n*/\nvoid luaK_exp2anyregup (FuncState *fs, expdesc *e) {\n  if (e->k != VUPVAL || hasjumps(e))\n    luaK_exp2anyreg(fs, e);\n}\n\n\n/*\n** Ensures final expression result is either in a register\n** or it is a constant.\n*/\nvoid luaK_exp2val (FuncState *fs, expdesc *e) {\n  if (hasjumps(e))\n    luaK_exp2anyreg(fs, e);\n  else\n    luaK_dischargevars(fs, e);\n}\n\n\n/*\n** Try to make 'e' a K expression with an index in the range of R/K\n** indices. Return true iff succeeded.\n*/\nstatic int luaK_exp2K (FuncState *fs, expdesc *e) {\n  if (!hasjumps(e)) {\n    int info;\n    switch (e->k) {  /* move constants to 'k' */\n      case VTRUE: info = boolT(fs); break;\n      case VFALSE: info = boolF(fs); break;\n      case VNIL: info = nilK(fs); break;\n      case VKINT: info = luaK_intK(fs, e->u.ival); break;\n      case VKFLT: info = luaK_numberK(fs, e->u.nval); break;\n      case VKSTR: info = stringK(fs, e->u.strval); break;\n      case VK: info = e->u.info; break;\n      default: return 0;  /* not a constant */\n    }\n    if (info <= MAXINDEXRK) {  /* does constant fit in 'argC'? */\n      e->k = VK;  /* make expression a 'K' expression */\n      e->u.info = info;\n      return 1;\n    }\n  }\n  /* else, expression doesn't fit; leave it unchanged */\n  return 0;\n}\n\n\n/*\n** Ensures final expression result is in a valid R/K index\n** (that is, it is either in a register or in 'k' with an index\n** in the range of R/K indices).\n** Returns 1 iff expression is K.\n*/\nint luaK_exp2RK (FuncState *fs, expdesc *e) {\n  if (luaK_exp2K(fs, e))\n    return 1;\n  else {  /* not a constant in the right range: put it in a register */\n    luaK_exp2anyreg(fs, e);\n    return 0;\n  }\n}\n\n\nstatic void codeABRK (FuncState *fs, OpCode o, int a, int b,\n                      expdesc *ec) {\n  int k = luaK_exp2RK(fs, ec);\n  luaK_codeABCk(fs, o, a, b, ec->u.info, k);\n}\n\n\n/*\n** Generate code to store result of expression 'ex' into variable 'var'.\n*/\nvoid luaK_storevar (FuncState *fs, expdesc *var, expdesc *ex) {\n  switch (var->k) {\n    case VLOCAL: {\n      freeexp(fs, ex);\n      exp2reg(fs, ex, var->u.var.ridx);  /* compute 'ex' into proper place */\n      return;\n    }\n    case VUPVAL: {\n      int e = luaK_exp2anyreg(fs, ex);\n      luaK_codeABC(fs, OP_SETUPVAL, e, var->u.info, 0);\n      break;\n    }\n    case VINDEXUP: {\n      codeABRK(fs, OP_SETTABUP, var->u.ind.t, var->u.ind.idx, ex);\n      break;\n    }\n    case VINDEXI: {\n      codeABRK(fs, OP_SETI, var->u.ind.t, var->u.ind.idx, ex);\n      break;\n    }\n    case VINDEXSTR: {\n      codeABRK(fs, OP_SETFIELD, var->u.ind.t, var->u.ind.idx, ex);\n      break;\n    }\n    case VINDEXED: {\n      codeABRK(fs, OP_SETTABLE, var->u.ind.t, var->u.ind.idx, ex);\n      break;\n    }\n    default: lua_assert(0);  /* invalid var kind to store */\n  }\n  freeexp(fs, ex);\n}\n\n\n/*\n** Emit SELF instruction (convert expression 'e' into 'e:key(e,').\n*/\nvoid luaK_self (FuncState *fs, expdesc *e, expdesc *key) {\n  int ereg;\n  luaK_exp2anyreg(fs, e);\n  ereg = e->u.info;  /* register where 'e' was placed */\n  freeexp(fs, e);\n  e->u.info = fs->freereg;  /* base register for op_self */\n  e->k = VNONRELOC;  /* self expression has a fixed register */\n  luaK_reserveregs(fs, 2);  /* function and 'self' produced by op_self */\n  codeABRK(fs, OP_SELF, e->u.info, ereg, key);\n  freeexp(fs, key);\n}\n\n\n/*\n** Negate condition 'e' (where 'e' is a comparison).\n*/\nstatic void negatecondition (FuncState *fs, expdesc *e) {\n  Instruction *pc = getjumpcontrol(fs, e->u.info);\n  lua_assert(testTMode(GET_OPCODE(*pc)) && GET_OPCODE(*pc) != OP_TESTSET &&\n                                           GET_OPCODE(*pc) != OP_TEST);\n  SETARG_k(*pc, (GETARG_k(*pc) ^ 1));\n}\n\n\n/*\n** Emit instruction to jump if 'e' is 'cond' (that is, if 'cond'\n** is true, code will jump if 'e' is true.) Return jump position.\n** Optimize when 'e' is 'not' something, inverting the condition\n** and removing the 'not'.\n*/\nstatic int jumponcond (FuncState *fs, expdesc *e, int cond) {\n  if (e->k == VRELOC) {\n    Instruction ie = getinstruction(fs, e);\n    if (GET_OPCODE(ie) == OP_NOT) {\n      removelastinstruction(fs);  /* remove previous OP_NOT */\n      return condjump(fs, OP_TEST, GETARG_B(ie), 0, 0, !cond);\n    }\n    /* else go through */\n  }\n  discharge2anyreg(fs, e);\n  freeexp(fs, e);\n  return condjump(fs, OP_TESTSET, NO_REG, e->u.info, 0, cond);\n}\n\n\n/*\n** Emit code to go through if 'e' is true, jump otherwise.\n*/\nvoid luaK_goiftrue (FuncState *fs, expdesc *e) {\n  int pc;  /* pc of new jump */\n  luaK_dischargevars(fs, e);\n  switch (e->k) {\n    case VJMP: {  /* condition? */\n      negatecondition(fs, e);  /* jump when it is false */\n      pc = e->u.info;  /* save jump position */\n      break;\n    }\n    case VK: case VKFLT: case VKINT: case VKSTR: case VTRUE: {\n      pc = NO_JUMP;  /* always true; do nothing */\n      break;\n    }\n    default: {\n      pc = jumponcond(fs, e, 0);  /* jump when false */\n      break;\n    }\n  }\n  luaK_concat(fs, &e->f, pc);  /* insert new jump in false list */\n  luaK_patchtohere(fs, e->t);  /* true list jumps to here (to go through) */\n  e->t = NO_JUMP;\n}\n\n\n/*\n** Emit code to go through if 'e' is false, jump otherwise.\n*/\nvoid luaK_goiffalse (FuncState *fs, expdesc *e) {\n  int pc;  /* pc of new jump */\n  luaK_dischargevars(fs, e);\n  switch (e->k) {\n    case VJMP: {\n      pc = e->u.info;  /* already jump if true */\n      break;\n    }\n    case VNIL: case VFALSE: {\n      pc = NO_JUMP;  /* always false; do nothing */\n      break;\n    }\n    default: {\n      pc = jumponcond(fs, e, 1);  /* jump if true */\n      break;\n    }\n  }\n  luaK_concat(fs, &e->t, pc);  /* insert new jump in 't' list */\n  luaK_patchtohere(fs, e->f);  /* false list jumps to here (to go through) */\n  e->f = NO_JUMP;\n}\n\n\n/*\n** Code 'not e', doing constant folding.\n*/\nstatic void codenot (FuncState *fs, expdesc *e) {\n  switch (e->k) {\n    case VNIL: case VFALSE: {\n      e->k = VTRUE;  /* true == not nil == not false */\n      break;\n    }\n    case VK: case VKFLT: case VKINT: case VKSTR: case VTRUE: {\n      e->k = VFALSE;  /* false == not \"x\" == not 0.5 == not 1 == not true */\n      break;\n    }\n    case VJMP: {\n      negatecondition(fs, e);\n      break;\n    }\n    case VRELOC:\n    case VNONRELOC: {\n      discharge2anyreg(fs, e);\n      freeexp(fs, e);\n      e->u.info = luaK_codeABC(fs, OP_NOT, 0, e->u.info, 0);\n      e->k = VRELOC;\n      break;\n    }\n    default: lua_assert(0);  /* cannot happen */\n  }\n  /* interchange true and false lists */\n  { int temp = e->f; e->f = e->t; e->t = temp; }\n  removevalues(fs, e->f);  /* values are useless when negated */\n  removevalues(fs, e->t);\n}\n\n\n/*\n** Check whether expression 'e' is a small literal string\n*/\nstatic int isKstr (FuncState *fs, expdesc *e) {\n  return (e->k == VK && !hasjumps(e) && e->u.info <= MAXARG_B &&\n          ttisshrstring(&fs->f->k[e->u.info]));\n}\n\n/*\n** Check whether expression 'e' is a literal integer.\n*/\nint luaK_isKint (expdesc *e) {\n  return (e->k == VKINT && !hasjumps(e));\n}\n\n\n/*\n** Check whether expression 'e' is a literal integer in\n** proper range to fit in register C\n*/\nstatic int isCint (expdesc *e) {\n  return luaK_isKint(e) && (l_castS2U(e->u.ival) <= l_castS2U(MAXARG_C));\n}\n\n\n/*\n** Check whether expression 'e' is a literal integer in\n** proper range to fit in register sC\n*/\nstatic int isSCint (expdesc *e) {\n  return luaK_isKint(e) && fitsC(e->u.ival);\n}\n\n\n/*\n** Check whether expression 'e' is a literal integer or float in\n** proper range to fit in a register (sB or sC).\n*/\nstatic int isSCnumber (expdesc *e, int *pi, int *isfloat) {\n  lua_Integer i;\n  if (e->k == VKINT)\n    i = e->u.ival;\n  else if (e->k == VKFLT && luaV_flttointeger(e->u.nval, &i, F2Ieq))\n    *isfloat = 1;\n  else\n    return 0;  /* not a number */\n  if (!hasjumps(e) && fitsC(i)) {\n    *pi = int2sC(cast_int(i));\n    return 1;\n  }\n  else\n    return 0;\n}\n\n\n/*\n** Create expression 't[k]'. 't' must have its final result already in a\n** register or upvalue. Upvalues can only be indexed by literal strings.\n** Keys can be literal strings in the constant table or arbitrary\n** values in registers.\n*/\nvoid luaK_indexed (FuncState *fs, expdesc *t, expdesc *k) {\n  if (k->k == VKSTR)\n    str2K(fs, k);\n  lua_assert(!hasjumps(t) &&\n             (t->k == VLOCAL || t->k == VNONRELOC || t->k == VUPVAL));\n  if (t->k == VUPVAL && !isKstr(fs, k))  /* upvalue indexed by non 'Kstr'? */\n    luaK_exp2anyreg(fs, t);  /* put it in a register */\n  if (t->k == VUPVAL) {\n    t->u.ind.t = t->u.info;  /* upvalue index */\n    t->u.ind.idx = k->u.info;  /* literal string */\n    t->k = VINDEXUP;\n  }\n  else {\n    /* register index of the table */\n    t->u.ind.t = (t->k == VLOCAL) ? t->u.var.ridx: t->u.info;\n    if (isKstr(fs, k)) {\n      t->u.ind.idx = k->u.info;  /* literal string */\n      t->k = VINDEXSTR;\n    }\n    else if (isCint(k)) {\n      t->u.ind.idx = cast_int(k->u.ival);  /* int. constant in proper range */\n      t->k = VINDEXI;\n    }\n    else {\n      t->u.ind.idx = luaK_exp2anyreg(fs, k);  /* register */\n      t->k = VINDEXED;\n    }\n  }\n}\n\n\n/*\n** Return false if folding can raise an error.\n** Bitwise operations need operands convertible to integers; division\n** operations cannot have 0 as divisor.\n*/\nstatic int validop (int op, TValue *v1, TValue *v2) {\n  switch (op) {\n    case LUA_OPBAND: case LUA_OPBOR: case LUA_OPBXOR:\n    case LUA_OPSHL: case LUA_OPSHR: case LUA_OPBNOT: {  /* conversion errors */\n      lua_Integer i;\n      return (luaV_tointegerns(v1, &i, LUA_FLOORN2I) &&\n              luaV_tointegerns(v2, &i, LUA_FLOORN2I));\n    }\n    case LUA_OPDIV: case LUA_OPIDIV: case LUA_OPMOD:  /* division by 0 */\n      return (nvalue(v2) != 0);\n    default: return 1;  /* everything else is valid */\n  }\n}\n\n\n/*\n** Try to \"constant-fold\" an operation; return 1 iff successful.\n** (In this case, 'e1' has the final result.)\n*/\nstatic int constfolding (FuncState *fs, int op, expdesc *e1,\n                                        const expdesc *e2) {\n  TValue v1, v2, res;\n  if (!tonumeral(e1, &v1) || !tonumeral(e2, &v2) || !validop(op, &v1, &v2))\n    return 0;  /* non-numeric operands or not safe to fold */\n  luaO_rawarith(fs->ls->L, op, &v1, &v2, &res);  /* does operation */\n  if (ttisinteger(&res)) {\n    e1->k = VKINT;\n    e1->u.ival = ivalue(&res);\n  }\n  else {  /* folds neither NaN nor 0.0 (to avoid problems with -0.0) */\n    lua_Number n = fltvalue(&res);\n    if (luai_numisnan(n) || n == 0)\n      return 0;\n    e1->k = VKFLT;\n    e1->u.nval = n;\n  }\n  return 1;\n}\n\n\n/*\n** Convert a BinOpr to an OpCode  (ORDER OPR - ORDER OP)\n*/\nl_sinline OpCode binopr2op (BinOpr opr, BinOpr baser, OpCode base) {\n  lua_assert(baser <= opr &&\n            ((baser == OPR_ADD && opr <= OPR_SHR) ||\n             (baser == OPR_LT && opr <= OPR_LE)));\n  return cast(OpCode, (cast_int(opr) - cast_int(baser)) + cast_int(base));\n}\n\n\n/*\n** Convert a UnOpr to an OpCode  (ORDER OPR - ORDER OP)\n*/\nl_sinline OpCode unopr2op (UnOpr opr) {\n  return cast(OpCode, (cast_int(opr) - cast_int(OPR_MINUS)) +\n                                       cast_int(OP_UNM));\n}\n\n\n/*\n** Convert a BinOpr to a tag method  (ORDER OPR - ORDER TM)\n*/\nl_sinline TMS binopr2TM (BinOpr opr) {\n  lua_assert(OPR_ADD <= opr && opr <= OPR_SHR);\n  return cast(TMS, (cast_int(opr) - cast_int(OPR_ADD)) + cast_int(TM_ADD));\n}\n\n\n/*\n** Emit code for unary expressions that \"produce values\"\n** (everything but 'not').\n** Expression to produce final result will be encoded in 'e'.\n*/\nstatic void codeunexpval (FuncState *fs, OpCode op, expdesc *e, int line) {\n  int r = luaK_exp2anyreg(fs, e);  /* opcodes operate only on registers */\n  freeexp(fs, e);\n  e->u.info = luaK_codeABC(fs, op, 0, r, 0);  /* generate opcode */\n  e->k = VRELOC;  /* all those operations are relocatable */\n  luaK_fixline(fs, line);\n}\n\n\n/*\n** Emit code for binary expressions that \"produce values\"\n** (everything but logical operators 'and'/'or' and comparison\n** operators).\n** Expression to produce final result will be encoded in 'e1'.\n*/\nstatic void finishbinexpval (FuncState *fs, expdesc *e1, expdesc *e2,\n                             OpCode op, int v2, int flip, int line,\n                             OpCode mmop, TMS event) {\n  int v1 = luaK_exp2anyreg(fs, e1);\n  int pc = luaK_codeABCk(fs, op, 0, v1, v2, 0);\n  freeexps(fs, e1, e2);\n  e1->u.info = pc;\n  e1->k = VRELOC;  /* all those operations are relocatable */\n  luaK_fixline(fs, line);\n  luaK_codeABCk(fs, mmop, v1, v2, event, flip);  /* to call metamethod */\n  luaK_fixline(fs, line);\n}\n\n\n/*\n** Emit code for binary expressions that \"produce values\" over\n** two registers.\n*/\nstatic void codebinexpval (FuncState *fs, BinOpr opr,\n                           expdesc *e1, expdesc *e2, int line) {\n  OpCode op = binopr2op(opr, OPR_ADD, OP_ADD);\n  int v2 = luaK_exp2anyreg(fs, e2);  /* make sure 'e2' is in a register */\n  /* 'e1' must be already in a register or it is a constant */\n  lua_assert((VNIL <= e1->k && e1->k <= VKSTR) ||\n             e1->k == VNONRELOC || e1->k == VRELOC);\n  lua_assert(OP_ADD <= op && op <= OP_SHR);\n  finishbinexpval(fs, e1, e2, op, v2, 0, line, OP_MMBIN, binopr2TM(opr));\n}\n\n\n/*\n** Code binary operators with immediate operands.\n*/\nstatic void codebini (FuncState *fs, OpCode op,\n                       expdesc *e1, expdesc *e2, int flip, int line,\n                       TMS event) {\n  int v2 = int2sC(cast_int(e2->u.ival));  /* immediate operand */\n  lua_assert(e2->k == VKINT);\n  finishbinexpval(fs, e1, e2, op, v2, flip, line, OP_MMBINI, event);\n}\n\n\n/*\n** Code binary operators with K operand.\n*/\nstatic void codebinK (FuncState *fs, BinOpr opr,\n                      expdesc *e1, expdesc *e2, int flip, int line) {\n  TMS event = binopr2TM(opr);\n  int v2 = e2->u.info;  /* K index */\n  OpCode op = binopr2op(opr, OPR_ADD, OP_ADDK);\n  finishbinexpval(fs, e1, e2, op, v2, flip, line, OP_MMBINK, event);\n}\n\n\n/* Try to code a binary operator negating its second operand.\n** For the metamethod, 2nd operand must keep its original value.\n*/\nstatic int finishbinexpneg (FuncState *fs, expdesc *e1, expdesc *e2,\n                             OpCode op, int line, TMS event) {\n  if (!luaK_isKint(e2))\n    return 0;  /* not an integer constant */\n  else {\n    lua_Integer i2 = e2->u.ival;\n    if (!(fitsC(i2) && fitsC(-i2)))\n      return 0;  /* not in the proper range */\n    else {  /* operating a small integer constant */\n      int v2 = cast_int(i2);\n      finishbinexpval(fs, e1, e2, op, int2sC(-v2), 0, line, OP_MMBINI, event);\n      /* correct metamethod argument */\n      SETARG_B(fs->f->code[fs->pc - 1], int2sC(v2));\n      return 1;  /* successfully coded */\n    }\n  }\n}\n\n\nstatic void swapexps (expdesc *e1, expdesc *e2) {\n  expdesc temp = *e1; *e1 = *e2; *e2 = temp;  /* swap 'e1' and 'e2' */\n}\n\n\n/*\n** Code binary operators with no constant operand.\n*/\nstatic void codebinNoK (FuncState *fs, BinOpr opr,\n                        expdesc *e1, expdesc *e2, int flip, int line) {\n  if (flip)\n    swapexps(e1, e2);  /* back to original order */\n  codebinexpval(fs, opr, e1, e2, line);  /* use standard operators */\n}\n\n\n/*\n** Code arithmetic operators ('+', '-', ...). If second operand is a\n** constant in the proper range, use variant opcodes with K operands.\n*/\nstatic void codearith (FuncState *fs, BinOpr opr,\n                       expdesc *e1, expdesc *e2, int flip, int line) {\n  if (tonumeral(e2, NULL) && luaK_exp2K(fs, e2))  /* K operand? */\n    codebinK(fs, opr, e1, e2, flip, line);\n  else  /* 'e2' is neither an immediate nor a K operand */\n    codebinNoK(fs, opr, e1, e2, flip, line);\n}\n\n\n/*\n** Code commutative operators ('+', '*'). If first operand is a\n** numeric constant, change order of operands to try to use an\n** immediate or K operator.\n*/\nstatic void codecommutative (FuncState *fs, BinOpr op,\n                             expdesc *e1, expdesc *e2, int line) {\n  int flip = 0;\n  if (tonumeral(e1, NULL)) {  /* is first operand a numeric constant? */\n    swapexps(e1, e2);  /* change order */\n    flip = 1;\n  }\n  if (op == OPR_ADD && isSCint(e2))  /* immediate operand? */\n    codebini(fs, OP_ADDI, e1, e2, flip, line, TM_ADD);\n  else\n    codearith(fs, op, e1, e2, flip, line);\n}\n\n\n/*\n** Code bitwise operations; they are all commutative, so the function\n** tries to put an integer constant as the 2nd operand (a K operand).\n*/\nstatic void codebitwise (FuncState *fs, BinOpr opr,\n                         expdesc *e1, expdesc *e2, int line) {\n  int flip = 0;\n  if (e1->k == VKINT) {\n    swapexps(e1, e2);  /* 'e2' will be the constant operand */\n    flip = 1;\n  }\n  if (e2->k == VKINT && luaK_exp2K(fs, e2))  /* K operand? */\n    codebinK(fs, opr, e1, e2, flip, line);\n  else  /* no constants */\n    codebinNoK(fs, opr, e1, e2, flip, line);\n}\n\n\n/*\n** Emit code for order comparisons. When using an immediate operand,\n** 'isfloat' tells whether the original value was a float.\n*/\nstatic void codeorder (FuncState *fs, BinOpr opr, expdesc *e1, expdesc *e2) {\n  int r1, r2;\n  int im;\n  int isfloat = 0;\n  OpCode op;\n  if (isSCnumber(e2, &im, &isfloat)) {\n    /* use immediate operand */\n    r1 = luaK_exp2anyreg(fs, e1);\n    r2 = im;\n    op = binopr2op(opr, OPR_LT, OP_LTI);\n  }\n  else if (isSCnumber(e1, &im, &isfloat)) {\n    /* transform (A < B) to (B > A) and (A <= B) to (B >= A) */\n    r1 = luaK_exp2anyreg(fs, e2);\n    r2 = im;\n    op = binopr2op(opr, OPR_LT, OP_GTI);\n  }\n  else {  /* regular case, compare two registers */\n    r1 = luaK_exp2anyreg(fs, e1);\n    r2 = luaK_exp2anyreg(fs, e2);\n    op = binopr2op(opr, OPR_LT, OP_LT);\n  }\n  freeexps(fs, e1, e2);\n  e1->u.info = condjump(fs, op, r1, r2, isfloat, 1);\n  e1->k = VJMP;\n}\n\n\n/*\n** Emit code for equality comparisons ('==', '~=').\n** 'e1' was already put as RK by 'luaK_infix'.\n*/\nstatic void codeeq (FuncState *fs, BinOpr opr, expdesc *e1, expdesc *e2) {\n  int r1, r2;\n  int im;\n  int isfloat = 0;  /* not needed here, but kept for symmetry */\n  OpCode op;\n  if (e1->k != VNONRELOC) {\n    lua_assert(e1->k == VK || e1->k == VKINT || e1->k == VKFLT);\n    swapexps(e1, e2);\n  }\n  r1 = luaK_exp2anyreg(fs, e1);  /* 1st expression must be in register */\n  if (isSCnumber(e2, &im, &isfloat)) {\n    op = OP_EQI;\n    r2 = im;  /* immediate operand */\n  }\n  else if (luaK_exp2RK(fs, e2)) {  /* 2nd expression is constant? */\n    op = OP_EQK;\n    r2 = e2->u.info;  /* constant index */\n  }\n  else {\n    op = OP_EQ;  /* will compare two registers */\n    r2 = luaK_exp2anyreg(fs, e2);\n  }\n  freeexps(fs, e1, e2);\n  e1->u.info = condjump(fs, op, r1, r2, isfloat, (opr == OPR_EQ));\n  e1->k = VJMP;\n}\n\n\n/*\n** Apply prefix operation 'op' to expression 'e'.\n*/\nvoid luaK_prefix (FuncState *fs, UnOpr opr, expdesc *e, int line) {\n  static const expdesc ef = {VKINT, {0}, NO_JUMP, NO_JUMP};\n  luaK_dischargevars(fs, e);\n  switch (opr) {\n    case OPR_MINUS: case OPR_BNOT:  /* use 'ef' as fake 2nd operand */\n      if (constfolding(fs, opr + LUA_OPUNM, e, &ef))\n        break;\n      /* else */ /* FALLTHROUGH */\n    case OPR_LEN:\n      codeunexpval(fs, unopr2op(opr), e, line);\n      break;\n    case OPR_NOT: codenot(fs, e); break;\n    default: lua_assert(0);\n  }\n}\n\n\n/*\n** Process 1st operand 'v' of binary operation 'op' before reading\n** 2nd operand.\n*/\nvoid luaK_infix (FuncState *fs, BinOpr op, expdesc *v) {\n  luaK_dischargevars(fs, v);\n  switch (op) {\n    case OPR_AND: {\n      luaK_goiftrue(fs, v);  /* go ahead only if 'v' is true */\n      break;\n    }\n    case OPR_OR: {\n      luaK_goiffalse(fs, v);  /* go ahead only if 'v' is false */\n      break;\n    }\n    case OPR_CONCAT: {\n      luaK_exp2nextreg(fs, v);  /* operand must be on the stack */\n      break;\n    }\n    case OPR_ADD: case OPR_SUB:\n    case OPR_MUL: case OPR_DIV: case OPR_IDIV:\n    case OPR_MOD: case OPR_POW:\n    case OPR_BAND: case OPR_BOR: case OPR_BXOR:\n    case OPR_SHL: case OPR_SHR: {\n      if (!tonumeral(v, NULL))\n        luaK_exp2anyreg(fs, v);\n      /* else keep numeral, which may be folded or used as an immediate\n         operand */\n      break;\n    }\n    case OPR_EQ: case OPR_NE: {\n      if (!tonumeral(v, NULL))\n        luaK_exp2RK(fs, v);\n      /* else keep numeral, which may be an immediate operand */\n      break;\n    }\n    case OPR_LT: case OPR_LE:\n    case OPR_GT: case OPR_GE: {\n      int dummy, dummy2;\n      if (!isSCnumber(v, &dummy, &dummy2))\n        luaK_exp2anyreg(fs, v);\n      /* else keep numeral, which may be an immediate operand */\n      break;\n    }\n    default: lua_assert(0);\n  }\n}\n\n/*\n** Create code for '(e1 .. e2)'.\n** For '(e1 .. e2.1 .. e2.2)' (which is '(e1 .. (e2.1 .. e2.2))',\n** because concatenation is right associative), merge both CONCATs.\n*/\nstatic void codeconcat (FuncState *fs, expdesc *e1, expdesc *e2, int line) {\n  Instruction *ie2 = previousinstruction(fs);\n  if (GET_OPCODE(*ie2) == OP_CONCAT) {  /* is 'e2' a concatenation? */\n    int n = GETARG_B(*ie2);  /* # of elements concatenated in 'e2' */\n    lua_assert(e1->u.info + 1 == GETARG_A(*ie2));\n    freeexp(fs, e2);\n    SETARG_A(*ie2, e1->u.info);  /* correct first element ('e1') */\n    SETARG_B(*ie2, n + 1);  /* will concatenate one more element */\n  }\n  else {  /* 'e2' is not a concatenation */\n    luaK_codeABC(fs, OP_CONCAT, e1->u.info, 2, 0);  /* new concat opcode */\n    freeexp(fs, e2);\n    luaK_fixline(fs, line);\n  }\n}\n\n\n/*\n** Finalize code for binary operation, after reading 2nd operand.\n*/\nvoid luaK_posfix (FuncState *fs, BinOpr opr,\n                  expdesc *e1, expdesc *e2, int line) {\n  luaK_dischargevars(fs, e2);\n  if (foldbinop(opr) && constfolding(fs, opr + LUA_OPADD, e1, e2))\n    return;  /* done by folding */\n  switch (opr) {\n    case OPR_AND: {\n      lua_assert(e1->t == NO_JUMP);  /* list closed by 'luaK_infix' */\n      luaK_concat(fs, &e2->f, e1->f);\n      *e1 = *e2;\n      break;\n    }\n    case OPR_OR: {\n      lua_assert(e1->f == NO_JUMP);  /* list closed by 'luaK_infix' */\n      luaK_concat(fs, &e2->t, e1->t);\n      *e1 = *e2;\n      break;\n    }\n    case OPR_CONCAT: {  /* e1 .. e2 */\n      luaK_exp2nextreg(fs, e2);\n      codeconcat(fs, e1, e2, line);\n      break;\n    }\n    case OPR_ADD: case OPR_MUL: {\n      codecommutative(fs, opr, e1, e2, line);\n      break;\n    }\n    case OPR_SUB: {\n      if (finishbinexpneg(fs, e1, e2, OP_ADDI, line, TM_SUB))\n        break; /* coded as (r1 + -I) */\n      /* ELSE */\n    }  /* FALLTHROUGH */\n    case OPR_DIV: case OPR_IDIV: case OPR_MOD: case OPR_POW: {\n      codearith(fs, opr, e1, e2, 0, line);\n      break;\n    }\n    case OPR_BAND: case OPR_BOR: case OPR_BXOR: {\n      codebitwise(fs, opr, e1, e2, line);\n      break;\n    }\n    case OPR_SHL: {\n      if (isSCint(e1)) {\n        swapexps(e1, e2);\n        codebini(fs, OP_SHLI, e1, e2, 1, line, TM_SHL);  /* I << r2 */\n      }\n      else if (finishbinexpneg(fs, e1, e2, OP_SHRI, line, TM_SHL)) {\n        /* coded as (r1 >> -I) */;\n      }\n      else  /* regular case (two registers) */\n       codebinexpval(fs, opr, e1, e2, line);\n      break;\n    }\n    case OPR_SHR: {\n      if (isSCint(e2))\n        codebini(fs, OP_SHRI, e1, e2, 0, line, TM_SHR);  /* r1 >> I */\n      else  /* regular case (two registers) */\n        codebinexpval(fs, opr, e1, e2, line);\n      break;\n    }\n    case OPR_EQ: case OPR_NE: {\n      codeeq(fs, opr, e1, e2);\n      break;\n    }\n    case OPR_GT: case OPR_GE: {\n      /* '(a > b)' <=> '(b < a)';  '(a >= b)' <=> '(b <= a)' */\n      swapexps(e1, e2);\n      opr = cast(BinOpr, (opr - OPR_GT) + OPR_LT);\n    }  /* FALLTHROUGH */\n    case OPR_LT: case OPR_LE: {\n      codeorder(fs, opr, e1, e2);\n      break;\n    }\n    default: lua_assert(0);\n  }\n}\n\n\n/*\n** Change line information associated with current position, by removing\n** previous info and adding it again with new line.\n*/\nvoid luaK_fixline (FuncState *fs, int line) {\n  removelastlineinfo(fs);\n  savelineinfo(fs, fs->f, line);\n}\n\n\nvoid luaK_settablesize (FuncState *fs, int pc, int ra, int asize, int hsize) {\n  Instruction *inst = &fs->f->code[pc];\n  int rb = (hsize != 0) ? luaO_ceillog2(hsize) + 1 : 0;  /* hash size */\n  int extra = asize / (MAXARG_C + 1);  /* higher bits of array size */\n  int rc = asize % (MAXARG_C + 1);  /* lower bits of array size */\n  int k = (extra > 0);  /* true iff needs extra argument */\n  *inst = CREATE_ABCk(OP_NEWTABLE, ra, rb, rc, k);\n  *(inst + 1) = CREATE_Ax(OP_EXTRAARG, extra);\n}\n\n\n/*\n** Emit a SETLIST instruction.\n** 'base' is register that keeps table;\n** 'nelems' is #table plus those to be stored now;\n** 'tostore' is number of values (in registers 'base + 1',...) to add to\n** table (or LUA_MULTRET to add up to stack top).\n*/\nvoid luaK_setlist (FuncState *fs, int base, int nelems, int tostore) {\n  lua_assert(tostore != 0 && tostore <= LFIELDS_PER_FLUSH);\n  if (tostore == LUA_MULTRET)\n    tostore = 0;\n  if (nelems <= MAXARG_C)\n    luaK_codeABC(fs, OP_SETLIST, base, tostore, nelems);\n  else {\n    int extra = nelems / (MAXARG_C + 1);\n    nelems %= (MAXARG_C + 1);\n    luaK_codeABCk(fs, OP_SETLIST, base, tostore, nelems, 1);\n    codeextraarg(fs, extra);\n  }\n  fs->freereg = base + 1;  /* free registers with list values */\n}\n\n\n/*\n** return the final target of a jump (skipping jumps to jumps)\n*/\nstatic int finaltarget (Instruction *code, int i) {\n  int count;\n  for (count = 0; count < 100; count++) {  /* avoid infinite loops */\n    Instruction pc = code[i];\n    if (GET_OPCODE(pc) != OP_JMP)\n      break;\n     else\n       i += GETARG_sJ(pc) + 1;\n  }\n  return i;\n}\n\n\n/*\n** Do a final pass over the code of a function, doing small peephole\n** optimizations and adjustments.\n*/\nvoid luaK_finish (FuncState *fs) {\n  int i;\n  Proto *p = fs->f;\n  for (i = 0; i < fs->pc; i++) {\n    Instruction *pc = &p->code[i];\n    lua_assert(i == 0 || isOT(*(pc - 1)) == isIT(*pc));\n    switch (GET_OPCODE(*pc)) {\n      case OP_RETURN0: case OP_RETURN1: {\n        if (!(fs->needclose || p->is_vararg))\n          break;  /* no extra work */\n        /* else use OP_RETURN to do the extra work */\n        SET_OPCODE(*pc, OP_RETURN);\n      }  /* FALLTHROUGH */\n      case OP_RETURN: case OP_TAILCALL: {\n        if (fs->needclose)\n          SETARG_k(*pc, 1);  /* signal that it needs to close */\n        if (p->is_vararg)\n          SETARG_C(*pc, p->numparams + 1);  /* signal that it is vararg */\n        break;\n      }\n      case OP_JMP: {\n        int target = finaltarget(p->code, i);\n        fixjump(fs, i, target);\n        break;\n      }\n      default: break;\n    }\n  }\n}\n/*\n** $Id: lparser.c $\n** Lua Parser\n** See Copyright Notice in lua.h\n*/\n\n#define lparser_c\n#define LUA_CORE\n\n/*#include \"lprefix.h\"*/\n\n\n#include <limits.h>\n#include <string.h>\n\n/*#include \"lua.h\"*/\n\n/*#include \"lcode.h\"*/\n/*#include \"ldebug.h\"*/\n/*#include \"ldo.h\"*/\n/*#include \"lfunc.h\"*/\n/*#include \"llex.h\"*/\n/*#include \"lmem.h\"*/\n/*#include \"lobject.h\"*/\n/*#include \"lopcodes.h\"*/\n/*#include \"lparser.h\"*/\n/*#include \"lstate.h\"*/\n/*#include \"lstring.h\"*/\n/*#include \"ltable.h\"*/\n\n\n\n/* maximum number of local variables per function (must be smaller\n   than 250, due to the bytecode format) */\n#define MAXVARS\t\t200\n\n\n#define hasmultret(k)\t\t((k) == VCALL || (k) == VVARARG)\n\n\n/* because all strings are unified by the scanner, the parser\n   can use pointer equality for string equality */\n#define eqstr(a,b)\t((a) == (b))\n\n\n/*\n** nodes for block list (list of active blocks)\n*/\ntypedef struct BlockCnt {\n  struct BlockCnt *previous;  /* chain */\n  int firstlabel;  /* index of first label in this block */\n  int firstgoto;  /* index of first pending goto in this block */\n  lu_byte nactvar;  /* # active locals outside the block */\n  lu_byte upval;  /* true if some variable in the block is an upvalue */\n  lu_byte isloop;  /* true if 'block' is a loop */\n  lu_byte insidetbc;  /* true if inside the scope of a to-be-closed var. */\n} BlockCnt;\n\n\n\n/*\n** prototypes for recursive non-terminal functions\n*/\nstatic void statement (LexState *ls);\nstatic void expr (LexState *ls, expdesc *v);\n\n\nstatic l_noret error_expected (LexState *ls, int token) {\n  luaX_syntaxerror(ls,\n      luaO_pushfstring(ls->L, \"%s expected\", luaX_token2str(ls, token)));\n}\n\n\nstatic l_noret errorlimit (FuncState *fs, int limit, const char *what) {\n  lua_State *L = fs->ls->L;\n  const char *msg;\n  int line = fs->f->linedefined;\n  const char *where = (line == 0)\n                      ? \"main function\"\n                      : luaO_pushfstring(L, \"function at line %d\", line);\n  msg = luaO_pushfstring(L, \"too many %s (limit is %d) in %s\",\n                             what, limit, where);\n  luaX_syntaxerror(fs->ls, msg);\n}\n\n\nstatic void checklimit (FuncState *fs, int v, int l, const char *what) {\n  if (v > l) errorlimit(fs, l, what);\n}\n\n\n/*\n** Test whether next token is 'c'; if so, skip it.\n*/\nstatic int testnext (LexState *ls, int c) {\n  if (ls->t.token == c) {\n    luaX_next(ls);\n    return 1;\n  }\n  else return 0;\n}\n\n\n/*\n** Check that next token is 'c'.\n*/\nstatic void check (LexState *ls, int c) {\n  if (ls->t.token != c)\n    error_expected(ls, c);\n}\n\n\n/*\n** Check that next token is 'c' and skip it.\n*/\nstatic void checknext (LexState *ls, int c) {\n  check(ls, c);\n  luaX_next(ls);\n}\n\n\n#define check_condition(ls,c,msg)\t{ if (!(c)) luaX_syntaxerror(ls, msg); }\n\n\n/*\n** Check that next token is 'what' and skip it. In case of error,\n** raise an error that the expected 'what' should match a 'who'\n** in line 'where' (if that is not the current line).\n*/\nstatic void check_match (LexState *ls, int what, int who, int where) {\n  if (l_unlikely(!testnext(ls, what))) {\n    if (where == ls->linenumber)  /* all in the same line? */\n      error_expected(ls, what);  /* do not need a complex message */\n    else {\n      luaX_syntaxerror(ls, luaO_pushfstring(ls->L,\n             \"%s expected (to close %s at line %d)\",\n              luaX_token2str(ls, what), luaX_token2str(ls, who), where));\n    }\n  }\n}\n\n\nstatic TString *str_checkname (LexState *ls) {\n  TString *ts;\n  check(ls, TK_NAME);\n  ts = ls->t.seminfo.ts;\n  luaX_next(ls);\n  return ts;\n}\n\n\nstatic void init_exp (expdesc *e, expkind k, int i) {\n  e->f = e->t = NO_JUMP;\n  e->k = k;\n  e->u.info = i;\n}\n\n\nstatic void codestring (expdesc *e, TString *s) {\n  e->f = e->t = NO_JUMP;\n  e->k = VKSTR;\n  e->u.strval = s;\n}\n\n\nstatic void codename (LexState *ls, expdesc *e) {\n  codestring(e, str_checkname(ls));\n}\n\n\n/*\n** Register a new local variable in the active 'Proto' (for debug\n** information).\n*/\nstatic int registerlocalvar (LexState *ls, FuncState *fs, TString *varname) {\n  Proto *f = fs->f;\n  int oldsize = f->sizelocvars;\n  luaM_growvector(ls->L, f->locvars, fs->ndebugvars, f->sizelocvars,\n                  LocVar, SHRT_MAX, \"local variables\");\n  while (oldsize < f->sizelocvars)\n    f->locvars[oldsize++].varname = NULL;\n  f->locvars[fs->ndebugvars].varname = varname;\n  f->locvars[fs->ndebugvars].startpc = fs->pc;\n  luaC_objbarrier(ls->L, f, varname);\n  return fs->ndebugvars++;\n}\n\n\n/*\n** Create a new local variable with the given 'name'. Return its index\n** in the function.\n*/\nstatic int new_localvar (LexState *ls, TString *name) {\n  lua_State *L = ls->L;\n  FuncState *fs = ls->fs;\n  Dyndata *dyd = ls->dyd;\n  Vardesc *var;\n  checklimit(fs, dyd->actvar.n + 1 - fs->firstlocal,\n                 MAXVARS, \"local variables\");\n  luaM_growvector(L, dyd->actvar.arr, dyd->actvar.n + 1,\n                  dyd->actvar.size, Vardesc, USHRT_MAX, \"local variables\");\n  var = &dyd->actvar.arr[dyd->actvar.n++];\n  var->vd.kind = VDKREG;  /* default */\n  var->vd.name = name;\n  return dyd->actvar.n - 1 - fs->firstlocal;\n}\n\n#define new_localvarliteral(ls,v) \\\n    new_localvar(ls,  \\\n      luaX_newstring(ls, \"\" v, (sizeof(v)/sizeof(char)) - 1));\n\n\n\n/*\n** Return the \"variable description\" (Vardesc) of a given variable.\n** (Unless noted otherwise, all variables are referred to by their\n** compiler indices.)\n*/\nstatic Vardesc *getlocalvardesc (FuncState *fs, int vidx) {\n  return &fs->ls->dyd->actvar.arr[fs->firstlocal + vidx];\n}\n\n\n/*\n** Convert 'nvar', a compiler index level, to its corresponding\n** register. For that, search for the highest variable below that level\n** that is in a register and uses its register index ('ridx') plus one.\n*/\nstatic int reglevel (FuncState *fs, int nvar) {\n  while (nvar-- > 0) {\n    Vardesc *vd = getlocalvardesc(fs, nvar);  /* get previous variable */\n    if (vd->vd.kind != RDKCTC)  /* is in a register? */\n      return vd->vd.ridx + 1;\n  }\n  return 0;  /* no variables in registers */\n}\n\n\n/*\n** Return the number of variables in the register stack for the given\n** function.\n*/\nint luaY_nvarstack (FuncState *fs) {\n  return reglevel(fs, fs->nactvar);\n}\n\n\n/*\n** Get the debug-information entry for current variable 'vidx'.\n*/\nstatic LocVar *localdebuginfo (FuncState *fs, int vidx) {\n  Vardesc *vd = getlocalvardesc(fs,  vidx);\n  if (vd->vd.kind == RDKCTC)\n    return NULL;  /* no debug info. for constants */\n  else {\n    int idx = vd->vd.pidx;\n    lua_assert(idx < fs->ndebugvars);\n    return &fs->f->locvars[idx];\n  }\n}\n\n\n/*\n** Create an expression representing variable 'vidx'\n*/\nstatic void init_var (FuncState *fs, expdesc *e, int vidx) {\n  e->f = e->t = NO_JUMP;\n  e->k = VLOCAL;\n  e->u.var.vidx = vidx;\n  e->u.var.ridx = getlocalvardesc(fs, vidx)->vd.ridx;\n}\n\n\n/*\n** Raises an error if variable described by 'e' is read only\n*/\nstatic void check_readonly (LexState *ls, expdesc *e) {\n  FuncState *fs = ls->fs;\n  TString *varname = NULL;  /* to be set if variable is const */\n  switch (e->k) {\n    case VCONST: {\n      varname = ls->dyd->actvar.arr[e->u.info].vd.name;\n      break;\n    }\n    case VLOCAL: {\n      Vardesc *vardesc = getlocalvardesc(fs, e->u.var.vidx);\n      if (vardesc->vd.kind != VDKREG)  /* not a regular variable? */\n        varname = vardesc->vd.name;\n      break;\n    }\n    case VUPVAL: {\n      Upvaldesc *up = &fs->f->upvalues[e->u.info];\n      if (up->kind != VDKREG)\n        varname = up->name;\n      break;\n    }\n    default:\n      return;  /* other cases cannot be read-only */\n  }\n  if (varname) {\n    const char *msg = luaO_pushfstring(ls->L,\n       \"attempt to assign to const variable '%s'\", getstr(varname));\n    luaK_semerror(ls, msg);  /* error */\n  }\n}\n\n\n/*\n** Start the scope for the last 'nvars' created variables.\n*/\nstatic void adjustlocalvars (LexState *ls, int nvars) {\n  FuncState *fs = ls->fs;\n  int reglevel = luaY_nvarstack(fs);\n  int i;\n  for (i = 0; i < nvars; i++) {\n    int vidx = fs->nactvar++;\n    Vardesc *var = getlocalvardesc(fs, vidx);\n    var->vd.ridx = reglevel++;\n    var->vd.pidx = registerlocalvar(ls, fs, var->vd.name);\n  }\n}\n\n\n/*\n** Close the scope for all variables up to level 'tolevel'.\n** (debug info.)\n*/\nstatic void removevars (FuncState *fs, int tolevel) {\n  fs->ls->dyd->actvar.n -= (fs->nactvar - tolevel);\n  while (fs->nactvar > tolevel) {\n    LocVar *var = localdebuginfo(fs, --fs->nactvar);\n    if (var)  /* does it have debug information? */\n      var->endpc = fs->pc;\n  }\n}\n\n\n/*\n** Search the upvalues of the function 'fs' for one\n** with the given 'name'.\n*/\nstatic int searchupvalue (FuncState *fs, TString *name) {\n  int i;\n  Upvaldesc *up = fs->f->upvalues;\n  for (i = 0; i < fs->nups; i++) {\n    if (eqstr(up[i].name, name)) return i;\n  }\n  return -1;  /* not found */\n}\n\n\nstatic Upvaldesc *allocupvalue (FuncState *fs) {\n  Proto *f = fs->f;\n  int oldsize = f->sizeupvalues;\n  checklimit(fs, fs->nups + 1, MAXUPVAL, \"upvalues\");\n  luaM_growvector(fs->ls->L, f->upvalues, fs->nups, f->sizeupvalues,\n                  Upvaldesc, MAXUPVAL, \"upvalues\");\n  while (oldsize < f->sizeupvalues)\n    f->upvalues[oldsize++].name = NULL;\n  return &f->upvalues[fs->nups++];\n}\n\n\nstatic int newupvalue (FuncState *fs, TString *name, expdesc *v) {\n  Upvaldesc *up = allocupvalue(fs);\n  FuncState *prev = fs->prev;\n  if (v->k == VLOCAL) {\n    up->instack = 1;\n    up->idx = v->u.var.ridx;\n    up->kind = getlocalvardesc(prev, v->u.var.vidx)->vd.kind;\n    lua_assert(eqstr(name, getlocalvardesc(prev, v->u.var.vidx)->vd.name));\n  }\n  else {\n    up->instack = 0;\n    up->idx = cast_byte(v->u.info);\n    up->kind = prev->f->upvalues[v->u.info].kind;\n    lua_assert(eqstr(name, prev->f->upvalues[v->u.info].name));\n  }\n  up->name = name;\n  luaC_objbarrier(fs->ls->L, fs->f, name);\n  return fs->nups - 1;\n}\n\n\n/*\n** Look for an active local variable with the name 'n' in the\n** function 'fs'. If found, initialize 'var' with it and return\n** its expression kind; otherwise return -1.\n*/\nstatic int searchvar (FuncState *fs, TString *n, expdesc *var) {\n  int i;\n  for (i = cast_int(fs->nactvar) - 1; i >= 0; i--) {\n    Vardesc *vd = getlocalvardesc(fs, i);\n    if (eqstr(n, vd->vd.name)) {  /* found? */\n      if (vd->vd.kind == RDKCTC)  /* compile-time constant? */\n        init_exp(var, VCONST, fs->firstlocal + i);\n      else  /* real variable */\n        init_var(fs, var, i);\n      return var->k;\n    }\n  }\n  return -1;  /* not found */\n}\n\n\n/*\n** Mark block where variable at given level was defined\n** (to emit close instructions later).\n*/\nstatic void markupval (FuncState *fs, int level) {\n  BlockCnt *bl = fs->bl;\n  while (bl->nactvar > level)\n    bl = bl->previous;\n  bl->upval = 1;\n  fs->needclose = 1;\n}\n\n\n/*\n** Mark that current block has a to-be-closed variable.\n*/\nstatic void marktobeclosed (FuncState *fs) {\n  BlockCnt *bl = fs->bl;\n  bl->upval = 1;\n  bl->insidetbc = 1;\n  fs->needclose = 1;\n}\n\n\n/*\n** Find a variable with the given name 'n'. If it is an upvalue, add\n** this upvalue into all intermediate functions. If it is a global, set\n** 'var' as 'void' as a flag.\n*/\nstatic void singlevaraux (FuncState *fs, TString *n, expdesc *var, int base) {\n  if (fs == NULL)  /* no more levels? */\n    init_exp(var, VVOID, 0);  /* default is global */\n  else {\n    int v = searchvar(fs, n, var);  /* look up locals at current level */\n    if (v >= 0) {  /* found? */\n      if (v == VLOCAL && !base)\n        markupval(fs, var->u.var.vidx);  /* local will be used as an upval */\n    }\n    else {  /* not found as local at current level; try upvalues */\n      int idx = searchupvalue(fs, n);  /* try existing upvalues */\n      if (idx < 0) {  /* not found? */\n        singlevaraux(fs->prev, n, var, 0);  /* try upper levels */\n        if (var->k == VLOCAL || var->k == VUPVAL)  /* local or upvalue? */\n          idx  = newupvalue(fs, n, var);  /* will be a new upvalue */\n        else  /* it is a global or a constant */\n          return;  /* don't need to do anything at this level */\n      }\n      init_exp(var, VUPVAL, idx);  /* new or old upvalue */\n    }\n  }\n}\n\n\n/*\n** Find a variable with the given name 'n', handling global variables\n** too.\n*/\nstatic void singlevar (LexState *ls, expdesc *var) {\n  TString *varname = str_checkname(ls);\n  FuncState *fs = ls->fs;\n  singlevaraux(fs, varname, var, 1);\n  if (var->k == VVOID) {  /* global name? */\n    expdesc key;\n    singlevaraux(fs, ls->envn, var, 1);  /* get environment variable */\n    lua_assert(var->k != VVOID);  /* this one must exist */\n    luaK_exp2anyregup(fs, var);  /* but could be a constant */\n    codestring(&key, varname);  /* key is variable name */\n    luaK_indexed(fs, var, &key);  /* env[varname] */\n  }\n}\n\n\n/*\n** Adjust the number of results from an expression list 'e' with 'nexps'\n** expressions to 'nvars' values.\n*/\nstatic void adjust_assign (LexState *ls, int nvars, int nexps, expdesc *e) {\n  FuncState *fs = ls->fs;\n  int needed = nvars - nexps;  /* extra values needed */\n  if (hasmultret(e->k)) {  /* last expression has multiple returns? */\n    int extra = needed + 1;  /* discount last expression itself */\n    if (extra < 0)\n      extra = 0;\n    luaK_setreturns(fs, e, extra);  /* last exp. provides the difference */\n  }\n  else {\n    if (e->k != VVOID)  /* at least one expression? */\n      luaK_exp2nextreg(fs, e);  /* close last expression */\n    if (needed > 0)  /* missing values? */\n      luaK_nil(fs, fs->freereg, needed);  /* complete with nils */\n  }\n  if (needed > 0)\n    luaK_reserveregs(fs, needed);  /* registers for extra values */\n  else  /* adding 'needed' is actually a subtraction */\n    fs->freereg += needed;  /* remove extra values */\n}\n\n\n#define enterlevel(ls)\tluaE_incCstack(ls->L)\n\n\n#define leavelevel(ls) ((ls)->L->nCcalls--)\n\n\n/*\n** Generates an error that a goto jumps into the scope of some\n** local variable.\n*/\nstatic l_noret jumpscopeerror (LexState *ls, Labeldesc *gt) {\n  const char *varname = getstr(getlocalvardesc(ls->fs, gt->nactvar)->vd.name);\n  const char *msg = \"<goto %s> at line %d jumps into the scope of local '%s'\";\n  msg = luaO_pushfstring(ls->L, msg, getstr(gt->name), gt->line, varname);\n  luaK_semerror(ls, msg);  /* raise the error */\n}\n\n\n/*\n** Solves the goto at index 'g' to given 'label' and removes it\n** from the list of pending gotos.\n** If it jumps into the scope of some variable, raises an error.\n*/\nstatic void solvegoto (LexState *ls, int g, Labeldesc *label) {\n  int i;\n  Labellist *gl = &ls->dyd->gt;  /* list of gotos */\n  Labeldesc *gt = &gl->arr[g];  /* goto to be resolved */\n  lua_assert(eqstr(gt->name, label->name));\n  if (l_unlikely(gt->nactvar < label->nactvar))  /* enter some scope? */\n    jumpscopeerror(ls, gt);\n  luaK_patchlist(ls->fs, gt->pc, label->pc);\n  for (i = g; i < gl->n - 1; i++)  /* remove goto from pending list */\n    gl->arr[i] = gl->arr[i + 1];\n  gl->n--;\n}\n\n\n/*\n** Search for an active label with the given name.\n*/\nstatic Labeldesc *findlabel (LexState *ls, TString *name) {\n  int i;\n  Dyndata *dyd = ls->dyd;\n  /* check labels in current function for a match */\n  for (i = ls->fs->firstlabel; i < dyd->label.n; i++) {\n    Labeldesc *lb = &dyd->label.arr[i];\n    if (eqstr(lb->name, name))  /* correct label? */\n      return lb;\n  }\n  return NULL;  /* label not found */\n}\n\n\n/*\n** Adds a new label/goto in the corresponding list.\n*/\nstatic int newlabelentry (LexState *ls, Labellist *l, TString *name,\n                          int line, int pc) {\n  int n = l->n;\n  luaM_growvector(ls->L, l->arr, n, l->size,\n                  Labeldesc, SHRT_MAX, \"labels/gotos\");\n  l->arr[n].name = name;\n  l->arr[n].line = line;\n  l->arr[n].nactvar = ls->fs->nactvar;\n  l->arr[n].close = 0;\n  l->arr[n].pc = pc;\n  l->n = n + 1;\n  return n;\n}\n\n\nstatic int newgotoentry (LexState *ls, TString *name, int line, int pc) {\n  return newlabelentry(ls, &ls->dyd->gt, name, line, pc);\n}\n\n\n/*\n** Solves forward jumps. Check whether new label 'lb' matches any\n** pending gotos in current block and solves them. Return true\n** if any of the gotos need to close upvalues.\n*/\nstatic int solvegotos (LexState *ls, Labeldesc *lb) {\n  Labellist *gl = &ls->dyd->gt;\n  int i = ls->fs->bl->firstgoto;\n  int needsclose = 0;\n  while (i < gl->n) {\n    if (eqstr(gl->arr[i].name, lb->name)) {\n      needsclose |= gl->arr[i].close;\n      solvegoto(ls, i, lb);  /* will remove 'i' from the list */\n    }\n    else\n      i++;\n  }\n  return needsclose;\n}\n\n\n/*\n** Create a new label with the given 'name' at the given 'line'.\n** 'last' tells whether label is the last non-op statement in its\n** block. Solves all pending gotos to this new label and adds\n** a close instruction if necessary.\n** Returns true iff it added a close instruction.\n*/\nstatic int createlabel (LexState *ls, TString *name, int line,\n                        int last) {\n  FuncState *fs = ls->fs;\n  Labellist *ll = &ls->dyd->label;\n  int l = newlabelentry(ls, ll, name, line, luaK_getlabel(fs));\n  if (last) {  /* label is last no-op statement in the block? */\n    /* assume that locals are already out of scope */\n    ll->arr[l].nactvar = fs->bl->nactvar;\n  }\n  if (solvegotos(ls, &ll->arr[l])) {  /* need close? */\n    luaK_codeABC(fs, OP_CLOSE, luaY_nvarstack(fs), 0, 0);\n    return 1;\n  }\n  return 0;\n}\n\n\n/*\n** Adjust pending gotos to outer level of a block.\n*/\nstatic void movegotosout (FuncState *fs, BlockCnt *bl) {\n  int i;\n  Labellist *gl = &fs->ls->dyd->gt;\n  /* correct pending gotos to current block */\n  for (i = bl->firstgoto; i < gl->n; i++) {  /* for each pending goto */\n    Labeldesc *gt = &gl->arr[i];\n    /* leaving a variable scope? */\n    if (reglevel(fs, gt->nactvar) > reglevel(fs, bl->nactvar))\n      gt->close |= bl->upval;  /* jump may need a close */\n    gt->nactvar = bl->nactvar;  /* update goto level */\n  }\n}\n\n\nstatic void enterblock (FuncState *fs, BlockCnt *bl, lu_byte isloop) {\n  bl->isloop = isloop;\n  bl->nactvar = fs->nactvar;\n  bl->firstlabel = fs->ls->dyd->label.n;\n  bl->firstgoto = fs->ls->dyd->gt.n;\n  bl->upval = 0;\n  bl->insidetbc = (fs->bl != NULL && fs->bl->insidetbc);\n  bl->previous = fs->bl;\n  fs->bl = bl;\n  lua_assert(fs->freereg == luaY_nvarstack(fs));\n}\n\n\n/*\n** generates an error for an undefined 'goto'.\n*/\nstatic l_noret undefgoto (LexState *ls, Labeldesc *gt) {\n  const char *msg;\n  if (eqstr(gt->name, luaS_newliteral(ls->L, \"break\"))) {\n    msg = \"break outside loop at line %d\";\n    msg = luaO_pushfstring(ls->L, msg, gt->line);\n  }\n  else {\n    msg = \"no visible label '%s' for <goto> at line %d\";\n    msg = luaO_pushfstring(ls->L, msg, getstr(gt->name), gt->line);\n  }\n  luaK_semerror(ls, msg);\n}\n\n\nstatic void leaveblock (FuncState *fs) {\n  BlockCnt *bl = fs->bl;\n  LexState *ls = fs->ls;\n  int hasclose = 0;\n  int stklevel = reglevel(fs, bl->nactvar);  /* level outside the block */\n  removevars(fs, bl->nactvar);  /* remove block locals */\n  lua_assert(bl->nactvar == fs->nactvar);  /* back to level on entry */\n  if (bl->isloop)  /* has to fix pending breaks? */\n    hasclose = createlabel(ls, luaS_newliteral(ls->L, \"break\"), 0, 0);\n  if (!hasclose && bl->previous && bl->upval)  /* still need a 'close'? */\n    luaK_codeABC(fs, OP_CLOSE, stklevel, 0, 0);\n  fs->freereg = stklevel;  /* free registers */\n  ls->dyd->label.n = bl->firstlabel;  /* remove local labels */\n  fs->bl = bl->previous;  /* current block now is previous one */\n  if (bl->previous)  /* was it a nested block? */\n    movegotosout(fs, bl);  /* update pending gotos to enclosing block */\n  else {\n    if (bl->firstgoto < ls->dyd->gt.n)  /* still pending gotos? */\n      undefgoto(ls, &ls->dyd->gt.arr[bl->firstgoto]);  /* error */\n  }\n}\n\n\n/*\n** adds a new prototype into list of prototypes\n*/\nstatic Proto *addprototype (LexState *ls) {\n  Proto *clp;\n  lua_State *L = ls->L;\n  FuncState *fs = ls->fs;\n  Proto *f = fs->f;  /* prototype of current function */\n  if (fs->np >= f->sizep) {\n    int oldsize = f->sizep;\n    luaM_growvector(L, f->p, fs->np, f->sizep, Proto *, MAXARG_Bx, \"functions\");\n    while (oldsize < f->sizep)\n      f->p[oldsize++] = NULL;\n  }\n  f->p[fs->np++] = clp = luaF_newproto(L);\n  luaC_objbarrier(L, f, clp);\n  return clp;\n}\n\n\n/*\n** codes instruction to create new closure in parent function.\n** The OP_CLOSURE instruction uses the last available register,\n** so that, if it invokes the GC, the GC knows which registers\n** are in use at that time.\n\n*/\nstatic void codeclosure (LexState *ls, expdesc *v) {\n  FuncState *fs = ls->fs->prev;\n  init_exp(v, VRELOC, luaK_codeABx(fs, OP_CLOSURE, 0, fs->np - 1));\n  luaK_exp2nextreg(fs, v);  /* fix it at the last register */\n}\n\n\nstatic void open_func (LexState *ls, FuncState *fs, BlockCnt *bl) {\n  Proto *f = fs->f;\n  fs->prev = ls->fs;  /* linked list of funcstates */\n  fs->ls = ls;\n  ls->fs = fs;\n  fs->pc = 0;\n  fs->previousline = f->linedefined;\n  fs->iwthabs = 0;\n  fs->lasttarget = 0;\n  fs->freereg = 0;\n  fs->nk = 0;\n  fs->nabslineinfo = 0;\n  fs->np = 0;\n  fs->nups = 0;\n  fs->ndebugvars = 0;\n  fs->nactvar = 0;\n  fs->needclose = 0;\n  fs->firstlocal = ls->dyd->actvar.n;\n  fs->firstlabel = ls->dyd->label.n;\n  fs->bl = NULL;\n  f->source = ls->source;\n  luaC_objbarrier(ls->L, f, f->source);\n  f->maxstacksize = 2;  /* registers 0/1 are always valid */\n  enterblock(fs, bl, 0);\n}\n\n\nstatic void close_func (LexState *ls) {\n  lua_State *L = ls->L;\n  FuncState *fs = ls->fs;\n  Proto *f = fs->f;\n  luaK_ret(fs, luaY_nvarstack(fs), 0);  /* final return */\n  leaveblock(fs);\n  lua_assert(fs->bl == NULL);\n  luaK_finish(fs);\n  luaM_shrinkvector(L, f->code, f->sizecode, fs->pc, Instruction);\n  luaM_shrinkvector(L, f->lineinfo, f->sizelineinfo, fs->pc, ls_byte);\n  luaM_shrinkvector(L, f->abslineinfo, f->sizeabslineinfo,\n                       fs->nabslineinfo, AbsLineInfo);\n  luaM_shrinkvector(L, f->k, f->sizek, fs->nk, TValue);\n  luaM_shrinkvector(L, f->p, f->sizep, fs->np, Proto *);\n  luaM_shrinkvector(L, f->locvars, f->sizelocvars, fs->ndebugvars, LocVar);\n  luaM_shrinkvector(L, f->upvalues, f->sizeupvalues, fs->nups, Upvaldesc);\n  ls->fs = fs->prev;\n  luaC_checkGC(L);\n}\n\n\n\n/*============================================================*/\n/* GRAMMAR RULES */\n/*============================================================*/\n\n\n/*\n** check whether current token is in the follow set of a block.\n** 'until' closes syntactical blocks, but do not close scope,\n** so it is handled in separate.\n*/\nstatic int block_follow (LexState *ls, int withuntil) {\n  switch (ls->t.token) {\n    case TK_ELSE: case TK_ELSEIF:\n    case TK_END: case TK_EOS:\n      return 1;\n    case TK_UNTIL: return withuntil;\n    default: return 0;\n  }\n}\n\n\nstatic void statlist (LexState *ls) {\n  /* statlist -> { stat [';'] } */\n  while (!block_follow(ls, 1)) {\n    if (ls->t.token == TK_RETURN) {\n      statement(ls);\n      return;  /* 'return' must be last statement */\n    }\n    statement(ls);\n  }\n}\n\n\nstatic void fieldsel (LexState *ls, expdesc *v) {\n  /* fieldsel -> ['.' | ':'] NAME */\n  FuncState *fs = ls->fs;\n  expdesc key;\n  luaK_exp2anyregup(fs, v);\n  luaX_next(ls);  /* skip the dot or colon */\n  codename(ls, &key);\n  luaK_indexed(fs, v, &key);\n}\n\n\nstatic void yindex (LexState *ls, expdesc *v) {\n  /* index -> '[' expr ']' */\n  luaX_next(ls);  /* skip the '[' */\n  expr(ls, v);\n  luaK_exp2val(ls->fs, v);\n  checknext(ls, ']');\n}\n\n\n/*\n** {======================================================================\n** Rules for Constructors\n** =======================================================================\n*/\n\n\ntypedef struct ConsControl {\n  expdesc v;  /* last list item read */\n  expdesc *t;  /* table descriptor */\n  int nh;  /* total number of 'record' elements */\n  int na;  /* number of array elements already stored */\n  int tostore;  /* number of array elements pending to be stored */\n} ConsControl;\n\n\nstatic void recfield (LexState *ls, ConsControl *cc) {\n  /* recfield -> (NAME | '['exp']') = exp */\n  FuncState *fs = ls->fs;\n  int reg = ls->fs->freereg;\n  expdesc tab, key, val;\n  if (ls->t.token == TK_NAME) {\n    checklimit(fs, cc->nh, MAX_INT, \"items in a constructor\");\n    codename(ls, &key);\n  }\n  else  /* ls->t.token == '[' */\n    yindex(ls, &key);\n  cc->nh++;\n  checknext(ls, '=');\n  tab = *cc->t;\n  luaK_indexed(fs, &tab, &key);\n  expr(ls, &val);\n  luaK_storevar(fs, &tab, &val);\n  fs->freereg = reg;  /* free registers */\n}\n\n\nstatic void closelistfield (FuncState *fs, ConsControl *cc) {\n  if (cc->v.k == VVOID) return;  /* there is no list item */\n  luaK_exp2nextreg(fs, &cc->v);\n  cc->v.k = VVOID;\n  if (cc->tostore == LFIELDS_PER_FLUSH) {\n    luaK_setlist(fs, cc->t->u.info, cc->na, cc->tostore);  /* flush */\n    cc->na += cc->tostore;\n    cc->tostore = 0;  /* no more items pending */\n  }\n}\n\n\nstatic void lastlistfield (FuncState *fs, ConsControl *cc) {\n  if (cc->tostore == 0) return;\n  if (hasmultret(cc->v.k)) {\n    luaK_setmultret(fs, &cc->v);\n    luaK_setlist(fs, cc->t->u.info, cc->na, LUA_MULTRET);\n    cc->na--;  /* do not count last expression (unknown number of elements) */\n  }\n  else {\n    if (cc->v.k != VVOID)\n      luaK_exp2nextreg(fs, &cc->v);\n    luaK_setlist(fs, cc->t->u.info, cc->na, cc->tostore);\n  }\n  cc->na += cc->tostore;\n}\n\n\nstatic void listfield (LexState *ls, ConsControl *cc) {\n  /* listfield -> exp */\n  expr(ls, &cc->v);\n  cc->tostore++;\n}\n\n\nstatic void field (LexState *ls, ConsControl *cc) {\n  /* field -> listfield | recfield */\n  switch(ls->t.token) {\n    case TK_NAME: {  /* may be 'listfield' or 'recfield' */\n      if (luaX_lookahead(ls) != '=')  /* expression? */\n        listfield(ls, cc);\n      else\n        recfield(ls, cc);\n      break;\n    }\n    case '[': {\n      recfield(ls, cc);\n      break;\n    }\n    default: {\n      listfield(ls, cc);\n      break;\n    }\n  }\n}\n\n\nstatic void constructor (LexState *ls, expdesc *t) {\n  /* constructor -> '{' [ field { sep field } [sep] ] '}'\n     sep -> ',' | ';' */\n  FuncState *fs = ls->fs;\n  int line = ls->linenumber;\n  int pc = luaK_codeABC(fs, OP_NEWTABLE, 0, 0, 0);\n  ConsControl cc;\n  luaK_code(fs, 0);  /* space for extra arg. */\n  cc.na = cc.nh = cc.tostore = 0;\n  cc.t = t;\n  init_exp(t, VNONRELOC, fs->freereg);  /* table will be at stack top */\n  luaK_reserveregs(fs, 1);\n  init_exp(&cc.v, VVOID, 0);  /* no value (yet) */\n  checknext(ls, '{');\n  do {\n    lua_assert(cc.v.k == VVOID || cc.tostore > 0);\n    if (ls->t.token == '}') break;\n    closelistfield(fs, &cc);\n    field(ls, &cc);\n  } while (testnext(ls, ',') || testnext(ls, ';'));\n  check_match(ls, '}', '{', line);\n  lastlistfield(fs, &cc);\n  luaK_settablesize(fs, pc, t->u.info, cc.na, cc.nh);\n}\n\n/* }====================================================================== */\n\n\nstatic void setvararg (FuncState *fs, int nparams) {\n  fs->f->is_vararg = 1;\n  luaK_codeABC(fs, OP_VARARGPREP, nparams, 0, 0);\n}\n\n\nstatic void parlist (LexState *ls) {\n  /* parlist -> [ {NAME ','} (NAME | '...') ] */\n  FuncState *fs = ls->fs;\n  Proto *f = fs->f;\n  int nparams = 0;\n  int isvararg = 0;\n  if (ls->t.token != ')') {  /* is 'parlist' not empty? */\n    do {\n      switch (ls->t.token) {\n        case TK_NAME: {\n          new_localvar(ls, str_checkname(ls));\n          nparams++;\n          break;\n        }\n        case TK_DOTS: {\n          luaX_next(ls);\n          isvararg = 1;\n          break;\n        }\n        default: luaX_syntaxerror(ls, \"<name> or '...' expected\");\n      }\n    } while (!isvararg && testnext(ls, ','));\n  }\n  adjustlocalvars(ls, nparams);\n  f->numparams = cast_byte(fs->nactvar);\n  if (isvararg)\n    setvararg(fs, f->numparams);  /* declared vararg */\n  luaK_reserveregs(fs, fs->nactvar);  /* reserve registers for parameters */\n}\n\n\nstatic void body (LexState *ls, expdesc *e, int ismethod, int line) {\n  /* body ->  '(' parlist ')' block END */\n  FuncState new_fs;\n  BlockCnt bl;\n  new_fs.f = addprototype(ls);\n  new_fs.f->linedefined = line;\n  open_func(ls, &new_fs, &bl);\n  checknext(ls, '(');\n  if (ismethod) {\n    new_localvarliteral(ls, \"self\");  /* create 'self' parameter */\n    adjustlocalvars(ls, 1);\n  }\n  parlist(ls);\n  checknext(ls, ')');\n  statlist(ls);\n  new_fs.f->lastlinedefined = ls->linenumber;\n  check_match(ls, TK_END, TK_FUNCTION, line);\n  codeclosure(ls, e);\n  close_func(ls);\n}\n\n\nstatic int explist (LexState *ls, expdesc *v) {\n  /* explist -> expr { ',' expr } */\n  int n = 1;  /* at least one expression */\n  expr(ls, v);\n  while (testnext(ls, ',')) {\n    luaK_exp2nextreg(ls->fs, v);\n    expr(ls, v);\n    n++;\n  }\n  return n;\n}\n\n\nstatic void funcargs (LexState *ls, expdesc *f, int line) {\n  FuncState *fs = ls->fs;\n  expdesc args;\n  int base, nparams;\n  switch (ls->t.token) {\n    case '(': {  /* funcargs -> '(' [ explist ] ')' */\n      luaX_next(ls);\n      if (ls->t.token == ')')  /* arg list is empty? */\n        args.k = VVOID;\n      else {\n        explist(ls, &args);\n        if (hasmultret(args.k))\n          luaK_setmultret(fs, &args);\n      }\n      check_match(ls, ')', '(', line);\n      break;\n    }\n    case '{': {  /* funcargs -> constructor */\n      constructor(ls, &args);\n      break;\n    }\n    case TK_STRING: {  /* funcargs -> STRING */\n      codestring(&args, ls->t.seminfo.ts);\n      luaX_next(ls);  /* must use 'seminfo' before 'next' */\n      break;\n    }\n    default: {\n      luaX_syntaxerror(ls, \"function arguments expected\");\n    }\n  }\n  lua_assert(f->k == VNONRELOC);\n  base = f->u.info;  /* base register for call */\n  if (hasmultret(args.k))\n    nparams = LUA_MULTRET;  /* open call */\n  else {\n    if (args.k != VVOID)\n      luaK_exp2nextreg(fs, &args);  /* close last argument */\n    nparams = fs->freereg - (base+1);\n  }\n  init_exp(f, VCALL, luaK_codeABC(fs, OP_CALL, base, nparams+1, 2));\n  luaK_fixline(fs, line);\n  fs->freereg = base+1;  /* call remove function and arguments and leaves\n                            (unless changed) one result */\n}\n\n\n\n\n/*\n** {======================================================================\n** Expression parsing\n** =======================================================================\n*/\n\n\nstatic void primaryexp (LexState *ls, expdesc *v) {\n  /* primaryexp -> NAME | '(' expr ')' */\n  switch (ls->t.token) {\n    case '(': {\n      int line = ls->linenumber;\n      luaX_next(ls);\n      expr(ls, v);\n      check_match(ls, ')', '(', line);\n      luaK_dischargevars(ls->fs, v);\n      return;\n    }\n    case TK_NAME: {\n      singlevar(ls, v);\n      return;\n    }\n    default: {\n      luaX_syntaxerror(ls, \"unexpected symbol\");\n    }\n  }\n}\n\n\nstatic void suffixedexp (LexState *ls, expdesc *v) {\n  /* suffixedexp ->\n       primaryexp { '.' NAME | '[' exp ']' | ':' NAME funcargs | funcargs } */\n  FuncState *fs = ls->fs;\n  int line = ls->linenumber;\n  primaryexp(ls, v);\n  for (;;) {\n    switch (ls->t.token) {\n      case '.': {  /* fieldsel */\n        fieldsel(ls, v);\n        break;\n      }\n      case '[': {  /* '[' exp ']' */\n        expdesc key;\n        luaK_exp2anyregup(fs, v);\n        yindex(ls, &key);\n        luaK_indexed(fs, v, &key);\n        break;\n      }\n      case ':': {  /* ':' NAME funcargs */\n        expdesc key;\n        luaX_next(ls);\n        codename(ls, &key);\n        luaK_self(fs, v, &key);\n        funcargs(ls, v, line);\n        break;\n      }\n      case '(': case TK_STRING: case '{': {  /* funcargs */\n        luaK_exp2nextreg(fs, v);\n        funcargs(ls, v, line);\n        break;\n      }\n      default: return;\n    }\n  }\n}\n\n\nstatic void simpleexp (LexState *ls, expdesc *v) {\n  /* simpleexp -> FLT | INT | STRING | NIL | TRUE | FALSE | ... |\n                  constructor | FUNCTION body | suffixedexp */\n  switch (ls->t.token) {\n    case TK_FLT: {\n      init_exp(v, VKFLT, 0);\n      v->u.nval = ls->t.seminfo.r;\n      break;\n    }\n    case TK_INT: {\n      init_exp(v, VKINT, 0);\n      v->u.ival = ls->t.seminfo.i;\n      break;\n    }\n    case TK_STRING: {\n      codestring(v, ls->t.seminfo.ts);\n      break;\n    }\n    case TK_NIL: {\n      init_exp(v, VNIL, 0);\n      break;\n    }\n    case TK_TRUE: {\n      init_exp(v, VTRUE, 0);\n      break;\n    }\n    case TK_FALSE: {\n      init_exp(v, VFALSE, 0);\n      break;\n    }\n    case TK_DOTS: {  /* vararg */\n      FuncState *fs = ls->fs;\n      check_condition(ls, fs->f->is_vararg,\n                      \"cannot use '...' outside a vararg function\");\n      init_exp(v, VVARARG, luaK_codeABC(fs, OP_VARARG, 0, 0, 1));\n      break;\n    }\n    case '{': {  /* constructor */\n      constructor(ls, v);\n      return;\n    }\n    case TK_FUNCTION: {\n      luaX_next(ls);\n      body(ls, v, 0, ls->linenumber);\n      return;\n    }\n    default: {\n      suffixedexp(ls, v);\n      return;\n    }\n  }\n  luaX_next(ls);\n}\n\n\nstatic UnOpr getunopr (int op) {\n  switch (op) {\n    case TK_NOT: return OPR_NOT;\n    case '-': return OPR_MINUS;\n    case '~': return OPR_BNOT;\n    case '#': return OPR_LEN;\n    default: return OPR_NOUNOPR;\n  }\n}\n\n\nstatic BinOpr getbinopr (int op) {\n  switch (op) {\n    case '+': return OPR_ADD;\n    case '-': return OPR_SUB;\n    case '*': return OPR_MUL;\n    case '%': return OPR_MOD;\n    case '^': return OPR_POW;\n    case '/': return OPR_DIV;\n    case TK_IDIV: return OPR_IDIV;\n    case '&': return OPR_BAND;\n    case '|': return OPR_BOR;\n    case '~': return OPR_BXOR;\n    case TK_SHL: return OPR_SHL;\n    case TK_SHR: return OPR_SHR;\n    case TK_CONCAT: return OPR_CONCAT;\n    case TK_NE: return OPR_NE;\n    case TK_EQ: return OPR_EQ;\n    case '<': return OPR_LT;\n    case TK_LE: return OPR_LE;\n    case '>': return OPR_GT;\n    case TK_GE: return OPR_GE;\n    case TK_AND: return OPR_AND;\n    case TK_OR: return OPR_OR;\n    default: return OPR_NOBINOPR;\n  }\n}\n\n\n/*\n** Priority table for binary operators.\n*/\nstatic const struct {\n  lu_byte left;  /* left priority for each binary operator */\n  lu_byte right; /* right priority */\n} priority[] = {  /* ORDER OPR */\n   {10, 10}, {10, 10},           /* '+' '-' */\n   {11, 11}, {11, 11},           /* '*' '%' */\n   {14, 13},                  /* '^' (right associative) */\n   {11, 11}, {11, 11},           /* '/' '//' */\n   {6, 6}, {4, 4}, {5, 5},   /* '&' '|' '~' */\n   {7, 7}, {7, 7},           /* '<<' '>>' */\n   {9, 8},                   /* '..' (right associative) */\n   {3, 3}, {3, 3}, {3, 3},   /* ==, <, <= */\n   {3, 3}, {3, 3}, {3, 3},   /* ~=, >, >= */\n   {2, 2}, {1, 1}            /* and, or */\n};\n\n#define UNARY_PRIORITY\t12  /* priority for unary operators */\n\n\n/*\n** subexpr -> (simpleexp | unop subexpr) { binop subexpr }\n** where 'binop' is any binary operator with a priority higher than 'limit'\n*/\nstatic BinOpr subexpr (LexState *ls, expdesc *v, int limit) {\n  BinOpr op;\n  UnOpr uop;\n  enterlevel(ls);\n  uop = getunopr(ls->t.token);\n  if (uop != OPR_NOUNOPR) {  /* prefix (unary) operator? */\n    int line = ls->linenumber;\n    luaX_next(ls);  /* skip operator */\n    subexpr(ls, v, UNARY_PRIORITY);\n    luaK_prefix(ls->fs, uop, v, line);\n  }\n  else simpleexp(ls, v);\n  /* expand while operators have priorities higher than 'limit' */\n  op = getbinopr(ls->t.token);\n  while (op != OPR_NOBINOPR && priority[op].left > limit) {\n    expdesc v2;\n    BinOpr nextop;\n    int line = ls->linenumber;\n    luaX_next(ls);  /* skip operator */\n    luaK_infix(ls->fs, op, v);\n    /* read sub-expression with higher priority */\n    nextop = subexpr(ls, &v2, priority[op].right);\n    luaK_posfix(ls->fs, op, v, &v2, line);\n    op = nextop;\n  }\n  leavelevel(ls);\n  return op;  /* return first untreated operator */\n}\n\n\nstatic void expr (LexState *ls, expdesc *v) {\n  subexpr(ls, v, 0);\n}\n\n/* }==================================================================== */\n\n\n\n/*\n** {======================================================================\n** Rules for Statements\n** =======================================================================\n*/\n\n\nstatic void block (LexState *ls) {\n  /* block -> statlist */\n  FuncState *fs = ls->fs;\n  BlockCnt bl;\n  enterblock(fs, &bl, 0);\n  statlist(ls);\n  leaveblock(fs);\n}\n\n\n/*\n** structure to chain all variables in the left-hand side of an\n** assignment\n*/\nstruct LHS_assign {\n  struct LHS_assign *prev;\n  expdesc v;  /* variable (global, local, upvalue, or indexed) */\n};\n\n\n/*\n** check whether, in an assignment to an upvalue/local variable, the\n** upvalue/local variable is begin used in a previous assignment to a\n** table. If so, save original upvalue/local value in a safe place and\n** use this safe copy in the previous assignment.\n*/\nstatic void check_conflict (LexState *ls, struct LHS_assign *lh, expdesc *v) {\n  FuncState *fs = ls->fs;\n  int extra = fs->freereg;  /* eventual position to save local variable */\n  int conflict = 0;\n  for (; lh; lh = lh->prev) {  /* check all previous assignments */\n    if (vkisindexed(lh->v.k)) {  /* assignment to table field? */\n      if (lh->v.k == VINDEXUP) {  /* is table an upvalue? */\n        if (v->k == VUPVAL && lh->v.u.ind.t == v->u.info) {\n          conflict = 1;  /* table is the upvalue being assigned now */\n          lh->v.k = VINDEXSTR;\n          lh->v.u.ind.t = extra;  /* assignment will use safe copy */\n        }\n      }\n      else {  /* table is a register */\n        if (v->k == VLOCAL && lh->v.u.ind.t == v->u.var.ridx) {\n          conflict = 1;  /* table is the local being assigned now */\n          lh->v.u.ind.t = extra;  /* assignment will use safe copy */\n        }\n        /* is index the local being assigned? */\n        if (lh->v.k == VINDEXED && v->k == VLOCAL &&\n            lh->v.u.ind.idx == v->u.var.ridx) {\n          conflict = 1;\n          lh->v.u.ind.idx = extra;  /* previous assignment will use safe copy */\n        }\n      }\n    }\n  }\n  if (conflict) {\n    /* copy upvalue/local value to a temporary (in position 'extra') */\n    if (v->k == VLOCAL)\n      luaK_codeABC(fs, OP_MOVE, extra, v->u.var.ridx, 0);\n    else\n      luaK_codeABC(fs, OP_GETUPVAL, extra, v->u.info, 0);\n    luaK_reserveregs(fs, 1);\n  }\n}\n\n/*\n** Parse and compile a multiple assignment. The first \"variable\"\n** (a 'suffixedexp') was already read by the caller.\n**\n** assignment -> suffixedexp restassign\n** restassign -> ',' suffixedexp restassign | '=' explist\n*/\nstatic void restassign (LexState *ls, struct LHS_assign *lh, int nvars) {\n  expdesc e;\n  check_condition(ls, vkisvar(lh->v.k), \"syntax error\");\n  check_readonly(ls, &lh->v);\n  if (testnext(ls, ',')) {  /* restassign -> ',' suffixedexp restassign */\n    struct LHS_assign nv;\n    nv.prev = lh;\n    suffixedexp(ls, &nv.v);\n    if (!vkisindexed(nv.v.k))\n      check_conflict(ls, lh, &nv.v);\n    enterlevel(ls);  /* control recursion depth */\n    restassign(ls, &nv, nvars+1);\n    leavelevel(ls);\n  }\n  else {  /* restassign -> '=' explist */\n    int nexps;\n    checknext(ls, '=');\n    nexps = explist(ls, &e);\n    if (nexps != nvars)\n      adjust_assign(ls, nvars, nexps, &e);\n    else {\n      luaK_setoneret(ls->fs, &e);  /* close last expression */\n      luaK_storevar(ls->fs, &lh->v, &e);\n      return;  /* avoid default */\n    }\n  }\n  init_exp(&e, VNONRELOC, ls->fs->freereg-1);  /* default assignment */\n  luaK_storevar(ls->fs, &lh->v, &e);\n}\n\n\nstatic int cond (LexState *ls) {\n  /* cond -> exp */\n  expdesc v;\n  expr(ls, &v);  /* read condition */\n  if (v.k == VNIL) v.k = VFALSE;  /* 'falses' are all equal here */\n  luaK_goiftrue(ls->fs, &v);\n  return v.f;\n}\n\n\nstatic void gotostat (LexState *ls) {\n  FuncState *fs = ls->fs;\n  int line = ls->linenumber;\n  TString *name = str_checkname(ls);  /* label's name */\n  Labeldesc *lb = findlabel(ls, name);\n  if (lb == NULL)  /* no label? */\n    /* forward jump; will be resolved when the label is declared */\n    newgotoentry(ls, name, line, luaK_jump(fs));\n  else {  /* found a label */\n    /* backward jump; will be resolved here */\n    int lblevel = reglevel(fs, lb->nactvar);  /* label level */\n    if (luaY_nvarstack(fs) > lblevel)  /* leaving the scope of a variable? */\n      luaK_codeABC(fs, OP_CLOSE, lblevel, 0, 0);\n    /* create jump and link it to the label */\n    luaK_patchlist(fs, luaK_jump(fs), lb->pc);\n  }\n}\n\n\n/*\n** Break statement. Semantically equivalent to \"goto break\".\n*/\nstatic void breakstat (LexState *ls) {\n  int line = ls->linenumber;\n  luaX_next(ls);  /* skip break */\n  newgotoentry(ls, luaS_newliteral(ls->L, \"break\"), line, luaK_jump(ls->fs));\n}\n\n\n/*\n** Check whether there is already a label with the given 'name'.\n*/\nstatic void checkrepeated (LexState *ls, TString *name) {\n  Labeldesc *lb = findlabel(ls, name);\n  if (l_unlikely(lb != NULL)) {  /* already defined? */\n    const char *msg = \"label '%s' already defined on line %d\";\n    msg = luaO_pushfstring(ls->L, msg, getstr(name), lb->line);\n    luaK_semerror(ls, msg);  /* error */\n  }\n}\n\n\nstatic void labelstat (LexState *ls, TString *name, int line) {\n  /* label -> '::' NAME '::' */\n  checknext(ls, TK_DBCOLON);  /* skip double colon */\n  while (ls->t.token == ';' || ls->t.token == TK_DBCOLON)\n    statement(ls);  /* skip other no-op statements */\n  checkrepeated(ls, name);  /* check for repeated labels */\n  createlabel(ls, name, line, block_follow(ls, 0));\n}\n\n\nstatic void whilestat (LexState *ls, int line) {\n  /* whilestat -> WHILE cond DO block END */\n  FuncState *fs = ls->fs;\n  int whileinit;\n  int condexit;\n  BlockCnt bl;\n  luaX_next(ls);  /* skip WHILE */\n  whileinit = luaK_getlabel(fs);\n  condexit = cond(ls);\n  enterblock(fs, &bl, 1);\n  checknext(ls, TK_DO);\n  block(ls);\n  luaK_jumpto(fs, whileinit);\n  check_match(ls, TK_END, TK_WHILE, line);\n  leaveblock(fs);\n  luaK_patchtohere(fs, condexit);  /* false conditions finish the loop */\n}\n\n\nstatic void repeatstat (LexState *ls, int line) {\n  /* repeatstat -> REPEAT block UNTIL cond */\n  int condexit;\n  FuncState *fs = ls->fs;\n  int repeat_init = luaK_getlabel(fs);\n  BlockCnt bl1, bl2;\n  enterblock(fs, &bl1, 1);  /* loop block */\n  enterblock(fs, &bl2, 0);  /* scope block */\n  luaX_next(ls);  /* skip REPEAT */\n  statlist(ls);\n  check_match(ls, TK_UNTIL, TK_REPEAT, line);\n  condexit = cond(ls);  /* read condition (inside scope block) */\n  leaveblock(fs);  /* finish scope */\n  if (bl2.upval) {  /* upvalues? */\n    int exit = luaK_jump(fs);  /* normal exit must jump over fix */\n    luaK_patchtohere(fs, condexit);  /* repetition must close upvalues */\n    luaK_codeABC(fs, OP_CLOSE, reglevel(fs, bl2.nactvar), 0, 0);\n    condexit = luaK_jump(fs);  /* repeat after closing upvalues */\n    luaK_patchtohere(fs, exit);  /* normal exit comes to here */\n  }\n  luaK_patchlist(fs, condexit, repeat_init);  /* close the loop */\n  leaveblock(fs);  /* finish loop */\n}\n\n\n/*\n** Read an expression and generate code to put its results in next\n** stack slot.\n**\n*/\nstatic void exp1 (LexState *ls) {\n  expdesc e;\n  expr(ls, &e);\n  luaK_exp2nextreg(ls->fs, &e);\n  lua_assert(e.k == VNONRELOC);\n}\n\n\n/*\n** Fix for instruction at position 'pc' to jump to 'dest'.\n** (Jump addresses are relative in Lua). 'back' true means\n** a back jump.\n*/\nstatic void fixforjump (FuncState *fs, int pc, int dest, int back) {\n  Instruction *jmp = &fs->f->code[pc];\n  int offset = dest - (pc + 1);\n  if (back)\n    offset = -offset;\n  if (l_unlikely(offset > MAXARG_Bx))\n    luaX_syntaxerror(fs->ls, \"control structure too long\");\n  SETARG_Bx(*jmp, offset);\n}\n\n\n/*\n** Generate code for a 'for' loop.\n*/\nstatic void forbody (LexState *ls, int base, int line, int nvars, int isgen) {\n  /* forbody -> DO block */\n  static const OpCode forprep[2] = {OP_FORPREP, OP_TFORPREP};\n  static const OpCode forloop[2] = {OP_FORLOOP, OP_TFORLOOP};\n  BlockCnt bl;\n  FuncState *fs = ls->fs;\n  int prep, endfor;\n  checknext(ls, TK_DO);\n  prep = luaK_codeABx(fs, forprep[isgen], base, 0);\n  enterblock(fs, &bl, 0);  /* scope for declared variables */\n  adjustlocalvars(ls, nvars);\n  luaK_reserveregs(fs, nvars);\n  block(ls);\n  leaveblock(fs);  /* end of scope for declared variables */\n  fixforjump(fs, prep, luaK_getlabel(fs), 0);\n  if (isgen) {  /* generic for? */\n    luaK_codeABC(fs, OP_TFORCALL, base, 0, nvars);\n    luaK_fixline(fs, line);\n  }\n  endfor = luaK_codeABx(fs, forloop[isgen], base, 0);\n  fixforjump(fs, endfor, prep + 1, 1);\n  luaK_fixline(fs, line);\n}\n\n\nstatic void fornum (LexState *ls, TString *varname, int line) {\n  /* fornum -> NAME = exp,exp[,exp] forbody */\n  FuncState *fs = ls->fs;\n  int base = fs->freereg;\n  new_localvarliteral(ls, \"(for state)\");\n  new_localvarliteral(ls, \"(for state)\");\n  new_localvarliteral(ls, \"(for state)\");\n  new_localvar(ls, varname);\n  checknext(ls, '=');\n  exp1(ls);  /* initial value */\n  checknext(ls, ',');\n  exp1(ls);  /* limit */\n  if (testnext(ls, ','))\n    exp1(ls);  /* optional step */\n  else {  /* default step = 1 */\n    luaK_int(fs, fs->freereg, 1);\n    luaK_reserveregs(fs, 1);\n  }\n  adjustlocalvars(ls, 3);  /* control variables */\n  forbody(ls, base, line, 1, 0);\n}\n\n\nstatic void forlist (LexState *ls, TString *indexname) {\n  /* forlist -> NAME {,NAME} IN explist forbody */\n  FuncState *fs = ls->fs;\n  expdesc e;\n  int nvars = 5;  /* gen, state, control, toclose, 'indexname' */\n  int line;\n  int base = fs->freereg;\n  /* create control variables */\n  new_localvarliteral(ls, \"(for state)\");\n  new_localvarliteral(ls, \"(for state)\");\n  new_localvarliteral(ls, \"(for state)\");\n  new_localvarliteral(ls, \"(for state)\");\n  /* create declared variables */\n  new_localvar(ls, indexname);\n  while (testnext(ls, ',')) {\n    new_localvar(ls, str_checkname(ls));\n    nvars++;\n  }\n  checknext(ls, TK_IN);\n  line = ls->linenumber;\n  adjust_assign(ls, 4, explist(ls, &e), &e);\n  adjustlocalvars(ls, 4);  /* control variables */\n  marktobeclosed(fs);  /* last control var. must be closed */\n  luaK_checkstack(fs, 3);  /* extra space to call generator */\n  forbody(ls, base, line, nvars - 4, 1);\n}\n\n\nstatic void forstat (LexState *ls, int line) {\n  /* forstat -> FOR (fornum | forlist) END */\n  FuncState *fs = ls->fs;\n  TString *varname;\n  BlockCnt bl;\n  enterblock(fs, &bl, 1);  /* scope for loop and control variables */\n  luaX_next(ls);  /* skip 'for' */\n  varname = str_checkname(ls);  /* first variable name */\n  switch (ls->t.token) {\n    case '=': fornum(ls, varname, line); break;\n    case ',': case TK_IN: forlist(ls, varname); break;\n    default: luaX_syntaxerror(ls, \"'=' or 'in' expected\");\n  }\n  check_match(ls, TK_END, TK_FOR, line);\n  leaveblock(fs);  /* loop scope ('break' jumps to this point) */\n}\n\n\nstatic void test_then_block (LexState *ls, int *escapelist) {\n  /* test_then_block -> [IF | ELSEIF] cond THEN block */\n  BlockCnt bl;\n  FuncState *fs = ls->fs;\n  expdesc v;\n  int jf;  /* instruction to skip 'then' code (if condition is false) */\n  luaX_next(ls);  /* skip IF or ELSEIF */\n  expr(ls, &v);  /* read condition */\n  checknext(ls, TK_THEN);\n  if (ls->t.token == TK_BREAK) {  /* 'if x then break' ? */\n    int line = ls->linenumber;\n    luaK_goiffalse(ls->fs, &v);  /* will jump if condition is true */\n    luaX_next(ls);  /* skip 'break' */\n    enterblock(fs, &bl, 0);  /* must enter block before 'goto' */\n    newgotoentry(ls, luaS_newliteral(ls->L, \"break\"), line, v.t);\n    while (testnext(ls, ';')) {}  /* skip semicolons */\n    if (block_follow(ls, 0)) {  /* jump is the entire block? */\n      leaveblock(fs);\n      return;  /* and that is it */\n    }\n    else  /* must skip over 'then' part if condition is false */\n      jf = luaK_jump(fs);\n  }\n  else {  /* regular case (not a break) */\n    luaK_goiftrue(ls->fs, &v);  /* skip over block if condition is false */\n    enterblock(fs, &bl, 0);\n    jf = v.f;\n  }\n  statlist(ls);  /* 'then' part */\n  leaveblock(fs);\n  if (ls->t.token == TK_ELSE ||\n      ls->t.token == TK_ELSEIF)  /* followed by 'else'/'elseif'? */\n    luaK_concat(fs, escapelist, luaK_jump(fs));  /* must jump over it */\n  luaK_patchtohere(fs, jf);\n}\n\n\nstatic void ifstat (LexState *ls, int line) {\n  /* ifstat -> IF cond THEN block {ELSEIF cond THEN block} [ELSE block] END */\n  FuncState *fs = ls->fs;\n  int escapelist = NO_JUMP;  /* exit list for finished parts */\n  test_then_block(ls, &escapelist);  /* IF cond THEN block */\n  while (ls->t.token == TK_ELSEIF)\n    test_then_block(ls, &escapelist);  /* ELSEIF cond THEN block */\n  if (testnext(ls, TK_ELSE))\n    block(ls);  /* 'else' part */\n  check_match(ls, TK_END, TK_IF, line);\n  luaK_patchtohere(fs, escapelist);  /* patch escape list to 'if' end */\n}\n\n\nstatic void localfunc (LexState *ls) {\n  expdesc b;\n  FuncState *fs = ls->fs;\n  int fvar = fs->nactvar;  /* function's variable index */\n  new_localvar(ls, str_checkname(ls));  /* new local variable */\n  adjustlocalvars(ls, 1);  /* enter its scope */\n  body(ls, &b, 0, ls->linenumber);  /* function created in next register */\n  /* debug information will only see the variable after this point! */\n  localdebuginfo(fs, fvar)->startpc = fs->pc;\n}\n\n\nstatic int getlocalattribute (LexState *ls) {\n  /* ATTRIB -> ['<' Name '>'] */\n  if (testnext(ls, '<')) {\n    const char *attr = getstr(str_checkname(ls));\n    checknext(ls, '>');\n    if (strcmp(attr, \"const\") == 0)\n      return RDKCONST;  /* read-only variable */\n    else if (strcmp(attr, \"close\") == 0)\n      return RDKTOCLOSE;  /* to-be-closed variable */\n    else\n      luaK_semerror(ls,\n        luaO_pushfstring(ls->L, \"unknown attribute '%s'\", attr));\n  }\n  return VDKREG;  /* regular variable */\n}\n\n\nstatic void checktoclose (FuncState *fs, int level) {\n  if (level != -1) {  /* is there a to-be-closed variable? */\n    marktobeclosed(fs);\n    luaK_codeABC(fs, OP_TBC, reglevel(fs, level), 0, 0);\n  }\n}\n\n\nstatic void localstat (LexState *ls) {\n  /* stat -> LOCAL NAME ATTRIB { ',' NAME ATTRIB } ['=' explist] */\n  FuncState *fs = ls->fs;\n  int toclose = -1;  /* index of to-be-closed variable (if any) */\n  Vardesc *var;  /* last variable */\n  int vidx, kind;  /* index and kind of last variable */\n  int nvars = 0;\n  int nexps;\n  expdesc e;\n  do {\n    vidx = new_localvar(ls, str_checkname(ls));\n    kind = getlocalattribute(ls);\n    getlocalvardesc(fs, vidx)->vd.kind = kind;\n    if (kind == RDKTOCLOSE) {  /* to-be-closed? */\n      if (toclose != -1)  /* one already present? */\n        luaK_semerror(ls, \"multiple to-be-closed variables in local list\");\n      toclose = fs->nactvar + nvars;\n    }\n    nvars++;\n  } while (testnext(ls, ','));\n  if (testnext(ls, '='))\n    nexps = explist(ls, &e);\n  else {\n    e.k = VVOID;\n    nexps = 0;\n  }\n  var = getlocalvardesc(fs, vidx);  /* get last variable */\n  if (nvars == nexps &&  /* no adjustments? */\n      var->vd.kind == RDKCONST &&  /* last variable is const? */\n      luaK_exp2const(fs, &e, &var->k)) {  /* compile-time constant? */\n    var->vd.kind = RDKCTC;  /* variable is a compile-time constant */\n    adjustlocalvars(ls, nvars - 1);  /* exclude last variable */\n    fs->nactvar++;  /* but count it */\n  }\n  else {\n    adjust_assign(ls, nvars, nexps, &e);\n    adjustlocalvars(ls, nvars);\n  }\n  checktoclose(fs, toclose);\n}\n\n\nstatic int funcname (LexState *ls, expdesc *v) {\n  /* funcname -> NAME {fieldsel} [':' NAME] */\n  int ismethod = 0;\n  singlevar(ls, v);\n  while (ls->t.token == '.')\n    fieldsel(ls, v);\n  if (ls->t.token == ':') {\n    ismethod = 1;\n    fieldsel(ls, v);\n  }\n  return ismethod;\n}\n\n\nstatic void funcstat (LexState *ls, int line) {\n  /* funcstat -> FUNCTION funcname body */\n  int ismethod;\n  expdesc v, b;\n  luaX_next(ls);  /* skip FUNCTION */\n  ismethod = funcname(ls, &v);\n  body(ls, &b, ismethod, line);\n  check_readonly(ls, &v);\n  luaK_storevar(ls->fs, &v, &b);\n  luaK_fixline(ls->fs, line);  /* definition \"happens\" in the first line */\n}\n\n\nstatic void exprstat (LexState *ls) {\n  /* stat -> func | assignment */\n  FuncState *fs = ls->fs;\n  struct LHS_assign v;\n  suffixedexp(ls, &v.v);\n  if (ls->t.token == '=' || ls->t.token == ',') { /* stat -> assignment ? */\n    v.prev = NULL;\n    restassign(ls, &v, 1);\n  }\n  else {  /* stat -> func */\n    Instruction *inst;\n    check_condition(ls, v.v.k == VCALL, \"syntax error\");\n    inst = &getinstruction(fs, &v.v);\n    SETARG_C(*inst, 1);  /* call statement uses no results */\n  }\n}\n\n\nstatic void retstat (LexState *ls) {\n  /* stat -> RETURN [explist] [';'] */\n  FuncState *fs = ls->fs;\n  expdesc e;\n  int nret;  /* number of values being returned */\n  int first = luaY_nvarstack(fs);  /* first slot to be returned */\n  if (block_follow(ls, 1) || ls->t.token == ';')\n    nret = 0;  /* return no values */\n  else {\n    nret = explist(ls, &e);  /* optional return values */\n    if (hasmultret(e.k)) {\n      luaK_setmultret(fs, &e);\n      if (e.k == VCALL && nret == 1 && !fs->bl->insidetbc) {  /* tail call? */\n        SET_OPCODE(getinstruction(fs,&e), OP_TAILCALL);\n        lua_assert(GETARG_A(getinstruction(fs,&e)) == luaY_nvarstack(fs));\n      }\n      nret = LUA_MULTRET;  /* return all values */\n    }\n    else {\n      if (nret == 1)  /* only one single value? */\n        first = luaK_exp2anyreg(fs, &e);  /* can use original slot */\n      else {  /* values must go to the top of the stack */\n        luaK_exp2nextreg(fs, &e);\n        lua_assert(nret == fs->freereg - first);\n      }\n    }\n  }\n  luaK_ret(fs, first, nret);\n  testnext(ls, ';');  /* skip optional semicolon */\n}\n\n\nstatic void statement (LexState *ls) {\n  int line = ls->linenumber;  /* may be needed for error messages */\n  enterlevel(ls);\n  switch (ls->t.token) {\n    case ';': {  /* stat -> ';' (empty statement) */\n      luaX_next(ls);  /* skip ';' */\n      break;\n    }\n    case TK_IF: {  /* stat -> ifstat */\n      ifstat(ls, line);\n      break;\n    }\n    case TK_WHILE: {  /* stat -> whilestat */\n      whilestat(ls, line);\n      break;\n    }\n    case TK_DO: {  /* stat -> DO block END */\n      luaX_next(ls);  /* skip DO */\n      block(ls);\n      check_match(ls, TK_END, TK_DO, line);\n      break;\n    }\n    case TK_FOR: {  /* stat -> forstat */\n      forstat(ls, line);\n      break;\n    }\n    case TK_REPEAT: {  /* stat -> repeatstat */\n      repeatstat(ls, line);\n      break;\n    }\n    case TK_FUNCTION: {  /* stat -> funcstat */\n      funcstat(ls, line);\n      break;\n    }\n    case TK_LOCAL: {  /* stat -> localstat */\n      luaX_next(ls);  /* skip LOCAL */\n      if (testnext(ls, TK_FUNCTION))  /* local function? */\n        localfunc(ls);\n      else\n        localstat(ls);\n      break;\n    }\n    case TK_DBCOLON: {  /* stat -> label */\n      luaX_next(ls);  /* skip double colon */\n      labelstat(ls, str_checkname(ls), line);\n      break;\n    }\n    case TK_RETURN: {  /* stat -> retstat */\n      luaX_next(ls);  /* skip RETURN */\n      retstat(ls);\n      break;\n    }\n    case TK_BREAK: {  /* stat -> breakstat */\n      breakstat(ls);\n      break;\n    }\n    case TK_GOTO: {  /* stat -> 'goto' NAME */\n      luaX_next(ls);  /* skip 'goto' */\n      gotostat(ls);\n      break;\n    }\n    default: {  /* stat -> func | assignment */\n      exprstat(ls);\n      break;\n    }\n  }\n  lua_assert(ls->fs->f->maxstacksize >= ls->fs->freereg &&\n             ls->fs->freereg >= luaY_nvarstack(ls->fs));\n  ls->fs->freereg = luaY_nvarstack(ls->fs);  /* free registers */\n  leavelevel(ls);\n}\n\n/* }====================================================================== */\n\n\n/*\n** compiles the main function, which is a regular vararg function with an\n** upvalue named LUA_ENV\n*/\nstatic void mainfunc (LexState *ls, FuncState *fs) {\n  BlockCnt bl;\n  Upvaldesc *env;\n  open_func(ls, fs, &bl);\n  setvararg(fs, 0);  /* main function is always declared vararg */\n  env = allocupvalue(fs);  /* ...set environment upvalue */\n  env->instack = 1;\n  env->idx = 0;\n  env->kind = VDKREG;\n  env->name = ls->envn;\n  luaC_objbarrier(ls->L, fs->f, env->name);\n  luaX_next(ls);  /* read first token */\n  statlist(ls);  /* parse main body */\n  check(ls, TK_EOS);\n  close_func(ls);\n}\n\n\nLClosure *luaY_parser (lua_State *L, ZIO *z, Mbuffer *buff,\n                       Dyndata *dyd, const char *name, int firstchar) {\n  LexState lexstate;\n  FuncState funcstate;\n  LClosure *cl = luaF_newLclosure(L, 1);  /* create main closure */\n  setclLvalue2s(L, L->top.p, cl);  /* anchor it (to avoid being collected) */\n  luaD_inctop(L);\n  lexstate.h = luaH_new(L);  /* create table for scanner */\n  sethvalue2s(L, L->top.p, lexstate.h);  /* anchor it */\n  luaD_inctop(L);\n  funcstate.f = cl->p = luaF_newproto(L);\n  luaC_objbarrier(L, cl, cl->p);\n  funcstate.f->source = luaS_new(L, name);  /* create and anchor TString */\n  luaC_objbarrier(L, funcstate.f, funcstate.f->source);\n  lexstate.buff = buff;\n  lexstate.dyd = dyd;\n  dyd->actvar.n = dyd->gt.n = dyd->label.n = 0;\n  luaX_setinput(L, &lexstate, z, funcstate.f->source, firstchar);\n  mainfunc(&lexstate, &funcstate);\n  lua_assert(!funcstate.prev && funcstate.nups == 1 && !lexstate.fs);\n  /* all scopes should be correctly finished */\n  lua_assert(dyd->actvar.n == 0 && dyd->gt.n == 0 && dyd->label.n == 0);\n  L->top.p--;  /* remove scanner's table */\n  return cl;  /* closure is on the stack, too */\n}\n\n/*\n** $Id: ldebug.c $\n** Debug Interface\n** See Copyright Notice in lua.h\n*/\n\n#define ldebug_c\n#define LUA_CORE\n\n/*#include \"lprefix.h\"*/\n\n\n#include <stdarg.h>\n#include <stddef.h>\n#include <string.h>\n\n/*#include \"lua.h\"*/\n\n/*#include \"lapi.h\"*/\n/*#include \"lcode.h\"*/\n/*#include \"ldebug.h\"*/\n/*#include \"ldo.h\"*/\n/*#include \"lfunc.h\"*/\n/*#include \"lobject.h\"*/\n/*#include \"lopcodes.h\"*/\n/*#include \"lstate.h\"*/\n/*#include \"lstring.h\"*/\n/*#include \"ltable.h\"*/\n/*#include \"ltm.h\"*/\n/*#include \"lvm.h\"*/\n\n\n\n#define noLuaClosure(f)\t\t((f) == NULL || (f)->c.tt == LUA_VCCL)\n\n\nstatic const char *funcnamefromcall (lua_State *L, CallInfo *ci,\n                                                   const char **name);\n\n\nstatic int currentpc (CallInfo *ci) {\n  lua_assert(isLua(ci));\n  return pcRel(ci->u.l.savedpc, ci_func(ci)->p);\n}\n\n\n/*\n** Get a \"base line\" to find the line corresponding to an instruction.\n** Base lines are regularly placed at MAXIWTHABS intervals, so usually\n** an integer division gets the right place. When the source file has\n** large sequences of empty/comment lines, it may need extra entries,\n** so the original estimate needs a correction.\n** If the original estimate is -1, the initial 'if' ensures that the\n** 'while' will run at least once.\n** The assertion that the estimate is a lower bound for the correct base\n** is valid as long as the debug info has been generated with the same\n** value for MAXIWTHABS or smaller. (Previous releases use a little\n** smaller value.)\n*/\nstatic int getbaseline (const Proto *f, int pc, int *basepc) {\n  if (f->sizeabslineinfo == 0 || pc < f->abslineinfo[0].pc) {\n    *basepc = -1;  /* start from the beginning */\n    return f->linedefined;\n  }\n  else {\n    int i = cast_uint(pc) / MAXIWTHABS - 1;  /* get an estimate */\n    /* estimate must be a lower bound of the correct base */\n    lua_assert(i < 0 ||\n              (i < f->sizeabslineinfo && f->abslineinfo[i].pc <= pc));\n    while (i + 1 < f->sizeabslineinfo && pc >= f->abslineinfo[i + 1].pc)\n      i++;  /* low estimate; adjust it */\n    *basepc = f->abslineinfo[i].pc;\n    return f->abslineinfo[i].line;\n  }\n}\n\n\n/*\n** Get the line corresponding to instruction 'pc' in function 'f';\n** first gets a base line and from there does the increments until\n** the desired instruction.\n*/\nint luaG_getfuncline (const Proto *f, int pc) {\n  if (f->lineinfo == NULL)  /* no debug information? */\n    return -1;\n  else {\n    int basepc;\n    int baseline = getbaseline(f, pc, &basepc);\n    while (basepc++ < pc) {  /* walk until given instruction */\n      lua_assert(f->lineinfo[basepc] != ABSLINEINFO);\n      baseline += f->lineinfo[basepc];  /* correct line */\n    }\n    return baseline;\n  }\n}\n\n\nstatic int getcurrentline (CallInfo *ci) {\n  return luaG_getfuncline(ci_func(ci)->p, currentpc(ci));\n}\n\n\n/*\n** Set 'trap' for all active Lua frames.\n** This function can be called during a signal, under \"reasonable\"\n** assumptions. A new 'ci' is completely linked in the list before it\n** becomes part of the \"active\" list, and we assume that pointers are\n** atomic; see comment in next function.\n** (A compiler doing interprocedural optimizations could, theoretically,\n** reorder memory writes in such a way that the list could be\n** temporarily broken while inserting a new element. We simply assume it\n** has no good reasons to do that.)\n*/\nstatic void settraps (CallInfo *ci) {\n  for (; ci != NULL; ci = ci->previous)\n    if (isLua(ci))\n      ci->u.l.trap = 1;\n}\n\n\n/*\n** This function can be called during a signal, under \"reasonable\"\n** assumptions.\n** Fields 'basehookcount' and 'hookcount' (set by 'resethookcount')\n** are for debug only, and it is no problem if they get arbitrary\n** values (causes at most one wrong hook call). 'hookmask' is an atomic\n** value. We assume that pointers are atomic too (e.g., gcc ensures that\n** for all platforms where it runs). Moreover, 'hook' is always checked\n** before being called (see 'luaD_hook').\n*/\nLUA_API void lua_sethook (lua_State *L, lua_Hook func, int mask, int count) {\n  if (func == NULL || mask == 0) {  /* turn off hooks? */\n    mask = 0;\n    func = NULL;\n  }\n  L->hook = func;\n  L->basehookcount = count;\n  resethookcount(L);\n  L->hookmask = cast_byte(mask);\n  if (mask)\n    settraps(L->ci);  /* to trace inside 'luaV_execute' */\n}\n\n\nLUA_API lua_Hook lua_gethook (lua_State *L) {\n  return L->hook;\n}\n\n\nLUA_API int lua_gethookmask (lua_State *L) {\n  return L->hookmask;\n}\n\n\nLUA_API int lua_gethookcount (lua_State *L) {\n  return L->basehookcount;\n}\n\n\nLUA_API int lua_getstack (lua_State *L, int level, lua_Debug *ar) {\n  int status;\n  CallInfo *ci;\n  if (level < 0) return 0;  /* invalid (negative) level */\n  lua_lock(L);\n  for (ci = L->ci; level > 0 && ci != &L->base_ci; ci = ci->previous)\n    level--;\n  if (level == 0 && ci != &L->base_ci) {  /* level found? */\n    status = 1;\n    ar->i_ci = ci;\n  }\n  else status = 0;  /* no such level */\n  lua_unlock(L);\n  return status;\n}\n\n\nstatic const char *upvalname (const Proto *p, int uv) {\n  TString *s = check_exp(uv < p->sizeupvalues, p->upvalues[uv].name);\n  if (s == NULL) return \"?\";\n  else return getstr(s);\n}\n\n\nstatic const char *findvararg (CallInfo *ci, int n, StkId *pos) {\n  if (clLvalue(s2v(ci->func.p))->p->is_vararg) {\n    int nextra = ci->u.l.nextraargs;\n    if (n >= -nextra) {  /* 'n' is negative */\n      *pos = ci->func.p - nextra - (n + 1);\n      return \"(vararg)\";  /* generic name for any vararg */\n    }\n  }\n  return NULL;  /* no such vararg */\n}\n\n\nconst char *luaG_findlocal (lua_State *L, CallInfo *ci, int n, StkId *pos) {\n  StkId base = ci->func.p + 1;\n  const char *name = NULL;\n  if (isLua(ci)) {\n    if (n < 0)  /* access to vararg values? */\n      return findvararg(ci, n, pos);\n    else\n      name = luaF_getlocalname(ci_func(ci)->p, n, currentpc(ci));\n  }\n  if (name == NULL) {  /* no 'standard' name? */\n    StkId limit = (ci == L->ci) ? L->top.p : ci->next->func.p;\n    if (limit - base >= n && n > 0) {  /* is 'n' inside 'ci' stack? */\n      /* generic name for any valid slot */\n      name = isLua(ci) ? \"(temporary)\" : \"(C temporary)\";\n    }\n    else\n      return NULL;  /* no name */\n  }\n  if (pos)\n    *pos = base + (n - 1);\n  return name;\n}\n\n\nLUA_API const char *lua_getlocal (lua_State *L, const lua_Debug *ar, int n) {\n  const char *name;\n  lua_lock(L);\n  if (ar == NULL) {  /* information about non-active function? */\n    if (!isLfunction(s2v(L->top.p - 1)))  /* not a Lua function? */\n      name = NULL;\n    else  /* consider live variables at function start (parameters) */\n      name = luaF_getlocalname(clLvalue(s2v(L->top.p - 1))->p, n, 0);\n  }\n  else {  /* active function; get information through 'ar' */\n    StkId pos = NULL;  /* to avoid warnings */\n    name = luaG_findlocal(L, ar->i_ci, n, &pos);\n    if (name) {\n      setobjs2s(L, L->top.p, pos);\n      api_incr_top(L);\n    }\n  }\n  lua_unlock(L);\n  return name;\n}\n\n\nLUA_API const char *lua_setlocal (lua_State *L, const lua_Debug *ar, int n) {\n  StkId pos = NULL;  /* to avoid warnings */\n  const char *name;\n  lua_lock(L);\n  name = luaG_findlocal(L, ar->i_ci, n, &pos);\n  if (name) {\n    setobjs2s(L, pos, L->top.p - 1);\n    L->top.p--;  /* pop value */\n  }\n  lua_unlock(L);\n  return name;\n}\n\n\nstatic void funcinfo (lua_Debug *ar, Closure *cl) {\n  if (noLuaClosure(cl)) {\n    ar->source = \"=[C]\";\n    ar->srclen = LL(\"=[C]\");\n    ar->linedefined = -1;\n    ar->lastlinedefined = -1;\n    ar->what = \"C\";\n  }\n  else {\n    const Proto *p = cl->l.p;\n    if (p->source) {\n      ar->source = getstr(p->source);\n      ar->srclen = tsslen(p->source);\n    }\n    else {\n      ar->source = \"=?\";\n      ar->srclen = LL(\"=?\");\n    }\n    ar->linedefined = p->linedefined;\n    ar->lastlinedefined = p->lastlinedefined;\n    ar->what = (ar->linedefined == 0) ? \"main\" : \"Lua\";\n  }\n  luaO_chunkid(ar->short_src, ar->source, ar->srclen);\n}\n\n\nstatic int nextline (const Proto *p, int currentline, int pc) {\n  if (p->lineinfo[pc] != ABSLINEINFO)\n    return currentline + p->lineinfo[pc];\n  else\n    return luaG_getfuncline(p, pc);\n}\n\n\nstatic void collectvalidlines (lua_State *L, Closure *f) {\n  if (noLuaClosure(f)) {\n    setnilvalue(s2v(L->top.p));\n    api_incr_top(L);\n  }\n  else {\n    int i;\n    TValue v;\n    const Proto *p = f->l.p;\n    int currentline = p->linedefined;\n    Table *t = luaH_new(L);  /* new table to store active lines */\n    sethvalue2s(L, L->top.p, t);  /* push it on stack */\n    api_incr_top(L);\n    setbtvalue(&v);  /* boolean 'true' to be the value of all indices */\n    if (!p->is_vararg)  /* regular function? */\n      i = 0;  /* consider all instructions */\n    else {  /* vararg function */\n      lua_assert(GET_OPCODE(p->code[0]) == OP_VARARGPREP);\n      currentline = nextline(p, currentline, 0);\n      i = 1;  /* skip first instruction (OP_VARARGPREP) */\n    }\n    for (; i < p->sizelineinfo; i++) {  /* for each instruction */\n      currentline = nextline(p, currentline, i);  /* get its line */\n      luaH_setint(L, t, currentline, &v);  /* table[line] = true */\n    }\n  }\n}\n\n\nstatic const char *getfuncname (lua_State *L, CallInfo *ci, const char **name) {\n  /* calling function is a known function? */\n  if (ci != NULL && !(ci->callstatus & CIST_TAIL))\n    return funcnamefromcall(L, ci->previous, name);\n  else return NULL;  /* no way to find a name */\n}\n\n\nstatic int auxgetinfo (lua_State *L, const char *what, lua_Debug *ar,\n                       Closure *f, CallInfo *ci) {\n  int status = 1;\n  for (; *what; what++) {\n    switch (*what) {\n      case 'S': {\n        funcinfo(ar, f);\n        break;\n      }\n      case 'l': {\n        ar->currentline = (ci && isLua(ci)) ? getcurrentline(ci) : -1;\n        break;\n      }\n      case 'u': {\n        ar->nups = (f == NULL) ? 0 : f->c.nupvalues;\n        if (noLuaClosure(f)) {\n          ar->isvararg = 1;\n          ar->nparams = 0;\n        }\n        else {\n          ar->isvararg = f->l.p->is_vararg;\n          ar->nparams = f->l.p->numparams;\n        }\n        break;\n      }\n      case 't': {\n        ar->istailcall = (ci) ? ci->callstatus & CIST_TAIL : 0;\n        break;\n      }\n      case 'n': {\n        ar->namewhat = getfuncname(L, ci, &ar->name);\n        if (ar->namewhat == NULL) {\n          ar->namewhat = \"\";  /* not found */\n          ar->name = NULL;\n        }\n        break;\n      }\n      case 'r': {\n        if (ci == NULL || !(ci->callstatus & CIST_TRAN))\n          ar->ftransfer = ar->ntransfer = 0;\n        else {\n          ar->ftransfer = ci->u2.transferinfo.ftransfer;\n          ar->ntransfer = ci->u2.transferinfo.ntransfer;\n        }\n        break;\n      }\n      case 'L':\n      case 'f':  /* handled by lua_getinfo */\n        break;\n      default: status = 0;  /* invalid option */\n    }\n  }\n  return status;\n}\n\n\nLUA_API int lua_getinfo (lua_State *L, const char *what, lua_Debug *ar) {\n  int status;\n  Closure *cl;\n  CallInfo *ci;\n  TValue *func;\n  lua_lock(L);\n  if (*what == '>') {\n    ci = NULL;\n    func = s2v(L->top.p - 1);\n    api_check(L, ttisfunction(func), \"function expected\");\n    what++;  /* skip the '>' */\n    L->top.p--;  /* pop function */\n  }\n  else {\n    ci = ar->i_ci;\n    func = s2v(ci->func.p);\n    lua_assert(ttisfunction(func));\n  }\n  cl = ttisclosure(func) ? clvalue(func) : NULL;\n  status = auxgetinfo(L, what, ar, cl, ci);\n  if (strchr(what, 'f')) {\n    setobj2s(L, L->top.p, func);\n    api_incr_top(L);\n  }\n  if (strchr(what, 'L'))\n    collectvalidlines(L, cl);\n  lua_unlock(L);\n  return status;\n}\n\n\n/*\n** {======================================================\n** Symbolic Execution\n** =======================================================\n*/\n\nstatic const char *getobjname (const Proto *p, int lastpc, int reg,\n                               const char **name);\n\n\n/*\n** Find a \"name\" for the constant 'c'.\n*/\nstatic void kname (const Proto *p, int c, const char **name) {\n  TValue *kvalue = &p->k[c];\n  *name = (ttisstring(kvalue)) ? svalue(kvalue) : \"?\";\n}\n\n\n/*\n** Find a \"name\" for the register 'c'.\n*/\nstatic void rname (const Proto *p, int pc, int c, const char **name) {\n  const char *what = getobjname(p, pc, c, name); /* search for 'c' */\n  if (!(what && *what == 'c'))  /* did not find a constant name? */\n    *name = \"?\";\n}\n\n\n/*\n** Find a \"name\" for a 'C' value in an RK instruction.\n*/\nstatic void rkname (const Proto *p, int pc, Instruction i, const char **name) {\n  int c = GETARG_C(i);  /* key index */\n  if (GETARG_k(i))  /* is 'c' a constant? */\n    kname(p, c, name);\n  else  /* 'c' is a register */\n    rname(p, pc, c, name);\n}\n\n\nstatic int filterpc (int pc, int jmptarget) {\n  if (pc < jmptarget)  /* is code conditional (inside a jump)? */\n    return -1;  /* cannot know who sets that register */\n  else return pc;  /* current position sets that register */\n}\n\n\n/*\n** Try to find last instruction before 'lastpc' that modified register 'reg'.\n*/\nstatic int findsetreg (const Proto *p, int lastpc, int reg) {\n  int pc;\n  int setreg = -1;  /* keep last instruction that changed 'reg' */\n  int jmptarget = 0;  /* any code before this address is conditional */\n  if (testMMMode(GET_OPCODE(p->code[lastpc])))\n    lastpc--;  /* previous instruction was not actually executed */\n  for (pc = 0; pc < lastpc; pc++) {\n    Instruction i = p->code[pc];\n    OpCode op = GET_OPCODE(i);\n    int a = GETARG_A(i);\n    int change;  /* true if current instruction changed 'reg' */\n    switch (op) {\n      case OP_LOADNIL: {  /* set registers from 'a' to 'a+b' */\n        int b = GETARG_B(i);\n        change = (a <= reg && reg <= a + b);\n        break;\n      }\n      case OP_TFORCALL: {  /* affect all regs above its base */\n        change = (reg >= a + 2);\n        break;\n      }\n      case OP_CALL:\n      case OP_TAILCALL: {  /* affect all registers above base */\n        change = (reg >= a);\n        break;\n      }\n      case OP_JMP: {  /* doesn't change registers, but changes 'jmptarget' */\n        int b = GETARG_sJ(i);\n        int dest = pc + 1 + b;\n        /* jump does not skip 'lastpc' and is larger than current one? */\n        if (dest <= lastpc && dest > jmptarget)\n          jmptarget = dest;  /* update 'jmptarget' */\n        change = 0;\n        break;\n      }\n      default:  /* any instruction that sets A */\n        change = (testAMode(op) && reg == a);\n        break;\n    }\n    if (change)\n      setreg = filterpc(pc, jmptarget);\n  }\n  return setreg;\n}\n\n\n/*\n** Check whether table being indexed by instruction 'i' is the\n** environment '_ENV'\n*/\nstatic const char *gxf (const Proto *p, int pc, Instruction i, int isup) {\n  int t = GETARG_B(i);  /* table index */\n  const char *name;  /* name of indexed variable */\n  if (isup)  /* is an upvalue? */\n    name = upvalname(p, t);\n  else\n    getobjname(p, pc, t, &name);\n  return (name && strcmp(name, LUA_ENV) == 0) ? \"global\" : \"field\";\n}\n\n\nstatic const char *getobjname (const Proto *p, int lastpc, int reg,\n                               const char **name) {\n  int pc;\n  *name = luaF_getlocalname(p, reg + 1, lastpc);\n  if (*name)  /* is a local? */\n    return \"local\";\n  /* else try symbolic execution */\n  pc = findsetreg(p, lastpc, reg);\n  if (pc != -1) {  /* could find instruction? */\n    Instruction i = p->code[pc];\n    OpCode op = GET_OPCODE(i);\n    switch (op) {\n      case OP_MOVE: {\n        int b = GETARG_B(i);  /* move from 'b' to 'a' */\n        if (b < GETARG_A(i))\n          return getobjname(p, pc, b, name);  /* get name for 'b' */\n        break;\n      }\n      case OP_GETTABUP: {\n        int k = GETARG_C(i);  /* key index */\n        kname(p, k, name);\n        return gxf(p, pc, i, 1);\n      }\n      case OP_GETTABLE: {\n        int k = GETARG_C(i);  /* key index */\n        rname(p, pc, k, name);\n        return gxf(p, pc, i, 0);\n      }\n      case OP_GETI: {\n        *name = \"integer index\";\n        return \"field\";\n      }\n      case OP_GETFIELD: {\n        int k = GETARG_C(i);  /* key index */\n        kname(p, k, name);\n        return gxf(p, pc, i, 0);\n      }\n      case OP_GETUPVAL: {\n        *name = upvalname(p, GETARG_B(i));\n        return \"upvalue\";\n      }\n      case OP_LOADK:\n      case OP_LOADKX: {\n        int b = (op == OP_LOADK) ? GETARG_Bx(i)\n                                 : GETARG_Ax(p->code[pc + 1]);\n        if (ttisstring(&p->k[b])) {\n          *name = svalue(&p->k[b]);\n          return \"constant\";\n        }\n        break;\n      }\n      case OP_SELF: {\n        rkname(p, pc, i, name);\n        return \"method\";\n      }\n      default: break;  /* go through to return NULL */\n    }\n  }\n  return NULL;  /* could not find reasonable name */\n}\n\n\n/*\n** Try to find a name for a function based on the code that called it.\n** (Only works when function was called by a Lua function.)\n** Returns what the name is (e.g., \"for iterator\", \"method\",\n** \"metamethod\") and sets '*name' to point to the name.\n*/\nstatic const char *funcnamefromcode (lua_State *L, const Proto *p,\n                                     int pc, const char **name) {\n  TMS tm = (TMS)0;  /* (initial value avoids warnings) */\n  Instruction i = p->code[pc];  /* calling instruction */\n  switch (GET_OPCODE(i)) {\n    case OP_CALL:\n    case OP_TAILCALL:\n      return getobjname(p, pc, GETARG_A(i), name);  /* get function name */\n    case OP_TFORCALL: {  /* for iterator */\n      *name = \"for iterator\";\n       return \"for iterator\";\n    }\n    /* other instructions can do calls through metamethods */\n    case OP_SELF: case OP_GETTABUP: case OP_GETTABLE:\n    case OP_GETI: case OP_GETFIELD:\n      tm = TM_INDEX;\n      break;\n    case OP_SETTABUP: case OP_SETTABLE: case OP_SETI: case OP_SETFIELD:\n      tm = TM_NEWINDEX;\n      break;\n    case OP_MMBIN: case OP_MMBINI: case OP_MMBINK: {\n      tm = cast(TMS, GETARG_C(i));\n      break;\n    }\n    case OP_UNM: tm = TM_UNM; break;\n    case OP_BNOT: tm = TM_BNOT; break;\n    case OP_LEN: tm = TM_LEN; break;\n    case OP_CONCAT: tm = TM_CONCAT; break;\n    case OP_EQ: tm = TM_EQ; break;\n    /* no cases for OP_EQI and OP_EQK, as they don't call metamethods */\n    case OP_LT: case OP_LTI: case OP_GTI: tm = TM_LT; break;\n    case OP_LE: case OP_LEI: case OP_GEI: tm = TM_LE; break;\n    case OP_CLOSE: case OP_RETURN: tm = TM_CLOSE; break;\n    default:\n      return NULL;  /* cannot find a reasonable name */\n  }\n  *name = getstr(G(L)->tmname[tm]) + 2;\n  return \"metamethod\";\n}\n\n\n/*\n** Try to find a name for a function based on how it was called.\n*/\nstatic const char *funcnamefromcall (lua_State *L, CallInfo *ci,\n                                                   const char **name) {\n  if (ci->callstatus & CIST_HOOKED) {  /* was it called inside a hook? */\n    *name = \"?\";\n    return \"hook\";\n  }\n  else if (ci->callstatus & CIST_FIN) {  /* was it called as a finalizer? */\n    *name = \"__gc\";\n    return \"metamethod\";  /* report it as such */\n  }\n  else if (isLua(ci))\n    return funcnamefromcode(L, ci_func(ci)->p, currentpc(ci), name);\n  else\n    return NULL;\n}\n\n/* }====================================================== */\n\n\n\n/*\n** Check whether pointer 'o' points to some value in the stack frame of\n** the current function and, if so, returns its index.  Because 'o' may\n** not point to a value in this stack, we cannot compare it with the\n** region boundaries (undefined behavior in ISO C).\n*/\nstatic int instack (CallInfo *ci, const TValue *o) {\n  int pos;\n  StkId base = ci->func.p + 1;\n  for (pos = 0; base + pos < ci->top.p; pos++) {\n    if (o == s2v(base + pos))\n      return pos;\n  }\n  return -1;  /* not found */\n}\n\n\n/*\n** Checks whether value 'o' came from an upvalue. (That can only happen\n** with instructions OP_GETTABUP/OP_SETTABUP, which operate directly on\n** upvalues.)\n*/\nstatic const char *getupvalname (CallInfo *ci, const TValue *o,\n                                 const char **name) {\n  LClosure *c = ci_func(ci);\n  int i;\n  for (i = 0; i < c->nupvalues; i++) {\n    if (c->upvals[i]->v.p == o) {\n      *name = upvalname(c->p, i);\n      return \"upvalue\";\n    }\n  }\n  return NULL;\n}\n\n\nstatic const char *formatvarinfo (lua_State *L, const char *kind,\n                                                const char *name) {\n  if (kind == NULL)\n    return \"\";  /* no information */\n  else\n    return luaO_pushfstring(L, \" (%s '%s')\", kind, name);\n}\n\n/*\n** Build a string with a \"description\" for the value 'o', such as\n** \"variable 'x'\" or \"upvalue 'y'\".\n*/\nstatic const char *varinfo (lua_State *L, const TValue *o) {\n  CallInfo *ci = L->ci;\n  const char *name = NULL;  /* to avoid warnings */\n  const char *kind = NULL;\n  if (isLua(ci)) {\n    kind = getupvalname(ci, o, &name);  /* check whether 'o' is an upvalue */\n    if (!kind) {  /* not an upvalue? */\n      int reg = instack(ci, o);  /* try a register */\n      if (reg >= 0)  /* is 'o' a register? */\n        kind = getobjname(ci_func(ci)->p, currentpc(ci), reg, &name);\n    }\n  }\n  return formatvarinfo(L, kind, name);\n}\n\n\n/*\n** Raise a type error\n*/\nstatic l_noret typeerror (lua_State *L, const TValue *o, const char *op,\n                          const char *extra) {\n  const char *t = luaT_objtypename(L, o);\n  luaG_runerror(L, \"attempt to %s a %s value%s\", op, t, extra);\n}\n\n\n/*\n** Raise a type error with \"standard\" information about the faulty\n** object 'o' (using 'varinfo').\n*/\nl_noret luaG_typeerror (lua_State *L, const TValue *o, const char *op) {\n  typeerror(L, o, op, varinfo(L, o));\n}\n\n\n/*\n** Raise an error for calling a non-callable object. Try to find a name\n** for the object based on how it was called ('funcnamefromcall'); if it\n** cannot get a name there, try 'varinfo'.\n*/\nl_noret luaG_callerror (lua_State *L, const TValue *o) {\n  CallInfo *ci = L->ci;\n  const char *name = NULL;  /* to avoid warnings */\n  const char *kind = funcnamefromcall(L, ci, &name);\n  const char *extra = kind ? formatvarinfo(L, kind, name) : varinfo(L, o);\n  typeerror(L, o, \"call\", extra);\n}\n\n\nl_noret luaG_forerror (lua_State *L, const TValue *o, const char *what) {\n  luaG_runerror(L, \"bad 'for' %s (number expected, got %s)\",\n                   what, luaT_objtypename(L, o));\n}\n\n\nl_noret luaG_concaterror (lua_State *L, const TValue *p1, const TValue *p2) {\n  if (ttisstring(p1) || cvt2str(p1)) p1 = p2;\n  luaG_typeerror(L, p1, \"concatenate\");\n}\n\n\nl_noret luaG_opinterror (lua_State *L, const TValue *p1,\n                         const TValue *p2, const char *msg) {\n  if (!ttisnumber(p1))  /* first operand is wrong? */\n    p2 = p1;  /* now second is wrong */\n  luaG_typeerror(L, p2, msg);\n}\n\n\n/*\n** Error when both values are convertible to numbers, but not to integers\n*/\nl_noret luaG_tointerror (lua_State *L, const TValue *p1, const TValue *p2) {\n  lua_Integer temp;\n  if (!luaV_tointegerns(p1, &temp, LUA_FLOORN2I))\n    p2 = p1;\n  luaG_runerror(L, \"number%s has no integer representation\", varinfo(L, p2));\n}\n\n\nl_noret luaG_ordererror (lua_State *L, const TValue *p1, const TValue *p2) {\n  const char *t1 = luaT_objtypename(L, p1);\n  const char *t2 = luaT_objtypename(L, p2);\n  if (strcmp(t1, t2) == 0)\n    luaG_runerror(L, \"attempt to compare two %s values\", t1);\n  else\n    luaG_runerror(L, \"attempt to compare %s with %s\", t1, t2);\n}\n\n\n/* add src:line information to 'msg' */\nconst char *luaG_addinfo (lua_State *L, const char *msg, TString *src,\n                                        int line) {\n  char buff[LUA_IDSIZE];\n  if (src)\n    luaO_chunkid(buff, getstr(src), tsslen(src));\n  else {  /* no source available; use \"?\" instead */\n    buff[0] = '?'; buff[1] = '\\0';\n  }\n  return luaO_pushfstring(L, \"%s:%d: %s\", buff, line, msg);\n}\n\n\nl_noret luaG_errormsg (lua_State *L) {\n  if (L->errfunc != 0) {  /* is there an error handling function? */\n    StkId errfunc = restorestack(L, L->errfunc);\n    lua_assert(ttisfunction(s2v(errfunc)));\n    setobjs2s(L, L->top.p, L->top.p - 1);  /* move argument */\n    setobjs2s(L, L->top.p - 1, errfunc);  /* push function */\n    L->top.p++;  /* assume EXTRA_STACK */\n    luaD_callnoyield(L, L->top.p - 2, 1);  /* call it */\n  }\n  luaD_throw(L, LUA_ERRRUN);\n}\n\n\nl_noret luaG_runerror (lua_State *L, const char *fmt, ...) {\n  CallInfo *ci = L->ci;\n  const char *msg;\n  va_list argp;\n  luaC_checkGC(L);  /* error message uses memory */\n  va_start(argp, fmt);\n  msg = luaO_pushvfstring(L, fmt, argp);  /* format message */\n  va_end(argp);\n  if (isLua(ci)) {  /* if Lua function, add source:line information */\n    luaG_addinfo(L, msg, ci_func(ci)->p->source, getcurrentline(ci));\n    setobjs2s(L, L->top.p - 2, L->top.p - 1);  /* remove 'msg' */\n    L->top.p--;\n  }\n  luaG_errormsg(L);\n}\n\n\n/*\n** Check whether new instruction 'newpc' is in a different line from\n** previous instruction 'oldpc'. More often than not, 'newpc' is only\n** one or a few instructions after 'oldpc' (it must be after, see\n** caller), so try to avoid calling 'luaG_getfuncline'. If they are\n** too far apart, there is a good chance of a ABSLINEINFO in the way,\n** so it goes directly to 'luaG_getfuncline'.\n*/\nstatic int changedline (const Proto *p, int oldpc, int newpc) {\n  if (p->lineinfo == NULL)  /* no debug information? */\n    return 0;\n  if (newpc - oldpc < MAXIWTHABS / 2) {  /* not too far apart? */\n    int delta = 0;  /* line difference */\n    int pc = oldpc;\n    for (;;) {\n      int lineinfo = p->lineinfo[++pc];\n      if (lineinfo == ABSLINEINFO)\n        break;  /* cannot compute delta; fall through */\n      delta += lineinfo;\n      if (pc == newpc)\n        return (delta != 0);  /* delta computed successfully */\n    }\n  }\n  /* either instructions are too far apart or there is an absolute line\n     info in the way; compute line difference explicitly */\n  return (luaG_getfuncline(p, oldpc) != luaG_getfuncline(p, newpc));\n}\n\n\n/*\n** Traces the execution of a Lua function. Called before the execution\n** of each opcode, when debug is on. 'L->oldpc' stores the last\n** instruction traced, to detect line changes. When entering a new\n** function, 'npci' will be zero and will test as a new line whatever\n** the value of 'oldpc'.  Some exceptional conditions may return to\n** a function without setting 'oldpc'. In that case, 'oldpc' may be\n** invalid; if so, use zero as a valid value. (A wrong but valid 'oldpc'\n** at most causes an extra call to a line hook.)\n** This function is not \"Protected\" when called, so it should correct\n** 'L->top.p' before calling anything that can run the GC.\n*/\nint luaG_traceexec (lua_State *L, const Instruction *pc) {\n  CallInfo *ci = L->ci;\n  lu_byte mask = L->hookmask;\n  const Proto *p = ci_func(ci)->p;\n  int counthook;\n  if (!(mask & (LUA_MASKLINE | LUA_MASKCOUNT))) {  /* no hooks? */\n    ci->u.l.trap = 0;  /* don't need to stop again */\n    return 0;  /* turn off 'trap' */\n  }\n  pc++;  /* reference is always next instruction */\n  ci->u.l.savedpc = pc;  /* save 'pc' */\n  counthook = (--L->hookcount == 0 && (mask & LUA_MASKCOUNT));\n  if (counthook)\n    resethookcount(L);  /* reset count */\n  else if (!(mask & LUA_MASKLINE))\n    return 1;  /* no line hook and count != 0; nothing to be done now */\n  if (ci->callstatus & CIST_HOOKYIELD) {  /* called hook last time? */\n    ci->callstatus &= ~CIST_HOOKYIELD;  /* erase mark */\n    return 1;  /* do not call hook again (VM yielded, so it did not move) */\n  }\n  if (!isIT(*(ci->u.l.savedpc - 1)))  /* top not being used? */\n    L->top.p = ci->top.p;  /* correct top */\n  if (counthook)\n    luaD_hook(L, LUA_HOOKCOUNT, -1, 0, 0);  /* call count hook */\n  if (mask & LUA_MASKLINE) {\n    /* 'L->oldpc' may be invalid; use zero in this case */\n    int oldpc = (L->oldpc < p->sizecode) ? L->oldpc : 0;\n    int npci = pcRel(pc, p);\n    if (npci <= oldpc ||  /* call hook when jump back (loop), */\n        changedline(p, oldpc, npci)) {  /* or when enter new line */\n      int newline = luaG_getfuncline(p, npci);\n      luaD_hook(L, LUA_HOOKLINE, newline, 0, 0);  /* call line hook */\n    }\n    L->oldpc = npci;  /* 'pc' of last call to line hook */\n  }\n  if (L->status == LUA_YIELD) {  /* did hook yield? */\n    if (counthook)\n      L->hookcount = 1;  /* undo decrement to zero */\n    ci->u.l.savedpc--;  /* undo increment (resume will increment it again) */\n    ci->callstatus |= CIST_HOOKYIELD;  /* mark that it yielded */\n    luaD_throw(L, LUA_YIELD);\n  }\n  return 1;  /* keep 'trap' on */\n}\n\n/*\n** $Id: lfunc.c $\n** Auxiliary functions to manipulate prototypes and closures\n** See Copyright Notice in lua.h\n*/\n\n#define lfunc_c\n#define LUA_CORE\n\n/*#include \"lprefix.h\"*/\n\n\n#include <stddef.h>\n\n/*#include \"lua.h\"*/\n\n/*#include \"ldebug.h\"*/\n/*#include \"ldo.h\"*/\n/*#include \"lfunc.h\"*/\n/*#include \"lgc.h\"*/\n/*#include \"lmem.h\"*/\n/*#include \"lobject.h\"*/\n/*#include \"lstate.h\"*/\n\n\n\nCClosure *luaF_newCclosure (lua_State *L, int nupvals) {\n  GCObject *o = luaC_newobj(L, LUA_VCCL, sizeCclosure(nupvals));\n  CClosure *c = gco2ccl(o);\n  c->nupvalues = cast_byte(nupvals);\n  return c;\n}\n\n\nLClosure *luaF_newLclosure (lua_State *L, int nupvals) {\n  GCObject *o = luaC_newobj(L, LUA_VLCL, sizeLclosure(nupvals));\n  LClosure *c = gco2lcl(o);\n  c->p = NULL;\n  c->nupvalues = cast_byte(nupvals);\n  while (nupvals--) c->upvals[nupvals] = NULL;\n  return c;\n}\n\n\n/*\n** fill a closure with new closed upvalues\n*/\nvoid luaF_initupvals (lua_State *L, LClosure *cl) {\n  int i;\n  for (i = 0; i < cl->nupvalues; i++) {\n    GCObject *o = luaC_newobj(L, LUA_VUPVAL, sizeof(UpVal));\n    UpVal *uv = gco2upv(o);\n    uv->v.p = &uv->u.value;  /* make it closed */\n    setnilvalue(uv->v.p);\n    cl->upvals[i] = uv;\n    luaC_objbarrier(L, cl, uv);\n  }\n}\n\n\n/*\n** Create a new upvalue at the given level, and link it to the list of\n** open upvalues of 'L' after entry 'prev'.\n**/\nstatic UpVal *newupval (lua_State *L, StkId level, UpVal **prev) {\n  GCObject *o = luaC_newobj(L, LUA_VUPVAL, sizeof(UpVal));\n  UpVal *uv = gco2upv(o);\n  UpVal *next = *prev;\n  uv->v.p = s2v(level);  /* current value lives in the stack */\n  uv->u.open.next = next;  /* link it to list of open upvalues */\n  uv->u.open.previous = prev;\n  if (next)\n    next->u.open.previous = &uv->u.open.next;\n  *prev = uv;\n  if (!isintwups(L)) {  /* thread not in list of threads with upvalues? */\n    L->twups = G(L)->twups;  /* link it to the list */\n    G(L)->twups = L;\n  }\n  return uv;\n}\n\n\n/*\n** Find and reuse, or create if it does not exist, an upvalue\n** at the given level.\n*/\nUpVal *luaF_findupval (lua_State *L, StkId level) {\n  UpVal **pp = &L->openupval;\n  UpVal *p;\n  lua_assert(isintwups(L) || L->openupval == NULL);\n  while ((p = *pp) != NULL && uplevel(p) >= level) {  /* search for it */\n    lua_assert(!isdead(G(L), p));\n    if (uplevel(p) == level)  /* corresponding upvalue? */\n      return p;  /* return it */\n    pp = &p->u.open.next;\n  }\n  /* not found: create a new upvalue after 'pp' */\n  return newupval(L, level, pp);\n}\n\n\n/*\n** Call closing method for object 'obj' with error message 'err'. The\n** boolean 'yy' controls whether the call is yieldable.\n** (This function assumes EXTRA_STACK.)\n*/\nstatic void callclosemethod (lua_State *L, TValue *obj, TValue *err, int yy) {\n  StkId top = L->top.p;\n  const TValue *tm = luaT_gettmbyobj(L, obj, TM_CLOSE);\n  setobj2s(L, top, tm);  /* will call metamethod... */\n  setobj2s(L, top + 1, obj);  /* with 'self' as the 1st argument */\n  setobj2s(L, top + 2, err);  /* and error msg. as 2nd argument */\n  L->top.p = top + 3;  /* add function and arguments */\n  if (yy)\n    luaD_call(L, top, 0);\n  else\n    luaD_callnoyield(L, top, 0);\n}\n\n\n/*\n** Check whether object at given level has a close metamethod and raise\n** an error if not.\n*/\nstatic void checkclosemth (lua_State *L, StkId level) {\n  const TValue *tm = luaT_gettmbyobj(L, s2v(level), TM_CLOSE);\n  if (ttisnil(tm)) {  /* no metamethod? */\n    int idx = cast_int(level - L->ci->func.p);  /* variable index */\n    const char *vname = luaG_findlocal(L, L->ci, idx, NULL);\n    if (vname == NULL) vname = \"?\";\n    luaG_runerror(L, \"variable '%s' got a non-closable value\", vname);\n  }\n}\n\n\n/*\n** Prepare and call a closing method.\n** If status is CLOSEKTOP, the call to the closing method will be pushed\n** at the top of the stack. Otherwise, values can be pushed right after\n** the 'level' of the upvalue being closed, as everything after that\n** won't be used again.\n*/\nstatic void prepcallclosemth (lua_State *L, StkId level, int status, int yy) {\n  TValue *uv = s2v(level);  /* value being closed */\n  TValue *errobj;\n  if (status == CLOSEKTOP)\n    errobj = &G(L)->nilvalue;  /* error object is nil */\n  else {  /* 'luaD_seterrorobj' will set top to level + 2 */\n    errobj = s2v(level + 1);  /* error object goes after 'uv' */\n    luaD_seterrorobj(L, status, level + 1);  /* set error object */\n  }\n  callclosemethod(L, uv, errobj, yy);\n}\n\n\n/*\n** Maximum value for deltas in 'tbclist', dependent on the type\n** of delta. (This macro assumes that an 'L' is in scope where it\n** is used.)\n*/\n#define MAXDELTA  \\\n\t((256ul << ((sizeof(L->stack.p->tbclist.delta) - 1) * 8)) - 1)\n\n\n/*\n** Insert a variable in the list of to-be-closed variables.\n*/\nvoid luaF_newtbcupval (lua_State *L, StkId level) {\n  lua_assert(level > L->tbclist.p);\n  if (l_isfalse(s2v(level)))\n    return;  /* false doesn't need to be closed */\n  checkclosemth(L, level);  /* value must have a close method */\n  while (cast_uint(level - L->tbclist.p) > MAXDELTA) {\n    L->tbclist.p += MAXDELTA;  /* create a dummy node at maximum delta */\n    L->tbclist.p->tbclist.delta = 0;\n  }\n  level->tbclist.delta = cast(unsigned short, level - L->tbclist.p);\n  L->tbclist.p = level;\n}\n\n\nvoid luaF_unlinkupval (UpVal *uv) {\n  lua_assert(upisopen(uv));\n  *uv->u.open.previous = uv->u.open.next;\n  if (uv->u.open.next)\n    uv->u.open.next->u.open.previous = uv->u.open.previous;\n}\n\n\n/*\n** Close all upvalues up to the given stack level.\n*/\nvoid luaF_closeupval (lua_State *L, StkId level) {\n  UpVal *uv;\n  StkId upl;  /* stack index pointed by 'uv' */\n  while ((uv = L->openupval) != NULL && (upl = uplevel(uv)) >= level) {\n    TValue *slot = &uv->u.value;  /* new position for value */\n    lua_assert(uplevel(uv) < L->top.p);\n    luaF_unlinkupval(uv);  /* remove upvalue from 'openupval' list */\n    setobj(L, slot, uv->v.p);  /* move value to upvalue slot */\n    uv->v.p = slot;  /* now current value lives here */\n    if (!iswhite(uv)) {  /* neither white nor dead? */\n      nw2black(uv);  /* closed upvalues cannot be gray */\n      luaC_barrier(L, uv, slot);\n    }\n  }\n}\n\n\n/*\n** Remove first element from the tbclist plus its dummy nodes.\n*/\nstatic void poptbclist (lua_State *L) {\n  StkId tbc = L->tbclist.p;\n  lua_assert(tbc->tbclist.delta > 0);  /* first element cannot be dummy */\n  tbc -= tbc->tbclist.delta;\n  while (tbc > L->stack.p && tbc->tbclist.delta == 0)\n    tbc -= MAXDELTA;  /* remove dummy nodes */\n  L->tbclist.p = tbc;\n}\n\n\n/*\n** Close all upvalues and to-be-closed variables up to the given stack\n** level. Return restored 'level'.\n*/\nStkId luaF_close (lua_State *L, StkId level, int status, int yy) {\n  ptrdiff_t levelrel = savestack(L, level);\n  luaF_closeupval(L, level);  /* first, close the upvalues */\n  while (L->tbclist.p >= level) {  /* traverse tbc's down to that level */\n    StkId tbc = L->tbclist.p;  /* get variable index */\n    poptbclist(L);  /* remove it from list */\n    prepcallclosemth(L, tbc, status, yy);  /* close variable */\n    level = restorestack(L, levelrel);\n  }\n  return level;\n}\n\n\nProto *luaF_newproto (lua_State *L) {\n  GCObject *o = luaC_newobj(L, LUA_VPROTO, sizeof(Proto));\n  Proto *f = gco2p(o);\n  f->k = NULL;\n  f->sizek = 0;\n  f->p = NULL;\n  f->sizep = 0;\n  f->code = NULL;\n  f->sizecode = 0;\n  f->lineinfo = NULL;\n  f->sizelineinfo = 0;\n  f->abslineinfo = NULL;\n  f->sizeabslineinfo = 0;\n  f->upvalues = NULL;\n  f->sizeupvalues = 0;\n  f->numparams = 0;\n  f->is_vararg = 0;\n  f->maxstacksize = 0;\n  f->locvars = NULL;\n  f->sizelocvars = 0;\n  f->linedefined = 0;\n  f->lastlinedefined = 0;\n  f->source = NULL;\n  return f;\n}\n\n\nvoid luaF_freeproto (lua_State *L, Proto *f) {\n  luaM_freearray(L, f->code, f->sizecode);\n  luaM_freearray(L, f->p, f->sizep);\n  luaM_freearray(L, f->k, f->sizek);\n  luaM_freearray(L, f->lineinfo, f->sizelineinfo);\n  luaM_freearray(L, f->abslineinfo, f->sizeabslineinfo);\n  luaM_freearray(L, f->locvars, f->sizelocvars);\n  luaM_freearray(L, f->upvalues, f->sizeupvalues);\n  luaM_free(L, f);\n}\n\n\n/*\n** Look for n-th local variable at line 'line' in function 'func'.\n** Returns NULL if not found.\n*/\nconst char *luaF_getlocalname (const Proto *f, int local_number, int pc) {\n  int i;\n  for (i = 0; i<f->sizelocvars && f->locvars[i].startpc <= pc; i++) {\n    if (pc < f->locvars[i].endpc) {  /* is variable active? */\n      local_number--;\n      if (local_number == 0)\n        return getstr(f->locvars[i].varname);\n    }\n  }\n  return NULL;  /* not found */\n}\n\n/*\n** $Id: lobject.c $\n** Some generic functions over Lua objects\n** See Copyright Notice in lua.h\n*/\n\n#define lobject_c\n#define LUA_CORE\n\n/*#include \"lprefix.h\"*/\n\n\n#include <locale.h>\n#include <math.h>\n#include <stdarg.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n/*#include \"lua.h\"*/\n\n/*#include \"lctype.h\"*/\n/*#include \"ldebug.h\"*/\n/*#include \"ldo.h\"*/\n/*#include \"lmem.h\"*/\n/*#include \"lobject.h\"*/\n/*#include \"lstate.h\"*/\n/*#include \"lstring.h\"*/\n/*#include \"lvm.h\"*/\n\n\n/*\n** Computes ceil(log2(x))\n*/\nint luaO_ceillog2 (unsigned int x) {\n  static const lu_byte log_2[256] = {  /* log_2[i] = ceil(log2(i - 1)) */\n    0,1,2,2,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,\n    6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,\n    7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,\n    7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,\n    8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,\n    8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,\n    8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,\n    8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8\n  };\n  int l = 0;\n  x--;\n  while (x >= 256) { l += 8; x >>= 8; }\n  return l + log_2[x];\n}\n\n\nstatic lua_Integer intarith (lua_State *L, int op, lua_Integer v1,\n                                                   lua_Integer v2) {\n  switch (op) {\n    case LUA_OPADD: return intop(+, v1, v2);\n    case LUA_OPSUB:return intop(-, v1, v2);\n    case LUA_OPMUL:return intop(*, v1, v2);\n    case LUA_OPMOD: return luaV_mod(L, v1, v2);\n    case LUA_OPIDIV: return luaV_idiv(L, v1, v2);\n    case LUA_OPBAND: return intop(&, v1, v2);\n    case LUA_OPBOR: return intop(|, v1, v2);\n    case LUA_OPBXOR: return intop(^, v1, v2);\n    case LUA_OPSHL: return luaV_shiftl(v1, v2);\n    case LUA_OPSHR: return luaV_shiftr(v1, v2);\n    case LUA_OPUNM: return intop(-, 0, v1);\n    case LUA_OPBNOT: return intop(^, ~l_castS2U(0), v1);\n    default: lua_assert(0); return 0;\n  }\n}\n\n\nstatic lua_Number numarith (lua_State *L, int op, lua_Number v1,\n                                                  lua_Number v2) {\n  switch (op) {\n    case LUA_OPADD: return luai_numadd(L, v1, v2);\n    case LUA_OPSUB: return luai_numsub(L, v1, v2);\n    case LUA_OPMUL: return luai_nummul(L, v1, v2);\n    case LUA_OPDIV: return luai_numdiv(L, v1, v2);\n    case LUA_OPPOW: return luai_numpow(L, v1, v2);\n    case LUA_OPIDIV: return luai_numidiv(L, v1, v2);\n    case LUA_OPUNM: return luai_numunm(L, v1);\n    case LUA_OPMOD: return luaV_modf(L, v1, v2);\n    default: lua_assert(0); return 0;\n  }\n}\n\n\nint luaO_rawarith (lua_State *L, int op, const TValue *p1, const TValue *p2,\n                   TValue *res) {\n  switch (op) {\n    case LUA_OPBAND: case LUA_OPBOR: case LUA_OPBXOR:\n    case LUA_OPSHL: case LUA_OPSHR:\n    case LUA_OPBNOT: {  /* operate only on integers */\n      lua_Integer i1; lua_Integer i2;\n      if (tointegerns(p1, &i1) && tointegerns(p2, &i2)) {\n        setivalue(res, intarith(L, op, i1, i2));\n        return 1;\n      }\n      else return 0;  /* fail */\n    }\n    case LUA_OPDIV: case LUA_OPPOW: {  /* operate only on floats */\n      lua_Number n1; lua_Number n2;\n      if (tonumberns(p1, n1) && tonumberns(p2, n2)) {\n        setfltvalue(res, numarith(L, op, n1, n2));\n        return 1;\n      }\n      else return 0;  /* fail */\n    }\n    default: {  /* other operations */\n      lua_Number n1; lua_Number n2;\n      if (ttisinteger(p1) && ttisinteger(p2)) {\n        setivalue(res, intarith(L, op, ivalue(p1), ivalue(p2)));\n        return 1;\n      }\n      else if (tonumberns(p1, n1) && tonumberns(p2, n2)) {\n        setfltvalue(res, numarith(L, op, n1, n2));\n        return 1;\n      }\n      else return 0;  /* fail */\n    }\n  }\n}\n\n\nvoid luaO_arith (lua_State *L, int op, const TValue *p1, const TValue *p2,\n                 StkId res) {\n  if (!luaO_rawarith(L, op, p1, p2, s2v(res))) {\n    /* could not perform raw operation; try metamethod */\n    luaT_trybinTM(L, p1, p2, res, cast(TMS, (op - LUA_OPADD) + TM_ADD));\n  }\n}\n\n\nint luaO_hexavalue (int c) {\n  if (lisdigit(c)) return c - '0';\n  else return (ltolower(c) - 'a') + 10;\n}\n\n\nstatic int isneg (const char **s) {\n  if (**s == '-') { (*s)++; return 1; }\n  else if (**s == '+') (*s)++;\n  return 0;\n}\n\n\n\n/*\n** {==================================================================\n** Lua's implementation for 'lua_strx2number'\n** ===================================================================\n*/\n\n#if !defined(lua_strx2number)\n\n/* maximum number of significant digits to read (to avoid overflows\n   even with single floats) */\n#define MAXSIGDIG\t30\n\n/*\n** convert a hexadecimal numeric string to a number, following\n** C99 specification for 'strtod'\n*/\nstatic lua_Number lua_strx2number (const char *s, char **endptr) {\n  int dot = lua_getlocaledecpoint();\n  lua_Number r = l_mathop(0.0);  /* result (accumulator) */\n  int sigdig = 0;  /* number of significant digits */\n  int nosigdig = 0;  /* number of non-significant digits */\n  int e = 0;  /* exponent correction */\n  int neg;  /* 1 if number is negative */\n  int hasdot = 0;  /* true after seen a dot */\n  *endptr = cast_charp(s);  /* nothing is valid yet */\n  while (lisspace(cast_uchar(*s))) s++;  /* skip initial spaces */\n  neg = isneg(&s);  /* check sign */\n  if (!(*s == '0' && (*(s + 1) == 'x' || *(s + 1) == 'X')))  /* check '0x' */\n    return l_mathop(0.0);  /* invalid format (no '0x') */\n  for (s += 2; ; s++) {  /* skip '0x' and read numeral */\n    if (*s == dot) {\n      if (hasdot) break;  /* second dot? stop loop */\n      else hasdot = 1;\n    }\n    else if (lisxdigit(cast_uchar(*s))) {\n      if (sigdig == 0 && *s == '0')  /* non-significant digit (zero)? */\n        nosigdig++;\n      else if (++sigdig <= MAXSIGDIG)  /* can read it without overflow? */\n          r = (r * l_mathop(16.0)) + luaO_hexavalue(*s);\n      else e++; /* too many digits; ignore, but still count for exponent */\n      if (hasdot) e--;  /* decimal digit? correct exponent */\n    }\n    else break;  /* neither a dot nor a digit */\n  }\n  if (nosigdig + sigdig == 0)  /* no digits? */\n    return l_mathop(0.0);  /* invalid format */\n  *endptr = cast_charp(s);  /* valid up to here */\n  e *= 4;  /* each digit multiplies/divides value by 2^4 */\n  if (*s == 'p' || *s == 'P') {  /* exponent part? */\n    int exp1 = 0;  /* exponent value */\n    int neg1;  /* exponent sign */\n    s++;  /* skip 'p' */\n    neg1 = isneg(&s);  /* sign */\n    if (!lisdigit(cast_uchar(*s)))\n      return l_mathop(0.0);  /* invalid; must have at least one digit */\n    while (lisdigit(cast_uchar(*s)))  /* read exponent */\n      exp1 = exp1 * 10 + *(s++) - '0';\n    if (neg1) exp1 = -exp1;\n    e += exp1;\n    *endptr = cast_charp(s);  /* valid up to here */\n  }\n  if (neg) r = -r;\n  return l_mathop(ldexp)(r, e);\n}\n\n#endif\n/* }====================================================== */\n\n\n/* maximum length of a numeral to be converted to a number */\n#if !defined (L_MAXLENNUM)\n#define L_MAXLENNUM\t200\n#endif\n\n/*\n** Convert string 's' to a Lua number (put in 'result'). Return NULL on\n** fail or the address of the ending '\\0' on success. ('mode' == 'x')\n** means a hexadecimal numeral.\n*/\nstatic const char *l_str2dloc (const char *s, lua_Number *result, int mode) {\n  char *endptr;\n  *result = (mode == 'x') ? lua_strx2number(s, &endptr)  /* try to convert */\n                          : lua_str2number(s, &endptr);\n  if (endptr == s) return NULL;  /* nothing recognized? */\n  while (lisspace(cast_uchar(*endptr))) endptr++;  /* skip trailing spaces */\n  return (*endptr == '\\0') ? endptr : NULL;  /* OK iff no trailing chars */\n}\n\n\n/*\n** Convert string 's' to a Lua number (put in 'result') handling the\n** current locale.\n** This function accepts both the current locale or a dot as the radix\n** mark. If the conversion fails, it may mean number has a dot but\n** locale accepts something else. In that case, the code copies 's'\n** to a buffer (because 's' is read-only), changes the dot to the\n** current locale radix mark, and tries to convert again.\n** The variable 'mode' checks for special characters in the string:\n** - 'n' means 'inf' or 'nan' (which should be rejected)\n** - 'x' means a hexadecimal numeral\n** - '.' just optimizes the search for the common case (no special chars)\n*/\nstatic const char *l_str2d (const char *s, lua_Number *result) {\n  const char *endptr;\n  const char *pmode = strpbrk(s, \".xXnN\");  /* look for special chars */\n  int mode = pmode ? ltolower(cast_uchar(*pmode)) : 0;\n  if (mode == 'n')  /* reject 'inf' and 'nan' */\n    return NULL;\n  endptr = l_str2dloc(s, result, mode);  /* try to convert */\n  if (endptr == NULL) {  /* failed? may be a different locale */\n    char buff[L_MAXLENNUM + 1];\n    const char *pdot = strchr(s, '.');\n    if (pdot == NULL || strlen(s) > L_MAXLENNUM)\n      return NULL;  /* string too long or no dot; fail */\n    strcpy(buff, s);  /* copy string to buffer */\n    buff[pdot - s] = lua_getlocaledecpoint();  /* correct decimal point */\n    endptr = l_str2dloc(buff, result, mode);  /* try again */\n    if (endptr != NULL)\n      endptr = s + (endptr - buff);  /* make relative to 's' */\n  }\n  return endptr;\n}\n\n\n#define MAXBY10\t\tcast(lua_Unsigned, LUA_MAXINTEGER / 10)\n#define MAXLASTD\tcast_int(LUA_MAXINTEGER % 10)\n\nstatic const char *l_str2int (const char *s, lua_Integer *result) {\n  lua_Unsigned a = 0;\n  int empty = 1;\n  int neg;\n  while (lisspace(cast_uchar(*s))) s++;  /* skip initial spaces */\n  neg = isneg(&s);\n  if (s[0] == '0' &&\n      (s[1] == 'x' || s[1] == 'X')) {  /* hex? */\n    s += 2;  /* skip '0x' */\n    for (; lisxdigit(cast_uchar(*s)); s++) {\n      a = a * 16 + luaO_hexavalue(*s);\n      empty = 0;\n    }\n  }\n  else {  /* decimal */\n    for (; lisdigit(cast_uchar(*s)); s++) {\n      int d = *s - '0';\n      if (a >= MAXBY10 && (a > MAXBY10 || d > MAXLASTD + neg))  /* overflow? */\n        return NULL;  /* do not accept it (as integer) */\n      a = a * 10 + d;\n      empty = 0;\n    }\n  }\n  while (lisspace(cast_uchar(*s))) s++;  /* skip trailing spaces */\n  if (empty || *s != '\\0') return NULL;  /* something wrong in the numeral */\n  else {\n    *result = l_castU2S((neg) ? 0u - a : a);\n    return s;\n  }\n}\n\n\nsize_t luaO_str2num (const char *s, TValue *o) {\n  lua_Integer i; lua_Number n;\n  const char *e;\n  if ((e = l_str2int(s, &i)) != NULL) {  /* try as an integer */\n    setivalue(o, i);\n  }\n  else if ((e = l_str2d(s, &n)) != NULL) {  /* else try as a float */\n    setfltvalue(o, n);\n  }\n  else\n    return 0;  /* conversion failed */\n  return (e - s) + 1;  /* success; return string size */\n}\n\n\nint luaO_utf8esc (char *buff, unsigned long x) {\n  int n = 1;  /* number of bytes put in buffer (backwards) */\n  lua_assert(x <= 0x7FFFFFFFu);\n  if (x < 0x80)  /* ascii? */\n    buff[UTF8BUFFSZ - 1] = cast_char(x);\n  else {  /* need continuation bytes */\n    unsigned int mfb = 0x3f;  /* maximum that fits in first byte */\n    do {  /* add continuation bytes */\n      buff[UTF8BUFFSZ - (n++)] = cast_char(0x80 | (x & 0x3f));\n      x >>= 6;  /* remove added bits */\n      mfb >>= 1;  /* now there is one less bit available in first byte */\n    } while (x > mfb);  /* still needs continuation byte? */\n    buff[UTF8BUFFSZ - n] = cast_char((~mfb << 1) | x);  /* add first byte */\n  }\n  return n;\n}\n\n\n/*\n** Maximum length of the conversion of a number to a string. Must be\n** enough to accommodate both LUA_INTEGER_FMT and LUA_NUMBER_FMT.\n** (For a long long int, this is 19 digits plus a sign and a final '\\0',\n** adding to 21. For a long double, it can go to a sign, 33 digits,\n** the dot, an exponent letter, an exponent sign, 5 exponent digits,\n** and a final '\\0', adding to 43.)\n*/\n#define MAXNUMBER2STR\t44\n\n\n/*\n** Convert a number object to a string, adding it to a buffer\n*/\nstatic int tostringbuff (TValue *obj, char *buff) {\n  int len;\n  lua_assert(ttisnumber(obj));\n  if (ttisinteger(obj))\n    len = lua_integer2str(buff, MAXNUMBER2STR, ivalue(obj));\n  else {\n    len = lua_number2str(buff, MAXNUMBER2STR, fltvalue(obj));\n    if (buff[strspn(buff, \"-0123456789\")] == '\\0') {  /* looks like an int? */\n      buff[len++] = lua_getlocaledecpoint();\n      buff[len++] = '0';  /* adds '.0' to result */\n    }\n  }\n  return len;\n}\n\n\n/*\n** Convert a number object to a Lua string, replacing the value at 'obj'\n*/\nvoid luaO_tostring (lua_State *L, TValue *obj) {\n  char buff[MAXNUMBER2STR];\n  int len = tostringbuff(obj, buff);\n  setsvalue(L, obj, luaS_newlstr(L, buff, len));\n}\n\n\n\n\n/*\n** {==================================================================\n** 'luaO_pushvfstring'\n** ===================================================================\n*/\n\n/*\n** Size for buffer space used by 'luaO_pushvfstring'. It should be\n** (LUA_IDSIZE + MAXNUMBER2STR) + a minimal space for basic messages,\n** so that 'luaG_addinfo' can work directly on the buffer.\n*/\n#define BUFVFS\t\t(LUA_IDSIZE + MAXNUMBER2STR + 95)\n\n/* buffer used by 'luaO_pushvfstring' */\ntypedef struct BuffFS {\n  lua_State *L;\n  int pushed;  /* true if there is a part of the result on the stack */\n  int blen;  /* length of partial string in 'space' */\n  char space[BUFVFS];  /* holds last part of the result */\n} BuffFS;\n\n\n/*\n** Push given string to the stack, as part of the result, and\n** join it to previous partial result if there is one.\n** It may call 'luaV_concat' while using one slot from EXTRA_STACK.\n** This call cannot invoke metamethods, as both operands must be\n** strings. It can, however, raise an error if the result is too\n** long. In that case, 'luaV_concat' frees the extra slot before\n** raising the error.\n*/\nstatic void pushstr (BuffFS *buff, const char *str, size_t lstr) {\n  lua_State *L = buff->L;\n  setsvalue2s(L, L->top.p, luaS_newlstr(L, str, lstr));\n  L->top.p++;  /* may use one slot from EXTRA_STACK */\n  if (!buff->pushed)  /* no previous string on the stack? */\n    buff->pushed = 1;  /* now there is one */\n  else  /* join previous string with new one */\n    luaV_concat(L, 2);\n}\n\n\n/*\n** empty the buffer space into the stack\n*/\nstatic void clearbuff (BuffFS *buff) {\n  pushstr(buff, buff->space, buff->blen);  /* push buffer contents */\n  buff->blen = 0;  /* space now is empty */\n}\n\n\n/*\n** Get a space of size 'sz' in the buffer. If buffer has not enough\n** space, empty it. 'sz' must fit in an empty buffer.\n*/\nstatic char *getbuff (BuffFS *buff, int sz) {\n  lua_assert(buff->blen <= BUFVFS); lua_assert(sz <= BUFVFS);\n  if (sz > BUFVFS - buff->blen)  /* not enough space? */\n    clearbuff(buff);\n  return buff->space + buff->blen;\n}\n\n\n#define addsize(b,sz)\t((b)->blen += (sz))\n\n\n/*\n** Add 'str' to the buffer. If string is larger than the buffer space,\n** push the string directly to the stack.\n*/\nstatic void addstr2buff (BuffFS *buff, const char *str, size_t slen) {\n  if (slen <= BUFVFS) {  /* does string fit into buffer? */\n    char *bf = getbuff(buff, cast_int(slen));\n    memcpy(bf, str, slen);  /* add string to buffer */\n    addsize(buff, cast_int(slen));\n  }\n  else {  /* string larger than buffer */\n    clearbuff(buff);  /* string comes after buffer's content */\n    pushstr(buff, str, slen);  /* push string */\n  }\n}\n\n\n/*\n** Add a numeral to the buffer.\n*/\nstatic void addnum2buff (BuffFS *buff, TValue *num) {\n  char *numbuff = getbuff(buff, MAXNUMBER2STR);\n  int len = tostringbuff(num, numbuff);  /* format number into 'numbuff' */\n  addsize(buff, len);\n}\n\n\n/*\n** this function handles only '%d', '%c', '%f', '%p', '%s', and '%%'\n   conventional formats, plus Lua-specific '%I' and '%U'\n*/\nconst char *luaO_pushvfstring (lua_State *L, const char *fmt, va_list argp) {\n  BuffFS buff;  /* holds last part of the result */\n  const char *e;  /* points to next '%' */\n  buff.pushed = buff.blen = 0;\n  buff.L = L;\n  while ((e = strchr(fmt, '%')) != NULL) {\n    addstr2buff(&buff, fmt, e - fmt);  /* add 'fmt' up to '%' */\n    switch (*(e + 1)) {  /* conversion specifier */\n      case 's': {  /* zero-terminated string */\n        const char *s = va_arg(argp, char *);\n        if (s == NULL) s = \"(null)\";\n        addstr2buff(&buff, s, strlen(s));\n        break;\n      }\n      case 'c': {  /* an 'int' as a character */\n        char c = cast_uchar(va_arg(argp, int));\n        addstr2buff(&buff, &c, sizeof(char));\n        break;\n      }\n      case 'd': {  /* an 'int' */\n        TValue num;\n        setivalue(&num, va_arg(argp, int));\n        addnum2buff(&buff, &num);\n        break;\n      }\n      case 'I': {  /* a 'lua_Integer' */\n        TValue num;\n        setivalue(&num, cast(lua_Integer, va_arg(argp, l_uacInt)));\n        addnum2buff(&buff, &num);\n        break;\n      }\n      case 'f': {  /* a 'lua_Number' */\n        TValue num;\n        setfltvalue(&num, cast_num(va_arg(argp, l_uacNumber)));\n        addnum2buff(&buff, &num);\n        break;\n      }\n      case 'p': {  /* a pointer */\n        const int sz = 3 * sizeof(void*) + 8; /* enough space for '%p' */\n        char *bf = getbuff(&buff, sz);\n        void *p = va_arg(argp, void *);\n        int len = lua_pointer2str(bf, sz, p);\n        addsize(&buff, len);\n        break;\n      }\n      case 'U': {  /* a 'long' as a UTF-8 sequence */\n        char bf[UTF8BUFFSZ];\n        int len = luaO_utf8esc(bf, va_arg(argp, long));\n        addstr2buff(&buff, bf + UTF8BUFFSZ - len, len);\n        break;\n      }\n      case '%': {\n        addstr2buff(&buff, \"%\", 1);\n        break;\n      }\n      default: {\n        luaG_runerror(L, \"invalid option '%%%c' to 'lua_pushfstring'\",\n                         *(e + 1));\n      }\n    }\n    fmt = e + 2;  /* skip '%' and the specifier */\n  }\n  addstr2buff(&buff, fmt, strlen(fmt));  /* rest of 'fmt' */\n  clearbuff(&buff);  /* empty buffer into the stack */\n  lua_assert(buff.pushed == 1);\n  return svalue(s2v(L->top.p - 1));\n}\n\n\nconst char *luaO_pushfstring (lua_State *L, const char *fmt, ...) {\n  const char *msg;\n  va_list argp;\n  va_start(argp, fmt);\n  msg = luaO_pushvfstring(L, fmt, argp);\n  va_end(argp);\n  return msg;\n}\n\n/* }================================================================== */\n\n\n#define RETS\t\"...\"\n#define PRE\t\"[string \\\"\"\n#define POS\t\"\\\"]\"\n\n#define addstr(a,b,l)\t( memcpy(a,b,(l) * sizeof(char)), a += (l) )\n\nvoid luaO_chunkid (char *out, const char *source, size_t srclen) {\n  size_t bufflen = LUA_IDSIZE;  /* free space in buffer */\n  if (*source == '=') {  /* 'literal' source */\n    if (srclen <= bufflen)  /* small enough? */\n      memcpy(out, source + 1, srclen * sizeof(char));\n    else {  /* truncate it */\n      addstr(out, source + 1, bufflen - 1);\n      *out = '\\0';\n    }\n  }\n  else if (*source == '@') {  /* file name */\n    if (srclen <= bufflen)  /* small enough? */\n      memcpy(out, source + 1, srclen * sizeof(char));\n    else {  /* add '...' before rest of name */\n      addstr(out, RETS, LL(RETS));\n      bufflen -= LL(RETS);\n      memcpy(out, source + 1 + srclen - bufflen, bufflen * sizeof(char));\n    }\n  }\n  else {  /* string; format as [string \"source\"] */\n    const char *nl = strchr(source, '\\n');  /* find first new line (if any) */\n    addstr(out, PRE, LL(PRE));  /* add prefix */\n    bufflen -= LL(PRE RETS POS) + 1;  /* save space for prefix+suffix+'\\0' */\n    if (srclen < bufflen && nl == NULL) {  /* small one-line source? */\n      addstr(out, source, srclen);  /* keep it */\n    }\n    else {\n      if (nl != NULL) srclen = nl - source;  /* stop at first newline */\n      if (srclen > bufflen) srclen = bufflen;\n      addstr(out, source, srclen);\n      addstr(out, RETS, LL(RETS));\n    }\n    memcpy(out, POS, (LL(POS) + 1) * sizeof(char));\n  }\n}\n\n/*\n** $Id: ltm.c $\n** Tag methods\n** See Copyright Notice in lua.h\n*/\n\n#define ltm_c\n#define LUA_CORE\n\n/*#include \"lprefix.h\"*/\n\n\n#include <string.h>\n\n/*#include \"lua.h\"*/\n\n/*#include \"ldebug.h\"*/\n/*#include \"ldo.h\"*/\n/*#include \"lgc.h\"*/\n/*#include \"lobject.h\"*/\n/*#include \"lstate.h\"*/\n/*#include \"lstring.h\"*/\n/*#include \"ltable.h\"*/\n/*#include \"ltm.h\"*/\n/*#include \"lvm.h\"*/\n\n\nstatic const char udatatypename[] = \"userdata\";\n\nLUAI_DDEF const char *const luaT_typenames_[LUA_TOTALTYPES] = {\n  \"no value\",\n  \"nil\", \"boolean\", udatatypename, \"number\",\n  \"string\", \"table\", \"function\", udatatypename, \"thread\",\n  \"upvalue\", \"proto\" /* these last cases are used for tests only */\n};\n\n\nvoid luaT_init (lua_State *L) {\n  static const char *const luaT_eventname[] = {  /* ORDER TM */\n    \"__index\", \"__newindex\",\n    \"__gc\", \"__mode\", \"__len\", \"__eq\",\n    \"__add\", \"__sub\", \"__mul\", \"__mod\", \"__pow\",\n    \"__div\", \"__idiv\",\n    \"__band\", \"__bor\", \"__bxor\", \"__shl\", \"__shr\",\n    \"__unm\", \"__bnot\", \"__lt\", \"__le\",\n    \"__concat\", \"__call\", \"__close\"\n  };\n  int i;\n  for (i=0; i<TM_N; i++) {\n    G(L)->tmname[i] = luaS_new(L, luaT_eventname[i]);\n    luaC_fix(L, obj2gco(G(L)->tmname[i]));  /* never collect these names */\n  }\n}\n\n\n/*\n** function to be used with macro \"fasttm\": optimized for absence of\n** tag methods\n*/\nconst TValue *luaT_gettm (Table *events, TMS event, TString *ename) {\n  const TValue *tm = luaH_getshortstr(events, ename);\n  lua_assert(event <= TM_EQ);\n  if (notm(tm)) {  /* no tag method? */\n    events->flags |= cast_byte(1u<<event);  /* cache this fact */\n    return NULL;\n  }\n  else return tm;\n}\n\n\nconst TValue *luaT_gettmbyobj (lua_State *L, const TValue *o, TMS event) {\n  Table *mt;\n  switch (ttype(o)) {\n    case LUA_TTABLE:\n      mt = hvalue(o)->metatable;\n      break;\n    case LUA_TUSERDATA:\n      mt = uvalue(o)->metatable;\n      break;\n    default:\n      mt = G(L)->mt[ttype(o)];\n  }\n  return (mt ? luaH_getshortstr(mt, G(L)->tmname[event]) : &G(L)->nilvalue);\n}\n\n\n/*\n** Return the name of the type of an object. For tables and userdata\n** with metatable, use their '__name' metafield, if present.\n*/\nconst char *luaT_objtypename (lua_State *L, const TValue *o) {\n  Table *mt;\n  if ((ttistable(o) && (mt = hvalue(o)->metatable) != NULL) ||\n      (ttisfulluserdata(o) && (mt = uvalue(o)->metatable) != NULL)) {\n    const TValue *name = luaH_getshortstr(mt, luaS_new(L, \"__name\"));\n    if (ttisstring(name))  /* is '__name' a string? */\n      return getstr(tsvalue(name));  /* use it as type name */\n  }\n  return ttypename(ttype(o));  /* else use standard type name */\n}\n\n\nvoid luaT_callTM (lua_State *L, const TValue *f, const TValue *p1,\n                  const TValue *p2, const TValue *p3) {\n  StkId func = L->top.p;\n  setobj2s(L, func, f);  /* push function (assume EXTRA_STACK) */\n  setobj2s(L, func + 1, p1);  /* 1st argument */\n  setobj2s(L, func + 2, p2);  /* 2nd argument */\n  setobj2s(L, func + 3, p3);  /* 3rd argument */\n  L->top.p = func + 4;\n  /* metamethod may yield only when called from Lua code */\n  if (isLuacode(L->ci))\n    luaD_call(L, func, 0);\n  else\n    luaD_callnoyield(L, func, 0);\n}\n\n\nvoid luaT_callTMres (lua_State *L, const TValue *f, const TValue *p1,\n                     const TValue *p2, StkId res) {\n  ptrdiff_t result = savestack(L, res);\n  StkId func = L->top.p;\n  setobj2s(L, func, f);  /* push function (assume EXTRA_STACK) */\n  setobj2s(L, func + 1, p1);  /* 1st argument */\n  setobj2s(L, func + 2, p2);  /* 2nd argument */\n  L->top.p += 3;\n  /* metamethod may yield only when called from Lua code */\n  if (isLuacode(L->ci))\n    luaD_call(L, func, 1);\n  else\n    luaD_callnoyield(L, func, 1);\n  res = restorestack(L, result);\n  setobjs2s(L, res, --L->top.p);  /* move result to its place */\n}\n\n\nstatic int callbinTM (lua_State *L, const TValue *p1, const TValue *p2,\n                      StkId res, TMS event) {\n  const TValue *tm = luaT_gettmbyobj(L, p1, event);  /* try first operand */\n  if (notm(tm))\n    tm = luaT_gettmbyobj(L, p2, event);  /* try second operand */\n  if (notm(tm)) return 0;\n  luaT_callTMres(L, tm, p1, p2, res);\n  return 1;\n}\n\n\nvoid luaT_trybinTM (lua_State *L, const TValue *p1, const TValue *p2,\n                    StkId res, TMS event) {\n  if (l_unlikely(!callbinTM(L, p1, p2, res, event))) {\n    switch (event) {\n      case TM_BAND: case TM_BOR: case TM_BXOR:\n      case TM_SHL: case TM_SHR: case TM_BNOT: {\n        if (ttisnumber(p1) && ttisnumber(p2))\n          luaG_tointerror(L, p1, p2);\n        else\n          luaG_opinterror(L, p1, p2, \"perform bitwise operation on\");\n      }\n      /* calls never return, but to avoid warnings: *//* FALLTHROUGH */\n      default:\n        luaG_opinterror(L, p1, p2, \"perform arithmetic on\");\n    }\n  }\n}\n\n\nvoid luaT_tryconcatTM (lua_State *L) {\n  StkId top = L->top.p;\n  if (l_unlikely(!callbinTM(L, s2v(top - 2), s2v(top - 1), top - 2,\n                               TM_CONCAT)))\n    luaG_concaterror(L, s2v(top - 2), s2v(top - 1));\n}\n\n\nvoid luaT_trybinassocTM (lua_State *L, const TValue *p1, const TValue *p2,\n                                       int flip, StkId res, TMS event) {\n  if (flip)\n    luaT_trybinTM(L, p2, p1, res, event);\n  else\n    luaT_trybinTM(L, p1, p2, res, event);\n}\n\n\nvoid luaT_trybiniTM (lua_State *L, const TValue *p1, lua_Integer i2,\n                                   int flip, StkId res, TMS event) {\n  TValue aux;\n  setivalue(&aux, i2);\n  luaT_trybinassocTM(L, p1, &aux, flip, res, event);\n}\n\n\n/*\n** Calls an order tag method.\n** For lessequal, LUA_COMPAT_LT_LE keeps compatibility with old\n** behavior: if there is no '__le', try '__lt', based on l <= r iff\n** !(r < l) (assuming a total order). If the metamethod yields during\n** this substitution, the continuation has to know about it (to negate\n** the result of r<l); bit CIST_LEQ in the call status keeps that\n** information.\n*/\nint luaT_callorderTM (lua_State *L, const TValue *p1, const TValue *p2,\n                      TMS event) {\n  if (callbinTM(L, p1, p2, L->top.p, event))  /* try original event */\n    return !l_isfalse(s2v(L->top.p));\n#if defined(LUA_COMPAT_LT_LE)\n  else if (event == TM_LE) {\n      /* try '!(p2 < p1)' for '(p1 <= p2)' */\n      L->ci->callstatus |= CIST_LEQ;  /* mark it is doing 'lt' for 'le' */\n      if (callbinTM(L, p2, p1, L->top.p, TM_LT)) {\n        L->ci->callstatus ^= CIST_LEQ;  /* clear mark */\n        return l_isfalse(s2v(L->top.p));\n      }\n      /* else error will remove this 'ci'; no need to clear mark */\n  }\n#endif\n  luaG_ordererror(L, p1, p2);  /* no metamethod found */\n  return 0;  /* to avoid warnings */\n}\n\n\nint luaT_callorderiTM (lua_State *L, const TValue *p1, int v2,\n                       int flip, int isfloat, TMS event) {\n  TValue aux; const TValue *p2;\n  if (isfloat) {\n    setfltvalue(&aux, cast_num(v2));\n  }\n  else\n    setivalue(&aux, v2);\n  if (flip) {  /* arguments were exchanged? */\n    p2 = p1; p1 = &aux;  /* correct them */\n  }\n  else\n    p2 = &aux;\n  return luaT_callorderTM(L, p1, p2, event);\n}\n\n\nvoid luaT_adjustvarargs (lua_State *L, int nfixparams, CallInfo *ci,\n                         const Proto *p) {\n  int i;\n  int actual = cast_int(L->top.p - ci->func.p) - 1;  /* number of arguments */\n  int nextra = actual - nfixparams;  /* number of extra arguments */\n  ci->u.l.nextraargs = nextra;\n  luaD_checkstack(L, p->maxstacksize + 1);\n  /* copy function to the top of the stack */\n  setobjs2s(L, L->top.p++, ci->func.p);\n  /* move fixed parameters to the top of the stack */\n  for (i = 1; i <= nfixparams; i++) {\n    setobjs2s(L, L->top.p++, ci->func.p + i);\n    setnilvalue(s2v(ci->func.p + i));  /* erase original parameter (for GC) */\n  }\n  ci->func.p += actual + 1;\n  ci->top.p += actual + 1;\n  lua_assert(L->top.p <= ci->top.p && ci->top.p <= L->stack_last.p);\n}\n\n\nvoid luaT_getvarargs (lua_State *L, CallInfo *ci, StkId where, int wanted) {\n  int i;\n  int nextra = ci->u.l.nextraargs;\n  if (wanted < 0) {\n    wanted = nextra;  /* get all extra arguments available */\n    checkstackGCp(L, nextra, where);  /* ensure stack space */\n    L->top.p = where + nextra;  /* next instruction will need top */\n  }\n  for (i = 0; i < wanted && i < nextra; i++)\n    setobjs2s(L, where + i, ci->func.p - nextra + i);\n  for (; i < wanted; i++)   /* complete required results with nil */\n    setnilvalue(s2v(where + i));\n}\n\n/*\n** $Id: lstring.c $\n** String table (keeps all strings handled by Lua)\n** See Copyright Notice in lua.h\n*/\n\n#define lstring_c\n#define LUA_CORE\n\n/*#include \"lprefix.h\"*/\n\n\n#include <string.h>\n\n/*#include \"lua.h\"*/\n\n/*#include \"ldebug.h\"*/\n/*#include \"ldo.h\"*/\n/*#include \"lmem.h\"*/\n/*#include \"lobject.h\"*/\n/*#include \"lstate.h\"*/\n/*#include \"lstring.h\"*/\n\n\n/*\n** Maximum size for string table.\n*/\n#define MAXSTRTB\tcast_int(luaM_limitN(MAX_INT, TString*))\n\n\n/*\n** equality for long strings\n*/\nint luaS_eqlngstr (TString *a, TString *b) {\n  size_t len = a->u.lnglen;\n  lua_assert(a->tt == LUA_VLNGSTR && b->tt == LUA_VLNGSTR);\n  return (a == b) ||  /* same instance or... */\n    ((len == b->u.lnglen) &&  /* equal length and ... */\n     (memcmp(getstr(a), getstr(b), len) == 0));  /* equal contents */\n}\n\n\nunsigned int luaS_hash (const char *str, size_t l, unsigned int seed) {\n  unsigned int h = seed ^ cast_uint(l);\n  for (; l > 0; l--)\n    h ^= ((h<<5) + (h>>2) + cast_byte(str[l - 1]));\n  return h;\n}\n\n\nunsigned int luaS_hashlongstr (TString *ts) {\n  lua_assert(ts->tt == LUA_VLNGSTR);\n  if (ts->extra == 0) {  /* no hash? */\n    size_t len = ts->u.lnglen;\n    ts->hash = luaS_hash(getstr(ts), len, ts->hash);\n    ts->extra = 1;  /* now it has its hash */\n  }\n  return ts->hash;\n}\n\n\nstatic void tablerehash (TString **vect, int osize, int nsize) {\n  int i;\n  for (i = osize; i < nsize; i++)  /* clear new elements */\n    vect[i] = NULL;\n  for (i = 0; i < osize; i++) {  /* rehash old part of the array */\n    TString *p = vect[i];\n    vect[i] = NULL;\n    while (p) {  /* for each string in the list */\n      TString *hnext = p->u.hnext;  /* save next */\n      unsigned int h = lmod(p->hash, nsize);  /* new position */\n      p->u.hnext = vect[h];  /* chain it into array */\n      vect[h] = p;\n      p = hnext;\n    }\n  }\n}\n\n\n/*\n** Resize the string table. If allocation fails, keep the current size.\n** (This can degrade performance, but any non-zero size should work\n** correctly.)\n*/\nvoid luaS_resize (lua_State *L, int nsize) {\n  stringtable *tb = &G(L)->strt;\n  int osize = tb->size;\n  TString **newvect;\n  if (nsize < osize)  /* shrinking table? */\n    tablerehash(tb->hash, osize, nsize);  /* depopulate shrinking part */\n  newvect = luaM_reallocvector(L, tb->hash, osize, nsize, TString*);\n  if (l_unlikely(newvect == NULL)) {  /* reallocation failed? */\n    if (nsize < osize)  /* was it shrinking table? */\n      tablerehash(tb->hash, nsize, osize);  /* restore to original size */\n    /* leave table as it was */\n  }\n  else {  /* allocation succeeded */\n    tb->hash = newvect;\n    tb->size = nsize;\n    if (nsize > osize)\n      tablerehash(newvect, osize, nsize);  /* rehash for new size */\n  }\n}\n\n\n/*\n** Clear API string cache. (Entries cannot be empty, so fill them with\n** a non-collectable string.)\n*/\nvoid luaS_clearcache (global_State *g) {\n  int i, j;\n  for (i = 0; i < STRCACHE_N; i++)\n    for (j = 0; j < STRCACHE_M; j++) {\n      if (iswhite(g->strcache[i][j]))  /* will entry be collected? */\n        g->strcache[i][j] = g->memerrmsg;  /* replace it with something fixed */\n    }\n}\n\n\n/*\n** Initialize the string table and the string cache\n*/\nvoid luaS_init (lua_State *L) {\n  global_State *g = G(L);\n  int i, j;\n  stringtable *tb = &G(L)->strt;\n  tb->hash = luaM_newvector(L, MINSTRTABSIZE, TString*);\n  tablerehash(tb->hash, 0, MINSTRTABSIZE);  /* clear array */\n  tb->size = MINSTRTABSIZE;\n  /* pre-create memory-error message */\n  g->memerrmsg = luaS_newliteral(L, MEMERRMSG);\n  luaC_fix(L, obj2gco(g->memerrmsg));  /* it should never be collected */\n  for (i = 0; i < STRCACHE_N; i++)  /* fill cache with valid strings */\n    for (j = 0; j < STRCACHE_M; j++)\n      g->strcache[i][j] = g->memerrmsg;\n}\n\n\n\n/*\n** creates a new string object\n*/\nstatic TString *createstrobj (lua_State *L, size_t l, int tag, unsigned int h) {\n  TString *ts;\n  GCObject *o;\n  size_t totalsize;  /* total size of TString object */\n  totalsize = sizelstring(l);\n  o = luaC_newobj(L, tag, totalsize);\n  ts = gco2ts(o);\n  ts->hash = h;\n  ts->extra = 0;\n  getstr(ts)[l] = '\\0';  /* ending 0 */\n  return ts;\n}\n\n\nTString *luaS_createlngstrobj (lua_State *L, size_t l) {\n  TString *ts = createstrobj(L, l, LUA_VLNGSTR, G(L)->seed);\n  ts->u.lnglen = l;\n  return ts;\n}\n\n\nvoid luaS_remove (lua_State *L, TString *ts) {\n  stringtable *tb = &G(L)->strt;\n  TString **p = &tb->hash[lmod(ts->hash, tb->size)];\n  while (*p != ts)  /* find previous element */\n    p = &(*p)->u.hnext;\n  *p = (*p)->u.hnext;  /* remove element from its list */\n  tb->nuse--;\n}\n\n\nstatic void growstrtab (lua_State *L, stringtable *tb) {\n  if (l_unlikely(tb->nuse == MAX_INT)) {  /* too many strings? */\n    luaC_fullgc(L, 1);  /* try to free some... */\n    if (tb->nuse == MAX_INT)  /* still too many? */\n      luaM_error(L);  /* cannot even create a message... */\n  }\n  if (tb->size <= MAXSTRTB / 2)  /* can grow string table? */\n    luaS_resize(L, tb->size * 2);\n}\n\n\n/*\n** Checks whether short string exists and reuses it or creates a new one.\n*/\nstatic TString *internshrstr (lua_State *L, const char *str, size_t l) {\n  TString *ts;\n  global_State *g = G(L);\n  stringtable *tb = &g->strt;\n  unsigned int h = luaS_hash(str, l, g->seed);\n  TString **list = &tb->hash[lmod(h, tb->size)];\n  lua_assert(str != NULL);  /* otherwise 'memcmp'/'memcpy' are undefined */\n  for (ts = *list; ts != NULL; ts = ts->u.hnext) {\n    if (l == ts->shrlen && (memcmp(str, getstr(ts), l * sizeof(char)) == 0)) {\n      /* found! */\n      if (isdead(g, ts))  /* dead (but not collected yet)? */\n        changewhite(ts);  /* resurrect it */\n      return ts;\n    }\n  }\n  /* else must create a new string */\n  if (tb->nuse >= tb->size) {  /* need to grow string table? */\n    growstrtab(L, tb);\n    list = &tb->hash[lmod(h, tb->size)];  /* rehash with new size */\n  }\n  ts = createstrobj(L, l, LUA_VSHRSTR, h);\n  memcpy(getstr(ts), str, l * sizeof(char));\n  ts->shrlen = cast_byte(l);\n  ts->u.hnext = *list;\n  *list = ts;\n  tb->nuse++;\n  return ts;\n}\n\n\n/*\n** new string (with explicit length)\n*/\nTString *luaS_newlstr (lua_State *L, const char *str, size_t l) {\n  if (l <= LUAI_MAXSHORTLEN)  /* short string? */\n    return internshrstr(L, str, l);\n  else {\n    TString *ts;\n    if (l_unlikely(l >= (MAX_SIZE - sizeof(TString))/sizeof(char)))\n      luaM_toobig(L);\n    ts = luaS_createlngstrobj(L, l);\n    memcpy(getstr(ts), str, l * sizeof(char));\n    return ts;\n  }\n}\n\n\n/*\n** Create or reuse a zero-terminated string, first checking in the\n** cache (using the string address as a key). The cache can contain\n** only zero-terminated strings, so it is safe to use 'strcmp' to\n** check hits.\n*/\nTString *luaS_new (lua_State *L, const char *str) {\n  unsigned int i = point2uint(str) % STRCACHE_N;  /* hash */\n  int j;\n  TString **p = G(L)->strcache[i];\n  for (j = 0; j < STRCACHE_M; j++) {\n    if (strcmp(str, getstr(p[j])) == 0)  /* hit? */\n      return p[j];  /* that is it */\n  }\n  /* normal route */\n  for (j = STRCACHE_M - 1; j > 0; j--)\n    p[j] = p[j - 1];  /* move out last element */\n  /* new element is first in the list */\n  p[0] = luaS_newlstr(L, str, strlen(str));\n  return p[0];\n}\n\n\nUdata *luaS_newudata (lua_State *L, size_t s, int nuvalue) {\n  Udata *u;\n  int i;\n  GCObject *o;\n  if (l_unlikely(s > MAX_SIZE - udatamemoffset(nuvalue)))\n    luaM_toobig(L);\n  o = luaC_newobj(L, LUA_VUSERDATA, sizeudata(nuvalue, s));\n  u = gco2u(o);\n  u->len = s;\n  u->nuvalue = nuvalue;\n  u->metatable = NULL;\n  for (i = 0; i < nuvalue; i++)\n    setnilvalue(&u->uv[i].uv);\n  return u;\n}\n\n/*\n** $Id: ltable.c $\n** Lua tables (hash)\n** See Copyright Notice in lua.h\n*/\n\n#define ltable_c\n#define LUA_CORE\n\n/*#include \"lprefix.h\"*/\n\n\n/*\n** Implementation of tables (aka arrays, objects, or hash tables).\n** Tables keep its elements in two parts: an array part and a hash part.\n** Non-negative integer keys are all candidates to be kept in the array\n** part. The actual size of the array is the largest 'n' such that\n** more than half the slots between 1 and n are in use.\n** Hash uses a mix of chained scatter table with Brent's variation.\n** A main invariant of these tables is that, if an element is not\n** in its main position (i.e. the 'original' position that its hash gives\n** to it), then the colliding element is in its own main position.\n** Hence even when the load factor reaches 100%, performance remains good.\n*/\n\n#include <math.h>\n#include <limits.h>\n\n/*#include \"lua.h\"*/\n\n/*#include \"ldebug.h\"*/\n/*#include \"ldo.h\"*/\n/*#include \"lgc.h\"*/\n/*#include \"lmem.h\"*/\n/*#include \"lobject.h\"*/\n/*#include \"lstate.h\"*/\n/*#include \"lstring.h\"*/\n/*#include \"ltable.h\"*/\n/*#include \"lvm.h\"*/\n\n\n/*\n** MAXABITS is the largest integer such that MAXASIZE fits in an\n** unsigned int.\n*/\n#define MAXABITS\tcast_int(sizeof(int) * CHAR_BIT - 1)\n\n\n/*\n** MAXASIZE is the maximum size of the array part. It is the minimum\n** between 2^MAXABITS and the maximum size that, measured in bytes,\n** fits in a 'size_t'.\n*/\n#define MAXASIZE\tluaM_limitN(1u << MAXABITS, TValue)\n\n/*\n** MAXHBITS is the largest integer such that 2^MAXHBITS fits in a\n** signed int.\n*/\n#define MAXHBITS\t(MAXABITS - 1)\n\n\n/*\n** MAXHSIZE is the maximum size of the hash part. It is the minimum\n** between 2^MAXHBITS and the maximum size such that, measured in bytes,\n** it fits in a 'size_t'.\n*/\n#define MAXHSIZE\tluaM_limitN(1u << MAXHBITS, Node)\n\n\n/*\n** When the original hash value is good, hashing by a power of 2\n** avoids the cost of '%'.\n*/\n#define hashpow2(t,n)\t\t(gnode(t, lmod((n), sizenode(t))))\n\n/*\n** for other types, it is better to avoid modulo by power of 2, as\n** they can have many 2 factors.\n*/\n#define hashmod(t,n)\t(gnode(t, ((n) % ((sizenode(t)-1)|1))))\n\n\n#define hashstr(t,str)\t\thashpow2(t, (str)->hash)\n#define hashboolean(t,p)\thashpow2(t, p)\n\n\n#define hashpointer(t,p)\thashmod(t, point2uint(p))\n\n\n#define dummynode\t\t(&dummynode_)\n\nstatic const Node dummynode_ = {\n  {{NULL}, LUA_VEMPTY,  /* value's value and type */\n   LUA_VNIL, 0, {NULL}}  /* key type, next, and key value */\n};\n\n\nstatic const TValue absentkey = {ABSTKEYCONSTANT};\n\n\n/*\n** Hash for integers. To allow a good hash, use the remainder operator\n** ('%'). If integer fits as a non-negative int, compute an int\n** remainder, which is faster. Otherwise, use an unsigned-integer\n** remainder, which uses all bits and ensures a non-negative result.\n*/\nstatic Node *hashint (const Table *t, lua_Integer i) {\n  lua_Unsigned ui = l_castS2U(i);\n  if (ui <= cast_uint(INT_MAX))\n    return hashmod(t, cast_int(ui));\n  else\n    return hashmod(t, ui);\n}\n\n\n/*\n** Hash for floating-point numbers.\n** The main computation should be just\n**     n = frexp(n, &i); return (n * INT_MAX) + i\n** but there are some numerical subtleties.\n** In a two-complement representation, INT_MAX does not has an exact\n** representation as a float, but INT_MIN does; because the absolute\n** value of 'frexp' is smaller than 1 (unless 'n' is inf/NaN), the\n** absolute value of the product 'frexp * -INT_MIN' is smaller or equal\n** to INT_MAX. Next, the use of 'unsigned int' avoids overflows when\n** adding 'i'; the use of '~u' (instead of '-u') avoids problems with\n** INT_MIN.\n*/\n#if !defined(l_hashfloat)\nstatic int l_hashfloat (lua_Number n) {\n  int i;\n  lua_Integer ni;\n  n = l_mathop(frexp)(n, &i) * -cast_num(INT_MIN);\n  if (!lua_numbertointeger(n, &ni)) {  /* is 'n' inf/-inf/NaN? */\n    lua_assert(luai_numisnan(n) || l_mathop(fabs)(n) == cast_num(HUGE_VAL));\n    return 0;\n  }\n  else {  /* normal case */\n    unsigned int u = cast_uint(i) + cast_uint(ni);\n    return cast_int(u <= cast_uint(INT_MAX) ? u : ~u);\n  }\n}\n#endif\n\n\n/*\n** returns the 'main' position of an element in a table (that is,\n** the index of its hash value).\n*/\nstatic Node *mainpositionTV (const Table *t, const TValue *key) {\n  switch (ttypetag(key)) {\n    case LUA_VNUMINT: {\n      lua_Integer i = ivalue(key);\n      return hashint(t, i);\n    }\n    case LUA_VNUMFLT: {\n      lua_Number n = fltvalue(key);\n      return hashmod(t, l_hashfloat(n));\n    }\n    case LUA_VSHRSTR: {\n      TString *ts = tsvalue(key);\n      return hashstr(t, ts);\n    }\n    case LUA_VLNGSTR: {\n      TString *ts = tsvalue(key);\n      return hashpow2(t, luaS_hashlongstr(ts));\n    }\n    case LUA_VFALSE:\n      return hashboolean(t, 0);\n    case LUA_VTRUE:\n      return hashboolean(t, 1);\n    case LUA_VLIGHTUSERDATA: {\n      void *p = pvalue(key);\n      return hashpointer(t, p);\n    }\n    case LUA_VLCF: {\n      lua_CFunction f = fvalue(key);\n      return hashpointer(t, f);\n    }\n    default: {\n      GCObject *o = gcvalue(key);\n      return hashpointer(t, o);\n    }\n  }\n}\n\n\nl_sinline Node *mainpositionfromnode (const Table *t, Node *nd) {\n  TValue key;\n  getnodekey(cast(lua_State *, NULL), &key, nd);\n  return mainpositionTV(t, &key);\n}\n\n\n/*\n** Check whether key 'k1' is equal to the key in node 'n2'. This\n** equality is raw, so there are no metamethods. Floats with integer\n** values have been normalized, so integers cannot be equal to\n** floats. It is assumed that 'eqshrstr' is simply pointer equality, so\n** that short strings are handled in the default case.\n** A true 'deadok' means to accept dead keys as equal to their original\n** values. All dead keys are compared in the default case, by pointer\n** identity. (Only collectable objects can produce dead keys.) Note that\n** dead long strings are also compared by identity.\n** Once a key is dead, its corresponding value may be collected, and\n** then another value can be created with the same address. If this\n** other value is given to 'next', 'equalkey' will signal a false\n** positive. In a regular traversal, this situation should never happen,\n** as all keys given to 'next' came from the table itself, and therefore\n** could not have been collected. Outside a regular traversal, we\n** have garbage in, garbage out. What is relevant is that this false\n** positive does not break anything.  (In particular, 'next' will return\n** some other valid item on the table or nil.)\n*/\nstatic int equalkey (const TValue *k1, const Node *n2, int deadok) {\n  if ((rawtt(k1) != keytt(n2)) &&  /* not the same variants? */\n       !(deadok && keyisdead(n2) && iscollectable(k1)))\n   return 0;  /* cannot be same key */\n  switch (keytt(n2)) {\n    case LUA_VNIL: case LUA_VFALSE: case LUA_VTRUE:\n      return 1;\n    case LUA_VNUMINT:\n      return (ivalue(k1) == keyival(n2));\n    case LUA_VNUMFLT:\n      return luai_numeq(fltvalue(k1), fltvalueraw(keyval(n2)));\n    case LUA_VLIGHTUSERDATA:\n      return pvalue(k1) == pvalueraw(keyval(n2));\n    case LUA_VLCF:\n      return fvalue(k1) == fvalueraw(keyval(n2));\n    case ctb(LUA_VLNGSTR):\n      return luaS_eqlngstr(tsvalue(k1), keystrval(n2));\n    default:\n      return gcvalue(k1) == gcvalueraw(keyval(n2));\n  }\n}\n\n\n/*\n** True if value of 'alimit' is equal to the real size of the array\n** part of table 't'. (Otherwise, the array part must be larger than\n** 'alimit'.)\n*/\n#define limitequalsasize(t)\t(isrealasize(t) || ispow2((t)->alimit))\n\n\n/*\n** Returns the real size of the 'array' array\n*/\nLUAI_FUNC unsigned int luaH_realasize (const Table *t) {\n  if (limitequalsasize(t))\n    return t->alimit;  /* this is the size */\n  else {\n    unsigned int size = t->alimit;\n    /* compute the smallest power of 2 not smaller than 'n' */\n    size |= (size >> 1);\n    size |= (size >> 2);\n    size |= (size >> 4);\n    size |= (size >> 8);\n#if (UINT_MAX >> 14) > 3  /* unsigned int has more than 16 bits */\n    size |= (size >> 16);\n#if (UINT_MAX >> 30) > 3\n    size |= (size >> 32);  /* unsigned int has more than 32 bits */\n#endif\n#endif\n    size++;\n    lua_assert(ispow2(size) && size/2 < t->alimit && t->alimit < size);\n    return size;\n  }\n}\n\n\n/*\n** Check whether real size of the array is a power of 2.\n** (If it is not, 'alimit' cannot be changed to any other value\n** without changing the real size.)\n*/\nstatic int ispow2realasize (const Table *t) {\n  return (!isrealasize(t) || ispow2(t->alimit));\n}\n\n\nstatic unsigned int setlimittosize (Table *t) {\n  t->alimit = luaH_realasize(t);\n  setrealasize(t);\n  return t->alimit;\n}\n\n\n#define limitasasize(t)\tcheck_exp(isrealasize(t), t->alimit)\n\n\n\n/*\n** \"Generic\" get version. (Not that generic: not valid for integers,\n** which may be in array part, nor for floats with integral values.)\n** See explanation about 'deadok' in function 'equalkey'.\n*/\nstatic const TValue *getgeneric (Table *t, const TValue *key, int deadok) {\n  Node *n = mainpositionTV(t, key);\n  for (;;) {  /* check whether 'key' is somewhere in the chain */\n    if (equalkey(key, n, deadok))\n      return gval(n);  /* that's it */\n    else {\n      int nx = gnext(n);\n      if (nx == 0)\n        return &absentkey;  /* not found */\n      n += nx;\n    }\n  }\n}\n\n\n/*\n** returns the index for 'k' if 'k' is an appropriate key to live in\n** the array part of a table, 0 otherwise.\n*/\nstatic unsigned int arrayindex (lua_Integer k) {\n  if (l_castS2U(k) - 1u < MAXASIZE)  /* 'k' in [1, MAXASIZE]? */\n    return cast_uint(k);  /* 'key' is an appropriate array index */\n  else\n    return 0;\n}\n\n\n/*\n** returns the index of a 'key' for table traversals. First goes all\n** elements in the array part, then elements in the hash part. The\n** beginning of a traversal is signaled by 0.\n*/\nstatic unsigned int findindex (lua_State *L, Table *t, TValue *key,\n                               unsigned int asize) {\n  unsigned int i;\n  if (ttisnil(key)) return 0;  /* first iteration */\n  i = ttisinteger(key) ? arrayindex(ivalue(key)) : 0;\n  if (i - 1u < asize)  /* is 'key' inside array part? */\n    return i;  /* yes; that's the index */\n  else {\n    const TValue *n = getgeneric(t, key, 1);\n    if (l_unlikely(isabstkey(n)))\n      luaG_runerror(L, \"invalid key to 'next'\");  /* key not found */\n    i = cast_int(nodefromval(n) - gnode(t, 0));  /* key index in hash table */\n    /* hash elements are numbered after array ones */\n    return (i + 1) + asize;\n  }\n}\n\n\nint luaH_next (lua_State *L, Table *t, StkId key) {\n  unsigned int asize = luaH_realasize(t);\n  unsigned int i = findindex(L, t, s2v(key), asize);  /* find original key */\n  for (; i < asize; i++) {  /* try first array part */\n    if (!isempty(&t->array[i])) {  /* a non-empty entry? */\n      setivalue(s2v(key), i + 1);\n      setobj2s(L, key + 1, &t->array[i]);\n      return 1;\n    }\n  }\n  for (i -= asize; cast_int(i) < sizenode(t); i++) {  /* hash part */\n    if (!isempty(gval(gnode(t, i)))) {  /* a non-empty entry? */\n      Node *n = gnode(t, i);\n      getnodekey(L, s2v(key), n);\n      setobj2s(L, key + 1, gval(n));\n      return 1;\n    }\n  }\n  return 0;  /* no more elements */\n}\n\n\nstatic void freehash (lua_State *L, Table *t) {\n  if (!isdummy(t))\n    luaM_freearray(L, t->node, cast_sizet(sizenode(t)));\n}\n\n\n/*\n** {=============================================================\n** Rehash\n** ==============================================================\n*/\n\n/*\n** Compute the optimal size for the array part of table 't'. 'nums' is a\n** \"count array\" where 'nums[i]' is the number of integers in the table\n** between 2^(i - 1) + 1 and 2^i. 'pna' enters with the total number of\n** integer keys in the table and leaves with the number of keys that\n** will go to the array part; return the optimal size.  (The condition\n** 'twotoi > 0' in the for loop stops the loop if 'twotoi' overflows.)\n*/\nstatic unsigned int computesizes (unsigned int nums[], unsigned int *pna) {\n  int i;\n  unsigned int twotoi;  /* 2^i (candidate for optimal size) */\n  unsigned int a = 0;  /* number of elements smaller than 2^i */\n  unsigned int na = 0;  /* number of elements to go to array part */\n  unsigned int optimal = 0;  /* optimal size for array part */\n  /* loop while keys can fill more than half of total size */\n  for (i = 0, twotoi = 1;\n       twotoi > 0 && *pna > twotoi / 2;\n       i++, twotoi *= 2) {\n    a += nums[i];\n    if (a > twotoi/2) {  /* more than half elements present? */\n      optimal = twotoi;  /* optimal size (till now) */\n      na = a;  /* all elements up to 'optimal' will go to array part */\n    }\n  }\n  lua_assert((optimal == 0 || optimal / 2 < na) && na <= optimal);\n  *pna = na;\n  return optimal;\n}\n\n\nstatic int countint (lua_Integer key, unsigned int *nums) {\n  unsigned int k = arrayindex(key);\n  if (k != 0) {  /* is 'key' an appropriate array index? */\n    nums[luaO_ceillog2(k)]++;  /* count as such */\n    return 1;\n  }\n  else\n    return 0;\n}\n\n\n/*\n** Count keys in array part of table 't': Fill 'nums[i]' with\n** number of keys that will go into corresponding slice and return\n** total number of non-nil keys.\n*/\nstatic unsigned int numusearray (const Table *t, unsigned int *nums) {\n  int lg;\n  unsigned int ttlg;  /* 2^lg */\n  unsigned int ause = 0;  /* summation of 'nums' */\n  unsigned int i = 1;  /* count to traverse all array keys */\n  unsigned int asize = limitasasize(t);  /* real array size */\n  /* traverse each slice */\n  for (lg = 0, ttlg = 1; lg <= MAXABITS; lg++, ttlg *= 2) {\n    unsigned int lc = 0;  /* counter */\n    unsigned int lim = ttlg;\n    if (lim > asize) {\n      lim = asize;  /* adjust upper limit */\n      if (i > lim)\n        break;  /* no more elements to count */\n    }\n    /* count elements in range (2^(lg - 1), 2^lg] */\n    for (; i <= lim; i++) {\n      if (!isempty(&t->array[i-1]))\n        lc++;\n    }\n    nums[lg] += lc;\n    ause += lc;\n  }\n  return ause;\n}\n\n\nstatic int numusehash (const Table *t, unsigned int *nums, unsigned int *pna) {\n  int totaluse = 0;  /* total number of elements */\n  int ause = 0;  /* elements added to 'nums' (can go to array part) */\n  int i = sizenode(t);\n  while (i--) {\n    Node *n = &t->node[i];\n    if (!isempty(gval(n))) {\n      if (keyisinteger(n))\n        ause += countint(keyival(n), nums);\n      totaluse++;\n    }\n  }\n  *pna += ause;\n  return totaluse;\n}\n\n\n/*\n** Creates an array for the hash part of a table with the given\n** size, or reuses the dummy node if size is zero.\n** The computation for size overflow is in two steps: the first\n** comparison ensures that the shift in the second one does not\n** overflow.\n*/\nstatic void setnodevector (lua_State *L, Table *t, unsigned int size) {\n  if (size == 0) {  /* no elements to hash part? */\n    t->node = cast(Node *, dummynode);  /* use common 'dummynode' */\n    t->lsizenode = 0;\n    t->lastfree = NULL;  /* signal that it is using dummy node */\n  }\n  else {\n    int i;\n    int lsize = luaO_ceillog2(size);\n    if (lsize > MAXHBITS || (1u << lsize) > MAXHSIZE)\n      luaG_runerror(L, \"table overflow\");\n    size = twoto(lsize);\n    t->node = luaM_newvector(L, size, Node);\n    for (i = 0; i < cast_int(size); i++) {\n      Node *n = gnode(t, i);\n      gnext(n) = 0;\n      setnilkey(n);\n      setempty(gval(n));\n    }\n    t->lsizenode = cast_byte(lsize);\n    t->lastfree = gnode(t, size);  /* all positions are free */\n  }\n}\n\n\n/*\n** (Re)insert all elements from the hash part of 'ot' into table 't'.\n*/\nstatic void reinsert (lua_State *L, Table *ot, Table *t) {\n  int j;\n  int size = sizenode(ot);\n  for (j = 0; j < size; j++) {\n    Node *old = gnode(ot, j);\n    if (!isempty(gval(old))) {\n      /* doesn't need barrier/invalidate cache, as entry was\n         already present in the table */\n      TValue k;\n      getnodekey(L, &k, old);\n      luaH_set(L, t, &k, gval(old));\n    }\n  }\n}\n\n\n/*\n** Exchange the hash part of 't1' and 't2'.\n*/\nstatic void exchangehashpart (Table *t1, Table *t2) {\n  lu_byte lsizenode = t1->lsizenode;\n  Node *node = t1->node;\n  Node *lastfree = t1->lastfree;\n  t1->lsizenode = t2->lsizenode;\n  t1->node = t2->node;\n  t1->lastfree = t2->lastfree;\n  t2->lsizenode = lsizenode;\n  t2->node = node;\n  t2->lastfree = lastfree;\n}\n\n\n/*\n** Resize table 't' for the new given sizes. Both allocations (for\n** the hash part and for the array part) can fail, which creates some\n** subtleties. If the first allocation, for the hash part, fails, an\n** error is raised and that is it. Otherwise, it copies the elements from\n** the shrinking part of the array (if it is shrinking) into the new\n** hash. Then it reallocates the array part.  If that fails, the table\n** is in its original state; the function frees the new hash part and then\n** raises the allocation error. Otherwise, it sets the new hash part\n** into the table, initializes the new part of the array (if any) with\n** nils and reinserts the elements of the old hash back into the new\n** parts of the table.\n*/\nvoid luaH_resize (lua_State *L, Table *t, unsigned int newasize,\n                                          unsigned int nhsize) {\n  unsigned int i;\n  Table newt;  /* to keep the new hash part */\n  unsigned int oldasize = setlimittosize(t);\n  TValue *newarray;\n  /* create new hash part with appropriate size into 'newt' */\n  setnodevector(L, &newt, nhsize);\n  if (newasize < oldasize) {  /* will array shrink? */\n    t->alimit = newasize;  /* pretend array has new size... */\n    exchangehashpart(t, &newt);  /* and new hash */\n    /* re-insert into the new hash the elements from vanishing slice */\n    for (i = newasize; i < oldasize; i++) {\n      if (!isempty(&t->array[i]))\n        luaH_setint(L, t, i + 1, &t->array[i]);\n    }\n    t->alimit = oldasize;  /* restore current size... */\n    exchangehashpart(t, &newt);  /* and hash (in case of errors) */\n  }\n  /* allocate new array */\n  newarray = luaM_reallocvector(L, t->array, oldasize, newasize, TValue);\n  if (l_unlikely(newarray == NULL && newasize > 0)) {  /* allocation failed? */\n    freehash(L, &newt);  /* release new hash part */\n    luaM_error(L);  /* raise error (with array unchanged) */\n  }\n  /* allocation ok; initialize new part of the array */\n  exchangehashpart(t, &newt);  /* 't' has the new hash ('newt' has the old) */\n  t->array = newarray;  /* set new array part */\n  t->alimit = newasize;\n  for (i = oldasize; i < newasize; i++)  /* clear new slice of the array */\n     setempty(&t->array[i]);\n  /* re-insert elements from old hash part into new parts */\n  reinsert(L, &newt, t);  /* 'newt' now has the old hash */\n  freehash(L, &newt);  /* free old hash part */\n}\n\n\nvoid luaH_resizearray (lua_State *L, Table *t, unsigned int nasize) {\n  int nsize = allocsizenode(t);\n  luaH_resize(L, t, nasize, nsize);\n}\n\n/*\n** nums[i] = number of keys 'k' where 2^(i - 1) < k <= 2^i\n*/\nstatic void rehash (lua_State *L, Table *t, const TValue *ek) {\n  unsigned int asize;  /* optimal size for array part */\n  unsigned int na;  /* number of keys in the array part */\n  unsigned int nums[MAXABITS + 1];\n  int i;\n  int totaluse;\n  for (i = 0; i <= MAXABITS; i++) nums[i] = 0;  /* reset counts */\n  setlimittosize(t);\n  na = numusearray(t, nums);  /* count keys in array part */\n  totaluse = na;  /* all those keys are integer keys */\n  totaluse += numusehash(t, nums, &na);  /* count keys in hash part */\n  /* count extra key */\n  if (ttisinteger(ek))\n    na += countint(ivalue(ek), nums);\n  totaluse++;\n  /* compute new size for array part */\n  asize = computesizes(nums, &na);\n  /* resize the table to new computed sizes */\n  luaH_resize(L, t, asize, totaluse - na);\n}\n\n\n\n/*\n** }=============================================================\n*/\n\n\nTable *luaH_new (lua_State *L) {\n  GCObject *o = luaC_newobj(L, LUA_VTABLE, sizeof(Table));\n  Table *t = gco2t(o);\n  t->metatable = NULL;\n  t->flags = cast_byte(maskflags);  /* table has no metamethod fields */\n  t->array = NULL;\n  t->alimit = 0;\n  setnodevector(L, t, 0);\n  return t;\n}\n\n\nvoid luaH_free (lua_State *L, Table *t) {\n  freehash(L, t);\n  luaM_freearray(L, t->array, luaH_realasize(t));\n  luaM_free(L, t);\n}\n\n\nstatic Node *getfreepos (Table *t) {\n  if (!isdummy(t)) {\n    while (t->lastfree > t->node) {\n      t->lastfree--;\n      if (keyisnil(t->lastfree))\n        return t->lastfree;\n    }\n  }\n  return NULL;  /* could not find a free place */\n}\n\n\n\n/*\n** inserts a new key into a hash table; first, check whether key's main\n** position is free. If not, check whether colliding node is in its main\n** position or not: if it is not, move colliding node to an empty place and\n** put new key in its main position; otherwise (colliding node is in its main\n** position), new key goes to an empty position.\n*/\nvoid luaH_newkey (lua_State *L, Table *t, const TValue *key, TValue *value) {\n  Node *mp;\n  TValue aux;\n  if (l_unlikely(ttisnil(key)))\n    luaG_runerror(L, \"table index is nil\");\n  else if (ttisfloat(key)) {\n    lua_Number f = fltvalue(key);\n    lua_Integer k;\n    if (luaV_flttointeger(f, &k, F2Ieq)) {  /* does key fit in an integer? */\n      setivalue(&aux, k);\n      key = &aux;  /* insert it as an integer */\n    }\n    else if (l_unlikely(luai_numisnan(f)))\n      luaG_runerror(L, \"table index is NaN\");\n  }\n  if (ttisnil(value))\n    return;  /* do not insert nil values */\n  mp = mainpositionTV(t, key);\n  if (!isempty(gval(mp)) || isdummy(t)) {  /* main position is taken? */\n    Node *othern;\n    Node *f = getfreepos(t);  /* get a free place */\n    if (f == NULL) {  /* cannot find a free place? */\n      rehash(L, t, key);  /* grow table */\n      /* whatever called 'newkey' takes care of TM cache */\n      luaH_set(L, t, key, value);  /* insert key into grown table */\n      return;\n    }\n    lua_assert(!isdummy(t));\n    othern = mainpositionfromnode(t, mp);\n    if (othern != mp) {  /* is colliding node out of its main position? */\n      /* yes; move colliding node into free position */\n      while (othern + gnext(othern) != mp)  /* find previous */\n        othern += gnext(othern);\n      gnext(othern) = cast_int(f - othern);  /* rechain to point to 'f' */\n      *f = *mp;  /* copy colliding node into free pos. (mp->next also goes) */\n      if (gnext(mp) != 0) {\n        gnext(f) += cast_int(mp - f);  /* correct 'next' */\n        gnext(mp) = 0;  /* now 'mp' is free */\n      }\n      setempty(gval(mp));\n    }\n    else {  /* colliding node is in its own main position */\n      /* new node will go into free position */\n      if (gnext(mp) != 0)\n        gnext(f) = cast_int((mp + gnext(mp)) - f);  /* chain new position */\n      else lua_assert(gnext(f) == 0);\n      gnext(mp) = cast_int(f - mp);\n      mp = f;\n    }\n  }\n  setnodekey(L, mp, key);\n  luaC_barrierback(L, obj2gco(t), key);\n  lua_assert(isempty(gval(mp)));\n  setobj2t(L, gval(mp), value);\n}\n\n\n/*\n** Search function for integers. If integer is inside 'alimit', get it\n** directly from the array part. Otherwise, if 'alimit' is not equal to\n** the real size of the array, key still can be in the array part. In\n** this case, try to avoid a call to 'luaH_realasize' when key is just\n** one more than the limit (so that it can be incremented without\n** changing the real size of the array).\n*/\nconst TValue *luaH_getint (Table *t, lua_Integer key) {\n  if (l_castS2U(key) - 1u < t->alimit)  /* 'key' in [1, t->alimit]? */\n    return &t->array[key - 1];\n  else if (!limitequalsasize(t) &&  /* key still may be in the array part? */\n           (l_castS2U(key) == t->alimit + 1 ||\n            l_castS2U(key) - 1u < luaH_realasize(t))) {\n    t->alimit = cast_uint(key);  /* probably '#t' is here now */\n    return &t->array[key - 1];\n  }\n  else {\n    Node *n = hashint(t, key);\n    for (;;) {  /* check whether 'key' is somewhere in the chain */\n      if (keyisinteger(n) && keyival(n) == key)\n        return gval(n);  /* that's it */\n      else {\n        int nx = gnext(n);\n        if (nx == 0) break;\n        n += nx;\n      }\n    }\n    return &absentkey;\n  }\n}\n\n\n/*\n** search function for short strings\n*/\nconst TValue *luaH_getshortstr (Table *t, TString *key) {\n  Node *n = hashstr(t, key);\n  lua_assert(key->tt == LUA_VSHRSTR);\n  for (;;) {  /* check whether 'key' is somewhere in the chain */\n    if (keyisshrstr(n) && eqshrstr(keystrval(n), key))\n      return gval(n);  /* that's it */\n    else {\n      int nx = gnext(n);\n      if (nx == 0)\n        return &absentkey;  /* not found */\n      n += nx;\n    }\n  }\n}\n\n\nconst TValue *luaH_getstr (Table *t, TString *key) {\n  if (key->tt == LUA_VSHRSTR)\n    return luaH_getshortstr(t, key);\n  else {  /* for long strings, use generic case */\n    TValue ko;\n    setsvalue(cast(lua_State *, NULL), &ko, key);\n    return getgeneric(t, &ko, 0);\n  }\n}\n\n\n/*\n** main search function\n*/\nconst TValue *luaH_get (Table *t, const TValue *key) {\n  switch (ttypetag(key)) {\n    case LUA_VSHRSTR: return luaH_getshortstr(t, tsvalue(key));\n    case LUA_VNUMINT: return luaH_getint(t, ivalue(key));\n    case LUA_VNIL: return &absentkey;\n    case LUA_VNUMFLT: {\n      lua_Integer k;\n      if (luaV_flttointeger(fltvalue(key), &k, F2Ieq)) /* integral index? */\n        return luaH_getint(t, k);  /* use specialized version */\n      /* else... */\n    }  /* FALLTHROUGH */\n    default:\n      return getgeneric(t, key, 0);\n  }\n}\n\n\n/*\n** Finish a raw \"set table\" operation, where 'slot' is where the value\n** should have been (the result of a previous \"get table\").\n** Beware: when using this function you probably need to check a GC\n** barrier and invalidate the TM cache.\n*/\nvoid luaH_finishset (lua_State *L, Table *t, const TValue *key,\n                                   const TValue *slot, TValue *value) {\n  if (isabstkey(slot))\n    luaH_newkey(L, t, key, value);\n  else\n    setobj2t(L, cast(TValue *, slot), value);\n}\n\n\n/*\n** beware: when using this function you probably need to check a GC\n** barrier and invalidate the TM cache.\n*/\nvoid luaH_set (lua_State *L, Table *t, const TValue *key, TValue *value) {\n  const TValue *slot = luaH_get(t, key);\n  luaH_finishset(L, t, key, slot, value);\n}\n\n\nvoid luaH_setint (lua_State *L, Table *t, lua_Integer key, TValue *value) {\n  const TValue *p = luaH_getint(t, key);\n  if (isabstkey(p)) {\n    TValue k;\n    setivalue(&k, key);\n    luaH_newkey(L, t, &k, value);\n  }\n  else\n    setobj2t(L, cast(TValue *, p), value);\n}\n\n\n/*\n** Try to find a boundary in the hash part of table 't'. From the\n** caller, we know that 'j' is zero or present and that 'j + 1' is\n** present. We want to find a larger key that is absent from the\n** table, so that we can do a binary search between the two keys to\n** find a boundary. We keep doubling 'j' until we get an absent index.\n** If the doubling would overflow, we try LUA_MAXINTEGER. If it is\n** absent, we are ready for the binary search. ('j', being max integer,\n** is larger or equal to 'i', but it cannot be equal because it is\n** absent while 'i' is present; so 'j > i'.) Otherwise, 'j' is a\n** boundary. ('j + 1' cannot be a present integer key because it is\n** not a valid integer in Lua.)\n*/\nstatic lua_Unsigned hash_search (Table *t, lua_Unsigned j) {\n  lua_Unsigned i;\n  if (j == 0) j++;  /* the caller ensures 'j + 1' is present */\n  do {\n    i = j;  /* 'i' is a present index */\n    if (j <= l_castS2U(LUA_MAXINTEGER) / 2)\n      j *= 2;\n    else {\n      j = LUA_MAXINTEGER;\n      if (isempty(luaH_getint(t, j)))  /* t[j] not present? */\n        break;  /* 'j' now is an absent index */\n      else  /* weird case */\n        return j;  /* well, max integer is a boundary... */\n    }\n  } while (!isempty(luaH_getint(t, j)));  /* repeat until an absent t[j] */\n  /* i < j  &&  t[i] present  &&  t[j] absent */\n  while (j - i > 1u) {  /* do a binary search between them */\n    lua_Unsigned m = (i + j) / 2;\n    if (isempty(luaH_getint(t, m))) j = m;\n    else i = m;\n  }\n  return i;\n}\n\n\nstatic unsigned int binsearch (const TValue *array, unsigned int i,\n                                                    unsigned int j) {\n  while (j - i > 1u) {  /* binary search */\n    unsigned int m = (i + j) / 2;\n    if (isempty(&array[m - 1])) j = m;\n    else i = m;\n  }\n  return i;\n}\n\n\n/*\n** Try to find a boundary in table 't'. (A 'boundary' is an integer index\n** such that t[i] is present and t[i+1] is absent, or 0 if t[1] is absent\n** and 'maxinteger' if t[maxinteger] is present.)\n** (In the next explanation, we use Lua indices, that is, with base 1.\n** The code itself uses base 0 when indexing the array part of the table.)\n** The code starts with 'limit = t->alimit', a position in the array\n** part that may be a boundary.\n**\n** (1) If 't[limit]' is empty, there must be a boundary before it.\n** As a common case (e.g., after 't[#t]=nil'), check whether 'limit-1'\n** is present. If so, it is a boundary. Otherwise, do a binary search\n** between 0 and limit to find a boundary. In both cases, try to\n** use this boundary as the new 'alimit', as a hint for the next call.\n**\n** (2) If 't[limit]' is not empty and the array has more elements\n** after 'limit', try to find a boundary there. Again, try first\n** the special case (which should be quite frequent) where 'limit+1'\n** is empty, so that 'limit' is a boundary. Otherwise, check the\n** last element of the array part. If it is empty, there must be a\n** boundary between the old limit (present) and the last element\n** (absent), which is found with a binary search. (This boundary always\n** can be a new limit.)\n**\n** (3) The last case is when there are no elements in the array part\n** (limit == 0) or its last element (the new limit) is present.\n** In this case, must check the hash part. If there is no hash part\n** or 'limit+1' is absent, 'limit' is a boundary.  Otherwise, call\n** 'hash_search' to find a boundary in the hash part of the table.\n** (In those cases, the boundary is not inside the array part, and\n** therefore cannot be used as a new limit.)\n*/\nlua_Unsigned luaH_getn (Table *t) {\n  unsigned int limit = t->alimit;\n  if (limit > 0 && isempty(&t->array[limit - 1])) {  /* (1)? */\n    /* there must be a boundary before 'limit' */\n    if (limit >= 2 && !isempty(&t->array[limit - 2])) {\n      /* 'limit - 1' is a boundary; can it be a new limit? */\n      if (ispow2realasize(t) && !ispow2(limit - 1)) {\n        t->alimit = limit - 1;\n        setnorealasize(t);  /* now 'alimit' is not the real size */\n      }\n      return limit - 1;\n    }\n    else {  /* must search for a boundary in [0, limit] */\n      unsigned int boundary = binsearch(t->array, 0, limit);\n      /* can this boundary represent the real size of the array? */\n      if (ispow2realasize(t) && boundary > luaH_realasize(t) / 2) {\n        t->alimit = boundary;  /* use it as the new limit */\n        setnorealasize(t);\n      }\n      return boundary;\n    }\n  }\n  /* 'limit' is zero or present in table */\n  if (!limitequalsasize(t)) {  /* (2)? */\n    /* 'limit' > 0 and array has more elements after 'limit' */\n    if (isempty(&t->array[limit]))  /* 'limit + 1' is empty? */\n      return limit;  /* this is the boundary */\n    /* else, try last element in the array */\n    limit = luaH_realasize(t);\n    if (isempty(&t->array[limit - 1])) {  /* empty? */\n      /* there must be a boundary in the array after old limit,\n         and it must be a valid new limit */\n      unsigned int boundary = binsearch(t->array, t->alimit, limit);\n      t->alimit = boundary;\n      return boundary;\n    }\n    /* else, new limit is present in the table; check the hash part */\n  }\n  /* (3) 'limit' is the last element and either is zero or present in table */\n  lua_assert(limit == luaH_realasize(t) &&\n             (limit == 0 || !isempty(&t->array[limit - 1])));\n  if (isdummy(t) || isempty(luaH_getint(t, cast(lua_Integer, limit + 1))))\n    return limit;  /* 'limit + 1' is absent */\n  else  /* 'limit + 1' is also present */\n    return hash_search(t, limit);\n}\n\n\n\n#if defined(LUA_DEBUG)\n\n/* export these functions for the test library */\n\nNode *luaH_mainposition (const Table *t, const TValue *key) {\n  return mainpositionTV(t, key);\n}\n\n#endif\n/*\n** $Id: ldo.c $\n** Stack and Call structure of Lua\n** See Copyright Notice in lua.h\n*/\n\n#define ldo_c\n#define LUA_CORE\n\n/*#include \"lprefix.h\"*/\n\n\n#include <setjmp.h>\n#include <stdlib.h>\n#include <string.h>\n\n/*#include \"lua.h\"*/\n\n/*#include \"lapi.h\"*/\n/*#include \"ldebug.h\"*/\n/*#include \"ldo.h\"*/\n/*#include \"lfunc.h\"*/\n/*#include \"lgc.h\"*/\n/*#include \"lmem.h\"*/\n/*#include \"lobject.h\"*/\n/*#include \"lopcodes.h\"*/\n/*#include \"lparser.h\"*/\n/*#include \"lstate.h\"*/\n/*#include \"lstring.h\"*/\n/*#include \"ltable.h\"*/\n/*#include \"ltm.h\"*/\n/*#include \"lundump.h\"*/\n/*#include \"lvm.h\"*/\n/*#include \"lzio.h\"*/\n\n\n\n#define errorstatus(s)\t((s) > LUA_YIELD)\n\n\n/*\n** {======================================================\n** Error-recovery functions\n** =======================================================\n*/\n\n/*\n** LUAI_THROW/LUAI_TRY define how Lua does exception handling. By\n** default, Lua handles errors with exceptions when compiling as\n** C++ code, with _longjmp/_setjmp when asked to use them, and with\n** longjmp/setjmp otherwise.\n*/\n#if !defined(LUAI_THROW)\t\t\t\t/* { */\n\n#if defined(__cplusplus) && !defined(LUA_USE_LONGJMP)\t/* { */\n\n/* C++ exceptions */\n#define LUAI_THROW(L,c)\t\tthrow(c)\n#define LUAI_TRY(L,c,a) \\\n\ttry { a } catch(...) { if ((c)->status == 0) (c)->status = -1; }\n#define luai_jmpbuf\t\tint  /* dummy variable */\n\n#elif defined(LUA_USE_POSIX)\t\t\t\t/* }{ */\n\n/* in POSIX, try _longjmp/_setjmp (more efficient) */\n#define LUAI_THROW(L,c)\t\t_longjmp((c)->b, 1)\n#define LUAI_TRY(L,c,a)\t\tif (_setjmp((c)->b) == 0) { a }\n#define luai_jmpbuf\t\tjmp_buf\n\n#else\t\t\t\t\t\t\t/* }{ */\n\n/* ISO C handling with long jumps */\n#define LUAI_THROW(L,c)\t\tlongjmp((c)->b, 1)\n#define LUAI_TRY(L,c,a)\t\tif (setjmp((c)->b) == 0) { a }\n#define luai_jmpbuf\t\tjmp_buf\n\n#endif\t\t\t\t\t\t\t/* } */\n\n#endif\t\t\t\t\t\t\t/* } */\n\n\n\n/* chain list of long jump buffers */\nstruct lua_longjmp {\n  struct lua_longjmp *previous;\n  luai_jmpbuf b;\n  volatile int status;  /* error code */\n};\n\n\nvoid luaD_seterrorobj (lua_State *L, int errcode, StkId oldtop) {\n  switch (errcode) {\n    case LUA_ERRMEM: {  /* memory error? */\n      setsvalue2s(L, oldtop, G(L)->memerrmsg); /* reuse preregistered msg. */\n      break;\n    }\n    case LUA_ERRERR: {\n      setsvalue2s(L, oldtop, luaS_newliteral(L, \"error in error handling\"));\n      break;\n    }\n    case LUA_OK: {  /* special case only for closing upvalues */\n      setnilvalue(s2v(oldtop));  /* no error message */\n      break;\n    }\n    default: {\n      lua_assert(errorstatus(errcode));  /* real error */\n      setobjs2s(L, oldtop, L->top.p - 1);  /* error message on current top */\n      break;\n    }\n  }\n  L->top.p = oldtop + 1;\n}\n\n\nl_noret luaD_throw (lua_State *L, int errcode) {\n  if (L->errorJmp) {  /* thread has an error handler? */\n    L->errorJmp->status = errcode;  /* set status */\n    LUAI_THROW(L, L->errorJmp);  /* jump to it */\n  }\n  else {  /* thread has no error handler */\n    global_State *g = G(L);\n    errcode = luaE_resetthread(L, errcode);  /* close all upvalues */\n    if (g->mainthread->errorJmp) {  /* main thread has a handler? */\n      setobjs2s(L, g->mainthread->top.p++, L->top.p - 1);  /* copy error obj. */\n      luaD_throw(g->mainthread, errcode);  /* re-throw in main thread */\n    }\n    else {  /* no handler at all; abort */\n      if (g->panic) {  /* panic function? */\n        lua_unlock(L);\n        g->panic(L);  /* call panic function (last chance to jump out) */\n      }\n      abort();\n    }\n  }\n}\n\n\nint luaD_rawrunprotected (lua_State *L, Pfunc f, void *ud) {\n  l_uint32 oldnCcalls = L->nCcalls;\n  struct lua_longjmp lj;\n  lj.status = LUA_OK;\n  lj.previous = L->errorJmp;  /* chain new error handler */\n  L->errorJmp = &lj;\n  LUAI_TRY(L, &lj,\n    (*f)(L, ud);\n  );\n  L->errorJmp = lj.previous;  /* restore old error handler */\n  L->nCcalls = oldnCcalls;\n  return lj.status;\n}\n\n/* }====================================================== */\n\n\n/*\n** {==================================================================\n** Stack reallocation\n** ===================================================================\n*/\n\n\n/*\n** Change all pointers to the stack into offsets.\n*/\nstatic void relstack (lua_State *L) {\n  CallInfo *ci;\n  UpVal *up;\n  L->top.offset = savestack(L, L->top.p);\n  L->tbclist.offset = savestack(L, L->tbclist.p);\n  for (up = L->openupval; up != NULL; up = up->u.open.next)\n    up->v.offset = savestack(L, uplevel(up));\n  for (ci = L->ci; ci != NULL; ci = ci->previous) {\n    ci->top.offset = savestack(L, ci->top.p);\n    ci->func.offset = savestack(L, ci->func.p);\n  }\n}\n\n\n/*\n** Change back all offsets into pointers.\n*/\nstatic void correctstack (lua_State *L) {\n  CallInfo *ci;\n  UpVal *up;\n  L->top.p = restorestack(L, L->top.offset);\n  L->tbclist.p = restorestack(L, L->tbclist.offset);\n  for (up = L->openupval; up != NULL; up = up->u.open.next)\n    up->v.p = s2v(restorestack(L, up->v.offset));\n  for (ci = L->ci; ci != NULL; ci = ci->previous) {\n    ci->top.p = restorestack(L, ci->top.offset);\n    ci->func.p = restorestack(L, ci->func.offset);\n    if (isLua(ci))\n      ci->u.l.trap = 1;  /* signal to update 'trap' in 'luaV_execute' */\n  }\n}\n\n\n/* some space for error handling */\n#define ERRORSTACKSIZE\t(LUAI_MAXSTACK + 200)\n\n/*\n** Reallocate the stack to a new size, correcting all pointers into it.\n** In ISO C, any pointer use after the pointer has been deallocated is\n** undefined behavior. So, before the reallocation, all pointers are\n** changed to offsets, and after the reallocation they are changed back\n** to pointers. As during the reallocation the pointers are invalid, the\n** reallocation cannot run emergency collections.\n**\n** In case of allocation error, raise an error or return false according\n** to 'raiseerror'.\n*/\nint luaD_reallocstack (lua_State *L, int newsize, int raiseerror) {\n  int oldsize = stacksize(L);\n  int i;\n  StkId newstack;\n  int oldgcstop = G(L)->gcstopem;\n  lua_assert(newsize <= LUAI_MAXSTACK || newsize == ERRORSTACKSIZE);\n  relstack(L);  /* change pointers to offsets */\n  G(L)->gcstopem = 1;  /* stop emergency collection */\n  newstack = luaM_reallocvector(L, L->stack.p, oldsize + EXTRA_STACK,\n                                   newsize + EXTRA_STACK, StackValue);\n  G(L)->gcstopem = oldgcstop;  /* restore emergency collection */\n  if (l_unlikely(newstack == NULL)) {  /* reallocation failed? */\n    correctstack(L);  /* change offsets back to pointers */\n    if (raiseerror)\n      luaM_error(L);\n    else return 0;  /* do not raise an error */\n  }\n  L->stack.p = newstack;\n  correctstack(L);  /* change offsets back to pointers */\n  L->stack_last.p = L->stack.p + newsize;\n  for (i = oldsize + EXTRA_STACK; i < newsize + EXTRA_STACK; i++)\n    setnilvalue(s2v(newstack + i)); /* erase new segment */\n  return 1;\n}\n\n\n/*\n** Try to grow the stack by at least 'n' elements. When 'raiseerror'\n** is true, raises any error; otherwise, return 0 in case of errors.\n*/\nint luaD_growstack (lua_State *L, int n, int raiseerror) {\n  int size = stacksize(L);\n  if (l_unlikely(size > LUAI_MAXSTACK)) {\n    /* if stack is larger than maximum, thread is already using the\n       extra space reserved for errors, that is, thread is handling\n       a stack error; cannot grow further than that. */\n    lua_assert(stacksize(L) == ERRORSTACKSIZE);\n    if (raiseerror)\n      luaD_throw(L, LUA_ERRERR);  /* error inside message handler */\n    return 0;  /* if not 'raiseerror', just signal it */\n  }\n  else if (n < LUAI_MAXSTACK) {  /* avoids arithmetic overflows */\n    int newsize = 2 * size;  /* tentative new size */\n    int needed = cast_int(L->top.p - L->stack.p) + n;\n    if (newsize > LUAI_MAXSTACK)  /* cannot cross the limit */\n      newsize = LUAI_MAXSTACK;\n    if (newsize < needed)  /* but must respect what was asked for */\n      newsize = needed;\n    if (l_likely(newsize <= LUAI_MAXSTACK))\n      return luaD_reallocstack(L, newsize, raiseerror);\n  }\n  /* else stack overflow */\n  /* add extra size to be able to handle the error message */\n  luaD_reallocstack(L, ERRORSTACKSIZE, raiseerror);\n  if (raiseerror)\n    luaG_runerror(L, \"stack overflow\");\n  return 0;\n}\n\n\n/*\n** Compute how much of the stack is being used, by computing the\n** maximum top of all call frames in the stack and the current top.\n*/\nstatic int stackinuse (lua_State *L) {\n  CallInfo *ci;\n  int res;\n  StkId lim = L->top.p;\n  for (ci = L->ci; ci != NULL; ci = ci->previous) {\n    if (lim < ci->top.p) lim = ci->top.p;\n  }\n  lua_assert(lim <= L->stack_last.p + EXTRA_STACK);\n  res = cast_int(lim - L->stack.p) + 1;  /* part of stack in use */\n  if (res < LUA_MINSTACK)\n    res = LUA_MINSTACK;  /* ensure a minimum size */\n  return res;\n}\n\n\n/*\n** If stack size is more than 3 times the current use, reduce that size\n** to twice the current use. (So, the final stack size is at most 2/3 the\n** previous size, and half of its entries are empty.)\n** As a particular case, if stack was handling a stack overflow and now\n** it is not, 'max' (limited by LUAI_MAXSTACK) will be smaller than\n** stacksize (equal to ERRORSTACKSIZE in this case), and so the stack\n** will be reduced to a \"regular\" size.\n*/\nvoid luaD_shrinkstack (lua_State *L) {\n  int inuse = stackinuse(L);\n  int max = (inuse > LUAI_MAXSTACK / 3) ? LUAI_MAXSTACK : inuse * 3;\n  /* if thread is currently not handling a stack overflow and its\n     size is larger than maximum \"reasonable\" size, shrink it */\n  if (inuse <= LUAI_MAXSTACK && stacksize(L) > max) {\n    int nsize = (inuse > LUAI_MAXSTACK / 2) ? LUAI_MAXSTACK : inuse * 2;\n    luaD_reallocstack(L, nsize, 0);  /* ok if that fails */\n  }\n  else  /* don't change stack */\n    condmovestack(L,{},{});  /* (change only for debugging) */\n  luaE_shrinkCI(L);  /* shrink CI list */\n}\n\n\nvoid luaD_inctop (lua_State *L) {\n  luaD_checkstack(L, 1);\n  L->top.p++;\n}\n\n/* }================================================================== */\n\n\n/*\n** Call a hook for the given event. Make sure there is a hook to be\n** called. (Both 'L->hook' and 'L->hookmask', which trigger this\n** function, can be changed asynchronously by signals.)\n*/\nvoid luaD_hook (lua_State *L, int event, int line,\n                              int ftransfer, int ntransfer) {\n  lua_Hook hook = L->hook;\n  if (hook && L->allowhook) {  /* make sure there is a hook */\n    int mask = CIST_HOOKED;\n    CallInfo *ci = L->ci;\n    ptrdiff_t top = savestack(L, L->top.p);  /* preserve original 'top' */\n    ptrdiff_t ci_top = savestack(L, ci->top.p);  /* idem for 'ci->top' */\n    lua_Debug ar;\n    ar.event = event;\n    ar.currentline = line;\n    ar.i_ci = ci;\n    if (ntransfer != 0) {\n      mask |= CIST_TRAN;  /* 'ci' has transfer information */\n      ci->u2.transferinfo.ftransfer = ftransfer;\n      ci->u2.transferinfo.ntransfer = ntransfer;\n    }\n    if (isLua(ci) && L->top.p < ci->top.p)\n      L->top.p = ci->top.p;  /* protect entire activation register */\n    luaD_checkstack(L, LUA_MINSTACK);  /* ensure minimum stack size */\n    if (ci->top.p < L->top.p + LUA_MINSTACK)\n      ci->top.p = L->top.p + LUA_MINSTACK;\n    L->allowhook = 0;  /* cannot call hooks inside a hook */\n    ci->callstatus |= mask;\n    lua_unlock(L);\n    (*hook)(L, &ar);\n    lua_lock(L);\n    lua_assert(!L->allowhook);\n    L->allowhook = 1;\n    ci->top.p = restorestack(L, ci_top);\n    L->top.p = restorestack(L, top);\n    ci->callstatus &= ~mask;\n  }\n}\n\n\n/*\n** Executes a call hook for Lua functions. This function is called\n** whenever 'hookmask' is not zero, so it checks whether call hooks are\n** active.\n*/\nvoid luaD_hookcall (lua_State *L, CallInfo *ci) {\n  L->oldpc = 0;  /* set 'oldpc' for new function */\n  if (L->hookmask & LUA_MASKCALL) {  /* is call hook on? */\n    int event = (ci->callstatus & CIST_TAIL) ? LUA_HOOKTAILCALL\n                                             : LUA_HOOKCALL;\n    Proto *p = ci_func(ci)->p;\n    ci->u.l.savedpc++;  /* hooks assume 'pc' is already incremented */\n    luaD_hook(L, event, -1, 1, p->numparams);\n    ci->u.l.savedpc--;  /* correct 'pc' */\n  }\n}\n\n\n/*\n** Executes a return hook for Lua and C functions and sets/corrects\n** 'oldpc'. (Note that this correction is needed by the line hook, so it\n** is done even when return hooks are off.)\n*/\nstatic void rethook (lua_State *L, CallInfo *ci, int nres) {\n  if (L->hookmask & LUA_MASKRET) {  /* is return hook on? */\n    StkId firstres = L->top.p - nres;  /* index of first result */\n    int delta = 0;  /* correction for vararg functions */\n    int ftransfer;\n    if (isLua(ci)) {\n      Proto *p = ci_func(ci)->p;\n      if (p->is_vararg)\n        delta = ci->u.l.nextraargs + p->numparams + 1;\n    }\n    ci->func.p += delta;  /* if vararg, back to virtual 'func' */\n    ftransfer = cast(unsigned short, firstres - ci->func.p);\n    luaD_hook(L, LUA_HOOKRET, -1, ftransfer, nres);  /* call it */\n    ci->func.p -= delta;\n  }\n  if (isLua(ci = ci->previous))\n    L->oldpc = pcRel(ci->u.l.savedpc, ci_func(ci)->p);  /* set 'oldpc' */\n}\n\n\n/*\n** Check whether 'func' has a '__call' metafield. If so, put it in the\n** stack, below original 'func', so that 'luaD_precall' can call it. Raise\n** an error if there is no '__call' metafield.\n*/\nStkId luaD_tryfuncTM (lua_State *L, StkId func) {\n  const TValue *tm;\n  StkId p;\n  checkstackGCp(L, 1, func);  /* space for metamethod */\n  tm = luaT_gettmbyobj(L, s2v(func), TM_CALL);  /* (after previous GC) */\n  if (l_unlikely(ttisnil(tm)))\n    luaG_callerror(L, s2v(func));  /* nothing to call */\n  for (p = L->top.p; p > func; p--)  /* open space for metamethod */\n    setobjs2s(L, p, p-1);\n  L->top.p++;  /* stack space pre-allocated by the caller */\n  setobj2s(L, func, tm);  /* metamethod is the new function to be called */\n  return func;\n}\n\n\n/*\n** Given 'nres' results at 'firstResult', move 'wanted' of them to 'res'.\n** Handle most typical cases (zero results for commands, one result for\n** expressions, multiple results for tail calls/single parameters)\n** separated.\n*/\nl_sinline void moveresults (lua_State *L, StkId res, int nres, int wanted) {\n  StkId firstresult;\n  int i;\n  switch (wanted) {  /* handle typical cases separately */\n    case 0:  /* no values needed */\n      L->top.p = res;\n      return;\n    case 1:  /* one value needed */\n      if (nres == 0)   /* no results? */\n        setnilvalue(s2v(res));  /* adjust with nil */\n      else  /* at least one result */\n        setobjs2s(L, res, L->top.p - nres);  /* move it to proper place */\n      L->top.p = res + 1;\n      return;\n    case LUA_MULTRET:\n      wanted = nres;  /* we want all results */\n      break;\n    default:  /* two/more results and/or to-be-closed variables */\n      if (hastocloseCfunc(wanted)) {  /* to-be-closed variables? */\n        L->ci->callstatus |= CIST_CLSRET;  /* in case of yields */\n        L->ci->u2.nres = nres;\n        res = luaF_close(L, res, CLOSEKTOP, 1);\n        L->ci->callstatus &= ~CIST_CLSRET;\n        if (L->hookmask) {  /* if needed, call hook after '__close's */\n          ptrdiff_t savedres = savestack(L, res);\n          rethook(L, L->ci, nres);\n          res = restorestack(L, savedres);  /* hook can move stack */\n        }\n        wanted = decodeNresults(wanted);\n        if (wanted == LUA_MULTRET)\n          wanted = nres;  /* we want all results */\n      }\n      break;\n  }\n  /* generic case */\n  firstresult = L->top.p - nres;  /* index of first result */\n  if (nres > wanted)  /* extra results? */\n    nres = wanted;  /* don't need them */\n  for (i = 0; i < nres; i++)  /* move all results to correct place */\n    setobjs2s(L, res + i, firstresult + i);\n  for (; i < wanted; i++)  /* complete wanted number of results */\n    setnilvalue(s2v(res + i));\n  L->top.p = res + wanted;  /* top points after the last result */\n}\n\n\n/*\n** Finishes a function call: calls hook if necessary, moves current\n** number of results to proper place, and returns to previous call\n** info. If function has to close variables, hook must be called after\n** that.\n*/\nvoid luaD_poscall (lua_State *L, CallInfo *ci, int nres) {\n  int wanted = ci->nresults;\n  if (l_unlikely(L->hookmask && !hastocloseCfunc(wanted)))\n    rethook(L, ci, nres);\n  /* move results to proper place */\n  moveresults(L, ci->func.p, nres, wanted);\n  /* function cannot be in any of these cases when returning */\n  lua_assert(!(ci->callstatus &\n        (CIST_HOOKED | CIST_YPCALL | CIST_FIN | CIST_TRAN | CIST_CLSRET)));\n  L->ci = ci->previous;  /* back to caller (after closing variables) */\n}\n\n\n\n#define next_ci(L)  (L->ci->next ? L->ci->next : luaE_extendCI(L))\n\n\nl_sinline CallInfo *prepCallInfo (lua_State *L, StkId func, int nret,\n                                                int mask, StkId top) {\n  CallInfo *ci = L->ci = next_ci(L);  /* new frame */\n  ci->func.p = func;\n  ci->nresults = nret;\n  ci->callstatus = mask;\n  ci->top.p = top;\n  return ci;\n}\n\n\n/*\n** precall for C functions\n*/\nl_sinline int precallC (lua_State *L, StkId func, int nresults,\n                                            lua_CFunction f) {\n  int n;  /* number of returns */\n  CallInfo *ci;\n  checkstackGCp(L, LUA_MINSTACK, func);  /* ensure minimum stack size */\n  L->ci = ci = prepCallInfo(L, func, nresults, CIST_C,\n                               L->top.p + LUA_MINSTACK);\n  lua_assert(ci->top.p <= L->stack_last.p);\n  if (l_unlikely(L->hookmask & LUA_MASKCALL)) {\n    int narg = cast_int(L->top.p - func) - 1;\n    luaD_hook(L, LUA_HOOKCALL, -1, 1, narg);\n  }\n  lua_unlock(L);\n  n = (*f)(L);  /* do the actual call */\n  lua_lock(L);\n  api_checknelems(L, n);\n  luaD_poscall(L, ci, n);\n  return n;\n}\n\n\n/*\n** Prepare a function for a tail call, building its call info on top\n** of the current call info. 'narg1' is the number of arguments plus 1\n** (so that it includes the function itself). Return the number of\n** results, if it was a C function, or -1 for a Lua function.\n*/\nint luaD_pretailcall (lua_State *L, CallInfo *ci, StkId func,\n                                    int narg1, int delta) {\n retry:\n  switch (ttypetag(s2v(func))) {\n    case LUA_VCCL:  /* C closure */\n      return precallC(L, func, LUA_MULTRET, clCvalue(s2v(func))->f);\n    case LUA_VLCF:  /* light C function */\n      return precallC(L, func, LUA_MULTRET, fvalue(s2v(func)));\n    case LUA_VLCL: {  /* Lua function */\n      Proto *p = clLvalue(s2v(func))->p;\n      int fsize = p->maxstacksize;  /* frame size */\n      int nfixparams = p->numparams;\n      int i;\n      checkstackGCp(L, fsize - delta, func);\n      ci->func.p -= delta;  /* restore 'func' (if vararg) */\n      for (i = 0; i < narg1; i++)  /* move down function and arguments */\n        setobjs2s(L, ci->func.p + i, func + i);\n      func = ci->func.p;  /* moved-down function */\n      for (; narg1 <= nfixparams; narg1++)\n        setnilvalue(s2v(func + narg1));  /* complete missing arguments */\n      ci->top.p = func + 1 + fsize;  /* top for new function */\n      lua_assert(ci->top.p <= L->stack_last.p);\n      ci->u.l.savedpc = p->code;  /* starting point */\n      ci->callstatus |= CIST_TAIL;\n      L->top.p = func + narg1;  /* set top */\n      return -1;\n    }\n    default: {  /* not a function */\n      func = luaD_tryfuncTM(L, func);  /* try to get '__call' metamethod */\n      /* return luaD_pretailcall(L, ci, func, narg1 + 1, delta); */\n      narg1++;\n      goto retry;  /* try again */\n    }\n  }\n}\n\n\n/*\n** Prepares the call to a function (C or Lua). For C functions, also do\n** the call. The function to be called is at '*func'.  The arguments\n** are on the stack, right after the function.  Returns the CallInfo\n** to be executed, if it was a Lua function. Otherwise (a C function)\n** returns NULL, with all the results on the stack, starting at the\n** original function position.\n*/\nCallInfo *luaD_precall (lua_State *L, StkId func, int nresults) {\n retry:\n  switch (ttypetag(s2v(func))) {\n    case LUA_VCCL:  /* C closure */\n      precallC(L, func, nresults, clCvalue(s2v(func))->f);\n      return NULL;\n    case LUA_VLCF:  /* light C function */\n      precallC(L, func, nresults, fvalue(s2v(func)));\n      return NULL;\n    case LUA_VLCL: {  /* Lua function */\n      CallInfo *ci;\n      Proto *p = clLvalue(s2v(func))->p;\n      int narg = cast_int(L->top.p - func) - 1;  /* number of real arguments */\n      int nfixparams = p->numparams;\n      int fsize = p->maxstacksize;  /* frame size */\n      checkstackGCp(L, fsize, func);\n      L->ci = ci = prepCallInfo(L, func, nresults, 0, func + 1 + fsize);\n      ci->u.l.savedpc = p->code;  /* starting point */\n      for (; narg < nfixparams; narg++)\n        setnilvalue(s2v(L->top.p++));  /* complete missing arguments */\n      lua_assert(ci->top.p <= L->stack_last.p);\n      return ci;\n    }\n    default: {  /* not a function */\n      func = luaD_tryfuncTM(L, func);  /* try to get '__call' metamethod */\n      /* return luaD_precall(L, func, nresults); */\n      goto retry;  /* try again with metamethod */\n    }\n  }\n}\n\n\n/*\n** Call a function (C or Lua) through C. 'inc' can be 1 (increment\n** number of recursive invocations in the C stack) or nyci (the same\n** plus increment number of non-yieldable calls).\n** This function can be called with some use of EXTRA_STACK, so it should\n** check the stack before doing anything else. 'luaD_precall' already\n** does that.\n*/\nl_sinline void ccall (lua_State *L, StkId func, int nResults, l_uint32 inc) {\n  CallInfo *ci;\n  L->nCcalls += inc;\n  if (l_unlikely(getCcalls(L) >= LUAI_MAXCCALLS)) {\n    checkstackp(L, 0, func);  /* free any use of EXTRA_STACK */\n    luaE_checkcstack(L);\n  }\n  if ((ci = luaD_precall(L, func, nResults)) != NULL) {  /* Lua function? */\n    ci->callstatus = CIST_FRESH;  /* mark that it is a \"fresh\" execute */\n    luaV_execute(L, ci);  /* call it */\n  }\n  L->nCcalls -= inc;\n}\n\n\n/*\n** External interface for 'ccall'\n*/\nvoid luaD_call (lua_State *L, StkId func, int nResults) {\n  ccall(L, func, nResults, 1);\n}\n\n\n/*\n** Similar to 'luaD_call', but does not allow yields during the call.\n*/\nvoid luaD_callnoyield (lua_State *L, StkId func, int nResults) {\n  ccall(L, func, nResults, nyci);\n}\n\n\n/*\n** Finish the job of 'lua_pcallk' after it was interrupted by an yield.\n** (The caller, 'finishCcall', does the final call to 'adjustresults'.)\n** The main job is to complete the 'luaD_pcall' called by 'lua_pcallk'.\n** If a '__close' method yields here, eventually control will be back\n** to 'finishCcall' (when that '__close' method finally returns) and\n** 'finishpcallk' will run again and close any still pending '__close'\n** methods. Similarly, if a '__close' method errs, 'precover' calls\n** 'unroll' which calls ''finishCcall' and we are back here again, to\n** close any pending '__close' methods.\n** Note that, up to the call to 'luaF_close', the corresponding\n** 'CallInfo' is not modified, so that this repeated run works like the\n** first one (except that it has at least one less '__close' to do). In\n** particular, field CIST_RECST preserves the error status across these\n** multiple runs, changing only if there is a new error.\n*/\nstatic int finishpcallk (lua_State *L,  CallInfo *ci) {\n  int status = getcistrecst(ci);  /* get original status */\n  if (l_likely(status == LUA_OK))  /* no error? */\n    status = LUA_YIELD;  /* was interrupted by an yield */\n  else {  /* error */\n    StkId func = restorestack(L, ci->u2.funcidx);\n    L->allowhook = getoah(ci->callstatus);  /* restore 'allowhook' */\n    func = luaF_close(L, func, status, 1);  /* can yield or raise an error */\n    luaD_seterrorobj(L, status, func);\n    luaD_shrinkstack(L);   /* restore stack size in case of overflow */\n    setcistrecst(ci, LUA_OK);  /* clear original status */\n  }\n  ci->callstatus &= ~CIST_YPCALL;\n  L->errfunc = ci->u.c.old_errfunc;\n  /* if it is here, there were errors or yields; unlike 'lua_pcallk',\n     do not change status */\n  return status;\n}\n\n\n/*\n** Completes the execution of a C function interrupted by an yield.\n** The interruption must have happened while the function was either\n** closing its tbc variables in 'moveresults' or executing\n** 'lua_callk'/'lua_pcallk'. In the first case, it just redoes\n** 'luaD_poscall'. In the second case, the call to 'finishpcallk'\n** finishes the interrupted execution of 'lua_pcallk'.  After that, it\n** calls the continuation of the interrupted function and finally it\n** completes the job of the 'luaD_call' that called the function.  In\n** the call to 'adjustresults', we do not know the number of results\n** of the function called by 'lua_callk'/'lua_pcallk', so we are\n** conservative and use LUA_MULTRET (always adjust).\n*/\nstatic void finishCcall (lua_State *L, CallInfo *ci) {\n  int n;  /* actual number of results from C function */\n  if (ci->callstatus & CIST_CLSRET) {  /* was returning? */\n    lua_assert(hastocloseCfunc(ci->nresults));\n    n = ci->u2.nres;  /* just redo 'luaD_poscall' */\n    /* don't need to reset CIST_CLSRET, as it will be set again anyway */\n  }\n  else {\n    int status = LUA_YIELD;  /* default if there were no errors */\n    /* must have a continuation and must be able to call it */\n    lua_assert(ci->u.c.k != NULL && yieldable(L));\n    if (ci->callstatus & CIST_YPCALL)   /* was inside a 'lua_pcallk'? */\n      status = finishpcallk(L, ci);  /* finish it */\n    adjustresults(L, LUA_MULTRET);  /* finish 'lua_callk' */\n    lua_unlock(L);\n    n = (*ci->u.c.k)(L, status, ci->u.c.ctx);  /* call continuation */\n    lua_lock(L);\n    api_checknelems(L, n);\n  }\n  luaD_poscall(L, ci, n);  /* finish 'luaD_call' */\n}\n\n\n/*\n** Executes \"full continuation\" (everything in the stack) of a\n** previously interrupted coroutine until the stack is empty (or another\n** interruption long-jumps out of the loop).\n*/\nstatic void unroll (lua_State *L, void *ud) {\n  CallInfo *ci;\n  UNUSED(ud);\n  while ((ci = L->ci) != &L->base_ci) {  /* something in the stack */\n    if (!isLua(ci))  /* C function? */\n      finishCcall(L, ci);  /* complete its execution */\n    else {  /* Lua function */\n      luaV_finishOp(L);  /* finish interrupted instruction */\n      luaV_execute(L, ci);  /* execute down to higher C 'boundary' */\n    }\n  }\n}\n\n\n/*\n** Try to find a suspended protected call (a \"recover point\") for the\n** given thread.\n*/\nstatic CallInfo *findpcall (lua_State *L) {\n  CallInfo *ci;\n  for (ci = L->ci; ci != NULL; ci = ci->previous) {  /* search for a pcall */\n    if (ci->callstatus & CIST_YPCALL)\n      return ci;\n  }\n  return NULL;  /* no pending pcall */\n}\n\n\n/*\n** Signal an error in the call to 'lua_resume', not in the execution\n** of the coroutine itself. (Such errors should not be handled by any\n** coroutine error handler and should not kill the coroutine.)\n*/\nstatic int resume_error (lua_State *L, const char *msg, int narg) {\n  L->top.p -= narg;  /* remove args from the stack */\n  setsvalue2s(L, L->top.p, luaS_new(L, msg));  /* push error message */\n  api_incr_top(L);\n  lua_unlock(L);\n  return LUA_ERRRUN;\n}\n\n\n/*\n** Do the work for 'lua_resume' in protected mode. Most of the work\n** depends on the status of the coroutine: initial state, suspended\n** inside a hook, or regularly suspended (optionally with a continuation\n** function), plus erroneous cases: non-suspended coroutine or dead\n** coroutine.\n*/\nstatic void resume (lua_State *L, void *ud) {\n  int n = *(cast(int*, ud));  /* number of arguments */\n  StkId firstArg = L->top.p - n;  /* first argument */\n  CallInfo *ci = L->ci;\n  if (L->status == LUA_OK)  /* starting a coroutine? */\n    ccall(L, firstArg - 1, LUA_MULTRET, 0);  /* just call its body */\n  else {  /* resuming from previous yield */\n    lua_assert(L->status == LUA_YIELD);\n    L->status = LUA_OK;  /* mark that it is running (again) */\n    if (isLua(ci)) {  /* yielded inside a hook? */\n      L->top.p = firstArg;  /* discard arguments */\n      luaV_execute(L, ci);  /* just continue running Lua code */\n    }\n    else {  /* 'common' yield */\n      if (ci->u.c.k != NULL) {  /* does it have a continuation function? */\n        lua_unlock(L);\n        n = (*ci->u.c.k)(L, LUA_YIELD, ci->u.c.ctx); /* call continuation */\n        lua_lock(L);\n        api_checknelems(L, n);\n      }\n      luaD_poscall(L, ci, n);  /* finish 'luaD_call' */\n    }\n    unroll(L, NULL);  /* run continuation */\n  }\n}\n\n\n/*\n** Unrolls a coroutine in protected mode while there are recoverable\n** errors, that is, errors inside a protected call. (Any error\n** interrupts 'unroll', and this loop protects it again so it can\n** continue.) Stops with a normal end (status == LUA_OK), an yield\n** (status == LUA_YIELD), or an unprotected error ('findpcall' doesn't\n** find a recover point).\n*/\nstatic int precover (lua_State *L, int status) {\n  CallInfo *ci;\n  while (errorstatus(status) && (ci = findpcall(L)) != NULL) {\n    L->ci = ci;  /* go down to recovery functions */\n    setcistrecst(ci, status);  /* status to finish 'pcall' */\n    status = luaD_rawrunprotected(L, unroll, NULL);\n  }\n  return status;\n}\n\n\nLUA_API int lua_resume (lua_State *L, lua_State *from, int nargs,\n                                      int *nresults) {\n  int status;\n  lua_lock(L);\n  if (L->status == LUA_OK) {  /* may be starting a coroutine */\n    if (L->ci != &L->base_ci)  /* not in base level? */\n      return resume_error(L, \"cannot resume non-suspended coroutine\", nargs);\n    else if (L->top.p - (L->ci->func.p + 1) == nargs)  /* no function? */\n      return resume_error(L, \"cannot resume dead coroutine\", nargs);\n  }\n  else if (L->status != LUA_YIELD)  /* ended with errors? */\n    return resume_error(L, \"cannot resume dead coroutine\", nargs);\n  L->nCcalls = (from) ? getCcalls(from) : 0;\n  if (getCcalls(L) >= LUAI_MAXCCALLS)\n    return resume_error(L, \"C stack overflow\", nargs);\n  L->nCcalls++;\n  luai_userstateresume(L, nargs);\n  api_checknelems(L, (L->status == LUA_OK) ? nargs + 1 : nargs);\n  status = luaD_rawrunprotected(L, resume, &nargs);\n   /* continue running after recoverable errors */\n  status = precover(L, status);\n  if (l_likely(!errorstatus(status)))\n    lua_assert(status == L->status);  /* normal end or yield */\n  else {  /* unrecoverable error */\n    L->status = cast_byte(status);  /* mark thread as 'dead' */\n    luaD_seterrorobj(L, status, L->top.p);  /* push error message */\n    L->ci->top.p = L->top.p;\n  }\n  *nresults = (status == LUA_YIELD) ? L->ci->u2.nyield\n                                    : cast_int(L->top.p - (L->ci->func.p + 1));\n  lua_unlock(L);\n  return status;\n}\n\n\nLUA_API int lua_isyieldable (lua_State *L) {\n  return yieldable(L);\n}\n\n\nLUA_API int lua_yieldk (lua_State *L, int nresults, lua_KContext ctx,\n                        lua_KFunction k) {\n  CallInfo *ci;\n  luai_userstateyield(L, nresults);\n  lua_lock(L);\n  ci = L->ci;\n  api_checknelems(L, nresults);\n  if (l_unlikely(!yieldable(L))) {\n    if (L != G(L)->mainthread)\n      luaG_runerror(L, \"attempt to yield across a C-call boundary\");\n    else\n      luaG_runerror(L, \"attempt to yield from outside a coroutine\");\n  }\n  L->status = LUA_YIELD;\n  ci->u2.nyield = nresults;  /* save number of results */\n  if (isLua(ci)) {  /* inside a hook? */\n    lua_assert(!isLuacode(ci));\n    api_check(L, nresults == 0, \"hooks cannot yield values\");\n    api_check(L, k == NULL, \"hooks cannot continue after yielding\");\n  }\n  else {\n    if ((ci->u.c.k = k) != NULL)  /* is there a continuation? */\n      ci->u.c.ctx = ctx;  /* save context */\n    luaD_throw(L, LUA_YIELD);\n  }\n  lua_assert(ci->callstatus & CIST_HOOKED);  /* must be inside a hook */\n  lua_unlock(L);\n  return 0;  /* return to 'luaD_hook' */\n}\n\n\n/*\n** Auxiliary structure to call 'luaF_close' in protected mode.\n*/\nstruct CloseP {\n  StkId level;\n  int status;\n};\n\n\n/*\n** Auxiliary function to call 'luaF_close' in protected mode.\n*/\nstatic void closepaux (lua_State *L, void *ud) {\n  struct CloseP *pcl = cast(struct CloseP *, ud);\n  luaF_close(L, pcl->level, pcl->status, 0);\n}\n\n\n/*\n** Calls 'luaF_close' in protected mode. Return the original status\n** or, in case of errors, the new status.\n*/\nint luaD_closeprotected (lua_State *L, ptrdiff_t level, int status) {\n  CallInfo *old_ci = L->ci;\n  lu_byte old_allowhooks = L->allowhook;\n  for (;;) {  /* keep closing upvalues until no more errors */\n    struct CloseP pcl;\n    pcl.level = restorestack(L, level); pcl.status = status;\n    status = luaD_rawrunprotected(L, &closepaux, &pcl);\n    if (l_likely(status == LUA_OK))  /* no more errors? */\n      return pcl.status;\n    else {  /* an error occurred; restore saved state and repeat */\n      L->ci = old_ci;\n      L->allowhook = old_allowhooks;\n    }\n  }\n}\n\n\n/*\n** Call the C function 'func' in protected mode, restoring basic\n** thread information ('allowhook', etc.) and in particular\n** its stack level in case of errors.\n*/\nint luaD_pcall (lua_State *L, Pfunc func, void *u,\n                ptrdiff_t old_top, ptrdiff_t ef) {\n  int status;\n  CallInfo *old_ci = L->ci;\n  lu_byte old_allowhooks = L->allowhook;\n  ptrdiff_t old_errfunc = L->errfunc;\n  L->errfunc = ef;\n  status = luaD_rawrunprotected(L, func, u);\n  if (l_unlikely(status != LUA_OK)) {  /* an error occurred? */\n    L->ci = old_ci;\n    L->allowhook = old_allowhooks;\n    status = luaD_closeprotected(L, old_top, status);\n    luaD_seterrorobj(L, status, restorestack(L, old_top));\n    luaD_shrinkstack(L);   /* restore stack size in case of overflow */\n  }\n  L->errfunc = old_errfunc;\n  return status;\n}\n\n\n\n/*\n** Execute a protected parser.\n*/\nstruct SParser {  /* data to 'f_parser' */\n  ZIO *z;\n  Mbuffer buff;  /* dynamic structure used by the scanner */\n  Dyndata dyd;  /* dynamic structures used by the parser */\n  const char *mode;\n  const char *name;\n};\n\n\nstatic void checkmode (lua_State *L, const char *mode, const char *x) {\n  if (mode && strchr(mode, x[0]) == NULL) {\n    luaO_pushfstring(L,\n       \"attempt to load a %s chunk (mode is '%s')\", x, mode);\n    luaD_throw(L, LUA_ERRSYNTAX);\n  }\n}\n\n\nstatic void f_parser (lua_State *L, void *ud) {\n  LClosure *cl;\n  struct SParser *p = cast(struct SParser *, ud);\n  int c = zgetc(p->z);  /* read first character */\n  if (c == LUA_SIGNATURE[0]) {\n    checkmode(L, p->mode, \"binary\");\n    cl = luaU_undump(L, p->z, p->name);\n  }\n  else {\n    checkmode(L, p->mode, \"text\");\n    cl = luaY_parser(L, p->z, &p->buff, &p->dyd, p->name, c);\n  }\n  lua_assert(cl->nupvalues == cl->p->sizeupvalues);\n  luaF_initupvals(L, cl);\n}\n\n\nint luaD_protectedparser (lua_State *L, ZIO *z, const char *name,\n                                        const char *mode) {\n  struct SParser p;\n  int status;\n  incnny(L);  /* cannot yield during parsing */\n  p.z = z; p.name = name; p.mode = mode;\n  p.dyd.actvar.arr = NULL; p.dyd.actvar.size = 0;\n  p.dyd.gt.arr = NULL; p.dyd.gt.size = 0;\n  p.dyd.label.arr = NULL; p.dyd.label.size = 0;\n  luaZ_initbuffer(L, &p.buff);\n  status = luaD_pcall(L, f_parser, &p, savestack(L, L->top.p), L->errfunc);\n  luaZ_freebuffer(L, &p.buff);\n  luaM_freearray(L, p.dyd.actvar.arr, p.dyd.actvar.size);\n  luaM_freearray(L, p.dyd.gt.arr, p.dyd.gt.size);\n  luaM_freearray(L, p.dyd.label.arr, p.dyd.label.size);\n  decnny(L);\n  return status;\n}\n\n\n/*\n** $Id: lvm.c $\n** Lua virtual machine\n** See Copyright Notice in lua.h\n*/\n\n#define lvm_c\n#define LUA_CORE\n\n/*#include \"lprefix.h\"*/\n\n#include <float.h>\n#include <limits.h>\n#include <math.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n/*#include \"lua.h\"*/\n\n/*#include \"ldebug.h\"*/\n/*#include \"ldo.h\"*/\n/*#include \"lfunc.h\"*/\n/*#include \"lgc.h\"*/\n/*#include \"lobject.h\"*/\n/*#include \"lopcodes.h\"*/\n/*#include \"lstate.h\"*/\n/*#include \"lstring.h\"*/\n/*#include \"ltable.h\"*/\n/*#include \"ltm.h\"*/\n/*#include \"lvm.h\"*/\n\n\n/*\n** By default, use jump tables in the main interpreter loop on gcc\n** and compatible compilers.\n*/\n#if !defined(LUA_USE_JUMPTABLE)\n#if defined(__GNUC__)\n#define LUA_USE_JUMPTABLE\t1\n#else\n#define LUA_USE_JUMPTABLE\t0\n#endif\n#endif\n\n\n\n/* limit for table tag-method chains (to avoid infinite loops) */\n#define MAXTAGLOOP\t2000\n\n\n/*\n** 'l_intfitsf' checks whether a given integer is in the range that\n** can be converted to a float without rounding. Used in comparisons.\n*/\n\n/* number of bits in the mantissa of a float */\n#define NBM\t\t(l_floatatt(MANT_DIG))\n\n/*\n** Check whether some integers may not fit in a float, testing whether\n** (maxinteger >> NBM) > 0. (That implies (1 << NBM) <= maxinteger.)\n** (The shifts are done in parts, to avoid shifting by more than the size\n** of an integer. In a worst case, NBM == 113 for long double and\n** sizeof(long) == 32.)\n*/\n#if ((((LUA_MAXINTEGER >> (NBM / 4)) >> (NBM / 4)) >> (NBM / 4)) \\\n\t>> (NBM - (3 * (NBM / 4))))  >  0\n\n/* limit for integers that fit in a float */\n#define MAXINTFITSF\t((lua_Unsigned)1 << NBM)\n\n/* check whether 'i' is in the interval [-MAXINTFITSF, MAXINTFITSF] */\n#define l_intfitsf(i)\t((MAXINTFITSF + l_castS2U(i)) <= (2 * MAXINTFITSF))\n\n#else  /* all integers fit in a float precisely */\n\n#define l_intfitsf(i)\t1\n\n#endif\n\n\n/*\n** Try to convert a value from string to a number value.\n** If the value is not a string or is a string not representing\n** a valid numeral (or if coercions from strings to numbers\n** are disabled via macro 'cvt2num'), do not modify 'result'\n** and return 0.\n*/\nstatic int l_strton (const TValue *obj, TValue *result) {\n  lua_assert(obj != result);\n  if (!cvt2num(obj))  /* is object not a string? */\n    return 0;\n  else\n    return (luaO_str2num(svalue(obj), result) == vslen(obj) + 1);\n}\n\n\n/*\n** Try to convert a value to a float. The float case is already handled\n** by the macro 'tonumber'.\n*/\nint luaV_tonumber_ (const TValue *obj, lua_Number *n) {\n  TValue v;\n  if (ttisinteger(obj)) {\n    *n = cast_num(ivalue(obj));\n    return 1;\n  }\n  else if (l_strton(obj, &v)) {  /* string coercible to number? */\n    *n = nvalue(&v);  /* convert result of 'luaO_str2num' to a float */\n    return 1;\n  }\n  else\n    return 0;  /* conversion failed */\n}\n\n\n/*\n** try to convert a float to an integer, rounding according to 'mode'.\n*/\nint luaV_flttointeger (lua_Number n, lua_Integer *p, F2Imod mode) {\n  lua_Number f = l_floor(n);\n  if (n != f) {  /* not an integral value? */\n    if (mode == F2Ieq) return 0;  /* fails if mode demands integral value */\n    else if (mode == F2Iceil)  /* needs ceil? */\n      f += 1;  /* convert floor to ceil (remember: n != f) */\n  }\n  return lua_numbertointeger(f, p);\n}\n\n\n/*\n** try to convert a value to an integer, rounding according to 'mode',\n** without string coercion.\n** (\"Fast track\" handled by macro 'tointegerns'.)\n*/\nint luaV_tointegerns (const TValue *obj, lua_Integer *p, F2Imod mode) {\n  if (ttisfloat(obj))\n    return luaV_flttointeger(fltvalue(obj), p, mode);\n  else if (ttisinteger(obj)) {\n    *p = ivalue(obj);\n    return 1;\n  }\n  else\n    return 0;\n}\n\n\n/*\n** try to convert a value to an integer.\n*/\nint luaV_tointeger (const TValue *obj, lua_Integer *p, F2Imod mode) {\n  TValue v;\n  if (l_strton(obj, &v))  /* does 'obj' point to a numerical string? */\n    obj = &v;  /* change it to point to its corresponding number */\n  return luaV_tointegerns(obj, p, mode);\n}\n\n\n/*\n** Try to convert a 'for' limit to an integer, preserving the semantics\n** of the loop. Return true if the loop must not run; otherwise, '*p'\n** gets the integer limit.\n** (The following explanation assumes a positive step; it is valid for\n** negative steps mutatis mutandis.)\n** If the limit is an integer or can be converted to an integer,\n** rounding down, that is the limit.\n** Otherwise, check whether the limit can be converted to a float. If\n** the float is too large, clip it to LUA_MAXINTEGER.  If the float\n** is too negative, the loop should not run, because any initial\n** integer value is greater than such limit; so, the function returns\n** true to signal that. (For this latter case, no integer limit would be\n** correct; even a limit of LUA_MININTEGER would run the loop once for\n** an initial value equal to LUA_MININTEGER.)\n*/\nstatic int forlimit (lua_State *L, lua_Integer init, const TValue *lim,\n                                   lua_Integer *p, lua_Integer step) {\n  if (!luaV_tointeger(lim, p, (step < 0 ? F2Iceil : F2Ifloor))) {\n    /* not coercible to in integer */\n    lua_Number flim;  /* try to convert to float */\n    if (!tonumber(lim, &flim)) /* cannot convert to float? */\n      luaG_forerror(L, lim, \"limit\");\n    /* else 'flim' is a float out of integer bounds */\n    if (luai_numlt(0, flim)) {  /* if it is positive, it is too large */\n      if (step < 0) return 1;  /* initial value must be less than it */\n      *p = LUA_MAXINTEGER;  /* truncate */\n    }\n    else {  /* it is less than min integer */\n      if (step > 0) return 1;  /* initial value must be greater than it */\n      *p = LUA_MININTEGER;  /* truncate */\n    }\n  }\n  return (step > 0 ? init > *p : init < *p);  /* not to run? */\n}\n\n\n/*\n** Prepare a numerical for loop (opcode OP_FORPREP).\n** Return true to skip the loop. Otherwise,\n** after preparation, stack will be as follows:\n**   ra : internal index (safe copy of the control variable)\n**   ra + 1 : loop counter (integer loops) or limit (float loops)\n**   ra + 2 : step\n**   ra + 3 : control variable\n*/\nstatic int forprep (lua_State *L, StkId ra) {\n  TValue *pinit = s2v(ra);\n  TValue *plimit = s2v(ra + 1);\n  TValue *pstep = s2v(ra + 2);\n  if (ttisinteger(pinit) && ttisinteger(pstep)) { /* integer loop? */\n    lua_Integer init = ivalue(pinit);\n    lua_Integer step = ivalue(pstep);\n    lua_Integer limit;\n    if (step == 0)\n      luaG_runerror(L, \"'for' step is zero\");\n    setivalue(s2v(ra + 3), init);  /* control variable */\n    if (forlimit(L, init, plimit, &limit, step))\n      return 1;  /* skip the loop */\n    else {  /* prepare loop counter */\n      lua_Unsigned count;\n      if (step > 0) {  /* ascending loop? */\n        count = l_castS2U(limit) - l_castS2U(init);\n        if (step != 1)  /* avoid division in the too common case */\n          count /= l_castS2U(step);\n      }\n      else {  /* step < 0; descending loop */\n        count = l_castS2U(init) - l_castS2U(limit);\n        /* 'step+1' avoids negating 'mininteger' */\n        count /= l_castS2U(-(step + 1)) + 1u;\n      }\n      /* store the counter in place of the limit (which won't be\n         needed anymore) */\n      setivalue(plimit, l_castU2S(count));\n    }\n  }\n  else {  /* try making all values floats */\n    lua_Number init; lua_Number limit; lua_Number step;\n    if (l_unlikely(!tonumber(plimit, &limit)))\n      luaG_forerror(L, plimit, \"limit\");\n    if (l_unlikely(!tonumber(pstep, &step)))\n      luaG_forerror(L, pstep, \"step\");\n    if (l_unlikely(!tonumber(pinit, &init)))\n      luaG_forerror(L, pinit, \"initial value\");\n    if (step == 0)\n      luaG_runerror(L, \"'for' step is zero\");\n    if (luai_numlt(0, step) ? luai_numlt(limit, init)\n                            : luai_numlt(init, limit))\n      return 1;  /* skip the loop */\n    else {\n      /* make sure internal values are all floats */\n      setfltvalue(plimit, limit);\n      setfltvalue(pstep, step);\n      setfltvalue(s2v(ra), init);  /* internal index */\n      setfltvalue(s2v(ra + 3), init);  /* control variable */\n    }\n  }\n  return 0;\n}\n\n\n/*\n** Execute a step of a float numerical for loop, returning\n** true iff the loop must continue. (The integer case is\n** written online with opcode OP_FORLOOP, for performance.)\n*/\nstatic int floatforloop (StkId ra) {\n  lua_Number step = fltvalue(s2v(ra + 2));\n  lua_Number limit = fltvalue(s2v(ra + 1));\n  lua_Number idx = fltvalue(s2v(ra));  /* internal index */\n  idx = luai_numadd(L, idx, step);  /* increment index */\n  if (luai_numlt(0, step) ? luai_numle(idx, limit)\n                          : luai_numle(limit, idx)) {\n    chgfltvalue(s2v(ra), idx);  /* update internal index */\n    setfltvalue(s2v(ra + 3), idx);  /* and control variable */\n    return 1;  /* jump back */\n  }\n  else\n    return 0;  /* finish the loop */\n}\n\n\n/*\n** Finish the table access 'val = t[key]'.\n** if 'slot' is NULL, 't' is not a table; otherwise, 'slot' points to\n** t[k] entry (which must be empty).\n*/\nvoid luaV_finishget (lua_State *L, const TValue *t, TValue *key, StkId val,\n                      const TValue *slot) {\n  int loop;  /* counter to avoid infinite loops */\n  const TValue *tm;  /* metamethod */\n  for (loop = 0; loop < MAXTAGLOOP; loop++) {\n    if (slot == NULL) {  /* 't' is not a table? */\n      lua_assert(!ttistable(t));\n      tm = luaT_gettmbyobj(L, t, TM_INDEX);\n      if (l_unlikely(notm(tm)))\n        luaG_typeerror(L, t, \"index\");  /* no metamethod */\n      /* else will try the metamethod */\n    }\n    else {  /* 't' is a table */\n      lua_assert(isempty(slot));\n      tm = fasttm(L, hvalue(t)->metatable, TM_INDEX);  /* table's metamethod */\n      if (tm == NULL) {  /* no metamethod? */\n        setnilvalue(s2v(val));  /* result is nil */\n        return;\n      }\n      /* else will try the metamethod */\n    }\n    if (ttisfunction(tm)) {  /* is metamethod a function? */\n      luaT_callTMres(L, tm, t, key, val);  /* call it */\n      return;\n    }\n    t = tm;  /* else try to access 'tm[key]' */\n    if (luaV_fastget(L, t, key, slot, luaH_get)) {  /* fast track? */\n      setobj2s(L, val, slot);  /* done */\n      return;\n    }\n    /* else repeat (tail call 'luaV_finishget') */\n  }\n  luaG_runerror(L, \"'__index' chain too long; possible loop\");\n}\n\n\n/*\n** Finish a table assignment 't[key] = val'.\n** If 'slot' is NULL, 't' is not a table.  Otherwise, 'slot' points\n** to the entry 't[key]', or to a value with an absent key if there\n** is no such entry.  (The value at 'slot' must be empty, otherwise\n** 'luaV_fastget' would have done the job.)\n*/\nvoid luaV_finishset (lua_State *L, const TValue *t, TValue *key,\n                     TValue *val, const TValue *slot) {\n  int loop;  /* counter to avoid infinite loops */\n  for (loop = 0; loop < MAXTAGLOOP; loop++) {\n    const TValue *tm;  /* '__newindex' metamethod */\n    if (slot != NULL) {  /* is 't' a table? */\n      Table *h = hvalue(t);  /* save 't' table */\n      lua_assert(isempty(slot));  /* slot must be empty */\n      tm = fasttm(L, h->metatable, TM_NEWINDEX);  /* get metamethod */\n      if (tm == NULL) {  /* no metamethod? */\n        luaH_finishset(L, h, key, slot, val);  /* set new value */\n        invalidateTMcache(h);\n        luaC_barrierback(L, obj2gco(h), val);\n        return;\n      }\n      /* else will try the metamethod */\n    }\n    else {  /* not a table; check metamethod */\n      tm = luaT_gettmbyobj(L, t, TM_NEWINDEX);\n      if (l_unlikely(notm(tm)))\n        luaG_typeerror(L, t, \"index\");\n    }\n    /* try the metamethod */\n    if (ttisfunction(tm)) {\n      luaT_callTM(L, tm, t, key, val);\n      return;\n    }\n    t = tm;  /* else repeat assignment over 'tm' */\n    if (luaV_fastget(L, t, key, slot, luaH_get)) {\n      luaV_finishfastset(L, t, slot, val);\n      return;  /* done */\n    }\n    /* else 'return luaV_finishset(L, t, key, val, slot)' (loop) */\n  }\n  luaG_runerror(L, \"'__newindex' chain too long; possible loop\");\n}\n\n\n/*\n** Compare two strings 'ls' x 'rs', returning an integer less-equal-\n** -greater than zero if 'ls' is less-equal-greater than 'rs'.\n** The code is a little tricky because it allows '\\0' in the strings\n** and it uses 'strcoll' (to respect locales) for each segments\n** of the strings.\n*/\nstatic int l_strcmp (const TString *ls, const TString *rs) {\n  const char *l = getstr(ls);\n  size_t ll = tsslen(ls);\n  const char *r = getstr(rs);\n  size_t lr = tsslen(rs);\n  for (;;) {  /* for each segment */\n    int temp = strcoll(l, r);\n    if (temp != 0)  /* not equal? */\n      return temp;  /* done */\n    else {  /* strings are equal up to a '\\0' */\n      size_t len = strlen(l);  /* index of first '\\0' in both strings */\n      if (len == lr)  /* 'rs' is finished? */\n        return (len == ll) ? 0 : 1;  /* check 'ls' */\n      else if (len == ll)  /* 'ls' is finished? */\n        return -1;  /* 'ls' is less than 'rs' ('rs' is not finished) */\n      /* both strings longer than 'len'; go on comparing after the '\\0' */\n      len++;\n      l += len; ll -= len; r += len; lr -= len;\n    }\n  }\n}\n\n\n/*\n** Check whether integer 'i' is less than float 'f'. If 'i' has an\n** exact representation as a float ('l_intfitsf'), compare numbers as\n** floats. Otherwise, use the equivalence 'i < f <=> i < ceil(f)'.\n** If 'ceil(f)' is out of integer range, either 'f' is greater than\n** all integers or less than all integers.\n** (The test with 'l_intfitsf' is only for performance; the else\n** case is correct for all values, but it is slow due to the conversion\n** from float to int.)\n** When 'f' is NaN, comparisons must result in false.\n*/\nl_sinline int LTintfloat (lua_Integer i, lua_Number f) {\n  if (l_intfitsf(i))\n    return luai_numlt(cast_num(i), f);  /* compare them as floats */\n  else {  /* i < f <=> i < ceil(f) */\n    lua_Integer fi;\n    if (luaV_flttointeger(f, &fi, F2Iceil))  /* fi = ceil(f) */\n      return i < fi;   /* compare them as integers */\n    else  /* 'f' is either greater or less than all integers */\n      return f > 0;  /* greater? */\n  }\n}\n\n\n/*\n** Check whether integer 'i' is less than or equal to float 'f'.\n** See comments on previous function.\n*/\nl_sinline int LEintfloat (lua_Integer i, lua_Number f) {\n  if (l_intfitsf(i))\n    return luai_numle(cast_num(i), f);  /* compare them as floats */\n  else {  /* i <= f <=> i <= floor(f) */\n    lua_Integer fi;\n    if (luaV_flttointeger(f, &fi, F2Ifloor))  /* fi = floor(f) */\n      return i <= fi;   /* compare them as integers */\n    else  /* 'f' is either greater or less than all integers */\n      return f > 0;  /* greater? */\n  }\n}\n\n\n/*\n** Check whether float 'f' is less than integer 'i'.\n** See comments on previous function.\n*/\nl_sinline int LTfloatint (lua_Number f, lua_Integer i) {\n  if (l_intfitsf(i))\n    return luai_numlt(f, cast_num(i));  /* compare them as floats */\n  else {  /* f < i <=> floor(f) < i */\n    lua_Integer fi;\n    if (luaV_flttointeger(f, &fi, F2Ifloor))  /* fi = floor(f) */\n      return fi < i;   /* compare them as integers */\n    else  /* 'f' is either greater or less than all integers */\n      return f < 0;  /* less? */\n  }\n}\n\n\n/*\n** Check whether float 'f' is less than or equal to integer 'i'.\n** See comments on previous function.\n*/\nl_sinline int LEfloatint (lua_Number f, lua_Integer i) {\n  if (l_intfitsf(i))\n    return luai_numle(f, cast_num(i));  /* compare them as floats */\n  else {  /* f <= i <=> ceil(f) <= i */\n    lua_Integer fi;\n    if (luaV_flttointeger(f, &fi, F2Iceil))  /* fi = ceil(f) */\n      return fi <= i;   /* compare them as integers */\n    else  /* 'f' is either greater or less than all integers */\n      return f < 0;  /* less? */\n  }\n}\n\n\n/*\n** Return 'l < r', for numbers.\n*/\nl_sinline int LTnum (const TValue *l, const TValue *r) {\n  lua_assert(ttisnumber(l) && ttisnumber(r));\n  if (ttisinteger(l)) {\n    lua_Integer li = ivalue(l);\n    if (ttisinteger(r))\n      return li < ivalue(r);  /* both are integers */\n    else  /* 'l' is int and 'r' is float */\n      return LTintfloat(li, fltvalue(r));  /* l < r ? */\n  }\n  else {\n    lua_Number lf = fltvalue(l);  /* 'l' must be float */\n    if (ttisfloat(r))\n      return luai_numlt(lf, fltvalue(r));  /* both are float */\n    else  /* 'l' is float and 'r' is int */\n      return LTfloatint(lf, ivalue(r));\n  }\n}\n\n\n/*\n** Return 'l <= r', for numbers.\n*/\nl_sinline int LEnum (const TValue *l, const TValue *r) {\n  lua_assert(ttisnumber(l) && ttisnumber(r));\n  if (ttisinteger(l)) {\n    lua_Integer li = ivalue(l);\n    if (ttisinteger(r))\n      return li <= ivalue(r);  /* both are integers */\n    else  /* 'l' is int and 'r' is float */\n      return LEintfloat(li, fltvalue(r));  /* l <= r ? */\n  }\n  else {\n    lua_Number lf = fltvalue(l);  /* 'l' must be float */\n    if (ttisfloat(r))\n      return luai_numle(lf, fltvalue(r));  /* both are float */\n    else  /* 'l' is float and 'r' is int */\n      return LEfloatint(lf, ivalue(r));\n  }\n}\n\n\n/*\n** return 'l < r' for non-numbers.\n*/\nstatic int lessthanothers (lua_State *L, const TValue *l, const TValue *r) {\n  lua_assert(!ttisnumber(l) || !ttisnumber(r));\n  if (ttisstring(l) && ttisstring(r))  /* both are strings? */\n    return l_strcmp(tsvalue(l), tsvalue(r)) < 0;\n  else\n    return luaT_callorderTM(L, l, r, TM_LT);\n}\n\n\n/*\n** Main operation less than; return 'l < r'.\n*/\nint luaV_lessthan (lua_State *L, const TValue *l, const TValue *r) {\n  if (ttisnumber(l) && ttisnumber(r))  /* both operands are numbers? */\n    return LTnum(l, r);\n  else return lessthanothers(L, l, r);\n}\n\n\n/*\n** return 'l <= r' for non-numbers.\n*/\nstatic int lessequalothers (lua_State *L, const TValue *l, const TValue *r) {\n  lua_assert(!ttisnumber(l) || !ttisnumber(r));\n  if (ttisstring(l) && ttisstring(r))  /* both are strings? */\n    return l_strcmp(tsvalue(l), tsvalue(r)) <= 0;\n  else\n    return luaT_callorderTM(L, l, r, TM_LE);\n}\n\n\n/*\n** Main operation less than or equal to; return 'l <= r'.\n*/\nint luaV_lessequal (lua_State *L, const TValue *l, const TValue *r) {\n  if (ttisnumber(l) && ttisnumber(r))  /* both operands are numbers? */\n    return LEnum(l, r);\n  else return lessequalothers(L, l, r);\n}\n\n\n/*\n** Main operation for equality of Lua values; return 't1 == t2'.\n** L == NULL means raw equality (no metamethods)\n*/\nint luaV_equalobj (lua_State *L, const TValue *t1, const TValue *t2) {\n  const TValue *tm;\n  if (ttypetag(t1) != ttypetag(t2)) {  /* not the same variant? */\n    if (ttype(t1) != ttype(t2) || ttype(t1) != LUA_TNUMBER)\n      return 0;  /* only numbers can be equal with different variants */\n    else {  /* two numbers with different variants */\n      /* One of them is an integer. If the other does not have an\n         integer value, they cannot be equal; otherwise, compare their\n         integer values. */\n      lua_Integer i1, i2;\n      return (luaV_tointegerns(t1, &i1, F2Ieq) &&\n              luaV_tointegerns(t2, &i2, F2Ieq) &&\n              i1 == i2);\n    }\n  }\n  /* values have same type and same variant */\n  switch (ttypetag(t1)) {\n    case LUA_VNIL: case LUA_VFALSE: case LUA_VTRUE: return 1;\n    case LUA_VNUMINT: return (ivalue(t1) == ivalue(t2));\n    case LUA_VNUMFLT: return luai_numeq(fltvalue(t1), fltvalue(t2));\n    case LUA_VLIGHTUSERDATA: return pvalue(t1) == pvalue(t2);\n    case LUA_VLCF: return fvalue(t1) == fvalue(t2);\n    case LUA_VSHRSTR: return eqshrstr(tsvalue(t1), tsvalue(t2));\n    case LUA_VLNGSTR: return luaS_eqlngstr(tsvalue(t1), tsvalue(t2));\n    case LUA_VUSERDATA: {\n      if (uvalue(t1) == uvalue(t2)) return 1;\n      else if (L == NULL) return 0;\n      tm = fasttm(L, uvalue(t1)->metatable, TM_EQ);\n      if (tm == NULL)\n        tm = fasttm(L, uvalue(t2)->metatable, TM_EQ);\n      break;  /* will try TM */\n    }\n    case LUA_VTABLE: {\n      if (hvalue(t1) == hvalue(t2)) return 1;\n      else if (L == NULL) return 0;\n      tm = fasttm(L, hvalue(t1)->metatable, TM_EQ);\n      if (tm == NULL)\n        tm = fasttm(L, hvalue(t2)->metatable, TM_EQ);\n      break;  /* will try TM */\n    }\n    default:\n      return gcvalue(t1) == gcvalue(t2);\n  }\n  if (tm == NULL)  /* no TM? */\n    return 0;  /* objects are different */\n  else {\n    luaT_callTMres(L, tm, t1, t2, L->top.p);  /* call TM */\n    return !l_isfalse(s2v(L->top.p));\n  }\n}\n\n\n/* macro used by 'luaV_concat' to ensure that element at 'o' is a string */\n#define tostring(L,o)  \\\n\t(ttisstring(o) || (cvt2str(o) && (luaO_tostring(L, o), 1)))\n\n#define isemptystr(o)\t(ttisshrstring(o) && tsvalue(o)->shrlen == 0)\n\n/* copy strings in stack from top - n up to top - 1 to buffer */\nstatic void copy2buff (StkId top, int n, char *buff) {\n  size_t tl = 0;  /* size already copied */\n  do {\n    size_t l = vslen(s2v(top - n));  /* length of string being copied */\n    memcpy(buff + tl, svalue(s2v(top - n)), l * sizeof(char));\n    tl += l;\n  } while (--n > 0);\n}\n\n\n/*\n** Main operation for concatenation: concat 'total' values in the stack,\n** from 'L->top.p - total' up to 'L->top.p - 1'.\n*/\nvoid luaV_concat (lua_State *L, int total) {\n  if (total == 1)\n    return;  /* \"all\" values already concatenated */\n  do {\n    StkId top = L->top.p;\n    int n = 2;  /* number of elements handled in this pass (at least 2) */\n    if (!(ttisstring(s2v(top - 2)) || cvt2str(s2v(top - 2))) ||\n        !tostring(L, s2v(top - 1)))\n      luaT_tryconcatTM(L);  /* may invalidate 'top' */\n    else if (isemptystr(s2v(top - 1)))  /* second operand is empty? */\n      cast_void(tostring(L, s2v(top - 2)));  /* result is first operand */\n    else if (isemptystr(s2v(top - 2))) {  /* first operand is empty string? */\n      setobjs2s(L, top - 2, top - 1);  /* result is second op. */\n    }\n    else {\n      /* at least two non-empty string values; get as many as possible */\n      size_t tl = vslen(s2v(top - 1));\n      TString *ts;\n      /* collect total length and number of strings */\n      for (n = 1; n < total && tostring(L, s2v(top - n - 1)); n++) {\n        size_t l = vslen(s2v(top - n - 1));\n        if (l_unlikely(l >= (MAX_SIZE/sizeof(char)) - tl)) {\n          L->top.p = top - total;  /* pop strings to avoid wasting stack */\n          luaG_runerror(L, \"string length overflow\");\n        }\n        tl += l;\n      }\n      if (tl <= LUAI_MAXSHORTLEN) {  /* is result a short string? */\n        char buff[LUAI_MAXSHORTLEN];\n        copy2buff(top, n, buff);  /* copy strings to buffer */\n        ts = luaS_newlstr(L, buff, tl);\n      }\n      else {  /* long string; copy strings directly to final result */\n        ts = luaS_createlngstrobj(L, tl);\n        copy2buff(top, n, getstr(ts));\n      }\n      setsvalue2s(L, top - n, ts);  /* create result */\n    }\n    total -= n - 1;  /* got 'n' strings to create one new */\n    L->top.p -= n - 1;  /* popped 'n' strings and pushed one */\n  } while (total > 1);  /* repeat until only 1 result left */\n}\n\n\n/*\n** Main operation 'ra = #rb'.\n*/\nvoid luaV_objlen (lua_State *L, StkId ra, const TValue *rb) {\n  const TValue *tm;\n  switch (ttypetag(rb)) {\n    case LUA_VTABLE: {\n      Table *h = hvalue(rb);\n      tm = fasttm(L, h->metatable, TM_LEN);\n      if (tm) break;  /* metamethod? break switch to call it */\n      setivalue(s2v(ra), luaH_getn(h));  /* else primitive len */\n      return;\n    }\n    case LUA_VSHRSTR: {\n      setivalue(s2v(ra), tsvalue(rb)->shrlen);\n      return;\n    }\n    case LUA_VLNGSTR: {\n      setivalue(s2v(ra), tsvalue(rb)->u.lnglen);\n      return;\n    }\n    default: {  /* try metamethod */\n      tm = luaT_gettmbyobj(L, rb, TM_LEN);\n      if (l_unlikely(notm(tm)))  /* no metamethod? */\n        luaG_typeerror(L, rb, \"get length of\");\n      break;\n    }\n  }\n  luaT_callTMres(L, tm, rb, rb, ra);\n}\n\n\n/*\n** Integer division; return 'm // n', that is, floor(m/n).\n** C division truncates its result (rounds towards zero).\n** 'floor(q) == trunc(q)' when 'q >= 0' or when 'q' is integer,\n** otherwise 'floor(q) == trunc(q) - 1'.\n*/\nlua_Integer luaV_idiv (lua_State *L, lua_Integer m, lua_Integer n) {\n  if (l_unlikely(l_castS2U(n) + 1u <= 1u)) {  /* special cases: -1 or 0 */\n    if (n == 0)\n      luaG_runerror(L, \"attempt to divide by zero\");\n    return intop(-, 0, m);   /* n==-1; avoid overflow with 0x80000...//-1 */\n  }\n  else {\n    lua_Integer q = m / n;  /* perform C division */\n    if ((m ^ n) < 0 && m % n != 0)  /* 'm/n' would be negative non-integer? */\n      q -= 1;  /* correct result for different rounding */\n    return q;\n  }\n}\n\n\n/*\n** Integer modulus; return 'm % n'. (Assume that C '%' with\n** negative operands follows C99 behavior. See previous comment\n** about luaV_idiv.)\n*/\nlua_Integer luaV_mod (lua_State *L, lua_Integer m, lua_Integer n) {\n  if (l_unlikely(l_castS2U(n) + 1u <= 1u)) {  /* special cases: -1 or 0 */\n    if (n == 0)\n      luaG_runerror(L, \"attempt to perform 'n%%0'\");\n    return 0;   /* m % -1 == 0; avoid overflow with 0x80000...%-1 */\n  }\n  else {\n    lua_Integer r = m % n;\n    if (r != 0 && (r ^ n) < 0)  /* 'm/n' would be non-integer negative? */\n      r += n;  /* correct result for different rounding */\n    return r;\n  }\n}\n\n\n/*\n** Float modulus\n*/\nlua_Number luaV_modf (lua_State *L, lua_Number m, lua_Number n) {\n  lua_Number r;\n  luai_nummod(L, m, n, r);\n  return r;\n}\n\n\n/* number of bits in an integer */\n#define NBITS\tcast_int(sizeof(lua_Integer) * CHAR_BIT)\n\n\n/*\n** Shift left operation. (Shift right just negates 'y'.)\n*/\nlua_Integer luaV_shiftl (lua_Integer x, lua_Integer y) {\n  if (y < 0) {  /* shift right? */\n    if (y <= -NBITS) return 0;\n    else return intop(>>, x, -y);\n  }\n  else {  /* shift left */\n    if (y >= NBITS) return 0;\n    else return intop(<<, x, y);\n  }\n}\n\n\n/*\n** create a new Lua closure, push it in the stack, and initialize\n** its upvalues.\n*/\nstatic void pushclosure (lua_State *L, Proto *p, UpVal **encup, StkId base,\n                         StkId ra) {\n  int nup = p->sizeupvalues;\n  Upvaldesc *uv = p->upvalues;\n  int i;\n  LClosure *ncl = luaF_newLclosure(L, nup);\n  ncl->p = p;\n  setclLvalue2s(L, ra, ncl);  /* anchor new closure in stack */\n  for (i = 0; i < nup; i++) {  /* fill in its upvalues */\n    if (uv[i].instack)  /* upvalue refers to local variable? */\n      ncl->upvals[i] = luaF_findupval(L, base + uv[i].idx);\n    else  /* get upvalue from enclosing function */\n      ncl->upvals[i] = encup[uv[i].idx];\n    luaC_objbarrier(L, ncl, ncl->upvals[i]);\n  }\n}\n\n\n/*\n** finish execution of an opcode interrupted by a yield\n*/\nvoid luaV_finishOp (lua_State *L) {\n  CallInfo *ci = L->ci;\n  StkId base = ci->func.p + 1;\n  Instruction inst = *(ci->u.l.savedpc - 1);  /* interrupted instruction */\n  OpCode op = GET_OPCODE(inst);\n  switch (op) {  /* finish its execution */\n    case OP_MMBIN: case OP_MMBINI: case OP_MMBINK: {\n      setobjs2s(L, base + GETARG_A(*(ci->u.l.savedpc - 2)), --L->top.p);\n      break;\n    }\n    case OP_UNM: case OP_BNOT: case OP_LEN:\n    case OP_GETTABUP: case OP_GETTABLE: case OP_GETI:\n    case OP_GETFIELD: case OP_SELF: {\n      setobjs2s(L, base + GETARG_A(inst), --L->top.p);\n      break;\n    }\n    case OP_LT: case OP_LE:\n    case OP_LTI: case OP_LEI:\n    case OP_GTI: case OP_GEI:\n    case OP_EQ: {  /* note that 'OP_EQI'/'OP_EQK' cannot yield */\n      int res = !l_isfalse(s2v(L->top.p - 1));\n      L->top.p--;\n#if defined(LUA_COMPAT_LT_LE)\n      if (ci->callstatus & CIST_LEQ) {  /* \"<=\" using \"<\" instead? */\n        ci->callstatus ^= CIST_LEQ;  /* clear mark */\n        res = !res;  /* negate result */\n      }\n#endif\n      lua_assert(GET_OPCODE(*ci->u.l.savedpc) == OP_JMP);\n      if (res != GETARG_k(inst))  /* condition failed? */\n        ci->u.l.savedpc++;  /* skip jump instruction */\n      break;\n    }\n    case OP_CONCAT: {\n      StkId top = L->top.p - 1;  /* top when 'luaT_tryconcatTM' was called */\n      int a = GETARG_A(inst);      /* first element to concatenate */\n      int total = cast_int(top - 1 - (base + a));  /* yet to concatenate */\n      setobjs2s(L, top - 2, top);  /* put TM result in proper position */\n      L->top.p = top - 1;  /* top is one after last element (at top-2) */\n      luaV_concat(L, total);  /* concat them (may yield again) */\n      break;\n    }\n    case OP_CLOSE: {  /* yielded closing variables */\n      ci->u.l.savedpc--;  /* repeat instruction to close other vars. */\n      break;\n    }\n    case OP_RETURN: {  /* yielded closing variables */\n      StkId ra = base + GETARG_A(inst);\n      /* adjust top to signal correct number of returns, in case the\n         return is \"up to top\" ('isIT') */\n      L->top.p = ra + ci->u2.nres;\n      /* repeat instruction to close other vars. and complete the return */\n      ci->u.l.savedpc--;\n      break;\n    }\n    default: {\n      /* only these other opcodes can yield */\n      lua_assert(op == OP_TFORCALL || op == OP_CALL ||\n           op == OP_TAILCALL || op == OP_SETTABUP || op == OP_SETTABLE ||\n           op == OP_SETI || op == OP_SETFIELD);\n      break;\n    }\n  }\n}\n\n\n\n\n/*\n** {==================================================================\n** Macros for arithmetic/bitwise/comparison opcodes in 'luaV_execute'\n** ===================================================================\n*/\n\n#define l_addi(L,a,b)\tintop(+, a, b)\n#define l_subi(L,a,b)\tintop(-, a, b)\n#define l_muli(L,a,b)\tintop(*, a, b)\n#define l_band(a,b)\tintop(&, a, b)\n#define l_bor(a,b)\tintop(|, a, b)\n#define l_bxor(a,b)\tintop(^, a, b)\n\n#define l_lti(a,b)\t(a < b)\n#define l_lei(a,b)\t(a <= b)\n#define l_gti(a,b)\t(a > b)\n#define l_gei(a,b)\t(a >= b)\n\n\n/*\n** Arithmetic operations with immediate operands. 'iop' is the integer\n** operation, 'fop' is the float operation.\n*/\n#define op_arithI(L,iop,fop) {  \\\n  StkId ra = RA(i); \\\n  TValue *v1 = vRB(i);  \\\n  int imm = GETARG_sC(i);  \\\n  if (ttisinteger(v1)) {  \\\n    lua_Integer iv1 = ivalue(v1);  \\\n    pc++; setivalue(s2v(ra), iop(L, iv1, imm));  \\\n  }  \\\n  else if (ttisfloat(v1)) {  \\\n    lua_Number nb = fltvalue(v1);  \\\n    lua_Number fimm = cast_num(imm);  \\\n    pc++; setfltvalue(s2v(ra), fop(L, nb, fimm)); \\\n  }}\n\n\n/*\n** Auxiliary function for arithmetic operations over floats and others\n** with two register operands.\n*/\n#define op_arithf_aux(L,v1,v2,fop) {  \\\n  lua_Number n1; lua_Number n2;  \\\n  if (tonumberns(v1, n1) && tonumberns(v2, n2)) {  \\\n    pc++; setfltvalue(s2v(ra), fop(L, n1, n2));  \\\n  }}\n\n\n/*\n** Arithmetic operations over floats and others with register operands.\n*/\n#define op_arithf(L,fop) {  \\\n  StkId ra = RA(i); \\\n  TValue *v1 = vRB(i);  \\\n  TValue *v2 = vRC(i);  \\\n  op_arithf_aux(L, v1, v2, fop); }\n\n\n/*\n** Arithmetic operations with K operands for floats.\n*/\n#define op_arithfK(L,fop) {  \\\n  StkId ra = RA(i); \\\n  TValue *v1 = vRB(i);  \\\n  TValue *v2 = KC(i); lua_assert(ttisnumber(v2));  \\\n  op_arithf_aux(L, v1, v2, fop); }\n\n\n/*\n** Arithmetic operations over integers and floats.\n*/\n#define op_arith_aux(L,v1,v2,iop,fop) {  \\\n  StkId ra = RA(i); \\\n  if (ttisinteger(v1) && ttisinteger(v2)) {  \\\n    lua_Integer i1 = ivalue(v1); lua_Integer i2 = ivalue(v2);  \\\n    pc++; setivalue(s2v(ra), iop(L, i1, i2));  \\\n  }  \\\n  else op_arithf_aux(L, v1, v2, fop); }\n\n\n/*\n** Arithmetic operations with register operands.\n*/\n#define op_arith(L,iop,fop) {  \\\n  TValue *v1 = vRB(i);  \\\n  TValue *v2 = vRC(i);  \\\n  op_arith_aux(L, v1, v2, iop, fop); }\n\n\n/*\n** Arithmetic operations with K operands.\n*/\n#define op_arithK(L,iop,fop) {  \\\n  TValue *v1 = vRB(i);  \\\n  TValue *v2 = KC(i); lua_assert(ttisnumber(v2));  \\\n  op_arith_aux(L, v1, v2, iop, fop); }\n\n\n/*\n** Bitwise operations with constant operand.\n*/\n#define op_bitwiseK(L,op) {  \\\n  StkId ra = RA(i); \\\n  TValue *v1 = vRB(i);  \\\n  TValue *v2 = KC(i);  \\\n  lua_Integer i1;  \\\n  lua_Integer i2 = ivalue(v2);  \\\n  if (tointegerns(v1, &i1)) {  \\\n    pc++; setivalue(s2v(ra), op(i1, i2));  \\\n  }}\n\n\n/*\n** Bitwise operations with register operands.\n*/\n#define op_bitwise(L,op) {  \\\n  StkId ra = RA(i); \\\n  TValue *v1 = vRB(i);  \\\n  TValue *v2 = vRC(i);  \\\n  lua_Integer i1; lua_Integer i2;  \\\n  if (tointegerns(v1, &i1) && tointegerns(v2, &i2)) {  \\\n    pc++; setivalue(s2v(ra), op(i1, i2));  \\\n  }}\n\n\n/*\n** Order operations with register operands. 'opn' actually works\n** for all numbers, but the fast track improves performance for\n** integers.\n*/\n#define op_order(L,opi,opn,other) {  \\\n  StkId ra = RA(i); \\\n  int cond;  \\\n  TValue *rb = vRB(i);  \\\n  if (ttisinteger(s2v(ra)) && ttisinteger(rb)) {  \\\n    lua_Integer ia = ivalue(s2v(ra));  \\\n    lua_Integer ib = ivalue(rb);  \\\n    cond = opi(ia, ib);  \\\n  }  \\\n  else if (ttisnumber(s2v(ra)) && ttisnumber(rb))  \\\n    cond = opn(s2v(ra), rb);  \\\n  else  \\\n    Protect(cond = other(L, s2v(ra), rb));  \\\n  docondjump(); }\n\n\n/*\n** Order operations with immediate operand. (Immediate operand is\n** always small enough to have an exact representation as a float.)\n*/\n#define op_orderI(L,opi,opf,inv,tm) {  \\\n  StkId ra = RA(i); \\\n  int cond;  \\\n  int im = GETARG_sB(i);  \\\n  if (ttisinteger(s2v(ra)))  \\\n    cond = opi(ivalue(s2v(ra)), im);  \\\n  else if (ttisfloat(s2v(ra))) {  \\\n    lua_Number fa = fltvalue(s2v(ra));  \\\n    lua_Number fim = cast_num(im);  \\\n    cond = opf(fa, fim);  \\\n  }  \\\n  else {  \\\n    int isf = GETARG_C(i);  \\\n    Protect(cond = luaT_callorderiTM(L, s2v(ra), im, inv, isf, tm));  \\\n  }  \\\n  docondjump(); }\n\n/* }================================================================== */\n\n\n/*\n** {==================================================================\n** Function 'luaV_execute': main interpreter loop\n** ===================================================================\n*/\n\n/*\n** some macros for common tasks in 'luaV_execute'\n*/\n\n\n#define RA(i)\t(base+GETARG_A(i))\n#define RB(i)\t(base+GETARG_B(i))\n#define vRB(i)\ts2v(RB(i))\n#define KB(i)\t(k+GETARG_B(i))\n#define RC(i)\t(base+GETARG_C(i))\n#define vRC(i)\ts2v(RC(i))\n#define KC(i)\t(k+GETARG_C(i))\n#define RKC(i)\t((TESTARG_k(i)) ? k + GETARG_C(i) : s2v(base + GETARG_C(i)))\n\n\n\n#define updatetrap(ci)  (trap = ci->u.l.trap)\n\n#define updatebase(ci)\t(base = ci->func.p + 1)\n\n\n#define updatestack(ci)  \\\n\t{ if (l_unlikely(trap)) { updatebase(ci); ra = RA(i); } }\n\n\n/*\n** Execute a jump instruction. The 'updatetrap' allows signals to stop\n** tight loops. (Without it, the local copy of 'trap' could never change.)\n*/\n#define dojump(ci,i,e)\t{ pc += GETARG_sJ(i) + e; updatetrap(ci); }\n\n\n/* for test instructions, execute the jump instruction that follows it */\n#define donextjump(ci)\t{ Instruction ni = *pc; dojump(ci, ni, 1); }\n\n/*\n** do a conditional jump: skip next instruction if 'cond' is not what\n** was expected (parameter 'k'), else do next instruction, which must\n** be a jump.\n*/\n#define docondjump()\tif (cond != GETARG_k(i)) pc++; else donextjump(ci);\n\n\n/*\n** Correct global 'pc'.\n*/\n#define savepc(L)\t(ci->u.l.savedpc = pc)\n\n\n/*\n** Whenever code can raise errors, the global 'pc' and the global\n** 'top' must be correct to report occasional errors.\n*/\n#define savestate(L,ci)\t\t(savepc(L), L->top.p = ci->top.p)\n\n\n/*\n** Protect code that, in general, can raise errors, reallocate the\n** stack, and change the hooks.\n*/\n#define Protect(exp)  (savestate(L,ci), (exp), updatetrap(ci))\n\n/* special version that does not change the top */\n#define ProtectNT(exp)  (savepc(L), (exp), updatetrap(ci))\n\n/*\n** Protect code that can only raise errors. (That is, it cannot change\n** the stack or hooks.)\n*/\n#define halfProtect(exp)  (savestate(L,ci), (exp))\n\n/* 'c' is the limit of live values in the stack */\n#define checkGC(L,c)  \\\n\t{ luaC_condGC(L, (savepc(L), L->top.p = (c)), \\\n                         updatetrap(ci)); \\\n           luai_threadyield(L); }\n\n\n/* fetch an instruction and prepare its execution */\n#define vmfetch()\t{ \\\n  if (l_unlikely(trap)) {  /* stack reallocation or hooks? */ \\\n    trap = luaG_traceexec(L, pc);  /* handle hooks */ \\\n    updatebase(ci);  /* correct stack */ \\\n  } \\\n  i = *(pc++); \\\n}\n\n#define vmdispatch(o)\tswitch(o)\n#define vmcase(l)\tcase l:\n#define vmbreak\t\tbreak\n\n\nvoid luaV_execute (lua_State *L, CallInfo *ci) {\n  LClosure *cl;\n  TValue *k;\n  StkId base;\n  const Instruction *pc;\n  int trap;\n#if LUA_USE_JUMPTABLE\n/*#include \"ljumptab.h\"*/\n/*\n** $Id: ljumptab.h $\n** Jump Table for the Lua interpreter\n** See Copyright Notice in lua.h\n*/\n\n\n#undef vmdispatch\n#undef vmcase\n#undef vmbreak\n\n#define vmdispatch(x)     goto *disptab[x];\n\n#define vmcase(l)     L_##l:\n\n#define vmbreak\t\tvmfetch(); vmdispatch(GET_OPCODE(i));\n\n\nstatic const void *const disptab[NUM_OPCODES] = {\n\n#if 0\n** you can update the following list with this command:\n**\n**  sed -n '/^OP_/\\!d; s/OP_/\\&\\&L_OP_/ ; s/,.*/,/ ; s/\\/.*// ; p'  lopcodes.h\n**\n#endif\n\n&&L_OP_MOVE,\n&&L_OP_LOADI,\n&&L_OP_LOADF,\n&&L_OP_LOADK,\n&&L_OP_LOADKX,\n&&L_OP_LOADFALSE,\n&&L_OP_LFALSESKIP,\n&&L_OP_LOADTRUE,\n&&L_OP_LOADNIL,\n&&L_OP_GETUPVAL,\n&&L_OP_SETUPVAL,\n&&L_OP_GETTABUP,\n&&L_OP_GETTABLE,\n&&L_OP_GETI,\n&&L_OP_GETFIELD,\n&&L_OP_SETTABUP,\n&&L_OP_SETTABLE,\n&&L_OP_SETI,\n&&L_OP_SETFIELD,\n&&L_OP_NEWTABLE,\n&&L_OP_SELF,\n&&L_OP_ADDI,\n&&L_OP_ADDK,\n&&L_OP_SUBK,\n&&L_OP_MULK,\n&&L_OP_MODK,\n&&L_OP_POWK,\n&&L_OP_DIVK,\n&&L_OP_IDIVK,\n&&L_OP_BANDK,\n&&L_OP_BORK,\n&&L_OP_BXORK,\n&&L_OP_SHRI,\n&&L_OP_SHLI,\n&&L_OP_ADD,\n&&L_OP_SUB,\n&&L_OP_MUL,\n&&L_OP_MOD,\n&&L_OP_POW,\n&&L_OP_DIV,\n&&L_OP_IDIV,\n&&L_OP_BAND,\n&&L_OP_BOR,\n&&L_OP_BXOR,\n&&L_OP_SHL,\n&&L_OP_SHR,\n&&L_OP_MMBIN,\n&&L_OP_MMBINI,\n&&L_OP_MMBINK,\n&&L_OP_UNM,\n&&L_OP_BNOT,\n&&L_OP_NOT,\n&&L_OP_LEN,\n&&L_OP_CONCAT,\n&&L_OP_CLOSE,\n&&L_OP_TBC,\n&&L_OP_JMP,\n&&L_OP_EQ,\n&&L_OP_LT,\n&&L_OP_LE,\n&&L_OP_EQK,\n&&L_OP_EQI,\n&&L_OP_LTI,\n&&L_OP_LEI,\n&&L_OP_GTI,\n&&L_OP_GEI,\n&&L_OP_TEST,\n&&L_OP_TESTSET,\n&&L_OP_CALL,\n&&L_OP_TAILCALL,\n&&L_OP_RETURN,\n&&L_OP_RETURN0,\n&&L_OP_RETURN1,\n&&L_OP_FORLOOP,\n&&L_OP_FORPREP,\n&&L_OP_TFORPREP,\n&&L_OP_TFORCALL,\n&&L_OP_TFORLOOP,\n&&L_OP_SETLIST,\n&&L_OP_CLOSURE,\n&&L_OP_VARARG,\n&&L_OP_VARARGPREP,\n&&L_OP_EXTRAARG\n\n};\n#endif\n startfunc:\n  trap = L->hookmask;\n returning:  /* trap already set */\n  cl = clLvalue(s2v(ci->func.p));\n  k = cl->p->k;\n  pc = ci->u.l.savedpc;\n  if (l_unlikely(trap)) {\n    if (pc == cl->p->code) {  /* first instruction (not resuming)? */\n      if (cl->p->is_vararg)\n        trap = 0;  /* hooks will start after VARARGPREP instruction */\n      else  /* check 'call' hook */\n        luaD_hookcall(L, ci);\n    }\n    ci->u.l.trap = 1;  /* assume trap is on, for now */\n  }\n  base = ci->func.p + 1;\n  /* main loop of interpreter */\n  for (;;) {\n    Instruction i;  /* instruction being executed */\n    vmfetch();\n    #if 0\n      /* low-level line tracing for debugging Lua */\n      printf(\"line: %d\\n\", luaG_getfuncline(cl->p, pcRel(pc, cl->p)));\n    #endif\n    lua_assert(base == ci->func.p + 1);\n    lua_assert(base <= L->top.p && L->top.p <= L->stack_last.p);\n    /* invalidate top for instructions not expecting it */\n    lua_assert(isIT(i) || (cast_void(L->top.p = base), 1));\n    vmdispatch (GET_OPCODE(i)) {\n      vmcase(OP_MOVE) {\n        StkId ra = RA(i);\n        setobjs2s(L, ra, RB(i));\n        vmbreak;\n      }\n      vmcase(OP_LOADI) {\n        StkId ra = RA(i);\n        lua_Integer b = GETARG_sBx(i);\n        setivalue(s2v(ra), b);\n        vmbreak;\n      }\n      vmcase(OP_LOADF) {\n        StkId ra = RA(i);\n        int b = GETARG_sBx(i);\n        setfltvalue(s2v(ra), cast_num(b));\n        vmbreak;\n      }\n      vmcase(OP_LOADK) {\n        StkId ra = RA(i);\n        TValue *rb = k + GETARG_Bx(i);\n        setobj2s(L, ra, rb);\n        vmbreak;\n      }\n      vmcase(OP_LOADKX) {\n        StkId ra = RA(i);\n        TValue *rb;\n        rb = k + GETARG_Ax(*pc); pc++;\n        setobj2s(L, ra, rb);\n        vmbreak;\n      }\n      vmcase(OP_LOADFALSE) {\n        StkId ra = RA(i);\n        setbfvalue(s2v(ra));\n        vmbreak;\n      }\n      vmcase(OP_LFALSESKIP) {\n        StkId ra = RA(i);\n        setbfvalue(s2v(ra));\n        pc++;  /* skip next instruction */\n        vmbreak;\n      }\n      vmcase(OP_LOADTRUE) {\n        StkId ra = RA(i);\n        setbtvalue(s2v(ra));\n        vmbreak;\n      }\n      vmcase(OP_LOADNIL) {\n        StkId ra = RA(i);\n        int b = GETARG_B(i);\n        do {\n          setnilvalue(s2v(ra++));\n        } while (b--);\n        vmbreak;\n      }\n      vmcase(OP_GETUPVAL) {\n        StkId ra = RA(i);\n        int b = GETARG_B(i);\n        setobj2s(L, ra, cl->upvals[b]->v.p);\n        vmbreak;\n      }\n      vmcase(OP_SETUPVAL) {\n        StkId ra = RA(i);\n        UpVal *uv = cl->upvals[GETARG_B(i)];\n        setobj(L, uv->v.p, s2v(ra));\n        luaC_barrier(L, uv, s2v(ra));\n        vmbreak;\n      }\n      vmcase(OP_GETTABUP) {\n        StkId ra = RA(i);\n        const TValue *slot;\n        TValue *upval = cl->upvals[GETARG_B(i)]->v.p;\n        TValue *rc = KC(i);\n        TString *key = tsvalue(rc);  /* key must be a string */\n        if (luaV_fastget(L, upval, key, slot, luaH_getshortstr)) {\n          setobj2s(L, ra, slot);\n        }\n        else\n          Protect(luaV_finishget(L, upval, rc, ra, slot));\n        vmbreak;\n      }\n      vmcase(OP_GETTABLE) {\n        StkId ra = RA(i);\n        const TValue *slot;\n        TValue *rb = vRB(i);\n        TValue *rc = vRC(i);\n        lua_Unsigned n;\n        if (ttisinteger(rc)  /* fast track for integers? */\n            ? (cast_void(n = ivalue(rc)), luaV_fastgeti(L, rb, n, slot))\n            : luaV_fastget(L, rb, rc, slot, luaH_get)) {\n          setobj2s(L, ra, slot);\n        }\n        else\n          Protect(luaV_finishget(L, rb, rc, ra, slot));\n        vmbreak;\n      }\n      vmcase(OP_GETI) {\n        StkId ra = RA(i);\n        const TValue *slot;\n        TValue *rb = vRB(i);\n        int c = GETARG_C(i);\n        if (luaV_fastgeti(L, rb, c, slot)) {\n          setobj2s(L, ra, slot);\n        }\n        else {\n          TValue key;\n          setivalue(&key, c);\n          Protect(luaV_finishget(L, rb, &key, ra, slot));\n        }\n        vmbreak;\n      }\n      vmcase(OP_GETFIELD) {\n        StkId ra = RA(i);\n        const TValue *slot;\n        TValue *rb = vRB(i);\n        TValue *rc = KC(i);\n        TString *key = tsvalue(rc);  /* key must be a string */\n        if (luaV_fastget(L, rb, key, slot, luaH_getshortstr)) {\n          setobj2s(L, ra, slot);\n        }\n        else\n          Protect(luaV_finishget(L, rb, rc, ra, slot));\n        vmbreak;\n      }\n      vmcase(OP_SETTABUP) {\n        const TValue *slot;\n        TValue *upval = cl->upvals[GETARG_A(i)]->v.p;\n        TValue *rb = KB(i);\n        TValue *rc = RKC(i);\n        TString *key = tsvalue(rb);  /* key must be a string */\n        if (luaV_fastget(L, upval, key, slot, luaH_getshortstr)) {\n          luaV_finishfastset(L, upval, slot, rc);\n        }\n        else\n          Protect(luaV_finishset(L, upval, rb, rc, slot));\n        vmbreak;\n      }\n      vmcase(OP_SETTABLE) {\n        StkId ra = RA(i);\n        const TValue *slot;\n        TValue *rb = vRB(i);  /* key (table is in 'ra') */\n        TValue *rc = RKC(i);  /* value */\n        lua_Unsigned n;\n        if (ttisinteger(rb)  /* fast track for integers? */\n            ? (cast_void(n = ivalue(rb)), luaV_fastgeti(L, s2v(ra), n, slot))\n            : luaV_fastget(L, s2v(ra), rb, slot, luaH_get)) {\n          luaV_finishfastset(L, s2v(ra), slot, rc);\n        }\n        else\n          Protect(luaV_finishset(L, s2v(ra), rb, rc, slot));\n        vmbreak;\n      }\n      vmcase(OP_SETI) {\n        StkId ra = RA(i);\n        const TValue *slot;\n        int c = GETARG_B(i);\n        TValue *rc = RKC(i);\n        if (luaV_fastgeti(L, s2v(ra), c, slot)) {\n          luaV_finishfastset(L, s2v(ra), slot, rc);\n        }\n        else {\n          TValue key;\n          setivalue(&key, c);\n          Protect(luaV_finishset(L, s2v(ra), &key, rc, slot));\n        }\n        vmbreak;\n      }\n      vmcase(OP_SETFIELD) {\n        StkId ra = RA(i);\n        const TValue *slot;\n        TValue *rb = KB(i);\n        TValue *rc = RKC(i);\n        TString *key = tsvalue(rb);  /* key must be a string */\n        if (luaV_fastget(L, s2v(ra), key, slot, luaH_getshortstr)) {\n          luaV_finishfastset(L, s2v(ra), slot, rc);\n        }\n        else\n          Protect(luaV_finishset(L, s2v(ra), rb, rc, slot));\n        vmbreak;\n      }\n      vmcase(OP_NEWTABLE) {\n        StkId ra = RA(i);\n        int b = GETARG_B(i);  /* log2(hash size) + 1 */\n        int c = GETARG_C(i);  /* array size */\n        Table *t;\n        if (b > 0)\n          b = 1 << (b - 1);  /* size is 2^(b - 1) */\n        lua_assert((!TESTARG_k(i)) == (GETARG_Ax(*pc) == 0));\n        if (TESTARG_k(i))  /* non-zero extra argument? */\n          c += GETARG_Ax(*pc) * (MAXARG_C + 1);  /* add it to size */\n        pc++;  /* skip extra argument */\n        L->top.p = ra + 1;  /* correct top in case of emergency GC */\n        t = luaH_new(L);  /* memory allocation */\n        sethvalue2s(L, ra, t);\n        if (b != 0 || c != 0)\n          luaH_resize(L, t, c, b);  /* idem */\n        checkGC(L, ra + 1);\n        vmbreak;\n      }\n      vmcase(OP_SELF) {\n        StkId ra = RA(i);\n        const TValue *slot;\n        TValue *rb = vRB(i);\n        TValue *rc = RKC(i);\n        TString *key = tsvalue(rc);  /* key must be a string */\n        setobj2s(L, ra + 1, rb);\n        if (luaV_fastget(L, rb, key, slot, luaH_getstr)) {\n          setobj2s(L, ra, slot);\n        }\n        else\n          Protect(luaV_finishget(L, rb, rc, ra, slot));\n        vmbreak;\n      }\n      vmcase(OP_ADDI) {\n        op_arithI(L, l_addi, luai_numadd);\n        vmbreak;\n      }\n      vmcase(OP_ADDK) {\n        op_arithK(L, l_addi, luai_numadd);\n        vmbreak;\n      }\n      vmcase(OP_SUBK) {\n        op_arithK(L, l_subi, luai_numsub);\n        vmbreak;\n      }\n      vmcase(OP_MULK) {\n        op_arithK(L, l_muli, luai_nummul);\n        vmbreak;\n      }\n      vmcase(OP_MODK) {\n        savestate(L, ci);  /* in case of division by 0 */\n        op_arithK(L, luaV_mod, luaV_modf);\n        vmbreak;\n      }\n      vmcase(OP_POWK) {\n        op_arithfK(L, luai_numpow);\n        vmbreak;\n      }\n      vmcase(OP_DIVK) {\n        op_arithfK(L, luai_numdiv);\n        vmbreak;\n      }\n      vmcase(OP_IDIVK) {\n        savestate(L, ci);  /* in case of division by 0 */\n        op_arithK(L, luaV_idiv, luai_numidiv);\n        vmbreak;\n      }\n      vmcase(OP_BANDK) {\n        op_bitwiseK(L, l_band);\n        vmbreak;\n      }\n      vmcase(OP_BORK) {\n        op_bitwiseK(L, l_bor);\n        vmbreak;\n      }\n      vmcase(OP_BXORK) {\n        op_bitwiseK(L, l_bxor);\n        vmbreak;\n      }\n      vmcase(OP_SHRI) {\n        StkId ra = RA(i);\n        TValue *rb = vRB(i);\n        int ic = GETARG_sC(i);\n        lua_Integer ib;\n        if (tointegerns(rb, &ib)) {\n          pc++; setivalue(s2v(ra), luaV_shiftl(ib, -ic));\n        }\n        vmbreak;\n      }\n      vmcase(OP_SHLI) {\n        StkId ra = RA(i);\n        TValue *rb = vRB(i);\n        int ic = GETARG_sC(i);\n        lua_Integer ib;\n        if (tointegerns(rb, &ib)) {\n          pc++; setivalue(s2v(ra), luaV_shiftl(ic, ib));\n        }\n        vmbreak;\n      }\n      vmcase(OP_ADD) {\n        op_arith(L, l_addi, luai_numadd);\n        vmbreak;\n      }\n      vmcase(OP_SUB) {\n        op_arith(L, l_subi, luai_numsub);\n        vmbreak;\n      }\n      vmcase(OP_MUL) {\n        op_arith(L, l_muli, luai_nummul);\n        vmbreak;\n      }\n      vmcase(OP_MOD) {\n        savestate(L, ci);  /* in case of division by 0 */\n        op_arith(L, luaV_mod, luaV_modf);\n        vmbreak;\n      }\n      vmcase(OP_POW) {\n        op_arithf(L, luai_numpow);\n        vmbreak;\n      }\n      vmcase(OP_DIV) {  /* float division (always with floats) */\n        op_arithf(L, luai_numdiv);\n        vmbreak;\n      }\n      vmcase(OP_IDIV) {  /* floor division */\n        savestate(L, ci);  /* in case of division by 0 */\n        op_arith(L, luaV_idiv, luai_numidiv);\n        vmbreak;\n      }\n      vmcase(OP_BAND) {\n        op_bitwise(L, l_band);\n        vmbreak;\n      }\n      vmcase(OP_BOR) {\n        op_bitwise(L, l_bor);\n        vmbreak;\n      }\n      vmcase(OP_BXOR) {\n        op_bitwise(L, l_bxor);\n        vmbreak;\n      }\n      vmcase(OP_SHR) {\n        op_bitwise(L, luaV_shiftr);\n        vmbreak;\n      }\n      vmcase(OP_SHL) {\n        op_bitwise(L, luaV_shiftl);\n        vmbreak;\n      }\n      vmcase(OP_MMBIN) {\n        StkId ra = RA(i);\n        Instruction pi = *(pc - 2);  /* original arith. expression */\n        TValue *rb = vRB(i);\n        TMS tm = (TMS)GETARG_C(i);\n        StkId result = RA(pi);\n        lua_assert(OP_ADD <= GET_OPCODE(pi) && GET_OPCODE(pi) <= OP_SHR);\n        Protect(luaT_trybinTM(L, s2v(ra), rb, result, tm));\n        vmbreak;\n      }\n      vmcase(OP_MMBINI) {\n        StkId ra = RA(i);\n        Instruction pi = *(pc - 2);  /* original arith. expression */\n        int imm = GETARG_sB(i);\n        TMS tm = (TMS)GETARG_C(i);\n        int flip = GETARG_k(i);\n        StkId result = RA(pi);\n        Protect(luaT_trybiniTM(L, s2v(ra), imm, flip, result, tm));\n        vmbreak;\n      }\n      vmcase(OP_MMBINK) {\n        StkId ra = RA(i);\n        Instruction pi = *(pc - 2);  /* original arith. expression */\n        TValue *imm = KB(i);\n        TMS tm = (TMS)GETARG_C(i);\n        int flip = GETARG_k(i);\n        StkId result = RA(pi);\n        Protect(luaT_trybinassocTM(L, s2v(ra), imm, flip, result, tm));\n        vmbreak;\n      }\n      vmcase(OP_UNM) {\n        StkId ra = RA(i);\n        TValue *rb = vRB(i);\n        lua_Number nb;\n        if (ttisinteger(rb)) {\n          lua_Integer ib = ivalue(rb);\n          setivalue(s2v(ra), intop(-, 0, ib));\n        }\n        else if (tonumberns(rb, nb)) {\n          setfltvalue(s2v(ra), luai_numunm(L, nb));\n        }\n        else\n          Protect(luaT_trybinTM(L, rb, rb, ra, TM_UNM));\n        vmbreak;\n      }\n      vmcase(OP_BNOT) {\n        StkId ra = RA(i);\n        TValue *rb = vRB(i);\n        lua_Integer ib;\n        if (tointegerns(rb, &ib)) {\n          setivalue(s2v(ra), intop(^, ~l_castS2U(0), ib));\n        }\n        else\n          Protect(luaT_trybinTM(L, rb, rb, ra, TM_BNOT));\n        vmbreak;\n      }\n      vmcase(OP_NOT) {\n        StkId ra = RA(i);\n        TValue *rb = vRB(i);\n        if (l_isfalse(rb))\n          setbtvalue(s2v(ra));\n        else\n          setbfvalue(s2v(ra));\n        vmbreak;\n      }\n      vmcase(OP_LEN) {\n        StkId ra = RA(i);\n        Protect(luaV_objlen(L, ra, vRB(i)));\n        vmbreak;\n      }\n      vmcase(OP_CONCAT) {\n        StkId ra = RA(i);\n        int n = GETARG_B(i);  /* number of elements to concatenate */\n        L->top.p = ra + n;  /* mark the end of concat operands */\n        ProtectNT(luaV_concat(L, n));\n        checkGC(L, L->top.p); /* 'luaV_concat' ensures correct top */\n        vmbreak;\n      }\n      vmcase(OP_CLOSE) {\n        StkId ra = RA(i);\n        Protect(luaF_close(L, ra, LUA_OK, 1));\n        vmbreak;\n      }\n      vmcase(OP_TBC) {\n        StkId ra = RA(i);\n        /* create new to-be-closed upvalue */\n        halfProtect(luaF_newtbcupval(L, ra));\n        vmbreak;\n      }\n      vmcase(OP_JMP) {\n        dojump(ci, i, 0);\n        vmbreak;\n      }\n      vmcase(OP_EQ) {\n        StkId ra = RA(i);\n        int cond;\n        TValue *rb = vRB(i);\n        Protect(cond = luaV_equalobj(L, s2v(ra), rb));\n        docondjump();\n        vmbreak;\n      }\n      vmcase(OP_LT) {\n        op_order(L, l_lti, LTnum, lessthanothers);\n        vmbreak;\n      }\n      vmcase(OP_LE) {\n        op_order(L, l_lei, LEnum, lessequalothers);\n        vmbreak;\n      }\n      vmcase(OP_EQK) {\n        StkId ra = RA(i);\n        TValue *rb = KB(i);\n        /* basic types do not use '__eq'; we can use raw equality */\n        int cond = luaV_rawequalobj(s2v(ra), rb);\n        docondjump();\n        vmbreak;\n      }\n      vmcase(OP_EQI) {\n        StkId ra = RA(i);\n        int cond;\n        int im = GETARG_sB(i);\n        if (ttisinteger(s2v(ra)))\n          cond = (ivalue(s2v(ra)) == im);\n        else if (ttisfloat(s2v(ra)))\n          cond = luai_numeq(fltvalue(s2v(ra)), cast_num(im));\n        else\n          cond = 0;  /* other types cannot be equal to a number */\n        docondjump();\n        vmbreak;\n      }\n      vmcase(OP_LTI) {\n        op_orderI(L, l_lti, luai_numlt, 0, TM_LT);\n        vmbreak;\n      }\n      vmcase(OP_LEI) {\n        op_orderI(L, l_lei, luai_numle, 0, TM_LE);\n        vmbreak;\n      }\n      vmcase(OP_GTI) {\n        op_orderI(L, l_gti, luai_numgt, 1, TM_LT);\n        vmbreak;\n      }\n      vmcase(OP_GEI) {\n        op_orderI(L, l_gei, luai_numge, 1, TM_LE);\n        vmbreak;\n      }\n      vmcase(OP_TEST) {\n        StkId ra = RA(i);\n        int cond = !l_isfalse(s2v(ra));\n        docondjump();\n        vmbreak;\n      }\n      vmcase(OP_TESTSET) {\n        StkId ra = RA(i);\n        TValue *rb = vRB(i);\n        if (l_isfalse(rb) == GETARG_k(i))\n          pc++;\n        else {\n          setobj2s(L, ra, rb);\n          donextjump(ci);\n        }\n        vmbreak;\n      }\n      vmcase(OP_CALL) {\n        StkId ra = RA(i);\n        CallInfo *newci;\n        int b = GETARG_B(i);\n        int nresults = GETARG_C(i) - 1;\n        if (b != 0)  /* fixed number of arguments? */\n          L->top.p = ra + b;  /* top signals number of arguments */\n        /* else previous instruction set top */\n        savepc(L);  /* in case of errors */\n        if ((newci = luaD_precall(L, ra, nresults)) == NULL)\n          updatetrap(ci);  /* C call; nothing else to be done */\n        else {  /* Lua call: run function in this same C frame */\n          ci = newci;\n          goto startfunc;\n        }\n        vmbreak;\n      }\n      vmcase(OP_TAILCALL) {\n        StkId ra = RA(i);\n        int b = GETARG_B(i);  /* number of arguments + 1 (function) */\n        int n;  /* number of results when calling a C function */\n        int nparams1 = GETARG_C(i);\n        /* delta is virtual 'func' - real 'func' (vararg functions) */\n        int delta = (nparams1) ? ci->u.l.nextraargs + nparams1 : 0;\n        if (b != 0)\n          L->top.p = ra + b;\n        else  /* previous instruction set top */\n          b = cast_int(L->top.p - ra);\n        savepc(ci);  /* several calls here can raise errors */\n        if (TESTARG_k(i)) {\n          luaF_closeupval(L, base);  /* close upvalues from current call */\n          lua_assert(L->tbclist.p < base);  /* no pending tbc variables */\n          lua_assert(base == ci->func.p + 1);\n        }\n        if ((n = luaD_pretailcall(L, ci, ra, b, delta)) < 0)  /* Lua function? */\n          goto startfunc;  /* execute the callee */\n        else {  /* C function? */\n          ci->func.p -= delta;  /* restore 'func' (if vararg) */\n          luaD_poscall(L, ci, n);  /* finish caller */\n          updatetrap(ci);  /* 'luaD_poscall' can change hooks */\n          goto ret;  /* caller returns after the tail call */\n        }\n      }\n      vmcase(OP_RETURN) {\n        StkId ra = RA(i);\n        int n = GETARG_B(i) - 1;  /* number of results */\n        int nparams1 = GETARG_C(i);\n        if (n < 0)  /* not fixed? */\n          n = cast_int(L->top.p - ra);  /* get what is available */\n        savepc(ci);\n        if (TESTARG_k(i)) {  /* may there be open upvalues? */\n          ci->u2.nres = n;  /* save number of returns */\n          if (L->top.p < ci->top.p)\n            L->top.p = ci->top.p;\n          luaF_close(L, base, CLOSEKTOP, 1);\n          updatetrap(ci);\n          updatestack(ci);\n        }\n        if (nparams1)  /* vararg function? */\n          ci->func.p -= ci->u.l.nextraargs + nparams1;\n        L->top.p = ra + n;  /* set call for 'luaD_poscall' */\n        luaD_poscall(L, ci, n);\n        updatetrap(ci);  /* 'luaD_poscall' can change hooks */\n        goto ret;\n      }\n      vmcase(OP_RETURN0) {\n        if (l_unlikely(L->hookmask)) {\n          StkId ra = RA(i);\n          L->top.p = ra;\n          savepc(ci);\n          luaD_poscall(L, ci, 0);  /* no hurry... */\n          trap = 1;\n        }\n        else {  /* do the 'poscall' here */\n          int nres;\n          L->ci = ci->previous;  /* back to caller */\n          L->top.p = base - 1;\n          for (nres = ci->nresults; l_unlikely(nres > 0); nres--)\n            setnilvalue(s2v(L->top.p++));  /* all results are nil */\n        }\n        goto ret;\n      }\n      vmcase(OP_RETURN1) {\n        if (l_unlikely(L->hookmask)) {\n          StkId ra = RA(i);\n          L->top.p = ra + 1;\n          savepc(ci);\n          luaD_poscall(L, ci, 1);  /* no hurry... */\n          trap = 1;\n        }\n        else {  /* do the 'poscall' here */\n          int nres = ci->nresults;\n          L->ci = ci->previous;  /* back to caller */\n          if (nres == 0)\n            L->top.p = base - 1;  /* asked for no results */\n          else {\n            StkId ra = RA(i);\n            setobjs2s(L, base - 1, ra);  /* at least this result */\n            L->top.p = base;\n            for (; l_unlikely(nres > 1); nres--)\n              setnilvalue(s2v(L->top.p++));  /* complete missing results */\n          }\n        }\n       ret:  /* return from a Lua function */\n        if (ci->callstatus & CIST_FRESH)\n          return;  /* end this frame */\n        else {\n          ci = ci->previous;\n          goto returning;  /* continue running caller in this frame */\n        }\n      }\n      vmcase(OP_FORLOOP) {\n        StkId ra = RA(i);\n        if (ttisinteger(s2v(ra + 2))) {  /* integer loop? */\n          lua_Unsigned count = l_castS2U(ivalue(s2v(ra + 1)));\n          if (count > 0) {  /* still more iterations? */\n            lua_Integer step = ivalue(s2v(ra + 2));\n            lua_Integer idx = ivalue(s2v(ra));  /* internal index */\n            chgivalue(s2v(ra + 1), count - 1);  /* update counter */\n            idx = intop(+, idx, step);  /* add step to index */\n            chgivalue(s2v(ra), idx);  /* update internal index */\n            setivalue(s2v(ra + 3), idx);  /* and control variable */\n            pc -= GETARG_Bx(i);  /* jump back */\n          }\n        }\n        else if (floatforloop(ra))  /* float loop */\n          pc -= GETARG_Bx(i);  /* jump back */\n        updatetrap(ci);  /* allows a signal to break the loop */\n        vmbreak;\n      }\n      vmcase(OP_FORPREP) {\n        StkId ra = RA(i);\n        savestate(L, ci);  /* in case of errors */\n        if (forprep(L, ra))\n          pc += GETARG_Bx(i) + 1;  /* skip the loop */\n        vmbreak;\n      }\n      vmcase(OP_TFORPREP) {\n       StkId ra = RA(i);\n        /* create to-be-closed upvalue (if needed) */\n        halfProtect(luaF_newtbcupval(L, ra + 3));\n        pc += GETARG_Bx(i);\n        i = *(pc++);  /* go to next instruction */\n        lua_assert(GET_OPCODE(i) == OP_TFORCALL && ra == RA(i));\n        goto l_tforcall;\n      }\n      vmcase(OP_TFORCALL) {\n       l_tforcall: {\n        StkId ra = RA(i);\n        /* 'ra' has the iterator function, 'ra + 1' has the state,\n           'ra + 2' has the control variable, and 'ra + 3' has the\n           to-be-closed variable. The call will use the stack after\n           these values (starting at 'ra + 4')\n        */\n        /* push function, state, and control variable */\n        memcpy(ra + 4, ra, 3 * sizeof(*ra));\n        L->top.p = ra + 4 + 3;\n        ProtectNT(luaD_call(L, ra + 4, GETARG_C(i)));  /* do the call */\n        updatestack(ci);  /* stack may have changed */\n        i = *(pc++);  /* go to next instruction */\n        lua_assert(GET_OPCODE(i) == OP_TFORLOOP && ra == RA(i));\n        goto l_tforloop;\n      }}\n      vmcase(OP_TFORLOOP) {\n       l_tforloop: {\n        StkId ra = RA(i);\n        if (!ttisnil(s2v(ra + 4))) {  /* continue loop? */\n          setobjs2s(L, ra + 2, ra + 4);  /* save control variable */\n          pc -= GETARG_Bx(i);  /* jump back */\n        }\n        vmbreak;\n      }}\n      vmcase(OP_SETLIST) {\n        StkId ra = RA(i);\n        int n = GETARG_B(i);\n        unsigned int last = GETARG_C(i);\n        Table *h = hvalue(s2v(ra));\n        if (n == 0)\n          n = cast_int(L->top.p - ra) - 1;  /* get up to the top */\n        else\n          L->top.p = ci->top.p;  /* correct top in case of emergency GC */\n        last += n;\n        if (TESTARG_k(i)) {\n          last += GETARG_Ax(*pc) * (MAXARG_C + 1);\n          pc++;\n        }\n        if (last > luaH_realasize(h))  /* needs more space? */\n          luaH_resizearray(L, h, last);  /* preallocate it at once */\n        for (; n > 0; n--) {\n          TValue *val = s2v(ra + n);\n          setobj2t(L, &h->array[last - 1], val);\n          last--;\n          luaC_barrierback(L, obj2gco(h), val);\n        }\n        vmbreak;\n      }\n      vmcase(OP_CLOSURE) {\n        StkId ra = RA(i);\n        Proto *p = cl->p->p[GETARG_Bx(i)];\n        halfProtect(pushclosure(L, p, cl->upvals, base, ra));\n        checkGC(L, ra + 1);\n        vmbreak;\n      }\n      vmcase(OP_VARARG) {\n        StkId ra = RA(i);\n        int n = GETARG_C(i) - 1;  /* required results */\n        Protect(luaT_getvarargs(L, ci, ra, n));\n        vmbreak;\n      }\n      vmcase(OP_VARARGPREP) {\n        ProtectNT(luaT_adjustvarargs(L, GETARG_A(i), ci, cl->p));\n        if (l_unlikely(trap)) {  /* previous \"Protect\" updated trap */\n          luaD_hookcall(L, ci);\n          L->oldpc = 1;  /* next opcode will be seen as a \"new\" line */\n        }\n        updatebase(ci);  /* function has new base after adjustment */\n        vmbreak;\n      }\n      vmcase(OP_EXTRAARG) {\n        lua_assert(0);\n        vmbreak;\n      }\n    }\n  }\n}\n\n/* }================================================================== */\n/*\n** $Id: lapi.c $\n** Lua API\n** See Copyright Notice in lua.h\n*/\n\n#define lapi_c\n#define LUA_CORE\n\n/*#include \"lprefix.h\"*/\n\n\n#include <limits.h>\n#include <stdarg.h>\n#include <string.h>\n\n/*#include \"lua.h\"*/\n\n/*#include \"lapi.h\"*/\n/*#include \"ldebug.h\"*/\n/*#include \"ldo.h\"*/\n/*#include \"lfunc.h\"*/\n/*#include \"lgc.h\"*/\n/*#include \"lmem.h\"*/\n/*#include \"lobject.h\"*/\n/*#include \"lstate.h\"*/\n/*#include \"lstring.h\"*/\n/*#include \"ltable.h\"*/\n/*#include \"ltm.h\"*/\n/*#include \"lundump.h\"*/\n/*#include \"lvm.h\"*/\n\n\n\nconst char lua_ident[] =\n  \"$LuaVersion: \" LUA_COPYRIGHT \" $\"\n  \"$LuaAuthors: \" LUA_AUTHORS \" $\";\n\n\n\n/*\n** Test for a valid index (one that is not the 'nilvalue').\n** '!ttisnil(o)' implies 'o != &G(L)->nilvalue', so it is not needed.\n** However, it covers the most common cases in a faster way.\n*/\n#define isvalid(L, o)\t(!ttisnil(o) || o != &G(L)->nilvalue)\n\n\n/* test for pseudo index */\n#define ispseudo(i)\t\t((i) <= LUA_REGISTRYINDEX)\n\n/* test for upvalue */\n#define isupvalue(i)\t\t((i) < LUA_REGISTRYINDEX)\n\n\n/*\n** Convert an acceptable index to a pointer to its respective value.\n** Non-valid indices return the special nil value 'G(L)->nilvalue'.\n*/\nstatic TValue *index2value (lua_State *L, int idx) {\n  CallInfo *ci = L->ci;\n  if (idx > 0) {\n    StkId o = ci->func.p + idx;\n    api_check(L, idx <= ci->top.p - (ci->func.p + 1), \"unacceptable index\");\n    if (o >= L->top.p) return &G(L)->nilvalue;\n    else return s2v(o);\n  }\n  else if (!ispseudo(idx)) {  /* negative index */\n    api_check(L, idx != 0 && -idx <= L->top.p - (ci->func.p + 1),\n                 \"invalid index\");\n    return s2v(L->top.p + idx);\n  }\n  else if (idx == LUA_REGISTRYINDEX)\n    return &G(L)->l_registry;\n  else {  /* upvalues */\n    idx = LUA_REGISTRYINDEX - idx;\n    api_check(L, idx <= MAXUPVAL + 1, \"upvalue index too large\");\n    if (ttisCclosure(s2v(ci->func.p))) {  /* C closure? */\n      CClosure *func = clCvalue(s2v(ci->func.p));\n      return (idx <= func->nupvalues) ? &func->upvalue[idx-1]\n                                      : &G(L)->nilvalue;\n    }\n    else {  /* light C function or Lua function (through a hook)?) */\n      api_check(L, ttislcf(s2v(ci->func.p)), \"caller not a C function\");\n      return &G(L)->nilvalue;  /* no upvalues */\n    }\n  }\n}\n\n\n\n/*\n** Convert a valid actual index (not a pseudo-index) to its address.\n*/\nl_sinline StkId index2stack (lua_State *L, int idx) {\n  CallInfo *ci = L->ci;\n  if (idx > 0) {\n    StkId o = ci->func.p + idx;\n    api_check(L, o < L->top.p, \"invalid index\");\n    return o;\n  }\n  else {    /* non-positive index */\n    api_check(L, idx != 0 && -idx <= L->top.p - (ci->func.p + 1),\n                 \"invalid index\");\n    api_check(L, !ispseudo(idx), \"invalid index\");\n    return L->top.p + idx;\n  }\n}\n\n\nLUA_API int lua_checkstack (lua_State *L, int n) {\n  int res;\n  CallInfo *ci;\n  lua_lock(L);\n  ci = L->ci;\n  api_check(L, n >= 0, \"negative 'n'\");\n  if (L->stack_last.p - L->top.p > n)  /* stack large enough? */\n    res = 1;  /* yes; check is OK */\n  else  /* need to grow stack */\n    res = luaD_growstack(L, n, 0);\n  if (res && ci->top.p < L->top.p + n)\n    ci->top.p = L->top.p + n;  /* adjust frame top */\n  lua_unlock(L);\n  return res;\n}\n\n\nLUA_API void lua_xmove (lua_State *from, lua_State *to, int n) {\n  int i;\n  if (from == to) return;\n  lua_lock(to);\n  api_checknelems(from, n);\n  api_check(from, G(from) == G(to), \"moving among independent states\");\n  api_check(from, to->ci->top.p - to->top.p >= n, \"stack overflow\");\n  from->top.p -= n;\n  for (i = 0; i < n; i++) {\n    setobjs2s(to, to->top.p, from->top.p + i);\n    to->top.p++;  /* stack already checked by previous 'api_check' */\n  }\n  lua_unlock(to);\n}\n\n\nLUA_API lua_CFunction lua_atpanic (lua_State *L, lua_CFunction panicf) {\n  lua_CFunction old;\n  lua_lock(L);\n  old = G(L)->panic;\n  G(L)->panic = panicf;\n  lua_unlock(L);\n  return old;\n}\n\n\nLUA_API lua_Number lua_version (lua_State *L) {\n  UNUSED(L);\n  return LUA_VERSION_NUM;\n}\n\n\n\n/*\n** basic stack manipulation\n*/\n\n\n/*\n** convert an acceptable stack index into an absolute index\n*/\nLUA_API int lua_absindex (lua_State *L, int idx) {\n  return (idx > 0 || ispseudo(idx))\n         ? idx\n         : cast_int(L->top.p - L->ci->func.p) + idx;\n}\n\n\nLUA_API int lua_gettop (lua_State *L) {\n  return cast_int(L->top.p - (L->ci->func.p + 1));\n}\n\n\nLUA_API void lua_settop (lua_State *L, int idx) {\n  CallInfo *ci;\n  StkId func, newtop;\n  ptrdiff_t diff;  /* difference for new top */\n  lua_lock(L);\n  ci = L->ci;\n  func = ci->func.p;\n  if (idx >= 0) {\n    api_check(L, idx <= ci->top.p - (func + 1), \"new top too large\");\n    diff = ((func + 1) + idx) - L->top.p;\n    for (; diff > 0; diff--)\n      setnilvalue(s2v(L->top.p++));  /* clear new slots */\n  }\n  else {\n    api_check(L, -(idx+1) <= (L->top.p - (func + 1)), \"invalid new top\");\n    diff = idx + 1;  /* will \"subtract\" index (as it is negative) */\n  }\n  api_check(L, L->tbclist.p < L->top.p, \"previous pop of an unclosed slot\");\n  newtop = L->top.p + diff;\n  if (diff < 0 && L->tbclist.p >= newtop) {\n    lua_assert(hastocloseCfunc(ci->nresults));\n    newtop = luaF_close(L, newtop, CLOSEKTOP, 0);\n  }\n  L->top.p = newtop;  /* correct top only after closing any upvalue */\n  lua_unlock(L);\n}\n\n\nLUA_API void lua_closeslot (lua_State *L, int idx) {\n  StkId level;\n  lua_lock(L);\n  level = index2stack(L, idx);\n  api_check(L, hastocloseCfunc(L->ci->nresults) && L->tbclist.p == level,\n     \"no variable to close at given level\");\n  level = luaF_close(L, level, CLOSEKTOP, 0);\n  setnilvalue(s2v(level));\n  lua_unlock(L);\n}\n\n\n/*\n** Reverse the stack segment from 'from' to 'to'\n** (auxiliary to 'lua_rotate')\n** Note that we move(copy) only the value inside the stack.\n** (We do not move additional fields that may exist.)\n*/\nl_sinline void reverse (lua_State *L, StkId from, StkId to) {\n  for (; from < to; from++, to--) {\n    TValue temp;\n    setobj(L, &temp, s2v(from));\n    setobjs2s(L, from, to);\n    setobj2s(L, to, &temp);\n  }\n}\n\n\n/*\n** Let x = AB, where A is a prefix of length 'n'. Then,\n** rotate x n == BA. But BA == (A^r . B^r)^r.\n*/\nLUA_API void lua_rotate (lua_State *L, int idx, int n) {\n  StkId p, t, m;\n  lua_lock(L);\n  t = L->top.p - 1;  /* end of stack segment being rotated */\n  p = index2stack(L, idx);  /* start of segment */\n  api_check(L, (n >= 0 ? n : -n) <= (t - p + 1), \"invalid 'n'\");\n  m = (n >= 0 ? t - n : p - n - 1);  /* end of prefix */\n  reverse(L, p, m);  /* reverse the prefix with length 'n' */\n  reverse(L, m + 1, t);  /* reverse the suffix */\n  reverse(L, p, t);  /* reverse the entire segment */\n  lua_unlock(L);\n}\n\n\nLUA_API void lua_copy (lua_State *L, int fromidx, int toidx) {\n  TValue *fr, *to;\n  lua_lock(L);\n  fr = index2value(L, fromidx);\n  to = index2value(L, toidx);\n  api_check(L, isvalid(L, to), \"invalid index\");\n  setobj(L, to, fr);\n  if (isupvalue(toidx))  /* function upvalue? */\n    luaC_barrier(L, clCvalue(s2v(L->ci->func.p)), fr);\n  /* LUA_REGISTRYINDEX does not need gc barrier\n     (collector revisits it before finishing collection) */\n  lua_unlock(L);\n}\n\n\nLUA_API void lua_pushvalue (lua_State *L, int idx) {\n  lua_lock(L);\n  setobj2s(L, L->top.p, index2value(L, idx));\n  api_incr_top(L);\n  lua_unlock(L);\n}\n\n\n\n/*\n** access functions (stack -> C)\n*/\n\n\nLUA_API int lua_type (lua_State *L, int idx) {\n  const TValue *o = index2value(L, idx);\n  return (isvalid(L, o) ? ttype(o) : LUA_TNONE);\n}\n\n\nLUA_API const char *lua_typename (lua_State *L, int t) {\n  UNUSED(L);\n  api_check(L, LUA_TNONE <= t && t < LUA_NUMTYPES, \"invalid type\");\n  return ttypename(t);\n}\n\n\nLUA_API int lua_iscfunction (lua_State *L, int idx) {\n  const TValue *o = index2value(L, idx);\n  return (ttislcf(o) || (ttisCclosure(o)));\n}\n\n\nLUA_API int lua_isinteger (lua_State *L, int idx) {\n  const TValue *o = index2value(L, idx);\n  return ttisinteger(o);\n}\n\n\nLUA_API int lua_isnumber (lua_State *L, int idx) {\n  lua_Number n;\n  const TValue *o = index2value(L, idx);\n  return tonumber(o, &n);\n}\n\n\nLUA_API int lua_isstring (lua_State *L, int idx) {\n  const TValue *o = index2value(L, idx);\n  return (ttisstring(o) || cvt2str(o));\n}\n\n\nLUA_API int lua_isuserdata (lua_State *L, int idx) {\n  const TValue *o = index2value(L, idx);\n  return (ttisfulluserdata(o) || ttislightuserdata(o));\n}\n\n\nLUA_API int lua_rawequal (lua_State *L, int index1, int index2) {\n  const TValue *o1 = index2value(L, index1);\n  const TValue *o2 = index2value(L, index2);\n  return (isvalid(L, o1) && isvalid(L, o2)) ? luaV_rawequalobj(o1, o2) : 0;\n}\n\n\nLUA_API void lua_arith (lua_State *L, int op) {\n  lua_lock(L);\n  if (op != LUA_OPUNM && op != LUA_OPBNOT)\n    api_checknelems(L, 2);  /* all other operations expect two operands */\n  else {  /* for unary operations, add fake 2nd operand */\n    api_checknelems(L, 1);\n    setobjs2s(L, L->top.p, L->top.p - 1);\n    api_incr_top(L);\n  }\n  /* first operand at top - 2, second at top - 1; result go to top - 2 */\n  luaO_arith(L, op, s2v(L->top.p - 2), s2v(L->top.p - 1), L->top.p - 2);\n  L->top.p--;  /* remove second operand */\n  lua_unlock(L);\n}\n\n\nLUA_API int lua_compare (lua_State *L, int index1, int index2, int op) {\n  const TValue *o1;\n  const TValue *o2;\n  int i = 0;\n  lua_lock(L);  /* may call tag method */\n  o1 = index2value(L, index1);\n  o2 = index2value(L, index2);\n  if (isvalid(L, o1) && isvalid(L, o2)) {\n    switch (op) {\n      case LUA_OPEQ: i = luaV_equalobj(L, o1, o2); break;\n      case LUA_OPLT: i = luaV_lessthan(L, o1, o2); break;\n      case LUA_OPLE: i = luaV_lessequal(L, o1, o2); break;\n      default: api_check(L, 0, \"invalid option\");\n    }\n  }\n  lua_unlock(L);\n  return i;\n}\n\n\nLUA_API size_t lua_stringtonumber (lua_State *L, const char *s) {\n  size_t sz = luaO_str2num(s, s2v(L->top.p));\n  if (sz != 0)\n    api_incr_top(L);\n  return sz;\n}\n\n\nLUA_API lua_Number lua_tonumberx (lua_State *L, int idx, int *pisnum) {\n  lua_Number n = 0;\n  const TValue *o = index2value(L, idx);\n  int isnum = tonumber(o, &n);\n  if (pisnum)\n    *pisnum = isnum;\n  return n;\n}\n\n\nLUA_API lua_Integer lua_tointegerx (lua_State *L, int idx, int *pisnum) {\n  lua_Integer res = 0;\n  const TValue *o = index2value(L, idx);\n  int isnum = tointeger(o, &res);\n  if (pisnum)\n    *pisnum = isnum;\n  return res;\n}\n\n\nLUA_API int lua_toboolean (lua_State *L, int idx) {\n  const TValue *o = index2value(L, idx);\n  return !l_isfalse(o);\n}\n\n\nLUA_API const char *lua_tolstring (lua_State *L, int idx, size_t *len) {\n  TValue *o;\n  lua_lock(L);\n  o = index2value(L, idx);\n  if (!ttisstring(o)) {\n    if (!cvt2str(o)) {  /* not convertible? */\n      if (len != NULL) *len = 0;\n      lua_unlock(L);\n      return NULL;\n    }\n    luaO_tostring(L, o);\n    luaC_checkGC(L);\n    o = index2value(L, idx);  /* previous call may reallocate the stack */\n  }\n  if (len != NULL)\n    *len = vslen(o);\n  lua_unlock(L);\n  return svalue(o);\n}\n\n\nLUA_API lua_Unsigned lua_rawlen (lua_State *L, int idx) {\n  const TValue *o = index2value(L, idx);\n  switch (ttypetag(o)) {\n    case LUA_VSHRSTR: return tsvalue(o)->shrlen;\n    case LUA_VLNGSTR: return tsvalue(o)->u.lnglen;\n    case LUA_VUSERDATA: return uvalue(o)->len;\n    case LUA_VTABLE: return luaH_getn(hvalue(o));\n    default: return 0;\n  }\n}\n\n\nLUA_API lua_CFunction lua_tocfunction (lua_State *L, int idx) {\n  const TValue *o = index2value(L, idx);\n  if (ttislcf(o)) return fvalue(o);\n  else if (ttisCclosure(o))\n    return clCvalue(o)->f;\n  else return NULL;  /* not a C function */\n}\n\n\nl_sinline void *touserdata (const TValue *o) {\n  switch (ttype(o)) {\n    case LUA_TUSERDATA: return getudatamem(uvalue(o));\n    case LUA_TLIGHTUSERDATA: return pvalue(o);\n    default: return NULL;\n  }\n}\n\n\nLUA_API void *lua_touserdata (lua_State *L, int idx) {\n  const TValue *o = index2value(L, idx);\n  return touserdata(o);\n}\n\n\nLUA_API lua_State *lua_tothread (lua_State *L, int idx) {\n  const TValue *o = index2value(L, idx);\n  return (!ttisthread(o)) ? NULL : thvalue(o);\n}\n\n\n/*\n** Returns a pointer to the internal representation of an object.\n** Note that ANSI C does not allow the conversion of a pointer to\n** function to a 'void*', so the conversion here goes through\n** a 'size_t'. (As the returned pointer is only informative, this\n** conversion should not be a problem.)\n*/\nLUA_API const void *lua_topointer (lua_State *L, int idx) {\n  const TValue *o = index2value(L, idx);\n  switch (ttypetag(o)) {\n    case LUA_VLCF: return cast_voidp(cast_sizet(fvalue(o)));\n    case LUA_VUSERDATA: case LUA_VLIGHTUSERDATA:\n      return touserdata(o);\n    default: {\n      if (iscollectable(o))\n        return gcvalue(o);\n      else\n        return NULL;\n    }\n  }\n}\n\n\n\n/*\n** push functions (C -> stack)\n*/\n\n\nLUA_API void lua_pushnil (lua_State *L) {\n  lua_lock(L);\n  setnilvalue(s2v(L->top.p));\n  api_incr_top(L);\n  lua_unlock(L);\n}\n\n\nLUA_API void lua_pushnumber (lua_State *L, lua_Number n) {\n  lua_lock(L);\n  setfltvalue(s2v(L->top.p), n);\n  api_incr_top(L);\n  lua_unlock(L);\n}\n\n\nLUA_API void lua_pushinteger (lua_State *L, lua_Integer n) {\n  lua_lock(L);\n  setivalue(s2v(L->top.p), n);\n  api_incr_top(L);\n  lua_unlock(L);\n}\n\n\n/*\n** Pushes on the stack a string with given length. Avoid using 's' when\n** 'len' == 0 (as 's' can be NULL in that case), due to later use of\n** 'memcmp' and 'memcpy'.\n*/\nLUA_API const char *lua_pushlstring (lua_State *L, const char *s, size_t len) {\n  TString *ts;\n  lua_lock(L);\n  ts = (len == 0) ? luaS_new(L, \"\") : luaS_newlstr(L, s, len);\n  setsvalue2s(L, L->top.p, ts);\n  api_incr_top(L);\n  luaC_checkGC(L);\n  lua_unlock(L);\n  return getstr(ts);\n}\n\n\nLUA_API const char *lua_pushstring (lua_State *L, const char *s) {\n  lua_lock(L);\n  if (s == NULL)\n    setnilvalue(s2v(L->top.p));\n  else {\n    TString *ts;\n    ts = luaS_new(L, s);\n    setsvalue2s(L, L->top.p, ts);\n    s = getstr(ts);  /* internal copy's address */\n  }\n  api_incr_top(L);\n  luaC_checkGC(L);\n  lua_unlock(L);\n  return s;\n}\n\n\nLUA_API const char *lua_pushvfstring (lua_State *L, const char *fmt,\n                                      va_list argp) {\n  const char *ret;\n  lua_lock(L);\n  ret = luaO_pushvfstring(L, fmt, argp);\n  luaC_checkGC(L);\n  lua_unlock(L);\n  return ret;\n}\n\n\nLUA_API const char *lua_pushfstring (lua_State *L, const char *fmt, ...) {\n  const char *ret;\n  va_list argp;\n  lua_lock(L);\n  va_start(argp, fmt);\n  ret = luaO_pushvfstring(L, fmt, argp);\n  va_end(argp);\n  luaC_checkGC(L);\n  lua_unlock(L);\n  return ret;\n}\n\n\nLUA_API void lua_pushcclosure (lua_State *L, lua_CFunction fn, int n) {\n  lua_lock(L);\n  if (n == 0) {\n    setfvalue(s2v(L->top.p), fn);\n    api_incr_top(L);\n  }\n  else {\n    CClosure *cl;\n    api_checknelems(L, n);\n    api_check(L, n <= MAXUPVAL, \"upvalue index too large\");\n    cl = luaF_newCclosure(L, n);\n    cl->f = fn;\n    L->top.p -= n;\n    while (n--) {\n      setobj2n(L, &cl->upvalue[n], s2v(L->top.p + n));\n      /* does not need barrier because closure is white */\n      lua_assert(iswhite(cl));\n    }\n    setclCvalue(L, s2v(L->top.p), cl);\n    api_incr_top(L);\n    luaC_checkGC(L);\n  }\n  lua_unlock(L);\n}\n\n\nLUA_API void lua_pushboolean (lua_State *L, int b) {\n  lua_lock(L);\n  if (b)\n    setbtvalue(s2v(L->top.p));\n  else\n    setbfvalue(s2v(L->top.p));\n  api_incr_top(L);\n  lua_unlock(L);\n}\n\n\nLUA_API void lua_pushlightuserdata (lua_State *L, void *p) {\n  lua_lock(L);\n  setpvalue(s2v(L->top.p), p);\n  api_incr_top(L);\n  lua_unlock(L);\n}\n\n\nLUA_API int lua_pushthread (lua_State *L) {\n  lua_lock(L);\n  setthvalue(L, s2v(L->top.p), L);\n  api_incr_top(L);\n  lua_unlock(L);\n  return (G(L)->mainthread == L);\n}\n\n\n\n/*\n** get functions (Lua -> stack)\n*/\n\n\nl_sinline int auxgetstr (lua_State *L, const TValue *t, const char *k) {\n  const TValue *slot;\n  TString *str = luaS_new(L, k);\n  if (luaV_fastget(L, t, str, slot, luaH_getstr)) {\n    setobj2s(L, L->top.p, slot);\n    api_incr_top(L);\n  }\n  else {\n    setsvalue2s(L, L->top.p, str);\n    api_incr_top(L);\n    luaV_finishget(L, t, s2v(L->top.p - 1), L->top.p - 1, slot);\n  }\n  lua_unlock(L);\n  return ttype(s2v(L->top.p - 1));\n}\n\n\n/*\n** Get the global table in the registry. Since all predefined\n** indices in the registry were inserted right when the registry\n** was created and never removed, they must always be in the array\n** part of the registry.\n*/\n#define getGtable(L)  \\\n\t(&hvalue(&G(L)->l_registry)->array[LUA_RIDX_GLOBALS - 1])\n\n\nLUA_API int lua_getglobal (lua_State *L, const char *name) {\n  const TValue *G;\n  lua_lock(L);\n  G = getGtable(L);\n  return auxgetstr(L, G, name);\n}\n\n\nLUA_API int lua_gettable (lua_State *L, int idx) {\n  const TValue *slot;\n  TValue *t;\n  lua_lock(L);\n  t = index2value(L, idx);\n  if (luaV_fastget(L, t, s2v(L->top.p - 1), slot, luaH_get)) {\n    setobj2s(L, L->top.p - 1, slot);\n  }\n  else\n    luaV_finishget(L, t, s2v(L->top.p - 1), L->top.p - 1, slot);\n  lua_unlock(L);\n  return ttype(s2v(L->top.p - 1));\n}\n\n\nLUA_API int lua_getfield (lua_State *L, int idx, const char *k) {\n  lua_lock(L);\n  return auxgetstr(L, index2value(L, idx), k);\n}\n\n\nLUA_API int lua_geti (lua_State *L, int idx, lua_Integer n) {\n  TValue *t;\n  const TValue *slot;\n  lua_lock(L);\n  t = index2value(L, idx);\n  if (luaV_fastgeti(L, t, n, slot)) {\n    setobj2s(L, L->top.p, slot);\n  }\n  else {\n    TValue aux;\n    setivalue(&aux, n);\n    luaV_finishget(L, t, &aux, L->top.p, slot);\n  }\n  api_incr_top(L);\n  lua_unlock(L);\n  return ttype(s2v(L->top.p - 1));\n}\n\n\nl_sinline int finishrawget (lua_State *L, const TValue *val) {\n  if (isempty(val))  /* avoid copying empty items to the stack */\n    setnilvalue(s2v(L->top.p));\n  else\n    setobj2s(L, L->top.p, val);\n  api_incr_top(L);\n  lua_unlock(L);\n  return ttype(s2v(L->top.p - 1));\n}\n\n\nstatic Table *gettable (lua_State *L, int idx) {\n  TValue *t = index2value(L, idx);\n  api_check(L, ttistable(t), \"table expected\");\n  return hvalue(t);\n}\n\n\nLUA_API int lua_rawget (lua_State *L, int idx) {\n  Table *t;\n  const TValue *val;\n  lua_lock(L);\n  api_checknelems(L, 1);\n  t = gettable(L, idx);\n  val = luaH_get(t, s2v(L->top.p - 1));\n  L->top.p--;  /* remove key */\n  return finishrawget(L, val);\n}\n\n\nLUA_API int lua_rawgeti (lua_State *L, int idx, lua_Integer n) {\n  Table *t;\n  lua_lock(L);\n  t = gettable(L, idx);\n  return finishrawget(L, luaH_getint(t, n));\n}\n\n\nLUA_API int lua_rawgetp (lua_State *L, int idx, const void *p) {\n  Table *t;\n  TValue k;\n  lua_lock(L);\n  t = gettable(L, idx);\n  setpvalue(&k, cast_voidp(p));\n  return finishrawget(L, luaH_get(t, &k));\n}\n\n\nLUA_API void lua_createtable (lua_State *L, int narray, int nrec) {\n  Table *t;\n  lua_lock(L);\n  t = luaH_new(L);\n  sethvalue2s(L, L->top.p, t);\n  api_incr_top(L);\n  if (narray > 0 || nrec > 0)\n    luaH_resize(L, t, narray, nrec);\n  luaC_checkGC(L);\n  lua_unlock(L);\n}\n\n\nLUA_API int lua_getmetatable (lua_State *L, int objindex) {\n  const TValue *obj;\n  Table *mt;\n  int res = 0;\n  lua_lock(L);\n  obj = index2value(L, objindex);\n  switch (ttype(obj)) {\n    case LUA_TTABLE:\n      mt = hvalue(obj)->metatable;\n      break;\n    case LUA_TUSERDATA:\n      mt = uvalue(obj)->metatable;\n      break;\n    default:\n      mt = G(L)->mt[ttype(obj)];\n      break;\n  }\n  if (mt != NULL) {\n    sethvalue2s(L, L->top.p, mt);\n    api_incr_top(L);\n    res = 1;\n  }\n  lua_unlock(L);\n  return res;\n}\n\n\nLUA_API int lua_getiuservalue (lua_State *L, int idx, int n) {\n  TValue *o;\n  int t;\n  lua_lock(L);\n  o = index2value(L, idx);\n  api_check(L, ttisfulluserdata(o), \"full userdata expected\");\n  if (n <= 0 || n > uvalue(o)->nuvalue) {\n    setnilvalue(s2v(L->top.p));\n    t = LUA_TNONE;\n  }\n  else {\n    setobj2s(L, L->top.p, &uvalue(o)->uv[n - 1].uv);\n    t = ttype(s2v(L->top.p));\n  }\n  api_incr_top(L);\n  lua_unlock(L);\n  return t;\n}\n\n\n/*\n** set functions (stack -> Lua)\n*/\n\n/*\n** t[k] = value at the top of the stack (where 'k' is a string)\n*/\nstatic void auxsetstr (lua_State *L, const TValue *t, const char *k) {\n  const TValue *slot;\n  TString *str = luaS_new(L, k);\n  api_checknelems(L, 1);\n  if (luaV_fastget(L, t, str, slot, luaH_getstr)) {\n    luaV_finishfastset(L, t, slot, s2v(L->top.p - 1));\n    L->top.p--;  /* pop value */\n  }\n  else {\n    setsvalue2s(L, L->top.p, str);  /* push 'str' (to make it a TValue) */\n    api_incr_top(L);\n    luaV_finishset(L, t, s2v(L->top.p - 1), s2v(L->top.p - 2), slot);\n    L->top.p -= 2;  /* pop value and key */\n  }\n  lua_unlock(L);  /* lock done by caller */\n}\n\n\nLUA_API void lua_setglobal (lua_State *L, const char *name) {\n  const TValue *G;\n  lua_lock(L);  /* unlock done in 'auxsetstr' */\n  G = getGtable(L);\n  auxsetstr(L, G, name);\n}\n\n\nLUA_API void lua_settable (lua_State *L, int idx) {\n  TValue *t;\n  const TValue *slot;\n  lua_lock(L);\n  api_checknelems(L, 2);\n  t = index2value(L, idx);\n  if (luaV_fastget(L, t, s2v(L->top.p - 2), slot, luaH_get)) {\n    luaV_finishfastset(L, t, slot, s2v(L->top.p - 1));\n  }\n  else\n    luaV_finishset(L, t, s2v(L->top.p - 2), s2v(L->top.p - 1), slot);\n  L->top.p -= 2;  /* pop index and value */\n  lua_unlock(L);\n}\n\n\nLUA_API void lua_setfield (lua_State *L, int idx, const char *k) {\n  lua_lock(L);  /* unlock done in 'auxsetstr' */\n  auxsetstr(L, index2value(L, idx), k);\n}\n\n\nLUA_API void lua_seti (lua_State *L, int idx, lua_Integer n) {\n  TValue *t;\n  const TValue *slot;\n  lua_lock(L);\n  api_checknelems(L, 1);\n  t = index2value(L, idx);\n  if (luaV_fastgeti(L, t, n, slot)) {\n    luaV_finishfastset(L, t, slot, s2v(L->top.p - 1));\n  }\n  else {\n    TValue aux;\n    setivalue(&aux, n);\n    luaV_finishset(L, t, &aux, s2v(L->top.p - 1), slot);\n  }\n  L->top.p--;  /* pop value */\n  lua_unlock(L);\n}\n\n\nstatic void aux_rawset (lua_State *L, int idx, TValue *key, int n) {\n  Table *t;\n  lua_lock(L);\n  api_checknelems(L, n);\n  t = gettable(L, idx);\n  luaH_set(L, t, key, s2v(L->top.p - 1));\n  invalidateTMcache(t);\n  luaC_barrierback(L, obj2gco(t), s2v(L->top.p - 1));\n  L->top.p -= n;\n  lua_unlock(L);\n}\n\n\nLUA_API void lua_rawset (lua_State *L, int idx) {\n  aux_rawset(L, idx, s2v(L->top.p - 2), 2);\n}\n\n\nLUA_API void lua_rawsetp (lua_State *L, int idx, const void *p) {\n  TValue k;\n  setpvalue(&k, cast_voidp(p));\n  aux_rawset(L, idx, &k, 1);\n}\n\n\nLUA_API void lua_rawseti (lua_State *L, int idx, lua_Integer n) {\n  Table *t;\n  lua_lock(L);\n  api_checknelems(L, 1);\n  t = gettable(L, idx);\n  luaH_setint(L, t, n, s2v(L->top.p - 1));\n  luaC_barrierback(L, obj2gco(t), s2v(L->top.p - 1));\n  L->top.p--;\n  lua_unlock(L);\n}\n\n\nLUA_API int lua_setmetatable (lua_State *L, int objindex) {\n  TValue *obj;\n  Table *mt;\n  lua_lock(L);\n  api_checknelems(L, 1);\n  obj = index2value(L, objindex);\n  if (ttisnil(s2v(L->top.p - 1)))\n    mt = NULL;\n  else {\n    api_check(L, ttistable(s2v(L->top.p - 1)), \"table expected\");\n    mt = hvalue(s2v(L->top.p - 1));\n  }\n  switch (ttype(obj)) {\n    case LUA_TTABLE: {\n      hvalue(obj)->metatable = mt;\n      if (mt) {\n        luaC_objbarrier(L, gcvalue(obj), mt);\n        luaC_checkfinalizer(L, gcvalue(obj), mt);\n      }\n      break;\n    }\n    case LUA_TUSERDATA: {\n      uvalue(obj)->metatable = mt;\n      if (mt) {\n        luaC_objbarrier(L, uvalue(obj), mt);\n        luaC_checkfinalizer(L, gcvalue(obj), mt);\n      }\n      break;\n    }\n    default: {\n      G(L)->mt[ttype(obj)] = mt;\n      break;\n    }\n  }\n  L->top.p--;\n  lua_unlock(L);\n  return 1;\n}\n\n\nLUA_API int lua_setiuservalue (lua_State *L, int idx, int n) {\n  TValue *o;\n  int res;\n  lua_lock(L);\n  api_checknelems(L, 1);\n  o = index2value(L, idx);\n  api_check(L, ttisfulluserdata(o), \"full userdata expected\");\n  if (!(cast_uint(n) - 1u < cast_uint(uvalue(o)->nuvalue)))\n    res = 0;  /* 'n' not in [1, uvalue(o)->nuvalue] */\n  else {\n    setobj(L, &uvalue(o)->uv[n - 1].uv, s2v(L->top.p - 1));\n    luaC_barrierback(L, gcvalue(o), s2v(L->top.p - 1));\n    res = 1;\n  }\n  L->top.p--;\n  lua_unlock(L);\n  return res;\n}\n\n\n/*\n** 'load' and 'call' functions (run Lua code)\n*/\n\n\n#define checkresults(L,na,nr) \\\n     api_check(L, (nr) == LUA_MULTRET \\\n               || (L->ci->top.p - L->top.p >= (nr) - (na)), \\\n\t\"results from function overflow current stack size\")\n\n\nLUA_API void lua_callk (lua_State *L, int nargs, int nresults,\n                        lua_KContext ctx, lua_KFunction k) {\n  StkId func;\n  lua_lock(L);\n  api_check(L, k == NULL || !isLua(L->ci),\n    \"cannot use continuations inside hooks\");\n  api_checknelems(L, nargs+1);\n  api_check(L, L->status == LUA_OK, \"cannot do calls on non-normal thread\");\n  checkresults(L, nargs, nresults);\n  func = L->top.p - (nargs+1);\n  if (k != NULL && yieldable(L)) {  /* need to prepare continuation? */\n    L->ci->u.c.k = k;  /* save continuation */\n    L->ci->u.c.ctx = ctx;  /* save context */\n    luaD_call(L, func, nresults);  /* do the call */\n  }\n  else  /* no continuation or no yieldable */\n    luaD_callnoyield(L, func, nresults);  /* just do the call */\n  adjustresults(L, nresults);\n  lua_unlock(L);\n}\n\n\n\n/*\n** Execute a protected call.\n*/\nstruct CallS {  /* data to 'f_call' */\n  StkId func;\n  int nresults;\n};\n\n\nstatic void f_call (lua_State *L, void *ud) {\n  struct CallS *c = cast(struct CallS *, ud);\n  luaD_callnoyield(L, c->func, c->nresults);\n}\n\n\n\nLUA_API int lua_pcallk (lua_State *L, int nargs, int nresults, int errfunc,\n                        lua_KContext ctx, lua_KFunction k) {\n  struct CallS c;\n  int status;\n  ptrdiff_t func;\n  lua_lock(L);\n  api_check(L, k == NULL || !isLua(L->ci),\n    \"cannot use continuations inside hooks\");\n  api_checknelems(L, nargs+1);\n  api_check(L, L->status == LUA_OK, \"cannot do calls on non-normal thread\");\n  checkresults(L, nargs, nresults);\n  if (errfunc == 0)\n    func = 0;\n  else {\n    StkId o = index2stack(L, errfunc);\n    api_check(L, ttisfunction(s2v(o)), \"error handler must be a function\");\n    func = savestack(L, o);\n  }\n  c.func = L->top.p - (nargs+1);  /* function to be called */\n  if (k == NULL || !yieldable(L)) {  /* no continuation or no yieldable? */\n    c.nresults = nresults;  /* do a 'conventional' protected call */\n    status = luaD_pcall(L, f_call, &c, savestack(L, c.func), func);\n  }\n  else {  /* prepare continuation (call is already protected by 'resume') */\n    CallInfo *ci = L->ci;\n    ci->u.c.k = k;  /* save continuation */\n    ci->u.c.ctx = ctx;  /* save context */\n    /* save information for error recovery */\n    ci->u2.funcidx = cast_int(savestack(L, c.func));\n    ci->u.c.old_errfunc = L->errfunc;\n    L->errfunc = func;\n    setoah(ci->callstatus, L->allowhook);  /* save value of 'allowhook' */\n    ci->callstatus |= CIST_YPCALL;  /* function can do error recovery */\n    luaD_call(L, c.func, nresults);  /* do the call */\n    ci->callstatus &= ~CIST_YPCALL;\n    L->errfunc = ci->u.c.old_errfunc;\n    status = LUA_OK;  /* if it is here, there were no errors */\n  }\n  adjustresults(L, nresults);\n  lua_unlock(L);\n  return status;\n}\n\n\nLUA_API int lua_load (lua_State *L, lua_Reader reader, void *data,\n                      const char *chunkname, const char *mode) {\n  ZIO z;\n  int status;\n  lua_lock(L);\n  if (!chunkname) chunkname = \"?\";\n  luaZ_init(L, &z, reader, data);\n  status = luaD_protectedparser(L, &z, chunkname, mode);\n  if (status == LUA_OK) {  /* no errors? */\n    LClosure *f = clLvalue(s2v(L->top.p - 1));  /* get new function */\n    if (f->nupvalues >= 1) {  /* does it have an upvalue? */\n      /* get global table from registry */\n      const TValue *gt = getGtable(L);\n      /* set global table as 1st upvalue of 'f' (may be LUA_ENV) */\n      setobj(L, f->upvals[0]->v.p, gt);\n      luaC_barrier(L, f->upvals[0], gt);\n    }\n  }\n  lua_unlock(L);\n  return status;\n}\n\n\nLUA_API int lua_dump (lua_State *L, lua_Writer writer, void *data, int strip) {\n  int status;\n  TValue *o;\n  lua_lock(L);\n  api_checknelems(L, 1);\n  o = s2v(L->top.p - 1);\n  if (isLfunction(o))\n    status = luaU_dump(L, getproto(o), writer, data, strip);\n  else\n    status = 1;\n  lua_unlock(L);\n  return status;\n}\n\n\nLUA_API int lua_status (lua_State *L) {\n  return L->status;\n}\n\n\n/*\n** Garbage-collection function\n*/\nLUA_API int lua_gc (lua_State *L, int what, ...) {\n  va_list argp;\n  int res = 0;\n  global_State *g = G(L);\n  if (g->gcstp & GCSTPGC)  /* internal stop? */\n    return -1;  /* all options are invalid when stopped */\n  lua_lock(L);\n  va_start(argp, what);\n  switch (what) {\n    case LUA_GCSTOP: {\n      g->gcstp = GCSTPUSR;  /* stopped by the user */\n      break;\n    }\n    case LUA_GCRESTART: {\n      luaE_setdebt(g, 0);\n      g->gcstp = 0;  /* (GCSTPGC must be already zero here) */\n      break;\n    }\n    case LUA_GCCOLLECT: {\n      luaC_fullgc(L, 0);\n      break;\n    }\n    case LUA_GCCOUNT: {\n      /* GC values are expressed in Kbytes: #bytes/2^10 */\n      res = cast_int(gettotalbytes(g) >> 10);\n      break;\n    }\n    case LUA_GCCOUNTB: {\n      res = cast_int(gettotalbytes(g) & 0x3ff);\n      break;\n    }\n    case LUA_GCSTEP: {\n      int data = va_arg(argp, int);\n      l_mem debt = 1;  /* =1 to signal that it did an actual step */\n      lu_byte oldstp = g->gcstp;\n      g->gcstp = 0;  /* allow GC to run (GCSTPGC must be zero here) */\n      if (data == 0) {\n        luaE_setdebt(g, 0);  /* do a basic step */\n        luaC_step(L);\n      }\n      else {  /* add 'data' to total debt */\n        debt = cast(l_mem, data) * 1024 + g->GCdebt;\n        luaE_setdebt(g, debt);\n        luaC_checkGC(L);\n      }\n      g->gcstp = oldstp;  /* restore previous state */\n      if (debt > 0 && g->gcstate == GCSpause)  /* end of cycle? */\n        res = 1;  /* signal it */\n      break;\n    }\n    case LUA_GCSETPAUSE: {\n      int data = va_arg(argp, int);\n      res = getgcparam(g->gcpause);\n      setgcparam(g->gcpause, data);\n      break;\n    }\n    case LUA_GCSETSTEPMUL: {\n      int data = va_arg(argp, int);\n      res = getgcparam(g->gcstepmul);\n      setgcparam(g->gcstepmul, data);\n      break;\n    }\n    case LUA_GCISRUNNING: {\n      res = gcrunning(g);\n      break;\n    }\n    case LUA_GCGEN: {\n      int minormul = va_arg(argp, int);\n      int majormul = va_arg(argp, int);\n      res = isdecGCmodegen(g) ? LUA_GCGEN : LUA_GCINC;\n      if (minormul != 0)\n        g->genminormul = minormul;\n      if (majormul != 0)\n        setgcparam(g->genmajormul, majormul);\n      luaC_changemode(L, KGC_GEN);\n      break;\n    }\n    case LUA_GCINC: {\n      int pause = va_arg(argp, int);\n      int stepmul = va_arg(argp, int);\n      int stepsize = va_arg(argp, int);\n      res = isdecGCmodegen(g) ? LUA_GCGEN : LUA_GCINC;\n      if (pause != 0)\n        setgcparam(g->gcpause, pause);\n      if (stepmul != 0)\n        setgcparam(g->gcstepmul, stepmul);\n      if (stepsize != 0)\n        g->gcstepsize = stepsize;\n      luaC_changemode(L, KGC_INC);\n      break;\n    }\n    default: res = -1;  /* invalid option */\n  }\n  va_end(argp);\n  lua_unlock(L);\n  return res;\n}\n\n\n\n/*\n** miscellaneous functions\n*/\n\n\nLUA_API int lua_error (lua_State *L) {\n  TValue *errobj;\n  lua_lock(L);\n  errobj = s2v(L->top.p - 1);\n  api_checknelems(L, 1);\n  /* error object is the memory error message? */\n  if (ttisshrstring(errobj) && eqshrstr(tsvalue(errobj), G(L)->memerrmsg))\n    luaM_error(L);  /* raise a memory error */\n  else\n    luaG_errormsg(L);  /* raise a regular error */\n  /* code unreachable; will unlock when control actually leaves the kernel */\n  return 0;  /* to avoid warnings */\n}\n\n\nLUA_API int lua_next (lua_State *L, int idx) {\n  Table *t;\n  int more;\n  lua_lock(L);\n  api_checknelems(L, 1);\n  t = gettable(L, idx);\n  more = luaH_next(L, t, L->top.p - 1);\n  if (more) {\n    api_incr_top(L);\n  }\n  else  /* no more elements */\n    L->top.p -= 1;  /* remove key */\n  lua_unlock(L);\n  return more;\n}\n\n\nLUA_API void lua_toclose (lua_State *L, int idx) {\n  int nresults;\n  StkId o;\n  lua_lock(L);\n  o = index2stack(L, idx);\n  nresults = L->ci->nresults;\n  api_check(L, L->tbclist.p < o, \"given index below or equal a marked one\");\n  luaF_newtbcupval(L, o);  /* create new to-be-closed upvalue */\n  if (!hastocloseCfunc(nresults))  /* function not marked yet? */\n    L->ci->nresults = codeNresults(nresults);  /* mark it */\n  lua_assert(hastocloseCfunc(L->ci->nresults));\n  lua_unlock(L);\n}\n\n\nLUA_API void lua_concat (lua_State *L, int n) {\n  lua_lock(L);\n  api_checknelems(L, n);\n  if (n > 0)\n    luaV_concat(L, n);\n  else {  /* nothing to concatenate */\n    setsvalue2s(L, L->top.p, luaS_newlstr(L, \"\", 0));  /* push empty string */\n    api_incr_top(L);\n  }\n  luaC_checkGC(L);\n  lua_unlock(L);\n}\n\n\nLUA_API void lua_len (lua_State *L, int idx) {\n  TValue *t;\n  lua_lock(L);\n  t = index2value(L, idx);\n  luaV_objlen(L, L->top.p, t);\n  api_incr_top(L);\n  lua_unlock(L);\n}\n\n\nLUA_API lua_Alloc lua_getallocf (lua_State *L, void **ud) {\n  lua_Alloc f;\n  lua_lock(L);\n  if (ud) *ud = G(L)->ud;\n  f = G(L)->frealloc;\n  lua_unlock(L);\n  return f;\n}\n\n\nLUA_API void lua_setallocf (lua_State *L, lua_Alloc f, void *ud) {\n  lua_lock(L);\n  G(L)->ud = ud;\n  G(L)->frealloc = f;\n  lua_unlock(L);\n}\n\n\nvoid lua_setwarnf (lua_State *L, lua_WarnFunction f, void *ud) {\n  lua_lock(L);\n  G(L)->ud_warn = ud;\n  G(L)->warnf = f;\n  lua_unlock(L);\n}\n\n\nvoid lua_warning (lua_State *L, const char *msg, int tocont) {\n  lua_lock(L);\n  luaE_warning(L, msg, tocont);\n  lua_unlock(L);\n}\n\n\n\nLUA_API void *lua_newuserdatauv (lua_State *L, size_t size, int nuvalue) {\n  Udata *u;\n  lua_lock(L);\n  api_check(L, 0 <= nuvalue && nuvalue < USHRT_MAX, \"invalid value\");\n  u = luaS_newudata(L, size, nuvalue);\n  setuvalue(L, s2v(L->top.p), u);\n  api_incr_top(L);\n  luaC_checkGC(L);\n  lua_unlock(L);\n  return getudatamem(u);\n}\n\n\n\nstatic const char *aux_upvalue (TValue *fi, int n, TValue **val,\n                                GCObject **owner) {\n  switch (ttypetag(fi)) {\n    case LUA_VCCL: {  /* C closure */\n      CClosure *f = clCvalue(fi);\n      if (!(cast_uint(n) - 1u < cast_uint(f->nupvalues)))\n        return NULL;  /* 'n' not in [1, f->nupvalues] */\n      *val = &f->upvalue[n-1];\n      if (owner) *owner = obj2gco(f);\n      return \"\";\n    }\n    case LUA_VLCL: {  /* Lua closure */\n      LClosure *f = clLvalue(fi);\n      TString *name;\n      Proto *p = f->p;\n      if (!(cast_uint(n) - 1u  < cast_uint(p->sizeupvalues)))\n        return NULL;  /* 'n' not in [1, p->sizeupvalues] */\n      *val = f->upvals[n-1]->v.p;\n      if (owner) *owner = obj2gco(f->upvals[n - 1]);\n      name = p->upvalues[n-1].name;\n      return (name == NULL) ? \"(no name)\" : getstr(name);\n    }\n    default: return NULL;  /* not a closure */\n  }\n}\n\n\nLUA_API const char *lua_getupvalue (lua_State *L, int funcindex, int n) {\n  const char *name;\n  TValue *val = NULL;  /* to avoid warnings */\n  lua_lock(L);\n  name = aux_upvalue(index2value(L, funcindex), n, &val, NULL);\n  if (name) {\n    setobj2s(L, L->top.p, val);\n    api_incr_top(L);\n  }\n  lua_unlock(L);\n  return name;\n}\n\n\nLUA_API const char *lua_setupvalue (lua_State *L, int funcindex, int n) {\n  const char *name;\n  TValue *val = NULL;  /* to avoid warnings */\n  GCObject *owner = NULL;  /* to avoid warnings */\n  TValue *fi;\n  lua_lock(L);\n  fi = index2value(L, funcindex);\n  api_checknelems(L, 1);\n  name = aux_upvalue(fi, n, &val, &owner);\n  if (name) {\n    L->top.p--;\n    setobj(L, val, s2v(L->top.p));\n    luaC_barrier(L, owner, val);\n  }\n  lua_unlock(L);\n  return name;\n}\n\n\nstatic UpVal **getupvalref (lua_State *L, int fidx, int n, LClosure **pf) {\n  static const UpVal *const nullup = NULL;\n  LClosure *f;\n  TValue *fi = index2value(L, fidx);\n  api_check(L, ttisLclosure(fi), \"Lua function expected\");\n  f = clLvalue(fi);\n  if (pf) *pf = f;\n  if (1 <= n && n <= f->p->sizeupvalues)\n    return &f->upvals[n - 1];  /* get its upvalue pointer */\n  else\n    return (UpVal**)&nullup;\n}\n\n\nLUA_API void *lua_upvalueid (lua_State *L, int fidx, int n) {\n  TValue *fi = index2value(L, fidx);\n  switch (ttypetag(fi)) {\n    case LUA_VLCL: {  /* lua closure */\n      return *getupvalref(L, fidx, n, NULL);\n    }\n    case LUA_VCCL: {  /* C closure */\n      CClosure *f = clCvalue(fi);\n      if (1 <= n && n <= f->nupvalues)\n        return &f->upvalue[n - 1];\n      /* else */\n    }  /* FALLTHROUGH */\n    case LUA_VLCF:\n      return NULL;  /* light C functions have no upvalues */\n    default: {\n      api_check(L, 0, \"function expected\");\n      return NULL;\n    }\n  }\n}\n\n\nLUA_API void lua_upvaluejoin (lua_State *L, int fidx1, int n1,\n                                            int fidx2, int n2) {\n  LClosure *f1;\n  UpVal **up1 = getupvalref(L, fidx1, n1, &f1);\n  UpVal **up2 = getupvalref(L, fidx2, n2, NULL);\n  api_check(L, *up1 != NULL && *up2 != NULL, \"invalid upvalue index\");\n  *up1 = *up2;\n  luaC_objbarrier(L, f1, *up1);\n}\n\n\n/*\n** $Id: lauxlib.c $\n** Auxiliary functions for building Lua libraries\n** See Copyright Notice in lua.h\n*/\n\n#define lauxlib_c\n#define LUA_LIB\n\n/*#include \"lprefix.h\"*/\n\n\n#include <errno.h>\n#include <stdarg.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n\n/*\n** This file uses only the official API of Lua.\n** Any function declared here could be written as an application function.\n*/\n\n/*#include \"lua.h\"*/\n\n/*#include \"lauxlib.h\"*/\n\n\n#if !defined(MAX_SIZET)\n/* maximum value for size_t */\n#define MAX_SIZET\t((size_t)(~(size_t)0))\n#endif\n\n\n/*\n** {======================================================\n** Traceback\n** =======================================================\n*/\n\n\n#define LEVELS1\t10\t/* size of the first part of the stack */\n#define LEVELS2\t11\t/* size of the second part of the stack */\n\n\n\n/*\n** Search for 'objidx' in table at index -1. ('objidx' must be an\n** absolute index.) Return 1 + string at top if it found a good name.\n*/\nstatic int findfield (lua_State *L, int objidx, int level) {\n  if (level == 0 || !lua_istable(L, -1))\n    return 0;  /* not found */\n  lua_pushnil(L);  /* start 'next' loop */\n  while (lua_next(L, -2)) {  /* for each pair in table */\n    if (lua_type(L, -2) == LUA_TSTRING) {  /* ignore non-string keys */\n      if (lua_rawequal(L, objidx, -1)) {  /* found object? */\n        lua_pop(L, 1);  /* remove value (but keep name) */\n        return 1;\n      }\n      else if (findfield(L, objidx, level - 1)) {  /* try recursively */\n        /* stack: lib_name, lib_table, field_name (top) */\n        lua_pushliteral(L, \".\");  /* place '.' between the two names */\n        lua_replace(L, -3);  /* (in the slot occupied by table) */\n        lua_concat(L, 3);  /* lib_name.field_name */\n        return 1;\n      }\n    }\n    lua_pop(L, 1);  /* remove value */\n  }\n  return 0;  /* not found */\n}\n\n\n/*\n** Search for a name for a function in all loaded modules\n*/\nstatic int pushglobalfuncname (lua_State *L, lua_Debug *ar) {\n  int top = lua_gettop(L);\n  lua_getinfo(L, \"f\", ar);  /* push function */\n  lua_getfield(L, LUA_REGISTRYINDEX, LUA_LOADED_TABLE);\n  if (findfield(L, top + 1, 2)) {\n    const char *name = lua_tostring(L, -1);\n    if (strncmp(name, LUA_GNAME \".\", 3) == 0) {  /* name start with '_G.'? */\n      lua_pushstring(L, name + 3);  /* push name without prefix */\n      lua_remove(L, -2);  /* remove original name */\n    }\n    lua_copy(L, -1, top + 1);  /* copy name to proper place */\n    lua_settop(L, top + 1);  /* remove table \"loaded\" and name copy */\n    return 1;\n  }\n  else {\n    lua_settop(L, top);  /* remove function and global table */\n    return 0;\n  }\n}\n\n\nstatic void pushfuncname (lua_State *L, lua_Debug *ar) {\n  if (pushglobalfuncname(L, ar)) {  /* try first a global name */\n    lua_pushfstring(L, \"function '%s'\", lua_tostring(L, -1));\n    lua_remove(L, -2);  /* remove name */\n  }\n  else if (*ar->namewhat != '\\0')  /* is there a name from code? */\n    lua_pushfstring(L, \"%s '%s'\", ar->namewhat, ar->name);  /* use it */\n  else if (*ar->what == 'm')  /* main? */\n      lua_pushliteral(L, \"main chunk\");\n  else if (*ar->what != 'C')  /* for Lua functions, use <file:line> */\n    lua_pushfstring(L, \"function <%s:%d>\", ar->short_src, ar->linedefined);\n  else  /* nothing left... */\n    lua_pushliteral(L, \"?\");\n}\n\n\nstatic int lastlevel (lua_State *L) {\n  lua_Debug ar;\n  int li = 1, le = 1;\n  /* find an upper bound */\n  while (lua_getstack(L, le, &ar)) { li = le; le *= 2; }\n  /* do a binary search */\n  while (li < le) {\n    int m = (li + le)/2;\n    if (lua_getstack(L, m, &ar)) li = m + 1;\n    else le = m;\n  }\n  return le - 1;\n}\n\n\nLUALIB_API void luaL_traceback (lua_State *L, lua_State *L1,\n                                const char *msg, int level) {\n  luaL_Buffer b;\n  lua_Debug ar;\n  int last = lastlevel(L1);\n  int limit2show = (last - level > LEVELS1 + LEVELS2) ? LEVELS1 : -1;\n  luaL_buffinit(L, &b);\n  if (msg) {\n    luaL_addstring(&b, msg);\n    luaL_addchar(&b, '\\n');\n  }\n  luaL_addstring(&b, \"stack traceback:\");\n  while (lua_getstack(L1, level++, &ar)) {\n    if (limit2show-- == 0) {  /* too many levels? */\n      int n = last - level - LEVELS2 + 1;  /* number of levels to skip */\n      lua_pushfstring(L, \"\\n\\t...\\t(skipping %d levels)\", n);\n      luaL_addvalue(&b);  /* add warning about skip */\n      level += n;  /* and skip to last levels */\n    }\n    else {\n      lua_getinfo(L1, \"Slnt\", &ar);\n      if (ar.currentline <= 0)\n        lua_pushfstring(L, \"\\n\\t%s: in \", ar.short_src);\n      else\n        lua_pushfstring(L, \"\\n\\t%s:%d: in \", ar.short_src, ar.currentline);\n      luaL_addvalue(&b);\n      pushfuncname(L, &ar);\n      luaL_addvalue(&b);\n      if (ar.istailcall)\n        luaL_addstring(&b, \"\\n\\t(...tail calls...)\");\n    }\n  }\n  luaL_pushresult(&b);\n}\n\n/* }====================================================== */\n\n\n/*\n** {======================================================\n** Error-report functions\n** =======================================================\n*/\n\nLUALIB_API int luaL_argerror (lua_State *L, int arg, const char *extramsg) {\n  lua_Debug ar;\n  if (!lua_getstack(L, 0, &ar))  /* no stack frame? */\n    return luaL_error(L, \"bad argument #%d (%s)\", arg, extramsg);\n  lua_getinfo(L, \"n\", &ar);\n  if (strcmp(ar.namewhat, \"method\") == 0) {\n    arg--;  /* do not count 'self' */\n    if (arg == 0)  /* error is in the self argument itself? */\n      return luaL_error(L, \"calling '%s' on bad self (%s)\",\n                           ar.name, extramsg);\n  }\n  if (ar.name == NULL)\n    ar.name = (pushglobalfuncname(L, &ar)) ? lua_tostring(L, -1) : \"?\";\n  return luaL_error(L, \"bad argument #%d to '%s' (%s)\",\n                        arg, ar.name, extramsg);\n}\n\n\nLUALIB_API int luaL_typeerror (lua_State *L, int arg, const char *tname) {\n  const char *msg;\n  const char *typearg;  /* name for the type of the actual argument */\n  if (luaL_getmetafield(L, arg, \"__name\") == LUA_TSTRING)\n    typearg = lua_tostring(L, -1);  /* use the given type name */\n  else if (lua_type(L, arg) == LUA_TLIGHTUSERDATA)\n    typearg = \"light userdata\";  /* special name for messages */\n  else\n    typearg = luaL_typename(L, arg);  /* standard name */\n  msg = lua_pushfstring(L, \"%s expected, got %s\", tname, typearg);\n  return luaL_argerror(L, arg, msg);\n}\n\n\nstatic void tag_error (lua_State *L, int arg, int tag) {\n  luaL_typeerror(L, arg, lua_typename(L, tag));\n}\n\n\n/*\n** The use of 'lua_pushfstring' ensures this function does not\n** need reserved stack space when called.\n*/\nLUALIB_API void luaL_where (lua_State *L, int level) {\n  lua_Debug ar;\n  if (lua_getstack(L, level, &ar)) {  /* check function at level */\n    lua_getinfo(L, \"Sl\", &ar);  /* get info about it */\n    if (ar.currentline > 0) {  /* is there info? */\n      lua_pushfstring(L, \"%s:%d: \", ar.short_src, ar.currentline);\n      return;\n    }\n  }\n  lua_pushfstring(L, \"\");  /* else, no information available... */\n}\n\n\n/*\n** Again, the use of 'lua_pushvfstring' ensures this function does\n** not need reserved stack space when called. (At worst, it generates\n** an error with \"stack overflow\" instead of the given message.)\n*/\nLUALIB_API int luaL_error (lua_State *L, const char *fmt, ...) {\n  va_list argp;\n  va_start(argp, fmt);\n  luaL_where(L, 1);\n  lua_pushvfstring(L, fmt, argp);\n  va_end(argp);\n  lua_concat(L, 2);\n  return lua_error(L);\n}\n\n\nLUALIB_API int luaL_fileresult (lua_State *L, int stat, const char *fname) {\n  int en = errno;  /* calls to Lua API may change this value */\n  if (stat) {\n    lua_pushboolean(L, 1);\n    return 1;\n  }\n  else {\n    luaL_pushfail(L);\n    if (fname)\n      lua_pushfstring(L, \"%s: %s\", fname, strerror(en));\n    else\n      lua_pushstring(L, strerror(en));\n    lua_pushinteger(L, en);\n    return 3;\n  }\n}\n\n\n#if !defined(l_inspectstat)\t/* { */\n\n#if defined(LUA_USE_POSIX)\n\n#include <sys/wait.h>\n\n/*\n** use appropriate macros to interpret 'pclose' return status\n*/\n#define l_inspectstat(stat,what)  \\\n   if (WIFEXITED(stat)) { stat = WEXITSTATUS(stat); } \\\n   else if (WIFSIGNALED(stat)) { stat = WTERMSIG(stat); what = \"signal\"; }\n\n#else\n\n#define l_inspectstat(stat,what)  /* no op */\n\n#endif\n\n#endif\t\t\t\t/* } */\n\n\nLUALIB_API int luaL_execresult (lua_State *L, int stat) {\n  if (stat != 0 && errno != 0)  /* error with an 'errno'? */\n    return luaL_fileresult(L, 0, NULL);\n  else {\n    const char *what = \"exit\";  /* type of termination */\n    l_inspectstat(stat, what);  /* interpret result */\n    if (*what == 'e' && stat == 0)  /* successful termination? */\n      lua_pushboolean(L, 1);\n    else\n      luaL_pushfail(L);\n    lua_pushstring(L, what);\n    lua_pushinteger(L, stat);\n    return 3;  /* return true/fail,what,code */\n  }\n}\n\n/* }====================================================== */\n\n\n\n/*\n** {======================================================\n** Userdata's metatable manipulation\n** =======================================================\n*/\n\nLUALIB_API int luaL_newmetatable (lua_State *L, const char *tname) {\n  if (luaL_getmetatable(L, tname) != LUA_TNIL)  /* name already in use? */\n    return 0;  /* leave previous value on top, but return 0 */\n  lua_pop(L, 1);\n  lua_createtable(L, 0, 2);  /* create metatable */\n  lua_pushstring(L, tname);\n  lua_setfield(L, -2, \"__name\");  /* metatable.__name = tname */\n  lua_pushvalue(L, -1);\n  lua_setfield(L, LUA_REGISTRYINDEX, tname);  /* registry.name = metatable */\n  return 1;\n}\n\n\nLUALIB_API void luaL_setmetatable (lua_State *L, const char *tname) {\n  luaL_getmetatable(L, tname);\n  lua_setmetatable(L, -2);\n}\n\n\nLUALIB_API void *luaL_testudata (lua_State *L, int ud, const char *tname) {\n  void *p = lua_touserdata(L, ud);\n  if (p != NULL) {  /* value is a userdata? */\n    if (lua_getmetatable(L, ud)) {  /* does it have a metatable? */\n      luaL_getmetatable(L, tname);  /* get correct metatable */\n      if (!lua_rawequal(L, -1, -2))  /* not the same? */\n        p = NULL;  /* value is a userdata with wrong metatable */\n      lua_pop(L, 2);  /* remove both metatables */\n      return p;\n    }\n  }\n  return NULL;  /* value is not a userdata with a metatable */\n}\n\n\nLUALIB_API void *luaL_checkudata (lua_State *L, int ud, const char *tname) {\n  void *p = luaL_testudata(L, ud, tname);\n  luaL_argexpected(L, p != NULL, ud, tname);\n  return p;\n}\n\n/* }====================================================== */\n\n\n/*\n** {======================================================\n** Argument check functions\n** =======================================================\n*/\n\nLUALIB_API int luaL_checkoption (lua_State *L, int arg, const char *def,\n                                 const char *const lst[]) {\n  const char *name = (def) ? luaL_optstring(L, arg, def) :\n                             luaL_checkstring(L, arg);\n  int i;\n  for (i=0; lst[i]; i++)\n    if (strcmp(lst[i], name) == 0)\n      return i;\n  return luaL_argerror(L, arg,\n                       lua_pushfstring(L, \"invalid option '%s'\", name));\n}\n\n\n/*\n** Ensures the stack has at least 'space' extra slots, raising an error\n** if it cannot fulfill the request. (The error handling needs a few\n** extra slots to format the error message. In case of an error without\n** this extra space, Lua will generate the same 'stack overflow' error,\n** but without 'msg'.)\n*/\nLUALIB_API void luaL_checkstack (lua_State *L, int space, const char *msg) {\n  if (l_unlikely(!lua_checkstack(L, space))) {\n    if (msg)\n      luaL_error(L, \"stack overflow (%s)\", msg);\n    else\n      luaL_error(L, \"stack overflow\");\n  }\n}\n\n\nLUALIB_API void luaL_checktype (lua_State *L, int arg, int t) {\n  if (l_unlikely(lua_type(L, arg) != t))\n    tag_error(L, arg, t);\n}\n\n\nLUALIB_API void luaL_checkany (lua_State *L, int arg) {\n  if (l_unlikely(lua_type(L, arg) == LUA_TNONE))\n    luaL_argerror(L, arg, \"value expected\");\n}\n\n\nLUALIB_API const char *luaL_checklstring (lua_State *L, int arg, size_t *len) {\n  const char *s = lua_tolstring(L, arg, len);\n  if (l_unlikely(!s)) tag_error(L, arg, LUA_TSTRING);\n  return s;\n}\n\n\nLUALIB_API const char *luaL_optlstring (lua_State *L, int arg,\n                                        const char *def, size_t *len) {\n  if (lua_isnoneornil(L, arg)) {\n    if (len)\n      *len = (def ? strlen(def) : 0);\n    return def;\n  }\n  else return luaL_checklstring(L, arg, len);\n}\n\n\nLUALIB_API lua_Number luaL_checknumber (lua_State *L, int arg) {\n  int isnum;\n  lua_Number d = lua_tonumberx(L, arg, &isnum);\n  if (l_unlikely(!isnum))\n    tag_error(L, arg, LUA_TNUMBER);\n  return d;\n}\n\n\nLUALIB_API lua_Number luaL_optnumber (lua_State *L, int arg, lua_Number def) {\n  return luaL_opt(L, luaL_checknumber, arg, def);\n}\n\n\nstatic void interror (lua_State *L, int arg) {\n  if (lua_isnumber(L, arg))\n    luaL_argerror(L, arg, \"number has no integer representation\");\n  else\n    tag_error(L, arg, LUA_TNUMBER);\n}\n\n\nLUALIB_API lua_Integer luaL_checkinteger (lua_State *L, int arg) {\n  int isnum;\n  lua_Integer d = lua_tointegerx(L, arg, &isnum);\n  if (l_unlikely(!isnum)) {\n    interror(L, arg);\n  }\n  return d;\n}\n\n\nLUALIB_API lua_Integer luaL_optinteger (lua_State *L, int arg,\n                                                      lua_Integer def) {\n  return luaL_opt(L, luaL_checkinteger, arg, def);\n}\n\n/* }====================================================== */\n\n\n/*\n** {======================================================\n** Generic Buffer manipulation\n** =======================================================\n*/\n\n/* userdata to box arbitrary data */\ntypedef struct UBox {\n  void *box;\n  size_t bsize;\n} UBox;\n\n\nstatic void *resizebox (lua_State *L, int idx, size_t newsize) {\n  void *ud;\n  lua_Alloc allocf = lua_getallocf(L, &ud);\n  UBox *box = (UBox *)lua_touserdata(L, idx);\n  void *temp = allocf(ud, box->box, box->bsize, newsize);\n  if (l_unlikely(temp == NULL && newsize > 0)) {  /* allocation error? */\n    lua_pushliteral(L, \"not enough memory\");\n    lua_error(L);  /* raise a memory error */\n  }\n  box->box = temp;\n  box->bsize = newsize;\n  return temp;\n}\n\n\nstatic int boxgc (lua_State *L) {\n  resizebox(L, 1, 0);\n  return 0;\n}\n\n\nstatic const luaL_Reg boxmt[] = {  /* box metamethods */\n  {\"__gc\", boxgc},\n  {\"__close\", boxgc},\n  {NULL, NULL}\n};\n\n\nstatic void newbox (lua_State *L) {\n  UBox *box = (UBox *)lua_newuserdatauv(L, sizeof(UBox), 0);\n  box->box = NULL;\n  box->bsize = 0;\n  if (luaL_newmetatable(L, \"_UBOX*\"))  /* creating metatable? */\n    luaL_setfuncs(L, boxmt, 0);  /* set its metamethods */\n  lua_setmetatable(L, -2);\n}\n\n\n/*\n** check whether buffer is using a userdata on the stack as a temporary\n** buffer\n*/\n#define buffonstack(B)\t((B)->b != (B)->init.b)\n\n\n/*\n** Whenever buffer is accessed, slot 'idx' must either be a box (which\n** cannot be NULL) or it is a placeholder for the buffer.\n*/\n#define checkbufferlevel(B,idx)  \\\n  lua_assert(buffonstack(B) ? lua_touserdata(B->L, idx) != NULL  \\\n                            : lua_touserdata(B->L, idx) == (void*)B)\n\n\n/*\n** Compute new size for buffer 'B', enough to accommodate extra 'sz'\n** bytes. (The test for \"not big enough\" also gets the case when the\n** computation of 'newsize' overflows.)\n*/\nstatic size_t newbuffsize (luaL_Buffer *B, size_t sz) {\n  size_t newsize = (B->size / 2) * 3;  /* buffer size * 1.5 */\n  if (l_unlikely(MAX_SIZET - sz < B->n))  /* overflow in (B->n + sz)? */\n    return luaL_error(B->L, \"buffer too large\");\n  if (newsize < B->n + sz)  /* not big enough? */\n    newsize = B->n + sz;\n  return newsize;\n}\n\n\n/*\n** Returns a pointer to a free area with at least 'sz' bytes in buffer\n** 'B'. 'boxidx' is the relative position in the stack where is the\n** buffer's box or its placeholder.\n*/\nstatic char *prepbuffsize (luaL_Buffer *B, size_t sz, int boxidx) {\n  checkbufferlevel(B, boxidx);\n  if (B->size - B->n >= sz)  /* enough space? */\n    return B->b + B->n;\n  else {\n    lua_State *L = B->L;\n    char *newbuff;\n    size_t newsize = newbuffsize(B, sz);\n    /* create larger buffer */\n    if (buffonstack(B))  /* buffer already has a box? */\n      newbuff = (char *)resizebox(L, boxidx, newsize);  /* resize it */\n    else {  /* no box yet */\n      lua_remove(L, boxidx);  /* remove placeholder */\n      newbox(L);  /* create a new box */\n      lua_insert(L, boxidx);  /* move box to its intended position */\n      lua_toclose(L, boxidx);\n      newbuff = (char *)resizebox(L, boxidx, newsize);\n      memcpy(newbuff, B->b, B->n * sizeof(char));  /* copy original content */\n    }\n    B->b = newbuff;\n    B->size = newsize;\n    return newbuff + B->n;\n  }\n}\n\n/*\n** returns a pointer to a free area with at least 'sz' bytes\n*/\nLUALIB_API char *luaL_prepbuffsize (luaL_Buffer *B, size_t sz) {\n  return prepbuffsize(B, sz, -1);\n}\n\n\nLUALIB_API void luaL_addlstring (luaL_Buffer *B, const char *s, size_t l) {\n  if (l > 0) {  /* avoid 'memcpy' when 's' can be NULL */\n    char *b = prepbuffsize(B, l, -1);\n    memcpy(b, s, l * sizeof(char));\n    luaL_addsize(B, l);\n  }\n}\n\n\nLUALIB_API void luaL_addstring (luaL_Buffer *B, const char *s) {\n  luaL_addlstring(B, s, strlen(s));\n}\n\n\nLUALIB_API void luaL_pushresult (luaL_Buffer *B) {\n  lua_State *L = B->L;\n  checkbufferlevel(B, -1);\n  lua_pushlstring(L, B->b, B->n);\n  if (buffonstack(B))\n    lua_closeslot(L, -2);  /* close the box */\n  lua_remove(L, -2);  /* remove box or placeholder from the stack */\n}\n\n\nLUALIB_API void luaL_pushresultsize (luaL_Buffer *B, size_t sz) {\n  luaL_addsize(B, sz);\n  luaL_pushresult(B);\n}\n\n\n/*\n** 'luaL_addvalue' is the only function in the Buffer system where the\n** box (if existent) is not on the top of the stack. So, instead of\n** calling 'luaL_addlstring', it replicates the code using -2 as the\n** last argument to 'prepbuffsize', signaling that the box is (or will\n** be) below the string being added to the buffer. (Box creation can\n** trigger an emergency GC, so we should not remove the string from the\n** stack before we have the space guaranteed.)\n*/\nLUALIB_API void luaL_addvalue (luaL_Buffer *B) {\n  lua_State *L = B->L;\n  size_t len;\n  const char *s = lua_tolstring(L, -1, &len);\n  char *b = prepbuffsize(B, len, -2);\n  memcpy(b, s, len * sizeof(char));\n  luaL_addsize(B, len);\n  lua_pop(L, 1);  /* pop string */\n}\n\n\nLUALIB_API void luaL_buffinit (lua_State *L, luaL_Buffer *B) {\n  B->L = L;\n  B->b = B->init.b;\n  B->n = 0;\n  B->size = LUAL_BUFFERSIZE;\n  lua_pushlightuserdata(L, (void*)B);  /* push placeholder */\n}\n\n\nLUALIB_API char *luaL_buffinitsize (lua_State *L, luaL_Buffer *B, size_t sz) {\n  luaL_buffinit(L, B);\n  return prepbuffsize(B, sz, -1);\n}\n\n/* }====================================================== */\n\n\n/*\n** {======================================================\n** Reference system\n** =======================================================\n*/\n\n/* index of free-list header (after the predefined values) */\n#define freelist\t(LUA_RIDX_LAST + 1)\n\n/*\n** The previously freed references form a linked list:\n** t[freelist] is the index of a first free index, or zero if list is\n** empty; t[t[freelist]] is the index of the second element; etc.\n*/\nLUALIB_API int luaL_ref (lua_State *L, int t) {\n  int ref;\n  if (lua_isnil(L, -1)) {\n    lua_pop(L, 1);  /* remove from stack */\n    return LUA_REFNIL;  /* 'nil' has a unique fixed reference */\n  }\n  t = lua_absindex(L, t);\n  if (lua_rawgeti(L, t, freelist) == LUA_TNIL) {  /* first access? */\n    ref = 0;  /* list is empty */\n    lua_pushinteger(L, 0);  /* initialize as an empty list */\n    lua_rawseti(L, t, freelist);  /* ref = t[freelist] = 0 */\n  }\n  else {  /* already initialized */\n    lua_assert(lua_isinteger(L, -1));\n    ref = (int)lua_tointeger(L, -1);  /* ref = t[freelist] */\n  }\n  lua_pop(L, 1);  /* remove element from stack */\n  if (ref != 0) {  /* any free element? */\n    lua_rawgeti(L, t, ref);  /* remove it from list */\n    lua_rawseti(L, t, freelist);  /* (t[freelist] = t[ref]) */\n  }\n  else  /* no free elements */\n    ref = (int)lua_rawlen(L, t) + 1;  /* get a new reference */\n  lua_rawseti(L, t, ref);\n  return ref;\n}\n\n\nLUALIB_API void luaL_unref (lua_State *L, int t, int ref) {\n  if (ref >= 0) {\n    t = lua_absindex(L, t);\n    lua_rawgeti(L, t, freelist);\n    lua_assert(lua_isinteger(L, -1));\n    lua_rawseti(L, t, ref);  /* t[ref] = t[freelist] */\n    lua_pushinteger(L, ref);\n    lua_rawseti(L, t, freelist);  /* t[freelist] = ref */\n  }\n}\n\n/* }====================================================== */\n\n\n/*\n** {======================================================\n** Load functions\n** =======================================================\n*/\n\ntypedef struct LoadF {\n  int n;  /* number of pre-read characters */\n  FILE *f;  /* file being read */\n  char buff[BUFSIZ];  /* area for reading file */\n} LoadF;\n\n\nstatic const char *getF (lua_State *L, void *ud, size_t *size) {\n  LoadF *lf = (LoadF *)ud;\n  (void)L;  /* not used */\n  if (lf->n > 0) {  /* are there pre-read characters to be read? */\n    *size = lf->n;  /* return them (chars already in buffer) */\n    lf->n = 0;  /* no more pre-read characters */\n  }\n  else {  /* read a block from file */\n    /* 'fread' can return > 0 *and* set the EOF flag. If next call to\n       'getF' called 'fread', it might still wait for user input.\n       The next check avoids this problem. */\n    if (feof(lf->f)) return NULL;\n    *size = fread(lf->buff, 1, sizeof(lf->buff), lf->f);  /* read block */\n  }\n  return lf->buff;\n}\n\n\nstatic int errfile (lua_State *L, const char *what, int fnameindex) {\n  const char *serr = strerror(errno);\n  const char *filename = lua_tostring(L, fnameindex) + 1;\n  lua_pushfstring(L, \"cannot %s %s: %s\", what, filename, serr);\n  lua_remove(L, fnameindex);\n  return LUA_ERRFILE;\n}\n\n\n/*\n** Skip an optional BOM at the start of a stream. If there is an\n** incomplete BOM (the first character is correct but the rest is\n** not), returns the first character anyway to force an error\n** (as no chunk can start with 0xEF).\n*/\nstatic int skipBOM (FILE *f) {\n  int c = getc(f);  /* read first character */\n  if (c == 0xEF && getc(f) == 0xBB && getc(f) == 0xBF)  /* correct BOM? */\n    return getc(f);  /* ignore BOM and return next char */\n  else  /* no (valid) BOM */\n    return c;  /* return first character */\n}\n\n\n/*\n** reads the first character of file 'f' and skips an optional BOM mark\n** in its beginning plus its first line if it starts with '#'. Returns\n** true if it skipped the first line.  In any case, '*cp' has the\n** first \"valid\" character of the file (after the optional BOM and\n** a first-line comment).\n*/\nstatic int skipcomment (FILE *f, int *cp) {\n  int c = *cp = skipBOM(f);\n  if (c == '#') {  /* first line is a comment (Unix exec. file)? */\n    do {  /* skip first line */\n      c = getc(f);\n    } while (c != EOF && c != '\\n');\n    *cp = getc(f);  /* next character after comment, if present */\n    return 1;  /* there was a comment */\n  }\n  else return 0;  /* no comment */\n}\n\n\nLUALIB_API int luaL_loadfilex (lua_State *L, const char *filename,\n                                             const char *mode) {\n  LoadF lf;\n  int status, readstatus;\n  int c;\n  int fnameindex = lua_gettop(L) + 1;  /* index of filename on the stack */\n  if (filename == NULL) {\n    lua_pushliteral(L, \"=stdin\");\n    lf.f = stdin;\n  }\n  else {\n    lua_pushfstring(L, \"@%s\", filename);\n    lf.f = fopen(filename, \"r\");\n    if (lf.f == NULL) return errfile(L, \"open\", fnameindex);\n  }\n  lf.n = 0;\n  if (skipcomment(lf.f, &c))  /* read initial portion */\n    lf.buff[lf.n++] = '\\n';  /* add newline to correct line numbers */\n  if (c == LUA_SIGNATURE[0]) {  /* binary file? */\n    lf.n = 0;  /* remove possible newline */\n    if (filename) {  /* \"real\" file? */\n      lf.f = freopen(filename, \"rb\", lf.f);  /* reopen in binary mode */\n      if (lf.f == NULL) return errfile(L, \"reopen\", fnameindex);\n      skipcomment(lf.f, &c);  /* re-read initial portion */\n    }\n  }\n  if (c != EOF)\n    lf.buff[lf.n++] = c;  /* 'c' is the first character of the stream */\n  status = lua_load(L, getF, &lf, lua_tostring(L, -1), mode);\n  readstatus = ferror(lf.f);\n  if (filename) fclose(lf.f);  /* close file (even in case of errors) */\n  if (readstatus) {\n    lua_settop(L, fnameindex);  /* ignore results from 'lua_load' */\n    return errfile(L, \"read\", fnameindex);\n  }\n  lua_remove(L, fnameindex);\n  return status;\n}\n\n\ntypedef struct LoadS {\n  const char *s;\n  size_t size;\n} LoadS;\n\n\nstatic const char *getS (lua_State *L, void *ud, size_t *size) {\n  LoadS *ls = (LoadS *)ud;\n  (void)L;  /* not used */\n  if (ls->size == 0) return NULL;\n  *size = ls->size;\n  ls->size = 0;\n  return ls->s;\n}\n\n\nLUALIB_API int luaL_loadbufferx (lua_State *L, const char *buff, size_t size,\n                                 const char *name, const char *mode) {\n  LoadS ls;\n  ls.s = buff;\n  ls.size = size;\n  return lua_load(L, getS, &ls, name, mode);\n}\n\n\nLUALIB_API int luaL_loadstring (lua_State *L, const char *s) {\n  return luaL_loadbuffer(L, s, strlen(s), s);\n}\n\n/* }====================================================== */\n\n\n\nLUALIB_API int luaL_getmetafield (lua_State *L, int obj, const char *event) {\n  if (!lua_getmetatable(L, obj))  /* no metatable? */\n    return LUA_TNIL;\n  else {\n    int tt;\n    lua_pushstring(L, event);\n    tt = lua_rawget(L, -2);\n    if (tt == LUA_TNIL)  /* is metafield nil? */\n      lua_pop(L, 2);  /* remove metatable and metafield */\n    else\n      lua_remove(L, -2);  /* remove only metatable */\n    return tt;  /* return metafield type */\n  }\n}\n\n\nLUALIB_API int luaL_callmeta (lua_State *L, int obj, const char *event) {\n  obj = lua_absindex(L, obj);\n  if (luaL_getmetafield(L, obj, event) == LUA_TNIL)  /* no metafield? */\n    return 0;\n  lua_pushvalue(L, obj);\n  lua_call(L, 1, 1);\n  return 1;\n}\n\n\nLUALIB_API lua_Integer luaL_len (lua_State *L, int idx) {\n  lua_Integer l;\n  int isnum;\n  lua_len(L, idx);\n  l = lua_tointegerx(L, -1, &isnum);\n  if (l_unlikely(!isnum))\n    luaL_error(L, \"object length is not an integer\");\n  lua_pop(L, 1);  /* remove object */\n  return l;\n}\n\n\nLUALIB_API const char *luaL_tolstring (lua_State *L, int idx, size_t *len) {\n  idx = lua_absindex(L,idx);\n  if (luaL_callmeta(L, idx, \"__tostring\")) {  /* metafield? */\n    if (!lua_isstring(L, -1))\n      luaL_error(L, \"'__tostring' must return a string\");\n  }\n  else {\n    switch (lua_type(L, idx)) {\n      case LUA_TNUMBER: {\n        if (lua_isinteger(L, idx))\n          lua_pushfstring(L, \"%I\", (LUAI_UACINT)lua_tointeger(L, idx));\n        else\n          lua_pushfstring(L, \"%f\", (LUAI_UACNUMBER)lua_tonumber(L, idx));\n        break;\n      }\n      case LUA_TSTRING:\n        lua_pushvalue(L, idx);\n        break;\n      case LUA_TBOOLEAN:\n        lua_pushstring(L, (lua_toboolean(L, idx) ? \"true\" : \"false\"));\n        break;\n      case LUA_TNIL:\n        lua_pushliteral(L, \"nil\");\n        break;\n      default: {\n        int tt = luaL_getmetafield(L, idx, \"__name\");  /* try name */\n        const char *kind = (tt == LUA_TSTRING) ? lua_tostring(L, -1) :\n                                                 luaL_typename(L, idx);\n        lua_pushfstring(L, \"%s: %p\", kind, lua_topointer(L, idx));\n        if (tt != LUA_TNIL)\n          lua_remove(L, -2);  /* remove '__name' */\n        break;\n      }\n    }\n  }\n  return lua_tolstring(L, -1, len);\n}\n\n\n/*\n** set functions from list 'l' into table at top - 'nup'; each\n** function gets the 'nup' elements at the top as upvalues.\n** Returns with only the table at the stack.\n*/\nLUALIB_API void luaL_setfuncs (lua_State *L, const luaL_Reg *l, int nup) {\n  luaL_checkstack(L, nup, \"too many upvalues\");\n  for (; l->name != NULL; l++) {  /* fill the table with given functions */\n    if (l->func == NULL)  /* place holder? */\n      lua_pushboolean(L, 0);\n    else {\n      int i;\n      for (i = 0; i < nup; i++)  /* copy upvalues to the top */\n        lua_pushvalue(L, -nup);\n      lua_pushcclosure(L, l->func, nup);  /* closure with those upvalues */\n    }\n    lua_setfield(L, -(nup + 2), l->name);\n  }\n  lua_pop(L, nup);  /* remove upvalues */\n}\n\n\n/*\n** ensure that stack[idx][fname] has a table and push that table\n** into the stack\n*/\nLUALIB_API int luaL_getsubtable (lua_State *L, int idx, const char *fname) {\n  if (lua_getfield(L, idx, fname) == LUA_TTABLE)\n    return 1;  /* table already there */\n  else {\n    lua_pop(L, 1);  /* remove previous result */\n    idx = lua_absindex(L, idx);\n    lua_newtable(L);\n    lua_pushvalue(L, -1);  /* copy to be left at top */\n    lua_setfield(L, idx, fname);  /* assign new table to field */\n    return 0;  /* false, because did not find table there */\n  }\n}\n\n\n/*\n** Stripped-down 'require': After checking \"loaded\" table, calls 'openf'\n** to open a module, registers the result in 'package.loaded' table and,\n** if 'glb' is true, also registers the result in the global table.\n** Leaves resulting module on the top.\n*/\nLUALIB_API void luaL_requiref (lua_State *L, const char *modname,\n                               lua_CFunction openf, int glb) {\n  luaL_getsubtable(L, LUA_REGISTRYINDEX, LUA_LOADED_TABLE);\n  lua_getfield(L, -1, modname);  /* LOADED[modname] */\n  if (!lua_toboolean(L, -1)) {  /* package not already loaded? */\n    lua_pop(L, 1);  /* remove field */\n    lua_pushcfunction(L, openf);\n    lua_pushstring(L, modname);  /* argument to open function */\n    lua_call(L, 1, 1);  /* call 'openf' to open module */\n    lua_pushvalue(L, -1);  /* make copy of module (call result) */\n    lua_setfield(L, -3, modname);  /* LOADED[modname] = module */\n  }\n  lua_remove(L, -2);  /* remove LOADED table */\n  if (glb) {\n    lua_pushvalue(L, -1);  /* copy of module */\n    lua_setglobal(L, modname);  /* _G[modname] = module */\n  }\n}\n\n\nLUALIB_API void luaL_addgsub (luaL_Buffer *b, const char *s,\n                                     const char *p, const char *r) {\n  const char *wild;\n  size_t l = strlen(p);\n  while ((wild = strstr(s, p)) != NULL) {\n    luaL_addlstring(b, s, wild - s);  /* push prefix */\n    luaL_addstring(b, r);  /* push replacement in place of pattern */\n    s = wild + l;  /* continue after 'p' */\n  }\n  luaL_addstring(b, s);  /* push last suffix */\n}\n\n\nLUALIB_API const char *luaL_gsub (lua_State *L, const char *s,\n                                  const char *p, const char *r) {\n  luaL_Buffer b;\n  luaL_buffinit(L, &b);\n  luaL_addgsub(&b, s, p, r);\n  luaL_pushresult(&b);\n  return lua_tostring(L, -1);\n}\n\n\nstatic void *l_alloc (void *ud, void *ptr, size_t osize, size_t nsize) {\n  (void)ud; (void)osize;  /* not used */\n  if (nsize == 0) {\n    free(ptr);\n    return NULL;\n  }\n  else\n    return realloc(ptr, nsize);\n}\n\n\nstatic int panic (lua_State *L) {\n  const char *msg = lua_tostring(L, -1);\n  if (msg == NULL) msg = \"error object is not a string\";\n  lua_writestringerror(\"PANIC: unprotected error in call to Lua API (%s)\\n\",\n                        msg);\n  return 0;  /* return to Lua to abort */\n}\n\n\n/*\n** Warning functions:\n** warnfoff: warning system is off\n** warnfon: ready to start a new message\n** warnfcont: previous message is to be continued\n*/\nstatic void warnfoff (void *ud, const char *message, int tocont);\nstatic void warnfon (void *ud, const char *message, int tocont);\nstatic void warnfcont (void *ud, const char *message, int tocont);\n\n\n/*\n** Check whether message is a control message. If so, execute the\n** control or ignore it if unknown.\n*/\nstatic int checkcontrol (lua_State *L, const char *message, int tocont) {\n  if (tocont || *(message++) != '@')  /* not a control message? */\n    return 0;\n  else {\n    if (strcmp(message, \"off\") == 0)\n      lua_setwarnf(L, warnfoff, L);  /* turn warnings off */\n    else if (strcmp(message, \"on\") == 0)\n      lua_setwarnf(L, warnfon, L);   /* turn warnings on */\n    return 1;  /* it was a control message */\n  }\n}\n\n\nstatic void warnfoff (void *ud, const char *message, int tocont) {\n  checkcontrol((lua_State *)ud, message, tocont);\n}\n\n\n/*\n** Writes the message and handle 'tocont', finishing the message\n** if needed and setting the next warn function.\n*/\nstatic void warnfcont (void *ud, const char *message, int tocont) {\n  lua_State *L = (lua_State *)ud;\n  lua_writestringerror(\"%s\", message);  /* write message */\n  if (tocont)  /* not the last part? */\n    lua_setwarnf(L, warnfcont, L);  /* to be continued */\n  else {  /* last part */\n    lua_writestringerror(\"%s\", \"\\n\");  /* finish message with end-of-line */\n    lua_setwarnf(L, warnfon, L);  /* next call is a new message */\n  }\n}\n\n\nstatic void warnfon (void *ud, const char *message, int tocont) {\n  if (checkcontrol((lua_State *)ud, message, tocont))  /* control message? */\n    return;  /* nothing else to be done */\n  lua_writestringerror(\"%s\", \"Lua warning: \");  /* start a new warning */\n  warnfcont(ud, message, tocont);  /* finish processing */\n}\n\n\nLUALIB_API lua_State *luaL_newstate (void) {\n  lua_State *L = lua_newstate(l_alloc, NULL);\n  if (l_likely(L)) {\n    lua_atpanic(L, &panic);\n    lua_setwarnf(L, warnfoff, L);  /* default is warnings off */\n  }\n  return L;\n}\n\n\nLUALIB_API void luaL_checkversion_ (lua_State *L, lua_Number ver, size_t sz) {\n  lua_Number v = lua_version(L);\n  if (sz != LUAL_NUMSIZES)  /* check numeric types */\n    luaL_error(L, \"core and library have incompatible numeric types\");\n  else if (v != ver)\n    luaL_error(L, \"version mismatch: app. needs %f, Lua core provides %f\",\n                  (LUAI_UACNUMBER)ver, (LUAI_UACNUMBER)v);\n}\n\n/*\n** $Id: lbaselib.c $\n** Basic library\n** See Copyright Notice in lua.h\n*/\n\n#define lbaselib_c\n#define LUA_LIB\n\n/*#include \"lprefix.h\"*/\n\n\n#include <ctype.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n/*#include \"lua.h\"*/\n\n/*#include \"lauxlib.h\"*/\n/*#include \"lualib.h\"*/\n\n\nstatic int luaB_print (lua_State *L) {\n  int n = lua_gettop(L);  /* number of arguments */\n  int i;\n  for (i = 1; i <= n; i++) {  /* for each argument */\n    size_t l;\n    const char *s = luaL_tolstring(L, i, &l);  /* convert it to string */\n    if (i > 1)  /* not the first element? */\n      lua_writestring(\"\\t\", 1);  /* add a tab before it */\n    lua_writestring(s, l);  /* print it */\n    lua_pop(L, 1);  /* pop result */\n  }\n  lua_writeline();\n  return 0;\n}\n\n\n/*\n** Creates a warning with all given arguments.\n** Check first for errors; otherwise an error may interrupt\n** the composition of a warning, leaving it unfinished.\n*/\nstatic int luaB_warn (lua_State *L) {\n  int n = lua_gettop(L);  /* number of arguments */\n  int i;\n  luaL_checkstring(L, 1);  /* at least one argument */\n  for (i = 2; i <= n; i++)\n    luaL_checkstring(L, i);  /* make sure all arguments are strings */\n  for (i = 1; i < n; i++)  /* compose warning */\n    lua_warning(L, lua_tostring(L, i), 1);\n  lua_warning(L, lua_tostring(L, n), 0);  /* close warning */\n  return 0;\n}\n\n\n#define SPACECHARS\t\" \\f\\n\\r\\t\\v\"\n\nstatic const char *b_str2int (const char *s, int base, lua_Integer *pn) {\n  lua_Unsigned n = 0;\n  int neg = 0;\n  s += strspn(s, SPACECHARS);  /* skip initial spaces */\n  if (*s == '-') { s++; neg = 1; }  /* handle sign */\n  else if (*s == '+') s++;\n  if (!isalnum((unsigned char)*s))  /* no digit? */\n    return NULL;\n  do {\n    int digit = (isdigit((unsigned char)*s)) ? *s - '0'\n                   : (toupper((unsigned char)*s) - 'A') + 10;\n    if (digit >= base) return NULL;  /* invalid numeral */\n    n = n * base + digit;\n    s++;\n  } while (isalnum((unsigned char)*s));\n  s += strspn(s, SPACECHARS);  /* skip trailing spaces */\n  *pn = (lua_Integer)((neg) ? (0u - n) : n);\n  return s;\n}\n\n\nstatic int luaB_tonumber (lua_State *L) {\n  if (lua_isnoneornil(L, 2)) {  /* standard conversion? */\n    if (lua_type(L, 1) == LUA_TNUMBER) {  /* already a number? */\n      lua_settop(L, 1);  /* yes; return it */\n      return 1;\n    }\n    else {\n      size_t l;\n      const char *s = lua_tolstring(L, 1, &l);\n      if (s != NULL && lua_stringtonumber(L, s) == l + 1)\n        return 1;  /* successful conversion to number */\n      /* else not a number */\n      luaL_checkany(L, 1);  /* (but there must be some parameter) */\n    }\n  }\n  else {\n    size_t l;\n    const char *s;\n    lua_Integer n = 0;  /* to avoid warnings */\n    lua_Integer base = luaL_checkinteger(L, 2);\n    luaL_checktype(L, 1, LUA_TSTRING);  /* no numbers as strings */\n    s = lua_tolstring(L, 1, &l);\n    luaL_argcheck(L, 2 <= base && base <= 36, 2, \"base out of range\");\n    if (b_str2int(s, (int)base, &n) == s + l) {\n      lua_pushinteger(L, n);\n      return 1;\n    }  /* else not a number */\n  }  /* else not a number */\n  luaL_pushfail(L);  /* not a number */\n  return 1;\n}\n\n\nstatic int luaB_error (lua_State *L) {\n  int level = (int)luaL_optinteger(L, 2, 1);\n  lua_settop(L, 1);\n  if (lua_type(L, 1) == LUA_TSTRING && level > 0) {\n    luaL_where(L, level);   /* add extra information */\n    lua_pushvalue(L, 1);\n    lua_concat(L, 2);\n  }\n  return lua_error(L);\n}\n\n\nstatic int luaB_getmetatable (lua_State *L) {\n  luaL_checkany(L, 1);\n  if (!lua_getmetatable(L, 1)) {\n    lua_pushnil(L);\n    return 1;  /* no metatable */\n  }\n  luaL_getmetafield(L, 1, \"__metatable\");\n  return 1;  /* returns either __metatable field (if present) or metatable */\n}\n\n\nstatic int luaB_setmetatable (lua_State *L) {\n  int t = lua_type(L, 2);\n  luaL_checktype(L, 1, LUA_TTABLE);\n  luaL_argexpected(L, t == LUA_TNIL || t == LUA_TTABLE, 2, \"nil or table\");\n  if (l_unlikely(luaL_getmetafield(L, 1, \"__metatable\") != LUA_TNIL))\n    return luaL_error(L, \"cannot change a protected metatable\");\n  lua_settop(L, 2);\n  lua_setmetatable(L, 1);\n  return 1;\n}\n\n\nstatic int luaB_rawequal (lua_State *L) {\n  luaL_checkany(L, 1);\n  luaL_checkany(L, 2);\n  lua_pushboolean(L, lua_rawequal(L, 1, 2));\n  return 1;\n}\n\n\nstatic int luaB_rawlen (lua_State *L) {\n  int t = lua_type(L, 1);\n  luaL_argexpected(L, t == LUA_TTABLE || t == LUA_TSTRING, 1,\n                      \"table or string\");\n  lua_pushinteger(L, lua_rawlen(L, 1));\n  return 1;\n}\n\n\nstatic int luaB_rawget (lua_State *L) {\n  luaL_checktype(L, 1, LUA_TTABLE);\n  luaL_checkany(L, 2);\n  lua_settop(L, 2);\n  lua_rawget(L, 1);\n  return 1;\n}\n\nstatic int luaB_rawset (lua_State *L) {\n  luaL_checktype(L, 1, LUA_TTABLE);\n  luaL_checkany(L, 2);\n  luaL_checkany(L, 3);\n  lua_settop(L, 3);\n  lua_rawset(L, 1);\n  return 1;\n}\n\n\nstatic int pushmode (lua_State *L, int oldmode) {\n  if (oldmode == -1)\n    luaL_pushfail(L);  /* invalid call to 'lua_gc' */\n  else\n    lua_pushstring(L, (oldmode == LUA_GCINC) ? \"incremental\"\n                                             : \"generational\");\n  return 1;\n}\n\n\n/*\n** check whether call to 'lua_gc' was valid (not inside a finalizer)\n*/\n#define checkvalres(res) { if (res == -1) break; }\n\nstatic int luaB_collectgarbage (lua_State *L) {\n  static const char *const opts[] = {\"stop\", \"restart\", \"collect\",\n    \"count\", \"step\", \"setpause\", \"setstepmul\",\n    \"isrunning\", \"generational\", \"incremental\", NULL};\n  static const int optsnum[] = {LUA_GCSTOP, LUA_GCRESTART, LUA_GCCOLLECT,\n    LUA_GCCOUNT, LUA_GCSTEP, LUA_GCSETPAUSE, LUA_GCSETSTEPMUL,\n    LUA_GCISRUNNING, LUA_GCGEN, LUA_GCINC};\n  int o = optsnum[luaL_checkoption(L, 1, \"collect\", opts)];\n  switch (o) {\n    case LUA_GCCOUNT: {\n      int k = lua_gc(L, o);\n      int b = lua_gc(L, LUA_GCCOUNTB);\n      checkvalres(k);\n      lua_pushnumber(L, (lua_Number)k + ((lua_Number)b/1024));\n      return 1;\n    }\n    case LUA_GCSTEP: {\n      int step = (int)luaL_optinteger(L, 2, 0);\n      int res = lua_gc(L, o, step);\n      checkvalres(res);\n      lua_pushboolean(L, res);\n      return 1;\n    }\n    case LUA_GCSETPAUSE:\n    case LUA_GCSETSTEPMUL: {\n      int p = (int)luaL_optinteger(L, 2, 0);\n      int previous = lua_gc(L, o, p);\n      checkvalres(previous);\n      lua_pushinteger(L, previous);\n      return 1;\n    }\n    case LUA_GCISRUNNING: {\n      int res = lua_gc(L, o);\n      checkvalres(res);\n      lua_pushboolean(L, res);\n      return 1;\n    }\n    case LUA_GCGEN: {\n      int minormul = (int)luaL_optinteger(L, 2, 0);\n      int majormul = (int)luaL_optinteger(L, 3, 0);\n      return pushmode(L, lua_gc(L, o, minormul, majormul));\n    }\n    case LUA_GCINC: {\n      int pause = (int)luaL_optinteger(L, 2, 0);\n      int stepmul = (int)luaL_optinteger(L, 3, 0);\n      int stepsize = (int)luaL_optinteger(L, 4, 0);\n      return pushmode(L, lua_gc(L, o, pause, stepmul, stepsize));\n    }\n    default: {\n      int res = lua_gc(L, o);\n      checkvalres(res);\n      lua_pushinteger(L, res);\n      return 1;\n    }\n  }\n  luaL_pushfail(L);  /* invalid call (inside a finalizer) */\n  return 1;\n}\n\n\nstatic int luaB_type (lua_State *L) {\n  int t = lua_type(L, 1);\n  luaL_argcheck(L, t != LUA_TNONE, 1, \"value expected\");\n  lua_pushstring(L, lua_typename(L, t));\n  return 1;\n}\n\n\nstatic int luaB_next (lua_State *L) {\n  luaL_checktype(L, 1, LUA_TTABLE);\n  lua_settop(L, 2);  /* create a 2nd argument if there isn't one */\n  if (lua_next(L, 1))\n    return 2;\n  else {\n    lua_pushnil(L);\n    return 1;\n  }\n}\n\n\nstatic int pairscont (lua_State *L, int status, lua_KContext k) {\n  (void)L; (void)status; (void)k;  /* unused */\n  return 3;\n}\n\nstatic int luaB_pairs (lua_State *L) {\n  luaL_checkany(L, 1);\n  if (luaL_getmetafield(L, 1, \"__pairs\") == LUA_TNIL) {  /* no metamethod? */\n    lua_pushcfunction(L, luaB_next);  /* will return generator, */\n    lua_pushvalue(L, 1);  /* state, */\n    lua_pushnil(L);  /* and initial value */\n  }\n  else {\n    lua_pushvalue(L, 1);  /* argument 'self' to metamethod */\n    lua_callk(L, 1, 3, 0, pairscont);  /* get 3 values from metamethod */\n  }\n  return 3;\n}\n\n\n/*\n** Traversal function for 'ipairs'\n*/\nstatic int ipairsaux (lua_State *L) {\n  lua_Integer i = luaL_checkinteger(L, 2);\n  i = luaL_intop(+, i, 1);\n  lua_pushinteger(L, i);\n  return (lua_geti(L, 1, i) == LUA_TNIL) ? 1 : 2;\n}\n\n\n/*\n** 'ipairs' function. Returns 'ipairsaux', given \"table\", 0.\n** (The given \"table\" may not be a table.)\n*/\nstatic int luaB_ipairs (lua_State *L) {\n  luaL_checkany(L, 1);\n  lua_pushcfunction(L, ipairsaux);  /* iteration function */\n  lua_pushvalue(L, 1);  /* state */\n  lua_pushinteger(L, 0);  /* initial value */\n  return 3;\n}\n\n\nstatic int load_aux (lua_State *L, int status, int envidx) {\n  if (l_likely(status == LUA_OK)) {\n    if (envidx != 0) {  /* 'env' parameter? */\n      lua_pushvalue(L, envidx);  /* environment for loaded function */\n      if (!lua_setupvalue(L, -2, 1))  /* set it as 1st upvalue */\n        lua_pop(L, 1);  /* remove 'env' if not used by previous call */\n    }\n    return 1;\n  }\n  else {  /* error (message is on top of the stack) */\n    luaL_pushfail(L);\n    lua_insert(L, -2);  /* put before error message */\n    return 2;  /* return fail plus error message */\n  }\n}\n\n\nstatic int luaB_loadfile (lua_State *L) {\n  const char *fname = luaL_optstring(L, 1, NULL);\n  const char *mode = luaL_optstring(L, 2, NULL);\n  int env = (!lua_isnone(L, 3) ? 3 : 0);  /* 'env' index or 0 if no 'env' */\n  int status = luaL_loadfilex(L, fname, mode);\n  return load_aux(L, status, env);\n}\n\n\n/*\n** {======================================================\n** Generic Read function\n** =======================================================\n*/\n\n\n/*\n** reserved slot, above all arguments, to hold a copy of the returned\n** string to avoid it being collected while parsed. 'load' has four\n** optional arguments (chunk, source name, mode, and environment).\n*/\n#define RESERVEDSLOT\t5\n\n\n/*\n** Reader for generic 'load' function: 'lua_load' uses the\n** stack for internal stuff, so the reader cannot change the\n** stack top. Instead, it keeps its resulting string in a\n** reserved slot inside the stack.\n*/\nstatic const char *generic_reader (lua_State *L, void *ud, size_t *size) {\n  (void)(ud);  /* not used */\n  luaL_checkstack(L, 2, \"too many nested functions\");\n  lua_pushvalue(L, 1);  /* get function */\n  lua_call(L, 0, 1);  /* call it */\n  if (lua_isnil(L, -1)) {\n    lua_pop(L, 1);  /* pop result */\n    *size = 0;\n    return NULL;\n  }\n  else if (l_unlikely(!lua_isstring(L, -1)))\n    luaL_error(L, \"reader function must return a string\");\n  lua_replace(L, RESERVEDSLOT);  /* save string in reserved slot */\n  return lua_tolstring(L, RESERVEDSLOT, size);\n}\n\n\nstatic int luaB_load (lua_State *L) {\n  int status;\n  size_t l;\n  const char *s = lua_tolstring(L, 1, &l);\n  const char *mode = luaL_optstring(L, 3, \"bt\");\n  int env = (!lua_isnone(L, 4) ? 4 : 0);  /* 'env' index or 0 if no 'env' */\n  if (s != NULL) {  /* loading a string? */\n    const char *chunkname = luaL_optstring(L, 2, s);\n    status = luaL_loadbufferx(L, s, l, chunkname, mode);\n  }\n  else {  /* loading from a reader function */\n    const char *chunkname = luaL_optstring(L, 2, \"=(load)\");\n    luaL_checktype(L, 1, LUA_TFUNCTION);\n    lua_settop(L, RESERVEDSLOT);  /* create reserved slot */\n    status = lua_load(L, generic_reader, NULL, chunkname, mode);\n  }\n  return load_aux(L, status, env);\n}\n\n/* }====================================================== */\n\n\nstatic int dofilecont (lua_State *L, int d1, lua_KContext d2) {\n  (void)d1;  (void)d2;  /* only to match 'lua_Kfunction' prototype */\n  return lua_gettop(L) - 1;\n}\n\n\nstatic int luaB_dofile (lua_State *L) {\n  const char *fname = luaL_optstring(L, 1, NULL);\n  lua_settop(L, 1);\n  if (l_unlikely(luaL_loadfile(L, fname) != LUA_OK))\n    return lua_error(L);\n  lua_callk(L, 0, LUA_MULTRET, 0, dofilecont);\n  return dofilecont(L, 0, 0);\n}\n\n\nstatic int luaB_assert (lua_State *L) {\n  if (l_likely(lua_toboolean(L, 1)))  /* condition is true? */\n    return lua_gettop(L);  /* return all arguments */\n  else {  /* error */\n    luaL_checkany(L, 1);  /* there must be a condition */\n    lua_remove(L, 1);  /* remove it */\n    lua_pushliteral(L, \"assertion failed!\");  /* default message */\n    lua_settop(L, 1);  /* leave only message (default if no other one) */\n    return luaB_error(L);  /* call 'error' */\n  }\n}\n\n\nstatic int luaB_select (lua_State *L) {\n  int n = lua_gettop(L);\n  if (lua_type(L, 1) == LUA_TSTRING && *lua_tostring(L, 1) == '#') {\n    lua_pushinteger(L, n-1);\n    return 1;\n  }\n  else {\n    lua_Integer i = luaL_checkinteger(L, 1);\n    if (i < 0) i = n + i;\n    else if (i > n) i = n;\n    luaL_argcheck(L, 1 <= i, 1, \"index out of range\");\n    return n - (int)i;\n  }\n}\n\n\n/*\n** Continuation function for 'pcall' and 'xpcall'. Both functions\n** already pushed a 'true' before doing the call, so in case of success\n** 'finishpcall' only has to return everything in the stack minus\n** 'extra' values (where 'extra' is exactly the number of items to be\n** ignored).\n*/\nstatic int finishpcall (lua_State *L, int status, lua_KContext extra) {\n  if (l_unlikely(status != LUA_OK && status != LUA_YIELD)) {  /* error? */\n    lua_pushboolean(L, 0);  /* first result (false) */\n    lua_pushvalue(L, -2);  /* error message */\n    return 2;  /* return false, msg */\n  }\n  else\n    return lua_gettop(L) - (int)extra;  /* return all results */\n}\n\n\nstatic int luaB_pcall (lua_State *L) {\n  int status;\n  luaL_checkany(L, 1);\n  lua_pushboolean(L, 1);  /* first result if no errors */\n  lua_insert(L, 1);  /* put it in place */\n  status = lua_pcallk(L, lua_gettop(L) - 2, LUA_MULTRET, 0, 0, finishpcall);\n  return finishpcall(L, status, 0);\n}\n\n\n/*\n** Do a protected call with error handling. After 'lua_rotate', the\n** stack will have <f, err, true, f, [args...]>; so, the function passes\n** 2 to 'finishpcall' to skip the 2 first values when returning results.\n*/\nstatic int luaB_xpcall (lua_State *L) {\n  int status;\n  int n = lua_gettop(L);\n  luaL_checktype(L, 2, LUA_TFUNCTION);  /* check error function */\n  lua_pushboolean(L, 1);  /* first result */\n  lua_pushvalue(L, 1);  /* function */\n  lua_rotate(L, 3, 2);  /* move them below function's arguments */\n  status = lua_pcallk(L, n - 2, LUA_MULTRET, 2, 2, finishpcall);\n  return finishpcall(L, status, 2);\n}\n\n\nstatic int luaB_tostring (lua_State *L) {\n  luaL_checkany(L, 1);\n  luaL_tolstring(L, 1, NULL);\n  return 1;\n}\n\n\nstatic const luaL_Reg base_funcs[] = {\n  {\"assert\", luaB_assert},\n  {\"collectgarbage\", luaB_collectgarbage},\n  {\"dofile\", luaB_dofile},\n  {\"error\", luaB_error},\n  {\"getmetatable\", luaB_getmetatable},\n  {\"ipairs\", luaB_ipairs},\n  {\"loadfile\", luaB_loadfile},\n  {\"load\", luaB_load},\n  {\"next\", luaB_next},\n  {\"pairs\", luaB_pairs},\n  {\"pcall\", luaB_pcall},\n  {\"print\", luaB_print},\n  {\"warn\", luaB_warn},\n  {\"rawequal\", luaB_rawequal},\n  {\"rawlen\", luaB_rawlen},\n  {\"rawget\", luaB_rawget},\n  {\"rawset\", luaB_rawset},\n  {\"select\", luaB_select},\n  {\"setmetatable\", luaB_setmetatable},\n  {\"tonumber\", luaB_tonumber},\n  {\"tostring\", luaB_tostring},\n  {\"type\", luaB_type},\n  {\"xpcall\", luaB_xpcall},\n  /* placeholders */\n  {LUA_GNAME, NULL},\n  {\"_VERSION\", NULL},\n  {NULL, NULL}\n};\n\n\nLUAMOD_API int luaopen_base (lua_State *L) {\n  /* open lib into global table */\n  lua_pushglobaltable(L);\n  luaL_setfuncs(L, base_funcs, 0);\n  /* set global _G */\n  lua_pushvalue(L, -1);\n  lua_setfield(L, -2, LUA_GNAME);\n  /* set global _VERSION */\n  lua_pushliteral(L, LUA_VERSION);\n  lua_setfield(L, -2, \"_VERSION\");\n  return 1;\n}\n\n/*\n** $Id: lcorolib.c $\n** Coroutine Library\n** See Copyright Notice in lua.h\n*/\n\n#define lcorolib_c\n#define LUA_LIB\n\n/*#include \"lprefix.h\"*/\n\n\n#include <stdlib.h>\n\n/*#include \"lua.h\"*/\n\n/*#include \"lauxlib.h\"*/\n/*#include \"lualib.h\"*/\n\n\nstatic lua_State *getco (lua_State *L) {\n  lua_State *co = lua_tothread(L, 1);\n  luaL_argexpected(L, co, 1, \"thread\");\n  return co;\n}\n\n\n/*\n** Resumes a coroutine. Returns the number of results for non-error\n** cases or -1 for errors.\n*/\nstatic int auxresume (lua_State *L, lua_State *co, int narg) {\n  int status, nres;\n  if (l_unlikely(!lua_checkstack(co, narg))) {\n    lua_pushliteral(L, \"too many arguments to resume\");\n    return -1;  /* error flag */\n  }\n  lua_xmove(L, co, narg);\n  status = lua_resume(co, L, narg, &nres);\n  if (l_likely(status == LUA_OK || status == LUA_YIELD)) {\n    if (l_unlikely(!lua_checkstack(L, nres + 1))) {\n      lua_pop(co, nres);  /* remove results anyway */\n      lua_pushliteral(L, \"too many results to resume\");\n      return -1;  /* error flag */\n    }\n    lua_xmove(co, L, nres);  /* move yielded values */\n    return nres;\n  }\n  else {\n    lua_xmove(co, L, 1);  /* move error message */\n    return -1;  /* error flag */\n  }\n}\n\n\nstatic int luaB_coresume (lua_State *L) {\n  lua_State *co = getco(L);\n  int r;\n  r = auxresume(L, co, lua_gettop(L) - 1);\n  if (l_unlikely(r < 0)) {\n    lua_pushboolean(L, 0);\n    lua_insert(L, -2);\n    return 2;  /* return false + error message */\n  }\n  else {\n    lua_pushboolean(L, 1);\n    lua_insert(L, -(r + 1));\n    return r + 1;  /* return true + 'resume' returns */\n  }\n}\n\n\nstatic int luaB_auxwrap (lua_State *L) {\n  lua_State *co = lua_tothread(L, lua_upvalueindex(1));\n  int r = auxresume(L, co, lua_gettop(L));\n  if (l_unlikely(r < 0)) {  /* error? */\n    int stat = lua_status(co);\n    if (stat != LUA_OK && stat != LUA_YIELD) {  /* error in the coroutine? */\n      stat = lua_closethread(co, L);  /* close its tbc variables */\n      lua_assert(stat != LUA_OK);\n      lua_xmove(co, L, 1);  /* move error message to the caller */\n    }\n    if (stat != LUA_ERRMEM &&  /* not a memory error and ... */\n        lua_type(L, -1) == LUA_TSTRING) {  /* ... error object is a string? */\n      luaL_where(L, 1);  /* add extra info, if available */\n      lua_insert(L, -2);\n      lua_concat(L, 2);\n    }\n    return lua_error(L);  /* propagate error */\n  }\n  return r;\n}\n\n\nstatic int luaB_cocreate (lua_State *L) {\n  lua_State *NL;\n  luaL_checktype(L, 1, LUA_TFUNCTION);\n  NL = lua_newthread(L);\n  lua_pushvalue(L, 1);  /* move function to top */\n  lua_xmove(L, NL, 1);  /* move function from L to NL */\n  return 1;\n}\n\n\nstatic int luaB_cowrap (lua_State *L) {\n  luaB_cocreate(L);\n  lua_pushcclosure(L, luaB_auxwrap, 1);\n  return 1;\n}\n\n\nstatic int luaB_yield (lua_State *L) {\n  return lua_yield(L, lua_gettop(L));\n}\n\n\n#define COS_RUN\t\t0\n#define COS_DEAD\t1\n#define COS_YIELD\t2\n#define COS_NORM\t3\n\n\nstatic const char *const statname[] =\n  {\"running\", \"dead\", \"suspended\", \"normal\"};\n\n\nstatic int auxstatus (lua_State *L, lua_State *co) {\n  if (L == co) return COS_RUN;\n  else {\n    switch (lua_status(co)) {\n      case LUA_YIELD:\n        return COS_YIELD;\n      case LUA_OK: {\n        lua_Debug ar;\n        if (lua_getstack(co, 0, &ar))  /* does it have frames? */\n          return COS_NORM;  /* it is running */\n        else if (lua_gettop(co) == 0)\n            return COS_DEAD;\n        else\n          return COS_YIELD;  /* initial state */\n      }\n      default:  /* some error occurred */\n        return COS_DEAD;\n    }\n  }\n}\n\n\nstatic int luaB_costatus (lua_State *L) {\n  lua_State *co = getco(L);\n  lua_pushstring(L, statname[auxstatus(L, co)]);\n  return 1;\n}\n\n\nstatic int luaB_yieldable (lua_State *L) {\n  lua_State *co = lua_isnone(L, 1) ? L : getco(L);\n  lua_pushboolean(L, lua_isyieldable(co));\n  return 1;\n}\n\n\nstatic int luaB_corunning (lua_State *L) {\n  int ismain = lua_pushthread(L);\n  lua_pushboolean(L, ismain);\n  return 2;\n}\n\n\nstatic int luaB_close (lua_State *L) {\n  lua_State *co = getco(L);\n  int status = auxstatus(L, co);\n  switch (status) {\n    case COS_DEAD: case COS_YIELD: {\n      status = lua_closethread(co, L);\n      if (status == LUA_OK) {\n        lua_pushboolean(L, 1);\n        return 1;\n      }\n      else {\n        lua_pushboolean(L, 0);\n        lua_xmove(co, L, 1);  /* move error message */\n        return 2;\n      }\n    }\n    default:  /* normal or running coroutine */\n      return luaL_error(L, \"cannot close a %s coroutine\", statname[status]);\n  }\n}\n\n\nstatic const luaL_Reg co_funcs[] = {\n  {\"create\", luaB_cocreate},\n  {\"resume\", luaB_coresume},\n  {\"running\", luaB_corunning},\n  {\"status\", luaB_costatus},\n  {\"wrap\", luaB_cowrap},\n  {\"yield\", luaB_yield},\n  {\"isyieldable\", luaB_yieldable},\n  {\"close\", luaB_close},\n  {NULL, NULL}\n};\n\n\n\nLUAMOD_API int luaopen_coroutine (lua_State *L) {\n  luaL_newlib(L, co_funcs);\n  return 1;\n}\n\n/*\n** $Id: ldblib.c $\n** Interface from Lua to its debug API\n** See Copyright Notice in lua.h\n*/\n\n#define ldblib_c\n#define LUA_LIB\n\n/*#include \"lprefix.h\"*/\n\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n/*#include \"lua.h\"*/\n\n/*#include \"lauxlib.h\"*/\n/*#include \"lualib.h\"*/\n\n\n/*\n** The hook table at registry[HOOKKEY] maps threads to their current\n** hook function.\n*/\nstatic const char *const HOOKKEY = \"_HOOKKEY\";\n\n\n/*\n** If L1 != L, L1 can be in any state, and therefore there are no\n** guarantees about its stack space; any push in L1 must be\n** checked.\n*/\nstatic void checkstack (lua_State *L, lua_State *L1, int n) {\n  if (l_unlikely(L != L1 && !lua_checkstack(L1, n)))\n    luaL_error(L, \"stack overflow\");\n}\n\n\nstatic int db_getregistry (lua_State *L) {\n  lua_pushvalue(L, LUA_REGISTRYINDEX);\n  return 1;\n}\n\n\nstatic int db_getmetatable (lua_State *L) {\n  luaL_checkany(L, 1);\n  if (!lua_getmetatable(L, 1)) {\n    lua_pushnil(L);  /* no metatable */\n  }\n  return 1;\n}\n\n\nstatic int db_setmetatable (lua_State *L) {\n  int t = lua_type(L, 2);\n  luaL_argexpected(L, t == LUA_TNIL || t == LUA_TTABLE, 2, \"nil or table\");\n  lua_settop(L, 2);\n  lua_setmetatable(L, 1);\n  return 1;  /* return 1st argument */\n}\n\n\nstatic int db_getuservalue (lua_State *L) {\n  int n = (int)luaL_optinteger(L, 2, 1);\n  if (lua_type(L, 1) != LUA_TUSERDATA)\n    luaL_pushfail(L);\n  else if (lua_getiuservalue(L, 1, n) != LUA_TNONE) {\n    lua_pushboolean(L, 1);\n    return 2;\n  }\n  return 1;\n}\n\n\nstatic int db_setuservalue (lua_State *L) {\n  int n = (int)luaL_optinteger(L, 3, 1);\n  luaL_checktype(L, 1, LUA_TUSERDATA);\n  luaL_checkany(L, 2);\n  lua_settop(L, 2);\n  if (!lua_setiuservalue(L, 1, n))\n    luaL_pushfail(L);\n  return 1;\n}\n\n\n/*\n** Auxiliary function used by several library functions: check for\n** an optional thread as function's first argument and set 'arg' with\n** 1 if this argument is present (so that functions can skip it to\n** access their other arguments)\n*/\nstatic lua_State *getthread (lua_State *L, int *arg) {\n  if (lua_isthread(L, 1)) {\n    *arg = 1;\n    return lua_tothread(L, 1);\n  }\n  else {\n    *arg = 0;\n    return L;  /* function will operate over current thread */\n  }\n}\n\n\n/*\n** Variations of 'lua_settable', used by 'db_getinfo' to put results\n** from 'lua_getinfo' into result table. Key is always a string;\n** value can be a string, an int, or a boolean.\n*/\nstatic void settabss (lua_State *L, const char *k, const char *v) {\n  lua_pushstring(L, v);\n  lua_setfield(L, -2, k);\n}\n\nstatic void settabsi (lua_State *L, const char *k, int v) {\n  lua_pushinteger(L, v);\n  lua_setfield(L, -2, k);\n}\n\nstatic void settabsb (lua_State *L, const char *k, int v) {\n  lua_pushboolean(L, v);\n  lua_setfield(L, -2, k);\n}\n\n\n/*\n** In function 'db_getinfo', the call to 'lua_getinfo' may push\n** results on the stack; later it creates the result table to put\n** these objects. Function 'treatstackoption' puts the result from\n** 'lua_getinfo' on top of the result table so that it can call\n** 'lua_setfield'.\n*/\nstatic void treatstackoption (lua_State *L, lua_State *L1, const char *fname) {\n  if (L == L1)\n    lua_rotate(L, -2, 1);  /* exchange object and table */\n  else\n    lua_xmove(L1, L, 1);  /* move object to the \"main\" stack */\n  lua_setfield(L, -2, fname);  /* put object into table */\n}\n\n\n/*\n** Calls 'lua_getinfo' and collects all results in a new table.\n** L1 needs stack space for an optional input (function) plus\n** two optional outputs (function and line table) from function\n** 'lua_getinfo'.\n*/\nstatic int db_getinfo (lua_State *L) {\n  lua_Debug ar;\n  int arg;\n  lua_State *L1 = getthread(L, &arg);\n  const char *options = luaL_optstring(L, arg+2, \"flnSrtu\");\n  checkstack(L, L1, 3);\n  luaL_argcheck(L, options[0] != '>', arg + 2, \"invalid option '>'\");\n  if (lua_isfunction(L, arg + 1)) {  /* info about a function? */\n    options = lua_pushfstring(L, \">%s\", options);  /* add '>' to 'options' */\n    lua_pushvalue(L, arg + 1);  /* move function to 'L1' stack */\n    lua_xmove(L, L1, 1);\n  }\n  else {  /* stack level */\n    if (!lua_getstack(L1, (int)luaL_checkinteger(L, arg + 1), &ar)) {\n      luaL_pushfail(L);  /* level out of range */\n      return 1;\n    }\n  }\n  if (!lua_getinfo(L1, options, &ar))\n    return luaL_argerror(L, arg+2, \"invalid option\");\n  lua_newtable(L);  /* table to collect results */\n  if (strchr(options, 'S')) {\n    lua_pushlstring(L, ar.source, ar.srclen);\n    lua_setfield(L, -2, \"source\");\n    settabss(L, \"short_src\", ar.short_src);\n    settabsi(L, \"linedefined\", ar.linedefined);\n    settabsi(L, \"lastlinedefined\", ar.lastlinedefined);\n    settabss(L, \"what\", ar.what);\n  }\n  if (strchr(options, 'l'))\n    settabsi(L, \"currentline\", ar.currentline);\n  if (strchr(options, 'u')) {\n    settabsi(L, \"nups\", ar.nups);\n    settabsi(L, \"nparams\", ar.nparams);\n    settabsb(L, \"isvararg\", ar.isvararg);\n  }\n  if (strchr(options, 'n')) {\n    settabss(L, \"name\", ar.name);\n    settabss(L, \"namewhat\", ar.namewhat);\n  }\n  if (strchr(options, 'r')) {\n    settabsi(L, \"ftransfer\", ar.ftransfer);\n    settabsi(L, \"ntransfer\", ar.ntransfer);\n  }\n  if (strchr(options, 't'))\n    settabsb(L, \"istailcall\", ar.istailcall);\n  if (strchr(options, 'L'))\n    treatstackoption(L, L1, \"activelines\");\n  if (strchr(options, 'f'))\n    treatstackoption(L, L1, \"func\");\n  return 1;  /* return table */\n}\n\n\nstatic int db_getlocal (lua_State *L) {\n  int arg;\n  lua_State *L1 = getthread(L, &arg);\n  int nvar = (int)luaL_checkinteger(L, arg + 2);  /* local-variable index */\n  if (lua_isfunction(L, arg + 1)) {  /* function argument? */\n    lua_pushvalue(L, arg + 1);  /* push function */\n    lua_pushstring(L, lua_getlocal(L, NULL, nvar));  /* push local name */\n    return 1;  /* return only name (there is no value) */\n  }\n  else {  /* stack-level argument */\n    lua_Debug ar;\n    const char *name;\n    int level = (int)luaL_checkinteger(L, arg + 1);\n    if (l_unlikely(!lua_getstack(L1, level, &ar)))  /* out of range? */\n      return luaL_argerror(L, arg+1, \"level out of range\");\n    checkstack(L, L1, 1);\n    name = lua_getlocal(L1, &ar, nvar);\n    if (name) {\n      lua_xmove(L1, L, 1);  /* move local value */\n      lua_pushstring(L, name);  /* push name */\n      lua_rotate(L, -2, 1);  /* re-order */\n      return 2;\n    }\n    else {\n      luaL_pushfail(L);  /* no name (nor value) */\n      return 1;\n    }\n  }\n}\n\n\nstatic int db_setlocal (lua_State *L) {\n  int arg;\n  const char *name;\n  lua_State *L1 = getthread(L, &arg);\n  lua_Debug ar;\n  int level = (int)luaL_checkinteger(L, arg + 1);\n  int nvar = (int)luaL_checkinteger(L, arg + 2);\n  if (l_unlikely(!lua_getstack(L1, level, &ar)))  /* out of range? */\n    return luaL_argerror(L, arg+1, \"level out of range\");\n  luaL_checkany(L, arg+3);\n  lua_settop(L, arg+3);\n  checkstack(L, L1, 1);\n  lua_xmove(L, L1, 1);\n  name = lua_setlocal(L1, &ar, nvar);\n  if (name == NULL)\n    lua_pop(L1, 1);  /* pop value (if not popped by 'lua_setlocal') */\n  lua_pushstring(L, name);\n  return 1;\n}\n\n\n/*\n** get (if 'get' is true) or set an upvalue from a closure\n*/\nstatic int auxupvalue (lua_State *L, int get) {\n  const char *name;\n  int n = (int)luaL_checkinteger(L, 2);  /* upvalue index */\n  luaL_checktype(L, 1, LUA_TFUNCTION);  /* closure */\n  name = get ? lua_getupvalue(L, 1, n) : lua_setupvalue(L, 1, n);\n  if (name == NULL) return 0;\n  lua_pushstring(L, name);\n  lua_insert(L, -(get+1));  /* no-op if get is false */\n  return get + 1;\n}\n\n\nstatic int db_getupvalue (lua_State *L) {\n  return auxupvalue(L, 1);\n}\n\n\nstatic int db_setupvalue (lua_State *L) {\n  luaL_checkany(L, 3);\n  return auxupvalue(L, 0);\n}\n\n\n/*\n** Check whether a given upvalue from a given closure exists and\n** returns its index\n*/\nstatic void *checkupval (lua_State *L, int argf, int argnup, int *pnup) {\n  void *id;\n  int nup = (int)luaL_checkinteger(L, argnup);  /* upvalue index */\n  luaL_checktype(L, argf, LUA_TFUNCTION);  /* closure */\n  id = lua_upvalueid(L, argf, nup);\n  if (pnup) {\n    luaL_argcheck(L, id != NULL, argnup, \"invalid upvalue index\");\n    *pnup = nup;\n  }\n  return id;\n}\n\n\nstatic int db_upvalueid (lua_State *L) {\n  void *id = checkupval(L, 1, 2, NULL);\n  if (id != NULL)\n    lua_pushlightuserdata(L, id);\n  else\n    luaL_pushfail(L);\n  return 1;\n}\n\n\nstatic int db_upvaluejoin (lua_State *L) {\n  int n1, n2;\n  checkupval(L, 1, 2, &n1);\n  checkupval(L, 3, 4, &n2);\n  luaL_argcheck(L, !lua_iscfunction(L, 1), 1, \"Lua function expected\");\n  luaL_argcheck(L, !lua_iscfunction(L, 3), 3, \"Lua function expected\");\n  lua_upvaluejoin(L, 1, n1, 3, n2);\n  return 0;\n}\n\n\n/*\n** Call hook function registered at hook table for the current\n** thread (if there is one)\n*/\nstatic void hookf (lua_State *L, lua_Debug *ar) {\n  static const char *const hooknames[] =\n    {\"call\", \"return\", \"line\", \"count\", \"tail call\"};\n  lua_getfield(L, LUA_REGISTRYINDEX, HOOKKEY);\n  lua_pushthread(L);\n  if (lua_rawget(L, -2) == LUA_TFUNCTION) {  /* is there a hook function? */\n    lua_pushstring(L, hooknames[(int)ar->event]);  /* push event name */\n    if (ar->currentline >= 0)\n      lua_pushinteger(L, ar->currentline);  /* push current line */\n    else lua_pushnil(L);\n    lua_assert(lua_getinfo(L, \"lS\", ar));\n    lua_call(L, 2, 0);  /* call hook function */\n  }\n}\n\n\n/*\n** Convert a string mask (for 'sethook') into a bit mask\n*/\nstatic int makemask (const char *smask, int count) {\n  int mask = 0;\n  if (strchr(smask, 'c')) mask |= LUA_MASKCALL;\n  if (strchr(smask, 'r')) mask |= LUA_MASKRET;\n  if (strchr(smask, 'l')) mask |= LUA_MASKLINE;\n  if (count > 0) mask |= LUA_MASKCOUNT;\n  return mask;\n}\n\n\n/*\n** Convert a bit mask (for 'gethook') into a string mask\n*/\nstatic char *unmakemask (int mask, char *smask) {\n  int i = 0;\n  if (mask & LUA_MASKCALL) smask[i++] = 'c';\n  if (mask & LUA_MASKRET) smask[i++] = 'r';\n  if (mask & LUA_MASKLINE) smask[i++] = 'l';\n  smask[i] = '\\0';\n  return smask;\n}\n\n\nstatic int db_sethook (lua_State *L) {\n  int arg, mask, count;\n  lua_Hook func;\n  lua_State *L1 = getthread(L, &arg);\n  if (lua_isnoneornil(L, arg+1)) {  /* no hook? */\n    lua_settop(L, arg+1);\n    func = NULL; mask = 0; count = 0;  /* turn off hooks */\n  }\n  else {\n    const char *smask = luaL_checkstring(L, arg+2);\n    luaL_checktype(L, arg+1, LUA_TFUNCTION);\n    count = (int)luaL_optinteger(L, arg + 3, 0);\n    func = hookf; mask = makemask(smask, count);\n  }\n  if (!luaL_getsubtable(L, LUA_REGISTRYINDEX, HOOKKEY)) {\n    /* table just created; initialize it */\n    lua_pushliteral(L, \"k\");\n    lua_setfield(L, -2, \"__mode\");  /** hooktable.__mode = \"k\" */\n    lua_pushvalue(L, -1);\n    lua_setmetatable(L, -2);  /* metatable(hooktable) = hooktable */\n  }\n  checkstack(L, L1, 1);\n  lua_pushthread(L1); lua_xmove(L1, L, 1);  /* key (thread) */\n  lua_pushvalue(L, arg + 1);  /* value (hook function) */\n  lua_rawset(L, -3);  /* hooktable[L1] = new Lua hook */\n  lua_sethook(L1, func, mask, count);\n  return 0;\n}\n\n\nstatic int db_gethook (lua_State *L) {\n  int arg;\n  lua_State *L1 = getthread(L, &arg);\n  char buff[5];\n  int mask = lua_gethookmask(L1);\n  lua_Hook hook = lua_gethook(L1);\n  if (hook == NULL) {  /* no hook? */\n    luaL_pushfail(L);\n    return 1;\n  }\n  else if (hook != hookf)  /* external hook? */\n    lua_pushliteral(L, \"external hook\");\n  else {  /* hook table must exist */\n    lua_getfield(L, LUA_REGISTRYINDEX, HOOKKEY);\n    checkstack(L, L1, 1);\n    lua_pushthread(L1); lua_xmove(L1, L, 1);\n    lua_rawget(L, -2);   /* 1st result = hooktable[L1] */\n    lua_remove(L, -2);  /* remove hook table */\n  }\n  lua_pushstring(L, unmakemask(mask, buff));  /* 2nd result = mask */\n  lua_pushinteger(L, lua_gethookcount(L1));  /* 3rd result = count */\n  return 3;\n}\n\n\nstatic int db_debug (lua_State *L) {\n  for (;;) {\n    char buffer[250];\n    lua_writestringerror(\"%s\", \"lua_debug> \");\n    if (fgets(buffer, sizeof(buffer), stdin) == NULL ||\n        strcmp(buffer, \"cont\\n\") == 0)\n      return 0;\n    if (luaL_loadbuffer(L, buffer, strlen(buffer), \"=(debug command)\") ||\n        lua_pcall(L, 0, 0, 0))\n      lua_writestringerror(\"%s\\n\", luaL_tolstring(L, -1, NULL));\n    lua_settop(L, 0);  /* remove eventual returns */\n  }\n}\n\n\nstatic int db_traceback (lua_State *L) {\n  int arg;\n  lua_State *L1 = getthread(L, &arg);\n  const char *msg = lua_tostring(L, arg + 1);\n  if (msg == NULL && !lua_isnoneornil(L, arg + 1))  /* non-string 'msg'? */\n    lua_pushvalue(L, arg + 1);  /* return it untouched */\n  else {\n    int level = (int)luaL_optinteger(L, arg + 2, (L == L1) ? 1 : 0);\n    luaL_traceback(L, L1, msg, level);\n  }\n  return 1;\n}\n\n\nstatic int db_setcstacklimit (lua_State *L) {\n  int limit = (int)luaL_checkinteger(L, 1);\n  int res = lua_setcstacklimit(L, limit);\n  lua_pushinteger(L, res);\n  return 1;\n}\n\n\nstatic const luaL_Reg dblib[] = {\n  {\"debug\", db_debug},\n  {\"getuservalue\", db_getuservalue},\n  {\"gethook\", db_gethook},\n  {\"getinfo\", db_getinfo},\n  {\"getlocal\", db_getlocal},\n  {\"getregistry\", db_getregistry},\n  {\"getmetatable\", db_getmetatable},\n  {\"getupvalue\", db_getupvalue},\n  {\"upvaluejoin\", db_upvaluejoin},\n  {\"upvalueid\", db_upvalueid},\n  {\"setuservalue\", db_setuservalue},\n  {\"sethook\", db_sethook},\n  {\"setlocal\", db_setlocal},\n  {\"setmetatable\", db_setmetatable},\n  {\"setupvalue\", db_setupvalue},\n  {\"traceback\", db_traceback},\n  {\"setcstacklimit\", db_setcstacklimit},\n  {NULL, NULL}\n};\n\n\nLUAMOD_API int luaopen_debug (lua_State *L) {\n  luaL_newlib(L, dblib);\n  return 1;\n}\n\n/*\n** $Id: liolib.c $\n** Standard I/O (and system) library\n** See Copyright Notice in lua.h\n*/\n\n#define liolib_c\n#define LUA_LIB\n\n/*#include \"lprefix.h\"*/\n\n\n#include <ctype.h>\n#include <errno.h>\n#include <locale.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n/*#include \"lua.h\"*/\n\n/*#include \"lauxlib.h\"*/\n/*#include \"lualib.h\"*/\n\n\n\n\n/*\n** Change this macro to accept other modes for 'fopen' besides\n** the standard ones.\n*/\n#if !defined(l_checkmode)\n\n/* accepted extensions to 'mode' in 'fopen' */\n#if !defined(L_MODEEXT)\n#define L_MODEEXT\t\"b\"\n#endif\n\n/* Check whether 'mode' matches '[rwa]%+?[L_MODEEXT]*' */\nstatic int l_checkmode (const char *mode) {\n  return (*mode != '\\0' && strchr(\"rwa\", *(mode++)) != NULL &&\n         (*mode != '+' || ((void)(++mode), 1)) &&  /* skip if char is '+' */\n         (strspn(mode, L_MODEEXT) == strlen(mode)));  /* check extensions */\n}\n\n#endif\n\n/*\n** {======================================================\n** l_popen spawns a new process connected to the current\n** one through the file streams.\n** =======================================================\n*/\n\n#if !defined(l_popen)\t\t/* { */\n\n#if defined(LUA_USE_POSIX)\t/* { */\n\n#define l_popen(L,c,m)\t\t(fflush(NULL), popen(c,m))\n#define l_pclose(L,file)\t(pclose(file))\n\n#elif defined(LUA_USE_WINDOWS)\t/* }{ */\n\n#define l_popen(L,c,m)\t\t(_popen(c,m))\n#define l_pclose(L,file)\t(_pclose(file))\n\n#if !defined(l_checkmodep)\n/* Windows accepts \"[rw][bt]?\" as valid modes */\n#define l_checkmodep(m)\t((m[0] == 'r' || m[0] == 'w') && \\\n  (m[1] == '\\0' || ((m[1] == 'b' || m[1] == 't') && m[2] == '\\0')))\n#endif\n\n#else\t\t\t\t/* }{ */\n\n/* ISO C definitions */\n#define l_popen(L,c,m)  \\\n\t  ((void)c, (void)m, \\\n\t  luaL_error(L, \"'popen' not supported\"), \\\n\t  (FILE*)0)\n#define l_pclose(L,file)\t\t((void)L, (void)file, -1)\n\n#endif\t\t\t\t/* } */\n\n#endif\t\t\t\t/* } */\n\n\n#if !defined(l_checkmodep)\n/* By default, Lua accepts only \"r\" or \"w\" as valid modes */\n#define l_checkmodep(m)        ((m[0] == 'r' || m[0] == 'w') && m[1] == '\\0')\n#endif\n\n/* }====================================================== */\n\n\n#if !defined(l_getc)\t\t/* { */\n\n#if defined(LUA_USE_POSIX)\n#define l_getc(f)\t\tgetc_unlocked(f)\n#define l_lockfile(f)\t\tflockfile(f)\n#define l_unlockfile(f)\t\tfunlockfile(f)\n#else\n#define l_getc(f)\t\tgetc(f)\n#define l_lockfile(f)\t\t((void)0)\n#define l_unlockfile(f)\t\t((void)0)\n#endif\n\n#endif\t\t\t\t/* } */\n\n\n/*\n** {======================================================\n** l_fseek: configuration for longer offsets\n** =======================================================\n*/\n\n#if !defined(l_fseek)\t\t/* { */\n\n#if defined(LUA_USE_POSIX)\t/* { */\n\n#include <sys/types.h>\n\n#define l_fseek(f,o,w)\t\tfseeko(f,o,w)\n#define l_ftell(f)\t\tftello(f)\n#define l_seeknum\t\toff_t\n\n#elif defined(LUA_USE_WINDOWS) && !defined(_CRTIMP_TYPEINFO) \\\n   && defined(_MSC_VER) && (_MSC_VER >= 1400)\t/* }{ */\n\n/* Windows (but not DDK) and Visual C++ 2005 or higher */\n#define l_fseek(f,o,w)\t\t_fseeki64(f,o,w)\n#define l_ftell(f)\t\t_ftelli64(f)\n#define l_seeknum\t\t__int64\n\n#else\t\t\t\t/* }{ */\n\n/* ISO C definitions */\n#define l_fseek(f,o,w)\t\tfseek(f,o,w)\n#define l_ftell(f)\t\tftell(f)\n#define l_seeknum\t\tlong\n\n#endif\t\t\t\t/* } */\n\n#endif\t\t\t\t/* } */\n\n/* }====================================================== */\n\n\n\n#define IO_PREFIX\t\"_IO_\"\n#define IOPREF_LEN\t(sizeof(IO_PREFIX)/sizeof(char) - 1)\n#define IO_INPUT\t(IO_PREFIX \"input\")\n#define IO_OUTPUT\t(IO_PREFIX \"output\")\n\n\ntypedef luaL_Stream LStream;\n\n\n#define tolstream(L)\t((LStream *)luaL_checkudata(L, 1, LUA_FILEHANDLE))\n\n#define isclosed(p)\t((p)->closef == NULL)\n\n\nstatic int io_type (lua_State *L) {\n  LStream *p;\n  luaL_checkany(L, 1);\n  p = (LStream *)luaL_testudata(L, 1, LUA_FILEHANDLE);\n  if (p == NULL)\n    luaL_pushfail(L);  /* not a file */\n  else if (isclosed(p))\n    lua_pushliteral(L, \"closed file\");\n  else\n    lua_pushliteral(L, \"file\");\n  return 1;\n}\n\n\nstatic int f_tostring (lua_State *L) {\n  LStream *p = tolstream(L);\n  if (isclosed(p))\n    lua_pushliteral(L, \"file (closed)\");\n  else\n    lua_pushfstring(L, \"file (%p)\", p->f);\n  return 1;\n}\n\n\nstatic FILE *tofile (lua_State *L) {\n  LStream *p = tolstream(L);\n  if (l_unlikely(isclosed(p)))\n    luaL_error(L, \"attempt to use a closed file\");\n  lua_assert(p->f);\n  return p->f;\n}\n\n\n/*\n** When creating file handles, always creates a 'closed' file handle\n** before opening the actual file; so, if there is a memory error, the\n** handle is in a consistent state.\n*/\nstatic LStream *newprefile (lua_State *L) {\n  LStream *p = (LStream *)lua_newuserdatauv(L, sizeof(LStream), 0);\n  p->closef = NULL;  /* mark file handle as 'closed' */\n  luaL_setmetatable(L, LUA_FILEHANDLE);\n  return p;\n}\n\n\n/*\n** Calls the 'close' function from a file handle. The 'volatile' avoids\n** a bug in some versions of the Clang compiler (e.g., clang 3.0 for\n** 32 bits).\n*/\nstatic int aux_close (lua_State *L) {\n  LStream *p = tolstream(L);\n  volatile lua_CFunction cf = p->closef;\n  p->closef = NULL;  /* mark stream as closed */\n  return (*cf)(L);  /* close it */\n}\n\n\nstatic int f_close (lua_State *L) {\n  tofile(L);  /* make sure argument is an open stream */\n  return aux_close(L);\n}\n\n\nstatic int io_close (lua_State *L) {\n  if (lua_isnone(L, 1))  /* no argument? */\n    lua_getfield(L, LUA_REGISTRYINDEX, IO_OUTPUT);  /* use default output */\n  return f_close(L);\n}\n\n\nstatic int f_gc (lua_State *L) {\n  LStream *p = tolstream(L);\n  if (!isclosed(p) && p->f != NULL)\n    aux_close(L);  /* ignore closed and incompletely open files */\n  return 0;\n}\n\n\n/*\n** function to close regular files\n*/\nstatic int io_fclose (lua_State *L) {\n  LStream *p = tolstream(L);\n  int res = fclose(p->f);\n  return luaL_fileresult(L, (res == 0), NULL);\n}\n\n\nstatic LStream *newfile (lua_State *L) {\n  LStream *p = newprefile(L);\n  p->f = NULL;\n  p->closef = &io_fclose;\n  return p;\n}\n\n\nstatic void opencheck (lua_State *L, const char *fname, const char *mode) {\n  LStream *p = newfile(L);\n  p->f = fopen(fname, mode);\n  if (l_unlikely(p->f == NULL))\n    luaL_error(L, \"cannot open file '%s' (%s)\", fname, strerror(errno));\n}\n\n\nstatic int io_open (lua_State *L) {\n  const char *filename = luaL_checkstring(L, 1);\n  const char *mode = luaL_optstring(L, 2, \"r\");\n  LStream *p = newfile(L);\n  const char *md = mode;  /* to traverse/check mode */\n  luaL_argcheck(L, l_checkmode(md), 2, \"invalid mode\");\n  p->f = fopen(filename, mode);\n  return (p->f == NULL) ? luaL_fileresult(L, 0, filename) : 1;\n}\n\n\n/*\n** function to close 'popen' files\n*/\nstatic int io_pclose (lua_State *L) {\n  LStream *p = tolstream(L);\n  errno = 0;\n  return luaL_execresult(L, l_pclose(L, p->f));\n}\n\n\nstatic int io_popen (lua_State *L) {\n  const char *filename = luaL_checkstring(L, 1);\n  const char *mode = luaL_optstring(L, 2, \"r\");\n  LStream *p = newprefile(L);\n  luaL_argcheck(L, l_checkmodep(mode), 2, \"invalid mode\");\n  p->f = l_popen(L, filename, mode);\n  p->closef = &io_pclose;\n  return (p->f == NULL) ? luaL_fileresult(L, 0, filename) : 1;\n}\n\n\nstatic int io_tmpfile (lua_State *L) {\n  LStream *p = newfile(L);\n  p->f = tmpfile();\n  return (p->f == NULL) ? luaL_fileresult(L, 0, NULL) : 1;\n}\n\n\nstatic FILE *getiofile (lua_State *L, const char *findex) {\n  LStream *p;\n  lua_getfield(L, LUA_REGISTRYINDEX, findex);\n  p = (LStream *)lua_touserdata(L, -1);\n  if (l_unlikely(isclosed(p)))\n    luaL_error(L, \"default %s file is closed\", findex + IOPREF_LEN);\n  return p->f;\n}\n\n\nstatic int g_iofile (lua_State *L, const char *f, const char *mode) {\n  if (!lua_isnoneornil(L, 1)) {\n    const char *filename = lua_tostring(L, 1);\n    if (filename)\n      opencheck(L, filename, mode);\n    else {\n      tofile(L);  /* check that it's a valid file handle */\n      lua_pushvalue(L, 1);\n    }\n    lua_setfield(L, LUA_REGISTRYINDEX, f);\n  }\n  /* return current value */\n  lua_getfield(L, LUA_REGISTRYINDEX, f);\n  return 1;\n}\n\n\nstatic int io_input (lua_State *L) {\n  return g_iofile(L, IO_INPUT, \"r\");\n}\n\n\nstatic int io_output (lua_State *L) {\n  return g_iofile(L, IO_OUTPUT, \"w\");\n}\n\n\nstatic int io_readline (lua_State *L);\n\n\n/*\n** maximum number of arguments to 'f:lines'/'io.lines' (it + 3 must fit\n** in the limit for upvalues of a closure)\n*/\n#define MAXARGLINE\t250\n\n/*\n** Auxiliary function to create the iteration function for 'lines'.\n** The iteration function is a closure over 'io_readline', with\n** the following upvalues:\n** 1) The file being read (first value in the stack)\n** 2) the number of arguments to read\n** 3) a boolean, true iff file has to be closed when finished ('toclose')\n** *) a variable number of format arguments (rest of the stack)\n*/\nstatic void aux_lines (lua_State *L, int toclose) {\n  int n = lua_gettop(L) - 1;  /* number of arguments to read */\n  luaL_argcheck(L, n <= MAXARGLINE, MAXARGLINE + 2, \"too many arguments\");\n  lua_pushvalue(L, 1);  /* file */\n  lua_pushinteger(L, n);  /* number of arguments to read */\n  lua_pushboolean(L, toclose);  /* close/not close file when finished */\n  lua_rotate(L, 2, 3);  /* move the three values to their positions */\n  lua_pushcclosure(L, io_readline, 3 + n);\n}\n\n\nstatic int f_lines (lua_State *L) {\n  tofile(L);  /* check that it's a valid file handle */\n  aux_lines(L, 0);\n  return 1;\n}\n\n\n/*\n** Return an iteration function for 'io.lines'. If file has to be\n** closed, also returns the file itself as a second result (to be\n** closed as the state at the exit of a generic for).\n*/\nstatic int io_lines (lua_State *L) {\n  int toclose;\n  if (lua_isnone(L, 1)) lua_pushnil(L);  /* at least one argument */\n  if (lua_isnil(L, 1)) {  /* no file name? */\n    lua_getfield(L, LUA_REGISTRYINDEX, IO_INPUT);  /* get default input */\n    lua_replace(L, 1);  /* put it at index 1 */\n    tofile(L);  /* check that it's a valid file handle */\n    toclose = 0;  /* do not close it after iteration */\n  }\n  else {  /* open a new file */\n    const char *filename = luaL_checkstring(L, 1);\n    opencheck(L, filename, \"r\");\n    lua_replace(L, 1);  /* put file at index 1 */\n    toclose = 1;  /* close it after iteration */\n  }\n  aux_lines(L, toclose);  /* push iteration function */\n  if (toclose) {\n    lua_pushnil(L);  /* state */\n    lua_pushnil(L);  /* control */\n    lua_pushvalue(L, 1);  /* file is the to-be-closed variable (4th result) */\n    return 4;\n  }\n  else\n    return 1;\n}\n\n\n/*\n** {======================================================\n** READ\n** =======================================================\n*/\n\n\n/* maximum length of a numeral */\n#if !defined (L_MAXLENNUM)\n#define L_MAXLENNUM     200\n#endif\n\n\n/* auxiliary structure used by 'read_number' */\ntypedef struct {\n  FILE *f;  /* file being read */\n  int c;  /* current character (look ahead) */\n  int n;  /* number of elements in buffer 'buff' */\n  char buff[L_MAXLENNUM + 1];  /* +1 for ending '\\0' */\n} RN;\n\n\n/*\n** Add current char to buffer (if not out of space) and read next one\n*/\nstatic int nextc (RN *rn) {\n  if (l_unlikely(rn->n >= L_MAXLENNUM)) {  /* buffer overflow? */\n    rn->buff[0] = '\\0';  /* invalidate result */\n    return 0;  /* fail */\n  }\n  else {\n    rn->buff[rn->n++] = rn->c;  /* save current char */\n    rn->c = l_getc(rn->f);  /* read next one */\n    return 1;\n  }\n}\n\n\n/*\n** Accept current char if it is in 'set' (of size 2)\n*/\nstatic int test2 (RN *rn, const char *set) {\n  if (rn->c == set[0] || rn->c == set[1])\n    return nextc(rn);\n  else return 0;\n}\n\n\n/*\n** Read a sequence of (hex)digits\n*/\nstatic int readdigits (RN *rn, int hex) {\n  int count = 0;\n  while ((hex ? isxdigit(rn->c) : isdigit(rn->c)) && nextc(rn))\n    count++;\n  return count;\n}\n\n\n/*\n** Read a number: first reads a valid prefix of a numeral into a buffer.\n** Then it calls 'lua_stringtonumber' to check whether the format is\n** correct and to convert it to a Lua number.\n*/\nstatic int read_number (lua_State *L, FILE *f) {\n  RN rn;\n  int count = 0;\n  int hex = 0;\n  char decp[2];\n  rn.f = f; rn.n = 0;\n  decp[0] = lua_getlocaledecpoint();  /* get decimal point from locale */\n  decp[1] = '.';  /* always accept a dot */\n  l_lockfile(rn.f);\n  do { rn.c = l_getc(rn.f); } while (isspace(rn.c));  /* skip spaces */\n  test2(&rn, \"-+\");  /* optional sign */\n  if (test2(&rn, \"00\")) {\n    if (test2(&rn, \"xX\")) hex = 1;  /* numeral is hexadecimal */\n    else count = 1;  /* count initial '0' as a valid digit */\n  }\n  count += readdigits(&rn, hex);  /* integral part */\n  if (test2(&rn, decp))  /* decimal point? */\n    count += readdigits(&rn, hex);  /* fractional part */\n  if (count > 0 && test2(&rn, (hex ? \"pP\" : \"eE\"))) {  /* exponent mark? */\n    test2(&rn, \"-+\");  /* exponent sign */\n    readdigits(&rn, 0);  /* exponent digits */\n  }\n  ungetc(rn.c, rn.f);  /* unread look-ahead char */\n  l_unlockfile(rn.f);\n  rn.buff[rn.n] = '\\0';  /* finish string */\n  if (l_likely(lua_stringtonumber(L, rn.buff)))\n    return 1;  /* ok, it is a valid number */\n  else {  /* invalid format */\n   lua_pushnil(L);  /* \"result\" to be removed */\n   return 0;  /* read fails */\n  }\n}\n\n\nstatic int test_eof (lua_State *L, FILE *f) {\n  int c = getc(f);\n  ungetc(c, f);  /* no-op when c == EOF */\n  lua_pushliteral(L, \"\");\n  return (c != EOF);\n}\n\n\nstatic int read_line (lua_State *L, FILE *f, int chop) {\n  luaL_Buffer b;\n  int c;\n  luaL_buffinit(L, &b);\n  do {  /* may need to read several chunks to get whole line */\n    char *buff = luaL_prepbuffer(&b);  /* preallocate buffer space */\n    int i = 0;\n    l_lockfile(f);  /* no memory errors can happen inside the lock */\n    while (i < LUAL_BUFFERSIZE && (c = l_getc(f)) != EOF && c != '\\n')\n      buff[i++] = c;  /* read up to end of line or buffer limit */\n    l_unlockfile(f);\n    luaL_addsize(&b, i);\n  } while (c != EOF && c != '\\n');  /* repeat until end of line */\n  if (!chop && c == '\\n')  /* want a newline and have one? */\n    luaL_addchar(&b, c);  /* add ending newline to result */\n  luaL_pushresult(&b);  /* close buffer */\n  /* return ok if read something (either a newline or something else) */\n  return (c == '\\n' || lua_rawlen(L, -1) > 0);\n}\n\n\nstatic void read_all (lua_State *L, FILE *f) {\n  size_t nr;\n  luaL_Buffer b;\n  luaL_buffinit(L, &b);\n  do {  /* read file in chunks of LUAL_BUFFERSIZE bytes */\n    char *p = luaL_prepbuffer(&b);\n    nr = fread(p, sizeof(char), LUAL_BUFFERSIZE, f);\n    luaL_addsize(&b, nr);\n  } while (nr == LUAL_BUFFERSIZE);\n  luaL_pushresult(&b);  /* close buffer */\n}\n\n\nstatic int read_chars (lua_State *L, FILE *f, size_t n) {\n  size_t nr;  /* number of chars actually read */\n  char *p;\n  luaL_Buffer b;\n  luaL_buffinit(L, &b);\n  p = luaL_prepbuffsize(&b, n);  /* prepare buffer to read whole block */\n  nr = fread(p, sizeof(char), n, f);  /* try to read 'n' chars */\n  luaL_addsize(&b, nr);\n  luaL_pushresult(&b);  /* close buffer */\n  return (nr > 0);  /* true iff read something */\n}\n\n\nstatic int g_read (lua_State *L, FILE *f, int first) {\n  int nargs = lua_gettop(L) - 1;\n  int n, success;\n  clearerr(f);\n  if (nargs == 0) {  /* no arguments? */\n    success = read_line(L, f, 1);\n    n = first + 1;  /* to return 1 result */\n  }\n  else {\n    /* ensure stack space for all results and for auxlib's buffer */\n    luaL_checkstack(L, nargs+LUA_MINSTACK, \"too many arguments\");\n    success = 1;\n    for (n = first; nargs-- && success; n++) {\n      if (lua_type(L, n) == LUA_TNUMBER) {\n        size_t l = (size_t)luaL_checkinteger(L, n);\n        success = (l == 0) ? test_eof(L, f) : read_chars(L, f, l);\n      }\n      else {\n        const char *p = luaL_checkstring(L, n);\n        if (*p == '*') p++;  /* skip optional '*' (for compatibility) */\n        switch (*p) {\n          case 'n':  /* number */\n            success = read_number(L, f);\n            break;\n          case 'l':  /* line */\n            success = read_line(L, f, 1);\n            break;\n          case 'L':  /* line with end-of-line */\n            success = read_line(L, f, 0);\n            break;\n          case 'a':  /* file */\n            read_all(L, f);  /* read entire file */\n            success = 1; /* always success */\n            break;\n          default:\n            return luaL_argerror(L, n, \"invalid format\");\n        }\n      }\n    }\n  }\n  if (ferror(f))\n    return luaL_fileresult(L, 0, NULL);\n  if (!success) {\n    lua_pop(L, 1);  /* remove last result */\n    luaL_pushfail(L);  /* push nil instead */\n  }\n  return n - first;\n}\n\n\nstatic int io_read (lua_State *L) {\n  return g_read(L, getiofile(L, IO_INPUT), 1);\n}\n\n\nstatic int f_read (lua_State *L) {\n  return g_read(L, tofile(L), 2);\n}\n\n\n/*\n** Iteration function for 'lines'.\n*/\nstatic int io_readline (lua_State *L) {\n  LStream *p = (LStream *)lua_touserdata(L, lua_upvalueindex(1));\n  int i;\n  int n = (int)lua_tointeger(L, lua_upvalueindex(2));\n  if (isclosed(p))  /* file is already closed? */\n    return luaL_error(L, \"file is already closed\");\n  lua_settop(L , 1);\n  luaL_checkstack(L, n, \"too many arguments\");\n  for (i = 1; i <= n; i++)  /* push arguments to 'g_read' */\n    lua_pushvalue(L, lua_upvalueindex(3 + i));\n  n = g_read(L, p->f, 2);  /* 'n' is number of results */\n  lua_assert(n > 0);  /* should return at least a nil */\n  if (lua_toboolean(L, -n))  /* read at least one value? */\n    return n;  /* return them */\n  else {  /* first result is false: EOF or error */\n    if (n > 1) {  /* is there error information? */\n      /* 2nd result is error message */\n      return luaL_error(L, \"%s\", lua_tostring(L, -n + 1));\n    }\n    if (lua_toboolean(L, lua_upvalueindex(3))) {  /* generator created file? */\n      lua_settop(L, 0);  /* clear stack */\n      lua_pushvalue(L, lua_upvalueindex(1));  /* push file at index 1 */\n      aux_close(L);  /* close it */\n    }\n    return 0;\n  }\n}\n\n/* }====================================================== */\n\n\nstatic int g_write (lua_State *L, FILE *f, int arg) {\n  int nargs = lua_gettop(L) - arg;\n  int status = 1;\n  for (; nargs--; arg++) {\n    if (lua_type(L, arg) == LUA_TNUMBER) {\n      /* optimization: could be done exactly as for strings */\n      int len = lua_isinteger(L, arg)\n                ? fprintf(f, LUA_INTEGER_FMT,\n                             (LUAI_UACINT)lua_tointeger(L, arg))\n                : fprintf(f, LUA_NUMBER_FMT,\n                             (LUAI_UACNUMBER)lua_tonumber(L, arg));\n      status = status && (len > 0);\n    }\n    else {\n      size_t l;\n      const char *s = luaL_checklstring(L, arg, &l);\n      status = status && (fwrite(s, sizeof(char), l, f) == l);\n    }\n  }\n  if (l_likely(status))\n    return 1;  /* file handle already on stack top */\n  else return luaL_fileresult(L, status, NULL);\n}\n\n\nstatic int io_write (lua_State *L) {\n  return g_write(L, getiofile(L, IO_OUTPUT), 1);\n}\n\n\nstatic int f_write (lua_State *L) {\n  FILE *f = tofile(L);\n  lua_pushvalue(L, 1);  /* push file at the stack top (to be returned) */\n  return g_write(L, f, 2);\n}\n\n\nstatic int f_seek (lua_State *L) {\n  static const int mode[] = {SEEK_SET, SEEK_CUR, SEEK_END};\n  static const char *const modenames[] = {\"set\", \"cur\", \"end\", NULL};\n  FILE *f = tofile(L);\n  int op = luaL_checkoption(L, 2, \"cur\", modenames);\n  lua_Integer p3 = luaL_optinteger(L, 3, 0);\n  l_seeknum offset = (l_seeknum)p3;\n  luaL_argcheck(L, (lua_Integer)offset == p3, 3,\n                  \"not an integer in proper range\");\n  op = l_fseek(f, offset, mode[op]);\n  if (l_unlikely(op))\n    return luaL_fileresult(L, 0, NULL);  /* error */\n  else {\n    lua_pushinteger(L, (lua_Integer)l_ftell(f));\n    return 1;\n  }\n}\n\n\nstatic int f_setvbuf (lua_State *L) {\n  static const int mode[] = {_IONBF, _IOFBF, _IOLBF};\n  static const char *const modenames[] = {\"no\", \"full\", \"line\", NULL};\n  FILE *f = tofile(L);\n  int op = luaL_checkoption(L, 2, NULL, modenames);\n  lua_Integer sz = luaL_optinteger(L, 3, LUAL_BUFFERSIZE);\n  int res = setvbuf(f, NULL, mode[op], (size_t)sz);\n  return luaL_fileresult(L, res == 0, NULL);\n}\n\n\n\nstatic int io_flush (lua_State *L) {\n  return luaL_fileresult(L, fflush(getiofile(L, IO_OUTPUT)) == 0, NULL);\n}\n\n\nstatic int f_flush (lua_State *L) {\n  return luaL_fileresult(L, fflush(tofile(L)) == 0, NULL);\n}\n\n\n/*\n** functions for 'io' library\n*/\nstatic const luaL_Reg iolib[] = {\n  {\"close\", io_close},\n  {\"flush\", io_flush},\n  {\"input\", io_input},\n  {\"lines\", io_lines},\n  {\"open\", io_open},\n  {\"output\", io_output},\n  {\"popen\", io_popen},\n  {\"read\", io_read},\n  {\"tmpfile\", io_tmpfile},\n  {\"type\", io_type},\n  {\"write\", io_write},\n  {NULL, NULL}\n};\n\n\n/*\n** methods for file handles\n*/\nstatic const luaL_Reg meth[] = {\n  {\"read\", f_read},\n  {\"write\", f_write},\n  {\"lines\", f_lines},\n  {\"flush\", f_flush},\n  {\"seek\", f_seek},\n  {\"close\", f_close},\n  {\"setvbuf\", f_setvbuf},\n  {NULL, NULL}\n};\n\n\n/*\n** metamethods for file handles\n*/\nstatic const luaL_Reg metameth[] = {\n  {\"__index\", NULL},  /* place holder */\n  {\"__gc\", f_gc},\n  {\"__close\", f_gc},\n  {\"__tostring\", f_tostring},\n  {NULL, NULL}\n};\n\n\nstatic void createmeta (lua_State *L) {\n  luaL_newmetatable(L, LUA_FILEHANDLE);  /* metatable for file handles */\n  luaL_setfuncs(L, metameth, 0);  /* add metamethods to new metatable */\n  luaL_newlibtable(L, meth);  /* create method table */\n  luaL_setfuncs(L, meth, 0);  /* add file methods to method table */\n  lua_setfield(L, -2, \"__index\");  /* metatable.__index = method table */\n  lua_pop(L, 1);  /* pop metatable */\n}\n\n\n/*\n** function to (not) close the standard files stdin, stdout, and stderr\n*/\nstatic int io_noclose (lua_State *L) {\n  LStream *p = tolstream(L);\n  p->closef = &io_noclose;  /* keep file opened */\n  luaL_pushfail(L);\n  lua_pushliteral(L, \"cannot close standard file\");\n  return 2;\n}\n\n\nstatic void createstdfile (lua_State *L, FILE *f, const char *k,\n                           const char *fname) {\n  LStream *p = newprefile(L);\n  p->f = f;\n  p->closef = &io_noclose;\n  if (k != NULL) {\n    lua_pushvalue(L, -1);\n    lua_setfield(L, LUA_REGISTRYINDEX, k);  /* add file to registry */\n  }\n  lua_setfield(L, -2, fname);  /* add file to module */\n}\n\n\nLUAMOD_API int luaopen_io (lua_State *L) {\n  luaL_newlib(L, iolib);  /* new module */\n  createmeta(L);\n  /* create (and set) default files */\n  createstdfile(L, stdin, IO_INPUT, \"stdin\");\n  createstdfile(L, stdout, IO_OUTPUT, \"stdout\");\n  createstdfile(L, stderr, NULL, \"stderr\");\n  return 1;\n}\n\n/*\n** $Id: lmathlib.c $\n** Standard mathematical library\n** See Copyright Notice in lua.h\n*/\n\n#define lmathlib_c\n#define LUA_LIB\n\n/*#include \"lprefix.h\"*/\n\n\n#include <float.h>\n#include <limits.h>\n#include <math.h>\n#include <stdlib.h>\n#include <time.h>\n\n/*#include \"lua.h\"*/\n\n/*#include \"lauxlib.h\"*/\n/*#include \"lualib.h\"*/\n\n\n#undef PI\n#define PI\t(l_mathop(3.141592653589793238462643383279502884))\n\n\nstatic int math_abs (lua_State *L) {\n  if (lua_isinteger(L, 1)) {\n    lua_Integer n = lua_tointeger(L, 1);\n    if (n < 0) n = (lua_Integer)(0u - (lua_Unsigned)n);\n    lua_pushinteger(L, n);\n  }\n  else\n    lua_pushnumber(L, l_mathop(fabs)(luaL_checknumber(L, 1)));\n  return 1;\n}\n\nstatic int math_sin (lua_State *L) {\n  lua_pushnumber(L, l_mathop(sin)(luaL_checknumber(L, 1)));\n  return 1;\n}\n\nstatic int math_cos (lua_State *L) {\n  lua_pushnumber(L, l_mathop(cos)(luaL_checknumber(L, 1)));\n  return 1;\n}\n\nstatic int math_tan (lua_State *L) {\n  lua_pushnumber(L, l_mathop(tan)(luaL_checknumber(L, 1)));\n  return 1;\n}\n\nstatic int math_asin (lua_State *L) {\n  lua_pushnumber(L, l_mathop(asin)(luaL_checknumber(L, 1)));\n  return 1;\n}\n\nstatic int math_acos (lua_State *L) {\n  lua_pushnumber(L, l_mathop(acos)(luaL_checknumber(L, 1)));\n  return 1;\n}\n\nstatic int math_atan (lua_State *L) {\n  lua_Number y = luaL_checknumber(L, 1);\n  lua_Number x = luaL_optnumber(L, 2, 1);\n  lua_pushnumber(L, l_mathop(atan2)(y, x));\n  return 1;\n}\n\n\nstatic int math_toint (lua_State *L) {\n  int valid;\n  lua_Integer n = lua_tointegerx(L, 1, &valid);\n  if (l_likely(valid))\n    lua_pushinteger(L, n);\n  else {\n    luaL_checkany(L, 1);\n    luaL_pushfail(L);  /* value is not convertible to integer */\n  }\n  return 1;\n}\n\n\nstatic void pushnumint (lua_State *L, lua_Number d) {\n  lua_Integer n;\n  if (lua_numbertointeger(d, &n))  /* does 'd' fit in an integer? */\n    lua_pushinteger(L, n);  /* result is integer */\n  else\n    lua_pushnumber(L, d);  /* result is float */\n}\n\n\nstatic int math_floor (lua_State *L) {\n  if (lua_isinteger(L, 1))\n    lua_settop(L, 1);  /* integer is its own floor */\n  else {\n    lua_Number d = l_mathop(floor)(luaL_checknumber(L, 1));\n    pushnumint(L, d);\n  }\n  return 1;\n}\n\n\nstatic int math_ceil (lua_State *L) {\n  if (lua_isinteger(L, 1))\n    lua_settop(L, 1);  /* integer is its own ceil */\n  else {\n    lua_Number d = l_mathop(ceil)(luaL_checknumber(L, 1));\n    pushnumint(L, d);\n  }\n  return 1;\n}\n\n\nstatic int math_fmod (lua_State *L) {\n  if (lua_isinteger(L, 1) && lua_isinteger(L, 2)) {\n    lua_Integer d = lua_tointeger(L, 2);\n    if ((lua_Unsigned)d + 1u <= 1u) {  /* special cases: -1 or 0 */\n      luaL_argcheck(L, d != 0, 2, \"zero\");\n      lua_pushinteger(L, 0);  /* avoid overflow with 0x80000... / -1 */\n    }\n    else\n      lua_pushinteger(L, lua_tointeger(L, 1) % d);\n  }\n  else\n    lua_pushnumber(L, l_mathop(fmod)(luaL_checknumber(L, 1),\n                                     luaL_checknumber(L, 2)));\n  return 1;\n}\n\n\n/*\n** next function does not use 'modf', avoiding problems with 'double*'\n** (which is not compatible with 'float*') when lua_Number is not\n** 'double'.\n*/\nstatic int math_modf (lua_State *L) {\n  if (lua_isinteger(L ,1)) {\n    lua_settop(L, 1);  /* number is its own integer part */\n    lua_pushnumber(L, 0);  /* no fractional part */\n  }\n  else {\n    lua_Number n = luaL_checknumber(L, 1);\n    /* integer part (rounds toward zero) */\n    lua_Number ip = (n < 0) ? l_mathop(ceil)(n) : l_mathop(floor)(n);\n    pushnumint(L, ip);\n    /* fractional part (test needed for inf/-inf) */\n    lua_pushnumber(L, (n == ip) ? l_mathop(0.0) : (n - ip));\n  }\n  return 2;\n}\n\n\nstatic int math_sqrt (lua_State *L) {\n  lua_pushnumber(L, l_mathop(sqrt)(luaL_checknumber(L, 1)));\n  return 1;\n}\n\n\nstatic int math_ult (lua_State *L) {\n  lua_Integer a = luaL_checkinteger(L, 1);\n  lua_Integer b = luaL_checkinteger(L, 2);\n  lua_pushboolean(L, (lua_Unsigned)a < (lua_Unsigned)b);\n  return 1;\n}\n\nstatic int math_log (lua_State *L) {\n  lua_Number x = luaL_checknumber(L, 1);\n  lua_Number res;\n  if (lua_isnoneornil(L, 2))\n    res = l_mathop(log)(x);\n  else {\n    lua_Number base = luaL_checknumber(L, 2);\n#if !defined(LUA_USE_C89)\n    if (base == l_mathop(2.0))\n      res = l_mathop(log2)(x);\n    else\n#endif\n    if (base == l_mathop(10.0))\n      res = l_mathop(log10)(x);\n    else\n      res = l_mathop(log)(x)/l_mathop(log)(base);\n  }\n  lua_pushnumber(L, res);\n  return 1;\n}\n\nstatic int math_exp (lua_State *L) {\n  lua_pushnumber(L, l_mathop(exp)(luaL_checknumber(L, 1)));\n  return 1;\n}\n\nstatic int math_deg (lua_State *L) {\n  lua_pushnumber(L, luaL_checknumber(L, 1) * (l_mathop(180.0) / PI));\n  return 1;\n}\n\nstatic int math_rad (lua_State *L) {\n  lua_pushnumber(L, luaL_checknumber(L, 1) * (PI / l_mathop(180.0)));\n  return 1;\n}\n\n\nstatic int math_min (lua_State *L) {\n  int n = lua_gettop(L);  /* number of arguments */\n  int imin = 1;  /* index of current minimum value */\n  int i;\n  luaL_argcheck(L, n >= 1, 1, \"value expected\");\n  for (i = 2; i <= n; i++) {\n    if (lua_compare(L, i, imin, LUA_OPLT))\n      imin = i;\n  }\n  lua_pushvalue(L, imin);\n  return 1;\n}\n\n\nstatic int math_max (lua_State *L) {\n  int n = lua_gettop(L);  /* number of arguments */\n  int imax = 1;  /* index of current maximum value */\n  int i;\n  luaL_argcheck(L, n >= 1, 1, \"value expected\");\n  for (i = 2; i <= n; i++) {\n    if (lua_compare(L, imax, i, LUA_OPLT))\n      imax = i;\n  }\n  lua_pushvalue(L, imax);\n  return 1;\n}\n\n\nstatic int math_type (lua_State *L) {\n  if (lua_type(L, 1) == LUA_TNUMBER)\n    lua_pushstring(L, (lua_isinteger(L, 1)) ? \"integer\" : \"float\");\n  else {\n    luaL_checkany(L, 1);\n    luaL_pushfail(L);\n  }\n  return 1;\n}\n\n\n\n/*\n** {==================================================================\n** Pseudo-Random Number Generator based on 'xoshiro256**'.\n** ===================================================================\n*/\n\n/* number of binary digits in the mantissa of a float */\n#define FIGS\tl_floatatt(MANT_DIG)\n\n#if FIGS > 64\n/* there are only 64 random bits; use them all */\n#undef FIGS\n#define FIGS\t64\n#endif\n\n\n/*\n** LUA_RAND32 forces the use of 32-bit integers in the implementation\n** of the PRN generator (mainly for testing).\n*/\n#if !defined(LUA_RAND32) && !defined(Rand64)\n\n/* try to find an integer type with at least 64 bits */\n\n#if ((ULONG_MAX >> 31) >> 31) >= 3\n\n/* 'long' has at least 64 bits */\n#define Rand64\t\tunsigned long\n\n#elif !defined(LUA_USE_C89) && defined(LLONG_MAX)\n\n/* there is a 'long long' type (which must have at least 64 bits) */\n#define Rand64\t\tunsigned long long\n\n#elif ((LUA_MAXUNSIGNED >> 31) >> 31) >= 3\n\n/* 'lua_Unsigned' has at least 64 bits */\n#define Rand64\t\tlua_Unsigned\n\n#endif\n\n#endif\n\n\n#if defined(Rand64)  /* { */\n\n/*\n** Standard implementation, using 64-bit integers.\n** If 'Rand64' has more than 64 bits, the extra bits do not interfere\n** with the 64 initial bits, except in a right shift. Moreover, the\n** final result has to discard the extra bits.\n*/\n\n/* avoid using extra bits when needed */\n#define trim64(x)\t((x) & 0xffffffffffffffffu)\n\n\n/* rotate left 'x' by 'n' bits */\nstatic Rand64 rotl (Rand64 x, int n) {\n  return (x << n) | (trim64(x) >> (64 - n));\n}\n\nstatic Rand64 nextrand (Rand64 *state) {\n  Rand64 state0 = state[0];\n  Rand64 state1 = state[1];\n  Rand64 state2 = state[2] ^ state0;\n  Rand64 state3 = state[3] ^ state1;\n  Rand64 res = rotl(state1 * 5, 7) * 9;\n  state[0] = state0 ^ state3;\n  state[1] = state1 ^ state2;\n  state[2] = state2 ^ (state1 << 17);\n  state[3] = rotl(state3, 45);\n  return res;\n}\n\n\n/* must take care to not shift stuff by more than 63 slots */\n\n\n/*\n** Convert bits from a random integer into a float in the\n** interval [0,1), getting the higher FIG bits from the\n** random unsigned integer and converting that to a float.\n*/\n\n/* must throw out the extra (64 - FIGS) bits */\n#define shift64_FIG\t(64 - FIGS)\n\n/* to scale to [0, 1), multiply by scaleFIG = 2^(-FIGS) */\n#define scaleFIG\t(l_mathop(0.5) / ((Rand64)1 << (FIGS - 1)))\n\nstatic lua_Number I2d (Rand64 x) {\n  return (lua_Number)(trim64(x) >> shift64_FIG) * scaleFIG;\n}\n\n/* convert a 'Rand64' to a 'lua_Unsigned' */\n#define I2UInt(x)\t((lua_Unsigned)trim64(x))\n\n/* convert a 'lua_Unsigned' to a 'Rand64' */\n#define Int2I(x)\t((Rand64)(x))\n\n\n#else\t/* no 'Rand64'   }{ */\n\n/* get an integer with at least 32 bits */\n#if LUAI_IS32INT\ntypedef unsigned int lu_int32;\n#else\ntypedef unsigned long lu_int32;\n#endif\n\n\n/*\n** Use two 32-bit integers to represent a 64-bit quantity.\n*/\ntypedef struct Rand64 {\n  lu_int32 h;  /* higher half */\n  lu_int32 l;  /* lower half */\n} Rand64;\n\n\n/*\n** If 'lu_int32' has more than 32 bits, the extra bits do not interfere\n** with the 32 initial bits, except in a right shift and comparisons.\n** Moreover, the final result has to discard the extra bits.\n*/\n\n/* avoid using extra bits when needed */\n#define trim32(x)\t((x) & 0xffffffffu)\n\n\n/*\n** basic operations on 'Rand64' values\n*/\n\n/* build a new Rand64 value */\nstatic Rand64 packI (lu_int32 h, lu_int32 l) {\n  Rand64 result;\n  result.h = h;\n  result.l = l;\n  return result;\n}\n\n/* return i << n */\nstatic Rand64 Ishl (Rand64 i, int n) {\n  lua_assert(n > 0 && n < 32);\n  return packI((i.h << n) | (trim32(i.l) >> (32 - n)), i.l << n);\n}\n\n/* i1 ^= i2 */\nstatic void Ixor (Rand64 *i1, Rand64 i2) {\n  i1->h ^= i2.h;\n  i1->l ^= i2.l;\n}\n\n/* return i1 + i2 */\nstatic Rand64 Iadd (Rand64 i1, Rand64 i2) {\n  Rand64 result = packI(i1.h + i2.h, i1.l + i2.l);\n  if (trim32(result.l) < trim32(i1.l))  /* carry? */\n    result.h++;\n  return result;\n}\n\n/* return i * 5 */\nstatic Rand64 times5 (Rand64 i) {\n  return Iadd(Ishl(i, 2), i);  /* i * 5 == (i << 2) + i */\n}\n\n/* return i * 9 */\nstatic Rand64 times9 (Rand64 i) {\n  return Iadd(Ishl(i, 3), i);  /* i * 9 == (i << 3) + i */\n}\n\n/* return 'i' rotated left 'n' bits */\nstatic Rand64 rotl (Rand64 i, int n) {\n  lua_assert(n > 0 && n < 32);\n  return packI((i.h << n) | (trim32(i.l) >> (32 - n)),\n               (trim32(i.h) >> (32 - n)) | (i.l << n));\n}\n\n/* for offsets larger than 32, rotate right by 64 - offset */\nstatic Rand64 rotl1 (Rand64 i, int n) {\n  lua_assert(n > 32 && n < 64);\n  n = 64 - n;\n  return packI((trim32(i.h) >> n) | (i.l << (32 - n)),\n               (i.h << (32 - n)) | (trim32(i.l) >> n));\n}\n\n/*\n** implementation of 'xoshiro256**' algorithm on 'Rand64' values\n*/\nstatic Rand64 nextrand (Rand64 *state) {\n  Rand64 res = times9(rotl(times5(state[1]), 7));\n  Rand64 t = Ishl(state[1], 17);\n  Ixor(&state[2], state[0]);\n  Ixor(&state[3], state[1]);\n  Ixor(&state[1], state[2]);\n  Ixor(&state[0], state[3]);\n  Ixor(&state[2], t);\n  state[3] = rotl1(state[3], 45);\n  return res;\n}\n\n\n/*\n** Converts a 'Rand64' into a float.\n*/\n\n/* an unsigned 1 with proper type */\n#define UONE\t\t((lu_int32)1)\n\n\n#if FIGS <= 32\n\n/* 2^(-FIGS) */\n#define scaleFIG       (l_mathop(0.5) / (UONE << (FIGS - 1)))\n\n/*\n** get up to 32 bits from higher half, shifting right to\n** throw out the extra bits.\n*/\nstatic lua_Number I2d (Rand64 x) {\n  lua_Number h = (lua_Number)(trim32(x.h) >> (32 - FIGS));\n  return h * scaleFIG;\n}\n\n#else\t/* 32 < FIGS <= 64 */\n\n/* must take care to not shift stuff by more than 31 slots */\n\n/* 2^(-FIGS) = 1.0 / 2^30 / 2^3 / 2^(FIGS-33) */\n#define scaleFIG  \\\n    (l_mathop(1.0) / (UONE << 30) / l_mathop(8.0) / (UONE << (FIGS - 33)))\n\n/*\n** use FIGS - 32 bits from lower half, throwing out the other\n** (32 - (FIGS - 32)) = (64 - FIGS) bits\n*/\n#define shiftLOW\t(64 - FIGS)\n\n/*\n** higher 32 bits go after those (FIGS - 32) bits: shiftHI = 2^(FIGS - 32)\n*/\n#define shiftHI\t\t((lua_Number)(UONE << (FIGS - 33)) * l_mathop(2.0))\n\n\nstatic lua_Number I2d (Rand64 x) {\n  lua_Number h = (lua_Number)trim32(x.h) * shiftHI;\n  lua_Number l = (lua_Number)(trim32(x.l) >> shiftLOW);\n  return (h + l) * scaleFIG;\n}\n\n#endif\n\n\n/* convert a 'Rand64' to a 'lua_Unsigned' */\nstatic lua_Unsigned I2UInt (Rand64 x) {\n  return (((lua_Unsigned)trim32(x.h) << 31) << 1) | (lua_Unsigned)trim32(x.l);\n}\n\n/* convert a 'lua_Unsigned' to a 'Rand64' */\nstatic Rand64 Int2I (lua_Unsigned n) {\n  return packI((lu_int32)((n >> 31) >> 1), (lu_int32)n);\n}\n\n#endif  /* } */\n\n\n/*\n** A state uses four 'Rand64' values.\n*/\ntypedef struct {\n  Rand64 s[4];\n} RanState;\n\n\n/*\n** Project the random integer 'ran' into the interval [0, n].\n** Because 'ran' has 2^B possible values, the projection can only be\n** uniform when the size of the interval is a power of 2 (exact\n** division). Otherwise, to get a uniform projection into [0, n], we\n** first compute 'lim', the smallest Mersenne number not smaller than\n** 'n'. We then project 'ran' into the interval [0, lim].  If the result\n** is inside [0, n], we are done. Otherwise, we try with another 'ran',\n** until we have a result inside the interval.\n*/\nstatic lua_Unsigned project (lua_Unsigned ran, lua_Unsigned n,\n                             RanState *state) {\n  if ((n & (n + 1)) == 0)  /* is 'n + 1' a power of 2? */\n    return ran & n;  /* no bias */\n  else {\n    lua_Unsigned lim = n;\n    /* compute the smallest (2^b - 1) not smaller than 'n' */\n    lim |= (lim >> 1);\n    lim |= (lim >> 2);\n    lim |= (lim >> 4);\n    lim |= (lim >> 8);\n    lim |= (lim >> 16);\n#if (LUA_MAXUNSIGNED >> 31) >= 3\n    lim |= (lim >> 32);  /* integer type has more than 32 bits */\n#endif\n    lua_assert((lim & (lim + 1)) == 0  /* 'lim + 1' is a power of 2, */\n      && lim >= n  /* not smaller than 'n', */\n      && (lim >> 1) < n);  /* and it is the smallest one */\n    while ((ran &= lim) > n)  /* project 'ran' into [0..lim] */\n      ran = I2UInt(nextrand(state->s));  /* not inside [0..n]? try again */\n    return ran;\n  }\n}\n\n\nstatic int math_random (lua_State *L) {\n  lua_Integer low, up;\n  lua_Unsigned p;\n  RanState *state = (RanState *)lua_touserdata(L, lua_upvalueindex(1));\n  Rand64 rv = nextrand(state->s);  /* next pseudo-random value */\n  switch (lua_gettop(L)) {  /* check number of arguments */\n    case 0: {  /* no arguments */\n      lua_pushnumber(L, I2d(rv));  /* float between 0 and 1 */\n      return 1;\n    }\n    case 1: {  /* only upper limit */\n      low = 1;\n      up = luaL_checkinteger(L, 1);\n      if (up == 0) {  /* single 0 as argument? */\n        lua_pushinteger(L, I2UInt(rv));  /* full random integer */\n        return 1;\n      }\n      break;\n    }\n    case 2: {  /* lower and upper limits */\n      low = luaL_checkinteger(L, 1);\n      up = luaL_checkinteger(L, 2);\n      break;\n    }\n    default: return luaL_error(L, \"wrong number of arguments\");\n  }\n  /* random integer in the interval [low, up] */\n  luaL_argcheck(L, low <= up, 1, \"interval is empty\");\n  /* project random integer into the interval [0, up - low] */\n  p = project(I2UInt(rv), (lua_Unsigned)up - (lua_Unsigned)low, state);\n  lua_pushinteger(L, p + (lua_Unsigned)low);\n  return 1;\n}\n\n\nstatic void setseed (lua_State *L, Rand64 *state,\n                     lua_Unsigned n1, lua_Unsigned n2) {\n  int i;\n  state[0] = Int2I(n1);\n  state[1] = Int2I(0xff);  /* avoid a zero state */\n  state[2] = Int2I(n2);\n  state[3] = Int2I(0);\n  for (i = 0; i < 16; i++)\n    nextrand(state);  /* discard initial values to \"spread\" seed */\n  lua_pushinteger(L, n1);\n  lua_pushinteger(L, n2);\n}\n\n\n/*\n** Set a \"random\" seed. To get some randomness, use the current time\n** and the address of 'L' (in case the machine does address space layout\n** randomization).\n*/\nstatic void randseed (lua_State *L, RanState *state) {\n  lua_Unsigned seed1 = (lua_Unsigned)time(NULL);\n  lua_Unsigned seed2 = (lua_Unsigned)(size_t)L;\n  setseed(L, state->s, seed1, seed2);\n}\n\n\nstatic int math_randomseed (lua_State *L) {\n  RanState *state = (RanState *)lua_touserdata(L, lua_upvalueindex(1));\n  if (lua_isnone(L, 1)) {\n    randseed(L, state);\n  }\n  else {\n    lua_Integer n1 = luaL_checkinteger(L, 1);\n    lua_Integer n2 = luaL_optinteger(L, 2, 0);\n    setseed(L, state->s, n1, n2);\n  }\n  return 2;  /* return seeds */\n}\n\n\nstatic const luaL_Reg randfuncs[] = {\n  {\"random\", math_random},\n  {\"randomseed\", math_randomseed},\n  {NULL, NULL}\n};\n\n\n/*\n** Register the random functions and initialize their state.\n*/\nstatic void setrandfunc (lua_State *L) {\n  RanState *state = (RanState *)lua_newuserdatauv(L, sizeof(RanState), 0);\n  randseed(L, state);  /* initialize with a \"random\" seed */\n  lua_pop(L, 2);  /* remove pushed seeds */\n  luaL_setfuncs(L, randfuncs, 1);\n}\n\n/* }================================================================== */\n\n\n/*\n** {==================================================================\n** Deprecated functions (for compatibility only)\n** ===================================================================\n*/\n#if defined(LUA_COMPAT_MATHLIB)\n\nstatic int math_cosh (lua_State *L) {\n  lua_pushnumber(L, l_mathop(cosh)(luaL_checknumber(L, 1)));\n  return 1;\n}\n\nstatic int math_sinh (lua_State *L) {\n  lua_pushnumber(L, l_mathop(sinh)(luaL_checknumber(L, 1)));\n  return 1;\n}\n\nstatic int math_tanh (lua_State *L) {\n  lua_pushnumber(L, l_mathop(tanh)(luaL_checknumber(L, 1)));\n  return 1;\n}\n\nstatic int math_pow (lua_State *L) {\n  lua_Number x = luaL_checknumber(L, 1);\n  lua_Number y = luaL_checknumber(L, 2);\n  lua_pushnumber(L, l_mathop(pow)(x, y));\n  return 1;\n}\n\nstatic int math_frexp (lua_State *L) {\n  int e;\n  lua_pushnumber(L, l_mathop(frexp)(luaL_checknumber(L, 1), &e));\n  lua_pushinteger(L, e);\n  return 2;\n}\n\nstatic int math_ldexp (lua_State *L) {\n  lua_Number x = luaL_checknumber(L, 1);\n  int ep = (int)luaL_checkinteger(L, 2);\n  lua_pushnumber(L, l_mathop(ldexp)(x, ep));\n  return 1;\n}\n\nstatic int math_log10 (lua_State *L) {\n  lua_pushnumber(L, l_mathop(log10)(luaL_checknumber(L, 1)));\n  return 1;\n}\n\n#endif\n/* }================================================================== */\n\n\n\nstatic const luaL_Reg mathlib[] = {\n  {\"abs\",   math_abs},\n  {\"acos\",  math_acos},\n  {\"asin\",  math_asin},\n  {\"atan\",  math_atan},\n  {\"ceil\",  math_ceil},\n  {\"cos\",   math_cos},\n  {\"deg\",   math_deg},\n  {\"exp\",   math_exp},\n  {\"tointeger\", math_toint},\n  {\"floor\", math_floor},\n  {\"fmod\",   math_fmod},\n  {\"ult\",   math_ult},\n  {\"log\",   math_log},\n  {\"max\",   math_max},\n  {\"min\",   math_min},\n  {\"modf\",   math_modf},\n  {\"rad\",   math_rad},\n  {\"sin\",   math_sin},\n  {\"sqrt\",  math_sqrt},\n  {\"tan\",   math_tan},\n  {\"type\", math_type},\n#if defined(LUA_COMPAT_MATHLIB)\n  {\"atan2\", math_atan},\n  {\"cosh\",   math_cosh},\n  {\"sinh\",   math_sinh},\n  {\"tanh\",   math_tanh},\n  {\"pow\",   math_pow},\n  {\"frexp\", math_frexp},\n  {\"ldexp\", math_ldexp},\n  {\"log10\", math_log10},\n#endif\n  /* placeholders */\n  {\"random\", NULL},\n  {\"randomseed\", NULL},\n  {\"pi\", NULL},\n  {\"huge\", NULL},\n  {\"maxinteger\", NULL},\n  {\"mininteger\", NULL},\n  {NULL, NULL}\n};\n\n\n/*\n** Open math library\n*/\nLUAMOD_API int luaopen_math (lua_State *L) {\n  luaL_newlib(L, mathlib);\n  lua_pushnumber(L, PI);\n  lua_setfield(L, -2, \"pi\");\n  lua_pushnumber(L, (lua_Number)HUGE_VAL);\n  lua_setfield(L, -2, \"huge\");\n  lua_pushinteger(L, LUA_MAXINTEGER);\n  lua_setfield(L, -2, \"maxinteger\");\n  lua_pushinteger(L, LUA_MININTEGER);\n  lua_setfield(L, -2, \"mininteger\");\n  setrandfunc(L);\n  return 1;\n}\n\n/*\n** $Id: loadlib.c $\n** Dynamic library loader for Lua\n** See Copyright Notice in lua.h\n**\n** This module contains an implementation of loadlib for Unix systems\n** that have dlfcn, an implementation for Windows, and a stub for other\n** systems.\n*/\n\n#define loadlib_c\n#define LUA_LIB\n\n/*#include \"lprefix.h\"*/\n\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n/*#include \"lua.h\"*/\n\n/*#include \"lauxlib.h\"*/\n/*#include \"lualib.h\"*/\n\n\n/*\n** LUA_IGMARK is a mark to ignore all before it when building the\n** luaopen_ function name.\n*/\n#if !defined (LUA_IGMARK)\n#define LUA_IGMARK\t\t\"-\"\n#endif\n\n\n/*\n** LUA_CSUBSEP is the character that replaces dots in submodule names\n** when searching for a C loader.\n** LUA_LSUBSEP is the character that replaces dots in submodule names\n** when searching for a Lua loader.\n*/\n#if !defined(LUA_CSUBSEP)\n#define LUA_CSUBSEP\t\tLUA_DIRSEP\n#endif\n\n#if !defined(LUA_LSUBSEP)\n#define LUA_LSUBSEP\t\tLUA_DIRSEP\n#endif\n\n\n/* prefix for open functions in C libraries */\n#define LUA_POF\t\t\"luaopen_\"\n\n/* separator for open functions in C libraries */\n#define LUA_OFSEP\t\"_\"\n\n\n/*\n** key for table in the registry that keeps handles\n** for all loaded C libraries\n*/\nstatic const char *const CLIBS = \"_CLIBS\";\n\n#define LIB_FAIL\t\"open\"\n\n\n#define setprogdir(L)           ((void)0)\n\n\n/*\n** Special type equivalent to '(void*)' for functions in gcc\n** (to suppress warnings when converting function pointers)\n*/\ntypedef void (*voidf)(void);\n\n\n/*\n** system-dependent functions\n*/\n\n/*\n** unload library 'lib'\n*/\nstatic void lsys_unloadlib (void *lib);\n\n/*\n** load C library in file 'path'. If 'seeglb', load with all names in\n** the library global.\n** Returns the library; in case of error, returns NULL plus an\n** error string in the stack.\n*/\nstatic void *lsys_load (lua_State *L, const char *path, int seeglb);\n\n/*\n** Try to find a function named 'sym' in library 'lib'.\n** Returns the function; in case of error, returns NULL plus an\n** error string in the stack.\n*/\nstatic lua_CFunction lsys_sym (lua_State *L, void *lib, const char *sym);\n\n\n\n\n#if defined(LUA_USE_DLOPEN)\t/* { */\n/*\n** {========================================================================\n** This is an implementation of loadlib based on the dlfcn interface.\n** The dlfcn interface is available in Linux, SunOS, Solaris, IRIX, FreeBSD,\n** NetBSD, AIX 4.2, HPUX 11, and  probably most other Unix flavors, at least\n** as an emulation layer on top of native functions.\n** =========================================================================\n*/\n\n#include <dlfcn.h>\n\n/*\n** Macro to convert pointer-to-void* to pointer-to-function. This cast\n** is undefined according to ISO C, but POSIX assumes that it works.\n** (The '__extension__' in gnu compilers is only to avoid warnings.)\n*/\n#if defined(__GNUC__)\n#define cast_func(p) (__extension__ (lua_CFunction)(p))\n#else\n#define cast_func(p) ((lua_CFunction)(p))\n#endif\n\n\nstatic void lsys_unloadlib (void *lib) {\n  dlclose(lib);\n}\n\n\nstatic void *lsys_load (lua_State *L, const char *path, int seeglb) {\n  void *lib = dlopen(path, RTLD_NOW | (seeglb ? RTLD_GLOBAL : RTLD_LOCAL));\n  if (l_unlikely(lib == NULL))\n    lua_pushstring(L, dlerror());\n  return lib;\n}\n\n\nstatic lua_CFunction lsys_sym (lua_State *L, void *lib, const char *sym) {\n  lua_CFunction f = cast_func(dlsym(lib, sym));\n  if (l_unlikely(f == NULL))\n    lua_pushstring(L, dlerror());\n  return f;\n}\n\n/* }====================================================== */\n\n\n\n#elif defined(LUA_DL_DLL)\t/* }{ */\n/*\n** {======================================================================\n** This is an implementation of loadlib for Windows using native functions.\n** =======================================================================\n*/\n\n#include <windows.h>\n\n\n/*\n** optional flags for LoadLibraryEx\n*/\n#if !defined(LUA_LLE_FLAGS)\n#define LUA_LLE_FLAGS\t0\n#endif\n\n\n#undef setprogdir\n\n\n/*\n** Replace in the path (on the top of the stack) any occurrence\n** of LUA_EXEC_DIR with the executable's path.\n*/\nstatic void setprogdir (lua_State *L) {\n  char buff[MAX_PATH + 1];\n  char *lb;\n  DWORD nsize = sizeof(buff)/sizeof(char);\n  DWORD n = GetModuleFileNameA(NULL, buff, nsize);  /* get exec. name */\n  if (n == 0 || n == nsize || (lb = strrchr(buff, '\\\\')) == NULL)\n    luaL_error(L, \"unable to get ModuleFileName\");\n  else {\n    *lb = '\\0';  /* cut name on the last '\\\\' to get the path */\n    luaL_gsub(L, lua_tostring(L, -1), LUA_EXEC_DIR, buff);\n    lua_remove(L, -2);  /* remove original string */\n  }\n}\n\n\n\n\nstatic void pusherror (lua_State *L) {\n  int error = GetLastError();\n  char buffer[128];\n  if (FormatMessageA(FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_SYSTEM,\n      NULL, error, 0, buffer, sizeof(buffer)/sizeof(char), NULL))\n    lua_pushstring(L, buffer);\n  else\n    lua_pushfstring(L, \"system error %d\\n\", error);\n}\n\nstatic void lsys_unloadlib (void *lib) {\n  FreeLibrary((HMODULE)lib);\n}\n\n\nstatic void *lsys_load (lua_State *L, const char *path, int seeglb) {\n  HMODULE lib = LoadLibraryExA(path, NULL, LUA_LLE_FLAGS);\n  (void)(seeglb);  /* not used: symbols are 'global' by default */\n  if (lib == NULL) pusherror(L);\n  return lib;\n}\n\n\nstatic lua_CFunction lsys_sym (lua_State *L, void *lib, const char *sym) {\n  lua_CFunction f = (lua_CFunction)(voidf)GetProcAddress((HMODULE)lib, sym);\n  if (f == NULL) pusherror(L);\n  return f;\n}\n\n/* }====================================================== */\n\n\n#else\t\t\t\t/* }{ */\n/*\n** {======================================================\n** Fallback for other systems\n** =======================================================\n*/\n\n#undef LIB_FAIL\n#define LIB_FAIL\t\"absent\"\n\n\n#define DLMSG\t\"dynamic libraries not enabled; check your Lua installation\"\n\n\nstatic void lsys_unloadlib (void *lib) {\n  (void)(lib);  /* not used */\n}\n\n\nstatic void *lsys_load (lua_State *L, const char *path, int seeglb) {\n  (void)(path); (void)(seeglb);  /* not used */\n  lua_pushliteral(L, DLMSG);\n  return NULL;\n}\n\n\nstatic lua_CFunction lsys_sym (lua_State *L, void *lib, const char *sym) {\n  (void)(lib); (void)(sym);  /* not used */\n  lua_pushliteral(L, DLMSG);\n  return NULL;\n}\n\n/* }====================================================== */\n#endif\t\t\t\t/* } */\n\n\n/*\n** {==================================================================\n** Set Paths\n** ===================================================================\n*/\n\n/*\n** LUA_PATH_VAR and LUA_CPATH_VAR are the names of the environment\n** variables that Lua check to set its paths.\n*/\n#if !defined(LUA_PATH_VAR)\n#define LUA_PATH_VAR    \"LUA_PATH\"\n#endif\n\n#if !defined(LUA_CPATH_VAR)\n#define LUA_CPATH_VAR   \"LUA_CPATH\"\n#endif\n\n\n\n/*\n** return registry.LUA_NOENV as a boolean\n*/\nstatic int noenv (lua_State *L) {\n  int b;\n  lua_getfield(L, LUA_REGISTRYINDEX, \"LUA_NOENV\");\n  b = lua_toboolean(L, -1);\n  lua_pop(L, 1);  /* remove value */\n  return b;\n}\n\n\n/*\n** Set a path\n*/\nstatic void setpath (lua_State *L, const char *fieldname,\n                                   const char *envname,\n                                   const char *dft) {\n  const char *dftmark;\n  const char *nver = lua_pushfstring(L, \"%s%s\", envname, LUA_VERSUFFIX);\n  const char *path = getenv(nver);  /* try versioned name */\n  if (path == NULL)  /* no versioned environment variable? */\n    path = getenv(envname);  /* try unversioned name */\n  if (path == NULL || noenv(L))  /* no environment variable? */\n    lua_pushstring(L, dft);  /* use default */\n  else if ((dftmark = strstr(path, LUA_PATH_SEP LUA_PATH_SEP)) == NULL)\n    lua_pushstring(L, path);  /* nothing to change */\n  else {  /* path contains a \";;\": insert default path in its place */\n    size_t len = strlen(path);\n    luaL_Buffer b;\n    luaL_buffinit(L, &b);\n    if (path < dftmark) {  /* is there a prefix before ';;'? */\n      luaL_addlstring(&b, path, dftmark - path);  /* add it */\n      luaL_addchar(&b, *LUA_PATH_SEP);\n    }\n    luaL_addstring(&b, dft);  /* add default */\n    if (dftmark < path + len - 2) {  /* is there a suffix after ';;'? */\n      luaL_addchar(&b, *LUA_PATH_SEP);\n      luaL_addlstring(&b, dftmark + 2, (path + len - 2) - dftmark);\n    }\n    luaL_pushresult(&b);\n  }\n  setprogdir(L);\n  lua_setfield(L, -3, fieldname);  /* package[fieldname] = path value */\n  lua_pop(L, 1);  /* pop versioned variable name ('nver') */\n}\n\n/* }================================================================== */\n\n\n/*\n** return registry.CLIBS[path]\n*/\nstatic void *checkclib (lua_State *L, const char *path) {\n  void *plib;\n  lua_getfield(L, LUA_REGISTRYINDEX, CLIBS);\n  lua_getfield(L, -1, path);\n  plib = lua_touserdata(L, -1);  /* plib = CLIBS[path] */\n  lua_pop(L, 2);  /* pop CLIBS table and 'plib' */\n  return plib;\n}\n\n\n/*\n** registry.CLIBS[path] = plib        -- for queries\n** registry.CLIBS[#CLIBS + 1] = plib  -- also keep a list of all libraries\n*/\nstatic void addtoclib (lua_State *L, const char *path, void *plib) {\n  lua_getfield(L, LUA_REGISTRYINDEX, CLIBS);\n  lua_pushlightuserdata(L, plib);\n  lua_pushvalue(L, -1);\n  lua_setfield(L, -3, path);  /* CLIBS[path] = plib */\n  lua_rawseti(L, -2, luaL_len(L, -2) + 1);  /* CLIBS[#CLIBS + 1] = plib */\n  lua_pop(L, 1);  /* pop CLIBS table */\n}\n\n\n/*\n** __gc tag method for CLIBS table: calls 'lsys_unloadlib' for all lib\n** handles in list CLIBS\n*/\nstatic int gctm (lua_State *L) {\n  lua_Integer n = luaL_len(L, 1);\n  for (; n >= 1; n--) {  /* for each handle, in reverse order */\n    lua_rawgeti(L, 1, n);  /* get handle CLIBS[n] */\n    lsys_unloadlib(lua_touserdata(L, -1));\n    lua_pop(L, 1);  /* pop handle */\n  }\n  return 0;\n}\n\n\n\n/* error codes for 'lookforfunc' */\n#define ERRLIB\t\t1\n#define ERRFUNC\t\t2\n\n/*\n** Look for a C function named 'sym' in a dynamically loaded library\n** 'path'.\n** First, check whether the library is already loaded; if not, try\n** to load it.\n** Then, if 'sym' is '*', return true (as library has been loaded).\n** Otherwise, look for symbol 'sym' in the library and push a\n** C function with that symbol.\n** Return 0 and 'true' or a function in the stack; in case of\n** errors, return an error code and an error message in the stack.\n*/\nstatic int lookforfunc (lua_State *L, const char *path, const char *sym) {\n  void *reg = checkclib(L, path);  /* check loaded C libraries */\n  if (reg == NULL) {  /* must load library? */\n    reg = lsys_load(L, path, *sym == '*');  /* global symbols if 'sym'=='*' */\n    if (reg == NULL) return ERRLIB;  /* unable to load library */\n    addtoclib(L, path, reg);\n  }\n  if (*sym == '*') {  /* loading only library (no function)? */\n    lua_pushboolean(L, 1);  /* return 'true' */\n    return 0;  /* no errors */\n  }\n  else {\n    lua_CFunction f = lsys_sym(L, reg, sym);\n    if (f == NULL)\n      return ERRFUNC;  /* unable to find function */\n    lua_pushcfunction(L, f);  /* else create new function */\n    return 0;  /* no errors */\n  }\n}\n\n\nstatic int ll_loadlib (lua_State *L) {\n  const char *path = luaL_checkstring(L, 1);\n  const char *init = luaL_checkstring(L, 2);\n  int stat = lookforfunc(L, path, init);\n  if (l_likely(stat == 0))  /* no errors? */\n    return 1;  /* return the loaded function */\n  else {  /* error; error message is on stack top */\n    luaL_pushfail(L);\n    lua_insert(L, -2);\n    lua_pushstring(L, (stat == ERRLIB) ?  LIB_FAIL : \"init\");\n    return 3;  /* return fail, error message, and where */\n  }\n}\n\n\n\n/*\n** {======================================================\n** 'require' function\n** =======================================================\n*/\n\n\nstatic int readable (const char *filename) {\n  FILE *f = fopen(filename, \"r\");  /* try to open file */\n  if (f == NULL) return 0;  /* open failed */\n  fclose(f);\n  return 1;\n}\n\n\n/*\n** Get the next name in '*path' = 'name1;name2;name3;...', changing\n** the ending ';' to '\\0' to create a zero-terminated string. Return\n** NULL when list ends.\n*/\nstatic const char *getnextfilename (char **path, char *end) {\n  char *sep;\n  char *name = *path;\n  if (name == end)\n    return NULL;  /* no more names */\n  else if (*name == '\\0') {  /* from previous iteration? */\n    *name = *LUA_PATH_SEP;  /* restore separator */\n    name++;  /* skip it */\n  }\n  sep = strchr(name, *LUA_PATH_SEP);  /* find next separator */\n  if (sep == NULL)  /* separator not found? */\n    sep = end;  /* name goes until the end */\n  *sep = '\\0';  /* finish file name */\n  *path = sep;  /* will start next search from here */\n  return name;\n}\n\n\n/*\n** Given a path such as \";blabla.so;blublu.so\", pushes the string\n**\n** no file 'blabla.so'\n**\tno file 'blublu.so'\n*/\nstatic void pusherrornotfound (lua_State *L, const char *path) {\n  luaL_Buffer b;\n  luaL_buffinit(L, &b);\n  luaL_addstring(&b, \"no file '\");\n  luaL_addgsub(&b, path, LUA_PATH_SEP, \"'\\n\\tno file '\");\n  luaL_addstring(&b, \"'\");\n  luaL_pushresult(&b);\n}\n\n\nstatic const char *searchpath (lua_State *L, const char *name,\n                                             const char *path,\n                                             const char *sep,\n                                             const char *dirsep) {\n  luaL_Buffer buff;\n  char *pathname;  /* path with name inserted */\n  char *endpathname;  /* its end */\n  const char *filename;\n  /* separator is non-empty and appears in 'name'? */\n  if (*sep != '\\0' && strchr(name, *sep) != NULL)\n    name = luaL_gsub(L, name, sep, dirsep);  /* replace it by 'dirsep' */\n  luaL_buffinit(L, &buff);\n  /* add path to the buffer, replacing marks ('?') with the file name */\n  luaL_addgsub(&buff, path, LUA_PATH_MARK, name);\n  luaL_addchar(&buff, '\\0');\n  pathname = luaL_buffaddr(&buff);  /* writable list of file names */\n  endpathname = pathname + luaL_bufflen(&buff) - 1;\n  while ((filename = getnextfilename(&pathname, endpathname)) != NULL) {\n    if (readable(filename))  /* does file exist and is readable? */\n      return lua_pushstring(L, filename);  /* save and return name */\n  }\n  luaL_pushresult(&buff);  /* push path to create error message */\n  pusherrornotfound(L, lua_tostring(L, -1));  /* create error message */\n  return NULL;  /* not found */\n}\n\n\nstatic int ll_searchpath (lua_State *L) {\n  const char *f = searchpath(L, luaL_checkstring(L, 1),\n                                luaL_checkstring(L, 2),\n                                luaL_optstring(L, 3, \".\"),\n                                luaL_optstring(L, 4, LUA_DIRSEP));\n  if (f != NULL) return 1;\n  else {  /* error message is on top of the stack */\n    luaL_pushfail(L);\n    lua_insert(L, -2);\n    return 2;  /* return fail + error message */\n  }\n}\n\n\nstatic const char *findfile (lua_State *L, const char *name,\n                                           const char *pname,\n                                           const char *dirsep) {\n  const char *path;\n  lua_getfield(L, lua_upvalueindex(1), pname);\n  path = lua_tostring(L, -1);\n  if (l_unlikely(path == NULL))\n    luaL_error(L, \"'package.%s' must be a string\", pname);\n  return searchpath(L, name, path, \".\", dirsep);\n}\n\n\nstatic int checkload (lua_State *L, int stat, const char *filename) {\n  if (l_likely(stat)) {  /* module loaded successfully? */\n    lua_pushstring(L, filename);  /* will be 2nd argument to module */\n    return 2;  /* return open function and file name */\n  }\n  else\n    return luaL_error(L, \"error loading module '%s' from file '%s':\\n\\t%s\",\n                          lua_tostring(L, 1), filename, lua_tostring(L, -1));\n}\n\n\nstatic int searcher_Lua (lua_State *L) {\n  const char *filename;\n  const char *name = luaL_checkstring(L, 1);\n  filename = findfile(L, name, \"path\", LUA_LSUBSEP);\n  if (filename == NULL) return 1;  /* module not found in this path */\n  return checkload(L, (luaL_loadfile(L, filename) == LUA_OK), filename);\n}\n\n\n/*\n** Try to find a load function for module 'modname' at file 'filename'.\n** First, change '.' to '_' in 'modname'; then, if 'modname' has\n** the form X-Y (that is, it has an \"ignore mark\"), build a function\n** name \"luaopen_X\" and look for it. (For compatibility, if that\n** fails, it also tries \"luaopen_Y\".) If there is no ignore mark,\n** look for a function named \"luaopen_modname\".\n*/\nstatic int loadfunc (lua_State *L, const char *filename, const char *modname) {\n  const char *openfunc;\n  const char *mark;\n  modname = luaL_gsub(L, modname, \".\", LUA_OFSEP);\n  mark = strchr(modname, *LUA_IGMARK);\n  if (mark) {\n    int stat;\n    openfunc = lua_pushlstring(L, modname, mark - modname);\n    openfunc = lua_pushfstring(L, LUA_POF\"%s\", openfunc);\n    stat = lookforfunc(L, filename, openfunc);\n    if (stat != ERRFUNC) return stat;\n    modname = mark + 1;  /* else go ahead and try old-style name */\n  }\n  openfunc = lua_pushfstring(L, LUA_POF\"%s\", modname);\n  return lookforfunc(L, filename, openfunc);\n}\n\n\nstatic int searcher_C (lua_State *L) {\n  const char *name = luaL_checkstring(L, 1);\n  const char *filename = findfile(L, name, \"cpath\", LUA_CSUBSEP);\n  if (filename == NULL) return 1;  /* module not found in this path */\n  return checkload(L, (loadfunc(L, filename, name) == 0), filename);\n}\n\n\nstatic int searcher_Croot (lua_State *L) {\n  const char *filename;\n  const char *name = luaL_checkstring(L, 1);\n  const char *p = strchr(name, '.');\n  int stat;\n  if (p == NULL) return 0;  /* is root */\n  lua_pushlstring(L, name, p - name);\n  filename = findfile(L, lua_tostring(L, -1), \"cpath\", LUA_CSUBSEP);\n  if (filename == NULL) return 1;  /* root not found */\n  if ((stat = loadfunc(L, filename, name)) != 0) {\n    if (stat != ERRFUNC)\n      return checkload(L, 0, filename);  /* real error */\n    else {  /* open function not found */\n      lua_pushfstring(L, \"no module '%s' in file '%s'\", name, filename);\n      return 1;\n    }\n  }\n  lua_pushstring(L, filename);  /* will be 2nd argument to module */\n  return 2;\n}\n\n\nstatic int searcher_preload (lua_State *L) {\n  const char *name = luaL_checkstring(L, 1);\n  lua_getfield(L, LUA_REGISTRYINDEX, LUA_PRELOAD_TABLE);\n  if (lua_getfield(L, -1, name) == LUA_TNIL) {  /* not found? */\n    lua_pushfstring(L, \"no field package.preload['%s']\", name);\n    return 1;\n  }\n  else {\n    lua_pushliteral(L, \":preload:\");\n    return 2;\n  }\n}\n\n\nstatic void findloader (lua_State *L, const char *name) {\n  int i;\n  luaL_Buffer msg;  /* to build error message */\n  /* push 'package.searchers' to index 3 in the stack */\n  if (l_unlikely(lua_getfield(L, lua_upvalueindex(1), \"searchers\")\n                 != LUA_TTABLE))\n    luaL_error(L, \"'package.searchers' must be a table\");\n  luaL_buffinit(L, &msg);\n  /*  iterate over available searchers to find a loader */\n  for (i = 1; ; i++) {\n    luaL_addstring(&msg, \"\\n\\t\");  /* error-message prefix */\n    if (l_unlikely(lua_rawgeti(L, 3, i) == LUA_TNIL)) {  /* no more searchers? */\n      lua_pop(L, 1);  /* remove nil */\n      luaL_buffsub(&msg, 2);  /* remove prefix */\n      luaL_pushresult(&msg);  /* create error message */\n      luaL_error(L, \"module '%s' not found:%s\", name, lua_tostring(L, -1));\n    }\n    lua_pushstring(L, name);\n    lua_call(L, 1, 2);  /* call it */\n    if (lua_isfunction(L, -2))  /* did it find a loader? */\n      return;  /* module loader found */\n    else if (lua_isstring(L, -2)) {  /* searcher returned error message? */\n      lua_pop(L, 1);  /* remove extra return */\n      luaL_addvalue(&msg);  /* concatenate error message */\n    }\n    else {  /* no error message */\n      lua_pop(L, 2);  /* remove both returns */\n      luaL_buffsub(&msg, 2);  /* remove prefix */\n    }\n  }\n}\n\n\nstatic int ll_require (lua_State *L) {\n  const char *name = luaL_checkstring(L, 1);\n  lua_settop(L, 1);  /* LOADED table will be at index 2 */\n  lua_getfield(L, LUA_REGISTRYINDEX, LUA_LOADED_TABLE);\n  lua_getfield(L, 2, name);  /* LOADED[name] */\n  if (lua_toboolean(L, -1))  /* is it there? */\n    return 1;  /* package is already loaded */\n  /* else must load package */\n  lua_pop(L, 1);  /* remove 'getfield' result */\n  findloader(L, name);\n  lua_rotate(L, -2, 1);  /* function <-> loader data */\n  lua_pushvalue(L, 1);  /* name is 1st argument to module loader */\n  lua_pushvalue(L, -3);  /* loader data is 2nd argument */\n  /* stack: ...; loader data; loader function; mod. name; loader data */\n  lua_call(L, 2, 1);  /* run loader to load module */\n  /* stack: ...; loader data; result from loader */\n  if (!lua_isnil(L, -1))  /* non-nil return? */\n    lua_setfield(L, 2, name);  /* LOADED[name] = returned value */\n  else\n    lua_pop(L, 1);  /* pop nil */\n  if (lua_getfield(L, 2, name) == LUA_TNIL) {   /* module set no value? */\n    lua_pushboolean(L, 1);  /* use true as result */\n    lua_copy(L, -1, -2);  /* replace loader result */\n    lua_setfield(L, 2, name);  /* LOADED[name] = true */\n  }\n  lua_rotate(L, -2, 1);  /* loader data <-> module result  */\n  return 2;  /* return module result and loader data */\n}\n\n/* }====================================================== */\n\n\n\n\nstatic const luaL_Reg pk_funcs[] = {\n  {\"loadlib\", ll_loadlib},\n  {\"searchpath\", ll_searchpath},\n  /* placeholders */\n  {\"preload\", NULL},\n  {\"cpath\", NULL},\n  {\"path\", NULL},\n  {\"searchers\", NULL},\n  {\"loaded\", NULL},\n  {NULL, NULL}\n};\n\n\nstatic const luaL_Reg ll_funcs[] = {\n  {\"require\", ll_require},\n  {NULL, NULL}\n};\n\n\nstatic void createsearcherstable (lua_State *L) {\n  static const lua_CFunction searchers[] = {\n    searcher_preload,\n    searcher_Lua,\n    searcher_C,\n    searcher_Croot,\n    NULL\n  };\n  int i;\n  /* create 'searchers' table */\n  lua_createtable(L, sizeof(searchers)/sizeof(searchers[0]) - 1, 0);\n  /* fill it with predefined searchers */\n  for (i=0; searchers[i] != NULL; i++) {\n    lua_pushvalue(L, -2);  /* set 'package' as upvalue for all searchers */\n    lua_pushcclosure(L, searchers[i], 1);\n    lua_rawseti(L, -2, i+1);\n  }\n  lua_setfield(L, -2, \"searchers\");  /* put it in field 'searchers' */\n}\n\n\n/*\n** create table CLIBS to keep track of loaded C libraries,\n** setting a finalizer to close all libraries when closing state.\n*/\nstatic void createclibstable (lua_State *L) {\n  luaL_getsubtable(L, LUA_REGISTRYINDEX, CLIBS);  /* create CLIBS table */\n  lua_createtable(L, 0, 1);  /* create metatable for CLIBS */\n  lua_pushcfunction(L, gctm);\n  lua_setfield(L, -2, \"__gc\");  /* set finalizer for CLIBS table */\n  lua_setmetatable(L, -2);\n}\n\n\nLUAMOD_API int luaopen_package (lua_State *L) {\n  createclibstable(L);\n  luaL_newlib(L, pk_funcs);  /* create 'package' table */\n  createsearcherstable(L);\n  /* set paths */\n  setpath(L, \"path\", LUA_PATH_VAR, LUA_PATH_DEFAULT);\n  setpath(L, \"cpath\", LUA_CPATH_VAR, LUA_CPATH_DEFAULT);\n  /* store config information */\n  lua_pushliteral(L, LUA_DIRSEP \"\\n\" LUA_PATH_SEP \"\\n\" LUA_PATH_MARK \"\\n\"\n                     LUA_EXEC_DIR \"\\n\" LUA_IGMARK \"\\n\");\n  lua_setfield(L, -2, \"config\");\n  /* set field 'loaded' */\n  luaL_getsubtable(L, LUA_REGISTRYINDEX, LUA_LOADED_TABLE);\n  lua_setfield(L, -2, \"loaded\");\n  /* set field 'preload' */\n  luaL_getsubtable(L, LUA_REGISTRYINDEX, LUA_PRELOAD_TABLE);\n  lua_setfield(L, -2, \"preload\");\n  lua_pushglobaltable(L);\n  lua_pushvalue(L, -2);  /* set 'package' as upvalue for next lib */\n  luaL_setfuncs(L, ll_funcs, 1);  /* open lib into global table */\n  lua_pop(L, 1);  /* pop global table */\n  return 1;  /* return 'package' table */\n}\n\n/*\n** $Id: loslib.c $\n** Standard Operating System library\n** See Copyright Notice in lua.h\n*/\n\n#define loslib_c\n#define LUA_LIB\n\n/*#include \"lprefix.h\"*/\n\n\n#include <errno.h>\n#include <locale.h>\n#include <stdlib.h>\n#include <string.h>\n#include <time.h>\n\n/*#include \"lua.h\"*/\n\n/*#include \"lauxlib.h\"*/\n/*#include \"lualib.h\"*/\n\n\n/*\n** {==================================================================\n** List of valid conversion specifiers for the 'strftime' function;\n** options are grouped by length; group of length 2 start with '||'.\n** ===================================================================\n*/\n#if !defined(LUA_STRFTIMEOPTIONS)\t/* { */\n\n#if defined(LUA_USE_WINDOWS)\n#define LUA_STRFTIMEOPTIONS  \"aAbBcdHIjmMpSUwWxXyYzZ%\" \\\n    \"||\" \"#c#x#d#H#I#j#m#M#S#U#w#W#y#Y\"  /* two-char options */\n#elif defined(LUA_USE_C89)  /* ANSI C 89 (only 1-char options) */\n#define LUA_STRFTIMEOPTIONS  \"aAbBcdHIjmMpSUwWxXyYZ%\"\n#else  /* C99 specification */\n#define LUA_STRFTIMEOPTIONS  \"aAbBcCdDeFgGhHIjmMnprRStTuUVwWxXyYzZ%\" \\\n    \"||\" \"EcECExEXEyEY\" \"OdOeOHOIOmOMOSOuOUOVOwOWOy\"  /* two-char options */\n#endif\n\n#endif\t\t\t\t\t/* } */\n/* }================================================================== */\n\n\n/*\n** {==================================================================\n** Configuration for time-related stuff\n** ===================================================================\n*/\n\n/*\n** type to represent time_t in Lua\n*/\n#if !defined(LUA_NUMTIME)\t/* { */\n\n#define l_timet\t\t\tlua_Integer\n#define l_pushtime(L,t)\t\tlua_pushinteger(L,(lua_Integer)(t))\n#define l_gettime(L,arg)\tluaL_checkinteger(L, arg)\n\n#else\t\t\t\t/* }{ */\n\n#define l_timet\t\t\tlua_Number\n#define l_pushtime(L,t)\t\tlua_pushnumber(L,(lua_Number)(t))\n#define l_gettime(L,arg)\tluaL_checknumber(L, arg)\n\n#endif\t\t\t\t/* } */\n\n\n#if !defined(l_gmtime)\t\t/* { */\n/*\n** By default, Lua uses gmtime/localtime, except when POSIX is available,\n** where it uses gmtime_r/localtime_r\n*/\n\n#if defined(LUA_USE_POSIX)\t/* { */\n\n#define l_gmtime(t,r)\t\tgmtime_r(t,r)\n#define l_localtime(t,r)\tlocaltime_r(t,r)\n\n#else\t\t\t\t/* }{ */\n\n/* ISO C definitions */\n#define l_gmtime(t,r)\t\t((void)(r)->tm_sec, gmtime(t))\n#define l_localtime(t,r)\t((void)(r)->tm_sec, localtime(t))\n\n#endif\t\t\t\t/* } */\n\n#endif\t\t\t\t/* } */\n\n/* }================================================================== */\n\n\n/*\n** {==================================================================\n** Configuration for 'tmpnam':\n** By default, Lua uses tmpnam except when POSIX is available, where\n** it uses mkstemp.\n** ===================================================================\n*/\n#if !defined(lua_tmpnam)\t/* { */\n\n#if defined(LUA_USE_POSIX)\t/* { */\n\n#include <unistd.h>\n\n#define LUA_TMPNAMBUFSIZE\t32\n\n#if !defined(LUA_TMPNAMTEMPLATE)\n#define LUA_TMPNAMTEMPLATE\t\"/tmp/lua_XXXXXX\"\n#endif\n\n#define lua_tmpnam(b,e) { \\\n        strcpy(b, LUA_TMPNAMTEMPLATE); \\\n        e = mkstemp(b); \\\n        if (e != -1) close(e); \\\n        e = (e == -1); }\n\n#else\t\t\t\t/* }{ */\n\n/* ISO C definitions */\n#define LUA_TMPNAMBUFSIZE\tL_tmpnam\n#define lua_tmpnam(b,e)\t\t{ e = (tmpnam(b) == NULL); }\n\n#endif\t\t\t\t/* } */\n\n#endif\t\t\t\t/* } */\n/* }================================================================== */\n\n\n#if !defined(l_system)\n#if defined(LUA_USE_IOS)\n/* Despite claiming to be ISO C, iOS does not implement 'system'. */\n#define l_system(cmd) ((cmd) == NULL ? 0 : -1)\n#else\n#define l_system(cmd)\tsystem(cmd)  /* default definition */\n#endif\n#endif\n\n\nstatic int os_execute (lua_State *L) {\n  const char *cmd = luaL_optstring(L, 1, NULL);\n  int stat;\n  errno = 0;\n  stat = l_system(cmd);\n  if (cmd != NULL)\n    return luaL_execresult(L, stat);\n  else {\n    lua_pushboolean(L, stat);  /* true if there is a shell */\n    return 1;\n  }\n}\n\n\nstatic int os_remove (lua_State *L) {\n  const char *filename = luaL_checkstring(L, 1);\n  return luaL_fileresult(L, remove(filename) == 0, filename);\n}\n\n\nstatic int os_rename (lua_State *L) {\n  const char *fromname = luaL_checkstring(L, 1);\n  const char *toname = luaL_checkstring(L, 2);\n  return luaL_fileresult(L, rename(fromname, toname) == 0, NULL);\n}\n\n\nstatic int os_tmpname (lua_State *L) {\n  char buff[LUA_TMPNAMBUFSIZE];\n  int err;\n  lua_tmpnam(buff, err);\n  if (l_unlikely(err))\n    return luaL_error(L, \"unable to generate a unique filename\");\n  lua_pushstring(L, buff);\n  return 1;\n}\n\n\nstatic int os_getenv (lua_State *L) {\n  lua_pushstring(L, getenv(luaL_checkstring(L, 1)));  /* if NULL push nil */\n  return 1;\n}\n\n\nstatic int os_clock (lua_State *L) {\n  lua_pushnumber(L, ((lua_Number)clock())/(lua_Number)CLOCKS_PER_SEC);\n  return 1;\n}\n\n\n/*\n** {======================================================\n** Time/Date operations\n** { year=%Y, month=%m, day=%d, hour=%H, min=%M, sec=%S,\n**   wday=%w+1, yday=%j, isdst=? }\n** =======================================================\n*/\n\n/*\n** About the overflow check: an overflow cannot occur when time\n** is represented by a lua_Integer, because either lua_Integer is\n** large enough to represent all int fields or it is not large enough\n** to represent a time that cause a field to overflow.  However, if\n** times are represented as doubles and lua_Integer is int, then the\n** time 0x1.e1853b0d184f6p+55 would cause an overflow when adding 1900\n** to compute the year.\n*/\nstatic void setfield (lua_State *L, const char *key, int value, int delta) {\n  #if (defined(LUA_NUMTIME) && LUA_MAXINTEGER <= INT_MAX)\n    if (l_unlikely(value > LUA_MAXINTEGER - delta))\n      luaL_error(L, \"field '%s' is out-of-bound\", key);\n  #endif\n  lua_pushinteger(L, (lua_Integer)value + delta);\n  lua_setfield(L, -2, key);\n}\n\n\nstatic void setboolfield (lua_State *L, const char *key, int value) {\n  if (value < 0)  /* undefined? */\n    return;  /* does not set field */\n  lua_pushboolean(L, value);\n  lua_setfield(L, -2, key);\n}\n\n\n/*\n** Set all fields from structure 'tm' in the table on top of the stack\n*/\nstatic void setallfields (lua_State *L, struct tm *stm) {\n  setfield(L, \"year\", stm->tm_year, 1900);\n  setfield(L, \"month\", stm->tm_mon, 1);\n  setfield(L, \"day\", stm->tm_mday, 0);\n  setfield(L, \"hour\", stm->tm_hour, 0);\n  setfield(L, \"min\", stm->tm_min, 0);\n  setfield(L, \"sec\", stm->tm_sec, 0);\n  setfield(L, \"yday\", stm->tm_yday, 1);\n  setfield(L, \"wday\", stm->tm_wday, 1);\n  setboolfield(L, \"isdst\", stm->tm_isdst);\n}\n\n\nstatic int getboolfield (lua_State *L, const char *key) {\n  int res;\n  res = (lua_getfield(L, -1, key) == LUA_TNIL) ? -1 : lua_toboolean(L, -1);\n  lua_pop(L, 1);\n  return res;\n}\n\n\nstatic int getfield (lua_State *L, const char *key, int d, int delta) {\n  int isnum;\n  int t = lua_getfield(L, -1, key);  /* get field and its type */\n  lua_Integer res = lua_tointegerx(L, -1, &isnum);\n  if (!isnum) {  /* field is not an integer? */\n    if (l_unlikely(t != LUA_TNIL))  /* some other value? */\n      return luaL_error(L, \"field '%s' is not an integer\", key);\n    else if (l_unlikely(d < 0))  /* absent field; no default? */\n      return luaL_error(L, \"field '%s' missing in date table\", key);\n    res = d;\n  }\n  else {\n    if (!(res >= 0 ? res - delta <= INT_MAX : INT_MIN + delta <= res))\n      return luaL_error(L, \"field '%s' is out-of-bound\", key);\n    res -= delta;\n  }\n  lua_pop(L, 1);\n  return (int)res;\n}\n\n\nstatic const char *checkoption (lua_State *L, const char *conv,\n                                ptrdiff_t convlen, char *buff) {\n  const char *option = LUA_STRFTIMEOPTIONS;\n  int oplen = 1;  /* length of options being checked */\n  for (; *option != '\\0' && oplen <= convlen; option += oplen) {\n    if (*option == '|')  /* next block? */\n      oplen++;  /* will check options with next length (+1) */\n    else if (memcmp(conv, option, oplen) == 0) {  /* match? */\n      memcpy(buff, conv, oplen);  /* copy valid option to buffer */\n      buff[oplen] = '\\0';\n      return conv + oplen;  /* return next item */\n    }\n  }\n  luaL_argerror(L, 1,\n    lua_pushfstring(L, \"invalid conversion specifier '%%%s'\", conv));\n  return conv;  /* to avoid warnings */\n}\n\n\nstatic time_t l_checktime (lua_State *L, int arg) {\n  l_timet t = l_gettime(L, arg);\n  luaL_argcheck(L, (time_t)t == t, arg, \"time out-of-bounds\");\n  return (time_t)t;\n}\n\n\n/* maximum size for an individual 'strftime' item */\n#define SIZETIMEFMT\t250\n\n\nstatic int os_date (lua_State *L) {\n  size_t slen;\n  const char *s = luaL_optlstring(L, 1, \"%c\", &slen);\n  time_t t = luaL_opt(L, l_checktime, 2, time(NULL));\n  const char *se = s + slen;  /* 's' end */\n  struct tm tmr, *stm;\n  if (*s == '!') {  /* UTC? */\n    stm = l_gmtime(&t, &tmr);\n    s++;  /* skip '!' */\n  }\n  else\n    stm = l_localtime(&t, &tmr);\n  if (stm == NULL)  /* invalid date? */\n    return luaL_error(L,\n                 \"date result cannot be represented in this installation\");\n  if (strcmp(s, \"*t\") == 0) {\n    lua_createtable(L, 0, 9);  /* 9 = number of fields */\n    setallfields(L, stm);\n  }\n  else {\n    char cc[4];  /* buffer for individual conversion specifiers */\n    luaL_Buffer b;\n    cc[0] = '%';\n    luaL_buffinit(L, &b);\n    while (s < se) {\n      if (*s != '%')  /* not a conversion specifier? */\n        luaL_addchar(&b, *s++);\n      else {\n        size_t reslen;\n        char *buff = luaL_prepbuffsize(&b, SIZETIMEFMT);\n        s++;  /* skip '%' */\n        s = checkoption(L, s, se - s, cc + 1);  /* copy specifier to 'cc' */\n        reslen = strftime(buff, SIZETIMEFMT, cc, stm);\n        luaL_addsize(&b, reslen);\n      }\n    }\n    luaL_pushresult(&b);\n  }\n  return 1;\n}\n\n\nstatic int os_time (lua_State *L) {\n  time_t t;\n  if (lua_isnoneornil(L, 1))  /* called without args? */\n    t = time(NULL);  /* get current time */\n  else {\n    struct tm ts;\n    luaL_checktype(L, 1, LUA_TTABLE);\n    lua_settop(L, 1);  /* make sure table is at the top */\n    ts.tm_year = getfield(L, \"year\", -1, 1900);\n    ts.tm_mon = getfield(L, \"month\", -1, 1);\n    ts.tm_mday = getfield(L, \"day\", -1, 0);\n    ts.tm_hour = getfield(L, \"hour\", 12, 0);\n    ts.tm_min = getfield(L, \"min\", 0, 0);\n    ts.tm_sec = getfield(L, \"sec\", 0, 0);\n    ts.tm_isdst = getboolfield(L, \"isdst\");\n    t = mktime(&ts);\n    setallfields(L, &ts);  /* update fields with normalized values */\n  }\n  if (t != (time_t)(l_timet)t || t == (time_t)(-1))\n    return luaL_error(L,\n                  \"time result cannot be represented in this installation\");\n  l_pushtime(L, t);\n  return 1;\n}\n\n\nstatic int os_difftime (lua_State *L) {\n  time_t t1 = l_checktime(L, 1);\n  time_t t2 = l_checktime(L, 2);\n  lua_pushnumber(L, (lua_Number)difftime(t1, t2));\n  return 1;\n}\n\n/* }====================================================== */\n\n\nstatic int os_setlocale (lua_State *L) {\n  static const int cat[] = {LC_ALL, LC_COLLATE, LC_CTYPE, LC_MONETARY,\n                      LC_NUMERIC, LC_TIME};\n  static const char *const catnames[] = {\"all\", \"collate\", \"ctype\", \"monetary\",\n     \"numeric\", \"time\", NULL};\n  const char *l = luaL_optstring(L, 1, NULL);\n  int op = luaL_checkoption(L, 2, \"all\", catnames);\n  lua_pushstring(L, setlocale(cat[op], l));\n  return 1;\n}\n\n\nstatic int os_exit (lua_State *L) {\n  int status;\n  if (lua_isboolean(L, 1))\n    status = (lua_toboolean(L, 1) ? EXIT_SUCCESS : EXIT_FAILURE);\n  else\n    status = (int)luaL_optinteger(L, 1, EXIT_SUCCESS);\n  if (lua_toboolean(L, 2))\n    lua_close(L);\n  if (L) exit(status);  /* 'if' to avoid warnings for unreachable 'return' */\n  return 0;\n}\n\n\nstatic const luaL_Reg syslib[] = {\n  {\"clock\",     os_clock},\n  {\"date\",      os_date},\n  {\"difftime\",  os_difftime},\n  {\"execute\",   os_execute},\n  {\"exit\",      os_exit},\n  {\"getenv\",    os_getenv},\n  {\"remove\",    os_remove},\n  {\"rename\",    os_rename},\n  {\"setlocale\", os_setlocale},\n  {\"time\",      os_time},\n  {\"tmpname\",   os_tmpname},\n  {NULL, NULL}\n};\n\n/* }====================================================== */\n\n\n\nLUAMOD_API int luaopen_os (lua_State *L) {\n  luaL_newlib(L, syslib);\n  return 1;\n}\n\n/*\n** $Id: lstrlib.c $\n** Standard library for string operations and pattern-matching\n** See Copyright Notice in lua.h\n*/\n\n#define lstrlib_c\n#define LUA_LIB\n\n/*#include \"lprefix.h\"*/\n\n\n#include <ctype.h>\n#include <float.h>\n#include <limits.h>\n#include <locale.h>\n#include <math.h>\n#include <stddef.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n/*#include \"lua.h\"*/\n\n/*#include \"lauxlib.h\"*/\n/*#include \"lualib.h\"*/\n\n\n/*\n** maximum number of captures that a pattern can do during\n** pattern-matching. This limit is arbitrary, but must fit in\n** an unsigned char.\n*/\n#if !defined(LUA_MAXCAPTURES)\n#define LUA_MAXCAPTURES\t\t32\n#endif\n\n\n/* macro to 'unsign' a character */\n#define uchar(c)\t((unsigned char)(c))\n\n\n/*\n** Some sizes are better limited to fit in 'int', but must also fit in\n** 'size_t'. (We assume that 'lua_Integer' cannot be smaller than 'int'.)\n*/\n#define MAX_SIZET\t((size_t)(~(size_t)0))\n\n#define MAXSIZE  \\\n\t(sizeof(size_t) < sizeof(int) ? MAX_SIZET : (size_t)(INT_MAX))\n\n\n\n\nstatic int str_len (lua_State *L) {\n  size_t l;\n  luaL_checklstring(L, 1, &l);\n  lua_pushinteger(L, (lua_Integer)l);\n  return 1;\n}\n\n\n/*\n** translate a relative initial string position\n** (negative means back from end): clip result to [1, inf).\n** The length of any string in Lua must fit in a lua_Integer,\n** so there are no overflows in the casts.\n** The inverted comparison avoids a possible overflow\n** computing '-pos'.\n*/\nstatic size_t posrelatI (lua_Integer pos, size_t len) {\n  if (pos > 0)\n    return (size_t)pos;\n  else if (pos == 0)\n    return 1;\n  else if (pos < -(lua_Integer)len)  /* inverted comparison */\n    return 1;  /* clip to 1 */\n  else return len + (size_t)pos + 1;\n}\n\n\n/*\n** Gets an optional ending string position from argument 'arg',\n** with default value 'def'.\n** Negative means back from end: clip result to [0, len]\n*/\nstatic size_t getendpos (lua_State *L, int arg, lua_Integer def,\n                         size_t len) {\n  lua_Integer pos = luaL_optinteger(L, arg, def);\n  if (pos > (lua_Integer)len)\n    return len;\n  else if (pos >= 0)\n    return (size_t)pos;\n  else if (pos < -(lua_Integer)len)\n    return 0;\n  else return len + (size_t)pos + 1;\n}\n\n\nstatic int str_sub (lua_State *L) {\n  size_t l;\n  const char *s = luaL_checklstring(L, 1, &l);\n  size_t start = posrelatI(luaL_checkinteger(L, 2), l);\n  size_t end = getendpos(L, 3, -1, l);\n  if (start <= end)\n    lua_pushlstring(L, s + start - 1, (end - start) + 1);\n  else lua_pushliteral(L, \"\");\n  return 1;\n}\n\n\nstatic int str_reverse (lua_State *L) {\n  size_t l, i;\n  luaL_Buffer b;\n  const char *s = luaL_checklstring(L, 1, &l);\n  char *p = luaL_buffinitsize(L, &b, l);\n  for (i = 0; i < l; i++)\n    p[i] = s[l - i - 1];\n  luaL_pushresultsize(&b, l);\n  return 1;\n}\n\n\nstatic int str_lower (lua_State *L) {\n  size_t l;\n  size_t i;\n  luaL_Buffer b;\n  const char *s = luaL_checklstring(L, 1, &l);\n  char *p = luaL_buffinitsize(L, &b, l);\n  for (i=0; i<l; i++)\n    p[i] = tolower(uchar(s[i]));\n  luaL_pushresultsize(&b, l);\n  return 1;\n}\n\n\nstatic int str_upper (lua_State *L) {\n  size_t l;\n  size_t i;\n  luaL_Buffer b;\n  const char *s = luaL_checklstring(L, 1, &l);\n  char *p = luaL_buffinitsize(L, &b, l);\n  for (i=0; i<l; i++)\n    p[i] = toupper(uchar(s[i]));\n  luaL_pushresultsize(&b, l);\n  return 1;\n}\n\n\nstatic int str_rep (lua_State *L) {\n  size_t l, lsep;\n  const char *s = luaL_checklstring(L, 1, &l);\n  lua_Integer n = luaL_checkinteger(L, 2);\n  const char *sep = luaL_optlstring(L, 3, \"\", &lsep);\n  if (n <= 0)\n    lua_pushliteral(L, \"\");\n  else if (l_unlikely(l + lsep < l || l + lsep > MAXSIZE / n))\n    return luaL_error(L, \"resulting string too large\");\n  else {\n    size_t totallen = (size_t)n * l + (size_t)(n - 1) * lsep;\n    luaL_Buffer b;\n    char *p = luaL_buffinitsize(L, &b, totallen);\n    while (n-- > 1) {  /* first n-1 copies (followed by separator) */\n      memcpy(p, s, l * sizeof(char)); p += l;\n      if (lsep > 0) {  /* empty 'memcpy' is not that cheap */\n        memcpy(p, sep, lsep * sizeof(char));\n        p += lsep;\n      }\n    }\n    memcpy(p, s, l * sizeof(char));  /* last copy (not followed by separator) */\n    luaL_pushresultsize(&b, totallen);\n  }\n  return 1;\n}\n\n\nstatic int str_byte (lua_State *L) {\n  size_t l;\n  const char *s = luaL_checklstring(L, 1, &l);\n  lua_Integer pi = luaL_optinteger(L, 2, 1);\n  size_t posi = posrelatI(pi, l);\n  size_t pose = getendpos(L, 3, pi, l);\n  int n, i;\n  if (posi > pose) return 0;  /* empty interval; return no values */\n  if (l_unlikely(pose - posi >= (size_t)INT_MAX))  /* arithmetic overflow? */\n    return luaL_error(L, \"string slice too long\");\n  n = (int)(pose -  posi) + 1;\n  luaL_checkstack(L, n, \"string slice too long\");\n  for (i=0; i<n; i++)\n    lua_pushinteger(L, uchar(s[posi+i-1]));\n  return n;\n}\n\n\nstatic int str_char (lua_State *L) {\n  int n = lua_gettop(L);  /* number of arguments */\n  int i;\n  luaL_Buffer b;\n  char *p = luaL_buffinitsize(L, &b, n);\n  for (i=1; i<=n; i++) {\n    lua_Unsigned c = (lua_Unsigned)luaL_checkinteger(L, i);\n    luaL_argcheck(L, c <= (lua_Unsigned)UCHAR_MAX, i, \"value out of range\");\n    p[i - 1] = uchar(c);\n  }\n  luaL_pushresultsize(&b, n);\n  return 1;\n}\n\n\n/*\n** Buffer to store the result of 'string.dump'. It must be initialized\n** after the call to 'lua_dump', to ensure that the function is on the\n** top of the stack when 'lua_dump' is called. ('luaL_buffinit' might\n** push stuff.)\n*/\nstruct str_Writer {\n  int init;  /* true iff buffer has been initialized */\n  luaL_Buffer B;\n};\n\n\nstatic int writer (lua_State *L, const void *b, size_t size, void *ud) {\n  struct str_Writer *state = (struct str_Writer *)ud;\n  if (!state->init) {\n    state->init = 1;\n    luaL_buffinit(L, &state->B);\n  }\n  luaL_addlstring(&state->B, (const char *)b, size);\n  return 0;\n}\n\n\nstatic int str_dump (lua_State *L) {\n  struct str_Writer state;\n  int strip = lua_toboolean(L, 2);\n  luaL_checktype(L, 1, LUA_TFUNCTION);\n  lua_settop(L, 1);  /* ensure function is on the top of the stack */\n  state.init = 0;\n  if (l_unlikely(lua_dump(L, writer, &state, strip) != 0))\n    return luaL_error(L, \"unable to dump given function\");\n  luaL_pushresult(&state.B);\n  return 1;\n}\n\n\n\n/*\n** {======================================================\n** METAMETHODS\n** =======================================================\n*/\n\n#if defined(LUA_NOCVTS2N)\t/* { */\n\n/* no coercion from strings to numbers */\n\nstatic const luaL_Reg stringmetamethods[] = {\n  {\"__index\", NULL},  /* placeholder */\n  {NULL, NULL}\n};\n\n#else\t\t/* }{ */\n\nstatic int tonum (lua_State *L, int arg) {\n  if (lua_type(L, arg) == LUA_TNUMBER) {  /* already a number? */\n    lua_pushvalue(L, arg);\n    return 1;\n  }\n  else {  /* check whether it is a numerical string */\n    size_t len;\n    const char *s = lua_tolstring(L, arg, &len);\n    return (s != NULL && lua_stringtonumber(L, s) == len + 1);\n  }\n}\n\n\nstatic void trymt (lua_State *L, const char *mtname) {\n  lua_settop(L, 2);  /* back to the original arguments */\n  if (l_unlikely(lua_type(L, 2) == LUA_TSTRING ||\n                 !luaL_getmetafield(L, 2, mtname)))\n    luaL_error(L, \"attempt to %s a '%s' with a '%s'\", mtname + 2,\n                  luaL_typename(L, -2), luaL_typename(L, -1));\n  lua_insert(L, -3);  /* put metamethod before arguments */\n  lua_call(L, 2, 1);  /* call metamethod */\n}\n\n\nstatic int arith (lua_State *L, int op, const char *mtname) {\n  if (tonum(L, 1) && tonum(L, 2))\n    lua_arith(L, op);  /* result will be on the top */\n  else\n    trymt(L, mtname);\n  return 1;\n}\n\n\nstatic int arith_add (lua_State *L) {\n  return arith(L, LUA_OPADD, \"__add\");\n}\n\nstatic int arith_sub (lua_State *L) {\n  return arith(L, LUA_OPSUB, \"__sub\");\n}\n\nstatic int arith_mul (lua_State *L) {\n  return arith(L, LUA_OPMUL, \"__mul\");\n}\n\nstatic int arith_mod (lua_State *L) {\n  return arith(L, LUA_OPMOD, \"__mod\");\n}\n\nstatic int arith_pow (lua_State *L) {\n  return arith(L, LUA_OPPOW, \"__pow\");\n}\n\nstatic int arith_div (lua_State *L) {\n  return arith(L, LUA_OPDIV, \"__div\");\n}\n\nstatic int arith_idiv (lua_State *L) {\n  return arith(L, LUA_OPIDIV, \"__idiv\");\n}\n\nstatic int arith_unm (lua_State *L) {\n  return arith(L, LUA_OPUNM, \"__unm\");\n}\n\n\nstatic const luaL_Reg stringmetamethods[] = {\n  {\"__add\", arith_add},\n  {\"__sub\", arith_sub},\n  {\"__mul\", arith_mul},\n  {\"__mod\", arith_mod},\n  {\"__pow\", arith_pow},\n  {\"__div\", arith_div},\n  {\"__idiv\", arith_idiv},\n  {\"__unm\", arith_unm},\n  {\"__index\", NULL},  /* placeholder */\n  {NULL, NULL}\n};\n\n#endif\t\t/* } */\n\n/* }====================================================== */\n\n/*\n** {======================================================\n** PATTERN MATCHING\n** =======================================================\n*/\n\n\n#define CAP_UNFINISHED\t(-1)\n#define CAP_POSITION\t(-2)\n\n\ntypedef struct MatchState {\n  const char *src_init;  /* init of source string */\n  const char *src_end;  /* end ('\\0') of source string */\n  const char *p_end;  /* end ('\\0') of pattern */\n  lua_State *L;\n  int matchdepth;  /* control for recursive depth (to avoid C stack overflow) */\n  unsigned char level;  /* total number of captures (finished or unfinished) */\n  struct {\n    const char *init;\n    ptrdiff_t len;\n  } capture[LUA_MAXCAPTURES];\n} MatchState;\n\n\n/* recursive function */\nstatic const char *match (MatchState *ms, const char *s, const char *p);\n\n\n/* maximum recursion depth for 'match' */\n#if !defined(MAXCCALLS)\n#define MAXCCALLS\t200\n#endif\n\n\n#define L_ESC\t\t'%'\n#define SPECIALS\t\"^$*+?.([%-\"\n\n\nstatic int check_capture (MatchState *ms, int l) {\n  l -= '1';\n  if (l_unlikely(l < 0 || l >= ms->level ||\n                 ms->capture[l].len == CAP_UNFINISHED))\n    return luaL_error(ms->L, \"invalid capture index %%%d\", l + 1);\n  return l;\n}\n\n\nstatic int capture_to_close (MatchState *ms) {\n  int level = ms->level;\n  for (level--; level>=0; level--)\n    if (ms->capture[level].len == CAP_UNFINISHED) return level;\n  return luaL_error(ms->L, \"invalid pattern capture\");\n}\n\n\nstatic const char *classend (MatchState *ms, const char *p) {\n  switch (*p++) {\n    case L_ESC: {\n      if (l_unlikely(p == ms->p_end))\n        luaL_error(ms->L, \"malformed pattern (ends with '%%')\");\n      return p+1;\n    }\n    case '[': {\n      if (*p == '^') p++;\n      do {  /* look for a ']' */\n        if (l_unlikely(p == ms->p_end))\n          luaL_error(ms->L, \"malformed pattern (missing ']')\");\n        if (*(p++) == L_ESC && p < ms->p_end)\n          p++;  /* skip escapes (e.g. '%]') */\n      } while (*p != ']');\n      return p+1;\n    }\n    default: {\n      return p;\n    }\n  }\n}\n\n\nstatic int match_class (int c, int cl) {\n  int res;\n  switch (tolower(cl)) {\n    case 'a' : res = isalpha(c); break;\n    case 'c' : res = iscntrl(c); break;\n    case 'd' : res = isdigit(c); break;\n    case 'g' : res = isgraph(c); break;\n    case 'l' : res = islower(c); break;\n    case 'p' : res = ispunct(c); break;\n    case 's' : res = isspace(c); break;\n    case 'u' : res = isupper(c); break;\n    case 'w' : res = isalnum(c); break;\n    case 'x' : res = isxdigit(c); break;\n    case 'z' : res = (c == 0); break;  /* deprecated option */\n    default: return (cl == c);\n  }\n  return (islower(cl) ? res : !res);\n}\n\n\nstatic int matchbracketclass (int c, const char *p, const char *ec) {\n  int sig = 1;\n  if (*(p+1) == '^') {\n    sig = 0;\n    p++;  /* skip the '^' */\n  }\n  while (++p < ec) {\n    if (*p == L_ESC) {\n      p++;\n      if (match_class(c, uchar(*p)))\n        return sig;\n    }\n    else if ((*(p+1) == '-') && (p+2 < ec)) {\n      p+=2;\n      if (uchar(*(p-2)) <= c && c <= uchar(*p))\n        return sig;\n    }\n    else if (uchar(*p) == c) return sig;\n  }\n  return !sig;\n}\n\n\nstatic int singlematch (MatchState *ms, const char *s, const char *p,\n                        const char *ep) {\n  if (s >= ms->src_end)\n    return 0;\n  else {\n    int c = uchar(*s);\n    switch (*p) {\n      case '.': return 1;  /* matches any char */\n      case L_ESC: return match_class(c, uchar(*(p+1)));\n      case '[': return matchbracketclass(c, p, ep-1);\n      default:  return (uchar(*p) == c);\n    }\n  }\n}\n\n\nstatic const char *matchbalance (MatchState *ms, const char *s,\n                                   const char *p) {\n  if (l_unlikely(p >= ms->p_end - 1))\n    luaL_error(ms->L, \"malformed pattern (missing arguments to '%%b')\");\n  if (*s != *p) return NULL;\n  else {\n    int b = *p;\n    int e = *(p+1);\n    int cont = 1;\n    while (++s < ms->src_end) {\n      if (*s == e) {\n        if (--cont == 0) return s+1;\n      }\n      else if (*s == b) cont++;\n    }\n  }\n  return NULL;  /* string ends out of balance */\n}\n\n\nstatic const char *max_expand (MatchState *ms, const char *s,\n                                 const char *p, const char *ep) {\n  ptrdiff_t i = 0;  /* counts maximum expand for item */\n  while (singlematch(ms, s + i, p, ep))\n    i++;\n  /* keeps trying to match with the maximum repetitions */\n  while (i>=0) {\n    const char *res = match(ms, (s+i), ep+1);\n    if (res) return res;\n    i--;  /* else didn't match; reduce 1 repetition to try again */\n  }\n  return NULL;\n}\n\n\nstatic const char *min_expand (MatchState *ms, const char *s,\n                                 const char *p, const char *ep) {\n  for (;;) {\n    const char *res = match(ms, s, ep+1);\n    if (res != NULL)\n      return res;\n    else if (singlematch(ms, s, p, ep))\n      s++;  /* try with one more repetition */\n    else return NULL;\n  }\n}\n\n\nstatic const char *start_capture (MatchState *ms, const char *s,\n                                    const char *p, int what) {\n  const char *res;\n  int level = ms->level;\n  if (level >= LUA_MAXCAPTURES) luaL_error(ms->L, \"too many captures\");\n  ms->capture[level].init = s;\n  ms->capture[level].len = what;\n  ms->level = level+1;\n  if ((res=match(ms, s, p)) == NULL)  /* match failed? */\n    ms->level--;  /* undo capture */\n  return res;\n}\n\n\nstatic const char *end_capture (MatchState *ms, const char *s,\n                                  const char *p) {\n  int l = capture_to_close(ms);\n  const char *res;\n  ms->capture[l].len = s - ms->capture[l].init;  /* close capture */\n  if ((res = match(ms, s, p)) == NULL)  /* match failed? */\n    ms->capture[l].len = CAP_UNFINISHED;  /* undo capture */\n  return res;\n}\n\n\nstatic const char *match_capture (MatchState *ms, const char *s, int l) {\n  size_t len;\n  l = check_capture(ms, l);\n  len = ms->capture[l].len;\n  if ((size_t)(ms->src_end-s) >= len &&\n      memcmp(ms->capture[l].init, s, len) == 0)\n    return s+len;\n  else return NULL;\n}\n\n\nstatic const char *match (MatchState *ms, const char *s, const char *p) {\n  if (l_unlikely(ms->matchdepth-- == 0))\n    luaL_error(ms->L, \"pattern too complex\");\n  init: /* using goto to optimize tail recursion */\n  if (p != ms->p_end) {  /* end of pattern? */\n    switch (*p) {\n      case '(': {  /* start capture */\n        if (*(p + 1) == ')')  /* position capture? */\n          s = start_capture(ms, s, p + 2, CAP_POSITION);\n        else\n          s = start_capture(ms, s, p + 1, CAP_UNFINISHED);\n        break;\n      }\n      case ')': {  /* end capture */\n        s = end_capture(ms, s, p + 1);\n        break;\n      }\n      case '$': {\n        if ((p + 1) != ms->p_end)  /* is the '$' the last char in pattern? */\n          goto dflt;  /* no; go to default */\n        s = (s == ms->src_end) ? s : NULL;  /* check end of string */\n        break;\n      }\n      case L_ESC: {  /* escaped sequences not in the format class[*+?-]? */\n        switch (*(p + 1)) {\n          case 'b': {  /* balanced string? */\n            s = matchbalance(ms, s, p + 2);\n            if (s != NULL) {\n              p += 4; goto init;  /* return match(ms, s, p + 4); */\n            }  /* else fail (s == NULL) */\n            break;\n          }\n          case 'f': {  /* frontier? */\n            const char *ep; char previous;\n            p += 2;\n            if (l_unlikely(*p != '['))\n              luaL_error(ms->L, \"missing '[' after '%%f' in pattern\");\n            ep = classend(ms, p);  /* points to what is next */\n            previous = (s == ms->src_init) ? '\\0' : *(s - 1);\n            if (!matchbracketclass(uchar(previous), p, ep - 1) &&\n               matchbracketclass(uchar(*s), p, ep - 1)) {\n              p = ep; goto init;  /* return match(ms, s, ep); */\n            }\n            s = NULL;  /* match failed */\n            break;\n          }\n          case '0': case '1': case '2': case '3':\n          case '4': case '5': case '6': case '7':\n          case '8': case '9': {  /* capture results (%0-%9)? */\n            s = match_capture(ms, s, uchar(*(p + 1)));\n            if (s != NULL) {\n              p += 2; goto init;  /* return match(ms, s, p + 2) */\n            }\n            break;\n          }\n          default: goto dflt;\n        }\n        break;\n      }\n      default: dflt: {  /* pattern class plus optional suffix */\n        const char *ep = classend(ms, p);  /* points to optional suffix */\n        /* does not match at least once? */\n        if (!singlematch(ms, s, p, ep)) {\n          if (*ep == '*' || *ep == '?' || *ep == '-') {  /* accept empty? */\n            p = ep + 1; goto init;  /* return match(ms, s, ep + 1); */\n          }\n          else  /* '+' or no suffix */\n            s = NULL;  /* fail */\n        }\n        else {  /* matched once */\n          switch (*ep) {  /* handle optional suffix */\n            case '?': {  /* optional */\n              const char *res;\n              if ((res = match(ms, s + 1, ep + 1)) != NULL)\n                s = res;\n              else {\n                p = ep + 1; goto init;  /* else return match(ms, s, ep + 1); */\n              }\n              break;\n            }\n            case '+':  /* 1 or more repetitions */\n              s++;  /* 1 match already done */\n              /* FALLTHROUGH */\n            case '*':  /* 0 or more repetitions */\n              s = max_expand(ms, s, p, ep);\n              break;\n            case '-':  /* 0 or more repetitions (minimum) */\n              s = min_expand(ms, s, p, ep);\n              break;\n            default:  /* no suffix */\n              s++; p = ep; goto init;  /* return match(ms, s + 1, ep); */\n          }\n        }\n        break;\n      }\n    }\n  }\n  ms->matchdepth++;\n  return s;\n}\n\n\n\nstatic const char *lmemfind (const char *s1, size_t l1,\n                               const char *s2, size_t l2) {\n  if (l2 == 0) return s1;  /* empty strings are everywhere */\n  else if (l2 > l1) return NULL;  /* avoids a negative 'l1' */\n  else {\n    const char *init;  /* to search for a '*s2' inside 's1' */\n    l2--;  /* 1st char will be checked by 'memchr' */\n    l1 = l1-l2;  /* 's2' cannot be found after that */\n    while (l1 > 0 && (init = (const char *)memchr(s1, *s2, l1)) != NULL) {\n      init++;   /* 1st char is already checked */\n      if (memcmp(init, s2+1, l2) == 0)\n        return init-1;\n      else {  /* correct 'l1' and 's1' to try again */\n        l1 -= init-s1;\n        s1 = init;\n      }\n    }\n    return NULL;  /* not found */\n  }\n}\n\n\n/*\n** get information about the i-th capture. If there are no captures\n** and 'i==0', return information about the whole match, which\n** is the range 's'..'e'. If the capture is a string, return\n** its length and put its address in '*cap'. If it is an integer\n** (a position), push it on the stack and return CAP_POSITION.\n*/\nstatic size_t get_onecapture (MatchState *ms, int i, const char *s,\n                              const char *e, const char **cap) {\n  if (i >= ms->level) {\n    if (l_unlikely(i != 0))\n      luaL_error(ms->L, \"invalid capture index %%%d\", i + 1);\n    *cap = s;\n    return e - s;\n  }\n  else {\n    ptrdiff_t capl = ms->capture[i].len;\n    *cap = ms->capture[i].init;\n    if (l_unlikely(capl == CAP_UNFINISHED))\n      luaL_error(ms->L, \"unfinished capture\");\n    else if (capl == CAP_POSITION)\n      lua_pushinteger(ms->L, (ms->capture[i].init - ms->src_init) + 1);\n    return capl;\n  }\n}\n\n\n/*\n** Push the i-th capture on the stack.\n*/\nstatic void push_onecapture (MatchState *ms, int i, const char *s,\n                                                    const char *e) {\n  const char *cap;\n  ptrdiff_t l = get_onecapture(ms, i, s, e, &cap);\n  if (l != CAP_POSITION)\n    lua_pushlstring(ms->L, cap, l);\n  /* else position was already pushed */\n}\n\n\nstatic int push_captures (MatchState *ms, const char *s, const char *e) {\n  int i;\n  int nlevels = (ms->level == 0 && s) ? 1 : ms->level;\n  luaL_checkstack(ms->L, nlevels, \"too many captures\");\n  for (i = 0; i < nlevels; i++)\n    push_onecapture(ms, i, s, e);\n  return nlevels;  /* number of strings pushed */\n}\n\n\n/* check whether pattern has no special characters */\nstatic int nospecials (const char *p, size_t l) {\n  size_t upto = 0;\n  do {\n    if (strpbrk(p + upto, SPECIALS))\n      return 0;  /* pattern has a special character */\n    upto += strlen(p + upto) + 1;  /* may have more after \\0 */\n  } while (upto <= l);\n  return 1;  /* no special chars found */\n}\n\n\nstatic void prepstate (MatchState *ms, lua_State *L,\n                       const char *s, size_t ls, const char *p, size_t lp) {\n  ms->L = L;\n  ms->matchdepth = MAXCCALLS;\n  ms->src_init = s;\n  ms->src_end = s + ls;\n  ms->p_end = p + lp;\n}\n\n\nstatic void reprepstate (MatchState *ms) {\n  ms->level = 0;\n  lua_assert(ms->matchdepth == MAXCCALLS);\n}\n\n\nstatic int str_find_aux (lua_State *L, int find) {\n  size_t ls, lp;\n  const char *s = luaL_checklstring(L, 1, &ls);\n  const char *p = luaL_checklstring(L, 2, &lp);\n  size_t init = posrelatI(luaL_optinteger(L, 3, 1), ls) - 1;\n  if (init > ls) {  /* start after string's end? */\n    luaL_pushfail(L);  /* cannot find anything */\n    return 1;\n  }\n  /* explicit request or no special characters? */\n  if (find && (lua_toboolean(L, 4) || nospecials(p, lp))) {\n    /* do a plain search */\n    const char *s2 = lmemfind(s + init, ls - init, p, lp);\n    if (s2) {\n      lua_pushinteger(L, (s2 - s) + 1);\n      lua_pushinteger(L, (s2 - s) + lp);\n      return 2;\n    }\n  }\n  else {\n    MatchState ms;\n    const char *s1 = s + init;\n    int anchor = (*p == '^');\n    if (anchor) {\n      p++; lp--;  /* skip anchor character */\n    }\n    prepstate(&ms, L, s, ls, p, lp);\n    do {\n      const char *res;\n      reprepstate(&ms);\n      if ((res=match(&ms, s1, p)) != NULL) {\n        if (find) {\n          lua_pushinteger(L, (s1 - s) + 1);  /* start */\n          lua_pushinteger(L, res - s);   /* end */\n          return push_captures(&ms, NULL, 0) + 2;\n        }\n        else\n          return push_captures(&ms, s1, res);\n      }\n    } while (s1++ < ms.src_end && !anchor);\n  }\n  luaL_pushfail(L);  /* not found */\n  return 1;\n}\n\n\nstatic int str_find (lua_State *L) {\n  return str_find_aux(L, 1);\n}\n\n\nstatic int str_match (lua_State *L) {\n  return str_find_aux(L, 0);\n}\n\n\n/* state for 'gmatch' */\ntypedef struct GMatchState {\n  const char *src;  /* current position */\n  const char *p;  /* pattern */\n  const char *lastmatch;  /* end of last match */\n  MatchState ms;  /* match state */\n} GMatchState;\n\n\nstatic int gmatch_aux (lua_State *L) {\n  GMatchState *gm = (GMatchState *)lua_touserdata(L, lua_upvalueindex(3));\n  const char *src;\n  gm->ms.L = L;\n  for (src = gm->src; src <= gm->ms.src_end; src++) {\n    const char *e;\n    reprepstate(&gm->ms);\n    if ((e = match(&gm->ms, src, gm->p)) != NULL && e != gm->lastmatch) {\n      gm->src = gm->lastmatch = e;\n      return push_captures(&gm->ms, src, e);\n    }\n  }\n  return 0;  /* not found */\n}\n\n\nstatic int gmatch (lua_State *L) {\n  size_t ls, lp;\n  const char *s = luaL_checklstring(L, 1, &ls);\n  const char *p = luaL_checklstring(L, 2, &lp);\n  size_t init = posrelatI(luaL_optinteger(L, 3, 1), ls) - 1;\n  GMatchState *gm;\n  lua_settop(L, 2);  /* keep strings on closure to avoid being collected */\n  gm = (GMatchState *)lua_newuserdatauv(L, sizeof(GMatchState), 0);\n  if (init > ls)  /* start after string's end? */\n    init = ls + 1;  /* avoid overflows in 's + init' */\n  prepstate(&gm->ms, L, s, ls, p, lp);\n  gm->src = s + init; gm->p = p; gm->lastmatch = NULL;\n  lua_pushcclosure(L, gmatch_aux, 3);\n  return 1;\n}\n\n\nstatic void add_s (MatchState *ms, luaL_Buffer *b, const char *s,\n                                                   const char *e) {\n  size_t l;\n  lua_State *L = ms->L;\n  const char *news = lua_tolstring(L, 3, &l);\n  const char *p;\n  while ((p = (char *)memchr(news, L_ESC, l)) != NULL) {\n    luaL_addlstring(b, news, p - news);\n    p++;  /* skip ESC */\n    if (*p == L_ESC)  /* '%%' */\n      luaL_addchar(b, *p);\n    else if (*p == '0')  /* '%0' */\n        luaL_addlstring(b, s, e - s);\n    else if (isdigit(uchar(*p))) {  /* '%n' */\n      const char *cap;\n      ptrdiff_t resl = get_onecapture(ms, *p - '1', s, e, &cap);\n      if (resl == CAP_POSITION)\n        luaL_addvalue(b);  /* add position to accumulated result */\n      else\n        luaL_addlstring(b, cap, resl);\n    }\n    else\n      luaL_error(L, \"invalid use of '%c' in replacement string\", L_ESC);\n    l -= p + 1 - news;\n    news = p + 1;\n  }\n  luaL_addlstring(b, news, l);\n}\n\n\n/*\n** Add the replacement value to the string buffer 'b'.\n** Return true if the original string was changed. (Function calls and\n** table indexing resulting in nil or false do not change the subject.)\n*/\nstatic int add_value (MatchState *ms, luaL_Buffer *b, const char *s,\n                                      const char *e, int tr) {\n  lua_State *L = ms->L;\n  switch (tr) {\n    case LUA_TFUNCTION: {  /* call the function */\n      int n;\n      lua_pushvalue(L, 3);  /* push the function */\n      n = push_captures(ms, s, e);  /* all captures as arguments */\n      lua_call(L, n, 1);  /* call it */\n      break;\n    }\n    case LUA_TTABLE: {  /* index the table */\n      push_onecapture(ms, 0, s, e);  /* first capture is the index */\n      lua_gettable(L, 3);\n      break;\n    }\n    default: {  /* LUA_TNUMBER or LUA_TSTRING */\n      add_s(ms, b, s, e);  /* add value to the buffer */\n      return 1;  /* something changed */\n    }\n  }\n  if (!lua_toboolean(L, -1)) {  /* nil or false? */\n    lua_pop(L, 1);  /* remove value */\n    luaL_addlstring(b, s, e - s);  /* keep original text */\n    return 0;  /* no changes */\n  }\n  else if (l_unlikely(!lua_isstring(L, -1)))\n    return luaL_error(L, \"invalid replacement value (a %s)\",\n                         luaL_typename(L, -1));\n  else {\n    luaL_addvalue(b);  /* add result to accumulator */\n    return 1;  /* something changed */\n  }\n}\n\n\nstatic int str_gsub (lua_State *L) {\n  size_t srcl, lp;\n  const char *src = luaL_checklstring(L, 1, &srcl);  /* subject */\n  const char *p = luaL_checklstring(L, 2, &lp);  /* pattern */\n  const char *lastmatch = NULL;  /* end of last match */\n  int tr = lua_type(L, 3);  /* replacement type */\n  lua_Integer max_s = luaL_optinteger(L, 4, srcl + 1);  /* max replacements */\n  int anchor = (*p == '^');\n  lua_Integer n = 0;  /* replacement count */\n  int changed = 0;  /* change flag */\n  MatchState ms;\n  luaL_Buffer b;\n  luaL_argexpected(L, tr == LUA_TNUMBER || tr == LUA_TSTRING ||\n                   tr == LUA_TFUNCTION || tr == LUA_TTABLE, 3,\n                      \"string/function/table\");\n  luaL_buffinit(L, &b);\n  if (anchor) {\n    p++; lp--;  /* skip anchor character */\n  }\n  prepstate(&ms, L, src, srcl, p, lp);\n  while (n < max_s) {\n    const char *e;\n    reprepstate(&ms);  /* (re)prepare state for new match */\n    if ((e = match(&ms, src, p)) != NULL && e != lastmatch) {  /* match? */\n      n++;\n      changed = add_value(&ms, &b, src, e, tr) | changed;\n      src = lastmatch = e;\n    }\n    else if (src < ms.src_end)  /* otherwise, skip one character */\n      luaL_addchar(&b, *src++);\n    else break;  /* end of subject */\n    if (anchor) break;\n  }\n  if (!changed)  /* no changes? */\n    lua_pushvalue(L, 1);  /* return original string */\n  else {  /* something changed */\n    luaL_addlstring(&b, src, ms.src_end-src);\n    luaL_pushresult(&b);  /* create and return new string */\n  }\n  lua_pushinteger(L, n);  /* number of substitutions */\n  return 2;\n}\n\n/* }====================================================== */\n\n\n\n/*\n** {======================================================\n** STRING FORMAT\n** =======================================================\n*/\n\n#if !defined(lua_number2strx)\t/* { */\n\n/*\n** Hexadecimal floating-point formatter\n*/\n\n#define SIZELENMOD\t(sizeof(LUA_NUMBER_FRMLEN)/sizeof(char))\n\n\n/*\n** Number of bits that goes into the first digit. It can be any value\n** between 1 and 4; the following definition tries to align the number\n** to nibble boundaries by making what is left after that first digit a\n** multiple of 4.\n*/\n#define L_NBFD\t\t((l_floatatt(MANT_DIG) - 1)%4 + 1)\n\n\n/*\n** Add integer part of 'x' to buffer and return new 'x'\n*/\nstatic lua_Number adddigit (char *buff, int n, lua_Number x) {\n  lua_Number dd = l_mathop(floor)(x);  /* get integer part from 'x' */\n  int d = (int)dd;\n  buff[n] = (d < 10 ? d + '0' : d - 10 + 'a');  /* add to buffer */\n  return x - dd;  /* return what is left */\n}\n\n\nstatic int num2straux (char *buff, int sz, lua_Number x) {\n  /* if 'inf' or 'NaN', format it like '%g' */\n  if (x != x || x == (lua_Number)HUGE_VAL || x == -(lua_Number)HUGE_VAL)\n    return l_sprintf(buff, sz, LUA_NUMBER_FMT, (LUAI_UACNUMBER)x);\n  else if (x == 0) {  /* can be -0... */\n    /* create \"0\" or \"-0\" followed by exponent */\n    return l_sprintf(buff, sz, LUA_NUMBER_FMT \"x0p+0\", (LUAI_UACNUMBER)x);\n  }\n  else {\n    int e;\n    lua_Number m = l_mathop(frexp)(x, &e);  /* 'x' fraction and exponent */\n    int n = 0;  /* character count */\n    if (m < 0) {  /* is number negative? */\n      buff[n++] = '-';  /* add sign */\n      m = -m;  /* make it positive */\n    }\n    buff[n++] = '0'; buff[n++] = 'x';  /* add \"0x\" */\n    m = adddigit(buff, n++, m * (1 << L_NBFD));  /* add first digit */\n    e -= L_NBFD;  /* this digit goes before the radix point */\n    if (m > 0) {  /* more digits? */\n      buff[n++] = lua_getlocaledecpoint();  /* add radix point */\n      do {  /* add as many digits as needed */\n        m = adddigit(buff, n++, m * 16);\n      } while (m > 0);\n    }\n    n += l_sprintf(buff + n, sz - n, \"p%+d\", e);  /* add exponent */\n    lua_assert(n < sz);\n    return n;\n  }\n}\n\n\nstatic int lua_number2strx (lua_State *L, char *buff, int sz,\n                            const char *fmt, lua_Number x) {\n  int n = num2straux(buff, sz, x);\n  if (fmt[SIZELENMOD] == 'A') {\n    int i;\n    for (i = 0; i < n; i++)\n      buff[i] = toupper(uchar(buff[i]));\n  }\n  else if (l_unlikely(fmt[SIZELENMOD] != 'a'))\n    return luaL_error(L, \"modifiers for format '%%a'/'%%A' not implemented\");\n  return n;\n}\n\n#endif\t\t\t\t/* } */\n\n\n/*\n** Maximum size for items formatted with '%f'. This size is produced\n** by format('%.99f', -maxfloat), and is equal to 99 + 3 ('-', '.',\n** and '\\0') + number of decimal digits to represent maxfloat (which\n** is maximum exponent + 1). (99+3+1, adding some extra, 110)\n*/\n#define MAX_ITEMF\t(110 + l_floatatt(MAX_10_EXP))\n\n\n/*\n** All formats except '%f' do not need that large limit.  The other\n** float formats use exponents, so that they fit in the 99 limit for\n** significant digits; 's' for large strings and 'q' add items directly\n** to the buffer; all integer formats also fit in the 99 limit.  The\n** worst case are floats: they may need 99 significant digits, plus\n** '0x', '-', '.', 'e+XXXX', and '\\0'. Adding some extra, 120.\n*/\n#define MAX_ITEM\t120\n\n\n/* valid flags in a format specification */\n#if !defined(L_FMTFLAGSF)\n\n/* valid flags for a, A, e, E, f, F, g, and G conversions */\n#define L_FMTFLAGSF\t\"-+#0 \"\n\n/* valid flags for o, x, and X conversions */\n#define L_FMTFLAGSX\t\"-#0\"\n\n/* valid flags for d and i conversions */\n#define L_FMTFLAGSI\t\"-+0 \"\n\n/* valid flags for u conversions */\n#define L_FMTFLAGSU\t\"-0\"\n\n/* valid flags for c, p, and s conversions */\n#define L_FMTFLAGSC\t\"-\"\n\n#endif\n\n\n/*\n** Maximum size of each format specification (such as \"%-099.99d\"):\n** Initial '%', flags (up to 5), width (2), period, precision (2),\n** length modifier (8), conversion specifier, and final '\\0', plus some\n** extra.\n*/\n#define MAX_FORMAT\t32\n\n\nstatic void addquoted (luaL_Buffer *b, const char *s, size_t len) {\n  luaL_addchar(b, '\"');\n  while (len--) {\n    if (*s == '\"' || *s == '\\\\' || *s == '\\n') {\n      luaL_addchar(b, '\\\\');\n      luaL_addchar(b, *s);\n    }\n    else if (iscntrl(uchar(*s))) {\n      char buff[10];\n      if (!isdigit(uchar(*(s+1))))\n        l_sprintf(buff, sizeof(buff), \"\\\\%d\", (int)uchar(*s));\n      else\n        l_sprintf(buff, sizeof(buff), \"\\\\%03d\", (int)uchar(*s));\n      luaL_addstring(b, buff);\n    }\n    else\n      luaL_addchar(b, *s);\n    s++;\n  }\n  luaL_addchar(b, '\"');\n}\n\n\n/*\n** Serialize a floating-point number in such a way that it can be\n** scanned back by Lua. Use hexadecimal format for \"common\" numbers\n** (to preserve precision); inf, -inf, and NaN are handled separately.\n** (NaN cannot be expressed as a numeral, so we write '(0/0)' for it.)\n*/\nstatic int quotefloat (lua_State *L, char *buff, lua_Number n) {\n  const char *s;  /* for the fixed representations */\n  if (n == (lua_Number)HUGE_VAL)  /* inf? */\n    s = \"1e9999\";\n  else if (n == -(lua_Number)HUGE_VAL)  /* -inf? */\n    s = \"-1e9999\";\n  else if (n != n)  /* NaN? */\n    s = \"(0/0)\";\n  else {  /* format number as hexadecimal */\n    int  nb = lua_number2strx(L, buff, MAX_ITEM,\n                                 \"%\" LUA_NUMBER_FRMLEN \"a\", n);\n    /* ensures that 'buff' string uses a dot as the radix character */\n    if (memchr(buff, '.', nb) == NULL) {  /* no dot? */\n      char point = lua_getlocaledecpoint();  /* try locale point */\n      char *ppoint = (char *)memchr(buff, point, nb);\n      if (ppoint) *ppoint = '.';  /* change it to a dot */\n    }\n    return nb;\n  }\n  /* for the fixed representations */\n  return l_sprintf(buff, MAX_ITEM, \"%s\", s);\n}\n\n\nstatic void addliteral (lua_State *L, luaL_Buffer *b, int arg) {\n  switch (lua_type(L, arg)) {\n    case LUA_TSTRING: {\n      size_t len;\n      const char *s = lua_tolstring(L, arg, &len);\n      addquoted(b, s, len);\n      break;\n    }\n    case LUA_TNUMBER: {\n      char *buff = luaL_prepbuffsize(b, MAX_ITEM);\n      int nb;\n      if (!lua_isinteger(L, arg))  /* float? */\n        nb = quotefloat(L, buff, lua_tonumber(L, arg));\n      else {  /* integers */\n        lua_Integer n = lua_tointeger(L, arg);\n        const char *format = (n == LUA_MININTEGER)  /* corner case? */\n                           ? \"0x%\" LUA_INTEGER_FRMLEN \"x\"  /* use hex */\n                           : LUA_INTEGER_FMT;  /* else use default format */\n        nb = l_sprintf(buff, MAX_ITEM, format, (LUAI_UACINT)n);\n      }\n      luaL_addsize(b, nb);\n      break;\n    }\n    case LUA_TNIL: case LUA_TBOOLEAN: {\n      luaL_tolstring(L, arg, NULL);\n      luaL_addvalue(b);\n      break;\n    }\n    default: {\n      luaL_argerror(L, arg, \"value has no literal form\");\n    }\n  }\n}\n\n\nstatic const char *get2digits (const char *s) {\n  if (isdigit(uchar(*s))) {\n    s++;\n    if (isdigit(uchar(*s))) s++;  /* (2 digits at most) */\n  }\n  return s;\n}\n\n\n/*\n** Check whether a conversion specification is valid. When called,\n** first character in 'form' must be '%' and last character must\n** be a valid conversion specifier. 'flags' are the accepted flags;\n** 'precision' signals whether to accept a precision.\n*/\nstatic void checkformat (lua_State *L, const char *form, const char *flags,\n                                       int precision) {\n  const char *spec = form + 1;  /* skip '%' */\n  spec += strspn(spec, flags);  /* skip flags */\n  if (*spec != '0') {  /* a width cannot start with '0' */\n    spec = get2digits(spec);  /* skip width */\n    if (*spec == '.' && precision) {\n      spec++;\n      spec = get2digits(spec);  /* skip precision */\n    }\n  }\n  if (!isalpha(uchar(*spec)))  /* did not go to the end? */\n    luaL_error(L, \"invalid conversion specification: '%s'\", form);\n}\n\n\n/*\n** Get a conversion specification and copy it to 'form'.\n** Return the address of its last character.\n*/\nstatic const char *getformat (lua_State *L, const char *strfrmt,\n                                            char *form) {\n  /* spans flags, width, and precision ('0' is included as a flag) */\n  size_t len = strspn(strfrmt, L_FMTFLAGSF \"123456789.\");\n  len++;  /* adds following character (should be the specifier) */\n  /* still needs space for '%', '\\0', plus a length modifier */\n  if (len >= MAX_FORMAT - 10)\n    luaL_error(L, \"invalid format (too long)\");\n  *(form++) = '%';\n  memcpy(form, strfrmt, len * sizeof(char));\n  *(form + len) = '\\0';\n  return strfrmt + len - 1;\n}\n\n\n/*\n** add length modifier into formats\n*/\nstatic void addlenmod (char *form, const char *lenmod) {\n  size_t l = strlen(form);\n  size_t lm = strlen(lenmod);\n  char spec = form[l - 1];\n  strcpy(form + l - 1, lenmod);\n  form[l + lm - 1] = spec;\n  form[l + lm] = '\\0';\n}\n\n\nstatic int str_format (lua_State *L) {\n  int top = lua_gettop(L);\n  int arg = 1;\n  size_t sfl;\n  const char *strfrmt = luaL_checklstring(L, arg, &sfl);\n  const char *strfrmt_end = strfrmt+sfl;\n  const char *flags;\n  luaL_Buffer b;\n  luaL_buffinit(L, &b);\n  while (strfrmt < strfrmt_end) {\n    if (*strfrmt != L_ESC)\n      luaL_addchar(&b, *strfrmt++);\n    else if (*++strfrmt == L_ESC)\n      luaL_addchar(&b, *strfrmt++);  /* %% */\n    else { /* format item */\n      char form[MAX_FORMAT];  /* to store the format ('%...') */\n      int maxitem = MAX_ITEM;  /* maximum length for the result */\n      char *buff = luaL_prepbuffsize(&b, maxitem);  /* to put result */\n      int nb = 0;  /* number of bytes in result */\n      if (++arg > top)\n        return luaL_argerror(L, arg, \"no value\");\n      strfrmt = getformat(L, strfrmt, form);\n      switch (*strfrmt++) {\n        case 'c': {\n          checkformat(L, form, L_FMTFLAGSC, 0);\n          nb = l_sprintf(buff, maxitem, form, (int)luaL_checkinteger(L, arg));\n          break;\n        }\n        case 'd': case 'i':\n          flags = L_FMTFLAGSI;\n          goto intcase;\n        case 'u':\n          flags = L_FMTFLAGSU;\n          goto intcase;\n        case 'o': case 'x': case 'X':\n          flags = L_FMTFLAGSX;\n         intcase: {\n          lua_Integer n = luaL_checkinteger(L, arg);\n          checkformat(L, form, flags, 1);\n          addlenmod(form, LUA_INTEGER_FRMLEN);\n          nb = l_sprintf(buff, maxitem, form, (LUAI_UACINT)n);\n          break;\n        }\n        case 'a': case 'A':\n          checkformat(L, form, L_FMTFLAGSF, 1);\n          addlenmod(form, LUA_NUMBER_FRMLEN);\n          nb = lua_number2strx(L, buff, maxitem, form,\n                                  luaL_checknumber(L, arg));\n          break;\n        case 'f':\n          maxitem = MAX_ITEMF;  /* extra space for '%f' */\n          buff = luaL_prepbuffsize(&b, maxitem);\n          /* FALLTHROUGH */\n        case 'e': case 'E': case 'g': case 'G': {\n          lua_Number n = luaL_checknumber(L, arg);\n          checkformat(L, form, L_FMTFLAGSF, 1);\n          addlenmod(form, LUA_NUMBER_FRMLEN);\n          nb = l_sprintf(buff, maxitem, form, (LUAI_UACNUMBER)n);\n          break;\n        }\n        case 'p': {\n          const void *p = lua_topointer(L, arg);\n          checkformat(L, form, L_FMTFLAGSC, 0);\n          if (p == NULL) {  /* avoid calling 'printf' with argument NULL */\n            p = \"(null)\";  /* result */\n            form[strlen(form) - 1] = 's';  /* format it as a string */\n          }\n          nb = l_sprintf(buff, maxitem, form, p);\n          break;\n        }\n        case 'q': {\n          if (form[2] != '\\0')  /* modifiers? */\n            return luaL_error(L, \"specifier '%%q' cannot have modifiers\");\n          addliteral(L, &b, arg);\n          break;\n        }\n        case 's': {\n          size_t l;\n          const char *s = luaL_tolstring(L, arg, &l);\n          if (form[2] == '\\0')  /* no modifiers? */\n            luaL_addvalue(&b);  /* keep entire string */\n          else {\n            luaL_argcheck(L, l == strlen(s), arg, \"string contains zeros\");\n            checkformat(L, form, L_FMTFLAGSC, 1);\n            if (strchr(form, '.') == NULL && l >= 100) {\n              /* no precision and string is too long to be formatted */\n              luaL_addvalue(&b);  /* keep entire string */\n            }\n            else {  /* format the string into 'buff' */\n              nb = l_sprintf(buff, maxitem, form, s);\n              lua_pop(L, 1);  /* remove result from 'luaL_tolstring' */\n            }\n          }\n          break;\n        }\n        default: {  /* also treat cases 'pnLlh' */\n          return luaL_error(L, \"invalid conversion '%s' to 'format'\", form);\n        }\n      }\n      lua_assert(nb < maxitem);\n      luaL_addsize(&b, nb);\n    }\n  }\n  luaL_pushresult(&b);\n  return 1;\n}\n\n/* }====================================================== */\n\n\n/*\n** {======================================================\n** PACK/UNPACK\n** =======================================================\n*/\n\n\n/* value used for padding */\n#if !defined(LUAL_PACKPADBYTE)\n#define LUAL_PACKPADBYTE\t\t0x00\n#endif\n\n/* maximum size for the binary representation of an integer */\n#define MAXINTSIZE\t16\n\n/* number of bits in a character */\n#define NB\tCHAR_BIT\n\n/* mask for one character (NB 1's) */\n#define MC\t((1 << NB) - 1)\n\n/* size of a lua_Integer */\n#define SZINT\t((int)sizeof(lua_Integer))\n\n\n/* dummy union to get native endianness */\nstatic const union {\n  int dummy;\n  char little;  /* true iff machine is little endian */\n} nativeendian = {1};\n\n\n/*\n** information to pack/unpack stuff\n*/\ntypedef struct Header {\n  lua_State *L;\n  int islittle;\n  int maxalign;\n} Header;\n\n\n/*\n** options for pack/unpack\n*/\ntypedef enum KOption {\n  Kint,\t\t/* signed integers */\n  Kuint,\t/* unsigned integers */\n  Kfloat,\t/* single-precision floating-point numbers */\n  Knumber,\t/* Lua \"native\" floating-point numbers */\n  Kdouble,\t/* double-precision floating-point numbers */\n  Kchar,\t/* fixed-length strings */\n  Kstring,\t/* strings with prefixed length */\n  Kzstr,\t/* zero-terminated strings */\n  Kpadding,\t/* padding */\n  Kpaddalign,\t/* padding for alignment */\n  Knop\t\t/* no-op (configuration or spaces) */\n} KOption;\n\n\n/*\n** Read an integer numeral from string 'fmt' or return 'df' if\n** there is no numeral\n*/\nstatic int digit (int c) { return '0' <= c && c <= '9'; }\n\nstatic int getnum (const char **fmt, int df) {\n  if (!digit(**fmt))  /* no number? */\n    return df;  /* return default value */\n  else {\n    int a = 0;\n    do {\n      a = a*10 + (*((*fmt)++) - '0');\n    } while (digit(**fmt) && a <= ((int)MAXSIZE - 9)/10);\n    return a;\n  }\n}\n\n\n/*\n** Read an integer numeral and raises an error if it is larger\n** than the maximum size for integers.\n*/\nstatic int getnumlimit (Header *h, const char **fmt, int df) {\n  int sz = getnum(fmt, df);\n  if (l_unlikely(sz > MAXINTSIZE || sz <= 0))\n    return luaL_error(h->L, \"integral size (%d) out of limits [1,%d]\",\n                            sz, MAXINTSIZE);\n  return sz;\n}\n\n\n/*\n** Initialize Header\n*/\nstatic void initheader (lua_State *L, Header *h) {\n  h->L = L;\n  h->islittle = nativeendian.little;\n  h->maxalign = 1;\n}\n\n\n/*\n** Read and classify next option. 'size' is filled with option's size.\n*/\nstatic KOption getoption (Header *h, const char **fmt, int *size) {\n  /* dummy structure to get native alignment requirements */\n  struct cD { char c; union { LUAI_MAXALIGN; } u; };\n  int opt = *((*fmt)++);\n  *size = 0;  /* default */\n  switch (opt) {\n    case 'b': *size = sizeof(char); return Kint;\n    case 'B': *size = sizeof(char); return Kuint;\n    case 'h': *size = sizeof(short); return Kint;\n    case 'H': *size = sizeof(short); return Kuint;\n    case 'l': *size = sizeof(long); return Kint;\n    case 'L': *size = sizeof(long); return Kuint;\n    case 'j': *size = sizeof(lua_Integer); return Kint;\n    case 'J': *size = sizeof(lua_Integer); return Kuint;\n    case 'T': *size = sizeof(size_t); return Kuint;\n    case 'f': *size = sizeof(float); return Kfloat;\n    case 'n': *size = sizeof(lua_Number); return Knumber;\n    case 'd': *size = sizeof(double); return Kdouble;\n    case 'i': *size = getnumlimit(h, fmt, sizeof(int)); return Kint;\n    case 'I': *size = getnumlimit(h, fmt, sizeof(int)); return Kuint;\n    case 's': *size = getnumlimit(h, fmt, sizeof(size_t)); return Kstring;\n    case 'c':\n      *size = getnum(fmt, -1);\n      if (l_unlikely(*size == -1))\n        luaL_error(h->L, \"missing size for format option 'c'\");\n      return Kchar;\n    case 'z': return Kzstr;\n    case 'x': *size = 1; return Kpadding;\n    case 'X': return Kpaddalign;\n    case ' ': break;\n    case '<': h->islittle = 1; break;\n    case '>': h->islittle = 0; break;\n    case '=': h->islittle = nativeendian.little; break;\n    case '!': {\n      const int maxalign = offsetof(struct cD, u);\n      h->maxalign = getnumlimit(h, fmt, maxalign);\n      break;\n    }\n    default: luaL_error(h->L, \"invalid format option '%c'\", opt);\n  }\n  return Knop;\n}\n\n\n/*\n** Read, classify, and fill other details about the next option.\n** 'psize' is filled with option's size, 'notoalign' with its\n** alignment requirements.\n** Local variable 'size' gets the size to be aligned. (Kpadal option\n** always gets its full alignment, other options are limited by\n** the maximum alignment ('maxalign'). Kchar option needs no alignment\n** despite its size.\n*/\nstatic KOption getdetails (Header *h, size_t totalsize,\n                           const char **fmt, int *psize, int *ntoalign) {\n  KOption opt = getoption(h, fmt, psize);\n  int align = *psize;  /* usually, alignment follows size */\n  if (opt == Kpaddalign) {  /* 'X' gets alignment from following option */\n    if (**fmt == '\\0' || getoption(h, fmt, &align) == Kchar || align == 0)\n      luaL_argerror(h->L, 1, \"invalid next option for option 'X'\");\n  }\n  if (align <= 1 || opt == Kchar)  /* need no alignment? */\n    *ntoalign = 0;\n  else {\n    if (align > h->maxalign)  /* enforce maximum alignment */\n      align = h->maxalign;\n    if (l_unlikely((align & (align - 1)) != 0))  /* not a power of 2? */\n      luaL_argerror(h->L, 1, \"format asks for alignment not power of 2\");\n    *ntoalign = (align - (int)(totalsize & (align - 1))) & (align - 1);\n  }\n  return opt;\n}\n\n\n/*\n** Pack integer 'n' with 'size' bytes and 'islittle' endianness.\n** The final 'if' handles the case when 'size' is larger than\n** the size of a Lua integer, correcting the extra sign-extension\n** bytes if necessary (by default they would be zeros).\n*/\nstatic void packint (luaL_Buffer *b, lua_Unsigned n,\n                     int islittle, int size, int neg) {\n  char *buff = luaL_prepbuffsize(b, size);\n  int i;\n  buff[islittle ? 0 : size - 1] = (char)(n & MC);  /* first byte */\n  for (i = 1; i < size; i++) {\n    n >>= NB;\n    buff[islittle ? i : size - 1 - i] = (char)(n & MC);\n  }\n  if (neg && size > SZINT) {  /* negative number need sign extension? */\n    for (i = SZINT; i < size; i++)  /* correct extra bytes */\n      buff[islittle ? i : size - 1 - i] = (char)MC;\n  }\n  luaL_addsize(b, size);  /* add result to buffer */\n}\n\n\n/*\n** Copy 'size' bytes from 'src' to 'dest', correcting endianness if\n** given 'islittle' is different from native endianness.\n*/\nstatic void copywithendian (char *dest, const char *src,\n                            int size, int islittle) {\n  if (islittle == nativeendian.little)\n    memcpy(dest, src, size);\n  else {\n    dest += size - 1;\n    while (size-- != 0)\n      *(dest--) = *(src++);\n  }\n}\n\n\nstatic int str_pack (lua_State *L) {\n  luaL_Buffer b;\n  Header h;\n  const char *fmt = luaL_checkstring(L, 1);  /* format string */\n  int arg = 1;  /* current argument to pack */\n  size_t totalsize = 0;  /* accumulate total size of result */\n  initheader(L, &h);\n  lua_pushnil(L);  /* mark to separate arguments from string buffer */\n  luaL_buffinit(L, &b);\n  while (*fmt != '\\0') {\n    int size, ntoalign;\n    KOption opt = getdetails(&h, totalsize, &fmt, &size, &ntoalign);\n    totalsize += ntoalign + size;\n    while (ntoalign-- > 0)\n     luaL_addchar(&b, LUAL_PACKPADBYTE);  /* fill alignment */\n    arg++;\n    switch (opt) {\n      case Kint: {  /* signed integers */\n        lua_Integer n = luaL_checkinteger(L, arg);\n        if (size < SZINT) {  /* need overflow check? */\n          lua_Integer lim = (lua_Integer)1 << ((size * NB) - 1);\n          luaL_argcheck(L, -lim <= n && n < lim, arg, \"integer overflow\");\n        }\n        packint(&b, (lua_Unsigned)n, h.islittle, size, (n < 0));\n        break;\n      }\n      case Kuint: {  /* unsigned integers */\n        lua_Integer n = luaL_checkinteger(L, arg);\n        if (size < SZINT)  /* need overflow check? */\n          luaL_argcheck(L, (lua_Unsigned)n < ((lua_Unsigned)1 << (size * NB)),\n                           arg, \"unsigned overflow\");\n        packint(&b, (lua_Unsigned)n, h.islittle, size, 0);\n        break;\n      }\n      case Kfloat: {  /* C float */\n        float f = (float)luaL_checknumber(L, arg);  /* get argument */\n        char *buff = luaL_prepbuffsize(&b, sizeof(f));\n        /* move 'f' to final result, correcting endianness if needed */\n        copywithendian(buff, (char *)&f, sizeof(f), h.islittle);\n        luaL_addsize(&b, size);\n        break;\n      }\n      case Knumber: {  /* Lua float */\n        lua_Number f = luaL_checknumber(L, arg);  /* get argument */\n        char *buff = luaL_prepbuffsize(&b, sizeof(f));\n        /* move 'f' to final result, correcting endianness if needed */\n        copywithendian(buff, (char *)&f, sizeof(f), h.islittle);\n        luaL_addsize(&b, size);\n        break;\n      }\n      case Kdouble: {  /* C double */\n        double f = (double)luaL_checknumber(L, arg);  /* get argument */\n        char *buff = luaL_prepbuffsize(&b, sizeof(f));\n        /* move 'f' to final result, correcting endianness if needed */\n        copywithendian(buff, (char *)&f, sizeof(f), h.islittle);\n        luaL_addsize(&b, size);\n        break;\n      }\n      case Kchar: {  /* fixed-size string */\n        size_t len;\n        const char *s = luaL_checklstring(L, arg, &len);\n        luaL_argcheck(L, len <= (size_t)size, arg,\n                         \"string longer than given size\");\n        luaL_addlstring(&b, s, len);  /* add string */\n        while (len++ < (size_t)size)  /* pad extra space */\n          luaL_addchar(&b, LUAL_PACKPADBYTE);\n        break;\n      }\n      case Kstring: {  /* strings with length count */\n        size_t len;\n        const char *s = luaL_checklstring(L, arg, &len);\n        luaL_argcheck(L, size >= (int)sizeof(size_t) ||\n                         len < ((size_t)1 << (size * NB)),\n                         arg, \"string length does not fit in given size\");\n        packint(&b, (lua_Unsigned)len, h.islittle, size, 0);  /* pack length */\n        luaL_addlstring(&b, s, len);\n        totalsize += len;\n        break;\n      }\n      case Kzstr: {  /* zero-terminated string */\n        size_t len;\n        const char *s = luaL_checklstring(L, arg, &len);\n        luaL_argcheck(L, strlen(s) == len, arg, \"string contains zeros\");\n        luaL_addlstring(&b, s, len);\n        luaL_addchar(&b, '\\0');  /* add zero at the end */\n        totalsize += len + 1;\n        break;\n      }\n      case Kpadding: luaL_addchar(&b, LUAL_PACKPADBYTE);  /* FALLTHROUGH */\n      case Kpaddalign: case Knop:\n        arg--;  /* undo increment */\n        break;\n    }\n  }\n  luaL_pushresult(&b);\n  return 1;\n}\n\n\nstatic int str_packsize (lua_State *L) {\n  Header h;\n  const char *fmt = luaL_checkstring(L, 1);  /* format string */\n  size_t totalsize = 0;  /* accumulate total size of result */\n  initheader(L, &h);\n  while (*fmt != '\\0') {\n    int size, ntoalign;\n    KOption opt = getdetails(&h, totalsize, &fmt, &size, &ntoalign);\n    luaL_argcheck(L, opt != Kstring && opt != Kzstr, 1,\n                     \"variable-length format\");\n    size += ntoalign;  /* total space used by option */\n    luaL_argcheck(L, totalsize <= MAXSIZE - size, 1,\n                     \"format result too large\");\n    totalsize += size;\n  }\n  lua_pushinteger(L, (lua_Integer)totalsize);\n  return 1;\n}\n\n\n/*\n** Unpack an integer with 'size' bytes and 'islittle' endianness.\n** If size is smaller than the size of a Lua integer and integer\n** is signed, must do sign extension (propagating the sign to the\n** higher bits); if size is larger than the size of a Lua integer,\n** it must check the unread bytes to see whether they do not cause an\n** overflow.\n*/\nstatic lua_Integer unpackint (lua_State *L, const char *str,\n                              int islittle, int size, int issigned) {\n  lua_Unsigned res = 0;\n  int i;\n  int limit = (size  <= SZINT) ? size : SZINT;\n  for (i = limit - 1; i >= 0; i--) {\n    res <<= NB;\n    res |= (lua_Unsigned)(unsigned char)str[islittle ? i : size - 1 - i];\n  }\n  if (size < SZINT) {  /* real size smaller than lua_Integer? */\n    if (issigned) {  /* needs sign extension? */\n      lua_Unsigned mask = (lua_Unsigned)1 << (size*NB - 1);\n      res = ((res ^ mask) - mask);  /* do sign extension */\n    }\n  }\n  else if (size > SZINT) {  /* must check unread bytes */\n    int mask = (!issigned || (lua_Integer)res >= 0) ? 0 : MC;\n    for (i = limit; i < size; i++) {\n      if (l_unlikely((unsigned char)str[islittle ? i : size - 1 - i] != mask))\n        luaL_error(L, \"%d-byte integer does not fit into Lua Integer\", size);\n    }\n  }\n  return (lua_Integer)res;\n}\n\n\nstatic int str_unpack (lua_State *L) {\n  Header h;\n  const char *fmt = luaL_checkstring(L, 1);\n  size_t ld;\n  const char *data = luaL_checklstring(L, 2, &ld);\n  size_t pos = posrelatI(luaL_optinteger(L, 3, 1), ld) - 1;\n  int n = 0;  /* number of results */\n  luaL_argcheck(L, pos <= ld, 3, \"initial position out of string\");\n  initheader(L, &h);\n  while (*fmt != '\\0') {\n    int size, ntoalign;\n    KOption opt = getdetails(&h, pos, &fmt, &size, &ntoalign);\n    luaL_argcheck(L, (size_t)ntoalign + size <= ld - pos, 2,\n                    \"data string too short\");\n    pos += ntoalign;  /* skip alignment */\n    /* stack space for item + next position */\n    luaL_checkstack(L, 2, \"too many results\");\n    n++;\n    switch (opt) {\n      case Kint:\n      case Kuint: {\n        lua_Integer res = unpackint(L, data + pos, h.islittle, size,\n                                       (opt == Kint));\n        lua_pushinteger(L, res);\n        break;\n      }\n      case Kfloat: {\n        float f;\n        copywithendian((char *)&f, data + pos, sizeof(f), h.islittle);\n        lua_pushnumber(L, (lua_Number)f);\n        break;\n      }\n      case Knumber: {\n        lua_Number f;\n        copywithendian((char *)&f, data + pos, sizeof(f), h.islittle);\n        lua_pushnumber(L, f);\n        break;\n      }\n      case Kdouble: {\n        double f;\n        copywithendian((char *)&f, data + pos, sizeof(f), h.islittle);\n        lua_pushnumber(L, (lua_Number)f);\n        break;\n      }\n      case Kchar: {\n        lua_pushlstring(L, data + pos, size);\n        break;\n      }\n      case Kstring: {\n        size_t len = (size_t)unpackint(L, data + pos, h.islittle, size, 0);\n        luaL_argcheck(L, len <= ld - pos - size, 2, \"data string too short\");\n        lua_pushlstring(L, data + pos + size, len);\n        pos += len;  /* skip string */\n        break;\n      }\n      case Kzstr: {\n        size_t len = strlen(data + pos);\n        luaL_argcheck(L, pos + len < ld, 2,\n                         \"unfinished string for format 'z'\");\n        lua_pushlstring(L, data + pos, len);\n        pos += len + 1;  /* skip string plus final '\\0' */\n        break;\n      }\n      case Kpaddalign: case Kpadding: case Knop:\n        n--;  /* undo increment */\n        break;\n    }\n    pos += size;\n  }\n  lua_pushinteger(L, pos + 1);  /* next position */\n  return n + 1;\n}\n\n/* }====================================================== */\n\n\nstatic const luaL_Reg strlib[] = {\n  {\"byte\", str_byte},\n  {\"char\", str_char},\n  {\"dump\", str_dump},\n  {\"find\", str_find},\n  {\"format\", str_format},\n  {\"gmatch\", gmatch},\n  {\"gsub\", str_gsub},\n  {\"len\", str_len},\n  {\"lower\", str_lower},\n  {\"match\", str_match},\n  {\"rep\", str_rep},\n  {\"reverse\", str_reverse},\n  {\"sub\", str_sub},\n  {\"upper\", str_upper},\n  {\"pack\", str_pack},\n  {\"packsize\", str_packsize},\n  {\"unpack\", str_unpack},\n  {NULL, NULL}\n};\n\n\nstatic void createmetatable (lua_State *L) {\n  /* table to be metatable for strings */\n  luaL_newlibtable(L, stringmetamethods);\n  luaL_setfuncs(L, stringmetamethods, 0);\n  lua_pushliteral(L, \"\");  /* dummy string */\n  lua_pushvalue(L, -2);  /* copy table */\n  lua_setmetatable(L, -2);  /* set table as metatable for strings */\n  lua_pop(L, 1);  /* pop dummy string */\n  lua_pushvalue(L, -2);  /* get string library */\n  lua_setfield(L, -2, \"__index\");  /* metatable.__index = string */\n  lua_pop(L, 1);  /* pop metatable */\n}\n\n\n/*\n** Open string library\n*/\nLUAMOD_API int luaopen_string (lua_State *L) {\n  luaL_newlib(L, strlib);\n  createmetatable(L);\n  return 1;\n}\n\n/*\n** $Id: ltablib.c $\n** Library for Table Manipulation\n** See Copyright Notice in lua.h\n*/\n\n#define ltablib_c\n#define LUA_LIB\n\n/*#include \"lprefix.h\"*/\n\n\n#include <limits.h>\n#include <stddef.h>\n#include <string.h>\n\n/*#include \"lua.h\"*/\n\n/*#include \"lauxlib.h\"*/\n/*#include \"lualib.h\"*/\n\n\n/*\n** Operations that an object must define to mimic a table\n** (some functions only need some of them)\n*/\n#define TAB_R\t1\t\t\t/* read */\n#define TAB_W\t2\t\t\t/* write */\n#define TAB_L\t4\t\t\t/* length */\n#define TAB_RW\t(TAB_R | TAB_W)\t\t/* read/write */\n\n\n#define aux_getn(L,n,w)\t(checktab(L, n, (w) | TAB_L), luaL_len(L, n))\n\n\nstatic int checkfield (lua_State *L, const char *key, int n) {\n  lua_pushstring(L, key);\n  return (lua_rawget(L, -n) != LUA_TNIL);\n}\n\n\n/*\n** Check that 'arg' either is a table or can behave like one (that is,\n** has a metatable with the required metamethods)\n*/\nstatic void checktab (lua_State *L, int arg, int what) {\n  if (lua_type(L, arg) != LUA_TTABLE) {  /* is it not a table? */\n    int n = 1;  /* number of elements to pop */\n    if (lua_getmetatable(L, arg) &&  /* must have metatable */\n        (!(what & TAB_R) || checkfield(L, \"__index\", ++n)) &&\n        (!(what & TAB_W) || checkfield(L, \"__newindex\", ++n)) &&\n        (!(what & TAB_L) || checkfield(L, \"__len\", ++n))) {\n      lua_pop(L, n);  /* pop metatable and tested metamethods */\n    }\n    else\n      luaL_checktype(L, arg, LUA_TTABLE);  /* force an error */\n  }\n}\n\n\nstatic int tinsert (lua_State *L) {\n  lua_Integer pos;  /* where to insert new element */\n  lua_Integer e = aux_getn(L, 1, TAB_RW);\n  e = luaL_intop(+, e, 1);  /* first empty element */\n  switch (lua_gettop(L)) {\n    case 2: {  /* called with only 2 arguments */\n      pos = e;  /* insert new element at the end */\n      break;\n    }\n    case 3: {\n      lua_Integer i;\n      pos = luaL_checkinteger(L, 2);  /* 2nd argument is the position */\n      /* check whether 'pos' is in [1, e] */\n      luaL_argcheck(L, (lua_Unsigned)pos - 1u < (lua_Unsigned)e, 2,\n                       \"position out of bounds\");\n      for (i = e; i > pos; i--) {  /* move up elements */\n        lua_geti(L, 1, i - 1);\n        lua_seti(L, 1, i);  /* t[i] = t[i - 1] */\n      }\n      break;\n    }\n    default: {\n      return luaL_error(L, \"wrong number of arguments to 'insert'\");\n    }\n  }\n  lua_seti(L, 1, pos);  /* t[pos] = v */\n  return 0;\n}\n\n\nstatic int tremove (lua_State *L) {\n  lua_Integer size = aux_getn(L, 1, TAB_RW);\n  lua_Integer pos = luaL_optinteger(L, 2, size);\n  if (pos != size)  /* validate 'pos' if given */\n    /* check whether 'pos' is in [1, size + 1] */\n    luaL_argcheck(L, (lua_Unsigned)pos - 1u <= (lua_Unsigned)size, 2,\n                     \"position out of bounds\");\n  lua_geti(L, 1, pos);  /* result = t[pos] */\n  for ( ; pos < size; pos++) {\n    lua_geti(L, 1, pos + 1);\n    lua_seti(L, 1, pos);  /* t[pos] = t[pos + 1] */\n  }\n  lua_pushnil(L);\n  lua_seti(L, 1, pos);  /* remove entry t[pos] */\n  return 1;\n}\n\n\n/*\n** Copy elements (1[f], ..., 1[e]) into (tt[t], tt[t+1], ...). Whenever\n** possible, copy in increasing order, which is better for rehashing.\n** \"possible\" means destination after original range, or smaller\n** than origin, or copying to another table.\n*/\nstatic int tmove (lua_State *L) {\n  lua_Integer f = luaL_checkinteger(L, 2);\n  lua_Integer e = luaL_checkinteger(L, 3);\n  lua_Integer t = luaL_checkinteger(L, 4);\n  int tt = !lua_isnoneornil(L, 5) ? 5 : 1;  /* destination table */\n  checktab(L, 1, TAB_R);\n  checktab(L, tt, TAB_W);\n  if (e >= f) {  /* otherwise, nothing to move */\n    lua_Integer n, i;\n    luaL_argcheck(L, f > 0 || e < LUA_MAXINTEGER + f, 3,\n                  \"too many elements to move\");\n    n = e - f + 1;  /* number of elements to move */\n    luaL_argcheck(L, t <= LUA_MAXINTEGER - n + 1, 4,\n                  \"destination wrap around\");\n    if (t > e || t <= f || (tt != 1 && !lua_compare(L, 1, tt, LUA_OPEQ))) {\n      for (i = 0; i < n; i++) {\n        lua_geti(L, 1, f + i);\n        lua_seti(L, tt, t + i);\n      }\n    }\n    else {\n      for (i = n - 1; i >= 0; i--) {\n        lua_geti(L, 1, f + i);\n        lua_seti(L, tt, t + i);\n      }\n    }\n  }\n  lua_pushvalue(L, tt);  /* return destination table */\n  return 1;\n}\n\n\nstatic void addfield (lua_State *L, luaL_Buffer *b, lua_Integer i) {\n  lua_geti(L, 1, i);\n  if (l_unlikely(!lua_isstring(L, -1)))\n    luaL_error(L, \"invalid value (%s) at index %I in table for 'concat'\",\n                  luaL_typename(L, -1), (LUAI_UACINT)i);\n  luaL_addvalue(b);\n}\n\n\nstatic int tconcat (lua_State *L) {\n  luaL_Buffer b;\n  lua_Integer last = aux_getn(L, 1, TAB_R);\n  size_t lsep;\n  const char *sep = luaL_optlstring(L, 2, \"\", &lsep);\n  lua_Integer i = luaL_optinteger(L, 3, 1);\n  last = luaL_optinteger(L, 4, last);\n  luaL_buffinit(L, &b);\n  for (; i < last; i++) {\n    addfield(L, &b, i);\n    luaL_addlstring(&b, sep, lsep);\n  }\n  if (i == last)  /* add last value (if interval was not empty) */\n    addfield(L, &b, i);\n  luaL_pushresult(&b);\n  return 1;\n}\n\n\n/*\n** {======================================================\n** Pack/unpack\n** =======================================================\n*/\n\nstatic int tpack (lua_State *L) {\n  int i;\n  int n = lua_gettop(L);  /* number of elements to pack */\n  lua_createtable(L, n, 1);  /* create result table */\n  lua_insert(L, 1);  /* put it at index 1 */\n  for (i = n; i >= 1; i--)  /* assign elements */\n    lua_seti(L, 1, i);\n  lua_pushinteger(L, n);\n  lua_setfield(L, 1, \"n\");  /* t.n = number of elements */\n  return 1;  /* return table */\n}\n\n\nstatic int tunpack (lua_State *L) {\n  lua_Unsigned n;\n  lua_Integer i = luaL_optinteger(L, 2, 1);\n  lua_Integer e = luaL_opt(L, luaL_checkinteger, 3, luaL_len(L, 1));\n  if (i > e) return 0;  /* empty range */\n  n = (lua_Unsigned)e - i;  /* number of elements minus 1 (avoid overflows) */\n  if (l_unlikely(n >= (unsigned int)INT_MAX  ||\n                 !lua_checkstack(L, (int)(++n))))\n    return luaL_error(L, \"too many results to unpack\");\n  for (; i < e; i++) {  /* push arg[i..e - 1] (to avoid overflows) */\n    lua_geti(L, 1, i);\n  }\n  lua_geti(L, 1, e);  /* push last element */\n  return (int)n;\n}\n\n/* }====================================================== */\n\n\n\n/*\n** {======================================================\n** Quicksort\n** (based on 'Algorithms in MODULA-3', Robert Sedgewick;\n**  Addison-Wesley, 1993.)\n** =======================================================\n*/\n\n\n/* type for array indices */\ntypedef unsigned int IdxT;\n\n\n/*\n** Produce a \"random\" 'unsigned int' to randomize pivot choice. This\n** macro is used only when 'sort' detects a big imbalance in the result\n** of a partition. (If you don't want/need this \"randomness\", ~0 is a\n** good choice.)\n*/\n#if !defined(l_randomizePivot)\t\t/* { */\n\n#include <time.h>\n\n/* size of 'e' measured in number of 'unsigned int's */\n#define sof(e)\t\t(sizeof(e) / sizeof(unsigned int))\n\n/*\n** Use 'time' and 'clock' as sources of \"randomness\". Because we don't\n** know the types 'clock_t' and 'time_t', we cannot cast them to\n** anything without risking overflows. A safe way to use their values\n** is to copy them to an array of a known type and use the array values.\n*/\nstatic unsigned int l_randomizePivot (void) {\n  clock_t c = clock();\n  time_t t = time(NULL);\n  unsigned int buff[sof(c) + sof(t)];\n  unsigned int i, rnd = 0;\n  memcpy(buff, &c, sof(c) * sizeof(unsigned int));\n  memcpy(buff + sof(c), &t, sof(t) * sizeof(unsigned int));\n  for (i = 0; i < sof(buff); i++)\n    rnd += buff[i];\n  return rnd;\n}\n\n#endif\t\t\t\t\t/* } */\n\n\n/* arrays larger than 'RANLIMIT' may use randomized pivots */\n#define RANLIMIT\t100u\n\n\nstatic void set2 (lua_State *L, IdxT i, IdxT j) {\n  lua_seti(L, 1, i);\n  lua_seti(L, 1, j);\n}\n\n\n/*\n** Return true iff value at stack index 'a' is less than the value at\n** index 'b' (according to the order of the sort).\n*/\nstatic int sort_comp (lua_State *L, int a, int b) {\n  if (lua_isnil(L, 2))  /* no function? */\n    return lua_compare(L, a, b, LUA_OPLT);  /* a < b */\n  else {  /* function */\n    int res;\n    lua_pushvalue(L, 2);    /* push function */\n    lua_pushvalue(L, a-1);  /* -1 to compensate function */\n    lua_pushvalue(L, b-2);  /* -2 to compensate function and 'a' */\n    lua_call(L, 2, 1);      /* call function */\n    res = lua_toboolean(L, -1);  /* get result */\n    lua_pop(L, 1);          /* pop result */\n    return res;\n  }\n}\n\n\n/*\n** Does the partition: Pivot P is at the top of the stack.\n** precondition: a[lo] <= P == a[up-1] <= a[up],\n** so it only needs to do the partition from lo + 1 to up - 2.\n** Pos-condition: a[lo .. i - 1] <= a[i] == P <= a[i + 1 .. up]\n** returns 'i'.\n*/\nstatic IdxT partition (lua_State *L, IdxT lo, IdxT up) {\n  IdxT i = lo;  /* will be incremented before first use */\n  IdxT j = up - 1;  /* will be decremented before first use */\n  /* loop invariant: a[lo .. i] <= P <= a[j .. up] */\n  for (;;) {\n    /* next loop: repeat ++i while a[i] < P */\n    while ((void)lua_geti(L, 1, ++i), sort_comp(L, -1, -2)) {\n      if (l_unlikely(i == up - 1))  /* a[i] < P  but a[up - 1] == P  ?? */\n        luaL_error(L, \"invalid order function for sorting\");\n      lua_pop(L, 1);  /* remove a[i] */\n    }\n    /* after the loop, a[i] >= P and a[lo .. i - 1] < P */\n    /* next loop: repeat --j while P < a[j] */\n    while ((void)lua_geti(L, 1, --j), sort_comp(L, -3, -1)) {\n      if (l_unlikely(j < i))  /* j < i  but  a[j] > P ?? */\n        luaL_error(L, \"invalid order function for sorting\");\n      lua_pop(L, 1);  /* remove a[j] */\n    }\n    /* after the loop, a[j] <= P and a[j + 1 .. up] >= P */\n    if (j < i) {  /* no elements out of place? */\n      /* a[lo .. i - 1] <= P <= a[j + 1 .. i .. up] */\n      lua_pop(L, 1);  /* pop a[j] */\n      /* swap pivot (a[up - 1]) with a[i] to satisfy pos-condition */\n      set2(L, up - 1, i);\n      return i;\n    }\n    /* otherwise, swap a[i] - a[j] to restore invariant and repeat */\n    set2(L, i, j);\n  }\n}\n\n\n/*\n** Choose an element in the middle (2nd-3th quarters) of [lo,up]\n** \"randomized\" by 'rnd'\n*/\nstatic IdxT choosePivot (IdxT lo, IdxT up, unsigned int rnd) {\n  IdxT r4 = (up - lo) / 4;  /* range/4 */\n  IdxT p = rnd % (r4 * 2) + (lo + r4);\n  lua_assert(lo + r4 <= p && p <= up - r4);\n  return p;\n}\n\n\n/*\n** Quicksort algorithm (recursive function)\n*/\nstatic void auxsort (lua_State *L, IdxT lo, IdxT up,\n                                   unsigned int rnd) {\n  while (lo < up) {  /* loop for tail recursion */\n    IdxT p;  /* Pivot index */\n    IdxT n;  /* to be used later */\n    /* sort elements 'lo', 'p', and 'up' */\n    lua_geti(L, 1, lo);\n    lua_geti(L, 1, up);\n    if (sort_comp(L, -1, -2))  /* a[up] < a[lo]? */\n      set2(L, lo, up);  /* swap a[lo] - a[up] */\n    else\n      lua_pop(L, 2);  /* remove both values */\n    if (up - lo == 1)  /* only 2 elements? */\n      return;  /* already sorted */\n    if (up - lo < RANLIMIT || rnd == 0)  /* small interval or no randomize? */\n      p = (lo + up)/2;  /* middle element is a good pivot */\n    else  /* for larger intervals, it is worth a random pivot */\n      p = choosePivot(lo, up, rnd);\n    lua_geti(L, 1, p);\n    lua_geti(L, 1, lo);\n    if (sort_comp(L, -2, -1))  /* a[p] < a[lo]? */\n      set2(L, p, lo);  /* swap a[p] - a[lo] */\n    else {\n      lua_pop(L, 1);  /* remove a[lo] */\n      lua_geti(L, 1, up);\n      if (sort_comp(L, -1, -2))  /* a[up] < a[p]? */\n        set2(L, p, up);  /* swap a[up] - a[p] */\n      else\n        lua_pop(L, 2);\n    }\n    if (up - lo == 2)  /* only 3 elements? */\n      return;  /* already sorted */\n    lua_geti(L, 1, p);  /* get middle element (Pivot) */\n    lua_pushvalue(L, -1);  /* push Pivot */\n    lua_geti(L, 1, up - 1);  /* push a[up - 1] */\n    set2(L, p, up - 1);  /* swap Pivot (a[p]) with a[up - 1] */\n    p = partition(L, lo, up);\n    /* a[lo .. p - 1] <= a[p] == P <= a[p + 1 .. up] */\n    if (p - lo < up - p) {  /* lower interval is smaller? */\n      auxsort(L, lo, p - 1, rnd);  /* call recursively for lower interval */\n      n = p - lo;  /* size of smaller interval */\n      lo = p + 1;  /* tail call for [p + 1 .. up] (upper interval) */\n    }\n    else {\n      auxsort(L, p + 1, up, rnd);  /* call recursively for upper interval */\n      n = up - p;  /* size of smaller interval */\n      up = p - 1;  /* tail call for [lo .. p - 1]  (lower interval) */\n    }\n    if ((up - lo) / 128 > n) /* partition too imbalanced? */\n      rnd = l_randomizePivot();  /* try a new randomization */\n  }  /* tail call auxsort(L, lo, up, rnd) */\n}\n\n\nstatic int sort (lua_State *L) {\n  lua_Integer n = aux_getn(L, 1, TAB_RW);\n  if (n > 1) {  /* non-trivial interval? */\n    luaL_argcheck(L, n < INT_MAX, 1, \"array too big\");\n    if (!lua_isnoneornil(L, 2))  /* is there a 2nd argument? */\n      luaL_checktype(L, 2, LUA_TFUNCTION);  /* must be a function */\n    lua_settop(L, 2);  /* make sure there are two arguments */\n    auxsort(L, 1, (IdxT)n, 0);\n  }\n  return 0;\n}\n\n/* }====================================================== */\n\n\nstatic const luaL_Reg tab_funcs[] = {\n  {\"concat\", tconcat},\n  {\"insert\", tinsert},\n  {\"pack\", tpack},\n  {\"unpack\", tunpack},\n  {\"remove\", tremove},\n  {\"move\", tmove},\n  {\"sort\", sort},\n  {NULL, NULL}\n};\n\n\nLUAMOD_API int luaopen_table (lua_State *L) {\n  luaL_newlib(L, tab_funcs);\n  return 1;\n}\n\n/*\n** $Id: lutf8lib.c $\n** Standard library for UTF-8 manipulation\n** See Copyright Notice in lua.h\n*/\n\n#define lutf8lib_c\n#define LUA_LIB\n\n/*#include \"lprefix.h\"*/\n\n\n#include <assert.h>\n#include <limits.h>\n#include <stdlib.h>\n#include <string.h>\n\n/*#include \"lua.h\"*/\n\n/*#include \"lauxlib.h\"*/\n/*#include \"lualib.h\"*/\n\n\n#define MAXUNICODE\t0x10FFFFu\n\n#define MAXUTF\t\t0x7FFFFFFFu\n\n\n#define MSGInvalid\t\"invalid UTF-8 code\"\n\n/*\n** Integer type for decoded UTF-8 values; MAXUTF needs 31 bits.\n*/\n#if (UINT_MAX >> 30) >= 1\ntypedef unsigned int utfint;\n#else\ntypedef unsigned long utfint;\n#endif\n\n\n#define iscont(c)\t(((c) & 0xC0) == 0x80)\n#define iscontp(p)\tiscont(*(p))\n\n\n/* from strlib */\n/* translate a relative string position: negative means back from end */\nstatic lua_Integer u_posrelat (lua_Integer pos, size_t len) {\n  if (pos >= 0) return pos;\n  else if (0u - (size_t)pos > len) return 0;\n  else return (lua_Integer)len + pos + 1;\n}\n\n\n/*\n** Decode one UTF-8 sequence, returning NULL if byte sequence is\n** invalid.  The array 'limits' stores the minimum value for each\n** sequence length, to check for overlong representations. Its first\n** entry forces an error for non-ascii bytes with no continuation\n** bytes (count == 0).\n*/\nstatic const char *utf8_decode (const char *s, utfint *val, int strict) {\n  static const utfint limits[] =\n        {~(utfint)0, 0x80, 0x800, 0x10000u, 0x200000u, 0x4000000u};\n  unsigned int c = (unsigned char)s[0];\n  utfint res = 0;  /* final result */\n  if (c < 0x80)  /* ascii? */\n    res = c;\n  else {\n    int count = 0;  /* to count number of continuation bytes */\n    for (; c & 0x40; c <<= 1) {  /* while it needs continuation bytes... */\n      unsigned int cc = (unsigned char)s[++count];  /* read next byte */\n      if (!iscont(cc))  /* not a continuation byte? */\n        return NULL;  /* invalid byte sequence */\n      res = (res << 6) | (cc & 0x3F);  /* add lower 6 bits from cont. byte */\n    }\n    res |= ((utfint)(c & 0x7F) << (count * 5));  /* add first byte */\n    if (count > 5 || res > MAXUTF || res < limits[count])\n      return NULL;  /* invalid byte sequence */\n    s += count;  /* skip continuation bytes read */\n  }\n  if (strict) {\n    /* check for invalid code points; too large or surrogates */\n    if (res > MAXUNICODE || (0xD800u <= res && res <= 0xDFFFu))\n      return NULL;\n  }\n  if (val) *val = res;\n  return s + 1;  /* +1 to include first byte */\n}\n\n\n/*\n** utf8len(s [, i [, j [, lax]]]) --> number of characters that\n** start in the range [i,j], or nil + current position if 's' is not\n** well formed in that interval\n*/\nstatic int utflen (lua_State *L) {\n  lua_Integer n = 0;  /* counter for the number of characters */\n  size_t len;  /* string length in bytes */\n  const char *s = luaL_checklstring(L, 1, &len);\n  lua_Integer posi = u_posrelat(luaL_optinteger(L, 2, 1), len);\n  lua_Integer posj = u_posrelat(luaL_optinteger(L, 3, -1), len);\n  int lax = lua_toboolean(L, 4);\n  luaL_argcheck(L, 1 <= posi && --posi <= (lua_Integer)len, 2,\n                   \"initial position out of bounds\");\n  luaL_argcheck(L, --posj < (lua_Integer)len, 3,\n                   \"final position out of bounds\");\n  while (posi <= posj) {\n    const char *s1 = utf8_decode(s + posi, NULL, !lax);\n    if (s1 == NULL) {  /* conversion error? */\n      luaL_pushfail(L);  /* return fail ... */\n      lua_pushinteger(L, posi + 1);  /* ... and current position */\n      return 2;\n    }\n    posi = s1 - s;\n    n++;\n  }\n  lua_pushinteger(L, n);\n  return 1;\n}\n\n\n/*\n** codepoint(s, [i, [j [, lax]]]) -> returns codepoints for all\n** characters that start in the range [i,j]\n*/\nstatic int codepoint (lua_State *L) {\n  size_t len;\n  const char *s = luaL_checklstring(L, 1, &len);\n  lua_Integer posi = u_posrelat(luaL_optinteger(L, 2, 1), len);\n  lua_Integer pose = u_posrelat(luaL_optinteger(L, 3, posi), len);\n  int lax = lua_toboolean(L, 4);\n  int n;\n  const char *se;\n  luaL_argcheck(L, posi >= 1, 2, \"out of bounds\");\n  luaL_argcheck(L, pose <= (lua_Integer)len, 3, \"out of bounds\");\n  if (posi > pose) return 0;  /* empty interval; return no values */\n  if (pose - posi >= INT_MAX)  /* (lua_Integer -> int) overflow? */\n    return luaL_error(L, \"string slice too long\");\n  n = (int)(pose -  posi) + 1;  /* upper bound for number of returns */\n  luaL_checkstack(L, n, \"string slice too long\");\n  n = 0;  /* count the number of returns */\n  se = s + pose;  /* string end */\n  for (s += posi - 1; s < se;) {\n    utfint code;\n    s = utf8_decode(s, &code, !lax);\n    if (s == NULL)\n      return luaL_error(L, MSGInvalid);\n    lua_pushinteger(L, code);\n    n++;\n  }\n  return n;\n}\n\n\nstatic void pushutfchar (lua_State *L, int arg) {\n  lua_Unsigned code = (lua_Unsigned)luaL_checkinteger(L, arg);\n  luaL_argcheck(L, code <= MAXUTF, arg, \"value out of range\");\n  lua_pushfstring(L, \"%U\", (long)code);\n}\n\n\n/*\n** utfchar(n1, n2, ...)  -> char(n1)..char(n2)...\n*/\nstatic int utfchar (lua_State *L) {\n  int n = lua_gettop(L);  /* number of arguments */\n  if (n == 1)  /* optimize common case of single char */\n    pushutfchar(L, 1);\n  else {\n    int i;\n    luaL_Buffer b;\n    luaL_buffinit(L, &b);\n    for (i = 1; i <= n; i++) {\n      pushutfchar(L, i);\n      luaL_addvalue(&b);\n    }\n    luaL_pushresult(&b);\n  }\n  return 1;\n}\n\n\n/*\n** offset(s, n, [i])  -> index where n-th character counting from\n**   position 'i' starts; 0 means character at 'i'.\n*/\nstatic int byteoffset (lua_State *L) {\n  size_t len;\n  const char *s = luaL_checklstring(L, 1, &len);\n  lua_Integer n  = luaL_checkinteger(L, 2);\n  lua_Integer posi = (n >= 0) ? 1 : len + 1;\n  posi = u_posrelat(luaL_optinteger(L, 3, posi), len);\n  luaL_argcheck(L, 1 <= posi && --posi <= (lua_Integer)len, 3,\n                   \"position out of bounds\");\n  if (n == 0) {\n    /* find beginning of current byte sequence */\n    while (posi > 0 && iscontp(s + posi)) posi--;\n  }\n  else {\n    if (iscontp(s + posi))\n      return luaL_error(L, \"initial position is a continuation byte\");\n    if (n < 0) {\n       while (n < 0 && posi > 0) {  /* move back */\n         do {  /* find beginning of previous character */\n           posi--;\n         } while (posi > 0 && iscontp(s + posi));\n         n++;\n       }\n     }\n     else {\n       n--;  /* do not move for 1st character */\n       while (n > 0 && posi < (lua_Integer)len) {\n         do {  /* find beginning of next character */\n           posi++;\n         } while (iscontp(s + posi));  /* (cannot pass final '\\0') */\n         n--;\n       }\n     }\n  }\n  if (n == 0)  /* did it find given character? */\n    lua_pushinteger(L, posi + 1);\n  else  /* no such character */\n    luaL_pushfail(L);\n  return 1;\n}\n\n\nstatic int iter_aux (lua_State *L, int strict) {\n  size_t len;\n  const char *s = luaL_checklstring(L, 1, &len);\n  lua_Unsigned n = (lua_Unsigned)lua_tointeger(L, 2);\n  if (n < len) {\n    while (iscontp(s + n)) n++;  /* go to next character */\n  }\n  if (n >= len)  /* (also handles original 'n' being negative) */\n    return 0;  /* no more codepoints */\n  else {\n    utfint code;\n    const char *next = utf8_decode(s + n, &code, strict);\n    if (next == NULL || iscontp(next))\n      return luaL_error(L, MSGInvalid);\n    lua_pushinteger(L, n + 1);\n    lua_pushinteger(L, code);\n    return 2;\n  }\n}\n\n\nstatic int iter_auxstrict (lua_State *L) {\n  return iter_aux(L, 1);\n}\n\nstatic int iter_auxlax (lua_State *L) {\n  return iter_aux(L, 0);\n}\n\n\nstatic int iter_codes (lua_State *L) {\n  int lax = lua_toboolean(L, 2);\n  const char *s = luaL_checkstring(L, 1);\n  luaL_argcheck(L, !iscontp(s), 1, MSGInvalid);\n  lua_pushcfunction(L, lax ? iter_auxlax : iter_auxstrict);\n  lua_pushvalue(L, 1);\n  lua_pushinteger(L, 0);\n  return 3;\n}\n\n\n/* pattern to match a single UTF-8 character */\n#define UTF8PATT\t\"[\\0-\\x7F\\xC2-\\xFD][\\x80-\\xBF]*\"\n\n\nstatic const luaL_Reg funcs[] = {\n  {\"offset\", byteoffset},\n  {\"codepoint\", codepoint},\n  {\"char\", utfchar},\n  {\"len\", utflen},\n  {\"codes\", iter_codes},\n  /* placeholders */\n  {\"charpattern\", NULL},\n  {NULL, NULL}\n};\n\n\nLUAMOD_API int luaopen_utf8 (lua_State *L) {\n  luaL_newlib(L, funcs);\n  lua_pushlstring(L, UTF8PATT, sizeof(UTF8PATT)/sizeof(char) - 1);\n  lua_setfield(L, -2, \"charpattern\");\n  return 1;\n}\n\n/*\n** $Id: linit.c $\n** Initialization of libraries for lua.c and other clients\n** See Copyright Notice in lua.h\n*/\n\n\n#define linit_c\n#define LUA_LIB\n\n/*\n** If you embed Lua in your program and need to open the standard\n** libraries, call luaL_openlibs in your program. If you need a\n** different set of libraries, copy this file to your project and edit\n** it to suit your needs.\n**\n** You can also *preload* libraries, so that a later 'require' can\n** open the library, which is already linked to the application.\n** For that, do the following code:\n**\n**  luaL_getsubtable(L, LUA_REGISTRYINDEX, LUA_PRELOAD_TABLE);\n**  lua_pushcfunction(L, luaopen_modname);\n**  lua_setfield(L, -2, modname);\n**  lua_pop(L, 1);  // remove PRELOAD table\n*/\n\n/*#include \"lprefix.h\"*/\n\n\n#include <stddef.h>\n\n/*#include \"lua.h\"*/\n\n/*#include \"lualib.h\"*/\n/*#include \"lauxlib.h\"*/\n\n\n/*\n** these libs are loaded by lua.c and are readily available to any Lua\n** program\n*/\nstatic const luaL_Reg loadedlibs[] = {\n  {LUA_GNAME, luaopen_base},\n  {LUA_LOADLIBNAME, luaopen_package},\n  {LUA_COLIBNAME, luaopen_coroutine},\n  {LUA_TABLIBNAME, luaopen_table},\n  {LUA_IOLIBNAME, luaopen_io},\n  {LUA_OSLIBNAME, luaopen_os},\n  {LUA_STRLIBNAME, luaopen_string},\n  {LUA_MATHLIBNAME, luaopen_math},\n  {LUA_UTF8LIBNAME, luaopen_utf8},\n  {LUA_DBLIBNAME, luaopen_debug},\n  {NULL, NULL}\n};\n\n\nLUALIB_API void luaL_openlibs (lua_State *L) {\n  const luaL_Reg *lib;\n  /* \"require\" functions from 'loadedlibs' and set results to global table */\n  for (lib = loadedlibs; lib->func; lib++) {\n    luaL_requiref(L, lib->name, lib->func, 1);\n    lua_pop(L, 1);  /* remove lib */\n  }\n}\n\n#endif /* LUA_IMPL */\n#ifdef __cplusplus\n}\n#endif\n#ifdef LUA_MAKE_LUA\n/*\n** $Id: lua.c $\n** Lua stand-alone interpreter\n** See Copyright Notice in lua.h\n*/\n\n#define lua_c\n\n/*#include \"lprefix.h\"*/\n\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include <signal.h>\n\n/*#include \"lua.h\"*/\n\n/*#include \"lauxlib.h\"*/\n/*#include \"lualib.h\"*/\n\n\n#if !defined(LUA_PROGNAME)\n#define LUA_PROGNAME\t\t\"lua\"\n#endif\n\n#if !defined(LUA_INIT_VAR)\n#define LUA_INIT_VAR\t\t\"LUA_INIT\"\n#endif\n\n#define LUA_INITVARVERSION\tLUA_INIT_VAR LUA_VERSUFFIX\n\n\nstatic lua_State *globalL = NULL;\n\nstatic const char *progname = LUA_PROGNAME;\n\n\n#if defined(LUA_USE_POSIX)   /* { */\n\n/*\n** Use 'sigaction' when available.\n*/\nstatic void setsignal (int sig, void (*handler)(int)) {\n  struct sigaction sa;\n  sa.sa_handler = handler;\n  sa.sa_flags = 0;\n  sigemptyset(&sa.sa_mask);  /* do not mask any signal */\n  sigaction(sig, &sa, NULL);\n}\n\n#else           /* }{ */\n\n#define setsignal            signal\n\n#endif                               /* } */\n\n\n/*\n** Hook set by signal function to stop the interpreter.\n*/\nstatic void lstop (lua_State *L, lua_Debug *ar) {\n  (void)ar;  /* unused arg. */\n  lua_sethook(L, NULL, 0, 0);  /* reset hook */\n  luaL_error(L, \"interrupted!\");\n}\n\n\n/*\n** Function to be called at a C signal. Because a C signal cannot\n** just change a Lua state (as there is no proper synchronization),\n** this function only sets a hook that, when called, will stop the\n** interpreter.\n*/\nstatic void laction (int i) {\n  int flag = LUA_MASKCALL | LUA_MASKRET | LUA_MASKLINE | LUA_MASKCOUNT;\n  setsignal(i, SIG_DFL); /* if another SIGINT happens, terminate process */\n  lua_sethook(globalL, lstop, flag, 1);\n}\n\n\nstatic void print_usage (const char *badoption) {\n  lua_writestringerror(\"%s: \", progname);\n  if (badoption[1] == 'e' || badoption[1] == 'l')\n    lua_writestringerror(\"'%s' needs argument\\n\", badoption);\n  else\n    lua_writestringerror(\"unrecognized option '%s'\\n\", badoption);\n  lua_writestringerror(\n  \"usage: %s [options] [script [args]]\\n\"\n  \"Available options are:\\n\"\n  \"  -e stat   execute string 'stat'\\n\"\n  \"  -i        enter interactive mode after executing 'script'\\n\"\n  \"  -l mod    require library 'mod' into global 'mod'\\n\"\n  \"  -l g=mod  require library 'mod' into global 'g'\\n\"\n  \"  -v        show version information\\n\"\n  \"  -E        ignore environment variables\\n\"\n  \"  -W        turn warnings on\\n\"\n  \"  --        stop handling options\\n\"\n  \"  -         stop handling options and execute stdin\\n\"\n  ,\n  progname);\n}\n\n\n/*\n** Prints an error message, adding the program name in front of it\n** (if present)\n*/\nstatic void l_message (const char *pname, const char *msg) {\n  if (pname) lua_writestringerror(\"%s: \", pname);\n  lua_writestringerror(\"%s\\n\", msg);\n}\n\n\n/*\n** Check whether 'status' is not OK and, if so, prints the error\n** message on the top of the stack. It assumes that the error object\n** is a string, as it was either generated by Lua or by 'msghandler'.\n*/\nstatic int report (lua_State *L, int status) {\n  if (status != LUA_OK) {\n    const char *msg = lua_tostring(L, -1);\n    l_message(progname, msg);\n    lua_pop(L, 1);  /* remove message */\n  }\n  return status;\n}\n\n\n/*\n** Message handler used to run all chunks\n*/\nstatic int msghandler (lua_State *L) {\n  const char *msg = lua_tostring(L, 1);\n  if (msg == NULL) {  /* is error object not a string? */\n    if (luaL_callmeta(L, 1, \"__tostring\") &&  /* does it have a metamethod */\n        lua_type(L, -1) == LUA_TSTRING)  /* that produces a string? */\n      return 1;  /* that is the message */\n    else\n      msg = lua_pushfstring(L, \"(error object is a %s value)\",\n                               luaL_typename(L, 1));\n  }\n  luaL_traceback(L, L, msg, 1);  /* append a standard traceback */\n  return 1;  /* return the traceback */\n}\n\n\n/*\n** Interface to 'lua_pcall', which sets appropriate message function\n** and C-signal handler. Used to run all chunks.\n*/\nstatic int docall (lua_State *L, int narg, int nres) {\n  int status;\n  int base = lua_gettop(L) - narg;  /* function index */\n  lua_pushcfunction(L, msghandler);  /* push message handler */\n  lua_insert(L, base);  /* put it under function and args */\n  globalL = L;  /* to be available to 'laction' */\n  setsignal(SIGINT, laction);  /* set C-signal handler */\n  status = lua_pcall(L, narg, nres, base);\n  setsignal(SIGINT, SIG_DFL); /* reset C-signal handler */\n  lua_remove(L, base);  /* remove message handler from the stack */\n  return status;\n}\n\n\nstatic void print_version (void) {\n  lua_writestring(LUA_COPYRIGHT, strlen(LUA_COPYRIGHT));\n  lua_writeline();\n}\n\n\n/*\n** Create the 'arg' table, which stores all arguments from the\n** command line ('argv'). It should be aligned so that, at index 0,\n** it has 'argv[script]', which is the script name. The arguments\n** to the script (everything after 'script') go to positive indices;\n** other arguments (before the script name) go to negative indices.\n** If there is no script name, assume interpreter's name as base.\n** (If there is no interpreter's name either, 'script' is -1, so\n** table sizes are zero.)\n*/\nstatic void createargtable (lua_State *L, char **argv, int argc, int script) {\n  int i, narg;\n  narg = argc - (script + 1);  /* number of positive indices */\n  lua_createtable(L, narg, script + 1);\n  for (i = 0; i < argc; i++) {\n    lua_pushstring(L, argv[i]);\n    lua_rawseti(L, -2, i - script);\n  }\n  lua_setglobal(L, \"arg\");\n}\n\n\nstatic int dochunk (lua_State *L, int status) {\n  if (status == LUA_OK) status = docall(L, 0, 0);\n  return report(L, status);\n}\n\n\nstatic int dofile (lua_State *L, const char *name) {\n  return dochunk(L, luaL_loadfile(L, name));\n}\n\n\nstatic int dostring (lua_State *L, const char *s, const char *name) {\n  return dochunk(L, luaL_loadbuffer(L, s, strlen(s), name));\n}\n\n\n/*\n** Receives 'globname[=modname]' and runs 'globname = require(modname)'.\n*/\nstatic int dolibrary (lua_State *L, char *globname) {\n  int status;\n  char *modname = strchr(globname, '=');\n  if (modname == NULL)  /* no explicit name? */\n    modname = globname;  /* module name is equal to global name */\n  else {\n    *modname = '\\0';  /* global name ends here */\n    modname++;  /* module name starts after the '=' */\n  }\n  lua_getglobal(L, \"require\");\n  lua_pushstring(L, modname);\n  status = docall(L, 1, 1);  /* call 'require(modname)' */\n  if (status == LUA_OK)\n    lua_setglobal(L, globname);  /* globname = require(modname) */\n  return report(L, status);\n}\n\n\n/*\n** Push on the stack the contents of table 'arg' from 1 to #arg\n*/\nstatic int pushargs (lua_State *L) {\n  int i, n;\n  if (lua_getglobal(L, \"arg\") != LUA_TTABLE)\n    luaL_error(L, \"'arg' is not a table\");\n  n = (int)luaL_len(L, -1);\n  luaL_checkstack(L, n + 3, \"too many arguments to script\");\n  for (i = 1; i <= n; i++)\n    lua_rawgeti(L, -i, i);\n  lua_remove(L, -i);  /* remove table from the stack */\n  return n;\n}\n\n\nstatic int handle_script (lua_State *L, char **argv) {\n  int status;\n  const char *fname = argv[0];\n  if (strcmp(fname, \"-\") == 0 && strcmp(argv[-1], \"--\") != 0)\n    fname = NULL;  /* stdin */\n  status = luaL_loadfile(L, fname);\n  if (status == LUA_OK) {\n    int n = pushargs(L);  /* push arguments to script */\n    status = docall(L, n, LUA_MULTRET);\n  }\n  return report(L, status);\n}\n\n\n/* bits of various argument indicators in 'args' */\n#define has_error\t1\t/* bad option */\n#define has_i\t\t2\t/* -i */\n#define has_v\t\t4\t/* -v */\n#define has_e\t\t8\t/* -e */\n#define has_E\t\t16\t/* -E */\n\n\n/*\n** Traverses all arguments from 'argv', returning a mask with those\n** needed before running any Lua code or an error code if it finds any\n** invalid argument. In case of error, 'first' is the index of the bad\n** argument.  Otherwise, 'first' is -1 if there is no program name,\n** 0 if there is no script name, or the index of the script name.\n*/\nstatic int collectargs (char **argv, int *first) {\n  int args = 0;\n  int i;\n  if (argv[0] != NULL) {  /* is there a program name? */\n    if (argv[0][0])  /* not empty? */\n      progname = argv[0];  /* save it */\n  }\n  else {  /* no program name */\n    *first = -1;\n    return 0;\n  }\n  for (i = 1; argv[i] != NULL; i++) {  /* handle arguments */\n    *first = i;\n    if (argv[i][0] != '-')  /* not an option? */\n        return args;  /* stop handling options */\n    switch (argv[i][1]) {  /* else check option */\n      case '-':  /* '--' */\n        if (argv[i][2] != '\\0')  /* extra characters after '--'? */\n          return has_error;  /* invalid option */\n        *first = i + 1;\n        return args;\n      case '\\0':  /* '-' */\n        return args;  /* script \"name\" is '-' */\n      case 'E':\n        if (argv[i][2] != '\\0')  /* extra characters? */\n          return has_error;  /* invalid option */\n        args |= has_E;\n        break;\n      case 'W':\n        if (argv[i][2] != '\\0')  /* extra characters? */\n          return has_error;  /* invalid option */\n        break;\n      case 'i':\n        args |= has_i;  /* (-i implies -v) *//* FALLTHROUGH */\n      case 'v':\n        if (argv[i][2] != '\\0')  /* extra characters? */\n          return has_error;  /* invalid option */\n        args |= has_v;\n        break;\n      case 'e':\n        args |= has_e;  /* FALLTHROUGH */\n      case 'l':  /* both options need an argument */\n        if (argv[i][2] == '\\0') {  /* no concatenated argument? */\n          i++;  /* try next 'argv' */\n          if (argv[i] == NULL || argv[i][0] == '-')\n            return has_error;  /* no next argument or it is another option */\n        }\n        break;\n      default:  /* invalid option */\n        return has_error;\n    }\n  }\n  *first = 0;  /* no script name */\n  return args;\n}\n\n\n/*\n** Processes options 'e' and 'l', which involve running Lua code, and\n** 'W', which also affects the state.\n** Returns 0 if some code raises an error.\n*/\nstatic int runargs (lua_State *L, char **argv, int n) {\n  int i;\n  for (i = 1; i < n; i++) {\n    int option = argv[i][1];\n    lua_assert(argv[i][0] == '-');  /* already checked */\n    switch (option) {\n      case 'e':  case 'l': {\n        int status;\n        char *extra = argv[i] + 2;  /* both options need an argument */\n        if (*extra == '\\0') extra = argv[++i];\n        lua_assert(extra != NULL);\n        status = (option == 'e')\n                 ? dostring(L, extra, \"=(command line)\")\n                 : dolibrary(L, extra);\n        if (status != LUA_OK) return 0;\n        break;\n      }\n      case 'W':\n        lua_warning(L, \"@on\", 0);  /* warnings on */\n        break;\n    }\n  }\n  return 1;\n}\n\n\nstatic int handle_luainit (lua_State *L) {\n  const char *name = \"=\" LUA_INITVARVERSION;\n  const char *init = getenv(name + 1);\n  if (init == NULL) {\n    name = \"=\" LUA_INIT_VAR;\n    init = getenv(name + 1);  /* try alternative name */\n  }\n  if (init == NULL) return LUA_OK;\n  else if (init[0] == '@')\n    return dofile(L, init+1);\n  else\n    return dostring(L, init, name);\n}\n\n\n/*\n** {==================================================================\n** Read-Eval-Print Loop (REPL)\n** ===================================================================\n*/\n\n#if !defined(LUA_PROMPT)\n#define LUA_PROMPT\t\t\"> \"\n#define LUA_PROMPT2\t\t\">> \"\n#endif\n\n#if !defined(LUA_MAXINPUT)\n#define LUA_MAXINPUT\t\t512\n#endif\n\n\n/*\n** lua_stdin_is_tty detects whether the standard input is a 'tty' (that\n** is, whether we're running lua interactively).\n*/\n#if !defined(lua_stdin_is_tty)\t/* { */\n\n#if defined(LUA_USE_POSIX)\t/* { */\n\n#include <unistd.h>\n#define lua_stdin_is_tty()\tisatty(0)\n\n#elif defined(LUA_USE_WINDOWS)\t/* }{ */\n\n#include <io.h>\n#include <windows.h>\n\n#define lua_stdin_is_tty()\t_isatty(_fileno(stdin))\n\n#else\t\t\t\t/* }{ */\n\n/* ISO C definition */\n#define lua_stdin_is_tty()\t1  /* assume stdin is a tty */\n\n#endif\t\t\t\t/* } */\n\n#endif\t\t\t\t/* } */\n\n\n/*\n** lua_readline defines how to show a prompt and then read a line from\n** the standard input.\n** lua_saveline defines how to \"save\" a read line in a \"history\".\n** lua_freeline defines how to free a line read by lua_readline.\n*/\n#if !defined(lua_readline)\t/* { */\n\n#if defined(LUA_USE_READLINE)\t/* { */\n\n#include <readline/readline.h>\n#include <readline/history.h>\n#define lua_initreadline(L)\t((void)L, rl_readline_name=\"lua\")\n#define lua_readline(L,b,p)\t((void)L, ((b)=readline(p)) != NULL)\n#define lua_saveline(L,line)\t((void)L, add_history(line))\n#define lua_freeline(L,b)\t((void)L, free(b))\n\n#else\t\t\t\t/* }{ */\n\n#define lua_initreadline(L)  ((void)L)\n#define lua_readline(L,b,p) \\\n        ((void)L, fputs(p, stdout), fflush(stdout),  /* show prompt */ \\\n        fgets(b, LUA_MAXINPUT, stdin) != NULL)  /* get line */\n#define lua_saveline(L,line)\t{ (void)L; (void)line; }\n#define lua_freeline(L,b)\t{ (void)L; (void)b; }\n\n#endif\t\t\t\t/* } */\n\n#endif\t\t\t\t/* } */\n\n\n/*\n** Return the string to be used as a prompt by the interpreter. Leave\n** the string (or nil, if using the default value) on the stack, to keep\n** it anchored.\n*/\nstatic const char *get_prompt (lua_State *L, int firstline) {\n  if (lua_getglobal(L, firstline ? \"_PROMPT\" : \"_PROMPT2\") == LUA_TNIL)\n    return (firstline ? LUA_PROMPT : LUA_PROMPT2);  /* use the default */\n  else {  /* apply 'tostring' over the value */\n    const char *p = luaL_tolstring(L, -1, NULL);\n    lua_remove(L, -2);  /* remove original value */\n    return p;\n  }\n}\n\n/* mark in error messages for incomplete statements */\n#define EOFMARK\t\t\"<eof>\"\n#define marklen\t\t(sizeof(EOFMARK)/sizeof(char) - 1)\n\n\n/*\n** Check whether 'status' signals a syntax error and the error\n** message at the top of the stack ends with the above mark for\n** incomplete statements.\n*/\nstatic int incomplete (lua_State *L, int status) {\n  if (status == LUA_ERRSYNTAX) {\n    size_t lmsg;\n    const char *msg = lua_tolstring(L, -1, &lmsg);\n    if (lmsg >= marklen && strcmp(msg + lmsg - marklen, EOFMARK) == 0) {\n      lua_pop(L, 1);\n      return 1;\n    }\n  }\n  return 0;  /* else... */\n}\n\n\n/*\n** Prompt the user, read a line, and push it into the Lua stack.\n*/\nstatic int pushline (lua_State *L, int firstline) {\n  char buffer[LUA_MAXINPUT];\n  char *b = buffer;\n  size_t l;\n  const char *prmt = get_prompt(L, firstline);\n  int readstatus = lua_readline(L, b, prmt);\n  if (readstatus == 0)\n    return 0;  /* no input (prompt will be popped by caller) */\n  lua_pop(L, 1);  /* remove prompt */\n  l = strlen(b);\n  if (l > 0 && b[l-1] == '\\n')  /* line ends with newline? */\n    b[--l] = '\\0';  /* remove it */\n  if (firstline && b[0] == '=')  /* for compatibility with 5.2, ... */\n    lua_pushfstring(L, \"return %s\", b + 1);  /* change '=' to 'return' */\n  else\n    lua_pushlstring(L, b, l);\n  lua_freeline(L, b);\n  return 1;\n}\n\n\n/*\n** Try to compile line on the stack as 'return <line>;'; on return, stack\n** has either compiled chunk or original line (if compilation failed).\n*/\nstatic int addreturn (lua_State *L) {\n  const char *line = lua_tostring(L, -1);  /* original line */\n  const char *retline = lua_pushfstring(L, \"return %s;\", line);\n  int status = luaL_loadbuffer(L, retline, strlen(retline), \"=stdin\");\n  if (status == LUA_OK) {\n    lua_remove(L, -2);  /* remove modified line */\n    if (line[0] != '\\0')  /* non empty? */\n      lua_saveline(L, line);  /* keep history */\n  }\n  else\n    lua_pop(L, 2);  /* pop result from 'luaL_loadbuffer' and modified line */\n  return status;\n}\n\n\n/*\n** Read multiple lines until a complete Lua statement\n*/\nstatic int multiline (lua_State *L) {\n  for (;;) {  /* repeat until gets a complete statement */\n    size_t len;\n    const char *line = lua_tolstring(L, 1, &len);  /* get what it has */\n    int status = luaL_loadbuffer(L, line, len, \"=stdin\");  /* try it */\n    if (!incomplete(L, status) || !pushline(L, 0)) {\n      lua_saveline(L, line);  /* keep history */\n      return status;  /* cannot or should not try to add continuation line */\n    }\n    lua_pushliteral(L, \"\\n\");  /* add newline... */\n    lua_insert(L, -2);  /* ...between the two lines */\n    lua_concat(L, 3);  /* join them */\n  }\n}\n\n\n/*\n** Read a line and try to load (compile) it first as an expression (by\n** adding \"return \" in front of it) and second as a statement. Return\n** the final status of load/call with the resulting function (if any)\n** in the top of the stack.\n*/\nstatic int loadline (lua_State *L) {\n  int status;\n  lua_settop(L, 0);\n  if (!pushline(L, 1))\n    return -1;  /* no input */\n  if ((status = addreturn(L)) != LUA_OK)  /* 'return ...' did not work? */\n    status = multiline(L);  /* try as command, maybe with continuation lines */\n  lua_remove(L, 1);  /* remove line from the stack */\n  lua_assert(lua_gettop(L) == 1);\n  return status;\n}\n\n\n/*\n** Prints (calling the Lua 'print' function) any values on the stack\n*/\nstatic void l_print (lua_State *L) {\n  int n = lua_gettop(L);\n  if (n > 0) {  /* any result to be printed? */\n    luaL_checkstack(L, LUA_MINSTACK, \"too many results to print\");\n    lua_getglobal(L, \"print\");\n    lua_insert(L, 1);\n    if (lua_pcall(L, n, 0, 0) != LUA_OK)\n      l_message(progname, lua_pushfstring(L, \"error calling 'print' (%s)\",\n                                             lua_tostring(L, -1)));\n  }\n}\n\n\n/*\n** Do the REPL: repeatedly read (load) a line, evaluate (call) it, and\n** print any results.\n*/\nstatic void doREPL (lua_State *L) {\n  int status;\n  const char *oldprogname = progname;\n  progname = NULL;  /* no 'progname' on errors in interactive mode */\n  lua_initreadline(L);\n  while ((status = loadline(L)) != -1) {\n    if (status == LUA_OK)\n      status = docall(L, 0, LUA_MULTRET);\n    if (status == LUA_OK) l_print(L);\n    else report(L, status);\n  }\n  lua_settop(L, 0);  /* clear stack */\n  lua_writeline();\n  progname = oldprogname;\n}\n\n/* }================================================================== */\n\n\n/*\n** Main body of stand-alone interpreter (to be called in protected mode).\n** Reads the options and handles them all.\n*/\nstatic int pmain (lua_State *L) {\n  int argc = (int)lua_tointeger(L, 1);\n  char **argv = (char **)lua_touserdata(L, 2);\n  int script;\n  int args = collectargs(argv, &script);\n  int optlim = (script > 0) ? script : argc; /* first argv not an option */\n  luaL_checkversion(L);  /* check that interpreter has correct version */\n  if (args == has_error) {  /* bad arg? */\n    print_usage(argv[script]);  /* 'script' has index of bad arg. */\n    return 0;\n  }\n  if (args & has_v)  /* option '-v'? */\n    print_version();\n  if (args & has_E) {  /* option '-E'? */\n    lua_pushboolean(L, 1);  /* signal for libraries to ignore env. vars. */\n    lua_setfield(L, LUA_REGISTRYINDEX, \"LUA_NOENV\");\n  }\n  luaL_openlibs(L);  /* open standard libraries */\n  createargtable(L, argv, argc, script);  /* create table 'arg' */\n  lua_gc(L, LUA_GCRESTART);  /* start GC... */\n  lua_gc(L, LUA_GCGEN, 0, 0);  /* ...in generational mode */\n  if (!(args & has_E)) {  /* no option '-E'? */\n    if (handle_luainit(L) != LUA_OK)  /* run LUA_INIT */\n      return 0;  /* error running LUA_INIT */\n  }\n  if (!runargs(L, argv, optlim))  /* execute arguments -e and -l */\n    return 0;  /* something failed */\n  if (script > 0) {  /* execute main script (if there is one) */\n    if (handle_script(L, argv + script) != LUA_OK)\n      return 0;  /* interrupt in case of error */\n  }\n  if (args & has_i)  /* -i option? */\n    doREPL(L);  /* do read-eval-print loop */\n  else if (script < 1 && !(args & (has_e | has_v))) { /* no active option? */\n    if (lua_stdin_is_tty()) {  /* running in interactive mode? */\n      print_version();\n      doREPL(L);  /* do read-eval-print loop */\n    }\n    else dofile(L, NULL);  /* executes stdin as a file */\n  }\n  lua_pushboolean(L, 1);  /* signal no errors */\n  return 1;\n}\n\n\nint main (int argc, char **argv) {\n  int status, result;\n  lua_State *L = luaL_newstate();  /* create state */\n  if (L == NULL) {\n    l_message(argv[0], \"cannot create state: not enough memory\");\n    return EXIT_FAILURE;\n  }\n  lua_gc(L, LUA_GCSTOP);  /* stop GC while building state */\n  lua_pushcfunction(L, &pmain);  /* to call 'pmain' in protected mode */\n  lua_pushinteger(L, argc);  /* 1st argument */\n  lua_pushlightuserdata(L, argv); /* 2nd argument */\n  status = lua_pcall(L, 2, 1, 0);  /* do the call */\n  result = lua_toboolean(L, -1);  /* get result */\n  report(L, status);\n  lua_close(L);\n  return (result && status == LUA_OK) ? EXIT_SUCCESS : EXIT_FAILURE;\n}\n\n#endif /* LUA_MAKE_LUA */\n\n/*\n  MIT License\n\n  Copyright (c) 1994–2019 Lua.org, PUC-Rio.\n  Copyright (c) 2020-2023 Eduardo Bart (https://github.com/edubart).\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 all\n  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 THE\n  SOFTWARE.\n*/\n"
  },
  {
    "path": "vendor/minilua/minilua_impl.c",
    "content": "#define LUA_IMPL\n#include \"minilua.h\""
  },
  {
    "path": "vendor/mio/mio.hpp",
    "content": "/* Copyright 2017 https://github.com/mandreyel\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so, subject to the following\n * conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies\n * or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF\n * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE\n * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n\n#ifndef MIO_MMAP_HEADER\n#define MIO_MMAP_HEADER\n\n// #include \"mio/page.hpp\"\n/* Copyright 2017 https://github.com/mandreyel\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so, subject to the following\n * conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies\n * or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF\n * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE\n * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n\n#ifndef MIO_PAGE_HEADER\n#define MIO_PAGE_HEADER\n\n#ifdef _WIN32\n#ifndef NOMINMAX\n# define NOMINMAX\n#endif // NOMINMAX\n#ifndef WIN32_LEAN_AND_MEAN\n# define WIN32_LEAN_AND_MEAN\n#endif // WIN32_LEAN_AND_MEAN\n# include <windows.h>\n#else\n# include <unistd.h>\n#endif\n\nnamespace mio {\n\n/**\n * This is used by `basic_mmap` to determine whether to create a read-only or\n * a read-write memory mapping.\n */\nenum class access_mode\n{\n    read,\n    write\n};\n\n/**\n * Determines the operating system's page allocation granularity.\n *\n * On the first call to this function, it invokes the operating system specific syscall\n * to determine the page size, caches the value, and returns it. Any subsequent call to\n * this function serves the cached value, so no further syscalls are made.\n */\ninline size_t page_size()\n{\n    static const size_t page_size = []\n    {\n#ifdef _WIN32\n        SYSTEM_INFO SystemInfo;\n        GetSystemInfo(&SystemInfo);\n        return SystemInfo.dwAllocationGranularity;\n#else\n        return sysconf(_SC_PAGE_SIZE);\n#endif\n    }();\n    return page_size;\n}\n\n/**\n * Alligns `offset` to the operating's system page size such that it subtracts the\n * difference until the nearest page boundary before `offset`, or does nothing if\n * `offset` is already page aligned.\n */\ninline size_t make_offset_page_aligned(size_t offset) noexcept\n{\n    const size_t page_size_ = page_size();\n    // Use integer division to round down to the nearest page alignment.\n    return offset / page_size_ * page_size_;\n}\n\n} // namespace mio\n\n#endif // MIO_PAGE_HEADER\n\n\n#include <iterator>\n#include <string>\n#include <system_error>\n#include <cstdint>\n\n#ifdef _WIN32\n#ifndef NOMINMAX\n# define NOMINMAX\n#endif // NOMINMAX\n#ifndef WIN32_LEAN_AND_MEAN\n# define WIN32_LEAN_AND_MEAN\n#endif // WIN32_LEAN_AND_MEAN\n# include <windows.h>\n#else // ifdef _WIN32\n# define INVALID_HANDLE_VALUE -1\n#endif // ifdef _WIN32\n\nnamespace mio {\n\n// This value may be provided as the `length` parameter to the constructor or\n// `map`, in which case a memory mapping of the entire file is created.\nenum { map_entire_file = 0 };\n\n#ifdef _WIN32\nusing file_handle_type = HANDLE;\n#else\nusing file_handle_type = int;\n#endif\n\n// This value represents an invalid file handle type. This can be used to\n// determine whether `basic_mmap::file_handle` is valid, for example.\nconst static file_handle_type invalid_handle = INVALID_HANDLE_VALUE;\n\ntemplate<access_mode AccessMode, typename ByteT>\nstruct basic_mmap\n{\n    using value_type = ByteT;\n    using size_type = size_t;\n    using reference = value_type&;\n    using const_reference = const value_type&;\n    using pointer = value_type*;\n    using const_pointer = const value_type*;\n    using difference_type = std::ptrdiff_t;\n    using iterator = pointer;\n    using const_iterator = const_pointer;\n    using reverse_iterator = std::reverse_iterator<iterator>;\n    using const_reverse_iterator = std::reverse_iterator<const_iterator>;\n    using iterator_category = std::random_access_iterator_tag;\n    using handle_type = file_handle_type;\n\n    static_assert(sizeof(ByteT) == sizeof(char), \"ByteT must be the same size as char.\");\n\nprivate:\n    // Points to the first requested byte, and not to the actual start of the mapping.\n    pointer data_ = nullptr;\n\n    // Length--in bytes--requested by user (which may not be the length of the\n    // full mapping) and the length of the full mapping.\n    size_type length_ = 0;\n    size_type mapped_length_ = 0;\n\n    // Letting user map a file using both an existing file handle and a path\n    // introcudes some complexity (see `is_handle_internal_`).\n    // On POSIX, we only need a file handle to create a mapping, while on\n    // Windows systems the file handle is necessary to retrieve a file mapping\n    // handle, but any subsequent operations on the mapped region must be done\n    // through the latter.\n    handle_type file_handle_ = INVALID_HANDLE_VALUE;\n#ifdef _WIN32\n    handle_type file_mapping_handle_ = INVALID_HANDLE_VALUE;\n#endif\n\n    // Letting user map a file using both an existing file handle and a path\n    // introcudes some complexity in that we must not close the file handle if\n    // user provided it, but we must close it if we obtained it using the\n    // provided path. For this reason, this flag is used to determine when to\n    // close `file_handle_`.\n    bool is_handle_internal_;\n\npublic:\n    /**\n     * The default constructed mmap object is in a non-mapped state, that is,\n     * any operation that attempts to access nonexistent underlying data will\n     * result in undefined behaviour/segmentation faults.\n     */\n    basic_mmap() = default;\n\n#ifdef __cpp_exceptions\n    /**\n     * The same as invoking the `map` function, except any error that may occur\n     * while establishing the mapping is wrapped in a `std::system_error` and is\n     * thrown.\n     */\n    template<typename String>\n    basic_mmap(const String& path, const size_type offset = 0, const size_type length = map_entire_file)\n    {\n        std::error_code error;\n        map(path, offset, length, error);\n        if(error) { throw std::system_error(error); }\n    }\n\n    /**\n     * The same as invoking the `map` function, except any error that may occur\n     * while establishing the mapping is wrapped in a `std::system_error` and is\n     * thrown.\n     */\n    basic_mmap(const handle_type handle, const size_type offset = 0, const size_type length = map_entire_file)\n    {\n        std::error_code error;\n        map(handle, offset, length, error);\n        if(error) { throw std::system_error(error); }\n    }\n#endif // __cpp_exceptions\n\n    /**\n     * `basic_mmap` has single-ownership semantics, so transferring ownership\n     * may only be accomplished by moving the object.\n     */\n    basic_mmap(const basic_mmap&) = delete;\n    basic_mmap(basic_mmap&&);\n    basic_mmap& operator=(const basic_mmap&) = delete;\n    basic_mmap& operator=(basic_mmap&&);\n\n    /**\n     * If this is a read-write mapping, the destructor invokes sync. Regardless\n     * of the access mode, unmap is invoked as a final step.\n     */\n    ~basic_mmap();\n\n    /**\n     * On UNIX systems 'file_handle' and 'mapping_handle' are the same. On Windows,\n     * however, a mapped region of a file gets its own handle, which is returned by\n     * 'mapping_handle'.\n     */\n    handle_type file_handle() const noexcept { return file_handle_; }\n    handle_type mapping_handle() const noexcept;\n\n    /** Returns whether a valid memory mapping has been created. */\n    bool is_open() const noexcept { return file_handle_ != invalid_handle; }\n\n    /**\n     * Returns true if no mapping was established, that is, conceptually the\n     * same as though the length that was mapped was 0. This function is\n     * provided so that this class has Container semantics.\n     */\n    bool empty() const noexcept { return length() == 0; }\n\n    /** Returns true if a mapping was established. */\n    bool is_mapped() const noexcept;\n\n    /**\n     * `size` and `length` both return the logical length, i.e. the number of bytes\n     * user requested to be mapped, while `mapped_length` returns the actual number of\n     * bytes that were mapped which is a multiple of the underlying operating system's\n     * page allocation granularity.\n     */\n    size_type size() const noexcept { return length(); }\n    size_type length() const noexcept { return length_; }\n    size_type mapped_length() const noexcept { return mapped_length_; }\n\n    /** Returns the offset relative to the start of the mapping. */\n    size_type mapping_offset() const noexcept\n    {\n        return mapped_length_ - length_;\n    }\n\n    /**\n     * Returns a pointer to the first requested byte, or `nullptr` if no memory mapping\n     * exists.\n     */\n    template<\n        access_mode A = AccessMode,\n        typename = typename std::enable_if<A == access_mode::write>::type\n    > pointer data() noexcept { return data_; }\n    const_pointer data() const noexcept { return data_; }\n\n    /**\n     * Returns an iterator to the first requested byte, if a valid memory mapping\n     * exists, otherwise this function call is undefined behaviour.\n     */\n    template<\n        access_mode A = AccessMode,\n        typename = typename std::enable_if<A == access_mode::write>::type\n    > iterator begin() noexcept { return data(); }\n    const_iterator begin() const noexcept { return data(); }\n    const_iterator cbegin() const noexcept { return data(); }\n\n    /**\n     * Returns an iterator one past the last requested byte, if a valid memory mapping\n     * exists, otherwise this function call is undefined behaviour.\n     */\n    template<\n        access_mode A = AccessMode,\n        typename = typename std::enable_if<A == access_mode::write>::type\n    > iterator end() noexcept { return data() + length(); }\n    const_iterator end() const noexcept { return data() + length(); }\n    const_iterator cend() const noexcept { return data() + length(); }\n\n    /**\n     * Returns a reverse iterator to the last memory mapped byte, if a valid\n     * memory mapping exists, otherwise this function call is undefined\n     * behaviour.\n     */\n    template<\n        access_mode A = AccessMode,\n        typename = typename std::enable_if<A == access_mode::write>::type\n    > reverse_iterator rbegin() noexcept { return reverse_iterator(end()); }\n    const_reverse_iterator rbegin() const noexcept\n    { return const_reverse_iterator(end()); }\n    const_reverse_iterator crbegin() const noexcept\n    { return const_reverse_iterator(end()); }\n\n    /**\n     * Returns a reverse iterator past the first mapped byte, if a valid memory\n     * mapping exists, otherwise this function call is undefined behaviour.\n     */\n    template<\n        access_mode A = AccessMode,\n        typename = typename std::enable_if<A == access_mode::write>::type\n    > reverse_iterator rend() noexcept { return reverse_iterator(begin()); }\n    const_reverse_iterator rend() const noexcept\n    { return const_reverse_iterator(begin()); }\n    const_reverse_iterator crend() const noexcept\n    { return const_reverse_iterator(begin()); }\n\n    /**\n     * Returns a reference to the `i`th byte from the first requested byte (as returned\n     * by `data`). If this is invoked when no valid memory mapping has been created\n     * prior to this call, undefined behaviour ensues.\n     */\n    reference operator[](const size_type i) noexcept { return data_[i]; }\n    const_reference operator[](const size_type i) const noexcept { return data_[i]; }\n\n    /**\n     * Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the\n     * reason is reported via `error` and the object remains in a state as if this\n     * function hadn't been called.\n     *\n     * `path`, which must be a path to an existing file, is used to retrieve a file\n     * handle (which is closed when the object destructs or `unmap` is called), which is\n     * then used to memory map the requested region. Upon failure, `error` is set to\n     * indicate the reason and the object remains in an unmapped state.\n     *\n     * `offset` is the number of bytes, relative to the start of the file, where the\n     * mapping should begin. When specifying it, there is no need to worry about\n     * providing a value that is aligned with the operating system's page allocation\n     * granularity. This is adjusted by the implementation such that the first requested\n     * byte (as returned by `data` or `begin`), so long as `offset` is valid, will be at\n     * `offset` from the start of the file.\n     *\n     * `length` is the number of bytes to map. It may be `map_entire_file`, in which\n     * case a mapping of the entire file is created.\n     */\n    template<typename String>\n    void map(const String& path, const size_type offset,\n            const size_type length, std::error_code& error);\n\n    /**\n     * Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the\n     * reason is reported via `error` and the object remains in a state as if this\n     * function hadn't been called.\n     *\n     * `path`, which must be a path to an existing file, is used to retrieve a file\n     * handle (which is closed when the object destructs or `unmap` is called), which is\n     * then used to memory map the requested region. Upon failure, `error` is set to\n     * indicate the reason and the object remains in an unmapped state.\n     * \n     * The entire file is mapped.\n     */\n    template<typename String>\n    void map(const String& path, std::error_code& error)\n    {\n        map(path, 0, map_entire_file, error);\n    }\n\n    /**\n     * Establishes a memory mapping with AccessMode. If the mapping is\n     * unsuccesful, the reason is reported via `error` and the object remains in\n     * a state as if this function hadn't been called.\n     *\n     * `handle`, which must be a valid file handle, which is used to memory map the\n     * requested region. Upon failure, `error` is set to indicate the reason and the\n     * object remains in an unmapped state.\n     *\n     * `offset` is the number of bytes, relative to the start of the file, where the\n     * mapping should begin. When specifying it, there is no need to worry about\n     * providing a value that is aligned with the operating system's page allocation\n     * granularity. This is adjusted by the implementation such that the first requested\n     * byte (as returned by `data` or `begin`), so long as `offset` is valid, will be at\n     * `offset` from the start of the file.\n     *\n     * `length` is the number of bytes to map. It may be `map_entire_file`, in which\n     * case a mapping of the entire file is created.\n     */\n    void map(const handle_type handle, const size_type offset,\n            const size_type length, std::error_code& error);\n\n    /**\n     * Establishes a memory mapping with AccessMode. If the mapping is\n     * unsuccesful, the reason is reported via `error` and the object remains in\n     * a state as if this function hadn't been called.\n     *\n     * `handle`, which must be a valid file handle, which is used to memory map the\n     * requested region. Upon failure, `error` is set to indicate the reason and the\n     * object remains in an unmapped state.\n     * \n     * The entire file is mapped.\n     */\n    void map(const handle_type handle, std::error_code& error)\n    {\n        map(handle, 0, map_entire_file, error);\n    }\n\n    /**\n     * If a valid memory mapping has been created prior to this call, this call\n     * instructs the kernel to unmap the memory region and disassociate this object\n     * from the file.\n     *\n     * The file handle associated with the file that is mapped is only closed if the\n     * mapping was created using a file path. If, on the other hand, an existing\n     * file handle was used to create the mapping, the file handle is not closed.\n     */\n    void unmap();\n\n    void swap(basic_mmap& other);\n\n    /** Flushes the memory mapped page to disk. Errors are reported via `error`. */\n    template<access_mode A = AccessMode>\n    typename std::enable_if<A == access_mode::write, void>::type\n    sync(std::error_code& error);\n\n    /**\n     * All operators compare the address of the first byte and size of the two mapped\n     * regions.\n     */\n\nprivate:\n    template<\n        access_mode A = AccessMode,\n        typename = typename std::enable_if<A == access_mode::write>::type\n    > pointer get_mapping_start() noexcept\n    {\n        return !data() ? nullptr : data() - mapping_offset();\n    }\n\n    const_pointer get_mapping_start() const noexcept\n    {\n        return !data() ? nullptr : data() - mapping_offset();\n    }\n\n    /**\n     * The destructor syncs changes to disk if `AccessMode` is `write`, but not\n     * if it's `read`, but since the destructor cannot be templated, we need to\n     * do SFINAE in a dedicated function, where one syncs and the other is a noop.\n     */\n    template<access_mode A = AccessMode>\n    typename std::enable_if<A == access_mode::write, void>::type\n    conditional_sync();\n    template<access_mode A = AccessMode>\n    typename std::enable_if<A == access_mode::read, void>::type conditional_sync();\n};\n\ntemplate<access_mode AccessMode, typename ByteT>\nbool operator==(const basic_mmap<AccessMode, ByteT>& a,\n        const basic_mmap<AccessMode, ByteT>& b);\n\ntemplate<access_mode AccessMode, typename ByteT>\nbool operator!=(const basic_mmap<AccessMode, ByteT>& a,\n        const basic_mmap<AccessMode, ByteT>& b);\n\ntemplate<access_mode AccessMode, typename ByteT>\nbool operator<(const basic_mmap<AccessMode, ByteT>& a,\n        const basic_mmap<AccessMode, ByteT>& b);\n\ntemplate<access_mode AccessMode, typename ByteT>\nbool operator<=(const basic_mmap<AccessMode, ByteT>& a,\n        const basic_mmap<AccessMode, ByteT>& b);\n\ntemplate<access_mode AccessMode, typename ByteT>\nbool operator>(const basic_mmap<AccessMode, ByteT>& a,\n        const basic_mmap<AccessMode, ByteT>& b);\n\ntemplate<access_mode AccessMode, typename ByteT>\nbool operator>=(const basic_mmap<AccessMode, ByteT>& a,\n        const basic_mmap<AccessMode, ByteT>& b);\n\n/**\n * This is the basis for all read-only mmap objects and should be preferred over\n * directly using `basic_mmap`.\n */\ntemplate<typename ByteT>\nusing basic_mmap_source = basic_mmap<access_mode::read, ByteT>;\n\n/**\n * This is the basis for all read-write mmap objects and should be preferred over\n * directly using `basic_mmap`.\n */\ntemplate<typename ByteT>\nusing basic_mmap_sink = basic_mmap<access_mode::write, ByteT>;\n\n/**\n * These aliases cover the most common use cases, both representing a raw byte stream\n * (either with a char or an unsigned char/uint8_t).\n */\nusing mmap_source = basic_mmap_source<char>;\nusing ummap_source = basic_mmap_source<unsigned char>;\n\nusing mmap_sink = basic_mmap_sink<char>;\nusing ummap_sink = basic_mmap_sink<unsigned char>;\n\n/**\n * Convenience factory method that constructs a mapping for any `basic_mmap` or\n * `basic_mmap` type.\n */\ntemplate<\n    typename MMap,\n    typename MappingToken\n> MMap make_mmap(const MappingToken& token,\n        int64_t offset, int64_t length, std::error_code& error)\n{\n    MMap mmap;\n    mmap.map(token, offset, length, error);\n    return mmap;\n}\n\n/**\n * Convenience factory method.\n *\n * MappingToken may be a String (`std::string`, `std::string_view`, `const char*`,\n * `std::filesystem::path`, `std::vector<char>`, or similar), or a\n * `mmap_source::handle_type`.\n */\ntemplate<typename MappingToken>\nmmap_source make_mmap_source(const MappingToken& token, mmap_source::size_type offset,\n        mmap_source::size_type length, std::error_code& error)\n{\n    return make_mmap<mmap_source>(token, offset, length, error);\n}\n\ntemplate<typename MappingToken>\nmmap_source make_mmap_source(const MappingToken& token, std::error_code& error)\n{\n    return make_mmap_source(token, 0, map_entire_file, error);\n}\n\n/**\n * Convenience factory method.\n *\n * MappingToken may be a String (`std::string`, `std::string_view`, `const char*`,\n * `std::filesystem::path`, `std::vector<char>`, or similar), or a\n * `mmap_sink::handle_type`.\n */\ntemplate<typename MappingToken>\nmmap_sink make_mmap_sink(const MappingToken& token, mmap_sink::size_type offset,\n        mmap_sink::size_type length, std::error_code& error)\n{\n    return make_mmap<mmap_sink>(token, offset, length, error);\n}\n\ntemplate<typename MappingToken>\nmmap_sink make_mmap_sink(const MappingToken& token, std::error_code& error)\n{\n    return make_mmap_sink(token, 0, map_entire_file, error);\n}\n\n} // namespace mio\n\n// #include \"detail/mmap.ipp\"\n/* Copyright 2017 https://github.com/mandreyel\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so, subject to the following\n * conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies\n * or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF\n * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE\n * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n\n#ifndef MIO_BASIC_MMAP_IMPL\n#define MIO_BASIC_MMAP_IMPL\n\n// #include \"mio/mmap.hpp\"\n\n// #include \"mio/page.hpp\"\n\n// #include \"mio/detail/string_util.hpp\"\n/* Copyright 2017 https://github.com/mandreyel\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so, subject to the following\n * conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies\n * or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF\n * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE\n * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n\n#ifndef MIO_STRING_UTIL_HEADER\n#define MIO_STRING_UTIL_HEADER\n\n#include <type_traits>\n\nnamespace mio {\nnamespace detail {\n\ntemplate<\n    typename S,\n    typename C = typename std::decay<S>::type,\n    typename = decltype(std::declval<C>().data()),\n    typename = typename std::enable_if<\n        std::is_same<typename C::value_type, char>::value\n#ifdef _WIN32\n        || std::is_same<typename C::value_type, wchar_t>::value\n#endif\n    >::type\n> struct char_type_helper {\n    using type = typename C::value_type;\n};\n\ntemplate<class T>\nstruct char_type {\n    using type = typename char_type_helper<T>::type;\n};\n\n// TODO: can we avoid this brute force approach?\ntemplate<>\nstruct char_type<char*> {\n    using type = char;\n};\n\ntemplate<>\nstruct char_type<const char*> {\n    using type = char;\n};\n\ntemplate<size_t N>\nstruct char_type<char[N]> {\n    using type = char;\n};\n\ntemplate<size_t N>\nstruct char_type<const char[N]> {\n    using type = char;\n};\n\n#ifdef _WIN32\ntemplate<>\nstruct char_type<wchar_t*> {\n    using type = wchar_t;\n};\n\ntemplate<>\nstruct char_type<const wchar_t*> {\n    using type = wchar_t;\n};\n\ntemplate<size_t N>\nstruct char_type<wchar_t[N]> {\n    using type = wchar_t;\n};\n\ntemplate<size_t N>\nstruct char_type<const wchar_t[N]> {\n    using type = wchar_t;\n};\n#endif // _WIN32\n\ntemplate<typename CharT, typename S>\nstruct is_c_str_helper\n{\n    static constexpr bool value = std::is_same<\n        CharT*,\n        // TODO: I'm so sorry for this... Can this be made cleaner?\n        typename std::add_pointer<\n            typename std::remove_cv<\n                typename std::remove_pointer<\n                    typename std::decay<\n                        S\n                    >::type\n                >::type\n            >::type\n        >::type\n    >::value;\n};\n\ntemplate<typename S>\nstruct is_c_str\n{\n    static constexpr bool value = is_c_str_helper<char, S>::value;\n};\n\n#ifdef _WIN32\ntemplate<typename S>\nstruct is_c_wstr\n{\n    static constexpr bool value = is_c_str_helper<wchar_t, S>::value;\n};\n#endif // _WIN32\n\ntemplate<typename S>\nstruct is_c_str_or_c_wstr\n{\n    static constexpr bool value = is_c_str<S>::value\n#ifdef _WIN32\n        || is_c_wstr<S>::value\n#endif\n        ;\n};\n\ntemplate<\n    typename String,\n    typename = decltype(std::declval<String>().data()),\n    typename = typename std::enable_if<!is_c_str_or_c_wstr<String>::value>::type\n> const typename char_type<String>::type* c_str(const String& path)\n{\n    return path.data();\n}\n\ntemplate<\n    typename String,\n    typename = decltype(std::declval<String>().empty()),\n    typename = typename std::enable_if<!is_c_str_or_c_wstr<String>::value>::type\n> bool empty(const String& path)\n{\n    return path.empty();\n}\n\ntemplate<\n    typename String,\n    typename = typename std::enable_if<is_c_str_or_c_wstr<String>::value>::type\n> const typename char_type<String>::type* c_str(String path)\n{\n    return path;\n}\n\ntemplate<\n    typename String,\n    typename = typename std::enable_if<is_c_str_or_c_wstr<String>::value>::type\n> bool empty(String path)\n{\n    return !path || (*path == 0);\n}\n\n} // namespace detail\n} // namespace mio\n\n#endif // MIO_STRING_UTIL_HEADER\n\n\n#include <algorithm>\n\n#ifndef _WIN32\n# include <unistd.h>\n# include <fcntl.h>\n# include <sys/mman.h>\n# include <sys/stat.h>\n#endif\n\nnamespace mio {\nnamespace detail {\n\n#ifdef _WIN32\nnamespace win {\n\n/** Returns the 4 upper bytes of an 8-byte integer. */\ninline DWORD int64_high(int64_t n) noexcept\n{\n    return n >> 32;\n}\n\n/** Returns the 4 lower bytes of an 8-byte integer. */\ninline DWORD int64_low(int64_t n) noexcept\n{\n    return n & 0xffffffff;\n}\n\ninline std::wstring s_2_ws(const std::string& s)\n{\n    std::wstring ret;\n    if (!s.empty())\n    {\n        ret.resize(s.size());\n        int wide_char_count = MultiByteToWideChar(CP_UTF8, 0, s.c_str(),\n            static_cast<int>(s.size()), &ret[0], static_cast<int>(s.size()));\n        ret.resize(wide_char_count);\n    }\n    return ret;\n}\n\ntemplate<\n    typename String,\n    typename = typename std::enable_if<\n        std::is_same<typename char_type<String>::type, char>::value\n    >::type\n> file_handle_type open_file_helper(const String& path, const access_mode mode)\n{\n    return ::CreateFileW(s_2_ws(path).c_str(),\n            mode == access_mode::read ? GENERIC_READ : GENERIC_READ | GENERIC_WRITE,\n            FILE_SHARE_READ | FILE_SHARE_WRITE,\n            0,\n            OPEN_EXISTING,\n            FILE_ATTRIBUTE_NORMAL,\n            0);\n}\n\ntemplate<typename String>\ntypename std::enable_if<\n    std::is_same<typename char_type<String>::type, wchar_t>::value,\n    file_handle_type\n>::type open_file_helper(const String& path, const access_mode mode)\n{\n    return ::CreateFileW(c_str(path),\n            mode == access_mode::read ? GENERIC_READ : GENERIC_READ | GENERIC_WRITE,\n            FILE_SHARE_READ | FILE_SHARE_WRITE,\n            0,\n            OPEN_EXISTING,\n            FILE_ATTRIBUTE_NORMAL,\n            0);\n}\n\n} // win\n#endif // _WIN32\n\n/**\n * Returns the last platform specific system error (errno on POSIX and\n * GetLastError on Win) as a `std::error_code`.\n */\ninline std::error_code last_error() noexcept\n{\n    std::error_code error;\n#ifdef _WIN32\n    error.assign(GetLastError(), std::system_category());\n#else\n    error.assign(errno, std::system_category());\n#endif\n    return error;\n}\n\ntemplate<typename String>\nfile_handle_type open_file(const String& path, const access_mode mode,\n        std::error_code& error)\n{\n    error.clear();\n    if(detail::empty(path))\n    {\n        error = std::make_error_code(std::errc::invalid_argument);\n        return invalid_handle;\n    }\n#ifdef _WIN32\n    const auto handle = win::open_file_helper(path, mode);\n#else // POSIX\n    const auto handle = ::open(c_str(path),\n            mode == access_mode::read ? O_RDONLY : O_RDWR);\n#endif\n    if(handle == invalid_handle)\n    {\n        error = detail::last_error();\n    }\n    return handle;\n}\n\ninline size_t query_file_size(file_handle_type handle, std::error_code& error)\n{\n    error.clear();\n#ifdef _WIN32\n    LARGE_INTEGER file_size;\n    if(::GetFileSizeEx(handle, &file_size) == 0)\n    {\n        error = detail::last_error();\n        return 0;\n    }\n\treturn static_cast<int64_t>(file_size.QuadPart);\n#else // POSIX\n    struct stat sbuf;\n    if(::fstat(handle, &sbuf) == -1)\n    {\n        error = detail::last_error();\n        return 0;\n    }\n    return sbuf.st_size;\n#endif\n}\n\nstruct mmap_context\n{\n    char* data;\n    int64_t length;\n    int64_t mapped_length;\n#ifdef _WIN32\n    file_handle_type file_mapping_handle;\n#endif\n};\n\ninline mmap_context memory_map(const file_handle_type file_handle, const int64_t offset,\n    const int64_t length, const access_mode mode, std::error_code& error)\n{\n    const int64_t aligned_offset = make_offset_page_aligned(offset);\n    const int64_t length_to_map = offset - aligned_offset + length;\n#ifdef _WIN32\n    const int64_t max_file_size = offset + length;\n    const auto file_mapping_handle = ::CreateFileMapping(\n            file_handle,\n            0,\n            mode == access_mode::read ? PAGE_READONLY : PAGE_READWRITE,\n            win::int64_high(max_file_size),\n            win::int64_low(max_file_size),\n            0);\n    if(file_mapping_handle == invalid_handle)\n    {\n        error = detail::last_error();\n        return {};\n    }\n    char* mapping_start = static_cast<char*>(::MapViewOfFile(\n            file_mapping_handle,\n            mode == access_mode::read ? FILE_MAP_READ : FILE_MAP_WRITE,\n            win::int64_high(aligned_offset),\n            win::int64_low(aligned_offset),\n            length_to_map));\n    if(mapping_start == nullptr)\n    {\n        // Close file handle if mapping it failed.\n        ::CloseHandle(file_mapping_handle);\n        error = detail::last_error();\n        return {};\n    }\n#else // POSIX\n    char* mapping_start = static_cast<char*>(::mmap(\n            0, // Don't give hint as to where to map.\n            length_to_map,\n            mode == access_mode::read ? PROT_READ : PROT_WRITE,\n            MAP_SHARED,\n            file_handle,\n            aligned_offset));\n    if(mapping_start == MAP_FAILED)\n    {\n        error = detail::last_error();\n        return {};\n    }\n#endif\n    mmap_context ctx;\n    ctx.data = mapping_start + offset - aligned_offset;\n    ctx.length = length;\n    ctx.mapped_length = length_to_map;\n#ifdef _WIN32\n    ctx.file_mapping_handle = file_mapping_handle;\n#endif\n    return ctx;\n}\n\n} // namespace detail\n\n// -- basic_mmap --\n\ntemplate<access_mode AccessMode, typename ByteT>\nbasic_mmap<AccessMode, ByteT>::~basic_mmap()\n{\n    conditional_sync();\n    unmap();\n}\n\ntemplate<access_mode AccessMode, typename ByteT>\nbasic_mmap<AccessMode, ByteT>::basic_mmap(basic_mmap&& other)\n    : data_(std::move(other.data_))\n    , length_(std::move(other.length_))\n    , mapped_length_(std::move(other.mapped_length_))\n    , file_handle_(std::move(other.file_handle_))\n#ifdef _WIN32\n    , file_mapping_handle_(std::move(other.file_mapping_handle_))\n#endif\n    , is_handle_internal_(std::move(other.is_handle_internal_))\n{\n    other.data_ = nullptr;\n    other.length_ = other.mapped_length_ = 0;\n    other.file_handle_ = invalid_handle;\n#ifdef _WIN32\n    other.file_mapping_handle_ = invalid_handle;\n#endif\n}\n\ntemplate<access_mode AccessMode, typename ByteT>\nbasic_mmap<AccessMode, ByteT>&\nbasic_mmap<AccessMode, ByteT>::operator=(basic_mmap&& other)\n{\n    if(this != &other)\n    {\n        // First the existing mapping needs to be removed.\n        unmap();\n        data_ = std::move(other.data_);\n        length_ = std::move(other.length_);\n        mapped_length_ = std::move(other.mapped_length_);\n        file_handle_ = std::move(other.file_handle_);\n#ifdef _WIN32\n        file_mapping_handle_ = std::move(other.file_mapping_handle_);\n#endif\n        is_handle_internal_ = std::move(other.is_handle_internal_);\n\n        // The moved from basic_mmap's fields need to be reset, because\n        // otherwise other's destructor will unmap the same mapping that was\n        // just moved into this.\n        other.data_ = nullptr;\n        other.length_ = other.mapped_length_ = 0;\n        other.file_handle_ = invalid_handle;\n#ifdef _WIN32\n        other.file_mapping_handle_ = invalid_handle;\n#endif\n        other.is_handle_internal_ = false;\n    }\n    return *this;\n}\n\ntemplate<access_mode AccessMode, typename ByteT>\ntypename basic_mmap<AccessMode, ByteT>::handle_type\nbasic_mmap<AccessMode, ByteT>::mapping_handle() const noexcept\n{\n#ifdef _WIN32\n    return file_mapping_handle_;\n#else\n    return file_handle_;\n#endif\n}\n\ntemplate<access_mode AccessMode, typename ByteT>\ntemplate<typename String>\nvoid basic_mmap<AccessMode, ByteT>::map(const String& path, const size_type offset,\n        const size_type length, std::error_code& error)\n{\n    error.clear();\n    if(detail::empty(path))\n    {\n        error = std::make_error_code(std::errc::invalid_argument);\n        return;\n    }\n    const auto handle = detail::open_file(path, AccessMode, error);\n    if(error)\n    {\n        return;\n    }\n\n    map(handle, offset, length, error);\n    // This MUST be after the call to map, as that sets this to true.\n    if(!error)\n    {\n        is_handle_internal_ = true;\n    }\n}\n\ntemplate<access_mode AccessMode, typename ByteT>\nvoid basic_mmap<AccessMode, ByteT>::map(const handle_type handle,\n        const size_type offset, const size_type length, std::error_code& error)\n{\n    error.clear();\n    if(handle == invalid_handle)\n    {\n        error = std::make_error_code(std::errc::bad_file_descriptor);\n        return;\n    }\n\n    const auto file_size = detail::query_file_size(handle, error);\n    if(error)\n    {\n        return;\n    }\n\n    if(offset + length > file_size)\n    {\n        error = std::make_error_code(std::errc::invalid_argument);\n        return;\n    }\n\n    const auto ctx = detail::memory_map(handle, offset,\n            length == map_entire_file ? (file_size - offset) : length,\n            AccessMode, error);\n    if(!error)\n    {\n        // We must unmap the previous mapping that may have existed prior to this call.\n        // Note that this must only be invoked after a new mapping has been created in\n        // order to provide the strong guarantee that, should the new mapping fail, the\n        // `map` function leaves this instance in a state as though the function had\n        // never been invoked.\n        unmap();\n        file_handle_ = handle;\n        is_handle_internal_ = false;\n        data_ = reinterpret_cast<pointer>(ctx.data);\n        length_ = ctx.length;\n        mapped_length_ = ctx.mapped_length;\n#ifdef _WIN32\n        file_mapping_handle_ = ctx.file_mapping_handle;\n#endif\n    }\n}\n\ntemplate<access_mode AccessMode, typename ByteT>\ntemplate<access_mode A>\ntypename std::enable_if<A == access_mode::write, void>::type\nbasic_mmap<AccessMode, ByteT>::sync(std::error_code& error)\n{\n    error.clear();\n    if(!is_open())\n    {\n        error = std::make_error_code(std::errc::bad_file_descriptor);\n        return;\n    }\n\n    if(data())\n    {\n#ifdef _WIN32\n        if(::FlushViewOfFile(get_mapping_start(), mapped_length_) == 0\n           || ::FlushFileBuffers(file_handle_) == 0)\n#else // POSIX\n        if(::msync(get_mapping_start(), mapped_length_, MS_SYNC) != 0)\n#endif\n        {\n            error = detail::last_error();\n            return;\n        }\n    }\n#ifdef _WIN32\n    if(::FlushFileBuffers(file_handle_) == 0)\n    {\n        error = detail::last_error();\n    }\n#endif\n}\n\ntemplate<access_mode AccessMode, typename ByteT>\nvoid basic_mmap<AccessMode, ByteT>::unmap()\n{\n    if(!is_open()) { return; }\n    // TODO do we care about errors here?\n#ifdef _WIN32\n    if(is_mapped())\n    {\n        ::UnmapViewOfFile(get_mapping_start());\n        ::CloseHandle(file_mapping_handle_);\n    }\n#else // POSIX\n    if(data_) { ::munmap(const_cast<pointer>(get_mapping_start()), mapped_length_); }\n#endif\n\n    // If `file_handle_` was obtained by our opening it (when map is called with\n    // a path, rather than an existing file handle), we need to close it,\n    // otherwise it must not be closed as it may still be used outside this\n    // instance.\n    if(is_handle_internal_)\n    {\n#ifdef _WIN32\n        ::CloseHandle(file_handle_);\n#else // POSIX\n        ::close(file_handle_);\n#endif\n    }\n\n    // Reset fields to their default values.\n    data_ = nullptr;\n    length_ = mapped_length_ = 0;\n    file_handle_ = invalid_handle;\n#ifdef _WIN32\n    file_mapping_handle_ = invalid_handle;\n#endif\n}\n\ntemplate<access_mode AccessMode, typename ByteT>\nbool basic_mmap<AccessMode, ByteT>::is_mapped() const noexcept\n{\n#ifdef _WIN32\n    return file_mapping_handle_ != invalid_handle;\n#else // POSIX\n    return is_open();\n#endif\n}\n\ntemplate<access_mode AccessMode, typename ByteT>\nvoid basic_mmap<AccessMode, ByteT>::swap(basic_mmap& other)\n{\n    if(this != &other)\n    {\n        using std::swap;\n        swap(data_, other.data_);\n        swap(file_handle_, other.file_handle_);\n#ifdef _WIN32\n        swap(file_mapping_handle_, other.file_mapping_handle_);\n#endif\n        swap(length_, other.length_);\n        swap(mapped_length_, other.mapped_length_);\n        swap(is_handle_internal_, other.is_handle_internal_);\n    }\n}\n\ntemplate<access_mode AccessMode, typename ByteT>\ntemplate<access_mode A>\ntypename std::enable_if<A == access_mode::write, void>::type\nbasic_mmap<AccessMode, ByteT>::conditional_sync()\n{\n    // This is invoked from the destructor, so not much we can do about\n    // failures here.\n    std::error_code ec;\n    sync(ec);\n}\n\ntemplate<access_mode AccessMode, typename ByteT>\ntemplate<access_mode A>\ntypename std::enable_if<A == access_mode::read, void>::type\nbasic_mmap<AccessMode, ByteT>::conditional_sync()\n{\n    // noop\n}\n\ntemplate<access_mode AccessMode, typename ByteT>\nbool operator==(const basic_mmap<AccessMode, ByteT>& a,\n        const basic_mmap<AccessMode, ByteT>& b)\n{\n    return a.data() == b.data()\n        && a.size() == b.size();\n}\n\ntemplate<access_mode AccessMode, typename ByteT>\nbool operator!=(const basic_mmap<AccessMode, ByteT>& a,\n        const basic_mmap<AccessMode, ByteT>& b)\n{\n    return !(a == b);\n}\n\ntemplate<access_mode AccessMode, typename ByteT>\nbool operator<(const basic_mmap<AccessMode, ByteT>& a,\n        const basic_mmap<AccessMode, ByteT>& b)\n{\n    if(a.data() == b.data()) { return a.size() < b.size(); }\n    return a.data() < b.data();\n}\n\ntemplate<access_mode AccessMode, typename ByteT>\nbool operator<=(const basic_mmap<AccessMode, ByteT>& a,\n        const basic_mmap<AccessMode, ByteT>& b)\n{\n    return !(a > b);\n}\n\ntemplate<access_mode AccessMode, typename ByteT>\nbool operator>(const basic_mmap<AccessMode, ByteT>& a,\n        const basic_mmap<AccessMode, ByteT>& b)\n{\n    if(a.data() == b.data()) { return a.size() > b.size(); }\n    return a.data() > b.data();\n}\n\ntemplate<access_mode AccessMode, typename ByteT>\nbool operator>=(const basic_mmap<AccessMode, ByteT>& a,\n        const basic_mmap<AccessMode, ByteT>& b)\n{\n    return !(a < b);\n}\n\n} // namespace mio\n\n#endif // MIO_BASIC_MMAP_IMPL\n\n\n#endif // MIO_MMAP_HEADER\n/* Copyright 2017 https://github.com/mandreyel\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so, subject to the following\n * conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies\n * or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF\n * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE\n * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n\n#ifndef MIO_PAGE_HEADER\n#define MIO_PAGE_HEADER\n\n#ifdef _WIN32\n#ifndef NOMINMAX\n# define NOMINMAX\n#endif // NOMINMAX\n#ifndef WIN32_LEAN_AND_MEAN\n# define WIN32_LEAN_AND_MEAN\n#endif // WIN32_LEAN_AND_MEAN\n# include <windows.h>\n#else\n# include <unistd.h>\n#endif\n\nnamespace mio {\n\n/**\n * This is used by `basic_mmap` to determine whether to create a read-only or\n * a read-write memory mapping.\n */\nenum class access_mode\n{\n    read,\n    write\n};\n\n/**\n * Determines the operating system's page allocation granularity.\n *\n * On the first call to this function, it invokes the operating system specific syscall\n * to determine the page size, caches the value, and returns it. Any subsequent call to\n * this function serves the cached value, so no further syscalls are made.\n */\ninline size_t page_size()\n{\n    static const size_t page_size = []\n    {\n#ifdef _WIN32\n        SYSTEM_INFO SystemInfo;\n        GetSystemInfo(&SystemInfo);\n        return SystemInfo.dwAllocationGranularity;\n#else\n        return sysconf(_SC_PAGE_SIZE);\n#endif\n    }();\n    return page_size;\n}\n\n/**\n * Alligns `offset` to the operating's system page size such that it subtracts the\n * difference until the nearest page boundary before `offset`, or does nothing if\n * `offset` is already page aligned.\n */\ninline size_t make_offset_page_aligned(size_t offset) noexcept\n{\n    const size_t page_size_ = page_size();\n    // Use integer division to round down to the nearest page alignment.\n    return offset / page_size_ * page_size_;\n}\n\n} // namespace mio\n\n#endif // MIO_PAGE_HEADER\n/* Copyright 2017 https://github.com/mandreyel\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so, subject to the following\n * conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies\n * or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF\n * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE\n * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n\n#ifndef MIO_SHARED_MMAP_HEADER\n#define MIO_SHARED_MMAP_HEADER\n\n// #include \"mio/mmap.hpp\"\n\n\n#include <system_error> // std::error_code\n#include <memory> // std::shared_ptr\n\nnamespace mio {\n\n/**\n * Exposes (nearly) the same interface as `basic_mmap`, but endowes it with\n * `std::shared_ptr` semantics.\n *\n * This is not the default behaviour of `basic_mmap` to avoid allocating on the heap if\n * shared semantics are not required.\n */\ntemplate<\n    access_mode AccessMode,\n    typename ByteT\n> class basic_shared_mmap\n{\n    using impl_type = basic_mmap<AccessMode, ByteT>;\n    std::shared_ptr<impl_type> pimpl_;\n\npublic:\n    using value_type = typename impl_type::value_type;\n    using size_type = typename impl_type::size_type;\n    using reference = typename impl_type::reference;\n    using const_reference = typename impl_type::const_reference;\n    using pointer = typename impl_type::pointer;\n    using const_pointer = typename impl_type::const_pointer;\n    using difference_type = typename impl_type::difference_type;\n    using iterator = typename impl_type::iterator;\n    using const_iterator = typename impl_type::const_iterator;\n    using reverse_iterator = typename impl_type::reverse_iterator;\n    using const_reverse_iterator = typename impl_type::const_reverse_iterator;\n    using iterator_category = typename impl_type::iterator_category;\n    using handle_type = typename impl_type::handle_type;\n    using mmap_type = impl_type;\n\n    basic_shared_mmap() = default;\n    basic_shared_mmap(const basic_shared_mmap&) = default;\n    basic_shared_mmap& operator=(const basic_shared_mmap&) = default;\n    basic_shared_mmap(basic_shared_mmap&&) = default;\n    basic_shared_mmap& operator=(basic_shared_mmap&&) = default;\n\n    /** Takes ownership of an existing mmap object. */\n    basic_shared_mmap(mmap_type&& mmap)\n        : pimpl_(std::make_shared<mmap_type>(std::move(mmap)))\n    {}\n\n    /** Takes ownership of an existing mmap object. */\n    basic_shared_mmap& operator=(mmap_type&& mmap)\n    {\n        pimpl_ = std::make_shared<mmap_type>(std::move(mmap));\n        return *this;\n    }\n\n    /** Initializes this object with an already established shared mmap. */\n    basic_shared_mmap(std::shared_ptr<mmap_type> mmap) : pimpl_(std::move(mmap)) {}\n\n    /** Initializes this object with an already established shared mmap. */\n    basic_shared_mmap& operator=(std::shared_ptr<mmap_type> mmap)\n    {\n        pimpl_ = std::move(mmap);\n        return *this;\n    }\n\n#ifdef __cpp_exceptions\n    /**\n     * The same as invoking the `map` function, except any error that may occur\n     * while establishing the mapping is wrapped in a `std::system_error` and is\n     * thrown.\n     */\n    template<typename String>\n    basic_shared_mmap(const String& path, const size_type offset = 0, const size_type length = map_entire_file)\n    {\n        std::error_code error;\n        map(path, offset, length, error);\n        if(error) { throw std::system_error(error); }\n    }\n\n    /**\n     * The same as invoking the `map` function, except any error that may occur\n     * while establishing the mapping is wrapped in a `std::system_error` and is\n     * thrown.\n     */\n    basic_shared_mmap(const handle_type handle, const size_type offset = 0, const size_type length = map_entire_file)\n    {\n        std::error_code error;\n        map(handle, offset, length, error);\n        if(error) { throw std::system_error(error); }\n    }\n#endif // __cpp_exceptions\n\n    /**\n     * If this is a read-write mapping and the last reference to the mapping,\n     * the destructor invokes sync. Regardless of the access mode, unmap is\n     * invoked as a final step.\n     */\n    ~basic_shared_mmap() = default;\n\n    /** Returns the underlying `std::shared_ptr` instance that holds the mmap. */\n    std::shared_ptr<mmap_type> get_shared_ptr() { return pimpl_; }\n\n    /**\n     * On UNIX systems 'file_handle' and 'mapping_handle' are the same. On Windows,\n     * however, a mapped region of a file gets its own handle, which is returned by\n     * 'mapping_handle'.\n     */\n    handle_type file_handle() const noexcept\n    {\n        return pimpl_ ? pimpl_->file_handle() : invalid_handle;\n    }\n\n    handle_type mapping_handle() const noexcept\n    {\n        return pimpl_ ? pimpl_->mapping_handle() : invalid_handle;\n    }\n\n    /** Returns whether a valid memory mapping has been created. */\n    bool is_open() const noexcept { return pimpl_ && pimpl_->is_open(); }\n\n    /**\n     * Returns true if no mapping was established, that is, conceptually the\n     * same as though the length that was mapped was 0. This function is\n     * provided so that this class has Container semantics.\n     */\n    bool empty() const noexcept { return !pimpl_ || pimpl_->empty(); }\n\n    /**\n     * `size` and `length` both return the logical length, i.e. the number of bytes\n     * user requested to be mapped, while `mapped_length` returns the actual number of\n     * bytes that were mapped which is a multiple of the underlying operating system's\n     * page allocation granularity.\n     */\n    size_type size() const noexcept { return pimpl_ ? pimpl_->length() : 0; }\n    size_type length() const noexcept { return pimpl_ ? pimpl_->length() : 0; }\n    size_type mapped_length() const noexcept\n    {\n        return pimpl_ ? pimpl_->mapped_length() : 0;\n    }\n\n    /**\n     * Returns a pointer to the first requested byte, or `nullptr` if no memory mapping\n     * exists.\n     */\n    template<\n        access_mode A = AccessMode,\n        typename = typename std::enable_if<A == access_mode::write>::type\n    > pointer data() noexcept { return pimpl_->data(); }\n    const_pointer data() const noexcept { return pimpl_ ? pimpl_->data() : nullptr; }\n\n    /**\n     * Returns an iterator to the first requested byte, if a valid memory mapping\n     * exists, otherwise this function call is undefined behaviour.\n     */\n    iterator begin() noexcept { return pimpl_->begin(); }\n    const_iterator begin() const noexcept { return pimpl_->begin(); }\n    const_iterator cbegin() const noexcept { return pimpl_->cbegin(); }\n\n    /**\n     * Returns an iterator one past the last requested byte, if a valid memory mapping\n     * exists, otherwise this function call is undefined behaviour.\n     */\n    template<\n        access_mode A = AccessMode,\n        typename = typename std::enable_if<A == access_mode::write>::type\n    > iterator end() noexcept { return pimpl_->end(); }\n    const_iterator end() const noexcept { return pimpl_->end(); }\n    const_iterator cend() const noexcept { return pimpl_->cend(); }\n\n    /**\n     * Returns a reverse iterator to the last memory mapped byte, if a valid\n     * memory mapping exists, otherwise this function call is undefined\n     * behaviour.\n     */\n    template<\n        access_mode A = AccessMode,\n        typename = typename std::enable_if<A == access_mode::write>::type\n    > reverse_iterator rbegin() noexcept { return pimpl_->rbegin(); }\n    const_reverse_iterator rbegin() const noexcept { return pimpl_->rbegin(); }\n    const_reverse_iterator crbegin() const noexcept { return pimpl_->crbegin(); }\n\n    /**\n     * Returns a reverse iterator past the first mapped byte, if a valid memory\n     * mapping exists, otherwise this function call is undefined behaviour.\n     */\n    template<\n        access_mode A = AccessMode,\n        typename = typename std::enable_if<A == access_mode::write>::type\n    > reverse_iterator rend() noexcept { return pimpl_->rend(); }\n    const_reverse_iterator rend() const noexcept { return pimpl_->rend(); }\n    const_reverse_iterator crend() const noexcept { return pimpl_->crend(); }\n\n    /**\n     * Returns a reference to the `i`th byte from the first requested byte (as returned\n     * by `data`). If this is invoked when no valid memory mapping has been created\n     * prior to this call, undefined behaviour ensues.\n     */\n    reference operator[](const size_type i) noexcept { return (*pimpl_)[i]; }\n    const_reference operator[](const size_type i) const noexcept { return (*pimpl_)[i]; }\n\n    /**\n     * Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the\n     * reason is reported via `error` and the object remains in a state as if this\n     * function hadn't been called.\n     *\n     * `path`, which must be a path to an existing file, is used to retrieve a file\n     * handle (which is closed when the object destructs or `unmap` is called), which is\n     * then used to memory map the requested region. Upon failure, `error` is set to\n     * indicate the reason and the object remains in an unmapped state.\n     *\n     * `offset` is the number of bytes, relative to the start of the file, where the\n     * mapping should begin. When specifying it, there is no need to worry about\n     * providing a value that is aligned with the operating system's page allocation\n     * granularity. This is adjusted by the implementation such that the first requested\n     * byte (as returned by `data` or `begin`), so long as `offset` is valid, will be at\n     * `offset` from the start of the file.\n     *\n     * `length` is the number of bytes to map. It may be `map_entire_file`, in which\n     * case a mapping of the entire file is created.\n     */\n    template<typename String>\n    void map(const String& path, const size_type offset,\n        const size_type length, std::error_code& error)\n    {\n        map_impl(path, offset, length, error);\n    }\n\n    /**\n     * Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the\n     * reason is reported via `error` and the object remains in a state as if this\n     * function hadn't been called.\n     *\n     * `path`, which must be a path to an existing file, is used to retrieve a file\n     * handle (which is closed when the object destructs or `unmap` is called), which is\n     * then used to memory map the requested region. Upon failure, `error` is set to\n     * indicate the reason and the object remains in an unmapped state.\n     *\n     * The entire file is mapped.\n     */\n    template<typename String>\n    void map(const String& path, std::error_code& error)\n    {\n        map_impl(path, 0, map_entire_file, error);\n    }\n\n    /**\n     * Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the\n     * reason is reported via `error` and the object remains in a state as if this\n     * function hadn't been called.\n     *\n     * `handle`, which must be a valid file handle, which is used to memory map the\n     * requested region. Upon failure, `error` is set to indicate the reason and the\n     * object remains in an unmapped state.\n     *\n     * `offset` is the number of bytes, relative to the start of the file, where the\n     * mapping should begin. When specifying it, there is no need to worry about\n     * providing a value that is aligned with the operating system's page allocation\n     * granularity. This is adjusted by the implementation such that the first requested\n     * byte (as returned by `data` or `begin`), so long as `offset` is valid, will be at\n     * `offset` from the start of the file.\n     *\n     * `length` is the number of bytes to map. It may be `map_entire_file`, in which\n     * case a mapping of the entire file is created.\n     */\n    void map(const handle_type handle, const size_type offset,\n        const size_type length, std::error_code& error)\n    {\n        map_impl(handle, offset, length, error);\n    }\n\n    /**\n     * Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the\n     * reason is reported via `error` and the object remains in a state as if this\n     * function hadn't been called.\n     *\n     * `handle`, which must be a valid file handle, which is used to memory map the\n     * requested region. Upon failure, `error` is set to indicate the reason and the\n     * object remains in an unmapped state.\n     *\n     * The entire file is mapped.\n     */\n    void map(const handle_type handle, std::error_code& error)\n    {\n        map_impl(handle, 0, map_entire_file, error);\n    }\n\n    /**\n     * If a valid memory mapping has been created prior to this call, this call\n     * instructs the kernel to unmap the memory region and disassociate this object\n     * from the file.\n     *\n     * The file handle associated with the file that is mapped is only closed if the\n     * mapping was created using a file path. If, on the other hand, an existing\n     * file handle was used to create the mapping, the file handle is not closed.\n     */\n    void unmap() { if(pimpl_) pimpl_->unmap(); }\n\n    void swap(basic_shared_mmap& other) { pimpl_.swap(other.pimpl_); }\n\n    /** Flushes the memory mapped page to disk. Errors are reported via `error`. */\n    template<\n        access_mode A = AccessMode,\n        typename = typename std::enable_if<A == access_mode::write>::type\n    > void sync(std::error_code& error) { if(pimpl_) pimpl_->sync(error); }\n\n    /** All operators compare the underlying `basic_mmap`'s addresses. */\n\n    friend bool operator==(const basic_shared_mmap& a, const basic_shared_mmap& b)\n    {\n        return a.pimpl_ == b.pimpl_;\n    }\n\n    friend bool operator!=(const basic_shared_mmap& a, const basic_shared_mmap& b)\n    {\n        return !(a == b);\n    }\n\n    friend bool operator<(const basic_shared_mmap& a, const basic_shared_mmap& b)\n    {\n        return a.pimpl_ < b.pimpl_;\n    }\n\n    friend bool operator<=(const basic_shared_mmap& a, const basic_shared_mmap& b)\n    {\n        return a.pimpl_ <= b.pimpl_;\n    }\n\n    friend bool operator>(const basic_shared_mmap& a, const basic_shared_mmap& b)\n    {\n        return a.pimpl_ > b.pimpl_;\n    }\n\n    friend bool operator>=(const basic_shared_mmap& a, const basic_shared_mmap& b)\n    {\n        return a.pimpl_ >= b.pimpl_;\n    }\n\nprivate:\n    template<typename MappingToken>\n    void map_impl(const MappingToken& token, const size_type offset,\n        const size_type length, std::error_code& error)\n    {\n        if(!pimpl_)\n        {\n            mmap_type mmap = make_mmap<mmap_type>(token, offset, length, error);\n            if(error) { return; }\n            pimpl_ = std::make_shared<mmap_type>(std::move(mmap));\n        }\n        else\n        {\n            pimpl_->map(token, offset, length, error);\n        }\n    }\n};\n\n/**\n * This is the basis for all read-only mmap objects and should be preferred over\n * directly using basic_shared_mmap.\n */\ntemplate<typename ByteT>\nusing basic_shared_mmap_source = basic_shared_mmap<access_mode::read, ByteT>;\n\n/**\n * This is the basis for all read-write mmap objects and should be preferred over\n * directly using basic_shared_mmap.\n */\ntemplate<typename ByteT>\nusing basic_shared_mmap_sink = basic_shared_mmap<access_mode::write, ByteT>;\n\n/**\n * These aliases cover the most common use cases, both representing a raw byte stream\n * (either with a char or an unsigned char/uint8_t).\n */\nusing shared_mmap_source = basic_shared_mmap_source<char>;\nusing shared_ummap_source = basic_shared_mmap_source<unsigned char>;\n\nusing shared_mmap_sink = basic_shared_mmap_sink<char>;\nusing shared_ummap_sink = basic_shared_mmap_sink<unsigned char>;\n\n} // namespace mio\n\n#endif // MIO_SHARED_MMAP_HEADER"
  },
  {
    "path": "vendor/mspack/lzx.h",
    "content": "/* This file is part of libmspack.\n * (C) 2003-2013 Stuart Caie.\n *\n * The LZX method was created by Jonathan Forbes and Tomi Poutanen, adapted\n * by Microsoft Corporation.\n *\n * libmspack is free software; you can redistribute it and/or modify it under\n * the terms of the GNU Lesser General Public License (LGPL) version 2.1\n *\n * For further details, see the file COPYING.LIB distributed with libmspack\n */\n\n#ifndef MSPACK_LZX_H\n#define MSPACK_LZX_H 1\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n/* LZX compression / decompression definitions */\n\n/* some constants defined by the LZX specification */\n#define LZX_MIN_MATCH                (2)\n#define LZX_MAX_MATCH                (257)\n#define LZX_NUM_CHARS                (256)\n#define LZX_BLOCKTYPE_INVALID        (0)   /* also blocktypes 4-7 invalid */\n#define LZX_BLOCKTYPE_VERBATIM       (1)\n#define LZX_BLOCKTYPE_ALIGNED        (2)\n#define LZX_BLOCKTYPE_UNCOMPRESSED   (3)\n#define LZX_PRETREE_NUM_ELEMENTS     (20)\n#define LZX_ALIGNED_NUM_ELEMENTS     (8)   /* aligned offset tree #elements */\n#define LZX_NUM_PRIMARY_LENGTHS      (7)   /* this one missing from spec! */\n#define LZX_NUM_SECONDARY_LENGTHS    (249) /* length tree #elements */\n\n/* LZX huffman defines: tweak tablebits as desired */\n#define LZX_PRETREE_MAXSYMBOLS  (LZX_PRETREE_NUM_ELEMENTS)\n#define LZX_PRETREE_TABLEBITS   (6)\n#define LZX_MAINTREE_MAXSYMBOLS (LZX_NUM_CHARS + 290*8)\n#define LZX_MAINTREE_TABLEBITS  (12)\n#define LZX_LENGTH_MAXSYMBOLS   (LZX_NUM_SECONDARY_LENGTHS+1)\n#define LZX_LENGTH_TABLEBITS    (12)\n#define LZX_ALIGNED_MAXSYMBOLS  (LZX_ALIGNED_NUM_ELEMENTS)\n#define LZX_ALIGNED_TABLEBITS   (7)\n#define LZX_LENTABLE_SAFETY (64)  /* table decoding overruns are allowed */\n\n#define LZX_FRAME_SIZE (32768) /* the size of a frame in LZX */\n\nstruct lzxd_stream {\n  struct mspack_system *sys;      /* I/O routines                            */\n  struct mspack_file   *input;    /* input file handle                       */\n  struct mspack_file   *output;   /* output file handle                      */\n\n  off_t   offset;                 /* number of bytes actually output         */\n  off_t   length;                 /* overall decompressed length of stream   */\n\n  unsigned char *window;          /* decoding window                         */\n  unsigned int   window_size;     /* window size                             */\n  unsigned int   ref_data_size;   /* LZX DELTA reference data size           */\n  unsigned int   num_offsets;     /* number of match_offset entries in table */\n  unsigned int   window_posn;     /* decompression offset within window      */\n  unsigned int   frame_posn;      /* current frame offset within in window   */\n  unsigned int   frame;           /* the number of 32kb frames processed     */\n  unsigned int   reset_interval;  /* which frame do we reset the compressor? */\n\n  unsigned int   R0, R1, R2;      /* for the LRU offset system               */\n  unsigned int   block_length;    /* uncompressed length of this LZX block   */\n  unsigned int   block_remaining; /* uncompressed bytes still left to decode */\n\n  signed int     intel_filesize;  /* magic header value used for transform   */\n\n  unsigned char  intel_started;   /* has intel E8 decoding started?          */\n  unsigned char  block_type;      /* type of the current block               */\n  unsigned char  header_read;     /* have we started decoding at all yet?    */\n  unsigned char  input_end;       /* have we reached the end of input?       */\n  unsigned char  is_delta;        /* does stream follow LZX DELTA spec?      */\n\n  int error;\n\n  /* I/O buffering */\n  unsigned char *inbuf, *i_ptr, *i_end, *o_ptr, *o_end;\n  unsigned int  bit_buffer, bits_left, inbuf_size;\n\n  /* huffman code lengths */\n  unsigned char PRETREE_len  [LZX_PRETREE_MAXSYMBOLS  + LZX_LENTABLE_SAFETY];\n  unsigned char MAINTREE_len [LZX_MAINTREE_MAXSYMBOLS + LZX_LENTABLE_SAFETY];\n  unsigned char LENGTH_len   [LZX_LENGTH_MAXSYMBOLS   + LZX_LENTABLE_SAFETY];\n  unsigned char ALIGNED_len  [LZX_ALIGNED_MAXSYMBOLS  + LZX_LENTABLE_SAFETY];\n\n  /* huffman decoding tables */\n  unsigned short PRETREE_table [(1 << LZX_PRETREE_TABLEBITS) +\n                                (LZX_PRETREE_MAXSYMBOLS * 2)];\n  unsigned short MAINTREE_table[(1 << LZX_MAINTREE_TABLEBITS) +\n                                (LZX_MAINTREE_MAXSYMBOLS * 2)];\n  unsigned short LENGTH_table  [(1 << LZX_LENGTH_TABLEBITS) +\n                                (LZX_LENGTH_MAXSYMBOLS * 2)];\n  unsigned short ALIGNED_table [(1 << LZX_ALIGNED_TABLEBITS) +\n                                (LZX_ALIGNED_MAXSYMBOLS * 2)];\n  unsigned char LENGTH_empty;\n\n  /* this is used purely for doing the intel E8 transform */\n  unsigned char  e8_buf[LZX_FRAME_SIZE];\n};\n\n/**\n * Allocates and initialises LZX decompression state for decoding an LZX\n * stream.\n *\n * This routine uses system->alloc() to allocate memory. If memory\n * allocation fails, or the parameters to this function are invalid,\n * NULL is returned.\n *\n * @param system             an mspack_system structure used to read from\n *                           the input stream and write to the output\n *                           stream, also to allocate and free memory.\n * @param input              an input stream with the LZX data.\n * @param output             an output stream to write the decoded data to.\n * @param window_bits        the size of the decoding window, which must be\n *                           between 15 and 21 inclusive for regular LZX\n *                           data, or between 17 and 25 inclusive for\n *                           LZX DELTA data.\n * @param reset_interval     the interval at which the LZX bitstream is\n *                           reset, in multiples of LZX frames (32678\n *                           bytes), e.g. a value of 2 indicates the input\n *                           stream resets after every 65536 output bytes.\n *                           A value of 0 indicates that the bitstream never\n *                           resets, such as in CAB LZX streams.\n * @param input_buffer_size  the number of bytes to use as an input\n *                           bitstream buffer.\n * @param output_length      the length in bytes of the entirely\n *                           decompressed output stream, if known in\n *                           advance. It is used to correctly perform the\n *                           Intel E8 transformation, which must stop 6\n *                           bytes before the very end of the\n *                           decompressed stream. It is not otherwise used\n *                           or adhered to. If the full decompressed\n *                           length is known in advance, set it here.\n *                           If it is NOT known, use the value 0, and call\n *                           lzxd_set_output_length() once it is\n *                           known. If never set, 4 of the final 6 bytes\n *                           of the output stream may be incorrect.\n * @param is_delta           should be zero for all regular LZX data,\n *                           non-zero for LZX DELTA encoded data.\n * @return a pointer to an initialised lzxd_stream structure, or NULL if\n * there was not enough memory or parameters to the function were wrong.\n */\nextern struct lzxd_stream *lzxd_init(struct mspack_system *system,\n                                     struct mspack_file *input,\n                                     struct mspack_file *output,\n                                     int window_bits,\n                                     int reset_interval,\n                                     int input_buffer_size,\n                                     off_t output_length,\n                                     char is_delta);\n\n/* see description of output_length in lzxd_init() */\nextern void lzxd_set_output_length(struct lzxd_stream *lzx,\n                                   off_t output_length);\n\n/**\n * Reads LZX DELTA reference data into the window and allows\n * lzxd_decompress() to reference it.\n *\n * Call this before the first call to lzxd_decompress().\n\n * @param lzx    the LZX stream to apply this reference data to\n * @param system an mspack_system implementation to use with the\n *               input param. Only read() will be called.\n * @param input  an input file handle to read reference data using\n *               system->read().\n * @param length the length of the reference data. Cannot be longer\n *               than the LZX window size.\n * @return an error code, or MSPACK_ERR_OK if successful\n */\nextern int lzxd_set_reference_data(struct lzxd_stream *lzx,\n                                   struct mspack_system *system,\n                                   struct mspack_file *input,\n                                   unsigned int length);\n\n/**\n * Decompresses entire or partial LZX streams.\n *\n * The number of bytes of data that should be decompressed is given as the\n * out_bytes parameter. If more bytes are decoded than are needed, they\n * will be kept over for a later invocation.\n *\n * The output bytes will be passed to the system->write() function given in\n * lzxd_init(), using the output file handle given in lzxd_init(). More than\n * one call may be made to system->write().\n\n * Input bytes will be read in as necessary using the system->read()\n * function given in lzxd_init(), using the input file handle given in\n * lzxd_init().  This will continue until system->read() returns 0 bytes,\n * or an error. Errors will be passed out of the function as\n * MSPACK_ERR_READ errors.  Input streams should convey an \"end of input\n * stream\" by refusing to supply all the bytes that LZX asks for when they\n * reach the end of the stream, rather than return an error code.\n *\n * If any error code other than MSPACK_ERR_OK is returned, the stream\n * should be considered unusable and lzxd_decompress() should not be\n * called again on this stream.\n *\n * @param lzx       LZX decompression state, as allocated by lzxd_init().\n * @param out_bytes the number of bytes of data to decompress.\n * @return an error code, or MSPACK_ERR_OK if successful\n */\nextern int lzxd_decompress(struct lzxd_stream *lzx, off_t out_bytes);\n\n/**\n * Frees all state associated with an LZX data stream. This will call\n * system->free() using the system pointer given in lzxd_init().\n *\n * @param lzx LZX decompression state to free.\n */\nvoid lzxd_free(struct lzxd_stream *lzx);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif\n"
  },
  {
    "path": "vendor/mspack/lzxd.c",
    "content": "/* This file is part of libmspack.\n * (C) 2003-2013 Stuart Caie.\n *\n * The LZX method was created by Jonathan Forbes and Tomi Poutanen, adapted\n * by Microsoft Corporation.\n *\n * libmspack is free software; you can redistribute it and/or modify it under\n * the terms of the GNU Lesser General Public License (LGPL) version 2.1\n *\n * For further details, see the file COPYING.LIB distributed with libmspack\n */\n\n/* LZX decompression implementation */\n\n#include <system.h>\n#include <lzx.h>\n\n/* Microsoft's LZX document (in cab-sdk.exe) and their implementation\n * of the com.ms.util.cab Java package do not concur.\n *\n * In the LZX document, there is a table showing the correlation between\n * window size and the number of position slots. It states that the 1MB\n * window = 40 slots and the 2MB window = 42 slots. In the implementation,\n * 1MB = 42 slots, 2MB = 50 slots. The actual calculation is 'find the\n * first slot whose position base is equal to or more than the required\n * window size'. This would explain why other tables in the document refer\n * to 50 slots rather than 42.\n *\n * The constant NUM_PRIMARY_LENGTHS used in the decompression pseudocode\n * is not defined in the specification.\n *\n * The LZX document does not state the uncompressed block has an\n * uncompressed length field. Where does this length field come from, so\n * we can know how large the block is? The implementation has it as the 24\n * bits following after the 3 blocktype bits, before the alignment\n * padding.\n *\n * The LZX document states that aligned offset blocks have their aligned\n * offset huffman tree AFTER the main and length trees. The implementation\n * suggests that the aligned offset tree is BEFORE the main and length\n * trees.\n *\n * The LZX document decoding algorithm states that, in an aligned offset\n * block, if an extra_bits value is 1, 2 or 3, then that number of bits\n * should be read and the result added to the match offset. This is\n * correct for 1 and 2, but not 3, where just a huffman symbol (using the\n * aligned tree) should be read.\n *\n * Regarding the E8 preprocessing, the LZX document states 'No translation\n * may be performed on the last 6 bytes of the input block'. This is\n * correct.  However, the pseudocode provided checks for the *E8 leader*\n * up to the last 6 bytes. If the leader appears between -10 and -7 bytes\n * from the end, this would cause the next four bytes to be modified, at\n * least one of which would be in the last 6 bytes, which is not allowed\n * according to the spec.\n *\n * The specification states that the huffman trees must always contain at\n * least one element. However, many CAB files contain blocks where the\n * length tree is completely empty (because there are no matches), and\n * this is expected to succeed.\n *\n * The errors in LZX documentation appear have been corrected in the\n * new documentation for the LZX DELTA format.\n *\n *     http://msdn.microsoft.com/en-us/library/cc483133.aspx\n *\n * However, this is a different format, an extension of regular LZX.\n * I have noticed the following differences, there may be more:\n *\n * The maximum window size has increased from 2MB to 32MB. This also\n * increases the maximum number of position slots, etc.\n *\n * If the match length is 257 (the maximum possible), this signals\n * a further length decoding step, that allows for matches up to\n * 33024 bytes long.\n *\n * The format now allows for \"reference data\", supplied by the caller.\n * If match offsets go further back than the number of bytes\n * decompressed so far, that is them accessing the reference data.\n */\n\n/* import bit-reading macros and code */\n#define BITS_TYPE struct lzxd_stream\n#define BITS_VAR lzx\n#define BITS_ORDER_MSB\n#define READ_BYTES do {                 \\\n    unsigned char b0, b1;               \\\n    READ_IF_NEEDED; b0 = *i_ptr++;      \\\n    READ_IF_NEEDED; b1 = *i_ptr++;      \\\n    INJECT_BITS((b1 << 8) | b0, 16);    \\\n} while (0)\n#include <readbits.h>\n\n/* import huffman-reading macros and code */\n#define TABLEBITS(tbl)      LZX_##tbl##_TABLEBITS\n#define MAXSYMBOLS(tbl)     LZX_##tbl##_MAXSYMBOLS\n#define HUFF_TABLE(tbl,idx) lzx->tbl##_table[idx]\n#define HUFF_LEN(tbl,idx)   lzx->tbl##_len[idx]\n#define HUFF_ERROR          return lzx->error = MSPACK_ERR_DECRUNCH\n#include <readhuff.h>\n\n/* BUILD_TABLE(tbl) builds a huffman lookup table from code lengths */\n#define BUILD_TABLE(tbl)                                                \\\n    if (make_decode_table(MAXSYMBOLS(tbl), TABLEBITS(tbl),              \\\n                          &HUFF_LEN(tbl,0), &HUFF_TABLE(tbl,0)))        \\\n    {                                                                   \\\n        D((\"failed to build %s table\", #tbl))                           \\\n        return lzx->error = MSPACK_ERR_DECRUNCH;                        \\\n    }\n\n#define BUILD_TABLE_MAYBE_EMPTY(tbl) do {                               \\\n    lzx->tbl##_empty = 0;                                               \\\n    if (make_decode_table(MAXSYMBOLS(tbl), TABLEBITS(tbl),              \\\n                          &HUFF_LEN(tbl,0), &HUFF_TABLE(tbl,0)))        \\\n    {                                                                   \\\n        for (i = 0; i < MAXSYMBOLS(tbl); i++) {                         \\\n            if (HUFF_LEN(tbl, i) > 0) {                                 \\\n                D((\"failed to build %s table\", #tbl))                   \\\n                return lzx->error = MSPACK_ERR_DECRUNCH;                \\\n            }                                                           \\\n        }                                                               \\\n        /* empty tree - allow it, but don't decode symbols with it */   \\\n        lzx->tbl##_empty = 1;                                           \\\n    }                                                                   \\\n} while (0)\n\n/* READ_LENGTHS(tablename, first, last) reads in code lengths for symbols\n * first to last in the given table. The code lengths are stored in their\n * own special LZX way.\n */\n#define READ_LENGTHS(tbl, first, last) do {             \\\n  STORE_BITS;                                           \\\n  if (lzxd_read_lens(lzx, &HUFF_LEN(tbl, 0), (first),   \\\n    (unsigned int)(last))) return lzx->error;           \\\n  RESTORE_BITS;                                         \\\n} while (0)\n\nstatic int lzxd_read_lens(struct lzxd_stream *lzx, unsigned char *lens,\n                          unsigned int first, unsigned int last)\n{\n  /* bit buffer and huffman symbol decode variables */\n  register unsigned int bit_buffer;\n  register int bits_left, i;\n  register unsigned short sym;\n  unsigned char *i_ptr, *i_end;\n\n  unsigned int x, y;\n  int z;\n\n  RESTORE_BITS;\n  \n  /* read lengths for pretree (20 symbols, lengths stored in fixed 4 bits) */\n  for (x = 0; x < 20; x++) {\n    READ_BITS(y, 4);\n    lzx->PRETREE_len[x] = y;\n  }\n  BUILD_TABLE(PRETREE);\n\n  for (x = first; x < last; ) {\n    READ_HUFFSYM(PRETREE, z);\n    if (z == 17) {\n      /* code = 17, run of ([read 4 bits]+4) zeros */\n      READ_BITS(y, 4); y += 4;\n      while (y--) lens[x++] = 0;\n    }\n    else if (z == 18) {\n      /* code = 18, run of ([read 5 bits]+20) zeros */\n      READ_BITS(y, 5); y += 20;\n      while (y--) lens[x++] = 0;\n    }\n    else if (z == 19) {\n      /* code = 19, run of ([read 1 bit]+4) [read huffman symbol] */\n      READ_BITS(y, 1); y += 4;\n      READ_HUFFSYM(PRETREE, z);\n      z = lens[x] - z; if (z < 0) z += 17;\n      while (y--) lens[x++] = z;\n    }\n    else {\n      /* code = 0 to 16, delta current length entry */\n      z = lens[x] - z; if (z < 0) z += 17;\n      lens[x++] = z;\n    }\n  }\n\n  STORE_BITS;\n\n  return MSPACK_ERR_OK;\n}\n\n/* LZX static data tables:\n *\n * LZX uses 'position slots' to represent match offsets.  For every match,\n * a small 'position slot' number and a small offset from that slot are\n * encoded instead of one large offset.\n *\n * The number of slots is decided by how many are needed to encode the\n * largest offset for a given window size. This is easy when the gap between\n * slots is less than 128Kb, it's a linear relationship. But when extra_bits\n * reaches its limit of 17 (because LZX can only ensure reading 17 bits of\n * data at a time), we can only jump 128Kb at a time and have to start\n * using more and more position slots as each window size doubles.\n *\n * position_base[] is an index to the position slot bases\n *\n * extra_bits[] states how many bits of offset-from-base data is needed.\n *\n * They are calculated as follows:\n * extra_bits[i] = 0 where i < 4\n * extra_bits[i] = floor(i/2)-1 where i >= 4 && i < 36\n * extra_bits[i] = 17 where i >= 36\n * position_base[0] = 0\n * position_base[i] = position_base[i-1] + (1 << extra_bits[i-1])\n */\nstatic const unsigned int position_slots[11] = {\n    30, 32, 34, 36, 38, 42, 50, 66, 98, 162, 290\n};\nstatic const unsigned char extra_bits[36] = {\n    0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8,\n    9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 16, 16\n};\nstatic const unsigned int position_base[290] = {\n    0, 1, 2, 3, 4, 6, 8, 12, 16, 24, 32, 48, 64, 96, 128, 192, 256, 384, 512,\n    768, 1024, 1536, 2048, 3072, 4096, 6144, 8192, 12288, 16384, 24576, 32768,\n    49152, 65536, 98304, 131072, 196608, 262144, 393216, 524288, 655360,\n    786432, 917504, 1048576, 1179648, 1310720, 1441792, 1572864, 1703936,\n    1835008, 1966080, 2097152, 2228224, 2359296, 2490368, 2621440, 2752512,\n    2883584, 3014656, 3145728, 3276800, 3407872, 3538944, 3670016, 3801088,\n    3932160, 4063232, 4194304, 4325376, 4456448, 4587520, 4718592, 4849664,\n    4980736, 5111808, 5242880, 5373952, 5505024, 5636096, 5767168, 5898240,\n    6029312, 6160384, 6291456, 6422528, 6553600, 6684672, 6815744, 6946816,\n    7077888, 7208960, 7340032, 7471104, 7602176, 7733248, 7864320, 7995392,\n    8126464, 8257536, 8388608, 8519680, 8650752, 8781824, 8912896, 9043968,\n    9175040, 9306112, 9437184, 9568256, 9699328, 9830400, 9961472, 10092544,\n    10223616, 10354688, 10485760, 10616832, 10747904, 10878976, 11010048,\n    11141120, 11272192, 11403264, 11534336, 11665408, 11796480, 11927552,\n    12058624, 12189696, 12320768, 12451840, 12582912, 12713984, 12845056,\n    12976128, 13107200, 13238272, 13369344, 13500416, 13631488, 13762560,\n    13893632, 14024704, 14155776, 14286848, 14417920, 14548992, 14680064,\n    14811136, 14942208, 15073280, 15204352, 15335424, 15466496, 15597568,\n    15728640, 15859712, 15990784, 16121856, 16252928, 16384000, 16515072,\n    16646144, 16777216, 16908288, 17039360, 17170432, 17301504, 17432576,\n    17563648, 17694720, 17825792, 17956864, 18087936, 18219008, 18350080,\n    18481152, 18612224, 18743296, 18874368, 19005440, 19136512, 19267584,\n    19398656, 19529728, 19660800, 19791872, 19922944, 20054016, 20185088,\n    20316160, 20447232, 20578304, 20709376, 20840448, 20971520, 21102592,\n    21233664, 21364736, 21495808, 21626880, 21757952, 21889024, 22020096,\n    22151168, 22282240, 22413312, 22544384, 22675456, 22806528, 22937600,\n    23068672, 23199744, 23330816, 23461888, 23592960, 23724032, 23855104,\n    23986176, 24117248, 24248320, 24379392, 24510464, 24641536, 24772608,\n    24903680, 25034752, 25165824, 25296896, 25427968, 25559040, 25690112,\n    25821184, 25952256, 26083328, 26214400, 26345472, 26476544, 26607616,\n    26738688, 26869760, 27000832, 27131904, 27262976, 27394048, 27525120,\n    27656192, 27787264, 27918336, 28049408, 28180480, 28311552, 28442624,\n    28573696, 28704768, 28835840, 28966912, 29097984, 29229056, 29360128,\n    29491200, 29622272, 29753344, 29884416, 30015488, 30146560, 30277632,\n    30408704, 30539776, 30670848, 30801920, 30932992, 31064064, 31195136,\n    31326208, 31457280, 31588352, 31719424, 31850496, 31981568, 32112640,\n    32243712, 32374784, 32505856, 32636928, 32768000, 32899072, 33030144,\n    33161216, 33292288, 33423360\n};\n\nstatic void lzxd_reset_state(struct lzxd_stream *lzx) {\n  int i;\n\n  lzx->R0              = 1;\n  lzx->R1              = 1;\n  lzx->R2              = 1;\n  lzx->header_read     = 0;\n  lzx->block_remaining = 0;\n  lzx->block_type      = LZX_BLOCKTYPE_INVALID;\n\n  /* initialise tables to 0 (because deltas will be applied to them) */\n  for (i = 0; i < LZX_MAINTREE_MAXSYMBOLS; i++) lzx->MAINTREE_len[i] = 0;\n  for (i = 0; i < LZX_LENGTH_MAXSYMBOLS; i++)   lzx->LENGTH_len[i]   = 0;\n}\n\n/*-------- main LZX code --------*/\n\nstruct lzxd_stream *lzxd_init(struct mspack_system *system,\n                              struct mspack_file *input,\n                              struct mspack_file *output,\n                              int window_bits,\n                              int reset_interval,\n                              int input_buffer_size,\n                              off_t output_length,\n                              char is_delta)\n{\n  unsigned int window_size = 1 << window_bits;\n  struct lzxd_stream *lzx;\n\n  if (!system) return NULL;\n\n  /* LZX DELTA window sizes are between 2^17 (128KiB) and 2^25 (32MiB),\n   * regular LZX windows are between 2^15 (32KiB) and 2^21 (2MiB)\n   */\n  if (is_delta) {\n      if (window_bits < 17 || window_bits > 25) return NULL;\n  }\n  else {\n      if (window_bits < 15 || window_bits > 21) return NULL;\n  }\n\n  if (reset_interval < 0 || output_length < 0) {\n      D((\"reset interval or output length < 0\"))\n      return NULL;\n  }\n\n  /* round up input buffer size to multiple of two */\n  input_buffer_size = (input_buffer_size + 1) & -2;\n  if (input_buffer_size < 2) return NULL;\n\n  /* allocate decompression state */\n  if (!(lzx = (struct lzxd_stream *) system->alloc(system, sizeof(struct lzxd_stream)))) {\n    return NULL;\n  }\n\n  /* allocate decompression window and input buffer */\n  lzx->window = (unsigned char *) system->alloc(system, (size_t) window_size);\n  lzx->inbuf  = (unsigned char *) system->alloc(system, (size_t) input_buffer_size);\n  if (!lzx->window || !lzx->inbuf) {\n    system->free(lzx->window);\n    system->free(lzx->inbuf);\n    system->free(lzx);\n    return NULL;\n  }\n\n  /* initialise decompression state */\n  lzx->sys             = system;\n  lzx->input           = input;\n  lzx->output          = output;\n  lzx->offset          = 0;\n  lzx->length          = output_length;\n\n  lzx->inbuf_size      = input_buffer_size;\n  lzx->window_size     = 1 << window_bits;\n  lzx->ref_data_size   = 0;\n  lzx->window_posn     = 0;\n  lzx->frame_posn      = 0;\n  lzx->frame           = 0;\n  lzx->reset_interval  = reset_interval;\n  lzx->intel_filesize  = 0;\n  lzx->intel_started   = 0;\n  lzx->error           = MSPACK_ERR_OK;\n  lzx->num_offsets     = position_slots[window_bits - 15] << 3;\n  lzx->is_delta        = is_delta;\n\n  lzx->o_ptr = lzx->o_end = &lzx->e8_buf[0];\n  lzxd_reset_state(lzx);\n  INIT_BITS;\n  return lzx;\n}\n\nint lzxd_set_reference_data(struct lzxd_stream *lzx,\n                            struct mspack_system *system,\n                            struct mspack_file *input,\n                            unsigned int length)\n{\n    if (!lzx) return MSPACK_ERR_ARGS;\n\n    if (!lzx->is_delta) {\n        D((\"only LZX DELTA streams support reference data\"))\n        return MSPACK_ERR_ARGS;\n    }\n    if (lzx->offset) {\n        D((\"too late to set reference data after decoding starts\"))\n        return MSPACK_ERR_ARGS;\n    }\n    if (length > lzx->window_size) {\n        D((\"reference length (%u) is longer than the window\", length))\n        return MSPACK_ERR_ARGS;\n    }\n    if (length > 0 && (!system || !input)) {\n        D((\"length > 0 but no system or input\"))\n        return MSPACK_ERR_ARGS;\n    }\n\n    lzx->ref_data_size = length;\n    if (length > 0) {\n        /* copy reference data */\n        unsigned char *pos = &lzx->window[lzx->window_size - length];\n        int bytes = system->read(input, pos, length);\n        /* length can't be more than 2^25, so no signedness problem */\n        if (bytes < (int)length) return MSPACK_ERR_READ;\n    }\n    lzx->ref_data_size = length;\n    return MSPACK_ERR_OK;\n}\n\nvoid lzxd_set_output_length(struct lzxd_stream *lzx, off_t out_bytes) {\n  if (lzx && out_bytes > 0) lzx->length = out_bytes;\n}\n\nint lzxd_decompress(struct lzxd_stream *lzx, off_t out_bytes) {\n  /* bitstream and huffman reading variables */\n  register unsigned int bit_buffer;\n  register int bits_left, i=0;\n  unsigned char *i_ptr, *i_end;\n  register unsigned short sym;\n\n  int match_length, length_footer, extra, verbatim_bits, bytes_todo;\n  int this_run, main_element, aligned_bits, j, warned = 0;\n  unsigned char *window, *runsrc, *rundest, buf[12];\n  unsigned int frame_size=0, end_frame, match_offset, window_posn;\n  unsigned int R0, R1, R2;\n\n  /* easy answers */\n  if (!lzx || (out_bytes < 0)) return MSPACK_ERR_ARGS;\n  if (lzx->error) return lzx->error;\n\n  /* flush out any stored-up bytes before we begin */\n  i = lzx->o_end - lzx->o_ptr;\n  if ((off_t) i > out_bytes) i = (int) out_bytes;\n  if (i) {\n    if (lzx->sys->write(lzx->output, lzx->o_ptr, i) != i) {\n      return lzx->error = MSPACK_ERR_WRITE;\n    }\n    lzx->o_ptr  += i;\n    lzx->offset += i;\n    out_bytes   -= i;\n  }\n  if (out_bytes == 0) return MSPACK_ERR_OK;\n\n  /* restore local state */\n  RESTORE_BITS;\n  window = lzx->window;\n  window_posn = lzx->window_posn;\n  R0 = lzx->R0;\n  R1 = lzx->R1;\n  R2 = lzx->R2;\n\n  end_frame = (unsigned int)((lzx->offset + out_bytes) / LZX_FRAME_SIZE) + 1;\n\n  while (lzx->frame < end_frame) {\n    /* have we reached the reset interval? (if there is one?) */\n    if (lzx->reset_interval && ((lzx->frame % lzx->reset_interval) == 0)) {\n      if (lzx->block_remaining) {\n        /* this is a file format error, we can make a best effort to extract what we can */\n        D((\"%d bytes remaining at reset interval\", lzx->block_remaining))\n        if (!warned) {\n          lzx->sys->message(NULL, \"WARNING; invalid reset interval detected during LZX decompression\");\n          warned++;\n        }\n      }\n\n      /* re-read the intel header and reset the huffman lengths */\n      lzxd_reset_state(lzx);\n      R0 = lzx->R0;\n      R1 = lzx->R1;\n      R2 = lzx->R2;\n    }\n\n    /* LZX DELTA format has chunk_size, not present in LZX format */\n    if (lzx->is_delta) {\n      ENSURE_BITS(16);\n      REMOVE_BITS(16);\n    }\n\n    /* read header if necessary */\n    if (!lzx->header_read) {\n      /* read 1 bit. if bit=0, intel filesize = 0.\n       * if bit=1, read intel filesize (32 bits) */\n      j = 0; READ_BITS(i, 1); if (i) { READ_BITS(i, 16); READ_BITS(j, 16); }\n      lzx->intel_filesize = (i << 16) | j;\n      lzx->header_read = 1;\n    } \n\n    /* calculate size of frame: all frames are 32k except the final frame\n     * which is 32kb or less. this can only be calculated when lzx->length\n     * has been filled in. */\n    frame_size = LZX_FRAME_SIZE;\n    if (lzx->length && (lzx->length - lzx->offset) < (off_t)frame_size) {\n      frame_size = lzx->length - lzx->offset;\n    }\n\n    /* decode until one more frame is available */\n    bytes_todo = lzx->frame_posn + frame_size - window_posn;\n    while (bytes_todo > 0) {\n      /* initialise new block, if one is needed */\n      if (lzx->block_remaining == 0) {\n        /* realign if previous block was an odd-sized UNCOMPRESSED block */\n        if ((lzx->block_type == LZX_BLOCKTYPE_UNCOMPRESSED) &&\n            (lzx->block_length & 1))\n        {\n          READ_IF_NEEDED;\n          i_ptr++;\n        }\n\n        /* read block type (3 bits) and block length (24 bits) */\n        READ_BITS(lzx->block_type, 3);\n        READ_BITS(i, 16); READ_BITS(j, 8);\n        lzx->block_remaining = lzx->block_length = (i << 8) | j;\n        /*D((\"new block t%d len %u\", lzx->block_type, lzx->block_length))*/\n\n        /* read individual block headers */\n        switch (lzx->block_type) {\n        case LZX_BLOCKTYPE_ALIGNED:\n          /* read lengths of and build aligned huffman decoding tree */\n          for (i = 0; i < 8; i++) { READ_BITS(j, 3); lzx->ALIGNED_len[i] = j; }\n          BUILD_TABLE(ALIGNED);\n          /* rest of aligned header is same as verbatim */ /*@fallthrough@*/\n        case LZX_BLOCKTYPE_VERBATIM:\n          /* read lengths of and build main huffman decoding tree */\n          READ_LENGTHS(MAINTREE, 0, 256);\n          READ_LENGTHS(MAINTREE, 256, LZX_NUM_CHARS + lzx->num_offsets);\n          BUILD_TABLE(MAINTREE);\n          /* if the literal 0xE8 is anywhere in the block... */\n          if (lzx->MAINTREE_len[0xE8] != 0) lzx->intel_started = 1;\n          /* read lengths of and build lengths huffman decoding tree */\n          READ_LENGTHS(LENGTH, 0, LZX_NUM_SECONDARY_LENGTHS);\n          BUILD_TABLE_MAYBE_EMPTY(LENGTH);\n          break;\n\n        case LZX_BLOCKTYPE_UNCOMPRESSED:\n          /* because we can't assume otherwise */\n          lzx->intel_started = 1;\n\n          /* read 1-16 (not 0-15) bits to align to bytes */\n          if (bits_left == 0) ENSURE_BITS(16);\n          bits_left = 0; bit_buffer = 0;\n\n          /* read 12 bytes of stored R0 / R1 / R2 values */\n          for (rundest = &buf[0], i = 0; i < 12; i++) {\n            READ_IF_NEEDED;\n            *rundest++ = *i_ptr++;\n          }\n          R0 = buf[0] | (buf[1] << 8) | (buf[2]  << 16) | (buf[3]  << 24);\n          R1 = buf[4] | (buf[5] << 8) | (buf[6]  << 16) | (buf[7]  << 24);\n          R2 = buf[8] | (buf[9] << 8) | (buf[10] << 16) | (buf[11] << 24);\n          break;\n\n        default:\n          D((\"bad block type\"))\n          return lzx->error = MSPACK_ERR_DECRUNCH;\n        }\n      }\n\n      /* decode more of the block:\n       * run = min(what's available, what's needed) */\n      this_run = lzx->block_remaining;\n      if (this_run > bytes_todo) this_run = bytes_todo;\n\n      /* assume we decode exactly this_run bytes, for now */\n      bytes_todo           -= this_run;\n      lzx->block_remaining -= this_run;\n\n      /* decode at least this_run bytes */\n      switch (lzx->block_type) {\n      case LZX_BLOCKTYPE_ALIGNED:\n      case LZX_BLOCKTYPE_VERBATIM:\n        while (this_run > 0) {\n          READ_HUFFSYM(MAINTREE, main_element);\n          if (main_element < LZX_NUM_CHARS) {\n            /* literal: 0 to LZX_NUM_CHARS-1 */\n            window[window_posn++] = main_element;\n            this_run--;\n          }\n          else {\n            /* match: LZX_NUM_CHARS + ((slot<<3) | length_header (3 bits)) */\n            main_element -= LZX_NUM_CHARS;\n\n            /* get match length */\n            match_length = main_element & LZX_NUM_PRIMARY_LENGTHS;\n            if (match_length == LZX_NUM_PRIMARY_LENGTHS) {\n              if (lzx->LENGTH_empty) {\n                D((\"LENGTH symbol needed but tree is empty\"))\n                return lzx->error = MSPACK_ERR_DECRUNCH;\n              }\n              READ_HUFFSYM(LENGTH, length_footer);\n              match_length += length_footer;\n            }\n            match_length += LZX_MIN_MATCH;\n\n            /* get match offset */\n            switch ((match_offset = (main_element >> 3))) {\n            case 0: match_offset = R0; break;\n            case 1: match_offset = R1; R1=R0; R0 = match_offset; break;\n            case 2: match_offset = R2; R2=R0; R0 = match_offset; break;\n            default:\n              if (lzx->block_type == LZX_BLOCKTYPE_VERBATIM) {\n                if (match_offset == 3) {\n                  match_offset = 1;\n                }\n                else {\n                  extra = (match_offset >= 36) ? 17 : extra_bits[match_offset];\n                  READ_BITS(verbatim_bits, extra);\n                  match_offset = position_base[match_offset] - 2 + verbatim_bits;\n                }\n              }\n              else { /* LZX_BLOCKTYPE_ALIGNED */\n                extra = (match_offset >= 36) ? 17 : extra_bits[match_offset];\n                match_offset = position_base[match_offset] - 2;\n                if (extra > 3) { /* >3: verbatim and aligned bits */\n                  extra -= 3;\n                  READ_BITS(verbatim_bits, extra);\n                  match_offset += (verbatim_bits << 3);\n                  READ_HUFFSYM(ALIGNED, aligned_bits);\n                  match_offset += aligned_bits;\n                }\n                else if (extra == 3) { /* 3: aligned bits only */\n                  READ_HUFFSYM(ALIGNED, aligned_bits);\n                  match_offset += aligned_bits;\n                }\n                else if (extra > 0) { /* 1-2: verbatim bits only */\n                  READ_BITS(verbatim_bits, extra);\n                  match_offset += verbatim_bits;\n                }\n                else { /* 0: not defined in LZX specification! */\n                  match_offset = 1;\n                }\n              }\n              /* update repeated offset LRU queue */\n              R2 = R1; R1 = R0; R0 = match_offset;\n            }\n\n            /* LZX DELTA uses max match length to signal even longer match */\n            if (match_length == LZX_MAX_MATCH && lzx->is_delta) {\n                int extra_len = 0;\n                ENSURE_BITS(3); /* 4 entry huffman tree */\n                if (PEEK_BITS(1) == 0) {\n                    REMOVE_BITS(1); /* '0' -> 8 extra length bits */\n                    READ_BITS(extra_len, 8);\n                }\n                else if (PEEK_BITS(2) == 2) {\n                    REMOVE_BITS(2); /* '10' -> 10 extra length bits + 0x100 */\n                    READ_BITS(extra_len, 10);\n                    extra_len += 0x100;\n                }\n                else if (PEEK_BITS(3) == 6) {\n                    REMOVE_BITS(3); /* '110' -> 12 extra length bits + 0x500 */\n                    READ_BITS(extra_len, 12);\n                    extra_len += 0x500;\n                }\n                else {\n                    REMOVE_BITS(3); /* '111' -> 15 extra length bits */\n                    READ_BITS(extra_len, 15);\n                }\n                match_length += extra_len;\n            }\n\n            if ((window_posn + match_length) > lzx->window_size) {\n              D((\"match ran over window wrap\"))\n              return lzx->error = MSPACK_ERR_DECRUNCH;\n            }\n\n            /* copy match */\n            rundest = &window[window_posn];\n            i = match_length;\n            /* does match offset wrap the window? */\n            if (match_offset > window_posn) {\n              if (match_offset > lzx->offset &&\n                  (match_offset - window_posn) > lzx->ref_data_size)\n              {\n                D((\"match offset beyond LZX stream\"))\n                return lzx->error = MSPACK_ERR_DECRUNCH;\n              }\n              /* j = length from match offset to end of window */\n              j = match_offset - window_posn;\n              if (j > (int) lzx->window_size) {\n                D((\"match offset beyond window boundaries\"))\n                return lzx->error = MSPACK_ERR_DECRUNCH;\n              }\n              runsrc = &window[lzx->window_size - j];\n              if (j < i) {\n                /* if match goes over the window edge, do two copy runs */\n                i -= j; while (j-- > 0) *rundest++ = *runsrc++;\n                runsrc = window;\n              }\n              while (i-- > 0) *rundest++ = *runsrc++;\n            }\n            else {\n              runsrc = rundest - match_offset;\n              while (i-- > 0) *rundest++ = *runsrc++;\n            }\n\n            this_run    -= match_length;\n            window_posn += match_length;\n          }\n        } /* while (this_run > 0) */\n        break;\n\n      case LZX_BLOCKTYPE_UNCOMPRESSED:\n        /* as this_run is limited not to wrap a frame, this also means it\n         * won't wrap the window (as the window is a multiple of 32k) */\n        rundest = &window[window_posn];\n        window_posn += this_run;\n        while (this_run > 0) {\n          if ((i = i_end - i_ptr) == 0) {\n            READ_IF_NEEDED;\n          }\n          else {\n            if (i > this_run) i = this_run;\n            lzx->sys->copy(i_ptr, rundest, (size_t) i);\n            rundest  += i;\n            i_ptr    += i;\n            this_run -= i;\n          }\n        }\n        break;\n\n      default:\n        return lzx->error = MSPACK_ERR_DECRUNCH; /* might as well */\n      }\n\n      /* did the final match overrun our desired this_run length? */\n      if (this_run < 0) {\n        if ((unsigned int)(-this_run) > lzx->block_remaining) {\n          D((\"overrun went past end of block by %d (%d remaining)\",\n             -this_run, lzx->block_remaining ))\n          return lzx->error = MSPACK_ERR_DECRUNCH;\n        }\n        lzx->block_remaining -= -this_run;\n      }\n    } /* while (bytes_todo > 0) */\n\n    /* streams don't extend over frame boundaries */\n    if ((window_posn - lzx->frame_posn) != frame_size) {\n      D((\"decode beyond output frame limits! %d != %d\",\n         window_posn - lzx->frame_posn, frame_size))\n      return lzx->error = MSPACK_ERR_DECRUNCH;\n    }\n\n    /* re-align input bitstream */\n    if (bits_left > 0) ENSURE_BITS(16);\n    if (bits_left & 15) REMOVE_BITS(bits_left & 15);\n\n    /* check that we've used all of the previous frame first */\n    if (lzx->o_ptr != lzx->o_end) {\n      D((\"%ld avail bytes, new %d frame\",\n          (long)(lzx->o_end - lzx->o_ptr), frame_size))\n      return lzx->error = MSPACK_ERR_DECRUNCH;\n    }\n\n    /* does this intel block _really_ need decoding? */\n    if (lzx->intel_started && lzx->intel_filesize &&\n        (lzx->frame < 32768) && (frame_size > 10))\n    {\n      unsigned char *data    = &lzx->e8_buf[0];\n      unsigned char *dataend = &lzx->e8_buf[frame_size - 10];\n      signed int curpos      = (int) lzx->offset;\n      signed int filesize    = lzx->intel_filesize;\n      signed int abs_off, rel_off;\n\n      /* copy e8 block to the e8 buffer and tweak if needed */\n      lzx->o_ptr = data;\n      lzx->sys->copy(&lzx->window[lzx->frame_posn], data, frame_size);\n\n      while (data < dataend) {\n        if (*data++ != 0xE8) { curpos++; continue; }\n        abs_off = data[0] | (data[1]<<8) | (data[2]<<16) | (data[3]<<24);\n        if ((abs_off >= -curpos) && (abs_off < filesize)) {\n          rel_off = (abs_off >= 0) ? abs_off - curpos : abs_off + filesize;\n          data[0] = (unsigned char) rel_off;\n          data[1] = (unsigned char) (rel_off >> 8);\n          data[2] = (unsigned char) (rel_off >> 16);\n          data[3] = (unsigned char) (rel_off >> 24);\n        }\n        data += 4;\n        curpos += 5;\n      }\n    }\n    else {\n      lzx->o_ptr = &lzx->window[lzx->frame_posn];\n    }\n    lzx->o_end = &lzx->o_ptr[frame_size];\n\n    /* write a frame */\n    i = (out_bytes < (off_t)frame_size) ? (unsigned int)out_bytes : frame_size;\n    if (lzx->sys->write(lzx->output, lzx->o_ptr, i) != i) {\n      return lzx->error = MSPACK_ERR_WRITE;\n    }\n    lzx->o_ptr  += i;\n    lzx->offset += i;\n    out_bytes   -= i;\n\n    /* advance frame start position */\n    lzx->frame_posn += frame_size;\n    lzx->frame++;\n\n    /* wrap window / frame position pointers */\n    if (window_posn == lzx->window_size)     window_posn = 0;\n    if (lzx->frame_posn == lzx->window_size) lzx->frame_posn = 0;\n\n  } /* while (lzx->frame < end_frame) */\n\n  if (out_bytes) {\n    D((\"bytes left to output\"))\n    return lzx->error = MSPACK_ERR_DECRUNCH;\n  }\n\n  /* store local state */\n  STORE_BITS;\n  lzx->window_posn = window_posn;\n  lzx->R0 = R0;\n  lzx->R1 = R1;\n  lzx->R2 = R2;\n\n  return MSPACK_ERR_OK;\n}\n\nvoid lzxd_free(struct lzxd_stream *lzx) {\n  struct mspack_system *sys;\n  if (lzx) {\n    sys = lzx->sys;\n    sys->free(lzx->inbuf);\n    sys->free(lzx->window);\n    sys->free(lzx);\n  }\n}\n"
  },
  {
    "path": "vendor/mspack/macros.h",
    "content": "/* This file is part of libmspack.\n * (C) 2003-2020 Stuart Caie.\n *\n * libmspack is free software; you can redistribute it and/or modify it under\n * the terms of the GNU Lesser General Public License (LGPL) version 2.1\n *\n * For further details, see the file COPYING.LIB distributed with libmspack\n */\n\n#ifndef MSPACK_MACROS_H\n#define MSPACK_MACROS_H 1\n\n/* define LD and LU as printf-format for signed and unsigned long offsets */\n#if HAVE_INTTYPES_H\n# include <inttypes.h>\n#else\n# define PRId64 \"lld\"\n# define PRIu64 \"llu\"\n# define PRId32 \"ld\"\n# define PRIu32 \"lu\"\n#endif\n\n#if SIZEOF_OFF_T >= 8\n# define LD PRId64\n# define LU PRIu64\n#else\n# define LD PRId32\n# define LU PRIu32\n#endif\n\n/* endian-neutral reading of little-endian data */\n#define __egi32(a,n) (((unsigned int) ((unsigned char *)(a))[n+3] << 24) | \\\n                      ((unsigned int) ((unsigned char *)(a))[n+2] << 16) | \\\n                      ((unsigned int) ((unsigned char *)(a))[n+1] <<  8) | \\\n                      ((unsigned int) ((unsigned char *)(a))[n]))\n#define EndGetI64(a) (((unsigned long long int) __egi32(a,4) << 32) | __egi32(a,0))\n#define EndGetI32(a) __egi32(a,0)\n#define EndGetI16(a) ((((a)[1])<<8)|((a)[0]))\n\n/* endian-neutral reading of big-endian data */\n#define EndGetM32(a) (((unsigned int) ((unsigned char *)(a))[0] << 24) | \\\n                      ((unsigned int) ((unsigned char *)(a))[1] << 16) | \\\n                      ((unsigned int) ((unsigned char *)(a))[2] <<  8) | \\\n                      ((unsigned int) ((unsigned char *)(a))[3]))\n#define EndGetM16(a) ((((a)[0])<<8)|((a)[1]))\n\n/* D((\"formatstring\", args)) prints debug messages if DEBUG defined */\n#if DEBUG\n /* http://gcc.gnu.org/onlinedocs/gcc/Function-Names.html */\n# if __STDC_VERSION__ < 199901L\n#  if __GNUC__ >= 2\n#   define __func__ __FUNCTION__\n#  else\n#   define __func__ \"<unknown>\"\n#  endif\n# endif\n# include <stdio.h>\n# define D(x) do { printf(\"%s:%d (%s) \",__FILE__, __LINE__, __func__); \\\n                   printf x ; fputc('\\n', stdout); fflush(stdout);} while (0);\n#else\n# define D(x)\n#endif\n\n#endif"
  },
  {
    "path": "vendor/mspack/mspack.h",
    "content": "/* libmspack -- a library for working with Microsoft compression formats.\n * (C) 2003-2019 Stuart Caie <kyzer@cabextract.org.uk>\n *\n * libmspack is free software; you can redistribute it and/or modify it under\n * the terms of the GNU Lesser General Public License (LGPL) version 2.1\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 Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.\n */\n\n/** \\mainpage\n *\n * \\section intro Introduction\n *\n * libmspack is a library which provides compressors and decompressors,\n * archivers and dearchivers for Microsoft compression formats.\n *\n * \\section formats Formats supported\n *\n * The following file formats are supported:\n * - SZDD files, which use LZSS compression\n * - KWAJ files, which use LZSS, LZSS+Huffman or deflate compression\n * - .HLP (MS Help) files, which use LZSS compression\n * - .CAB (MS Cabinet) files, which use deflate, LZX or Quantum compression\n * - .CHM (HTML Help) files, which use LZX compression\n * - .LIT (MS EBook) files, which use LZX compression and DES encryption\n * - .LZX (Exchange Offline Addressbook) files, which use LZX compression\n *\n * To determine the capabilities of the library, and the binary\n * compatibility version of any particular compressor or decompressor, use\n * the mspack_version() function. The UNIX library interface version is\n * defined as the highest-versioned library component.\n *\n * \\section starting Getting started\n *\n * The macro MSPACK_SYS_SELFTEST() should be used to ensure the library can\n * be used. In particular, it checks if the caller is using 32-bit file I/O\n * when the library is compiled for 64-bit file I/O and vice versa.\n *\n * If compiled normally, the library includes basic file I/O and memory\n * management functionality using the standard C library. This can be\n * customised and replaced entirely by creating a mspack_system structure.\n *\n * A compressor or decompressor for the required format must be\n * instantiated before it can be used. Each construction function takes\n * one parameter, which is either a pointer to a custom mspack_system\n * structure, or NULL to use the default. The instantiation returned, if\n * not NULL, contains function pointers (methods) to work with the given\n * file format.\n * \n * For compression:\n * - mspack_create_cab_compressor() creates a mscab_compressor\n * - mspack_create_chm_compressor() creates a mschm_compressor\n * - mspack_create_lit_compressor() creates a mslit_compressor\n * - mspack_create_hlp_compressor() creates a mshlp_compressor\n * - mspack_create_szdd_compressor() creates a msszdd_compressor\n * - mspack_create_kwaj_compressor() creates a mskwaj_compressor\n * - mspack_create_oab_compressor() creates a msoab_compressor\n *\n * For decompression:\n * - mspack_create_cab_decompressor() creates a mscab_decompressor\n * - mspack_create_chm_decompressor() creates a mschm_decompressor\n * - mspack_create_lit_decompressor() creates a mslit_decompressor\n * - mspack_create_hlp_decompressor() creates a mshlp_decompressor\n * - mspack_create_szdd_decompressor() creates a msszdd_decompressor\n * - mspack_create_kwaj_decompressor() creates a mskwaj_decompressor\n * - mspack_create_oab_decompressor() creates a msoab_decompressor\n *\n * Once finished working with a format, each kind of\n * compressor/decompressor has its own specific destructor:\n * - mspack_destroy_cab_compressor()\n * - mspack_destroy_cab_decompressor()\n * - mspack_destroy_chm_compressor()\n * - mspack_destroy_chm_decompressor()\n * - mspack_destroy_lit_compressor()\n * - mspack_destroy_lit_decompressor()\n * - mspack_destroy_hlp_compressor()\n * - mspack_destroy_hlp_decompressor()\n * - mspack_destroy_szdd_compressor()\n * - mspack_destroy_szdd_decompressor()\n * - mspack_destroy_kwaj_compressor()\n * - mspack_destroy_kwaj_decompressor()\n * - mspack_destroy_oab_compressor()\n * - mspack_destroy_oab_decompressor()\n *\n * Destroying a compressor or decompressor does not destroy any objects,\n * structures or handles that have been created using that compressor or\n * decompressor. Ensure that everything created or opened is destroyed or\n * closed before compressor/decompressor is itself destroyed.\n *\n * \\section errors Error codes\n *\n * All compressors and decompressors use the same set of error codes. Most\n * methods return an error code directly. For methods which do not\n * return error codes directly, the error code can be obtained with the\n * last_error() method.\n *\n * - #MSPACK_ERR_OK is used to indicate success. This error code is defined\n *   as zero, all other code are non-zero.\n * - #MSPACK_ERR_ARGS indicates that a method was called with inappropriate\n *   arguments.\n * - #MSPACK_ERR_OPEN indicates that mspack_system::open() failed.\n * - #MSPACK_ERR_READ indicates that mspack_system::read() failed.\n * - #MSPACK_ERR_WRITE indicates that mspack_system::write() failed.\n * - #MSPACK_ERR_SEEK indicates that mspack_system::seek() failed.\n * - #MSPACK_ERR_NOMEMORY indicates that mspack_system::alloc() failed.\n * - #MSPACK_ERR_SIGNATURE indicates that the file being read does not\n *   have the correct \"signature\". It is probably not a valid file for\n *   whatever format is being read.\n * - #MSPACK_ERR_DATAFORMAT indicates that the file being used or read\n *   is corrupt.\n * - #MSPACK_ERR_CHECKSUM indicates that a data checksum has failed.\n * - #MSPACK_ERR_CRUNCH indicates an error occured during compression.\n * - #MSPACK_ERR_DECRUNCH indicates an error occured during decompression.\n *\n * \\section threading Multi-threading\n *\n * libmspack methods are reentrant and multithreading-safe when each\n * thread has its own compressor or decompressor.\n\n * You should not call multiple methods simultaneously on a single\n * compressor or decompressor instance.\n *\n * If this may happen, you can either use one compressor or\n * decompressor per thread, or you can use your preferred lock,\n * semaphore or mutex library to ensure no more than one method on a\n * compressor/decompressor is called simultaneously. libmspack will\n * not do this locking for you.\n *\n * Example of incorrect behaviour:\n * - thread 1 calls mspack_create_cab_decompressor()\n * - thread 1 calls open()\n * - thread 1 calls extract() for one file\n * - thread 2 simultaneously calls extract() for another file\n *\n * Correct behaviour:\n * - thread 1 calls mspack_create_cab_decompressor()\n * - thread 2 calls mspack_create_cab_decompressor()\n * - thread 1 calls its own open() / extract()\n * - thread 2 simultaneously calls its own open() / extract()\n *\n * Also correct behaviour:\n * - thread 1 calls mspack_create_cab_decompressor()\n * - thread 1 locks a mutex for with the decompressor before\n *   calling any methods on it, and unlocks the mutex after each\n *   method returns.\n * - thread 1 can share the results of open() with thread 2, and both\n *   can call extract(), provided they both guard against simultaneous\n *   use of extract(), and any other methods, with the mutex\n */\n\n#ifndef LIB_MSPACK_H\n#define LIB_MSPACK_H 1\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n#include <sys/types.h>\n#include <stdlib.h>\n\n/**\n * System self-test function, to ensure both library and calling program\n * can use one another.\n *\n * A result of MSPACK_ERR_OK means the library and caller are\n * compatible. Any other result indicates that the library and caller are\n * not compatible and should not be used. In particular, a value of\n * MSPACK_ERR_SEEK means the library and caller use different off_t\n * datatypes.\n *\n * It should be used like so:\n *\n * @code\n * int selftest_result;\n * MSPACK_SYS_SELFTEST(selftest_result);\n * if (selftest_result != MSPACK_ERR_OK) {\n *   fprintf(stderr, \"incompatible with this build of libmspack\\n\");\n *   exit(0);\n * }\n * @endcode\n *\n * @param  result   an int variable to store the result of the self-test\n */\n#define MSPACK_SYS_SELFTEST(result)  do { \\\n  (result) = mspack_sys_selftest_internal(sizeof(off_t)); \\\n} while (0)\n\n/** Part of the MSPACK_SYS_SELFTEST() macro, must not be used directly. */\nextern int mspack_sys_selftest_internal(int);\n\n/**\n * Enquire about the binary compatibility version of a specific interface in\n * the library. Currently, the following interfaces are defined:\n *\n * - #MSPACK_VER_LIBRARY: the overall library\n * - #MSPACK_VER_SYSTEM: the mspack_system interface\n * - #MSPACK_VER_MSCABD: the mscab_decompressor interface\n * - #MSPACK_VER_MSCABC: the mscab_compressor interface\n * - #MSPACK_VER_MSCHMD: the mschm_decompressor interface\n * - #MSPACK_VER_MSCHMC: the mschm_compressor interface\n * - #MSPACK_VER_MSLITD: the mslit_decompressor interface\n * - #MSPACK_VER_MSLITC: the mslit_compressor interface\n * - #MSPACK_VER_MSHLPD: the mshlp_decompressor interface\n * - #MSPACK_VER_MSHLPC: the mshlp_compressor interface\n * - #MSPACK_VER_MSSZDDD: the msszdd_decompressor interface\n * - #MSPACK_VER_MSSZDDC: the msszdd_compressor interface\n * - #MSPACK_VER_MSKWAJD: the mskwaj_decompressor interface\n * - #MSPACK_VER_MSKWAJC: the mskwaj_compressor interface\n * - #MSPACK_VER_MSOABD: the msoab_decompressor interface\n * - #MSPACK_VER_MSOABC: the msoab_compressor interface\n *\n * The result of the function should be interpreted as follows:\n * - -1: this interface is completely unknown to the library\n * - 0: this interface is known, but non-functioning\n * - 1: this interface has all basic functionality\n * - 2, 3, ...: this interface has additional functionality, clearly marked\n *   in the documentation as \"version 2\", \"version 3\" and so on.\n *\n * @param entity the interface to request current version of\n * @return the version of the requested interface\n */\nextern int mspack_version(int entity);\n\n/** Pass to mspack_version() to get the overall library version */\n#define MSPACK_VER_LIBRARY   (0)\n/** Pass to mspack_version() to get the mspack_system version */\n#define MSPACK_VER_SYSTEM    (1)\n/** Pass to mspack_version() to get the mscab_decompressor version */\n#define MSPACK_VER_MSCABD    (2)\n/** Pass to mspack_version() to get the mscab_compressor version */\n#define MSPACK_VER_MSCABC    (3)\n/** Pass to mspack_version() to get the mschm_decompressor version */\n#define MSPACK_VER_MSCHMD    (4)\n/** Pass to mspack_version() to get the mschm_compressor version */\n#define MSPACK_VER_MSCHMC    (5)\n/** Pass to mspack_version() to get the mslit_decompressor version */\n#define MSPACK_VER_MSLITD    (6)\n/** Pass to mspack_version() to get the mslit_compressor version */\n#define MSPACK_VER_MSLITC    (7)\n/** Pass to mspack_version() to get the mshlp_decompressor version */\n#define MSPACK_VER_MSHLPD    (8)\n/** Pass to mspack_version() to get the mshlp_compressor version */\n#define MSPACK_VER_MSHLPC    (9)\n/** Pass to mspack_version() to get the msszdd_decompressor version */\n#define MSPACK_VER_MSSZDDD   (10)\n/** Pass to mspack_version() to get the msszdd_compressor version */\n#define MSPACK_VER_MSSZDDC   (11)\n/** Pass to mspack_version() to get the mskwaj_decompressor version */\n#define MSPACK_VER_MSKWAJD   (12)\n/** Pass to mspack_version() to get the mskwaj_compressor version */\n#define MSPACK_VER_MSKWAJC   (13)\n/** Pass to mspack_version() to get the msoab_decompressor version */\n#define MSPACK_VER_MSOABD    (14)\n/** Pass to mspack_version() to get the msoab_compressor version */\n#define MSPACK_VER_MSOABC    (15)\n\n/* --- file I/O abstraction ------------------------------------------------ */\n\n/**\n * A structure which abstracts file I/O and memory management.\n *\n * The library always uses the mspack_system structure for interaction\n * with the file system and to allocate, free and copy all memory. It also\n * uses it to send literal messages to the library user.\n *\n * When the library is compiled normally, passing NULL to a compressor or\n * decompressor constructor will result in a default mspack_system being\n * used, where all methods are implemented with the standard C library.\n * However, all constructors support being given a custom created\n * mspack_system structure, with the library user's own methods. This\n * allows for more abstract interaction, such as reading and writing files\n * directly to memory, or from a network socket or pipe.\n *\n * Implementors of an mspack_system structure should read all\n * documentation entries for every structure member, and write methods\n * which conform to those standards.\n */\nstruct mspack_system {\n  /**\n   * Opens a file for reading, writing, appending or updating.\n   *\n   * @param self     a self-referential pointer to the mspack_system\n   *                 structure whose open() method is being called. If\n   *                 this pointer is required by close(), read(), write(),\n   *                 seek() or tell(), it should be stored in the result\n   *                 structure at this time.\n   * @param filename the file to be opened. It is passed directly from the\n   *                 library caller without being modified, so it is up to\n   *                 the caller what this parameter actually represents.\n   * @param mode     one of #MSPACK_SYS_OPEN_READ (open an existing file\n   *                 for reading), #MSPACK_SYS_OPEN_WRITE (open a new file\n   *                 for writing), #MSPACK_SYS_OPEN_UPDATE (open an existing\n   *                 file for reading/writing from the start of the file) or\n   *                 #MSPACK_SYS_OPEN_APPEND (open an existing file for\n   *                 reading/writing from the end of the file)\n   * @return a pointer to a mspack_file structure. This structure officially\n   *         contains no members, its true contents are up to the\n   *         mspack_system implementor. It should contain whatever is needed\n   *         for other mspack_system methods to operate. Returning the NULL\n   *         pointer indicates an error condition.\n   * @see close(), read(), write(), seek(), tell(), message()\n   */\n  struct mspack_file * (*open)(struct mspack_system *self,\n                               const char *filename,\n                               int mode);\n\n  /**\n   * Closes a previously opened file. If any memory was allocated for this\n   * particular file handle, it should be freed at this time.\n   * \n   * @param file the file to close\n   * @see open()\n   */\n  void (*close)(struct mspack_file *file);\n\n  /**\n   * Reads a given number of bytes from an open file.\n   *\n   * @param file    the file to read from\n   * @param buffer  the location where the read bytes should be stored\n   * @param bytes   the number of bytes to read from the file.\n   * @return the number of bytes successfully read (this can be less than\n   *         the number requested), zero to mark the end of file, or less\n   *         than zero to indicate an error. The library does not \"retry\"\n   *         reads and assumes short reads are due to EOF, so you should\n   *         avoid returning short reads because of transient errors.\n   * @see open(), write()\n   */\n  int (*read)(struct mspack_file *file,\n              void *buffer,\n              int bytes);\n\n  /**\n   * Writes a given number of bytes to an open file.\n   *\n   * @param file    the file to write to\n   * @param buffer  the location where the written bytes should be read from\n   * @param bytes   the number of bytes to write to the file.\n   * @return the number of bytes successfully written, this can be less\n   *         than the number requested. Zero or less can indicate an error\n   *         where no bytes at all could be written. All cases where less\n   *         bytes were written than requested are considered by the library\n   *         to be an error.\n   * @see open(), read()\n   */\n  int (*write)(struct mspack_file *file,\n               void *buffer,\n               int bytes);\n\n  /**\n   * Seeks to a specific file offset within an open file.\n   *\n   * Sometimes the library needs to know the length of a file. It does\n   * this by seeking to the end of the file with seek(file, 0,\n   * MSPACK_SYS_SEEK_END), then calling tell(). Implementations may want\n   * to make a special case for this.\n   *\n   * Due to the potentially varying 32/64 bit datatype off_t on some\n   * architectures, the #MSPACK_SYS_SELFTEST macro MUST be used before\n   * using the library. If not, the error caused by the library passing an\n   * inappropriate stackframe to seek() is subtle and hard to trace.\n   *\n   * @param file   the file to be seeked\n   * @param offset an offset to seek, measured in bytes\n   * @param mode   one of #MSPACK_SYS_SEEK_START (the offset should be\n   *               measured from the start of the file), #MSPACK_SYS_SEEK_CUR\n   *               (the offset should be measured from the current file offset)\n   *               or #MSPACK_SYS_SEEK_END (the offset should be measured from\n   *               the end of the file)\n   * @return zero for success, non-zero for an error\n   * @see open(), tell()\n   */\n  int (*seek)(struct mspack_file *file,\n              off_t offset,\n              int mode);\n\n  /**\n   * Returns the current file position (in bytes) of the given file.\n   *\n   * @param file the file whose file position is wanted\n   * @return the current file position of the file\n   * @see open(), seek()\n   */\n  off_t (*tell)(struct mspack_file *file);\n  \n  /**\n   * Used to send messages from the library to the user.\n   *\n   * Occasionally, the library generates warnings or other messages in\n   * plain english to inform the human user. These are informational only\n   * and can be ignored if not wanted.\n   *\n   * @param file   may be a file handle returned from open() if this message\n   *               pertains to a specific open file, or NULL if not related to\n   *               a specific file.\n   * @param format a printf() style format string. It does NOT include a\n   *               trailing newline.\n   * @see open()\n   */\n  void (*message)(struct mspack_file *file,\n                  const char *format,\n                  ...);\n\n  /**\n   * Allocates memory.\n   *\n   * @param self     a self-referential pointer to the mspack_system\n   *                 structure whose alloc() method is being called.\n   * @param bytes    the number of bytes to allocate\n   * @result a pointer to the requested number of bytes, or NULL if\n   *         not enough memory is available\n   * @see free()\n   */\n  void * (*alloc)(struct mspack_system *self,\n                  size_t bytes);\n  \n  /**\n   * Frees memory.\n   * \n   * @param ptr the memory to be freed. NULL is accepted and ignored.\n   * @see alloc()\n   */\n  void (*free)(void *ptr);\n\n  /**\n   * Copies from one region of memory to another.\n   * \n   * The regions of memory are guaranteed not to overlap, are usually less\n   * than 256 bytes, and may not be aligned. Please note that the source\n   * parameter comes before the destination parameter, unlike the standard\n   * C function memcpy().\n   *\n   * @param src   the region of memory to copy from\n   * @param dest  the region of memory to copy to\n   * @param bytes the size of the memory region, in bytes\n   */\n  void (*copy)(void *src,\n               void *dest,\n               size_t bytes);\n\n  /**\n   * A null pointer to mark the end of mspack_system. It must equal NULL.\n   *\n   * Should the mspack_system structure extend in the future, this NULL\n   * will be seen, rather than have an invalid method pointer called.\n   */\n  void *null_ptr;\n};\n\n/** mspack_system::open() mode: open existing file for reading. */\n#define MSPACK_SYS_OPEN_READ   (0)\n/** mspack_system::open() mode: open new file for writing */\n#define MSPACK_SYS_OPEN_WRITE  (1)\n/** mspack_system::open() mode: open existing file for writing */\n#define MSPACK_SYS_OPEN_UPDATE (2)\n/** mspack_system::open() mode: open existing file for writing */\n#define MSPACK_SYS_OPEN_APPEND (3)\n\n/** mspack_system::seek() mode: seek relative to start of file */\n#define MSPACK_SYS_SEEK_START  (0)\n/** mspack_system::seek() mode: seek relative to current offset */\n#define MSPACK_SYS_SEEK_CUR    (1)\n/** mspack_system::seek() mode: seek relative to end of file */\n#define MSPACK_SYS_SEEK_END    (2)\n\n/** \n * A structure which represents an open file handle. The contents of this\n * structure are determined by the implementation of the\n * mspack_system::open() method.\n */\nstruct mspack_file {\n  int dummy;\n};\n\n/* --- error codes --------------------------------------------------------- */\n\n/** Error code: no error */\n#define MSPACK_ERR_OK          (0)\n/** Error code: bad arguments to method */\n#define MSPACK_ERR_ARGS        (1)\n/** Error code: error opening file */\n#define MSPACK_ERR_OPEN        (2)\n/** Error code: error reading file */\n#define MSPACK_ERR_READ        (3)\n/** Error code: error writing file */\n#define MSPACK_ERR_WRITE       (4)\n/** Error code: seek error */\n#define MSPACK_ERR_SEEK        (5)\n/** Error code: out of memory */\n#define MSPACK_ERR_NOMEMORY    (6)\n/** Error code: bad \"magic id\" in file */\n#define MSPACK_ERR_SIGNATURE   (7)\n/** Error code: bad or corrupt file format */\n#define MSPACK_ERR_DATAFORMAT  (8)\n/** Error code: bad checksum or CRC */\n#define MSPACK_ERR_CHECKSUM    (9)\n/** Error code: error during compression */\n#define MSPACK_ERR_CRUNCH      (10)\n/** Error code: error during decompression */\n#define MSPACK_ERR_DECRUNCH    (11)\n\n/* --- functions available in library -------------------------------------- */\n\n/** Creates a new CAB compressor.\n * @param sys a custom mspack_system structure, or NULL to use the default\n * @return a #mscab_compressor or NULL\n */\nextern struct mscab_compressor *\n  mspack_create_cab_compressor(struct mspack_system *sys);\n\n/** Creates a new CAB decompressor.\n * @param sys a custom mspack_system structure, or NULL to use the default\n * @return a #mscab_decompressor or NULL\n */\nextern struct mscab_decompressor *\n  mspack_create_cab_decompressor(struct mspack_system *sys);\n\n/** Destroys an existing CAB compressor.\n * @param self the #mscab_compressor to destroy\n */\nextern void mspack_destroy_cab_compressor(struct mscab_compressor *self);\n\n/** Destroys an existing CAB decompressor.\n * @param self the #mscab_decompressor to destroy\n */\nextern void mspack_destroy_cab_decompressor(struct mscab_decompressor *self);\n\n\n/** Creates a new CHM compressor.\n * @param sys a custom mspack_system structure, or NULL to use the default\n * @return a #mschm_compressor or NULL\n */\nextern struct mschm_compressor *\n  mspack_create_chm_compressor(struct mspack_system *sys);\n\n/** Creates a new CHM decompressor.\n * @param sys a custom mspack_system structure, or NULL to use the default\n * @return a #mschm_decompressor or NULL\n */\nextern struct mschm_decompressor *\n  mspack_create_chm_decompressor(struct mspack_system *sys);\n\n/** Destroys an existing CHM compressor.\n * @param self the #mschm_compressor to destroy\n */\nextern void mspack_destroy_chm_compressor(struct mschm_compressor *self);\n\n/** Destroys an existing CHM decompressor.\n * @param self the #mschm_decompressor to destroy\n */\nextern void mspack_destroy_chm_decompressor(struct mschm_decompressor *self);\n\n\n/** Creates a new LIT compressor.\n * @param sys a custom mspack_system structure, or NULL to use the default\n * @return a #mslit_compressor or NULL\n */\nextern struct mslit_compressor *\n  mspack_create_lit_compressor(struct mspack_system *sys);\n\n/** Creates a new LIT decompressor.\n * @param sys a custom mspack_system structure, or NULL to use the default\n * @return a #mslit_decompressor or NULL\n */\nextern struct mslit_decompressor *\n  mspack_create_lit_decompressor(struct mspack_system *sys);\n\n/** Destroys an existing LIT compressor.\n * @param self the #mslit_compressor to destroy\n */\nextern void mspack_destroy_lit_compressor(struct mslit_compressor *self);\n\n/** Destroys an existing LIT decompressor.\n * @param self the #mslit_decompressor to destroy\n */\nextern void mspack_destroy_lit_decompressor(struct mslit_decompressor *self);\n\n\n/** Creates a new HLP compressor.\n * @param sys a custom mspack_system structure, or NULL to use the default\n * @return a #mshlp_compressor or NULL\n */\nextern struct mshlp_compressor *\n  mspack_create_hlp_compressor(struct mspack_system *sys);\n\n/** Creates a new HLP decompressor.\n * @param sys a custom mspack_system structure, or NULL to use the default\n * @return a #mshlp_decompressor or NULL\n */\nextern struct mshlp_decompressor *\n  mspack_create_hlp_decompressor(struct mspack_system *sys);\n\n/** Destroys an existing hlp compressor.\n * @param self the #mshlp_compressor to destroy\n */\nextern void mspack_destroy_hlp_compressor(struct mshlp_compressor *self);\n\n/** Destroys an existing hlp decompressor.\n * @param self the #mshlp_decompressor to destroy\n */\nextern void mspack_destroy_hlp_decompressor(struct mshlp_decompressor *self);\n\n\n/** Creates a new SZDD compressor.\n * @param sys a custom mspack_system structure, or NULL to use the default\n * @return a #msszdd_compressor or NULL\n */\nextern struct msszdd_compressor *\n  mspack_create_szdd_compressor(struct mspack_system *sys);\n\n/** Creates a new SZDD decompressor.\n * @param sys a custom mspack_system structure, or NULL to use the default\n * @return a #msszdd_decompressor or NULL\n */\nextern struct msszdd_decompressor *\n  mspack_create_szdd_decompressor(struct mspack_system *sys);\n\n/** Destroys an existing SZDD compressor.\n * @param self the #msszdd_compressor to destroy\n */\nextern void mspack_destroy_szdd_compressor(struct msszdd_compressor *self);\n\n/** Destroys an existing SZDD decompressor.\n * @param self the #msszdd_decompressor to destroy\n */\nextern void mspack_destroy_szdd_decompressor(struct msszdd_decompressor *self);\n\n\n/** Creates a new KWAJ compressor.\n * @param sys a custom mspack_system structure, or NULL to use the default\n * @return a #mskwaj_compressor or NULL\n */\nextern struct mskwaj_compressor *\n  mspack_create_kwaj_compressor(struct mspack_system *sys);\n\n/** Creates a new KWAJ decompressor.\n * @param sys a custom mspack_system structure, or NULL to use the default\n * @return a #mskwaj_decompressor or NULL\n */\nextern struct mskwaj_decompressor *\n  mspack_create_kwaj_decompressor(struct mspack_system *sys);\n\n/** Destroys an existing KWAJ compressor.\n * @param self the #mskwaj_compressor to destroy\n */\nextern void mspack_destroy_kwaj_compressor(struct mskwaj_compressor *self);\n\n/** Destroys an existing KWAJ decompressor.\n * @param self the #mskwaj_decompressor to destroy\n */\nextern void mspack_destroy_kwaj_decompressor(struct mskwaj_decompressor *self);\n\n\n/** Creates a new OAB compressor.\n * @param sys a custom mspack_system structure, or NULL to use the default\n * @return a #msoab_compressor or NULL\n */\nextern struct msoab_compressor *\n  mspack_create_oab_compressor(struct mspack_system *sys);\n\n/** Creates a new OAB decompressor.\n * @param sys a custom mspack_system structure, or NULL to use the default\n * @return a #msoab_decompressor or NULL\n */\nextern struct msoab_decompressor *\n  mspack_create_oab_decompressor(struct mspack_system *sys);\n\n/** Destroys an existing OAB compressor.\n * @param self the #msoab_compressor to destroy\n */\nextern void mspack_destroy_oab_compressor(struct msoab_compressor *self);\n\n/** Destroys an existing OAB decompressor.\n * @param self the #msoab_decompressor to destroy\n */\nextern void mspack_destroy_oab_decompressor(struct msoab_decompressor *self);\n\n\n/* --- support for .CAB (MS Cabinet) file format --------------------------- */\n\n/**\n * A structure which represents a single cabinet file.\n *\n * All fields are READ ONLY.\n *\n * If this cabinet is part of a merged cabinet set, the #files and #folders\n * fields are common to all cabinets in the set, and will be identical.\n *\n * @see mscab_decompressor::open(), mscab_decompressor::close(),\n *      mscab_decompressor::search()\n */\nstruct mscabd_cabinet {\n  /**\n   * The next cabinet in a chained list, if this cabinet was opened with\n   * mscab_decompressor::search(). May be NULL to mark the end of the\n   * list.\n   */\n  struct mscabd_cabinet *next;\n\n  /**\n   * The filename of the cabinet. More correctly, the filename of the\n   * physical file that the cabinet resides in. This is given by the\n   * library user and may be in any format.\n   */\n  const char *filename;\n  \n  /** The file offset of cabinet within the physical file it resides in. */\n  off_t base_offset;\n\n  /** The length of the cabinet file in bytes. */\n  unsigned int length;\n\n  /** The previous cabinet in a cabinet set, or NULL. */\n  struct mscabd_cabinet *prevcab;\n\n  /** The next cabinet in a cabinet set, or NULL. */\n  struct mscabd_cabinet *nextcab;\n\n  /** The filename of the previous cabinet in a cabinet set, or NULL. */\n  char *prevname;\n\n  /** The filename of the next cabinet in a cabinet set, or NULL. */\n  char *nextname;\n\n  /** The name of the disk containing the previous cabinet in a cabinet\n   * set, or NULL.\n   */\n  char *previnfo;\n\n  /** The name of the disk containing the next cabinet in a cabinet set,\n   * or NULL.\n   */\n  char *nextinfo;\n\n  /** A list of all files in the cabinet or cabinet set. */\n  struct mscabd_file *files;\n\n  /** A list of all folders in the cabinet or cabinet set. */\n  struct mscabd_folder *folders;\n\n  /** \n   * The set ID of the cabinet. All cabinets in the same set should have\n   * the same set ID.\n   */\n  unsigned short set_id;\n\n  /**\n   * The index number of the cabinet within the set. Numbering should\n   * start from 0 for the first cabinet in the set, and increment by 1 for\n   * each following cabinet.\n   */\n  unsigned short set_index;\n\n  /**\n   * The number of bytes reserved in the header area of the cabinet.\n   *\n   * If this is non-zero and flags has MSCAB_HDR_RESV set, this data can\n   * be read by the calling application. It is of the given length,\n   * located at offset (base_offset + MSCAB_HDR_RESV_OFFSET) in the\n   * cabinet file.\n   *\n   * @see flags\n   */\n  unsigned short header_resv;\n\n  /**\n   * Header flags.\n   *\n   * - MSCAB_HDR_PREVCAB indicates the cabinet is part of a cabinet set, and\n   *                     has a predecessor cabinet.\n   * - MSCAB_HDR_NEXTCAB indicates the cabinet is part of a cabinet set, and\n   *                     has a successor cabinet.\n   * - MSCAB_HDR_RESV indicates the cabinet has reserved header space.\n   *\n   * @see prevname, previnfo, nextname, nextinfo, header_resv\n   */\n  int flags;\n};\n\n/** Offset from start of cabinet to the reserved header data (if present). */\n#define MSCAB_HDR_RESV_OFFSET (0x28)\n\n/** Cabinet header flag: cabinet has a predecessor */\n#define MSCAB_HDR_PREVCAB (0x01)\n/** Cabinet header flag: cabinet has a successor */\n#define MSCAB_HDR_NEXTCAB (0x02)\n/** Cabinet header flag: cabinet has reserved header space */\n#define MSCAB_HDR_RESV    (0x04)\n\n/**\n * A structure which represents a single folder in a cabinet or cabinet set.\n *\n * All fields are READ ONLY.\n *\n * A folder is a single compressed stream of data. When uncompressed, it\n * holds the data of one or more files. A folder may be split across more\n * than one cabinet.\n */\nstruct mscabd_folder {\n  /**\n   * A pointer to the next folder in this cabinet or cabinet set, or NULL\n   * if this is the final folder.\n   */\n  struct mscabd_folder *next;\n\n  /** \n   * The compression format used by this folder.\n   *\n   * The macro MSCABD_COMP_METHOD() should be used on this field to get\n   * the algorithm used. The macro MSCABD_COMP_LEVEL() should be used to get\n   * the \"compression level\".\n   *\n   * @see MSCABD_COMP_METHOD(), MSCABD_COMP_LEVEL()\n   */\n  int comp_type;\n\n  /**\n   * The total number of data blocks used by this folder. This includes\n   * data blocks present in other files, if this folder spans more than\n   * one cabinet.\n   */\n  unsigned int num_blocks;\n};\n\n/**\n * Returns the compression method used by a folder.\n *\n * @param comp_type a mscabd_folder::comp_type value\n * @return one of #MSCAB_COMP_NONE, #MSCAB_COMP_MSZIP, #MSCAB_COMP_QUANTUM\n *         or #MSCAB_COMP_LZX\n */\n#define MSCABD_COMP_METHOD(comp_type) ((comp_type) & 0x0F)\n/**\n * Returns the compression level used by a folder.\n *\n * @param comp_type a mscabd_folder::comp_type value\n * @return the compression level. This is only defined by LZX and Quantum\n *         compression\n */\n#define MSCABD_COMP_LEVEL(comp_type) (((comp_type) >> 8) & 0x1F)\n\n/** Compression mode: no compression. */\n#define MSCAB_COMP_NONE       (0)\n/** Compression mode: MSZIP (deflate) compression. */\n#define MSCAB_COMP_MSZIP      (1)\n/** Compression mode: Quantum compression */\n#define MSCAB_COMP_QUANTUM    (2)\n/** Compression mode: LZX compression */\n#define MSCAB_COMP_LZX        (3)\n\n/**\n * A structure which represents a single file in a cabinet or cabinet set.\n *\n * All fields are READ ONLY.\n */\nstruct mscabd_file {\n  /**\n   * The next file in the cabinet or cabinet set, or NULL if this is the\n   * final file.\n   */\n  struct mscabd_file *next;\n\n  /**\n   * The filename of the file.\n   *\n   * A null terminated string of up to 255 bytes in length, it may be in\n   * either ISO-8859-1 or UTF8 format, depending on the file attributes.\n   *\n   * @see attribs\n   */\n  char *filename;\n\n  /** The uncompressed length of the file, in bytes. */\n  unsigned int length;\n\n  /**\n   * File attributes.\n   *\n   * The following attributes are defined:\n   * - #MSCAB_ATTRIB_RDONLY indicates the file is write protected.\n   * - #MSCAB_ATTRIB_HIDDEN indicates the file is hidden.\n   * - #MSCAB_ATTRIB_SYSTEM indicates the file is a operating system file.\n   * - #MSCAB_ATTRIB_ARCH indicates the file is \"archived\".\n   * - #MSCAB_ATTRIB_EXEC indicates the file is an executable program.\n   * - #MSCAB_ATTRIB_UTF_NAME indicates the filename is in UTF8 format rather\n   *   than ISO-8859-1.\n   */\n  int attribs;\n\n  /** File's last modified time, hour field. */\n  char time_h;\n  /** File's last modified time, minute field. */\n  char time_m;\n  /** File's last modified time, second field. */\n  char time_s;\n\n  /** File's last modified date, day field. */\n  char date_d;\n  /** File's last modified date, month field. */\n  char date_m;\n  /** File's last modified date, year field. */\n  int date_y;\n\n  /** A pointer to the folder that contains this file. */\n  struct mscabd_folder *folder;\n\n  /** The uncompressed offset of this file in its folder. */\n  unsigned int offset;\n};\n\n/** mscabd_file::attribs attribute: file is read-only. */\n#define MSCAB_ATTRIB_RDONLY   (0x01)\n/** mscabd_file::attribs attribute: file is hidden. */\n#define MSCAB_ATTRIB_HIDDEN   (0x02)\n/** mscabd_file::attribs attribute: file is an operating system file. */\n#define MSCAB_ATTRIB_SYSTEM   (0x04)\n/** mscabd_file::attribs attribute: file is \"archived\". */\n#define MSCAB_ATTRIB_ARCH     (0x20)\n/** mscabd_file::attribs attribute: file is an executable program. */\n#define MSCAB_ATTRIB_EXEC     (0x40)\n/** mscabd_file::attribs attribute: filename is UTF8, not ISO-8859-1. */\n#define MSCAB_ATTRIB_UTF_NAME (0x80)\n\n/** mscab_decompressor::set_param() parameter: search buffer size. */\n#define MSCABD_PARAM_SEARCHBUF (0)\n/** mscab_decompressor::set_param() parameter: repair MS-ZIP streams? */\n#define MSCABD_PARAM_FIXMSZIP  (1)\n/** mscab_decompressor::set_param() parameter: size of decompression buffer */\n#define MSCABD_PARAM_DECOMPBUF (2)\n/** mscab_decompressor::set_param() parameter: salvage data from bad cabinets?\n * If enabled, open() will skip file with bad folder indices or filenames\n * rather than reject the whole cabinet, and extract() will limit rather than\n * reject files with invalid offsets and lengths, and bad data block checksums\n * will be ignored. Available only in CAB decoder version 2 and above.\n */\n#define MSCABD_PARAM_SALVAGE   (3)\n\n/** TODO */\nstruct mscab_compressor {\n  int dummy; \n};\n\n/**\n * A decompressor for .CAB (Microsoft Cabinet) files\n *\n * All fields are READ ONLY.\n *\n * @see mspack_create_cab_decompressor(), mspack_destroy_cab_decompressor()\n */\nstruct mscab_decompressor {\n  /**\n   * Opens a cabinet file and reads its contents.\n   *\n   * If the file opened is a valid cabinet file, all headers will be read\n   * and a mscabd_cabinet structure will be returned, with a full list of\n   * folders and files.\n   *\n   * In the case of an error occuring, NULL is returned and the error code\n   * is available from last_error().\n   *\n   * The filename pointer should be considered \"in use\" until close() is\n   * called on the cabinet.\n   *\n   * @param  self     a self-referential pointer to the mscab_decompressor\n   *                  instance being called\n   * @param  filename the filename of the cabinet file. This is passed\n   *                  directly to mspack_system::open().\n   * @return a pointer to a mscabd_cabinet structure, or NULL on failure\n   * @see close(), search(), last_error()\n   */\n  struct mscabd_cabinet * (*open) (struct mscab_decompressor *self,\n                                   const char *filename);\n\n  /**\n   * Closes a previously opened cabinet or cabinet set.\n   *\n   * This closes a cabinet, all cabinets associated with it via the\n   * mscabd_cabinet::next, mscabd_cabinet::prevcab and\n   * mscabd_cabinet::nextcab pointers, and all folders and files. All\n   * memory used by these entities is freed.\n   *\n   * The cabinet pointer is now invalid and cannot be used again. All\n   * mscabd_folder and mscabd_file pointers from that cabinet or cabinet\n   * set are also now invalid, and cannot be used again.\n   *\n   * If the cabinet pointer given was created using search(), it MUST be\n   * the cabinet pointer returned by search() and not one of the later\n   * cabinet pointers further along the mscabd_cabinet::next chain.\n\n   * If extra cabinets have been added using append() or prepend(), these\n   * will all be freed, even if the cabinet pointer given is not the first\n   * cabinet in the set. Do NOT close() more than one cabinet in the set.\n   *\n   * The mscabd_cabinet::filename is not freed by the library, as it is\n   * not allocated by the library. The caller should free this itself if\n   * necessary, before it is lost forever.\n   *\n   * @param  self     a self-referential pointer to the mscab_decompressor\n   *                  instance being called\n   * @param  cab      the cabinet to close\n   * @see open(), search(), append(), prepend()\n   */\n  void (*close)(struct mscab_decompressor *self,\n                struct mscabd_cabinet *cab);\n\n  /**\n   * Searches a regular file for embedded cabinets.\n   *\n   * This opens a normal file with the given filename and will search the\n   * entire file for embedded cabinet files\n   *\n   * If any cabinets are found, the equivalent of open() is called on each\n   * potential cabinet file at the offset it was found. All successfully\n   * open()ed cabinets are kept in a list.\n   *\n   * The first cabinet found will be returned directly as the result of\n   * this method. Any further cabinets found will be chained in a list\n   * using the mscabd_cabinet::next field.\n   *\n   * In the case of an error occuring anywhere other than the simulated\n   * open(), NULL is returned and the error code is available from\n   * last_error().\n   *\n   * If no error occurs, but no cabinets can be found in the file, NULL is\n   * returned and last_error() returns MSPACK_ERR_OK.\n   *\n   * The filename pointer should be considered in use until close() is\n   * called on the cabinet.\n   *\n   * close() should only be called on the result of search(), not on any\n   * subsequent cabinets in the mscabd_cabinet::next chain.\n   *\n   * @param  self     a self-referential pointer to the mscab_decompressor\n   *                  instance being called\n   * @param  filename the filename of the file to search for cabinets. This\n   *                  is passed directly to mspack_system::open().\n   * @return a pointer to a mscabd_cabinet structure, or NULL\n   * @see close(), open(), last_error()\n   */\n  struct mscabd_cabinet * (*search) (struct mscab_decompressor *self,\n                                     const char *filename);\n\n  /**\n   * Appends one mscabd_cabinet to another, forming or extending a cabinet\n   * set.\n   *\n   * This will attempt to append one cabinet to another such that\n   * <tt>(cab->nextcab == nextcab) && (nextcab->prevcab == cab)</tt> and\n   * any folders split between the two cabinets are merged.\n   *\n   * The cabinets MUST be part of a cabinet set -- a cabinet set is a\n   * cabinet that spans more than one physical cabinet file on disk -- and\n   * must be appropriately matched.\n   *\n   * It can be determined if a cabinet has further parts to load by\n   * examining the mscabd_cabinet::flags field:\n   *\n   * - if <tt>(flags & MSCAB_HDR_PREVCAB)</tt> is non-zero, there is a\n   *   predecessor cabinet to open() and prepend(). Its MS-DOS\n   *   case-insensitive filename is mscabd_cabinet::prevname\n   * - if <tt>(flags & MSCAB_HDR_NEXTCAB)</tt> is non-zero, there is a\n   *   successor cabinet to open() and append(). Its MS-DOS case-insensitive\n   *   filename is mscabd_cabinet::nextname\n   *\n   * If the cabinets do not match, an error code will be returned. Neither\n   * cabinet has been altered, and both should be closed seperately.\n   *\n   * Files and folders in a cabinet set are a single entity. All cabinets\n   * in a set use the same file list, which is updated as cabinets in the\n   * set are added. All pointers to mscabd_folder and mscabd_file\n   * structures in either cabinet must be discarded and re-obtained after\n   * merging.\n   *\n   * @param  self     a self-referential pointer to the mscab_decompressor\n   *                  instance being called\n   * @param  cab      the cabinet which will be appended to,\n   *                  predecessor of nextcab\n   * @param  nextcab  the cabinet which will be appended,\n   *                  successor of cab\n   * @return an error code, or MSPACK_ERR_OK if successful\n   * @see prepend(), open(), close()\n   */\n  int (*append) (struct mscab_decompressor *self,\n                 struct mscabd_cabinet *cab,\n                 struct mscabd_cabinet *nextcab);\n\n  /**\n   * Prepends one mscabd_cabinet to another, forming or extending a\n   * cabinet set.\n   *\n   * This will attempt to prepend one cabinet to another, such that\n   * <tt>(cab->prevcab == prevcab) && (prevcab->nextcab == cab)</tt>. In\n   * all other respects, it is identical to append(). See append() for the\n   * full documentation.\n   *\n   * @param  self     a self-referential pointer to the mscab_decompressor\n   *                  instance being called\n   * @param  cab      the cabinet which will be prepended to,\n   *                  successor of prevcab\n   * @param  prevcab  the cabinet which will be prepended,\n   *                  predecessor of cab\n   * @return an error code, or MSPACK_ERR_OK if successful\n   * @see append(), open(), close()\n   */\n  int (*prepend) (struct mscab_decompressor *self,\n                  struct mscabd_cabinet *cab,\n                  struct mscabd_cabinet *prevcab);\n\n  /**\n   * Extracts a file from a cabinet or cabinet set.\n   *\n   * This extracts a compressed file in a cabinet and writes it to the given\n   * filename.\n   *\n   * The MS-DOS filename of the file, mscabd_file::filename, is NOT USED\n   * by extract(). The caller must examine this MS-DOS filename, copy and\n   * change it as necessary, create directories as necessary, and provide\n   * the correct filename as a parameter, which will be passed unchanged\n   * to the decompressor's mspack_system::open()\n   *\n   * If the file belongs to a split folder in a multi-part cabinet set,\n   * and not enough parts of the cabinet set have been loaded and appended\n   * or prepended, an error will be returned immediately.\n   *\n   * @param  self     a self-referential pointer to the mscab_decompressor\n   *                  instance being called\n   * @param  file     the file to be decompressed\n   * @param  filename the filename of the file being written to\n   * @return an error code, or MSPACK_ERR_OK if successful\n   */\n  int (*extract)(struct mscab_decompressor *self,\n                 struct mscabd_file *file,\n                 const char *filename);\n\n  /**\n   * Sets a CAB decompression engine parameter.\n   *\n   * The following parameters are defined:\n   * - #MSCABD_PARAM_SEARCHBUF: How many bytes should be allocated as a\n   *   buffer when using search()? The minimum value is 4.  The default\n   *   value is 32768.\n   * - #MSCABD_PARAM_FIXMSZIP: If non-zero, extract() will ignore bad\n   *   checksums and recover from decompression errors in MS-ZIP\n   *   compressed folders. The default value is 0 (don't recover).\n   * - #MSCABD_PARAM_DECOMPBUF: How many bytes should be used as an input\n   *   bit buffer by decompressors? The minimum value is 4. The default\n   *   value is 4096.\n   *\n   * @param  self     a self-referential pointer to the mscab_decompressor\n   *                  instance being called\n   * @param  param    the parameter to set\n   * @param  value    the value to set the parameter to\n   * @return MSPACK_ERR_OK if all is OK, or MSPACK_ERR_ARGS if there\n   *         is a problem with either parameter or value.\n   * @see search(), extract()\n   */\n  int (*set_param)(struct mscab_decompressor *self,\n                   int param,\n                   int value);\n\n  /**\n   * Returns the error code set by the most recently called method.\n   *\n   * This is useful for open() and search(), which do not return an error\n   * code directly.\n   *\n   * @param  self     a self-referential pointer to the mscab_decompressor\n   *                  instance being called\n   * @return the most recent error code\n   * @see open(), search()\n   */\n  int (*last_error)(struct mscab_decompressor *self);\n};\n\n/* --- support for .CHM (HTMLHelp) file format ----------------------------- */\n\n/**\n * A structure which represents a file to be placed in a CHM helpfile.\n *\n * A contiguous array of these structures should be passed to\n * mschm_compressor::generate(). The array list is terminated with an\n * entry whose mschmc_file::section field is set to #MSCHMC_ENDLIST, the\n * other fields in this entry are ignored.\n */\nstruct mschmc_file {\n  /** One of #MSCHMC_ENDLIST, #MSCHMC_UNCOMP or #MSCHMC_MSCOMP. */\n  int section;\n\n  /** The filename of the source file that will be added to the CHM. This\n   * is passed directly to mspack_system::open(). */\n  const char *filename;\n\n  /** The full path and filename of the file within the CHM helpfile, a\n   * UTF-1 encoded null-terminated string. */\n  char *chm_filename;\n\n  /** The length of the file, in bytes. This will be adhered to strictly\n   * and a read error will be issued if this many bytes cannot be read\n   * from the real file at CHM generation time. */\n  off_t length;\n};\n\n/**\n * A structure which represents a section of a CHM helpfile.\n *\n * All fields are READ ONLY.\n *\n * Not used directly, but used as a generic base type for\n * mschmd_sec_uncompressed and mschmd_sec_mscompressed.\n */\nstruct mschmd_section {\n  /** A pointer to the CHM helpfile that contains this section. */\n  struct mschmd_header *chm;\n\n  /**\n   * The section ID. Either 0 for the uncompressed section\n   * mschmd_sec_uncompressed, or 1 for the LZX compressed section\n   * mschmd_sec_mscompressed. No other section IDs are known.\n   */\n  unsigned int id;\n};\n\n/**\n * A structure which represents the uncompressed section of a CHM helpfile.\n * \n * All fields are READ ONLY.\n */\nstruct mschmd_sec_uncompressed {\n  /** Generic section data. */\n  struct mschmd_section base;\n\n  /** The file offset of where this section begins in the CHM helpfile. */\n  off_t offset;\n};\n\n/**\n * A structure which represents the LZX compressed section of a CHM helpfile. \n * \n * All fields are READ ONLY.\n */\nstruct mschmd_sec_mscompressed {\n  /** Generic section data. */\n  struct mschmd_section base;\n\n  /** A pointer to the meta-file which represents all LZX compressed data. */\n  struct mschmd_file *content;\n\n  /** A pointer to the file which contains the LZX control data. */\n  struct mschmd_file *control;\n\n  /** A pointer to the file which contains the LZX reset table. */\n  struct mschmd_file *rtable;\n\n  /** A pointer to the file which contains the LZX span information.\n   * Available only in CHM decoder version 2 and above.\n   */\n  struct mschmd_file *spaninfo;\n};\n\n/**\n * A structure which represents a CHM helpfile.\n * \n * All fields are READ ONLY.\n */\nstruct mschmd_header {\n  /** The version of the CHM file format used in this file. */\n  unsigned int version;\n\n  /**\n   * The \"timestamp\" of the CHM helpfile. \n   *\n   * It is the lower 32 bits of a 64-bit value representing the number of\n   * centiseconds since 1601-01-01 00:00:00 UTC, plus 42. It is not useful\n   * as a timestamp, but it is useful as a semi-unique ID.\n   */\n  unsigned int timestamp;\n      \n  /**\n   * The default Language and Country ID (LCID) of the user who ran the\n   * HTMLHelp Compiler. This is not the language of the CHM file itself.\n   */\n  unsigned int language;\n\n  /**\n   * The filename of the CHM helpfile. This is given by the library user\n   * and may be in any format.\n   */\n  const char *filename;\n\n  /** The length of the CHM helpfile, in bytes. */\n  off_t length;\n\n  /** A list of all non-system files in the CHM helpfile. */\n  struct mschmd_file *files;\n\n  /**\n   * A list of all system files in the CHM helpfile.\n   *\n   * System files are files which begin with \"::\". They are meta-files\n   * generated by the CHM creation process.\n   */\n  struct mschmd_file *sysfiles;\n\n  /** The section 0 (uncompressed) data in this CHM helpfile. */\n  struct mschmd_sec_uncompressed sec0;\n\n  /** The section 1 (MSCompressed) data in this CHM helpfile. */\n  struct mschmd_sec_mscompressed sec1;\n\n  /** The file offset of the first PMGL/PMGI directory chunk. */\n  off_t dir_offset;\n\n  /** The number of PMGL/PMGI directory chunks in this CHM helpfile. */\n  unsigned int num_chunks;\n\n  /** The size of each PMGL/PMGI chunk, in bytes. */\n  unsigned int chunk_size;\n\n  /** The \"density\" of the quick-reference section in PMGL/PMGI chunks. */\n  unsigned int density;\n\n  /** The depth of the index tree.\n   *\n   * - if 1, there are no PMGI chunks, only PMGL chunks.\n   * - if 2, there is 1 PMGI chunk. All chunk indices point to PMGL chunks.\n   * - if 3, the root PMGI chunk points to secondary PMGI chunks, which in\n   *         turn point to PMGL chunks.\n   * - and so on...\n   */\n  unsigned int depth;\n\n  /**\n   * The number of the root PMGI chunk.\n   *\n   * If there is no index in the CHM helpfile, this will be 0xFFFFFFFF.\n   */\n  unsigned int index_root;\n\n  /**\n   * The number of the first PMGL chunk. Usually zero.\n   * Available only in CHM decoder version 2 and above.\n   */\n  unsigned int first_pmgl;\n\n  /**\n   * The number of the last PMGL chunk. Usually num_chunks-1.\n   * Available only in CHM decoder version 2 and above.\n   */\n  unsigned int last_pmgl;\n\n  /**\n   * A cache of loaded chunks, filled in by mschm_decoder::fast_find().\n   * Available only in CHM decoder version 2 and above.\n   */\n  unsigned char **chunk_cache;\n};\n\n/**\n * A structure which represents a file stored in a CHM helpfile.\n * \n * All fields are READ ONLY.\n */\nstruct mschmd_file {\n  /**\n   * A pointer to the next file in the list, or NULL if this is the final\n   * file.\n   */\n  struct mschmd_file *next;\n\n  /**\n   * A pointer to the section that this file is located in. Indirectly,\n   * it also points to the CHM helpfile the file is located in.\n   */\n  struct mschmd_section *section;\n\n  /** The offset within the section data that this file is located at. */\n  off_t offset;\n\n  /** The length of this file, in bytes */\n  off_t length;\n\n  /** The filename of this file -- a null terminated string in UTF-8. */\n  char *filename;\n};\n\n/** mschmc_file::section value: end of CHM file list */\n#define MSCHMC_ENDLIST   (0)\n/** mschmc_file::section value: this file is in the Uncompressed section */\n#define MSCHMC_UNCOMP    (1)\n/** mschmc_file::section value: this file is in the MSCompressed section */\n#define MSCHMC_MSCOMP    (2)\n \n/** mschm_compressor::set_param() parameter: \"timestamp\" header */\n#define MSCHMC_PARAM_TIMESTAMP  (0)\n/** mschm_compressor::set_param() parameter: \"language\" header */\n#define MSCHMC_PARAM_LANGUAGE   (1)\n/** mschm_compressor::set_param() parameter: LZX window size */\n#define MSCHMC_PARAM_LZXWINDOW  (2)\n/** mschm_compressor::set_param() parameter: intra-chunk quickref density */\n#define MSCHMC_PARAM_DENSITY    (3)\n/** mschm_compressor::set_param() parameter: whether to create indices */\n#define MSCHMC_PARAM_INDEX      (4)\n\n/**\n * A compressor for .CHM (Microsoft HTMLHelp) files.\n *\n * All fields are READ ONLY.\n *\n * @see mspack_create_chm_compressor(), mspack_destroy_chm_compressor()\n */\nstruct mschm_compressor {\n  /**\n   * Generates a CHM help file.\n   *\n   * The help file will contain up to two sections, an Uncompressed\n   * section and potentially an MSCompressed (LZX compressed)\n   * section.\n   *\n   * While the contents listing of a CHM file is always in lexical order,\n   * the file list passed in will be taken as the correct order for files\n   * within the sections.  It is in your interest to place similar files\n   * together for better compression.\n   *\n   * There are two modes of generation, to use a temporary file or not to\n   * use one. See use_temporary_file() for the behaviour of generate() in\n   * these two different modes.\n   *\n   * @param  self        a self-referential pointer to the mschm_compressor\n   *                     instance being called\n   * @param  file_list   an array of mschmc_file structures, terminated\n   *                     with an entry whose mschmc_file::section field is\n   *                     #MSCHMC_ENDLIST. The order of the list is\n   *                     preserved within each section. The length of any\n   *                     mschmc_file::chm_filename string cannot exceed\n   *                     roughly 4096 bytes. Each source file must be able\n   *                     to supply as many bytes as given in the\n   *                     mschmc_file::length field.\n   * @param  output_file the file to write the generated CHM helpfile to.\n   *                     This is passed directly to mspack_system::open()\n   * @return an error code, or MSPACK_ERR_OK if successful\n   * @see use_temporary_file() set_param()\n   */\n  int (*generate)(struct mschm_compressor *self,\n                  struct mschmc_file file_list[],\n                  const char *output_file);\n\n  /**\n   * Specifies whether a temporary file is used during CHM generation.\n   *\n   * The CHM file format includes data about the compressed section (such\n   * as its overall size) that is stored in the output CHM file prior to\n   * the compressed section itself. This unavoidably requires that the\n   * compressed section has to be generated, before these details can be\n   * set. There are several ways this can be handled. Firstly, the\n   * compressed section could be generated entirely in memory before\n   * writing any of the output CHM file. This approach is not used in\n   * libmspack, as the compressed section can exceed the addressable\n   * memory space on most architectures.\n   *\n   * libmspack has two options, either to write these unknowable sections\n   * with blank data, generate the compressed section, then re-open the\n   * output file for update once the compressed section has been\n   * completed, or to write the compressed section to a temporary file,\n   * then write the entire output file at once, performing a simple\n   * file-to-file copy for the compressed section.\n   *\n   * The simple solution of buffering the entire compressed section in\n   * memory can still be used, if desired. As the temporary file's\n   * filename is passed directly to mspack_system::open(), it is possible\n   * for a custom mspack_system implementation to hold this file in memory,\n   * without writing to a disk.\n   *\n   * If a temporary file is set, generate() performs the following\n   * sequence of events: the temporary file is opened for writing, the\n   * compression algorithm writes to the temporary file, the temporary\n   * file is closed.  Then the output file is opened for writing and the\n   * temporary file is re-opened for reading. The output file is written\n   * and the temporary file is read from. Both files are then closed. The\n   * temporary file itself is not deleted. If that is desired, the\n   * temporary file should be deleted after the completion of generate(),\n   * if it exists.\n   *\n   * If a temporary file is set not to be used, generate() performs the\n   * following sequence of events: the output file is opened for writing,\n   * then it is written and closed. The output file is then re-opened for\n   * update, the appropriate sections are seek()ed to and re-written, then\n   * the output file is closed.\n   *\n   * @param  self          a self-referential pointer to the\n   *                       mschm_compressor instance being called\n   * @param  use_temp_file non-zero if the temporary file should be used,\n   *                       zero if the temporary file should not be used.\n   * @param  temp_file     a file to temporarily write compressed data to,\n   *                       before opening it for reading and copying the\n   *                       contents to the output file. This is passed\n   *                       directly to mspack_system::open().\n   * @return an error code, or MSPACK_ERR_OK if successful\n   * @see generate()\n   */\n  int (*use_temporary_file)(struct mschm_compressor *self,\n                            int use_temp_file,\n                            const char *temp_file);\n  /**\n   * Sets a CHM compression engine parameter.\n   *\n   * The following parameters are defined:\n\n   * - #MSCHMC_PARAM_TIMESTAMP: Sets the \"timestamp\" of the CHM file\n   *   generated. This is not a timestamp, see mschmd_header::timestamp\n   *   for a description. If this timestamp is 0, generate() will use its\n   *   own algorithm for making a unique ID, based on the lengths and\n   *   names of files in the CHM itself. Defaults to 0, any value between\n   *   0 and (2^32)-1 is valid.\n   * - #MSCHMC_PARAM_LANGUAGE: Sets the \"language\" of the CHM file\n   *   generated.  This is not the language used in the CHM file, but the\n   *   language setting of the user who ran the HTMLHelp compiler. It\n   *   defaults to 0x0409. The valid range is between 0x0000 and 0x7F7F.\n   * - #MSCHMC_PARAM_LZXWINDOW: Sets the size of the LZX history window,\n   *   which is also the interval at which the compressed data stream can be\n   *   randomly accessed. The value is not a size in bytes, but a power of\n   *   two. The default value is 16 (which makes the window 2^16 bytes, or\n   *   64 kilobytes), the valid range is from 15 (32 kilobytes) to 21 (2\n   *   megabytes).\n   * - #MSCHMC_PARAM_DENSITY: Sets the \"density\" of quick reference\n   *   entries stored at the end of directory listing chunk. Each chunk is\n   *   4096 bytes in size, and contains as many file entries as there is\n   *   room for. At the other end of the chunk, a list of \"quick reference\"\n   *   pointers is included. The offset of every 'N'th file entry is given a\n   *   quick reference, where N = (2^density) + 1. The default density is\n   *   2. The smallest density is 0 (N=2), the maximum is 10 (N=1025). As\n   *   each file entry requires at least 5 bytes, the maximum number of\n   *   entries in a single chunk is roughly 800, so the maximum value 10\n   *   can be used to indicate there are no quickrefs at all.\n   * - #MSCHMC_PARAM_INDEX: Sets whether or not to include quick lookup\n   *   index chunk(s), in addition to normal directory listing chunks. A\n   *   value of zero means no index chunks will be created, a non-zero value\n   *   means index chunks will be created. The default is zero, \"don't\n   *   create an index\".\n   *\n   * @param  self     a self-referential pointer to the mschm_compressor\n   *                  instance being called\n   * @param  param    the parameter to set\n   * @param  value    the value to set the parameter to\n   * @return MSPACK_ERR_OK if all is OK, or MSPACK_ERR_ARGS if there\n   *         is a problem with either parameter or value.\n   * @see generate()\n   */\n  int (*set_param)(struct mschm_compressor *self,\n                   int param,\n                   int value);\n\n  /**\n   * Returns the error code set by the most recently called method.\n   *\n   * @param  self     a self-referential pointer to the mschm_compressor\n   *                  instance being called\n   * @return the most recent error code\n   * @see set_param(), generate()\n   */\n  int (*last_error)(struct mschm_compressor *self);\n};\n\n/**\n * A decompressor for .CHM (Microsoft HTMLHelp) files\n *\n * All fields are READ ONLY.\n *\n * @see mspack_create_chm_decompressor(), mspack_destroy_chm_decompressor()\n */\nstruct mschm_decompressor {\n  /**\n   * Opens a CHM helpfile and reads its contents.\n   *\n   * If the file opened is a valid CHM helpfile, all headers will be read\n   * and a mschmd_header structure will be returned, with a full list of\n   * files.\n   *\n   * In the case of an error occuring, NULL is returned and the error code\n   * is available from last_error().\n   *\n   * The filename pointer should be considered \"in use\" until close() is\n   * called on the CHM helpfile.\n   *\n   * @param  self     a self-referential pointer to the mschm_decompressor\n   *                  instance being called\n   * @param  filename the filename of the CHM helpfile. This is passed\n   *                  directly to mspack_system::open().\n   * @return a pointer to a mschmd_header structure, or NULL on failure\n   * @see close()\n   */\n  struct mschmd_header *(*open)(struct mschm_decompressor *self,\n                                const char *filename);\n\n  /**\n   * Closes a previously opened CHM helpfile.\n   *\n   * This closes a CHM helpfile, frees the mschmd_header and all\n   * mschmd_file structures associated with it (if any). This works on\n   * both helpfiles opened with open() and helpfiles opened with\n   * fast_open().\n   *\n   * The CHM header pointer is now invalid and cannot be used again. All\n   * mschmd_file pointers referencing that CHM are also now invalid, and\n   * cannot be used again.\n   *\n   * @param  self     a self-referential pointer to the mschm_decompressor\n   *                  instance being called\n   * @param  chm      the CHM helpfile to close\n   * @see open(), fast_open()\n   */\n  void (*close)(struct mschm_decompressor *self,\n                struct mschmd_header *chm);\n\n  /**\n   * Extracts a file from a CHM helpfile.\n   *\n   * This extracts a file from a CHM helpfile and writes it to the given\n   * filename. The filename of the file, mscabd_file::filename, is not\n   * used by extract(), but can be used by the caller as a guide for\n   * constructing an appropriate filename.\n   *\n   * This method works both with files found in the mschmd_header::files\n   * and mschmd_header::sysfiles list and mschmd_file structures generated\n   * on the fly by fast_find().\n   *\n   * @param  self     a self-referential pointer to the mschm_decompressor\n   *                  instance being called\n   * @param  file     the file to be decompressed\n   * @param  filename the filename of the file being written to\n   * @return an error code, or MSPACK_ERR_OK if successful\n   */\n  int (*extract)(struct mschm_decompressor *self,\n                 struct mschmd_file *file,\n                 const char *filename);\n\n  /**\n   * Returns the error code set by the most recently called method.\n   *\n   * This is useful for open() and fast_open(), which do not return an\n   * error code directly.\n   *\n   * @param  self     a self-referential pointer to the mschm_decompressor\n   *                  instance being called\n   * @return the most recent error code\n   * @see open(), extract()\n   */\n  int (*last_error)(struct mschm_decompressor *self);\n\n  /**\n   * Opens a CHM helpfile quickly.\n   *\n   * If the file opened is a valid CHM helpfile, only essential headers\n   * will be read. A mschmd_header structure will be still be returned, as\n   * with open(), but the mschmd_header::files field will be NULL. No\n   * files details will be automatically read.  The fast_find() method\n   * must be used to obtain file details.\n   *\n   * In the case of an error occuring, NULL is returned and the error code\n   * is available from last_error().\n   *\n   * The filename pointer should be considered \"in use\" until close() is\n   * called on the CHM helpfile.\n   *\n   * @param  self     a self-referential pointer to the mschm_decompressor\n   *                  instance being called\n   * @param  filename the filename of the CHM helpfile. This is passed\n   *                  directly to mspack_system::open().\n   * @return a pointer to a mschmd_header structure, or NULL on failure\n   * @see open(), close(), fast_find(), extract()\n   */\n  struct mschmd_header *(*fast_open)(struct mschm_decompressor *self,\n                                     const char *filename);\n\n  /**\n   * Finds file details quickly.\n   *\n   * Instead of reading all CHM helpfile headers and building a list of\n   * files, fast_open() and fast_find() are intended for finding file\n   * details only when they are needed. The CHM file format includes an\n   * on-disk file index to allow this.\n   *\n   * Given a case-sensitive filename, fast_find() will search the on-disk\n   * index for that file.\n   *\n   * If the file was found, the caller-provided mschmd_file structure will\n   * be filled out like so:\n   * - section: the correct value for the found file\n   * - offset: the correct value for the found file\n   * - length: the correct value for the found file\n   * - all other structure elements: NULL or 0\n   *\n   * If the file was not found, MSPACK_ERR_OK will still be returned as the\n   * result, but the caller-provided structure will be filled out like so:\n   * - section: NULL\n   * - offset: 0\n   * - length: 0\n   * - all other structure elements: NULL or 0\n   *\n   * This method is intended to be used in conjunction with CHM helpfiles\n   * opened with fast_open(), but it also works with helpfiles opened\n   * using the regular open().\n   *\n   * @param  self     a self-referential pointer to the mschm_decompressor\n   *                  instance being called\n   * @param  chm      the CHM helpfile to search for the file\n   * @param  filename the filename of the file to search for\n   * @param  f_ptr    a pointer to a caller-provded mschmd_file structure\n   * @param  f_size   <tt>sizeof(struct mschmd_file)</tt>\n   * @return an error code, or MSPACK_ERR_OK if successful\n   * @see open(), close(), fast_find(), extract()\n   */\n  int (*fast_find)(struct mschm_decompressor *self,\n                   struct mschmd_header *chm,\n                   const char *filename,\n                   struct mschmd_file *f_ptr,\n                   int f_size);\n};\n\n/* --- support for .LIT (EBook) file format -------------------------------- */\n\n/** TODO */\nstruct mslit_compressor {\n  int dummy; \n};\n\n/** TODO */\nstruct mslit_decompressor {\n  int dummy; \n};\n\n\n/* --- support for .HLP (MS Help) file format ------------------------------ */\n\n/** TODO */\nstruct mshlp_compressor {\n  int dummy; \n};\n\n/** TODO */\nstruct mshlp_decompressor {\n  int dummy; \n};\n\n\n/* --- support for SZDD file format ---------------------------------------- */\n\n/** msszdd_compressor::set_param() parameter: the missing character */\n#define MSSZDDC_PARAM_MISSINGCHAR (0)\n\n/** msszddd_header::format value - a regular SZDD file */\n#define MSSZDD_FMT_NORMAL (0)\n\n/** msszddd_header::format value - a special QBasic SZDD file */\n#define MSSZDD_FMT_QBASIC (1)\n\n/**\n * A structure which represents an SZDD compressed file.\n *\n * All fields are READ ONLY.\n */\nstruct msszddd_header {\n  /** The file format; either #MSSZDD_FMT_NORMAL or #MSSZDD_FMT_QBASIC */\n  int format;\n\n  /** The amount of data in the SZDD file once uncompressed. */\n  off_t length;\n\n  /**\n   * The last character in the filename, traditionally replaced with an\n   * underscore to show the file is compressed. The null character is used\n   * to show that this character has not been stored (e.g. because the\n   * filename is not known). Generally, only characters that may appear in\n   * an MS-DOS filename (except \".\") are valid.\n   */\n  char missing_char;\n};\n\n/**\n * A compressor for the SZDD file format.\n *\n * All fields are READ ONLY.\n *\n * @see mspack_create_szdd_compressor(), mspack_destroy_szdd_compressor()\n */\nstruct msszdd_compressor {\n  /**\n   * Reads an input file and creates a compressed output file in the\n   * SZDD compressed file format. The SZDD compression format is quick\n   * but gives poor compression. It is possible for the compressed output\n   * file to be larger than the input file.\n   *\n   * Conventionally, SZDD compressed files have the final character in\n   * their filename replaced with an underscore, to show they are\n   * compressed.  The missing character is stored in the compressed file\n   * itself. This is due to the restricted filename conventions of MS-DOS,\n   * most operating systems, such as UNIX, simply append another file\n   * extension to the existing filename. As mspack does not deal with\n   * filenames, this is left up to you. If you wish to set the missing\n   * character stored in the file header, use set_param() with the\n   * #MSSZDDC_PARAM_MISSINGCHAR parameter.\n   *\n   * \"Stream\" compression (where the length of the input data is not\n   * known) is not possible. The length of the input data is stored in the\n   * header of the SZDD file and must therefore be known before any data\n   * is compressed. Due to technical limitations of the file format, the\n   * maximum size of uncompressed file that will be accepted is 2147483647\n   * bytes.\n   *\n   * @param  self    a self-referential pointer to the msszdd_compressor\n   *                 instance being called\n   * @param  input   the name of the file to compressed. This is passed\n   *                 passed directly to mspack_system::open()\n   * @param  output  the name of the file to write compressed data to.\n   *                 This is passed directly to mspack_system::open().\n   * @param  length  the length of the uncompressed file, or -1 to indicate\n   *                 that this should be determined automatically by using\n   *                 mspack_system::seek() on the input file.\n   * @return an error code, or MSPACK_ERR_OK if successful\n   * @see set_param()\n   */\n  int (*compress)(struct msszdd_compressor *self,\n                  const char *input,\n                  const char *output,\n                  off_t length);\n\n  /**\n   * Sets an SZDD compression engine parameter.\n   *\n   * The following parameters are defined:\n\n   * - #MSSZDDC_PARAM_CHARACTER: the \"missing character\", the last character\n   *   in the uncompressed file's filename, which is traditionally replaced\n   *   with an underscore to show the file is compressed. Traditionally,\n   *   this can only be a character that is a valid part of an MS-DOS,\n   *   filename, but libmspack permits any character between 0x00 and 0xFF\n   *   to be stored. 0x00 is the default, and it represents \"no character\n   *   stored\".\n   *\n   * @param  self     a self-referential pointer to the msszdd_compressor\n   *                  instance being called\n   * @param  param    the parameter to set\n   * @param  value    the value to set the parameter to\n   * @return MSPACK_ERR_OK if all is OK, or MSPACK_ERR_ARGS if there\n   *         is a problem with either parameter or value.\n   * @see compress()\n   */\n  int (*set_param)(struct msszdd_compressor *self,\n                   int param,\n                   int value);\n\n  /**\n   * Returns the error code set by the most recently called method.\n   *\n   * @param  self     a self-referential pointer to the msszdd_compressor\n   *                  instance being called\n   * @return the most recent error code\n   * @see compress()\n   */\n  int (*last_error)(struct mschm_decompressor *self);\n};\n\n/**\n * A decompressor for SZDD compressed files.\n *\n * All fields are READ ONLY.\n *\n * @see mspack_create_szdd_decompressor(), mspack_destroy_szdd_decompressor()\n */\nstruct msszdd_decompressor {\n  /**\n   * Opens a SZDD file and reads the header.\n   *\n   * If the file opened is a valid SZDD file, all headers will be read and\n   * a msszddd_header structure will be returned.\n   *\n   * In the case of an error occuring, NULL is returned and the error code\n   * is available from last_error().\n   *\n   * The filename pointer should be considered \"in use\" until close() is\n   * called on the SZDD file.\n   *\n   * @param  self     a self-referential pointer to the msszdd_decompressor\n   *                  instance being called\n   * @param  filename the filename of the SZDD compressed file. This is\n   *                  passed directly to mspack_system::open().\n   * @return a pointer to a msszddd_header structure, or NULL on failure\n   * @see close()\n   */\n  struct msszddd_header *(*open)(struct msszdd_decompressor *self,\n                                 const char *filename);\n\n  /**\n   * Closes a previously opened SZDD file.\n   *\n   * This closes a SZDD file and frees the msszddd_header associated with\n   * it.\n   *\n   * The SZDD header pointer is now invalid and cannot be used again.\n   *\n   * @param  self     a self-referential pointer to the msszdd_decompressor\n   *                  instance being called\n   * @param  szdd     the SZDD file to close\n   * @see open()\n   */\n  void (*close)(struct msszdd_decompressor *self,\n                struct msszddd_header *szdd);\n\n  /**\n   * Extracts the compressed data from a SZDD file.\n   *\n   * This decompresses the compressed SZDD data stream and writes it to\n   * an output file.\n   *\n   * @param  self     a self-referential pointer to the msszdd_decompressor\n   *                  instance being called\n   * @param  szdd     the SZDD file to extract data from\n   * @param  filename the filename to write the decompressed data to. This\n   *                  is passed directly to mspack_system::open().\n   * @return an error code, or MSPACK_ERR_OK if successful\n   */\n  int (*extract)(struct msszdd_decompressor *self,\n                 struct msszddd_header *szdd,\n                 const char *filename);\n\n  /**\n   * Decompresses an SZDD file to an output file in one step.\n   *\n   * This opens an SZDD file as input, reads the header, then decompresses\n   * the compressed data immediately to an output file, finally closing\n   * both the input and output file. It is more convenient to use than\n   * open() then extract() then close(), if you do not need to know the\n   * SZDD output size or missing character.\n   *\n   * @param  self     a self-referential pointer to the msszdd_decompressor\n   *                  instance being called\n   * @param  input    the filename of the input SZDD file. This is passed\n   *                  directly to mspack_system::open().\n   * @param  output   the filename to write the decompressed data to. This\n   *                  is passed directly to mspack_system::open().\n   * @return an error code, or MSPACK_ERR_OK if successful\n   */\n  int (*decompress)(struct msszdd_decompressor *self,\n                    const char *input,\n                    const char *output);\n\n  /**\n   * Returns the error code set by the most recently called method.\n   *\n   * This is useful for open() which does not return an\n   * error code directly.\n   *\n   * @param  self     a self-referential pointer to the msszdd_decompressor\n   *                  instance being called\n   * @return the most recent error code\n   * @see open(), extract(), decompress()\n   */\n  int (*last_error)(struct msszdd_decompressor *self);\n};\n\n/* --- support for KWAJ file format ---------------------------------------- */\n\n/** mskwaj_compressor::set_param() parameter: compression type */\n#define MSKWAJC_PARAM_COMP_TYPE  (0)\n\n/** mskwaj_compressor::set_param() parameter: include the length of the\n * uncompressed file in the header?\n */\n#define MSKWAJC_PARAM_INCLUDE_LENGTH (1)\n\n/** KWAJ compression type: no compression. */\n#define MSKWAJ_COMP_NONE (0)\n/** KWAJ compression type: no compression, 0xFF XOR \"encryption\". */\n#define MSKWAJ_COMP_XOR (1)\n/** KWAJ compression type: LZSS (same method as SZDD) */\n#define MSKWAJ_COMP_SZDD (2)\n/** KWAJ compression type: LZ+Huffman compression */\n#define MSKWAJ_COMP_LZH (3)\n/** KWAJ compression type: MSZIP */\n#define MSKWAJ_COMP_MSZIP (4)\n\n/** KWAJ optional header flag: decompressed file length is included */\n#define MSKWAJ_HDR_HASLENGTH (0x01)\n\n/** KWAJ optional header flag: unknown 2-byte structure is included */\n#define MSKWAJ_HDR_HASUNKNOWN1 (0x02)\n\n/** KWAJ optional header flag: unknown multi-sized structure is included */\n#define MSKWAJ_HDR_HASUNKNOWN2 (0x04)\n\n/** KWAJ optional header flag: file name (no extension) is included */\n#define MSKWAJ_HDR_HASFILENAME (0x08)\n\n/** KWAJ optional header flag: file extension is included */\n#define MSKWAJ_HDR_HASFILEEXT (0x10)\n\n/** KWAJ optional header flag: extra text is included */\n#define MSKWAJ_HDR_HASEXTRATEXT (0x20)\n\n/**\n * A structure which represents an KWAJ compressed file.\n *\n * All fields are READ ONLY.\n */\nstruct mskwajd_header {\n  /** The compression type; should be one of #MSKWAJ_COMP_NONE,\n   * #MSKWAJ_COMP_XOR, #MSKWAJ_COMP_SZDD or #MSKWAJ_COMP_LZH\n   */\n  unsigned short comp_type;\n\n  /** The offset in the file where the compressed data stream begins */\n  off_t data_offset;\n\n  /** Flags indicating which optional headers were included. */\n  int headers;\n\n  /** The amount of uncompressed data in the file, or 0 if not present. */\n  off_t length;\n\n  /** output filename, or NULL if not present */\n  char *filename;\n\n  /** extra uncompressed data (usually text) in the header.\n   * This data can contain nulls so use extra_length to get the size.\n   */\n  char *extra;\n\n  /** length of extra uncompressed data in the header */\n  unsigned short extra_length;\n};\n\n/**\n * A compressor for the KWAJ file format.\n *\n * All fields are READ ONLY.\n *\n * @see mspack_create_kwaj_compressor(), mspack_destroy_kwaj_compressor()\n */\nstruct mskwaj_compressor {\n  /**\n   * Reads an input file and creates a compressed output file in the\n   * KWAJ compressed file format. The KWAJ compression format is quick\n   * but gives poor compression. It is possible for the compressed output\n   * file to be larger than the input file.\n   *\n   * @param  self    a self-referential pointer to the mskwaj_compressor\n   *                 instance being called\n   * @param  input   the name of the file to compressed. This is passed\n   *                 passed directly to mspack_system::open()\n   * @param  output  the name of the file to write compressed data to.\n   *                 This is passed directly to mspack_system::open().\n   * @param  length  the length of the uncompressed file, or -1 to indicate\n   *                 that this should be determined automatically by using\n   *                 mspack_system::seek() on the input file.\n   * @return an error code, or MSPACK_ERR_OK if successful\n   * @see set_param()\n   */\n  int (*compress)(struct mskwaj_compressor *self,\n                  const char *input,\n                  const char *output,\n                  off_t length);\n\n  /**\n   * Sets an KWAJ compression engine parameter.\n   *\n   * The following parameters are defined:\n   *\n   * - #MSKWAJC_PARAM_COMP_TYPE: the compression method to use. Must\n   *   be one of #MSKWAJC_COMP_NONE, #MSKWAJC_COMP_XOR, #MSKWAJ_COMP_SZDD\n   *   or #MSKWAJ_COMP_LZH. The default is #MSKWAJ_COMP_LZH.\n   *\n   * - #MSKWAJC_PARAM_INCLUDE_LENGTH: a boolean; should the compressed\n   *   output file should include the uncompressed length of the input\n   *   file in the header? This adds 4 bytes to the size of the output\n   *   file. A value of zero says \"no\", non-zero says \"yes\". The default\n   *   is \"no\".\n   *\n   * @param  self     a self-referential pointer to the mskwaj_compressor\n   *                  instance being called\n   * @param  param    the parameter to set\n   * @param  value    the value to set the parameter to\n   * @return MSPACK_ERR_OK if all is OK, or MSPACK_ERR_ARGS if there\n   *         is a problem with either parameter or value.\n   * @see generate()\n   */\n  int (*set_param)(struct mskwaj_compressor *self,\n                   int param,\n                   int value);\n\n\n  /**\n   * Sets the original filename of the file before compression,\n   * which will be stored in the header of the output file.\n   *\n   * The filename should be a null-terminated string, it must be an\n   * MS-DOS \"8.3\" type filename (up to 8 bytes for the filename, then\n   * optionally a \".\" and up to 3 bytes for a filename extension).\n   *\n   * If NULL is passed as the filename, no filename is included in the\n   * header. This is the default.\n   *\n   * @param  self     a self-referential pointer to the mskwaj_compressor\n   *                  instance being called\n   * @param  filename the original filename to use\n   * @return MSPACK_ERR_OK if all is OK, or MSPACK_ERR_ARGS if the\n   *         filename is too long\n   */\n  int (*set_filename)(struct mskwaj_compressor *self,\n                      const char *filename);\n\n  /**\n   * Sets arbitrary data that will be stored in the header of the\n   * output file, uncompressed. It can be up to roughly 64 kilobytes,\n   * as the overall size of the header must not exceed 65535 bytes.\n   * The data can contain null bytes if desired.\n   *\n   * If NULL is passed as the data pointer, or zero is passed as the\n   * length, no extra data is included in the header. This is the\n   * default.\n   *\n   * @param  self     a self-referential pointer to the mskwaj_compressor\n   *                  instance being called\n   * @param  data     a pointer to the data to be stored in the header\n   * @param  bytes    the length of the data in bytes\n   * @return MSPACK_ERR_OK if all is OK, or MSPACK_ERR_ARGS extra data\n   *         is too long\n   */\n  int (*set_extra_data)(struct mskwaj_compressor *self,\n                        void *data,\n                        size_t bytes);\n\n  /**\n   * Returns the error code set by the most recently called method.\n   *\n   * @param  self     a self-referential pointer to the mskwaj_compressor\n   *                  instance being called\n   * @return the most recent error code\n   * @see compress()\n   */\n  int (*last_error)(struct mschm_decompressor *self);\n};\n\n/**\n * A decompressor for KWAJ compressed files.\n *\n * All fields are READ ONLY.\n *\n * @see mspack_create_kwaj_decompressor(), mspack_destroy_kwaj_decompressor()\n */\nstruct mskwaj_decompressor {\n  /**\n   * Opens a KWAJ file and reads the header.\n   *\n   * If the file opened is a valid KWAJ file, all headers will be read and\n   * a mskwajd_header structure will be returned.\n   *\n   * In the case of an error occuring, NULL is returned and the error code\n   * is available from last_error().\n   *\n   * The filename pointer should be considered \"in use\" until close() is\n   * called on the KWAJ file.\n   *\n   * @param  self     a self-referential pointer to the mskwaj_decompressor\n   *                  instance being called\n   * @param  filename the filename of the KWAJ compressed file. This is\n   *                  passed directly to mspack_system::open().\n   * @return a pointer to a mskwajd_header structure, or NULL on failure\n   * @see close()\n   */\n  struct mskwajd_header *(*open)(struct mskwaj_decompressor *self,\n                                 const char *filename);\n\n  /**\n   * Closes a previously opened KWAJ file.\n   *\n   * This closes a KWAJ file and frees the mskwajd_header associated\n   * with it. The KWAJ header pointer is now invalid and cannot be\n   * used again.\n   *\n   * @param  self     a self-referential pointer to the mskwaj_decompressor\n   *                  instance being called\n   * @param  kwaj     the KWAJ file to close\n   * @see open()\n   */\n  void (*close)(struct mskwaj_decompressor *self,\n                struct mskwajd_header *kwaj);\n\n  /**\n   * Extracts the compressed data from a KWAJ file.\n   *\n   * This decompresses the compressed KWAJ data stream and writes it to\n   * an output file.\n   *\n   * @param  self     a self-referential pointer to the mskwaj_decompressor\n   *                  instance being called\n   * @param  kwaj     the KWAJ file to extract data from\n   * @param  filename the filename to write the decompressed data to. This\n   *                  is passed directly to mspack_system::open().\n   * @return an error code, or MSPACK_ERR_OK if successful\n   */\n  int (*extract)(struct mskwaj_decompressor *self,\n                 struct mskwajd_header *kwaj,\n                 const char *filename);\n\n  /**\n   * Decompresses an KWAJ file to an output file in one step.\n   *\n   * This opens an KWAJ file as input, reads the header, then decompresses\n   * the compressed data immediately to an output file, finally closing\n   * both the input and output file. It is more convenient to use than\n   * open() then extract() then close(), if you do not need to know the\n   * KWAJ output size or output filename.\n   *\n   * @param  self     a self-referential pointer to the mskwaj_decompressor\n   *                  instance being called\n   * @param  input    the filename of the input KWAJ file. This is passed\n   *                  directly to mspack_system::open().\n   * @param  output   the filename to write the decompressed data to. This\n   *                  is passed directly to mspack_system::open().\n   * @return an error code, or MSPACK_ERR_OK if successful\n   */\n  int (*decompress)(struct mskwaj_decompressor *self,\n                    const char *input,\n                    const char *output);\n\n  /**\n   * Returns the error code set by the most recently called method.\n   *\n   * This is useful for open() which does not return an\n   * error code directly.\n   *\n   * @param  self     a self-referential pointer to the mskwaj_decompressor\n   *                  instance being called\n   * @return the most recent error code\n   * @see open(), search()\n   */\n  int (*last_error)(struct mskwaj_decompressor *self);\n};\n\n/* --- support for .LZX (Offline Address Book) file format ----------------- */\n\n/**\n * A compressor for the Offline Address Book (OAB) format.\n *\n * All fields are READ ONLY.\n *\n * @see mspack_create_oab_compressor(), mspack_destroy_oab_compressor()\n */\nstruct msoab_compressor {\n  /**\n   * Compress a full OAB file.\n   *\n   * The input file will be read and the compressed contents written to the\n   * output file.\n   *\n   * @param  self     a self-referential pointer to the msoab_decompressor\n   *                  instance being called\n   * @param  input    the filename of the input file. This is passed\n   *                  directly to mspack_system::open().\n   * @param  output   the filename of the output file. This is passed\n   *                  directly to mspack_system::open().\n   * @return an error code, or MSPACK_ERR_OK if successful\n   */\n  int (*compress) (struct msoab_compressor *self,\n                   const char *input,\n                   const char *output);\n\n  /**\n   * Generate a compressed incremental OAB patch file.\n   *\n   * The two uncompressed files \"input\" and \"base\" will be read, and an\n   * incremental patch to generate \"input\" from \"base\" will be written to\n   * the output file.\n   *\n   * @param  self     a self-referential pointer to the msoab_compressor\n   *                  instance being called\n   * @param  input    the filename of the input file containing the new\n   *                  version of its contents. This is passed directly\n   *                  to mspack_system::open().\n   * @param  base     the filename of the original base file containing\n   *                  the old version of its contents, against which the\n   *                  incremental patch shall generated. This is passed\n   *                  directly to mspack_system::open().\n   * @param  output   the filename of the output file. This is passed\n   *                  directly to mspack_system::open().\n   * @return an error code, or MSPACK_ERR_OK if successful\n   */\n  int (*compress_incremental) (struct msoab_compressor *self,\n                               const char *input,\n                               const char *base,\n                               const char *output);\n};\n\n/**\n * A decompressor for .LZX (Offline Address Book) files\n *\n * All fields are READ ONLY.\n *\n * @see mspack_create_oab_decompressor(), mspack_destroy_oab_decompressor()\n */\nstruct msoab_decompressor {\n  /**\n   * Decompresses a full Offline Address Book file.\n   *\n   * If the input file is a valid compressed Offline Address Book file, \n   * it will be read and the decompressed contents will be written to\n   * the output file.\n   *\n   * @param  self     a self-referential pointer to the msoab_decompressor\n   *                  instance being called\n   * @param  input    the filename of the input file. This is passed\n   *                  directly to mspack_system::open().\n   * @param  output   the filename of the output file. This is passed\n   *                  directly to mspack_system::open().\n   * @return an error code, or MSPACK_ERR_OK if successful\n   */\n  int (*decompress) (struct msoab_decompressor *self,\n                     const char *input,\n                     const char *output);\n\n  /**\n   * Decompresses an Offline Address Book with an incremental patch file.\n   *\n   * This requires both a full UNCOMPRESSED Offline Address Book file to\n   * act as the \"base\", and a compressed incremental patch file as input.\n   * If the input file is valid, it will be decompressed with reference to\n   * the base file, and the decompressed contents will be written to the\n   * output file.\n   *\n   * There is no way to tell what the right base file is for the given\n   * incremental patch, but if you get it wrong, this will usually result\n   * in incorrect data being decompressed, which will then fail a checksum\n   * test.\n   *\n   * @param  self     a self-referential pointer to the msoab_decompressor\n   *                  instance being called\n   * @param  input    the filename of the input file. This is passed\n   *                  directly to mspack_system::open().\n   * @param  base     the filename of the base file to which the\n   *                  incremental patch shall be applied. This is passed\n   *                  directly to mspack_system::open().\n   * @param  output   the filename of the output file. This is passed\n   *                  directly to mspack_system::open().\n   * @return an error code, or MSPACK_ERR_OK if successful\n   */\n  int (*decompress_incremental) (struct msoab_decompressor *self,\n                                 const char *input,\n                                 const char *base,\n                                 const char *output);\n\n  /**\n   * Sets an OAB decompression engine parameter. Available only in OAB\n   * decompressor version 2 and above.\n   *\n   * - #MSOABD_PARAM_DECOMPBUF: How many bytes should be used as an input\n   *   buffer by decompressors? The minimum value is 16. The default value\n   *   is 4096.\n   *\n   * @param  self     a self-referential pointer to the msoab_decompressor\n   *                  instance being called\n   * @param  param    the parameter to set\n   * @param  value    the value to set the parameter to\n   * @return MSPACK_ERR_OK if all is OK, or MSPACK_ERR_ARGS if there\n   *         is a problem with either parameter or value.\n   */\n  int (*set_param)(struct msoab_decompressor *self,\n                   int param,\n                   int value);\n\n};\n\n/** msoab_decompressor::set_param() parameter: size of decompression buffer */\n#define MSOABD_PARAM_DECOMPBUF (0)\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif"
  },
  {
    "path": "vendor/mspack/readbits.h",
    "content": "/* This file is part of libmspack.\n * (C) 2003-2010 Stuart Caie.\n *\n * libmspack is free software; you can redistribute it and/or modify it under\n * the terms of the GNU Lesser General Public License (LGPL) version 2.1\n *\n * For further details, see the file COPYING.LIB distributed with libmspack\n */\n\n#ifndef MSPACK_READBITS_H\n#define MSPACK_READBITS_H 1\n\n/* this header defines macros that read data streams by\n * the individual bits\n *\n * INIT_BITS         initialises bitstream state in state structure\n * STORE_BITS        stores bitstream state in state structure\n * RESTORE_BITS      restores bitstream state from state structure\n * ENSURE_BITS(n)    ensure there are at least N bits in the bit buffer\n * READ_BITS(var,n)  takes N bits from the buffer and puts them in var\n * PEEK_BITS(n)      extracts without removing N bits from the bit buffer\n * REMOVE_BITS(n)    removes N bits from the bit buffer\n *\n * READ_BITS simply calls ENSURE_BITS, PEEK_BITS and REMOVE_BITS,\n * which means it's limited to reading the number of bits you can\n * ensure at any one time. It also fails if asked to read zero bits.\n * If you need to read zero bits, or more bits than can be ensured in\n * one go, use READ_MANY_BITS instead.\n *\n * These macros have variable names baked into them, so to use them\n * you have to define some macros:\n * - BITS_TYPE: the type name of your state structure\n * - BITS_VAR: the variable that points to your state structure\n * - define BITS_ORDER_MSB if bits are read from the MSB, or\n *   define BITS_ORDER_LSB if bits are read from the LSB\n * - READ_BYTES: some code that reads more data into the bit buffer,\n *   it should use READ_IF_NEEDED (calls read_input if the byte buffer\n *   is empty), then INJECT_BITS(data,n) to put data from the byte\n *   buffer into the bit buffer.\n *\n * You also need to define some variables and structure members:\n * - unsigned char *i_ptr;    // current position in the byte buffer\n * - unsigned char *i_end;    // end of the byte buffer\n * - unsigned int bit_buffer; // the bit buffer itself\n * - unsigned int bits_left;  // number of bits remaining\n *\n * If you use read_input() and READ_IF_NEEDED, they also expect these\n * structure members:\n * - struct mspack_system *sys;  // to access sys->read()\n * - unsigned int error;         // to record/return read errors\n * - unsigned char input_end;    // to mark reaching the EOF\n * - unsigned char *inbuf;       // the input byte buffer\n * - unsigned int inbuf_size;    // the size of the input byte buffer\n *\n * Your READ_BYTES implementation should read data from *i_ptr and\n * put them in the bit buffer. READ_IF_NEEDED will call read_input()\n * if i_ptr reaches i_end, and will fill up inbuf and set i_ptr to\n * the start of inbuf and i_end to the end of inbuf.\n *\n * If you're reading in MSB order, the routines work by using the area\n * beyond the MSB and the LSB of the bit buffer as a free source of\n * zeroes when shifting. This avoids having to mask any bits. So we\n * have to know the bit width of the bit buffer variable. We use\n * <limits.h> and CHAR_BIT to find the size of the bit buffer in bits.\n *\n * If you are reading in LSB order, bits need to be masked. Normally\n * this is done by computing the mask: N bits are masked by the value\n * (1<<N)-1). However, you can define BITS_LSB_TABLE to use a lookup\n * table instead of computing this. This adds two new macros,\n * PEEK_BITS_T and READ_BITS_T which work the same way as PEEK_BITS\n * and READ_BITS, except they use this lookup table. This is useful if\n * you need to look up a number of bits that are only known at\n * runtime, so the bit mask can't be turned into a constant by the\n * compiler.\n\n * The bit buffer datatype should be at least 32 bits wide: it must be\n * possible to ENSURE_BITS(17), so it must be possible to add 16 new bits\n * to the bit buffer when the bit buffer already has 1 to 15 bits left.\n */\n\n#ifndef BITS_VAR\n# error \"define BITS_VAR as the state structure poiner variable name\"\n#endif\n#ifndef BITS_TYPE\n# error \"define BITS_TYPE as the state structure type\"\n#endif\n#if defined(BITS_ORDER_MSB) && defined(BITS_ORDER_LSB)\n# error \"you must define either BITS_ORDER_MSB or BITS_ORDER_LSB\"\n#else\n# if !(defined(BITS_ORDER_MSB) || defined(BITS_ORDER_LSB))\n#  error \"you must define BITS_ORDER_MSB or BITS_ORDER_LSB\"\n# endif\n#endif\n\n#if HAVE_LIMITS_H\n# include <limits.h>\n#endif\n#ifndef CHAR_BIT\n# define CHAR_BIT (8)\n#endif\n#define BITBUF_WIDTH (sizeof(bit_buffer) * CHAR_BIT)\n\n#define INIT_BITS do {                          \\\n    BITS_VAR->i_ptr      = &BITS_VAR->inbuf[0]; \\\n    BITS_VAR->i_end      = &BITS_VAR->inbuf[0]; \\\n    BITS_VAR->bit_buffer = 0;                   \\\n    BITS_VAR->bits_left  = 0;                   \\\n    BITS_VAR->input_end  = 0;                   \\\n} while (0)\n\n#define STORE_BITS do {                 \\\n    BITS_VAR->i_ptr      = i_ptr;       \\\n    BITS_VAR->i_end      = i_end;       \\\n    BITS_VAR->bit_buffer = bit_buffer;  \\\n    BITS_VAR->bits_left  = bits_left;   \\\n} while (0)\n\n#define RESTORE_BITS do {               \\\n    i_ptr      = BITS_VAR->i_ptr;       \\\n    i_end      = BITS_VAR->i_end;       \\\n    bit_buffer = BITS_VAR->bit_buffer;  \\\n    bits_left  = BITS_VAR->bits_left;   \\\n} while (0)\n\n#define ENSURE_BITS(nbits) do {                 \\\n    while (bits_left < (nbits)) READ_BYTES;     \\\n} while (0)\n\n#define READ_BITS(val, nbits) do {              \\\n    ENSURE_BITS(nbits);                         \\\n    (val) = PEEK_BITS(nbits);                   \\\n    REMOVE_BITS(nbits);                         \\\n} while (0)\n\n#define READ_MANY_BITS(val, bits) do {                          \\\n    unsigned char needed = (bits), bitrun;                      \\\n    (val) = 0;                                                  \\\n    while (needed > 0) {                                        \\\n        if (bits_left <= (BITBUF_WIDTH - 16)) READ_BYTES;       \\\n        bitrun = (bits_left < needed) ? bits_left : needed;     \\\n        (val) = ((val) << bitrun) | PEEK_BITS(bitrun);          \\\n        REMOVE_BITS(bitrun);                                    \\\n        needed -= bitrun;                                       \\\n    }                                                           \\\n} while (0)\n\n#ifdef BITS_ORDER_MSB\n# define PEEK_BITS(nbits)   (bit_buffer >> (BITBUF_WIDTH - (nbits)))\n# define REMOVE_BITS(nbits) ((bit_buffer <<= (nbits)), (bits_left -= (nbits)))\n# define INJECT_BITS(bitdata,nbits) ((bit_buffer |= \\\n    (bitdata) << (BITBUF_WIDTH - (nbits) - bits_left)), (bits_left += (nbits)))\n#else /* BITS_ORDER_LSB */\n# define PEEK_BITS(nbits)   (bit_buffer & ((1 << (nbits))-1))\n# define REMOVE_BITS(nbits) ((bit_buffer >>= (nbits)), (bits_left -= (nbits)))\n# define INJECT_BITS(bitdata,nbits) ((bit_buffer |= \\\n    (bitdata) << bits_left), (bits_left += (nbits)))\n#endif\n\n#ifdef BITS_LSB_TABLE\n/* lsb_bit_mask[n] = (1 << n) - 1 */\nstatic const unsigned short lsb_bit_mask[17] = {\n    0x0000, 0x0001, 0x0003, 0x0007, 0x000f, 0x001f, 0x003f, 0x007f, 0x00ff,\n    0x01ff, 0x03ff, 0x07ff, 0x0fff, 0x1fff, 0x3fff, 0x7fff, 0xffff\n};\n# define PEEK_BITS_T(nbits) (bit_buffer & lsb_bit_mask[(nbits)])\n# define READ_BITS_T(val, nbits) do {   \\\n    ENSURE_BITS(nbits);                 \\\n    (val) = PEEK_BITS_T(nbits);         \\\n    REMOVE_BITS(nbits);                 \\\n} while (0)\n#endif\n\n#ifndef BITS_NO_READ_INPUT\n# define READ_IF_NEEDED do {            \\\n    if (i_ptr >= i_end) {               \\\n        if (read_input(BITS_VAR))       \\\n            return BITS_VAR->error;     \\\n        i_ptr = BITS_VAR->i_ptr;        \\\n        i_end = BITS_VAR->i_end;        \\\n    }                                   \\\n} while (0)\n\nstatic int read_input(BITS_TYPE *p) {\n    int read = p->sys->read(p->input, &p->inbuf[0], (int)p->inbuf_size);\n    if (read < 0) return p->error = MSPACK_ERR_READ;\n\n    /* we might overrun the input stream by asking for bits we don't use,\n     * so fake 2 more bytes at the end of input */\n    if (read == 0) {\n        if (p->input_end) {\n            D((\"out of input bytes\"))\n            return p->error = MSPACK_ERR_READ;\n        }\n        else {\n            read = 2;\n            p->inbuf[0] = p->inbuf[1] = 0;\n            p->input_end = 1;\n        }\n    }\n\n    /* update i_ptr and i_end */\n    p->i_ptr = &p->inbuf[0];\n    p->i_end = &p->inbuf[read];\n    return MSPACK_ERR_OK;\n}\n#endif\n#endif\n"
  },
  {
    "path": "vendor/mspack/readhuff.h",
    "content": "/* This file is part of libmspack.\n * (C) 2003-2014 Stuart Caie.\n *\n * libmspack is free software; you can redistribute it and/or modify it under\n * the terms of the GNU Lesser General Public License (LGPL) version 2.1\n *\n * For further details, see the file COPYING.LIB distributed with libmspack\n */\n\n#ifndef MSPACK_READHUFF_H\n#define MSPACK_READHUFF_H 1\n\n/* This implements a fast Huffman tree decoding system. */\n\n#if !(defined(BITS_ORDER_MSB) || defined(BITS_ORDER_LSB))\n# error \"readhuff.h is used in conjunction with readbits.h, include that first\"\n#endif\n#if !(defined(TABLEBITS) && defined(MAXSYMBOLS))\n# error \"define TABLEBITS(tbl) and MAXSYMBOLS(tbl) before using readhuff.h\"\n#endif\n#if !(defined(HUFF_TABLE) && defined(HUFF_LEN))\n# error \"define HUFF_TABLE(tbl) and HUFF_LEN(tbl) before using readhuff.h\"\n#endif\n#ifndef HUFF_ERROR\n# error \"define HUFF_ERROR before using readhuff.h\"\n#endif\n#ifndef HUFF_MAXBITS\n# define HUFF_MAXBITS 16\n#endif\n\n/* Decodes the next huffman symbol from the input bitstream into var.\n * Do not use this macro on a table unless build_decode_table() succeeded.\n */\n#define READ_HUFFSYM(tbl, var) do {                     \\\n    ENSURE_BITS(HUFF_MAXBITS);                          \\\n    sym = HUFF_TABLE(tbl, PEEK_BITS(TABLEBITS(tbl)));   \\\n    if (sym >= MAXSYMBOLS(tbl)) HUFF_TRAVERSE(tbl);     \\\n    (var) = sym;                                        \\\n    i = HUFF_LEN(tbl, sym);                             \\\n    REMOVE_BITS(i);                                     \\\n} while (0)\n\n#ifdef BITS_ORDER_LSB\n# define HUFF_TRAVERSE(tbl) do {                        \\\n    i = TABLEBITS(tbl) - 1;                             \\\n    do {                                                \\\n        if (i++ > HUFF_MAXBITS) HUFF_ERROR;             \\\n        sym = HUFF_TABLE(tbl,                           \\\n            (sym << 1) | ((bit_buffer >> i) & 1));      \\\n    } while (sym >= MAXSYMBOLS(tbl));                   \\\n} while (0)\n#else\n#define HUFF_TRAVERSE(tbl) do {                         \\\n    i = 1 << (BITBUF_WIDTH - TABLEBITS(tbl));           \\\n    do {                                                \\\n        if ((i >>= 1) == 0) HUFF_ERROR;                 \\\n        sym = HUFF_TABLE(tbl,                           \\\n            (sym << 1) | ((bit_buffer & i) ? 1 : 0));   \\\n    } while (sym >= MAXSYMBOLS(tbl));                   \\\n} while (0)\n#endif\n\n/* make_decode_table(nsyms, nbits, length[], table[])\n *\n * This function was originally coded by David Tritscher.\n * It builds a fast huffman decoding table from\n * a canonical huffman code lengths table.\n *\n * nsyms  = total number of symbols in this huffman tree.\n * nbits  = any symbols with a code length of nbits or less can be decoded\n *          in one lookup of the table.\n * length = A table to get code lengths from [0 to nsyms-1]\n * table  = The table to fill up with decoded symbols and pointers.\n *          Should be ((1<<nbits) + (nsyms*2)) in length.\n *\n * Returns 0 for OK or 1 for error\n */\nstatic int make_decode_table(unsigned int nsyms, unsigned int nbits,\n                             unsigned char *length, unsigned short *table)\n{\n    register unsigned short sym, next_symbol;\n    register unsigned int leaf, fill;\n#ifdef BITS_ORDER_LSB\n    register unsigned int reverse;\n#endif\n    register unsigned char bit_num;\n    unsigned int pos         = 0; /* the current position in the decode table */\n    unsigned int table_mask  = 1 << nbits;\n    unsigned int bit_mask    = table_mask >> 1; /* don't do 0 length codes */\n\n    /* fill entries for codes short enough for a direct mapping */\n    for (bit_num = 1; bit_num <= nbits; bit_num++) {\n        for (sym = 0; sym < nsyms; sym++) {\n            if (length[sym] != bit_num) continue;\n#ifdef BITS_ORDER_MSB\n            leaf = pos;\n#else\n            /* reverse the significant bits */\n            fill = length[sym]; reverse = pos >> (nbits - fill); leaf = 0;\n            do {leaf <<= 1; leaf |= reverse & 1; reverse >>= 1;} while (--fill);\n#endif\n\n            if((pos += bit_mask) > table_mask) return 1; /* table overrun */\n\n            /* fill all possible lookups of this symbol with the symbol itself */\n#ifdef BITS_ORDER_MSB\n            for (fill = bit_mask; fill-- > 0;) table[leaf++] = sym;\n#else\n            fill = bit_mask; next_symbol = 1 << bit_num;\n            do { table[leaf] = sym; leaf += next_symbol; } while (--fill);\n#endif\n        }\n        bit_mask >>= 1;\n    }\n\n    /* exit with success if table is now complete */\n    if (pos == table_mask) return 0;\n\n    /* mark all remaining table entries as unused */\n    for (sym = pos; sym < table_mask; sym++) {\n#ifdef BITS_ORDER_MSB\n        table[sym] = 0xFFFF;\n#else\n        reverse = sym; leaf = 0; fill = nbits;\n        do { leaf <<= 1; leaf |= reverse & 1; reverse >>= 1; } while (--fill);\n        table[leaf] = 0xFFFF;\n#endif\n    }\n\n    /* next_symbol = base of allocation for long codes */\n    next_symbol = ((table_mask >> 1) < nsyms) ? nsyms : (table_mask >> 1);\n\n    /* give ourselves room for codes to grow by up to 16 more bits.\n     * codes now start at bit nbits+16 and end at (nbits+16-codelength) */\n    pos <<= 16;\n    table_mask <<= 16;\n    bit_mask = 1 << 15;\n\n    for (bit_num = nbits+1; bit_num <= HUFF_MAXBITS; bit_num++) {\n        for (sym = 0; sym < nsyms; sym++) {\n            if (length[sym] != bit_num) continue;\n            if (pos >= table_mask) return 1; /* table overflow */\n\n#ifdef BITS_ORDER_MSB\n            leaf = pos >> 16;\n#else\n            /* leaf = the first nbits of the code, reversed */\n            reverse = pos >> 16; leaf = 0; fill = nbits;\n            do {leaf <<= 1; leaf |= reverse & 1; reverse >>= 1;} while (--fill);\n#endif\n            for (fill = 0; fill < (bit_num - nbits); fill++) {\n                /* if this path hasn't been taken yet, 'allocate' two entries */\n                if (table[leaf] == 0xFFFF) {\n                    table[(next_symbol << 1)     ] = 0xFFFF;\n                    table[(next_symbol << 1) + 1 ] = 0xFFFF;\n                    table[leaf] = next_symbol++;\n                }\n\n                /* follow the path and select either left or right for next bit */\n                leaf = table[leaf] << 1;\n                if ((pos >> (15-fill)) & 1) leaf++;\n            }\n            table[leaf] = sym;\n            pos += bit_mask;\n        }\n        bit_mask >>= 1;\n    }\n\n    /* full table? */\n    return (pos == table_mask) ? 0 : 1;\n}\n#endif\n"
  },
  {
    "path": "vendor/mspack/system.c",
    "content": "/* This file is part of libmspack.\n * (C) 2003-2004 Stuart Caie.\n *\n * libmspack is free software; you can redistribute it and/or modify it under\n * the terms of the GNU Lesser General Public License (LGPL) version 2.1\n *\n * For further details, see the file COPYING.LIB distributed with libmspack\n */\n\n#ifdef HAVE_CONFIG_H\n# include <config.h>\n#endif\n\n#include <system.h>\n\nint mspack_version(int entity) {\n  switch (entity) {\n   /* CHM decoder version 1 -> 2 changes:\n    * - added mschmd_sec_mscompressed::spaninfo\n    * - added mschmd_header::first_pmgl\n    * - added mschmd_header::last_pmgl\n    * - added mschmd_header::chunk_cache;\n    */\n  case MSPACK_VER_MSCHMD:\n  /* CAB decoder version 1 -> 2 changes:\n   * - added MSCABD_PARAM_SALVAGE\n   */\n  case MSPACK_VER_MSCABD:\n  /* OAB decoder version  1 -> 2 changes:\n   * - added msoab_decompressor::set_param and MSOABD_PARAM_DECOMPBUF\n   */\n  case MSPACK_VER_MSOABD:\n    return 2;\n  case MSPACK_VER_LIBRARY:\n  case MSPACK_VER_SYSTEM:\n  case MSPACK_VER_MSSZDDD:\n  case MSPACK_VER_MSKWAJD:\n    return 1;\n  case MSPACK_VER_MSCABC:\n  case MSPACK_VER_MSCHMC:\n  case MSPACK_VER_MSLITD:\n  case MSPACK_VER_MSLITC:\n  case MSPACK_VER_MSHLPD:\n  case MSPACK_VER_MSHLPC:\n  case MSPACK_VER_MSSZDDC:\n  case MSPACK_VER_MSKWAJC:\n  case MSPACK_VER_MSOABC:\n    return 0;\n  }\n  return -1;\n}\n\nint mspack_sys_selftest_internal(int offt_size) {\n  return (sizeof(off_t) == offt_size) ? MSPACK_ERR_OK : MSPACK_ERR_SEEK;\n}\n\n/* validates a system structure */\nint mspack_valid_system(struct mspack_system *sys) {\n  return (sys != NULL) && (sys->open != NULL) && (sys->close != NULL) &&\n    (sys->read != NULL) && (sys->write != NULL) && (sys->seek != NULL) &&\n    (sys->tell != NULL) && (sys->message != NULL) && (sys->alloc != NULL) &&\n    (sys->free != NULL) && (sys->copy != NULL) && (sys->null_ptr == NULL);\n}\n\n/* returns the length of a file opened for reading */\nint mspack_sys_filelen(struct mspack_system *system,\n                       struct mspack_file *file, off_t *length)\n{\n  off_t current;\n\n  if (!system || !file || !length) return MSPACK_ERR_OPEN;\n\n  /* get current offset */\n  current = system->tell(file);\n\n  /* seek to end of file */\n  if (system->seek(file, (off_t) 0, MSPACK_SYS_SEEK_END)) {\n    return MSPACK_ERR_SEEK;\n  }\n\n  /* get offset of end of file */\n  *length = system->tell(file);\n\n  /* seek back to original offset */\n  if (system->seek(file, current, MSPACK_SYS_SEEK_START)) {\n    return MSPACK_ERR_SEEK;\n  }\n\n  return MSPACK_ERR_OK;\n}\n\n\n\n/* definition of mspack_default_system -- if the library is compiled with\n * MSPACK_NO_DEFAULT_SYSTEM, no default system will be provided. Otherwise,\n * an appropriate default system (e.g. the standard C library, or some native\n * API calls)\n */\n\n#ifdef MSPACK_NO_DEFAULT_SYSTEM\nstruct mspack_system *mspack_default_system = NULL;\n#else\n\n/* implementation of mspack_default_system for standard C library */\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <stdarg.h>\n\nstruct mspack_file_p {\n  FILE *fh;\n  const char *name;\n};\n\nstatic struct mspack_file *msp_open(struct mspack_system *self,\n                                    const char *filename, int mode)\n{\n  struct mspack_file_p *fh;\n  const char *fmode;\n\n  switch (mode) {\n  case MSPACK_SYS_OPEN_READ:   fmode = \"rb\";  break;\n  case MSPACK_SYS_OPEN_WRITE:  fmode = \"wb\";  break;\n  case MSPACK_SYS_OPEN_UPDATE: fmode = \"r+b\"; break;\n  case MSPACK_SYS_OPEN_APPEND: fmode = \"ab\";  break;\n  default: return NULL;\n  }\n\n  if ((fh = (struct mspack_file_p *) malloc(sizeof(struct mspack_file_p)))) {\n    fh->name = filename;\n    if ((fh->fh = fopen(filename, fmode))) return (struct mspack_file *) fh;\n    free(fh);\n  }\n  return NULL;\n}\n\nstatic void msp_close(struct mspack_file *file) {\n  struct mspack_file_p *self = (struct mspack_file_p *) file;\n  if (self) {\n    fclose(self->fh);\n    free(self);\n  }\n}\n\nstatic int msp_read(struct mspack_file *file, void *buffer, int bytes) {\n  struct mspack_file_p *self = (struct mspack_file_p *) file;\n  if (self && buffer && bytes >= 0) {\n    size_t count = fread(buffer, 1, (size_t) bytes, self->fh);\n    if (!ferror(self->fh)) return (int) count;\n  }\n  return -1;\n}\n\nstatic int msp_write(struct mspack_file *file, void *buffer, int bytes) {\n  struct mspack_file_p *self = (struct mspack_file_p *) file;\n  if (self && buffer && bytes >= 0) {\n    size_t count = fwrite(buffer, 1, (size_t) bytes, self->fh);\n    if (!ferror(self->fh)) return (int) count;\n  }\n  return -1;\n}\n\nstatic int msp_seek(struct mspack_file *file, off_t offset, int mode) {\n  struct mspack_file_p *self = (struct mspack_file_p *) file;\n  if (self) {\n    switch (mode) {\n    case MSPACK_SYS_SEEK_START: mode = SEEK_SET; break;\n    case MSPACK_SYS_SEEK_CUR:   mode = SEEK_CUR; break;\n    case MSPACK_SYS_SEEK_END:   mode = SEEK_END; break;\n    default: return -1;\n    }\n#if HAVE_FSEEKO\n    return fseeko(self->fh, offset, mode);\n#else\n    return fseek(self->fh, offset, mode);\n#endif\n  }\n  return -1;\n}\n\nstatic off_t msp_tell(struct mspack_file *file) {\n  struct mspack_file_p *self = (struct mspack_file_p *) file;\n#if HAVE_FSEEKO\n  return (self) ? (off_t) ftello(self->fh) : 0;\n#else\n  return (self) ? (off_t) ftell(self->fh) : 0;\n#endif\n}\n\nstatic void msp_msg(struct mspack_file *file, const char *format, ...) {\n  va_list ap;\n  if (file) fprintf(stderr, \"%s: \", ((struct mspack_file_p *) file)->name);\n  va_start(ap, format);\n  vfprintf(stderr, format, ap);\n  va_end(ap);\n  fputc((int) '\\n', stderr);\n  fflush(stderr);\n}\n\nstatic void *msp_alloc(struct mspack_system *self, size_t bytes) {\n#if DEBUG\n  /* make uninitialised data obvious */\n  char *buf = malloc(bytes + 8);\n  if (buf) memset(buf, 0xDC, bytes);\n  *((size_t *)buf) = bytes;\n  return &buf[8];\n#else\n  return malloc(bytes);\n#endif\n}\n\nstatic void msp_free(void *buffer) {\n#if DEBUG\n  char *buf = buffer;\n  size_t bytes;\n  if (buf) {\n    buf -= 8;\n    bytes = *((size_t *)buf);\n    /* make freed data obvious */\n    memset(buf, 0xED, bytes);\n    free(buf);\n  }\n#else\n  free(buffer);\n#endif\n}\n\nstatic void msp_copy(void *src, void *dest, size_t bytes) {\n  memcpy(dest, src, bytes);\n}\n\nstatic struct mspack_system msp_system = {\n  &msp_open, &msp_close, &msp_read,  &msp_write, &msp_seek,\n  &msp_tell, &msp_msg, &msp_alloc, &msp_free, &msp_copy, NULL\n};\n\nstruct mspack_system *mspack_default_system = &msp_system;\n\n#endif\n"
  },
  {
    "path": "vendor/mspack/system.h",
    "content": "/* This file is part of libmspack.\n * (C) 2003-2018 Stuart Caie.\n *\n * libmspack is free software; you can redistribute it and/or modify it under\n * the terms of the GNU Lesser General Public License (LGPL) version 2.1\n *\n * For further details, see the file COPYING.LIB distributed with libmspack\n */\n\n#ifndef MSPACK_SYSTEM_H\n#define MSPACK_SYSTEM_H 1\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n/* ensure config.h is read before mspack.h */\n#ifdef HAVE_CONFIG_H\n# include <config.h>\n#endif\n#include <mspack.h>\n#include <macros.h>\n\n/* assume <string.h> exists */\n#ifndef MSPACK_NO_DEFAULT_SYSTEM\n# include <string.h>\n#else\n /* but if no default system wanted, avoid using <string.h> entirely,\n  * to avoid linking to even these standard C library functions */\nstatic inline int memcmp(const void *s1, const void *s2, size_t n) {\n    const unsigned char *a = s1, *b = s2;\n    while (n--) if (*a++ != *b++) return a[-1] - b[-1];\n    return 0;\n}\nstatic inline void *memset(void *s, int c, size_t n) {\n    unsigned char *s2 = s, c2 = (unsigned char) c;\n    while (n--) *s2++ = c2;\n    return s;\n}\nstatic inline size_t strlen(const char *s) {\n    size_t c = 0; while (*s++) c++; return c;\n}\n#endif\n\n/* fix for problem with GCC 4 and glibc (thanks to Ville Skytta)\n * http://bugzilla.redhat.com/bugzilla/show_bug.cgi?id=150429\n */\n#ifdef read\n# undef read\n#endif\n\nextern struct mspack_system *mspack_default_system;\n\n/* returns the length of a file opened for reading */\nextern int mspack_sys_filelen(struct mspack_system *system,\n                              struct mspack_file *file, off_t *length);\n\n/* validates a system structure */\nextern int mspack_valid_system(struct mspack_system *sys);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif\n"
  },
  {
    "path": "vendor/patches/LibAtrac9/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.20)\n\nproject(atrac9 LANGUAGES C)\n\ninclude(CheckIPOSupported)\ncheck_ipo_supported(RESULT IPO_SUPPORTED OUTPUT IPO_ERROR_MESSAGE)\noption(LIBATRAC9_BUILD_SHARED \"Builds libatrac9 as a shared library\" ON)\n\nset(LIBATRAC9_SOURCES \n    C/src/band_extension.c\n    C/src/bit_allocation.c\n    C/src/bit_reader.c\n    C/src/decinit.c\n    C/src/decoder.c\n    C/src/huffCodes.c\n    C/src/imdct.c\n    C/src/libatrac9.c\n    C/src/quantization.c\n    C/src/scale_factors.c\n    C/src/tables.c\n    C/src/unpack.c\n    C/src/utility.c\n)\n\nif (LIBATRAC9_BUILD_SHARED)\n    add_library(${PROJECT_NAME} SHARED\n        ${LIBATRAC9_SOURCES})\nelse()\n    add_library(${PROJECT_NAME} STATIC\n        ${LIBATRAC9_SOURCES})\nendif()\n\nif(MSVC)\n    target_compile_definitions(${PROJECT_NAME} PRIVATE COMPILING_DLL)\n    set(LIBATRAC9_BUILD_SHARED ON)\nelseif (CMAKE_CXX_COMPILER_ID STREQUAL \"GNU\" OR CMAKE_CXX_COMPILER_ID STREQUAL \"Clang\")\n    set_target_properties(${PROJECT_NAME} PROPERTIES LINK_FLAGS_RELEASE -s)\nendif()\n\nadd_library(atrac9::atrac9 ALIAS ${PROJECT_NAME})\n\nfile(COPY C/src/libatrac9.h DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/include/libatrac9)\nset_target_properties(${PROJECT_NAME} PROPERTIES PUBLIC_HEADER ${CMAKE_CURRENT_SOURCE_DIR}/include/libatrac9/libatrac9.h)\n\n# Include directories\ntarget_include_directories(${PROJECT_NAME} PUBLIC include)\n\n\nif(IPO_SUPPORTED)\n    set_target_properties(${PROJECT_NAME} PROPERTIES INTERPROCEDURAL_OPTIMIZATION TRUE)\nelse()\n    message(WARNING \"IPO is not supported: ${IPO_ERROR_MESSAGE}\")\nendif()\n\n# Install rules\ninstall(TARGETS ${PROJECT_NAME}\n    EXPORT ${PROJECT_NAME}Targets\n    LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}\n    ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}\n    RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}\n    PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/libatrac9\n)\n"
  },
  {
    "path": "vendor/pcg/src/pcg_basic.c",
    "content": "/*\n * PCG Random Number Generation for C.\n *\n * Copyright 2014 Melissa O'Neill <oneill@pcg-random.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * For additional information about the PCG random number generation scheme,\n * including its license and other licensing options, visit\n *\n *       http://www.pcg-random.org\n */\n\n/*\n * This code is derived from the full C implementation, which is in turn\n * derived from the canonical C++ PCG implementation. The C++ version\n * has many additional features and is preferable if you can use C++ in\n * your project.\n */\n\n#include \"pcg_basic.h\"\n\n// state for global RNGs\n\nstatic pcg32_random_t pcg32_global = PCG32_INITIALIZER;\n\n// pcg32_srandom(initstate, initseq)\n// pcg32_srandom_r(rng, initstate, initseq):\n//     Seed the rng.  Specified in two parts, state initializer and a\n//     sequence selection constant (a.k.a. stream id)\n\nvoid pcg32_srandom_r(pcg32_random_t* rng, uint64_t initstate, uint64_t initseq)\n{\n    rng->state = 0U;\n    rng->inc = (initseq << 1u) | 1u;\n    pcg32_random_r(rng);\n    rng->state += initstate;\n    pcg32_random_r(rng);\n}\n\nvoid pcg32_srandom(uint64_t seed, uint64_t seq)\n{\n    pcg32_srandom_r(&pcg32_global, seed, seq);\n}\n\n// pcg32_random()\n// pcg32_random_r(rng)\n//     Generate a uniformly distributed 32-bit random number\n\nuint32_t pcg32_random_r(pcg32_random_t* rng)\n{\n    uint64_t oldstate = rng->state;\n    rng->state = oldstate * 6364136223846793005ULL + rng->inc;\n    uint32_t xorshifted = ((oldstate >> 18u) ^ oldstate) >> 27u;\n    uint32_t rot = oldstate >> 59u;\n    return (xorshifted >> rot) | (xorshifted << ((-rot) & 31));\n}\n\nuint32_t pcg32_random()\n{\n    return pcg32_random_r(&pcg32_global);\n}\n\n\n// pcg32_boundedrand(bound):\n// pcg32_boundedrand_r(rng, bound):\n//     Generate a uniformly distributed number, r, where 0 <= r < bound\n\nuint32_t pcg32_boundedrand_r(pcg32_random_t* rng, uint32_t bound)\n{\n    // To avoid bias, we need to make the range of the RNG a multiple of\n    // bound, which we do by dropping output less than a threshold.\n    // A naive scheme to calculate the threshold would be to do\n    //\n    //     uint32_t threshold = 0x100000000ull % bound;\n    //\n    // but 64-bit div/mod is slower than 32-bit div/mod (especially on\n    // 32-bit platforms).  In essence, we do\n    //\n    //     uint32_t threshold = (0x100000000ull-bound) % bound;\n    //\n    // because this version will calculate the same modulus, but the LHS\n    // value is less than 2^32.\n\n    uint32_t threshold = -bound % bound;\n\n    // Uniformity guarantees that this loop will terminate.  In practice, it\n    // should usually terminate quickly; on average (assuming all bounds are\n    // equally likely), 82.25% of the time, we can expect it to require just\n    // one iteration.  In the worst case, someone passes a bound of 2^31 + 1\n    // (i.e., 2147483649), which invalidates almost 50% of the range.  In \n    // practice, bounds are typically small and only a tiny amount of the range\n    // is eliminated.\n    for (;;) {\n        uint32_t r = pcg32_random_r(rng);\n        if (r >= threshold)\n            return r % bound;\n    }\n}\n\n\nuint32_t pcg32_boundedrand(uint32_t bound)\n{\n    return pcg32_boundedrand_r(&pcg32_global, bound);\n}\n\n"
  },
  {
    "path": "vendor/squish/ChangeLog",
    "content": "1.10\n* Iterative cluster fit is now considered to be a new compression mode\n* The core cluster fit is now 4x faster using contributions by Ignacio\nCastano from NVIDIA\n* The single colour lookup table has been halved by exploiting symmetry\n\n1.9\n* Added contributed SSE1 truncate implementation\n* Changed use of SQUISH_USE_SSE to be 1 for SSE and 2 for SSE2 instructions\n* Cluster fit is now iterative to further reduce image error\n\n1.8\n* Switched from using floor to trunc for much better SSE performance (again)\n* Xcode build now expects libpng in /usr/local for extra/squishpng\n\n1.7\n* Fixed floating-point equality issue in clusterfit sort (x86 affected only)\n* Implemented proper SSE(2) floor function for 50% speedup on SSE builds \n* The range fit implementation now uses the correct colour metric\n\n1.6\n* Fixed bug in CompressImage where masked pixels were not skipped over\n* DXT3 and DXT5 alpha compression now properly use the mask to ignore pixels\n* Fixed major DXT1 bug that can generate unexpected transparent pixels\n\n1.5\n* Added CompressMasked function to handle incomplete DXT blocks more cleanly\n* Added kWeightColourByAlpha flag for better quality images when alpha blending\n\n1.4\n* Fixed stack overflow in rangefit\n\n1.3\n* Worked around SSE floor implementation bug, proper fix needed!\n* This release has visual studio and makefile builds that work\n\n1.2\n* Added provably optimal single colour compressor\n* Added extra/squishgen.cpp that generates single colour lookup tables\n\n1.1\n* Fixed a DXT1 colour output bug\n* Changed argument order for Decompress function to match Compress\n* Added GetStorageRequirements function\n* Added CompressImage function\n* Added DecompressImage function\n* Moved squishtool.cpp to extra/squishpng.cpp\n* Added extra/squishtest.cpp\n\n1.0\n* Initial release\n\n"
  },
  {
    "path": "vendor/squish/README",
    "content": "LICENSE\n-------\n\nThe squish library is distributed under the terms and conditions of the MIT\nlicense. This license is specified at the top of each source file and must be\npreserved in its entirety.\n\nBUILDING AND INSTALLING THE LIBRARY\n-----------------------------------\n\nIf you are using Visual Studio 2003 or above under Windows then load the Visual\nStudio 2003 project in the vs7 folder. By default, the library is built using\nSSE2 optimisations. To change this either change or remove the SQUISH_USE_SSE=2\nfrom the preprocessor symbols.\n\nIf you are using a Mac then load the Xcode 2.2 project in the distribution. By\ndefault, the library is built using Altivec optimisations. To change this\neither change or remove SQUISH_USE_ALTIVEC=1 from the preprocessor symbols. I\nguess I'll have to think about changing this for the new Intel Macs that are\nrolling out...\n\nIf you are using unix then first edit the config file in the base directory of\nthe distribution, enabling Altivec or SSE with the USE_ALTIVEC or USE_SSE\nvariables, and editing the optimisation flags passed to the C++ compiler if\nnecessary. Then make can be used to build the library, and make install (from\nthe superuser account) can be used to install (into /usr/local by default).\n\nREPORTING BUGS OR FEATURE REQUESTS\n----------------------------------\n\nFeedback can be sent to Simon Brown (the developer) at si@sjbrown.co.uk\n\nNew releases are announced on the squish library homepage at\nhttp://sjbrown.co.uk/?code=squish\n\n"
  },
  {
    "path": "vendor/squish/alpha.cpp",
    "content": "/* -----------------------------------------------------------------------------\n\n\tCopyright (c) 2006 Simon Brown                          si@sjbrown.co.uk\n\n\tPermission is hereby granted, free of charge, to any person obtaining\n\ta copy of this software and associated documentation files (the \n\t\"Software\"), to\tdeal in the Software without restriction, including\n\twithout limitation the rights to use, copy, modify, merge, publish,\n\tdistribute, sublicense, and/or sell copies of the Software, and to \n\tpermit persons to whom the Software is furnished to do so, subject to \n\tthe following conditions:\n\n\tThe above copyright notice and this permission notice shall be included\n\tin all copies or substantial portions of the Software.\n\n\tTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n\tOR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF \n\tMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\n\tIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY \n\tCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, \n\tTORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE \n\tSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\t\n   -------------------------------------------------------------------------- */\n   \n#include \"alpha.h\"\n#include <climits>\n#include <algorithm>\n\nnamespace squish {\n\nstatic int FloatToInt( float a, int limit )\n{\n\t// use ANSI round-to-zero behaviour to get round-to-nearest\n\tint i = ( int )( a + 0.5f );\n\n\t// clamp to the limit\n\tif( i < 0 )\n\t\ti = 0;\n\telse if( i > limit )\n\t\ti = limit; \n\n\t// done\n\treturn i;\n}\n\nvoid CompressAlphaDxt3( u8 const* rgba, int mask, void* block )\n{\n\tu8* bytes = reinterpret_cast< u8* >( block );\n\t\n\t// quantise and pack the alpha values pairwise\n\tfor( int i = 0; i < 8; ++i )\n\t{\n\t\t// quantise down to 4 bits\n\t\tfloat alpha1 = ( float )rgba[8*i + 3] * ( 15.0f/255.0f );\n\t\tfloat alpha2 = ( float )rgba[8*i + 7] * ( 15.0f/255.0f );\n\t\tint quant1 = FloatToInt( alpha1, 15 );\n\t\tint quant2 = FloatToInt( alpha2, 15 );\n\t\t\n\t\t// set alpha to zero where masked\n\t\tint bit1 = 1 << ( 2*i );\n\t\tint bit2 = 1 << ( 2*i + 1 );\n\t\tif( ( mask & bit1 ) == 0 )\n\t\t\tquant1 = 0;\n\t\tif( ( mask & bit2 ) == 0 )\n\t\t\tquant2 = 0;\n\n\t\t// pack into the byte\n\t\tbytes[i] = ( u8 )( quant1 | ( quant2 << 4 ) );\n\t}\n}\n\nvoid DecompressAlphaDxt3( u8* rgba, void const* block )\n{\n\tu8 const* bytes = reinterpret_cast< u8 const* >( block );\n\t\n\t// unpack the alpha values pairwise\n\tfor( int i = 0; i < 8; ++i )\n\t{\n\t\t// quantise down to 4 bits\n\t\tu8 quant = bytes[i];\n\t\t\n\t\t// unpack the values\n\t\tu8 lo = quant & 0x0f;\n\t\tu8 hi = quant & 0xf0;\n\n\t\t// convert back up to bytes\n\t\trgba[8*i + 3] = lo | ( lo << 4 );\n\t\trgba[8*i + 7] = hi | ( hi >> 4 );\n\t}\n}\n\nstatic void FixRange( int& min, int& max, int steps )\n{\n\tif( max - min < steps )\n\t\tmax = std::min( min + steps, 255 );\n\tif( max - min < steps )\n\t\tmin = std::max( 0, max - steps );\n}\n\nstatic int FitCodes( u8 const* rgba, int mask, u8 const* codes, u8* indices )\n{\n\t// fit each alpha value to the codebook\n\tint err = 0;\n\tfor( int i = 0; i < 16; ++i )\n\t{\n\t\t// check this pixel is valid\n\t\tint bit = 1 << i;\n\t\tif( ( mask & bit ) == 0 )\n\t\t{\n\t\t\t// use the first code\n\t\t\tindices[i] = 0;\n\t\t\tcontinue;\n\t\t}\n\t\t\n\t\t// find the least error and corresponding index\n\t\tint value = rgba[4*i + 3];\n\t\tint least = INT_MAX;\n\t\tint index = 0;\n\t\tfor( int j = 0; j < 8; ++j )\n\t\t{\n\t\t\t// get the squared error from this code\n\t\t\tint dist = ( int )value - ( int )codes[j];\n\t\t\tdist *= dist;\n\t\t\t\n\t\t\t// compare with the best so far\n\t\t\tif( dist < least )\n\t\t\t{\n\t\t\t\tleast = dist;\n\t\t\t\tindex = j;\n\t\t\t}\n\t\t}\n\t\t\n\t\t// save this index and accumulate the error\n\t\tindices[i] = ( u8 )index;\n\t\terr += least;\n\t}\n\t\n\t// return the total error\n\treturn err;\n}\n\nstatic void WriteAlphaBlock( int alpha0, int alpha1, u8 const* indices, void* block )\n{\n\tu8* bytes = reinterpret_cast< u8* >( block );\n\t\n\t// write the first two bytes\n\tbytes[0] = ( u8 )alpha0;\n\tbytes[1] = ( u8 )alpha1;\n\t\n\t// pack the indices with 3 bits each\n\tu8* dest = bytes + 2;\n\tu8 const* src = indices;\n\tfor( int i = 0; i < 2; ++i )\n\t{\n\t\t// pack 8 3-bit values\n\t\tint value = 0;\n\t\tfor( int j = 0; j < 8; ++j )\n\t\t{\n\t\t\tint index = *src++;\n\t\t\tvalue |= ( index << 3*j );\n\t\t}\n\t\t\t\n\t\t// store in 3 bytes\n\t\tfor( int j = 0; j < 3; ++j )\n\t\t{\n\t\t\tint byte = ( value >> 8*j ) & 0xff;\n\t\t\t*dest++ = ( u8 )byte;\n\t\t}\n\t}\n}\n\nstatic void WriteAlphaBlock5( int alpha0, int alpha1, u8 const* indices, void* block )\n{\n\t// check the relative values of the endpoints\n\tif( alpha0 > alpha1 )\n\t{\n\t\t// swap the indices\n\t\tu8 swapped[16];\n\t\tfor( int i = 0; i < 16; ++i )\n\t\t{\n\t\t\tu8 index = indices[i];\n\t\t\tif( index == 0 )\n\t\t\t\tswapped[i] = 1;\n\t\t\telse if( index == 1 )\n\t\t\t\tswapped[i] = 0;\n\t\t\telse if( index <= 5 )\n\t\t\t\tswapped[i] = 7 - index;\n\t\t\telse \n\t\t\t\tswapped[i] = index;\n\t\t}\n\t\t\n\t\t// write the block\n\t\tWriteAlphaBlock( alpha1, alpha0, swapped, block );\n\t}\n\telse\n\t{\n\t\t// write the block\n\t\tWriteAlphaBlock( alpha0, alpha1, indices, block );\n\t}\t\n}\n\nstatic void WriteAlphaBlock7( int alpha0, int alpha1, u8 const* indices, void* block )\n{\n\t// check the relative values of the endpoints\n\tif( alpha0 < alpha1 )\n\t{\n\t\t// swap the indices\n\t\tu8 swapped[16];\n\t\tfor( int i = 0; i < 16; ++i )\n\t\t{\n\t\t\tu8 index = indices[i];\n\t\t\tif( index == 0 )\n\t\t\t\tswapped[i] = 1;\n\t\t\telse if( index == 1 )\n\t\t\t\tswapped[i] = 0;\n\t\t\telse\n\t\t\t\tswapped[i] = 9 - index;\n\t\t}\n\t\t\n\t\t// write the block\n\t\tWriteAlphaBlock( alpha1, alpha0, swapped, block );\n\t}\n\telse\n\t{\n\t\t// write the block\n\t\tWriteAlphaBlock( alpha0, alpha1, indices, block );\n\t}\t\n}\n\nvoid CompressAlphaDxt5( u8 const* rgba, int mask, void* block )\n{\n\t// get the range for 5-alpha and 7-alpha interpolation\n\tint min5 = 255;\n\tint max5 = 0;\n\tint min7 = 255;\n\tint max7 = 0;\n\tfor( int i = 0; i < 16; ++i )\n\t{\n\t\t// check this pixel is valid\n\t\tint bit = 1 << i;\n\t\tif( ( mask & bit ) == 0 )\n\t\t\tcontinue;\n\n\t\t// incorporate into the min/max\n\t\tint value = rgba[4*i + 3];\n\t\tif( value < min7 )\n\t\t\tmin7 = value;\n\t\tif( value > max7 )\n\t\t\tmax7 = value;\n\t\tif( value != 0 && value < min5 )\n\t\t\tmin5 = value;\n\t\tif( value != 255 && value > max5 )\n\t\t\tmax5 = value;\n\t}\n\t\n\t// handle the case that no valid range was found\n\tif( min5 > max5 )\n\t\tmin5 = max5;\n\tif( min7 > max7 )\n\t\tmin7 = max7;\n\t\t\n\t// fix the range to be the minimum in each case\n\tFixRange( min5, max5, 5 );\n\tFixRange( min7, max7, 7 );\n\t\n\t// set up the 5-alpha code book\n\tu8 codes5[8];\n\tcodes5[0] = ( u8 )min5;\n\tcodes5[1] = ( u8 )max5;\n\tfor( int i = 1; i < 5; ++i )\n\t\tcodes5[1 + i] = ( u8 )( ( ( 5 - i )*min5 + i*max5 )/5 );\n\tcodes5[6] = 0;\n\tcodes5[7] = 255;\n\t\n\t// set up the 7-alpha code book\n\tu8 codes7[8];\n\tcodes7[0] = ( u8 )min7;\n\tcodes7[1] = ( u8 )max7;\n\tfor( int i = 1; i < 7; ++i )\n\t\tcodes7[1 + i] = ( u8 )( ( ( 7 - i )*min7 + i*max7 )/7 );\n\t\t\n\t// fit the data to both code books\n\tu8 indices5[16];\n\tu8 indices7[16];\n\tint err5 = FitCodes( rgba, mask, codes5, indices5 );\n\tint err7 = FitCodes( rgba, mask, codes7, indices7 );\n\t\n\t// save the block with least error\n\tif( err5 <= err7 )\n\t\tWriteAlphaBlock5( min5, max5, indices5, block );\n\telse\n\t\tWriteAlphaBlock7( min7, max7, indices7, block );\n}\n\nvoid DecompressAlphaDxt5( u8* rgba, void const* block )\n{\n\t// get the two alpha values\n\tu8 const* bytes = reinterpret_cast< u8 const* >( block );\n\tint alpha0 = bytes[0];\n\tint alpha1 = bytes[1];\n\t\n\t// compare the values to build the codebook\n\tu8 codes[8];\n\tcodes[0] = ( u8 )alpha0;\n\tcodes[1] = ( u8 )alpha1;\n\tif( alpha0 <= alpha1 )\n\t{\n\t\t// use 5-alpha codebook\n\t\tfor( int i = 1; i < 5; ++i )\n\t\t\tcodes[1 + i] = ( u8 )( ( ( 5 - i )*alpha0 + i*alpha1 )/5 );\n\t\tcodes[6] = 0;\n\t\tcodes[7] = 255;\n\t}\n\telse\n\t{\n\t\t// use 7-alpha codebook\n\t\tfor( int i = 1; i < 7; ++i )\n\t\t\tcodes[1 + i] = ( u8 )( ( ( 7 - i )*alpha0 + i*alpha1 )/7 );\n\t}\n\t\n\t// decode the indices\n\tu8 indices[16];\n\tu8 const* src = bytes + 2;\n\tu8* dest = indices;\n\tfor( int i = 0; i < 2; ++i )\n\t{\n\t\t// grab 3 bytes\n\t\tint value = 0;\n\t\tfor( int j = 0; j < 3; ++j )\n\t\t{\n\t\t\tint byte = *src++;\n\t\t\tvalue |= ( byte << 8*j );\n\t\t}\n\t\t\n\t\t// unpack 8 3-bit values from it\n\t\tfor( int j = 0; j < 8; ++j )\n\t\t{\n\t\t\tint index = ( value >> 3*j ) & 0x7;\n\t\t\t*dest++ = ( u8 )index;\n\t\t}\n\t}\n\t\n\t// write out the indexed codebook values\n\tfor( int i = 0; i < 16; ++i )\n\t\trgba[4*i + 3] = codes[indices[i]];\n}\n\n} // namespace squish\n"
  },
  {
    "path": "vendor/squish/alpha.h",
    "content": "/* -----------------------------------------------------------------------------\n\n\tCopyright (c) 2006 Simon Brown                          si@sjbrown.co.uk\n\n\tPermission is hereby granted, free of charge, to any person obtaining\n\ta copy of this software and associated documentation files (the \n\t\"Software\"), to\tdeal in the Software without restriction, including\n\twithout limitation the rights to use, copy, modify, merge, publish,\n\tdistribute, sublicense, and/or sell copies of the Software, and to \n\tpermit persons to whom the Software is furnished to do so, subject to \n\tthe following conditions:\n\n\tThe above copyright notice and this permission notice shall be included\n\tin all copies or substantial portions of the Software.\n\n\tTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n\tOR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF \n\tMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\n\tIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY \n\tCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, \n\tTORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE \n\tSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\t\n   -------------------------------------------------------------------------- */\n   \n#ifndef SQUISH_ALPHA_H\n#define SQUISH_ALPHA_H\n\n#include \"squish.h\"\n\nnamespace squish {\n\nvoid CompressAlphaDxt3( u8 const* rgba, int mask, void* block );\nvoid CompressAlphaDxt5( u8 const* rgba, int mask, void* block );\n\nvoid DecompressAlphaDxt3( u8* rgba, void const* block );\nvoid DecompressAlphaDxt5( u8* rgba, void const* block );\n\n} // namespace squish\n\n#endif // ndef SQUISH_ALPHA_H\n"
  },
  {
    "path": "vendor/squish/clusterfit.cpp",
    "content": "/* -----------------------------------------------------------------------------\n\n\tCopyright (c) 2006 Simon Brown                          si@sjbrown.co.uk\n\tCopyright (c) 2007 Ignacio Castano                   icastano@nvidia.com\n\n\tPermission is hereby granted, free of charge, to any person obtaining\n\ta copy of this software and associated documentation files (the \n\t\"Software\"), to\tdeal in the Software without restriction, including\n\twithout limitation the rights to use, copy, modify, merge, publish,\n\tdistribute, sublicense, and/or sell copies of the Software, and to \n\tpermit persons to whom the Software is furnished to do so, subject to \n\tthe following conditions:\n\n\tThe above copyright notice and this permission notice shall be included\n\tin all copies or substantial portions of the Software.\n\n\tTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n\tOR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF \n\tMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\n\tIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY \n\tCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, \n\tTORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE \n\tSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\t\n   -------------------------------------------------------------------------- */\n   \n#include \"clusterfit.h\"\n#include \"colourset.h\"\n#include \"colourblock.h\"\n#include <cfloat>\n\nnamespace squish {\n\nClusterFit::ClusterFit( ColourSet const* colours, int flags, float* metric ) \n  : ColourFit( colours, flags )\n{\n\t// set the iteration count\n\tm_iterationCount = ( m_flags & kColourIterativeClusterFit ) ? kMaxIterations : 1;\n\n\t// initialise the metric (old perceptual = 0.2126f, 0.7152f, 0.0722f)\n\tif( metric )\n\t\tm_metric = Vec4( metric[0], metric[1], metric[2], 1.0f );\n\telse\n\t\tm_metric = VEC4_CONST( 1.0f );\t\n\n\t// initialise the best error\n\tm_besterror = VEC4_CONST( FLT_MAX );\n\n\t// cache some values\n\tint const count = m_colours->GetCount();\n\tVec3 const* values = m_colours->GetPoints();\n\n\t// get the covariance matrix\n\tSym3x3 covariance = ComputeWeightedCovariance( count, values, m_colours->GetWeights() );\n\t\n\t// compute the principle component\n\tm_principle = ComputePrincipleComponent( covariance );\n}\n\nbool ClusterFit::ConstructOrdering( Vec3 const& axis, int iteration )\n{\n\t// cache some values\n\tint const count = m_colours->GetCount();\n\tVec3 const* values = m_colours->GetPoints();\n\n\t// build the list of dot products\n\tfloat dps[16];\n\tu8* order = ( u8* )m_order + 16*iteration;\n\tfor( int i = 0; i < count; ++i )\n\t{\n\t\tdps[i] = Dot( values[i], axis );\n\t\torder[i] = ( u8 )i;\n\t}\n\t\t\n\t// stable sort using them\n\tfor( int i = 0; i < count; ++i )\n\t{\n\t\tfor( int j = i; j > 0 && dps[j] < dps[j - 1]; --j )\n\t\t{\n\t\t\tstd::swap( dps[j], dps[j - 1] );\n\t\t\tstd::swap( order[j], order[j - 1] );\n\t\t}\n\t}\n\t\n\t// check this ordering is unique\n\tfor( int it = 0; it < iteration; ++it )\n\t{\n\t\tu8 const* prev = ( u8* )m_order + 16*it;\n\t\tbool same = true;\n\t\tfor( int i = 0; i < count; ++i )\n\t\t{\n\t\t\tif( order[i] != prev[i] )\n\t\t\t{\n\t\t\t\tsame = false;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tif( same )\n\t\t\treturn false;\n\t}\n\t\n\t// copy the ordering and weight all the points\n\tVec3 const* unweighted = m_colours->GetPoints();\n\tfloat const* weights = m_colours->GetWeights();\n\tm_xsum_wsum = VEC4_CONST( 0.0f );\n\tfor( int i = 0; i < count; ++i )\n\t{\n\t\tint j = order[i];\n\t\tVec4 p( unweighted[j].X(), unweighted[j].Y(), unweighted[j].Z(), 1.0f );\n\t\tVec4 w( weights[j] );\n\t\tVec4 x = p*w;\n\t\tm_points_weights[i] = x;\n\t\tm_xsum_wsum += x;\n\t}\n\treturn true;\n}\n\nvoid ClusterFit::Compress3( void* block )\n{\n\t// declare variables\n\tint const count = m_colours->GetCount();\n\tVec4 const two = VEC4_CONST( 2.0 );\n\tVec4 const one = VEC4_CONST( 1.0f );\n\tVec4 const half_half2( 0.5f, 0.5f, 0.5f, 0.25f );\n\tVec4 const zero = VEC4_CONST( 0.0f );\n\tVec4 const half = VEC4_CONST( 0.5f );\n\tVec4 const grid( 31.0f, 63.0f, 31.0f, 0.0f );\n\tVec4 const gridrcp( 1.0f/31.0f, 1.0f/63.0f, 1.0f/31.0f, 0.0f );\n\n\t// prepare an ordering using the principle axis\n\tConstructOrdering( m_principle, 0 );\n\t\n\t// check all possible clusters and iterate on the total order\n\tVec4 beststart = VEC4_CONST( 0.0f );\n\tVec4 bestend = VEC4_CONST( 0.0f );\n\tVec4 besterror = m_besterror;\n\tu8 bestindices[16];\n\tint bestiteration = 0;\n\tint besti = 0, bestj = 0;\n\t\n\t// loop over iterations (we avoid the case that all points in first or last cluster)\n\tfor( int iterationIndex = 0;; )\n\t{\n\t\t// first cluster [0,i) is at the start\n\t\tVec4 part0 = VEC4_CONST( 0.0f );\n\t\tfor( int i = 0; i < count; ++i )\n\t\t{\n\t\t\t// second cluster [i,j) is half along\n\t\t\tVec4 part1 = ( i == 0 ) ? m_points_weights[0] : VEC4_CONST( 0.0f );\n\t\t\tint jmin = ( i == 0 ) ? 1 : i;\n\t\t\tfor( int j = jmin;; )\n\t\t\t{\n\t\t\t\t// last cluster [j,count) is at the end\n\t\t\t\tVec4 part2 = m_xsum_wsum - part1 - part0;\n\t\t\t\t\n\t\t\t\t// compute least squares terms directly\n\t\t\t\tVec4 alphax_sum = MultiplyAdd( part1, half_half2, part0 );\n\t\t\t\tVec4 alpha2_sum = alphax_sum.SplatW();\n\n\t\t\t\tVec4 betax_sum = MultiplyAdd( part1, half_half2, part2 );\n\t\t\t\tVec4 beta2_sum = betax_sum.SplatW();\n\n\t\t\t\tVec4 alphabeta_sum = ( part1*half_half2 ).SplatW();\n\n\t\t\t\t// compute the least-squares optimal points\n\t\t\t\tVec4 factor = Reciprocal( NegativeMultiplySubtract( alphabeta_sum, alphabeta_sum, alpha2_sum*beta2_sum ) );\n\t\t\t\tVec4 a = NegativeMultiplySubtract( betax_sum, alphabeta_sum, alphax_sum*beta2_sum )*factor;\n\t\t\t\tVec4 b = NegativeMultiplySubtract( alphax_sum, alphabeta_sum, betax_sum*alpha2_sum )*factor;\n\n\t\t\t\t// clamp to the grid\n\t\t\t\ta = Min( one, Max( zero, a ) );\n\t\t\t\tb = Min( one, Max( zero, b ) );\n\t\t\t\ta = Truncate( MultiplyAdd( grid, a, half ) )*gridrcp;\n\t\t\t\tb = Truncate( MultiplyAdd( grid, b, half ) )*gridrcp;\n\t\t\t\t\n\t\t\t\t// compute the error (we skip the constant xxsum)\n\t\t\t\tVec4 e1 = MultiplyAdd( a*a, alpha2_sum, b*b*beta2_sum );\n\t\t\t\tVec4 e2 = NegativeMultiplySubtract( a, alphax_sum, a*b*alphabeta_sum );\n\t\t\t\tVec4 e3 = NegativeMultiplySubtract( b, betax_sum, e2 );\n\t\t\t\tVec4 e4 = MultiplyAdd( two, e3, e1 );\n\n\t\t\t\t// apply the metric to the error term\n\t\t\t\tVec4 e5 = e4*m_metric;\n\t\t\t\tVec4 error = e5.SplatX() + e5.SplatY() + e5.SplatZ();\n\t\t\t\t\n\t\t\t\t// keep the solution if it wins\n\t\t\t\tif( CompareAnyLessThan( error, besterror ) )\n\t\t\t\t{\n\t\t\t\t\tbeststart = a;\n\t\t\t\t\tbestend = b;\n\t\t\t\t\tbesti = i;\n\t\t\t\t\tbestj = j;\n\t\t\t\t\tbesterror = error;\n\t\t\t\t\tbestiteration = iterationIndex;\n\t\t\t\t}\n\n\t\t\t\t// advance\n\t\t\t\tif( j == count )\n\t\t\t\t\tbreak;\n\t\t\t\tpart1 += m_points_weights[j];\n\t\t\t\t++j;\n\t\t\t}\n\n\t\t\t// advance\n\t\t\tpart0 += m_points_weights[i];\n\t\t}\n\t\t\n\t\t// stop if we didn't improve in this iteration\n\t\tif( bestiteration != iterationIndex )\n\t\t\tbreak;\n\t\t\t\n\t\t// advance if possible\n\t\t++iterationIndex;\n\t\tif( iterationIndex == m_iterationCount )\n\t\t\tbreak;\n\t\t\t\n\t\t// stop if a new iteration is an ordering that has already been tried\n\t\tVec3 axis = ( bestend - beststart ).GetVec3();\n\t\tif( !ConstructOrdering( axis, iterationIndex ) )\n\t\t\tbreak;\n\t}\n\t\t\n\t// save the block if necessary\n\tif( CompareAnyLessThan( besterror, m_besterror ) )\n\t{\n\t\t// remap the indices\n\t\tu8 const* order = ( u8* )m_order + 16*bestiteration;\n\n\t\tu8 unordered[16];\n\t\tfor( int m = 0; m < besti; ++m )\n\t\t\tunordered[order[m]] = 0;\n\t\tfor( int m = besti; m < bestj; ++m )\n\t\t\tunordered[order[m]] = 2;\n\t\tfor( int m = bestj; m < count; ++m )\n\t\t\tunordered[order[m]] = 1;\n\n\t\tm_colours->RemapIndices( unordered, bestindices );\n\t\t\n\t\t// save the block\n\t\tWriteColourBlock3( beststart.GetVec3(), bestend.GetVec3(), bestindices, block );\n\n\t\t// save the error\n\t\tm_besterror = besterror;\n\t}\n}\n\nvoid ClusterFit::Compress4( void* block )\n{\n\t// declare variables\n\tint const count = m_colours->GetCount();\n\tVec4 const two = VEC4_CONST( 2.0f );\n\tVec4 const one = VEC4_CONST( 1.0f );\n\tVec4 const onethird_onethird2( 1.0f/3.0f, 1.0f/3.0f, 1.0f/3.0f, 1.0f/9.0f );\n\tVec4 const twothirds_twothirds2( 2.0f/3.0f, 2.0f/3.0f, 2.0f/3.0f, 4.0f/9.0f );\n\tVec4 const twonineths = VEC4_CONST( 2.0f/9.0f );\n\tVec4 const zero = VEC4_CONST( 0.0f );\n\tVec4 const half = VEC4_CONST( 0.5f );\n\tVec4 const grid( 31.0f, 63.0f, 31.0f, 0.0f );\n\tVec4 const gridrcp( 1.0f/31.0f, 1.0f/63.0f, 1.0f/31.0f, 0.0f );\n\n\t// prepare an ordering using the principle axis\n\tConstructOrdering( m_principle, 0 );\n\t\n\t// check all possible clusters and iterate on the total order\n\tVec4 beststart = VEC4_CONST( 0.0f );\n\tVec4 bestend = VEC4_CONST( 0.0f );\n\tVec4 besterror = m_besterror;\n\tu8 bestindices[16];\n\tint bestiteration = 0;\n\tint besti = 0, bestj = 0, bestk = 0;\n\t\n\t// loop over iterations (we avoid the case that all points in first or last cluster)\n\tfor( int iterationIndex = 0;; )\n\t{\n\t\t// first cluster [0,i) is at the start\n\t\tVec4 part0 = VEC4_CONST( 0.0f );\n\t\tfor( int i = 0; i < count; ++i )\n\t\t{\n\t\t\t// second cluster [i,j) is one third along\n\t\t\tVec4 part1 = VEC4_CONST( 0.0f );\n\t\t\tfor( int j = i;; )\n\t\t\t{\n\t\t\t\t// third cluster [j,k) is two thirds along\n\t\t\t\tVec4 part2 = ( j == 0 ) ? m_points_weights[0] : VEC4_CONST( 0.0f );\n\t\t\t\tint kmin = ( j == 0 ) ? 1 : j;\n\t\t\t\tfor( int k = kmin;; )\n\t\t\t\t{\n\t\t\t\t\t// last cluster [k,count) is at the end\n\t\t\t\t\tVec4 part3 = m_xsum_wsum - part2 - part1 - part0;\n\n\t\t\t\t\t// compute least squares terms directly\n\t\t\t\t\tVec4 const alphax_sum = MultiplyAdd( part2, onethird_onethird2, MultiplyAdd( part1, twothirds_twothirds2, part0 ) );\n\t\t\t\t\tVec4 const alpha2_sum = alphax_sum.SplatW();\n\t\t\t\t\t\n\t\t\t\t\tVec4 const betax_sum = MultiplyAdd( part1, onethird_onethird2, MultiplyAdd( part2, twothirds_twothirds2, part3 ) );\n\t\t\t\t\tVec4 const beta2_sum = betax_sum.SplatW();\n\t\t\t\t\t\n\t\t\t\t\tVec4 const alphabeta_sum = twonineths*( part1 + part2 ).SplatW();\n\n\t\t\t\t\t// compute the least-squares optimal points\n\t\t\t\t\tVec4 factor = Reciprocal( NegativeMultiplySubtract( alphabeta_sum, alphabeta_sum, alpha2_sum*beta2_sum ) );\n\t\t\t\t\tVec4 a = NegativeMultiplySubtract( betax_sum, alphabeta_sum, alphax_sum*beta2_sum )*factor;\n\t\t\t\t\tVec4 b = NegativeMultiplySubtract( alphax_sum, alphabeta_sum, betax_sum*alpha2_sum )*factor;\n\n\t\t\t\t\t// clamp to the grid\n\t\t\t\t\ta = Min( one, Max( zero, a ) );\n\t\t\t\t\tb = Min( one, Max( zero, b ) );\n\t\t\t\t\ta = Truncate( MultiplyAdd( grid, a, half ) )*gridrcp;\n\t\t\t\t\tb = Truncate( MultiplyAdd( grid, b, half ) )*gridrcp;\n\t\t\t\t\t\n\t\t\t\t\t// compute the error (we skip the constant xxsum)\n\t\t\t\t\tVec4 e1 = MultiplyAdd( a*a, alpha2_sum, b*b*beta2_sum );\n\t\t\t\t\tVec4 e2 = NegativeMultiplySubtract( a, alphax_sum, a*b*alphabeta_sum );\n\t\t\t\t\tVec4 e3 = NegativeMultiplySubtract( b, betax_sum, e2 );\n\t\t\t\t\tVec4 e4 = MultiplyAdd( two, e3, e1 );\n\n\t\t\t\t\t// apply the metric to the error term\n\t\t\t\t\tVec4 e5 = e4*m_metric;\n\t\t\t\t\tVec4 error = e5.SplatX() + e5.SplatY() + e5.SplatZ();\n\n\t\t\t\t\t// keep the solution if it wins\n\t\t\t\t\tif( CompareAnyLessThan( error, besterror ) )\n\t\t\t\t\t{\n\t\t\t\t\t\tbeststart = a;\n\t\t\t\t\t\tbestend = b;\n\t\t\t\t\t\tbesterror = error;\n\t\t\t\t\t\tbesti = i;\n\t\t\t\t\t\tbestj = j;\n\t\t\t\t\t\tbestk = k;\n\t\t\t\t\t\tbestiteration = iterationIndex;\n\t\t\t\t\t}\n\n\t\t\t\t\t// advance\n\t\t\t\t\tif( k == count )\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tpart2 += m_points_weights[k];\n\t\t\t\t\t++k;\n\t\t\t\t}\n\n\t\t\t\t// advance\n\t\t\t\tif( j == count )\n\t\t\t\t\tbreak;\n\t\t\t\tpart1 += m_points_weights[j];\n\t\t\t\t++j;\n\t\t\t}\n\n\t\t\t// advance\n\t\t\tpart0 += m_points_weights[i];\n\t\t}\n\t\t\n\t\t// stop if we didn't improve in this iteration\n\t\tif( bestiteration != iterationIndex )\n\t\t\tbreak;\n\t\t\t\n\t\t// advance if possible\n\t\t++iterationIndex;\n\t\tif( iterationIndex == m_iterationCount )\n\t\t\tbreak;\n\t\t\t\n\t\t// stop if a new iteration is an ordering that has already been tried\n\t\tVec3 axis = ( bestend - beststart ).GetVec3();\n\t\tif( !ConstructOrdering( axis, iterationIndex ) )\n\t\t\tbreak;\n\t}\n\n\t// save the block if necessary\n\tif( CompareAnyLessThan( besterror, m_besterror ) )\n\t{\n\t\t// remap the indices\n\t\tu8 const* order = ( u8* )m_order + 16*bestiteration;\n\n\t\tu8 unordered[16];\n\t\tfor( int m = 0; m < besti; ++m )\n\t\t\tunordered[order[m]] = 0;\n\t\tfor( int m = besti; m < bestj; ++m )\n\t\t\tunordered[order[m]] = 2;\n\t\tfor( int m = bestj; m < bestk; ++m )\n\t\t\tunordered[order[m]] = 3;\n\t\tfor( int m = bestk; m < count; ++m )\n\t\t\tunordered[order[m]] = 1;\n\n\t\tm_colours->RemapIndices( unordered, bestindices );\n\t\t\n\t\t// save the block\n\t\tWriteColourBlock4( beststart.GetVec3(), bestend.GetVec3(), bestindices, block );\n\n\t\t// save the error\n\t\tm_besterror = besterror;\n\t}\n}\n\n} // namespace squish\n"
  },
  {
    "path": "vendor/squish/clusterfit.h",
    "content": "/* -----------------------------------------------------------------------------\n\n\tCopyright (c) 2006 Simon Brown                          si@sjbrown.co.uk\n\tCopyright (c) 2007 Ignacio Castano                   icastano@nvidia.com\n\n\tPermission is hereby granted, free of charge, to any person obtaining\n\ta copy of this software and associated documentation files (the \n\t\"Software\"), to\tdeal in the Software without restriction, including\n\twithout limitation the rights to use, copy, modify, merge, publish,\n\tdistribute, sublicense, and/or sell copies of the Software, and to \n\tpermit persons to whom the Software is furnished to do so, subject to \n\tthe following conditions:\n\n\tThe above copyright notice and this permission notice shall be included\n\tin all copies or substantial portions of the Software.\n\n\tTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n\tOR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF \n\tMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\n\tIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY \n\tCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, \n\tTORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE \n\tSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\t\n   -------------------------------------------------------------------------- */\n   \n#ifndef SQUISH_CLUSTERFIT_H\n#define SQUISH_CLUSTERFIT_H\n\n#include \"squish.h\"\n#include \"maths.h\"\n#include \"simd.h\"\n#include \"colourfit.h\"\n\nnamespace squish {\n\nclass ClusterFit : public ColourFit\n{\npublic:\n\tClusterFit( ColourSet const* colours, int flags, float* metric );\n\t\nprivate:\n\tbool ConstructOrdering( Vec3 const& axis, int iteration );\n\n\tvirtual void Compress3( void* block );\n\tvirtual void Compress4( void* block );\n\n\tenum { kMaxIterations = 8 };\n\n\tint m_iterationCount;\n\tVec3 m_principle;\n\tu8 m_order[16*kMaxIterations];\n\tVec4 m_points_weights[16];\n\tVec4 m_xsum_wsum;\n\tVec4 m_metric;\n\tVec4 m_besterror;\n};\n\n} // namespace squish\n\n#endif // ndef SQUISH_CLUSTERFIT_H\n"
  },
  {
    "path": "vendor/squish/colourblock.cpp",
    "content": "/* -----------------------------------------------------------------------------\n\n\tCopyright (c) 2006 Simon Brown                          si@sjbrown.co.uk\n\n\tPermission is hereby granted, free of charge, to any person obtaining\n\ta copy of this software and associated documentation files (the \n\t\"Software\"), to\tdeal in the Software without restriction, including\n\twithout limitation the rights to use, copy, modify, merge, publish,\n\tdistribute, sublicense, and/or sell copies of the Software, and to \n\tpermit persons to whom the Software is furnished to do so, subject to \n\tthe following conditions:\n\n\tThe above copyright notice and this permission notice shall be included\n\tin all copies or substantial portions of the Software.\n\n\tTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n\tOR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF \n\tMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\n\tIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY \n\tCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, \n\tTORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE \n\tSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\t\n   -------------------------------------------------------------------------- */\n   \n#include \"colourblock.h\"\n\nnamespace squish {\n\nstatic int FloatToInt( float a, int limit )\n{\n\t// use ANSI round-to-zero behaviour to get round-to-nearest\n\tint i = ( int )( a + 0.5f );\n\n\t// clamp to the limit\n\tif( i < 0 )\n\t\ti = 0;\n\telse if( i > limit )\n\t\ti = limit; \n\n\t// done\n\treturn i;\n}\n\nstatic int FloatTo565( Vec3::Arg colour )\n{\n\t// get the components in the correct range\n\tint r = FloatToInt( 31.0f*colour.X(), 31 );\n\tint g = FloatToInt( 63.0f*colour.Y(), 63 );\n\tint b = FloatToInt( 31.0f*colour.Z(), 31 );\n\t\n\t// pack into a single value\n\treturn ( r << 11 ) | ( g << 5 ) | b;\n}\n\nstatic void WriteColourBlock( int a, int b, u8* indices, void* block )\n{\n\t// get the block as bytes\n\tu8* bytes = ( u8* )block;\n\n\t// write the endpoints\n\tbytes[0] = ( u8 )( a & 0xff );\n\tbytes[1] = ( u8 )( a >> 8 );\n\tbytes[2] = ( u8 )( b & 0xff );\n\tbytes[3] = ( u8 )( b >> 8 );\n\t\n\t// write the indices\n\tfor( int i = 0; i < 4; ++i )\n\t{\n\t\tu8 const* ind = indices + 4*i;\n\t\tbytes[4 + i] = ind[0] | ( ind[1] << 2 ) | ( ind[2] << 4 ) | ( ind[3] << 6 );\n\t}\n}\n\nvoid WriteColourBlock3( Vec3::Arg start, Vec3::Arg end, u8 const* indices, void* block )\n{\n\t// get the packed values\n\tint a = FloatTo565( start );\n\tint b = FloatTo565( end );\n\n\t// remap the indices\n\tu8 remapped[16];\n\tif( a <= b )\n\t{\n\t\t// use the indices directly\n\t\tfor( int i = 0; i < 16; ++i )\n\t\t\tremapped[i] = indices[i];\n\t}\n\telse\n\t{\n\t\t// swap a and b\n\t\tstd::swap( a, b );\n\t\tfor( int i = 0; i < 16; ++i )\n\t\t{\n\t\t\tif( indices[i] == 0 )\n\t\t\t\tremapped[i] = 1;\n\t\t\telse if( indices[i] == 1 )\n\t\t\t\tremapped[i] = 0;\n\t\t\telse\n\t\t\t\tremapped[i] = indices[i];\n\t\t}\n\t}\n\t\n\t// write the block\n\tWriteColourBlock( a, b, remapped, block );\n}\n\nvoid WriteColourBlock4( Vec3::Arg start, Vec3::Arg end, u8 const* indices, void* block )\n{\n\t// get the packed values\n\tint a = FloatTo565( start );\n\tint b = FloatTo565( end );\n\n\t// remap the indices\n\tu8 remapped[16];\n\tif( a < b )\n\t{\n\t\t// swap a and b\n\t\tstd::swap( a, b );\n\t\tfor( int i = 0; i < 16; ++i )\n\t\t\tremapped[i] = ( indices[i] ^ 0x1 ) & 0x3;\n\t}\n\telse if( a == b )\n\t{\n\t\t// use index 0\n\t\tfor( int i = 0; i < 16; ++i )\n\t\t\tremapped[i] = 0;\n\t}\n\telse\n\t{\n\t\t// use the indices directly\n\t\tfor( int i = 0; i < 16; ++i )\n\t\t\tremapped[i] = indices[i];\n\t}\n\t\n\t// write the block\n\tWriteColourBlock( a, b, remapped, block );\n}\n\nstatic int Unpack565( u8 const* packed, u8* colour )\n{\n\t// build the packed value\n\tint value = ( int )packed[0] | ( ( int )packed[1] << 8 );\n\t\n\t// get the components in the stored range\n\tu8 red = ( u8 )( ( value >> 11 ) & 0x1f );\n\tu8 green = ( u8 )( ( value >> 5 ) & 0x3f );\n\tu8 blue = ( u8 )( value & 0x1f );\n\n\t// scale up to 8 bits\n\tcolour[0] = ( red << 3 ) | ( red >> 2 );\n\tcolour[1] = ( green << 2 ) | ( green >> 4 );\n\tcolour[2] = ( blue << 3 ) | ( blue >> 2 );\n\tcolour[3] = 255;\n\t\n\t// return the value\n\treturn value;\n}\n\nvoid DecompressColour( u8* rgba, void const* block, bool isDxt1 )\n{\n\t// get the block bytes\n\tu8 const* bytes = reinterpret_cast< u8 const* >( block );\n\t\n\t// unpack the endpoints\n\tu8 codes[16];\n\tint a = Unpack565( bytes, codes );\n\tint b = Unpack565( bytes + 2, codes + 4 );\n\t\n\t// generate the midpoints\n\tfor( int i = 0; i < 3; ++i )\n\t{\n\t\tint c = codes[i];\n\t\tint d = codes[4 + i];\n\n\t\tif( isDxt1 && a <= b )\n\t\t{\n\t\t\tcodes[8 + i] = ( u8 )( ( c + d )/2 );\n\t\t\tcodes[12 + i] = 0;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tcodes[8 + i] = ( u8 )( ( 2*c + d )/3 );\n\t\t\tcodes[12 + i] = ( u8 )( ( c + 2*d )/3 );\n\t\t}\n\t}\n\t\n\t// fill in alpha for the intermediate values\n\tcodes[8 + 3] = 255;\n\tcodes[12 + 3] = ( isDxt1 && a <= b ) ? 0 : 255;\n\t\n\t// unpack the indices\n\tu8 indices[16];\n\tfor( int i = 0; i < 4; ++i )\n\t{\n\t\tu8* ind = indices + 4*i;\n\t\tu8 packed = bytes[4 + i];\n\t\t\n\t\tind[0] = packed & 0x3;\n\t\tind[1] = ( packed >> 2 ) & 0x3;\n\t\tind[2] = ( packed >> 4 ) & 0x3;\n\t\tind[3] = ( packed >> 6 ) & 0x3;\n\t}\n\n\t// store out the colours\n\tfor( int i = 0; i < 16; ++i )\n\t{\n\t\tu8 offset = 4*indices[i];\n\t\tfor( int j = 0; j < 4; ++j )\n\t\t\trgba[4*i + j] = codes[offset + j];\n\t}\n}\n\n} // namespace squish\n"
  },
  {
    "path": "vendor/squish/colourblock.h",
    "content": "/* -----------------------------------------------------------------------------\n\n\tCopyright (c) 2006 Simon Brown                          si@sjbrown.co.uk\n\n\tPermission is hereby granted, free of charge, to any person obtaining\n\ta copy of this software and associated documentation files (the \n\t\"Software\"), to\tdeal in the Software without restriction, including\n\twithout limitation the rights to use, copy, modify, merge, publish,\n\tdistribute, sublicense, and/or sell copies of the Software, and to \n\tpermit persons to whom the Software is furnished to do so, subject to \n\tthe following conditions:\n\n\tThe above copyright notice and this permission notice shall be included\n\tin all copies or substantial portions of the Software.\n\n\tTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n\tOR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF \n\tMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\n\tIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY \n\tCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, \n\tTORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE \n\tSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\t\n   -------------------------------------------------------------------------- */\n   \n#ifndef SQUISH_COLOURBLOCK_H\n#define SQUISH_COLOURBLOCK_H\n\n#include \"squish.h\"\n#include \"maths.h\"\n\nnamespace squish {\n\nvoid WriteColourBlock3( Vec3::Arg start, Vec3::Arg end, u8 const* indices, void* block );\nvoid WriteColourBlock4( Vec3::Arg start, Vec3::Arg end, u8 const* indices, void* block );\n\nvoid DecompressColour( u8* rgba, void const* block, bool isDxt1 );\n\n} // namespace squish\n\n#endif // ndef SQUISH_COLOURBLOCK_H\n"
  },
  {
    "path": "vendor/squish/colourfit.cpp",
    "content": "/* -----------------------------------------------------------------------------\n\n\tCopyright (c) 2006 Simon Brown                          si@sjbrown.co.uk\n\n\tPermission is hereby granted, free of charge, to any person obtaining\n\ta copy of this software and associated documentation files (the \n\t\"Software\"), to\tdeal in the Software without restriction, including\n\twithout limitation the rights to use, copy, modify, merge, publish,\n\tdistribute, sublicense, and/or sell copies of the Software, and to \n\tpermit persons to whom the Software is furnished to do so, subject to \n\tthe following conditions:\n\n\tThe above copyright notice and this permission notice shall be included\n\tin all copies or substantial portions of the Software.\n\n\tTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n\tOR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF \n\tMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\n\tIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY \n\tCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, \n\tTORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE \n\tSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\t\n   -------------------------------------------------------------------------- */\n   \n#include \"colourfit.h\"\n#include \"colourset.h\"\n\nnamespace squish {\n\nColourFit::ColourFit( ColourSet const* colours, int flags ) \n  : m_colours( colours ), \n\tm_flags( flags )\n{\n}\n\nColourFit::~ColourFit()\n{\n}\n\nvoid ColourFit::Compress( void* block )\n{\n\tbool isDxt1 = ( ( m_flags & kDxt1 ) != 0 );\n\tif( isDxt1 )\n\t{\n\t\tCompress3( block );\n\t\tif( !m_colours->IsTransparent() )\n\t\t\tCompress4( block );\n\t}\n\telse\n\t\tCompress4( block );\n}\n\n} // namespace squish\n"
  },
  {
    "path": "vendor/squish/colourfit.h",
    "content": "/* -----------------------------------------------------------------------------\n\n\tCopyright (c) 2006 Simon Brown                          si@sjbrown.co.uk\n\n\tPermission is hereby granted, free of charge, to any person obtaining\n\ta copy of this software and associated documentation files (the \n\t\"Software\"), to\tdeal in the Software without restriction, including\n\twithout limitation the rights to use, copy, modify, merge, publish,\n\tdistribute, sublicense, and/or sell copies of the Software, and to \n\tpermit persons to whom the Software is furnished to do so, subject to \n\tthe following conditions:\n\n\tThe above copyright notice and this permission notice shall be included\n\tin all copies or substantial portions of the Software.\n\n\tTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n\tOR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF \n\tMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\n\tIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY \n\tCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, \n\tTORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE \n\tSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\t\n   -------------------------------------------------------------------------- */\n   \n#ifndef SQUISH_COLOURFIT_H\n#define SQUISH_COLOURFIT_H\n\n#include \"squish.h\"\n#include <climits>\n#include \"maths.h\"\n\nnamespace squish {\n\nclass ColourSet;\n\nclass ColourFit\n{\npublic:\n\tColourFit( ColourSet const* colours, int flags );\n\tvirtual ~ColourFit();\n\n\tvoid Compress( void* block );\n\nprotected:\n\tvirtual void Compress3( void* block ) = 0;\n\tvirtual void Compress4( void* block ) = 0;\n\n\tColourSet const* m_colours;\n\tint m_flags;\n};\n\n} // namespace squish\n\n#endif // ndef SQUISH_COLOURFIT_H\n"
  },
  {
    "path": "vendor/squish/colourset.cpp",
    "content": "/* -----------------------------------------------------------------------------\n\n\tCopyright (c) 2006 Simon Brown                          si@sjbrown.co.uk\n\n\tPermission is hereby granted, free of charge, to any person obtaining\n\ta copy of this software and associated documentation files (the \n\t\"Software\"), to\tdeal in the Software without restriction, including\n\twithout limitation the rights to use, copy, modify, merge, publish,\n\tdistribute, sublicense, and/or sell copies of the Software, and to \n\tpermit persons to whom the Software is furnished to do so, subject to \n\tthe following conditions:\n\n\tThe above copyright notice and this permission notice shall be included\n\tin all copies or substantial portions of the Software.\n\n\tTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n\tOR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF \n\tMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\n\tIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY \n\tCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, \n\tTORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE \n\tSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\t\n   -------------------------------------------------------------------------- */\n   \n#include \"colourset.h\"\n\nnamespace squish {\n\nColourSet::ColourSet( u8 const* rgba, int mask, int flags )\n  : m_count( 0 ), \n\tm_transparent( false )\n{\n\t// check the compression mode for dxt1\n\tbool isDxt1 = ( ( flags & kDxt1 ) != 0 );\n\tbool weightByAlpha = ( ( flags & kWeightColourByAlpha ) != 0 );\n\n\t// create the minimal set\n\tfor( int i = 0; i < 16; ++i )\n\t{\n\t\t// check this pixel is enabled\n\t\tint bit = 1 << i;\n\t\tif( ( mask & bit ) == 0 )\n\t\t{\n\t\t\tm_remap[i] = -1;\n\t\t\tcontinue;\n\t\t}\n\t\n\t\t// check for transparent pixels when using dxt1\n\t\tif( isDxt1 && rgba[4*i + 3] < 128 )\n\t\t{\n\t\t\tm_remap[i] = -1;\n\t\t\tm_transparent = true;\n\t\t\tcontinue;\n\t\t}\n\n\t\t// loop over previous points for a match\n\t\tfor( int j = 0;; ++j )\n\t\t{\n\t\t\t// allocate a new point\n\t\t\tif( j == i )\n\t\t\t{\n\t\t\t\t// normalise coordinates to [0,1]\n\t\t\t\tfloat x = ( float )rgba[4*i] / 255.0f;\n\t\t\t\tfloat y = ( float )rgba[4*i + 1] / 255.0f;\n\t\t\t\tfloat z = ( float )rgba[4*i + 2] / 255.0f;\n\t\t\t\t\n\t\t\t\t// ensure there is always non-zero weight even for zero alpha\n\t\t\t\tfloat w = ( float )( rgba[4*i + 3] + 1 ) / 256.0f;\n\n\t\t\t\t// add the point\n\t\t\t\tm_points[m_count] = Vec3( x, y, z );\n\t\t\t\tm_weights[m_count] = ( weightByAlpha ? w : 1.0f );\n\t\t\t\tm_remap[i] = m_count;\n\t\t\t\t\n\t\t\t\t// advance\n\t\t\t\t++m_count;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\n\t\t\t// check for a match\n\t\t\tint oldbit = 1 << j;\n\t\t\tbool match = ( ( mask & oldbit ) != 0 )\n\t\t\t\t&& ( rgba[4*i] == rgba[4*j] )\n\t\t\t\t&& ( rgba[4*i + 1] == rgba[4*j + 1] )\n\t\t\t\t&& ( rgba[4*i + 2] == rgba[4*j + 2] )\n\t\t\t\t&& ( rgba[4*j + 3] >= 128 || !isDxt1 );\n\t\t\tif( match )\n\t\t\t{\n\t\t\t\t// get the index of the match\n\t\t\t\tint index = m_remap[j];\n\t\t\t\t\n\t\t\t\t// ensure there is always non-zero weight even for zero alpha\n\t\t\t\tfloat w = ( float )( rgba[4*i + 3] + 1 ) / 256.0f;\n\n\t\t\t\t// map to this point and increase the weight\n\t\t\t\tm_weights[index] += ( weightByAlpha ? w : 1.0f );\n\t\t\t\tm_remap[i] = index;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\t// square root the weights\n\tfor( int i = 0; i < m_count; ++i )\n\t\tm_weights[i] = std::sqrt( m_weights[i] );\n}\n\nvoid ColourSet::RemapIndices( u8 const* source, u8* target ) const\n{\n\tfor( int i = 0; i < 16; ++i )\n\t{\n\t\tint j = m_remap[i];\n\t\tif( j == -1 )\n\t\t\ttarget[i] = 3;\n\t\telse\n\t\t\ttarget[i] = source[j];\n\t}\n}\n\n} // namespace squish\n"
  },
  {
    "path": "vendor/squish/colourset.h",
    "content": "/* -----------------------------------------------------------------------------\n\n\tCopyright (c) 2006 Simon Brown                          si@sjbrown.co.uk\n\n\tPermission is hereby granted, free of charge, to any person obtaining\n\ta copy of this software and associated documentation files (the \n\t\"Software\"), to\tdeal in the Software without restriction, including\n\twithout limitation the rights to use, copy, modify, merge, publish,\n\tdistribute, sublicense, and/or sell copies of the Software, and to \n\tpermit persons to whom the Software is furnished to do so, subject to \n\tthe following conditions:\n\n\tThe above copyright notice and this permission notice shall be included\n\tin all copies or substantial portions of the Software.\n\n\tTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n\tOR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF \n\tMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\n\tIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY \n\tCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, \n\tTORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE \n\tSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\t\n   -------------------------------------------------------------------------- */\n   \n#ifndef SQUISH_COLOURSET_H\n#define SQUISH_COLOURSET_H\n\n#include \"squish.h\"\n#include \"maths.h\"\n\nnamespace squish {\n\n/*! @brief Represents a set of block colours\n*/\nclass ColourSet\n{\npublic:\n\tColourSet( u8 const* rgba, int mask, int flags );\n\n\tint GetCount() const { return m_count; }\n\tVec3 const* GetPoints() const { return m_points; }\n\tfloat const* GetWeights() const { return m_weights; }\n\tbool IsTransparent() const { return m_transparent; }\n\n\tvoid RemapIndices( u8 const* source, u8* target ) const;\n\nprivate:\n\tint m_count;\n\tVec3 m_points[16];\n\tfloat m_weights[16];\n\tint m_remap[16];\n\tbool m_transparent;\n};\n\n} // namespace sqish\n\n#endif // ndef SQUISH_COLOURSET_H\n"
  },
  {
    "path": "vendor/squish/config.h",
    "content": "/* -----------------------------------------------------------------------------\n\n\tCopyright (c) 2006 Simon Brown                          si@sjbrown.co.uk\n\n\tPermission is hereby granted, free of charge, to any person obtaining\n\ta copy of this software and associated documentation files (the \n\t\"Software\"), to\tdeal in the Software without restriction, including\n\twithout limitation the rights to use, copy, modify, merge, publish,\n\tdistribute, sublicense, and/or sell copies of the Software, and to \n\tpermit persons to whom the Software is furnished to do so, subject to \n\tthe following conditions:\n\n\tThe above copyright notice and this permission notice shall be included\n\tin all copies or substantial portions of the Software.\n\n\tTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n\tOR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF \n\tMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\n\tIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY \n\tCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, \n\tTORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE \n\tSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\t\n   -------------------------------------------------------------------------- */\n   \n#ifndef SQUISH_CONFIG_H\n#define SQUISH_CONFIG_H\n\n// Set to 1 when building squish to use Altivec instructions.\n#ifndef SQUISH_USE_ALTIVEC\n#define SQUISH_USE_ALTIVEC 0\n#endif\n\n// Set to 1 or 2 when building squish to use SSE or SSE2 instructions.\n#ifndef SQUISH_USE_SSE\n#define SQUISH_USE_SSE 0\n#endif\n\n// Internally set SQUISH_USE_SIMD when either Altivec or SSE is available.\n#if SQUISH_USE_ALTIVEC && SQUISH_USE_SSE\n#error \"Cannot enable both Altivec and SSE!\"\n#endif\n#if SQUISH_USE_ALTIVEC || SQUISH_USE_SSE\n#define SQUISH_USE_SIMD 1\n#else\n#define SQUISH_USE_SIMD 0\n#endif\n\n#endif // ndef SQUISH_CONFIG_H\n"
  },
  {
    "path": "vendor/squish/maths.cpp",
    "content": "/* -----------------------------------------------------------------------------\n\n\tCopyright (c) 2006 Simon Brown                          si@sjbrown.co.uk\n\n\tPermission is hereby granted, free of charge, to any person obtaining\n\ta copy of this software and associated documentation files (the \n\t\"Software\"), to\tdeal in the Software without restriction, including\n\twithout limitation the rights to use, copy, modify, merge, publish,\n\tdistribute, sublicense, and/or sell copies of the Software, and to \n\tpermit persons to whom the Software is furnished to do so, subject to \n\tthe following conditions:\n\n\tThe above copyright notice and this permission notice shall be included\n\tin all copies or substantial portions of the Software.\n\n\tTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n\tOR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF \n\tMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\n\tIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY \n\tCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, \n\tTORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE \n\tSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\t\n   -------------------------------------------------------------------------- */\n   \n/*! @file\n\n\tThe symmetric eigensystem solver algorithm is from \n\thttp://www.geometrictools.com/Documentation/EigenSymmetric3x3.pdf\n*/\n\n#include \"maths.h\"\n#include \"simd.h\"\n#include <cfloat>\n\nnamespace squish {\n\nSym3x3 ComputeWeightedCovariance( int n, Vec3 const* points, float const* weights )\n{\n\t// compute the centroid\n\tfloat total = 0.0f;\n\tVec3 centroid( 0.0f );\n\tfor( int i = 0; i < n; ++i )\n\t{\n\t\ttotal += weights[i];\n\t\tcentroid += weights[i]*points[i];\n\t}\n\tif( total > FLT_EPSILON )\n\t\tcentroid /= total;\n\n\t// accumulate the covariance matrix\n\tSym3x3 covariance( 0.0f );\n\tfor( int i = 0; i < n; ++i )\n\t{\n\t\tVec3 a = points[i] - centroid;\n\t\tVec3 b = weights[i]*a;\n\t\t\n\t\tcovariance[0] += a.X()*b.X();\n\t\tcovariance[1] += a.X()*b.Y();\n\t\tcovariance[2] += a.X()*b.Z();\n\t\tcovariance[3] += a.Y()*b.Y();\n\t\tcovariance[4] += a.Y()*b.Z();\n\t\tcovariance[5] += a.Z()*b.Z();\n\t}\n\t\n\t// return it\n\treturn covariance;\n}\n\n#if 0\n\nstatic Vec3 GetMultiplicity1Evector( Sym3x3 const& matrix, float evalue )\n{\n\t// compute M\n\tSym3x3 m;\n\tm[0] = matrix[0] - evalue;\n\tm[1] = matrix[1];\n\tm[2] = matrix[2];\n\tm[3] = matrix[3] - evalue;\n\tm[4] = matrix[4];\n\tm[5] = matrix[5] - evalue;\n\n\t// compute U\n\tSym3x3 u;\n\tu[0] = m[3]*m[5] - m[4]*m[4];\n\tu[1] = m[2]*m[4] - m[1]*m[5];\n\tu[2] = m[1]*m[4] - m[2]*m[3];\n\tu[3] = m[0]*m[5] - m[2]*m[2];\n\tu[4] = m[1]*m[2] - m[4]*m[0];\n\tu[5] = m[0]*m[3] - m[1]*m[1];\n\n\t// find the largest component\n\tfloat mc = std::fabs( u[0] );\n\tint mi = 0;\n\tfor( int i = 1; i < 6; ++i )\n\t{\n\t\tfloat c = std::fabs( u[i] );\n\t\tif( c > mc )\n\t\t{\n\t\t\tmc = c;\n\t\t\tmi = i;\n\t\t}\n\t}\n\n\t// pick the column with this component\n\tswitch( mi )\n\t{\n\tcase 0:\n\t\treturn Vec3( u[0], u[1], u[2] );\n\n\tcase 1:\n\tcase 3:\n\t\treturn Vec3( u[1], u[3], u[4] );\n\n\tdefault:\n\t\treturn Vec3( u[2], u[4], u[5] );\n\t}\n}\n\nstatic Vec3 GetMultiplicity2Evector( Sym3x3 const& matrix, float evalue )\n{\n\t// compute M\n\tSym3x3 m;\n\tm[0] = matrix[0] - evalue;\n\tm[1] = matrix[1];\n\tm[2] = matrix[2];\n\tm[3] = matrix[3] - evalue;\n\tm[4] = matrix[4];\n\tm[5] = matrix[5] - evalue;\n\n\t// find the largest component\n\tfloat mc = std::fabs( m[0] );\n\tint mi = 0;\n\tfor( int i = 1; i < 6; ++i )\n\t{\n\t\tfloat c = std::fabs( m[i] );\n\t\tif( c > mc )\n\t\t{\n\t\t\tmc = c;\n\t\t\tmi = i;\n\t\t}\n\t}\n\n\t// pick the first eigenvector based on this index\n\tswitch( mi )\n\t{\n\tcase 0:\n\tcase 1:\n\t\treturn Vec3( -m[1], m[0], 0.0f );\n\n\tcase 2:\n\t\treturn Vec3( m[2], 0.0f, -m[0] );\n\n\tcase 3:\n\tcase 4:\n\t\treturn Vec3( 0.0f, -m[4], m[3] );\n\n\tdefault:\n\t\treturn Vec3( 0.0f, -m[5], m[4] );\n\t}\n}\n\nVec3 ComputePrincipleComponent( Sym3x3 const& matrix )\n{\n\t// compute the cubic coefficients\n\tfloat c0 = matrix[0]*matrix[3]*matrix[5] \n\t\t+ 2.0f*matrix[1]*matrix[2]*matrix[4] \n\t\t- matrix[0]*matrix[4]*matrix[4] \n\t\t- matrix[3]*matrix[2]*matrix[2] \n\t\t- matrix[5]*matrix[1]*matrix[1];\n\tfloat c1 = matrix[0]*matrix[3] + matrix[0]*matrix[5] + matrix[3]*matrix[5]\n\t\t- matrix[1]*matrix[1] - matrix[2]*matrix[2] - matrix[4]*matrix[4];\n\tfloat c2 = matrix[0] + matrix[3] + matrix[5];\n\n\t// compute the quadratic coefficients\n\tfloat a = c1 - ( 1.0f/3.0f )*c2*c2;\n\tfloat b = ( -2.0f/27.0f )*c2*c2*c2 + ( 1.0f/3.0f )*c1*c2 - c0;\n\n\t// compute the root count check\n\tfloat Q = 0.25f*b*b + ( 1.0f/27.0f )*a*a*a;\n\n\t// test the multiplicity\n\tif( FLT_EPSILON < Q )\n\t{\n\t\t// only one root, which implies we have a multiple of the identity\n        return Vec3( 1.0f );\n\t}\n\telse if( Q < -FLT_EPSILON )\n\t{\n\t\t// three distinct roots\n\t\tfloat theta = std::atan2( std::sqrt( -Q ), -0.5f*b );\n\t\tfloat rho = std::sqrt( 0.25f*b*b - Q );\n\n\t\tfloat rt = std::pow( rho, 1.0f/3.0f );\n\t\tfloat ct = std::cos( theta/3.0f );\n\t\tfloat st = std::sin( theta/3.0f );\n\n\t\tfloat l1 = ( 1.0f/3.0f )*c2 + 2.0f*rt*ct;\n\t\tfloat l2 = ( 1.0f/3.0f )*c2 - rt*( ct + ( float )sqrt( 3.0f )*st );\n\t\tfloat l3 = ( 1.0f/3.0f )*c2 - rt*( ct - ( float )sqrt( 3.0f )*st );\n\n\t\t// pick the larger\n\t\tif( std::fabs( l2 ) > std::fabs( l1 ) )\n\t\t\tl1 = l2;\n\t\tif( std::fabs( l3 ) > std::fabs( l1 ) )\n\t\t\tl1 = l3;\n\n\t\t// get the eigenvector\n\t\treturn GetMultiplicity1Evector( matrix, l1 );\n\t}\n\telse // if( -FLT_EPSILON <= Q && Q <= FLT_EPSILON )\n\t{\n\t\t// two roots\n\t\tfloat rt;\n\t\tif( b < 0.0f )\n\t\t\trt = -std::pow( -0.5f*b, 1.0f/3.0f );\n\t\telse\n\t\t\trt = std::pow( 0.5f*b, 1.0f/3.0f );\n\t\t\n\t\tfloat l1 = ( 1.0f/3.0f )*c2 + rt;\t\t// repeated\n\t\tfloat l2 = ( 1.0f/3.0f )*c2 - 2.0f*rt;\n\t\t\n\t\t// get the eigenvector\n\t\tif( std::fabs( l1 ) > std::fabs( l2 ) )\n\t\t\treturn GetMultiplicity2Evector( matrix, l1 );\n\t\telse\n\t\t\treturn GetMultiplicity1Evector( matrix, l2 );\n\t}\n}\n\n#else\n\n#define POWER_ITERATION_COUNT \t8\n\nVec3 ComputePrincipleComponent( Sym3x3 const& matrix )\n{\n\tVec4 const row0( matrix[0], matrix[1], matrix[2], 0.0f );\n\tVec4 const row1( matrix[1], matrix[3], matrix[4], 0.0f );\n\tVec4 const row2( matrix[2], matrix[4], matrix[5], 0.0f );\n\tVec4 v = VEC4_CONST( 1.0f );\n\tfor( int i = 0; i < POWER_ITERATION_COUNT; ++i )\n\t{\n\t\t// matrix multiply\n\t\tVec4 w = row0*v.SplatX();\n\t\tw = MultiplyAdd(row1, v.SplatY(), w);\n\t\tw = MultiplyAdd(row2, v.SplatZ(), w);\n\n\t\t// get max component from xyz in all channels\n\t\tVec4 a = Max(w.SplatX(), Max(w.SplatY(), w.SplatZ()));\n\n\t\t// divide through and advance\n\t\tv = w*Reciprocal(a);\n\t}\n\treturn v.GetVec3();\n}\n\n#endif\n\n} // namespace squish\n"
  },
  {
    "path": "vendor/squish/maths.h",
    "content": "/* -----------------------------------------------------------------------------\n\n\tCopyright (c) 2006 Simon Brown                          si@sjbrown.co.uk\n\n\tPermission is hereby granted, free of charge, to any person obtaining\n\ta copy of this software and associated documentation files (the \n\t\"Software\"), to\tdeal in the Software without restriction, including\n\twithout limitation the rights to use, copy, modify, merge, publish,\n\tdistribute, sublicense, and/or sell copies of the Software, and to \n\tpermit persons to whom the Software is furnished to do so, subject to \n\tthe following conditions:\n\n\tThe above copyright notice and this permission notice shall be included\n\tin all copies or substantial portions of the Software.\n\n\tTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n\tOR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF \n\tMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\n\tIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY \n\tCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, \n\tTORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE \n\tSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\t\n   -------------------------------------------------------------------------- */\n   \n#ifndef SQUISH_MATHS_H\n#define SQUISH_MATHS_H\n\n#include <cmath>\n#include <algorithm>\n#include \"config.h\"\n\nnamespace squish {\n\nclass Vec3\n{\npublic:\n\ttypedef Vec3 const& Arg;\n\n\tVec3()\n\t{\n\t}\n\n\texplicit Vec3( float s )\n\t{\n\t\tm_x = s;\n\t\tm_y = s;\n\t\tm_z = s;\n\t}\n\n\tVec3( float x, float y, float z )\n\t{\n\t\tm_x = x;\n\t\tm_y = y;\n\t\tm_z = z;\n\t}\n\t\n\tfloat X() const { return m_x; }\n\tfloat Y() const { return m_y; }\n\tfloat Z() const { return m_z; }\n\t\n\tVec3 operator-() const\n\t{\n\t\treturn Vec3( -m_x, -m_y, -m_z );\n\t}\n\t\n\tVec3& operator+=( Arg v )\n\t{\n\t\tm_x += v.m_x;\n\t\tm_y += v.m_y;\n\t\tm_z += v.m_z;\n\t\treturn *this;\n\t}\n\t\n\tVec3& operator-=( Arg v )\n\t{\n\t\tm_x -= v.m_x;\n\t\tm_y -= v.m_y;\n\t\tm_z -= v.m_z;\n\t\treturn *this;\n\t}\n\t\n\tVec3& operator*=( Arg v )\n\t{\n\t\tm_x *= v.m_x;\n\t\tm_y *= v.m_y;\n\t\tm_z *= v.m_z;\n\t\treturn *this;\n\t}\n\t\n\tVec3& operator*=( float s )\n\t{\n\t\tm_x *= s;\n\t\tm_y *= s;\n\t\tm_z *= s;\n\t\treturn *this;\n\t}\n\t\n\tVec3& operator/=( Arg v )\n\t{\n\t\tm_x /= v.m_x;\n\t\tm_y /= v.m_y;\n\t\tm_z /= v.m_z;\n\t\treturn *this;\n\t}\n\t\n\tVec3& operator/=( float s )\n\t{\n\t\tfloat t = 1.0f/s;\n\t\tm_x *= t;\n\t\tm_y *= t;\n\t\tm_z *= t;\n\t\treturn *this;\n\t}\n\t\n\tfriend Vec3 operator+( Arg left, Arg right )\n\t{\n\t\tVec3 copy( left );\n\t\treturn copy += right;\n\t}\n\t\n\tfriend Vec3 operator-( Arg left, Arg right )\n\t{\n\t\tVec3 copy( left );\n\t\treturn copy -= right;\n\t}\n\t\n\tfriend Vec3 operator*( Arg left, Arg right )\n\t{\n\t\tVec3 copy( left );\n\t\treturn copy *= right;\n\t}\n\t\n\tfriend Vec3 operator*( Arg left, float right )\n\t{\n\t\tVec3 copy( left );\n\t\treturn copy *= right;\n\t}\n\t\n\tfriend Vec3 operator*( float left, Arg right )\n\t{\n\t\tVec3 copy( right );\n\t\treturn copy *= left;\n\t}\n\t\n\tfriend Vec3 operator/( Arg left, Arg right )\n\t{\n\t\tVec3 copy( left );\n\t\treturn copy /= right;\n\t}\n\t\n\tfriend Vec3 operator/( Arg left, float right )\n\t{\n\t\tVec3 copy( left );\n\t\treturn copy /= right;\n\t}\n\t\n\tfriend float Dot( Arg left, Arg right )\n\t{\n\t\treturn left.m_x*right.m_x + left.m_y*right.m_y + left.m_z*right.m_z;\n\t}\n\t\n\tfriend Vec3 Min( Arg left, Arg right )\n\t{\n\t\treturn Vec3(\n\t\t\tstd::min( left.m_x, right.m_x ), \n\t\t\tstd::min( left.m_y, right.m_y ), \n\t\t\tstd::min( left.m_z, right.m_z )\n\t\t);\n\t}\n\n\tfriend Vec3 Max( Arg left, Arg right )\n\t{\n\t\treturn Vec3(\n\t\t\tstd::max( left.m_x, right.m_x ), \n\t\t\tstd::max( left.m_y, right.m_y ), \n\t\t\tstd::max( left.m_z, right.m_z )\n\t\t);\n\t}\n\n\tfriend Vec3 Truncate( Arg v )\n\t{\n\t\treturn Vec3(\n\t\t\tv.m_x > 0.0f ? std::floor( v.m_x ) : std::ceil( v.m_x ), \n\t\t\tv.m_y > 0.0f ? std::floor( v.m_y ) : std::ceil( v.m_y ), \n\t\t\tv.m_z > 0.0f ? std::floor( v.m_z ) : std::ceil( v.m_z )\n\t\t);\n\t}\n\nprivate:\n\tfloat m_x;\n\tfloat m_y;\n\tfloat m_z;\n};\n\ninline float LengthSquared( Vec3::Arg v )\n{\n\treturn Dot( v, v );\n}\n\nclass Sym3x3\n{\npublic:\n\tSym3x3()\n\t{\n\t}\n\n\tSym3x3( float s )\n\t{\n\t\tfor( int i = 0; i < 6; ++i )\n\t\t\tm_x[i] = s;\n\t}\n\n\tfloat operator[]( int index ) const\n\t{\n\t\treturn m_x[index];\n\t}\n\n\tfloat& operator[]( int index )\n\t{\n\t\treturn m_x[index];\n\t}\n\nprivate:\n\tfloat m_x[6];\n};\n\nSym3x3 ComputeWeightedCovariance( int n, Vec3 const* points, float const* weights );\nVec3 ComputePrincipleComponent( Sym3x3 const& matrix );\n\n} // namespace squish\n\n#endif // ndef SQUISH_MATHS_H\n"
  },
  {
    "path": "vendor/squish/rangefit.cpp",
    "content": "/* -----------------------------------------------------------------------------\n\n\tCopyright (c) 2006 Simon Brown                          si@sjbrown.co.uk\n\n\tPermission is hereby granted, free of charge, to any person obtaining\n\ta copy of this software and associated documentation files (the \n\t\"Software\"), to\tdeal in the Software without restriction, including\n\twithout limitation the rights to use, copy, modify, merge, publish,\n\tdistribute, sublicense, and/or sell copies of the Software, and to \n\tpermit persons to whom the Software is furnished to do so, subject to \n\tthe following conditions:\n\n\tThe above copyright notice and this permission notice shall be included\n\tin all copies or substantial portions of the Software.\n\n\tTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n\tOR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF \n\tMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\n\tIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY \n\tCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, \n\tTORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE \n\tSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\t\n   -------------------------------------------------------------------------- */\n   \n#include \"rangefit.h\"\n#include \"colourset.h\"\n#include \"colourblock.h\"\n#include <cfloat>\n\nnamespace squish {\n\nRangeFit::RangeFit( ColourSet const* colours, int flags, float* metric ) \n  : ColourFit( colours, flags )\n{\n\t// initialise the metric (old perceptual = 0.2126f, 0.7152f, 0.0722f)\n\tif( metric )\n\t\tm_metric = Vec3( metric[0], metric[1], metric[2] );\n\telse\n\t\tm_metric = Vec3( 1.0f );\t\n\n\t// initialise the best error\n\tm_besterror = FLT_MAX;\n\n\t// cache some values\n\tint const count = m_colours->GetCount();\n\tVec3 const* values = m_colours->GetPoints();\n\tfloat const* weights = m_colours->GetWeights();\n\t\n\t// get the covariance matrix\n\tSym3x3 covariance = ComputeWeightedCovariance( count, values, weights );\n\t\n\t// compute the principle component\n\tVec3 principle = ComputePrincipleComponent( covariance );\n\n\t// get the min and max range as the codebook endpoints\n\tVec3 start( 0.0f );\n\tVec3 end( 0.0f );\n\tif( count > 0 )\n\t{\n\t\tfloat min, max;\n\t\t\n\t\t// compute the range\n\t\tstart = end = values[0];\n\t\tmin = max = Dot( values[0], principle );\n\t\tfor( int i = 1; i < count; ++i )\n\t\t{\n\t\t\tfloat val = Dot( values[i], principle );\n\t\t\tif( val < min )\n\t\t\t{\n\t\t\t\tstart = values[i];\n\t\t\t\tmin = val;\n\t\t\t}\n\t\t\telse if( val > max )\n\t\t\t{\n\t\t\t\tend = values[i];\n\t\t\t\tmax = val;\n\t\t\t}\n\t\t}\n\t}\n\t\t\t\n\t// clamp the output to [0, 1]\n\tVec3 const one( 1.0f );\n\tVec3 const zero( 0.0f );\n\tstart = Min( one, Max( zero, start ) );\n\tend = Min( one, Max( zero, end ) );\n\n\t// clamp to the grid and save\n\tVec3 const grid( 31.0f, 63.0f, 31.0f );\n\tVec3 const gridrcp( 1.0f/31.0f, 1.0f/63.0f, 1.0f/31.0f );\n\tVec3 const half( 0.5f );\n\tm_start = Truncate( grid*start + half )*gridrcp;\n\tm_end = Truncate( grid*end + half )*gridrcp;\n}\n\nvoid RangeFit::Compress3( void* block )\n{\n\t// cache some values\n\tint const count = m_colours->GetCount();\n\tVec3 const* values = m_colours->GetPoints();\n\t\n\t// create a codebook\n\tVec3 codes[3];\n\tcodes[0] = m_start;\n\tcodes[1] = m_end;\n\tcodes[2] = 0.5f*m_start + 0.5f*m_end;\n\n\t// match each point to the closest code\n\tu8 closest[16];\n\tfloat error = 0.0f;\n\tfor( int i = 0; i < count; ++i )\n\t{\n\t\t// find the closest code\n\t\tfloat dist = FLT_MAX;\n\t\tint idx = 0;\n\t\tfor( int j = 0; j < 3; ++j )\n\t\t{\n\t\t\tfloat d = LengthSquared( m_metric*( values[i] - codes[j] ) );\n\t\t\tif( d < dist )\n\t\t\t{\n\t\t\t\tdist = d;\n\t\t\t\tidx = j;\n\t\t\t}\n\t\t}\n\t\t\n\t\t// save the index\n\t\tclosest[i] = ( u8 )idx;\n\t\t\n\t\t// accumulate the error\n\t\terror += dist;\n\t}\n\t\n\t// save this scheme if it wins\n\tif( error < m_besterror )\n\t{\n\t\t// remap the indices\n\t\tu8 indices[16];\n\t\tm_colours->RemapIndices( closest, indices );\n\t\t\n\t\t// save the block\n\t\tWriteColourBlock3( m_start, m_end, indices, block );\n\t\t\n\t\t// save the error\n\t\tm_besterror = error;\n\t}\n}\n\nvoid RangeFit::Compress4( void* block )\n{\n\t// cache some values\n\tint const count = m_colours->GetCount();\n\tVec3 const* values = m_colours->GetPoints();\n\t\n\t// create a codebook\n\tVec3 codes[4];\n\tcodes[0] = m_start;\n\tcodes[1] = m_end;\n\tcodes[2] = ( 2.0f/3.0f )*m_start + ( 1.0f/3.0f )*m_end;\n\tcodes[3] = ( 1.0f/3.0f )*m_start + ( 2.0f/3.0f )*m_end;\n\n\t// match each point to the closest code\n\tu8 closest[16];\n\tfloat error = 0.0f;\n\tfor( int i = 0; i < count; ++i )\n\t{\n\t\t// find the closest code\n\t\tfloat dist = FLT_MAX;\n\t\tint idx = 0;\n\t\tfor( int j = 0; j < 4; ++j )\n\t\t{\n\t\t\tfloat d = LengthSquared( m_metric*( values[i] - codes[j] ) );\n\t\t\tif( d < dist )\n\t\t\t{\n\t\t\t\tdist = d;\n\t\t\t\tidx = j;\n\t\t\t}\n\t\t}\n\t\t\n\t\t// save the index\n\t\tclosest[i] = ( u8 )idx;\n\t\t\n\t\t// accumulate the error\n\t\terror += dist;\n\t}\n\t\n\t// save this scheme if it wins\n\tif( error < m_besterror )\n\t{\n\t\t// remap the indices\n\t\tu8 indices[16];\n\t\tm_colours->RemapIndices( closest, indices );\n\t\t\n\t\t// save the block\n\t\tWriteColourBlock4( m_start, m_end, indices, block );\n\n\t\t// save the error\n\t\tm_besterror = error;\n\t}\n}\n\n} // namespace squish\n"
  },
  {
    "path": "vendor/squish/rangefit.h",
    "content": "/* -----------------------------------------------------------------------------\n\n\tCopyright (c) 2006 Simon Brown                          si@sjbrown.co.uk\n\n\tPermission is hereby granted, free of charge, to any person obtaining\n\ta copy of this software and associated documentation files (the \n\t\"Software\"), to\tdeal in the Software without restriction, including\n\twithout limitation the rights to use, copy, modify, merge, publish,\n\tdistribute, sublicense, and/or sell copies of the Software, and to \n\tpermit persons to whom the Software is furnished to do so, subject to \n\tthe following conditions:\n\n\tThe above copyright notice and this permission notice shall be included\n\tin all copies or substantial portions of the Software.\n\n\tTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n\tOR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF \n\tMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\n\tIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY \n\tCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, \n\tTORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE \n\tSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\t\n   -------------------------------------------------------------------------- */\n   \n#ifndef SQUISH_RANGEFIT_H\n#define SQUISH_RANGEFIT_H\n\n#include \"squish.h\"\n#include \"colourfit.h\"\n#include \"maths.h\"\n\nnamespace squish {\n\nclass ColourSet;\n\nclass RangeFit : public ColourFit\n{\npublic:\n\tRangeFit( ColourSet const* colours, int flags, float* metric );\n\t\nprivate:\n\tvirtual void Compress3( void* block );\n\tvirtual void Compress4( void* block );\n\t\n\tVec3 m_metric;\n\tVec3 m_start;\n\tVec3 m_end;\n\tfloat m_besterror;\n};\n\n} // squish\n\n#endif // ndef SQUISH_RANGEFIT_H\n"
  },
  {
    "path": "vendor/squish/simd.h",
    "content": "/* -----------------------------------------------------------------------------\n\n\tCopyright (c) 2006 Simon Brown                          si@sjbrown.co.uk\n\n\tPermission is hereby granted, free of charge, to any person obtaining\n\ta copy of this software and associated documentation files (the \n\t\"Software\"), to\tdeal in the Software without restriction, including\n\twithout limitation the rights to use, copy, modify, merge, publish,\n\tdistribute, sublicense, and/or sell copies of the Software, and to \n\tpermit persons to whom the Software is furnished to do so, subject to \n\tthe following conditions:\n\n\tThe above copyright notice and this permission notice shall be included\n\tin all copies or substantial portions of the Software.\n\n\tTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n\tOR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF \n\tMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\n\tIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY \n\tCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, \n\tTORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE \n\tSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\t\n   -------------------------------------------------------------------------- */\n   \n#ifndef SQUISH_SIMD_H\n#define SQUISH_SIMD_H\n\n#include \"maths.h\"\n\n#if SQUISH_USE_ALTIVEC\n#include \"simd_ve.h\"\n#elif SQUISH_USE_SSE\n#include \"simd_sse.h\"\n#else\n#include \"simd_float.h\"\n#endif\n\n\n#endif // ndef SQUISH_SIMD_H\n"
  },
  {
    "path": "vendor/squish/simd_float.h",
    "content": "/* -----------------------------------------------------------------------------\n\n\tCopyright (c) 2006 Simon Brown                          si@sjbrown.co.uk\n\n\tPermission is hereby granted, free of charge, to any person obtaining\n\ta copy of this software and associated documentation files (the \n\t\"Software\"), to\tdeal in the Software without restriction, including\n\twithout limitation the rights to use, copy, modify, merge, publish,\n\tdistribute, sublicense, and/or sell copies of the Software, and to \n\tpermit persons to whom the Software is furnished to do so, subject to \n\tthe following conditions:\n\n\tThe above copyright notice and this permission notice shall be included\n\tin all copies or substantial portions of the Software.\n\n\tTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n\tOR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF \n\tMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\n\tIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY \n\tCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, \n\tTORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE \n\tSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\t\n   -------------------------------------------------------------------------- */\n   \n#ifndef SQUISH_SIMD_FLOAT_H\n#define SQUISH_SIMD_FLOAT_H\n\n#include <algorithm>\n\nnamespace squish {\n\n#define VEC4_CONST( X ) Vec4( X )\n\nclass Vec4\n{\npublic:\n\ttypedef Vec4 const& Arg;\n\n\tVec4() {}\n\t\t\n\texplicit Vec4( float s )\n\t  : m_x( s ),\n\t\tm_y( s ),\n\t\tm_z( s ),\n\t\tm_w( s )\n\t{\n\t}\n\t\n\tVec4( float x, float y, float z, float w )\n\t  : m_x( x ),\n\t\tm_y( y ),\n\t\tm_z( z ),\n\t\tm_w( w )\n\t{\n\t}\n\t\n\tVec3 GetVec3() const\n\t{\n\t\treturn Vec3( m_x, m_y, m_z );\n\t}\n\t\n\tVec4 SplatX() const { return Vec4( m_x ); }\n\tVec4 SplatY() const { return Vec4( m_y ); }\n\tVec4 SplatZ() const { return Vec4( m_z ); }\n\tVec4 SplatW() const { return Vec4( m_w ); }\n\n\tVec4& operator+=( Arg v )\n\t{\n\t\tm_x += v.m_x;\n\t\tm_y += v.m_y;\n\t\tm_z += v.m_z;\n\t\tm_w += v.m_w;\n\t\treturn *this;\n\t}\n\t\n\tVec4& operator-=( Arg v )\n\t{\n\t\tm_x -= v.m_x;\n\t\tm_y -= v.m_y;\n\t\tm_z -= v.m_z;\n\t\tm_w -= v.m_w;\n\t\treturn *this;\n\t}\n\t\n\tVec4& operator*=( Arg v )\n\t{\n\t\tm_x *= v.m_x;\n\t\tm_y *= v.m_y;\n\t\tm_z *= v.m_z;\n\t\tm_w *= v.m_w;\n\t\treturn *this;\n\t}\n\t\n\tfriend Vec4 operator+( Vec4::Arg left, Vec4::Arg right  )\n\t{\n\t\tVec4 copy( left );\n\t\treturn copy += right;\n\t}\n\t\n\tfriend Vec4 operator-( Vec4::Arg left, Vec4::Arg right  )\n\t{\n\t\tVec4 copy( left );\n\t\treturn copy -= right;\n\t}\n\t\n\tfriend Vec4 operator*( Vec4::Arg left, Vec4::Arg right  )\n\t{\n\t\tVec4 copy( left );\n\t\treturn copy *= right;\n\t}\n\t\n\t//! Returns a*b + c\n\tfriend Vec4 MultiplyAdd( Vec4::Arg a, Vec4::Arg b, Vec4::Arg c )\n\t{\n\t\treturn a*b + c;\n\t}\n\t\n\t//! Returns -( a*b - c )\n\tfriend Vec4 NegativeMultiplySubtract( Vec4::Arg a, Vec4::Arg b, Vec4::Arg c )\n\t{\n\t\treturn c - a*b;\n\t}\n\t\n\tfriend Vec4 Reciprocal( Vec4::Arg v )\n\t{\n\t\treturn Vec4( \n\t\t\t1.0f/v.m_x, \n\t\t\t1.0f/v.m_y, \n\t\t\t1.0f/v.m_z, \n\t\t\t1.0f/v.m_w \n\t\t);\n\t}\n\t\n\tfriend Vec4 Min( Vec4::Arg left, Vec4::Arg right )\n\t{\n\t\treturn Vec4( \n\t\t\tstd::min( left.m_x, right.m_x ), \n\t\t\tstd::min( left.m_y, right.m_y ), \n\t\t\tstd::min( left.m_z, right.m_z ), \n\t\t\tstd::min( left.m_w, right.m_w ) \n\t\t);\n\t}\n\t\n\tfriend Vec4 Max( Vec4::Arg left, Vec4::Arg right )\n\t{\n\t\treturn Vec4( \n\t\t\tstd::max( left.m_x, right.m_x ), \n\t\t\tstd::max( left.m_y, right.m_y ), \n\t\t\tstd::max( left.m_z, right.m_z ), \n\t\t\tstd::max( left.m_w, right.m_w ) \n\t\t);\n\t}\n\t\n\tfriend Vec4 Truncate( Vec4::Arg v )\n\t{\n\t\treturn Vec4(\n\t\t\tv.m_x > 0.0f ? std::floor( v.m_x ) : std::ceil( v.m_x ), \n\t\t\tv.m_y > 0.0f ? std::floor( v.m_y ) : std::ceil( v.m_y ), \n\t\t\tv.m_z > 0.0f ? std::floor( v.m_z ) : std::ceil( v.m_z ),\n\t\t\tv.m_w > 0.0f ? std::floor( v.m_w ) : std::ceil( v.m_w )\n\t\t);\n\t}\n\t\n\tfriend bool CompareAnyLessThan( Vec4::Arg left, Vec4::Arg right ) \n\t{\n\t\treturn left.m_x < right.m_x\n\t\t\t|| left.m_y < right.m_y\n\t\t\t|| left.m_z < right.m_z\n\t\t\t|| left.m_w < right.m_w;\n\t}\n\t\nprivate:\n\tfloat m_x;\n\tfloat m_y;\n\tfloat m_z;\n\tfloat m_w;\n};\n\n} // namespace squish\n\n#endif // ndef SQUISH_SIMD_FLOAT_H\n\n"
  },
  {
    "path": "vendor/squish/simd_sse.h",
    "content": "/* -----------------------------------------------------------------------------\n\n\tCopyright (c) 2006 Simon Brown                          si@sjbrown.co.uk\n\n\tPermission is hereby granted, free of charge, to any person obtaining\n\ta copy of this software and associated documentation files (the \n\t\"Software\"), to\tdeal in the Software without restriction, including\n\twithout limitation the rights to use, copy, modify, merge, publish,\n\tdistribute, sublicense, and/or sell copies of the Software, and to \n\tpermit persons to whom the Software is furnished to do so, subject to \n\tthe following conditions:\n\n\tThe above copyright notice and this permission notice shall be included\n\tin all copies or substantial portions of the Software.\n\n\tTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n\tOR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF \n\tMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\n\tIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY \n\tCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, \n\tTORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE \n\tSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\t\n   -------------------------------------------------------------------------- */\n   \n#ifndef SQUISH_SIMD_SSE_H\n#define SQUISH_SIMD_SSE_H\n\n#include <xmmintrin.h>\n#if ( SQUISH_USE_SSE > 1 )\n#include <emmintrin.h>\n#endif\n\n#define SQUISH_SSE_SPLAT( a )\t\t\t\t\t\t\t\t\t\t\\\n\t( ( a ) | ( ( a ) << 2 ) | ( ( a ) << 4 ) | ( ( a ) << 6 ) )\n\n#define SQUISH_SSE_SHUF( x, y, z, w )\t\t\t\t\t\t\t\t\\\n\t( ( x ) | ( ( y ) << 2 ) | ( ( z ) << 4 ) | ( ( w ) << 6 ) )\n\nnamespace squish {\n\n#define VEC4_CONST( X ) Vec4( X )\n\nclass Vec4\n{\npublic:\n\ttypedef Vec4 const& Arg;\n\n\tVec4() {}\n\t\t\n\texplicit Vec4( __m128 v ) : m_v( v ) {}\n\t\n\tVec4( Vec4 const& arg ) : m_v( arg.m_v ) {}\n\t\n\tVec4& operator=( Vec4 const& arg )\n\t{\n\t\tm_v = arg.m_v;\n\t\treturn *this;\n\t}\n\t\n\texplicit Vec4( float s ) : m_v( _mm_set1_ps( s ) ) {}\n\t\n\tVec4( float x, float y, float z, float w ) : m_v( _mm_setr_ps( x, y, z, w ) ) {}\n\t\n\tVec3 GetVec3() const\n\t{\n#ifdef __GNUC__\n\t\t__attribute__ ((__aligned__ (16))) float c[4];\n#else\n\t\t__declspec(align(16)) float c[4];\n#endif\n\t\t_mm_store_ps( c, m_v );\n\t\treturn Vec3( c[0], c[1], c[2] );\n\t}\n\t\n\tVec4 SplatX() const { return Vec4( _mm_shuffle_ps( m_v, m_v, SQUISH_SSE_SPLAT( 0 ) ) ); }\n\tVec4 SplatY() const { return Vec4( _mm_shuffle_ps( m_v, m_v, SQUISH_SSE_SPLAT( 1 ) ) ); }\n\tVec4 SplatZ() const { return Vec4( _mm_shuffle_ps( m_v, m_v, SQUISH_SSE_SPLAT( 2 ) ) ); }\n\tVec4 SplatW() const { return Vec4( _mm_shuffle_ps( m_v, m_v, SQUISH_SSE_SPLAT( 3 ) ) ); }\n\n\tVec4& operator+=( Arg v )\n\t{\n\t\tm_v = _mm_add_ps( m_v, v.m_v );\n\t\treturn *this;\n\t}\n\t\n\tVec4& operator-=( Arg v )\n\t{\n\t\tm_v = _mm_sub_ps( m_v, v.m_v );\n\t\treturn *this;\n\t}\n\t\n\tVec4& operator*=( Arg v )\n\t{\n\t\tm_v = _mm_mul_ps( m_v, v.m_v );\n\t\treturn *this;\n\t}\n\t\n\tfriend Vec4 operator+( Vec4::Arg left, Vec4::Arg right  )\n\t{\n\t\treturn Vec4( _mm_add_ps( left.m_v, right.m_v ) );\n\t}\n\t\n\tfriend Vec4 operator-( Vec4::Arg left, Vec4::Arg right  )\n\t{\n\t\treturn Vec4( _mm_sub_ps( left.m_v, right.m_v ) );\n\t}\n\t\n\tfriend Vec4 operator*( Vec4::Arg left, Vec4::Arg right  )\n\t{\n\t\treturn Vec4( _mm_mul_ps( left.m_v, right.m_v ) );\n\t}\n\t\n\t//! Returns a*b + c\n\tfriend Vec4 MultiplyAdd( Vec4::Arg a, Vec4::Arg b, Vec4::Arg c )\n\t{\n\t\treturn Vec4( _mm_add_ps( _mm_mul_ps( a.m_v, b.m_v ), c.m_v ) );\n\t}\n\t\n\t//! Returns -( a*b - c )\n\tfriend Vec4 NegativeMultiplySubtract( Vec4::Arg a, Vec4::Arg b, Vec4::Arg c )\n\t{\n\t\treturn Vec4( _mm_sub_ps( c.m_v, _mm_mul_ps( a.m_v, b.m_v ) ) );\n\t}\n\t\n\tfriend Vec4 Reciprocal( Vec4::Arg v )\n\t{\n\t\t// get the reciprocal estimate\n\t\t__m128 estimate = _mm_rcp_ps( v.m_v );\n\n\t\t// one round of Newton-Rhaphson refinement\n\t\t__m128 diff = _mm_sub_ps( _mm_set1_ps( 1.0f ), _mm_mul_ps( estimate, v.m_v ) );\n\t\treturn Vec4( _mm_add_ps( _mm_mul_ps( diff, estimate ), estimate ) );\n\t}\n\t\n\tfriend Vec4 Min( Vec4::Arg left, Vec4::Arg right )\n\t{\n\t\treturn Vec4( _mm_min_ps( left.m_v, right.m_v ) );\n\t}\n\t\n\tfriend Vec4 Max( Vec4::Arg left, Vec4::Arg right )\n\t{\n\t\treturn Vec4( _mm_max_ps( left.m_v, right.m_v ) );\n\t}\n\t\n\tfriend Vec4 Truncate( Vec4::Arg v )\n\t{\n#if ( SQUISH_USE_SSE == 1 )\n\t\t// convert to ints\n\t\t__m128 input = v.m_v;\n\t\t__m64 lo = _mm_cvttps_pi32( input );\n\t\t__m64 hi = _mm_cvttps_pi32( _mm_movehl_ps( input, input ) );\n\n\t\t// convert to floats\n\t\t__m128 part = _mm_movelh_ps( input, _mm_cvtpi32_ps( input, hi ) );\n\t\t__m128 truncated = _mm_cvtpi32_ps( part, lo );\n\t\t\n\t\t// clear out the MMX multimedia state to allow FP calls later\n\t\t_mm_empty(); \n\t\treturn Vec4( truncated );\n#else\n\t\t// use SSE2 instructions\n\t\treturn Vec4( _mm_cvtepi32_ps( _mm_cvttps_epi32( v.m_v ) ) );\n#endif\n\t}\n\t\n\tfriend bool CompareAnyLessThan( Vec4::Arg left, Vec4::Arg right ) \n\t{\n\t\t__m128 bits = _mm_cmplt_ps( left.m_v, right.m_v );\n\t\tint value = _mm_movemask_ps( bits );\n\t\treturn value != 0;\n\t}\n\t\nprivate:\n\t__m128 m_v;\n};\n\n} // namespace squish\n\n#endif // ndef SQUISH_SIMD_SSE_H\n"
  },
  {
    "path": "vendor/squish/simd_ve.h",
    "content": "/* -----------------------------------------------------------------------------\n\n\tCopyright (c) 2006 Simon Brown                          si@sjbrown.co.uk\n\n\tPermission is hereby granted, free of charge, to any person obtaining\n\ta copy of this software and associated documentation files (the \n\t\"Software\"), to\tdeal in the Software without restriction, including\n\twithout limitation the rights to use, copy, modify, merge, publish,\n\tdistribute, sublicense, and/or sell copies of the Software, and to \n\tpermit persons to whom the Software is furnished to do so, subject to \n\tthe following conditions:\n\n\tThe above copyright notice and this permission notice shall be included\n\tin all copies or substantial portions of the Software.\n\n\tTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n\tOR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF \n\tMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\n\tIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY \n\tCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, \n\tTORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE \n\tSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\t\n   -------------------------------------------------------------------------- */\n   \n#ifndef SQUISH_SIMD_VE_H\n#define SQUISH_SIMD_VE_H\n\n#include <altivec.h>\n#undef bool\n\nnamespace squish {\n\n#define VEC4_CONST( X ) Vec4( ( vector float ){ X } )\n\nclass Vec4\n{\npublic:\n\ttypedef Vec4 Arg;\n\n\tVec4() {}\n\t\t\n\texplicit Vec4( vector float v ) : m_v( v ) {}\n\t\n\tVec4( Vec4 const& arg ) : m_v( arg.m_v ) {}\n\t\n\tVec4& operator=( Vec4 const& arg )\n\t{\n\t\tm_v = arg.m_v;\n\t\treturn *this;\n\t}\n\t\n\texplicit Vec4( float s )\n\t{\n\t\tunion { vector float v; float c[4]; } u;\n\t\tu.c[0] = s;\n\t\tu.c[1] = s;\n\t\tu.c[2] = s;\n\t\tu.c[3] = s;\n\t\tm_v = u.v;\n\t}\n\t\n\tVec4( float x, float y, float z, float w )\n\t{\n\t\tunion { vector float v; float c[4]; } u;\n\t\tu.c[0] = x;\n\t\tu.c[1] = y;\n\t\tu.c[2] = z;\n\t\tu.c[3] = w;\n\t\tm_v = u.v;\n\t}\n\t\n\tVec3 GetVec3() const\n\t{\n\t\tunion { vector float v; float c[4]; } u;\n\t\tu.v = m_v;\n\t\treturn Vec3( u.c[0], u.c[1], u.c[2] );\n\t}\n\t\n\tVec4 SplatX() const { return Vec4( vec_splat( m_v, 0 ) ); }\n\tVec4 SplatY() const { return Vec4( vec_splat( m_v, 1 ) ); }\n\tVec4 SplatZ() const { return Vec4( vec_splat( m_v, 2 ) ); }\n\tVec4 SplatW() const { return Vec4( vec_splat( m_v, 3 ) ); }\n\n\tVec4& operator+=( Arg v )\n\t{\n\t\tm_v = vec_add( m_v, v.m_v );\n\t\treturn *this;\n\t}\n\t\n\tVec4& operator-=( Arg v )\n\t{\n\t\tm_v = vec_sub( m_v, v.m_v );\n\t\treturn *this;\n\t}\n\t\n\tVec4& operator*=( Arg v )\n\t{\n\t\tm_v = vec_madd( m_v, v.m_v, ( vector float ){ -0.0f } );\n\t\treturn *this;\n\t}\n\t\n\tfriend Vec4 operator+( Vec4::Arg left, Vec4::Arg right  )\n\t{\n\t\treturn Vec4( vec_add( left.m_v, right.m_v ) );\n\t}\n\t\n\tfriend Vec4 operator-( Vec4::Arg left, Vec4::Arg right  )\n\t{\n\t\treturn Vec4( vec_sub( left.m_v, right.m_v ) );\n\t}\n\t\n\tfriend Vec4 operator*( Vec4::Arg left, Vec4::Arg right  )\n\t{\n\t\treturn Vec4( vec_madd( left.m_v, right.m_v, ( vector float ){ -0.0f } ) );\n\t}\n\t\n\t//! Returns a*b + c\n\tfriend Vec4 MultiplyAdd( Vec4::Arg a, Vec4::Arg b, Vec4::Arg c )\n\t{\n\t\treturn Vec4( vec_madd( a.m_v, b.m_v, c.m_v ) );\n\t}\n\t\n\t//! Returns -( a*b - c )\n\tfriend Vec4 NegativeMultiplySubtract( Vec4::Arg a, Vec4::Arg b, Vec4::Arg c )\n\t{\n\t\treturn Vec4( vec_nmsub( a.m_v, b.m_v, c.m_v ) );\n\t}\n\t\n\tfriend Vec4 Reciprocal( Vec4::Arg v )\n\t{\n\t\t// get the reciprocal estimate\n\t\tvector float estimate = vec_re( v.m_v );\n\t\t\n\t\t// one round of Newton-Rhaphson refinement\n\t\tvector float diff = vec_nmsub( estimate, v.m_v, ( vector float ){ 1.0f } );\n\t\treturn Vec4( vec_madd( diff, estimate, estimate ) );\n\t}\n\t\n\tfriend Vec4 Min( Vec4::Arg left, Vec4::Arg right )\n\t{\n\t\treturn Vec4( vec_min( left.m_v, right.m_v ) );\n\t}\n\t\n\tfriend Vec4 Max( Vec4::Arg left, Vec4::Arg right )\n\t{\n\t\treturn Vec4( vec_max( left.m_v, right.m_v ) );\n\t}\n\t\n\tfriend Vec4 Truncate( Vec4::Arg v )\n\t{\n\t\treturn Vec4( vec_trunc( v.m_v ) );\n\t}\n\t\n\tfriend bool CompareAnyLessThan( Vec4::Arg left, Vec4::Arg right ) \n\t{\n\t\treturn vec_any_lt( left.m_v, right.m_v ) != 0;\n\t}\n\t\nprivate:\n\tvector float m_v;\n};\n\n} // namespace squish\n\n#endif // ndef SQUISH_SIMD_VE_H\n"
  },
  {
    "path": "vendor/squish/singlecolourfit.cpp",
    "content": "/* -----------------------------------------------------------------------------\n\n\tCopyright (c) 2006 Simon Brown                          si@sjbrown.co.uk\n\n\tPermission is hereby granted, free of charge, to any person obtaining\n\ta copy of this software and associated documentation files (the \n\t\"Software\"), to\tdeal in the Software without restriction, including\n\twithout limitation the rights to use, copy, modify, merge, publish,\n\tdistribute, sublicense, and/or sell copies of the Software, and to \n\tpermit persons to whom the Software is furnished to do so, subject to \n\tthe following conditions:\n\n\tThe above copyright notice and this permission notice shall be included\n\tin all copies or substantial portions of the Software.\n\n\tTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n\tOR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF \n\tMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\n\tIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY \n\tCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, \n\tTORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE \n\tSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\t\n   -------------------------------------------------------------------------- */\n   \n#include \"singlecolourfit.h\"\n#include \"colourset.h\"\n#include \"colourblock.h\"\n\nnamespace squish {\n\nstruct SourceBlock\n{\n\tu8 start;\n\tu8 end;\n\tu8 error;\n};\n\nstruct SingleColourLookup\n{\n\tSourceBlock sources[2];\n};\n\n#include \"singlecolourlookup.inl\"\n\nstatic int FloatToInt( float a, int limit )\n{\n\t// use ANSI round-to-zero behaviour to get round-to-nearest\n\tint i = ( int )( a + 0.5f );\n\n\t// clamp to the limit\n\tif( i < 0 )\n\t\ti = 0;\n\telse if( i > limit )\n\t\ti = limit; \n\n\t// done\n\treturn i;\n}\n\nSingleColourFit::SingleColourFit( ColourSet const* colours, int flags )\n  : ColourFit( colours, flags )\n{\n\t// grab the single colour\n\tVec3 const* values = m_colours->GetPoints();\n\tm_colour[0] = ( u8 )FloatToInt( 255.0f*values->X(), 255 );\n\tm_colour[1] = ( u8 )FloatToInt( 255.0f*values->Y(), 255 );\n\tm_colour[2] = ( u8 )FloatToInt( 255.0f*values->Z(), 255 );\n\t\t\n\t// initialise the best error\n\tm_besterror = INT_MAX;\n}\n\nvoid SingleColourFit::Compress3( void* block )\n{\n\t// build the table of lookups\n\tSingleColourLookup const* const lookups[] = \n\t{\n\t\tlookup_5_3, \n\t\tlookup_6_3, \n\t\tlookup_5_3\n\t};\n\t\n\t// find the best end-points and index\n\tComputeEndPoints( lookups );\n\t\n\t// build the block if we win\n\tif( m_error < m_besterror )\n\t{\n\t\t// remap the indices\n\t\tu8 indices[16];\n\t\tm_colours->RemapIndices( &m_index, indices );\n\t\t\n\t\t// save the block\n\t\tWriteColourBlock3( m_start, m_end, indices, block );\n\n\t\t// save the error\n\t\tm_besterror = m_error;\n\t}\n}\n\nvoid SingleColourFit::Compress4( void* block )\n{\n\t// build the table of lookups\n\tSingleColourLookup const* const lookups[] = \n\t{\n\t\tlookup_5_4, \n\t\tlookup_6_4, \n\t\tlookup_5_4\n\t};\n\t\n\t// find the best end-points and index\n\tComputeEndPoints( lookups );\n\t\n\t// build the block if we win\n\tif( m_error < m_besterror )\n\t{\n\t\t// remap the indices\n\t\tu8 indices[16];\n\t\tm_colours->RemapIndices( &m_index, indices );\n\t\t\n\t\t// save the block\n\t\tWriteColourBlock4( m_start, m_end, indices, block );\n\n\t\t// save the error\n\t\tm_besterror = m_error;\n\t}\n}\n\nvoid SingleColourFit::ComputeEndPoints( SingleColourLookup const* const* lookups )\n{\n\t// check each index combination (endpoint or intermediate)\n\tm_error = INT_MAX;\n\tfor( int index = 0; index < 2; ++index )\n\t{\n\t\t// check the error for this codebook index\n\t\tSourceBlock const* sources[3];\n\t\tint error = 0;\n\t\tfor( int channel = 0; channel < 3; ++channel )\n\t\t{\n\t\t\t// grab the lookup table and index for this channel\n\t\t\tSingleColourLookup const* lookup = lookups[channel];\n\t\t\tint target = m_colour[channel];\n\t\t\t\n\t\t\t// store a pointer to the source for this channel\n\t\t\tsources[channel] = lookup[target].sources + index;\n\t\t\t\n\t\t\t// accumulate the error\n\t\t\tint diff = sources[channel]->error;\n\t\t\terror += diff*diff;\t\t\t\n\t\t}\n\t\t\n\t\t// keep it if the error is lower\n\t\tif( error < m_error )\n\t\t{\n\t\t\tm_start = Vec3(\n\t\t\t\t( float )sources[0]->start/31.0f, \n\t\t\t\t( float )sources[1]->start/63.0f, \n\t\t\t\t( float )sources[2]->start/31.0f\n\t\t\t);\n\t\t\tm_end = Vec3(\n\t\t\t\t( float )sources[0]->end/31.0f, \n\t\t\t\t( float )sources[1]->end/63.0f, \n\t\t\t\t( float )sources[2]->end/31.0f\n\t\t\t);\n\t\t\tm_index = ( u8 )( 2*index );\n\t\t\tm_error = error;\n\t\t}\n\t}\n}\n\n} // namespace squish\n"
  },
  {
    "path": "vendor/squish/singlecolourfit.h",
    "content": "/* -----------------------------------------------------------------------------\n\n\tCopyright (c) 2006 Simon Brown                          si@sjbrown.co.uk\n\n\tPermission is hereby granted, free of charge, to any person obtaining\n\ta copy of this software and associated documentation files (the \n\t\"Software\"), to\tdeal in the Software without restriction, including\n\twithout limitation the rights to use, copy, modify, merge, publish,\n\tdistribute, sublicense, and/or sell copies of the Software, and to \n\tpermit persons to whom the Software is furnished to do so, subject to \n\tthe following conditions:\n\n\tThe above copyright notice and this permission notice shall be included\n\tin all copies or substantial portions of the Software.\n\n\tTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n\tOR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF \n\tMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\n\tIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY \n\tCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, \n\tTORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE \n\tSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\t\n   -------------------------------------------------------------------------- */\n   \n#ifndef SQUISH_SINGLECOLOURFIT_H\n#define SQUISH_SINGLECOLOURFIT_H\n\n#include \"squish.h\"\n#include \"colourfit.h\"\n\nnamespace squish {\n\nclass ColourSet;\nstruct SingleColourLookup;\n\nclass SingleColourFit : public ColourFit\n{\npublic:\n\tSingleColourFit( ColourSet const* colours, int flags );\n\t\nprivate:\n\tvirtual void Compress3( void* block );\n\tvirtual void Compress4( void* block );\n\t\n\tvoid ComputeEndPoints( SingleColourLookup const* const* lookups );\n\t\n\tu8 m_colour[3];\n\tVec3 m_start;\n\tVec3 m_end;\n\tu8 m_index;\n\tint m_error;\n\tint m_besterror;\n};\n\n} // namespace squish\n\n#endif // ndef SQUISH_SINGLECOLOURFIT_H\n"
  },
  {
    "path": "vendor/squish/singlecolourlookup.inl",
    "content": "\nstatic SingleColourLookup const lookup_5_3[] = \n{\n\t{ { { 0, 0, 0 }, { 0, 0, 0 } } },\n\t{ { { 0, 0, 1 }, { 0, 0, 1 } } },\n\t{ { { 0, 0, 2 }, { 0, 0, 2 } } },\n\t{ { { 0, 0, 3 }, { 0, 1, 1 } } },\n\t{ { { 0, 0, 4 }, { 0, 1, 0 } } },\n\t{ { { 1, 0, 3 }, { 0, 1, 1 } } },\n\t{ { { 1, 0, 2 }, { 0, 1, 2 } } },\n\t{ { { 1, 0, 1 }, { 0, 2, 1 } } },\n\t{ { { 1, 0, 0 }, { 0, 2, 0 } } },\n\t{ { { 1, 0, 1 }, { 0, 2, 1 } } },\n\t{ { { 1, 0, 2 }, { 0, 2, 2 } } },\n\t{ { { 1, 0, 3 }, { 0, 3, 1 } } },\n\t{ { { 1, 0, 4 }, { 0, 3, 0 } } },\n\t{ { { 2, 0, 3 }, { 0, 3, 1 } } },\n\t{ { { 2, 0, 2 }, { 0, 3, 2 } } },\n\t{ { { 2, 0, 1 }, { 0, 4, 1 } } },\n\t{ { { 2, 0, 0 }, { 0, 4, 0 } } },\n\t{ { { 2, 0, 1 }, { 0, 4, 1 } } },\n\t{ { { 2, 0, 2 }, { 0, 4, 2 } } },\n\t{ { { 2, 0, 3 }, { 0, 5, 1 } } },\n\t{ { { 2, 0, 4 }, { 0, 5, 0 } } },\n\t{ { { 3, 0, 3 }, { 0, 5, 1 } } },\n\t{ { { 3, 0, 2 }, { 0, 5, 2 } } },\n\t{ { { 3, 0, 1 }, { 0, 6, 1 } } },\n\t{ { { 3, 0, 0 }, { 0, 6, 0 } } },\n\t{ { { 3, 0, 1 }, { 0, 6, 1 } } },\n\t{ { { 3, 0, 2 }, { 0, 6, 2 } } },\n\t{ { { 3, 0, 3 }, { 0, 7, 1 } } },\n\t{ { { 3, 0, 4 }, { 0, 7, 0 } } },\n\t{ { { 4, 0, 4 }, { 0, 7, 1 } } },\n\t{ { { 4, 0, 3 }, { 0, 7, 2 } } },\n\t{ { { 4, 0, 2 }, { 1, 7, 1 } } },\n\t{ { { 4, 0, 1 }, { 1, 7, 0 } } },\n\t{ { { 4, 0, 0 }, { 0, 8, 0 } } },\n\t{ { { 4, 0, 1 }, { 0, 8, 1 } } },\n\t{ { { 4, 0, 2 }, { 2, 7, 1 } } },\n\t{ { { 4, 0, 3 }, { 2, 7, 0 } } },\n\t{ { { 4, 0, 4 }, { 0, 9, 0 } } },\n\t{ { { 5, 0, 3 }, { 0, 9, 1 } } },\n\t{ { { 5, 0, 2 }, { 3, 7, 1 } } },\n\t{ { { 5, 0, 1 }, { 3, 7, 0 } } },\n\t{ { { 5, 0, 0 }, { 0, 10, 0 } } },\n\t{ { { 5, 0, 1 }, { 0, 10, 1 } } },\n\t{ { { 5, 0, 2 }, { 0, 10, 2 } } },\n\t{ { { 5, 0, 3 }, { 0, 11, 1 } } },\n\t{ { { 5, 0, 4 }, { 0, 11, 0 } } },\n\t{ { { 6, 0, 3 }, { 0, 11, 1 } } },\n\t{ { { 6, 0, 2 }, { 0, 11, 2 } } },\n\t{ { { 6, 0, 1 }, { 0, 12, 1 } } },\n\t{ { { 6, 0, 0 }, { 0, 12, 0 } } },\n\t{ { { 6, 0, 1 }, { 0, 12, 1 } } },\n\t{ { { 6, 0, 2 }, { 0, 12, 2 } } },\n\t{ { { 6, 0, 3 }, { 0, 13, 1 } } },\n\t{ { { 6, 0, 4 }, { 0, 13, 0 } } },\n\t{ { { 7, 0, 3 }, { 0, 13, 1 } } },\n\t{ { { 7, 0, 2 }, { 0, 13, 2 } } },\n\t{ { { 7, 0, 1 }, { 0, 14, 1 } } },\n\t{ { { 7, 0, 0 }, { 0, 14, 0 } } },\n\t{ { { 7, 0, 1 }, { 0, 14, 1 } } },\n\t{ { { 7, 0, 2 }, { 0, 14, 2 } } },\n\t{ { { 7, 0, 3 }, { 0, 15, 1 } } },\n\t{ { { 7, 0, 4 }, { 0, 15, 0 } } },\n\t{ { { 8, 0, 4 }, { 0, 15, 1 } } },\n\t{ { { 8, 0, 3 }, { 0, 15, 2 } } },\n\t{ { { 8, 0, 2 }, { 1, 15, 1 } } },\n\t{ { { 8, 0, 1 }, { 1, 15, 0 } } },\n\t{ { { 8, 0, 0 }, { 0, 16, 0 } } },\n\t{ { { 8, 0, 1 }, { 0, 16, 1 } } },\n\t{ { { 8, 0, 2 }, { 2, 15, 1 } } },\n\t{ { { 8, 0, 3 }, { 2, 15, 0 } } },\n\t{ { { 8, 0, 4 }, { 0, 17, 0 } } },\n\t{ { { 9, 0, 3 }, { 0, 17, 1 } } },\n\t{ { { 9, 0, 2 }, { 3, 15, 1 } } },\n\t{ { { 9, 0, 1 }, { 3, 15, 0 } } },\n\t{ { { 9, 0, 0 }, { 0, 18, 0 } } },\n\t{ { { 9, 0, 1 }, { 0, 18, 1 } } },\n\t{ { { 9, 0, 2 }, { 0, 18, 2 } } },\n\t{ { { 9, 0, 3 }, { 0, 19, 1 } } },\n\t{ { { 9, 0, 4 }, { 0, 19, 0 } } },\n\t{ { { 10, 0, 3 }, { 0, 19, 1 } } },\n\t{ { { 10, 0, 2 }, { 0, 19, 2 } } },\n\t{ { { 10, 0, 1 }, { 0, 20, 1 } } },\n\t{ { { 10, 0, 0 }, { 0, 20, 0 } } },\n\t{ { { 10, 0, 1 }, { 0, 20, 1 } } },\n\t{ { { 10, 0, 2 }, { 0, 20, 2 } } },\n\t{ { { 10, 0, 3 }, { 0, 21, 1 } } },\n\t{ { { 10, 0, 4 }, { 0, 21, 0 } } },\n\t{ { { 11, 0, 3 }, { 0, 21, 1 } } },\n\t{ { { 11, 0, 2 }, { 0, 21, 2 } } },\n\t{ { { 11, 0, 1 }, { 0, 22, 1 } } },\n\t{ { { 11, 0, 0 }, { 0, 22, 0 } } },\n\t{ { { 11, 0, 1 }, { 0, 22, 1 } } },\n\t{ { { 11, 0, 2 }, { 0, 22, 2 } } },\n\t{ { { 11, 0, 3 }, { 0, 23, 1 } } },\n\t{ { { 11, 0, 4 }, { 0, 23, 0 } } },\n\t{ { { 12, 0, 4 }, { 0, 23, 1 } } },\n\t{ { { 12, 0, 3 }, { 0, 23, 2 } } },\n\t{ { { 12, 0, 2 }, { 1, 23, 1 } } },\n\t{ { { 12, 0, 1 }, { 1, 23, 0 } } },\n\t{ { { 12, 0, 0 }, { 0, 24, 0 } } },\n\t{ { { 12, 0, 1 }, { 0, 24, 1 } } },\n\t{ { { 12, 0, 2 }, { 2, 23, 1 } } },\n\t{ { { 12, 0, 3 }, { 2, 23, 0 } } },\n\t{ { { 12, 0, 4 }, { 0, 25, 0 } } },\n\t{ { { 13, 0, 3 }, { 0, 25, 1 } } },\n\t{ { { 13, 0, 2 }, { 3, 23, 1 } } },\n\t{ { { 13, 0, 1 }, { 3, 23, 0 } } },\n\t{ { { 13, 0, 0 }, { 0, 26, 0 } } },\n\t{ { { 13, 0, 1 }, { 0, 26, 1 } } },\n\t{ { { 13, 0, 2 }, { 0, 26, 2 } } },\n\t{ { { 13, 0, 3 }, { 0, 27, 1 } } },\n\t{ { { 13, 0, 4 }, { 0, 27, 0 } } },\n\t{ { { 14, 0, 3 }, { 0, 27, 1 } } },\n\t{ { { 14, 0, 2 }, { 0, 27, 2 } } },\n\t{ { { 14, 0, 1 }, { 0, 28, 1 } } },\n\t{ { { 14, 0, 0 }, { 0, 28, 0 } } },\n\t{ { { 14, 0, 1 }, { 0, 28, 1 } } },\n\t{ { { 14, 0, 2 }, { 0, 28, 2 } } },\n\t{ { { 14, 0, 3 }, { 0, 29, 1 } } },\n\t{ { { 14, 0, 4 }, { 0, 29, 0 } } },\n\t{ { { 15, 0, 3 }, { 0, 29, 1 } } },\n\t{ { { 15, 0, 2 }, { 0, 29, 2 } } },\n\t{ { { 15, 0, 1 }, { 0, 30, 1 } } },\n\t{ { { 15, 0, 0 }, { 0, 30, 0 } } },\n\t{ { { 15, 0, 1 }, { 0, 30, 1 } } },\n\t{ { { 15, 0, 2 }, { 0, 30, 2 } } },\n\t{ { { 15, 0, 3 }, { 0, 31, 1 } } },\n\t{ { { 15, 0, 4 }, { 0, 31, 0 } } },\n\t{ { { 16, 0, 4 }, { 0, 31, 1 } } },\n\t{ { { 16, 0, 3 }, { 0, 31, 2 } } },\n\t{ { { 16, 0, 2 }, { 1, 31, 1 } } },\n\t{ { { 16, 0, 1 }, { 1, 31, 0 } } },\n\t{ { { 16, 0, 0 }, { 4, 28, 0 } } },\n\t{ { { 16, 0, 1 }, { 4, 28, 1 } } },\n\t{ { { 16, 0, 2 }, { 2, 31, 1 } } },\n\t{ { { 16, 0, 3 }, { 2, 31, 0 } } },\n\t{ { { 16, 0, 4 }, { 4, 29, 0 } } },\n\t{ { { 17, 0, 3 }, { 4, 29, 1 } } },\n\t{ { { 17, 0, 2 }, { 3, 31, 1 } } },\n\t{ { { 17, 0, 1 }, { 3, 31, 0 } } },\n\t{ { { 17, 0, 0 }, { 4, 30, 0 } } },\n\t{ { { 17, 0, 1 }, { 4, 30, 1 } } },\n\t{ { { 17, 0, 2 }, { 4, 30, 2 } } },\n\t{ { { 17, 0, 3 }, { 4, 31, 1 } } },\n\t{ { { 17, 0, 4 }, { 4, 31, 0 } } },\n\t{ { { 18, 0, 3 }, { 4, 31, 1 } } },\n\t{ { { 18, 0, 2 }, { 4, 31, 2 } } },\n\t{ { { 18, 0, 1 }, { 5, 31, 1 } } },\n\t{ { { 18, 0, 0 }, { 5, 31, 0 } } },\n\t{ { { 18, 0, 1 }, { 5, 31, 1 } } },\n\t{ { { 18, 0, 2 }, { 5, 31, 2 } } },\n\t{ { { 18, 0, 3 }, { 6, 31, 1 } } },\n\t{ { { 18, 0, 4 }, { 6, 31, 0 } } },\n\t{ { { 19, 0, 3 }, { 6, 31, 1 } } },\n\t{ { { 19, 0, 2 }, { 6, 31, 2 } } },\n\t{ { { 19, 0, 1 }, { 7, 31, 1 } } },\n\t{ { { 19, 0, 0 }, { 7, 31, 0 } } },\n\t{ { { 19, 0, 1 }, { 7, 31, 1 } } },\n\t{ { { 19, 0, 2 }, { 7, 31, 2 } } },\n\t{ { { 19, 0, 3 }, { 8, 31, 1 } } },\n\t{ { { 19, 0, 4 }, { 8, 31, 0 } } },\n\t{ { { 20, 0, 4 }, { 8, 31, 1 } } },\n\t{ { { 20, 0, 3 }, { 8, 31, 2 } } },\n\t{ { { 20, 0, 2 }, { 9, 31, 1 } } },\n\t{ { { 20, 0, 1 }, { 9, 31, 0 } } },\n\t{ { { 20, 0, 0 }, { 12, 28, 0 } } },\n\t{ { { 20, 0, 1 }, { 12, 28, 1 } } },\n\t{ { { 20, 0, 2 }, { 10, 31, 1 } } },\n\t{ { { 20, 0, 3 }, { 10, 31, 0 } } },\n\t{ { { 20, 0, 4 }, { 12, 29, 0 } } },\n\t{ { { 21, 0, 3 }, { 12, 29, 1 } } },\n\t{ { { 21, 0, 2 }, { 11, 31, 1 } } },\n\t{ { { 21, 0, 1 }, { 11, 31, 0 } } },\n\t{ { { 21, 0, 0 }, { 12, 30, 0 } } },\n\t{ { { 21, 0, 1 }, { 12, 30, 1 } } },\n\t{ { { 21, 0, 2 }, { 12, 30, 2 } } },\n\t{ { { 21, 0, 3 }, { 12, 31, 1 } } },\n\t{ { { 21, 0, 4 }, { 12, 31, 0 } } },\n\t{ { { 22, 0, 3 }, { 12, 31, 1 } } },\n\t{ { { 22, 0, 2 }, { 12, 31, 2 } } },\n\t{ { { 22, 0, 1 }, { 13, 31, 1 } } },\n\t{ { { 22, 0, 0 }, { 13, 31, 0 } } },\n\t{ { { 22, 0, 1 }, { 13, 31, 1 } } },\n\t{ { { 22, 0, 2 }, { 13, 31, 2 } } },\n\t{ { { 22, 0, 3 }, { 14, 31, 1 } } },\n\t{ { { 22, 0, 4 }, { 14, 31, 0 } } },\n\t{ { { 23, 0, 3 }, { 14, 31, 1 } } },\n\t{ { { 23, 0, 2 }, { 14, 31, 2 } } },\n\t{ { { 23, 0, 1 }, { 15, 31, 1 } } },\n\t{ { { 23, 0, 0 }, { 15, 31, 0 } } },\n\t{ { { 23, 0, 1 }, { 15, 31, 1 } } },\n\t{ { { 23, 0, 2 }, { 15, 31, 2 } } },\n\t{ { { 23, 0, 3 }, { 16, 31, 1 } } },\n\t{ { { 23, 0, 4 }, { 16, 31, 0 } } },\n\t{ { { 24, 0, 4 }, { 16, 31, 1 } } },\n\t{ { { 24, 0, 3 }, { 16, 31, 2 } } },\n\t{ { { 24, 0, 2 }, { 17, 31, 1 } } },\n\t{ { { 24, 0, 1 }, { 17, 31, 0 } } },\n\t{ { { 24, 0, 0 }, { 20, 28, 0 } } },\n\t{ { { 24, 0, 1 }, { 20, 28, 1 } } },\n\t{ { { 24, 0, 2 }, { 18, 31, 1 } } },\n\t{ { { 24, 0, 3 }, { 18, 31, 0 } } },\n\t{ { { 24, 0, 4 }, { 20, 29, 0 } } },\n\t{ { { 25, 0, 3 }, { 20, 29, 1 } } },\n\t{ { { 25, 0, 2 }, { 19, 31, 1 } } },\n\t{ { { 25, 0, 1 }, { 19, 31, 0 } } },\n\t{ { { 25, 0, 0 }, { 20, 30, 0 } } },\n\t{ { { 25, 0, 1 }, { 20, 30, 1 } } },\n\t{ { { 25, 0, 2 }, { 20, 30, 2 } } },\n\t{ { { 25, 0, 3 }, { 20, 31, 1 } } },\n\t{ { { 25, 0, 4 }, { 20, 31, 0 } } },\n\t{ { { 26, 0, 3 }, { 20, 31, 1 } } },\n\t{ { { 26, 0, 2 }, { 20, 31, 2 } } },\n\t{ { { 26, 0, 1 }, { 21, 31, 1 } } },\n\t{ { { 26, 0, 0 }, { 21, 31, 0 } } },\n\t{ { { 26, 0, 1 }, { 21, 31, 1 } } },\n\t{ { { 26, 0, 2 }, { 21, 31, 2 } } },\n\t{ { { 26, 0, 3 }, { 22, 31, 1 } } },\n\t{ { { 26, 0, 4 }, { 22, 31, 0 } } },\n\t{ { { 27, 0, 3 }, { 22, 31, 1 } } },\n\t{ { { 27, 0, 2 }, { 22, 31, 2 } } },\n\t{ { { 27, 0, 1 }, { 23, 31, 1 } } },\n\t{ { { 27, 0, 0 }, { 23, 31, 0 } } },\n\t{ { { 27, 0, 1 }, { 23, 31, 1 } } },\n\t{ { { 27, 0, 2 }, { 23, 31, 2 } } },\n\t{ { { 27, 0, 3 }, { 24, 31, 1 } } },\n\t{ { { 27, 0, 4 }, { 24, 31, 0 } } },\n\t{ { { 28, 0, 4 }, { 24, 31, 1 } } },\n\t{ { { 28, 0, 3 }, { 24, 31, 2 } } },\n\t{ { { 28, 0, 2 }, { 25, 31, 1 } } },\n\t{ { { 28, 0, 1 }, { 25, 31, 0 } } },\n\t{ { { 28, 0, 0 }, { 28, 28, 0 } } },\n\t{ { { 28, 0, 1 }, { 28, 28, 1 } } },\n\t{ { { 28, 0, 2 }, { 26, 31, 1 } } },\n\t{ { { 28, 0, 3 }, { 26, 31, 0 } } },\n\t{ { { 28, 0, 4 }, { 28, 29, 0 } } },\n\t{ { { 29, 0, 3 }, { 28, 29, 1 } } },\n\t{ { { 29, 0, 2 }, { 27, 31, 1 } } },\n\t{ { { 29, 0, 1 }, { 27, 31, 0 } } },\n\t{ { { 29, 0, 0 }, { 28, 30, 0 } } },\n\t{ { { 29, 0, 1 }, { 28, 30, 1 } } },\n\t{ { { 29, 0, 2 }, { 28, 30, 2 } } },\n\t{ { { 29, 0, 3 }, { 28, 31, 1 } } },\n\t{ { { 29, 0, 4 }, { 28, 31, 0 } } },\n\t{ { { 30, 0, 3 }, { 28, 31, 1 } } },\n\t{ { { 30, 0, 2 }, { 28, 31, 2 } } },\n\t{ { { 30, 0, 1 }, { 29, 31, 1 } } },\n\t{ { { 30, 0, 0 }, { 29, 31, 0 } } },\n\t{ { { 30, 0, 1 }, { 29, 31, 1 } } },\n\t{ { { 30, 0, 2 }, { 29, 31, 2 } } },\n\t{ { { 30, 0, 3 }, { 30, 31, 1 } } },\n\t{ { { 30, 0, 4 }, { 30, 31, 0 } } },\n\t{ { { 31, 0, 3 }, { 30, 31, 1 } } },\n\t{ { { 31, 0, 2 }, { 30, 31, 2 } } },\n\t{ { { 31, 0, 1 }, { 31, 31, 1 } } },\n\t{ { { 31, 0, 0 }, { 31, 31, 0 } } }\n};\n\nstatic SingleColourLookup const lookup_6_3[] = \n{\n\t{ { { 0, 0, 0 }, { 0, 0, 0 } } },\n\t{ { { 0, 0, 1 }, { 0, 1, 1 } } },\n\t{ { { 0, 0, 2 }, { 0, 1, 0 } } },\n\t{ { { 1, 0, 1 }, { 0, 2, 1 } } },\n\t{ { { 1, 0, 0 }, { 0, 2, 0 } } },\n\t{ { { 1, 0, 1 }, { 0, 3, 1 } } },\n\t{ { { 1, 0, 2 }, { 0, 3, 0 } } },\n\t{ { { 2, 0, 1 }, { 0, 4, 1 } } },\n\t{ { { 2, 0, 0 }, { 0, 4, 0 } } },\n\t{ { { 2, 0, 1 }, { 0, 5, 1 } } },\n\t{ { { 2, 0, 2 }, { 0, 5, 0 } } },\n\t{ { { 3, 0, 1 }, { 0, 6, 1 } } },\n\t{ { { 3, 0, 0 }, { 0, 6, 0 } } },\n\t{ { { 3, 0, 1 }, { 0, 7, 1 } } },\n\t{ { { 3, 0, 2 }, { 0, 7, 0 } } },\n\t{ { { 4, 0, 1 }, { 0, 8, 1 } } },\n\t{ { { 4, 0, 0 }, { 0, 8, 0 } } },\n\t{ { { 4, 0, 1 }, { 0, 9, 1 } } },\n\t{ { { 4, 0, 2 }, { 0, 9, 0 } } },\n\t{ { { 5, 0, 1 }, { 0, 10, 1 } } },\n\t{ { { 5, 0, 0 }, { 0, 10, 0 } } },\n\t{ { { 5, 0, 1 }, { 0, 11, 1 } } },\n\t{ { { 5, 0, 2 }, { 0, 11, 0 } } },\n\t{ { { 6, 0, 1 }, { 0, 12, 1 } } },\n\t{ { { 6, 0, 0 }, { 0, 12, 0 } } },\n\t{ { { 6, 0, 1 }, { 0, 13, 1 } } },\n\t{ { { 6, 0, 2 }, { 0, 13, 0 } } },\n\t{ { { 7, 0, 1 }, { 0, 14, 1 } } },\n\t{ { { 7, 0, 0 }, { 0, 14, 0 } } },\n\t{ { { 7, 0, 1 }, { 0, 15, 1 } } },\n\t{ { { 7, 0, 2 }, { 0, 15, 0 } } },\n\t{ { { 8, 0, 1 }, { 0, 16, 1 } } },\n\t{ { { 8, 0, 0 }, { 0, 16, 0 } } },\n\t{ { { 8, 0, 1 }, { 0, 17, 1 } } },\n\t{ { { 8, 0, 2 }, { 0, 17, 0 } } },\n\t{ { { 9, 0, 1 }, { 0, 18, 1 } } },\n\t{ { { 9, 0, 0 }, { 0, 18, 0 } } },\n\t{ { { 9, 0, 1 }, { 0, 19, 1 } } },\n\t{ { { 9, 0, 2 }, { 0, 19, 0 } } },\n\t{ { { 10, 0, 1 }, { 0, 20, 1 } } },\n\t{ { { 10, 0, 0 }, { 0, 20, 0 } } },\n\t{ { { 10, 0, 1 }, { 0, 21, 1 } } },\n\t{ { { 10, 0, 2 }, { 0, 21, 0 } } },\n\t{ { { 11, 0, 1 }, { 0, 22, 1 } } },\n\t{ { { 11, 0, 0 }, { 0, 22, 0 } } },\n\t{ { { 11, 0, 1 }, { 0, 23, 1 } } },\n\t{ { { 11, 0, 2 }, { 0, 23, 0 } } },\n\t{ { { 12, 0, 1 }, { 0, 24, 1 } } },\n\t{ { { 12, 0, 0 }, { 0, 24, 0 } } },\n\t{ { { 12, 0, 1 }, { 0, 25, 1 } } },\n\t{ { { 12, 0, 2 }, { 0, 25, 0 } } },\n\t{ { { 13, 0, 1 }, { 0, 26, 1 } } },\n\t{ { { 13, 0, 0 }, { 0, 26, 0 } } },\n\t{ { { 13, 0, 1 }, { 0, 27, 1 } } },\n\t{ { { 13, 0, 2 }, { 0, 27, 0 } } },\n\t{ { { 14, 0, 1 }, { 0, 28, 1 } } },\n\t{ { { 14, 0, 0 }, { 0, 28, 0 } } },\n\t{ { { 14, 0, 1 }, { 0, 29, 1 } } },\n\t{ { { 14, 0, 2 }, { 0, 29, 0 } } },\n\t{ { { 15, 0, 1 }, { 0, 30, 1 } } },\n\t{ { { 15, 0, 0 }, { 0, 30, 0 } } },\n\t{ { { 15, 0, 1 }, { 0, 31, 1 } } },\n\t{ { { 15, 0, 2 }, { 0, 31, 0 } } },\n\t{ { { 16, 0, 2 }, { 1, 31, 1 } } },\n\t{ { { 16, 0, 1 }, { 1, 31, 0 } } },\n\t{ { { 16, 0, 0 }, { 0, 32, 0 } } },\n\t{ { { 16, 0, 1 }, { 2, 31, 0 } } },\n\t{ { { 16, 0, 2 }, { 0, 33, 0 } } },\n\t{ { { 17, 0, 1 }, { 3, 31, 0 } } },\n\t{ { { 17, 0, 0 }, { 0, 34, 0 } } },\n\t{ { { 17, 0, 1 }, { 4, 31, 0 } } },\n\t{ { { 17, 0, 2 }, { 0, 35, 0 } } },\n\t{ { { 18, 0, 1 }, { 5, 31, 0 } } },\n\t{ { { 18, 0, 0 }, { 0, 36, 0 } } },\n\t{ { { 18, 0, 1 }, { 6, 31, 0 } } },\n\t{ { { 18, 0, 2 }, { 0, 37, 0 } } },\n\t{ { { 19, 0, 1 }, { 7, 31, 0 } } },\n\t{ { { 19, 0, 0 }, { 0, 38, 0 } } },\n\t{ { { 19, 0, 1 }, { 8, 31, 0 } } },\n\t{ { { 19, 0, 2 }, { 0, 39, 0 } } },\n\t{ { { 20, 0, 1 }, { 9, 31, 0 } } },\n\t{ { { 20, 0, 0 }, { 0, 40, 0 } } },\n\t{ { { 20, 0, 1 }, { 10, 31, 0 } } },\n\t{ { { 20, 0, 2 }, { 0, 41, 0 } } },\n\t{ { { 21, 0, 1 }, { 11, 31, 0 } } },\n\t{ { { 21, 0, 0 }, { 0, 42, 0 } } },\n\t{ { { 21, 0, 1 }, { 12, 31, 0 } } },\n\t{ { { 21, 0, 2 }, { 0, 43, 0 } } },\n\t{ { { 22, 0, 1 }, { 13, 31, 0 } } },\n\t{ { { 22, 0, 0 }, { 0, 44, 0 } } },\n\t{ { { 22, 0, 1 }, { 14, 31, 0 } } },\n\t{ { { 22, 0, 2 }, { 0, 45, 0 } } },\n\t{ { { 23, 0, 1 }, { 15, 31, 0 } } },\n\t{ { { 23, 0, 0 }, { 0, 46, 0 } } },\n\t{ { { 23, 0, 1 }, { 0, 47, 1 } } },\n\t{ { { 23, 0, 2 }, { 0, 47, 0 } } },\n\t{ { { 24, 0, 1 }, { 0, 48, 1 } } },\n\t{ { { 24, 0, 0 }, { 0, 48, 0 } } },\n\t{ { { 24, 0, 1 }, { 0, 49, 1 } } },\n\t{ { { 24, 0, 2 }, { 0, 49, 0 } } },\n\t{ { { 25, 0, 1 }, { 0, 50, 1 } } },\n\t{ { { 25, 0, 0 }, { 0, 50, 0 } } },\n\t{ { { 25, 0, 1 }, { 0, 51, 1 } } },\n\t{ { { 25, 0, 2 }, { 0, 51, 0 } } },\n\t{ { { 26, 0, 1 }, { 0, 52, 1 } } },\n\t{ { { 26, 0, 0 }, { 0, 52, 0 } } },\n\t{ { { 26, 0, 1 }, { 0, 53, 1 } } },\n\t{ { { 26, 0, 2 }, { 0, 53, 0 } } },\n\t{ { { 27, 0, 1 }, { 0, 54, 1 } } },\n\t{ { { 27, 0, 0 }, { 0, 54, 0 } } },\n\t{ { { 27, 0, 1 }, { 0, 55, 1 } } },\n\t{ { { 27, 0, 2 }, { 0, 55, 0 } } },\n\t{ { { 28, 0, 1 }, { 0, 56, 1 } } },\n\t{ { { 28, 0, 0 }, { 0, 56, 0 } } },\n\t{ { { 28, 0, 1 }, { 0, 57, 1 } } },\n\t{ { { 28, 0, 2 }, { 0, 57, 0 } } },\n\t{ { { 29, 0, 1 }, { 0, 58, 1 } } },\n\t{ { { 29, 0, 0 }, { 0, 58, 0 } } },\n\t{ { { 29, 0, 1 }, { 0, 59, 1 } } },\n\t{ { { 29, 0, 2 }, { 0, 59, 0 } } },\n\t{ { { 30, 0, 1 }, { 0, 60, 1 } } },\n\t{ { { 30, 0, 0 }, { 0, 60, 0 } } },\n\t{ { { 30, 0, 1 }, { 0, 61, 1 } } },\n\t{ { { 30, 0, 2 }, { 0, 61, 0 } } },\n\t{ { { 31, 0, 1 }, { 0, 62, 1 } } },\n\t{ { { 31, 0, 0 }, { 0, 62, 0 } } },\n\t{ { { 31, 0, 1 }, { 0, 63, 1 } } },\n\t{ { { 31, 0, 2 }, { 0, 63, 0 } } },\n\t{ { { 32, 0, 2 }, { 1, 63, 1 } } },\n\t{ { { 32, 0, 1 }, { 1, 63, 0 } } },\n\t{ { { 32, 0, 0 }, { 16, 48, 0 } } },\n\t{ { { 32, 0, 1 }, { 2, 63, 0 } } },\n\t{ { { 32, 0, 2 }, { 16, 49, 0 } } },\n\t{ { { 33, 0, 1 }, { 3, 63, 0 } } },\n\t{ { { 33, 0, 0 }, { 16, 50, 0 } } },\n\t{ { { 33, 0, 1 }, { 4, 63, 0 } } },\n\t{ { { 33, 0, 2 }, { 16, 51, 0 } } },\n\t{ { { 34, 0, 1 }, { 5, 63, 0 } } },\n\t{ { { 34, 0, 0 }, { 16, 52, 0 } } },\n\t{ { { 34, 0, 1 }, { 6, 63, 0 } } },\n\t{ { { 34, 0, 2 }, { 16, 53, 0 } } },\n\t{ { { 35, 0, 1 }, { 7, 63, 0 } } },\n\t{ { { 35, 0, 0 }, { 16, 54, 0 } } },\n\t{ { { 35, 0, 1 }, { 8, 63, 0 } } },\n\t{ { { 35, 0, 2 }, { 16, 55, 0 } } },\n\t{ { { 36, 0, 1 }, { 9, 63, 0 } } },\n\t{ { { 36, 0, 0 }, { 16, 56, 0 } } },\n\t{ { { 36, 0, 1 }, { 10, 63, 0 } } },\n\t{ { { 36, 0, 2 }, { 16, 57, 0 } } },\n\t{ { { 37, 0, 1 }, { 11, 63, 0 } } },\n\t{ { { 37, 0, 0 }, { 16, 58, 0 } } },\n\t{ { { 37, 0, 1 }, { 12, 63, 0 } } },\n\t{ { { 37, 0, 2 }, { 16, 59, 0 } } },\n\t{ { { 38, 0, 1 }, { 13, 63, 0 } } },\n\t{ { { 38, 0, 0 }, { 16, 60, 0 } } },\n\t{ { { 38, 0, 1 }, { 14, 63, 0 } } },\n\t{ { { 38, 0, 2 }, { 16, 61, 0 } } },\n\t{ { { 39, 0, 1 }, { 15, 63, 0 } } },\n\t{ { { 39, 0, 0 }, { 16, 62, 0 } } },\n\t{ { { 39, 0, 1 }, { 16, 63, 1 } } },\n\t{ { { 39, 0, 2 }, { 16, 63, 0 } } },\n\t{ { { 40, 0, 1 }, { 17, 63, 1 } } },\n\t{ { { 40, 0, 0 }, { 17, 63, 0 } } },\n\t{ { { 40, 0, 1 }, { 18, 63, 1 } } },\n\t{ { { 40, 0, 2 }, { 18, 63, 0 } } },\n\t{ { { 41, 0, 1 }, { 19, 63, 1 } } },\n\t{ { { 41, 0, 0 }, { 19, 63, 0 } } },\n\t{ { { 41, 0, 1 }, { 20, 63, 1 } } },\n\t{ { { 41, 0, 2 }, { 20, 63, 0 } } },\n\t{ { { 42, 0, 1 }, { 21, 63, 1 } } },\n\t{ { { 42, 0, 0 }, { 21, 63, 0 } } },\n\t{ { { 42, 0, 1 }, { 22, 63, 1 } } },\n\t{ { { 42, 0, 2 }, { 22, 63, 0 } } },\n\t{ { { 43, 0, 1 }, { 23, 63, 1 } } },\n\t{ { { 43, 0, 0 }, { 23, 63, 0 } } },\n\t{ { { 43, 0, 1 }, { 24, 63, 1 } } },\n\t{ { { 43, 0, 2 }, { 24, 63, 0 } } },\n\t{ { { 44, 0, 1 }, { 25, 63, 1 } } },\n\t{ { { 44, 0, 0 }, { 25, 63, 0 } } },\n\t{ { { 44, 0, 1 }, { 26, 63, 1 } } },\n\t{ { { 44, 0, 2 }, { 26, 63, 0 } } },\n\t{ { { 45, 0, 1 }, { 27, 63, 1 } } },\n\t{ { { 45, 0, 0 }, { 27, 63, 0 } } },\n\t{ { { 45, 0, 1 }, { 28, 63, 1 } } },\n\t{ { { 45, 0, 2 }, { 28, 63, 0 } } },\n\t{ { { 46, 0, 1 }, { 29, 63, 1 } } },\n\t{ { { 46, 0, 0 }, { 29, 63, 0 } } },\n\t{ { { 46, 0, 1 }, { 30, 63, 1 } } },\n\t{ { { 46, 0, 2 }, { 30, 63, 0 } } },\n\t{ { { 47, 0, 1 }, { 31, 63, 1 } } },\n\t{ { { 47, 0, 0 }, { 31, 63, 0 } } },\n\t{ { { 47, 0, 1 }, { 32, 63, 1 } } },\n\t{ { { 47, 0, 2 }, { 32, 63, 0 } } },\n\t{ { { 48, 0, 2 }, { 33, 63, 1 } } },\n\t{ { { 48, 0, 1 }, { 33, 63, 0 } } },\n\t{ { { 48, 0, 0 }, { 48, 48, 0 } } },\n\t{ { { 48, 0, 1 }, { 34, 63, 0 } } },\n\t{ { { 48, 0, 2 }, { 48, 49, 0 } } },\n\t{ { { 49, 0, 1 }, { 35, 63, 0 } } },\n\t{ { { 49, 0, 0 }, { 48, 50, 0 } } },\n\t{ { { 49, 0, 1 }, { 36, 63, 0 } } },\n\t{ { { 49, 0, 2 }, { 48, 51, 0 } } },\n\t{ { { 50, 0, 1 }, { 37, 63, 0 } } },\n\t{ { { 50, 0, 0 }, { 48, 52, 0 } } },\n\t{ { { 50, 0, 1 }, { 38, 63, 0 } } },\n\t{ { { 50, 0, 2 }, { 48, 53, 0 } } },\n\t{ { { 51, 0, 1 }, { 39, 63, 0 } } },\n\t{ { { 51, 0, 0 }, { 48, 54, 0 } } },\n\t{ { { 51, 0, 1 }, { 40, 63, 0 } } },\n\t{ { { 51, 0, 2 }, { 48, 55, 0 } } },\n\t{ { { 52, 0, 1 }, { 41, 63, 0 } } },\n\t{ { { 52, 0, 0 }, { 48, 56, 0 } } },\n\t{ { { 52, 0, 1 }, { 42, 63, 0 } } },\n\t{ { { 52, 0, 2 }, { 48, 57, 0 } } },\n\t{ { { 53, 0, 1 }, { 43, 63, 0 } } },\n\t{ { { 53, 0, 0 }, { 48, 58, 0 } } },\n\t{ { { 53, 0, 1 }, { 44, 63, 0 } } },\n\t{ { { 53, 0, 2 }, { 48, 59, 0 } } },\n\t{ { { 54, 0, 1 }, { 45, 63, 0 } } },\n\t{ { { 54, 0, 0 }, { 48, 60, 0 } } },\n\t{ { { 54, 0, 1 }, { 46, 63, 0 } } },\n\t{ { { 54, 0, 2 }, { 48, 61, 0 } } },\n\t{ { { 55, 0, 1 }, { 47, 63, 0 } } },\n\t{ { { 55, 0, 0 }, { 48, 62, 0 } } },\n\t{ { { 55, 0, 1 }, { 48, 63, 1 } } },\n\t{ { { 55, 0, 2 }, { 48, 63, 0 } } },\n\t{ { { 56, 0, 1 }, { 49, 63, 1 } } },\n\t{ { { 56, 0, 0 }, { 49, 63, 0 } } },\n\t{ { { 56, 0, 1 }, { 50, 63, 1 } } },\n\t{ { { 56, 0, 2 }, { 50, 63, 0 } } },\n\t{ { { 57, 0, 1 }, { 51, 63, 1 } } },\n\t{ { { 57, 0, 0 }, { 51, 63, 0 } } },\n\t{ { { 57, 0, 1 }, { 52, 63, 1 } } },\n\t{ { { 57, 0, 2 }, { 52, 63, 0 } } },\n\t{ { { 58, 0, 1 }, { 53, 63, 1 } } },\n\t{ { { 58, 0, 0 }, { 53, 63, 0 } } },\n\t{ { { 58, 0, 1 }, { 54, 63, 1 } } },\n\t{ { { 58, 0, 2 }, { 54, 63, 0 } } },\n\t{ { { 59, 0, 1 }, { 55, 63, 1 } } },\n\t{ { { 59, 0, 0 }, { 55, 63, 0 } } },\n\t{ { { 59, 0, 1 }, { 56, 63, 1 } } },\n\t{ { { 59, 0, 2 }, { 56, 63, 0 } } },\n\t{ { { 60, 0, 1 }, { 57, 63, 1 } } },\n\t{ { { 60, 0, 0 }, { 57, 63, 0 } } },\n\t{ { { 60, 0, 1 }, { 58, 63, 1 } } },\n\t{ { { 60, 0, 2 }, { 58, 63, 0 } } },\n\t{ { { 61, 0, 1 }, { 59, 63, 1 } } },\n\t{ { { 61, 0, 0 }, { 59, 63, 0 } } },\n\t{ { { 61, 0, 1 }, { 60, 63, 1 } } },\n\t{ { { 61, 0, 2 }, { 60, 63, 0 } } },\n\t{ { { 62, 0, 1 }, { 61, 63, 1 } } },\n\t{ { { 62, 0, 0 }, { 61, 63, 0 } } },\n\t{ { { 62, 0, 1 }, { 62, 63, 1 } } },\n\t{ { { 62, 0, 2 }, { 62, 63, 0 } } },\n\t{ { { 63, 0, 1 }, { 63, 63, 1 } } },\n\t{ { { 63, 0, 0 }, { 63, 63, 0 } } }\n};\n\nstatic SingleColourLookup const lookup_5_4[] = \n{\n\t{ { { 0, 0, 0 }, { 0, 0, 0 } } },\n\t{ { { 0, 0, 1 }, { 0, 1, 1 } } },\n\t{ { { 0, 0, 2 }, { 0, 1, 0 } } },\n\t{ { { 0, 0, 3 }, { 0, 1, 1 } } },\n\t{ { { 0, 0, 4 }, { 0, 2, 1 } } },\n\t{ { { 1, 0, 3 }, { 0, 2, 0 } } },\n\t{ { { 1, 0, 2 }, { 0, 2, 1 } } },\n\t{ { { 1, 0, 1 }, { 0, 3, 1 } } },\n\t{ { { 1, 0, 0 }, { 0, 3, 0 } } },\n\t{ { { 1, 0, 1 }, { 1, 2, 1 } } },\n\t{ { { 1, 0, 2 }, { 1, 2, 0 } } },\n\t{ { { 1, 0, 3 }, { 0, 4, 0 } } },\n\t{ { { 1, 0, 4 }, { 0, 5, 1 } } },\n\t{ { { 2, 0, 3 }, { 0, 5, 0 } } },\n\t{ { { 2, 0, 2 }, { 0, 5, 1 } } },\n\t{ { { 2, 0, 1 }, { 0, 6, 1 } } },\n\t{ { { 2, 0, 0 }, { 0, 6, 0 } } },\n\t{ { { 2, 0, 1 }, { 2, 3, 1 } } },\n\t{ { { 2, 0, 2 }, { 2, 3, 0 } } },\n\t{ { { 2, 0, 3 }, { 0, 7, 0 } } },\n\t{ { { 2, 0, 4 }, { 1, 6, 1 } } },\n\t{ { { 3, 0, 3 }, { 1, 6, 0 } } },\n\t{ { { 3, 0, 2 }, { 0, 8, 0 } } },\n\t{ { { 3, 0, 1 }, { 0, 9, 1 } } },\n\t{ { { 3, 0, 0 }, { 0, 9, 0 } } },\n\t{ { { 3, 0, 1 }, { 0, 9, 1 } } },\n\t{ { { 3, 0, 2 }, { 0, 10, 1 } } },\n\t{ { { 3, 0, 3 }, { 0, 10, 0 } } },\n\t{ { { 3, 0, 4 }, { 2, 7, 1 } } },\n\t{ { { 4, 0, 4 }, { 2, 7, 0 } } },\n\t{ { { 4, 0, 3 }, { 0, 11, 0 } } },\n\t{ { { 4, 0, 2 }, { 1, 10, 1 } } },\n\t{ { { 4, 0, 1 }, { 1, 10, 0 } } },\n\t{ { { 4, 0, 0 }, { 0, 12, 0 } } },\n\t{ { { 4, 0, 1 }, { 0, 13, 1 } } },\n\t{ { { 4, 0, 2 }, { 0, 13, 0 } } },\n\t{ { { 4, 0, 3 }, { 0, 13, 1 } } },\n\t{ { { 4, 0, 4 }, { 0, 14, 1 } } },\n\t{ { { 5, 0, 3 }, { 0, 14, 0 } } },\n\t{ { { 5, 0, 2 }, { 2, 11, 1 } } },\n\t{ { { 5, 0, 1 }, { 2, 11, 0 } } },\n\t{ { { 5, 0, 0 }, { 0, 15, 0 } } },\n\t{ { { 5, 0, 1 }, { 1, 14, 1 } } },\n\t{ { { 5, 0, 2 }, { 1, 14, 0 } } },\n\t{ { { 5, 0, 3 }, { 0, 16, 0 } } },\n\t{ { { 5, 0, 4 }, { 0, 17, 1 } } },\n\t{ { { 6, 0, 3 }, { 0, 17, 0 } } },\n\t{ { { 6, 0, 2 }, { 0, 17, 1 } } },\n\t{ { { 6, 0, 1 }, { 0, 18, 1 } } },\n\t{ { { 6, 0, 0 }, { 0, 18, 0 } } },\n\t{ { { 6, 0, 1 }, { 2, 15, 1 } } },\n\t{ { { 6, 0, 2 }, { 2, 15, 0 } } },\n\t{ { { 6, 0, 3 }, { 0, 19, 0 } } },\n\t{ { { 6, 0, 4 }, { 1, 18, 1 } } },\n\t{ { { 7, 0, 3 }, { 1, 18, 0 } } },\n\t{ { { 7, 0, 2 }, { 0, 20, 0 } } },\n\t{ { { 7, 0, 1 }, { 0, 21, 1 } } },\n\t{ { { 7, 0, 0 }, { 0, 21, 0 } } },\n\t{ { { 7, 0, 1 }, { 0, 21, 1 } } },\n\t{ { { 7, 0, 2 }, { 0, 22, 1 } } },\n\t{ { { 7, 0, 3 }, { 0, 22, 0 } } },\n\t{ { { 7, 0, 4 }, { 2, 19, 1 } } },\n\t{ { { 8, 0, 4 }, { 2, 19, 0 } } },\n\t{ { { 8, 0, 3 }, { 0, 23, 0 } } },\n\t{ { { 8, 0, 2 }, { 1, 22, 1 } } },\n\t{ { { 8, 0, 1 }, { 1, 22, 0 } } },\n\t{ { { 8, 0, 0 }, { 0, 24, 0 } } },\n\t{ { { 8, 0, 1 }, { 0, 25, 1 } } },\n\t{ { { 8, 0, 2 }, { 0, 25, 0 } } },\n\t{ { { 8, 0, 3 }, { 0, 25, 1 } } },\n\t{ { { 8, 0, 4 }, { 0, 26, 1 } } },\n\t{ { { 9, 0, 3 }, { 0, 26, 0 } } },\n\t{ { { 9, 0, 2 }, { 2, 23, 1 } } },\n\t{ { { 9, 0, 1 }, { 2, 23, 0 } } },\n\t{ { { 9, 0, 0 }, { 0, 27, 0 } } },\n\t{ { { 9, 0, 1 }, { 1, 26, 1 } } },\n\t{ { { 9, 0, 2 }, { 1, 26, 0 } } },\n\t{ { { 9, 0, 3 }, { 0, 28, 0 } } },\n\t{ { { 9, 0, 4 }, { 0, 29, 1 } } },\n\t{ { { 10, 0, 3 }, { 0, 29, 0 } } },\n\t{ { { 10, 0, 2 }, { 0, 29, 1 } } },\n\t{ { { 10, 0, 1 }, { 0, 30, 1 } } },\n\t{ { { 10, 0, 0 }, { 0, 30, 0 } } },\n\t{ { { 10, 0, 1 }, { 2, 27, 1 } } },\n\t{ { { 10, 0, 2 }, { 2, 27, 0 } } },\n\t{ { { 10, 0, 3 }, { 0, 31, 0 } } },\n\t{ { { 10, 0, 4 }, { 1, 30, 1 } } },\n\t{ { { 11, 0, 3 }, { 1, 30, 0 } } },\n\t{ { { 11, 0, 2 }, { 4, 24, 0 } } },\n\t{ { { 11, 0, 1 }, { 1, 31, 1 } } },\n\t{ { { 11, 0, 0 }, { 1, 31, 0 } } },\n\t{ { { 11, 0, 1 }, { 1, 31, 1 } } },\n\t{ { { 11, 0, 2 }, { 2, 30, 1 } } },\n\t{ { { 11, 0, 3 }, { 2, 30, 0 } } },\n\t{ { { 11, 0, 4 }, { 2, 31, 1 } } },\n\t{ { { 12, 0, 4 }, { 2, 31, 0 } } },\n\t{ { { 12, 0, 3 }, { 4, 27, 0 } } },\n\t{ { { 12, 0, 2 }, { 3, 30, 1 } } },\n\t{ { { 12, 0, 1 }, { 3, 30, 0 } } },\n\t{ { { 12, 0, 0 }, { 4, 28, 0 } } },\n\t{ { { 12, 0, 1 }, { 3, 31, 1 } } },\n\t{ { { 12, 0, 2 }, { 3, 31, 0 } } },\n\t{ { { 12, 0, 3 }, { 3, 31, 1 } } },\n\t{ { { 12, 0, 4 }, { 4, 30, 1 } } },\n\t{ { { 13, 0, 3 }, { 4, 30, 0 } } },\n\t{ { { 13, 0, 2 }, { 6, 27, 1 } } },\n\t{ { { 13, 0, 1 }, { 6, 27, 0 } } },\n\t{ { { 13, 0, 0 }, { 4, 31, 0 } } },\n\t{ { { 13, 0, 1 }, { 5, 30, 1 } } },\n\t{ { { 13, 0, 2 }, { 5, 30, 0 } } },\n\t{ { { 13, 0, 3 }, { 8, 24, 0 } } },\n\t{ { { 13, 0, 4 }, { 5, 31, 1 } } },\n\t{ { { 14, 0, 3 }, { 5, 31, 0 } } },\n\t{ { { 14, 0, 2 }, { 5, 31, 1 } } },\n\t{ { { 14, 0, 1 }, { 6, 30, 1 } } },\n\t{ { { 14, 0, 0 }, { 6, 30, 0 } } },\n\t{ { { 14, 0, 1 }, { 6, 31, 1 } } },\n\t{ { { 14, 0, 2 }, { 6, 31, 0 } } },\n\t{ { { 14, 0, 3 }, { 8, 27, 0 } } },\n\t{ { { 14, 0, 4 }, { 7, 30, 1 } } },\n\t{ { { 15, 0, 3 }, { 7, 30, 0 } } },\n\t{ { { 15, 0, 2 }, { 8, 28, 0 } } },\n\t{ { { 15, 0, 1 }, { 7, 31, 1 } } },\n\t{ { { 15, 0, 0 }, { 7, 31, 0 } } },\n\t{ { { 15, 0, 1 }, { 7, 31, 1 } } },\n\t{ { { 15, 0, 2 }, { 8, 30, 1 } } },\n\t{ { { 15, 0, 3 }, { 8, 30, 0 } } },\n\t{ { { 15, 0, 4 }, { 10, 27, 1 } } },\n\t{ { { 16, 0, 4 }, { 10, 27, 0 } } },\n\t{ { { 16, 0, 3 }, { 8, 31, 0 } } },\n\t{ { { 16, 0, 2 }, { 9, 30, 1 } } },\n\t{ { { 16, 0, 1 }, { 9, 30, 0 } } },\n\t{ { { 16, 0, 0 }, { 12, 24, 0 } } },\n\t{ { { 16, 0, 1 }, { 9, 31, 1 } } },\n\t{ { { 16, 0, 2 }, { 9, 31, 0 } } },\n\t{ { { 16, 0, 3 }, { 9, 31, 1 } } },\n\t{ { { 16, 0, 4 }, { 10, 30, 1 } } },\n\t{ { { 17, 0, 3 }, { 10, 30, 0 } } },\n\t{ { { 17, 0, 2 }, { 10, 31, 1 } } },\n\t{ { { 17, 0, 1 }, { 10, 31, 0 } } },\n\t{ { { 17, 0, 0 }, { 12, 27, 0 } } },\n\t{ { { 17, 0, 1 }, { 11, 30, 1 } } },\n\t{ { { 17, 0, 2 }, { 11, 30, 0 } } },\n\t{ { { 17, 0, 3 }, { 12, 28, 0 } } },\n\t{ { { 17, 0, 4 }, { 11, 31, 1 } } },\n\t{ { { 18, 0, 3 }, { 11, 31, 0 } } },\n\t{ { { 18, 0, 2 }, { 11, 31, 1 } } },\n\t{ { { 18, 0, 1 }, { 12, 30, 1 } } },\n\t{ { { 18, 0, 0 }, { 12, 30, 0 } } },\n\t{ { { 18, 0, 1 }, { 14, 27, 1 } } },\n\t{ { { 18, 0, 2 }, { 14, 27, 0 } } },\n\t{ { { 18, 0, 3 }, { 12, 31, 0 } } },\n\t{ { { 18, 0, 4 }, { 13, 30, 1 } } },\n\t{ { { 19, 0, 3 }, { 13, 30, 0 } } },\n\t{ { { 19, 0, 2 }, { 16, 24, 0 } } },\n\t{ { { 19, 0, 1 }, { 13, 31, 1 } } },\n\t{ { { 19, 0, 0 }, { 13, 31, 0 } } },\n\t{ { { 19, 0, 1 }, { 13, 31, 1 } } },\n\t{ { { 19, 0, 2 }, { 14, 30, 1 } } },\n\t{ { { 19, 0, 3 }, { 14, 30, 0 } } },\n\t{ { { 19, 0, 4 }, { 14, 31, 1 } } },\n\t{ { { 20, 0, 4 }, { 14, 31, 0 } } },\n\t{ { { 20, 0, 3 }, { 16, 27, 0 } } },\n\t{ { { 20, 0, 2 }, { 15, 30, 1 } } },\n\t{ { { 20, 0, 1 }, { 15, 30, 0 } } },\n\t{ { { 20, 0, 0 }, { 16, 28, 0 } } },\n\t{ { { 20, 0, 1 }, { 15, 31, 1 } } },\n\t{ { { 20, 0, 2 }, { 15, 31, 0 } } },\n\t{ { { 20, 0, 3 }, { 15, 31, 1 } } },\n\t{ { { 20, 0, 4 }, { 16, 30, 1 } } },\n\t{ { { 21, 0, 3 }, { 16, 30, 0 } } },\n\t{ { { 21, 0, 2 }, { 18, 27, 1 } } },\n\t{ { { 21, 0, 1 }, { 18, 27, 0 } } },\n\t{ { { 21, 0, 0 }, { 16, 31, 0 } } },\n\t{ { { 21, 0, 1 }, { 17, 30, 1 } } },\n\t{ { { 21, 0, 2 }, { 17, 30, 0 } } },\n\t{ { { 21, 0, 3 }, { 20, 24, 0 } } },\n\t{ { { 21, 0, 4 }, { 17, 31, 1 } } },\n\t{ { { 22, 0, 3 }, { 17, 31, 0 } } },\n\t{ { { 22, 0, 2 }, { 17, 31, 1 } } },\n\t{ { { 22, 0, 1 }, { 18, 30, 1 } } },\n\t{ { { 22, 0, 0 }, { 18, 30, 0 } } },\n\t{ { { 22, 0, 1 }, { 18, 31, 1 } } },\n\t{ { { 22, 0, 2 }, { 18, 31, 0 } } },\n\t{ { { 22, 0, 3 }, { 20, 27, 0 } } },\n\t{ { { 22, 0, 4 }, { 19, 30, 1 } } },\n\t{ { { 23, 0, 3 }, { 19, 30, 0 } } },\n\t{ { { 23, 0, 2 }, { 20, 28, 0 } } },\n\t{ { { 23, 0, 1 }, { 19, 31, 1 } } },\n\t{ { { 23, 0, 0 }, { 19, 31, 0 } } },\n\t{ { { 23, 0, 1 }, { 19, 31, 1 } } },\n\t{ { { 23, 0, 2 }, { 20, 30, 1 } } },\n\t{ { { 23, 0, 3 }, { 20, 30, 0 } } },\n\t{ { { 23, 0, 4 }, { 22, 27, 1 } } },\n\t{ { { 24, 0, 4 }, { 22, 27, 0 } } },\n\t{ { { 24, 0, 3 }, { 20, 31, 0 } } },\n\t{ { { 24, 0, 2 }, { 21, 30, 1 } } },\n\t{ { { 24, 0, 1 }, { 21, 30, 0 } } },\n\t{ { { 24, 0, 0 }, { 24, 24, 0 } } },\n\t{ { { 24, 0, 1 }, { 21, 31, 1 } } },\n\t{ { { 24, 0, 2 }, { 21, 31, 0 } } },\n\t{ { { 24, 0, 3 }, { 21, 31, 1 } } },\n\t{ { { 24, 0, 4 }, { 22, 30, 1 } } },\n\t{ { { 25, 0, 3 }, { 22, 30, 0 } } },\n\t{ { { 25, 0, 2 }, { 22, 31, 1 } } },\n\t{ { { 25, 0, 1 }, { 22, 31, 0 } } },\n\t{ { { 25, 0, 0 }, { 24, 27, 0 } } },\n\t{ { { 25, 0, 1 }, { 23, 30, 1 } } },\n\t{ { { 25, 0, 2 }, { 23, 30, 0 } } },\n\t{ { { 25, 0, 3 }, { 24, 28, 0 } } },\n\t{ { { 25, 0, 4 }, { 23, 31, 1 } } },\n\t{ { { 26, 0, 3 }, { 23, 31, 0 } } },\n\t{ { { 26, 0, 2 }, { 23, 31, 1 } } },\n\t{ { { 26, 0, 1 }, { 24, 30, 1 } } },\n\t{ { { 26, 0, 0 }, { 24, 30, 0 } } },\n\t{ { { 26, 0, 1 }, { 26, 27, 1 } } },\n\t{ { { 26, 0, 2 }, { 26, 27, 0 } } },\n\t{ { { 26, 0, 3 }, { 24, 31, 0 } } },\n\t{ { { 26, 0, 4 }, { 25, 30, 1 } } },\n\t{ { { 27, 0, 3 }, { 25, 30, 0 } } },\n\t{ { { 27, 0, 2 }, { 28, 24, 0 } } },\n\t{ { { 27, 0, 1 }, { 25, 31, 1 } } },\n\t{ { { 27, 0, 0 }, { 25, 31, 0 } } },\n\t{ { { 27, 0, 1 }, { 25, 31, 1 } } },\n\t{ { { 27, 0, 2 }, { 26, 30, 1 } } },\n\t{ { { 27, 0, 3 }, { 26, 30, 0 } } },\n\t{ { { 27, 0, 4 }, { 26, 31, 1 } } },\n\t{ { { 28, 0, 4 }, { 26, 31, 0 } } },\n\t{ { { 28, 0, 3 }, { 28, 27, 0 } } },\n\t{ { { 28, 0, 2 }, { 27, 30, 1 } } },\n\t{ { { 28, 0, 1 }, { 27, 30, 0 } } },\n\t{ { { 28, 0, 0 }, { 28, 28, 0 } } },\n\t{ { { 28, 0, 1 }, { 27, 31, 1 } } },\n\t{ { { 28, 0, 2 }, { 27, 31, 0 } } },\n\t{ { { 28, 0, 3 }, { 27, 31, 1 } } },\n\t{ { { 28, 0, 4 }, { 28, 30, 1 } } },\n\t{ { { 29, 0, 3 }, { 28, 30, 0 } } },\n\t{ { { 29, 0, 2 }, { 30, 27, 1 } } },\n\t{ { { 29, 0, 1 }, { 30, 27, 0 } } },\n\t{ { { 29, 0, 0 }, { 28, 31, 0 } } },\n\t{ { { 29, 0, 1 }, { 29, 30, 1 } } },\n\t{ { { 29, 0, 2 }, { 29, 30, 0 } } },\n\t{ { { 29, 0, 3 }, { 29, 30, 1 } } },\n\t{ { { 29, 0, 4 }, { 29, 31, 1 } } },\n\t{ { { 30, 0, 3 }, { 29, 31, 0 } } },\n\t{ { { 30, 0, 2 }, { 29, 31, 1 } } },\n\t{ { { 30, 0, 1 }, { 30, 30, 1 } } },\n\t{ { { 30, 0, 0 }, { 30, 30, 0 } } },\n\t{ { { 30, 0, 1 }, { 30, 31, 1 } } },\n\t{ { { 30, 0, 2 }, { 30, 31, 0 } } },\n\t{ { { 30, 0, 3 }, { 30, 31, 1 } } },\n\t{ { { 30, 0, 4 }, { 31, 30, 1 } } },\n\t{ { { 31, 0, 3 }, { 31, 30, 0 } } },\n\t{ { { 31, 0, 2 }, { 31, 30, 1 } } },\n\t{ { { 31, 0, 1 }, { 31, 31, 1 } } },\n\t{ { { 31, 0, 0 }, { 31, 31, 0 } } }\n};\n\nstatic SingleColourLookup const lookup_6_4[] = \n{\n\t{ { { 0, 0, 0 }, { 0, 0, 0 } } },\n\t{ { { 0, 0, 1 }, { 0, 1, 0 } } },\n\t{ { { 0, 0, 2 }, { 0, 2, 0 } } },\n\t{ { { 1, 0, 1 }, { 0, 3, 1 } } },\n\t{ { { 1, 0, 0 }, { 0, 3, 0 } } },\n\t{ { { 1, 0, 1 }, { 0, 4, 0 } } },\n\t{ { { 1, 0, 2 }, { 0, 5, 0 } } },\n\t{ { { 2, 0, 1 }, { 0, 6, 1 } } },\n\t{ { { 2, 0, 0 }, { 0, 6, 0 } } },\n\t{ { { 2, 0, 1 }, { 0, 7, 0 } } },\n\t{ { { 2, 0, 2 }, { 0, 8, 0 } } },\n\t{ { { 3, 0, 1 }, { 0, 9, 1 } } },\n\t{ { { 3, 0, 0 }, { 0, 9, 0 } } },\n\t{ { { 3, 0, 1 }, { 0, 10, 0 } } },\n\t{ { { 3, 0, 2 }, { 0, 11, 0 } } },\n\t{ { { 4, 0, 1 }, { 0, 12, 1 } } },\n\t{ { { 4, 0, 0 }, { 0, 12, 0 } } },\n\t{ { { 4, 0, 1 }, { 0, 13, 0 } } },\n\t{ { { 4, 0, 2 }, { 0, 14, 0 } } },\n\t{ { { 5, 0, 1 }, { 0, 15, 1 } } },\n\t{ { { 5, 0, 0 }, { 0, 15, 0 } } },\n\t{ { { 5, 0, 1 }, { 0, 16, 0 } } },\n\t{ { { 5, 0, 2 }, { 1, 15, 0 } } },\n\t{ { { 6, 0, 1 }, { 0, 17, 0 } } },\n\t{ { { 6, 0, 0 }, { 0, 18, 0 } } },\n\t{ { { 6, 0, 1 }, { 0, 19, 0 } } },\n\t{ { { 6, 0, 2 }, { 3, 14, 0 } } },\n\t{ { { 7, 0, 1 }, { 0, 20, 0 } } },\n\t{ { { 7, 0, 0 }, { 0, 21, 0 } } },\n\t{ { { 7, 0, 1 }, { 0, 22, 0 } } },\n\t{ { { 7, 0, 2 }, { 4, 15, 0 } } },\n\t{ { { 8, 0, 1 }, { 0, 23, 0 } } },\n\t{ { { 8, 0, 0 }, { 0, 24, 0 } } },\n\t{ { { 8, 0, 1 }, { 0, 25, 0 } } },\n\t{ { { 8, 0, 2 }, { 6, 14, 0 } } },\n\t{ { { 9, 0, 1 }, { 0, 26, 0 } } },\n\t{ { { 9, 0, 0 }, { 0, 27, 0 } } },\n\t{ { { 9, 0, 1 }, { 0, 28, 0 } } },\n\t{ { { 9, 0, 2 }, { 7, 15, 0 } } },\n\t{ { { 10, 0, 1 }, { 0, 29, 0 } } },\n\t{ { { 10, 0, 0 }, { 0, 30, 0 } } },\n\t{ { { 10, 0, 1 }, { 0, 31, 0 } } },\n\t{ { { 10, 0, 2 }, { 9, 14, 0 } } },\n\t{ { { 11, 0, 1 }, { 0, 32, 0 } } },\n\t{ { { 11, 0, 0 }, { 0, 33, 0 } } },\n\t{ { { 11, 0, 1 }, { 2, 30, 0 } } },\n\t{ { { 11, 0, 2 }, { 0, 34, 0 } } },\n\t{ { { 12, 0, 1 }, { 0, 35, 0 } } },\n\t{ { { 12, 0, 0 }, { 0, 36, 0 } } },\n\t{ { { 12, 0, 1 }, { 3, 31, 0 } } },\n\t{ { { 12, 0, 2 }, { 0, 37, 0 } } },\n\t{ { { 13, 0, 1 }, { 0, 38, 0 } } },\n\t{ { { 13, 0, 0 }, { 0, 39, 0 } } },\n\t{ { { 13, 0, 1 }, { 5, 30, 0 } } },\n\t{ { { 13, 0, 2 }, { 0, 40, 0 } } },\n\t{ { { 14, 0, 1 }, { 0, 41, 0 } } },\n\t{ { { 14, 0, 0 }, { 0, 42, 0 } } },\n\t{ { { 14, 0, 1 }, { 6, 31, 0 } } },\n\t{ { { 14, 0, 2 }, { 0, 43, 0 } } },\n\t{ { { 15, 0, 1 }, { 0, 44, 0 } } },\n\t{ { { 15, 0, 0 }, { 0, 45, 0 } } },\n\t{ { { 15, 0, 1 }, { 8, 30, 0 } } },\n\t{ { { 15, 0, 2 }, { 0, 46, 0 } } },\n\t{ { { 16, 0, 2 }, { 0, 47, 0 } } },\n\t{ { { 16, 0, 1 }, { 1, 46, 0 } } },\n\t{ { { 16, 0, 0 }, { 0, 48, 0 } } },\n\t{ { { 16, 0, 1 }, { 0, 49, 0 } } },\n\t{ { { 16, 0, 2 }, { 0, 50, 0 } } },\n\t{ { { 17, 0, 1 }, { 2, 47, 0 } } },\n\t{ { { 17, 0, 0 }, { 0, 51, 0 } } },\n\t{ { { 17, 0, 1 }, { 0, 52, 0 } } },\n\t{ { { 17, 0, 2 }, { 0, 53, 0 } } },\n\t{ { { 18, 0, 1 }, { 4, 46, 0 } } },\n\t{ { { 18, 0, 0 }, { 0, 54, 0 } } },\n\t{ { { 18, 0, 1 }, { 0, 55, 0 } } },\n\t{ { { 18, 0, 2 }, { 0, 56, 0 } } },\n\t{ { { 19, 0, 1 }, { 5, 47, 0 } } },\n\t{ { { 19, 0, 0 }, { 0, 57, 0 } } },\n\t{ { { 19, 0, 1 }, { 0, 58, 0 } } },\n\t{ { { 19, 0, 2 }, { 0, 59, 0 } } },\n\t{ { { 20, 0, 1 }, { 7, 46, 0 } } },\n\t{ { { 20, 0, 0 }, { 0, 60, 0 } } },\n\t{ { { 20, 0, 1 }, { 0, 61, 0 } } },\n\t{ { { 20, 0, 2 }, { 0, 62, 0 } } },\n\t{ { { 21, 0, 1 }, { 8, 47, 0 } } },\n\t{ { { 21, 0, 0 }, { 0, 63, 0 } } },\n\t{ { { 21, 0, 1 }, { 1, 62, 0 } } },\n\t{ { { 21, 0, 2 }, { 1, 63, 0 } } },\n\t{ { { 22, 0, 1 }, { 10, 46, 0 } } },\n\t{ { { 22, 0, 0 }, { 2, 62, 0 } } },\n\t{ { { 22, 0, 1 }, { 2, 63, 0 } } },\n\t{ { { 22, 0, 2 }, { 3, 62, 0 } } },\n\t{ { { 23, 0, 1 }, { 11, 47, 0 } } },\n\t{ { { 23, 0, 0 }, { 3, 63, 0 } } },\n\t{ { { 23, 0, 1 }, { 4, 62, 0 } } },\n\t{ { { 23, 0, 2 }, { 4, 63, 0 } } },\n\t{ { { 24, 0, 1 }, { 13, 46, 0 } } },\n\t{ { { 24, 0, 0 }, { 5, 62, 0 } } },\n\t{ { { 24, 0, 1 }, { 5, 63, 0 } } },\n\t{ { { 24, 0, 2 }, { 6, 62, 0 } } },\n\t{ { { 25, 0, 1 }, { 14, 47, 0 } } },\n\t{ { { 25, 0, 0 }, { 6, 63, 0 } } },\n\t{ { { 25, 0, 1 }, { 7, 62, 0 } } },\n\t{ { { 25, 0, 2 }, { 7, 63, 0 } } },\n\t{ { { 26, 0, 1 }, { 16, 45, 0 } } },\n\t{ { { 26, 0, 0 }, { 8, 62, 0 } } },\n\t{ { { 26, 0, 1 }, { 8, 63, 0 } } },\n\t{ { { 26, 0, 2 }, { 9, 62, 0 } } },\n\t{ { { 27, 0, 1 }, { 16, 48, 0 } } },\n\t{ { { 27, 0, 0 }, { 9, 63, 0 } } },\n\t{ { { 27, 0, 1 }, { 10, 62, 0 } } },\n\t{ { { 27, 0, 2 }, { 10, 63, 0 } } },\n\t{ { { 28, 0, 1 }, { 16, 51, 0 } } },\n\t{ { { 28, 0, 0 }, { 11, 62, 0 } } },\n\t{ { { 28, 0, 1 }, { 11, 63, 0 } } },\n\t{ { { 28, 0, 2 }, { 12, 62, 0 } } },\n\t{ { { 29, 0, 1 }, { 16, 54, 0 } } },\n\t{ { { 29, 0, 0 }, { 12, 63, 0 } } },\n\t{ { { 29, 0, 1 }, { 13, 62, 0 } } },\n\t{ { { 29, 0, 2 }, { 13, 63, 0 } } },\n\t{ { { 30, 0, 1 }, { 16, 57, 0 } } },\n\t{ { { 30, 0, 0 }, { 14, 62, 0 } } },\n\t{ { { 30, 0, 1 }, { 14, 63, 0 } } },\n\t{ { { 30, 0, 2 }, { 15, 62, 0 } } },\n\t{ { { 31, 0, 1 }, { 16, 60, 0 } } },\n\t{ { { 31, 0, 0 }, { 15, 63, 0 } } },\n\t{ { { 31, 0, 1 }, { 24, 46, 0 } } },\n\t{ { { 31, 0, 2 }, { 16, 62, 0 } } },\n\t{ { { 32, 0, 2 }, { 16, 63, 0 } } },\n\t{ { { 32, 0, 1 }, { 17, 62, 0 } } },\n\t{ { { 32, 0, 0 }, { 25, 47, 0 } } },\n\t{ { { 32, 0, 1 }, { 17, 63, 0 } } },\n\t{ { { 32, 0, 2 }, { 18, 62, 0 } } },\n\t{ { { 33, 0, 1 }, { 18, 63, 0 } } },\n\t{ { { 33, 0, 0 }, { 27, 46, 0 } } },\n\t{ { { 33, 0, 1 }, { 19, 62, 0 } } },\n\t{ { { 33, 0, 2 }, { 19, 63, 0 } } },\n\t{ { { 34, 0, 1 }, { 20, 62, 0 } } },\n\t{ { { 34, 0, 0 }, { 28, 47, 0 } } },\n\t{ { { 34, 0, 1 }, { 20, 63, 0 } } },\n\t{ { { 34, 0, 2 }, { 21, 62, 0 } } },\n\t{ { { 35, 0, 1 }, { 21, 63, 0 } } },\n\t{ { { 35, 0, 0 }, { 30, 46, 0 } } },\n\t{ { { 35, 0, 1 }, { 22, 62, 0 } } },\n\t{ { { 35, 0, 2 }, { 22, 63, 0 } } },\n\t{ { { 36, 0, 1 }, { 23, 62, 0 } } },\n\t{ { { 36, 0, 0 }, { 31, 47, 0 } } },\n\t{ { { 36, 0, 1 }, { 23, 63, 0 } } },\n\t{ { { 36, 0, 2 }, { 24, 62, 0 } } },\n\t{ { { 37, 0, 1 }, { 24, 63, 0 } } },\n\t{ { { 37, 0, 0 }, { 32, 47, 0 } } },\n\t{ { { 37, 0, 1 }, { 25, 62, 0 } } },\n\t{ { { 37, 0, 2 }, { 25, 63, 0 } } },\n\t{ { { 38, 0, 1 }, { 26, 62, 0 } } },\n\t{ { { 38, 0, 0 }, { 32, 50, 0 } } },\n\t{ { { 38, 0, 1 }, { 26, 63, 0 } } },\n\t{ { { 38, 0, 2 }, { 27, 62, 0 } } },\n\t{ { { 39, 0, 1 }, { 27, 63, 0 } } },\n\t{ { { 39, 0, 0 }, { 32, 53, 0 } } },\n\t{ { { 39, 0, 1 }, { 28, 62, 0 } } },\n\t{ { { 39, 0, 2 }, { 28, 63, 0 } } },\n\t{ { { 40, 0, 1 }, { 29, 62, 0 } } },\n\t{ { { 40, 0, 0 }, { 32, 56, 0 } } },\n\t{ { { 40, 0, 1 }, { 29, 63, 0 } } },\n\t{ { { 40, 0, 2 }, { 30, 62, 0 } } },\n\t{ { { 41, 0, 1 }, { 30, 63, 0 } } },\n\t{ { { 41, 0, 0 }, { 32, 59, 0 } } },\n\t{ { { 41, 0, 1 }, { 31, 62, 0 } } },\n\t{ { { 41, 0, 2 }, { 31, 63, 0 } } },\n\t{ { { 42, 0, 1 }, { 32, 61, 0 } } },\n\t{ { { 42, 0, 0 }, { 32, 62, 0 } } },\n\t{ { { 42, 0, 1 }, { 32, 63, 0 } } },\n\t{ { { 42, 0, 2 }, { 41, 46, 0 } } },\n\t{ { { 43, 0, 1 }, { 33, 62, 0 } } },\n\t{ { { 43, 0, 0 }, { 33, 63, 0 } } },\n\t{ { { 43, 0, 1 }, { 34, 62, 0 } } },\n\t{ { { 43, 0, 2 }, { 42, 47, 0 } } },\n\t{ { { 44, 0, 1 }, { 34, 63, 0 } } },\n\t{ { { 44, 0, 0 }, { 35, 62, 0 } } },\n\t{ { { 44, 0, 1 }, { 35, 63, 0 } } },\n\t{ { { 44, 0, 2 }, { 44, 46, 0 } } },\n\t{ { { 45, 0, 1 }, { 36, 62, 0 } } },\n\t{ { { 45, 0, 0 }, { 36, 63, 0 } } },\n\t{ { { 45, 0, 1 }, { 37, 62, 0 } } },\n\t{ { { 45, 0, 2 }, { 45, 47, 0 } } },\n\t{ { { 46, 0, 1 }, { 37, 63, 0 } } },\n\t{ { { 46, 0, 0 }, { 38, 62, 0 } } },\n\t{ { { 46, 0, 1 }, { 38, 63, 0 } } },\n\t{ { { 46, 0, 2 }, { 47, 46, 0 } } },\n\t{ { { 47, 0, 1 }, { 39, 62, 0 } } },\n\t{ { { 47, 0, 0 }, { 39, 63, 0 } } },\n\t{ { { 47, 0, 1 }, { 40, 62, 0 } } },\n\t{ { { 47, 0, 2 }, { 48, 46, 0 } } },\n\t{ { { 48, 0, 2 }, { 40, 63, 0 } } },\n\t{ { { 48, 0, 1 }, { 41, 62, 0 } } },\n\t{ { { 48, 0, 0 }, { 41, 63, 0 } } },\n\t{ { { 48, 0, 1 }, { 48, 49, 0 } } },\n\t{ { { 48, 0, 2 }, { 42, 62, 0 } } },\n\t{ { { 49, 0, 1 }, { 42, 63, 0 } } },\n\t{ { { 49, 0, 0 }, { 43, 62, 0 } } },\n\t{ { { 49, 0, 1 }, { 48, 52, 0 } } },\n\t{ { { 49, 0, 2 }, { 43, 63, 0 } } },\n\t{ { { 50, 0, 1 }, { 44, 62, 0 } } },\n\t{ { { 50, 0, 0 }, { 44, 63, 0 } } },\n\t{ { { 50, 0, 1 }, { 48, 55, 0 } } },\n\t{ { { 50, 0, 2 }, { 45, 62, 0 } } },\n\t{ { { 51, 0, 1 }, { 45, 63, 0 } } },\n\t{ { { 51, 0, 0 }, { 46, 62, 0 } } },\n\t{ { { 51, 0, 1 }, { 48, 58, 0 } } },\n\t{ { { 51, 0, 2 }, { 46, 63, 0 } } },\n\t{ { { 52, 0, 1 }, { 47, 62, 0 } } },\n\t{ { { 52, 0, 0 }, { 47, 63, 0 } } },\n\t{ { { 52, 0, 1 }, { 48, 61, 0 } } },\n\t{ { { 52, 0, 2 }, { 48, 62, 0 } } },\n\t{ { { 53, 0, 1 }, { 56, 47, 0 } } },\n\t{ { { 53, 0, 0 }, { 48, 63, 0 } } },\n\t{ { { 53, 0, 1 }, { 49, 62, 0 } } },\n\t{ { { 53, 0, 2 }, { 49, 63, 0 } } },\n\t{ { { 54, 0, 1 }, { 58, 46, 0 } } },\n\t{ { { 54, 0, 0 }, { 50, 62, 0 } } },\n\t{ { { 54, 0, 1 }, { 50, 63, 0 } } },\n\t{ { { 54, 0, 2 }, { 51, 62, 0 } } },\n\t{ { { 55, 0, 1 }, { 59, 47, 0 } } },\n\t{ { { 55, 0, 0 }, { 51, 63, 0 } } },\n\t{ { { 55, 0, 1 }, { 52, 62, 0 } } },\n\t{ { { 55, 0, 2 }, { 52, 63, 0 } } },\n\t{ { { 56, 0, 1 }, { 61, 46, 0 } } },\n\t{ { { 56, 0, 0 }, { 53, 62, 0 } } },\n\t{ { { 56, 0, 1 }, { 53, 63, 0 } } },\n\t{ { { 56, 0, 2 }, { 54, 62, 0 } } },\n\t{ { { 57, 0, 1 }, { 62, 47, 0 } } },\n\t{ { { 57, 0, 0 }, { 54, 63, 0 } } },\n\t{ { { 57, 0, 1 }, { 55, 62, 0 } } },\n\t{ { { 57, 0, 2 }, { 55, 63, 0 } } },\n\t{ { { 58, 0, 1 }, { 56, 62, 1 } } },\n\t{ { { 58, 0, 0 }, { 56, 62, 0 } } },\n\t{ { { 58, 0, 1 }, { 56, 63, 0 } } },\n\t{ { { 58, 0, 2 }, { 57, 62, 0 } } },\n\t{ { { 59, 0, 1 }, { 57, 63, 1 } } },\n\t{ { { 59, 0, 0 }, { 57, 63, 0 } } },\n\t{ { { 59, 0, 1 }, { 58, 62, 0 } } },\n\t{ { { 59, 0, 2 }, { 58, 63, 0 } } },\n\t{ { { 60, 0, 1 }, { 59, 62, 1 } } },\n\t{ { { 60, 0, 0 }, { 59, 62, 0 } } },\n\t{ { { 60, 0, 1 }, { 59, 63, 0 } } },\n\t{ { { 60, 0, 2 }, { 60, 62, 0 } } },\n\t{ { { 61, 0, 1 }, { 60, 63, 1 } } },\n\t{ { { 61, 0, 0 }, { 60, 63, 0 } } },\n\t{ { { 61, 0, 1 }, { 61, 62, 0 } } },\n\t{ { { 61, 0, 2 }, { 61, 63, 0 } } },\n\t{ { { 62, 0, 1 }, { 62, 62, 1 } } },\n\t{ { { 62, 0, 0 }, { 62, 62, 0 } } },\n\t{ { { 62, 0, 1 }, { 62, 63, 0 } } },\n\t{ { { 62, 0, 2 }, { 63, 62, 0 } } },\n\t{ { { 63, 0, 1 }, { 63, 63, 1 } } },\n\t{ { { 63, 0, 0 }, { 63, 63, 0 } } }\n};\n"
  },
  {
    "path": "vendor/squish/squish.cpp",
    "content": "/* -----------------------------------------------------------------------------\n\n\tCopyright (c) 2006 Simon Brown                          si@sjbrown.co.uk\n\n\tPermission is hereby granted, free of charge, to any person obtaining\n\ta copy of this software and associated documentation files (the \n\t\"Software\"), to\tdeal in the Software without restriction, including\n\twithout limitation the rights to use, copy, modify, merge, publish,\n\tdistribute, sublicense, and/or sell copies of the Software, and to \n\tpermit persons to whom the Software is furnished to do so, subject to \n\tthe following conditions:\n\n\tThe above copyright notice and this permission notice shall be included\n\tin all copies or substantial portions of the Software.\n\n\tTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n\tOR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF \n\tMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\n\tIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY \n\tCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, \n\tTORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE \n\tSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\t\n   -------------------------------------------------------------------------- */\n   \n#include \"squish.h\"\n#include \"colourset.h\"\n#include \"maths.h\"\n#include \"rangefit.h\"\n#include \"clusterfit.h\"\n#include \"colourblock.h\"\n#include \"alpha.h\"\n#include \"singlecolourfit.h\"\n\nnamespace squish {\n\nstatic int FixFlags( int flags )\n{\n\t// grab the flag bits\n\tint method = flags & ( kDxt1 | kDxt3 | kDxt5 );\n\tint fit = flags & ( kColourIterativeClusterFit | kColourClusterFit | kColourRangeFit );\n\tint extra = flags & kWeightColourByAlpha;\n\t\n\t// set defaults\n\tif( method != kDxt3 && method != kDxt5 )\n\t\tmethod = kDxt1;\n\tif( fit != kColourRangeFit && fit != kColourIterativeClusterFit )\n\t\tfit = kColourClusterFit;\n\t\t\n\t// done\n\treturn method | fit | extra;\n}\n\nvoid CompressMasked( u8 const* rgba, int mask, void* block, int flags, float* metric )\n{\n\t// fix any bad flags\n\tflags = FixFlags( flags );\n\n\t// get the block locations\n\tvoid* colourBlock = block;\n\tvoid* alphaBock = block;\n\tif( ( flags & ( kDxt3 | kDxt5 ) ) != 0 )\n\t\tcolourBlock = reinterpret_cast< u8* >( block ) + 8;\n\n\t// create the minimal point set\n\tColourSet colours( rgba, mask, flags );\n\t\n\t// check the compression type and compress colour\n\tif( colours.GetCount() == 1 )\n\t{\n\t\t// always do a single colour fit\n\t\tSingleColourFit fit( &colours, flags );\n\t\tfit.Compress( colourBlock );\n\t}\n\telse if( ( flags & kColourRangeFit ) != 0 || colours.GetCount() == 0 )\n\t{\n\t\t// do a range fit\n\t\tRangeFit fit( &colours, flags, metric );\n\t\tfit.Compress( colourBlock );\n\t}\n\telse\n\t{\n\t\t// default to a cluster fit (could be iterative or not)\n\t\tClusterFit fit( &colours, flags, metric );\n\t\tfit.Compress( colourBlock );\n\t}\n\t\n\t// compress alpha separately if necessary\n\tif( ( flags & kDxt3 ) != 0 )\n\t\tCompressAlphaDxt3( rgba, mask, alphaBock );\n\telse if( ( flags & kDxt5 ) != 0 )\n\t\tCompressAlphaDxt5( rgba, mask, alphaBock );\n}\n\nvoid Decompress( u8* rgba, void const* block, int flags )\n{\n\t// fix any bad flags\n\tflags = FixFlags( flags );\n\n\t// get the block locations\n\tvoid const* colourBlock = block;\n\tvoid const* alphaBock = block;\n\tif( ( flags & ( kDxt3 | kDxt5 ) ) != 0 )\n\t\tcolourBlock = reinterpret_cast< u8 const* >( block ) + 8;\n\n\t// decompress colour\n\tDecompressColour( rgba, colourBlock, ( flags & kDxt1 ) != 0 );\n\n\t// decompress alpha separately if necessary\n\tif( ( flags & kDxt3 ) != 0 )\n\t\tDecompressAlphaDxt3( rgba, alphaBock );\n\telse if( ( flags & kDxt5 ) != 0 )\n\t\tDecompressAlphaDxt5( rgba, alphaBock );\n}\n\nint GetStorageRequirements( int width, int height, int flags )\n{\n\t// fix any bad flags\n\tflags = FixFlags( flags );\n\t\n\t// compute the storage requirements\n\tint blockcount = ( ( width + 3 )/4 ) * ( ( height + 3 )/4 );\n\tint blocksize = ( ( flags & kDxt1 ) != 0 ) ? 8 : 16;\n\treturn blockcount*blocksize;\t\n}\n\nvoid CompressImage( u8 const* rgba, int width, int height, void* blocks, int flags, float* metric )\n{\n\t// fix any bad flags\n\tflags = FixFlags( flags );\n\n\t// initialise the block output\n\tu8* targetBlock = reinterpret_cast< u8* >( blocks );\n\tint bytesPerBlock = ( ( flags & kDxt1 ) != 0 ) ? 8 : 16;\n\n\t// loop over blocks\n\tfor( int y = 0; y < height; y += 4 )\n\t{\n\t\tfor( int x = 0; x < width; x += 4 )\n\t\t{\n\t\t\t// build the 4x4 block of pixels\n\t\t\tu8 sourceRgba[16*4];\n\t\t\tu8* targetPixel = sourceRgba;\n\t\t\tint mask = 0;\n\t\t\tfor( int py = 0; py < 4; ++py )\n\t\t\t{\n\t\t\t\tfor( int px = 0; px < 4; ++px )\n\t\t\t\t{\n\t\t\t\t\t// get the source pixel in the image\n\t\t\t\t\tint sx = x + px;\n\t\t\t\t\tint sy = y + py;\n\t\t\t\t\t\n\t\t\t\t\t// enable if we're in the image\n\t\t\t\t\tif( sx < width && sy < height )\n\t\t\t\t\t{\n\t\t\t\t\t\t// copy the rgba value\n\t\t\t\t\t\tu8 const* sourcePixel = rgba + 4*( width*sy + sx );\n\t\t\t\t\t\tfor( int i = 0; i < 4; ++i )\n\t\t\t\t\t\t\t*targetPixel++ = *sourcePixel++;\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t// enable this pixel\n\t\t\t\t\t\tmask |= ( 1 << ( 4*py + px ) );\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\t// skip this pixel as its outside the image\n\t\t\t\t\t\ttargetPixel += 4;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\t\n\t\t\t// compress it into the output\n\t\t\tCompressMasked( sourceRgba, mask, targetBlock, flags, metric );\n\t\t\t\n\t\t\t// advance\n\t\t\ttargetBlock += bytesPerBlock;\n\t\t}\n\t}\n}\n\nvoid DecompressImage( u8* rgba, int width, int height, void const* blocks, int flags )\n{\n\t// fix any bad flags\n\tflags = FixFlags( flags );\n\n\t// initialise the block input\n\tu8 const* sourceBlock = reinterpret_cast< u8 const* >( blocks );\n\tint bytesPerBlock = ( ( flags & kDxt1 ) != 0 ) ? 8 : 16;\n\n\t// loop over blocks\n\tfor( int y = 0; y < height; y += 4 )\n\t{\n\t\tfor( int x = 0; x < width; x += 4 )\n\t\t{\n\t\t\t// decompress the block\n\t\t\tu8 targetRgba[4*16];\n\t\t\tDecompress( targetRgba, sourceBlock, flags );\n\t\t\t\n\t\t\t// write the decompressed pixels to the correct image locations\n\t\t\tu8 const* sourcePixel = targetRgba;\n\t\t\tfor( int py = 0; py < 4; ++py )\n\t\t\t{\n\t\t\t\tfor( int px = 0; px < 4; ++px )\n\t\t\t\t{\n\t\t\t\t\t// get the target location\n\t\t\t\t\tint sx = x + px;\n\t\t\t\t\tint sy = y + py;\n\t\t\t\t\tif( sx < width && sy < height )\n\t\t\t\t\t{\n\t\t\t\t\t\tu8* targetPixel = rgba + 4*( width*sy + sx );\n\t\t\t\t\t\t\n\t\t\t\t\t\t// copy the rgba value\n\t\t\t\t\t\tfor( int i = 0; i < 4; ++i )\n\t\t\t\t\t\t\t*targetPixel++ = *sourcePixel++;\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\t// skip this pixel as its outside the image\n\t\t\t\t\t\tsourcePixel += 4;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\t\n\t\t\t// advance\n\t\t\tsourceBlock += bytesPerBlock;\n\t\t}\n\t}\n}\n\n} // namespace squish\n"
  },
  {
    "path": "vendor/squish/squish.h",
    "content": "/* -----------------------------------------------------------------------------\n\n\tCopyright (c) 2006 Simon Brown                          si@sjbrown.co.uk\n\n\tPermission is hereby granted, free of charge, to any person obtaining\n\ta copy of this software and associated documentation files (the \n\t\"Software\"), to\tdeal in the Software without restriction, including\n\twithout limitation the rights to use, copy, modify, merge, publish,\n\tdistribute, sublicense, and/or sell copies of the Software, and to \n\tpermit persons to whom the Software is furnished to do so, subject to \n\tthe following conditions:\n\n\tThe above copyright notice and this permission notice shall be included\n\tin all copies or substantial portions of the Software.\n\n\tTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n\tOR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF \n\tMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\n\tIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY \n\tCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, \n\tTORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE \n\tSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\t\n   -------------------------------------------------------------------------- */\n   \n#ifndef SQUISH_H\n#define SQUISH_H\n\n//! All squish API functions live in this namespace.\nnamespace squish {\n\n// -----------------------------------------------------------------------------\n\n//! Typedef a quantity that is a single unsigned byte.\ntypedef unsigned char u8;\n\n// -----------------------------------------------------------------------------\n\nenum\n{\n\t//! Use DXT1 compression.\n\tkDxt1 = ( 1 << 0 ), \n\t\n\t//! Use DXT3 compression.\n\tkDxt3 = ( 1 << 1 ), \n\t\n\t//! Use DXT5 compression.\n\tkDxt5 = ( 1 << 2 ), \n\t\n\t//! Use a very slow but very high quality colour compressor.\n\tkColourIterativeClusterFit = ( 1 << 8 ),\t\n\t\n\t//! Use a slow but high quality colour compressor (the default).\n\tkColourClusterFit = ( 1 << 3 ),\t\n\t\n\t//! Use a fast but low quality colour compressor.\n\tkColourRangeFit\t= ( 1 << 4 ),\n\t\n\t//! Weight the colour by alpha during cluster fit (disabled by default).\n\tkWeightColourByAlpha = ( 1 << 7 )\n};\n\n// -----------------------------------------------------------------------------\n\n/*! @brief Compresses a 4x4 block of pixels.\n\n\t@param rgba\t\tThe rgba values of the 16 source pixels.\n\t@param mask\t\tThe valid pixel mask.\n\t@param block\tStorage for the compressed DXT block.\n\t@param flags\tCompression flags.\n\t@param metric\tAn optional perceptual metric.\n\t\n\tThe source pixels should be presented as a contiguous array of 16 rgba\n\tvalues, with each component as 1 byte each. In memory this should be:\n\t\n\t\t{ r1, g1, b1, a1, .... , r16, g16, b16, a16 }\n\t\t\n\tThe mask parameter enables only certain pixels within the block. The lowest\n\tbit enables the first pixel and so on up to the 16th bit. Bits beyond the\n\t16th bit are ignored. Pixels that are not enabled are allowed to take\n\tarbitrary colours in the output block. An example of how this can be used\n\tis in the CompressImage function to disable pixels outside the bounds of\n\tthe image when the width or height is not divisible by 4.\n\t\n\tThe flags parameter should specify either kDxt1, kDxt3 or kDxt5 compression, \n\thowever, DXT1 will be used by default if none is specified. When using DXT1 \n\tcompression, 8 bytes of storage are required for the compressed DXT block. \n\tDXT3 and DXT5 compression require 16 bytes of storage per block.\n\t\n\tThe flags parameter can also specify a preferred colour compressor to use \n\twhen fitting the RGB components of the data. Possible colour compressors \n\tare: kColourClusterFit (the default), kColourRangeFit (very fast, low \n\tquality) or kColourIterativeClusterFit (slowest, best quality).\n\t\t\n\tWhen using kColourClusterFit or kColourIterativeClusterFit, an additional \n\tflag can be specified to weight the importance of each pixel by its alpha \n\tvalue. For images that are rendered using alpha blending, this can \n\tsignificantly increase the perceived quality.\n\t\n\tThe metric parameter can be used to weight the relative importance of each\n\tcolour channel, or pass NULL to use the default uniform weight of \n\t{ 1.0f, 1.0f, 1.0f }. This replaces the previous flag-based control that \n\tallowed either uniform or \"perceptual\" weights with the fixed values\n\t{ 0.2126f, 0.7152f, 0.0722f }. If non-NULL, the metric should point to a \n\tcontiguous array of 3 floats.\n*/\nvoid CompressMasked( u8 const* rgba, int mask, void* block, int flags, float* metric = 0 );\n\n// -----------------------------------------------------------------------------\n\n/*! @brief Compresses a 4x4 block of pixels.\n\n\t@param rgba\t\tThe rgba values of the 16 source pixels.\n\t@param block\tStorage for the compressed DXT block.\n\t@param flags\tCompression flags.\n\t@param metric\tAn optional perceptual metric.\n\t\n\tThe source pixels should be presented as a contiguous array of 16 rgba\n\tvalues, with each component as 1 byte each. In memory this should be:\n\t\n\t\t{ r1, g1, b1, a1, .... , r16, g16, b16, a16 }\n\t\n\tThe flags parameter should specify either kDxt1, kDxt3 or kDxt5 compression, \n\thowever, DXT1 will be used by default if none is specified. When using DXT1 \n\tcompression, 8 bytes of storage are required for the compressed DXT block. \n\tDXT3 and DXT5 compression require 16 bytes of storage per block.\n\t\n\tThe flags parameter can also specify a preferred colour compressor to use \n\twhen fitting the RGB components of the data. Possible colour compressors \n\tare: kColourClusterFit (the default), kColourRangeFit (very fast, low \n\tquality) or kColourIterativeClusterFit (slowest, best quality).\n\t\t\n\tWhen using kColourClusterFit or kColourIterativeClusterFit, an additional \n\tflag can be specified to weight the importance of each pixel by its alpha \n\tvalue. For images that are rendered using alpha blending, this can \n\tsignificantly increase the perceived quality.\n\t\n\tThe metric parameter can be used to weight the relative importance of each\n\tcolour channel, or pass NULL to use the default uniform weight of \n\t{ 1.0f, 1.0f, 1.0f }. This replaces the previous flag-based control that \n\tallowed either uniform or \"perceptual\" weights with the fixed values\n\t{ 0.2126f, 0.7152f, 0.0722f }. If non-NULL, the metric should point to a \n\tcontiguous array of 3 floats.\n\t\n\tThis method is an inline that calls CompressMasked with a mask of 0xffff, \n\tprovided for compatibility with older versions of squish.\n*/\ninline void Compress( u8 const* rgba, void* block, int flags, float* metric = 0 )\n{\n\tCompressMasked( rgba, 0xffff, block, flags, metric );\n}\n\n// -----------------------------------------------------------------------------\n\n/*! @brief Decompresses a 4x4 block of pixels.\n\n\t@param rgba\t\tStorage for the 16 decompressed pixels.\n\t@param block\tThe compressed DXT block.\n\t@param flags\tCompression flags.\n\n\tThe decompressed pixels will be written as a contiguous array of 16 rgba\n\tvalues, with each component as 1 byte each. In memory this is:\n\t\n\t\t{ r1, g1, b1, a1, .... , r16, g16, b16, a16 }\n\t\n\tThe flags parameter should specify either kDxt1, kDxt3 or kDxt5 compression, \n\thowever, DXT1 will be used by default if none is specified. All other flags \n\tare ignored.\n*/\nvoid Decompress( u8* rgba, void const* block, int flags );\n\n// -----------------------------------------------------------------------------\n\n/*! @brief Computes the amount of compressed storage required.\n\n\t@param width\tThe width of the image.\n\t@param height\tThe height of the image.\n\t@param flags\tCompression flags.\n\t\n\tThe flags parameter should specify either kDxt1, kDxt3 or kDxt5 compression, \n\thowever, DXT1 will be used by default if none is specified. All other flags \n\tare ignored.\n\t\n\tMost DXT images will be a multiple of 4 in each dimension, but this \n\tfunction supports arbitrary size images by allowing the outer blocks to\n\tbe only partially used.\n*/\nint GetStorageRequirements( int width, int height, int flags );\n\n// -----------------------------------------------------------------------------\n\n/*! @brief Compresses an image in memory.\n\n\t@param rgba\t\tThe pixels of the source.\n\t@param width\tThe width of the source image.\n\t@param height\tThe height of the source image.\n\t@param blocks\tStorage for the compressed output.\n\t@param flags\tCompression flags.\n\t@param metric\tAn optional perceptual metric.\n\t\n\tThe source pixels should be presented as a contiguous array of width*height\n\trgba values, with each component as 1 byte each. In memory this should be:\n\t\n\t\t{ r1, g1, b1, a1, .... , rn, gn, bn, an } for n = width*height\n\t\t\n\tThe flags parameter should specify either kDxt1, kDxt3 or kDxt5 compression, \n\thowever, DXT1 will be used by default if none is specified. When using DXT1 \n\tcompression, 8 bytes of storage are required for each compressed DXT block. \n\tDXT3 and DXT5 compression require 16 bytes of storage per block.\n\t\n\tThe flags parameter can also specify a preferred colour compressor to use \n\twhen fitting the RGB components of the data. Possible colour compressors \n\tare: kColourClusterFit (the default), kColourRangeFit (very fast, low \n\tquality) or kColourIterativeClusterFit (slowest, best quality).\n\t\t\n\tWhen using kColourClusterFit or kColourIterativeClusterFit, an additional \n\tflag can be specified to weight the importance of each pixel by its alpha \n\tvalue. For images that are rendered using alpha blending, this can \n\tsignificantly increase the perceived quality.\n\t\n\tThe metric parameter can be used to weight the relative importance of each\n\tcolour channel, or pass NULL to use the default uniform weight of \n\t{ 1.0f, 1.0f, 1.0f }. This replaces the previous flag-based control that \n\tallowed either uniform or \"perceptual\" weights with the fixed values\n\t{ 0.2126f, 0.7152f, 0.0722f }. If non-NULL, the metric should point to a \n\tcontiguous array of 3 floats.\n\t\n\tInternally this function calls squish::CompressMasked for each block, which \n\tallows for pixels outside the image to take arbitrary values. The function \n\tsquish::GetStorageRequirements can be called to compute the amount of memory\n\tto allocate for the compressed output.\n*/\nvoid CompressImage( u8 const* rgba, int width, int height, void* blocks, int flags, float* metric = 0 );\n\n// -----------------------------------------------------------------------------\n\n/*! @brief Decompresses an image in memory.\n\n\t@param rgba\t\tStorage for the decompressed pixels.\n\t@param width\tThe width of the source image.\n\t@param height\tThe height of the source image.\n\t@param blocks\tThe compressed DXT blocks.\n\t@param flags\tCompression flags.\n\t\n\tThe decompressed pixels will be written as a contiguous array of width*height\n\t16 rgba values, with each component as 1 byte each. In memory this is:\n\t\n\t\t{ r1, g1, b1, a1, .... , rn, gn, bn, an } for n = width*height\n\t\t\n\tThe flags parameter should specify either kDxt1, kDxt3 or kDxt5 compression, \n\thowever, DXT1 will be used by default if none is specified. All other flags \n\tare ignored.\n\n\tInternally this function calls squish::Decompress for each block.\n*/\nvoid DecompressImage( u8* rgba, int width, int height, void const* blocks, int flags );\n\n// -----------------------------------------------------------------------------\n\n} // namespace squish\n\n#endif // ndef SQUISH_H\n\n"
  },
  {
    "path": "vendor/vma/vk_mem_alloc.h",
    "content": "//\n// Copyright (c) 2017-2022 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/** \\mainpage Vulkan Memory Allocator\n\n<b>Version 3.1.0-development</b>\n\nCopyright (c) 2017-2022 Advanced Micro Devices, Inc. All rights reserved. \\n\nLicense: MIT\n\n<b>API documentation divided into groups:</b> [Modules](modules.html)\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  - \\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 resource_aliasing\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  - \\subpage defragmentation\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 virtual_allocator\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 opengl_interop\n- \\subpage usage_patterns\n    - [GPU-only resource](@ref usage_patterns_gpu_only)\n    - [Staging copy for upload](@ref usage_patterns_staging_copy_upload)\n    - [Readback](@ref usage_patterns_readback)\n    - [Advanced data uploading](@ref usage_patterns_advanced_data_uploading)\n    - [Other use cases](@ref usage_patterns_other_use_cases)\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- <b>Extension support</b>\n    - \\subpage vk_khr_dedicated_allocation\n    - \\subpage enabling_buffer_device_address\n    - \\subpage vk_ext_memory_priority\n    - \\subpage vk_amd_device_coherent_memory\n- \\subpage general_considerations\n  - [Thread safety](@ref general_considerations_thread_safety)\n  - [Versioning and compatibility](@ref general_considerations_versioning_and_compatibility)\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\\defgroup group_init Library initialization\n\n\\brief API elements related to the initialization and management of the entire library, especially #VmaAllocator object.\n\n\\defgroup group_alloc Memory allocation\n\n\\brief API elements related to the allocation, deallocation, and management of Vulkan memory, buffers, images.\nMost basic ones being: vmaCreateBuffer(), vmaCreateImage().\n\n\\defgroup group_virtual Virtual allocator\n\n\\brief API elements related to the mechanism of \\ref virtual_allocator - using the core allocation algorithm\nfor user-defined purpose without allocating any real GPU memory.\n\n\\defgroup group_stats Statistics\n\n\\brief API elements that query current status of the allocator, from memory usage, budget, to full dump of the internal state in JSON format.\nSee documentation chapter: \\ref statistics.\n*/\n\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n#ifndef VULKAN_H_\n#include <vulkan/vulkan.h>\n#endif\n\n#if !defined(VMA_VULKAN_VERSION)\n#if defined(VK_VERSION_1_3)\n#define VMA_VULKAN_VERSION 1003000\n#elif defined(VK_VERSION_1_2)\n#define VMA_VULKAN_VERSION 1002000\n#elif 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(__ANDROID__) && defined(VK_NO_PROTOTYPES) && VMA_STATIC_VULKAN_FUNCTIONS\nextern PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr;\nextern PFN_vkGetDeviceProcAddr vkGetDeviceProcAddr;\nextern PFN_vkGetPhysicalDeviceProperties vkGetPhysicalDeviceProperties;\nextern PFN_vkGetPhysicalDeviceMemoryProperties vkGetPhysicalDeviceMemoryProperties;\nextern PFN_vkAllocateMemory vkAllocateMemory;\nextern PFN_vkFreeMemory vkFreeMemory;\nextern PFN_vkMapMemory vkMapMemory;\nextern PFN_vkUnmapMemory vkUnmapMemory;\nextern PFN_vkFlushMappedMemoryRanges vkFlushMappedMemoryRanges;\nextern PFN_vkInvalidateMappedMemoryRanges vkInvalidateMappedMemoryRanges;\nextern PFN_vkBindBufferMemory vkBindBufferMemory;\nextern PFN_vkBindImageMemory vkBindImageMemory;\nextern PFN_vkGetBufferMemoryRequirements vkGetBufferMemoryRequirements;\nextern PFN_vkGetImageMemoryRequirements vkGetImageMemoryRequirements;\nextern PFN_vkCreateBuffer vkCreateBuffer;\nextern PFN_vkDestroyBuffer vkDestroyBuffer;\nextern PFN_vkCreateImage vkCreateImage;\nextern PFN_vkDestroyImage vkDestroyImage;\nextern PFN_vkCmdCopyBuffer vkCmdCopyBuffer;\n#if VMA_VULKAN_VERSION >= 1001000\nextern PFN_vkGetBufferMemoryRequirements2 vkGetBufferMemoryRequirements2;\nextern PFN_vkGetImageMemoryRequirements2 vkGetImageMemoryRequirements2;\nextern PFN_vkBindBufferMemory2 vkBindBufferMemory2;\nextern PFN_vkBindImageMemory2 vkBindImageMemory2;\nextern PFN_vkGetPhysicalDeviceMemoryProperties2 vkGetPhysicalDeviceMemoryProperties2;\n#endif // #if VMA_VULKAN_VERSION >= 1001000\n#endif // #if defined(__ANDROID__) && VMA_STATIC_VULKAN_FUNCTIONS && VK_NO_PROTOTYPES\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// Defined to 1 when VK_KHR_buffer_device_address device extension or equivalent core Vulkan 1.2 feature is defined in its headers.\n#if !defined(VMA_BUFFER_DEVICE_ADDRESS)\n#if VK_KHR_buffer_device_address || VMA_VULKAN_VERSION >= 1002000\n#define VMA_BUFFER_DEVICE_ADDRESS 1\n#else\n#define VMA_BUFFER_DEVICE_ADDRESS 0\n#endif\n#endif\n\n// Defined to 1 when VK_EXT_memory_priority device extension is defined in Vulkan headers.\n#if !defined(VMA_MEMORY_PRIORITY)\n#if VK_EXT_memory_priority\n#define VMA_MEMORY_PRIORITY 1\n#else\n#define VMA_MEMORY_PRIORITY 0\n#endif\n#endif\n\n// Defined to 1 when VK_KHR_external_memory device extension is defined in Vulkan headers.\n#if !defined(VMA_EXTERNAL_MEMORY)\n#if VK_KHR_external_memory\n#define VMA_EXTERNAL_MEMORY 1\n#else\n#define VMA_EXTERNAL_MEMORY 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// exporting 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// Define this macro to decorate pointers with an attribute specifying the\n// length of the array they point to if they are not null.\n//\n// The length may be one of\n// - The name of another parameter in the argument list where the pointer is declared\n// - The name of another member in the struct where the pointer is declared\n// - The name of a member of a struct type, meaning the value of that member in\n//   the context of the call. For example\n//   VMA_LEN_IF_NOT_NULL(\"VkPhysicalDeviceMemoryProperties::memoryHeapCount\"),\n//   this means the number of memory heaps available in the device associated\n//   with the VmaAllocator being dealt with.\n#ifndef VMA_LEN_IF_NOT_NULL\n#define VMA_LEN_IF_NOT_NULL(len)\n#endif\n\n// The VMA_NULLABLE macro is defined to be _Nullable when compiling with Clang.\n// see: https://clang.llvm.org/docs/AttributeReference.html#nullable\n#ifndef VMA_NULLABLE\n#ifdef __clang__\n#define VMA_NULLABLE _Nullable\n#else\n#define VMA_NULLABLE\n#endif\n#endif\n\n// The VMA_NOT_NULL macro is defined to be _Nonnull when compiling with Clang.\n// see: https://clang.llvm.org/docs/AttributeReference.html#nonnull\n#ifndef VMA_NOT_NULL\n#ifdef __clang__\n#define VMA_NOT_NULL _Nonnull\n#else\n#define VMA_NOT_NULL\n#endif\n#endif\n\n// If non-dispatchable handles are represented as pointers then we can give\n// then nullability annotations\n#ifndef VMA_NOT_NULL_NON_DISPATCHABLE\n#if defined(__LP64__) || defined(_WIN64) || (defined(__x86_64__) && !defined(__ILP32__) ) || defined(_M_X64) || defined(__ia64) || defined (_M_IA64) || defined(__aarch64__) || defined(__powerpc64__)\n#define VMA_NOT_NULL_NON_DISPATCHABLE VMA_NOT_NULL\n#else\n#define VMA_NOT_NULL_NON_DISPATCHABLE\n#endif\n#endif\n\n#ifndef VMA_NULLABLE_NON_DISPATCHABLE\n#if defined(__LP64__) || defined(_WIN64) || (defined(__x86_64__) && !defined(__ILP32__) ) || defined(_M_X64) || defined(__ia64) || defined (_M_IA64) || defined(__aarch64__) || defined(__powerpc64__)\n#define VMA_NULLABLE_NON_DISPATCHABLE VMA_NULLABLE\n#else\n#define VMA_NULLABLE_NON_DISPATCHABLE\n#endif\n#endif\n\n#ifndef VMA_STATS_STRING_ENABLED\n#define VMA_STATS_STRING_ENABLED 1\n#endif\n\n////////////////////////////////////////////////////////////////////////////////\n////////////////////////////////////////////////////////////////////////////////\n//\n//    INTERFACE\n//\n////////////////////////////////////////////////////////////////////////////////\n////////////////////////////////////////////////////////////////////////////////\n\n// Sections for managing code placement in file, only for development purposes e.g. for convenient folding inside an IDE.\n#ifndef _VMA_ENUM_DECLARATIONS\n\n/**\n\\addtogroup group_init\n@{\n*/\n\n/// Flags for created #VmaAllocator.\ntypedef 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 is `VK_API_VERSION_1_1`, the flag is ignored because the extension has been promoted to Vulkan 1.1.\n\n  Using this extension 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 is `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  Enables usage of VK_AMD_device_coherent_memory extension.\n\n  You may set this flag only if you:\n\n  - found out that this device extension is supported and enabled it while creating Vulkan device passed as VmaAllocatorCreateInfo::device,\n  - checked that `VkPhysicalDeviceCoherentMemoryFeaturesAMD::deviceCoherentMemory` is true and set it while creating the Vulkan device,\n  - want it to be used internally by this library.\n\n  The extension and accompanying device feature provide access to memory types with\n  `VK_MEMORY_PROPERTY_DEVICE_COHERENT_BIT_AMD` and `VK_MEMORY_PROPERTY_DEVICE_UNCACHED_BIT_AMD` flags.\n  They are useful mostly for writing breadcrumb markers - a common method for debugging GPU crash/hang/TDR.\n\n  When the extension is not enabled, such memory types are still enumerated, but their usage is illegal.\n  To protect from this error, if you don't create the allocator with this flag, it will refuse to allocate any memory or create a custom pool in such memory type,\n  returning `VK_ERROR_FEATURE_NOT_PRESENT`.\n  */\n  VMA_ALLOCATOR_CREATE_AMD_DEVICE_COHERENT_MEMORY_BIT = 0x00000010,\n  /**\n  Enables usage of \"buffer device address\" feature, which allows you to use function\n  `vkGetBufferDeviceAddress*` to get raw GPU pointer to a buffer and pass it for usage inside a shader.\n\n  You may set this flag only if you:\n\n  1. (For Vulkan version < 1.2) Found as available and enabled device extension\n  VK_KHR_buffer_device_address.\n  This extension is promoted to core Vulkan 1.2.\n  2. Found as available and enabled device feature `VkPhysicalDeviceBufferDeviceAddressFeatures::bufferDeviceAddress`.\n\n  When this flag is set, you can create buffers with `VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT` using VMA.\n  The library automatically adds `VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT` to\n  allocated memory blocks wherever it might be needed.\n\n  For more information, see documentation chapter \\ref enabling_buffer_device_address.\n  */\n  VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT = 0x00000020,\n  /**\n  Enables usage of VK_EXT_memory_priority extension in the library.\n\n  You may set this flag only if you found available and enabled this device extension,\n  along with `VkPhysicalDeviceMemoryPriorityFeaturesEXT::memoryPriority == VK_TRUE`,\n  while creating Vulkan device passed as VmaAllocatorCreateInfo::device.\n\n  When this flag is used, VmaAllocationCreateInfo::priority and VmaPoolCreateInfo::priority\n  are used to set priorities of allocated Vulkan memory. Without it, these variables are ignored.\n\n  A priority must be a floating-point value between 0 and 1, indicating the priority of the allocation relative to other memory allocations.\n  Larger values are higher priority. The granularity of the priorities is implementation-dependent.\n  It is automatically passed to every call to `vkAllocateMemory` done by the library using structure `VkMemoryPriorityAllocateInfoEXT`.\n  The value to be used for default priority is 0.5.\n  For more details, see the documentation of the VK_EXT_memory_priority extension.\n  */\n  VMA_ALLOCATOR_CREATE_EXT_MEMORY_PRIORITY_BIT = 0x00000040,\n\n  VMA_ALLOCATOR_CREATE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF\n} VmaAllocatorCreateFlagBits;\n/// See #VmaAllocatorCreateFlagBits.\ntypedef VkFlags VmaAllocatorCreateFlags;\n\n/** @} */\n\n/**\n\\addtogroup group_alloc\n@{\n*/\n\n/// \\brief Intended usage of the allocated memory.\ntypedef 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  /**\n  \\deprecated Obsolete, preserved for backward compatibility.\n  Prefers `VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT`.\n  */\n  VMA_MEMORY_USAGE_GPU_ONLY = 1,\n  /**\n  \\deprecated Obsolete, preserved for backward compatibility.\n  Guarantees `VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT` and `VK_MEMORY_PROPERTY_HOST_COHERENT_BIT`.\n  */\n  VMA_MEMORY_USAGE_CPU_ONLY = 2,\n  /**\n  \\deprecated Obsolete, preserved for backward compatibility.\n  Guarantees `VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT`, prefers `VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT`.\n  */\n  VMA_MEMORY_USAGE_CPU_TO_GPU = 3,\n  /**\n  \\deprecated Obsolete, preserved for backward compatibility.\n  Guarantees `VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT`, prefers `VK_MEMORY_PROPERTY_HOST_CACHED_BIT`.\n  */\n  VMA_MEMORY_USAGE_GPU_TO_CPU = 4,\n  /**\n  \\deprecated Obsolete, preserved for backward compatibility.\n  Prefers not `VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT`.\n  */\n  VMA_MEMORY_USAGE_CPU_COPY = 5,\n  /**\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  Selects best memory type automatically.\n  This flag is recommended for most common use cases.\n\n  When using this flag, if you want to map the allocation (using vmaMapMemory() or #VMA_ALLOCATION_CREATE_MAPPED_BIT),\n  you must pass one of the flags: #VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT or #VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT\n  in VmaAllocationCreateInfo::flags.\n\n  It can be used only with functions that let the library know `VkBufferCreateInfo` or `VkImageCreateInfo`, e.g.\n  vmaCreateBuffer(), vmaCreateImage(), vmaFindMemoryTypeIndexForBufferInfo(), vmaFindMemoryTypeIndexForImageInfo()\n  and not with generic memory allocation functions.\n  */\n  VMA_MEMORY_USAGE_AUTO = 7,\n  /**\n  Selects best memory type automatically with preference for GPU (device) memory.\n\n  When using this flag, if you want to map the allocation (using vmaMapMemory() or #VMA_ALLOCATION_CREATE_MAPPED_BIT),\n  you must pass one of the flags: #VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT or #VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT\n  in VmaAllocationCreateInfo::flags.\n\n  It can be used only with functions that let the library know `VkBufferCreateInfo` or `VkImageCreateInfo`, e.g.\n  vmaCreateBuffer(), vmaCreateImage(), vmaFindMemoryTypeIndexForBufferInfo(), vmaFindMemoryTypeIndexForImageInfo()\n  and not with generic memory allocation functions.\n  */\n  VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE = 8,\n  /**\n  Selects best memory type automatically with preference for CPU (host) memory.\n\n  When using this flag, if you want to map the allocation (using vmaMapMemory() or #VMA_ALLOCATION_CREATE_MAPPED_BIT),\n  you must pass one of the flags: #VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT or #VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT\n  in VmaAllocationCreateInfo::flags.\n\n  It can be used only with functions that let the library know `VkBufferCreateInfo` or `VkImageCreateInfo`, e.g.\n  vmaCreateBuffer(), vmaCreateImage(), vmaFindMemoryTypeIndexForBufferInfo(), vmaFindMemoryTypeIndexForImageInfo()\n  and not with generic memory allocation functions.\n  */\n  VMA_MEMORY_USAGE_AUTO_PREFER_HOST = 9,\n\n  VMA_MEMORY_USAGE_MAX_ENUM = 0x7FFFFFFF\n} VmaMemoryUsage;\n\n/// Flags to be passed as VmaAllocationCreateInfo::flags.\ntypedef 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  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  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  It is 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  VMA_ALLOCATION_CREATE_MAPPED_BIT = 0x00000004,\n  /** \\deprecated Preserved for backward compatibility. Consider using vmaSetAllocationName() instead.\n\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 `pName`. 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  If you want to make sure the new buffer/image is not tied to the new memory allocation\n  through `VkMemoryDedicatedAllocateInfoKHR` structure in case the allocation ends up in its own memory block,\n  use also flag #VMA_ALLOCATION_CREATE_CAN_ALIAS_BIT.\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  /** \\brief Set this flag if the allocated memory will have aliasing resources.\n\n  Usage of this flag prevents supplying `VkMemoryDedicatedAllocateInfoKHR` when #VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT is specified.\n  Otherwise created dedicated memory will not be suitable for aliasing resources, resulting in Vulkan Validation Layer errors.\n  */\n  VMA_ALLOCATION_CREATE_CAN_ALIAS_BIT = 0x00000200,\n  /**\n  Requests possibility to map the allocation (using vmaMapMemory() or #VMA_ALLOCATION_CREATE_MAPPED_BIT).\n\n  - If you use #VMA_MEMORY_USAGE_AUTO or other `VMA_MEMORY_USAGE_AUTO*` value,\n    you must use this flag to be able to map the allocation. Otherwise, mapping is incorrect.\n  - If you use other value of #VmaMemoryUsage, this flag is ignored and mapping is always possible in memory types that are `HOST_VISIBLE`.\n    This includes allocations created in \\ref custom_memory_pools.\n\n  Declares that mapped memory will only be written sequentially, e.g. using `memcpy()` or a loop writing number-by-number,\n  never read or accessed randomly, so a memory type can be selected that is uncached and write-combined.\n\n  \\warning Violating this declaration may work correctly, but will likely be very slow.\n  Watch out for implicit reads introduced by doing e.g. `pMappedData[i] += x;`\n  Better prepare your data in a local variable and `memcpy()` it to the mapped pointer all at once.\n  */\n  VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT = 0x00000400,\n  /**\n  Requests possibility to map the allocation (using vmaMapMemory() or #VMA_ALLOCATION_CREATE_MAPPED_BIT).\n\n  - If you use #VMA_MEMORY_USAGE_AUTO or other `VMA_MEMORY_USAGE_AUTO*` value,\n    you must use this flag to be able to map the allocation. Otherwise, mapping is incorrect.\n  - If you use other value of #VmaMemoryUsage, this flag is ignored and mapping is always possible in memory types that are `HOST_VISIBLE`.\n    This includes allocations created in \\ref custom_memory_pools.\n\n  Declares that mapped memory can be read, written, and accessed in random order,\n  so a `HOST_CACHED` memory type is required.\n  */\n  VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT = 0x00000800,\n  /**\n  Together with #VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT or #VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT,\n  it says that despite request for host access, a not-`HOST_VISIBLE` memory type can be selected\n  if it may improve performance.\n\n  By using this flag, you declare that you will check if the allocation ended up in a `HOST_VISIBLE` memory type\n  (e.g. using vmaGetAllocationMemoryProperties()) and if not, you will create some \"staging\" buffer and\n  issue an explicit transfer to write/read your data.\n  To prepare for this possibility, don't forget to add appropriate flags like\n  `VK_BUFFER_USAGE_TRANSFER_DST_BIT`, `VK_BUFFER_USAGE_TRANSFER_SRC_BIT` to the parameters of created buffer or image.\n  */\n  VMA_ALLOCATION_CREATE_HOST_ACCESS_ALLOW_TRANSFER_INSTEAD_BIT = 0x00001000,\n  /** Allocation strategy that chooses smallest possible free range for the allocation\n  to minimize memory usage and fragmentation, possibly at the expense of allocation time.\n  */\n  VMA_ALLOCATION_CREATE_STRATEGY_MIN_MEMORY_BIT = 0x00010000,\n  /** Allocation strategy that chooses first suitable free range for the allocation -\n  not necessarily in terms of the smallest offset but the one that is easiest and fastest to find\n  to minimize allocation time, possibly at the expense of allocation quality.\n  */\n  VMA_ALLOCATION_CREATE_STRATEGY_MIN_TIME_BIT = 0x00020000,\n  /** Allocation strategy that chooses always the lowest offset in available space.\n  This is not the most efficient strategy but achieves highly packed data.\n  Used internally by defragmentation, not recommended in typical usage.\n  */\n  VMA_ALLOCATION_CREATE_STRATEGY_MIN_OFFSET_BIT  = 0x00040000,\n  /** Alias to #VMA_ALLOCATION_CREATE_STRATEGY_MIN_MEMORY_BIT.\n   */\n  VMA_ALLOCATION_CREATE_STRATEGY_BEST_FIT_BIT = VMA_ALLOCATION_CREATE_STRATEGY_MIN_MEMORY_BIT,\n  /** Alias to #VMA_ALLOCATION_CREATE_STRATEGY_MIN_TIME_BIT.\n   */\n  VMA_ALLOCATION_CREATE_STRATEGY_FIRST_FIT_BIT = VMA_ALLOCATION_CREATE_STRATEGY_MIN_TIME_BIT,\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_MIN_MEMORY_BIT |\n      VMA_ALLOCATION_CREATE_STRATEGY_MIN_TIME_BIT |\n      VMA_ALLOCATION_CREATE_STRATEGY_MIN_OFFSET_BIT,\n\n  VMA_ALLOCATION_CREATE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF\n} VmaAllocationCreateFlagBits;\n/// See #VmaAllocationCreateFlagBits.\ntypedef VkFlags VmaAllocationCreateFlags;\n\n/// Flags to be passed as VmaPoolCreateInfo::flags.\ntypedef 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.\n  For details, see documentation chapter \\ref linear_algorithm.\n  */\n  VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT = 0x00000004,\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\n  VMA_POOL_CREATE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF\n} VmaPoolCreateFlagBits;\n/// Flags to be passed as VmaPoolCreateInfo::flags. See #VmaPoolCreateFlagBits.\ntypedef VkFlags VmaPoolCreateFlags;\n\n/// Flags to be passed as VmaDefragmentationInfo::flags.\ntypedef enum VmaDefragmentationFlagBits\n{\n  /* \\brief Use simple but fast algorithm for defragmentation.\n  May not achieve best results but will require least time to compute and least allocations to copy.\n  */\n  VMA_DEFRAGMENTATION_FLAG_ALGORITHM_FAST_BIT = 0x1,\n  /* \\brief Default defragmentation algorithm, applied also when no `ALGORITHM` flag is specified.\n  Offers a balance between defragmentation quality and the amount of allocations and bytes that need to be moved.\n  */\n  VMA_DEFRAGMENTATION_FLAG_ALGORITHM_BALANCED_BIT = 0x2,\n  /* \\brief Perform full defragmentation of memory.\n  Can result in notably more time to compute and allocations to copy, but will achieve best memory packing.\n  */\n  VMA_DEFRAGMENTATION_FLAG_ALGORITHM_FULL_BIT = 0x4,\n  /** \\brief Use the most roboust algorithm at the cost of time to compute and number of copies to make.\n  Only available when bufferImageGranularity is greater than 1, since it aims to reduce\n  alignment issues between different types of resources.\n  Otherwise falls back to same behavior as #VMA_DEFRAGMENTATION_FLAG_ALGORITHM_FULL_BIT.\n  */\n  VMA_DEFRAGMENTATION_FLAG_ALGORITHM_EXTENSIVE_BIT = 0x8,\n\n  /// A bit mask to extract only `ALGORITHM` bits from entire set of flags.\n  VMA_DEFRAGMENTATION_FLAG_ALGORITHM_MASK =\n      VMA_DEFRAGMENTATION_FLAG_ALGORITHM_FAST_BIT |\n      VMA_DEFRAGMENTATION_FLAG_ALGORITHM_BALANCED_BIT |\n      VMA_DEFRAGMENTATION_FLAG_ALGORITHM_FULL_BIT |\n      VMA_DEFRAGMENTATION_FLAG_ALGORITHM_EXTENSIVE_BIT,\n\n  VMA_DEFRAGMENTATION_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF\n} VmaDefragmentationFlagBits;\n/// See #VmaDefragmentationFlagBits.\ntypedef VkFlags VmaDefragmentationFlags;\n\n/// Operation performed on single defragmentation move. See structure #VmaDefragmentationMove.\ntypedef enum VmaDefragmentationMoveOperation\n{\n  /// Buffer/image has been recreated at `dstTmpAllocation`, data has been copied, old buffer/image has been destroyed. `srcAllocation` should be changed to point to the new place. This is the default value set by vmaBeginDefragmentationPass().\n  VMA_DEFRAGMENTATION_MOVE_OPERATION_COPY = 0,\n  /// Set this value if you cannot move the allocation. New place reserved at `dstTmpAllocation` will be freed. `srcAllocation` will remain unchanged.\n  VMA_DEFRAGMENTATION_MOVE_OPERATION_IGNORE = 1,\n  /// Set this value if you decide to abandon the allocation and you destroyed the buffer/image. New place reserved at `dstTmpAllocation` will be freed, along with `srcAllocation`, which will be destroyed.\n  VMA_DEFRAGMENTATION_MOVE_OPERATION_DESTROY = 2,\n} VmaDefragmentationMoveOperation;\n\n/** @} */\n\n/**\n\\addtogroup group_virtual\n@{\n*/\n\n/// Flags to be passed as VmaVirtualBlockCreateInfo::flags.\ntypedef enum VmaVirtualBlockCreateFlagBits\n{\n  /** \\brief Enables alternative, linear allocation algorithm in this virtual block.\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.\n  For details, see documentation chapter \\ref linear_algorithm.\n  */\n  VMA_VIRTUAL_BLOCK_CREATE_LINEAR_ALGORITHM_BIT = 0x00000001,\n\n  /** \\brief Bit mask to extract only `ALGORITHM` bits from entire set of flags.\n   */\n  VMA_VIRTUAL_BLOCK_CREATE_ALGORITHM_MASK =\n      VMA_VIRTUAL_BLOCK_CREATE_LINEAR_ALGORITHM_BIT,\n\n  VMA_VIRTUAL_BLOCK_CREATE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF\n} VmaVirtualBlockCreateFlagBits;\n/// Flags to be passed as VmaVirtualBlockCreateInfo::flags. See #VmaVirtualBlockCreateFlagBits.\ntypedef VkFlags VmaVirtualBlockCreateFlags;\n\n/// Flags to be passed as VmaVirtualAllocationCreateInfo::flags.\ntypedef enum VmaVirtualAllocationCreateFlagBits\n{\n  /** \\brief Allocation will be created from upper stack in a double stack pool.\n\n  This flag is only allowed for virtual blocks created with #VMA_VIRTUAL_BLOCK_CREATE_LINEAR_ALGORITHM_BIT flag.\n  */\n  VMA_VIRTUAL_ALLOCATION_CREATE_UPPER_ADDRESS_BIT = VMA_ALLOCATION_CREATE_UPPER_ADDRESS_BIT,\n  /** \\brief Allocation strategy that tries to minimize memory usage.\n   */\n  VMA_VIRTUAL_ALLOCATION_CREATE_STRATEGY_MIN_MEMORY_BIT = VMA_ALLOCATION_CREATE_STRATEGY_MIN_MEMORY_BIT,\n  /** \\brief Allocation strategy that tries to minimize allocation time.\n   */\n  VMA_VIRTUAL_ALLOCATION_CREATE_STRATEGY_MIN_TIME_BIT = VMA_ALLOCATION_CREATE_STRATEGY_MIN_TIME_BIT,\n  /** Allocation strategy that chooses always the lowest offset in available space.\n  This is not the most efficient strategy but achieves highly packed data.\n  */\n  VMA_VIRTUAL_ALLOCATION_CREATE_STRATEGY_MIN_OFFSET_BIT = VMA_ALLOCATION_CREATE_STRATEGY_MIN_OFFSET_BIT,\n  /** \\brief A bit mask to extract only `STRATEGY` bits from entire set of flags.\n\n  These strategy flags are binary compatible with equivalent flags in #VmaAllocationCreateFlagBits.\n  */\n  VMA_VIRTUAL_ALLOCATION_CREATE_STRATEGY_MASK = VMA_ALLOCATION_CREATE_STRATEGY_MASK,\n\n  VMA_VIRTUAL_ALLOCATION_CREATE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF\n} VmaVirtualAllocationCreateFlagBits;\n/// Flags to be passed as VmaVirtualAllocationCreateInfo::flags. See #VmaVirtualAllocationCreateFlagBits.\ntypedef VkFlags VmaVirtualAllocationCreateFlags;\n\n/** @} */\n\n#endif // _VMA_ENUM_DECLARATIONS\n\n#ifndef _VMA_DATA_TYPES_DECLARATIONS\n\n/**\n\\addtogroup group_init\n@{ */\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*/\nVK_DEFINE_HANDLE(VmaAllocator)\n\n/** @} */\n\n/**\n\\addtogroup group_alloc\n@{\n*/\n\n/** \\struct VmaPool\n\\brief Represents custom memory pool\n\nFill structure VmaPoolCreateInfo and call function vmaCreatePool() to create it.\nCall function vmaDestroyPool() to destroy it.\n\nFor more information see [Custom memory pools](@ref choosing_memory_type_custom_memory_pools).\n*/\nVK_DEFINE_HANDLE(VmaPool)\n\n/** \\struct VmaAllocation\n\\brief Represents single memory allocation.\n\nIt may be either dedicated block of `VkDeviceMemory` or a specific region of a bigger block of this type\nplus unique offset.\n\nThere are multiple ways to create such object.\nYou need to fill structure VmaAllocationCreateInfo.\nFor more information see [Choosing memory type](@ref choosing_memory_type).\n\nAlthough the library provides convenience functions that create Vulkan buffer or image,\nallocate memory for it and bind them together,\nbinding of the allocation to a buffer or an image is out of scope of the allocation itself.\nAllocation object can exist without buffer/image bound,\nbinding can be done manually by the user, and destruction of it can be done\nindependently of destruction of the allocation.\n\nThe object also remembers its size and some other information.\nTo retrieve this information, use function vmaGetAllocationInfo() and inspect\nreturned structure VmaAllocationInfo.\n*/\nVK_DEFINE_HANDLE(VmaAllocation)\n\n/** \\struct VmaDefragmentationContext\n\\brief An opaque object that represents started defragmentation process.\n\nFill structure #VmaDefragmentationInfo and call function vmaBeginDefragmentation() to create it.\nCall function vmaEndDefragmentation() to destroy it.\n*/\nVK_DEFINE_HANDLE(VmaDefragmentationContext)\n\n/** @} */\n\n/**\n\\addtogroup group_virtual\n@{\n*/\n\n/** \\struct VmaVirtualAllocation\n\\brief Represents single memory allocation done inside VmaVirtualBlock.\n\nUse it as a unique identifier to virtual allocation within the single block.\n\nUse value `VK_NULL_HANDLE` to represent a null/invalid allocation.\n*/\nVK_DEFINE_NON_DISPATCHABLE_HANDLE(VmaVirtualAllocation)\n\n/** @} */\n\n/**\n\\addtogroup group_virtual\n@{\n*/\n\n/** \\struct VmaVirtualBlock\n\\brief Handle to a virtual block object that allows to use core allocation algorithm without allocating any real GPU memory.\n\nFill in #VmaVirtualBlockCreateInfo structure and use vmaCreateVirtualBlock() to create it. Use vmaDestroyVirtualBlock() to destroy it.\nFor more information, see documentation chapter \\ref virtual_allocator.\n\nThis object is not thread-safe - should not be used from multiple threads simultaneously, must be synchronized externally.\n*/\nVK_DEFINE_HANDLE(VmaVirtualBlock)\n\n/** @} */\n\n/**\n\\addtogroup group_init\n@{\n*/\n\n/// Callback function called after successful vkAllocateMemory.\ntypedef void (VKAPI_PTR* PFN_vmaAllocateDeviceMemoryFunction)(\n    VmaAllocator VMA_NOT_NULL                    allocator,\n    uint32_t                                     memoryType,\n    VkDeviceMemory VMA_NOT_NULL_NON_DISPATCHABLE memory,\n    VkDeviceSize                                 size,\n    void* VMA_NULLABLE                           pUserData);\n\n/// Callback function called before vkFreeMemory.\ntypedef void (VKAPI_PTR* PFN_vmaFreeDeviceMemoryFunction)(\n    VmaAllocator VMA_NOT_NULL                    allocator,\n    uint32_t                                     memoryType,\n    VkDeviceMemory VMA_NOT_NULL_NON_DISPATCHABLE memory,\n    VkDeviceSize                                 size,\n    void* VMA_NULLABLE                           pUserData);\n\n/** \\brief Set of callbacks that the library will call for `vkAllocateMemory` and `vkFreeMemory`.\n\nProvided for informative purpose, e.g. to gather statistics about number of\nallocations or total amount of memory allocated in Vulkan.\n\nUsed in VmaAllocatorCreateInfo::pDeviceMemoryCallbacks.\n*/\ntypedef struct VmaDeviceMemoryCallbacks\n{\n  /// Optional, can be null.\n  PFN_vmaAllocateDeviceMemoryFunction VMA_NULLABLE pfnAllocate;\n  /// Optional, can be null.\n  PFN_vmaFreeDeviceMemoryFunction VMA_NULLABLE pfnFree;\n  /// Optional, can be null.\n  void* VMA_NULLABLE pUserData;\n} VmaDeviceMemoryCallbacks;\n\n/** \\brief Pointers to some Vulkan functions - a subset used by the library.\n\nUsed in VmaAllocatorCreateInfo::pVulkanFunctions.\n*/\ntypedef struct VmaVulkanFunctions\n{\n  /// Required when using VMA_DYNAMIC_VULKAN_FUNCTIONS.\n  PFN_vkGetInstanceProcAddr VMA_NULLABLE vkGetInstanceProcAddr;\n  /// Required when using VMA_DYNAMIC_VULKAN_FUNCTIONS.\n  PFN_vkGetDeviceProcAddr VMA_NULLABLE vkGetDeviceProcAddr;\n  PFN_vkGetPhysicalDeviceProperties VMA_NULLABLE vkGetPhysicalDeviceProperties;\n  PFN_vkGetPhysicalDeviceMemoryProperties VMA_NULLABLE vkGetPhysicalDeviceMemoryProperties;\n  PFN_vkAllocateMemory VMA_NULLABLE vkAllocateMemory;\n  PFN_vkFreeMemory VMA_NULLABLE vkFreeMemory;\n  PFN_vkMapMemory VMA_NULLABLE vkMapMemory;\n  PFN_vkUnmapMemory VMA_NULLABLE vkUnmapMemory;\n  PFN_vkFlushMappedMemoryRanges VMA_NULLABLE vkFlushMappedMemoryRanges;\n  PFN_vkInvalidateMappedMemoryRanges VMA_NULLABLE vkInvalidateMappedMemoryRanges;\n  PFN_vkBindBufferMemory VMA_NULLABLE vkBindBufferMemory;\n  PFN_vkBindImageMemory VMA_NULLABLE vkBindImageMemory;\n  PFN_vkGetBufferMemoryRequirements VMA_NULLABLE vkGetBufferMemoryRequirements;\n  PFN_vkGetImageMemoryRequirements VMA_NULLABLE vkGetImageMemoryRequirements;\n  PFN_vkCreateBuffer VMA_NULLABLE vkCreateBuffer;\n  PFN_vkDestroyBuffer VMA_NULLABLE vkDestroyBuffer;\n  PFN_vkCreateImage VMA_NULLABLE vkCreateImage;\n  PFN_vkDestroyImage VMA_NULLABLE vkDestroyImage;\n  PFN_vkCmdCopyBuffer VMA_NULLABLE vkCmdCopyBuffer;\n#if VMA_DEDICATED_ALLOCATION || VMA_VULKAN_VERSION >= 1001000\n  /// Fetch \"vkGetBufferMemoryRequirements2\" on Vulkan >= 1.1, fetch \"vkGetBufferMemoryRequirements2KHR\" when using VK_KHR_dedicated_allocation extension.\n  PFN_vkGetBufferMemoryRequirements2KHR VMA_NULLABLE vkGetBufferMemoryRequirements2KHR;\n  /// Fetch \"vkGetImageMemoryRequirements2\" on Vulkan >= 1.1, fetch \"vkGetImageMemoryRequirements2KHR\" when using VK_KHR_dedicated_allocation extension.\n  PFN_vkGetImageMemoryRequirements2KHR VMA_NULLABLE vkGetImageMemoryRequirements2KHR;\n#endif\n#if VMA_BIND_MEMORY2 || VMA_VULKAN_VERSION >= 1001000\n  /// Fetch \"vkBindBufferMemory2\" on Vulkan >= 1.1, fetch \"vkBindBufferMemory2KHR\" when using VK_KHR_bind_memory2 extension.\n  PFN_vkBindBufferMemory2KHR VMA_NULLABLE vkBindBufferMemory2KHR;\n  /// Fetch \"vkBindImageMemory2\" on Vulkan >= 1.1, fetch \"vkBindImageMemory2KHR\" when using VK_KHR_bind_memory2 extension.\n  PFN_vkBindImageMemory2KHR VMA_NULLABLE vkBindImageMemory2KHR;\n#endif\n#if VMA_MEMORY_BUDGET || VMA_VULKAN_VERSION >= 1001000\n  PFN_vkGetPhysicalDeviceMemoryProperties2KHR VMA_NULLABLE vkGetPhysicalDeviceMemoryProperties2KHR;\n#endif\n#if VMA_VULKAN_VERSION >= 1003000\n  /// Fetch from \"vkGetDeviceBufferMemoryRequirements\" on Vulkan >= 1.3, but you can also fetch it from \"vkGetDeviceBufferMemoryRequirementsKHR\" if you enabled extension VK_KHR_maintenance4.\n  PFN_vkGetDeviceBufferMemoryRequirements VMA_NULLABLE vkGetDeviceBufferMemoryRequirements;\n  /// Fetch from \"vkGetDeviceImageMemoryRequirements\" on Vulkan >= 1.3, but you can also fetch it from \"vkGetDeviceImageMemoryRequirementsKHR\" if you enabled extension VK_KHR_maintenance4.\n  PFN_vkGetDeviceImageMemoryRequirements VMA_NULLABLE vkGetDeviceImageMemoryRequirements;\n#endif\n} VmaVulkanFunctions;\n\n/// Description of a Allocator to be created.\ntypedef 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 VMA_NOT_NULL physicalDevice;\n  /// Vulkan device.\n  /** It must be valid throughout whole lifetime of created allocator. */\n  VkDevice VMA_NOT_NULL 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* VMA_NULLABLE pAllocationCallbacks;\n  /// Informative callbacks for `vkAllocateMemory`, `vkFreeMemory`. Optional.\n  /** Optional, can be null. */\n  const VmaDeviceMemoryCallbacks* VMA_NULLABLE pDeviceMemoryCallbacks;\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* VMA_NULLABLE VMA_LEN_IF_NOT_NULL(\"VkPhysicalDeviceMemoryProperties::memoryHeapCount\") pHeapSizeLimit;\n\n  /** \\brief Pointers to Vulkan functions. Can be null.\n\n  For details see [Pointers to Vulkan functions](@ref config_Vulkan_functions).\n  */\n  const VmaVulkanFunctions* VMA_NULLABLE pVulkanFunctions;\n  /** \\brief Handle to Vulkan instance object.\n\n  Starting from version 3.0.0 this member is no longer optional, it must be set!\n  */\n  VkInstance VMA_NOT_NULL 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 equal (preferably equal) to value as passed to `vkCreateInstance` as `VkApplicationInfo::apiVersion`.\n  Only versions 1.0, 1.1, 1.2, 1.3 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#if VMA_EXTERNAL_MEMORY\n  /** \\brief Either null or a pointer to an array of external memory handle types for each Vulkan memory type.\n\n  If not NULL, it must be a pointer to an array of `VkPhysicalDeviceMemoryProperties::memoryTypeCount`\n  elements, defining external memory handle types of particular Vulkan memory type,\n  to be passed using `VkExportMemoryAllocateInfoKHR`.\n\n  Any of the elements may be equal to 0, which means not to use `VkExportMemoryAllocateInfoKHR` on this memory type.\n  This is also the default in case of `pTypeExternalMemoryHandleTypes` = NULL.\n  */\n  const VkExternalMemoryHandleTypeFlagsKHR* VMA_NULLABLE VMA_LEN_IF_NOT_NULL(\"VkPhysicalDeviceMemoryProperties::memoryTypeCount\") pTypeExternalMemoryHandleTypes;\n#endif // #if VMA_EXTERNAL_MEMORY\n} VmaAllocatorCreateInfo;\n\n/// Information about existing #VmaAllocator object.\ntypedef struct VmaAllocatorInfo\n{\n  /** \\brief Handle to Vulkan instance object.\n\n  This is the same value as has been passed through VmaAllocatorCreateInfo::instance.\n  */\n  VkInstance VMA_NOT_NULL instance;\n  /** \\brief Handle to Vulkan physical device object.\n\n  This is the same value as has been passed through VmaAllocatorCreateInfo::physicalDevice.\n  */\n  VkPhysicalDevice VMA_NOT_NULL physicalDevice;\n  /** \\brief Handle to Vulkan device object.\n\n  This is the same value as has been passed through VmaAllocatorCreateInfo::device.\n  */\n  VkDevice VMA_NOT_NULL device;\n} VmaAllocatorInfo;\n\n/** @} */\n\n/**\n\\addtogroup group_stats\n@{\n*/\n\n/** \\brief Calculated statistics of memory usage e.g. in a specific memory type, heap, custom pool, or total.\n\nThese are fast to calculate.\nSee functions: vmaGetHeapBudgets(), vmaGetPoolStatistics().\n*/\ntypedef struct VmaStatistics\n{\n  /** \\brief Number of `VkDeviceMemory` objects - Vulkan memory blocks allocated.\n   */\n  uint32_t blockCount;\n  /** \\brief Number of #VmaAllocation objects allocated.\n\n  Dedicated allocations have their own blocks, so each one adds 1 to `allocationCount` as well as `blockCount`.\n  */\n  uint32_t allocationCount;\n  /** \\brief Number of bytes allocated in `VkDeviceMemory` blocks.\n\n  \\note To avoid confusion, please be aware that what Vulkan calls an \"allocation\" - a whole `VkDeviceMemory` object\n  (e.g. as in `VkPhysicalDeviceLimits::maxMemoryAllocationCount`) is called a \"block\" in VMA, while VMA calls\n  \"allocation\" a #VmaAllocation object that represents a memory region sub-allocated from such block, usually for a single buffer or image.\n  */\n  VkDeviceSize blockBytes;\n  /** \\brief Total number of bytes occupied by all #VmaAllocation objects.\n\n  Always less or equal than `blockBytes`.\n  Difference `(blockBytes - allocationBytes)` is the amount of memory allocated from Vulkan\n  but unused by any #VmaAllocation.\n  */\n  VkDeviceSize allocationBytes;\n} VmaStatistics;\n\n/** \\brief More detailed statistics than #VmaStatistics.\n\nThese are slower to calculate. Use for debugging purposes.\nSee functions: vmaCalculateStatistics(), vmaCalculatePoolStatistics().\n\nPrevious version of the statistics API provided averages, but they have been removed\nbecause they can be easily calculated as:\n\n\\code\nVkDeviceSize allocationSizeAvg = detailedStats.statistics.allocationBytes / detailedStats.statistics.allocationCount;\nVkDeviceSize unusedBytes = detailedStats.statistics.blockBytes - detailedStats.statistics.allocationBytes;\nVkDeviceSize unusedRangeSizeAvg = unusedBytes / detailedStats.unusedRangeCount;\n\\endcode\n*/\ntypedef struct VmaDetailedStatistics\n{\n  /// Basic statistics.\n  VmaStatistics statistics;\n  /// Number of free ranges of memory between allocations.\n  uint32_t unusedRangeCount;\n  /// Smallest allocation size. `VK_WHOLE_SIZE` if there are 0 allocations.\n  VkDeviceSize allocationSizeMin;\n  /// Largest allocation size. 0 if there are 0 allocations.\n  VkDeviceSize allocationSizeMax;\n  /// Smallest empty range size. `VK_WHOLE_SIZE` if there are 0 empty ranges.\n  VkDeviceSize unusedRangeSizeMin;\n  /// Largest empty range size. 0 if there are 0 empty ranges.\n  VkDeviceSize unusedRangeSizeMax;\n} VmaDetailedStatistics;\n\n/** \\brief  General statistics from current state of the Allocator -\ntotal memory usage across all memory heaps and types.\n\nThese are slower to calculate. Use for debugging purposes.\nSee function vmaCalculateStatistics().\n*/\ntypedef struct VmaTotalStatistics\n{\n  VmaDetailedStatistics memoryType[VK_MAX_MEMORY_TYPES];\n  VmaDetailedStatistics memoryHeap[VK_MAX_MEMORY_HEAPS];\n  VmaDetailedStatistics total;\n} VmaTotalStatistics;\n\n/** \\brief Statistics of current memory usage and available budget for a specific memory heap.\n\nThese are fast to calculate.\nSee function vmaGetHeapBudgets().\n*/\ntypedef struct VmaBudget\n{\n  /** \\brief Statistics fetched from the library.\n   */\n  VmaStatistics statistics;\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 `statistics.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  /** \\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, decided by the operating system.\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/** @} */\n\n/**\n\\addtogroup group_alloc\n@{\n*/\n\n/** \\brief Parameters of new #VmaAllocation.\n\nTo be used with functions like vmaCreateBuffer(), vmaCreateImage(), and many others.\n*/\ntypedef 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 preferred. \\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 VMA_NULLABLE 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* VMA_NULLABLE pUserData;\n  /** \\brief A floating-point value between 0 and 1, indicating the priority of the allocation relative to other memory allocations.\n\n  It is used only when #VMA_ALLOCATOR_CREATE_EXT_MEMORY_PRIORITY_BIT flag was used during creation of the #VmaAllocator object\n  and this allocation ends up as dedicated or is explicitly forced as dedicated using #VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT.\n  Otherwise, it has the priority of a memory block where it is placed and this variable is ignored.\n  */\n  float priority;\n} VmaAllocationCreateInfo;\n\n/// Describes parameter of created #VmaPool.\ntypedef 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  In this case, the pool will also support dedicated allocations.\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 A floating-point value between 0 and 1, indicating the priority of the allocations in this pool relative to other memory allocations.\n\n  It is used only when #VMA_ALLOCATOR_CREATE_EXT_MEMORY_PRIORITY_BIT flag was used during creation of the #VmaAllocator object.\n  Otherwise, this variable is ignored.\n  */\n  float priority;\n  /** \\brief Additional minimum alignment to be used for all allocations created from this pool. Can be 0.\n\n  Leave 0 (default) not to impose any additional alignment. If not 0, it must be a power of two.\n  It can be useful in cases where alignment returned by Vulkan by functions like `vkGetBufferMemoryRequirements` is not enough,\n  e.g. when doing interop with OpenGL.\n  */\n  VkDeviceSize minAllocationAlignment;\n  /** \\brief Additional `pNext` chain to be attached to `VkMemoryAllocateInfo` used for every allocation made by this pool. Optional.\n\n  Optional, can be null. If not null, it must point to a `pNext` chain of structures that can be attached to `VkMemoryAllocateInfo`.\n  It can be useful for special needs such as adding `VkExportMemoryAllocateInfoKHR`.\n  Structures pointed by this member must remain alive and unchanged for the whole lifetime of the custom pool.\n\n  Please note that some structures, e.g. `VkMemoryPriorityAllocateInfoEXT`, `VkMemoryDedicatedAllocateInfoKHR`,\n  can be attached automatically by this library when using other, more convenient of its features.\n  */\n  void* VMA_NULLABLE pMemoryAllocateNext;\n} VmaPoolCreateInfo;\n\n/** @} */\n\n/**\n\\addtogroup group_alloc\n@{\n*/\n\n/// Parameters of #VmaAllocation objects, that can be retrieved using function vmaGetAllocationInfo().\ntypedef 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 the allocation is moved during \\ref defragmentation.\n  */\n  VkDeviceMemory VMA_NULLABLE_NON_DISPATCHABLE deviceMemory;\n  /** \\brief Offset in `VkDeviceMemory` object to the beginning of this allocation, in bytes. `(deviceMemory, offset)` pair is unique to this allocation.\n\n  You usually don't need to use this offset. If you create a buffer or an image together with the allocation using e.g. function\n  vmaCreateBuffer(), vmaCreateImage(), functions that operate on these resources refer to the beginning of the buffer or image,\n  not entire device memory block. Functions like vmaMapMemory(), vmaBindBufferMemory() also refer to the beginning of the allocation\n  and apply this offset automatically.\n\n  It can change after the allocation is moved during \\ref defragmentation.\n  */\n  VkDeviceSize offset;\n  /** \\brief Size of this allocation, in bytes.\n\n  It never changes.\n\n  \\note Allocation size returned in this variable may be greater than the size\n  requested for the resource e.g. as `VkBufferCreateInfo::size`. Whole size of the\n  allocation is accessible for operations on memory e.g. using a pointer after\n  mapping with vmaMapMemory(), but operations on the resource e.g. using\n  `vkCmdCopyBuffer` must be limited to the size of the resource.\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 is null.\n\n  It can change after call to vmaMapMemory(), vmaUnmapMemory().\n  It can also change after the allocation is moved during \\ref defragmentation.\n  */\n  void* VMA_NULLABLE 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* VMA_NULLABLE pUserData;\n  /** \\brief Custom allocation name that was set with vmaSetAllocationName().\n\n  It can change after call to vmaSetAllocationName() for this allocation.\n\n  Another way to set custom name is to pass it in VmaAllocationCreateInfo::pUserData with\n  additional flag #VMA_ALLOCATION_CREATE_USER_DATA_COPY_STRING_BIT set [DEPRECATED].\n  */\n  const char* VMA_NULLABLE pName;\n} VmaAllocationInfo;\n\n/** \\brief Parameters for defragmentation.\n\nTo be used with function vmaBeginDefragmentation().\n*/\ntypedef struct VmaDefragmentationInfo\n{\n  /// \\brief Use combination of #VmaDefragmentationFlagBits.\n  VmaDefragmentationFlags flags;\n  /** \\brief Custom pool to be defragmented.\n\n  If null then default pools will undergo defragmentation process.\n  */\n  VmaPool VMA_NULLABLE pool;\n  /** \\brief Maximum numbers of bytes that can be copied during single pass, while moving allocations to different places.\n\n  `0` means no limit.\n  */\n  VkDeviceSize maxBytesPerPass;\n  /** \\brief Maximum number of allocations that can be moved during single pass to a different place.\n\n  `0` means no limit.\n  */\n  uint32_t maxAllocationsPerPass;\n} VmaDefragmentationInfo;\n\n/// Single move of an allocation to be done for defragmentation.\ntypedef struct VmaDefragmentationMove\n{\n  /// Operation to be performed on the allocation by vmaEndDefragmentationPass(). Default value is #VMA_DEFRAGMENTATION_MOVE_OPERATION_COPY. You can modify it.\n  VmaDefragmentationMoveOperation operation;\n  /// Allocation that should be moved.\n  VmaAllocation VMA_NOT_NULL srcAllocation;\n  /** \\brief Temporary allocation pointing to destination memory that will replace `srcAllocation`.\n\n  \\warning Do not store this allocation in your data structures! It exists only temporarily, for the duration of the defragmentation pass,\n  to be used for binding new buffer/image to the destination memory using e.g. vmaBindBufferMemory().\n  vmaEndDefragmentationPass() will destroy it and make `srcAllocation` point to this memory.\n  */\n  VmaAllocation VMA_NOT_NULL dstTmpAllocation;\n} VmaDefragmentationMove;\n\n/** \\brief Parameters for incremental defragmentation steps.\n\nTo be used with function vmaBeginDefragmentationPass().\n*/\ntypedef struct VmaDefragmentationPassMoveInfo\n{\n  /// Number of elements in the `pMoves` array.\n  uint32_t moveCount;\n  /** \\brief Array of moves to be performed by the user in the current defragmentation pass.\n\n  Pointer to an array of `moveCount` elements, owned by VMA, created in vmaBeginDefragmentationPass(), destroyed in vmaEndDefragmentationPass().\n\n  For each element, you should:\n\n  1. Create a new buffer/image in the place pointed by VmaDefragmentationMove::dstMemory + VmaDefragmentationMove::dstOffset.\n  2. Copy data from the VmaDefragmentationMove::srcAllocation e.g. using `vkCmdCopyBuffer`, `vkCmdCopyImage`.\n  3. Make sure these commands finished executing on the GPU.\n  4. Destroy the old buffer/image.\n\n  Only then you can finish defragmentation pass by calling vmaEndDefragmentationPass().\n  After this call, the allocation will point to the new place in memory.\n\n  Alternatively, if you cannot move specific allocation, you can set VmaDefragmentationMove::operation to #VMA_DEFRAGMENTATION_MOVE_OPERATION_IGNORE.\n\n  Alternatively, if you decide you want to completely remove the allocation:\n\n  1. Destroy its buffer/image.\n  2. Set VmaDefragmentationMove::operation to #VMA_DEFRAGMENTATION_MOVE_OPERATION_DESTROY.\n\n  Then, after vmaEndDefragmentationPass() the allocation will be freed.\n  */\n  VmaDefragmentationMove* VMA_NULLABLE VMA_LEN_IF_NOT_NULL(moveCount) pMoves;\n} VmaDefragmentationPassMoveInfo;\n\n/// Statistics returned for defragmentation process in function vmaEndDefragmentation().\ntypedef 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/** @} */\n\n/**\n\\addtogroup group_virtual\n@{\n*/\n\n/// Parameters of created #VmaVirtualBlock object to be passed to vmaCreateVirtualBlock().\ntypedef struct VmaVirtualBlockCreateInfo\n{\n  /** \\brief Total size of the virtual block.\n\n  Sizes can be expressed in bytes or any units you want as long as you are consistent in using them.\n  For example, if you allocate from some array of structures, 1 can mean single instance of entire structure.\n  */\n  VkDeviceSize size;\n\n  /** \\brief Use combination of #VmaVirtualBlockCreateFlagBits.\n   */\n  VmaVirtualBlockCreateFlags flags;\n\n  /** \\brief Custom CPU memory allocation callbacks. Optional.\n\n  Optional, can be null. When specified, they will be used for all CPU-side memory allocations.\n  */\n  const VkAllocationCallbacks* VMA_NULLABLE pAllocationCallbacks;\n} VmaVirtualBlockCreateInfo;\n\n/// Parameters of created virtual allocation to be passed to vmaVirtualAllocate().\ntypedef struct VmaVirtualAllocationCreateInfo\n{\n  /** \\brief Size of the allocation.\n\n  Cannot be zero.\n  */\n  VkDeviceSize size;\n  /** \\brief Required alignment of the allocation. Optional.\n\n  Must be power of two. Special value 0 has the same meaning as 1 - means no special alignment is required, so allocation can start at any offset.\n  */\n  VkDeviceSize alignment;\n  /** \\brief Use combination of #VmaVirtualAllocationCreateFlagBits.\n   */\n  VmaVirtualAllocationCreateFlags flags;\n  /** \\brief Custom pointer to be associated with the allocation. Optional.\n\n  It can be any value and can be used for user-defined purposes. It can be fetched or changed later.\n  */\n  void* VMA_NULLABLE pUserData;\n} VmaVirtualAllocationCreateInfo;\n\n/// Parameters of an existing virtual allocation, returned by vmaGetVirtualAllocationInfo().\ntypedef struct VmaVirtualAllocationInfo\n{\n  /** \\brief Offset of the allocation.\n\n  Offset at which the allocation was made.\n  */\n  VkDeviceSize offset;\n  /** \\brief Size of the allocation.\n\n  Same value as passed in VmaVirtualAllocationCreateInfo::size.\n  */\n  VkDeviceSize size;\n  /** \\brief Custom pointer associated with the allocation.\n\n  Same value as passed in VmaVirtualAllocationCreateInfo::pUserData or to vmaSetVirtualAllocationUserData().\n  */\n  void* VMA_NULLABLE pUserData;\n} VmaVirtualAllocationInfo;\n\n/** @} */\n\n#endif // _VMA_DATA_TYPES_DECLARATIONS\n\n#ifndef _VMA_FUNCTION_HEADERS\n\n/**\n\\addtogroup group_init\n@{\n*/\n\n/// Creates #VmaAllocator object.\nVMA_CALL_PRE VkResult VMA_CALL_POST vmaCreateAllocator(\n    const VmaAllocatorCreateInfo* VMA_NOT_NULL pCreateInfo,\n    VmaAllocator VMA_NULLABLE* VMA_NOT_NULL pAllocator);\n\n/// Destroys allocator object.\nVMA_CALL_PRE void VMA_CALL_POST vmaDestroyAllocator(\n    VmaAllocator VMA_NULLABLE allocator);\n\n/** \\brief Returns information about existing #VmaAllocator object - handle to Vulkan device etc.\n\nIt might be useful if you want to keep just the #VmaAllocator handle and fetch other required handles to\n`VkPhysicalDevice`, `VkDevice` etc. every time using this function.\n*/\nVMA_CALL_PRE void VMA_CALL_POST vmaGetAllocatorInfo(\n    VmaAllocator VMA_NOT_NULL allocator,\n    VmaAllocatorInfo* VMA_NOT_NULL pAllocatorInfo);\n\n/**\nPhysicalDeviceProperties are fetched from physicalDevice by the allocator.\nYou can access it here, without fetching it again on your own.\n*/\nVMA_CALL_PRE void VMA_CALL_POST vmaGetPhysicalDeviceProperties(\n    VmaAllocator VMA_NOT_NULL allocator,\n    const VkPhysicalDeviceProperties* VMA_NULLABLE* VMA_NOT_NULL ppPhysicalDeviceProperties);\n\n/**\nPhysicalDeviceMemoryProperties are fetched from physicalDevice by the allocator.\nYou can access it here, without fetching it again on your own.\n*/\nVMA_CALL_PRE void VMA_CALL_POST vmaGetMemoryProperties(\n    VmaAllocator VMA_NOT_NULL allocator,\n    const VkPhysicalDeviceMemoryProperties* VMA_NULLABLE* VMA_NOT_NULL ppPhysicalDeviceMemoryProperties);\n\n/**\n\\brief Given Memory Type Index, returns Property Flags of this memory type.\n\nThis is just a convenience function. Same information can be obtained using\nvmaGetMemoryProperties().\n*/\nVMA_CALL_PRE void VMA_CALL_POST vmaGetMemoryTypeProperties(\n    VmaAllocator VMA_NOT_NULL allocator,\n    uint32_t memoryTypeIndex,\n    VkMemoryPropertyFlags* VMA_NOT_NULL pFlags);\n\n/** \\brief Sets index of the current frame.\n */\nVMA_CALL_PRE void VMA_CALL_POST vmaSetCurrentFrameIndex(\n    VmaAllocator VMA_NOT_NULL allocator,\n    uint32_t frameIndex);\n\n/** @} */\n\n/**\n\\addtogroup group_stats\n@{\n*/\n\n/** \\brief Retrieves statistics from current state of the Allocator.\n\nThis function is called \"calculate\" not \"get\" because it has to traverse all\ninternal data structures, so it may be quite slow. Use it for debugging purposes.\nFor faster but more brief statistics suitable to be called every frame or every allocation,\nuse vmaGetHeapBudgets().\n\nNote that when using allocator from multiple threads, returned information may immediately\nbecome outdated.\n*/\nVMA_CALL_PRE void VMA_CALL_POST vmaCalculateStatistics(\n    VmaAllocator VMA_NOT_NULL allocator,\n    VmaTotalStatistics* VMA_NOT_NULL pStats);\n\n/** \\brief Retrieves information about current memory usage and budget for all memory heaps.\n\n\\param allocator\n\\param[out] pBudgets Must point to array with number of elements at least equal to number of memory heaps in physical device used.\n\nThis function is called \"get\" not \"calculate\" because it is very fast, suitable to be called\nevery frame or every allocation. For more detailed statistics use vmaCalculateStatistics().\n\nNote that when using allocator from multiple threads, returned information may immediately\nbecome outdated.\n*/\nVMA_CALL_PRE void VMA_CALL_POST vmaGetHeapBudgets(\n    VmaAllocator VMA_NOT_NULL allocator,\n    VmaBudget* VMA_NOT_NULL VMA_LEN_IF_NOT_NULL(\"VkPhysicalDeviceMemoryProperties::memoryHeapCount\") pBudgets);\n\n/** @} */\n\n/**\n\\addtogroup group_alloc\n@{\n*/\n\n/**\n\\brief Helps to find memoryTypeIndex, given memoryTypeBits and VmaAllocationCreateInfo.\n\nThis 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\nfrom this function or any other allocating function probably means that your\ndevice doesn't support any memory type with requested features for the specific\ntype of resource you want to use it for. Please check parameters of your\nresource, like image layout (OPTIMAL versus LINEAR) or mip level count.\n*/\nVMA_CALL_PRE VkResult VMA_CALL_POST vmaFindMemoryTypeIndex(\n    VmaAllocator VMA_NOT_NULL allocator,\n    uint32_t memoryTypeBits,\n    const VmaAllocationCreateInfo* VMA_NOT_NULL pAllocationCreateInfo,\n    uint32_t* VMA_NOT_NULL pMemoryTypeIndex);\n\n/**\n\\brief Helps to find memoryTypeIndex, given VkBufferCreateInfo and VmaAllocationCreateInfo.\n\nIt can be useful e.g. to determine value to be used as VmaPoolCreateInfo::memoryTypeIndex.\nIt internally creates a temporary, dummy buffer that never has memory bound.\n*/\nVMA_CALL_PRE VkResult VMA_CALL_POST vmaFindMemoryTypeIndexForBufferInfo(\n    VmaAllocator VMA_NOT_NULL allocator,\n    const VkBufferCreateInfo* VMA_NOT_NULL pBufferCreateInfo,\n    const VmaAllocationCreateInfo* VMA_NOT_NULL pAllocationCreateInfo,\n    uint32_t* VMA_NOT_NULL pMemoryTypeIndex);\n\n/**\n\\brief Helps to find memoryTypeIndex, given VkImageCreateInfo and VmaAllocationCreateInfo.\n\nIt can be useful e.g. to determine value to be used as VmaPoolCreateInfo::memoryTypeIndex.\nIt internally creates a temporary, dummy image that never has memory bound.\n*/\nVMA_CALL_PRE VkResult VMA_CALL_POST vmaFindMemoryTypeIndexForImageInfo(\n    VmaAllocator VMA_NOT_NULL allocator,\n    const VkImageCreateInfo* VMA_NOT_NULL pImageCreateInfo,\n    const VmaAllocationCreateInfo* VMA_NOT_NULL pAllocationCreateInfo,\n    uint32_t* VMA_NOT_NULL pMemoryTypeIndex);\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*/\nVMA_CALL_PRE VkResult VMA_CALL_POST vmaCreatePool(\n    VmaAllocator VMA_NOT_NULL allocator,\n    const VmaPoolCreateInfo* VMA_NOT_NULL pCreateInfo,\n    VmaPool VMA_NULLABLE* VMA_NOT_NULL pPool);\n\n/** \\brief Destroys #VmaPool object and frees Vulkan device memory.\n */\nVMA_CALL_PRE void VMA_CALL_POST vmaDestroyPool(\n    VmaAllocator VMA_NOT_NULL allocator,\n    VmaPool VMA_NULLABLE pool);\n\n/** @} */\n\n/**\n\\addtogroup group_stats\n@{\n*/\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*/\nVMA_CALL_PRE void VMA_CALL_POST vmaGetPoolStatistics(\n    VmaAllocator VMA_NOT_NULL allocator,\n    VmaPool VMA_NOT_NULL pool,\n    VmaStatistics* VMA_NOT_NULL pPoolStats);\n\n/** \\brief Retrieves detailed 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*/\nVMA_CALL_PRE void VMA_CALL_POST vmaCalculatePoolStatistics(\n    VmaAllocator VMA_NOT_NULL allocator,\n    VmaPool VMA_NOT_NULL pool,\n    VmaDetailedStatistics* VMA_NOT_NULL pPoolStats);\n\n/** @} */\n\n/**\n\\addtogroup group_alloc\n@{\n*/\n\n/** \\brief Checks magic number in margins around all allocations in given memory pool in search for corruptions.\n\nCorruption 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\nPossible 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_UNKNOWN` - 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*/\nVMA_CALL_PRE VkResult VMA_CALL_POST vmaCheckPoolCorruption(\n    VmaAllocator VMA_NOT_NULL allocator,\n    VmaPool VMA_NOT_NULL pool);\n\n/** \\brief Retrieves name of a custom pool.\n\nAfter the call `ppName` is either null or points to an internally-owned null-terminated string\ncontaining name of the pool that was previously set. The pointer becomes invalid when the pool is\ndestroyed or its name is changed using vmaSetPoolName().\n*/\nVMA_CALL_PRE void VMA_CALL_POST vmaGetPoolName(\n    VmaAllocator VMA_NOT_NULL allocator,\n    VmaPool VMA_NOT_NULL pool,\n    const char* VMA_NULLABLE* VMA_NOT_NULL 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.\nFunction makes internal copy of the string, so it can be changed or freed immediately after this call.\n*/\nVMA_CALL_PRE void VMA_CALL_POST vmaSetPoolName(\n    VmaAllocator VMA_NOT_NULL allocator,\n    VmaPool VMA_NOT_NULL pool,\n    const char* VMA_NULLABLE pName);\n\n/** \\brief General purpose memory allocation.\n\n\\param allocator\n\\param pVkMemoryRequirements\n\\param pCreateInfo\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\nYou should free the memory using vmaFreeMemory() or vmaFreeMemoryPages().\n\nIt is recommended to use vmaAllocateMemoryForBuffer(), vmaAllocateMemoryForImage(),\nvmaCreateBuffer(), vmaCreateImage() instead whenever possible.\n*/\nVMA_CALL_PRE VkResult VMA_CALL_POST vmaAllocateMemory(\n    VmaAllocator VMA_NOT_NULL allocator,\n    const VkMemoryRequirements* VMA_NOT_NULL pVkMemoryRequirements,\n    const VmaAllocationCreateInfo* VMA_NOT_NULL pCreateInfo,\n    VmaAllocation VMA_NULLABLE* VMA_NOT_NULL pAllocation,\n    VmaAllocationInfo* VMA_NULLABLE 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 allocation.\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\nYou should free the memory using vmaFreeMemory() or vmaFreeMemoryPages().\n\nWord \"pages\" is just a suggestion to use this function to allocate pieces of memory needed for sparse binding.\nIt is just a general purpose allocation function able to make multiple allocations at once.\nIt may be internally optimized to be more efficient than calling vmaAllocateMemory() `allocationCount` times.\n\nAll allocations are made using same parameters. All of them are created out of the same memory pool and type.\nIf any allocation fails, all allocations already made within this function call are also freed, so that when\nreturned result is not `VK_SUCCESS`, `pAllocation` array is always entirely filled with `VK_NULL_HANDLE`.\n*/\nVMA_CALL_PRE VkResult VMA_CALL_POST vmaAllocateMemoryPages(\n    VmaAllocator VMA_NOT_NULL allocator,\n    const VkMemoryRequirements* VMA_NOT_NULL VMA_LEN_IF_NOT_NULL(allocationCount) pVkMemoryRequirements,\n    const VmaAllocationCreateInfo* VMA_NOT_NULL VMA_LEN_IF_NOT_NULL(allocationCount) pCreateInfo,\n    size_t allocationCount,\n    VmaAllocation VMA_NULLABLE* VMA_NOT_NULL VMA_LEN_IF_NOT_NULL(allocationCount) pAllocations,\n    VmaAllocationInfo* VMA_NULLABLE VMA_LEN_IF_NOT_NULL(allocationCount) pAllocationInfo);\n\n/** \\brief Allocates memory suitable for given `VkBuffer`.\n\n\\param allocator\n\\param buffer\n\\param pCreateInfo\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\nIt only creates #VmaAllocation. To bind the memory to the buffer, use vmaBindBufferMemory().\n\nThis is a special-purpose function. In most cases you should use vmaCreateBuffer().\n\nYou must free the allocation using vmaFreeMemory() when no longer needed.\n*/\nVMA_CALL_PRE VkResult VMA_CALL_POST vmaAllocateMemoryForBuffer(\n    VmaAllocator VMA_NOT_NULL allocator,\n    VkBuffer VMA_NOT_NULL_NON_DISPATCHABLE buffer,\n    const VmaAllocationCreateInfo* VMA_NOT_NULL pCreateInfo,\n    VmaAllocation VMA_NULLABLE* VMA_NOT_NULL pAllocation,\n    VmaAllocationInfo* VMA_NULLABLE pAllocationInfo);\n\n/** \\brief Allocates memory suitable for given `VkImage`.\n\n\\param allocator\n\\param image\n\\param pCreateInfo\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\nIt only creates #VmaAllocation. To bind the memory to the buffer, use vmaBindImageMemory().\n\nThis is a special-purpose function. In most cases you should use vmaCreateImage().\n\nYou must free the allocation using vmaFreeMemory() when no longer needed.\n*/\nVMA_CALL_PRE VkResult VMA_CALL_POST vmaAllocateMemoryForImage(\n    VmaAllocator VMA_NOT_NULL allocator,\n    VkImage VMA_NOT_NULL_NON_DISPATCHABLE image,\n    const VmaAllocationCreateInfo* VMA_NOT_NULL pCreateInfo,\n    VmaAllocation VMA_NULLABLE* VMA_NOT_NULL pAllocation,\n    VmaAllocationInfo* VMA_NULLABLE pAllocationInfo);\n\n/** \\brief Frees memory previously allocated using vmaAllocateMemory(), vmaAllocateMemoryForBuffer(), or vmaAllocateMemoryForImage().\n\nPassing `VK_NULL_HANDLE` as `allocation` is valid. Such function call is just skipped.\n*/\nVMA_CALL_PRE void VMA_CALL_POST vmaFreeMemory(\n    VmaAllocator VMA_NOT_NULL allocator,\n    const VmaAllocation VMA_NULLABLE allocation);\n\n/** \\brief Frees memory and destroys multiple allocations.\n\nWord \"pages\" is just a suggestion to use this function to free pieces of memory used for sparse binding.\nIt is just a general purpose function to free memory and destroy allocations made using e.g. vmaAllocateMemory(),\nvmaAllocateMemoryPages() and other functions.\nIt may be internally optimized to be more efficient than calling vmaFreeMemory() `allocationCount` times.\n\nAllocations in `pAllocations` array can come from any memory pools and types.\nPassing `VK_NULL_HANDLE` as elements of `pAllocations` array is valid. Such entries are just skipped.\n*/\nVMA_CALL_PRE void VMA_CALL_POST vmaFreeMemoryPages(\n    VmaAllocator VMA_NOT_NULL allocator,\n    size_t allocationCount,\n    const VmaAllocation VMA_NULLABLE* VMA_NOT_NULL VMA_LEN_IF_NOT_NULL(allocationCount) pAllocations);\n\n/** \\brief Returns current information about specified allocation.\n\nCurrent parameters of given allocation are returned in `pAllocationInfo`.\n\nAlthough this function doesn't lock any mutex, so it should be quite efficient,\nyou should avoid calling it too often.\nYou can retrieve same VmaAllocationInfo structure while creating your resource, from function\nvmaCreateBuffer(), vmaCreateImage(). You can remember it if you are sure parameters don't change\n(e.g. due to defragmentation).\n*/\nVMA_CALL_PRE void VMA_CALL_POST vmaGetAllocationInfo(\n    VmaAllocator VMA_NOT_NULL allocator,\n    VmaAllocation VMA_NOT_NULL allocation,\n    VmaAllocationInfo* VMA_NOT_NULL pAllocationInfo);\n\n/** \\brief Sets pUserData in given allocation to new value.\n\nThe value of pointer `pUserData` is copied to allocation's `pUserData`.\nIt is opaque, so you can use it however you want - e.g.\nas a pointer, ordinal number or some handle to you own data.\n*/\nVMA_CALL_PRE void VMA_CALL_POST vmaSetAllocationUserData(\n    VmaAllocator VMA_NOT_NULL allocator,\n    VmaAllocation VMA_NOT_NULL allocation,\n    void* VMA_NULLABLE pUserData);\n\n/** \\brief Sets pName in given allocation to new value.\n\n`pName` must be either null, or pointer to a null-terminated string. The function\nmakes local copy of the string and sets it as allocation's `pName`. String\npassed as pName doesn't need to be valid for whole lifetime of the allocation -\nyou can free it after this call. String previously pointed by allocation's\n`pName` is freed from memory.\n*/\nVMA_CALL_PRE void VMA_CALL_POST vmaSetAllocationName(\n    VmaAllocator VMA_NOT_NULL allocator,\n    VmaAllocation VMA_NOT_NULL allocation,\n    const char* VMA_NULLABLE pName);\n\n/**\n\\brief Given an allocation, returns Property Flags of its memory type.\n\nThis is just a convenience function. Same information can be obtained using\nvmaGetAllocationInfo() + vmaGetMemoryProperties().\n*/\nVMA_CALL_PRE void VMA_CALL_POST vmaGetAllocationMemoryProperties(\n    VmaAllocator VMA_NOT_NULL allocator,\n    VmaAllocation VMA_NOT_NULL allocation,\n    VkMemoryPropertyFlags* VMA_NOT_NULL pFlags);\n\n/** \\brief Maps memory represented by given allocation and returns pointer to it.\n\nMaps memory represented by given allocation to make it accessible to CPU code.\nWhen succeeded, `*ppData` contains pointer to first byte of this memory.\n\n\\warning\nIf the allocation is part of a bigger `VkDeviceMemory` block, returned pointer is\ncorrectly offsetted to the beginning of region assigned to this particular allocation.\nUnlike the result of `vkMapMemory`, it points to the allocation, not to the beginning of the whole block.\nYou should not add VmaAllocationInfo::offset to it!\n\nMapping is internally reference-counted and synchronized, so despite raw Vulkan\nfunction `vkMapMemory()` cannot be used to map same block of `VkDeviceMemory`\nmultiple times simultaneously, it is safe to call this function on allocations\nassigned to the same memory block. Actual Vulkan memory will be mapped on first\nmapping and unmapped on last unmapping.\n\nIf the function succeeded, you must call vmaUnmapMemory() to unmap the\nallocation when mapping is no longer needed or before freeing the allocation, at\nthe latest.\n\nIt also safe to call this function multiple times on the same allocation. You\nmust call vmaUnmapMemory() same number of times as you called vmaMapMemory().\n\nIt 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.\nYou must still call vmaUnmapMemory() same number of times as you called\nvmaMapMemory(). 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\nThis function fails when used on allocation made in memory type that is not\n`HOST_VISIBLE`.\n\nThis function doesn't automatically flush or invalidate caches.\nIf the allocation is made from a memory types that is not `HOST_COHERENT`,\nyou also need to use vmaInvalidateAllocation() / vmaFlushAllocation(), as required by Vulkan specification.\n*/\nVMA_CALL_PRE VkResult VMA_CALL_POST vmaMapMemory(\n    VmaAllocator VMA_NOT_NULL allocator,\n    VmaAllocation VMA_NOT_NULL allocation,\n    void* VMA_NULLABLE* VMA_NOT_NULL ppData);\n\n/** \\brief Unmaps memory represented by given allocation, mapped previously using vmaMapMemory().\n\nFor details, see description of vmaMapMemory().\n\nThis function doesn't automatically flush or invalidate caches.\nIf the allocation is made from a memory types that is not `HOST_COHERENT`,\nyou also need to use vmaInvalidateAllocation() / vmaFlushAllocation(), as required by Vulkan specification.\n*/\nVMA_CALL_PRE void VMA_CALL_POST vmaUnmapMemory(\n    VmaAllocator VMA_NOT_NULL allocator,\n    VmaAllocation VMA_NOT_NULL allocation);\n\n/** \\brief Flushes memory of given allocation.\n\nCalls `vkFlushMappedMemoryRanges()` for memory associated with given range of given allocation.\nIt needs to be called after writing to a mapped memory for memory types that are not `HOST_COHERENT`.\nUnmap 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\nWarning! `offset` and `size` are relative to the contents of given `allocation`.\nIf you mean whole allocation, you can pass 0 and `VK_WHOLE_SIZE`, respectively.\nDo not pass allocation's offset as `offset`!!!\n\nThis function returns the `VkResult` from `vkFlushMappedMemoryRanges` if it is\ncalled, otherwise `VK_SUCCESS`.\n*/\nVMA_CALL_PRE VkResult VMA_CALL_POST vmaFlushAllocation(\n    VmaAllocator VMA_NOT_NULL allocator,\n    VmaAllocation VMA_NOT_NULL allocation,\n    VkDeviceSize offset,\n    VkDeviceSize size);\n\n/** \\brief Invalidates memory of given allocation.\n\nCalls `vkInvalidateMappedMemoryRanges()` for memory associated with given range of given allocation.\nIt needs to be called before reading from a mapped memory for memory types that are not `HOST_COHERENT`.\nMap 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\nWarning! `offset` and `size` are relative to the contents of given `allocation`.\nIf you mean whole allocation, you can pass 0 and `VK_WHOLE_SIZE`, respectively.\nDo not pass allocation's offset as `offset`!!!\n\nThis function returns the `VkResult` from `vkInvalidateMappedMemoryRanges` if\nit is called, otherwise `VK_SUCCESS`.\n*/\nVMA_CALL_PRE VkResult VMA_CALL_POST vmaInvalidateAllocation(\n    VmaAllocator VMA_NOT_NULL allocator,\n    VmaAllocation VMA_NOT_NULL allocation,\n    VkDeviceSize offset,\n    VkDeviceSize size);\n\n/** \\brief Flushes memory of given set of allocations.\n\nCalls `vkFlushMappedMemoryRanges()` for memory associated with given ranges of given allocations.\nFor more information, see documentation of vmaFlushAllocation().\n\n\\param allocator\n\\param allocationCount\n\\param allocations\n\\param offsets If not null, it must point to an array of offsets of regions to flush, relative to the beginning of respective allocations. Null means all ofsets are zero.\n\\param sizes If not null, it must point to an array of sizes of regions to flush in respective allocations. Null means `VK_WHOLE_SIZE` for all allocations.\n\nThis function returns the `VkResult` from `vkFlushMappedMemoryRanges` if it is\ncalled, otherwise `VK_SUCCESS`.\n*/\nVMA_CALL_PRE VkResult VMA_CALL_POST vmaFlushAllocations(\n    VmaAllocator VMA_NOT_NULL allocator,\n    uint32_t allocationCount,\n    const VmaAllocation VMA_NOT_NULL* VMA_NULLABLE VMA_LEN_IF_NOT_NULL(allocationCount) allocations,\n    const VkDeviceSize* VMA_NULLABLE VMA_LEN_IF_NOT_NULL(allocationCount) offsets,\n    const VkDeviceSize* VMA_NULLABLE VMA_LEN_IF_NOT_NULL(allocationCount) sizes);\n\n/** \\brief Invalidates memory of given set of allocations.\n\nCalls `vkInvalidateMappedMemoryRanges()` for memory associated with given ranges of given allocations.\nFor more information, see documentation of vmaInvalidateAllocation().\n\n\\param allocator\n\\param allocationCount\n\\param allocations\n\\param offsets If not null, it must point to an array of offsets of regions to flush, relative to the beginning of respective allocations. Null means all ofsets are zero.\n\\param sizes If not null, it must point to an array of sizes of regions to flush in respective allocations. Null means `VK_WHOLE_SIZE` for all allocations.\n\nThis function returns the `VkResult` from `vkInvalidateMappedMemoryRanges` if it is\ncalled, otherwise `VK_SUCCESS`.\n*/\nVMA_CALL_PRE VkResult VMA_CALL_POST vmaInvalidateAllocations(\n    VmaAllocator VMA_NOT_NULL allocator,\n    uint32_t allocationCount,\n    const VmaAllocation VMA_NOT_NULL* VMA_NULLABLE VMA_LEN_IF_NOT_NULL(allocationCount) allocations,\n    const VkDeviceSize* VMA_NULLABLE VMA_LEN_IF_NOT_NULL(allocationCount) offsets,\n    const VkDeviceSize* VMA_NULLABLE VMA_LEN_IF_NOT_NULL(allocationCount) sizes);\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 allocator\n\\param memoryTypeBits Bit mask, where each bit set means that a memory type with that index should be checked.\n\nCorruption 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\nPossible 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_UNKNOWN` - 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*/\nVMA_CALL_PRE VkResult VMA_CALL_POST vmaCheckCorruption(\n    VmaAllocator VMA_NOT_NULL allocator,\n    uint32_t memoryTypeBits);\n\n/** \\brief Begins defragmentation process.\n\n\\param allocator Allocator object.\n\\param pInfo Structure filled with parameters of defragmentation.\n\\param[out] pContext Context object that must be passed to vmaEndDefragmentation() to finish defragmentation.\n\\returns\n- `VK_SUCCESS` if defragmentation can begin.\n- `VK_ERROR_FEATURE_NOT_PRESENT` if defragmentation is not supported.\n\nFor more information about defragmentation, see documentation chapter:\n[Defragmentation](@ref defragmentation).\n*/\nVMA_CALL_PRE VkResult VMA_CALL_POST vmaBeginDefragmentation(\n    VmaAllocator VMA_NOT_NULL allocator,\n    const VmaDefragmentationInfo* VMA_NOT_NULL pInfo,\n    VmaDefragmentationContext VMA_NULLABLE* VMA_NOT_NULL pContext);\n\n/** \\brief Ends defragmentation process.\n\n\\param allocator Allocator object.\n\\param context Context object that has been created by vmaBeginDefragmentation().\n\\param[out] pStats Optional stats for the defragmentation. Can be null.\n\nUse this function to finish defragmentation started by vmaBeginDefragmentation().\n*/\nVMA_CALL_PRE void VMA_CALL_POST vmaEndDefragmentation(\n    VmaAllocator VMA_NOT_NULL allocator,\n    VmaDefragmentationContext VMA_NOT_NULL context,\n    VmaDefragmentationStats* VMA_NULLABLE pStats);\n\n/** \\brief Starts single defragmentation pass.\n\n\\param allocator Allocator object.\n\\param context Context object that has been created by vmaBeginDefragmentation().\n\\param[out] pPassInfo Computed information for current pass.\n\\returns\n- `VK_SUCCESS` if no more moves are possible. Then you can omit call to vmaEndDefragmentationPass() and simply end whole defragmentation.\n- `VK_INCOMPLETE` if there are pending moves returned in `pPassInfo`. You need to perform them, call vmaEndDefragmentationPass(),\n  and then preferably try another pass with vmaBeginDefragmentationPass().\n*/\nVMA_CALL_PRE VkResult VMA_CALL_POST vmaBeginDefragmentationPass(\n    VmaAllocator VMA_NOT_NULL allocator,\n    VmaDefragmentationContext VMA_NOT_NULL context,\n    VmaDefragmentationPassMoveInfo* VMA_NOT_NULL pPassInfo);\n\n/** \\brief Ends single defragmentation pass.\n\n\\param allocator Allocator object.\n\\param context Context object that has been created by vmaBeginDefragmentation().\n\\param pPassInfo Computed information for current pass filled by vmaBeginDefragmentationPass() and possibly modified by you.\n\nReturns `VK_SUCCESS` if no more moves are possible or `VK_INCOMPLETE` if more defragmentations are possible.\n\nEnds incremental defragmentation pass and commits all defragmentation moves from `pPassInfo`.\nAfter this call:\n\n- Allocations at `pPassInfo[i].srcAllocation` that had `pPassInfo[i].operation ==` #VMA_DEFRAGMENTATION_MOVE_OPERATION_COPY\n  (which is the default) will be pointing to the new destination place.\n- Allocation at `pPassInfo[i].srcAllocation` that had `pPassInfo[i].operation ==` #VMA_DEFRAGMENTATION_MOVE_OPERATION_DESTROY\n  will be freed.\n\nIf no more moves are possible you can end whole defragmentation.\n*/\nVMA_CALL_PRE VkResult VMA_CALL_POST vmaEndDefragmentationPass(\n    VmaAllocator VMA_NOT_NULL allocator,\n    VmaDefragmentationContext VMA_NOT_NULL context,\n    VmaDefragmentationPassMoveInfo* VMA_NOT_NULL pPassInfo);\n\n/** \\brief Binds buffer to allocation.\n\nBinds specified buffer to region of memory represented by specified allocation.\nGets `VkDeviceMemory` handle and offset from the allocation.\nIf you want to create a buffer, allocate memory for it and bind them together separately,\nyou should use this function for binding instead of standard `vkBindBufferMemory()`,\nbecause it ensures proper synchronization so that when a `VkDeviceMemory` object is used by multiple\nallocations, calls to `vkBind*Memory()` or `vkMapMemory()` won't happen from multiple threads simultaneously\n(which is illegal in Vulkan).\n\nIt is recommended to use function vmaCreateBuffer() instead of this one.\n*/\nVMA_CALL_PRE VkResult VMA_CALL_POST vmaBindBufferMemory(\n    VmaAllocator VMA_NOT_NULL allocator,\n    VmaAllocation VMA_NOT_NULL allocation,\n    VkBuffer VMA_NOT_NULL_NON_DISPATCHABLE buffer);\n\n/** \\brief Binds buffer to allocation with additional parameters.\n\n\\param allocator\n\\param allocation\n\\param allocationLocalOffset Additional offset to be added while binding, relative to the beginning of the `allocation`. Normally it should be 0.\n\\param buffer\n\\param pNext A chain of structures to be attached to `VkBindBufferMemoryInfoKHR` structure used internally. Normally it should be null.\n\nThis function is similar to vmaBindBufferMemory(), but it provides additional parameters.\n\nIf `pNext` is not null, #VmaAllocator object must have been created with #VMA_ALLOCATOR_CREATE_KHR_BIND_MEMORY2_BIT flag\nor with VmaAllocatorCreateInfo::vulkanApiVersion `>= VK_API_VERSION_1_1`. Otherwise the call fails.\n*/\nVMA_CALL_PRE VkResult VMA_CALL_POST vmaBindBufferMemory2(\n    VmaAllocator VMA_NOT_NULL allocator,\n    VmaAllocation VMA_NOT_NULL allocation,\n    VkDeviceSize allocationLocalOffset,\n    VkBuffer VMA_NOT_NULL_NON_DISPATCHABLE buffer,\n    const void* VMA_NULLABLE pNext);\n\n/** \\brief Binds image to allocation.\n\nBinds specified image to region of memory represented by specified allocation.\nGets `VkDeviceMemory` handle and offset from the allocation.\nIf you want to create an image, allocate memory for it and bind them together separately,\nyou should use this function for binding instead of standard `vkBindImageMemory()`,\nbecause it ensures proper synchronization so that when a `VkDeviceMemory` object is used by multiple\nallocations, calls to `vkBind*Memory()` or `vkMapMemory()` won't happen from multiple threads simultaneously\n(which is illegal in Vulkan).\n\nIt is recommended to use function vmaCreateImage() instead of this one.\n*/\nVMA_CALL_PRE VkResult VMA_CALL_POST vmaBindImageMemory(\n    VmaAllocator VMA_NOT_NULL allocator,\n    VmaAllocation VMA_NOT_NULL allocation,\n    VkImage VMA_NOT_NULL_NON_DISPATCHABLE image);\n\n/** \\brief Binds image to allocation with additional parameters.\n\n\\param allocator\n\\param allocation\n\\param allocationLocalOffset Additional offset to be added while binding, relative to the beginning of the `allocation`. Normally it should be 0.\n\\param image\n\\param pNext A chain of structures to be attached to `VkBindImageMemoryInfoKHR` structure used internally. Normally it should be null.\n\nThis function is similar to vmaBindImageMemory(), but it provides additional parameters.\n\nIf `pNext` is not null, #VmaAllocator object must have been created with #VMA_ALLOCATOR_CREATE_KHR_BIND_MEMORY2_BIT flag\nor with VmaAllocatorCreateInfo::vulkanApiVersion `>= VK_API_VERSION_1_1`. Otherwise the call fails.\n*/\nVMA_CALL_PRE VkResult VMA_CALL_POST vmaBindImageMemory2(\n    VmaAllocator VMA_NOT_NULL allocator,\n    VmaAllocation VMA_NOT_NULL allocation,\n    VkDeviceSize allocationLocalOffset,\n    VkImage VMA_NOT_NULL_NON_DISPATCHABLE image,\n    const void* VMA_NULLABLE pNext);\n\n/** \\brief Creates a new `VkBuffer`, allocates and binds memory for it.\n\n\\param allocator\n\\param pBufferCreateInfo\n\\param pAllocationCreateInfo\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\nThis function automatically:\n\n-# Creates buffer.\n-# Allocates appropriate memory for it.\n-# Binds the buffer with the memory.\n\nIf any of these operations fail, buffer and allocation are not created,\nreturned value is negative error code, `*pBuffer` and `*pAllocation` are null.\n\nIf the function succeeded, you must destroy both buffer and allocation when you\nno longer need them using either convenience function vmaDestroyBuffer() or\nseparately, using `vkDestroyBuffer()` and vmaFreeMemory().\n\nIf #VMA_ALLOCATOR_CREATE_KHR_DEDICATED_ALLOCATION_BIT flag was used,\nVK_KHR_dedicated_allocation extension is used internally to query driver whether\nit requires or prefers the new buffer to have dedicated allocation. If yes,\nand if dedicated allocation is possible\n(#VMA_ALLOCATION_CREATE_NEVER_ALLOCATE_BIT is not used), it creates dedicated\nallocation for this buffer, just like when using\n#VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT.\n\n\\note This function creates a new `VkBuffer`. Sub-allocation of parts of one large buffer,\nalthough recommended as a good practice, is out of scope of this library and could be implemented\nby the user as a higher-level logic on top of VMA.\n*/\nVMA_CALL_PRE VkResult VMA_CALL_POST vmaCreateBuffer(\n    VmaAllocator VMA_NOT_NULL allocator,\n    const VkBufferCreateInfo* VMA_NOT_NULL pBufferCreateInfo,\n    const VmaAllocationCreateInfo* VMA_NOT_NULL pAllocationCreateInfo,\n    VkBuffer VMA_NULLABLE_NON_DISPATCHABLE* VMA_NOT_NULL pBuffer,\n    VmaAllocation VMA_NULLABLE* VMA_NOT_NULL pAllocation,\n    VmaAllocationInfo* VMA_NULLABLE pAllocationInfo);\n\n/** \\brief Creates a buffer with additional minimum alignment.\n\nSimilar to vmaCreateBuffer() but provides additional parameter `minAlignment` which allows to specify custom,\nminimum alignment to be used when placing the buffer inside a larger memory block, which may be needed e.g.\nfor interop with OpenGL.\n*/\nVMA_CALL_PRE VkResult VMA_CALL_POST vmaCreateBufferWithAlignment(\n    VmaAllocator VMA_NOT_NULL allocator,\n    const VkBufferCreateInfo* VMA_NOT_NULL pBufferCreateInfo,\n    const VmaAllocationCreateInfo* VMA_NOT_NULL pAllocationCreateInfo,\n    VkDeviceSize minAlignment,\n    VkBuffer VMA_NULLABLE_NON_DISPATCHABLE* VMA_NOT_NULL pBuffer,\n    VmaAllocation VMA_NULLABLE* VMA_NOT_NULL pAllocation,\n    VmaAllocationInfo* VMA_NULLABLE pAllocationInfo);\n\n/** \\brief Creates a new `VkBuffer`, binds already created memory for it.\n\n\\param allocator\n\\param allocation Allocation that provides memory to be used for binding new buffer to it.\n\\param pBufferCreateInfo\n\\param[out] pBuffer Buffer that was created.\n\nThis function automatically:\n\n-# Creates buffer.\n-# Binds the buffer with the supplied memory.\n\nIf any of these operations fail, buffer is not created,\nreturned value is negative error code and `*pBuffer` is null.\n\nIf the function succeeded, you must destroy the buffer when you\nno longer need it using `vkDestroyBuffer()`. If you want to also destroy the corresponding\nallocation you can use convenience function vmaDestroyBuffer().\n\n\\note There is a new version of this function augmented with parameter `allocationLocalOffset` - see vmaCreateAliasingBuffer2().\n*/\nVMA_CALL_PRE VkResult VMA_CALL_POST vmaCreateAliasingBuffer(\n    VmaAllocator VMA_NOT_NULL allocator,\n    VmaAllocation VMA_NOT_NULL allocation,\n    const VkBufferCreateInfo* VMA_NOT_NULL pBufferCreateInfo,\n    VkBuffer VMA_NULLABLE_NON_DISPATCHABLE* VMA_NOT_NULL pBuffer);\n\n/** \\brief Creates a new `VkBuffer`, binds already created memory for it.\n\n\\param allocator\n\\param allocation Allocation that provides memory to be used for binding new buffer to it.\n\\param allocationLocalOffset Additional offset to be added while binding, relative to the beginning of the allocation. Normally it should be 0.\n\\param pBufferCreateInfo\n\\param[out] pBuffer Buffer that was created.\n\nThis function automatically:\n\n-# Creates buffer.\n-# Binds the buffer with the supplied memory.\n\nIf any of these operations fail, buffer is not created,\nreturned value is negative error code and `*pBuffer` is null.\n\nIf the function succeeded, you must destroy the buffer when you\nno longer need it using `vkDestroyBuffer()`. If you want to also destroy the corresponding\nallocation you can use convenience function vmaDestroyBuffer().\n\n\\note This is a new version of the function augmented with parameter `allocationLocalOffset`.\n*/\nVMA_CALL_PRE VkResult VMA_CALL_POST vmaCreateAliasingBuffer2(\n    VmaAllocator VMA_NOT_NULL allocator,\n    VmaAllocation VMA_NOT_NULL allocation,\n    VkDeviceSize allocationLocalOffset,\n    const VkBufferCreateInfo* VMA_NOT_NULL pBufferCreateInfo,\n    VkBuffer VMA_NULLABLE_NON_DISPATCHABLE* VMA_NOT_NULL pBuffer);\n\n/** \\brief Destroys Vulkan buffer and frees allocated memory.\n\nThis is just a convenience function equivalent to:\n\n\\code\nvkDestroyBuffer(device, buffer, allocationCallbacks);\nvmaFreeMemory(allocator, allocation);\n\\endcode\n\nIt is safe to pass null as buffer and/or allocation.\n*/\nVMA_CALL_PRE void VMA_CALL_POST vmaDestroyBuffer(\n    VmaAllocator VMA_NOT_NULL allocator,\n    VkBuffer VMA_NULLABLE_NON_DISPATCHABLE buffer,\n    VmaAllocation VMA_NULLABLE allocation);\n\n/// Function similar to vmaCreateBuffer().\nVMA_CALL_PRE VkResult VMA_CALL_POST vmaCreateImage(\n    VmaAllocator VMA_NOT_NULL allocator,\n    const VkImageCreateInfo* VMA_NOT_NULL pImageCreateInfo,\n    const VmaAllocationCreateInfo* VMA_NOT_NULL pAllocationCreateInfo,\n    VkImage VMA_NULLABLE_NON_DISPATCHABLE* VMA_NOT_NULL pImage,\n    VmaAllocation VMA_NULLABLE* VMA_NOT_NULL pAllocation,\n    VmaAllocationInfo* VMA_NULLABLE pAllocationInfo);\n\n/// Function similar to vmaCreateAliasingBuffer() but for images.\nVMA_CALL_PRE VkResult VMA_CALL_POST vmaCreateAliasingImage(\n    VmaAllocator VMA_NOT_NULL allocator,\n    VmaAllocation VMA_NOT_NULL allocation,\n    const VkImageCreateInfo* VMA_NOT_NULL pImageCreateInfo,\n    VkImage VMA_NULLABLE_NON_DISPATCHABLE* VMA_NOT_NULL pImage);\n\n/// Function similar to vmaCreateAliasingBuffer2() but for images.\nVMA_CALL_PRE VkResult VMA_CALL_POST vmaCreateAliasingImage2(\n    VmaAllocator VMA_NOT_NULL allocator,\n    VmaAllocation VMA_NOT_NULL allocation,\n    VkDeviceSize allocationLocalOffset,\n    const VkImageCreateInfo* VMA_NOT_NULL pImageCreateInfo,\n    VkImage VMA_NULLABLE_NON_DISPATCHABLE* VMA_NOT_NULL pImage);\n\n/** \\brief Destroys Vulkan image and frees allocated memory.\n\nThis is just a convenience function equivalent to:\n\n\\code\nvkDestroyImage(device, image, allocationCallbacks);\nvmaFreeMemory(allocator, allocation);\n\\endcode\n\nIt is safe to pass null as image and/or allocation.\n*/\nVMA_CALL_PRE void VMA_CALL_POST vmaDestroyImage(\n    VmaAllocator VMA_NOT_NULL allocator,\n    VkImage VMA_NULLABLE_NON_DISPATCHABLE image,\n    VmaAllocation VMA_NULLABLE allocation);\n\n/** @} */\n\n/**\n\\addtogroup group_virtual\n@{\n*/\n\n/** \\brief Creates new #VmaVirtualBlock object.\n\n\\param pCreateInfo Parameters for creation.\n\\param[out] pVirtualBlock Returned virtual block object or `VMA_NULL` if creation failed.\n*/\nVMA_CALL_PRE VkResult VMA_CALL_POST vmaCreateVirtualBlock(\n    const VmaVirtualBlockCreateInfo* VMA_NOT_NULL pCreateInfo,\n    VmaVirtualBlock VMA_NULLABLE* VMA_NOT_NULL pVirtualBlock);\n\n/** \\brief Destroys #VmaVirtualBlock object.\n\nPlease note that you should consciously handle virtual allocations that could remain unfreed in the block.\nYou should either free them individually using vmaVirtualFree() or call vmaClearVirtualBlock()\nif you are sure this is what you want. If you do neither, an assert is called.\n\nIf you keep pointers to some additional metadata associated with your virtual allocations in their `pUserData`,\ndon't forget to free them.\n*/\nVMA_CALL_PRE void VMA_CALL_POST vmaDestroyVirtualBlock(\n    VmaVirtualBlock VMA_NULLABLE virtualBlock);\n\n/** \\brief Returns true of the #VmaVirtualBlock is empty - contains 0 virtual allocations and has all its space available for new allocations.\n */\nVMA_CALL_PRE VkBool32 VMA_CALL_POST vmaIsVirtualBlockEmpty(\n    VmaVirtualBlock VMA_NOT_NULL virtualBlock);\n\n/** \\brief Returns information about a specific virtual allocation within a virtual block, like its size and `pUserData` pointer.\n */\nVMA_CALL_PRE void VMA_CALL_POST vmaGetVirtualAllocationInfo(\n    VmaVirtualBlock VMA_NOT_NULL virtualBlock,\n    VmaVirtualAllocation VMA_NOT_NULL_NON_DISPATCHABLE allocation, VmaVirtualAllocationInfo* VMA_NOT_NULL pVirtualAllocInfo);\n\n/** \\brief Allocates new virtual allocation inside given #VmaVirtualBlock.\n\nIf the allocation fails due to not enough free space available, `VK_ERROR_OUT_OF_DEVICE_MEMORY` is returned\n(despite the function doesn't ever allocate actual GPU memory).\n`pAllocation` is then set to `VK_NULL_HANDLE` and `pOffset`, if not null, it set to `UINT64_MAX`.\n\n\\param virtualBlock Virtual block\n\\param pCreateInfo Parameters for the allocation\n\\param[out] pAllocation Returned handle of the new allocation\n\\param[out] pOffset Returned offset of the new allocation. Optional, can be null.\n*/\nVMA_CALL_PRE VkResult VMA_CALL_POST vmaVirtualAllocate(\n    VmaVirtualBlock VMA_NOT_NULL virtualBlock,\n    const VmaVirtualAllocationCreateInfo* VMA_NOT_NULL pCreateInfo,\n    VmaVirtualAllocation VMA_NULLABLE_NON_DISPATCHABLE* VMA_NOT_NULL pAllocation,\n    VkDeviceSize* VMA_NULLABLE pOffset);\n\n/** \\brief Frees virtual allocation inside given #VmaVirtualBlock.\n\nIt is correct to call this function with `allocation == VK_NULL_HANDLE` - it does nothing.\n*/\nVMA_CALL_PRE void VMA_CALL_POST vmaVirtualFree(\n    VmaVirtualBlock VMA_NOT_NULL virtualBlock,\n    VmaVirtualAllocation VMA_NULLABLE_NON_DISPATCHABLE allocation);\n\n/** \\brief Frees all virtual allocations inside given #VmaVirtualBlock.\n\nYou must either call this function or free each virtual allocation individually with vmaVirtualFree()\nbefore destroying a virtual block. Otherwise, an assert is called.\n\nIf you keep pointer to some additional metadata associated with your virtual allocation in its `pUserData`,\ndon't forget to free it as well.\n*/\nVMA_CALL_PRE void VMA_CALL_POST vmaClearVirtualBlock(\n    VmaVirtualBlock VMA_NOT_NULL virtualBlock);\n\n/** \\brief Changes custom pointer associated with given virtual allocation.\n */\nVMA_CALL_PRE void VMA_CALL_POST vmaSetVirtualAllocationUserData(\n    VmaVirtualBlock VMA_NOT_NULL virtualBlock,\n    VmaVirtualAllocation VMA_NOT_NULL_NON_DISPATCHABLE allocation,\n    void* VMA_NULLABLE pUserData);\n\n/** \\brief Calculates and returns statistics about virtual allocations and memory usage in given #VmaVirtualBlock.\n\nThis function is fast to call. For more detailed statistics, see vmaCalculateVirtualBlockStatistics().\n*/\nVMA_CALL_PRE void VMA_CALL_POST vmaGetVirtualBlockStatistics(\n    VmaVirtualBlock VMA_NOT_NULL virtualBlock,\n    VmaStatistics* VMA_NOT_NULL pStats);\n\n/** \\brief Calculates and returns detailed statistics about virtual allocations and memory usage in given #VmaVirtualBlock.\n\nThis function is slow to call. Use for debugging purposes.\nFor less detailed statistics, see vmaGetVirtualBlockStatistics().\n*/\nVMA_CALL_PRE void VMA_CALL_POST vmaCalculateVirtualBlockStatistics(\n    VmaVirtualBlock VMA_NOT_NULL virtualBlock,\n    VmaDetailedStatistics* VMA_NOT_NULL pStats);\n\n/** @} */\n\n#if VMA_STATS_STRING_ENABLED\n/**\n\\addtogroup group_stats\n@{\n*/\n\n/** \\brief Builds and returns a null-terminated string in JSON format with information about given #VmaVirtualBlock.\n\\param virtualBlock Virtual block.\n\\param[out] ppStatsString Returned string.\n\\param detailedMap Pass `VK_FALSE` to only obtain statistics as returned by vmaCalculateVirtualBlockStatistics(). Pass `VK_TRUE` to also obtain full list of allocations and free spaces.\n\nReturned string must be freed using vmaFreeVirtualBlockStatsString().\n*/\nVMA_CALL_PRE void VMA_CALL_POST vmaBuildVirtualBlockStatsString(\n    VmaVirtualBlock VMA_NOT_NULL virtualBlock,\n    char* VMA_NULLABLE* VMA_NOT_NULL ppStatsString,\n    VkBool32 detailedMap);\n\n/// Frees a string returned by vmaBuildVirtualBlockStatsString().\nVMA_CALL_PRE void VMA_CALL_POST vmaFreeVirtualBlockStatsString(\n    VmaVirtualBlock VMA_NOT_NULL virtualBlock,\n    char* VMA_NULLABLE pStatsString);\n\n/** \\brief Builds and returns statistics as a null-terminated string in JSON format.\n\\param allocator\n\\param[out] ppStatsString Must be freed using vmaFreeStatsString() function.\n\\param detailedMap\n*/\nVMA_CALL_PRE void VMA_CALL_POST vmaBuildStatsString(\n    VmaAllocator VMA_NOT_NULL allocator,\n    char* VMA_NULLABLE* VMA_NOT_NULL ppStatsString,\n    VkBool32 detailedMap);\n\nVMA_CALL_PRE void VMA_CALL_POST vmaFreeStatsString(\n    VmaAllocator VMA_NOT_NULL allocator,\n    char* VMA_NULLABLE pStatsString);\n\n/** @} */\n\n#endif // VMA_STATS_STRING_ENABLED\n\n#endif // _VMA_FUNCTION_HEADERS\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif // AMD_VULKAN_MEMORY_ALLOCATOR_H\n\n////////////////////////////////////////////////////////////////////////////////\n////////////////////////////////////////////////////////////////////////////////\n//\n//    IMPLEMENTATION\n//\n////////////////////////////////////////////////////////////////////////////////\n////////////////////////////////////////////////////////////////////////////////\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#include <utility>\n#include <type_traits>\n\n#ifdef _MSC_VER\n#include <intrin.h> // For functions like __popcnt, _BitScanForward etc.\n#endif\n#if __cplusplus >= 202002L || _MSVC_LANG >= 202002L // C++20\n#include <bit> // For std::popcount\n#endif\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#ifndef _VMA_CONFIGURATION\n\n/*\nDefine this macro to 1 to make the library fetch pointers to Vulkan functions\ninternally, like:\n\n    vulkanFunctions.vkAllocateMemory = &vkAllocateMemory;\n*/\n#if !defined(VMA_STATIC_VULKAN_FUNCTIONS) && !defined(VK_NO_PROTOTYPES)\n#define VMA_STATIC_VULKAN_FUNCTIONS 1\n#endif\n\n/*\nDefine this macro to 1 to make the library fetch pointers to Vulkan functions\ninternally, like:\n\n    vulkanFunctions.vkAllocateMemory = (PFN_vkAllocateMemory)vkGetDeviceProcAddr(device, \"vkAllocateMemory\");\n\nTo use this feature in new versions of VMA you now have to pass\nVmaVulkanFunctions::vkGetInstanceProcAddr and vkGetDeviceProcAddr as\nVmaAllocatorCreateInfo::pVulkanFunctions. Other members can be null.\n*/\n#if !defined(VMA_DYNAMIC_VULKAN_FUNCTIONS)\n#define VMA_DYNAMIC_VULKAN_FUNCTIONS 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 is always 199711L, despite shared_mutex works since Visual Studio 2015 Update 2.\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/*\nDefine this macro to include custom header files without having to edit this file directly, e.g.:\n\n    // Inside of \"my_vma_configuration_user_includes.h\":\n\n    #include \"my_custom_assert.h\" // for MY_CUSTOM_ASSERT\n    #include \"my_custom_min.h\" // for my_custom_min\n    #include <algorithm>\n    #include <mutex>\n\n    // Inside a different file, which includes \"vk_mem_alloc.h\":\n\n    #define VMA_CONFIGURATION_USER_INCLUDES_H \"my_vma_configuration_user_includes.h\"\n    #define VMA_ASSERT(expr) MY_CUSTOM_ASSERT(expr)\n    #define VMA_MIN(v1, v2)  (my_custom_min(v1, v2))\n    #include \"vk_mem_alloc.h\"\n    ...\n\nThe following headers are used in this CONFIGURATION section only, so feel free to\nremove them if not needed.\n*/\n#if !defined(VMA_CONFIGURATION_USER_INCLUDES_H)\n#include <cassert> // for assert\n#include <algorithm> // for min, max\n#include <mutex>\n#else\n#include VMA_CONFIGURATION_USER_INCLUDES_H\n#endif\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// Normal assert to check for programmer's errors, especially in Debug configuration.\n#ifndef VMA_ASSERT\n#ifdef NDEBUG\n#define VMA_ASSERT(expr)\n#else\n#define VMA_ASSERT(expr)         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 NDEBUG\n#define VMA_HEAVY_ASSERT(expr)\n#else\n#define VMA_HEAVY_ASSERT(expr)   //VMA_ASSERT(expr)\n#endif\n#endif\n\n// If your compiler is not compatible with C++17 and definition of\n// aligned_alloc() function is missing, uncommenting following line may help:\n\n//#include <malloc.h>\n\n#if defined(__ANDROID_API__) && (__ANDROID_API__ < 16)\n#include <cstdlib>\nstatic void* vma_aligned_alloc(size_t alignment, size_t size)\n{\n  // alignment must be >= sizeof(void*)\n  if(alignment < sizeof(void*))\n  {\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>\n\n#if defined(__APPLE__)\n#include <AvailabilityMacros.h>\n#endif\n\nstatic void* vma_aligned_alloc(size_t alignment, size_t size)\n{\n  // Unfortunately, aligned_alloc causes VMA to crash due to it returning null pointers. (At least under 11.4)\n  // Therefore, for now disable this specific exception until a proper solution is found.\n  //#if defined(__APPLE__) && (defined(MAC_OS_X_VERSION_10_16) || defined(__IPHONE_14_0))\n  //#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_16 || __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_14_0\n  //    // For C++14, usr/include/malloc/_malloc.h declares aligned_alloc()) only\n  //    // with the MacOSX11.0 SDK in Xcode 12 (which is what adds\n  //    // MAC_OS_X_VERSION_10_16), even though the function is marked\n  //    // available for 10.15. That is why the preprocessor checks for 10.16 but\n  //    // the __builtin_available checks for 10.15.\n  //    // People who use C++17 could call aligned_alloc with the 10.15 SDK already.\n  //    if (__builtin_available(macOS 10.15, iOS 13, *))\n  //        return aligned_alloc(alignment, size);\n  //#endif\n  //#endif\n\n  // alignment must be >= sizeof(void*)\n  if(alignment < sizeof(void*))\n  {\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#elif defined(_WIN32)\nstatic void* vma_aligned_alloc(size_t alignment, size_t size)\n{\n  return _aligned_malloc(size, alignment);\n}\n#elif __cplusplus >= 201703L // Compiler conforms to C++17.\nstatic void* vma_aligned_alloc(size_t alignment, size_t size)\n{\n  return aligned_alloc(alignment, size);\n}\n#else\nstatic void* vma_aligned_alloc(size_t alignment, size_t size)\n{\n  VMA_ASSERT(0 && \"Could not implement aligned_alloc automatically. Please enable C++17 or later in your compiler or provide custom implementation of macro VMA_SYSTEM_ALIGNED_MALLOC (and VMA_SYSTEM_ALIGNED_FREE if needed) using the API of your system.\");\n  return VMA_NULL;\n}\n#endif\n\n#if defined(_WIN32)\nstatic void vma_aligned_free(void* ptr)\n{\n  _aligned_free(ptr);\n}\n#else\nstatic void vma_aligned_free(void* VMA_NULLABLE ptr)\n{\n  free(ptr);\n}\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#define VMA_SYSTEM_ALIGNED_MALLOC(size, alignment) vma_aligned_alloc((alignment), (size))\n#endif\n\n#ifndef VMA_SYSTEM_ALIGNED_FREE\n// VMA_SYSTEM_FREE is the old name, but might have been defined by the user\n#if defined(VMA_SYSTEM_FREE)\n#define VMA_SYSTEM_ALIGNED_FREE(ptr)     VMA_SYSTEM_FREE(ptr)\n#else\n#define VMA_SYSTEM_ALIGNED_FREE(ptr)     vma_aligned_free(ptr)\n#endif\n#endif\n\n#ifndef VMA_COUNT_BITS_SET\n// Returns number of bits set to 1 in (v)\n#define VMA_COUNT_BITS_SET(v) VmaCountBitsSet(v)\n#endif\n\n#ifndef VMA_BITSCAN_LSB\n// Scans integer for index of first nonzero value from the Least Significant Bit (LSB). If mask is 0 then returns UINT8_MAX\n#define VMA_BITSCAN_LSB(mask) VmaBitScanLSB(mask)\n#endif\n\n#ifndef VMA_BITSCAN_MSB\n// Scans integer for index of first nonzero value from the Most Significant Bit (MSB). If mask is 0 then returns UINT8_MAX\n#define VMA_BITSCAN_MSB(mask) VmaBitScanMSB(mask)\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_FORMAT\n#define VMA_DEBUG_LOG_FORMAT(format, ...)\n/*\n#define VMA_DEBUG_LOG_FORMAT(format, ...) do { \\\n    printf((format), __VA_ARGS__); \\\n    printf(\"\\n\"); \\\n} while(false)\n*/\n#endif\n\n#ifndef VMA_DEBUG_LOG\n#define VMA_DEBUG_LOG(str)   VMA_DEBUG_LOG_FORMAT(\"%s\", (str))\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* VMA_NOT_NULL outStr, size_t strLen, uint32_t num)\n{\n  snprintf(outStr, strLen, \"%u\", static_cast<unsigned int>(num));\n}\nstatic inline void VmaUint64ToStr(char* VMA_NOT_NULL 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* VMA_NOT_NULL 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{\n public:\n  void Lock() { m_Mutex.lock(); }\n  void Unlock() { m_Mutex.unlock(); }\n  bool TryLock() { return m_Mutex.try_lock(); }\n private:\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{\n public:\n  void LockRead() { m_Mutex.lock_shared(); }\n  void UnlockRead() { m_Mutex.unlock_shared(); }\n  bool TryLockRead() { return m_Mutex.try_lock_shared(); }\n  void LockWrite() { m_Mutex.lock(); }\n  void UnlockWrite() { m_Mutex.unlock(); }\n  bool TryLockWrite() { return m_Mutex.try_lock(); }\n private:\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{\n public:\n  VmaRWMutex() { InitializeSRWLock(&m_Lock); }\n  void LockRead() { AcquireSRWLockShared(&m_Lock); }\n  void UnlockRead() { ReleaseSRWLockShared(&m_Lock); }\n  bool TryLockRead() { return TryAcquireSRWLockShared(&m_Lock) != FALSE; }\n  void LockWrite() { AcquireSRWLockExclusive(&m_Lock); }\n  void UnlockWrite() { ReleaseSRWLockExclusive(&m_Lock); }\n  bool TryLockWrite() { return TryAcquireSRWLockExclusive(&m_Lock) != FALSE; }\n private:\n  SRWLOCK m_Lock;\n};\n#define VMA_RW_MUTEX VmaRWMutex\n#else\n// Less efficient fallback: Use normal mutex.\nclass VmaRWMutex\n{\n public:\n  void LockRead() { m_Mutex.Lock(); }\n  void UnlockRead() { m_Mutex.Unlock(); }\n  bool TryLockRead() { return m_Mutex.TryLock(); }\n  void LockWrite() { m_Mutex.Lock(); }\n  void UnlockWrite() { m_Mutex.Unlock(); }\n  bool TryLockWrite() { return m_Mutex.TryLock(); }\n private:\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_MIN_ALIGNMENT\n/**\nMinimum alignment of all allocations, in bytes.\nSet to more than 1 for debugging purposes. Must be power of two.\n*/\n#ifdef VMA_DEBUG_ALIGNMENT // Old name\n#define VMA_MIN_ALIGNMENT VMA_DEBUG_ALIGNMENT\n#else\n#define VMA_MIN_ALIGNMENT (1)\n#endif\n#endif\n\n#ifndef VMA_DEBUG_MARGIN\n/**\nMinimum margin 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 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_DEBUG_DONT_EXCEED_MAX_MEMORY_ALLOCATION_COUNT\n/*\nSet this to 1 to make VMA never exceed VkPhysicalDeviceLimits::maxMemoryAllocationCount\nand return error instead of leaving up to Vulkan implementation what to do in such cases.\n*/\n#define VMA_DEBUG_DONT_EXCEED_MAX_MEMORY_ALLOCATION_COUNT (0)\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/*\nMapping hysteresis is a logic that launches when vmaMapMemory/vmaUnmapMemory is called\nor a persistently mapped allocation is created and destroyed several times in a row.\nIt keeps additional +1 mapping of a device memory block to prevent calling actual\nvkMapMemory/vkUnmapMemory too many times, which may improve performance and help\ntools like RenderDoc.\n*/\n#ifndef VMA_MAPPING_HYSTERESIS_ENABLED\n#define VMA_MAPPING_HYSTERESIS_ENABLED 1\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\n#define VMA_VALIDATE(cond) do { if(!(cond)) { \\\n        VMA_ASSERT(0 && \"Validation failed: \" #cond); \\\n        return false; \\\n    } } while(false)\n\n/*******************************************************************************\nEND OF CONFIGURATION\n*/\n#endif // _VMA_CONFIGURATION\n\n\nstatic const uint8_t VMA_ALLOCATION_FILL_PATTERN_CREATED = 0xDC;\nstatic const uint8_t VMA_ALLOCATION_FILL_PATTERN_DESTROYED = 0xEF;\n// Decimal 2139416166, float NaN, little-endian binary 66 E6 84 7F.\nstatic const uint32_t VMA_CORRUPTION_DETECTION_MAGIC_VALUE = 0x7F84E666;\n\n// Copy of some Vulkan definitions so we don't need to check their existence just to handle few constants.\nstatic const uint32_t VK_MEMORY_PROPERTY_DEVICE_COHERENT_BIT_AMD_COPY = 0x00000040;\nstatic const uint32_t VK_MEMORY_PROPERTY_DEVICE_UNCACHED_BIT_AMD_COPY = 0x00000080;\nstatic const uint32_t VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT_COPY = 0x00020000;\nstatic const uint32_t VK_IMAGE_CREATE_DISJOINT_BIT_COPY = 0x00000200;\nstatic const int32_t VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT_COPY = 1000158000;\nstatic const uint32_t VMA_ALLOCATION_INTERNAL_STRATEGY_MIN_OFFSET = 0x10000000u;\nstatic const uint32_t VMA_ALLOCATION_TRY_COUNT = 32;\nstatic const uint32_t VMA_VENDOR_ID_AMD = 4098;\n\n// This one is tricky. Vulkan specification defines this code as available since\n// Vulkan 1.0, but doesn't actually define it in Vulkan SDK earlier than 1.2.131.\n// See pull request #207.\n#define VK_ERROR_UNKNOWN_COPY ((VkResult)-13)\n\n\n#if VMA_STATS_STRING_ENABLED\n// Correspond to values of enum VmaSuballocationType.\nstatic const char* VMA_SUBALLOCATION_TYPE_NAMES[] =\n    {\n        \"FREE\",\n        \"UNKNOWN\",\n        \"BUFFER\",\n        \"IMAGE_UNKNOWN\",\n        \"IMAGE_LINEAR\",\n        \"IMAGE_OPTIMAL\",\n};\n#endif\n\nstatic VkAllocationCallbacks VmaEmptyAllocationCallbacks =\n    { VMA_NULL, VMA_NULL, VMA_NULL, VMA_NULL, VMA_NULL, VMA_NULL };\n\n\n#ifndef _VMA_ENUM_DECLARATIONS\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\nenum VMA_CACHE_OPERATION\n{\n  VMA_CACHE_FLUSH,\n  VMA_CACHE_INVALIDATE\n};\n\nenum class VmaAllocationRequestType\n{\n  Normal,\n  TLSF,\n  // Used by \"Linear\" algorithm.\n  UpperAddress,\n  EndOf1st,\n  EndOf2nd,\n};\n\n#endif // _VMA_ENUM_DECLARATIONS\n\n#ifndef _VMA_FORWARD_DECLARATIONS\n// Opaque handle used by allocation algorithms to identify single allocation in any conforming way.\nVK_DEFINE_NON_DISPATCHABLE_HANDLE(VmaAllocHandle)\n\nstruct VmaMutexLock;\nstruct VmaMutexLockRead;\nstruct VmaMutexLockWrite;\n\ntemplate<typename T>\nstruct AtomicTransactionalIncrement;\n\ntemplate<typename T>\nstruct VmaStlAllocator;\n\ntemplate<typename T, typename AllocatorT>\nclass VmaVector;\n\ntemplate<typename T, typename AllocatorT, size_t N>\nclass VmaSmallVector;\n\ntemplate<typename T>\nclass VmaPoolAllocator;\n\ntemplate<typename T>\nstruct VmaListItem;\n\ntemplate<typename T>\nclass VmaRawList;\n\ntemplate<typename T, typename AllocatorT>\nclass VmaList;\n\ntemplate<typename ItemTypeTraits>\nclass VmaIntrusiveLinkedList;\n\n// Unused in this version\n#if 0\ntemplate<typename T1, typename T2>\nstruct VmaPair;\ntemplate<typename FirstT, typename SecondT>\nstruct VmaPairFirstLess;\n\ntemplate<typename KeyT, typename ValueT>\nclass VmaMap;\n#endif\n\n#if VMA_STATS_STRING_ENABLED\nclass VmaStringBuilder;\nclass VmaJsonWriter;\n#endif\n\nclass VmaDeviceMemoryBlock;\n\nstruct VmaDedicatedAllocationListItemTraits;\nclass VmaDedicatedAllocationList;\n\nstruct VmaSuballocation;\nstruct VmaSuballocationOffsetLess;\nstruct VmaSuballocationOffsetGreater;\nstruct VmaSuballocationItemSizeLess;\n\ntypedef VmaList<VmaSuballocation, VmaStlAllocator<VmaSuballocation>> VmaSuballocationList;\n\nstruct VmaAllocationRequest;\n\nclass VmaBlockMetadata;\nclass VmaBlockMetadata_Linear;\nclass VmaBlockMetadata_TLSF;\n\nclass VmaBlockVector;\n\nstruct VmaPoolListItemTraits;\n\nstruct VmaCurrentBudgetData;\n\nclass VmaAllocationObjectAllocator;\n\n#endif // _VMA_FORWARD_DECLARATIONS\n\n\n#ifndef _VMA_FUNCTIONS\n\n/*\nReturns number of bits set to 1 in (v).\n\nOn specific platforms and compilers you can use instrinsics like:\n\nVisual Studio:\n    return __popcnt(v);\nGCC, Clang:\n    return static_cast<uint32_t>(__builtin_popcount(v));\n\nDefine macro VMA_COUNT_BITS_SET to provide your optimized implementation.\nBut you need to check in runtime whether user's CPU supports these, as some old processors don't.\n*/\nstatic inline uint32_t VmaCountBitsSet(uint32_t v)\n{\n#if __cplusplus >= 202002L || _MSVC_LANG >= 202002L // C++20\n  return std::popcount(v);\n#else\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#endif\n}\n\nstatic inline uint8_t VmaBitScanLSB(uint64_t mask)\n{\n#if defined(_MSC_VER) && defined(_WIN64)\n  unsigned long pos;\n  if (_BitScanForward64(&pos, mask))\n    return static_cast<uint8_t>(pos);\n  return UINT8_MAX;\n#elif defined __GNUC__ || defined __clang__\n  return static_cast<uint8_t>(__builtin_ffsll(mask)) - 1U;\n#else\n  uint8_t pos = 0;\n  uint64_t bit = 1;\n  do\n  {\n    if (mask & bit)\n      return pos;\n    bit <<= 1;\n  } while (pos++ < 63);\n  return UINT8_MAX;\n#endif\n}\n\nstatic inline uint8_t VmaBitScanLSB(uint32_t mask)\n{\n#ifdef _MSC_VER\n  unsigned long pos;\n  if (_BitScanForward(&pos, mask))\n    return static_cast<uint8_t>(pos);\n  return UINT8_MAX;\n#elif defined __GNUC__ || defined __clang__\n  return static_cast<uint8_t>(__builtin_ffs(mask)) - 1U;\n#else\n  uint8_t pos = 0;\n  uint32_t bit = 1;\n  do\n  {\n    if (mask & bit)\n      return pos;\n    bit <<= 1;\n  } while (pos++ < 31);\n  return UINT8_MAX;\n#endif\n}\n\nstatic inline uint8_t VmaBitScanMSB(uint64_t mask)\n{\n#if defined(_MSC_VER) && defined(_WIN64)\n  unsigned long pos;\n  if (_BitScanReverse64(&pos, mask))\n    return static_cast<uint8_t>(pos);\n#elif defined __GNUC__ || defined __clang__\n  if (mask)\n    return 63 - static_cast<uint8_t>(__builtin_clzll(mask));\n#else\n  uint8_t pos = 63;\n  uint64_t bit = 1ULL << 63;\n  do\n  {\n    if (mask & bit)\n      return pos;\n    bit >>= 1;\n  } while (pos-- > 0);\n#endif\n  return UINT8_MAX;\n}\n\nstatic inline uint8_t VmaBitScanMSB(uint32_t mask)\n{\n#ifdef _MSC_VER\n  unsigned long pos;\n  if (_BitScanReverse(&pos, mask))\n    return static_cast<uint8_t>(pos);\n#elif defined __GNUC__ || defined __clang__\n  if (mask)\n    return 31 - static_cast<uint8_t>(__builtin_clz(mask));\n#else\n  uint8_t pos = 31;\n  uint32_t bit = 1UL << 31;\n  do\n  {\n    if (mask & bit)\n      return pos;\n    bit >>= 1;\n  } while (pos-- > 0);\n#endif\n  return UINT8_MAX;\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// 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 alignment)\n{\n  VMA_HEAVY_ASSERT(VmaIsPow2(alignment));\n  return (val + alignment - 1) & ~(alignment - 1);\n}\n\n// Aligns given value down to nearest multiply of align value. For example: VmaAlignDown(11, 8) = 8.\n// Use types like uint32_t, uint64_t as T.\ntemplate <typename T>\nstatic inline T VmaAlignDown(T val, T alignment)\n{\n  VMA_HEAVY_ASSERT(VmaIsPow2(alignment));\n  return val & ~(alignment - 1);\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// Divide by 'y' and round up to nearest integer.\ntemplate <typename T>\nstatic inline T VmaDivideRoundingUp(T x, T y)\n{\n  return (x + y - (T)1) / y;\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}\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}\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/*\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\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  {\n    VMA_SWAP(suballocType1, suballocType2);\n  }\n\n  switch (suballocType1)\n  {\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  {\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  {\n    if (*pSrc != VMA_CORRUPTION_DETECTION_MAGIC_VALUE)\n    {\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\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  {\n    const size_t mid = down + (up - down) / 2;  // Overflow-safe midpoint calculation\n    if (cmp(*(beg + mid), key))\n    {\n      down = mid + 1;\n    }\n    else\n    {\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  {\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  {\n    const T iPtr = arr[i];\n    if (iPtr == VMA_NULL)\n    {\n      return false;\n    }\n    for (uint32_t j = i + 1; j < count; ++j)\n    {\n      if (iPtr == arr[j])\n      {\n        return false;\n      }\n    }\n  }\n  return true;\n}\n\ntemplate<typename MainT, typename NewT>\nstatic inline void VmaPnextChainPushFront(MainT* mainStruct, NewT* newStruct)\n{\n  newStruct->pNext = mainStruct->pNext;\n  mainStruct->pNext = newStruct;\n}\n\n// This is the main algorithm that guides the selection of a memory type best for an allocation -\n// converts usage to required/preferred/not preferred flags.\nstatic bool FindMemoryPreferences(\n    bool isIntegratedGPU,\n    const VmaAllocationCreateInfo& allocCreateInfo,\n    VkFlags bufImgUsage, // VkBufferCreateInfo::usage or VkImageCreateInfo::usage. UINT32_MAX if unknown.\n    VkMemoryPropertyFlags& outRequiredFlags,\n    VkMemoryPropertyFlags& outPreferredFlags,\n    VkMemoryPropertyFlags& outNotPreferredFlags)\n{\n  outRequiredFlags = allocCreateInfo.requiredFlags;\n  outPreferredFlags = allocCreateInfo.preferredFlags;\n  outNotPreferredFlags = 0;\n\n  switch(allocCreateInfo.usage)\n  {\n    case VMA_MEMORY_USAGE_UNKNOWN:\n      break;\n    case VMA_MEMORY_USAGE_GPU_ONLY:\n      if(!isIntegratedGPU || (outPreferredFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) == 0)\n      {\n        outPreferredFlags |= VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;\n      }\n      break;\n    case VMA_MEMORY_USAGE_CPU_ONLY:\n      outRequiredFlags |= VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;\n      break;\n    case VMA_MEMORY_USAGE_CPU_TO_GPU:\n      outRequiredFlags |= VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT;\n      if(!isIntegratedGPU || (outPreferredFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) == 0)\n      {\n        outPreferredFlags |= VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;\n      }\n      break;\n    case VMA_MEMORY_USAGE_GPU_TO_CPU:\n      outRequiredFlags |= VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT;\n      outPreferredFlags |= VK_MEMORY_PROPERTY_HOST_CACHED_BIT;\n      break;\n    case VMA_MEMORY_USAGE_CPU_COPY:\n      outNotPreferredFlags |= VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;\n      break;\n    case VMA_MEMORY_USAGE_GPU_LAZILY_ALLOCATED:\n      outRequiredFlags |= VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT;\n      break;\n    case VMA_MEMORY_USAGE_AUTO:\n    case VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE:\n    case VMA_MEMORY_USAGE_AUTO_PREFER_HOST:\n    {\n      if(bufImgUsage == UINT32_MAX)\n      {\n        VMA_ASSERT(0 && \"VMA_MEMORY_USAGE_AUTO* values can only be used with functions like vmaCreateBuffer, vmaCreateImage so that the details of the created resource are known.\");\n        return false;\n      }\n      // This relies on values of VK_IMAGE_USAGE_TRANSFER* being the same VK_BUFFER_IMAGE_TRANSFER*.\n      const bool deviceAccess = (bufImgUsage & ~(VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT)) != 0;\n      const bool hostAccessSequentialWrite = (allocCreateInfo.flags & VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT) != 0;\n      const bool hostAccessRandom = (allocCreateInfo.flags & VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT) != 0;\n      const bool hostAccessAllowTransferInstead = (allocCreateInfo.flags & VMA_ALLOCATION_CREATE_HOST_ACCESS_ALLOW_TRANSFER_INSTEAD_BIT) != 0;\n      const bool preferDevice = allocCreateInfo.usage == VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;\n      const bool preferHost = allocCreateInfo.usage == VMA_MEMORY_USAGE_AUTO_PREFER_HOST;\n\n      // CPU random access - e.g. a buffer written to or transferred from GPU to read back on CPU.\n      if(hostAccessRandom)\n      {\n        if(!isIntegratedGPU && deviceAccess && hostAccessAllowTransferInstead && !preferHost)\n        {\n          // Nice if it will end up in HOST_VISIBLE, but more importantly prefer DEVICE_LOCAL.\n          // Omitting HOST_VISIBLE here is intentional.\n          // In case there is DEVICE_LOCAL | HOST_VISIBLE | HOST_CACHED, it will pick that one.\n          // Otherwise, this will give same weight to DEVICE_LOCAL as HOST_VISIBLE | HOST_CACHED and select the former if occurs first on the list.\n          outPreferredFlags |= VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT;\n        }\n        else\n        {\n          // Always CPU memory, cached.\n          outRequiredFlags |= VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT;\n        }\n      }\n      // CPU sequential write - may be CPU or host-visible GPU memory, uncached and write-combined.\n      else if(hostAccessSequentialWrite)\n      {\n        // Want uncached and write-combined.\n        outNotPreferredFlags |= VK_MEMORY_PROPERTY_HOST_CACHED_BIT;\n\n        if(!isIntegratedGPU && deviceAccess && hostAccessAllowTransferInstead && !preferHost)\n        {\n          outPreferredFlags |= VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT;\n        }\n        else\n        {\n          outRequiredFlags |= VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT;\n          // Direct GPU access, CPU sequential write (e.g. a dynamic uniform buffer updated every frame)\n          if(deviceAccess)\n          {\n            // Could go to CPU memory or GPU BAR/unified. Up to the user to decide. If no preference, choose GPU memory.\n            if(preferHost)\n              outNotPreferredFlags |= VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;\n            else\n              outPreferredFlags |= VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;\n          }\n          // GPU no direct access, CPU sequential write (e.g. an upload buffer to be transferred to the GPU)\n          else\n          {\n            // Could go to CPU memory or GPU BAR/unified. Up to the user to decide. If no preference, choose CPU memory.\n            if(preferDevice)\n              outPreferredFlags |= VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;\n            else\n              outNotPreferredFlags |= VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;\n          }\n        }\n      }\n      // No CPU access\n      else\n      {\n        // GPU access, no CPU access (e.g. a color attachment image) - prefer GPU memory\n        if(deviceAccess)\n        {\n          // ...unless there is a clear preference from the user not to do so.\n          if(preferHost)\n            outNotPreferredFlags |= VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;\n          else\n            outPreferredFlags |= VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;\n        }\n        // No direct GPU access, no CPU access, just transfers.\n        // It may be staging copy intended for e.g. preserving image for next frame (then better GPU memory) or\n        // a \"swap file\" copy to free some GPU memory (then better CPU memory).\n        // Up to the user to decide. If no preferece, assume the former and choose GPU memory.\n        if(preferHost)\n          outNotPreferredFlags |= VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;\n        else\n          outPreferredFlags |= VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;\n      }\n      break;\n    }\n    default:\n      VMA_ASSERT(0);\n  }\n\n  // Avoid DEVICE_COHERENT unless explicitly requested.\n  if(((allocCreateInfo.requiredFlags | allocCreateInfo.preferredFlags) &\n       (VK_MEMORY_PROPERTY_DEVICE_COHERENT_BIT_AMD_COPY | VK_MEMORY_PROPERTY_DEVICE_UNCACHED_BIT_AMD_COPY)) == 0)\n  {\n    outNotPreferredFlags |= VK_MEMORY_PROPERTY_DEVICE_UNCACHED_BIT_AMD_COPY;\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  void* result = VMA_NULL;\n  if ((pAllocationCallbacks != VMA_NULL) &&\n      (pAllocationCallbacks->pfnAllocation != VMA_NULL))\n  {\n    result = (*pAllocationCallbacks->pfnAllocation)(\n        pAllocationCallbacks->pUserData,\n        size,\n        alignment,\n        VK_SYSTEM_ALLOCATION_SCOPE_OBJECT);\n  }\n  else\n  {\n    result = VMA_SYSTEM_ALIGNED_MALLOC(size, alignment);\n  }\n  VMA_ASSERT(result != VMA_NULL && \"CPU memory allocation failed.\");\n  return result;\n}\n\nstatic void VmaFree(const VkAllocationCallbacks* pAllocationCallbacks, void* ptr)\n{\n  if ((pAllocationCallbacks != VMA_NULL) &&\n      (pAllocationCallbacks->pfnFree != VMA_NULL))\n  {\n    (*pAllocationCallbacks->pfnFree)(pAllocationCallbacks->pUserData, ptr);\n  }\n  else\n  {\n    VMA_SYSTEM_ALIGNED_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  {\n    for (size_t i = count; i--; )\n    {\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  {\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  }\n  return VMA_NULL;\n}\n\n#if VMA_STATS_STRING_ENABLED\nstatic char* VmaCreateStringCopy(const VkAllocationCallbacks* allocs, const char* srcStr, size_t strLen)\n{\n  if (srcStr != VMA_NULL)\n  {\n    char* const result = vma_new_array(allocs, char, strLen + 1);\n    memcpy(result, srcStr, strLen);\n    result[strLen] = '\\0';\n    return result;\n  }\n  return VMA_NULL;\n}\n#endif // VMA_STATS_STRING_ENABLED\n\nstatic void VmaFreeString(const VkAllocationCallbacks* allocs, char* str)\n{\n  if (str != VMA_NULL)\n  {\n    const size_t len = strlen(str);\n    vma_delete_array(allocs, str, len + 1);\n  }\n}\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  {\n    size_t indexToRemove = it - vector.begin();\n    VmaVectorRemove(vector, indexToRemove);\n    return true;\n  }\n  return false;\n}\n#endif // _VMA_FUNCTIONS\n\n#ifndef _VMA_STATISTICS_FUNCTIONS\n\nstatic void VmaClearStatistics(VmaStatistics& outStats)\n{\n  outStats.blockCount = 0;\n  outStats.allocationCount = 0;\n  outStats.blockBytes = 0;\n  outStats.allocationBytes = 0;\n}\n\nstatic void VmaAddStatistics(VmaStatistics& inoutStats, const VmaStatistics& src)\n{\n  inoutStats.blockCount += src.blockCount;\n  inoutStats.allocationCount += src.allocationCount;\n  inoutStats.blockBytes += src.blockBytes;\n  inoutStats.allocationBytes += src.allocationBytes;\n}\n\nstatic void VmaClearDetailedStatistics(VmaDetailedStatistics& outStats)\n{\n  VmaClearStatistics(outStats.statistics);\n  outStats.unusedRangeCount = 0;\n  outStats.allocationSizeMin = VK_WHOLE_SIZE;\n  outStats.allocationSizeMax = 0;\n  outStats.unusedRangeSizeMin = VK_WHOLE_SIZE;\n  outStats.unusedRangeSizeMax = 0;\n}\n\nstatic void VmaAddDetailedStatisticsAllocation(VmaDetailedStatistics& inoutStats, VkDeviceSize size)\n{\n  inoutStats.statistics.allocationCount++;\n  inoutStats.statistics.allocationBytes += size;\n  inoutStats.allocationSizeMin = VMA_MIN(inoutStats.allocationSizeMin, size);\n  inoutStats.allocationSizeMax = VMA_MAX(inoutStats.allocationSizeMax, size);\n}\n\nstatic void VmaAddDetailedStatisticsUnusedRange(VmaDetailedStatistics& inoutStats, VkDeviceSize size)\n{\n  inoutStats.unusedRangeCount++;\n  inoutStats.unusedRangeSizeMin = VMA_MIN(inoutStats.unusedRangeSizeMin, size);\n  inoutStats.unusedRangeSizeMax = VMA_MAX(inoutStats.unusedRangeSizeMax, size);\n}\n\nstatic void VmaAddDetailedStatistics(VmaDetailedStatistics& inoutStats, const VmaDetailedStatistics& src)\n{\n  VmaAddStatistics(inoutStats.statistics, src.statistics);\n  inoutStats.unusedRangeCount += src.unusedRangeCount;\n  inoutStats.allocationSizeMin = VMA_MIN(inoutStats.allocationSizeMin, src.allocationSizeMin);\n  inoutStats.allocationSizeMax = VMA_MAX(inoutStats.allocationSizeMax, src.allocationSizeMax);\n  inoutStats.unusedRangeSizeMin = VMA_MIN(inoutStats.unusedRangeSizeMin, src.unusedRangeSizeMin);\n  inoutStats.unusedRangeSizeMax = VMA_MAX(inoutStats.unusedRangeSizeMax, src.unusedRangeSizeMax);\n}\n\n#endif // _VMA_STATISTICS_FUNCTIONS\n\n#ifndef _VMA_MUTEX_LOCK\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)\n public:\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() {  if (m_pMutex) { m_pMutex->Unlock(); } }\n\n private:\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)\n public:\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(); } }\n\n private:\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)\n public:\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(); } }\n\n private:\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#endif // _VMA_MUTEX_LOCK\n\n#ifndef _VMA_ATOMIC_TRANSACTIONAL_INCREMENT\n// An object that increments given atomic but decrements it back in the destructor unless Commit() is called.\ntemplate<typename T>\nstruct AtomicTransactionalIncrement\n{\n public:\n  typedef std::atomic<T> AtomicT;\n\n  ~AtomicTransactionalIncrement()\n  {\n    if(m_Atomic)\n      --(*m_Atomic);\n  }\n\n  void Commit() { m_Atomic = nullptr; }\n  T Increment(AtomicT* atomic)\n  {\n    m_Atomic = atomic;\n    return m_Atomic->fetch_add(1);\n  }\n\n private:\n  AtomicT* m_Atomic = nullptr;\n};\n#endif // _VMA_ATOMIC_TRANSACTIONAL_INCREMENT\n\n#ifndef _VMA_STL_ALLOCATOR\n// STL-compatible allocator.\ntemplate<typename T>\nstruct VmaStlAllocator\n{\n  const VkAllocationCallbacks* const m_pCallbacks;\n  typedef T value_type;\n\n  VmaStlAllocator(const VkAllocationCallbacks* pCallbacks) : m_pCallbacks(pCallbacks) {}\n  template<typename U>\n  VmaStlAllocator(const VmaStlAllocator<U>& src) : m_pCallbacks(src.m_pCallbacks) {}\n  VmaStlAllocator(const VmaStlAllocator&) = default;\n  VmaStlAllocator& operator=(const VmaStlAllocator&) = delete;\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#endif // _VMA_STL_ALLOCATOR\n\n#ifndef _VMA_VECTOR\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{\n public:\n  typedef T value_type;\n  typedef T* iterator;\n  typedef const T* const_iterator;\n\n  VmaVector(const AllocatorT& allocator);\n  VmaVector(size_t count, const AllocatorT& allocator);\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) : VmaVector(count, allocator) {}\n  VmaVector(const VmaVector<T, AllocatorT>& src);\n  VmaVector& operator=(const VmaVector& rhs);\n  ~VmaVector() { VmaFree(m_Allocator.m_pCallbacks, m_pArray); }\n\n  bool empty() const { return m_Count == 0; }\n  size_t size() const { return m_Count; }\n  T* data() { return m_pArray; }\n  T& front() { VMA_HEAVY_ASSERT(m_Count > 0); return m_pArray[0]; }\n  T& back() { VMA_HEAVY_ASSERT(m_Count > 0); return m_pArray[m_Count - 1]; }\n  const T* data() const { return m_pArray; }\n  const T& front() const { VMA_HEAVY_ASSERT(m_Count > 0); return m_pArray[0]; }\n  const T& back() const { VMA_HEAVY_ASSERT(m_Count > 0); return m_pArray[m_Count - 1]; }\n\n  iterator begin() { return m_pArray; }\n  iterator end() { return m_pArray + m_Count; }\n  const_iterator cbegin() const { return m_pArray; }\n  const_iterator cend() const { return m_pArray + m_Count; }\n  const_iterator begin() const { return cbegin(); }\n  const_iterator end() const { return cend(); }\n\n  void pop_front() { VMA_HEAVY_ASSERT(m_Count > 0); remove(0); }\n  void pop_back() { VMA_HEAVY_ASSERT(m_Count > 0); resize(size() - 1); }\n  void push_front(const T& src) { insert(0, src); }\n\n  void push_back(const T& src);\n  void reserve(size_t newCapacity, bool freeMemory = false);\n  void resize(size_t newCount);\n  void clear() { resize(0); }\n  void shrink_to_fit();\n  void insert(size_t index, const T& src);\n  void remove(size_t index);\n\n  T& operator[](size_t index) { VMA_HEAVY_ASSERT(index < m_Count); return m_pArray[index]; }\n  const T& operator[](size_t index) const { VMA_HEAVY_ASSERT(index < m_Count); return m_pArray[index]; }\n\n private:\n  AllocatorT m_Allocator;\n  T* m_pArray;\n  size_t m_Count;\n  size_t m_Capacity;\n};\n\n#ifndef _VMA_VECTOR_FUNCTIONS\ntemplate<typename T, typename AllocatorT>\nVmaVector<T, AllocatorT>::VmaVector(const AllocatorT& allocator)\n    : m_Allocator(allocator),\n      m_pArray(VMA_NULL),\n      m_Count(0),\n      m_Capacity(0) {}\n\ntemplate<typename T, typename AllocatorT>\nVmaVector<T, AllocatorT>::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\ntemplate<typename T, typename AllocatorT>\nVmaVector<T, AllocatorT>::VmaVector(const VmaVector& 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  {\n    memcpy(m_pArray, src.m_pArray, m_Count * sizeof(T));\n  }\n}\n\ntemplate<typename T, typename AllocatorT>\nVmaVector<T, AllocatorT>& VmaVector<T, AllocatorT>::operator=(const VmaVector& rhs)\n{\n  if (&rhs != this)\n  {\n    resize(rhs.m_Count);\n    if (m_Count != 0)\n    {\n      memcpy(m_pArray, rhs.m_pArray, m_Count * sizeof(T));\n    }\n  }\n  return *this;\n}\n\ntemplate<typename T, typename AllocatorT>\nvoid VmaVector<T, AllocatorT>::push_back(const T& src)\n{\n  const size_t newIndex = size();\n  resize(newIndex + 1);\n  m_pArray[newIndex] = src;\n}\n\ntemplate<typename T, typename AllocatorT>\nvoid VmaVector<T, AllocatorT>::reserve(size_t newCapacity, bool freeMemory)\n{\n  newCapacity = VMA_MAX(newCapacity, m_Count);\n\n  if ((newCapacity < m_Capacity) && !freeMemory)\n  {\n    newCapacity = m_Capacity;\n  }\n\n  if (newCapacity != m_Capacity)\n  {\n    T* const newArray = newCapacity ? VmaAllocateArray<T>(m_Allocator, newCapacity) : VMA_NULL;\n    if (m_Count != 0)\n    {\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\ntemplate<typename T, typename AllocatorT>\nvoid VmaVector<T, AllocatorT>::resize(size_t newCount)\n{\n  size_t newCapacity = m_Capacity;\n  if (newCount > m_Capacity)\n  {\n    newCapacity = VMA_MAX(newCount, VMA_MAX(m_Capacity * 3 / 2, (size_t)8));\n  }\n\n  if (newCapacity != m_Capacity)\n  {\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    {\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\ntemplate<typename T, typename AllocatorT>\nvoid VmaVector<T, AllocatorT>::shrink_to_fit()\n{\n  if (m_Capacity > m_Count)\n  {\n    T* newArray = VMA_NULL;\n    if (m_Count > 0)\n    {\n      newArray = VmaAllocateArray<T>(m_Allocator.m_pCallbacks, m_Count);\n      memcpy(newArray, m_pArray, m_Count * sizeof(T));\n    }\n    VmaFree(m_Allocator.m_pCallbacks, m_pArray);\n    m_Capacity = m_Count;\n    m_pArray = newArray;\n  }\n}\n\ntemplate<typename T, typename AllocatorT>\nvoid VmaVector<T, AllocatorT>::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  {\n    memmove(m_pArray + (index + 1), m_pArray + index, (oldCount - index) * sizeof(T));\n  }\n  m_pArray[index] = src;\n}\n\ntemplate<typename T, typename AllocatorT>\nvoid VmaVector<T, AllocatorT>::remove(size_t index)\n{\n  VMA_HEAVY_ASSERT(index < m_Count);\n  const size_t oldCount = size();\n  if (index < oldCount - 1)\n  {\n    memmove(m_pArray + index, m_pArray + (index + 1), (oldCount - index - 1) * sizeof(T));\n  }\n  resize(oldCount - 1);\n}\n#endif // _VMA_VECTOR_FUNCTIONS\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#endif // _VMA_VECTOR\n\n#ifndef _VMA_SMALL_VECTOR\n/*\nThis is a vector (a variable-sized array), optimized for the case when the array is small.\n\nIt contains some number of elements in-place, which allows it to avoid heap allocation\nwhen the actual number of elements is below that threshold. This allows normal \"small\"\ncases to be fast without losing generality for large inputs.\n*/\ntemplate<typename T, typename AllocatorT, size_t N>\nclass VmaSmallVector\n{\n public:\n  typedef T value_type;\n  typedef T* iterator;\n\n  VmaSmallVector(const AllocatorT& allocator);\n  VmaSmallVector(size_t count, const AllocatorT& allocator);\n  template<typename SrcT, typename SrcAllocatorT, size_t SrcN>\n  VmaSmallVector(const VmaSmallVector<SrcT, SrcAllocatorT, SrcN>&) = delete;\n  template<typename SrcT, typename SrcAllocatorT, size_t SrcN>\n  VmaSmallVector<T, AllocatorT, N>& operator=(const VmaSmallVector<SrcT, SrcAllocatorT, SrcN>&) = delete;\n  ~VmaSmallVector() = default;\n\n  bool empty() const { return m_Count == 0; }\n  size_t size() const { return m_Count; }\n  T* data() { return m_Count > N ? m_DynamicArray.data() : m_StaticArray; }\n  T& front() { VMA_HEAVY_ASSERT(m_Count > 0); return data()[0]; }\n  T& back() { VMA_HEAVY_ASSERT(m_Count > 0); return data()[m_Count - 1]; }\n  const T* data() const { return m_Count > N ? m_DynamicArray.data() : m_StaticArray; }\n  const T& front() const { VMA_HEAVY_ASSERT(m_Count > 0); return data()[0]; }\n  const T& back() const { VMA_HEAVY_ASSERT(m_Count > 0); return data()[m_Count - 1]; }\n\n  iterator begin() { return data(); }\n  iterator end() { return data() + m_Count; }\n\n  void pop_front() { VMA_HEAVY_ASSERT(m_Count > 0); remove(0); }\n  void pop_back() { VMA_HEAVY_ASSERT(m_Count > 0); resize(size() - 1); }\n  void push_front(const T& src) { insert(0, src); }\n\n  void push_back(const T& src);\n  void resize(size_t newCount, bool freeMemory = false);\n  void clear(bool freeMemory = false);\n  void insert(size_t index, const T& src);\n  void remove(size_t index);\n\n  T& operator[](size_t index) { VMA_HEAVY_ASSERT(index < m_Count); return data()[index]; }\n  const T& operator[](size_t index) const { VMA_HEAVY_ASSERT(index < m_Count); return data()[index]; }\n\n private:\n  size_t m_Count;\n  T m_StaticArray[N]; // Used when m_Size <= N\n  VmaVector<T, AllocatorT> m_DynamicArray; // Used when m_Size > N\n};\n\n#ifndef _VMA_SMALL_VECTOR_FUNCTIONS\ntemplate<typename T, typename AllocatorT, size_t N>\nVmaSmallVector<T, AllocatorT, N>::VmaSmallVector(const AllocatorT& allocator)\n    : m_Count(0),\n      m_DynamicArray(allocator) {}\n\ntemplate<typename T, typename AllocatorT, size_t N>\nVmaSmallVector<T, AllocatorT, N>::VmaSmallVector(size_t count, const AllocatorT& allocator)\n    : m_Count(count),\n      m_DynamicArray(count > N ? count : 0, allocator) {}\n\ntemplate<typename T, typename AllocatorT, size_t N>\nvoid VmaSmallVector<T, AllocatorT, N>::push_back(const T& src)\n{\n  const size_t newIndex = size();\n  resize(newIndex + 1);\n  data()[newIndex] = src;\n}\n\ntemplate<typename T, typename AllocatorT, size_t N>\nvoid VmaSmallVector<T, AllocatorT, N>::resize(size_t newCount, bool freeMemory)\n{\n  if (newCount > N && m_Count > N)\n  {\n    // Any direction, staying in m_DynamicArray\n    m_DynamicArray.resize(newCount);\n    if (freeMemory)\n    {\n      m_DynamicArray.shrink_to_fit();\n    }\n  }\n  else if (newCount > N && m_Count <= N)\n  {\n    // Growing, moving from m_StaticArray to m_DynamicArray\n    m_DynamicArray.resize(newCount);\n    if (m_Count > 0)\n    {\n      memcpy(m_DynamicArray.data(), m_StaticArray, m_Count * sizeof(T));\n    }\n  }\n  else if (newCount <= N && m_Count > N)\n  {\n    // Shrinking, moving from m_DynamicArray to m_StaticArray\n    if (newCount > 0)\n    {\n      memcpy(m_StaticArray, m_DynamicArray.data(), newCount * sizeof(T));\n    }\n    m_DynamicArray.resize(0);\n    if (freeMemory)\n    {\n      m_DynamicArray.shrink_to_fit();\n    }\n  }\n  else\n  {\n    // Any direction, staying in m_StaticArray - nothing to do here\n  }\n  m_Count = newCount;\n}\n\ntemplate<typename T, typename AllocatorT, size_t N>\nvoid VmaSmallVector<T, AllocatorT, N>::clear(bool freeMemory)\n{\n  m_DynamicArray.clear();\n  if (freeMemory)\n  {\n    m_DynamicArray.shrink_to_fit();\n  }\n  m_Count = 0;\n}\n\ntemplate<typename T, typename AllocatorT, size_t N>\nvoid VmaSmallVector<T, AllocatorT, N>::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  T* const dataPtr = data();\n  if (index < oldCount)\n  {\n    //  I know, this could be more optimal for case where memmove can be memcpy directly from m_StaticArray to m_DynamicArray.\n    memmove(dataPtr + (index + 1), dataPtr + index, (oldCount - index) * sizeof(T));\n  }\n  dataPtr[index] = src;\n}\n\ntemplate<typename T, typename AllocatorT, size_t N>\nvoid VmaSmallVector<T, AllocatorT, N>::remove(size_t index)\n{\n  VMA_HEAVY_ASSERT(index < m_Count);\n  const size_t oldCount = size();\n  if (index < oldCount - 1)\n  {\n    //  I know, this could be more optimal for case where memmove can be memcpy directly from m_DynamicArray to m_StaticArray.\n    T* const dataPtr = data();\n    memmove(dataPtr + index, dataPtr + (index + 1), (oldCount - index - 1) * sizeof(T));\n  }\n  resize(oldCount - 1);\n}\n#endif // _VMA_SMALL_VECTOR_FUNCTIONS\n#endif // _VMA_SMALL_VECTOR\n\n#ifndef _VMA_POOL_ALLOCATOR\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)\n public:\n  VmaPoolAllocator(const VkAllocationCallbacks* pAllocationCallbacks, uint32_t firstBlockCapacity);\n  ~VmaPoolAllocator();\n  template<typename... Types> T* Alloc(Types&&... args);\n  void Free(T* ptr);\n\n private:\n  union Item\n  {\n    uint32_t NextFreeIndex;\n    alignas(T) char Value[sizeof(T)];\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\n#ifndef _VMA_POOL_ALLOCATOR_FUNCTIONS\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>\ntemplate<typename... Types> T* VmaPoolAllocator<T>::Alloc(Types&&... args)\n{\n  for (size_t i = m_ItemBlocks.size(); i--; )\n  {\n    ItemBlock& block = m_ItemBlocks[i];\n    // This block has some free items: Use first one.\n    if (block.FirstFreeIndex != UINT32_MAX)\n    {\n      Item* const pItem = &block.pItems[block.FirstFreeIndex];\n      block.FirstFreeIndex = pItem->NextFreeIndex;\n      T* result = (T*)&pItem->Value;\n      new(result)T(std::forward<Types>(args)...); // 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(std::forward<Types>(args)...); // 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  {\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    {\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      {\n          vma_new_array(m_pAllocationCallbacks, Item, newBlockCapacity),\n          newBlockCapacity,\n          0\n      };\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#endif // _VMA_POOL_ALLOCATOR_FUNCTIONS\n#endif // _VMA_POOL_ALLOCATOR\n\n#ifndef _VMA_RAW_LIST\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)\n public:\n  typedef VmaListItem<T> ItemType;\n\n  VmaRawList(const VkAllocationCallbacks* pAllocationCallbacks);\n  // Intentionally not calling Clear, because that would be unnecessary\n  // computations to return all items to m_ItemAllocator as free.\n  ~VmaRawList() = default;\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  ItemType* Back() { return m_pBack; }\n  const ItemType* Front() const { return m_pFront; }\n  const ItemType* Back() const { return m_pBack; }\n\n  ItemType* PushFront();\n  ItemType* PushBack();\n  ItemType* PushFront(const T& value);\n  ItemType* PushBack(const T& value);\n  void PopFront();\n  void PopBack();\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  ItemType* InsertBefore(ItemType* pItem, const T& value);\n  ItemType* InsertAfter(ItemType* pItem, const T& value);\n\n  void Clear();\n  void Remove(ItemType* pItem);\n\n private:\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\n#ifndef _VMA_RAW_LIST_FUNCTIONS\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\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  {\n    pNewItem->pNext = VMA_NULL;\n    m_pFront = pNewItem;\n    m_pBack = pNewItem;\n    m_Count = 1;\n  }\n  else\n  {\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()\n{\n  ItemType* const pNewItem = m_ItemAllocator.Alloc();\n  pNewItem->pNext = VMA_NULL;\n  if(IsEmpty())\n  {\n    pNewItem->pPrev = VMA_NULL;\n    m_pFront = pNewItem;\n    m_pBack = pNewItem;\n    m_Count = 1;\n  }\n  else\n  {\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(const T& value)\n{\n  ItemType* const pNewItem = PushFront();\n  pNewItem->Value = value;\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>\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  {\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>::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  {\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>::Clear()\n{\n  if (IsEmpty() == false)\n  {\n    ItemType* pItem = m_pBack;\n    while (pItem != VMA_NULL)\n    {\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>\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  {\n    pItem->pPrev->pNext = pItem->pNext;\n  }\n  else\n  {\n    VMA_HEAVY_ASSERT(m_pFront == pItem);\n    m_pFront = pItem->pNext;\n  }\n\n  if(pItem->pNext != VMA_NULL)\n  {\n    pItem->pNext->pPrev = pItem->pPrev;\n  }\n  else\n  {\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  {\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    {\n      prevItem->pNext = newItem;\n    }\n    else\n    {\n      VMA_HEAVY_ASSERT(m_pFront == pItem);\n      m_pFront = newItem;\n    }\n    ++m_Count;\n    return newItem;\n  }\n  else\n    return PushBack();\n}\n\ntemplate<typename T>\nVmaListItem<T>* VmaRawList<T>::InsertAfter(ItemType* pItem)\n{\n  if(pItem != VMA_NULL)\n  {\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    {\n      nextItem->pPrev = newItem;\n    }\n    else\n    {\n      VMA_HEAVY_ASSERT(m_pBack == pItem);\n      m_pBack = newItem;\n    }\n    ++m_Count;\n    return newItem;\n  }\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#endif // _VMA_RAW_LIST_FUNCTIONS\n#endif // _VMA_RAW_LIST\n\n#ifndef _VMA_LIST\ntemplate<typename T, typename AllocatorT>\nclass VmaList\n{\n  VMA_CLASS_NO_COPY(VmaList)\n public:\n  class reverse_iterator;\n  class const_iterator;\n  class const_reverse_iterator;\n\n  class iterator\n  {\n    friend class const_iterator;\n    friend class VmaList<T, AllocatorT>;\n   public:\n    iterator() :  m_pList(VMA_NULL), m_pItem(VMA_NULL) {}\n    iterator(const reverse_iterator& src) : m_pList(src.m_pList), m_pItem(src.m_pItem) {}\n\n    T& operator*() const { VMA_HEAVY_ASSERT(m_pItem != VMA_NULL); return m_pItem->Value; }\n    T* operator->() const { VMA_HEAVY_ASSERT(m_pItem != VMA_NULL); return &m_pItem->Value; }\n\n    bool operator==(const iterator& rhs) const { VMA_HEAVY_ASSERT(m_pList == rhs.m_pList); return m_pItem == rhs.m_pItem; }\n    bool operator!=(const iterator& rhs) const { VMA_HEAVY_ASSERT(m_pList == rhs.m_pList); return m_pItem != rhs.m_pItem; }\n\n    iterator operator++(int) { iterator result = *this; ++*this; return result; }\n    iterator operator--(int) { iterator result = *this; --*this; return result; }\n\n    iterator& operator++() { VMA_HEAVY_ASSERT(m_pItem != VMA_NULL); m_pItem = m_pItem->pNext; return *this; }\n    iterator& operator--();\n\n   private:\n    VmaRawList<T>* m_pList;\n    VmaListItem<T>* m_pItem;\n\n    iterator(VmaRawList<T>* pList, VmaListItem<T>* pItem) : m_pList(pList),  m_pItem(pItem) {}\n  };\n  class reverse_iterator\n  {\n    friend class const_reverse_iterator;\n    friend class VmaList<T, AllocatorT>;\n   public:\n    reverse_iterator() : m_pList(VMA_NULL), m_pItem(VMA_NULL) {}\n    reverse_iterator(const iterator& src) : m_pList(src.m_pList), m_pItem(src.m_pItem) {}\n\n    T& operator*() const { VMA_HEAVY_ASSERT(m_pItem != VMA_NULL); return m_pItem->Value; }\n    T* operator->() const { VMA_HEAVY_ASSERT(m_pItem != VMA_NULL); return &m_pItem->Value; }\n\n    bool operator==(const reverse_iterator& rhs) const { VMA_HEAVY_ASSERT(m_pList == rhs.m_pList); return m_pItem == rhs.m_pItem; }\n    bool operator!=(const reverse_iterator& rhs) const { VMA_HEAVY_ASSERT(m_pList == rhs.m_pList); return m_pItem != rhs.m_pItem; }\n\n    reverse_iterator operator++(int) { reverse_iterator result = *this; ++* this; return result; }\n    reverse_iterator operator--(int) { reverse_iterator result = *this; --* this; return result; }\n\n    reverse_iterator& operator++() { VMA_HEAVY_ASSERT(m_pItem != VMA_NULL); m_pItem = m_pItem->pPrev; return *this; }\n    reverse_iterator& operator--();\n\n   private:\n    VmaRawList<T>* m_pList;\n    VmaListItem<T>* m_pItem;\n\n    reverse_iterator(VmaRawList<T>* pList, VmaListItem<T>* pItem) : m_pList(pList),  m_pItem(pItem) {}\n  };\n  class const_iterator\n  {\n    friend class VmaList<T, AllocatorT>;\n   public:\n    const_iterator() : m_pList(VMA_NULL), m_pItem(VMA_NULL) {}\n    const_iterator(const iterator& src) : m_pList(src.m_pList), m_pItem(src.m_pItem) {}\n    const_iterator(const reverse_iterator& src) : m_pList(src.m_pList), m_pItem(src.m_pItem) {}\n\n    iterator drop_const() { return { const_cast<VmaRawList<T>*>(m_pList), const_cast<VmaListItem<T>*>(m_pItem) }; }\n\n    const T& operator*() const { VMA_HEAVY_ASSERT(m_pItem != VMA_NULL); return m_pItem->Value; }\n    const T* operator->() const { VMA_HEAVY_ASSERT(m_pItem != VMA_NULL); return &m_pItem->Value; }\n\n    bool operator==(const const_iterator& rhs) const { VMA_HEAVY_ASSERT(m_pList == rhs.m_pList); return m_pItem == rhs.m_pItem; }\n    bool operator!=(const const_iterator& rhs) const { VMA_HEAVY_ASSERT(m_pList == rhs.m_pList); return m_pItem != rhs.m_pItem; }\n\n    const_iterator operator++(int) { const_iterator result = *this; ++* this; return result; }\n    const_iterator operator--(int) { const_iterator result = *this; --* this; return result; }\n\n    const_iterator& operator++() { VMA_HEAVY_ASSERT(m_pItem != VMA_NULL); m_pItem = m_pItem->pNext; return *this; }\n    const_iterator& operator--();\n\n   private:\n    const VmaRawList<T>* m_pList;\n    const VmaListItem<T>* m_pItem;\n\n    const_iterator(const VmaRawList<T>* pList, const VmaListItem<T>* pItem) : m_pList(pList), m_pItem(pItem) {}\n  };\n  class const_reverse_iterator\n  {\n    friend class VmaList<T, AllocatorT>;\n   public:\n    const_reverse_iterator() : m_pList(VMA_NULL), m_pItem(VMA_NULL) {}\n    const_reverse_iterator(const reverse_iterator& src) : m_pList(src.m_pList), m_pItem(src.m_pItem) {}\n    const_reverse_iterator(const iterator& src) : m_pList(src.m_pList), m_pItem(src.m_pItem) {}\n\n    reverse_iterator drop_const() { return { const_cast<VmaRawList<T>*>(m_pList), const_cast<VmaListItem<T>*>(m_pItem) }; }\n\n    const T& operator*() const { VMA_HEAVY_ASSERT(m_pItem != VMA_NULL); return m_pItem->Value; }\n    const T* operator->() const { VMA_HEAVY_ASSERT(m_pItem != VMA_NULL); return &m_pItem->Value; }\n\n    bool operator==(const const_reverse_iterator& rhs) const { VMA_HEAVY_ASSERT(m_pList == rhs.m_pList); return m_pItem == rhs.m_pItem; }\n    bool operator!=(const const_reverse_iterator& rhs) const { VMA_HEAVY_ASSERT(m_pList == rhs.m_pList); return m_pItem != rhs.m_pItem; }\n\n    const_reverse_iterator operator++(int) { const_reverse_iterator result = *this; ++* this; return result; }\n    const_reverse_iterator operator--(int) { const_reverse_iterator result = *this; --* this; return result; }\n\n    const_reverse_iterator& operator++() { VMA_HEAVY_ASSERT(m_pItem != VMA_NULL); m_pItem = m_pItem->pPrev; return *this; }\n    const_reverse_iterator& operator--();\n\n   private:\n    const VmaRawList<T>* m_pList;\n    const VmaListItem<T>* m_pItem;\n\n    const_reverse_iterator(const VmaRawList<T>* pList, const VmaListItem<T>* pItem) : m_pList(pList), m_pItem(pItem) {}\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  const_iterator begin() const { return cbegin(); }\n  const_iterator end() const { return cend(); }\n\n  reverse_iterator rbegin() { return reverse_iterator(&m_RawList, m_RawList.Back()); }\n  reverse_iterator rend() { return reverse_iterator(&m_RawList, VMA_NULL); }\n\n  const_reverse_iterator crbegin() const { return const_reverse_iterator(&m_RawList, m_RawList.Back()); }\n  const_reverse_iterator crend() const { return const_reverse_iterator(&m_RawList, VMA_NULL); }\n\n  const_reverse_iterator rbegin() const { return crbegin(); }\n  const_reverse_iterator rend() const { return crend(); }\n\n  void push_back(const T& value) { m_RawList.PushBack(value); }\n  iterator insert(iterator it, const T& value) { return iterator(&m_RawList, m_RawList.InsertBefore(it.m_pItem, value)); }\n\n  void clear() { m_RawList.Clear(); }\n  void erase(iterator it) { m_RawList.Remove(it.m_pItem); }\n\n private:\n  VmaRawList<T> m_RawList;\n};\n\n#ifndef _VMA_LIST_FUNCTIONS\ntemplate<typename T, typename AllocatorT>\ntypename VmaList<T, AllocatorT>::iterator& VmaList<T, AllocatorT>::iterator::operator--()\n{\n  if (m_pItem != VMA_NULL)\n  {\n    m_pItem = m_pItem->pPrev;\n  }\n  else\n  {\n    VMA_HEAVY_ASSERT(!m_pList->IsEmpty());\n    m_pItem = m_pList->Back();\n  }\n  return *this;\n}\n\ntemplate<typename T, typename AllocatorT>\ntypename VmaList<T, AllocatorT>::reverse_iterator& VmaList<T, AllocatorT>::reverse_iterator::operator--()\n{\n  if (m_pItem != VMA_NULL)\n  {\n    m_pItem = m_pItem->pNext;\n  }\n  else\n  {\n    VMA_HEAVY_ASSERT(!m_pList->IsEmpty());\n    m_pItem = m_pList->Front();\n  }\n  return *this;\n}\n\ntemplate<typename T, typename AllocatorT>\ntypename VmaList<T, AllocatorT>::const_iterator& VmaList<T, AllocatorT>::const_iterator::operator--()\n{\n  if (m_pItem != VMA_NULL)\n  {\n    m_pItem = m_pItem->pPrev;\n  }\n  else\n  {\n    VMA_HEAVY_ASSERT(!m_pList->IsEmpty());\n    m_pItem = m_pList->Back();\n  }\n  return *this;\n}\n\ntemplate<typename T, typename AllocatorT>\ntypename VmaList<T, AllocatorT>::const_reverse_iterator& VmaList<T, AllocatorT>::const_reverse_iterator::operator--()\n{\n  if (m_pItem != VMA_NULL)\n  {\n    m_pItem = m_pItem->pNext;\n  }\n  else\n  {\n    VMA_HEAVY_ASSERT(!m_pList->IsEmpty());\n    m_pItem = m_pList->Back();\n  }\n  return *this;\n}\n#endif // _VMA_LIST_FUNCTIONS\n#endif // _VMA_LIST\n\n#ifndef _VMA_INTRUSIVE_LINKED_LIST\n/*\nExpected interface of ItemTypeTraits:\nstruct MyItemTypeTraits\n{\n    typedef MyItem ItemType;\n    static ItemType* GetPrev(const ItemType* item) { return item->myPrevPtr; }\n    static ItemType* GetNext(const ItemType* item) { return item->myNextPtr; }\n    static ItemType*& AccessPrev(ItemType* item) { return item->myPrevPtr; }\n    static ItemType*& AccessNext(ItemType* item) { return item->myNextPtr; }\n};\n*/\ntemplate<typename ItemTypeTraits>\nclass VmaIntrusiveLinkedList\n{\n public:\n  typedef typename ItemTypeTraits::ItemType ItemType;\n  static ItemType* GetPrev(const ItemType* item) { return ItemTypeTraits::GetPrev(item); }\n  static ItemType* GetNext(const ItemType* item) { return ItemTypeTraits::GetNext(item); }\n\n  // Movable, not copyable.\n  VmaIntrusiveLinkedList() = default;\n  VmaIntrusiveLinkedList(VmaIntrusiveLinkedList && src);\n  VmaIntrusiveLinkedList(const VmaIntrusiveLinkedList&) = delete;\n  VmaIntrusiveLinkedList& operator=(VmaIntrusiveLinkedList&& src);\n  VmaIntrusiveLinkedList& operator=(const VmaIntrusiveLinkedList&) = delete;\n  ~VmaIntrusiveLinkedList() { VMA_HEAVY_ASSERT(IsEmpty()); }\n\n  size_t GetCount() const { return m_Count; }\n  bool IsEmpty() const { return m_Count == 0; }\n  ItemType* Front() { return m_Front; }\n  ItemType* Back() { return m_Back; }\n  const ItemType* Front() const { return m_Front; }\n  const ItemType* Back() const { return m_Back; }\n\n  void PushBack(ItemType* item);\n  void PushFront(ItemType* item);\n  ItemType* PopBack();\n  ItemType* PopFront();\n\n  // MyItem can be null - it means PushBack.\n  void InsertBefore(ItemType* existingItem, ItemType* newItem);\n  // MyItem can be null - it means PushFront.\n  void InsertAfter(ItemType* existingItem, ItemType* newItem);\n  void Remove(ItemType* item);\n  void RemoveAll();\n\n private:\n  ItemType* m_Front = VMA_NULL;\n  ItemType* m_Back = VMA_NULL;\n  size_t m_Count = 0;\n};\n\n#ifndef _VMA_INTRUSIVE_LINKED_LIST_FUNCTIONS\ntemplate<typename ItemTypeTraits>\nVmaIntrusiveLinkedList<ItemTypeTraits>::VmaIntrusiveLinkedList(VmaIntrusiveLinkedList&& src)\n    : m_Front(src.m_Front), m_Back(src.m_Back), m_Count(src.m_Count)\n{\n  src.m_Front = src.m_Back = VMA_NULL;\n  src.m_Count = 0;\n}\n\ntemplate<typename ItemTypeTraits>\nVmaIntrusiveLinkedList<ItemTypeTraits>& VmaIntrusiveLinkedList<ItemTypeTraits>::operator=(VmaIntrusiveLinkedList&& src)\n{\n  if (&src != this)\n  {\n    VMA_HEAVY_ASSERT(IsEmpty());\n    m_Front = src.m_Front;\n    m_Back = src.m_Back;\n    m_Count = src.m_Count;\n    src.m_Front = src.m_Back = VMA_NULL;\n    src.m_Count = 0;\n  }\n  return *this;\n}\n\ntemplate<typename ItemTypeTraits>\nvoid VmaIntrusiveLinkedList<ItemTypeTraits>::PushBack(ItemType* item)\n{\n  VMA_HEAVY_ASSERT(ItemTypeTraits::GetPrev(item) == VMA_NULL && ItemTypeTraits::GetNext(item) == VMA_NULL);\n  if (IsEmpty())\n  {\n    m_Front = item;\n    m_Back = item;\n    m_Count = 1;\n  }\n  else\n  {\n    ItemTypeTraits::AccessPrev(item) = m_Back;\n    ItemTypeTraits::AccessNext(m_Back) = item;\n    m_Back = item;\n    ++m_Count;\n  }\n}\n\ntemplate<typename ItemTypeTraits>\nvoid VmaIntrusiveLinkedList<ItemTypeTraits>::PushFront(ItemType* item)\n{\n  VMA_HEAVY_ASSERT(ItemTypeTraits::GetPrev(item) == VMA_NULL && ItemTypeTraits::GetNext(item) == VMA_NULL);\n  if (IsEmpty())\n  {\n    m_Front = item;\n    m_Back = item;\n    m_Count = 1;\n  }\n  else\n  {\n    ItemTypeTraits::AccessNext(item) = m_Front;\n    ItemTypeTraits::AccessPrev(m_Front) = item;\n    m_Front = item;\n    ++m_Count;\n  }\n}\n\ntemplate<typename ItemTypeTraits>\ntypename VmaIntrusiveLinkedList<ItemTypeTraits>::ItemType* VmaIntrusiveLinkedList<ItemTypeTraits>::PopBack()\n{\n  VMA_HEAVY_ASSERT(m_Count > 0);\n  ItemType* const backItem = m_Back;\n  ItemType* const prevItem = ItemTypeTraits::GetPrev(backItem);\n  if (prevItem != VMA_NULL)\n  {\n    ItemTypeTraits::AccessNext(prevItem) = VMA_NULL;\n  }\n  m_Back = prevItem;\n  --m_Count;\n  ItemTypeTraits::AccessPrev(backItem) = VMA_NULL;\n  ItemTypeTraits::AccessNext(backItem) = VMA_NULL;\n  return backItem;\n}\n\ntemplate<typename ItemTypeTraits>\ntypename VmaIntrusiveLinkedList<ItemTypeTraits>::ItemType* VmaIntrusiveLinkedList<ItemTypeTraits>::PopFront()\n{\n  VMA_HEAVY_ASSERT(m_Count > 0);\n  ItemType* const frontItem = m_Front;\n  ItemType* const nextItem = ItemTypeTraits::GetNext(frontItem);\n  if (nextItem != VMA_NULL)\n  {\n    ItemTypeTraits::AccessPrev(nextItem) = VMA_NULL;\n  }\n  m_Front = nextItem;\n  --m_Count;\n  ItemTypeTraits::AccessPrev(frontItem) = VMA_NULL;\n  ItemTypeTraits::AccessNext(frontItem) = VMA_NULL;\n  return frontItem;\n}\n\ntemplate<typename ItemTypeTraits>\nvoid VmaIntrusiveLinkedList<ItemTypeTraits>::InsertBefore(ItemType* existingItem, ItemType* newItem)\n{\n  VMA_HEAVY_ASSERT(newItem != VMA_NULL && ItemTypeTraits::GetPrev(newItem) == VMA_NULL && ItemTypeTraits::GetNext(newItem) == VMA_NULL);\n  if (existingItem != VMA_NULL)\n  {\n    ItemType* const prevItem = ItemTypeTraits::GetPrev(existingItem);\n    ItemTypeTraits::AccessPrev(newItem) = prevItem;\n    ItemTypeTraits::AccessNext(newItem) = existingItem;\n    ItemTypeTraits::AccessPrev(existingItem) = newItem;\n    if (prevItem != VMA_NULL)\n    {\n      ItemTypeTraits::AccessNext(prevItem) = newItem;\n    }\n    else\n    {\n      VMA_HEAVY_ASSERT(m_Front == existingItem);\n      m_Front = newItem;\n    }\n    ++m_Count;\n  }\n  else\n    PushBack(newItem);\n}\n\ntemplate<typename ItemTypeTraits>\nvoid VmaIntrusiveLinkedList<ItemTypeTraits>::InsertAfter(ItemType* existingItem, ItemType* newItem)\n{\n  VMA_HEAVY_ASSERT(newItem != VMA_NULL && ItemTypeTraits::GetPrev(newItem) == VMA_NULL && ItemTypeTraits::GetNext(newItem) == VMA_NULL);\n  if (existingItem != VMA_NULL)\n  {\n    ItemType* const nextItem = ItemTypeTraits::GetNext(existingItem);\n    ItemTypeTraits::AccessNext(newItem) = nextItem;\n    ItemTypeTraits::AccessPrev(newItem) = existingItem;\n    ItemTypeTraits::AccessNext(existingItem) = newItem;\n    if (nextItem != VMA_NULL)\n    {\n      ItemTypeTraits::AccessPrev(nextItem) = newItem;\n    }\n    else\n    {\n      VMA_HEAVY_ASSERT(m_Back == existingItem);\n      m_Back = newItem;\n    }\n    ++m_Count;\n  }\n  else\n    return PushFront(newItem);\n}\n\ntemplate<typename ItemTypeTraits>\nvoid VmaIntrusiveLinkedList<ItemTypeTraits>::Remove(ItemType* item)\n{\n  VMA_HEAVY_ASSERT(item != VMA_NULL && m_Count > 0);\n  if (ItemTypeTraits::GetPrev(item) != VMA_NULL)\n  {\n    ItemTypeTraits::AccessNext(ItemTypeTraits::AccessPrev(item)) = ItemTypeTraits::GetNext(item);\n  }\n  else\n  {\n    VMA_HEAVY_ASSERT(m_Front == item);\n    m_Front = ItemTypeTraits::GetNext(item);\n  }\n\n  if (ItemTypeTraits::GetNext(item) != VMA_NULL)\n  {\n    ItemTypeTraits::AccessPrev(ItemTypeTraits::AccessNext(item)) = ItemTypeTraits::GetPrev(item);\n  }\n  else\n  {\n    VMA_HEAVY_ASSERT(m_Back == item);\n    m_Back = ItemTypeTraits::GetPrev(item);\n  }\n  ItemTypeTraits::AccessPrev(item) = VMA_NULL;\n  ItemTypeTraits::AccessNext(item) = VMA_NULL;\n  --m_Count;\n}\n\ntemplate<typename ItemTypeTraits>\nvoid VmaIntrusiveLinkedList<ItemTypeTraits>::RemoveAll()\n{\n  if (!IsEmpty())\n  {\n    ItemType* item = m_Back;\n    while (item != VMA_NULL)\n    {\n      ItemType* const prevItem = ItemTypeTraits::AccessPrev(item);\n      ItemTypeTraits::AccessPrev(item) = VMA_NULL;\n      ItemTypeTraits::AccessNext(item) = VMA_NULL;\n      item = prevItem;\n    }\n    m_Front = VMA_NULL;\n    m_Back = VMA_NULL;\n    m_Count = 0;\n  }\n}\n#endif // _VMA_INTRUSIVE_LINKED_LIST_FUNCTIONS\n#endif // _VMA_INTRUSIVE_LINKED_LIST\n\n// Unused in this version.\n#if 0\n\n#ifndef _VMA_PAIR\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\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#endif // _VMA_PAIR\n\n#ifndef _VMA_MAP\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    size_t size() { return m_Vector.size(); }\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#ifndef _VMA_MAP_FUNCTIONS\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    {\n        return it;\n    }\n    else\n    {\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#endif // _VMA_MAP_FUNCTIONS\n#endif // _VMA_MAP\n\n#endif // #if 0\n\n#if !defined(_VMA_STRING_BUILDER) && VMA_STATS_STRING_ENABLED\nclass VmaStringBuilder\n{\n public:\n  VmaStringBuilder(const VkAllocationCallbacks* allocationCallbacks) : m_Data(VmaStlAllocator<char>(allocationCallbacks)) {}\n  ~VmaStringBuilder() = default;\n\n  size_t GetLength() const { return m_Data.size(); }\n  const char* GetData() const { return m_Data.data(); }\n  void AddNewLine() { Add('\\n'); }\n  void Add(char ch) { m_Data.push_back(ch); }\n\n  void Add(const char* pStr);\n  void AddNumber(uint32_t num);\n  void AddNumber(uint64_t num);\n  void AddPointer(const void* ptr);\n\n private:\n  VmaVector<char, VmaStlAllocator<char>> m_Data;\n};\n\n#ifndef _VMA_STRING_BUILDER_FUNCTIONS\nvoid VmaStringBuilder::Add(const char* pStr)\n{\n  const size_t strLen = strlen(pStr);\n  if (strLen > 0)\n  {\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  {\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  {\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#endif //_VMA_STRING_BUILDER_FUNCTIONS\n#endif // _VMA_STRING_BUILDER\n\n#if !defined(_VMA_JSON_WRITER) && VMA_STATS_STRING_ENABLED\n/*\nAllows to conveniently build a correct JSON document to be written to the\nVmaStringBuilder passed to the constructor.\n*/\nclass VmaJsonWriter\n{\n  VMA_CLASS_NO_COPY(VmaJsonWriter)\n public:\n  // sb - string builder to write the document to. Must remain alive for the whole lifetime of this object.\n  VmaJsonWriter(const VkAllocationCallbacks* pAllocationCallbacks, VmaStringBuilder& sb);\n  ~VmaJsonWriter();\n\n  // Begins object by writing \"{\".\n  // Inside an object, you must call pairs of WriteString and a value, e.g.:\n  // j.BeginObject(true); j.WriteString(\"A\"); j.WriteNumber(1); j.WriteString(\"B\"); j.WriteNumber(2); j.EndObject();\n  // Will write: { \"A\": 1, \"B\": 2 }\n  void BeginObject(bool singleLine = false);\n  // Ends object by writing \"}\".\n  void EndObject();\n\n  // Begins array by writing \"[\".\n  // Inside an array, you can write a sequence of any values.\n  void BeginArray(bool singleLine = false);\n  // Ends array by writing \"[\".\n  void EndArray();\n\n  // Writes a string value inside \"\".\n  // pStr can contain any ANSI characters, including '\"', new line etc. - they will be properly escaped.\n  void WriteString(const char* pStr);\n\n  // Begins writing a string value.\n  // Call BeginString, ContinueString, ContinueString, ..., EndString instead of\n  // WriteString to conveniently build the string content incrementally, made of\n  // parts including numbers.\n  void BeginString(const char* pStr = VMA_NULL);\n  // Posts next part of an open string.\n  void ContinueString(const char* pStr);\n  // Posts next part of an open string. The number is converted to decimal characters.\n  void ContinueString(uint32_t n);\n  void ContinueString(uint64_t n);\n  // Posts next part of an open string. Pointer value is converted to characters\n  // using \"%p\" formatting - shown as hexadecimal number, e.g.: 000000081276Ad00\n  void ContinueString_Pointer(const void* ptr);\n  // Ends writing a string value by writing '\"'.\n  void EndString(const char* pStr = VMA_NULL);\n\n  // Writes a number value.\n  void WriteNumber(uint32_t n);\n  void WriteNumber(uint64_t n);\n  // Writes a boolean value - false or true.\n  void WriteBool(bool b);\n  // Writes a null value.\n  void WriteNull();\n\n private:\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  static const char* const INDENT;\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};\nconst char* const VmaJsonWriter::INDENT = \"  \";\n\n#ifndef _VMA_JSON_WRITER_FUNCTIONS\nVmaJsonWriter::VmaJsonWriter(const VkAllocationCallbacks* pAllocationCallbacks, VmaStringBuilder& sb)\n    : m_SB(sb),\n      m_Stack(VmaStlAllocator<StackItem>(pAllocationCallbacks)),\n      m_InsideString(false) {}\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  {\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  {\n    char ch = pStr[i];\n    if (ch == '\\\\')\n    {\n      m_SB.Add(\"\\\\\\\\\");\n    }\n    else if (ch == '\"')\n    {\n      m_SB.Add(\"\\\\\\\"\");\n    }\n    else if (ch >= 32)\n    {\n      m_SB.Add(ch);\n    }\n    else switch (ch)\n      {\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  {\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  {\n    StackItem& currItem = m_Stack.back();\n    if (currItem.type == COLLECTION_TYPE_OBJECT &&\n        currItem.valueCount % 2 == 0)\n    {\n      VMA_ASSERT(isString);\n    }\n\n    if (currItem.type == COLLECTION_TYPE_OBJECT &&\n        currItem.valueCount % 2 != 0)\n    {\n      m_SB.Add(\": \");\n    }\n    else if (currItem.valueCount > 0)\n    {\n      m_SB.Add(\", \");\n      WriteIndent();\n    }\n    else\n    {\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  {\n    m_SB.AddNewLine();\n\n    size_t count = m_Stack.size();\n    if (count > 0 && oneLess)\n    {\n      --count;\n    }\n    for (size_t i = 0; i < count; ++i)\n    {\n      m_SB.Add(INDENT);\n    }\n  }\n}\n#endif // _VMA_JSON_WRITER_FUNCTIONS\n\nstatic void VmaPrintDetailedStatistics(VmaJsonWriter& json, const VmaDetailedStatistics& stat)\n{\n  json.BeginObject();\n\n  json.WriteString(\"BlockCount\");\n  json.WriteNumber(stat.statistics.blockCount);\n  json.WriteString(\"BlockBytes\");\n  json.WriteNumber(stat.statistics.blockBytes);\n  json.WriteString(\"AllocationCount\");\n  json.WriteNumber(stat.statistics.allocationCount);\n  json.WriteString(\"AllocationBytes\");\n  json.WriteNumber(stat.statistics.allocationBytes);\n  json.WriteString(\"UnusedRangeCount\");\n  json.WriteNumber(stat.unusedRangeCount);\n\n  if (stat.statistics.allocationCount > 1)\n  {\n    json.WriteString(\"AllocationSizeMin\");\n    json.WriteNumber(stat.allocationSizeMin);\n    json.WriteString(\"AllocationSizeMax\");\n    json.WriteNumber(stat.allocationSizeMax);\n  }\n  if (stat.unusedRangeCount > 1)\n  {\n    json.WriteString(\"UnusedRangeSizeMin\");\n    json.WriteNumber(stat.unusedRangeSizeMin);\n    json.WriteString(\"UnusedRangeSizeMax\");\n    json.WriteNumber(stat.unusedRangeSizeMax);\n  }\n  json.EndObject();\n}\n#endif // _VMA_JSON_WRITER\n\n#ifndef _VMA_MAPPING_HYSTERESIS\n\nclass VmaMappingHysteresis\n{\n  VMA_CLASS_NO_COPY(VmaMappingHysteresis)\n public:\n  VmaMappingHysteresis() = default;\n\n  uint32_t GetExtraMapping() const { return m_ExtraMapping; }\n\n  // Call when Map was called.\n  // Returns true if switched to extra +1 mapping reference count.\n  bool PostMap()\n  {\n#if VMA_MAPPING_HYSTERESIS_ENABLED\n    if(m_ExtraMapping == 0)\n    {\n      ++m_MajorCounter;\n      if(m_MajorCounter >= COUNTER_MIN_EXTRA_MAPPING)\n      {\n        m_ExtraMapping = 1;\n        m_MajorCounter = 0;\n        m_MinorCounter = 0;\n        return true;\n      }\n    }\n    else // m_ExtraMapping == 1\n      PostMinorCounter();\n#endif // #if VMA_MAPPING_HYSTERESIS_ENABLED\n    return false;\n  }\n\n  // Call when Unmap was called.\n  void PostUnmap()\n  {\n#if VMA_MAPPING_HYSTERESIS_ENABLED\n    if(m_ExtraMapping == 0)\n      ++m_MajorCounter;\n    else // m_ExtraMapping == 1\n      PostMinorCounter();\n#endif // #if VMA_MAPPING_HYSTERESIS_ENABLED\n  }\n\n  // Call when allocation was made from the memory block.\n  void PostAlloc()\n  {\n#if VMA_MAPPING_HYSTERESIS_ENABLED\n    if(m_ExtraMapping == 1)\n      ++m_MajorCounter;\n    else // m_ExtraMapping == 0\n      PostMinorCounter();\n#endif // #if VMA_MAPPING_HYSTERESIS_ENABLED\n  }\n\n  // Call when allocation was freed from the memory block.\n  // Returns true if switched to extra -1 mapping reference count.\n  bool PostFree()\n  {\n#if VMA_MAPPING_HYSTERESIS_ENABLED\n    if(m_ExtraMapping == 1)\n    {\n      ++m_MajorCounter;\n      if(m_MajorCounter >= COUNTER_MIN_EXTRA_MAPPING &&\n          m_MajorCounter > m_MinorCounter + 1)\n      {\n        m_ExtraMapping = 0;\n        m_MajorCounter = 0;\n        m_MinorCounter = 0;\n        return true;\n      }\n    }\n    else // m_ExtraMapping == 0\n      PostMinorCounter();\n#endif // #if VMA_MAPPING_HYSTERESIS_ENABLED\n    return false;\n  }\n\n private:\n  static const int32_t COUNTER_MIN_EXTRA_MAPPING = 7;\n\n  uint32_t m_MinorCounter = 0;\n  uint32_t m_MajorCounter = 0;\n  uint32_t m_ExtraMapping = 0; // 0 or 1.\n\n  void PostMinorCounter()\n  {\n    if(m_MinorCounter < m_MajorCounter)\n    {\n      ++m_MinorCounter;\n    }\n    else if(m_MajorCounter > 0)\n    {\n      --m_MajorCounter;\n      --m_MinorCounter;\n    }\n  }\n};\n\n#endif // _VMA_MAPPING_HYSTERESIS\n\n#ifndef _VMA_DEVICE_MEMORY_BLOCK\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:\n- Access to m_pMetadata must be externally synchronized.\n- Map, Unmap, Bind* are synchronized internally.\n*/\nclass VmaDeviceMemoryBlock\n{\n  VMA_CLASS_NO_COPY(VmaDeviceMemoryBlock)\n public:\n  VmaBlockMetadata* m_pMetadata;\n\n  VmaDeviceMemoryBlock(VmaAllocator hAllocator);\n  ~VmaDeviceMemoryBlock();\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      VkDeviceSize bufferImageGranularity);\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  uint32_t GetMapRefCount() const { return m_MapCount; }\n\n  // Call when allocation/free was made from m_pMetadata.\n  // Used for m_MappingHysteresis.\n  void PostAlloc(VmaAllocator hAllocator);\n  void PostFree(VmaAllocator hAllocator);\n\n  // Validates all data structures inside this object. If not valid, returns false.\n  bool Validate() const;\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 WriteMagicValueAfterAllocation(VmaAllocator hAllocator, VkDeviceSize allocOffset, VkDeviceSize allocSize);\n  VkResult ValidateMagicValueAfterAllocation(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\n private:\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 is 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_MapAndBindMutex;\n  VmaMappingHysteresis m_MappingHysteresis;\n  uint32_t m_MapCount;\n  void* m_pMappedData;\n};\n#endif // _VMA_DEVICE_MEMORY_BLOCK\n\n#ifndef _VMA_ALLOCATION_T\nstruct VmaAllocation_T\n{\n  friend struct VmaDedicatedAllocationListItemTraits;\n\n  enum FLAGS\n  {\n    FLAG_PERSISTENT_MAP   = 0x01,\n    FLAG_MAPPING_ALLOWED  = 0x02,\n  };\n\n public:\n  enum ALLOCATION_TYPE\n  {\n    ALLOCATION_TYPE_NONE,\n    ALLOCATION_TYPE_BLOCK,\n    ALLOCATION_TYPE_DEDICATED,\n  };\n\n  // This struct is allocated using VmaPoolAllocator.\n  VmaAllocation_T(bool mappingAllowed);\n  ~VmaAllocation_T();\n\n  void InitBlockAllocation(\n      VmaDeviceMemoryBlock* block,\n      VmaAllocHandle allocHandle,\n      VkDeviceSize alignment,\n      VkDeviceSize size,\n      uint32_t memoryTypeIndex,\n      VmaSuballocationType suballocationType,\n      bool mapped);\n  // pMappedData not null means allocation is created with MAPPED flag.\n  void InitDedicatedAllocation(\n      VmaPool hParentPool,\n      uint32_t memoryTypeIndex,\n      VkDeviceMemory hMemory,\n      VmaSuballocationType suballocationType,\n      void* pMappedData,\n      VkDeviceSize size);\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  void* GetUserData() const { return m_pUserData; }\n  const char* GetName() const { return m_pName; }\n  VmaSuballocationType GetSuballocationType() const { return (VmaSuballocationType)m_SuballocationType; }\n\n  VmaDeviceMemoryBlock* GetBlock() const { VMA_ASSERT(m_Type == ALLOCATION_TYPE_BLOCK); return m_BlockAllocation.m_Block; }\n  uint32_t GetMemoryTypeIndex() const { return m_MemoryTypeIndex; }\n  bool IsPersistentMap() const { return (m_Flags & FLAG_PERSISTENT_MAP) != 0; }\n  bool IsMappingAllowed() const { return (m_Flags & FLAG_MAPPING_ALLOWED) != 0; }\n\n  void SetUserData(VmaAllocator hAllocator, void* pUserData) { m_pUserData = pUserData; }\n  void SetName(VmaAllocator hAllocator, const char* pName);\n  void FreeName(VmaAllocator hAllocator);\n  uint8_t SwapBlockAllocation(VmaAllocator hAllocator, VmaAllocation allocation);\n  VmaAllocHandle GetAllocHandle() const;\n  VkDeviceSize GetOffset() const;\n  VmaPool GetParentPool() const;\n  VkDeviceMemory GetMemory() const;\n  void* GetMappedData() const;\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 GetBufferImageUsage() const { return m_BufferImageUsage; }\n\n  void InitBufferImageUsage(uint32_t bufferImageUsage);\n  void PrintParameters(class VmaJsonWriter& json) const;\n#endif\n\n private:\n  // Allocation out of VmaDeviceMemoryBlock.\n  struct BlockAllocation\n  {\n    VmaDeviceMemoryBlock* m_Block;\n    VmaAllocHandle m_AllocHandle;\n  };\n  // Allocation for an object that has its own private VkDeviceMemory.\n  struct DedicatedAllocation\n  {\n    VmaPool m_hParentPool; // VK_NULL_HANDLE if not belongs to custom pool.\n    VkDeviceMemory m_hMemory;\n    void* m_pMappedData; // Not null means memory is mapped.\n    VmaAllocation_T* m_Prev;\n    VmaAllocation_T* m_Next;\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  VkDeviceSize m_Alignment;\n  VkDeviceSize m_Size;\n  void* m_pUserData;\n  char* m_pName;\n  uint32_t m_MemoryTypeIndex;\n  uint8_t m_Type; // ALLOCATION_TYPE\n  uint8_t m_SuballocationType; // VmaSuballocationType\n  // Reference counter for vmaMapMemory()/vmaUnmapMemory().\n  uint8_t m_MapCount;\n  uint8_t m_Flags; // enum FLAGS\n#if VMA_STATS_STRING_ENABLED\n  uint32_t m_BufferImageUsage; // 0 if unknown.\n#endif\n};\n#endif // _VMA_ALLOCATION_T\n\n#ifndef _VMA_DEDICATED_ALLOCATION_LIST_ITEM_TRAITS\nstruct VmaDedicatedAllocationListItemTraits\n{\n  typedef VmaAllocation_T ItemType;\n\n  static ItemType* GetPrev(const ItemType* item)\n  {\n    VMA_HEAVY_ASSERT(item->GetType() == VmaAllocation_T::ALLOCATION_TYPE_DEDICATED);\n    return item->m_DedicatedAllocation.m_Prev;\n  }\n  static ItemType* GetNext(const ItemType* item)\n  {\n    VMA_HEAVY_ASSERT(item->GetType() == VmaAllocation_T::ALLOCATION_TYPE_DEDICATED);\n    return item->m_DedicatedAllocation.m_Next;\n  }\n  static ItemType*& AccessPrev(ItemType* item)\n  {\n    VMA_HEAVY_ASSERT(item->GetType() == VmaAllocation_T::ALLOCATION_TYPE_DEDICATED);\n    return item->m_DedicatedAllocation.m_Prev;\n  }\n  static ItemType*& AccessNext(ItemType* item)\n  {\n    VMA_HEAVY_ASSERT(item->GetType() == VmaAllocation_T::ALLOCATION_TYPE_DEDICATED);\n    return item->m_DedicatedAllocation.m_Next;\n  }\n};\n#endif // _VMA_DEDICATED_ALLOCATION_LIST_ITEM_TRAITS\n\n#ifndef _VMA_DEDICATED_ALLOCATION_LIST\n/*\nStores linked list of VmaAllocation_T objects.\nThread-safe, synchronized internally.\n*/\nclass VmaDedicatedAllocationList\n{\n public:\n  VmaDedicatedAllocationList() {}\n  ~VmaDedicatedAllocationList();\n\n  void Init(bool useMutex) { m_UseMutex = useMutex; }\n  bool Validate();\n\n  void AddDetailedStatistics(VmaDetailedStatistics& inoutStats);\n  void AddStatistics(VmaStatistics& inoutStats);\n#if VMA_STATS_STRING_ENABLED\n  // Writes JSON array with the list of allocations.\n  void BuildStatsString(VmaJsonWriter& json);\n#endif\n\n  bool IsEmpty();\n  void Register(VmaAllocation alloc);\n  void Unregister(VmaAllocation alloc);\n\n private:\n  typedef VmaIntrusiveLinkedList<VmaDedicatedAllocationListItemTraits> DedicatedAllocationLinkedList;\n\n  bool m_UseMutex = true;\n  VMA_RW_MUTEX m_Mutex;\n  DedicatedAllocationLinkedList m_AllocationList;\n};\n\n#ifndef _VMA_DEDICATED_ALLOCATION_LIST_FUNCTIONS\n\nVmaDedicatedAllocationList::~VmaDedicatedAllocationList()\n{\n  VMA_HEAVY_ASSERT(Validate());\n\n  if (!m_AllocationList.IsEmpty())\n  {\n    VMA_ASSERT(false && \"Unfreed dedicated allocations found!\");\n  }\n}\n\nbool VmaDedicatedAllocationList::Validate()\n{\n  const size_t declaredCount = m_AllocationList.GetCount();\n  size_t actualCount = 0;\n  VmaMutexLockRead lock(m_Mutex, m_UseMutex);\n  for (VmaAllocation alloc = m_AllocationList.Front();\n       alloc != VMA_NULL; alloc = m_AllocationList.GetNext(alloc))\n  {\n    ++actualCount;\n  }\n  VMA_VALIDATE(actualCount == declaredCount);\n\n  return true;\n}\n\nvoid VmaDedicatedAllocationList::AddDetailedStatistics(VmaDetailedStatistics& inoutStats)\n{\n  for(auto* item = m_AllocationList.Front(); item != nullptr; item = DedicatedAllocationLinkedList::GetNext(item))\n  {\n    const VkDeviceSize size = item->GetSize();\n    inoutStats.statistics.blockCount++;\n    inoutStats.statistics.blockBytes += size;\n    VmaAddDetailedStatisticsAllocation(inoutStats, item->GetSize());\n  }\n}\n\nvoid VmaDedicatedAllocationList::AddStatistics(VmaStatistics& inoutStats)\n{\n  VmaMutexLockRead lock(m_Mutex, m_UseMutex);\n\n  const uint32_t allocCount = (uint32_t)m_AllocationList.GetCount();\n  inoutStats.blockCount += allocCount;\n  inoutStats.allocationCount += allocCount;\n\n  for(auto* item = m_AllocationList.Front(); item != nullptr; item = DedicatedAllocationLinkedList::GetNext(item))\n  {\n    const VkDeviceSize size = item->GetSize();\n    inoutStats.blockBytes += size;\n    inoutStats.allocationBytes += size;\n  }\n}\n\n#if VMA_STATS_STRING_ENABLED\nvoid VmaDedicatedAllocationList::BuildStatsString(VmaJsonWriter& json)\n{\n  VmaMutexLockRead lock(m_Mutex, m_UseMutex);\n  json.BeginArray();\n  for (VmaAllocation alloc = m_AllocationList.Front();\n       alloc != VMA_NULL; alloc = m_AllocationList.GetNext(alloc))\n  {\n    json.BeginObject(true);\n    alloc->PrintParameters(json);\n    json.EndObject();\n  }\n  json.EndArray();\n}\n#endif // VMA_STATS_STRING_ENABLED\n\nbool VmaDedicatedAllocationList::IsEmpty()\n{\n  VmaMutexLockRead lock(m_Mutex, m_UseMutex);\n  return m_AllocationList.IsEmpty();\n}\n\nvoid VmaDedicatedAllocationList::Register(VmaAllocation alloc)\n{\n  VmaMutexLockWrite lock(m_Mutex, m_UseMutex);\n  m_AllocationList.PushBack(alloc);\n}\n\nvoid VmaDedicatedAllocationList::Unregister(VmaAllocation alloc)\n{\n  VmaMutexLockWrite lock(m_Mutex, m_UseMutex);\n  m_AllocationList.Remove(alloc);\n}\n#endif // _VMA_DEDICATED_ALLOCATION_LIST_FUNCTIONS\n#endif // _VMA_DEDICATED_ALLOCATION_LIST\n\n#ifndef _VMA_SUBALLOCATION\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  void* userData;\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};\n\nstruct VmaSuballocationOffsetGreater\n{\n  bool operator()(const VmaSuballocation& lhs, const VmaSuballocation& rhs) const\n  {\n    return lhs.offset > rhs.offset;\n  }\n};\n\nstruct VmaSuballocationItemSizeLess\n{\n  bool operator()(const VmaSuballocationList::iterator lhs,\n                  const VmaSuballocationList::iterator rhs) const\n  {\n    return lhs->size < rhs->size;\n  }\n\n  bool operator()(const VmaSuballocationList::iterator lhs,\n                  VkDeviceSize rhsSize) const\n  {\n    return lhs->size < rhsSize;\n  }\n};\n#endif // _VMA_SUBALLOCATION\n\n#ifndef _VMA_ALLOCATION_REQUEST\n/*\nParameters of planned allocation inside a VmaDeviceMemoryBlock.\nitem points to a FREE suballocation.\n*/\nstruct VmaAllocationRequest\n{\n  VmaAllocHandle allocHandle;\n  VkDeviceSize size;\n  VmaSuballocationList::iterator item;\n  void* customData;\n  uint64_t algorithmData;\n  VmaAllocationRequestType type;\n};\n#endif // _VMA_ALLOCATION_REQUEST\n\n#ifndef _VMA_BLOCK_METADATA\n/*\nData structure used for bookkeeping of allocations and unused ranges of memory\nin a single VkDeviceMemory block.\n*/\nclass VmaBlockMetadata\n{\n public:\n  // pAllocationCallbacks, if not null, must be owned externally - alive and unchanged for the whole lifetime of this object.\n  VmaBlockMetadata(const VkAllocationCallbacks* pAllocationCallbacks,\n                   VkDeviceSize bufferImageGranularity, bool isVirtual);\n  virtual ~VmaBlockMetadata() = default;\n\n  virtual void Init(VkDeviceSize size) { m_Size = size; }\n  bool IsVirtual() const { return m_IsVirtual; }\n  VkDeviceSize GetSize() const { return m_Size; }\n\n  // Validates all data structures inside this object. If not valid, returns false.\n  virtual bool Validate() const = 0;\n  virtual size_t GetAllocationCount() const = 0;\n  virtual size_t GetFreeRegionsCount() const = 0;\n  virtual VkDeviceSize GetSumFreeSize() const = 0;\n  // Returns true if this block is empty - contains only single free suballocation.\n  virtual bool IsEmpty() const = 0;\n  virtual void GetAllocationInfo(VmaAllocHandle allocHandle, VmaVirtualAllocationInfo& outInfo) = 0;\n  virtual VkDeviceSize GetAllocationOffset(VmaAllocHandle allocHandle) const = 0;\n  virtual void* GetAllocationUserData(VmaAllocHandle allocHandle) const = 0;\n\n  virtual VmaAllocHandle GetAllocationListBegin() const = 0;\n  virtual VmaAllocHandle GetNextAllocation(VmaAllocHandle prevAlloc) const = 0;\n  virtual VkDeviceSize GetNextFreeRegionSize(VmaAllocHandle alloc) const = 0;\n\n  // Shouldn't modify blockCount.\n  virtual void AddDetailedStatistics(VmaDetailedStatistics& inoutStats) const = 0;\n  virtual void AddStatistics(VmaStatistics& 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      VkDeviceSize allocSize,\n      VkDeviceSize allocAlignment,\n      bool upperAddress,\n      VmaSuballocationType allocType,\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 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      void* userData) = 0;\n\n  // Frees suballocation assigned to given memory region.\n  virtual void Free(VmaAllocHandle allocHandle) = 0;\n\n  // Frees all allocations.\n  // Careful! Don't call it if there are VmaAllocation objects owned by userData of cleared allocations!\n  virtual void Clear() = 0;\n\n  virtual void SetAllocationUserData(VmaAllocHandle allocHandle, void* userData) = 0;\n  virtual void DebugLogAllAllocations() const = 0;\n\n protected:\n  const VkAllocationCallbacks* GetAllocationCallbacks() const { return m_pAllocationCallbacks; }\n  VkDeviceSize GetBufferImageGranularity() const { return m_BufferImageGranularity; }\n  VkDeviceSize GetDebugMargin() const { return IsVirtual() ? 0 : VMA_DEBUG_MARGIN; }\n\n  void DebugLogAllocation(VkDeviceSize offset, VkDeviceSize size, void* userData) const;\n#if VMA_STATS_STRING_ENABLED\n  // mapRefCount == UINT32_MAX means unspecified.\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, VkDeviceSize size, void* userData) 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\n private:\n  VkDeviceSize m_Size;\n  const VkAllocationCallbacks* m_pAllocationCallbacks;\n  const VkDeviceSize m_BufferImageGranularity;\n  const bool m_IsVirtual;\n};\n\n#ifndef _VMA_BLOCK_METADATA_FUNCTIONS\nVmaBlockMetadata::VmaBlockMetadata(const VkAllocationCallbacks* pAllocationCallbacks,\n                                   VkDeviceSize bufferImageGranularity, bool isVirtual)\n    : m_Size(0),\n      m_pAllocationCallbacks(pAllocationCallbacks),\n      m_BufferImageGranularity(bufferImageGranularity),\n      m_IsVirtual(isVirtual) {}\n\nvoid VmaBlockMetadata::DebugLogAllocation(VkDeviceSize offset, VkDeviceSize size, void* userData) const\n{\n  if (IsVirtual())\n  {\n    VMA_DEBUG_LOG_FORMAT(\"UNFREED VIRTUAL ALLOCATION; Offset: %llu; Size: %llu; UserData: %p\", offset, size, userData);\n  }\n  else\n  {\n    VMA_ASSERT(userData != VMA_NULL);\n    VmaAllocation allocation = reinterpret_cast<VmaAllocation>(userData);\n\n    userData = allocation->GetUserData();\n    const char* name = allocation->GetName();\n\n#if VMA_STATS_STRING_ENABLED\n    VMA_DEBUG_LOG_FORMAT(\"UNFREED ALLOCATION; Offset: %llu; Size: %llu; UserData: %p; Name: %s; Type: %s; Usage: %u\",\n                         offset, size, userData, name ? name : \"vma_empty\",\n                         VMA_SUBALLOCATION_TYPE_NAMES[allocation->GetSuballocationType()],\n                         allocation->GetBufferImageUsage());\n#else\n    VMA_DEBUG_LOG_FORMAT(\"UNFREED ALLOCATION; Offset: %llu; Size: %llu; UserData: %p; Name: %s; Type: %u\",\n                         offset, size, userData, name ? name : \"vma_empty\",\n                         (uint32_t)allocation->GetSuballocationType());\n#endif // VMA_STATS_STRING_ENABLED\n  }\n\n}\n\n#if VMA_STATS_STRING_ENABLED\nvoid VmaBlockMetadata::PrintDetailedMap_Begin(class VmaJsonWriter& json,\n                                              VkDeviceSize unusedBytes, size_t allocationCount, size_t unusedRangeCount) const\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, VkDeviceSize size, void* userData) const\n{\n  json.BeginObject(true);\n\n  json.WriteString(\"Offset\");\n  json.WriteNumber(offset);\n\n  if (IsVirtual())\n  {\n    json.WriteString(\"Size\");\n    json.WriteNumber(size);\n    if (userData)\n    {\n      json.WriteString(\"CustomData\");\n      json.BeginString();\n      json.ContinueString_Pointer(userData);\n      json.EndString();\n    }\n  }\n  else\n  {\n    ((VmaAllocation)userData)->PrintParameters(json);\n  }\n\n  json.EndObject();\n}\n\nvoid VmaBlockMetadata::PrintDetailedMap_UnusedRange(class VmaJsonWriter& json,\n                                                    VkDeviceSize offset, 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}\n#endif // VMA_STATS_STRING_ENABLED\n#endif // _VMA_BLOCK_METADATA_FUNCTIONS\n#endif // _VMA_BLOCK_METADATA\n\n#ifndef _VMA_BLOCK_BUFFER_IMAGE_GRANULARITY\n// Before deleting object of this class remember to call 'Destroy()'\nclass VmaBlockBufferImageGranularity final\n{\n public:\n  struct ValidationContext\n  {\n    const VkAllocationCallbacks* allocCallbacks;\n    uint16_t* pageAllocs;\n  };\n\n  VmaBlockBufferImageGranularity(VkDeviceSize bufferImageGranularity);\n  ~VmaBlockBufferImageGranularity();\n\n  bool IsEnabled() const { return m_BufferImageGranularity > MAX_LOW_BUFFER_IMAGE_GRANULARITY; }\n\n  void Init(const VkAllocationCallbacks* pAllocationCallbacks, VkDeviceSize size);\n  // Before destroying object you must call free it's memory\n  void Destroy(const VkAllocationCallbacks* pAllocationCallbacks);\n\n  void RoundupAllocRequest(VmaSuballocationType allocType,\n                           VkDeviceSize& inOutAllocSize,\n                           VkDeviceSize& inOutAllocAlignment) const;\n\n  bool CheckConflictAndAlignUp(VkDeviceSize& inOutAllocOffset,\n                               VkDeviceSize allocSize,\n                               VkDeviceSize blockOffset,\n                               VkDeviceSize blockSize,\n                               VmaSuballocationType allocType) const;\n\n  void AllocPages(uint8_t allocType, VkDeviceSize offset, VkDeviceSize size);\n  void FreePages(VkDeviceSize offset, VkDeviceSize size);\n  void Clear();\n\n  ValidationContext StartValidation(const VkAllocationCallbacks* pAllocationCallbacks,\n                                    bool isVirutal) const;\n  bool Validate(ValidationContext& ctx, VkDeviceSize offset, VkDeviceSize size) const;\n  bool FinishValidation(ValidationContext& ctx) const;\n\n private:\n  static const uint16_t MAX_LOW_BUFFER_IMAGE_GRANULARITY = 256;\n\n  struct RegionInfo\n  {\n    uint8_t allocType;\n    uint16_t allocCount;\n  };\n\n  VkDeviceSize m_BufferImageGranularity;\n  uint32_t m_RegionCount;\n  RegionInfo* m_RegionInfo;\n\n  uint32_t GetStartPage(VkDeviceSize offset) const { return OffsetToPageIndex(offset & ~(m_BufferImageGranularity - 1)); }\n  uint32_t GetEndPage(VkDeviceSize offset, VkDeviceSize size) const { return OffsetToPageIndex((offset + size - 1) & ~(m_BufferImageGranularity - 1)); }\n\n  uint32_t OffsetToPageIndex(VkDeviceSize offset) const;\n  void AllocPage(RegionInfo& page, uint8_t allocType);\n};\n\n#ifndef _VMA_BLOCK_BUFFER_IMAGE_GRANULARITY_FUNCTIONS\nVmaBlockBufferImageGranularity::VmaBlockBufferImageGranularity(VkDeviceSize bufferImageGranularity)\n    : m_BufferImageGranularity(bufferImageGranularity),\n      m_RegionCount(0),\n      m_RegionInfo(VMA_NULL) {}\n\nVmaBlockBufferImageGranularity::~VmaBlockBufferImageGranularity()\n{\n  VMA_ASSERT(m_RegionInfo == VMA_NULL && \"Free not called before destroying object!\");\n}\n\nvoid VmaBlockBufferImageGranularity::Init(const VkAllocationCallbacks* pAllocationCallbacks, VkDeviceSize size)\n{\n  if (IsEnabled())\n  {\n    m_RegionCount = static_cast<uint32_t>(VmaDivideRoundingUp(size, m_BufferImageGranularity));\n    m_RegionInfo = vma_new_array(pAllocationCallbacks, RegionInfo, m_RegionCount);\n    memset(m_RegionInfo, 0, m_RegionCount * sizeof(RegionInfo));\n  }\n}\n\nvoid VmaBlockBufferImageGranularity::Destroy(const VkAllocationCallbacks* pAllocationCallbacks)\n{\n  if (m_RegionInfo)\n  {\n    vma_delete_array(pAllocationCallbacks, m_RegionInfo, m_RegionCount);\n    m_RegionInfo = VMA_NULL;\n  }\n}\n\nvoid VmaBlockBufferImageGranularity::RoundupAllocRequest(VmaSuballocationType allocType,\n                                                         VkDeviceSize& inOutAllocSize,\n                                                         VkDeviceSize& inOutAllocAlignment) const\n{\n  if (m_BufferImageGranularity > 1 &&\n      m_BufferImageGranularity <= MAX_LOW_BUFFER_IMAGE_GRANULARITY)\n  {\n    if (allocType == VMA_SUBALLOCATION_TYPE_UNKNOWN ||\n        allocType == VMA_SUBALLOCATION_TYPE_IMAGE_UNKNOWN ||\n        allocType == VMA_SUBALLOCATION_TYPE_IMAGE_OPTIMAL)\n    {\n      inOutAllocAlignment = VMA_MAX(inOutAllocAlignment, m_BufferImageGranularity);\n      inOutAllocSize = VmaAlignUp(inOutAllocSize, m_BufferImageGranularity);\n    }\n  }\n}\n\nbool VmaBlockBufferImageGranularity::CheckConflictAndAlignUp(VkDeviceSize& inOutAllocOffset,\n                                                             VkDeviceSize allocSize,\n                                                             VkDeviceSize blockOffset,\n                                                             VkDeviceSize blockSize,\n                                                             VmaSuballocationType allocType) const\n{\n  if (IsEnabled())\n  {\n    uint32_t startPage = GetStartPage(inOutAllocOffset);\n    if (m_RegionInfo[startPage].allocCount > 0 &&\n        VmaIsBufferImageGranularityConflict(static_cast<VmaSuballocationType>(m_RegionInfo[startPage].allocType), allocType))\n    {\n      inOutAllocOffset = VmaAlignUp(inOutAllocOffset, m_BufferImageGranularity);\n      if (blockSize < allocSize + inOutAllocOffset - blockOffset)\n        return true;\n      ++startPage;\n    }\n    uint32_t endPage = GetEndPage(inOutAllocOffset, allocSize);\n    if (endPage != startPage &&\n        m_RegionInfo[endPage].allocCount > 0 &&\n        VmaIsBufferImageGranularityConflict(static_cast<VmaSuballocationType>(m_RegionInfo[endPage].allocType), allocType))\n    {\n      return true;\n    }\n  }\n  return false;\n}\n\nvoid VmaBlockBufferImageGranularity::AllocPages(uint8_t allocType, VkDeviceSize offset, VkDeviceSize size)\n{\n  if (IsEnabled())\n  {\n    uint32_t startPage = GetStartPage(offset);\n    AllocPage(m_RegionInfo[startPage], allocType);\n\n    uint32_t endPage = GetEndPage(offset, size);\n    if (startPage != endPage)\n      AllocPage(m_RegionInfo[endPage], allocType);\n  }\n}\n\nvoid VmaBlockBufferImageGranularity::FreePages(VkDeviceSize offset, VkDeviceSize size)\n{\n  if (IsEnabled())\n  {\n    uint32_t startPage = GetStartPage(offset);\n    --m_RegionInfo[startPage].allocCount;\n    if (m_RegionInfo[startPage].allocCount == 0)\n      m_RegionInfo[startPage].allocType = VMA_SUBALLOCATION_TYPE_FREE;\n    uint32_t endPage = GetEndPage(offset, size);\n    if (startPage != endPage)\n    {\n      --m_RegionInfo[endPage].allocCount;\n      if (m_RegionInfo[endPage].allocCount == 0)\n        m_RegionInfo[endPage].allocType = VMA_SUBALLOCATION_TYPE_FREE;\n    }\n  }\n}\n\nvoid VmaBlockBufferImageGranularity::Clear()\n{\n  if (m_RegionInfo)\n    memset(m_RegionInfo, 0, m_RegionCount * sizeof(RegionInfo));\n}\n\nVmaBlockBufferImageGranularity::ValidationContext VmaBlockBufferImageGranularity::StartValidation(\n    const VkAllocationCallbacks* pAllocationCallbacks, bool isVirutal) const\n{\n  ValidationContext ctx{ pAllocationCallbacks, VMA_NULL };\n  if (!isVirutal && IsEnabled())\n  {\n    ctx.pageAllocs = vma_new_array(pAllocationCallbacks, uint16_t, m_RegionCount);\n    memset(ctx.pageAllocs, 0, m_RegionCount * sizeof(uint16_t));\n  }\n  return ctx;\n}\n\nbool VmaBlockBufferImageGranularity::Validate(ValidationContext& ctx,\n                                              VkDeviceSize offset, VkDeviceSize size) const\n{\n  if (IsEnabled())\n  {\n    uint32_t start = GetStartPage(offset);\n    ++ctx.pageAllocs[start];\n    VMA_VALIDATE(m_RegionInfo[start].allocCount > 0);\n\n    uint32_t end = GetEndPage(offset, size);\n    if (start != end)\n    {\n      ++ctx.pageAllocs[end];\n      VMA_VALIDATE(m_RegionInfo[end].allocCount > 0);\n    }\n  }\n  return true;\n}\n\nbool VmaBlockBufferImageGranularity::FinishValidation(ValidationContext& ctx) const\n{\n  // Check proper page structure\n  if (IsEnabled())\n  {\n    VMA_ASSERT(ctx.pageAllocs != VMA_NULL && \"Validation context not initialized!\");\n\n    for (uint32_t page = 0; page < m_RegionCount; ++page)\n    {\n      VMA_VALIDATE(ctx.pageAllocs[page] == m_RegionInfo[page].allocCount);\n    }\n    vma_delete_array(ctx.allocCallbacks, ctx.pageAllocs, m_RegionCount);\n    ctx.pageAllocs = VMA_NULL;\n  }\n  return true;\n}\n\nuint32_t VmaBlockBufferImageGranularity::OffsetToPageIndex(VkDeviceSize offset) const\n{\n  return static_cast<uint32_t>(offset >> VMA_BITSCAN_MSB(m_BufferImageGranularity));\n}\n\nvoid VmaBlockBufferImageGranularity::AllocPage(RegionInfo& page, uint8_t allocType)\n{\n  // When current alloc type is free then it can be overridden by new type\n  if (page.allocCount == 0 || (page.allocCount > 0 && page.allocType == VMA_SUBALLOCATION_TYPE_FREE))\n    page.allocType = allocType;\n\n  ++page.allocCount;\n}\n#endif // _VMA_BLOCK_BUFFER_IMAGE_GRANULARITY_FUNCTIONS\n#endif // _VMA_BLOCK_BUFFER_IMAGE_GRANULARITY\n\n#if 0\n#ifndef _VMA_BLOCK_METADATA_GENERIC\nclass VmaBlockMetadata_Generic : public VmaBlockMetadata\n{\n    friend class VmaDefragmentationAlgorithm_Generic;\n    friend class VmaDefragmentationAlgorithm_Fast;\n    VMA_CLASS_NO_COPY(VmaBlockMetadata_Generic)\npublic:\n    VmaBlockMetadata_Generic(const VkAllocationCallbacks* pAllocationCallbacks,\n        VkDeviceSize bufferImageGranularity, bool isVirtual);\n    virtual ~VmaBlockMetadata_Generic() = default;\n\n    size_t GetAllocationCount() const override { return m_Suballocations.size() - m_FreeCount; }\n    VkDeviceSize GetSumFreeSize() const override { return m_SumFreeSize; }\n    bool IsEmpty() const override { return (m_Suballocations.size() == 1) && (m_FreeCount == 1); }\n    void Free(VmaAllocHandle allocHandle) override { FreeSuballocation(FindAtOffset((VkDeviceSize)allocHandle - 1)); }\n    VkDeviceSize GetAllocationOffset(VmaAllocHandle allocHandle) const override { return (VkDeviceSize)allocHandle - 1; }\n\n    void Init(VkDeviceSize size) override;\n    bool Validate() const override;\n\n    void AddDetailedStatistics(VmaDetailedStatistics& inoutStats) const override;\n    void AddStatistics(VmaStatistics& inoutStats) const override;\n\n#if VMA_STATS_STRING_ENABLED\n    void PrintDetailedMap(class VmaJsonWriter& json, uint32_t mapRefCount) const override;\n#endif\n\n    bool CreateAllocationRequest(\n        VkDeviceSize allocSize,\n        VkDeviceSize allocAlignment,\n        bool upperAddress,\n        VmaSuballocationType allocType,\n        uint32_t strategy,\n        VmaAllocationRequest* pAllocationRequest) override;\n\n    VkResult CheckCorruption(const void* pBlockData) override;\n\n    void Alloc(\n        const VmaAllocationRequest& request,\n        VmaSuballocationType type,\n        void* userData) override;\n\n    void GetAllocationInfo(VmaAllocHandle allocHandle, VmaVirtualAllocationInfo& outInfo) override;\n    void* GetAllocationUserData(VmaAllocHandle allocHandle) const override;\n    VmaAllocHandle GetAllocationListBegin() const override;\n    VmaAllocHandle GetNextAllocation(VmaAllocHandle prevAlloc) const override;\n    void Clear() override;\n    void SetAllocationUserData(VmaAllocHandle allocHandle, void* userData) override;\n    void DebugLogAllAllocations() const override;\n\nprivate:\n    uint32_t m_FreeCount;\n    VkDeviceSize m_SumFreeSize;\n    VmaSuballocationList m_Suballocations;\n    // Suballocations that are free. Sorted by size, ascending.\n    VmaVector<VmaSuballocationList::iterator, VmaStlAllocator<VmaSuballocationList::iterator>> m_FreeSuballocationsBySize;\n\n    VkDeviceSize AlignAllocationSize(VkDeviceSize size) const { return IsVirtual() ? size : VmaAlignUp(size, (VkDeviceSize)16); }\n\n    VmaSuballocationList::iterator FindAtOffset(VkDeviceSize offset) const;\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        VkDeviceSize allocSize,\n        VkDeviceSize allocAlignment,\n        VmaSuballocationType allocType,\n        VmaSuballocationList::const_iterator suballocItem,\n        VmaAllocHandle* pAllocHandle) const;\n\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 is suitable.\n    void RegisterFreeSuballocation(VmaSuballocationList::iterator item);\n    // Given free suballocation, it removes it from sorted list of\n    // m_FreeSuballocationsBySize if it is suitable.\n    void UnregisterFreeSuballocation(VmaSuballocationList::iterator item);\n};\n\n#ifndef _VMA_BLOCK_METADATA_GENERIC_FUNCTIONS\nVmaBlockMetadata_Generic::VmaBlockMetadata_Generic(const VkAllocationCallbacks* pAllocationCallbacks,\n    VkDeviceSize bufferImageGranularity, bool isVirtual)\n    : VmaBlockMetadata(pAllocationCallbacks, bufferImageGranularity, isVirtual),\n    m_FreeCount(0),\n    m_SumFreeSize(0),\n    m_Suballocations(VmaStlAllocator<VmaSuballocation>(pAllocationCallbacks)),\n    m_FreeSuballocationsBySize(VmaStlAllocator<VmaSuballocationList::iterator>(pAllocationCallbacks)) {}\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\n    m_Suballocations.push_back(suballoc);\n    m_FreeSuballocationsBySize.push_back(m_Suballocations.begin());\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    const VkDeviceSize debugMargin = GetDebugMargin();\n\n    for (const auto& subAlloc : m_Suballocations)\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        VmaAllocation alloc = (VmaAllocation)subAlloc.userData;\n        if (!IsVirtual())\n        {\n            VMA_VALIDATE(currFree == (alloc == VK_NULL_HANDLE));\n        }\n\n        if (currFree)\n        {\n            calculatedSumFreeSize += subAlloc.size;\n            ++calculatedFreeCount;\n            ++freeSuballocationsToRegister;\n\n            // Margin required between allocations - every free space must be at least that large.\n            VMA_VALIDATE(subAlloc.size >= debugMargin);\n        }\n        else\n        {\n            if (!IsVirtual())\n            {\n                VMA_VALIDATE((VkDeviceSize)alloc->GetAllocHandle() == subAlloc.offset + 1);\n                VMA_VALIDATE(alloc->GetSize() == subAlloc.size);\n            }\n\n            // Margin required between allocations - previous allocation must be free.\n            VMA_VALIDATE(debugMargin == 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    {\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 calculated 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\nvoid VmaBlockMetadata_Generic::AddDetailedStatistics(VmaDetailedStatistics& inoutStats) const\n{\n    const uint32_t rangeCount = (uint32_t)m_Suballocations.size();\n    inoutStats.statistics.blockCount++;\n    inoutStats.statistics.blockBytes += GetSize();\n\n    for (const auto& suballoc : m_Suballocations)\n    {\n        if (suballoc.type != VMA_SUBALLOCATION_TYPE_FREE)\n            VmaAddDetailedStatisticsAllocation(inoutStats, suballoc.size);\n        else\n            VmaAddDetailedStatisticsUnusedRange(inoutStats, suballoc.size);\n    }\n}\n\nvoid VmaBlockMetadata_Generic::AddStatistics(VmaStatistics& inoutStats) const\n{\n    inoutStats.blockCount++;\n    inoutStats.allocationCount += (uint32_t)m_Suballocations.size() - m_FreeCount;\n    inoutStats.blockBytes += GetSize();\n    inoutStats.allocationBytes += GetSize() - m_SumFreeSize;\n}\n\n#if VMA_STATS_STRING_ENABLED\nvoid VmaBlockMetadata_Generic::PrintDetailedMap(class VmaJsonWriter& json, uint32_t mapRefCount) const\n{\n    PrintDetailedMap_Begin(json,\n        m_SumFreeSize, // unusedBytes\n        m_Suballocations.size() - (size_t)m_FreeCount, // allocationCount\n        m_FreeCount, // unusedRangeCount\n        mapRefCount);\n\n    for (const auto& suballoc : m_Suballocations)\n    {\n        if (suballoc.type == VMA_SUBALLOCATION_TYPE_FREE)\n        {\n            PrintDetailedMap_UnusedRange(json, suballoc.offset, suballoc.size);\n        }\n        else\n        {\n            PrintDetailedMap_Allocation(json, suballoc.offset, suballoc.size, suballoc.userData);\n        }\n    }\n\n    PrintDetailedMap_End(json);\n}\n#endif // VMA_STATS_STRING_ENABLED\n\nbool VmaBlockMetadata_Generic::CreateAllocationRequest(\n    VkDeviceSize allocSize,\n    VkDeviceSize allocAlignment,\n    bool upperAddress,\n    VmaSuballocationType allocType,\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    allocSize = AlignAllocationSize(allocSize);\n\n    pAllocationRequest->type = VmaAllocationRequestType::Normal;\n    pAllocationRequest->size = allocSize;\n\n    const VkDeviceSize debugMargin = GetDebugMargin();\n\n    // There is not enough total free space in this block to fulfill the request: Early return.\n    if (m_SumFreeSize < allocSize + debugMargin)\n    {\n        return false;\n    }\n\n    // New algorithm, efficiently searching freeSuballocationsBySize.\n    const size_t freeSuballocCount = m_FreeSuballocationsBySize.size();\n    if (freeSuballocCount > 0)\n    {\n        if (strategy == 0 ||\n            strategy == VMA_ALLOCATION_CREATE_STRATEGY_MIN_MEMORY_BIT)\n        {\n            // Find first free suballocation with size not less than allocSize + debugMargin.\n            VmaSuballocationList::iterator* const it = VmaBinaryFindFirstNotLess(\n                m_FreeSuballocationsBySize.data(),\n                m_FreeSuballocationsBySize.data() + freeSuballocCount,\n                allocSize + debugMargin,\n                VmaSuballocationItemSizeLess());\n            size_t index = it - m_FreeSuballocationsBySize.data();\n            for (; index < freeSuballocCount; ++index)\n            {\n                if (CheckAllocation(\n                    allocSize,\n                    allocAlignment,\n                    allocType,\n                    m_FreeSuballocationsBySize[index],\n                    &pAllocationRequest->allocHandle))\n                {\n                    pAllocationRequest->item = m_FreeSuballocationsBySize[index];\n                    return true;\n                }\n            }\n        }\n        else if (strategy == VMA_ALLOCATION_INTERNAL_STRATEGY_MIN_OFFSET)\n        {\n            for (VmaSuballocationList::iterator it = m_Suballocations.begin();\n                it != m_Suballocations.end();\n                ++it)\n            {\n                if (it->type == VMA_SUBALLOCATION_TYPE_FREE && CheckAllocation(\n                    allocSize,\n                    allocAlignment,\n                    allocType,\n                    it,\n                    &pAllocationRequest->allocHandle))\n                {\n                    pAllocationRequest->item = it;\n                    return true;\n                }\n            }\n        }\n        else\n        {\n            VMA_ASSERT(strategy & (VMA_ALLOCATION_CREATE_STRATEGY_MIN_TIME_BIT | VMA_ALLOCATION_CREATE_STRATEGY_MIN_OFFSET_BIT ));\n            // Search staring from biggest suballocations.\n            for (size_t index = freeSuballocCount; index--; )\n            {\n                if (CheckAllocation(\n                    allocSize,\n                    allocAlignment,\n                    allocType,\n                    m_FreeSuballocationsBySize[index],\n                    &pAllocationRequest->allocHandle))\n                {\n                    pAllocationRequest->item = m_FreeSuballocationsBySize[index];\n                    return true;\n                }\n            }\n        }\n    }\n\n    return false;\n}\n\nVkResult VmaBlockMetadata_Generic::CheckCorruption(const void* pBlockData)\n{\n    for (auto& suballoc : m_Suballocations)\n    {\n        if (suballoc.type != VMA_SUBALLOCATION_TYPE_FREE)\n        {\n            if (!VmaValidateMagicValue(pBlockData, suballoc.offset + suballoc.size))\n            {\n                VMA_ASSERT(0 && \"MEMORY CORRUPTION DETECTED AFTER VALIDATED ALLOCATION!\");\n                return VK_ERROR_UNKNOWN_COPY;\n            }\n        }\n    }\n\n    return VK_SUCCESS;\n}\n\nvoid VmaBlockMetadata_Generic::Alloc(\n    const VmaAllocationRequest& request,\n    VmaSuballocationType type,\n    void* userData)\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\n    // Given offset is inside this suballocation.\n    VMA_ASSERT((VkDeviceSize)request.allocHandle - 1 >= suballoc.offset);\n    const VkDeviceSize paddingBegin = (VkDeviceSize)request.allocHandle - suballoc.offset - 1;\n    VMA_ASSERT(suballoc.size >= paddingBegin + request.size);\n    const VkDeviceSize paddingEnd = suballoc.size - paddingBegin - request.size;\n\n    // Unregister this free suballocation from m_FreeSuballocationsBySize and update\n    // it to become used.\n    UnregisterFreeSuballocation(request.item);\n\n    suballoc.offset = (VkDeviceSize)request.allocHandle - 1;\n    suballoc.size = request.size;\n    suballoc.type = type;\n    suballoc.userData = userData;\n\n    // If there are any free bytes remaining at the end, insert new free suballocation after current one.\n    if (paddingEnd)\n    {\n        VmaSuballocation paddingSuballoc = {};\n        paddingSuballoc.offset = suballoc.offset + suballoc.size;\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    {\n        VmaSuballocation paddingSuballoc = {};\n        paddingSuballoc.offset = suballoc.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    {\n        ++m_FreeCount;\n    }\n    if (paddingEnd > 0)\n    {\n        ++m_FreeCount;\n    }\n    m_SumFreeSize -= request.size;\n}\n\nvoid VmaBlockMetadata_Generic::GetAllocationInfo(VmaAllocHandle allocHandle, VmaVirtualAllocationInfo& outInfo)\n{\n    outInfo.offset = (VkDeviceSize)allocHandle - 1;\n    const VmaSuballocation& suballoc = *FindAtOffset(outInfo.offset);\n    outInfo.size = suballoc.size;\n    outInfo.pUserData = suballoc.userData;\n}\n\nvoid* VmaBlockMetadata_Generic::GetAllocationUserData(VmaAllocHandle allocHandle) const\n{\n    return FindAtOffset((VkDeviceSize)allocHandle - 1)->userData;\n}\n\nVmaAllocHandle VmaBlockMetadata_Generic::GetAllocationListBegin() const\n{\n    if (IsEmpty())\n        return VK_NULL_HANDLE;\n\n    for (const auto& suballoc : m_Suballocations)\n    {\n        if (suballoc.type != VMA_SUBALLOCATION_TYPE_FREE)\n            return (VmaAllocHandle)(suballoc.offset + 1);\n    }\n    VMA_ASSERT(false && \"Should contain at least 1 allocation!\");\n    return VK_NULL_HANDLE;\n}\n\nVmaAllocHandle VmaBlockMetadata_Generic::GetNextAllocation(VmaAllocHandle prevAlloc) const\n{\n    VmaSuballocationList::const_iterator prev = FindAtOffset((VkDeviceSize)prevAlloc - 1);\n\n    for (VmaSuballocationList::const_iterator it = ++prev; it != m_Suballocations.end(); ++it)\n    {\n        if (it->type != VMA_SUBALLOCATION_TYPE_FREE)\n            return (VmaAllocHandle)(it->offset + 1);\n    }\n    return VK_NULL_HANDLE;\n}\n\nvoid VmaBlockMetadata_Generic::Clear()\n{\n    const VkDeviceSize size = GetSize();\n\n    VMA_ASSERT(IsVirtual());\n    m_FreeCount = 1;\n    m_SumFreeSize = size;\n    m_Suballocations.clear();\n    m_FreeSuballocationsBySize.clear();\n\n    VmaSuballocation suballoc = {};\n    suballoc.offset = 0;\n    suballoc.size = size;\n    suballoc.type = VMA_SUBALLOCATION_TYPE_FREE;\n    m_Suballocations.push_back(suballoc);\n\n    m_FreeSuballocationsBySize.push_back(m_Suballocations.begin());\n}\n\nvoid VmaBlockMetadata_Generic::SetAllocationUserData(VmaAllocHandle allocHandle, void* userData)\n{\n    VmaSuballocation& suballoc = *FindAtOffset((VkDeviceSize)allocHandle - 1);\n    suballoc.userData = userData;\n}\n\nvoid VmaBlockMetadata_Generic::DebugLogAllAllocations() const\n{\n    for (const auto& suballoc : m_Suballocations)\n    {\n        if (suballoc.type != VMA_SUBALLOCATION_TYPE_FREE)\n            DebugLogAllocation(suballoc.offset, suballoc.size, suballoc.userData);\n    }\n}\n\nVmaSuballocationList::iterator VmaBlockMetadata_Generic::FindAtOffset(VkDeviceSize offset) const\n{\n    VMA_HEAVY_ASSERT(!m_Suballocations.empty());\n    const VkDeviceSize last = m_Suballocations.rbegin()->offset;\n    if (last == offset)\n        return m_Suballocations.rbegin().drop_const();\n    const VkDeviceSize first = m_Suballocations.begin()->offset;\n    if (first == offset)\n        return m_Suballocations.begin().drop_const();\n\n    const size_t suballocCount = m_Suballocations.size();\n    const VkDeviceSize step = (last - first + m_Suballocations.begin()->size) / suballocCount;\n    auto findSuballocation = [&](auto begin, auto end) -> VmaSuballocationList::iterator\n    {\n        for (auto suballocItem = begin;\n            suballocItem != end;\n            ++suballocItem)\n        {\n            if (suballocItem->offset == offset)\n                return suballocItem.drop_const();\n        }\n        VMA_ASSERT(false && \"Not found!\");\n        return m_Suballocations.end().drop_const();\n    };\n    // If requested offset is closer to the end of range, search from the end\n    if (offset - first > suballocCount * step / 2)\n    {\n        return findSuballocation(m_Suballocations.rbegin(), m_Suballocations.rend());\n    }\n    return findSuballocation(m_Suballocations.begin(), m_Suballocations.end());\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    {\n        const VmaSuballocationList::iterator it = m_FreeSuballocationsBySize[i];\n\n        VMA_VALIDATE(it->type == VMA_SUBALLOCATION_TYPE_FREE);\n        VMA_VALIDATE(it->size >= lastSize);\n        lastSize = it->size;\n    }\n    return true;\n}\n\nbool VmaBlockMetadata_Generic::CheckAllocation(\n    VkDeviceSize allocSize,\n    VkDeviceSize allocAlignment,\n    VmaSuballocationType allocType,\n    VmaSuballocationList::const_iterator suballocItem,\n    VmaAllocHandle* pAllocHandle) 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(pAllocHandle != VMA_NULL);\n\n    const VkDeviceSize debugMargin = GetDebugMargin();\n    const VkDeviceSize bufferImageGranularity = GetBufferImageGranularity();\n\n    const VmaSuballocation& suballoc = *suballocItem;\n    VMA_ASSERT(suballoc.type == VMA_SUBALLOCATION_TYPE_FREE);\n\n    // Size of this suballocation is too small for this request: Early return.\n    if (suballoc.size < allocSize)\n    {\n        return false;\n    }\n\n    // Start from offset equal to beginning of this suballocation.\n    VkDeviceSize offset = suballoc.offset + (suballocItem == m_Suballocations.cbegin() ? 0 : GetDebugMargin());\n\n    // Apply debugMargin from the end of previous alloc.\n    if (debugMargin > 0)\n    {\n        offset += debugMargin;\n    }\n\n    // Apply alignment.\n    offset = VmaAlignUp(offset, allocAlignment);\n\n    // Check previous suballocations for BufferImageGranularity conflicts.\n    // Make bigger alignment if necessary.\n    if (bufferImageGranularity > 1 && bufferImageGranularity != allocAlignment)\n    {\n        bool bufferImageGranularityConflict = false;\n        VmaSuballocationList::const_iterator prevSuballocItem = suballocItem;\n        while (prevSuballocItem != m_Suballocations.cbegin())\n        {\n            --prevSuballocItem;\n            const VmaSuballocation& prevSuballoc = *prevSuballocItem;\n            if (VmaBlocksOnSamePage(prevSuballoc.offset, prevSuballoc.size, offset, bufferImageGranularity))\n            {\n                if (VmaIsBufferImageGranularityConflict(prevSuballoc.type, allocType))\n                {\n                    bufferImageGranularityConflict = true;\n                    break;\n                }\n            }\n            else\n                // Already on previous page.\n                break;\n        }\n        if (bufferImageGranularityConflict)\n        {\n            offset = VmaAlignUp(offset, bufferImageGranularity);\n        }\n    }\n\n    // Calculate padding at the beginning based on current offset.\n    const VkDeviceSize paddingBegin = offset - suballoc.offset;\n\n    // Fail if requested size plus margin after is bigger than size of this suballocation.\n    if (paddingBegin + allocSize + debugMargin > suballoc.size)\n    {\n        return false;\n    }\n\n    // Check next suballocations for BufferImageGranularity conflicts.\n    // If conflict exists, allocation cannot be made here.\n    if (allocSize % bufferImageGranularity || offset % bufferImageGranularity)\n    {\n        VmaSuballocationList::const_iterator nextSuballocItem = suballocItem;\n        ++nextSuballocItem;\n        while (nextSuballocItem != m_Suballocations.cend())\n        {\n            const VmaSuballocation& nextSuballoc = *nextSuballocItem;\n            if (VmaBlocksOnSamePage(offset, allocSize, nextSuballoc.offset, bufferImageGranularity))\n            {\n                if (VmaIsBufferImageGranularityConflict(allocType, nextSuballoc.type))\n                {\n                    return false;\n                }\n            }\n            else\n            {\n                // Already on next page.\n                break;\n            }\n            ++nextSuballocItem;\n        }\n    }\n\n    *pAllocHandle = (VmaAllocHandle)(offset + 1);\n    // All tests passed: Success. pAllocHandle 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.userData = VMA_NULL;\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    {\n        mergeWithNext = true;\n    }\n\n    VmaSuballocationList::iterator prevItem = suballocItem;\n    if (suballocItem != m_Suballocations.begin())\n    {\n        --prevItem;\n        if (prevItem->type == VMA_SUBALLOCATION_TYPE_FREE)\n        {\n            mergeWithPrev = true;\n        }\n    }\n\n    if (mergeWithNext)\n    {\n        UnregisterFreeSuballocation(nextItem);\n        MergeFreeWithNext(suballocItem);\n    }\n\n    if (mergeWithPrev)\n    {\n        UnregisterFreeSuballocation(prevItem);\n        MergeFreeWithNext(prevItem);\n        RegisterFreeSuballocation(prevItem);\n        return prevItem;\n    }\n    else\n    {\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 (m_FreeSuballocationsBySize.empty())\n    {\n        m_FreeSuballocationsBySize.push_back(item);\n    }\n    else\n    {\n        VmaVectorInsertSorted<VmaSuballocationItemSizeLess>(m_FreeSuballocationsBySize, item);\n    }\n\n    //VMA_HEAVY_ASSERT(ValidateFreeSuballocationList());\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    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    {\n        if (m_FreeSuballocationsBySize[index] == item)\n        {\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    //VMA_HEAVY_ASSERT(ValidateFreeSuballocationList());\n}\n#endif // _VMA_BLOCK_METADATA_GENERIC_FUNCTIONS\n#endif // _VMA_BLOCK_METADATA_GENERIC\n#endif // #if 0\n\n#ifndef _VMA_BLOCK_METADATA_LINEAR\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)\n public:\n  VmaBlockMetadata_Linear(const VkAllocationCallbacks* pAllocationCallbacks,\n                          VkDeviceSize bufferImageGranularity, bool isVirtual);\n  virtual ~VmaBlockMetadata_Linear() = default;\n\n  VkDeviceSize GetSumFreeSize() const override { return m_SumFreeSize; }\n  bool IsEmpty() const override { return GetAllocationCount() == 0; }\n  VkDeviceSize GetAllocationOffset(VmaAllocHandle allocHandle) const override { return (VkDeviceSize)allocHandle - 1; }\n\n  void Init(VkDeviceSize size) override;\n  bool Validate() const override;\n  size_t GetAllocationCount() const override;\n  size_t GetFreeRegionsCount() const override;\n\n  void AddDetailedStatistics(VmaDetailedStatistics& inoutStats) const override;\n  void AddStatistics(VmaStatistics& inoutStats) const override;\n\n#if VMA_STATS_STRING_ENABLED\n  void PrintDetailedMap(class VmaJsonWriter& json) const override;\n#endif\n\n  bool CreateAllocationRequest(\n      VkDeviceSize allocSize,\n      VkDeviceSize allocAlignment,\n      bool upperAddress,\n      VmaSuballocationType allocType,\n      uint32_t strategy,\n      VmaAllocationRequest* pAllocationRequest) override;\n\n  VkResult CheckCorruption(const void* pBlockData) override;\n\n  void Alloc(\n      const VmaAllocationRequest& request,\n      VmaSuballocationType type,\n      void* userData) override;\n\n  void Free(VmaAllocHandle allocHandle) override;\n  void GetAllocationInfo(VmaAllocHandle allocHandle, VmaVirtualAllocationInfo& outInfo) override;\n  void* GetAllocationUserData(VmaAllocHandle allocHandle) const override;\n  VmaAllocHandle GetAllocationListBegin() const override;\n  VmaAllocHandle GetNextAllocation(VmaAllocHandle prevAlloc) const override;\n  VkDeviceSize GetNextFreeRegionSize(VmaAllocHandle alloc) const override;\n  void Clear() override;\n  void SetAllocationUserData(VmaAllocHandle allocHandle, void* userData) override;\n  void DebugLogAllAllocations() const override;\n\n private:\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  // 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  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  VmaSuballocation& FindSuballocation(VkDeviceSize offset) const;\n  bool ShouldCompact1st() const;\n  void CleanupAfterFree();\n\n  bool CreateAllocationRequest_LowerAddress(\n      VkDeviceSize allocSize,\n      VkDeviceSize allocAlignment,\n      VmaSuballocationType allocType,\n      uint32_t strategy,\n      VmaAllocationRequest* pAllocationRequest);\n  bool CreateAllocationRequest_UpperAddress(\n      VkDeviceSize allocSize,\n      VkDeviceSize allocAlignment,\n      VmaSuballocationType allocType,\n      uint32_t strategy,\n      VmaAllocationRequest* pAllocationRequest);\n};\n\n#ifndef _VMA_BLOCK_METADATA_LINEAR_FUNCTIONS\nVmaBlockMetadata_Linear::VmaBlockMetadata_Linear(const VkAllocationCallbacks* pAllocationCallbacks,\n                                                 VkDeviceSize bufferImageGranularity, bool isVirtual)\n    : VmaBlockMetadata(pAllocationCallbacks, bufferImageGranularity, isVirtual),\n      m_SumFreeSize(0),\n      m_Suballocations0(VmaStlAllocator<VmaSuballocation>(pAllocationCallbacks)),\n      m_Suballocations1(VmaStlAllocator<VmaSuballocation>(pAllocationCallbacks)),\n      m_1stVectorIndex(0),\n      m_2ndVectorMode(SECOND_VECTOR_EMPTY),\n      m_1stNullItemsBeginCount(0),\n      m_1stNullItemsMiddleCount(0),\n      m_2ndNullItemsCount(0) {}\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  {\n    // Null item at the beginning should be accounted into m_1stNullItemsBeginCount.\n    VMA_VALIDATE(suballocations1st[m_1stNullItemsBeginCount].type != VMA_SUBALLOCATION_TYPE_FREE);\n    // Null item at the end should be just pop_back().\n    VMA_VALIDATE(suballocations1st.back().type != VMA_SUBALLOCATION_TYPE_FREE);\n  }\n  if (!suballocations2nd.empty())\n  {\n    // Null item at the end should be just pop_back().\n    VMA_VALIDATE(suballocations2nd.back().type != VMA_SUBALLOCATION_TYPE_FREE);\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  const VkDeviceSize debugMargin = GetDebugMargin();\n  VkDeviceSize offset = 0;\n\n  if (m_2ndVectorMode == SECOND_VECTOR_RING_BUFFER)\n  {\n    const size_t suballoc2ndCount = suballocations2nd.size();\n    size_t nullItem2ndCount = 0;\n    for (size_t i = 0; i < suballoc2ndCount; ++i)\n    {\n      const VmaSuballocation& suballoc = suballocations2nd[i];\n      const bool currFree = (suballoc.type == VMA_SUBALLOCATION_TYPE_FREE);\n\n      VmaAllocation const alloc = (VmaAllocation)suballoc.userData;\n      if (!IsVirtual())\n      {\n        VMA_VALIDATE(currFree == (alloc == VK_NULL_HANDLE));\n      }\n      VMA_VALIDATE(suballoc.offset >= offset);\n\n      if (!currFree)\n      {\n        if (!IsVirtual())\n        {\n          VMA_VALIDATE((VkDeviceSize)alloc->GetAllocHandle() == suballoc.offset + 1);\n          VMA_VALIDATE(alloc->GetSize() == suballoc.size);\n        }\n        sumUsedSize += suballoc.size;\n      }\n      else\n      {\n        ++nullItem2ndCount;\n      }\n\n      offset = suballoc.offset + suballoc.size + debugMargin;\n    }\n\n    VMA_VALIDATE(nullItem2ndCount == m_2ndNullItemsCount);\n  }\n\n  for (size_t i = 0; i < m_1stNullItemsBeginCount; ++i)\n  {\n    const VmaSuballocation& suballoc = suballocations1st[i];\n    VMA_VALIDATE(suballoc.type == VMA_SUBALLOCATION_TYPE_FREE &&\n                 suballoc.userData == VMA_NULL);\n  }\n\n  size_t nullItem1stCount = m_1stNullItemsBeginCount;\n\n  for (size_t i = m_1stNullItemsBeginCount; i < suballoc1stCount; ++i)\n  {\n    const VmaSuballocation& suballoc = suballocations1st[i];\n    const bool currFree = (suballoc.type == VMA_SUBALLOCATION_TYPE_FREE);\n\n    VmaAllocation const alloc = (VmaAllocation)suballoc.userData;\n    if (!IsVirtual())\n    {\n      VMA_VALIDATE(currFree == (alloc == VK_NULL_HANDLE));\n    }\n    VMA_VALIDATE(suballoc.offset >= offset);\n    VMA_VALIDATE(i >= m_1stNullItemsBeginCount || currFree);\n\n    if (!currFree)\n    {\n      if (!IsVirtual())\n      {\n        VMA_VALIDATE((VkDeviceSize)alloc->GetAllocHandle() == suballoc.offset + 1);\n        VMA_VALIDATE(alloc->GetSize() == suballoc.size);\n      }\n      sumUsedSize += suballoc.size;\n    }\n    else\n    {\n      ++nullItem1stCount;\n    }\n\n    offset = suballoc.offset + suballoc.size + debugMargin;\n  }\n  VMA_VALIDATE(nullItem1stCount == m_1stNullItemsBeginCount + m_1stNullItemsMiddleCount);\n\n  if (m_2ndVectorMode == SECOND_VECTOR_DOUBLE_STACK)\n  {\n    const size_t suballoc2ndCount = suballocations2nd.size();\n    size_t nullItem2ndCount = 0;\n    for (size_t i = suballoc2ndCount; i--; )\n    {\n      const VmaSuballocation& suballoc = suballocations2nd[i];\n      const bool currFree = (suballoc.type == VMA_SUBALLOCATION_TYPE_FREE);\n\n      VmaAllocation const alloc = (VmaAllocation)suballoc.userData;\n      if (!IsVirtual())\n      {\n        VMA_VALIDATE(currFree == (alloc == VK_NULL_HANDLE));\n      }\n      VMA_VALIDATE(suballoc.offset >= offset);\n\n      if (!currFree)\n      {\n        if (!IsVirtual())\n        {\n          VMA_VALIDATE((VkDeviceSize)alloc->GetAllocHandle() == suballoc.offset + 1);\n          VMA_VALIDATE(alloc->GetSize() == suballoc.size);\n        }\n        sumUsedSize += suballoc.size;\n      }\n      else\n      {\n        ++nullItem2ndCount;\n      }\n\n      offset = suballoc.offset + suballoc.size + debugMargin;\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\nsize_t VmaBlockMetadata_Linear::GetFreeRegionsCount() const\n{\n  // Function only used for defragmentation, which is disabled for this algorithm\n  VMA_ASSERT(0);\n  return SIZE_MAX;\n}\n\nvoid VmaBlockMetadata_Linear::AddDetailedStatistics(VmaDetailedStatistics& inoutStats) 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  inoutStats.statistics.blockCount++;\n  inoutStats.statistics.blockBytes += size;\n\n  VkDeviceSize lastOffset = 0;\n\n  if (m_2ndVectorMode == SECOND_VECTOR_RING_BUFFER)\n  {\n    const VkDeviceSize freeSpace2ndTo1stEnd = suballocations1st[m_1stNullItemsBeginCount].offset;\n    size_t nextAlloc2ndIndex = 0;\n    while (lastOffset < freeSpace2ndTo1stEnd)\n    {\n      // Find next non-null allocation or move nextAllocIndex to the end.\n      while (nextAlloc2ndIndex < suballoc2ndCount &&\n             suballocations2nd[nextAlloc2ndIndex].userData == VMA_NULL)\n      {\n        ++nextAlloc2ndIndex;\n      }\n\n      // Found non-null allocation.\n      if (nextAlloc2ndIndex < suballoc2ndCount)\n      {\n        const VmaSuballocation& suballoc = suballocations2nd[nextAlloc2ndIndex];\n\n        // 1. Process free space before this allocation.\n        if (lastOffset < suballoc.offset)\n        {\n          // There is free space from lastOffset to suballoc.offset.\n          const VkDeviceSize unusedRangeSize = suballoc.offset - lastOffset;\n          VmaAddDetailedStatisticsUnusedRange(inoutStats, unusedRangeSize);\n        }\n\n        // 2. Process this allocation.\n        // There is allocation with suballoc.offset, suballoc.size.\n        VmaAddDetailedStatisticsAllocation(inoutStats, 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      {\n        // There is free space from lastOffset to freeSpace2ndTo1stEnd.\n        if (lastOffset < freeSpace2ndTo1stEnd)\n        {\n          const VkDeviceSize unusedRangeSize = freeSpace2ndTo1stEnd - lastOffset;\n          VmaAddDetailedStatisticsUnusedRange(inoutStats, 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  {\n    // Find next non-null allocation or move nextAllocIndex to the end.\n    while (nextAlloc1stIndex < suballoc1stCount &&\n           suballocations1st[nextAlloc1stIndex].userData == VMA_NULL)\n    {\n      ++nextAlloc1stIndex;\n    }\n\n    // Found non-null allocation.\n    if (nextAlloc1stIndex < suballoc1stCount)\n    {\n      const VmaSuballocation& suballoc = suballocations1st[nextAlloc1stIndex];\n\n      // 1. Process free space before this allocation.\n      if (lastOffset < suballoc.offset)\n      {\n        // There is free space from lastOffset to suballoc.offset.\n        const VkDeviceSize unusedRangeSize = suballoc.offset - lastOffset;\n        VmaAddDetailedStatisticsUnusedRange(inoutStats, unusedRangeSize);\n      }\n\n      // 2. Process this allocation.\n      // There is allocation with suballoc.offset, suballoc.size.\n      VmaAddDetailedStatisticsAllocation(inoutStats, 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    {\n      // There is free space from lastOffset to freeSpace1stTo2ndEnd.\n      if (lastOffset < freeSpace1stTo2ndEnd)\n      {\n        const VkDeviceSize unusedRangeSize = freeSpace1stTo2ndEnd - lastOffset;\n        VmaAddDetailedStatisticsUnusedRange(inoutStats, unusedRangeSize);\n      }\n\n      // End of loop.\n      lastOffset = freeSpace1stTo2ndEnd;\n    }\n  }\n\n  if (m_2ndVectorMode == SECOND_VECTOR_DOUBLE_STACK)\n  {\n    size_t nextAlloc2ndIndex = suballocations2nd.size() - 1;\n    while (lastOffset < size)\n    {\n      // Find next non-null allocation or move nextAllocIndex to the end.\n      while (nextAlloc2ndIndex != SIZE_MAX &&\n             suballocations2nd[nextAlloc2ndIndex].userData == VMA_NULL)\n      {\n        --nextAlloc2ndIndex;\n      }\n\n      // Found non-null allocation.\n      if (nextAlloc2ndIndex != SIZE_MAX)\n      {\n        const VmaSuballocation& suballoc = suballocations2nd[nextAlloc2ndIndex];\n\n        // 1. Process free space before this allocation.\n        if (lastOffset < suballoc.offset)\n        {\n          // There is free space from lastOffset to suballoc.offset.\n          const VkDeviceSize unusedRangeSize = suballoc.offset - lastOffset;\n          VmaAddDetailedStatisticsUnusedRange(inoutStats, unusedRangeSize);\n        }\n\n        // 2. Process this allocation.\n        // There is allocation with suballoc.offset, suballoc.size.\n        VmaAddDetailedStatisticsAllocation(inoutStats, 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      {\n        // There is free space from lastOffset to size.\n        if (lastOffset < size)\n        {\n          const VkDeviceSize unusedRangeSize = size - lastOffset;\n          VmaAddDetailedStatisticsUnusedRange(inoutStats, unusedRangeSize);\n        }\n\n        // End of loop.\n        lastOffset = size;\n      }\n    }\n  }\n}\n\nvoid VmaBlockMetadata_Linear::AddStatistics(VmaStatistics& 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.blockCount++;\n  inoutStats.blockBytes += size;\n  inoutStats.allocationBytes += size - m_SumFreeSize;\n\n  VkDeviceSize lastOffset = 0;\n\n  if (m_2ndVectorMode == SECOND_VECTOR_RING_BUFFER)\n  {\n    const VkDeviceSize freeSpace2ndTo1stEnd = suballocations1st[m_1stNullItemsBeginCount].offset;\n    size_t nextAlloc2ndIndex = m_1stNullItemsBeginCount;\n    while (lastOffset < freeSpace2ndTo1stEnd)\n    {\n      // Find next non-null allocation or move nextAlloc2ndIndex to the end.\n      while (nextAlloc2ndIndex < suballoc2ndCount &&\n             suballocations2nd[nextAlloc2ndIndex].userData == VMA_NULL)\n      {\n        ++nextAlloc2ndIndex;\n      }\n\n      // Found non-null allocation.\n      if (nextAlloc2ndIndex < suballoc2ndCount)\n      {\n        const VmaSuballocation& suballoc = suballocations2nd[nextAlloc2ndIndex];\n\n        // Process this allocation.\n        // There is allocation with suballoc.offset, suballoc.size.\n        ++inoutStats.allocationCount;\n\n        // Prepare for next iteration.\n        lastOffset = suballoc.offset + suballoc.size;\n        ++nextAlloc2ndIndex;\n      }\n      // We are at the end.\n      else\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  {\n    // Find next non-null allocation or move nextAllocIndex to the end.\n    while (nextAlloc1stIndex < suballoc1stCount &&\n           suballocations1st[nextAlloc1stIndex].userData == VMA_NULL)\n    {\n      ++nextAlloc1stIndex;\n    }\n\n    // Found non-null allocation.\n    if (nextAlloc1stIndex < suballoc1stCount)\n    {\n      const VmaSuballocation& suballoc = suballocations1st[nextAlloc1stIndex];\n\n      // Process this allocation.\n      // There is allocation with suballoc.offset, suballoc.size.\n      ++inoutStats.allocationCount;\n\n      // Prepare for next iteration.\n      lastOffset = suballoc.offset + suballoc.size;\n      ++nextAlloc1stIndex;\n    }\n    // We are at the end.\n    else\n    {\n      // End of loop.\n      lastOffset = freeSpace1stTo2ndEnd;\n    }\n  }\n\n  if (m_2ndVectorMode == SECOND_VECTOR_DOUBLE_STACK)\n  {\n    size_t nextAlloc2ndIndex = suballocations2nd.size() - 1;\n    while (lastOffset < size)\n    {\n      // Find next non-null allocation or move nextAlloc2ndIndex to the end.\n      while (nextAlloc2ndIndex != SIZE_MAX &&\n             suballocations2nd[nextAlloc2ndIndex].userData == VMA_NULL)\n      {\n        --nextAlloc2ndIndex;\n      }\n\n      // Found non-null allocation.\n      if (nextAlloc2ndIndex != SIZE_MAX)\n      {\n        const VmaSuballocation& suballoc = suballocations2nd[nextAlloc2ndIndex];\n\n        // Process this allocation.\n        // There is allocation with suballoc.offset, suballoc.size.\n        ++inoutStats.allocationCount;\n\n        // Prepare for next iteration.\n        lastOffset = suballoc.offset + suballoc.size;\n        --nextAlloc2ndIndex;\n      }\n      // We are at the end.\n      else\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  {\n    const VkDeviceSize freeSpace2ndTo1stEnd = suballocations1st[m_1stNullItemsBeginCount].offset;\n    size_t nextAlloc2ndIndex = 0;\n    while (lastOffset < freeSpace2ndTo1stEnd)\n    {\n      // Find next non-null allocation or move nextAlloc2ndIndex to the end.\n      while (nextAlloc2ndIndex < suballoc2ndCount &&\n             suballocations2nd[nextAlloc2ndIndex].userData == VMA_NULL)\n      {\n        ++nextAlloc2ndIndex;\n      }\n\n      // Found non-null allocation.\n      if (nextAlloc2ndIndex < suballoc2ndCount)\n      {\n        const VmaSuballocation& suballoc = suballocations2nd[nextAlloc2ndIndex];\n\n        // 1. Process free space before this allocation.\n        if (lastOffset < suballoc.offset)\n        {\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      {\n        if (lastOffset < freeSpace2ndTo1stEnd)\n        {\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  {\n    // Find next non-null allocation or move nextAllocIndex to the end.\n    while (nextAlloc1stIndex < suballoc1stCount &&\n           suballocations1st[nextAlloc1stIndex].userData == VMA_NULL)\n    {\n      ++nextAlloc1stIndex;\n    }\n\n    // Found non-null allocation.\n    if (nextAlloc1stIndex < suballoc1stCount)\n    {\n      const VmaSuballocation& suballoc = suballocations1st[nextAlloc1stIndex];\n\n      // 1. Process free space before this allocation.\n      if (lastOffset < suballoc.offset)\n      {\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    {\n      if (lastOffset < size)\n      {\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  {\n    size_t nextAlloc2ndIndex = suballocations2nd.size() - 1;\n    while (lastOffset < size)\n    {\n      // Find next non-null allocation or move nextAlloc2ndIndex to the end.\n      while (nextAlloc2ndIndex != SIZE_MAX &&\n             suballocations2nd[nextAlloc2ndIndex].userData == VMA_NULL)\n      {\n        --nextAlloc2ndIndex;\n      }\n\n      // Found non-null allocation.\n      if (nextAlloc2ndIndex != SIZE_MAX)\n      {\n        const VmaSuballocation& suballoc = suballocations2nd[nextAlloc2ndIndex];\n\n        // 1. Process free space before this allocation.\n        if (lastOffset < suballoc.offset)\n        {\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      {\n        if (lastOffset < size)\n        {\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  {\n    const VkDeviceSize freeSpace2ndTo1stEnd = suballocations1st[m_1stNullItemsBeginCount].offset;\n    size_t nextAlloc2ndIndex = 0;\n    while (lastOffset < freeSpace2ndTo1stEnd)\n    {\n      // Find next non-null allocation or move nextAlloc2ndIndex to the end.\n      while (nextAlloc2ndIndex < suballoc2ndCount &&\n             suballocations2nd[nextAlloc2ndIndex].userData == VMA_NULL)\n      {\n        ++nextAlloc2ndIndex;\n      }\n\n      // Found non-null allocation.\n      if (nextAlloc2ndIndex < suballoc2ndCount)\n      {\n        const VmaSuballocation& suballoc = suballocations2nd[nextAlloc2ndIndex];\n\n        // 1. Process free space before this allocation.\n        if (lastOffset < suballoc.offset)\n        {\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.size, suballoc.userData);\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      {\n        if (lastOffset < freeSpace2ndTo1stEnd)\n        {\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  {\n    // Find next non-null allocation or move nextAllocIndex to the end.\n    while (nextAlloc1stIndex < suballoc1stCount &&\n           suballocations1st[nextAlloc1stIndex].userData == VMA_NULL)\n    {\n      ++nextAlloc1stIndex;\n    }\n\n    // Found non-null allocation.\n    if (nextAlloc1stIndex < suballoc1stCount)\n    {\n      const VmaSuballocation& suballoc = suballocations1st[nextAlloc1stIndex];\n\n      // 1. Process free space before this allocation.\n      if (lastOffset < suballoc.offset)\n      {\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.size, suballoc.userData);\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    {\n      if (lastOffset < freeSpace1stTo2ndEnd)\n      {\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  {\n    size_t nextAlloc2ndIndex = suballocations2nd.size() - 1;\n    while (lastOffset < size)\n    {\n      // Find next non-null allocation or move nextAlloc2ndIndex to the end.\n      while (nextAlloc2ndIndex != SIZE_MAX &&\n             suballocations2nd[nextAlloc2ndIndex].userData == VMA_NULL)\n      {\n        --nextAlloc2ndIndex;\n      }\n\n      // Found non-null allocation.\n      if (nextAlloc2ndIndex != SIZE_MAX)\n      {\n        const VmaSuballocation& suballoc = suballocations2nd[nextAlloc2ndIndex];\n\n        // 1. Process free space before this allocation.\n        if (lastOffset < suballoc.offset)\n        {\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.size, suballoc.userData);\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      {\n        if (lastOffset < size)\n        {\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 // VMA_STATS_STRING_ENABLED\n\nbool VmaBlockMetadata_Linear::CreateAllocationRequest(\n    VkDeviceSize allocSize,\n    VkDeviceSize allocAlignment,\n    bool upperAddress,\n    VmaSuballocationType allocType,\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  pAllocationRequest->size = allocSize;\n  return upperAddress ?\n                      CreateAllocationRequest_UpperAddress(\n                          allocSize, allocAlignment, allocType, strategy, pAllocationRequest) :\n                      CreateAllocationRequest_LowerAddress(\n                          allocSize, allocAlignment, allocType, strategy, pAllocationRequest);\n}\n\nVkResult VmaBlockMetadata_Linear::CheckCorruption(const void* pBlockData)\n{\n  VMA_ASSERT(!IsVirtual());\n  SuballocationVectorType& suballocations1st = AccessSuballocations1st();\n  for (size_t i = m_1stNullItemsBeginCount, count = suballocations1st.size(); i < count; ++i)\n  {\n    const VmaSuballocation& suballoc = suballocations1st[i];\n    if (suballoc.type != VMA_SUBALLOCATION_TYPE_FREE)\n    {\n      if (!VmaValidateMagicValue(pBlockData, suballoc.offset + suballoc.size))\n      {\n        VMA_ASSERT(0 && \"MEMORY CORRUPTION DETECTED AFTER VALIDATED ALLOCATION!\");\n        return VK_ERROR_UNKNOWN_COPY;\n      }\n    }\n  }\n\n  SuballocationVectorType& suballocations2nd = AccessSuballocations2nd();\n  for (size_t i = 0, count = suballocations2nd.size(); i < count; ++i)\n  {\n    const VmaSuballocation& suballoc = suballocations2nd[i];\n    if (suballoc.type != VMA_SUBALLOCATION_TYPE_FREE)\n    {\n      if (!VmaValidateMagicValue(pBlockData, suballoc.offset + suballoc.size))\n      {\n        VMA_ASSERT(0 && \"MEMORY CORRUPTION DETECTED AFTER VALIDATED ALLOCATION!\");\n        return VK_ERROR_UNKNOWN_COPY;\n      }\n    }\n  }\n\n  return VK_SUCCESS;\n}\n\nvoid VmaBlockMetadata_Linear::Alloc(\n    const VmaAllocationRequest& request,\n    VmaSuballocationType type,\n    void* userData)\n{\n  const VkDeviceSize offset = (VkDeviceSize)request.allocHandle - 1;\n  const VmaSuballocation newSuballoc = { offset, request.size, userData, type };\n\n  switch (request.type)\n  {\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                 offset >= suballocations1st.back().offset + suballocations1st.back().size);\n      // Check if it fits before the end of the block.\n      VMA_ASSERT(offset + request.size <= 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                 offset + request.size <= suballocations1st[m_1stNullItemsBeginCount].offset);\n      SuballocationVectorType& suballocations2nd = AccessSuballocations2nd();\n\n      switch (m_2ndVectorMode)\n      {\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(VmaAllocHandle allocHandle)\n{\n  SuballocationVectorType& suballocations1st = AccessSuballocations1st();\n  SuballocationVectorType& suballocations2nd = AccessSuballocations2nd();\n  VkDeviceSize offset = (VkDeviceSize)allocHandle - 1;\n\n  if (!suballocations1st.empty())\n  {\n    // First allocation: Mark it as next empty at the beginning.\n    VmaSuballocation& firstSuballoc = suballocations1st[m_1stNullItemsBeginCount];\n    if (firstSuballoc.offset == offset)\n    {\n      firstSuballoc.type = VMA_SUBALLOCATION_TYPE_FREE;\n      firstSuballoc.userData = VMA_NULL;\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  {\n    VmaSuballocation& lastSuballoc = suballocations2nd.back();\n    if (lastSuballoc.offset == offset)\n    {\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  {\n    VmaSuballocation& lastSuballoc = suballocations1st.back();\n    if (lastSuballoc.offset == offset)\n    {\n      m_SumFreeSize += lastSuballoc.size;\n      suballocations1st.pop_back();\n      CleanupAfterFree();\n      return;\n    }\n  }\n\n  VmaSuballocation refSuballoc;\n  refSuballoc.offset = offset;\n  // Rest of members stays uninitialized intentionally for better performance.\n\n  // Item from the middle of 1st vector.\n  {\n    const SuballocationVectorType::iterator it = VmaBinaryFindSorted(\n        suballocations1st.begin() + m_1stNullItemsBeginCount,\n        suballocations1st.end(),\n        refSuballoc,\n        VmaSuballocationOffsetLess());\n    if (it != suballocations1st.end())\n    {\n      it->type = VMA_SUBALLOCATION_TYPE_FREE;\n      it->userData = VMA_NULL;\n      ++m_1stNullItemsMiddleCount;\n      m_SumFreeSize += it->size;\n      CleanupAfterFree();\n      return;\n    }\n  }\n\n  if (m_2ndVectorMode != SECOND_VECTOR_EMPTY)\n  {\n    // Item from the middle of 2nd vector.\n    const 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    {\n      it->type = VMA_SUBALLOCATION_TYPE_FREE;\n      it->userData = VMA_NULL;\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\nvoid VmaBlockMetadata_Linear::GetAllocationInfo(VmaAllocHandle allocHandle, VmaVirtualAllocationInfo& outInfo)\n{\n  outInfo.offset = (VkDeviceSize)allocHandle - 1;\n  VmaSuballocation& suballoc = FindSuballocation(outInfo.offset);\n  outInfo.size = suballoc.size;\n  outInfo.pUserData = suballoc.userData;\n}\n\nvoid* VmaBlockMetadata_Linear::GetAllocationUserData(VmaAllocHandle allocHandle) const\n{\n  return FindSuballocation((VkDeviceSize)allocHandle - 1).userData;\n}\n\nVmaAllocHandle VmaBlockMetadata_Linear::GetAllocationListBegin() const\n{\n  // Function only used for defragmentation, which is disabled for this algorithm\n  VMA_ASSERT(0);\n  return VK_NULL_HANDLE;\n}\n\nVmaAllocHandle VmaBlockMetadata_Linear::GetNextAllocation(VmaAllocHandle prevAlloc) const\n{\n  // Function only used for defragmentation, which is disabled for this algorithm\n  VMA_ASSERT(0);\n  return VK_NULL_HANDLE;\n}\n\nVkDeviceSize VmaBlockMetadata_Linear::GetNextFreeRegionSize(VmaAllocHandle alloc) const\n{\n  // Function only used for defragmentation, which is disabled for this algorithm\n  VMA_ASSERT(0);\n  return 0;\n}\n\nvoid VmaBlockMetadata_Linear::Clear()\n{\n  m_SumFreeSize = GetSize();\n  m_Suballocations0.clear();\n  m_Suballocations1.clear();\n  // Leaving m_1stVectorIndex unchanged - it doesn't matter.\n  m_2ndVectorMode = SECOND_VECTOR_EMPTY;\n  m_1stNullItemsBeginCount = 0;\n  m_1stNullItemsMiddleCount = 0;\n  m_2ndNullItemsCount = 0;\n}\n\nvoid VmaBlockMetadata_Linear::SetAllocationUserData(VmaAllocHandle allocHandle, void* userData)\n{\n  VmaSuballocation& suballoc = FindSuballocation((VkDeviceSize)allocHandle - 1);\n  suballoc.userData = userData;\n}\n\nvoid VmaBlockMetadata_Linear::DebugLogAllAllocations() const\n{\n  const SuballocationVectorType& suballocations1st = AccessSuballocations1st();\n  for (auto it = suballocations1st.begin() + m_1stNullItemsBeginCount; it != suballocations1st.end(); ++it)\n    if (it->type != VMA_SUBALLOCATION_TYPE_FREE)\n      DebugLogAllocation(it->offset, it->size, it->userData);\n\n  const SuballocationVectorType& suballocations2nd = AccessSuballocations2nd();\n  for (auto it = suballocations2nd.begin(); it != suballocations2nd.end(); ++it)\n    if (it->type != VMA_SUBALLOCATION_TYPE_FREE)\n      DebugLogAllocation(it->offset, it->size, it->userData);\n}\n\nVmaSuballocation& VmaBlockMetadata_Linear::FindSuballocation(VkDeviceSize offset) const\n{\n  const SuballocationVectorType& suballocations1st = AccessSuballocations1st();\n  const SuballocationVectorType& suballocations2nd = AccessSuballocations2nd();\n\n  VmaSuballocation refSuballoc;\n  refSuballoc.offset = offset;\n  // Rest of members stays uninitialized intentionally for better performance.\n\n  // Item from the 1st vector.\n  {\n    SuballocationVectorType::const_iterator it = VmaBinaryFindSorted(\n        suballocations1st.begin() + m_1stNullItemsBeginCount,\n        suballocations1st.end(),\n        refSuballoc,\n        VmaSuballocationOffsetLess());\n    if (it != suballocations1st.end())\n    {\n      return const_cast<VmaSuballocation&>(*it);\n    }\n  }\n\n  if (m_2ndVectorMode != SECOND_VECTOR_EMPTY)\n  {\n    // Rest of members stays uninitialized intentionally for better performance.\n    SuballocationVectorType::const_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    {\n      return const_cast<VmaSuballocation&>(*it);\n    }\n  }\n\n  VMA_ASSERT(0 && \"Allocation not found in linear allocator!\");\n  return const_cast<VmaSuballocation&>(suballocations1st.back()); // Should never occur.\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  {\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  }\n  else\n  {\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].type == VMA_SUBALLOCATION_TYPE_FREE)\n    {\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().type == VMA_SUBALLOCATION_TYPE_FREE)\n    {\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().type == VMA_SUBALLOCATION_TYPE_FREE)\n    {\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].type == VMA_SUBALLOCATION_TYPE_FREE)\n    {\n      --m_2ndNullItemsCount;\n      VmaVectorRemove(suballocations2nd, 0);\n    }\n\n    if (ShouldCompact1st())\n    {\n      const size_t nonNullItemCount = suballoc1stCount - nullItem1stCount;\n      size_t srcIndex = m_1stNullItemsBeginCount;\n      for (size_t dstIndex = 0; dstIndex < nonNullItemCount; ++dstIndex)\n      {\n        while (suballocations1st[srcIndex].type == VMA_SUBALLOCATION_TYPE_FREE)\n        {\n          ++srcIndex;\n        }\n        if (dstIndex != srcIndex)\n        {\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    {\n      m_2ndVectorMode = SECOND_VECTOR_EMPTY;\n    }\n\n    // 1st vector became empty.\n    if (suballocations1st.size() - m_1stNullItemsBeginCount == 0)\n    {\n      suballocations1st.clear();\n      m_1stNullItemsBeginCount = 0;\n\n      if (!suballocations2nd.empty() && m_2ndVectorMode == SECOND_VECTOR_RING_BUFFER)\n      {\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].type == VMA_SUBALLOCATION_TYPE_FREE)\n        {\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\nbool VmaBlockMetadata_Linear::CreateAllocationRequest_LowerAddress(\n    VkDeviceSize allocSize,\n    VkDeviceSize allocAlignment,\n    VmaSuballocationType allocType,\n    uint32_t strategy,\n    VmaAllocationRequest* pAllocationRequest)\n{\n  const VkDeviceSize blockSize = GetSize();\n  const VkDeviceSize debugMargin = GetDebugMargin();\n  const VkDeviceSize bufferImageGranularity = GetBufferImageGranularity();\n  SuballocationVectorType& suballocations1st = AccessSuballocations1st();\n  SuballocationVectorType& suballocations2nd = AccessSuballocations2nd();\n\n  if (m_2ndVectorMode == SECOND_VECTOR_EMPTY || m_2ndVectorMode == SECOND_VECTOR_DOUBLE_STACK)\n  {\n    // Try to allocate at the end of 1st vector.\n\n    VkDeviceSize resultBaseOffset = 0;\n    if (!suballocations1st.empty())\n    {\n      const VmaSuballocation& lastSuballoc = suballocations1st.back();\n      resultBaseOffset = lastSuballoc.offset + lastSuballoc.size + debugMargin;\n    }\n\n    // Start from offset equal to beginning of free space.\n    VkDeviceSize resultOffset = resultBaseOffset;\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 && bufferImageGranularity != allocAlignment && !suballocations1st.empty())\n    {\n      bool bufferImageGranularityConflict = false;\n      for (size_t prevSuballocIndex = suballocations1st.size(); prevSuballocIndex--; )\n      {\n        const VmaSuballocation& prevSuballoc = suballocations1st[prevSuballocIndex];\n        if (VmaBlocksOnSamePage(prevSuballoc.offset, prevSuballoc.size, resultOffset, bufferImageGranularity))\n        {\n          if (VmaIsBufferImageGranularityConflict(prevSuballoc.type, allocType))\n          {\n            bufferImageGranularityConflict = true;\n            break;\n          }\n        }\n        else\n          // Already on previous page.\n          break;\n      }\n      if (bufferImageGranularityConflict)\n      {\n        resultOffset = VmaAlignUp(resultOffset, bufferImageGranularity);\n      }\n    }\n\n    const VkDeviceSize freeSpaceEnd = m_2ndVectorMode == SECOND_VECTOR_DOUBLE_STACK ?\n                                                                                    suballocations2nd.back().offset : blockSize;\n\n    // There is enough free space at the end after alignment.\n    if (resultOffset + allocSize + debugMargin <= freeSpaceEnd)\n    {\n      // Check next suballocations for BufferImageGranularity conflicts.\n      // If conflict exists, allocation cannot be made here.\n      if ((allocSize % bufferImageGranularity || resultOffset % bufferImageGranularity) && m_2ndVectorMode == SECOND_VECTOR_DOUBLE_STACK)\n      {\n        for (size_t nextSuballocIndex = suballocations2nd.size(); nextSuballocIndex--; )\n        {\n          const VmaSuballocation& nextSuballoc = suballocations2nd[nextSuballocIndex];\n          if (VmaBlocksOnSamePage(resultOffset, allocSize, nextSuballoc.offset, bufferImageGranularity))\n          {\n            if (VmaIsBufferImageGranularityConflict(allocType, nextSuballoc.type))\n            {\n              return false;\n            }\n          }\n          else\n          {\n            // Already on previous page.\n            break;\n          }\n        }\n      }\n\n      // All tests passed: Success.\n      pAllocationRequest->allocHandle = (VmaAllocHandle)(resultOffset + 1);\n      // pAllocationRequest->item, customData unused.\n      pAllocationRequest->type = VmaAllocationRequestType::EndOf1st;\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  {\n    VMA_ASSERT(!suballocations1st.empty());\n\n    VkDeviceSize resultBaseOffset = 0;\n    if (!suballocations2nd.empty())\n    {\n      const VmaSuballocation& lastSuballoc = suballocations2nd.back();\n      resultBaseOffset = lastSuballoc.offset + lastSuballoc.size + debugMargin;\n    }\n\n    // Start from offset equal to beginning of free space.\n    VkDeviceSize resultOffset = resultBaseOffset;\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 && bufferImageGranularity != allocAlignment && !suballocations2nd.empty())\n    {\n      bool bufferImageGranularityConflict = false;\n      for (size_t prevSuballocIndex = suballocations2nd.size(); prevSuballocIndex--; )\n      {\n        const VmaSuballocation& prevSuballoc = suballocations2nd[prevSuballocIndex];\n        if (VmaBlocksOnSamePage(prevSuballoc.offset, prevSuballoc.size, resultOffset, bufferImageGranularity))\n        {\n          if (VmaIsBufferImageGranularityConflict(prevSuballoc.type, allocType))\n          {\n            bufferImageGranularityConflict = true;\n            break;\n          }\n        }\n        else\n          // Already on previous page.\n          break;\n      }\n      if (bufferImageGranularityConflict)\n      {\n        resultOffset = VmaAlignUp(resultOffset, bufferImageGranularity);\n      }\n    }\n\n    size_t index1st = m_1stNullItemsBeginCount;\n\n    // There is enough free space at the end after alignment.\n    if ((index1st == suballocations1st.size() && resultOffset + allocSize + debugMargin <= blockSize) ||\n        (index1st < suballocations1st.size() && resultOffset + allocSize + debugMargin <= suballocations1st[index1st].offset))\n    {\n      // Check next suballocations for BufferImageGranularity conflicts.\n      // If conflict exists, allocation cannot be made here.\n      if (allocSize % bufferImageGranularity || resultOffset % bufferImageGranularity)\n      {\n        for (size_t nextSuballocIndex = index1st;\n             nextSuballocIndex < suballocations1st.size();\n             nextSuballocIndex++)\n        {\n          const VmaSuballocation& nextSuballoc = suballocations1st[nextSuballocIndex];\n          if (VmaBlocksOnSamePage(resultOffset, allocSize, nextSuballoc.offset, bufferImageGranularity))\n          {\n            if (VmaIsBufferImageGranularityConflict(allocType, nextSuballoc.type))\n            {\n              return false;\n            }\n          }\n          else\n          {\n            // Already on next page.\n            break;\n          }\n        }\n      }\n\n      // All tests passed: Success.\n      pAllocationRequest->allocHandle = (VmaAllocHandle)(resultOffset + 1);\n      pAllocationRequest->type = VmaAllocationRequestType::EndOf2nd;\n      // pAllocationRequest->item, customData unused.\n      return true;\n    }\n  }\n\n  return false;\n}\n\nbool VmaBlockMetadata_Linear::CreateAllocationRequest_UpperAddress(\n    VkDeviceSize allocSize,\n    VkDeviceSize allocAlignment,\n    VmaSuballocationType allocType,\n    uint32_t strategy,\n    VmaAllocationRequest* pAllocationRequest)\n{\n  const VkDeviceSize blockSize = GetSize();\n  const VkDeviceSize bufferImageGranularity = GetBufferImageGranularity();\n  SuballocationVectorType& suballocations1st = AccessSuballocations1st();\n  SuballocationVectorType& suballocations2nd = AccessSuballocations2nd();\n\n  if (m_2ndVectorMode == SECOND_VECTOR_RING_BUFFER)\n  {\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 > blockSize)\n  {\n    return false;\n  }\n  VkDeviceSize resultBaseOffset = blockSize - allocSize;\n  if (!suballocations2nd.empty())\n  {\n    const VmaSuballocation& lastSuballoc = suballocations2nd.back();\n    resultBaseOffset = lastSuballoc.offset - allocSize;\n    if (allocSize > lastSuballoc.offset)\n    {\n      return false;\n    }\n  }\n\n  // Start from offset equal to end of free space.\n  VkDeviceSize resultOffset = resultBaseOffset;\n\n  const VkDeviceSize debugMargin = GetDebugMargin();\n\n  // Apply debugMargin at the end.\n  if (debugMargin > 0)\n  {\n    if (resultOffset < debugMargin)\n    {\n      return false;\n    }\n    resultOffset -= debugMargin;\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 && bufferImageGranularity != allocAlignment && !suballocations2nd.empty())\n  {\n    bool bufferImageGranularityConflict = false;\n    for (size_t nextSuballocIndex = suballocations2nd.size(); nextSuballocIndex--; )\n    {\n      const VmaSuballocation& nextSuballoc = suballocations2nd[nextSuballocIndex];\n      if (VmaBlocksOnSamePage(resultOffset, allocSize, nextSuballoc.offset, bufferImageGranularity))\n      {\n        if (VmaIsBufferImageGranularityConflict(nextSuballoc.type, allocType))\n        {\n          bufferImageGranularityConflict = true;\n          break;\n        }\n      }\n      else\n        // Already on previous page.\n        break;\n    }\n    if (bufferImageGranularityConflict)\n    {\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 + debugMargin <= resultOffset)\n  {\n    // Check previous suballocations for BufferImageGranularity conflicts.\n    // If conflict exists, allocation cannot be made here.\n    if (bufferImageGranularity > 1)\n    {\n      for (size_t prevSuballocIndex = suballocations1st.size(); prevSuballocIndex--; )\n      {\n        const VmaSuballocation& prevSuballoc = suballocations1st[prevSuballocIndex];\n        if (VmaBlocksOnSamePage(prevSuballoc.offset, prevSuballoc.size, resultOffset, bufferImageGranularity))\n        {\n          if (VmaIsBufferImageGranularityConflict(allocType, prevSuballoc.type))\n          {\n            return false;\n          }\n        }\n        else\n        {\n          // Already on next page.\n          break;\n        }\n      }\n    }\n\n    // All tests passed: Success.\n    pAllocationRequest->allocHandle = (VmaAllocHandle)(resultOffset + 1);\n    // pAllocationRequest->item unused.\n    pAllocationRequest->type = VmaAllocationRequestType::UpperAddress;\n    return true;\n  }\n\n  return false;\n}\n#endif // _VMA_BLOCK_METADATA_LINEAR_FUNCTIONS\n#endif // _VMA_BLOCK_METADATA_LINEAR\n\n#if 0\n#ifndef _VMA_BLOCK_METADATA_BUDDY\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 reported 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(const VkAllocationCallbacks* pAllocationCallbacks,\n        VkDeviceSize bufferImageGranularity, bool isVirtual);\n    virtual ~VmaBlockMetadata_Buddy();\n\n    size_t GetAllocationCount() const override { return m_AllocationCount; }\n    VkDeviceSize GetSumFreeSize() const override { return m_SumFreeSize + GetUnusableSize(); }\n    bool IsEmpty() const override { return m_Root->type == Node::TYPE_FREE; }\n    VkResult CheckCorruption(const void* pBlockData) override { return VK_ERROR_FEATURE_NOT_PRESENT; }\n    VkDeviceSize GetAllocationOffset(VmaAllocHandle allocHandle) const override { return (VkDeviceSize)allocHandle - 1; }\n    void DebugLogAllAllocations() const override { DebugLogAllAllocationNode(m_Root, 0); }\n\n    void Init(VkDeviceSize size) override;\n    bool Validate() const override;\n\n    void AddDetailedStatistics(VmaDetailedStatistics& inoutStats) const override;\n    void AddStatistics(VmaStatistics& inoutStats) const override;\n\n#if VMA_STATS_STRING_ENABLED\n    void PrintDetailedMap(class VmaJsonWriter& json, uint32_t mapRefCount) const override;\n#endif\n\n    bool CreateAllocationRequest(\n        VkDeviceSize allocSize,\n        VkDeviceSize allocAlignment,\n        bool upperAddress,\n        VmaSuballocationType allocType,\n        uint32_t strategy,\n        VmaAllocationRequest* pAllocationRequest) override;\n\n    void Alloc(\n        const VmaAllocationRequest& request,\n        VmaSuballocationType type,\n        void* userData) override;\n\n    void Free(VmaAllocHandle allocHandle) override;\n    void GetAllocationInfo(VmaAllocHandle allocHandle, VmaVirtualAllocationInfo& outInfo) override;\n    void* GetAllocationUserData(VmaAllocHandle allocHandle) const override;\n    VmaAllocHandle GetAllocationListBegin() const override;\n    VmaAllocHandle GetNextAllocation(VmaAllocHandle prevAlloc) const override;\n    void Clear() override;\n    void SetAllocationUserData(VmaAllocHandle allocHandle, void* userData) override;\n\nprivate:\n    static const size_t MAX_LEVELS = 48;\n\n    struct ValidationContext\n    {\n        size_t calculatedAllocationCount = 0;\n        size_t calculatedFreeCount = 0;\n        VkDeviceSize calculatedSumFreeSize = 0;\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                void* userData;\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    VmaPoolAllocator<Node> m_NodeAllocator;\n    Node* m_Root;\n    struct\n    {\n        Node* front;\n        Node* back;\n    } m_FreeList[MAX_LEVELS];\n\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    // Doesn't include space wasted due to internal fragmentation - allocation sizes are just aligned up to node sizes.\n    // Doesn't include unusable size.\n    VkDeviceSize m_SumFreeSize;\n\n    VkDeviceSize GetUnusableSize() const { return GetSize() - m_UsableSize; }\n    VkDeviceSize LevelToNodeSize(uint32_t level) const { return m_UsableSize >> level; }\n\n    VkDeviceSize AlignAllocationSize(VkDeviceSize size) const\n    {\n        if (!IsVirtual())\n        {\n            size = VmaAlignUp(size, (VkDeviceSize)16);\n        }\n        return VmaNextPow2(size);\n    }\n    Node* FindAllocationNode(VkDeviceSize offset, uint32_t& outLevel) const;\n    void DeleteNodeChildren(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    void AddNodeToDetailedStatistics(VmaDetailedStatistics& inoutStats, 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    void DebugLogAllAllocationNode(Node* node, uint32_t level) const;\n\n#if VMA_STATS_STRING_ENABLED\n    void PrintDetailedMapNode(class VmaJsonWriter& json, const Node* node, VkDeviceSize levelNodeSize) const;\n#endif\n};\n\n#ifndef _VMA_BLOCK_METADATA_BUDDY_FUNCTIONS\nVmaBlockMetadata_Buddy::VmaBlockMetadata_Buddy(const VkAllocationCallbacks* pAllocationCallbacks,\n    VkDeviceSize bufferImageGranularity, bool isVirtual)\n    : VmaBlockMetadata(pAllocationCallbacks, bufferImageGranularity, isVirtual),\n    m_NodeAllocator(pAllocationCallbacks, 32), // firstBlockCapacity\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    DeleteNodeChildren(m_Root);\n    m_NodeAllocator.Free(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    const VkDeviceSize minNodeSize = IsVirtual() ? 1 : 16;\n    m_LevelCount = 1;\n    while (m_LevelCount < MAX_LEVELS &&\n        LevelToNodeSize(m_LevelCount) >= minNodeSize)\n    {\n        ++m_LevelCount;\n    }\n\n    Node* rootNode = m_NodeAllocator.Alloc();\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    {\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    {\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        {\n            VMA_VALIDATE(node->type == Node::TYPE_FREE);\n\n            if (node->free.next == VMA_NULL)\n            {\n                VMA_VALIDATE(m_FreeList[level].back == node);\n            }\n            else\n            {\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    {\n        VMA_VALIDATE(m_FreeList[level].front == VMA_NULL && m_FreeList[level].back == VMA_NULL);\n    }\n\n    return true;\n}\n\nvoid VmaBlockMetadata_Buddy::AddDetailedStatistics(VmaDetailedStatistics& inoutStats) const\n{\n    inoutStats.statistics.blockCount++;\n    inoutStats.statistics.blockBytes += GetSize();\n\n    AddNodeToDetailedStatistics(inoutStats, m_Root, LevelToNodeSize(0));\n\n    const VkDeviceSize unusableSize = GetUnusableSize();\n    if (unusableSize > 0)\n        VmaAddDetailedStatisticsUnusedRange(inoutStats, unusableSize);\n}\n\nvoid VmaBlockMetadata_Buddy::AddStatistics(VmaStatistics& inoutStats) const\n{\n    inoutStats.blockCount++;\n    inoutStats.allocationCount += (uint32_t)m_AllocationCount;\n    inoutStats.blockBytes += GetSize();\n    inoutStats.allocationBytes += GetSize() - m_SumFreeSize;\n}\n\n#if VMA_STATS_STRING_ENABLED\nvoid VmaBlockMetadata_Buddy::PrintDetailedMap(class VmaJsonWriter& json, uint32_t mapRefCount) const\n{\n    VmaDetailedStatistics stats;\n    VmaClearDetailedStatistics(stats);\n    AddDetailedStatistics(stats);\n\n    PrintDetailedMap_Begin(\n        json,\n        stats.statistics.blockBytes - stats.statistics.allocationBytes,\n        stats.statistics.allocationCount,\n        stats.unusedRangeCount,\n        mapRefCount);\n\n    PrintDetailedMapNode(json, m_Root, LevelToNodeSize(0));\n\n    const VkDeviceSize unusableSize = GetUnusableSize();\n    if (unusableSize > 0)\n    {\n        PrintDetailedMap_UnusedRange(json,\n            m_UsableSize, // offset\n            unusableSize); // size\n    }\n\n    PrintDetailedMap_End(json);\n}\n#endif // VMA_STATS_STRING_ENABLED\n\nbool VmaBlockMetadata_Buddy::CreateAllocationRequest(\n    VkDeviceSize allocSize,\n    VkDeviceSize allocAlignment,\n    bool upperAddress,\n    VmaSuballocationType allocType,\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    allocSize = AlignAllocationSize(allocSize);\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    {\n        allocAlignment = VMA_MAX(allocAlignment, GetBufferImageGranularity());\n        allocSize = VmaAlignUp(allocSize, GetBufferImageGranularity());\n    }\n\n    if (allocSize > m_UsableSize)\n    {\n        return false;\n    }\n\n    const uint32_t targetLevel = AllocSizeToLevel(allocSize);\n    for (uint32_t level = targetLevel; level--; )\n    {\n        for (Node* freeNode = m_FreeList[level].front;\n            freeNode != VMA_NULL;\n            freeNode = freeNode->free.next)\n        {\n            if (freeNode->offset % allocAlignment == 0)\n            {\n                pAllocationRequest->type = VmaAllocationRequestType::Normal;\n                pAllocationRequest->allocHandle = (VmaAllocHandle)(freeNode->offset + 1);\n                pAllocationRequest->size = allocSize;\n                pAllocationRequest->customData = (void*)(uintptr_t)level;\n                return true;\n            }\n        }\n    }\n\n    return false;\n}\n\nvoid VmaBlockMetadata_Buddy::Alloc(\n    const VmaAllocationRequest& request,\n    VmaSuballocationType type,\n    void* userData)\n{\n    VMA_ASSERT(request.type == VmaAllocationRequestType::Normal);\n\n    const uint32_t targetLevel = AllocSizeToLevel(request.size);\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    const VkDeviceSize offset = (VkDeviceSize)request.allocHandle - 1;\n    while (currNode->offset != offset)\n    {\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    {\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 = m_NodeAllocator.Alloc();\n        Node* rightChild = m_NodeAllocator.Alloc();\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        ++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 fulfills 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.userData = userData;\n\n    ++m_AllocationCount;\n    --m_FreeCount;\n    m_SumFreeSize -= request.size;\n}\n\nvoid VmaBlockMetadata_Buddy::GetAllocationInfo(VmaAllocHandle allocHandle, VmaVirtualAllocationInfo& outInfo)\n{\n    uint32_t level = 0;\n    outInfo.offset = (VkDeviceSize)allocHandle - 1;\n    const Node* const node = FindAllocationNode(outInfo.offset, level);\n    outInfo.size = LevelToNodeSize(level);\n    outInfo.pUserData = node->allocation.userData;\n}\n\nvoid* VmaBlockMetadata_Buddy::GetAllocationUserData(VmaAllocHandle allocHandle) const\n{\n    uint32_t level = 0;\n    const Node* const node = FindAllocationNode((VkDeviceSize)allocHandle - 1, level);\n    return node->allocation.userData;\n}\n\nVmaAllocHandle VmaBlockMetadata_Buddy::GetAllocationListBegin() const\n{\n    // Function only used for defragmentation, which is disabled for this algorithm\n    return VK_NULL_HANDLE;\n}\n\nVmaAllocHandle VmaBlockMetadata_Buddy::GetNextAllocation(VmaAllocHandle prevAlloc) const\n{\n    // Function only used for defragmentation, which is disabled for this algorithm\n    return VK_NULL_HANDLE;\n}\n\nvoid VmaBlockMetadata_Buddy::DeleteNodeChildren(Node* node)\n{\n    if (node->type == Node::TYPE_SPLIT)\n    {\n        DeleteNodeChildren(node->split.leftChild->buddy);\n        DeleteNodeChildren(node->split.leftChild);\n        const VkAllocationCallbacks* allocationCallbacks = GetAllocationCallbacks();\n        m_NodeAllocator.Free(node->split.leftChild->buddy);\n        m_NodeAllocator.Free(node->split.leftChild);\n    }\n}\n\nvoid VmaBlockMetadata_Buddy::Clear()\n{\n    DeleteNodeChildren(m_Root);\n    m_Root->type = Node::TYPE_FREE;\n    m_AllocationCount = 0;\n    m_FreeCount = 1;\n    m_SumFreeSize = m_UsableSize;\n}\n\nvoid VmaBlockMetadata_Buddy::SetAllocationUserData(VmaAllocHandle allocHandle, void* userData)\n{\n    uint32_t level = 0;\n    Node* const node = FindAllocationNode((VkDeviceSize)allocHandle - 1, level);\n    node->allocation.userData = userData;\n}\n\nVmaBlockMetadata_Buddy::Node* VmaBlockMetadata_Buddy::FindAllocationNode(VkDeviceSize offset, uint32_t& outLevel) const\n{\n    Node* node = m_Root;\n    VkDeviceSize nodeOffset = 0;\n    outLevel = 0;\n    VkDeviceSize levelNodeSize = LevelToNodeSize(0);\n    while (node->type == Node::TYPE_SPLIT)\n    {\n        const VkDeviceSize nextLevelNodeSize = levelNodeSize >> 1;\n        if (offset < nodeOffset + nextLevelNodeSize)\n        {\n            node = node->split.leftChild;\n        }\n        else\n        {\n            node = node->split.leftChild->buddy;\n            nodeOffset += nextLevelNodeSize;\n        }\n        ++outLevel;\n        levelNodeSize = nextLevelNodeSize;\n    }\n\n    VMA_ASSERT(node != VMA_NULL && node->type == Node::TYPE_ALLOCATION);\n    return 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    {\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        if (!IsVirtual())\n        {\n            VMA_VALIDATE(curr->allocation.userData != VMA_NULL);\n        }\n        break;\n    case Node::TYPE_SPLIT:\n    {\n        const uint32_t childrenLevel = level + 1;\n        const VkDeviceSize childrenLevelNodeSize = levelNodeSize >> 1;\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        {\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        {\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    {\n        ++level;\n        currLevelNodeSize >>= 1;\n        nextLevelNodeSize >>= 1;\n    }\n    return level;\n}\n\nvoid VmaBlockMetadata_Buddy::Free(VmaAllocHandle allocHandle)\n{\n    uint32_t level = 0;\n    Node* node = FindAllocationNode((VkDeviceSize)allocHandle - 1, level);\n\n    ++m_FreeCount;\n    --m_AllocationCount;\n    m_SumFreeSize += LevelToNodeSize(level);\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    {\n        RemoveFromFreeList(level, node->buddy);\n        Node* const parent = node->parent;\n\n        m_NodeAllocator.Free(node->buddy);\n        m_NodeAllocator.Free(node);\n        parent->type = Node::TYPE_FREE;\n\n        node = parent;\n        --level;\n        --m_FreeCount;\n    }\n\n    AddToFreeListFront(level, node);\n}\n\nvoid VmaBlockMetadata_Buddy::AddNodeToDetailedStatistics(VmaDetailedStatistics& inoutStats, const Node* node, VkDeviceSize levelNodeSize) const\n{\n    switch (node->type)\n    {\n    case Node::TYPE_FREE:\n        VmaAddDetailedStatisticsUnusedRange(inoutStats, levelNodeSize);\n        break;\n    case Node::TYPE_ALLOCATION:\n        VmaAddDetailedStatisticsAllocation(inoutStats, levelNodeSize);\n        break;\n    case Node::TYPE_SPLIT:\n    {\n        const VkDeviceSize childrenNodeSize = levelNodeSize / 2;\n        const Node* const leftChild = node->split.leftChild;\n        AddNodeToDetailedStatistics(inoutStats, leftChild, childrenNodeSize);\n        const Node* const rightChild = leftChild->buddy;\n        AddNodeToDetailedStatistics(inoutStats, 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    {\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    }\n    else\n    {\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    {\n        VMA_ASSERT(m_FreeList[level].front == node);\n        m_FreeList[level].front = node->free.next;\n    }\n    else\n    {\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    {\n        VMA_ASSERT(m_FreeList[level].back == node);\n        m_FreeList[level].back = node->free.prev;\n    }\n    else\n    {\n        Node* const nextFreeNode = node->free.next;\n        VMA_ASSERT(nextFreeNode->free.prev == node);\n        nextFreeNode->free.prev = node->free.prev;\n    }\n}\n\nvoid VmaBlockMetadata_Buddy::DebugLogAllAllocationNode(Node* node, uint32_t level) const\n{\n    switch (node->type)\n    {\n    case Node::TYPE_FREE:\n        break;\n    case Node::TYPE_ALLOCATION:\n        DebugLogAllocation(node->offset, LevelToNodeSize(level), node->allocation.userData);\n        break;\n    case Node::TYPE_SPLIT:\n    {\n        ++level;\n        DebugLogAllAllocationNode(node->split.leftChild, level);\n        DebugLogAllAllocationNode(node->split.leftChild->buddy, level);\n    }\n    break;\n    default:\n        VMA_ASSERT(0);\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    {\n    case Node::TYPE_FREE:\n        PrintDetailedMap_UnusedRange(json, node->offset, levelNodeSize);\n        break;\n    case Node::TYPE_ALLOCATION:\n        PrintDetailedMap_Allocation(json, node->offset, levelNodeSize, node->allocation.userData);\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 // VMA_STATS_STRING_ENABLED\n#endif // _VMA_BLOCK_METADATA_BUDDY_FUNCTIONS\n#endif // _VMA_BLOCK_METADATA_BUDDY\n#endif // #if 0\n\n#ifndef _VMA_BLOCK_METADATA_TLSF\n// To not search current larger region if first allocation won't succeed and skip to smaller range\n// use with VMA_ALLOCATION_CREATE_STRATEGY_MIN_MEMORY_BIT as strategy in CreateAllocationRequest().\n// When fragmentation and reusal of previous blocks doesn't matter then use with\n// VMA_ALLOCATION_CREATE_STRATEGY_MIN_TIME_BIT for fastest alloc time possible.\nclass VmaBlockMetadata_TLSF : public VmaBlockMetadata\n{\n  VMA_CLASS_NO_COPY(VmaBlockMetadata_TLSF)\n public:\n  VmaBlockMetadata_TLSF(const VkAllocationCallbacks* pAllocationCallbacks,\n                        VkDeviceSize bufferImageGranularity, bool isVirtual);\n  virtual ~VmaBlockMetadata_TLSF();\n\n  size_t GetAllocationCount() const override { return m_AllocCount; }\n  size_t GetFreeRegionsCount() const override { return m_BlocksFreeCount + 1; }\n  VkDeviceSize GetSumFreeSize() const override { return m_BlocksFreeSize + m_NullBlock->size; }\n  bool IsEmpty() const override { return m_NullBlock->offset == 0; }\n  VkDeviceSize GetAllocationOffset(VmaAllocHandle allocHandle) const override { return ((Block*)allocHandle)->offset; }\n\n  void Init(VkDeviceSize size) override;\n  bool Validate() const override;\n\n  void AddDetailedStatistics(VmaDetailedStatistics& inoutStats) const override;\n  void AddStatistics(VmaStatistics& inoutStats) const override;\n\n#if VMA_STATS_STRING_ENABLED\n  void PrintDetailedMap(class VmaJsonWriter& json) const override;\n#endif\n\n  bool CreateAllocationRequest(\n      VkDeviceSize allocSize,\n      VkDeviceSize allocAlignment,\n      bool upperAddress,\n      VmaSuballocationType allocType,\n      uint32_t strategy,\n      VmaAllocationRequest* pAllocationRequest) override;\n\n  VkResult CheckCorruption(const void* pBlockData) override;\n  void Alloc(\n      const VmaAllocationRequest& request,\n      VmaSuballocationType type,\n      void* userData) override;\n\n  void Free(VmaAllocHandle allocHandle) override;\n  void GetAllocationInfo(VmaAllocHandle allocHandle, VmaVirtualAllocationInfo& outInfo) override;\n  void* GetAllocationUserData(VmaAllocHandle allocHandle) const override;\n  VmaAllocHandle GetAllocationListBegin() const override;\n  VmaAllocHandle GetNextAllocation(VmaAllocHandle prevAlloc) const override;\n  VkDeviceSize GetNextFreeRegionSize(VmaAllocHandle alloc) const override;\n  void Clear() override;\n  void SetAllocationUserData(VmaAllocHandle allocHandle, void* userData) override;\n  void DebugLogAllAllocations() const override;\n\n private:\n  // According to original paper it should be preferable 4 or 5:\n  // M. Masmano, I. Ripoll, A. Crespo, and J. Real \"TLSF: a New Dynamic Memory Allocator for Real-Time Systems\"\n  // http://www.gii.upv.es/tlsf/files/ecrts04_tlsf.pdf\n  static const uint8_t SECOND_LEVEL_INDEX = 5;\n  static const uint16_t SMALL_BUFFER_SIZE = 256;\n  static const uint32_t INITIAL_BLOCK_ALLOC_COUNT = 16;\n  static const uint8_t MEMORY_CLASS_SHIFT = 7;\n  static const uint8_t MAX_MEMORY_CLASSES = 65 - MEMORY_CLASS_SHIFT;\n\n  class Block\n  {\n   public:\n    VkDeviceSize offset;\n    VkDeviceSize size;\n    Block* prevPhysical;\n    Block* nextPhysical;\n\n    void MarkFree() { prevFree = VMA_NULL; }\n    void MarkTaken() { prevFree = this; }\n    bool IsFree() const { return prevFree != this; }\n    void*& UserData() { VMA_HEAVY_ASSERT(!IsFree()); return userData; }\n    Block*& PrevFree() { return prevFree; }\n    Block*& NextFree() { VMA_HEAVY_ASSERT(IsFree()); return nextFree; }\n\n   private:\n    Block* prevFree; // Address of the same block here indicates that block is taken\n    union\n    {\n      Block* nextFree;\n      void* userData;\n    };\n  };\n\n  size_t m_AllocCount;\n  // Total number of free blocks besides null block\n  size_t m_BlocksFreeCount;\n  // Total size of free blocks excluding null block\n  VkDeviceSize m_BlocksFreeSize;\n  uint32_t m_IsFreeBitmap;\n  uint8_t m_MemoryClasses;\n  uint32_t m_InnerIsFreeBitmap[MAX_MEMORY_CLASSES];\n  uint32_t m_ListsCount;\n  /*\n    * 0: 0-3 lists for small buffers\n    * 1+: 0-(2^SLI-1) lists for normal buffers\n   */\n  Block** m_FreeList;\n  VmaPoolAllocator<Block> m_BlockAllocator;\n  Block* m_NullBlock;\n  VmaBlockBufferImageGranularity m_GranularityHandler;\n\n  uint8_t SizeToMemoryClass(VkDeviceSize size) const;\n  uint16_t SizeToSecondIndex(VkDeviceSize size, uint8_t memoryClass) const;\n  uint32_t GetListIndex(uint8_t memoryClass, uint16_t secondIndex) const;\n  uint32_t GetListIndex(VkDeviceSize size) const;\n\n  void RemoveFreeBlock(Block* block);\n  void InsertFreeBlock(Block* block);\n  void MergeBlock(Block* block, Block* prev);\n\n  Block* FindFreeBlock(VkDeviceSize size, uint32_t& listIndex) const;\n  bool CheckBlock(\n      Block& block,\n      uint32_t listIndex,\n      VkDeviceSize allocSize,\n      VkDeviceSize allocAlignment,\n      VmaSuballocationType allocType,\n      VmaAllocationRequest* pAllocationRequest);\n};\n\n#ifndef _VMA_BLOCK_METADATA_TLSF_FUNCTIONS\nVmaBlockMetadata_TLSF::VmaBlockMetadata_TLSF(const VkAllocationCallbacks* pAllocationCallbacks,\n                                             VkDeviceSize bufferImageGranularity, bool isVirtual)\n    : VmaBlockMetadata(pAllocationCallbacks, bufferImageGranularity, isVirtual),\n      m_AllocCount(0),\n      m_BlocksFreeCount(0),\n      m_BlocksFreeSize(0),\n      m_IsFreeBitmap(0),\n      m_MemoryClasses(0),\n      m_ListsCount(0),\n      m_FreeList(VMA_NULL),\n      m_BlockAllocator(pAllocationCallbacks, INITIAL_BLOCK_ALLOC_COUNT),\n      m_NullBlock(VMA_NULL),\n      m_GranularityHandler(bufferImageGranularity) {}\n\nVmaBlockMetadata_TLSF::~VmaBlockMetadata_TLSF()\n{\n  if (m_FreeList)\n    vma_delete_array(GetAllocationCallbacks(), m_FreeList, m_ListsCount);\n  m_GranularityHandler.Destroy(GetAllocationCallbacks());\n}\n\nvoid VmaBlockMetadata_TLSF::Init(VkDeviceSize size)\n{\n  VmaBlockMetadata::Init(size);\n\n  if (!IsVirtual())\n    m_GranularityHandler.Init(GetAllocationCallbacks(), size);\n\n  m_NullBlock = m_BlockAllocator.Alloc();\n  m_NullBlock->size = size;\n  m_NullBlock->offset = 0;\n  m_NullBlock->prevPhysical = VMA_NULL;\n  m_NullBlock->nextPhysical = VMA_NULL;\n  m_NullBlock->MarkFree();\n  m_NullBlock->NextFree() = VMA_NULL;\n  m_NullBlock->PrevFree() = VMA_NULL;\n  uint8_t memoryClass = SizeToMemoryClass(size);\n  uint16_t sli = SizeToSecondIndex(size, memoryClass);\n  m_ListsCount = (memoryClass == 0 ? 0 : (memoryClass - 1) * (1UL << SECOND_LEVEL_INDEX) + sli) + 1;\n  if (IsVirtual())\n    m_ListsCount += 1UL << SECOND_LEVEL_INDEX;\n  else\n    m_ListsCount += 4;\n\n  m_MemoryClasses = memoryClass + 2;\n  memset(m_InnerIsFreeBitmap, 0, MAX_MEMORY_CLASSES * sizeof(uint32_t));\n\n  m_FreeList = vma_new_array(GetAllocationCallbacks(), Block*, m_ListsCount);\n  memset(m_FreeList, 0, m_ListsCount * sizeof(Block*));\n}\n\nbool VmaBlockMetadata_TLSF::Validate() const\n{\n  VMA_VALIDATE(GetSumFreeSize() <= GetSize());\n\n  VkDeviceSize calculatedSize = m_NullBlock->size;\n  VkDeviceSize calculatedFreeSize = m_NullBlock->size;\n  size_t allocCount = 0;\n  size_t freeCount = 0;\n\n  // Check integrity of free lists\n  for (uint32_t list = 0; list < m_ListsCount; ++list)\n  {\n    Block* block = m_FreeList[list];\n    if (block != VMA_NULL)\n    {\n      VMA_VALIDATE(block->IsFree());\n      VMA_VALIDATE(block->PrevFree() == VMA_NULL);\n      while (block->NextFree())\n      {\n        VMA_VALIDATE(block->NextFree()->IsFree());\n        VMA_VALIDATE(block->NextFree()->PrevFree() == block);\n        block = block->NextFree();\n      }\n    }\n  }\n\n  VkDeviceSize nextOffset = m_NullBlock->offset;\n  auto validateCtx = m_GranularityHandler.StartValidation(GetAllocationCallbacks(), IsVirtual());\n\n  VMA_VALIDATE(m_NullBlock->nextPhysical == VMA_NULL);\n  if (m_NullBlock->prevPhysical)\n  {\n    VMA_VALIDATE(m_NullBlock->prevPhysical->nextPhysical == m_NullBlock);\n  }\n  // Check all blocks\n  for (Block* prev = m_NullBlock->prevPhysical; prev != VMA_NULL; prev = prev->prevPhysical)\n  {\n    VMA_VALIDATE(prev->offset + prev->size == nextOffset);\n    nextOffset = prev->offset;\n    calculatedSize += prev->size;\n\n    uint32_t listIndex = GetListIndex(prev->size);\n    if (prev->IsFree())\n    {\n      ++freeCount;\n      // Check if free block belongs to free list\n      Block* freeBlock = m_FreeList[listIndex];\n      VMA_VALIDATE(freeBlock != VMA_NULL);\n\n      bool found = false;\n      do\n      {\n        if (freeBlock == prev)\n          found = true;\n\n        freeBlock = freeBlock->NextFree();\n      } while (!found && freeBlock != VMA_NULL);\n\n      VMA_VALIDATE(found);\n      calculatedFreeSize += prev->size;\n    }\n    else\n    {\n      ++allocCount;\n      // Check if taken block is not on a free list\n      Block* freeBlock = m_FreeList[listIndex];\n      while (freeBlock)\n      {\n        VMA_VALIDATE(freeBlock != prev);\n        freeBlock = freeBlock->NextFree();\n      }\n\n      if (!IsVirtual())\n      {\n        VMA_VALIDATE(m_GranularityHandler.Validate(validateCtx, prev->offset, prev->size));\n      }\n    }\n\n    if (prev->prevPhysical)\n    {\n      VMA_VALIDATE(prev->prevPhysical->nextPhysical == prev);\n    }\n  }\n\n  if (!IsVirtual())\n  {\n    VMA_VALIDATE(m_GranularityHandler.FinishValidation(validateCtx));\n  }\n\n  VMA_VALIDATE(nextOffset == 0);\n  VMA_VALIDATE(calculatedSize == GetSize());\n  VMA_VALIDATE(calculatedFreeSize == GetSumFreeSize());\n  VMA_VALIDATE(allocCount == m_AllocCount);\n  VMA_VALIDATE(freeCount == m_BlocksFreeCount);\n\n  return true;\n}\n\nvoid VmaBlockMetadata_TLSF::AddDetailedStatistics(VmaDetailedStatistics& inoutStats) const\n{\n  inoutStats.statistics.blockCount++;\n  inoutStats.statistics.blockBytes += GetSize();\n  if (m_NullBlock->size > 0)\n    VmaAddDetailedStatisticsUnusedRange(inoutStats, m_NullBlock->size);\n\n  for (Block* block = m_NullBlock->prevPhysical; block != VMA_NULL; block = block->prevPhysical)\n  {\n    if (block->IsFree())\n      VmaAddDetailedStatisticsUnusedRange(inoutStats, block->size);\n    else\n      VmaAddDetailedStatisticsAllocation(inoutStats, block->size);\n  }\n}\n\nvoid VmaBlockMetadata_TLSF::AddStatistics(VmaStatistics& inoutStats) const\n{\n  inoutStats.blockCount++;\n  inoutStats.allocationCount += (uint32_t)m_AllocCount;\n  inoutStats.blockBytes += GetSize();\n  inoutStats.allocationBytes += GetSize() - GetSumFreeSize();\n}\n\n#if VMA_STATS_STRING_ENABLED\nvoid VmaBlockMetadata_TLSF::PrintDetailedMap(class VmaJsonWriter& json) const\n{\n  size_t blockCount = m_AllocCount + m_BlocksFreeCount;\n  VmaStlAllocator<Block*> allocator(GetAllocationCallbacks());\n  VmaVector<Block*, VmaStlAllocator<Block*>> blockList(blockCount, allocator);\n\n  size_t i = blockCount;\n  for (Block* block = m_NullBlock->prevPhysical; block != VMA_NULL; block = block->prevPhysical)\n  {\n    blockList[--i] = block;\n  }\n  VMA_ASSERT(i == 0);\n\n  VmaDetailedStatistics stats;\n  VmaClearDetailedStatistics(stats);\n  AddDetailedStatistics(stats);\n\n  PrintDetailedMap_Begin(json,\n                         stats.statistics.blockBytes - stats.statistics.allocationBytes,\n                         stats.statistics.allocationCount,\n                         stats.unusedRangeCount);\n\n  for (; i < blockCount; ++i)\n  {\n    Block* block = blockList[i];\n    if (block->IsFree())\n      PrintDetailedMap_UnusedRange(json, block->offset, block->size);\n    else\n      PrintDetailedMap_Allocation(json, block->offset, block->size, block->UserData());\n  }\n  if (m_NullBlock->size > 0)\n    PrintDetailedMap_UnusedRange(json, m_NullBlock->offset, m_NullBlock->size);\n\n  PrintDetailedMap_End(json);\n}\n#endif\n\nbool VmaBlockMetadata_TLSF::CreateAllocationRequest(\n    VkDeviceSize allocSize,\n    VkDeviceSize allocAlignment,\n    bool upperAddress,\n    VmaSuballocationType allocType,\n    uint32_t strategy,\n    VmaAllocationRequest* pAllocationRequest)\n{\n  VMA_ASSERT(allocSize > 0 && \"Cannot allocate empty block!\");\n  VMA_ASSERT(!upperAddress && \"VMA_ALLOCATION_CREATE_UPPER_ADDRESS_BIT can be used only with linear algorithm.\");\n\n  // For small granularity round up\n  if (!IsVirtual())\n    m_GranularityHandler.RoundupAllocRequest(allocType, allocSize, allocAlignment);\n\n  allocSize += GetDebugMargin();\n  // Quick check for too small pool\n  if (allocSize > GetSumFreeSize())\n    return false;\n\n  // If no free blocks in pool then check only null block\n  if (m_BlocksFreeCount == 0)\n    return CheckBlock(*m_NullBlock, m_ListsCount, allocSize, allocAlignment, allocType, pAllocationRequest);\n\n  // Round up to the next block\n  VkDeviceSize sizeForNextList = allocSize;\n  VkDeviceSize smallSizeStep = SMALL_BUFFER_SIZE / (IsVirtual() ? 1 << SECOND_LEVEL_INDEX : 4);\n  if (allocSize > SMALL_BUFFER_SIZE)\n  {\n    sizeForNextList += (1ULL << (VMA_BITSCAN_MSB(allocSize) - SECOND_LEVEL_INDEX));\n  }\n  else if (allocSize > SMALL_BUFFER_SIZE - smallSizeStep)\n    sizeForNextList = SMALL_BUFFER_SIZE + 1;\n  else\n    sizeForNextList += smallSizeStep;\n\n  uint32_t nextListIndex = 0;\n  uint32_t prevListIndex = 0;\n  Block* nextListBlock = VMA_NULL;\n  Block* prevListBlock = VMA_NULL;\n\n  // Check blocks according to strategies\n  if (strategy & VMA_ALLOCATION_CREATE_STRATEGY_MIN_TIME_BIT)\n  {\n    // Quick check for larger block first\n    nextListBlock = FindFreeBlock(sizeForNextList, nextListIndex);\n    if (nextListBlock != VMA_NULL && CheckBlock(*nextListBlock, nextListIndex, allocSize, allocAlignment, allocType, pAllocationRequest))\n      return true;\n\n    // If not fitted then null block\n    if (CheckBlock(*m_NullBlock, m_ListsCount, allocSize, allocAlignment, allocType, pAllocationRequest))\n      return true;\n\n    // Null block failed, search larger bucket\n    while (nextListBlock)\n    {\n      if (CheckBlock(*nextListBlock, nextListIndex, allocSize, allocAlignment, allocType, pAllocationRequest))\n        return true;\n      nextListBlock = nextListBlock->NextFree();\n    }\n\n    // Failed again, check best fit bucket\n    prevListBlock = FindFreeBlock(allocSize, prevListIndex);\n    while (prevListBlock)\n    {\n      if (CheckBlock(*prevListBlock, prevListIndex, allocSize, allocAlignment, allocType, pAllocationRequest))\n        return true;\n      prevListBlock = prevListBlock->NextFree();\n    }\n  }\n  else if (strategy & VMA_ALLOCATION_CREATE_STRATEGY_MIN_MEMORY_BIT)\n  {\n    // Check best fit bucket\n    prevListBlock = FindFreeBlock(allocSize, prevListIndex);\n    while (prevListBlock)\n    {\n      if (CheckBlock(*prevListBlock, prevListIndex, allocSize, allocAlignment, allocType, pAllocationRequest))\n        return true;\n      prevListBlock = prevListBlock->NextFree();\n    }\n\n    // If failed check null block\n    if (CheckBlock(*m_NullBlock, m_ListsCount, allocSize, allocAlignment, allocType, pAllocationRequest))\n      return true;\n\n    // Check larger bucket\n    nextListBlock = FindFreeBlock(sizeForNextList, nextListIndex);\n    while (nextListBlock)\n    {\n      if (CheckBlock(*nextListBlock, nextListIndex, allocSize, allocAlignment, allocType, pAllocationRequest))\n        return true;\n      nextListBlock = nextListBlock->NextFree();\n    }\n  }\n  else if (strategy & VMA_ALLOCATION_CREATE_STRATEGY_MIN_OFFSET_BIT )\n  {\n    // Perform search from the start\n    VmaStlAllocator<Block*> allocator(GetAllocationCallbacks());\n    VmaVector<Block*, VmaStlAllocator<Block*>> blockList(m_BlocksFreeCount, allocator);\n\n    size_t i = m_BlocksFreeCount;\n    for (Block* block = m_NullBlock->prevPhysical; block != VMA_NULL; block = block->prevPhysical)\n    {\n      if (block->IsFree() && block->size >= allocSize)\n        blockList[--i] = block;\n    }\n\n    for (; i < m_BlocksFreeCount; ++i)\n    {\n      Block& block = *blockList[i];\n      if (CheckBlock(block, GetListIndex(block.size), allocSize, allocAlignment, allocType, pAllocationRequest))\n        return true;\n    }\n\n    // If failed check null block\n    if (CheckBlock(*m_NullBlock, m_ListsCount, allocSize, allocAlignment, allocType, pAllocationRequest))\n      return true;\n\n    // Whole range searched, no more memory\n    return false;\n  }\n  else\n  {\n    // Check larger bucket\n    nextListBlock = FindFreeBlock(sizeForNextList, nextListIndex);\n    while (nextListBlock)\n    {\n      if (CheckBlock(*nextListBlock, nextListIndex, allocSize, allocAlignment, allocType, pAllocationRequest))\n        return true;\n      nextListBlock = nextListBlock->NextFree();\n    }\n\n    // If failed check null block\n    if (CheckBlock(*m_NullBlock, m_ListsCount, allocSize, allocAlignment, allocType, pAllocationRequest))\n      return true;\n\n    // Check best fit bucket\n    prevListBlock = FindFreeBlock(allocSize, prevListIndex);\n    while (prevListBlock)\n    {\n      if (CheckBlock(*prevListBlock, prevListIndex, allocSize, allocAlignment, allocType, pAllocationRequest))\n        return true;\n      prevListBlock = prevListBlock->NextFree();\n    }\n  }\n\n  // Worst case, full search has to be done\n  while (++nextListIndex < m_ListsCount)\n  {\n    nextListBlock = m_FreeList[nextListIndex];\n    while (nextListBlock)\n    {\n      if (CheckBlock(*nextListBlock, nextListIndex, allocSize, allocAlignment, allocType, pAllocationRequest))\n        return true;\n      nextListBlock = nextListBlock->NextFree();\n    }\n  }\n\n  // No more memory sadly\n  return false;\n}\n\nVkResult VmaBlockMetadata_TLSF::CheckCorruption(const void* pBlockData)\n{\n  for (Block* block = m_NullBlock->prevPhysical; block != VMA_NULL; block = block->prevPhysical)\n  {\n    if (!block->IsFree())\n    {\n      if (!VmaValidateMagicValue(pBlockData, block->offset + block->size))\n      {\n        VMA_ASSERT(0 && \"MEMORY CORRUPTION DETECTED AFTER VALIDATED ALLOCATION!\");\n        return VK_ERROR_UNKNOWN_COPY;\n      }\n    }\n  }\n\n  return VK_SUCCESS;\n}\n\nvoid VmaBlockMetadata_TLSF::Alloc(\n    const VmaAllocationRequest& request,\n    VmaSuballocationType type,\n    void* userData)\n{\n  VMA_ASSERT(request.type == VmaAllocationRequestType::TLSF);\n\n  // Get block and pop it from the free list\n  Block* currentBlock = (Block*)request.allocHandle;\n  VkDeviceSize offset = request.algorithmData;\n  VMA_ASSERT(currentBlock != VMA_NULL);\n  VMA_ASSERT(currentBlock->offset <= offset);\n\n  if (currentBlock != m_NullBlock)\n    RemoveFreeBlock(currentBlock);\n\n  VkDeviceSize debugMargin = GetDebugMargin();\n  VkDeviceSize misssingAlignment = offset - currentBlock->offset;\n\n  // Append missing alignment to prev block or create new one\n  if (misssingAlignment)\n  {\n    Block* prevBlock = currentBlock->prevPhysical;\n    VMA_ASSERT(prevBlock != VMA_NULL && \"There should be no missing alignment at offset 0!\");\n\n    if (prevBlock->IsFree() && prevBlock->size != debugMargin)\n    {\n      uint32_t oldList = GetListIndex(prevBlock->size);\n      prevBlock->size += misssingAlignment;\n      // Check if new size crosses list bucket\n      if (oldList != GetListIndex(prevBlock->size))\n      {\n        prevBlock->size -= misssingAlignment;\n        RemoveFreeBlock(prevBlock);\n        prevBlock->size += misssingAlignment;\n        InsertFreeBlock(prevBlock);\n      }\n      else\n        m_BlocksFreeSize += misssingAlignment;\n    }\n    else\n    {\n      Block* newBlock = m_BlockAllocator.Alloc();\n      currentBlock->prevPhysical = newBlock;\n      prevBlock->nextPhysical = newBlock;\n      newBlock->prevPhysical = prevBlock;\n      newBlock->nextPhysical = currentBlock;\n      newBlock->size = misssingAlignment;\n      newBlock->offset = currentBlock->offset;\n      newBlock->MarkTaken();\n\n      InsertFreeBlock(newBlock);\n    }\n\n    currentBlock->size -= misssingAlignment;\n    currentBlock->offset += misssingAlignment;\n  }\n\n  VkDeviceSize size = request.size + debugMargin;\n  if (currentBlock->size == size)\n  {\n    if (currentBlock == m_NullBlock)\n    {\n      // Setup new null block\n      m_NullBlock = m_BlockAllocator.Alloc();\n      m_NullBlock->size = 0;\n      m_NullBlock->offset = currentBlock->offset + size;\n      m_NullBlock->prevPhysical = currentBlock;\n      m_NullBlock->nextPhysical = VMA_NULL;\n      m_NullBlock->MarkFree();\n      m_NullBlock->PrevFree() = VMA_NULL;\n      m_NullBlock->NextFree() = VMA_NULL;\n      currentBlock->nextPhysical = m_NullBlock;\n      currentBlock->MarkTaken();\n    }\n  }\n  else\n  {\n    VMA_ASSERT(currentBlock->size > size && \"Proper block already found, shouldn't find smaller one!\");\n\n    // Create new free block\n    Block* newBlock = m_BlockAllocator.Alloc();\n    newBlock->size = currentBlock->size - size;\n    newBlock->offset = currentBlock->offset + size;\n    newBlock->prevPhysical = currentBlock;\n    newBlock->nextPhysical = currentBlock->nextPhysical;\n    currentBlock->nextPhysical = newBlock;\n    currentBlock->size = size;\n\n    if (currentBlock == m_NullBlock)\n    {\n      m_NullBlock = newBlock;\n      m_NullBlock->MarkFree();\n      m_NullBlock->NextFree() = VMA_NULL;\n      m_NullBlock->PrevFree() = VMA_NULL;\n      currentBlock->MarkTaken();\n    }\n    else\n    {\n      newBlock->nextPhysical->prevPhysical = newBlock;\n      newBlock->MarkTaken();\n      InsertFreeBlock(newBlock);\n    }\n  }\n  currentBlock->UserData() = userData;\n\n  if (debugMargin > 0)\n  {\n    currentBlock->size -= debugMargin;\n    Block* newBlock = m_BlockAllocator.Alloc();\n    newBlock->size = debugMargin;\n    newBlock->offset = currentBlock->offset + currentBlock->size;\n    newBlock->prevPhysical = currentBlock;\n    newBlock->nextPhysical = currentBlock->nextPhysical;\n    newBlock->MarkTaken();\n    currentBlock->nextPhysical->prevPhysical = newBlock;\n    currentBlock->nextPhysical = newBlock;\n    InsertFreeBlock(newBlock);\n  }\n\n  if (!IsVirtual())\n    m_GranularityHandler.AllocPages((uint8_t)(uintptr_t)request.customData,\n                                    currentBlock->offset, currentBlock->size);\n  ++m_AllocCount;\n}\n\nvoid VmaBlockMetadata_TLSF::Free(VmaAllocHandle allocHandle)\n{\n  Block* block = (Block*)allocHandle;\n  Block* next = block->nextPhysical;\n  VMA_ASSERT(!block->IsFree() && \"Block is already free!\");\n\n  if (!IsVirtual())\n    m_GranularityHandler.FreePages(block->offset, block->size);\n  --m_AllocCount;\n\n  VkDeviceSize debugMargin = GetDebugMargin();\n  if (debugMargin > 0)\n  {\n    RemoveFreeBlock(next);\n    MergeBlock(next, block);\n    block = next;\n    next = next->nextPhysical;\n  }\n\n  // Try merging\n  Block* prev = block->prevPhysical;\n  if (prev != VMA_NULL && prev->IsFree() && prev->size != debugMargin)\n  {\n    RemoveFreeBlock(prev);\n    MergeBlock(block, prev);\n  }\n\n  if (!next->IsFree())\n    InsertFreeBlock(block);\n  else if (next == m_NullBlock)\n    MergeBlock(m_NullBlock, block);\n  else\n  {\n    RemoveFreeBlock(next);\n    MergeBlock(next, block);\n    InsertFreeBlock(next);\n  }\n}\n\nvoid VmaBlockMetadata_TLSF::GetAllocationInfo(VmaAllocHandle allocHandle, VmaVirtualAllocationInfo& outInfo)\n{\n  Block* block = (Block*)allocHandle;\n  VMA_ASSERT(!block->IsFree() && \"Cannot get allocation info for free block!\");\n  outInfo.offset = block->offset;\n  outInfo.size = block->size;\n  outInfo.pUserData = block->UserData();\n}\n\nvoid* VmaBlockMetadata_TLSF::GetAllocationUserData(VmaAllocHandle allocHandle) const\n{\n  Block* block = (Block*)allocHandle;\n  VMA_ASSERT(!block->IsFree() && \"Cannot get user data for free block!\");\n  return block->UserData();\n}\n\nVmaAllocHandle VmaBlockMetadata_TLSF::GetAllocationListBegin() const\n{\n  if (m_AllocCount == 0)\n    return VK_NULL_HANDLE;\n\n  for (Block* block = m_NullBlock->prevPhysical; block; block = block->prevPhysical)\n  {\n    if (!block->IsFree())\n      return (VmaAllocHandle)block;\n  }\n  VMA_ASSERT(false && \"If m_AllocCount > 0 then should find any allocation!\");\n  return VK_NULL_HANDLE;\n}\n\nVmaAllocHandle VmaBlockMetadata_TLSF::GetNextAllocation(VmaAllocHandle prevAlloc) const\n{\n  Block* startBlock = (Block*)prevAlloc;\n  VMA_ASSERT(!startBlock->IsFree() && \"Incorrect block!\");\n\n  for (Block* block = startBlock->prevPhysical; block; block = block->prevPhysical)\n  {\n    if (!block->IsFree())\n      return (VmaAllocHandle)block;\n  }\n  return VK_NULL_HANDLE;\n}\n\nVkDeviceSize VmaBlockMetadata_TLSF::GetNextFreeRegionSize(VmaAllocHandle alloc) const\n{\n  Block* block = (Block*)alloc;\n  VMA_ASSERT(!block->IsFree() && \"Incorrect block!\");\n\n  if (block->prevPhysical)\n    return block->prevPhysical->IsFree() ? block->prevPhysical->size : 0;\n  return 0;\n}\n\nvoid VmaBlockMetadata_TLSF::Clear()\n{\n  m_AllocCount = 0;\n  m_BlocksFreeCount = 0;\n  m_BlocksFreeSize = 0;\n  m_IsFreeBitmap = 0;\n  m_NullBlock->offset = 0;\n  m_NullBlock->size = GetSize();\n  Block* block = m_NullBlock->prevPhysical;\n  m_NullBlock->prevPhysical = VMA_NULL;\n  while (block)\n  {\n    Block* prev = block->prevPhysical;\n    m_BlockAllocator.Free(block);\n    block = prev;\n  }\n  memset(m_FreeList, 0, m_ListsCount * sizeof(Block*));\n  memset(m_InnerIsFreeBitmap, 0, m_MemoryClasses * sizeof(uint32_t));\n  m_GranularityHandler.Clear();\n}\n\nvoid VmaBlockMetadata_TLSF::SetAllocationUserData(VmaAllocHandle allocHandle, void* userData)\n{\n  Block* block = (Block*)allocHandle;\n  VMA_ASSERT(!block->IsFree() && \"Trying to set user data for not allocated block!\");\n  block->UserData() = userData;\n}\n\nvoid VmaBlockMetadata_TLSF::DebugLogAllAllocations() const\n{\n  for (Block* block = m_NullBlock->prevPhysical; block != VMA_NULL; block = block->prevPhysical)\n    if (!block->IsFree())\n      DebugLogAllocation(block->offset, block->size, block->UserData());\n}\n\nuint8_t VmaBlockMetadata_TLSF::SizeToMemoryClass(VkDeviceSize size) const\n{\n  if (size > SMALL_BUFFER_SIZE)\n    return VMA_BITSCAN_MSB(size) - MEMORY_CLASS_SHIFT;\n  return 0;\n}\n\nuint16_t VmaBlockMetadata_TLSF::SizeToSecondIndex(VkDeviceSize size, uint8_t memoryClass) const\n{\n  if (memoryClass == 0)\n  {\n    if (IsVirtual())\n      return static_cast<uint16_t>((size - 1) / 8);\n    else\n      return static_cast<uint16_t>((size - 1) / 64);\n  }\n  return static_cast<uint16_t>((size >> (memoryClass + MEMORY_CLASS_SHIFT - SECOND_LEVEL_INDEX)) ^ (1U << SECOND_LEVEL_INDEX));\n}\n\nuint32_t VmaBlockMetadata_TLSF::GetListIndex(uint8_t memoryClass, uint16_t secondIndex) const\n{\n  if (memoryClass == 0)\n    return secondIndex;\n\n  const uint32_t index = static_cast<uint32_t>(memoryClass - 1) * (1 << SECOND_LEVEL_INDEX) + secondIndex;\n  if (IsVirtual())\n    return index + (1 << SECOND_LEVEL_INDEX);\n  else\n    return index + 4;\n}\n\nuint32_t VmaBlockMetadata_TLSF::GetListIndex(VkDeviceSize size) const\n{\n  uint8_t memoryClass = SizeToMemoryClass(size);\n  return GetListIndex(memoryClass, SizeToSecondIndex(size, memoryClass));\n}\n\nvoid VmaBlockMetadata_TLSF::RemoveFreeBlock(Block* block)\n{\n  VMA_ASSERT(block != m_NullBlock);\n  VMA_ASSERT(block->IsFree());\n\n  if (block->NextFree() != VMA_NULL)\n    block->NextFree()->PrevFree() = block->PrevFree();\n  if (block->PrevFree() != VMA_NULL)\n    block->PrevFree()->NextFree() = block->NextFree();\n  else\n  {\n    uint8_t memClass = SizeToMemoryClass(block->size);\n    uint16_t secondIndex = SizeToSecondIndex(block->size, memClass);\n    uint32_t index = GetListIndex(memClass, secondIndex);\n    VMA_ASSERT(m_FreeList[index] == block);\n    m_FreeList[index] = block->NextFree();\n    if (block->NextFree() == VMA_NULL)\n    {\n      m_InnerIsFreeBitmap[memClass] &= ~(1U << secondIndex);\n      if (m_InnerIsFreeBitmap[memClass] == 0)\n        m_IsFreeBitmap &= ~(1UL << memClass);\n    }\n  }\n  block->MarkTaken();\n  block->UserData() = VMA_NULL;\n  --m_BlocksFreeCount;\n  m_BlocksFreeSize -= block->size;\n}\n\nvoid VmaBlockMetadata_TLSF::InsertFreeBlock(Block* block)\n{\n  VMA_ASSERT(block != m_NullBlock);\n  VMA_ASSERT(!block->IsFree() && \"Cannot insert block twice!\");\n\n  uint8_t memClass = SizeToMemoryClass(block->size);\n  uint16_t secondIndex = SizeToSecondIndex(block->size, memClass);\n  uint32_t index = GetListIndex(memClass, secondIndex);\n  VMA_ASSERT(index < m_ListsCount);\n  block->PrevFree() = VMA_NULL;\n  block->NextFree() = m_FreeList[index];\n  m_FreeList[index] = block;\n  if (block->NextFree() != VMA_NULL)\n    block->NextFree()->PrevFree() = block;\n  else\n  {\n    m_InnerIsFreeBitmap[memClass] |= 1U << secondIndex;\n    m_IsFreeBitmap |= 1UL << memClass;\n  }\n  ++m_BlocksFreeCount;\n  m_BlocksFreeSize += block->size;\n}\n\nvoid VmaBlockMetadata_TLSF::MergeBlock(Block* block, Block* prev)\n{\n  VMA_ASSERT(block->prevPhysical == prev && \"Cannot merge separate physical regions!\");\n  VMA_ASSERT(!prev->IsFree() && \"Cannot merge block that belongs to free list!\");\n\n  block->offset = prev->offset;\n  block->size += prev->size;\n  block->prevPhysical = prev->prevPhysical;\n  if (block->prevPhysical)\n    block->prevPhysical->nextPhysical = block;\n  m_BlockAllocator.Free(prev);\n}\n\nVmaBlockMetadata_TLSF::Block* VmaBlockMetadata_TLSF::FindFreeBlock(VkDeviceSize size, uint32_t& listIndex) const\n{\n  uint8_t memoryClass = SizeToMemoryClass(size);\n  uint32_t innerFreeMap = m_InnerIsFreeBitmap[memoryClass] & (~0U << SizeToSecondIndex(size, memoryClass));\n  if (!innerFreeMap)\n  {\n    // Check higher levels for available blocks\n    uint32_t freeMap = m_IsFreeBitmap & (~0UL << (memoryClass + 1));\n    if (!freeMap)\n      return VMA_NULL; // No more memory available\n\n    // Find lowest free region\n    memoryClass = VMA_BITSCAN_LSB(freeMap);\n    innerFreeMap = m_InnerIsFreeBitmap[memoryClass];\n    VMA_ASSERT(innerFreeMap != 0);\n  }\n  // Find lowest free subregion\n  listIndex = GetListIndex(memoryClass, VMA_BITSCAN_LSB(innerFreeMap));\n  VMA_ASSERT(m_FreeList[listIndex]);\n  return m_FreeList[listIndex];\n}\n\nbool VmaBlockMetadata_TLSF::CheckBlock(\n    Block& block,\n    uint32_t listIndex,\n    VkDeviceSize allocSize,\n    VkDeviceSize allocAlignment,\n    VmaSuballocationType allocType,\n    VmaAllocationRequest* pAllocationRequest)\n{\n  VMA_ASSERT(block.IsFree() && \"Block is already taken!\");\n\n  VkDeviceSize alignedOffset = VmaAlignUp(block.offset, allocAlignment);\n  if (block.size < allocSize + alignedOffset - block.offset)\n    return false;\n\n  // Check for granularity conflicts\n  if (!IsVirtual() &&\n      m_GranularityHandler.CheckConflictAndAlignUp(alignedOffset, allocSize, block.offset, block.size, allocType))\n    return false;\n\n  // Alloc successful\n  pAllocationRequest->type = VmaAllocationRequestType::TLSF;\n  pAllocationRequest->allocHandle = (VmaAllocHandle)&block;\n  pAllocationRequest->size = allocSize - GetDebugMargin();\n  pAllocationRequest->customData = (void*)allocType;\n  pAllocationRequest->algorithmData = alignedOffset;\n\n  // Place block at the start of list if it's normal block\n  if (listIndex != m_ListsCount && block.PrevFree())\n  {\n    block.PrevFree()->NextFree() = block.NextFree();\n    if (block.NextFree())\n      block.NextFree()->PrevFree() = block.PrevFree();\n    block.PrevFree() = VMA_NULL;\n    block.NextFree() = m_FreeList[listIndex];\n    m_FreeList[listIndex] = &block;\n    if (block.NextFree())\n      block.NextFree()->PrevFree() = &block;\n  }\n\n  return true;\n}\n#endif // _VMA_BLOCK_METADATA_TLSF_FUNCTIONS\n#endif // _VMA_BLOCK_METADATA_TLSF\n\n#ifndef _VMA_BLOCK_VECTOR\n/*\nSequence of VmaDeviceMemoryBlock. Represents memory blocks allocated for a specific\nVulkan memory type.\n\nSynchronized internally with a mutex.\n*/\nclass VmaBlockVector\n{\n  friend struct VmaDefragmentationContext_T;\n  VMA_CLASS_NO_COPY(VmaBlockVector)\n public:\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      bool explicitBlockSize,\n      uint32_t algorithm,\n      float priority,\n      VkDeviceSize minAllocationAlignment,\n      void* pMemoryAllocateNext);\n  ~VmaBlockVector();\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 GetAlgorithm() const { return m_Algorithm; }\n  bool HasExplicitBlockSize() const { return m_ExplicitBlockSize; }\n  float GetPriority() const { return m_Priority; }\n  const void* GetAllocationNextPtr() const { return m_pMemoryAllocateNext; }\n  // To be used only while the m_Mutex is locked. Used during defragmentation.\n  size_t GetBlockCount() const { return m_Blocks.size(); }\n  // To be used only while the m_Mutex is locked. Used during defragmentation.\n  VmaDeviceMemoryBlock* GetBlock(size_t index) const { return m_Blocks[index]; }\n  VMA_RW_MUTEX &GetMutex() { return m_Mutex; }\n\n  VkResult CreateMinBlocks();\n  void AddStatistics(VmaStatistics& inoutStats);\n  void AddDetailedStatistics(VmaDetailedStatistics& inoutStats);\n  bool IsEmpty();\n  bool IsCorruptionDetectionEnabled() const;\n\n  VkResult Allocate(\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#if VMA_STATS_STRING_ENABLED\n  void PrintDetailedMap(class VmaJsonWriter& json);\n#endif\n\n  VkResult CheckCorruption();\n\n private:\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 bool m_ExplicitBlockSize;\n  const uint32_t m_Algorithm;\n  const float m_Priority;\n  const VkDeviceSize m_MinAllocationAlignment;\n\n  void* const m_pMemoryAllocateNext;\n  VMA_RW_MUTEX m_Mutex;\n  // Incrementally sorted by sumFreeSize, ascending.\n  VmaVector<VmaDeviceMemoryBlock*, VmaStlAllocator<VmaDeviceMemoryBlock*>> m_Blocks;\n  uint32_t m_NextBlockId;\n  bool m_IncrementalSort = true;\n\n  void SetIncrementalSort(bool val) { m_IncrementalSort = val; }\n\n  VkDeviceSize CalcMaxBlockSize() const;\n  // Finds and removes given block from vector.\n  void Remove(VmaDeviceMemoryBlock* pBlock);\n  // Performs single step in sorting m_Blocks. They may not be fully sorted\n  // after this call.\n  void IncrementallySortBlocks();\n  void SortByFreeSize();\n\n  VkResult AllocatePage(\n      VkDeviceSize size,\n      VkDeviceSize alignment,\n      const VmaAllocationCreateInfo& createInfo,\n      VmaSuballocationType suballocType,\n      VmaAllocation* pAllocation);\n\n  VkResult AllocateFromBlock(\n      VmaDeviceMemoryBlock* pBlock,\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 CommitAllocationRequest(\n      VmaAllocationRequest& allocRequest,\n      VmaDeviceMemoryBlock* pBlock,\n      VkDeviceSize alignment,\n      VmaAllocationCreateFlags allocFlags,\n      void* pUserData,\n      VmaSuballocationType suballocType,\n      VmaAllocation* pAllocation);\n\n  VkResult CreateBlock(VkDeviceSize blockSize, size_t* pNewBlockIndex);\n  bool HasEmptyBlock();\n};\n#endif // _VMA_BLOCK_VECTOR\n\n#ifndef _VMA_DEFRAGMENTATION_CONTEXT\nstruct VmaDefragmentationContext_T\n{\n  VMA_CLASS_NO_COPY(VmaDefragmentationContext_T)\n public:\n  VmaDefragmentationContext_T(\n      VmaAllocator hAllocator,\n      const VmaDefragmentationInfo& info);\n  ~VmaDefragmentationContext_T();\n\n  void GetStats(VmaDefragmentationStats& outStats) { outStats = m_GlobalStats; }\n\n  VkResult DefragmentPassBegin(VmaDefragmentationPassMoveInfo& moveInfo);\n  VkResult DefragmentPassEnd(VmaDefragmentationPassMoveInfo& moveInfo);\n\n private:\n  // Max number of allocations to ignore due to size constraints before ending single pass\n  static const uint8_t MAX_ALLOCS_TO_IGNORE = 16;\n  enum class CounterStatus { Pass, Ignore, End };\n\n  struct FragmentedBlock\n  {\n    uint32_t data;\n    VmaDeviceMemoryBlock* block;\n  };\n  struct StateBalanced\n  {\n    VkDeviceSize avgFreeSize = 0;\n    VkDeviceSize avgAllocSize = UINT64_MAX;\n  };\n  struct StateExtensive\n  {\n    enum class Operation : uint8_t\n    {\n      FindFreeBlockBuffer, FindFreeBlockTexture, FindFreeBlockAll,\n      MoveBuffers, MoveTextures, MoveAll,\n      Cleanup, Done\n    };\n\n    Operation operation = Operation::FindFreeBlockTexture;\n    size_t firstFreeBlock = SIZE_MAX;\n  };\n  struct MoveAllocationData\n  {\n    VkDeviceSize size;\n    VkDeviceSize alignment;\n    VmaSuballocationType type;\n    VmaAllocationCreateFlags flags;\n    VmaDefragmentationMove move = {};\n  };\n\n  const VkDeviceSize m_MaxPassBytes;\n  const uint32_t m_MaxPassAllocations;\n\n  VmaStlAllocator<VmaDefragmentationMove> m_MoveAllocator;\n  VmaVector<VmaDefragmentationMove, VmaStlAllocator<VmaDefragmentationMove>> m_Moves;\n\n  uint8_t m_IgnoredAllocs = 0;\n  uint32_t m_Algorithm;\n  uint32_t m_BlockVectorCount;\n  VmaBlockVector* m_PoolBlockVector;\n  VmaBlockVector** m_pBlockVectors;\n  size_t m_ImmovableBlockCount = 0;\n  VmaDefragmentationStats m_GlobalStats = { 0 };\n  VmaDefragmentationStats m_PassStats = { 0 };\n  void* m_AlgorithmState = VMA_NULL;\n\n  static MoveAllocationData GetMoveData(VmaAllocHandle handle, VmaBlockMetadata* metadata);\n  CounterStatus CheckCounters(VkDeviceSize bytes);\n  bool IncrementCounters(VkDeviceSize bytes);\n  bool ReallocWithinBlock(VmaBlockVector& vector, VmaDeviceMemoryBlock* block);\n  bool AllocInOtherBlock(size_t start, size_t end, MoveAllocationData& data, VmaBlockVector& vector);\n\n  bool ComputeDefragmentation(VmaBlockVector& vector, size_t index);\n  bool ComputeDefragmentation_Fast(VmaBlockVector& vector);\n  bool ComputeDefragmentation_Balanced(VmaBlockVector& vector, size_t index, bool update);\n  bool ComputeDefragmentation_Full(VmaBlockVector& vector);\n  bool ComputeDefragmentation_Extensive(VmaBlockVector& vector, size_t index);\n\n  void UpdateVectorStatistics(VmaBlockVector& vector, StateBalanced& state);\n  bool MoveDataToFreeBlocks(VmaSuballocationType currentType,\n                            VmaBlockVector& vector, size_t firstFreeBlock,\n                            bool& texturePresent, bool& bufferPresent, bool& otherPresent);\n};\n#endif // _VMA_DEFRAGMENTATION_CONTEXT\n\n#ifndef _VMA_POOL_T\nstruct VmaPool_T\n{\n  friend struct VmaPoolListItemTraits;\n  VMA_CLASS_NO_COPY(VmaPool_T)\n public:\n  VmaBlockVector m_BlockVector;\n  VmaDedicatedAllocationList m_DedicatedAllocations;\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\n private:\n  uint32_t m_Id;\n  char* m_Name;\n  VmaPool_T* m_PrevPool = VMA_NULL;\n  VmaPool_T* m_NextPool = VMA_NULL;\n};\n\nstruct VmaPoolListItemTraits\n{\n  typedef VmaPool_T ItemType;\n\n  static ItemType* GetPrev(const ItemType* item) { return item->m_PrevPool; }\n  static ItemType* GetNext(const ItemType* item) { return item->m_NextPool; }\n  static ItemType*& AccessPrev(ItemType* item) { return item->m_PrevPool; }\n  static ItemType*& AccessNext(ItemType* item) { return item->m_NextPool; }\n};\n#endif // _VMA_POOL_T\n\n#ifndef _VMA_CURRENT_BUDGET_DATA\nstruct VmaCurrentBudgetData\n{\n  VMA_ATOMIC_UINT32 m_BlockCount[VK_MAX_MEMORY_HEAPS];\n  VMA_ATOMIC_UINT32 m_AllocationCount[VK_MAX_MEMORY_HEAPS];\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 // VMA_MEMORY_BUDGET\n\n  VmaCurrentBudgetData();\n\n  void AddAllocation(uint32_t heapIndex, VkDeviceSize allocationSize);\n  void RemoveAllocation(uint32_t heapIndex, VkDeviceSize allocationSize);\n};\n\n#ifndef _VMA_CURRENT_BUDGET_DATA_FUNCTIONS\nVmaCurrentBudgetData::VmaCurrentBudgetData()\n{\n  for (uint32_t heapIndex = 0; heapIndex < VK_MAX_MEMORY_HEAPS; ++heapIndex)\n  {\n    m_BlockCount[heapIndex] = 0;\n    m_AllocationCount[heapIndex] = 0;\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\nvoid VmaCurrentBudgetData::AddAllocation(uint32_t heapIndex, VkDeviceSize allocationSize)\n{\n  m_AllocationBytes[heapIndex] += allocationSize;\n  ++m_AllocationCount[heapIndex];\n#if VMA_MEMORY_BUDGET\n  ++m_OperationsSinceBudgetFetch;\n#endif\n}\n\nvoid VmaCurrentBudgetData::RemoveAllocation(uint32_t heapIndex, VkDeviceSize allocationSize)\n{\n  VMA_ASSERT(m_AllocationBytes[heapIndex] >= allocationSize);\n  m_AllocationBytes[heapIndex] -= allocationSize;\n  VMA_ASSERT(m_AllocationCount[heapIndex] > 0);\n  --m_AllocationCount[heapIndex];\n#if VMA_MEMORY_BUDGET\n  ++m_OperationsSinceBudgetFetch;\n#endif\n}\n#endif // _VMA_CURRENT_BUDGET_DATA_FUNCTIONS\n#endif // _VMA_CURRENT_BUDGET_DATA\n\n#ifndef _VMA_ALLOCATION_OBJECT_ALLOCATOR\n/*\nThread-safe wrapper over VmaPoolAllocator free list, for allocation of VmaAllocation_T objects.\n*/\nclass VmaAllocationObjectAllocator\n{\n  VMA_CLASS_NO_COPY(VmaAllocationObjectAllocator)\n public:\n  VmaAllocationObjectAllocator(const VkAllocationCallbacks* pAllocationCallbacks)\n      : m_Allocator(pAllocationCallbacks, 1024) {}\n\n  template<typename... Types> VmaAllocation Allocate(Types&&... args);\n  void Free(VmaAllocation hAlloc);\n\n private:\n  VMA_MUTEX m_Mutex;\n  VmaPoolAllocator<VmaAllocation_T> m_Allocator;\n};\n\ntemplate<typename... Types>\nVmaAllocation VmaAllocationObjectAllocator::Allocate(Types&&... args)\n{\n  VmaMutexLock mutexLock(m_Mutex);\n  return m_Allocator.Alloc<Types...>(std::forward<Types>(args)...);\n}\n\nvoid VmaAllocationObjectAllocator::Free(VmaAllocation hAlloc)\n{\n  VmaMutexLock mutexLock(m_Mutex);\n  m_Allocator.Free(hAlloc);\n}\n#endif // _VMA_ALLOCATION_OBJECT_ALLOCATOR\n\n#ifndef _VMA_VIRTUAL_BLOCK_T\nstruct VmaVirtualBlock_T\n{\n  VMA_CLASS_NO_COPY(VmaVirtualBlock_T)\n public:\n  const bool m_AllocationCallbacksSpecified;\n  const VkAllocationCallbacks m_AllocationCallbacks;\n\n  VmaVirtualBlock_T(const VmaVirtualBlockCreateInfo& createInfo);\n  ~VmaVirtualBlock_T();\n\n  VkResult Init() { return VK_SUCCESS; }\n  bool IsEmpty() const { return m_Metadata->IsEmpty(); }\n  void Free(VmaVirtualAllocation allocation) { m_Metadata->Free((VmaAllocHandle)allocation); }\n  void SetAllocationUserData(VmaVirtualAllocation allocation, void* userData) { m_Metadata->SetAllocationUserData((VmaAllocHandle)allocation, userData); }\n  void Clear() { m_Metadata->Clear(); }\n\n  const VkAllocationCallbacks* GetAllocationCallbacks() const;\n  void GetAllocationInfo(VmaVirtualAllocation allocation, VmaVirtualAllocationInfo& outInfo);\n  VkResult Allocate(const VmaVirtualAllocationCreateInfo& createInfo, VmaVirtualAllocation& outAllocation,\n                    VkDeviceSize* outOffset);\n  void GetStatistics(VmaStatistics& outStats) const;\n  void CalculateDetailedStatistics(VmaDetailedStatistics& outStats) const;\n#if VMA_STATS_STRING_ENABLED\n  void BuildStatsString(bool detailedMap, VmaStringBuilder& sb) const;\n#endif\n\n private:\n  VmaBlockMetadata* m_Metadata;\n};\n\n#ifndef _VMA_VIRTUAL_BLOCK_T_FUNCTIONS\nVmaVirtualBlock_T::VmaVirtualBlock_T(const VmaVirtualBlockCreateInfo& createInfo)\n    : m_AllocationCallbacksSpecified(createInfo.pAllocationCallbacks != VMA_NULL),\n      m_AllocationCallbacks(createInfo.pAllocationCallbacks != VMA_NULL ? *createInfo.pAllocationCallbacks : VmaEmptyAllocationCallbacks)\n{\n  const uint32_t algorithm = createInfo.flags & VMA_VIRTUAL_BLOCK_CREATE_ALGORITHM_MASK;\n  switch (algorithm)\n  {\n    case 0:\n      m_Metadata = vma_new(GetAllocationCallbacks(), VmaBlockMetadata_TLSF)(VK_NULL_HANDLE, 1, true);\n      break;\n    case VMA_VIRTUAL_BLOCK_CREATE_LINEAR_ALGORITHM_BIT:\n      m_Metadata = vma_new(GetAllocationCallbacks(), VmaBlockMetadata_Linear)(VK_NULL_HANDLE, 1, true);\n      break;\n    default:\n      VMA_ASSERT(0);\n      m_Metadata = vma_new(GetAllocationCallbacks(), VmaBlockMetadata_TLSF)(VK_NULL_HANDLE, 1, true);\n  }\n\n  m_Metadata->Init(createInfo.size);\n}\n\nVmaVirtualBlock_T::~VmaVirtualBlock_T()\n{\n  // Define macro VMA_DEBUG_LOG_FORMAT to receive the list of the unfreed allocations\n  if (!m_Metadata->IsEmpty())\n    m_Metadata->DebugLogAllAllocations();\n  // This is the most important assert in the entire library.\n  // Hitting it means you have some memory leak - unreleased virtual allocations.\n  VMA_ASSERT(m_Metadata->IsEmpty() && \"Some virtual allocations were not freed before destruction of this virtual block!\");\n\n  vma_delete(GetAllocationCallbacks(), m_Metadata);\n}\n\nconst VkAllocationCallbacks* VmaVirtualBlock_T::GetAllocationCallbacks() const\n{\n  return m_AllocationCallbacksSpecified ? &m_AllocationCallbacks : VMA_NULL;\n}\n\nvoid VmaVirtualBlock_T::GetAllocationInfo(VmaVirtualAllocation allocation, VmaVirtualAllocationInfo& outInfo)\n{\n  m_Metadata->GetAllocationInfo((VmaAllocHandle)allocation, outInfo);\n}\n\nVkResult VmaVirtualBlock_T::Allocate(const VmaVirtualAllocationCreateInfo& createInfo, VmaVirtualAllocation& outAllocation,\n                                     VkDeviceSize* outOffset)\n{\n  VmaAllocationRequest request = {};\n  if (m_Metadata->CreateAllocationRequest(\n          createInfo.size, // allocSize\n          VMA_MAX(createInfo.alignment, (VkDeviceSize)1), // allocAlignment\n          (createInfo.flags & VMA_VIRTUAL_ALLOCATION_CREATE_UPPER_ADDRESS_BIT) != 0, // upperAddress\n          VMA_SUBALLOCATION_TYPE_UNKNOWN, // allocType - unimportant\n          createInfo.flags & VMA_VIRTUAL_ALLOCATION_CREATE_STRATEGY_MASK, // strategy\n          &request))\n  {\n    m_Metadata->Alloc(request,\n                      VMA_SUBALLOCATION_TYPE_UNKNOWN, // type - unimportant\n                      createInfo.pUserData);\n    outAllocation = (VmaVirtualAllocation)request.allocHandle;\n    if(outOffset)\n      *outOffset = m_Metadata->GetAllocationOffset(request.allocHandle);\n    return VK_SUCCESS;\n  }\n  outAllocation = (VmaVirtualAllocation)VK_NULL_HANDLE;\n  if (outOffset)\n    *outOffset = UINT64_MAX;\n  return VK_ERROR_OUT_OF_DEVICE_MEMORY;\n}\n\nvoid VmaVirtualBlock_T::GetStatistics(VmaStatistics& outStats) const\n{\n  VmaClearStatistics(outStats);\n  m_Metadata->AddStatistics(outStats);\n}\n\nvoid VmaVirtualBlock_T::CalculateDetailedStatistics(VmaDetailedStatistics& outStats) const\n{\n  VmaClearDetailedStatistics(outStats);\n  m_Metadata->AddDetailedStatistics(outStats);\n}\n\n#if VMA_STATS_STRING_ENABLED\nvoid VmaVirtualBlock_T::BuildStatsString(bool detailedMap, VmaStringBuilder& sb) const\n{\n  VmaJsonWriter json(GetAllocationCallbacks(), sb);\n  json.BeginObject();\n\n  VmaDetailedStatistics stats;\n  CalculateDetailedStatistics(stats);\n\n  json.WriteString(\"Stats\");\n  VmaPrintDetailedStatistics(json, stats);\n\n  if (detailedMap)\n  {\n    json.WriteString(\"Details\");\n    json.BeginObject();\n    m_Metadata->PrintDetailedMap(json);\n    json.EndObject();\n  }\n\n  json.EndObject();\n}\n#endif // VMA_STATS_STRING_ENABLED\n#endif // _VMA_VIRTUAL_BLOCK_T_FUNCTIONS\n#endif // _VMA_VIRTUAL_BLOCK_T\n\n\n// Main allocator object.\nstruct VmaAllocator_T\n{\n  VMA_CLASS_NO_COPY(VmaAllocator_T)\n public:\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  bool m_UseAmdDeviceCoherentMemory;\n  bool m_UseKhrBufferDeviceAddress;\n  bool m_UseExtMemoryPriority;\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  VmaDedicatedAllocationList m_DedicatedAllocations[VK_MAX_MEMORY_TYPES];\n\n  VmaCurrentBudgetData m_Budget;\n  VMA_ATOMIC_UINT32 m_DeviceMemoryCount; // Total number of VkDeviceMemory objects.\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 : VMA_NULL;\n  }\n  const VmaVulkanFunctions& GetVulkanFunctions() const\n  {\n    return m_VulkanFunctions;\n  }\n\n  VkPhysicalDevice GetPhysicalDevice() const { return m_PhysicalDevice; }\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_MIN_ALIGNMENT, m_PhysicalDeviceProperties.limits.nonCoherentAtomSize) :\n                                                 (VkDeviceSize)VMA_MIN_ALIGNMENT;\n  }\n\n  bool IsIntegratedGpu() const\n  {\n    return m_PhysicalDeviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU;\n  }\n\n  uint32_t GetGlobalMemoryTypeBits() const { return m_GlobalMemoryTypeBits; }\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  VkResult FindMemoryTypeIndex(\n      uint32_t memoryTypeBits,\n      const VmaAllocationCreateInfo* pAllocationCreateInfo,\n      VkFlags bufImgUsage, // VkBufferCreateInfo::usage or VkImageCreateInfo::usage. UINT32_MAX if unknown.\n      uint32_t* pMemoryTypeIndex) 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      VkFlags dedicatedBufferImageUsage, // UINT32_MAX if unknown.\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  void CalculateStatistics(VmaTotalStatistics* pStats);\n\n  void GetHeapBudgets(\n      VmaBudget* outBudgets, uint32_t firstHeap, uint32_t heapCount);\n\n#if VMA_STATS_STRING_ENABLED\n  void PrintDetailedMap(class VmaJsonWriter& json);\n#endif\n\n  void GetAllocationInfo(VmaAllocation hAllocation, VmaAllocationInfo* pAllocationInfo);\n\n  VkResult CreatePool(const VmaPoolCreateInfo* pCreateInfo, VmaPool* pPool);\n  void DestroyPool(VmaPool pool);\n  void GetPoolStatistics(VmaPool pool, VmaStatistics* pPoolStats);\n  void CalculatePoolStatistics(VmaPool pool, VmaDetailedStatistics* pPoolStats);\n\n  void SetCurrentFrameIndex(uint32_t frameIndex);\n  uint32_t GetCurrentFrameIndex() const { return m_CurrentFrameIndex.load(); }\n\n  VkResult CheckPoolCorruption(VmaPool hPool);\n  VkResult CheckCorruption(uint32_t memoryTypeBits);\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  VkResult FlushOrInvalidateAllocation(\n      VmaAllocation hAllocation,\n      VkDeviceSize offset, VkDeviceSize size,\n      VMA_CACHE_OPERATION op);\n  VkResult FlushOrInvalidateAllocations(\n      uint32_t allocationCount,\n      const VmaAllocation* allocations,\n      const VkDeviceSize* offsets, const VkDeviceSize* sizes,\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\n#if VMA_EXTERNAL_MEMORY\n  VkExternalMemoryHandleTypeFlagsKHR GetExternalMemoryHandleTypeFlags(uint32_t memTypeIndex) const\n  {\n    return m_TypeExternalMemoryHandleTypes[memTypeIndex];\n  }\n#endif // #if VMA_EXTERNAL_MEMORY\n\n private:\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#if VMA_EXTERNAL_MEMORY\n  VkExternalMemoryHandleTypeFlagsKHR m_TypeExternalMemoryHandleTypes[VK_MAX_MEMORY_TYPES];\n#endif // #if VMA_EXTERNAL_MEMORY\n\n  VMA_RW_MUTEX m_PoolsMutex;\n  typedef VmaIntrusiveLinkedList<VmaPoolListItemTraits> PoolList;\n  // Protected by m_PoolsMutex.\n  PoolList m_Pools;\n  uint32_t m_NextPoolId;\n\n  VmaVulkanFunctions m_VulkanFunctions;\n\n  // Global bit mask AND-ed with any memoryTypeBits to disallow certain memory types.\n  uint32_t m_GlobalMemoryTypeBits;\n\n  void ImportVulkanFunctions(const VmaVulkanFunctions* pVulkanFunctions);\n\n#if VMA_STATIC_VULKAN_FUNCTIONS == 1\n  void ImportVulkanFunctions_Static();\n#endif\n\n  void ImportVulkanFunctions_Custom(const VmaVulkanFunctions* pVulkanFunctions);\n\n#if VMA_DYNAMIC_VULKAN_FUNCTIONS == 1\n  void ImportVulkanFunctions_Dynamic();\n#endif\n\n  void ValidateVulkanFunctions();\n\n  VkDeviceSize CalcPreferredBlockSize(uint32_t memTypeIndex);\n\n  VkResult AllocateMemoryOfType(\n      VmaPool pool,\n      VkDeviceSize size,\n      VkDeviceSize alignment,\n      bool dedicatedPreferred,\n      VkBuffer dedicatedBuffer,\n      VkImage dedicatedImage,\n      VkFlags dedicatedBufferImageUsage,\n      const VmaAllocationCreateInfo& createInfo,\n      uint32_t memTypeIndex,\n      VmaSuballocationType suballocType,\n      VmaDedicatedAllocationList& dedicatedAllocations,\n      VmaBlockVector& blockVector,\n      size_t allocationCount,\n      VmaAllocation* pAllocations);\n\n  // Helper function only to be used inside AllocateDedicatedMemory.\n  VkResult AllocateDedicatedMemoryPage(\n      VmaPool pool,\n      VkDeviceSize size,\n      VmaSuballocationType suballocType,\n      uint32_t memTypeIndex,\n      const VkMemoryAllocateInfo& allocInfo,\n      bool map,\n      bool isUserDataString,\n      bool isMappingAllowed,\n      void* pUserData,\n      VmaAllocation* pAllocation);\n\n  // Allocates and registers new VkDeviceMemory specifically for dedicated allocations.\n  VkResult AllocateDedicatedMemory(\n      VmaPool pool,\n      VkDeviceSize size,\n      VmaSuballocationType suballocType,\n      VmaDedicatedAllocationList& dedicatedAllocations,\n      uint32_t memTypeIndex,\n      bool map,\n      bool isUserDataString,\n      bool isMappingAllowed,\n      bool canAliasMemory,\n      void* pUserData,\n      float priority,\n      VkBuffer dedicatedBuffer,\n      VkImage dedicatedImage,\n      VkFlags dedicatedBufferImageUsage,\n      size_t allocationCount,\n      VmaAllocation* pAllocations,\n      const void* pNextChain = nullptr);\n\n  void FreeDedicatedMemory(const VmaAllocation allocation);\n\n  VkResult CalcMemTypeParams(\n      VmaAllocationCreateInfo& outCreateInfo,\n      uint32_t memTypeIndex,\n      VkDeviceSize size,\n      size_t allocationCount);\n  VkResult CalcAllocationParams(\n      VmaAllocationCreateInfo& outCreateInfo,\n      bool dedicatedRequired,\n      bool dedicatedPreferred);\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  uint32_t CalculateGlobalMemoryTypeBits() const;\n\n  bool GetFlushOrInvalidateRange(\n      VmaAllocation allocation,\n      VkDeviceSize offset, VkDeviceSize size,\n      VkMappedMemoryRange& outRange) const;\n\n#if VMA_MEMORY_BUDGET\n  void UpdateVulkanBudget();\n#endif // #if VMA_MEMORY_BUDGET\n};\n\n\n#ifndef _VMA_MEMORY_FUNCTIONS\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  {\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  {\n    for(size_t i = count; i--; )\n      ptr[i].~T();\n    VmaFree(hAllocator, ptr);\n  }\n}\n#endif // _VMA_MEMORY_FUNCTIONS\n\n#ifndef _VMA_DEVICE_MEMORY_BLOCK_FUNCTIONS\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\nVmaDeviceMemoryBlock::~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\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    VkDeviceSize bufferImageGranularity)\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  {\n    case 0:\n      m_pMetadata = vma_new(hAllocator, VmaBlockMetadata_TLSF)(hAllocator->GetAllocationCallbacks(),\n                                                               bufferImageGranularity, false); // isVirtual\n      break;\n    case VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT:\n      m_pMetadata = vma_new(hAllocator, VmaBlockMetadata_Linear)(hAllocator->GetAllocationCallbacks(),\n                                                                 bufferImageGranularity, false); // isVirtual\n      break;\n    default:\n      VMA_ASSERT(0);\n      m_pMetadata = vma_new(hAllocator, VmaBlockMetadata_TLSF)(hAllocator->GetAllocationCallbacks(),\n                                                               bufferImageGranularity, false); // isVirtual\n  }\n  m_pMetadata->Init(newSize);\n}\n\nvoid VmaDeviceMemoryBlock::Destroy(VmaAllocator allocator)\n{\n  // Define macro VMA_DEBUG_LOG_FORMAT to receive the list of the unfreed allocations\n  if (!m_pMetadata->IsEmpty())\n    m_pMetadata->DebugLogAllAllocations();\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\nvoid VmaDeviceMemoryBlock::PostAlloc(VmaAllocator hAllocator)\n{\n  VmaMutexLock lock(m_MapAndBindMutex, hAllocator->m_UseMutex);\n  m_MappingHysteresis.PostAlloc();\n}\n\nvoid VmaDeviceMemoryBlock::PostFree(VmaAllocator hAllocator)\n{\n  VmaMutexLock lock(m_MapAndBindMutex, hAllocator->m_UseMutex);\n  if(m_MappingHysteresis.PostFree())\n  {\n    VMA_ASSERT(m_MappingHysteresis.GetExtraMapping() == 0);\n    if (m_MapCount == 0)\n    {\n      m_pMappedData = VMA_NULL;\n      (*hAllocator->GetVulkanFunctions().vkUnmapMemory)(hAllocator->m_hDevice, m_hMemory);\n    }\n  }\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  {\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  {\n    return VK_SUCCESS;\n  }\n\n  VmaMutexLock lock(m_MapAndBindMutex, hAllocator->m_UseMutex);\n  const uint32_t oldTotalMapCount = m_MapCount + m_MappingHysteresis.GetExtraMapping();\n  m_MappingHysteresis.PostMap();\n  if (oldTotalMapCount != 0)\n  {\n    m_MapCount += count;\n    VMA_ASSERT(m_pMappedData != VMA_NULL);\n    if (ppData != VMA_NULL)\n    {\n      *ppData = m_pMappedData;\n    }\n    return VK_SUCCESS;\n  }\n  else\n  {\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    {\n      if (ppData != VMA_NULL)\n      {\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  {\n    return;\n  }\n\n  VmaMutexLock lock(m_MapAndBindMutex, hAllocator->m_UseMutex);\n  if (m_MapCount >= count)\n  {\n    m_MapCount -= count;\n    const uint32_t totalMapCount = m_MapCount + m_MappingHysteresis.GetExtraMapping();\n    if (totalMapCount == 0)\n    {\n      m_pMappedData = VMA_NULL;\n      (*hAllocator->GetVulkanFunctions().vkUnmapMemory)(hAllocator->m_hDevice, m_hMemory);\n    }\n    m_MappingHysteresis.PostUnmap();\n  }\n  else\n  {\n    VMA_ASSERT(0 && \"VkDeviceMemory block is being unmapped while it was not previously mapped.\");\n  }\n}\n\nVkResult VmaDeviceMemoryBlock::WriteMagicValueAfterAllocation(VmaAllocator hAllocator, VkDeviceSize allocOffset, VkDeviceSize allocSize)\n{\n  VMA_ASSERT(VMA_DEBUG_MARGIN > 0 && VMA_DEBUG_MARGIN % 4 == 0 && VMA_DEBUG_DETECT_CORRUPTION);\n\n  void* pData;\n  VkResult res = Map(hAllocator, 1, &pData);\n  if (res != VK_SUCCESS)\n  {\n    return res;\n  }\n\n  VmaWriteMagicValue(pData, allocOffset + allocSize);\n\n  Unmap(hAllocator, 1);\n  return VK_SUCCESS;\n}\n\nVkResult VmaDeviceMemoryBlock::ValidateMagicValueAfterAllocation(VmaAllocator hAllocator, VkDeviceSize allocOffset, VkDeviceSize allocSize)\n{\n  VMA_ASSERT(VMA_DEBUG_MARGIN > 0 && VMA_DEBUG_MARGIN % 4 == 0 && VMA_DEBUG_DETECT_CORRUPTION);\n\n  void* pData;\n  VkResult res = Map(hAllocator, 1, &pData);\n  if (res != VK_SUCCESS)\n  {\n    return res;\n  }\n\n  if (!VmaValidateMagicValue(pData, allocOffset + allocSize))\n  {\n    VMA_ASSERT(0 && \"MEMORY CORRUPTION DETECTED AFTER FREED ALLOCATION!\");\n  }\n\n  Unmap(hAllocator, 1);\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_MapAndBindMutex, 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_MapAndBindMutex, hAllocator->m_UseMutex);\n  return hAllocator->BindVulkanImage(m_hMemory, memoryOffset, hImage, pNext);\n}\n#endif // _VMA_DEVICE_MEMORY_BLOCK_FUNCTIONS\n\n#ifndef _VMA_ALLOCATION_T_FUNCTIONS\nVmaAllocation_T::VmaAllocation_T(bool mappingAllowed)\n    : m_Alignment{ 1 },\n      m_Size{ 0 },\n      m_pUserData{ VMA_NULL },\n      m_pName{ VMA_NULL },\n      m_MemoryTypeIndex{ 0 },\n      m_Type{ (uint8_t)ALLOCATION_TYPE_NONE },\n      m_SuballocationType{ (uint8_t)VMA_SUBALLOCATION_TYPE_UNKNOWN },\n      m_MapCount{ 0 },\n      m_Flags{ 0 }\n{\n  if(mappingAllowed)\n    m_Flags |= (uint8_t)FLAG_MAPPING_ALLOWED;\n\n#if VMA_STATS_STRING_ENABLED\n  m_BufferImageUsage = 0;\n#endif\n}\n\nVmaAllocation_T::~VmaAllocation_T()\n{\n  VMA_ASSERT(m_MapCount == 0 && \"Allocation was not unmapped before destruction.\");\n\n  // Check if owned string was freed.\n  VMA_ASSERT(m_pName == VMA_NULL);\n}\n\nvoid VmaAllocation_T::InitBlockAllocation(\n    VmaDeviceMemoryBlock* block,\n    VmaAllocHandle allocHandle,\n    VkDeviceSize alignment,\n    VkDeviceSize size,\n    uint32_t memoryTypeIndex,\n    VmaSuballocationType suballocationType,\n    bool mapped)\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  if(mapped)\n  {\n    VMA_ASSERT(IsMappingAllowed() && \"Mapping is not allowed on this allocation! Please use one of the new VMA_ALLOCATION_CREATE_HOST_ACCESS_* flags when creating it.\");\n    m_Flags |= (uint8_t)FLAG_PERSISTENT_MAP;\n  }\n  m_SuballocationType = (uint8_t)suballocationType;\n  m_BlockAllocation.m_Block = block;\n  m_BlockAllocation.m_AllocHandle = allocHandle;\n}\n\nvoid VmaAllocation_T::InitDedicatedAllocation(\n    VmaPool hParentPool,\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  if(pMappedData != VMA_NULL)\n  {\n    VMA_ASSERT(IsMappingAllowed() && \"Mapping is not allowed on this allocation! Please use one of the new VMA_ALLOCATION_CREATE_HOST_ACCESS_* flags when creating it.\");\n    m_Flags |= (uint8_t)FLAG_PERSISTENT_MAP;\n  }\n  m_DedicatedAllocation.m_hParentPool = hParentPool;\n  m_DedicatedAllocation.m_hMemory = hMemory;\n  m_DedicatedAllocation.m_pMappedData = pMappedData;\n  m_DedicatedAllocation.m_Prev = VMA_NULL;\n  m_DedicatedAllocation.m_Next = VMA_NULL;\n}\n\nvoid VmaAllocation_T::SetName(VmaAllocator hAllocator, const char* pName)\n{\n  VMA_ASSERT(pName == VMA_NULL || pName != m_pName);\n\n  FreeName(hAllocator);\n\n  if (pName != VMA_NULL)\n    m_pName = VmaCreateStringCopy(hAllocator->GetAllocationCallbacks(), pName);\n}\n\nuint8_t VmaAllocation_T::SwapBlockAllocation(VmaAllocator hAllocator, VmaAllocation allocation)\n{\n  VMA_ASSERT(allocation != VMA_NULL);\n  VMA_ASSERT(m_Type == ALLOCATION_TYPE_BLOCK);\n  VMA_ASSERT(allocation->m_Type == ALLOCATION_TYPE_BLOCK);\n\n  if (m_MapCount != 0)\n    m_BlockAllocation.m_Block->Unmap(hAllocator, m_MapCount);\n\n  m_BlockAllocation.m_Block->m_pMetadata->SetAllocationUserData(m_BlockAllocation.m_AllocHandle, allocation);\n  VMA_SWAP(m_BlockAllocation, allocation->m_BlockAllocation);\n  m_BlockAllocation.m_Block->m_pMetadata->SetAllocationUserData(m_BlockAllocation.m_AllocHandle, this);\n\n#if VMA_STATS_STRING_ENABLED\n  VMA_SWAP(m_BufferImageUsage, allocation->m_BufferImageUsage);\n#endif\n  return m_MapCount;\n}\n\nVmaAllocHandle VmaAllocation_T::GetAllocHandle() const\n{\n  switch (m_Type)\n  {\n    case ALLOCATION_TYPE_BLOCK:\n      return m_BlockAllocation.m_AllocHandle;\n    case ALLOCATION_TYPE_DEDICATED:\n      return VK_NULL_HANDLE;\n    default:\n      VMA_ASSERT(0);\n      return VK_NULL_HANDLE;\n  }\n}\n\nVkDeviceSize VmaAllocation_T::GetOffset() const\n{\n  switch (m_Type)\n  {\n    case ALLOCATION_TYPE_BLOCK:\n      return m_BlockAllocation.m_Block->m_pMetadata->GetAllocationOffset(m_BlockAllocation.m_AllocHandle);\n    case ALLOCATION_TYPE_DEDICATED:\n      return 0;\n    default:\n      VMA_ASSERT(0);\n      return 0;\n  }\n}\n\nVmaPool VmaAllocation_T::GetParentPool() const\n{\n  switch (m_Type)\n  {\n    case ALLOCATION_TYPE_BLOCK:\n      return m_BlockAllocation.m_Block->GetParentPool();\n    case ALLOCATION_TYPE_DEDICATED:\n      return m_DedicatedAllocation.m_hParentPool;\n    default:\n      VMA_ASSERT(0);\n      return VK_NULL_HANDLE;\n  }\n}\n\nVkDeviceMemory VmaAllocation_T::GetMemory() const\n{\n  switch (m_Type)\n  {\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  {\n    case ALLOCATION_TYPE_BLOCK:\n      if (m_MapCount != 0 || IsPersistentMap())\n      {\n        void* pBlockData = m_BlockAllocation.m_Block->GetMappedData();\n        VMA_ASSERT(pBlockData != VMA_NULL);\n        return (char*)pBlockData + GetOffset();\n      }\n      else\n      {\n        return VMA_NULL;\n      }\n      break;\n    case ALLOCATION_TYPE_DEDICATED:\n      VMA_ASSERT((m_DedicatedAllocation.m_pMappedData != VMA_NULL) == (m_MapCount != 0 || IsPersistentMap()));\n      return m_DedicatedAllocation.m_pMappedData;\n    default:\n      VMA_ASSERT(0);\n      return VMA_NULL;\n  }\n}\n\nvoid VmaAllocation_T::BlockAllocMap()\n{\n  VMA_ASSERT(GetType() == ALLOCATION_TYPE_BLOCK);\n  VMA_ASSERT(IsMappingAllowed() && \"Mapping is not allowed on this allocation! Please use one of the new VMA_ALLOCATION_CREATE_HOST_ACCESS_* flags when creating it.\");\n\n  if (m_MapCount < 0xFF)\n  {\n    ++m_MapCount;\n  }\n  else\n  {\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 > 0)\n  {\n    --m_MapCount;\n  }\n  else\n  {\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  VMA_ASSERT(IsMappingAllowed() && \"Mapping is not allowed on this allocation! Please use one of the new VMA_ALLOCATION_CREATE_HOST_ACCESS_* flags when creating it.\");\n\n  if (m_MapCount != 0 || IsPersistentMap())\n  {\n    if (m_MapCount < 0xFF)\n    {\n      VMA_ASSERT(m_DedicatedAllocation.m_pMappedData != VMA_NULL);\n      *ppData = m_DedicatedAllocation.m_pMappedData;\n      ++m_MapCount;\n      return VK_SUCCESS;\n    }\n    else\n    {\n      VMA_ASSERT(0 && \"Dedicated allocation mapped too many times simultaneously.\");\n      return VK_ERROR_MEMORY_MAP_FAILED;\n    }\n  }\n  else\n  {\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    {\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 > 0)\n  {\n    --m_MapCount;\n    if (m_MapCount == 0 && !IsPersistentMap())\n    {\n      m_DedicatedAllocation.m_pMappedData = VMA_NULL;\n      (*hAllocator->GetVulkanFunctions().vkUnmapMemory)(\n          hAllocator->m_hDevice,\n          m_DedicatedAllocation.m_hMemory);\n    }\n  }\n  else\n  {\n    VMA_ASSERT(0 && \"Unmapping dedicated allocation not previously mapped.\");\n  }\n}\n\n#if VMA_STATS_STRING_ENABLED\nvoid VmaAllocation_T::InitBufferImageUsage(uint32_t bufferImageUsage)\n{\n  VMA_ASSERT(m_BufferImageUsage == 0);\n  m_BufferImageUsage = bufferImageUsage;\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  json.WriteString(\"Usage\");\n  json.WriteNumber(m_BufferImageUsage);\n\n  if (m_pUserData != VMA_NULL)\n  {\n    json.WriteString(\"CustomData\");\n    json.BeginString();\n    json.ContinueString_Pointer(m_pUserData);\n    json.EndString();\n  }\n  if (m_pName != VMA_NULL)\n  {\n    json.WriteString(\"Name\");\n    json.WriteString(m_pName);\n  }\n}\n#endif // VMA_STATS_STRING_ENABLED\n\nvoid VmaAllocation_T::FreeName(VmaAllocator hAllocator)\n{\n  if(m_pName)\n  {\n    VmaFreeString(hAllocator->GetAllocationCallbacks(), m_pName);\n    m_pName = VMA_NULL;\n  }\n}\n#endif // _VMA_ALLOCATION_T_FUNCTIONS\n\n#ifndef _VMA_BLOCK_VECTOR_FUNCTIONS\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    bool explicitBlockSize,\n    uint32_t algorithm,\n    float priority,\n    VkDeviceSize minAllocationAlignment,\n    void* pMemoryAllocateNext)\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_ExplicitBlockSize(explicitBlockSize),\n      m_Algorithm(algorithm),\n      m_Priority(priority),\n      m_MinAllocationAlignment(minAllocationAlignment),\n      m_pMemoryAllocateNext(pMemoryAllocateNext),\n      m_Blocks(VmaStlAllocator<VmaDeviceMemoryBlock*>(hAllocator->GetAllocationCallbacks())),\n      m_NextBlockId(0) {}\n\nVmaBlockVector::~VmaBlockVector()\n{\n  for (size_t i = m_Blocks.size(); i--; )\n  {\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  {\n    VkResult res = CreateBlock(m_PreferredBlockSize, VMA_NULL);\n    if (res != VK_SUCCESS)\n    {\n      return res;\n    }\n  }\n  return VK_SUCCESS;\n}\n\nvoid VmaBlockVector::AddStatistics(VmaStatistics& inoutStats)\n{\n  VmaMutexLockRead lock(m_Mutex, m_hAllocator->m_UseMutex);\n\n  const size_t blockCount = m_Blocks.size();\n  for (uint32_t blockIndex = 0; blockIndex < blockCount; ++blockIndex)\n  {\n    const VmaDeviceMemoryBlock* const pBlock = m_Blocks[blockIndex];\n    VMA_ASSERT(pBlock);\n    VMA_HEAVY_ASSERT(pBlock->Validate());\n    pBlock->m_pMetadata->AddStatistics(inoutStats);\n  }\n}\n\nvoid VmaBlockVector::AddDetailedStatistics(VmaDetailedStatistics& inoutStats)\n{\n  VmaMutexLockRead lock(m_Mutex, m_hAllocator->m_UseMutex);\n\n  const size_t blockCount = m_Blocks.size();\n  for (uint32_t blockIndex = 0; blockIndex < blockCount; ++blockIndex)\n  {\n    const VmaDeviceMemoryBlock* const pBlock = m_Blocks[blockIndex];\n    VMA_ASSERT(pBlock);\n    VMA_HEAVY_ASSERT(pBlock->Validate());\n    pBlock->m_pMetadata->AddDetailedStatistics(inoutStats);\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\nVkResult VmaBlockVector::Allocate(\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  alignment = VMA_MAX(alignment, m_MinAllocationAlignment);\n\n  if (IsCorruptionDetectionEnabled())\n  {\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    {\n      res = AllocatePage(\n          size,\n          alignment,\n          createInfo,\n          suballocType,\n          pAllocations + allocIndex);\n      if (res != VK_SUCCESS)\n      {\n        break;\n      }\n    }\n  }\n\n  if (res != VK_SUCCESS)\n  {\n    // Free all already created allocations.\n    while (allocIndex--)\n      Free(pAllocations[allocIndex]);\n    memset(pAllocations, 0, sizeof(VmaAllocation) * allocationCount);\n  }\n\n  return res;\n}\n\nVkResult VmaBlockVector::AllocatePage(\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\n  VkDeviceSize freeMemory;\n  {\n    const uint32_t heapIndex = m_hAllocator->MemoryTypeIndexToHeapIndex(m_MemoryTypeIndex);\n    VmaBudget heapBudget = {};\n    m_hAllocator->GetHeapBudgets(&heapBudget, heapIndex, 1);\n    freeMemory = (heapBudget.usage < heapBudget.budget) ? (heapBudget.budget - heapBudget.usage) : 0;\n  }\n\n  const bool canFallbackToDedicated = !HasExplicitBlockSize() &&\n                                      (createInfo.flags & VMA_ALLOCATION_CREATE_NEVER_ALLOCATE_BIT) == 0;\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  // 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  {\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 + VMA_DEBUG_MARGIN > m_PreferredBlockSize)\n  {\n    return VK_ERROR_OUT_OF_DEVICE_MEMORY;\n  }\n\n  // 1. Search existing allocations. Try to allocate.\n  if (m_Algorithm == VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT)\n  {\n    // Use only last block.\n    if (!m_Blocks.empty())\n    {\n      VmaDeviceMemoryBlock* const pCurrBlock = m_Blocks.back();\n      VMA_ASSERT(pCurrBlock);\n      VkResult res = AllocateFromBlock(\n          pCurrBlock, size, alignment, createInfo.flags, createInfo.pUserData, suballocType, strategy, pAllocation);\n      if (res == VK_SUCCESS)\n      {\n        VMA_DEBUG_LOG_FORMAT(\"    Returned from last block #%u\", pCurrBlock->GetId());\n        IncrementallySortBlocks();\n        return VK_SUCCESS;\n      }\n    }\n  }\n  else\n  {\n    if (strategy != VMA_ALLOCATION_CREATE_STRATEGY_MIN_TIME_BIT) // MIN_MEMORY or default\n    {\n      const bool isHostVisible =\n          (m_hAllocator->m_MemProps.memoryTypes[m_MemoryTypeIndex].propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) != 0;\n      if(isHostVisible)\n      {\n        const bool isMappingAllowed = (createInfo.flags &\n                                       (VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT)) != 0;\n        /*\n        For non-mappable allocations, check blocks that are not mapped first.\n        For mappable allocations, check blocks that are already mapped first.\n        This way, having many blocks, we will separate mappable and non-mappable allocations,\n        hopefully limiting the number of blocks that are mapped, which will help tools like RenderDoc.\n        */\n        for(size_t mappingI = 0; mappingI < 2; ++mappingI)\n        {\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          {\n            VmaDeviceMemoryBlock* const pCurrBlock = m_Blocks[blockIndex];\n            VMA_ASSERT(pCurrBlock);\n            const bool isBlockMapped = pCurrBlock->GetMappedData() != VMA_NULL;\n            if((mappingI == 0) == (isMappingAllowed == isBlockMapped))\n            {\n              VkResult res = AllocateFromBlock(\n                  pCurrBlock, size, alignment, createInfo.flags, createInfo.pUserData, suballocType, strategy, pAllocation);\n              if (res == VK_SUCCESS)\n              {\n                VMA_DEBUG_LOG_FORMAT(\"    Returned from existing block #%u\", pCurrBlock->GetId());\n                IncrementallySortBlocks();\n                return VK_SUCCESS;\n              }\n            }\n          }\n        }\n      }\n      else\n      {\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        {\n          VmaDeviceMemoryBlock* const pCurrBlock = m_Blocks[blockIndex];\n          VMA_ASSERT(pCurrBlock);\n          VkResult res = AllocateFromBlock(\n              pCurrBlock, size, alignment, createInfo.flags, createInfo.pUserData, suballocType, strategy, pAllocation);\n          if (res == VK_SUCCESS)\n          {\n            VMA_DEBUG_LOG_FORMAT(\"    Returned from existing block #%u\", pCurrBlock->GetId());\n            IncrementallySortBlocks();\n            return VK_SUCCESS;\n          }\n        }\n      }\n    }\n    else // VMA_ALLOCATION_CREATE_STRATEGY_MIN_TIME_BIT\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      {\n        VmaDeviceMemoryBlock* const pCurrBlock = m_Blocks[blockIndex];\n        VMA_ASSERT(pCurrBlock);\n        VkResult res = AllocateFromBlock(pCurrBlock, size, alignment, createInfo.flags, createInfo.pUserData, suballocType, strategy, pAllocation);\n        if (res == VK_SUCCESS)\n        {\n          VMA_DEBUG_LOG_FORMAT(\"    Returned from existing block #%u\", pCurrBlock->GetId());\n          IncrementallySortBlocks();\n          return VK_SUCCESS;\n        }\n      }\n    }\n  }\n\n  // 2. Try to create new block.\n  if (canCreateNewBlock)\n  {\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    {\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      {\n        const VkDeviceSize smallerNewBlockSize = newBlockSize / 2;\n        if (smallerNewBlockSize > maxExistingBlockSize && smallerNewBlockSize >= size * 2)\n        {\n          newBlockSize = smallerNewBlockSize;\n          ++newBlockSizeShift;\n        }\n        else\n        {\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    {\n      while (res < 0 && newBlockSizeShift < NEW_BLOCK_SIZE_SHIFT_MAX)\n      {\n        const VkDeviceSize smallerNewBlockSize = newBlockSize / 2;\n        if (smallerNewBlockSize >= size)\n        {\n          newBlockSize = smallerNewBlockSize;\n          ++newBlockSizeShift;\n          res = (newBlockSize <= freeMemory || !canFallbackToDedicated) ?\n                                                                        CreateBlock(newBlockSize, &newBlockIndex) : VK_ERROR_OUT_OF_DEVICE_MEMORY;\n        }\n        else\n        {\n          break;\n        }\n      }\n    }\n\n    if (res == VK_SUCCESS)\n    {\n      VmaDeviceMemoryBlock* const pBlock = m_Blocks[newBlockIndex];\n      VMA_ASSERT(pBlock->m_pMetadata->GetSize() >= size);\n\n      res = AllocateFromBlock(\n          pBlock, size, alignment, createInfo.flags, createInfo.pUserData, suballocType, strategy, pAllocation);\n      if (res == VK_SUCCESS)\n      {\n        VMA_DEBUG_LOG_FORMAT(\"    Created new block #%u Size=%llu\", pBlock->GetId(), newBlockSize);\n        IncrementallySortBlocks();\n        return VK_SUCCESS;\n      }\n      else\n      {\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  return VK_ERROR_OUT_OF_DEVICE_MEMORY;\n}\n\nvoid VmaBlockVector::Free(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->GetHeapBudgets(&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    {\n      VkResult res = pBlock->ValidateMagicValueAfterAllocation(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    {\n      pBlock->Unmap(m_hAllocator, 1);\n    }\n\n    const bool hadEmptyBlockBeforeFree = HasEmptyBlock();\n    pBlock->m_pMetadata->Free(hAllocation->GetAllocHandle());\n    pBlock->PostFree(m_hAllocator);\n    VMA_HEAVY_ASSERT(pBlock->Validate());\n\n    VMA_DEBUG_LOG_FORMAT(\"  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    {\n      // Already had empty block. We don't want to have two, so delete this one.\n      if ((hadEmptyBlockBeforeFree || budgetExceeded) && canDeleteBlock)\n      {\n        pBlockToDelete = pBlock;\n        Remove(pBlock);\n      }\n      // else: We now have one empty block - leave it. A hysteresis to avoid allocating whole block back and forth.\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 (hadEmptyBlockBeforeFree && canDeleteBlock)\n    {\n      VmaDeviceMemoryBlock* pLastBlock = m_Blocks.back();\n      if (pLastBlock->m_pMetadata->IsEmpty())\n      {\n        pBlockToDelete = pLastBlock;\n        m_Blocks.pop_back();\n      }\n    }\n\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  {\n    VMA_DEBUG_LOG_FORMAT(\"    Deleted empty block #%u\", pBlockToDelete->GetId());\n    pBlockToDelete->Destroy(m_hAllocator);\n    vma_delete(m_hAllocator, pBlockToDelete);\n  }\n\n  m_hAllocator->m_Budget.RemoveAllocation(m_hAllocator->MemoryTypeIndexToHeapIndex(m_MemoryTypeIndex), hAllocation->GetSize());\n  m_hAllocator->m_AllocationObjectAllocator.Free(hAllocation);\n}\n\nVkDeviceSize VmaBlockVector::CalcMaxBlockSize() const\n{\n  VkDeviceSize result = 0;\n  for (size_t i = m_Blocks.size(); i--; )\n  {\n    result = VMA_MAX(result, m_Blocks[i]->m_pMetadata->GetSize());\n    if (result >= m_PreferredBlockSize)\n    {\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  {\n    if (m_Blocks[blockIndex] == pBlock)\n    {\n      VmaVectorRemove(m_Blocks, blockIndex);\n      return;\n    }\n  }\n  VMA_ASSERT(0);\n}\n\nvoid VmaBlockVector::IncrementallySortBlocks()\n{\n  if (!m_IncrementalSort)\n    return;\n  if (m_Algorithm != VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT)\n  {\n    // Bubble sort only until first swap.\n    for (size_t i = 1; i < m_Blocks.size(); ++i)\n    {\n      if (m_Blocks[i - 1]->m_pMetadata->GetSumFreeSize() > m_Blocks[i]->m_pMetadata->GetSumFreeSize())\n      {\n        VMA_SWAP(m_Blocks[i - 1], m_Blocks[i]);\n        return;\n      }\n    }\n  }\n}\n\nvoid VmaBlockVector::SortByFreeSize()\n{\n  VMA_SORT(m_Blocks.begin(), m_Blocks.end(),\n           [](VmaDeviceMemoryBlock* b1, VmaDeviceMemoryBlock* b2) -> bool\n           {\n             return b1->m_pMetadata->GetSumFreeSize() < b2->m_pMetadata->GetSumFreeSize();\n           });\n}\n\nVkResult VmaBlockVector::AllocateFromBlock(\n    VmaDeviceMemoryBlock* pBlock,\n    VkDeviceSize size,\n    VkDeviceSize alignment,\n    VmaAllocationCreateFlags allocFlags,\n    void* pUserData,\n    VmaSuballocationType suballocType,\n    uint32_t strategy,\n    VmaAllocation* pAllocation)\n{\n  const bool isUpperAddress = (allocFlags & VMA_ALLOCATION_CREATE_UPPER_ADDRESS_BIT) != 0;\n\n  VmaAllocationRequest currRequest = {};\n  if (pBlock->m_pMetadata->CreateAllocationRequest(\n          size,\n          alignment,\n          isUpperAddress,\n          suballocType,\n          strategy,\n          &currRequest))\n  {\n    return CommitAllocationRequest(currRequest, pBlock, alignment, allocFlags, pUserData, suballocType, pAllocation);\n  }\n  return VK_ERROR_OUT_OF_DEVICE_MEMORY;\n}\n\nVkResult VmaBlockVector::CommitAllocationRequest(\n    VmaAllocationRequest& allocRequest,\n    VmaDeviceMemoryBlock* pBlock,\n    VkDeviceSize alignment,\n    VmaAllocationCreateFlags allocFlags,\n    void* pUserData,\n    VmaSuballocationType suballocType,\n    VmaAllocation* pAllocation)\n{\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  const bool isMappingAllowed = (allocFlags &\n                                 (VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT)) != 0;\n\n  pBlock->PostAlloc(m_hAllocator);\n  // Allocate from pCurrBlock.\n  if (mapped)\n  {\n    VkResult res = pBlock->Map(m_hAllocator, 1, VMA_NULL);\n    if (res != VK_SUCCESS)\n    {\n      return res;\n    }\n  }\n\n  *pAllocation = m_hAllocator->m_AllocationObjectAllocator.Allocate(isMappingAllowed);\n  pBlock->m_pMetadata->Alloc(allocRequest, suballocType, *pAllocation);\n  (*pAllocation)->InitBlockAllocation(\n      pBlock,\n      allocRequest.allocHandle,\n      alignment,\n      allocRequest.size, // Not size, as actual allocation size may be larger than requested!\n      m_MemoryTypeIndex,\n      suballocType,\n      mapped);\n  VMA_HEAVY_ASSERT(pBlock->Validate());\n  if (isUserDataString)\n    (*pAllocation)->SetName(m_hAllocator, (const char*)pUserData);\n  else\n    (*pAllocation)->SetUserData(m_hAllocator, pUserData);\n  m_hAllocator->m_Budget.AddAllocation(m_hAllocator->MemoryTypeIndexToHeapIndex(m_MemoryTypeIndex), allocRequest.size);\n  if (VMA_DEBUG_INITIALIZE_ALLOCATIONS)\n  {\n    m_hAllocator->FillAllocation(*pAllocation, VMA_ALLOCATION_FILL_PATTERN_CREATED);\n  }\n  if (IsCorruptionDetectionEnabled())\n  {\n    VkResult res = pBlock->WriteMagicValueAfterAllocation(m_hAllocator, (*pAllocation)->GetOffset(), allocRequest.size);\n    VMA_ASSERT(res == VK_SUCCESS && \"Couldn't map block memory to write magic value.\");\n  }\n  return VK_SUCCESS;\n}\n\nVkResult VmaBlockVector::CreateBlock(VkDeviceSize blockSize, size_t* pNewBlockIndex)\n{\n  VkMemoryAllocateInfo allocInfo = { VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO };\n  allocInfo.pNext = m_pMemoryAllocateNext;\n  allocInfo.memoryTypeIndex = m_MemoryTypeIndex;\n  allocInfo.allocationSize = blockSize;\n\n#if VMA_BUFFER_DEVICE_ADDRESS\n  // Every standalone block can potentially contain a buffer with VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT - always enable the feature.\n  VkMemoryAllocateFlagsInfoKHR allocFlagsInfo = { VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_FLAGS_INFO_KHR };\n  if (m_hAllocator->m_UseKhrBufferDeviceAddress)\n  {\n    allocFlagsInfo.flags = VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT_KHR;\n    VmaPnextChainPushFront(&allocInfo, &allocFlagsInfo);\n  }\n#endif // VMA_BUFFER_DEVICE_ADDRESS\n\n#if VMA_MEMORY_PRIORITY\n  VkMemoryPriorityAllocateInfoEXT priorityInfo = { VK_STRUCTURE_TYPE_MEMORY_PRIORITY_ALLOCATE_INFO_EXT };\n  if (m_hAllocator->m_UseExtMemoryPriority)\n  {\n    VMA_ASSERT(m_Priority >= 0.f && m_Priority <= 1.f);\n    priorityInfo.priority = m_Priority;\n    VmaPnextChainPushFront(&allocInfo, &priorityInfo);\n  }\n#endif // VMA_MEMORY_PRIORITY\n\n#if VMA_EXTERNAL_MEMORY\n  // Attach VkExportMemoryAllocateInfoKHR if necessary.\n  VkExportMemoryAllocateInfoKHR exportMemoryAllocInfo = { VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO_KHR };\n  exportMemoryAllocInfo.handleTypes = m_hAllocator->GetExternalMemoryHandleTypeFlags(m_MemoryTypeIndex);\n  if (exportMemoryAllocInfo.handleTypes != 0)\n  {\n    VmaPnextChainPushFront(&allocInfo, &exportMemoryAllocInfo);\n  }\n#endif // VMA_EXTERNAL_MEMORY\n\n  VkDeviceMemory mem = VK_NULL_HANDLE;\n  VkResult res = m_hAllocator->AllocateVulkanMemory(&allocInfo, &mem);\n  if (res < 0)\n  {\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      m_BufferImageGranularity);\n\n  m_Blocks.push_back(pBlock);\n  if (pNewBlockIndex != VMA_NULL)\n  {\n    *pNewBlockIndex = m_Blocks.size() - 1;\n  }\n\n  return VK_SUCCESS;\n}\n\nbool VmaBlockVector::HasEmptyBlock()\n{\n  for (size_t index = 0, count = m_Blocks.size(); index < count; ++index)\n  {\n    VmaDeviceMemoryBlock* const pBlock = m_Blocks[index];\n    if (pBlock->m_pMetadata->IsEmpty())\n    {\n      return true;\n    }\n  }\n  return false;\n}\n\n#if VMA_STATS_STRING_ENABLED\nvoid VmaBlockVector::PrintDetailedMap(class VmaJsonWriter& json)\n{\n  VmaMutexLockRead lock(m_Mutex, m_hAllocator->m_UseMutex);\n\n\n  json.BeginObject();\n  for (size_t i = 0; i < m_Blocks.size(); ++i)\n  {\n    json.BeginString();\n    json.ContinueString(m_Blocks[i]->GetId());\n    json.EndString();\n\n    json.BeginObject();\n    json.WriteString(\"MapRefCount\");\n    json.WriteNumber(m_Blocks[i]->GetMapRefCount());\n\n    m_Blocks[i]->m_pMetadata->PrintDetailedMap(json);\n    json.EndObject();\n  }\n  json.EndObject();\n}\n#endif // VMA_STATS_STRING_ENABLED\n\nVkResult VmaBlockVector::CheckCorruption()\n{\n  if (!IsCorruptionDetectionEnabled())\n  {\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  {\n    VmaDeviceMemoryBlock* const pBlock = m_Blocks[blockIndex];\n    VMA_ASSERT(pBlock);\n    VkResult res = pBlock->CheckCorruption(m_hAllocator);\n    if (res != VK_SUCCESS)\n    {\n      return res;\n    }\n  }\n  return VK_SUCCESS;\n}\n\n#endif // _VMA_BLOCK_VECTOR_FUNCTIONS\n\n#ifndef _VMA_DEFRAGMENTATION_CONTEXT_FUNCTIONS\nVmaDefragmentationContext_T::VmaDefragmentationContext_T(\n    VmaAllocator hAllocator,\n    const VmaDefragmentationInfo& info)\n    : m_MaxPassBytes(info.maxBytesPerPass == 0 ? VK_WHOLE_SIZE : info.maxBytesPerPass),\n      m_MaxPassAllocations(info.maxAllocationsPerPass == 0 ? UINT32_MAX : info.maxAllocationsPerPass),\n      m_MoveAllocator(hAllocator->GetAllocationCallbacks()),\n      m_Moves(m_MoveAllocator)\n{\n  m_Algorithm = info.flags & VMA_DEFRAGMENTATION_FLAG_ALGORITHM_MASK;\n\n  if (info.pool != VMA_NULL)\n  {\n    m_BlockVectorCount = 1;\n    m_PoolBlockVector = &info.pool->m_BlockVector;\n    m_pBlockVectors = &m_PoolBlockVector;\n    m_PoolBlockVector->SetIncrementalSort(false);\n    m_PoolBlockVector->SortByFreeSize();\n  }\n  else\n  {\n    m_BlockVectorCount = hAllocator->GetMemoryTypeCount();\n    m_PoolBlockVector = VMA_NULL;\n    m_pBlockVectors = hAllocator->m_pBlockVectors;\n    for (uint32_t i = 0; i < m_BlockVectorCount; ++i)\n    {\n      VmaBlockVector* vector = m_pBlockVectors[i];\n      if (vector != VMA_NULL)\n      {\n        vector->SetIncrementalSort(false);\n        vector->SortByFreeSize();\n      }\n    }\n  }\n\n  switch (m_Algorithm)\n  {\n    case 0: // Default algorithm\n      m_Algorithm = VMA_DEFRAGMENTATION_FLAG_ALGORITHM_BALANCED_BIT;\n      m_AlgorithmState = vma_new_array(hAllocator, StateBalanced, m_BlockVectorCount);\n      break;\n    case VMA_DEFRAGMENTATION_FLAG_ALGORITHM_BALANCED_BIT:\n      m_AlgorithmState = vma_new_array(hAllocator, StateBalanced, m_BlockVectorCount);\n      break;\n    case VMA_DEFRAGMENTATION_FLAG_ALGORITHM_EXTENSIVE_BIT:\n      if (hAllocator->GetBufferImageGranularity() > 1)\n      {\n        m_AlgorithmState = vma_new_array(hAllocator, StateExtensive, m_BlockVectorCount);\n      }\n      break;\n  }\n}\n\nVmaDefragmentationContext_T::~VmaDefragmentationContext_T()\n{\n  if (m_PoolBlockVector != VMA_NULL)\n  {\n    m_PoolBlockVector->SetIncrementalSort(true);\n  }\n  else\n  {\n    for (uint32_t i = 0; i < m_BlockVectorCount; ++i)\n    {\n      VmaBlockVector* vector = m_pBlockVectors[i];\n      if (vector != VMA_NULL)\n        vector->SetIncrementalSort(true);\n    }\n  }\n\n  if (m_AlgorithmState)\n  {\n    switch (m_Algorithm)\n    {\n      case VMA_DEFRAGMENTATION_FLAG_ALGORITHM_BALANCED_BIT:\n        vma_delete_array(m_MoveAllocator.m_pCallbacks, reinterpret_cast<StateBalanced*>(m_AlgorithmState), m_BlockVectorCount);\n        break;\n      case VMA_DEFRAGMENTATION_FLAG_ALGORITHM_EXTENSIVE_BIT:\n        vma_delete_array(m_MoveAllocator.m_pCallbacks, reinterpret_cast<StateExtensive*>(m_AlgorithmState), m_BlockVectorCount);\n        break;\n      default:\n        VMA_ASSERT(0);\n    }\n  }\n}\n\nVkResult VmaDefragmentationContext_T::DefragmentPassBegin(VmaDefragmentationPassMoveInfo& moveInfo)\n{\n  if (m_PoolBlockVector != VMA_NULL)\n  {\n    VmaMutexLockWrite lock(m_PoolBlockVector->GetMutex(), m_PoolBlockVector->GetAllocator()->m_UseMutex);\n\n    if (m_PoolBlockVector->GetBlockCount() > 1)\n      ComputeDefragmentation(*m_PoolBlockVector, 0);\n    else if (m_PoolBlockVector->GetBlockCount() == 1)\n      ReallocWithinBlock(*m_PoolBlockVector, m_PoolBlockVector->GetBlock(0));\n  }\n  else\n  {\n    for (uint32_t i = 0; i < m_BlockVectorCount; ++i)\n    {\n      if (m_pBlockVectors[i] != VMA_NULL)\n      {\n        VmaMutexLockWrite lock(m_pBlockVectors[i]->GetMutex(), m_pBlockVectors[i]->GetAllocator()->m_UseMutex);\n\n        if (m_pBlockVectors[i]->GetBlockCount() > 1)\n        {\n          if (ComputeDefragmentation(*m_pBlockVectors[i], i))\n            break;\n        }\n        else if (m_pBlockVectors[i]->GetBlockCount() == 1)\n        {\n          if (ReallocWithinBlock(*m_pBlockVectors[i], m_pBlockVectors[i]->GetBlock(0)))\n            break;\n        }\n      }\n    }\n  }\n\n  moveInfo.moveCount = static_cast<uint32_t>(m_Moves.size());\n  if (moveInfo.moveCount > 0)\n  {\n    moveInfo.pMoves = m_Moves.data();\n    return VK_INCOMPLETE;\n  }\n\n  moveInfo.pMoves = VMA_NULL;\n  return VK_SUCCESS;\n}\n\nVkResult VmaDefragmentationContext_T::DefragmentPassEnd(VmaDefragmentationPassMoveInfo& moveInfo)\n{\n  VMA_ASSERT(moveInfo.moveCount > 0 ? moveInfo.pMoves != VMA_NULL : true);\n\n  VkResult result = VK_SUCCESS;\n  VmaStlAllocator<FragmentedBlock> blockAllocator(m_MoveAllocator.m_pCallbacks);\n  VmaVector<FragmentedBlock, VmaStlAllocator<FragmentedBlock>> immovableBlocks(blockAllocator);\n  VmaVector<FragmentedBlock, VmaStlAllocator<FragmentedBlock>> mappedBlocks(blockAllocator);\n\n  VmaAllocator allocator = VMA_NULL;\n  for (uint32_t i = 0; i < moveInfo.moveCount; ++i)\n  {\n    VmaDefragmentationMove& move = moveInfo.pMoves[i];\n    size_t prevCount = 0, currentCount = 0;\n    VkDeviceSize freedBlockSize = 0;\n\n    uint32_t vectorIndex;\n    VmaBlockVector* vector;\n    if (m_PoolBlockVector != VMA_NULL)\n    {\n      vectorIndex = 0;\n      vector = m_PoolBlockVector;\n    }\n    else\n    {\n      vectorIndex = move.srcAllocation->GetMemoryTypeIndex();\n      vector = m_pBlockVectors[vectorIndex];\n      VMA_ASSERT(vector != VMA_NULL);\n    }\n\n    switch (move.operation)\n    {\n      case VMA_DEFRAGMENTATION_MOVE_OPERATION_COPY:\n      {\n        uint8_t mapCount = move.srcAllocation->SwapBlockAllocation(vector->m_hAllocator, move.dstTmpAllocation);\n        if (mapCount > 0)\n        {\n          allocator = vector->m_hAllocator;\n          VmaDeviceMemoryBlock* newMapBlock = move.srcAllocation->GetBlock();\n          bool notPresent = true;\n          for (FragmentedBlock& block : mappedBlocks)\n          {\n            if (block.block == newMapBlock)\n            {\n              notPresent = false;\n              block.data += mapCount;\n              break;\n            }\n          }\n          if (notPresent)\n            mappedBlocks.push_back({ mapCount, newMapBlock });\n        }\n\n        // Scope for locks, Free have it's own lock\n        {\n          VmaMutexLockRead lock(vector->GetMutex(), vector->GetAllocator()->m_UseMutex);\n          prevCount = vector->GetBlockCount();\n          freedBlockSize = move.dstTmpAllocation->GetBlock()->m_pMetadata->GetSize();\n        }\n        vector->Free(move.dstTmpAllocation);\n        {\n          VmaMutexLockRead lock(vector->GetMutex(), vector->GetAllocator()->m_UseMutex);\n          currentCount = vector->GetBlockCount();\n        }\n\n        result = VK_INCOMPLETE;\n        break;\n      }\n      case VMA_DEFRAGMENTATION_MOVE_OPERATION_IGNORE:\n      {\n        m_PassStats.bytesMoved -= move.srcAllocation->GetSize();\n        --m_PassStats.allocationsMoved;\n        vector->Free(move.dstTmpAllocation);\n\n        VmaDeviceMemoryBlock* newBlock = move.srcAllocation->GetBlock();\n        bool notPresent = true;\n        for (const FragmentedBlock& block : immovableBlocks)\n        {\n          if (block.block == newBlock)\n          {\n            notPresent = false;\n            break;\n          }\n        }\n        if (notPresent)\n          immovableBlocks.push_back({ vectorIndex, newBlock });\n        break;\n      }\n      case VMA_DEFRAGMENTATION_MOVE_OPERATION_DESTROY:\n      {\n        m_PassStats.bytesMoved -= move.srcAllocation->GetSize();\n        --m_PassStats.allocationsMoved;\n        // Scope for locks, Free have it's own lock\n        {\n          VmaMutexLockRead lock(vector->GetMutex(), vector->GetAllocator()->m_UseMutex);\n          prevCount = vector->GetBlockCount();\n          freedBlockSize = move.srcAllocation->GetBlock()->m_pMetadata->GetSize();\n        }\n        vector->Free(move.srcAllocation);\n        {\n          VmaMutexLockRead lock(vector->GetMutex(), vector->GetAllocator()->m_UseMutex);\n          currentCount = vector->GetBlockCount();\n        }\n        freedBlockSize *= prevCount - currentCount;\n\n        VkDeviceSize dstBlockSize;\n        {\n          VmaMutexLockRead lock(vector->GetMutex(), vector->GetAllocator()->m_UseMutex);\n          dstBlockSize = move.dstTmpAllocation->GetBlock()->m_pMetadata->GetSize();\n        }\n        vector->Free(move.dstTmpAllocation);\n        {\n          VmaMutexLockRead lock(vector->GetMutex(), vector->GetAllocator()->m_UseMutex);\n          freedBlockSize += dstBlockSize * (currentCount - vector->GetBlockCount());\n          currentCount = vector->GetBlockCount();\n        }\n\n        result = VK_INCOMPLETE;\n        break;\n      }\n      default:\n        VMA_ASSERT(0);\n    }\n\n    if (prevCount > currentCount)\n    {\n      size_t freedBlocks = prevCount - currentCount;\n      m_PassStats.deviceMemoryBlocksFreed += static_cast<uint32_t>(freedBlocks);\n      m_PassStats.bytesFreed += freedBlockSize;\n    }\n\n    switch (m_Algorithm)\n    {\n      case VMA_DEFRAGMENTATION_FLAG_ALGORITHM_EXTENSIVE_BIT:\n      {\n        if (m_AlgorithmState != VMA_NULL)\n        {\n          // Avoid unnecessary tries to allocate when new free block is available\n          StateExtensive& state = reinterpret_cast<StateExtensive*>(m_AlgorithmState)[vectorIndex];\n          if (state.firstFreeBlock != SIZE_MAX)\n          {\n            const size_t diff = prevCount - currentCount;\n            if (state.firstFreeBlock >= diff)\n            {\n              state.firstFreeBlock -= diff;\n              if (state.firstFreeBlock != 0)\n                state.firstFreeBlock -= vector->GetBlock(state.firstFreeBlock - 1)->m_pMetadata->IsEmpty();\n            }\n            else\n              state.firstFreeBlock = 0;\n          }\n        }\n      }\n    }\n  }\n  moveInfo.moveCount = 0;\n  moveInfo.pMoves = VMA_NULL;\n  m_Moves.clear();\n\n  // Update stats\n  m_GlobalStats.allocationsMoved += m_PassStats.allocationsMoved;\n  m_GlobalStats.bytesFreed += m_PassStats.bytesFreed;\n  m_GlobalStats.bytesMoved += m_PassStats.bytesMoved;\n  m_GlobalStats.deviceMemoryBlocksFreed += m_PassStats.deviceMemoryBlocksFreed;\n  m_PassStats = { 0 };\n\n  // Move blocks with immovable allocations according to algorithm\n  if (immovableBlocks.size() > 0)\n  {\n    do\n    {\n      if(m_Algorithm == VMA_DEFRAGMENTATION_FLAG_ALGORITHM_EXTENSIVE_BIT)\n      {\n        if (m_AlgorithmState != VMA_NULL)\n        {\n          bool swapped = false;\n          // Move to the start of free blocks range\n          for (const FragmentedBlock& block : immovableBlocks)\n          {\n            StateExtensive& state = reinterpret_cast<StateExtensive*>(m_AlgorithmState)[block.data];\n            if (state.operation != StateExtensive::Operation::Cleanup)\n            {\n              VmaBlockVector* vector = m_pBlockVectors[block.data];\n              VmaMutexLockWrite lock(vector->GetMutex(), vector->GetAllocator()->m_UseMutex);\n\n              for (size_t i = 0, count = vector->GetBlockCount() - m_ImmovableBlockCount; i < count; ++i)\n              {\n                if (vector->GetBlock(i) == block.block)\n                {\n                  VMA_SWAP(vector->m_Blocks[i], vector->m_Blocks[vector->GetBlockCount() - ++m_ImmovableBlockCount]);\n                  if (state.firstFreeBlock != SIZE_MAX)\n                  {\n                    if (i + 1 < state.firstFreeBlock)\n                    {\n                      if (state.firstFreeBlock > 1)\n                        VMA_SWAP(vector->m_Blocks[i], vector->m_Blocks[--state.firstFreeBlock]);\n                      else\n                        --state.firstFreeBlock;\n                    }\n                  }\n                  swapped = true;\n                  break;\n                }\n              }\n            }\n          }\n          if (swapped)\n            result = VK_INCOMPLETE;\n          break;\n        }\n      }\n\n      // Move to the beginning\n      for (const FragmentedBlock& block : immovableBlocks)\n      {\n        VmaBlockVector* vector = m_pBlockVectors[block.data];\n        VmaMutexLockWrite lock(vector->GetMutex(), vector->GetAllocator()->m_UseMutex);\n\n        for (size_t i = m_ImmovableBlockCount; i < vector->GetBlockCount(); ++i)\n        {\n          if (vector->GetBlock(i) == block.block)\n          {\n            VMA_SWAP(vector->m_Blocks[i], vector->m_Blocks[m_ImmovableBlockCount++]);\n            break;\n          }\n        }\n      }\n    } while (false);\n  }\n\n  // Bulk-map destination blocks\n  for (const FragmentedBlock& block : mappedBlocks)\n  {\n    VkResult res = block.block->Map(allocator, block.data, VMA_NULL);\n    VMA_ASSERT(res == VK_SUCCESS);\n  }\n  return result;\n}\n\nbool VmaDefragmentationContext_T::ComputeDefragmentation(VmaBlockVector& vector, size_t index)\n{\n  switch (m_Algorithm)\n  {\n    case VMA_DEFRAGMENTATION_FLAG_ALGORITHM_FAST_BIT:\n      return ComputeDefragmentation_Fast(vector);\n    case VMA_DEFRAGMENTATION_FLAG_ALGORITHM_BALANCED_BIT:\n      return ComputeDefragmentation_Balanced(vector, index, true);\n    case VMA_DEFRAGMENTATION_FLAG_ALGORITHM_FULL_BIT:\n      return ComputeDefragmentation_Full(vector);\n    case VMA_DEFRAGMENTATION_FLAG_ALGORITHM_EXTENSIVE_BIT:\n      return ComputeDefragmentation_Extensive(vector, index);\n    default:\n      VMA_ASSERT(0);\n      return ComputeDefragmentation_Balanced(vector, index, true);\n  }\n}\n\nVmaDefragmentationContext_T::MoveAllocationData VmaDefragmentationContext_T::GetMoveData(\n    VmaAllocHandle handle, VmaBlockMetadata* metadata)\n{\n  MoveAllocationData moveData;\n  moveData.move.srcAllocation = (VmaAllocation)metadata->GetAllocationUserData(handle);\n  moveData.size = moveData.move.srcAllocation->GetSize();\n  moveData.alignment = moveData.move.srcAllocation->GetAlignment();\n  moveData.type = moveData.move.srcAllocation->GetSuballocationType();\n  moveData.flags = 0;\n\n  if (moveData.move.srcAllocation->IsPersistentMap())\n    moveData.flags |= VMA_ALLOCATION_CREATE_MAPPED_BIT;\n  if (moveData.move.srcAllocation->IsMappingAllowed())\n    moveData.flags |= VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT;\n\n  return moveData;\n}\n\nVmaDefragmentationContext_T::CounterStatus VmaDefragmentationContext_T::CheckCounters(VkDeviceSize bytes)\n{\n  // Ignore allocation if will exceed max size for copy\n  if (m_PassStats.bytesMoved + bytes > m_MaxPassBytes)\n  {\n    if (++m_IgnoredAllocs < MAX_ALLOCS_TO_IGNORE)\n      return CounterStatus::Ignore;\n    else\n      return CounterStatus::End;\n  }\n  return CounterStatus::Pass;\n}\n\nbool VmaDefragmentationContext_T::IncrementCounters(VkDeviceSize bytes)\n{\n  m_PassStats.bytesMoved += bytes;\n  // Early return when max found\n  if (++m_PassStats.allocationsMoved >= m_MaxPassAllocations || m_PassStats.bytesMoved >= m_MaxPassBytes)\n  {\n    VMA_ASSERT(m_PassStats.allocationsMoved == m_MaxPassAllocations ||\n               m_PassStats.bytesMoved == m_MaxPassBytes && \"Exceeded maximal pass threshold!\");\n    return true;\n  }\n  return false;\n}\n\nbool VmaDefragmentationContext_T::ReallocWithinBlock(VmaBlockVector& vector, VmaDeviceMemoryBlock* block)\n{\n  VmaBlockMetadata* metadata = block->m_pMetadata;\n\n  for (VmaAllocHandle handle = metadata->GetAllocationListBegin();\n       handle != VK_NULL_HANDLE;\n       handle = metadata->GetNextAllocation(handle))\n  {\n    MoveAllocationData moveData = GetMoveData(handle, metadata);\n    // Ignore newly created allocations by defragmentation algorithm\n    if (moveData.move.srcAllocation->GetUserData() == this)\n      continue;\n    switch (CheckCounters(moveData.move.srcAllocation->GetSize()))\n    {\n      case CounterStatus::Ignore:\n        continue;\n      case CounterStatus::End:\n        return true;\n      case CounterStatus::Pass:\n        break;\n      default:\n        VMA_ASSERT(0);\n    }\n\n    VkDeviceSize offset = moveData.move.srcAllocation->GetOffset();\n    if (offset != 0 && metadata->GetSumFreeSize() >= moveData.size)\n    {\n      VmaAllocationRequest request = {};\n      if (metadata->CreateAllocationRequest(\n              moveData.size,\n              moveData.alignment,\n              false,\n              moveData.type,\n              VMA_ALLOCATION_CREATE_STRATEGY_MIN_OFFSET_BIT,\n              &request))\n      {\n        if (metadata->GetAllocationOffset(request.allocHandle) < offset)\n        {\n          if (vector.CommitAllocationRequest(\n                  request,\n                  block,\n                  moveData.alignment,\n                  moveData.flags,\n                  this,\n                  moveData.type,\n                  &moveData.move.dstTmpAllocation) == VK_SUCCESS)\n          {\n            m_Moves.push_back(moveData.move);\n            if (IncrementCounters(moveData.size))\n              return true;\n          }\n        }\n      }\n    }\n  }\n  return false;\n}\n\nbool VmaDefragmentationContext_T::AllocInOtherBlock(size_t start, size_t end, MoveAllocationData& data, VmaBlockVector& vector)\n{\n  for (; start < end; ++start)\n  {\n    VmaDeviceMemoryBlock* dstBlock = vector.GetBlock(start);\n    if (dstBlock->m_pMetadata->GetSumFreeSize() >= data.size)\n    {\n      if (vector.AllocateFromBlock(dstBlock,\n                                   data.size,\n                                   data.alignment,\n                                   data.flags,\n                                   this,\n                                   data.type,\n                                   0,\n                                   &data.move.dstTmpAllocation) == VK_SUCCESS)\n      {\n        m_Moves.push_back(data.move);\n        if (IncrementCounters(data.size))\n          return true;\n        break;\n      }\n    }\n  }\n  return false;\n}\n\nbool VmaDefragmentationContext_T::ComputeDefragmentation_Fast(VmaBlockVector& vector)\n{\n  // Move only between blocks\n\n  // Go through allocations in last blocks and try to fit them inside first ones\n  for (size_t i = vector.GetBlockCount() - 1; i > m_ImmovableBlockCount; --i)\n  {\n    VmaBlockMetadata* metadata = vector.GetBlock(i)->m_pMetadata;\n\n    for (VmaAllocHandle handle = metadata->GetAllocationListBegin();\n         handle != VK_NULL_HANDLE;\n         handle = metadata->GetNextAllocation(handle))\n    {\n      MoveAllocationData moveData = GetMoveData(handle, metadata);\n      // Ignore newly created allocations by defragmentation algorithm\n      if (moveData.move.srcAllocation->GetUserData() == this)\n        continue;\n      switch (CheckCounters(moveData.move.srcAllocation->GetSize()))\n      {\n        case CounterStatus::Ignore:\n          continue;\n        case CounterStatus::End:\n          return true;\n        case CounterStatus::Pass:\n          break;\n        default:\n          VMA_ASSERT(0);\n      }\n\n      // Check all previous blocks for free space\n      if (AllocInOtherBlock(0, i, moveData, vector))\n        return true;\n    }\n  }\n  return false;\n}\n\nbool VmaDefragmentationContext_T::ComputeDefragmentation_Balanced(VmaBlockVector& vector, size_t index, bool update)\n{\n  // Go over every allocation and try to fit it in previous blocks at lowest offsets,\n  // if not possible: realloc within single block to minimize offset (exclude offset == 0),\n  // but only if there are noticeable gaps between them (some heuristic, ex. average size of allocation in block)\n  VMA_ASSERT(m_AlgorithmState != VMA_NULL);\n\n  StateBalanced& vectorState = reinterpret_cast<StateBalanced*>(m_AlgorithmState)[index];\n  if (update && vectorState.avgAllocSize == UINT64_MAX)\n    UpdateVectorStatistics(vector, vectorState);\n\n  const size_t startMoveCount = m_Moves.size();\n  VkDeviceSize minimalFreeRegion = vectorState.avgFreeSize / 2;\n  for (size_t i = vector.GetBlockCount() - 1; i > m_ImmovableBlockCount; --i)\n  {\n    VmaDeviceMemoryBlock* block = vector.GetBlock(i);\n    VmaBlockMetadata* metadata = block->m_pMetadata;\n    VkDeviceSize prevFreeRegionSize = 0;\n\n    for (VmaAllocHandle handle = metadata->GetAllocationListBegin();\n         handle != VK_NULL_HANDLE;\n         handle = metadata->GetNextAllocation(handle))\n    {\n      MoveAllocationData moveData = GetMoveData(handle, metadata);\n      // Ignore newly created allocations by defragmentation algorithm\n      if (moveData.move.srcAllocation->GetUserData() == this)\n        continue;\n      switch (CheckCounters(moveData.move.srcAllocation->GetSize()))\n      {\n        case CounterStatus::Ignore:\n          continue;\n        case CounterStatus::End:\n          return true;\n        case CounterStatus::Pass:\n          break;\n        default:\n          VMA_ASSERT(0);\n      }\n\n      // Check all previous blocks for free space\n      const size_t prevMoveCount = m_Moves.size();\n      if (AllocInOtherBlock(0, i, moveData, vector))\n        return true;\n\n      VkDeviceSize nextFreeRegionSize = metadata->GetNextFreeRegionSize(handle);\n      // If no room found then realloc within block for lower offset\n      VkDeviceSize offset = moveData.move.srcAllocation->GetOffset();\n      if (prevMoveCount == m_Moves.size() && offset != 0 && metadata->GetSumFreeSize() >= moveData.size)\n      {\n        // Check if realloc will make sense\n        if (prevFreeRegionSize >= minimalFreeRegion ||\n            nextFreeRegionSize >= minimalFreeRegion ||\n            moveData.size <= vectorState.avgFreeSize ||\n            moveData.size <= vectorState.avgAllocSize)\n        {\n          VmaAllocationRequest request = {};\n          if (metadata->CreateAllocationRequest(\n                  moveData.size,\n                  moveData.alignment,\n                  false,\n                  moveData.type,\n                  VMA_ALLOCATION_CREATE_STRATEGY_MIN_OFFSET_BIT,\n                  &request))\n          {\n            if (metadata->GetAllocationOffset(request.allocHandle) < offset)\n            {\n              if (vector.CommitAllocationRequest(\n                      request,\n                      block,\n                      moveData.alignment,\n                      moveData.flags,\n                      this,\n                      moveData.type,\n                      &moveData.move.dstTmpAllocation) == VK_SUCCESS)\n              {\n                m_Moves.push_back(moveData.move);\n                if (IncrementCounters(moveData.size))\n                  return true;\n              }\n            }\n          }\n        }\n      }\n      prevFreeRegionSize = nextFreeRegionSize;\n    }\n  }\n\n  // No moves performed, update statistics to current vector state\n  if (startMoveCount == m_Moves.size() && !update)\n  {\n    vectorState.avgAllocSize = UINT64_MAX;\n    return ComputeDefragmentation_Balanced(vector, index, false);\n  }\n  return false;\n}\n\nbool VmaDefragmentationContext_T::ComputeDefragmentation_Full(VmaBlockVector& vector)\n{\n  // Go over every allocation and try to fit it in previous blocks at lowest offsets,\n  // if not possible: realloc within single block to minimize offset (exclude offset == 0)\n\n  for (size_t i = vector.GetBlockCount() - 1; i > m_ImmovableBlockCount; --i)\n  {\n    VmaDeviceMemoryBlock* block = vector.GetBlock(i);\n    VmaBlockMetadata* metadata = block->m_pMetadata;\n\n    for (VmaAllocHandle handle = metadata->GetAllocationListBegin();\n         handle != VK_NULL_HANDLE;\n         handle = metadata->GetNextAllocation(handle))\n    {\n      MoveAllocationData moveData = GetMoveData(handle, metadata);\n      // Ignore newly created allocations by defragmentation algorithm\n      if (moveData.move.srcAllocation->GetUserData() == this)\n        continue;\n      switch (CheckCounters(moveData.move.srcAllocation->GetSize()))\n      {\n        case CounterStatus::Ignore:\n          continue;\n        case CounterStatus::End:\n          return true;\n        case CounterStatus::Pass:\n          break;\n        default:\n          VMA_ASSERT(0);\n      }\n\n      // Check all previous blocks for free space\n      const size_t prevMoveCount = m_Moves.size();\n      if (AllocInOtherBlock(0, i, moveData, vector))\n        return true;\n\n      // If no room found then realloc within block for lower offset\n      VkDeviceSize offset = moveData.move.srcAllocation->GetOffset();\n      if (prevMoveCount == m_Moves.size() && offset != 0 && metadata->GetSumFreeSize() >= moveData.size)\n      {\n        VmaAllocationRequest request = {};\n        if (metadata->CreateAllocationRequest(\n                moveData.size,\n                moveData.alignment,\n                false,\n                moveData.type,\n                VMA_ALLOCATION_CREATE_STRATEGY_MIN_OFFSET_BIT,\n                &request))\n        {\n          if (metadata->GetAllocationOffset(request.allocHandle) < offset)\n          {\n            if (vector.CommitAllocationRequest(\n                    request,\n                    block,\n                    moveData.alignment,\n                    moveData.flags,\n                    this,\n                    moveData.type,\n                    &moveData.move.dstTmpAllocation) == VK_SUCCESS)\n            {\n              m_Moves.push_back(moveData.move);\n              if (IncrementCounters(moveData.size))\n                return true;\n            }\n          }\n        }\n      }\n    }\n  }\n  return false;\n}\n\nbool VmaDefragmentationContext_T::ComputeDefragmentation_Extensive(VmaBlockVector& vector, size_t index)\n{\n  // First free single block, then populate it to the brim, then free another block, and so on\n\n  // Fallback to previous algorithm since without granularity conflicts it can achieve max packing\n  if (vector.m_BufferImageGranularity == 1)\n    return ComputeDefragmentation_Full(vector);\n\n  VMA_ASSERT(m_AlgorithmState != VMA_NULL);\n\n  StateExtensive& vectorState = reinterpret_cast<StateExtensive*>(m_AlgorithmState)[index];\n\n  bool texturePresent = false, bufferPresent = false, otherPresent = false;\n  switch (vectorState.operation)\n  {\n    case StateExtensive::Operation::Done: // Vector defragmented\n      return false;\n    case StateExtensive::Operation::FindFreeBlockBuffer:\n    case StateExtensive::Operation::FindFreeBlockTexture:\n    case StateExtensive::Operation::FindFreeBlockAll:\n    {\n      // No more blocks to free, just perform fast realloc and move to cleanup\n      if (vectorState.firstFreeBlock == 0)\n      {\n        vectorState.operation = StateExtensive::Operation::Cleanup;\n        return ComputeDefragmentation_Fast(vector);\n      }\n\n      // No free blocks, have to clear last one\n      size_t last = (vectorState.firstFreeBlock == SIZE_MAX ? vector.GetBlockCount() : vectorState.firstFreeBlock) - 1;\n      VmaBlockMetadata* freeMetadata = vector.GetBlock(last)->m_pMetadata;\n\n      const size_t prevMoveCount = m_Moves.size();\n      for (VmaAllocHandle handle = freeMetadata->GetAllocationListBegin();\n           handle != VK_NULL_HANDLE;\n           handle = freeMetadata->GetNextAllocation(handle))\n      {\n        MoveAllocationData moveData = GetMoveData(handle, freeMetadata);\n        switch (CheckCounters(moveData.move.srcAllocation->GetSize()))\n        {\n          case CounterStatus::Ignore:\n            continue;\n          case CounterStatus::End:\n            return true;\n          case CounterStatus::Pass:\n            break;\n          default:\n            VMA_ASSERT(0);\n        }\n\n        // Check all previous blocks for free space\n        if (AllocInOtherBlock(0, last, moveData, vector))\n        {\n          // Full clear performed already\n          if (prevMoveCount != m_Moves.size() && freeMetadata->GetNextAllocation(handle) == VK_NULL_HANDLE)\n            reinterpret_cast<size_t*>(m_AlgorithmState)[index] = last;\n          return true;\n        }\n      }\n\n      if (prevMoveCount == m_Moves.size())\n      {\n        // Cannot perform full clear, have to move data in other blocks around\n        if (last != 0)\n        {\n          for (size_t i = last - 1; i; --i)\n          {\n            if (ReallocWithinBlock(vector, vector.GetBlock(i)))\n              return true;\n          }\n        }\n\n        if (prevMoveCount == m_Moves.size())\n        {\n          // No possible reallocs within blocks, try to move them around fast\n          return ComputeDefragmentation_Fast(vector);\n        }\n      }\n      else\n      {\n        switch (vectorState.operation)\n        {\n          case StateExtensive::Operation::FindFreeBlockBuffer:\n            vectorState.operation = StateExtensive::Operation::MoveBuffers;\n            break;\n          case StateExtensive::Operation::FindFreeBlockTexture:\n            vectorState.operation = StateExtensive::Operation::MoveTextures;\n            break;\n          case StateExtensive::Operation::FindFreeBlockAll:\n            vectorState.operation = StateExtensive::Operation::MoveAll;\n            break;\n          default:\n            VMA_ASSERT(0);\n            vectorState.operation = StateExtensive::Operation::MoveTextures;\n        }\n        vectorState.firstFreeBlock = last;\n        // Nothing done, block found without reallocations, can perform another reallocs in same pass\n        return ComputeDefragmentation_Extensive(vector, index);\n      }\n      break;\n    }\n    case StateExtensive::Operation::MoveTextures:\n    {\n      if (MoveDataToFreeBlocks(VMA_SUBALLOCATION_TYPE_IMAGE_OPTIMAL, vector,\n                               vectorState.firstFreeBlock, texturePresent, bufferPresent, otherPresent))\n      {\n        if (texturePresent)\n        {\n          vectorState.operation = StateExtensive::Operation::FindFreeBlockTexture;\n          return ComputeDefragmentation_Extensive(vector, index);\n        }\n\n        if (!bufferPresent && !otherPresent)\n        {\n          vectorState.operation = StateExtensive::Operation::Cleanup;\n          break;\n        }\n\n        // No more textures to move, check buffers\n        vectorState.operation = StateExtensive::Operation::MoveBuffers;\n        bufferPresent = false;\n        otherPresent = false;\n      }\n      else\n        break;\n    }\n    case StateExtensive::Operation::MoveBuffers:\n    {\n      if (MoveDataToFreeBlocks(VMA_SUBALLOCATION_TYPE_BUFFER, vector,\n                               vectorState.firstFreeBlock, texturePresent, bufferPresent, otherPresent))\n      {\n        if (bufferPresent)\n        {\n          vectorState.operation = StateExtensive::Operation::FindFreeBlockBuffer;\n          return ComputeDefragmentation_Extensive(vector, index);\n        }\n\n        if (!otherPresent)\n        {\n          vectorState.operation = StateExtensive::Operation::Cleanup;\n          break;\n        }\n\n        // No more buffers to move, check all others\n        vectorState.operation = StateExtensive::Operation::MoveAll;\n        otherPresent = false;\n      }\n      else\n        break;\n    }\n    case StateExtensive::Operation::MoveAll:\n    {\n      if (MoveDataToFreeBlocks(VMA_SUBALLOCATION_TYPE_FREE, vector,\n                               vectorState.firstFreeBlock, texturePresent, bufferPresent, otherPresent))\n      {\n        if (otherPresent)\n        {\n          vectorState.operation = StateExtensive::Operation::FindFreeBlockBuffer;\n          return ComputeDefragmentation_Extensive(vector, index);\n        }\n        // Everything moved\n        vectorState.operation = StateExtensive::Operation::Cleanup;\n      }\n      break;\n    }\n    case StateExtensive::Operation::Cleanup:\n      // Cleanup is handled below so that other operations may reuse the cleanup code. This case is here to prevent the unhandled enum value warning (C4062).\n      break;\n  }\n\n  if (vectorState.operation == StateExtensive::Operation::Cleanup)\n  {\n    // All other work done, pack data in blocks even tighter if possible\n    const size_t prevMoveCount = m_Moves.size();\n    for (size_t i = 0; i < vector.GetBlockCount(); ++i)\n    {\n      if (ReallocWithinBlock(vector, vector.GetBlock(i)))\n        return true;\n    }\n\n    if (prevMoveCount == m_Moves.size())\n      vectorState.operation = StateExtensive::Operation::Done;\n  }\n  return false;\n}\n\nvoid VmaDefragmentationContext_T::UpdateVectorStatistics(VmaBlockVector& vector, StateBalanced& state)\n{\n  size_t allocCount = 0;\n  size_t freeCount = 0;\n  state.avgFreeSize = 0;\n  state.avgAllocSize = 0;\n\n  for (size_t i = 0; i < vector.GetBlockCount(); ++i)\n  {\n    VmaBlockMetadata* metadata = vector.GetBlock(i)->m_pMetadata;\n\n    allocCount += metadata->GetAllocationCount();\n    freeCount += metadata->GetFreeRegionsCount();\n    state.avgFreeSize += metadata->GetSumFreeSize();\n    state.avgAllocSize += metadata->GetSize();\n  }\n\n  state.avgAllocSize = (state.avgAllocSize - state.avgFreeSize) / allocCount;\n  state.avgFreeSize /= freeCount;\n}\n\nbool VmaDefragmentationContext_T::MoveDataToFreeBlocks(VmaSuballocationType currentType,\n                                                       VmaBlockVector& vector, size_t firstFreeBlock,\n                                                       bool& texturePresent, bool& bufferPresent, bool& otherPresent)\n{\n  const size_t prevMoveCount = m_Moves.size();\n  for (size_t i = firstFreeBlock ; i;)\n  {\n    VmaDeviceMemoryBlock* block = vector.GetBlock(--i);\n    VmaBlockMetadata* metadata = block->m_pMetadata;\n\n    for (VmaAllocHandle handle = metadata->GetAllocationListBegin();\n         handle != VK_NULL_HANDLE;\n         handle = metadata->GetNextAllocation(handle))\n    {\n      MoveAllocationData moveData = GetMoveData(handle, metadata);\n      // Ignore newly created allocations by defragmentation algorithm\n      if (moveData.move.srcAllocation->GetUserData() == this)\n        continue;\n      switch (CheckCounters(moveData.move.srcAllocation->GetSize()))\n      {\n        case CounterStatus::Ignore:\n          continue;\n        case CounterStatus::End:\n          return true;\n        case CounterStatus::Pass:\n          break;\n        default:\n          VMA_ASSERT(0);\n      }\n\n      // Move only single type of resources at once\n      if (!VmaIsBufferImageGranularityConflict(moveData.type, currentType))\n      {\n        // Try to fit allocation into free blocks\n        if (AllocInOtherBlock(firstFreeBlock, vector.GetBlockCount(), moveData, vector))\n          return false;\n      }\n\n      if (!VmaIsBufferImageGranularityConflict(moveData.type, VMA_SUBALLOCATION_TYPE_IMAGE_OPTIMAL))\n        texturePresent = true;\n      else if (!VmaIsBufferImageGranularityConflict(moveData.type, VMA_SUBALLOCATION_TYPE_BUFFER))\n        bufferPresent = true;\n      else\n        otherPresent = true;\n    }\n  }\n  return prevMoveCount == m_Moves.size();\n}\n#endif // _VMA_DEFRAGMENTATION_CONTEXT_FUNCTIONS\n\n#ifndef _VMA_POOL_T_FUNCTIONS\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.blockSize != 0, // explicitBlockSize\n          createInfo.flags & VMA_POOL_CREATE_ALGORITHM_MASK, // algorithm\n          createInfo.priority,\n          VMA_MAX(hAllocator->GetMemoryTypeMinAlignment(createInfo.memoryTypeIndex), createInfo.minAllocationAlignment),\n          createInfo.pMemoryAllocateNext),\n      m_Id(0),\n      m_Name(VMA_NULL) {}\n\nVmaPool_T::~VmaPool_T()\n{\n  VMA_ASSERT(m_PrevPool == VMA_NULL && m_NextPool == VMA_NULL);\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  {\n    m_Name = VmaCreateStringCopy(allocs, pName);\n  }\n  else\n  {\n    m_Name = VMA_NULL;\n  }\n}\n#endif // _VMA_POOL_T_FUNCTIONS\n\n#ifndef _VMA_ALLOCATOR_T_FUNCTIONS\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_UseAmdDeviceCoherentMemory((pCreateInfo->flags & VMA_ALLOCATOR_CREATE_AMD_DEVICE_COHERENT_MEMORY_BIT) != 0),\n                                                                            m_UseKhrBufferDeviceAddress((pCreateInfo->flags & VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT) != 0),\n                                                                            m_UseExtMemoryPriority((pCreateInfo->flags & VMA_ALLOCATOR_CREATE_EXT_MEMORY_PRIORITY_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_DeviceMemoryCount(0),\n                                                                            m_PreferredLargeHeapBlockSize(0),\n                                                                            m_PhysicalDevice(pCreateInfo->physicalDevice),\n                                                                            m_GpuDefragmentationMemoryTypeBits(UINT32_MAX),\n                                                                            m_NextPoolId(0),\n                                                                            m_GlobalMemoryTypeBits(UINT32_MAX)\n{\n  if(m_VulkanApiVersion >= VK_MAKE_VERSION(1, 1, 0))\n  {\n    m_UseKhrDedicatedAllocation = false;\n    m_UseKhrBindMemory2 = false;\n  }\n\n  if(VMA_DEBUG_DETECT_CORRUPTION)\n  {\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 && pCreateInfo->instance);\n\n  if(m_VulkanApiVersion < VK_MAKE_VERSION(1, 1, 0))\n  {\n#if !(VMA_DEDICATED_ALLOCATION)\n    if((pCreateInfo->flags & VMA_ALLOCATOR_CREATE_KHR_DEDICATED_ALLOCATION_BIT) != 0)\n    {\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    {\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  {\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_BUFFER_DEVICE_ADDRESS)\n  if(m_UseKhrBufferDeviceAddress)\n  {\n    VMA_ASSERT(0 && \"VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT is set but required extension or Vulkan 1.2 is not available in your Vulkan header or its support in VMA has been disabled by a preprocessor macro.\");\n  }\n#endif\n#if VMA_VULKAN_VERSION < 1003000\n  if(m_VulkanApiVersion >= VK_MAKE_VERSION(1, 3, 0))\n  {\n    VMA_ASSERT(0 && \"vulkanApiVersion >= VK_API_VERSION_1_3 but required Vulkan version is disabled by preprocessor macros.\");\n  }\n#endif\n#if VMA_VULKAN_VERSION < 1002000\n  if(m_VulkanApiVersion >= VK_MAKE_VERSION(1, 2, 0))\n  {\n    VMA_ASSERT(0 && \"vulkanApiVersion >= VK_API_VERSION_1_2 but required Vulkan version 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  {\n    VMA_ASSERT(0 && \"vulkanApiVersion >= VK_API_VERSION_1_1 but required Vulkan version is disabled by preprocessor macros.\");\n  }\n#endif\n#if !(VMA_MEMORY_PRIORITY)\n  if(m_UseExtMemoryPriority)\n  {\n    VMA_ASSERT(0 && \"VMA_ALLOCATOR_CREATE_EXT_MEMORY_PRIORITY_BIT is set but required extension is not available in your Vulkan header or its support in VMA has been disabled by a preprocessor macro.\");\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_VulkanFunctions, 0, sizeof(m_VulkanFunctions));\n\n#if VMA_EXTERNAL_MEMORY\n  memset(&m_TypeExternalMemoryHandleTypes, 0, sizeof(m_TypeExternalMemoryHandleTypes));\n#endif // #if VMA_EXTERNAL_MEMORY\n\n  if(pCreateInfo->pDeviceMemoryCallbacks != VMA_NULL)\n  {\n    m_DeviceMemoryCallbacks.pUserData = pCreateInfo->pDeviceMemoryCallbacks->pUserData;\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_MIN_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  m_GlobalMemoryTypeBits = CalculateGlobalMemoryTypeBits();\n\n#if VMA_EXTERNAL_MEMORY\n  if(pCreateInfo->pTypeExternalMemoryHandleTypes != VMA_NULL)\n  {\n    memcpy(m_TypeExternalMemoryHandleTypes, pCreateInfo->pTypeExternalMemoryHandleTypes,\n           sizeof(VkExternalMemoryHandleTypeFlagsKHR) * GetMemoryTypeCount());\n  }\n#endif // #if VMA_EXTERNAL_MEMORY\n\n  if(pCreateInfo->pHeapSizeLimit != VMA_NULL)\n  {\n    for(uint32_t heapIndex = 0; heapIndex < GetMemoryHeapCount(); ++heapIndex)\n    {\n      const VkDeviceSize limit = pCreateInfo->pHeapSizeLimit[heapIndex];\n      if(limit != VK_WHOLE_SIZE)\n      {\n        m_HeapSizeLimitMask |= 1u << heapIndex;\n        if(limit < m_MemProps.memoryHeaps[heapIndex].size)\n        {\n          m_MemProps.memoryHeaps[heapIndex].size = limit;\n        }\n      }\n    }\n  }\n\n  for(uint32_t memTypeIndex = 0; memTypeIndex < GetMemoryTypeCount(); ++memTypeIndex)\n  {\n    // Create only supported types\n    if((m_GlobalMemoryTypeBits & (1u << memTypeIndex)) != 0)\n    {\n      const VkDeviceSize preferredBlockSize = CalcPreferredBlockSize(memTypeIndex);\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          false, // explicitBlockSize\n          0, // algorithm\n          0.5f, // priority (0.5 is the default per Vulkan spec)\n          GetMemoryTypeMinAlignment(memTypeIndex), // minAllocationAlignment\n          VMA_NULL); // // pMemoryAllocateNext\n                                                    // No need to call m_pBlockVectors[memTypeIndex][blockVectorTypeIndex]->CreateMinBlocks here,\n                                                    // becase minBlockCount is 0.\n    }\n  }\n}\n\nVkResult VmaAllocator_T::Init(const VmaAllocatorCreateInfo* pCreateInfo)\n{\n  VkResult res = VK_SUCCESS;\n\n#if VMA_MEMORY_BUDGET\n  if(m_UseExtMemoryBudget)\n  {\n    UpdateVulkanBudget();\n  }\n#endif // #if VMA_MEMORY_BUDGET\n\n  return res;\n}\n\nVmaAllocator_T::~VmaAllocator_T()\n{\n  VMA_ASSERT(m_Pools.IsEmpty());\n\n  for(size_t memTypeIndex = GetMemoryTypeCount(); memTypeIndex--; )\n  {\n    vma_delete(this, m_pBlockVectors[memTypeIndex]);\n  }\n}\n\nvoid VmaAllocator_T::ImportVulkanFunctions(const VmaVulkanFunctions* pVulkanFunctions)\n{\n#if VMA_STATIC_VULKAN_FUNCTIONS == 1\n  ImportVulkanFunctions_Static();\n#endif\n\n  if(pVulkanFunctions != VMA_NULL)\n  {\n    ImportVulkanFunctions_Custom(pVulkanFunctions);\n  }\n\n#if VMA_DYNAMIC_VULKAN_FUNCTIONS == 1\n  ImportVulkanFunctions_Dynamic();\n#endif\n\n  ValidateVulkanFunctions();\n}\n\n#if VMA_STATIC_VULKAN_FUNCTIONS == 1\n\nvoid VmaAllocator_T::ImportVulkanFunctions_Static()\n{\n  // Vulkan 1.0\n  m_VulkanFunctions.vkGetInstanceProcAddr = (PFN_vkGetInstanceProcAddr)vkGetInstanceProcAddr;\n  m_VulkanFunctions.vkGetDeviceProcAddr = (PFN_vkGetDeviceProcAddr)vkGetDeviceProcAddr;\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\n  // Vulkan 1.1\n#if VMA_VULKAN_VERSION >= 1001000\n  if(m_VulkanApiVersion >= VK_MAKE_VERSION(1, 1, 0))\n  {\n    m_VulkanFunctions.vkGetBufferMemoryRequirements2KHR = (PFN_vkGetBufferMemoryRequirements2)vkGetBufferMemoryRequirements2;\n    m_VulkanFunctions.vkGetImageMemoryRequirements2KHR = (PFN_vkGetImageMemoryRequirements2)vkGetImageMemoryRequirements2;\n    m_VulkanFunctions.vkBindBufferMemory2KHR = (PFN_vkBindBufferMemory2)vkBindBufferMemory2;\n    m_VulkanFunctions.vkBindImageMemory2KHR = (PFN_vkBindImageMemory2)vkBindImageMemory2;\n    m_VulkanFunctions.vkGetPhysicalDeviceMemoryProperties2KHR = (PFN_vkGetPhysicalDeviceMemoryProperties2)vkGetPhysicalDeviceMemoryProperties2;\n  }\n#endif\n\n#if VMA_VULKAN_VERSION >= 1003000\n  if(m_VulkanApiVersion >= VK_MAKE_VERSION(1, 3, 0))\n  {\n    m_VulkanFunctions.vkGetDeviceBufferMemoryRequirements = (PFN_vkGetDeviceBufferMemoryRequirements)vkGetDeviceBufferMemoryRequirements;\n    m_VulkanFunctions.vkGetDeviceImageMemoryRequirements = (PFN_vkGetDeviceImageMemoryRequirements)vkGetDeviceImageMemoryRequirements;\n  }\n#endif\n}\n\n#endif // VMA_STATIC_VULKAN_FUNCTIONS == 1\n\nvoid VmaAllocator_T::ImportVulkanFunctions_Custom(const VmaVulkanFunctions* pVulkanFunctions)\n{\n  VMA_ASSERT(pVulkanFunctions != VMA_NULL);\n\n#define VMA_COPY_IF_NOT_NULL(funcName) \\\n    if(pVulkanFunctions->funcName != VMA_NULL) m_VulkanFunctions.funcName = pVulkanFunctions->funcName;\n\n  VMA_COPY_IF_NOT_NULL(vkGetInstanceProcAddr);\n  VMA_COPY_IF_NOT_NULL(vkGetDeviceProcAddr);\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\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\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\n#if VMA_MEMORY_BUDGET\n  VMA_COPY_IF_NOT_NULL(vkGetPhysicalDeviceMemoryProperties2KHR);\n#endif\n\n#if VMA_VULKAN_VERSION >= 1003000\n  VMA_COPY_IF_NOT_NULL(vkGetDeviceBufferMemoryRequirements);\n  VMA_COPY_IF_NOT_NULL(vkGetDeviceImageMemoryRequirements);\n#endif\n\n#undef VMA_COPY_IF_NOT_NULL\n}\n\n#if VMA_DYNAMIC_VULKAN_FUNCTIONS == 1\n\nvoid VmaAllocator_T::ImportVulkanFunctions_Dynamic()\n{\n  VMA_ASSERT(m_VulkanFunctions.vkGetInstanceProcAddr && m_VulkanFunctions.vkGetDeviceProcAddr &&\n             \"To use VMA_DYNAMIC_VULKAN_FUNCTIONS in new versions of VMA you now have to pass \"\n             \"VmaVulkanFunctions::vkGetInstanceProcAddr and vkGetDeviceProcAddr as VmaAllocatorCreateInfo::pVulkanFunctions. \"\n             \"Other members can be null.\");\n\n#define VMA_FETCH_INSTANCE_FUNC(memberName, functionPointerType, functionNameString) \\\n    if(m_VulkanFunctions.memberName == VMA_NULL) \\\n        m_VulkanFunctions.memberName = \\\n            (functionPointerType)m_VulkanFunctions.vkGetInstanceProcAddr(m_hInstance, functionNameString);\n#define VMA_FETCH_DEVICE_FUNC(memberName, functionPointerType, functionNameString) \\\n    if(m_VulkanFunctions.memberName == VMA_NULL) \\\n        m_VulkanFunctions.memberName = \\\n            (functionPointerType)m_VulkanFunctions.vkGetDeviceProcAddr(m_hDevice, functionNameString);\n\n  VMA_FETCH_INSTANCE_FUNC(vkGetPhysicalDeviceProperties, PFN_vkGetPhysicalDeviceProperties, \"vkGetPhysicalDeviceProperties\");\n  VMA_FETCH_INSTANCE_FUNC(vkGetPhysicalDeviceMemoryProperties, PFN_vkGetPhysicalDeviceMemoryProperties, \"vkGetPhysicalDeviceMemoryProperties\");\n  VMA_FETCH_DEVICE_FUNC(vkAllocateMemory, PFN_vkAllocateMemory, \"vkAllocateMemory\");\n  VMA_FETCH_DEVICE_FUNC(vkFreeMemory, PFN_vkFreeMemory, \"vkFreeMemory\");\n  VMA_FETCH_DEVICE_FUNC(vkMapMemory, PFN_vkMapMemory, \"vkMapMemory\");\n  VMA_FETCH_DEVICE_FUNC(vkUnmapMemory, PFN_vkUnmapMemory, \"vkUnmapMemory\");\n  VMA_FETCH_DEVICE_FUNC(vkFlushMappedMemoryRanges, PFN_vkFlushMappedMemoryRanges, \"vkFlushMappedMemoryRanges\");\n  VMA_FETCH_DEVICE_FUNC(vkInvalidateMappedMemoryRanges, PFN_vkInvalidateMappedMemoryRanges, \"vkInvalidateMappedMemoryRanges\");\n  VMA_FETCH_DEVICE_FUNC(vkBindBufferMemory, PFN_vkBindBufferMemory, \"vkBindBufferMemory\");\n  VMA_FETCH_DEVICE_FUNC(vkBindImageMemory, PFN_vkBindImageMemory, \"vkBindImageMemory\");\n  VMA_FETCH_DEVICE_FUNC(vkGetBufferMemoryRequirements, PFN_vkGetBufferMemoryRequirements, \"vkGetBufferMemoryRequirements\");\n  VMA_FETCH_DEVICE_FUNC(vkGetImageMemoryRequirements, PFN_vkGetImageMemoryRequirements, \"vkGetImageMemoryRequirements\");\n  VMA_FETCH_DEVICE_FUNC(vkCreateBuffer, PFN_vkCreateBuffer, \"vkCreateBuffer\");\n  VMA_FETCH_DEVICE_FUNC(vkDestroyBuffer, PFN_vkDestroyBuffer, \"vkDestroyBuffer\");\n  VMA_FETCH_DEVICE_FUNC(vkCreateImage, PFN_vkCreateImage, \"vkCreateImage\");\n  VMA_FETCH_DEVICE_FUNC(vkDestroyImage, PFN_vkDestroyImage, \"vkDestroyImage\");\n  VMA_FETCH_DEVICE_FUNC(vkCmdCopyBuffer, PFN_vkCmdCopyBuffer, \"vkCmdCopyBuffer\");\n\n#if VMA_VULKAN_VERSION >= 1001000\n  if(m_VulkanApiVersion >= VK_MAKE_VERSION(1, 1, 0))\n  {\n    VMA_FETCH_DEVICE_FUNC(vkGetBufferMemoryRequirements2KHR, PFN_vkGetBufferMemoryRequirements2, \"vkGetBufferMemoryRequirements2\");\n    VMA_FETCH_DEVICE_FUNC(vkGetImageMemoryRequirements2KHR, PFN_vkGetImageMemoryRequirements2, \"vkGetImageMemoryRequirements2\");\n    VMA_FETCH_DEVICE_FUNC(vkBindBufferMemory2KHR, PFN_vkBindBufferMemory2, \"vkBindBufferMemory2\");\n    VMA_FETCH_DEVICE_FUNC(vkBindImageMemory2KHR, PFN_vkBindImageMemory2, \"vkBindImageMemory2\");\n    VMA_FETCH_INSTANCE_FUNC(vkGetPhysicalDeviceMemoryProperties2KHR, PFN_vkGetPhysicalDeviceMemoryProperties2, \"vkGetPhysicalDeviceMemoryProperties2\");\n  }\n#endif\n\n#if VMA_DEDICATED_ALLOCATION\n  if(m_UseKhrDedicatedAllocation)\n  {\n    VMA_FETCH_DEVICE_FUNC(vkGetBufferMemoryRequirements2KHR, PFN_vkGetBufferMemoryRequirements2KHR, \"vkGetBufferMemoryRequirements2KHR\");\n    VMA_FETCH_DEVICE_FUNC(vkGetImageMemoryRequirements2KHR, PFN_vkGetImageMemoryRequirements2KHR, \"vkGetImageMemoryRequirements2KHR\");\n  }\n#endif\n\n#if VMA_BIND_MEMORY2\n  if(m_UseKhrBindMemory2)\n  {\n    VMA_FETCH_DEVICE_FUNC(vkBindBufferMemory2KHR, PFN_vkBindBufferMemory2KHR, \"vkBindBufferMemory2KHR\");\n    VMA_FETCH_DEVICE_FUNC(vkBindImageMemory2KHR, PFN_vkBindImageMemory2KHR, \"vkBindImageMemory2KHR\");\n  }\n#endif // #if VMA_BIND_MEMORY2\n\n#if VMA_MEMORY_BUDGET\n  if(m_UseExtMemoryBudget)\n  {\n    VMA_FETCH_INSTANCE_FUNC(vkGetPhysicalDeviceMemoryProperties2KHR, PFN_vkGetPhysicalDeviceMemoryProperties2KHR, \"vkGetPhysicalDeviceMemoryProperties2KHR\");\n  }\n#endif // #if VMA_MEMORY_BUDGET\n\n#if VMA_VULKAN_VERSION >= 1003000\n  if(m_VulkanApiVersion >= VK_MAKE_VERSION(1, 3, 0))\n  {\n    VMA_FETCH_DEVICE_FUNC(vkGetDeviceBufferMemoryRequirements, PFN_vkGetDeviceBufferMemoryRequirements, \"vkGetDeviceBufferMemoryRequirements\");\n    VMA_FETCH_DEVICE_FUNC(vkGetDeviceImageMemoryRequirements, PFN_vkGetDeviceImageMemoryRequirements, \"vkGetDeviceImageMemoryRequirements\");\n  }\n#endif\n\n#undef VMA_FETCH_DEVICE_FUNC\n#undef VMA_FETCH_INSTANCE_FUNC\n}\n\n#endif // VMA_DYNAMIC_VULKAN_FUNCTIONS == 1\n\nvoid VmaAllocator_T::ValidateVulkanFunctions()\n{\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\n#if VMA_DEDICATED_ALLOCATION || VMA_VULKAN_VERSION >= 1001000\n  if(m_VulkanApiVersion >= VK_MAKE_VERSION(1, 1, 0) || m_UseKhrDedicatedAllocation)\n  {\n    VMA_ASSERT(m_VulkanFunctions.vkGetBufferMemoryRequirements2KHR != VMA_NULL);\n    VMA_ASSERT(m_VulkanFunctions.vkGetImageMemoryRequirements2KHR != VMA_NULL);\n  }\n#endif\n\n#if VMA_BIND_MEMORY2 || VMA_VULKAN_VERSION >= 1001000\n  if(m_VulkanApiVersion >= VK_MAKE_VERSION(1, 1, 0) || m_UseKhrBindMemory2)\n  {\n    VMA_ASSERT(m_VulkanFunctions.vkBindBufferMemory2KHR != VMA_NULL);\n    VMA_ASSERT(m_VulkanFunctions.vkBindImageMemory2KHR != VMA_NULL);\n  }\n#endif\n\n#if VMA_MEMORY_BUDGET || VMA_VULKAN_VERSION >= 1001000\n  if(m_UseExtMemoryBudget || m_VulkanApiVersion >= VK_MAKE_VERSION(1, 1, 0))\n  {\n    VMA_ASSERT(m_VulkanFunctions.vkGetPhysicalDeviceMemoryProperties2KHR != VMA_NULL);\n  }\n#endif\n\n#if VMA_VULKAN_VERSION >= 1003000\n  if(m_VulkanApiVersion >= VK_MAKE_VERSION(1, 3, 0))\n  {\n    VMA_ASSERT(m_VulkanFunctions.vkGetDeviceBufferMemoryRequirements != VMA_NULL);\n    VMA_ASSERT(m_VulkanFunctions.vkGetDeviceImageMemoryRequirements != 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    VmaPool pool,\n    VkDeviceSize size,\n    VkDeviceSize alignment,\n    bool dedicatedPreferred,\n    VkBuffer dedicatedBuffer,\n    VkImage dedicatedImage,\n    VkFlags dedicatedBufferImageUsage,\n    const VmaAllocationCreateInfo& createInfo,\n    uint32_t memTypeIndex,\n    VmaSuballocationType suballocType,\n    VmaDedicatedAllocationList& dedicatedAllocations,\n    VmaBlockVector& blockVector,\n    size_t allocationCount,\n    VmaAllocation* pAllocations)\n{\n  VMA_ASSERT(pAllocations != VMA_NULL);\n  VMA_DEBUG_LOG_FORMAT(\"  AllocateMemory: MemoryTypeIndex=%u, AllocationCount=%zu, Size=%llu\", memTypeIndex, allocationCount, size);\n\n  VmaAllocationCreateInfo finalCreateInfo = createInfo;\n  VkResult res = CalcMemTypeParams(\n      finalCreateInfo,\n      memTypeIndex,\n      size,\n      allocationCount);\n  if(res != VK_SUCCESS)\n    return res;\n\n  if((finalCreateInfo.flags & VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT) != 0)\n  {\n    return AllocateDedicatedMemory(\n        pool,\n        size,\n        suballocType,\n        dedicatedAllocations,\n        memTypeIndex,\n        (finalCreateInfo.flags & VMA_ALLOCATION_CREATE_MAPPED_BIT) != 0,\n        (finalCreateInfo.flags & VMA_ALLOCATION_CREATE_USER_DATA_COPY_STRING_BIT) != 0,\n        (finalCreateInfo.flags &\n         (VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT)) != 0,\n        (finalCreateInfo.flags & VMA_ALLOCATION_CREATE_CAN_ALIAS_BIT) != 0,\n        finalCreateInfo.pUserData,\n        finalCreateInfo.priority,\n        dedicatedBuffer,\n        dedicatedImage,\n        dedicatedBufferImageUsage,\n        allocationCount,\n        pAllocations,\n        blockVector.GetAllocationNextPtr());\n  }\n  else\n  {\n    const bool canAllocateDedicated =\n        (finalCreateInfo.flags & VMA_ALLOCATION_CREATE_NEVER_ALLOCATE_BIT) == 0 &&\n        (pool == VK_NULL_HANDLE || !blockVector.HasExplicitBlockSize());\n\n    if(canAllocateDedicated)\n    {\n      // Heuristics: Allocate dedicated memory if requested size if greater than half of preferred block size.\n      if(size > blockVector.GetPreferredBlockSize() / 2)\n      {\n        dedicatedPreferred = true;\n      }\n      // Protection against creating each allocation as dedicated when we reach or exceed heap size/budget,\n      // which can quickly deplete maxMemoryAllocationCount: Don't prefer dedicated allocations when above\n      // 3/4 of the maximum allocation count.\n      if(m_PhysicalDeviceProperties.limits.maxMemoryAllocationCount < UINT32_MAX / 4 &&\n          m_DeviceMemoryCount.load() > m_PhysicalDeviceProperties.limits.maxMemoryAllocationCount * 3 / 4)\n      {\n        dedicatedPreferred = false;\n      }\n\n      if(dedicatedPreferred)\n      {\n        res = AllocateDedicatedMemory(\n            pool,\n            size,\n            suballocType,\n            dedicatedAllocations,\n            memTypeIndex,\n            (finalCreateInfo.flags & VMA_ALLOCATION_CREATE_MAPPED_BIT) != 0,\n            (finalCreateInfo.flags & VMA_ALLOCATION_CREATE_USER_DATA_COPY_STRING_BIT) != 0,\n            (finalCreateInfo.flags &\n             (VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT)) != 0,\n            (finalCreateInfo.flags & VMA_ALLOCATION_CREATE_CAN_ALIAS_BIT) != 0,\n            finalCreateInfo.pUserData,\n            finalCreateInfo.priority,\n            dedicatedBuffer,\n            dedicatedImage,\n            dedicatedBufferImageUsage,\n            allocationCount,\n            pAllocations,\n            blockVector.GetAllocationNextPtr());\n        if(res == VK_SUCCESS)\n        {\n          // Succeeded: AllocateDedicatedMemory function already filled pMemory, nothing more to do here.\n          VMA_DEBUG_LOG(\"    Allocated as DedicatedMemory\");\n          return VK_SUCCESS;\n        }\n      }\n    }\n\n    res = blockVector.Allocate(\n        size,\n        alignment,\n        finalCreateInfo,\n        suballocType,\n        allocationCount,\n        pAllocations);\n    if(res == VK_SUCCESS)\n      return VK_SUCCESS;\n\n    // Try dedicated memory.\n    if(canAllocateDedicated && !dedicatedPreferred)\n    {\n      res = AllocateDedicatedMemory(\n          pool,\n          size,\n          suballocType,\n          dedicatedAllocations,\n          memTypeIndex,\n          (finalCreateInfo.flags & VMA_ALLOCATION_CREATE_MAPPED_BIT) != 0,\n          (finalCreateInfo.flags & VMA_ALLOCATION_CREATE_USER_DATA_COPY_STRING_BIT) != 0,\n          (finalCreateInfo.flags &\n           (VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT)) != 0,\n          (finalCreateInfo.flags & VMA_ALLOCATION_CREATE_CAN_ALIAS_BIT) != 0,\n          finalCreateInfo.pUserData,\n          finalCreateInfo.priority,\n          dedicatedBuffer,\n          dedicatedImage,\n          dedicatedBufferImageUsage,\n          allocationCount,\n          pAllocations,\n          blockVector.GetAllocationNextPtr());\n      if(res == VK_SUCCESS)\n      {\n        // Succeeded: AllocateDedicatedMemory function already filled pMemory, nothing more to do here.\n        VMA_DEBUG_LOG(\"    Allocated as DedicatedMemory\");\n        return VK_SUCCESS;\n      }\n    }\n    // Everything failed: Return error code.\n    VMA_DEBUG_LOG(\"    vkAllocateMemory FAILED\");\n    return res;\n  }\n}\n\nVkResult VmaAllocator_T::AllocateDedicatedMemory(\n    VmaPool pool,\n    VkDeviceSize size,\n    VmaSuballocationType suballocType,\n    VmaDedicatedAllocationList& dedicatedAllocations,\n    uint32_t memTypeIndex,\n    bool map,\n    bool isUserDataString,\n    bool isMappingAllowed,\n    bool canAliasMemory,\n    void* pUserData,\n    float priority,\n    VkBuffer dedicatedBuffer,\n    VkImage dedicatedImage,\n    VkFlags dedicatedBufferImageUsage,\n    size_t allocationCount,\n    VmaAllocation* pAllocations,\n    const void* pNextChain)\n{\n  VMA_ASSERT(allocationCount > 0 && pAllocations);\n\n  VkMemoryAllocateInfo allocInfo = { VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO };\n  allocInfo.memoryTypeIndex = memTypeIndex;\n  allocInfo.allocationSize = size;\n  allocInfo.pNext = pNextChain;\n\n#if VMA_DEDICATED_ALLOCATION || VMA_VULKAN_VERSION >= 1001000\n  VkMemoryDedicatedAllocateInfoKHR dedicatedAllocInfo = { VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO_KHR };\n  if(!canAliasMemory)\n  {\n    if(m_UseKhrDedicatedAllocation || m_VulkanApiVersion >= VK_MAKE_VERSION(1, 1, 0))\n    {\n      if(dedicatedBuffer != VK_NULL_HANDLE)\n      {\n        VMA_ASSERT(dedicatedImage == VK_NULL_HANDLE);\n        dedicatedAllocInfo.buffer = dedicatedBuffer;\n        VmaPnextChainPushFront(&allocInfo, &dedicatedAllocInfo);\n      }\n      else if(dedicatedImage != VK_NULL_HANDLE)\n      {\n        dedicatedAllocInfo.image = dedicatedImage;\n        VmaPnextChainPushFront(&allocInfo, &dedicatedAllocInfo);\n      }\n    }\n  }\n#endif // #if VMA_DEDICATED_ALLOCATION || VMA_VULKAN_VERSION >= 1001000\n\n#if VMA_BUFFER_DEVICE_ADDRESS\n  VkMemoryAllocateFlagsInfoKHR allocFlagsInfo = { VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_FLAGS_INFO_KHR };\n  if(m_UseKhrBufferDeviceAddress)\n  {\n    bool canContainBufferWithDeviceAddress = true;\n    if(dedicatedBuffer != VK_NULL_HANDLE)\n    {\n      canContainBufferWithDeviceAddress = dedicatedBufferImageUsage == UINT32_MAX || // Usage flags unknown\n                                          (dedicatedBufferImageUsage & VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT_EXT) != 0;\n    }\n    else if(dedicatedImage != VK_NULL_HANDLE)\n    {\n      canContainBufferWithDeviceAddress = false;\n    }\n    if(canContainBufferWithDeviceAddress)\n    {\n      allocFlagsInfo.flags = VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT_KHR;\n      VmaPnextChainPushFront(&allocInfo, &allocFlagsInfo);\n    }\n  }\n#endif // #if VMA_BUFFER_DEVICE_ADDRESS\n\n#if VMA_MEMORY_PRIORITY\n  VkMemoryPriorityAllocateInfoEXT priorityInfo = { VK_STRUCTURE_TYPE_MEMORY_PRIORITY_ALLOCATE_INFO_EXT };\n  if(m_UseExtMemoryPriority)\n  {\n    VMA_ASSERT(priority >= 0.f && priority <= 1.f);\n    priorityInfo.priority = priority;\n    VmaPnextChainPushFront(&allocInfo, &priorityInfo);\n  }\n#endif // #if VMA_MEMORY_PRIORITY\n\n#if VMA_EXTERNAL_MEMORY\n  // Attach VkExportMemoryAllocateInfoKHR if necessary.\n  VkExportMemoryAllocateInfoKHR exportMemoryAllocInfo = { VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO_KHR };\n  exportMemoryAllocInfo.handleTypes = GetExternalMemoryHandleTypeFlags(memTypeIndex);\n  if(exportMemoryAllocInfo.handleTypes != 0)\n  {\n    VmaPnextChainPushFront(&allocInfo, &exportMemoryAllocInfo);\n  }\n#endif // #if VMA_EXTERNAL_MEMORY\n\n  size_t allocIndex;\n  VkResult res = VK_SUCCESS;\n  for(allocIndex = 0; allocIndex < allocationCount; ++allocIndex)\n  {\n    res = AllocateDedicatedMemoryPage(\n        pool,\n        size,\n        suballocType,\n        memTypeIndex,\n        allocInfo,\n        map,\n        isUserDataString,\n        isMappingAllowed,\n        pUserData,\n        pAllocations + allocIndex);\n    if(res != VK_SUCCESS)\n    {\n      break;\n    }\n  }\n\n  if(res == VK_SUCCESS)\n  {\n    for (allocIndex = 0; allocIndex < allocationCount; ++allocIndex)\n    {\n      dedicatedAllocations.Register(pAllocations[allocIndex]);\n    }\n    VMA_DEBUG_LOG_FORMAT(\"    Allocated DedicatedMemory Count=%zu, MemoryTypeIndex=#%u\", allocationCount, memTypeIndex);\n  }\n  else\n  {\n    // Free all already created allocations.\n    while(allocIndex--)\n    {\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      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    VmaPool pool,\n    VkDeviceSize size,\n    VmaSuballocationType suballocType,\n    uint32_t memTypeIndex,\n    const VkMemoryAllocateInfo& allocInfo,\n    bool map,\n    bool isUserDataString,\n    bool isMappingAllowed,\n    void* pUserData,\n    VmaAllocation* pAllocation)\n{\n  VkDeviceMemory hMemory = VK_NULL_HANDLE;\n  VkResult res = AllocateVulkanMemory(&allocInfo, &hMemory);\n  if(res < 0)\n  {\n    VMA_DEBUG_LOG(\"    vkAllocateMemory FAILED\");\n    return res;\n  }\n\n  void* pMappedData = VMA_NULL;\n  if(map)\n  {\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    {\n      VMA_DEBUG_LOG(\"    vkMapMemory FAILED\");\n      FreeVulkanMemory(memTypeIndex, size, hMemory);\n      return res;\n    }\n  }\n\n  *pAllocation = m_AllocationObjectAllocator.Allocate(isMappingAllowed);\n  (*pAllocation)->InitDedicatedAllocation(pool, memTypeIndex, hMemory, suballocType, pMappedData, size);\n  if (isUserDataString)\n    (*pAllocation)->SetName(this, (const char*)pUserData);\n  else\n    (*pAllocation)->SetUserData(this, pUserData);\n  m_Budget.AddAllocation(MemoryTypeIndexToHeapIndex(memTypeIndex), size);\n  if(VMA_DEBUG_INITIALIZE_ALLOCATIONS)\n  {\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  {\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    VmaPnextChainPushFront(&memReq2, &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  }\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  {\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    VmaPnextChainPushFront(&memReq2, &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  }\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::FindMemoryTypeIndex(\n    uint32_t memoryTypeBits,\n    const VmaAllocationCreateInfo* pAllocationCreateInfo,\n    VkFlags bufImgUsage,\n    uint32_t* pMemoryTypeIndex) const\n{\n  memoryTypeBits &= GetGlobalMemoryTypeBits();\n\n  if(pAllocationCreateInfo->memoryTypeBits != 0)\n  {\n    memoryTypeBits &= pAllocationCreateInfo->memoryTypeBits;\n  }\n\n  VkMemoryPropertyFlags requiredFlags = 0, preferredFlags = 0, notPreferredFlags = 0;\n  if(!FindMemoryPreferences(\n          IsIntegratedGpu(),\n          *pAllocationCreateInfo,\n          bufImgUsage,\n          requiredFlags, preferredFlags, notPreferredFlags))\n  {\n    return VK_ERROR_FEATURE_NOT_PRESENT;\n  }\n\n  *pMemoryTypeIndex = UINT32_MAX;\n  uint32_t minCost = UINT32_MAX;\n  for(uint32_t memTypeIndex = 0, memTypeBit = 1;\n       memTypeIndex < GetMemoryTypeCount();\n       ++memTypeIndex, memTypeBit <<= 1)\n  {\n    // This memory type is acceptable according to memoryTypeBits bitmask.\n    if((memTypeBit & memoryTypeBits) != 0)\n    {\n      const VkMemoryPropertyFlags currFlags =\n          m_MemProps.memoryTypes[memTypeIndex].propertyFlags;\n      // This memory type contains requiredFlags.\n      if((requiredFlags & ~currFlags) == 0)\n      {\n        // Calculate cost as number of bits from preferredFlags not present in this memory type.\n        uint32_t currCost = VMA_COUNT_BITS_SET(preferredFlags & ~currFlags) +\n                            VMA_COUNT_BITS_SET(currFlags & notPreferredFlags);\n        // Remember memory type with lowest cost.\n        if(currCost < minCost)\n        {\n          *pMemoryTypeIndex = memTypeIndex;\n          if(currCost == 0)\n          {\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\nVkResult VmaAllocator_T::CalcMemTypeParams(\n    VmaAllocationCreateInfo& inoutCreateInfo,\n    uint32_t memTypeIndex,\n    VkDeviceSize size,\n    size_t allocationCount)\n{\n  // If memory type is not HOST_VISIBLE, disable MAPPED.\n  if((inoutCreateInfo.flags & VMA_ALLOCATION_CREATE_MAPPED_BIT) != 0 &&\n      (m_MemProps.memoryTypes[memTypeIndex].propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) == 0)\n  {\n    inoutCreateInfo.flags &= ~VMA_ALLOCATION_CREATE_MAPPED_BIT;\n  }\n\n  if((inoutCreateInfo.flags & VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT) != 0 &&\n      (inoutCreateInfo.flags & VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT) != 0)\n  {\n    const uint32_t heapIndex = MemoryTypeIndexToHeapIndex(memTypeIndex);\n    VmaBudget heapBudget = {};\n    GetHeapBudgets(&heapBudget, heapIndex, 1);\n    if(heapBudget.usage + size * allocationCount > heapBudget.budget)\n    {\n      return VK_ERROR_OUT_OF_DEVICE_MEMORY;\n    }\n  }\n  return VK_SUCCESS;\n}\n\nVkResult VmaAllocator_T::CalcAllocationParams(\n    VmaAllocationCreateInfo& inoutCreateInfo,\n    bool dedicatedRequired,\n    bool dedicatedPreferred)\n{\n  VMA_ASSERT((inoutCreateInfo.flags &\n              (VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT)) !=\n                 (VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT) &&\n             \"Specifying both flags VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT and VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT is incorrect.\");\n  VMA_ASSERT((((inoutCreateInfo.flags & VMA_ALLOCATION_CREATE_HOST_ACCESS_ALLOW_TRANSFER_INSTEAD_BIT) == 0 ||\n               (inoutCreateInfo.flags & (VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT)) != 0)) &&\n             \"Specifying VMA_ALLOCATION_CREATE_HOST_ACCESS_ALLOW_TRANSFER_INSTEAD_BIT requires also VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT or VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT.\");\n  if(inoutCreateInfo.usage == VMA_MEMORY_USAGE_AUTO || inoutCreateInfo.usage == VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE || inoutCreateInfo.usage == VMA_MEMORY_USAGE_AUTO_PREFER_HOST)\n  {\n    if((inoutCreateInfo.flags & VMA_ALLOCATION_CREATE_MAPPED_BIT) != 0)\n    {\n      VMA_ASSERT((inoutCreateInfo.flags & (VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT)) != 0 &&\n                 \"When using VMA_ALLOCATION_CREATE_MAPPED_BIT and usage = VMA_MEMORY_USAGE_AUTO*, you must also specify VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT or VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT.\");\n    }\n  }\n\n  // If memory is lazily allocated, it should be always dedicated.\n  if(dedicatedRequired ||\n      inoutCreateInfo.usage == VMA_MEMORY_USAGE_GPU_LAZILY_ALLOCATED)\n  {\n    inoutCreateInfo.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT;\n  }\n\n  if(inoutCreateInfo.pool != VK_NULL_HANDLE)\n  {\n    if(inoutCreateInfo.pool->m_BlockVector.HasExplicitBlockSize() &&\n        (inoutCreateInfo.flags & VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT) != 0)\n    {\n      VMA_ASSERT(0 && \"Specifying VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT while current custom pool doesn't support dedicated allocations.\");\n      return VK_ERROR_FEATURE_NOT_PRESENT;\n    }\n    inoutCreateInfo.priority = inoutCreateInfo.pool->m_BlockVector.GetPriority();\n  }\n\n  if((inoutCreateInfo.flags & VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT) != 0 &&\n      (inoutCreateInfo.flags & VMA_ALLOCATION_CREATE_NEVER_ALLOCATE_BIT) != 0)\n  {\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_FEATURE_NOT_PRESENT;\n  }\n\n  if(VMA_DEBUG_ALWAYS_DEDICATED_MEMORY &&\n      (inoutCreateInfo.flags & VMA_ALLOCATION_CREATE_NEVER_ALLOCATE_BIT) != 0)\n  {\n    inoutCreateInfo.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT;\n  }\n\n  // Non-auto USAGE values imply HOST_ACCESS flags.\n  // And so does VMA_MEMORY_USAGE_UNKNOWN because it is used with custom pools.\n  // Which specific flag is used doesn't matter. They change things only when used with VMA_MEMORY_USAGE_AUTO*.\n  // Otherwise they just protect from assert on mapping.\n  if(inoutCreateInfo.usage != VMA_MEMORY_USAGE_AUTO &&\n      inoutCreateInfo.usage != VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE &&\n      inoutCreateInfo.usage != VMA_MEMORY_USAGE_AUTO_PREFER_HOST)\n  {\n    if((inoutCreateInfo.flags & (VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT)) == 0)\n    {\n      inoutCreateInfo.flags |= VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT;\n    }\n  }\n\n  return VK_SUCCESS;\n}\n\nVkResult VmaAllocator_T::AllocateMemory(\n    const VkMemoryRequirements& vkMemReq,\n    bool requiresDedicatedAllocation,\n    bool prefersDedicatedAllocation,\n    VkBuffer dedicatedBuffer,\n    VkImage dedicatedImage,\n    VkFlags dedicatedBufferImageUsage,\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  {\n    return VK_ERROR_INITIALIZATION_FAILED;\n  }\n\n  VmaAllocationCreateInfo createInfoFinal = createInfo;\n  VkResult res = CalcAllocationParams(createInfoFinal, requiresDedicatedAllocation, prefersDedicatedAllocation);\n  if(res != VK_SUCCESS)\n    return res;\n\n  if(createInfoFinal.pool != VK_NULL_HANDLE)\n  {\n    VmaBlockVector& blockVector = createInfoFinal.pool->m_BlockVector;\n    return AllocateMemoryOfType(\n        createInfoFinal.pool,\n        vkMemReq.size,\n        vkMemReq.alignment,\n        prefersDedicatedAllocation,\n        dedicatedBuffer,\n        dedicatedImage,\n        dedicatedBufferImageUsage,\n        createInfoFinal,\n        blockVector.GetMemoryTypeIndex(),\n        suballocType,\n        createInfoFinal.pool->m_DedicatedAllocations,\n        blockVector,\n        allocationCount,\n        pAllocations);\n  }\n  else\n  {\n    // Bit mask of memory Vulkan types acceptable for this allocation.\n    uint32_t memoryTypeBits = vkMemReq.memoryTypeBits;\n    uint32_t memTypeIndex = UINT32_MAX;\n    res = FindMemoryTypeIndex(memoryTypeBits, &createInfoFinal, dedicatedBufferImageUsage, &memTypeIndex);\n    // Can't find any single memory type matching requirements. res is VK_ERROR_FEATURE_NOT_PRESENT.\n    if(res != VK_SUCCESS)\n      return res;\n    do\n    {\n      VmaBlockVector* blockVector = m_pBlockVectors[memTypeIndex];\n      VMA_ASSERT(blockVector && \"Trying to use unsupported memory type!\");\n      res = AllocateMemoryOfType(\n          VK_NULL_HANDLE,\n          vkMemReq.size,\n          vkMemReq.alignment,\n          requiresDedicatedAllocation || prefersDedicatedAllocation,\n          dedicatedBuffer,\n          dedicatedImage,\n          dedicatedBufferImageUsage,\n          createInfoFinal,\n          memTypeIndex,\n          suballocType,\n          m_DedicatedAllocations[memTypeIndex],\n          *blockVector,\n          allocationCount,\n          pAllocations);\n      // Allocation succeeded\n      if(res == VK_SUCCESS)\n        return VK_SUCCESS;\n\n      // Remove old memTypeIndex from list of possibilities.\n      memoryTypeBits &= ~(1u << memTypeIndex);\n      // Find alternative memTypeIndex.\n      res = FindMemoryTypeIndex(memoryTypeBits, &createInfoFinal, dedicatedBufferImageUsage, &memTypeIndex);\n    } while(res == VK_SUCCESS);\n\n    // No other matching memory type index could be found.\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\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  {\n    VmaAllocation allocation = pAllocations[allocIndex];\n\n    if(allocation != VK_NULL_HANDLE)\n    {\n      if(VMA_DEBUG_INITIALIZE_ALLOCATIONS)\n      {\n        FillAllocation(allocation, VMA_ALLOCATION_FILL_PATTERN_DESTROYED);\n      }\n\n      allocation->FreeName(this);\n\n      switch(allocation->GetType())\n      {\n        case VmaAllocation_T::ALLOCATION_TYPE_BLOCK:\n        {\n          VmaBlockVector* pBlockVector = VMA_NULL;\n          VmaPool hPool = allocation->GetParentPool();\n          if(hPool != VK_NULL_HANDLE)\n          {\n            pBlockVector = &hPool->m_BlockVector;\n          }\n          else\n          {\n            const uint32_t memTypeIndex = allocation->GetMemoryTypeIndex();\n            pBlockVector = m_pBlockVectors[memTypeIndex];\n            VMA_ASSERT(pBlockVector && \"Trying to free memory of unsupported type!\");\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}\n\nvoid VmaAllocator_T::CalculateStatistics(VmaTotalStatistics* pStats)\n{\n  // Initialize.\n  VmaClearDetailedStatistics(pStats->total);\n  for(uint32_t i = 0; i < VK_MAX_MEMORY_TYPES; ++i)\n    VmaClearDetailedStatistics(pStats->memoryType[i]);\n  for(uint32_t i = 0; i < VK_MAX_MEMORY_HEAPS; ++i)\n    VmaClearDetailedStatistics(pStats->memoryHeap[i]);\n\n  // Process default pools.\n  for(uint32_t memTypeIndex = 0; memTypeIndex < GetMemoryTypeCount(); ++memTypeIndex)\n  {\n    VmaBlockVector* const pBlockVector = m_pBlockVectors[memTypeIndex];\n    if (pBlockVector != VMA_NULL)\n      pBlockVector->AddDetailedStatistics(pStats->memoryType[memTypeIndex]);\n  }\n\n  // Process custom pools.\n  {\n    VmaMutexLockRead lock(m_PoolsMutex, m_UseMutex);\n    for(VmaPool pool = m_Pools.Front(); pool != VMA_NULL; pool = m_Pools.GetNext(pool))\n    {\n      VmaBlockVector& blockVector = pool->m_BlockVector;\n      const uint32_t memTypeIndex = blockVector.GetMemoryTypeIndex();\n      blockVector.AddDetailedStatistics(pStats->memoryType[memTypeIndex]);\n      pool->m_DedicatedAllocations.AddDetailedStatistics(pStats->memoryType[memTypeIndex]);\n    }\n  }\n\n  // Process dedicated allocations.\n  for(uint32_t memTypeIndex = 0; memTypeIndex < GetMemoryTypeCount(); ++memTypeIndex)\n  {\n    m_DedicatedAllocations[memTypeIndex].AddDetailedStatistics(pStats->memoryType[memTypeIndex]);\n  }\n\n  // Sum from memory types to memory heaps.\n  for(uint32_t memTypeIndex = 0; memTypeIndex < GetMemoryTypeCount(); ++memTypeIndex)\n  {\n    const uint32_t memHeapIndex = m_MemProps.memoryTypes[memTypeIndex].heapIndex;\n    VmaAddDetailedStatistics(pStats->memoryHeap[memHeapIndex], pStats->memoryType[memTypeIndex]);\n  }\n\n  // Sum from memory heaps to total.\n  for(uint32_t memHeapIndex = 0; memHeapIndex < GetMemoryHeapCount(); ++memHeapIndex)\n    VmaAddDetailedStatistics(pStats->total, pStats->memoryHeap[memHeapIndex]);\n\n  VMA_ASSERT(pStats->total.statistics.allocationCount == 0 ||\n             pStats->total.allocationSizeMax >= pStats->total.allocationSizeMin);\n  VMA_ASSERT(pStats->total.unusedRangeCount == 0 ||\n             pStats->total.unusedRangeSizeMax >= pStats->total.unusedRangeSizeMin);\n}\n\nvoid VmaAllocator_T::GetHeapBudgets(VmaBudget* outBudgets, uint32_t firstHeap, uint32_t heapCount)\n{\n#if VMA_MEMORY_BUDGET\n  if(m_UseExtMemoryBudget)\n  {\n    if(m_Budget.m_OperationsSinceBudgetFetch < 30)\n    {\n      VmaMutexLockRead lockRead(m_Budget.m_BudgetMutex, m_UseMutex);\n      for(uint32_t i = 0; i < heapCount; ++i, ++outBudgets)\n      {\n        const uint32_t heapIndex = firstHeap + i;\n\n        outBudgets->statistics.blockCount = m_Budget.m_BlockCount[heapIndex];\n        outBudgets->statistics.allocationCount = m_Budget.m_AllocationCount[heapIndex];\n        outBudgets->statistics.blockBytes = m_Budget.m_BlockBytes[heapIndex];\n        outBudgets->statistics.allocationBytes = m_Budget.m_AllocationBytes[heapIndex];\n\n        if(m_Budget.m_VulkanUsage[heapIndex] + outBudgets->statistics.blockBytes > m_Budget.m_BlockBytesAtBudgetFetch[heapIndex])\n        {\n          outBudgets->usage = m_Budget.m_VulkanUsage[heapIndex] +\n                              outBudgets->statistics.blockBytes - m_Budget.m_BlockBytesAtBudgetFetch[heapIndex];\n        }\n        else\n        {\n          outBudgets->usage = 0;\n        }\n\n        // Have to take MIN with heap size because explicit HeapSizeLimit is included in it.\n        outBudgets->budget = VMA_MIN(\n            m_Budget.m_VulkanBudget[heapIndex], m_MemProps.memoryHeaps[heapIndex].size);\n      }\n    }\n    else\n    {\n      UpdateVulkanBudget(); // Outside of mutex lock\n      GetHeapBudgets(outBudgets, firstHeap, heapCount); // Recursion\n    }\n  }\n  else\n#endif\n  {\n    for(uint32_t i = 0; i < heapCount; ++i, ++outBudgets)\n    {\n      const uint32_t heapIndex = firstHeap + i;\n\n      outBudgets->statistics.blockCount = m_Budget.m_BlockCount[heapIndex];\n      outBudgets->statistics.allocationCount = m_Budget.m_AllocationCount[heapIndex];\n      outBudgets->statistics.blockBytes = m_Budget.m_BlockBytes[heapIndex];\n      outBudgets->statistics.allocationBytes = m_Budget.m_AllocationBytes[heapIndex];\n\n      outBudgets->usage = outBudgets->statistics.blockBytes;\n      outBudgets->budget = m_MemProps.memoryHeaps[heapIndex].size * 8 / 10; // 80% heuristics.\n    }\n  }\n}\n\nvoid VmaAllocator_T::GetAllocationInfo(VmaAllocation hAllocation, VmaAllocationInfo* pAllocationInfo)\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  pAllocationInfo->pName = hAllocation->GetName();\n}\n\nVkResult VmaAllocator_T::CreatePool(const VmaPoolCreateInfo* pCreateInfo, VmaPool* pPool)\n{\n  VMA_DEBUG_LOG_FORMAT(\"  CreatePool: MemoryTypeIndex=%u, flags=%u\", pCreateInfo->memoryTypeIndex, pCreateInfo->flags);\n\n  VmaPoolCreateInfo newCreateInfo = *pCreateInfo;\n\n  // Protection against uninitialized new structure member. If garbage data are left there, this pointer dereference would crash.\n  if(pCreateInfo->pMemoryAllocateNext)\n  {\n    VMA_ASSERT(((const VkBaseInStructure*)pCreateInfo->pMemoryAllocateNext)->sType != 0);\n  }\n\n  if(newCreateInfo.maxBlockCount == 0)\n  {\n    newCreateInfo.maxBlockCount = SIZE_MAX;\n  }\n  if(newCreateInfo.minBlockCount > newCreateInfo.maxBlockCount)\n  {\n    return VK_ERROR_INITIALIZATION_FAILED;\n  }\n  // Memory type index out of range or forbidden.\n  if(pCreateInfo->memoryTypeIndex >= GetMemoryTypeCount() ||\n      ((1u << pCreateInfo->memoryTypeIndex) & m_GlobalMemoryTypeBits) == 0)\n  {\n    return VK_ERROR_FEATURE_NOT_PRESENT;\n  }\n  if(newCreateInfo.minAllocationAlignment > 0)\n  {\n    VMA_ASSERT(VmaIsPow2(newCreateInfo.minAllocationAlignment));\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  {\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    m_Pools.PushBack(*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    m_Pools.Remove(pool);\n  }\n\n  vma_delete(this, pool);\n}\n\nvoid VmaAllocator_T::GetPoolStatistics(VmaPool pool, VmaStatistics* pPoolStats)\n{\n  VmaClearStatistics(*pPoolStats);\n  pool->m_BlockVector.AddStatistics(*pPoolStats);\n  pool->m_DedicatedAllocations.AddStatistics(*pPoolStats);\n}\n\nvoid VmaAllocator_T::CalculatePoolStatistics(VmaPool pool, VmaDetailedStatistics* pPoolStats)\n{\n  VmaClearDetailedStatistics(*pPoolStats);\n  pool->m_BlockVector.AddDetailedStatistics(*pPoolStats);\n  pool->m_DedicatedAllocations.AddDetailedStatistics(*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  {\n    UpdateVulkanBudget();\n  }\n#endif // #if VMA_MEMORY_BUDGET\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  {\n    VmaBlockVector* const pBlockVector = m_pBlockVectors[memTypeIndex];\n    if(pBlockVector != VMA_NULL)\n    {\n      VkResult localRes = pBlockVector->CheckCorruption();\n      switch(localRes)\n      {\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(VmaPool pool = m_Pools.Front(); pool != VMA_NULL; pool = m_Pools.GetNext(pool))\n    {\n      if(((1u << pool->m_BlockVector.GetMemoryTypeIndex()) & memoryTypeBits) != 0)\n      {\n        VkResult localRes = pool->m_BlockVector.CheckCorruption();\n        switch(localRes)\n        {\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\nVkResult VmaAllocator_T::AllocateVulkanMemory(const VkMemoryAllocateInfo* pAllocateInfo, VkDeviceMemory* pMemory)\n{\n  AtomicTransactionalIncrement<uint32_t> deviceMemoryCountIncrement;\n  const uint64_t prevDeviceMemoryCount = deviceMemoryCountIncrement.Increment(&m_DeviceMemoryCount);\n#if VMA_DEBUG_DONT_EXCEED_MAX_MEMORY_ALLOCATION_COUNT\n  if(prevDeviceMemoryCount >= m_PhysicalDeviceProperties.limits.maxMemoryAllocationCount)\n  {\n    return VK_ERROR_TOO_MANY_OBJECTS;\n  }\n#endif\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  {\n    const VkDeviceSize heapSize = m_MemProps.memoryHeaps[heapIndex].size;\n    VkDeviceSize blockBytes = m_Budget.m_BlockBytes[heapIndex];\n    for(;;)\n    {\n      const VkDeviceSize blockBytesAfterAllocation = blockBytes + pAllocateInfo->allocationSize;\n      if(blockBytesAfterAllocation > heapSize)\n      {\n        return VK_ERROR_OUT_OF_DEVICE_MEMORY;\n      }\n      if(m_Budget.m_BlockBytes[heapIndex].compare_exchange_strong(blockBytes, blockBytesAfterAllocation))\n      {\n        break;\n      }\n    }\n  }\n  else\n  {\n    m_Budget.m_BlockBytes[heapIndex] += pAllocateInfo->allocationSize;\n  }\n  ++m_Budget.m_BlockCount[heapIndex];\n\n  // VULKAN CALL vkAllocateMemory.\n  VkResult res = (*m_VulkanFunctions.vkAllocateMemory)(m_hDevice, pAllocateInfo, GetAllocationCallbacks(), pMemory);\n\n  if(res == VK_SUCCESS)\n  {\n#if VMA_MEMORY_BUDGET\n    ++m_Budget.m_OperationsSinceBudgetFetch;\n#endif\n\n    // Informative callback.\n    if(m_DeviceMemoryCallbacks.pfnAllocate != VMA_NULL)\n    {\n      (*m_DeviceMemoryCallbacks.pfnAllocate)(this, pAllocateInfo->memoryTypeIndex, *pMemory, pAllocateInfo->allocationSize, m_DeviceMemoryCallbacks.pUserData);\n    }\n\n    deviceMemoryCountIncrement.Commit();\n  }\n  else\n  {\n    --m_Budget.m_BlockCount[heapIndex];\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  {\n    (*m_DeviceMemoryCallbacks.pfnFree)(this, memoryType, hMemory, size, m_DeviceMemoryCallbacks.pUserData);\n  }\n\n  // VULKAN CALL vkFreeMemory.\n  (*m_VulkanFunctions.vkFreeMemory)(m_hDevice, hMemory, GetAllocationCallbacks());\n\n  const uint32_t heapIndex = MemoryTypeIndexToHeapIndex(memoryType);\n  --m_Budget.m_BlockCount[heapIndex];\n  m_Budget.m_BlockBytes[heapIndex] -= size;\n\n  --m_DeviceMemoryCount;\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  {\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    {\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    }\n    else\n#endif // #if VMA_VULKAN_VERSION >= 1001000 || VMA_BIND_MEMORY2\n    {\n      return VK_ERROR_EXTENSION_NOT_PRESENT;\n    }\n  }\n  else\n  {\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  {\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    {\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    }\n    else\n#endif // #if VMA_BIND_MEMORY2\n    {\n      return VK_ERROR_EXTENSION_NOT_PRESENT;\n    }\n  }\n  else\n  {\n    return (*m_VulkanFunctions.vkBindImageMemory)(m_hDevice, image, memory, memoryOffset);\n  }\n}\n\nVkResult VmaAllocator_T::Map(VmaAllocation hAllocation, void** ppData)\n{\n  switch(hAllocation->GetType())\n  {\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      {\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  {\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_ERROR_UNKNOWN;\n  switch(hAllocation->GetType())\n  {\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.\");\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_ERROR_UNKNOWN;\n  switch(hAllocation->GetType())\n  {\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.\");\n      res = pBlock->BindImageMemory(this, hAllocation, allocationLocalOffset, hImage, pNext);\n      break;\n    }\n    default:\n      VMA_ASSERT(0);\n  }\n  return res;\n}\n\nVkResult VmaAllocator_T::FlushOrInvalidateAllocation(\n    VmaAllocation hAllocation,\n    VkDeviceSize offset, VkDeviceSize size,\n    VMA_CACHE_OPERATION op)\n{\n  VkResult res = VK_SUCCESS;\n\n  VkMappedMemoryRange memRange = {};\n  if(GetFlushOrInvalidateRange(hAllocation, offset, size, memRange))\n  {\n    switch(op)\n    {\n      case VMA_CACHE_FLUSH:\n        res = (*GetVulkanFunctions().vkFlushMappedMemoryRanges)(m_hDevice, 1, &memRange);\n        break;\n      case VMA_CACHE_INVALIDATE:\n        res = (*GetVulkanFunctions().vkInvalidateMappedMemoryRanges)(m_hDevice, 1, &memRange);\n        break;\n      default:\n        VMA_ASSERT(0);\n    }\n  }\n  // else: Just ignore this call.\n  return res;\n}\n\nVkResult VmaAllocator_T::FlushOrInvalidateAllocations(\n    uint32_t allocationCount,\n    const VmaAllocation* allocations,\n    const VkDeviceSize* offsets, const VkDeviceSize* sizes,\n    VMA_CACHE_OPERATION op)\n{\n  typedef VmaStlAllocator<VkMappedMemoryRange> RangeAllocator;\n  typedef VmaSmallVector<VkMappedMemoryRange, RangeAllocator, 16> RangeVector;\n  RangeVector ranges = RangeVector(RangeAllocator(GetAllocationCallbacks()));\n\n  for(uint32_t allocIndex = 0; allocIndex < allocationCount; ++allocIndex)\n  {\n    const VmaAllocation alloc = allocations[allocIndex];\n    const VkDeviceSize offset = offsets != VMA_NULL ? offsets[allocIndex] : 0;\n    const VkDeviceSize size = sizes != VMA_NULL ? sizes[allocIndex] : VK_WHOLE_SIZE;\n    VkMappedMemoryRange newRange;\n    if(GetFlushOrInvalidateRange(alloc, offset, size, newRange))\n    {\n      ranges.push_back(newRange);\n    }\n  }\n\n  VkResult res = VK_SUCCESS;\n  if(!ranges.empty())\n  {\n    switch(op)\n    {\n      case VMA_CACHE_FLUSH:\n        res = (*GetVulkanFunctions().vkFlushMappedMemoryRanges)(m_hDevice, (uint32_t)ranges.size(), ranges.data());\n        break;\n      case VMA_CACHE_INVALIDATE:\n        res = (*GetVulkanFunctions().vkInvalidateMappedMemoryRanges)(m_hDevice, (uint32_t)ranges.size(), ranges.data());\n        break;\n      default:\n        VMA_ASSERT(0);\n    }\n  }\n  // else: Just ignore this call.\n  return res;\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  VmaPool parentPool = allocation->GetParentPool();\n  if(parentPool == VK_NULL_HANDLE)\n  {\n    // Default pool\n    m_DedicatedAllocations[memTypeIndex].Unregister(allocation);\n  }\n  else\n  {\n    // Custom pool\n    parentPool->m_DedicatedAllocations.Unregister(allocation);\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  m_Budget.RemoveAllocation(MemoryTypeIndexToHeapIndex(allocation->GetMemoryTypeIndex()), allocation->GetSize());\n  m_AllocationObjectAllocator.Free(allocation);\n\n  VMA_DEBUG_LOG_FORMAT(\"    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  {\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\nuint32_t VmaAllocator_T::CalculateGlobalMemoryTypeBits() const\n{\n  // Make sure memory information is already fetched.\n  VMA_ASSERT(GetMemoryTypeCount() > 0);\n\n  uint32_t memoryTypeBits = UINT32_MAX;\n\n  if(!m_UseAmdDeviceCoherentMemory)\n  {\n    // Exclude memory types that have VK_MEMORY_PROPERTY_DEVICE_COHERENT_BIT_AMD.\n    for(uint32_t memTypeIndex = 0; memTypeIndex < GetMemoryTypeCount(); ++memTypeIndex)\n    {\n      if((m_MemProps.memoryTypes[memTypeIndex].propertyFlags & VK_MEMORY_PROPERTY_DEVICE_COHERENT_BIT_AMD_COPY) != 0)\n      {\n        memoryTypeBits &= ~(1u << memTypeIndex);\n      }\n    }\n  }\n\n  return memoryTypeBits;\n}\n\nbool VmaAllocator_T::GetFlushOrInvalidateRange(\n    VmaAllocation allocation,\n    VkDeviceSize offset, VkDeviceSize size,\n    VkMappedMemoryRange& outRange) const\n{\n  const uint32_t memTypeIndex = allocation->GetMemoryTypeIndex();\n  if(size > 0 && IsMemoryTypeNonCoherent(memTypeIndex))\n  {\n    const VkDeviceSize nonCoherentAtomSize = m_PhysicalDeviceProperties.limits.nonCoherentAtomSize;\n    const VkDeviceSize allocationSize = allocation->GetSize();\n    VMA_ASSERT(offset <= allocationSize);\n\n    outRange.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE;\n    outRange.pNext = VMA_NULL;\n    outRange.memory = allocation->GetMemory();\n\n    switch(allocation->GetType())\n    {\n      case VmaAllocation_T::ALLOCATION_TYPE_DEDICATED:\n        outRange.offset = VmaAlignDown(offset, nonCoherentAtomSize);\n        if(size == VK_WHOLE_SIZE)\n        {\n          outRange.size = allocationSize - outRange.offset;\n        }\n        else\n        {\n          VMA_ASSERT(offset + size <= allocationSize);\n          outRange.size = VMA_MIN(\n              VmaAlignUp(size + (offset - outRange.offset), nonCoherentAtomSize),\n              allocationSize - outRange.offset);\n        }\n        break;\n      case VmaAllocation_T::ALLOCATION_TYPE_BLOCK:\n      {\n        // 1. Still within this allocation.\n        outRange.offset = VmaAlignDown(offset, nonCoherentAtomSize);\n        if(size == VK_WHOLE_SIZE)\n        {\n          size = allocationSize - offset;\n        }\n        else\n        {\n          VMA_ASSERT(offset + size <= allocationSize);\n        }\n        outRange.size = VmaAlignUp(size + (offset - outRange.offset), nonCoherentAtomSize);\n\n        // 2. Adjust to whole block.\n        const VkDeviceSize allocationOffset = allocation->GetOffset();\n        VMA_ASSERT(allocationOffset % nonCoherentAtomSize == 0);\n        const VkDeviceSize blockSize = allocation->GetBlock()->m_pMetadata->GetSize();\n        outRange.offset += allocationOffset;\n        outRange.size = VMA_MIN(outRange.size, blockSize - outRange.offset);\n\n        break;\n      }\n      default:\n        VMA_ASSERT(0);\n    }\n    return true;\n  }\n  return false;\n}\n\n#if VMA_MEMORY_BUDGET\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  VmaPnextChainPushFront(&memProps, &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    {\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      // Some bugged drivers return the budget incorrectly, e.g. 0 or much bigger than heap size.\n      if(m_Budget.m_VulkanBudget[heapIndex] == 0)\n      {\n        m_Budget.m_VulkanBudget[heapIndex] = m_MemProps.memoryHeaps[heapIndex].size * 8 / 10; // 80% heuristics.\n      }\n      else if(m_Budget.m_VulkanBudget[heapIndex] > m_MemProps.memoryHeaps[heapIndex].size)\n      {\n        m_Budget.m_VulkanBudget[heapIndex] = m_MemProps.memoryHeaps[heapIndex].size;\n      }\n      if(m_Budget.m_VulkanUsage[heapIndex] == 0 && m_Budget.m_BlockBytesAtBudgetFetch[heapIndex] > 0)\n      {\n        m_Budget.m_VulkanUsage[heapIndex] = m_Budget.m_BlockBytesAtBudgetFetch[heapIndex];\n      }\n    }\n    m_Budget.m_OperationsSinceBudgetFetch = 0;\n  }\n}\n#endif // VMA_MEMORY_BUDGET\n\nvoid VmaAllocator_T::FillAllocation(const VmaAllocation hAllocation, uint8_t pattern)\n{\n  if(VMA_DEBUG_INITIALIZE_ALLOCATIONS &&\n      hAllocation->IsMappingAllowed() &&\n      (m_MemProps.memoryTypes[hAllocation->GetMemoryTypeIndex()].propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) != 0)\n  {\n    void* pData = VMA_NULL;\n    VkResult res = Map(hAllocation, &pData);\n    if(res == VK_SUCCESS)\n    {\n      memset(pData, (int)pattern, (size_t)hAllocation->GetSize());\n      FlushOrInvalidateAllocation(hAllocation, 0, VK_WHOLE_SIZE, VMA_CACHE_FLUSH);\n      Unmap(hAllocation);\n    }\n    else\n    {\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  {\n    memoryTypeBits = CalculateGpuDefragmentationMemoryTypeBits();\n    m_GpuDefragmentationMemoryTypeBits.store(memoryTypeBits);\n  }\n  return memoryTypeBits;\n}\n\n#if VMA_STATS_STRING_ENABLED\nvoid VmaAllocator_T::PrintDetailedMap(VmaJsonWriter& json)\n{\n  json.WriteString(\"DefaultPools\");\n  json.BeginObject();\n  {\n    for (uint32_t memTypeIndex = 0; memTypeIndex < GetMemoryTypeCount(); ++memTypeIndex)\n    {\n      VmaBlockVector* pBlockVector = m_pBlockVectors[memTypeIndex];\n      VmaDedicatedAllocationList& dedicatedAllocList = m_DedicatedAllocations[memTypeIndex];\n      if (pBlockVector != VMA_NULL)\n      {\n        json.BeginString(\"Type \");\n        json.ContinueString(memTypeIndex);\n        json.EndString();\n        json.BeginObject();\n        {\n          json.WriteString(\"PreferredBlockSize\");\n          json.WriteNumber(pBlockVector->GetPreferredBlockSize());\n\n          json.WriteString(\"Blocks\");\n          pBlockVector->PrintDetailedMap(json);\n\n          json.WriteString(\"DedicatedAllocations\");\n          dedicatedAllocList.BuildStatsString(json);\n        }\n        json.EndObject();\n      }\n    }\n  }\n  json.EndObject();\n\n  json.WriteString(\"CustomPools\");\n  json.BeginObject();\n  {\n    VmaMutexLockRead lock(m_PoolsMutex, m_UseMutex);\n    if (!m_Pools.IsEmpty())\n    {\n      for (uint32_t memTypeIndex = 0; memTypeIndex < GetMemoryTypeCount(); ++memTypeIndex)\n      {\n        bool displayType = true;\n        size_t index = 0;\n        for (VmaPool pool = m_Pools.Front(); pool != VMA_NULL; pool = m_Pools.GetNext(pool))\n        {\n          VmaBlockVector& blockVector = pool->m_BlockVector;\n          if (blockVector.GetMemoryTypeIndex() == memTypeIndex)\n          {\n            if (displayType)\n            {\n              json.BeginString(\"Type \");\n              json.ContinueString(memTypeIndex);\n              json.EndString();\n              json.BeginArray();\n              displayType = false;\n            }\n\n            json.BeginObject();\n            {\n              json.WriteString(\"Name\");\n              json.BeginString();\n              json.ContinueString((uint64_t)index++);\n              if (pool->GetName())\n              {\n                json.ContinueString(\" - \");\n                json.ContinueString(pool->GetName());\n              }\n              json.EndString();\n\n              json.WriteString(\"PreferredBlockSize\");\n              json.WriteNumber(blockVector.GetPreferredBlockSize());\n\n              json.WriteString(\"Blocks\");\n              blockVector.PrintDetailedMap(json);\n\n              json.WriteString(\"DedicatedAllocations\");\n              pool->m_DedicatedAllocations.BuildStatsString(json);\n            }\n            json.EndObject();\n          }\n        }\n\n        if (!displayType)\n          json.EndArray();\n      }\n    }\n  }\n  json.EndObject();\n}\n#endif // VMA_STATS_STRING_ENABLED\n#endif // _VMA_ALLOCATOR_T_FUNCTIONS\n\n\n#ifndef _VMA_PUBLIC_INTERFACE\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) <= 3));\n  VMA_DEBUG_LOG(\"vmaCreateAllocator\");\n  *pAllocator = vma_new(pCreateInfo->pAllocationCallbacks, VmaAllocator_T)(pCreateInfo);\n  VkResult result = (*pAllocator)->Init(pCreateInfo);\n  if(result < 0)\n  {\n    vma_delete(pCreateInfo->pAllocationCallbacks, *pAllocator);\n    *pAllocator = VK_NULL_HANDLE;\n  }\n  return result;\n}\n\nVMA_CALL_PRE void VMA_CALL_POST vmaDestroyAllocator(\n    VmaAllocator allocator)\n{\n  if(allocator != VK_NULL_HANDLE)\n  {\n    VMA_DEBUG_LOG(\"vmaDestroyAllocator\");\n    VkAllocationCallbacks allocationCallbacks = allocator->m_AllocationCallbacks; // Have to copy the callbacks when destroying.\n    vma_delete(&allocationCallbacks, allocator);\n  }\n}\n\nVMA_CALL_PRE void VMA_CALL_POST vmaGetAllocatorInfo(VmaAllocator allocator, VmaAllocatorInfo* pAllocatorInfo)\n{\n  VMA_ASSERT(allocator && pAllocatorInfo);\n  pAllocatorInfo->instance = allocator->m_hInstance;\n  pAllocatorInfo->physicalDevice = allocator->GetPhysicalDevice();\n  pAllocatorInfo->device = allocator->m_hDevice;\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\n  VMA_DEBUG_GLOBAL_MUTEX_LOCK\n\n  allocator->SetCurrentFrameIndex(frameIndex);\n}\n\nVMA_CALL_PRE void VMA_CALL_POST vmaCalculateStatistics(\n    VmaAllocator allocator,\n    VmaTotalStatistics* pStats)\n{\n  VMA_ASSERT(allocator && pStats);\n  VMA_DEBUG_GLOBAL_MUTEX_LOCK\n  allocator->CalculateStatistics(pStats);\n}\n\nVMA_CALL_PRE void VMA_CALL_POST vmaGetHeapBudgets(\n    VmaAllocator allocator,\n    VmaBudget* pBudgets)\n{\n  VMA_ASSERT(allocator && pBudgets);\n  VMA_DEBUG_GLOBAL_MUTEX_LOCK\n  allocator->GetHeapBudgets(pBudgets, 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->GetAllocationCallbacks());\n  {\n    VmaBudget budgets[VK_MAX_MEMORY_HEAPS];\n    allocator->GetHeapBudgets(budgets, 0, allocator->GetMemoryHeapCount());\n\n    VmaTotalStatistics stats;\n    allocator->CalculateStatistics(&stats);\n\n    VmaJsonWriter json(allocator->GetAllocationCallbacks(), sb);\n    json.BeginObject();\n    {\n      json.WriteString(\"General\");\n      json.BeginObject();\n      {\n        const VkPhysicalDeviceProperties& deviceProperties = allocator->m_PhysicalDeviceProperties;\n        const VkPhysicalDeviceMemoryProperties& memoryProperties = allocator->m_MemProps;\n\n        json.WriteString(\"API\");\n        json.WriteString(\"Vulkan\");\n\n        json.WriteString(\"apiVersion\");\n        json.BeginString();\n        json.ContinueString(VK_VERSION_MAJOR(deviceProperties.apiVersion));\n        json.ContinueString(\".\");\n        json.ContinueString(VK_VERSION_MINOR(deviceProperties.apiVersion));\n        json.ContinueString(\".\");\n        json.ContinueString(VK_VERSION_PATCH(deviceProperties.apiVersion));\n        json.EndString();\n\n        json.WriteString(\"GPU\");\n        json.WriteString(deviceProperties.deviceName);\n        json.WriteString(\"deviceType\");\n        json.WriteNumber(static_cast<uint32_t>(deviceProperties.deviceType));\n\n        json.WriteString(\"maxMemoryAllocationCount\");\n        json.WriteNumber(deviceProperties.limits.maxMemoryAllocationCount);\n        json.WriteString(\"bufferImageGranularity\");\n        json.WriteNumber(deviceProperties.limits.bufferImageGranularity);\n        json.WriteString(\"nonCoherentAtomSize\");\n        json.WriteNumber(deviceProperties.limits.nonCoherentAtomSize);\n\n        json.WriteString(\"memoryHeapCount\");\n        json.WriteNumber(memoryProperties.memoryHeapCount);\n        json.WriteString(\"memoryTypeCount\");\n        json.WriteNumber(memoryProperties.memoryTypeCount);\n      }\n      json.EndObject();\n    }\n    {\n      json.WriteString(\"Total\");\n      VmaPrintDetailedStatistics(json, stats.total);\n    }\n    {\n      json.WriteString(\"MemoryInfo\");\n      json.BeginObject();\n      {\n        for (uint32_t heapIndex = 0; heapIndex < allocator->GetMemoryHeapCount(); ++heapIndex)\n        {\n          json.BeginString(\"Heap \");\n          json.ContinueString(heapIndex);\n          json.EndString();\n          json.BeginObject();\n          {\n            const VkMemoryHeap& heapInfo = allocator->m_MemProps.memoryHeaps[heapIndex];\n            json.WriteString(\"Flags\");\n            json.BeginArray(true);\n            {\n              if (heapInfo.flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT)\n                json.WriteString(\"DEVICE_LOCAL\");\n#if VMA_VULKAN_VERSION >= 1001000\n              if (heapInfo.flags & VK_MEMORY_HEAP_MULTI_INSTANCE_BIT)\n                json.WriteString(\"MULTI_INSTANCE\");\n#endif\n\n              VkMemoryHeapFlags flags = heapInfo.flags &\n                                        ~(VK_MEMORY_HEAP_DEVICE_LOCAL_BIT\n#if VMA_VULKAN_VERSION >= 1001000\n                                          | VK_MEMORY_HEAP_MULTI_INSTANCE_BIT\n#endif\n                                        );\n              if (flags != 0)\n                json.WriteNumber(flags);\n            }\n            json.EndArray();\n\n            json.WriteString(\"Size\");\n            json.WriteNumber(heapInfo.size);\n\n            json.WriteString(\"Budget\");\n            json.BeginObject();\n            {\n              json.WriteString(\"BudgetBytes\");\n              json.WriteNumber(budgets[heapIndex].budget);\n              json.WriteString(\"UsageBytes\");\n              json.WriteNumber(budgets[heapIndex].usage);\n            }\n            json.EndObject();\n\n            json.WriteString(\"Stats\");\n            VmaPrintDetailedStatistics(json, stats.memoryHeap[heapIndex]);\n\n            json.WriteString(\"MemoryPools\");\n            json.BeginObject();\n            {\n              for (uint32_t typeIndex = 0; typeIndex < allocator->GetMemoryTypeCount(); ++typeIndex)\n              {\n                if (allocator->MemoryTypeIndexToHeapIndex(typeIndex) == heapIndex)\n                {\n                  json.BeginString(\"Type \");\n                  json.ContinueString(typeIndex);\n                  json.EndString();\n                  json.BeginObject();\n                  {\n                    json.WriteString(\"Flags\");\n                    json.BeginArray(true);\n                    {\n                      VkMemoryPropertyFlags flags = allocator->m_MemProps.memoryTypes[typeIndex].propertyFlags;\n                      if (flags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)\n                        json.WriteString(\"DEVICE_LOCAL\");\n                      if (flags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT)\n                        json.WriteString(\"HOST_VISIBLE\");\n                      if (flags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)\n                        json.WriteString(\"HOST_COHERENT\");\n                      if (flags & VK_MEMORY_PROPERTY_HOST_CACHED_BIT)\n                        json.WriteString(\"HOST_CACHED\");\n                      if (flags & VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT)\n                        json.WriteString(\"LAZILY_ALLOCATED\");\n#if VMA_VULKAN_VERSION >= 1001000\n                      if (flags & VK_MEMORY_PROPERTY_PROTECTED_BIT)\n                        json.WriteString(\"PROTECTED\");\n#endif\n#if VK_AMD_device_coherent_memory\n                      if (flags & VK_MEMORY_PROPERTY_DEVICE_COHERENT_BIT_AMD_COPY)\n                        json.WriteString(\"DEVICE_COHERENT_AMD\");\n                      if (flags & VK_MEMORY_PROPERTY_DEVICE_UNCACHED_BIT_AMD_COPY)\n                        json.WriteString(\"DEVICE_UNCACHED_AMD\");\n#endif\n\n                      flags &= ~(VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT\n#if VMA_VULKAN_VERSION >= 1001000\n                                 | VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT\n#endif\n#if VK_AMD_device_coherent_memory\n                                 | VK_MEMORY_PROPERTY_DEVICE_COHERENT_BIT_AMD_COPY\n                                 | VK_MEMORY_PROPERTY_DEVICE_UNCACHED_BIT_AMD_COPY\n#endif\n                                 | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT\n                                 | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT\n                                 | VK_MEMORY_PROPERTY_HOST_CACHED_BIT);\n                      if (flags != 0)\n                        json.WriteNumber(flags);\n                    }\n                    json.EndArray();\n\n                    json.WriteString(\"Stats\");\n                    VmaPrintDetailedStatistics(json, stats.memoryType[typeIndex]);\n                  }\n                  json.EndObject();\n                }\n              }\n\n            }\n            json.EndObject();\n          }\n          json.EndObject();\n        }\n      }\n      json.EndObject();\n    }\n\n    if (detailedMap == VK_TRUE)\n      allocator->PrintDetailedMap(json);\n\n    json.EndObject();\n  }\n\n  *ppStatsString = VmaCreateStringCopy(allocator->GetAllocationCallbacks(), sb.GetData(), sb.GetLength());\n}\n\nVMA_CALL_PRE void VMA_CALL_POST vmaFreeStatsString(\n    VmaAllocator allocator,\n    char* pStatsString)\n{\n  if(pStatsString != VMA_NULL)\n  {\n    VMA_ASSERT(allocator);\n    VmaFreeString(allocator->GetAllocationCallbacks(), pStatsString);\n  }\n}\n\n#endif // 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  return allocator->FindMemoryTypeIndex(memoryTypeBits, pAllocationCreateInfo, UINT32_MAX, pMemoryTypeIndex);\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  const VmaVulkanFunctions* funcs = &allocator->GetVulkanFunctions();\n  VkResult res;\n\n#if VMA_VULKAN_VERSION >= 1003000\n  if(funcs->vkGetDeviceBufferMemoryRequirements)\n  {\n    // Can query straight from VkBufferCreateInfo :)\n    VkDeviceBufferMemoryRequirements devBufMemReq = {VK_STRUCTURE_TYPE_DEVICE_BUFFER_MEMORY_REQUIREMENTS};\n    devBufMemReq.pCreateInfo = pBufferCreateInfo;\n\n    VkMemoryRequirements2 memReq = {VK_STRUCTURE_TYPE_MEMORY_REQUIREMENTS_2};\n    (*funcs->vkGetDeviceBufferMemoryRequirements)(hDev, &devBufMemReq, &memReq);\n\n    res = allocator->FindMemoryTypeIndex(\n        memReq.memoryRequirements.memoryTypeBits, pAllocationCreateInfo, pBufferCreateInfo->usage, pMemoryTypeIndex);\n  }\n  else\n#endif // #if VMA_VULKAN_VERSION >= 1003000\n  {\n    // Must create a dummy buffer to query :(\n    VkBuffer hBuffer = VK_NULL_HANDLE;\n    res = funcs->vkCreateBuffer(\n        hDev, pBufferCreateInfo, allocator->GetAllocationCallbacks(), &hBuffer);\n    if(res == VK_SUCCESS)\n    {\n      VkMemoryRequirements memReq = {};\n      funcs->vkGetBufferMemoryRequirements(hDev, hBuffer, &memReq);\n\n      res = allocator->FindMemoryTypeIndex(\n          memReq.memoryTypeBits, pAllocationCreateInfo, pBufferCreateInfo->usage, pMemoryTypeIndex);\n\n      funcs->vkDestroyBuffer(\n          hDev, hBuffer, allocator->GetAllocationCallbacks());\n    }\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  const VmaVulkanFunctions* funcs = &allocator->GetVulkanFunctions();\n  VkResult res;\n\n#if VMA_VULKAN_VERSION >= 1003000\n  if(funcs->vkGetDeviceImageMemoryRequirements)\n  {\n    // Can query straight from VkImageCreateInfo :)\n    VkDeviceImageMemoryRequirements devImgMemReq = {VK_STRUCTURE_TYPE_DEVICE_IMAGE_MEMORY_REQUIREMENTS};\n    devImgMemReq.pCreateInfo = pImageCreateInfo;\n    VMA_ASSERT(pImageCreateInfo->tiling != VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT_COPY && (pImageCreateInfo->flags & VK_IMAGE_CREATE_DISJOINT_BIT_COPY) == 0 &&\n               \"Cannot use this VkImageCreateInfo with vmaFindMemoryTypeIndexForImageInfo as I don't know what to pass as VkDeviceImageMemoryRequirements::planeAspect.\");\n\n    VkMemoryRequirements2 memReq = {VK_STRUCTURE_TYPE_MEMORY_REQUIREMENTS_2};\n    (*funcs->vkGetDeviceImageMemoryRequirements)(hDev, &devImgMemReq, &memReq);\n\n    res = allocator->FindMemoryTypeIndex(\n        memReq.memoryRequirements.memoryTypeBits, pAllocationCreateInfo, pImageCreateInfo->usage, pMemoryTypeIndex);\n  }\n  else\n#endif // #if VMA_VULKAN_VERSION >= 1003000\n  {\n    // Must create a dummy image to query :(\n    VkImage hImage = VK_NULL_HANDLE;\n    res = funcs->vkCreateImage(\n        hDev, pImageCreateInfo, allocator->GetAllocationCallbacks(), &hImage);\n    if(res == VK_SUCCESS)\n    {\n      VkMemoryRequirements memReq = {};\n      funcs->vkGetImageMemoryRequirements(hDev, hImage, &memReq);\n\n      res = allocator->FindMemoryTypeIndex(\n          memReq.memoryTypeBits, pAllocationCreateInfo, pImageCreateInfo->usage, pMemoryTypeIndex);\n\n      funcs->vkDestroyImage(\n          hDev, hImage, allocator->GetAllocationCallbacks());\n    }\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  return allocator->CreatePool(pCreateInfo, pPool);\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  {\n    return;\n  }\n\n  VMA_DEBUG_LOG(\"vmaDestroyPool\");\n\n  VMA_DEBUG_GLOBAL_MUTEX_LOCK\n\n  allocator->DestroyPool(pool);\n}\n\nVMA_CALL_PRE void VMA_CALL_POST vmaGetPoolStatistics(\n    VmaAllocator allocator,\n    VmaPool pool,\n    VmaStatistics* pPoolStats)\n{\n  VMA_ASSERT(allocator && pool && pPoolStats);\n\n  VMA_DEBUG_GLOBAL_MUTEX_LOCK\n\n  allocator->GetPoolStatistics(pool, pPoolStats);\n}\n\nVMA_CALL_PRE void VMA_CALL_POST vmaCalculatePoolStatistics(\n    VmaAllocator allocator,\n    VmaPool pool,\n    VmaDetailedStatistics* pPoolStats)\n{\n  VMA_ASSERT(allocator && pool && pPoolStats);\n\n  VMA_DEBUG_GLOBAL_MUTEX_LOCK\n\n  allocator->CalculatePoolStatistics(pool, pPoolStats);\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 && ppName);\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\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      UINT32_MAX, // dedicatedBufferImageUsage\n      *pCreateInfo,\n      VMA_SUBALLOCATION_TYPE_UNKNOWN,\n      1, // allocationCount\n      pAllocation);\n\n  if(pAllocationInfo != VMA_NULL && result == VK_SUCCESS)\n  {\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  {\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      UINT32_MAX, // dedicatedBufferImageUsage\n      *pCreateInfo,\n      VMA_SUBALLOCATION_TYPE_UNKNOWN,\n      allocationCount,\n      pAllocations);\n\n  if(pAllocationInfo != VMA_NULL && result == VK_SUCCESS)\n  {\n    for(size_t i = 0; i < allocationCount; ++i)\n    {\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      UINT32_MAX, // dedicatedBufferImageUsage\n      *pCreateInfo,\n      VMA_SUBALLOCATION_TYPE_BUFFER,\n      1, // allocationCount\n      pAllocation);\n\n  if(pAllocationInfo && result == VK_SUCCESS)\n  {\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      UINT32_MAX, // dedicatedBufferImageUsage\n      *pCreateInfo,\n      VMA_SUBALLOCATION_TYPE_IMAGE_UNKNOWN,\n      1, // allocationCount\n      pAllocation);\n\n  if(pAllocationInfo && result == VK_SUCCESS)\n  {\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  {\n    return;\n  }\n\n  VMA_DEBUG_LOG(\"vmaFreeMemory\");\n\n  VMA_DEBUG_GLOBAL_MUTEX_LOCK\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    const VmaAllocation* pAllocations)\n{\n  if(allocationCount == 0)\n  {\n    return;\n  }\n\n  VMA_ASSERT(allocator);\n\n  VMA_DEBUG_LOG(\"vmaFreeMemoryPages\");\n\n  VMA_DEBUG_GLOBAL_MUTEX_LOCK\n\n  allocator->FreeMemory(allocationCount, pAllocations);\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  allocator->GetAllocationInfo(allocation, pAllocationInfo);\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\nVMA_CALL_PRE void VMA_CALL_POST vmaSetAllocationName(\n    VmaAllocator VMA_NOT_NULL allocator,\n    VmaAllocation VMA_NOT_NULL allocation,\n    const char* VMA_NULLABLE pName)\n{\n  allocation->SetName(allocator, pName);\n}\n\nVMA_CALL_PRE void VMA_CALL_POST vmaGetAllocationMemoryProperties(\n    VmaAllocator VMA_NOT_NULL allocator,\n    VmaAllocation VMA_NOT_NULL allocation,\n    VkMemoryPropertyFlags* VMA_NOT_NULL pFlags)\n{\n  VMA_ASSERT(allocator && allocation && pFlags);\n  const uint32_t memTypeIndex = allocation->GetMemoryTypeIndex();\n  *pFlags = allocator->m_MemProps.memoryTypes[memTypeIndex].propertyFlags;\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  return allocator->Map(allocation, ppData);\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  allocator->Unmap(allocation);\n}\n\nVMA_CALL_PRE VkResult VMA_CALL_POST vmaFlushAllocation(\n    VmaAllocator allocator,\n    VmaAllocation allocation,\n    VkDeviceSize offset,\n    VkDeviceSize size)\n{\n  VMA_ASSERT(allocator && allocation);\n\n  VMA_DEBUG_LOG(\"vmaFlushAllocation\");\n\n  VMA_DEBUG_GLOBAL_MUTEX_LOCK\n\n  const VkResult res = allocator->FlushOrInvalidateAllocation(allocation, offset, size, VMA_CACHE_FLUSH);\n\n  return res;\n}\n\nVMA_CALL_PRE VkResult VMA_CALL_POST vmaInvalidateAllocation(\n    VmaAllocator allocator,\n    VmaAllocation allocation,\n    VkDeviceSize offset,\n    VkDeviceSize size)\n{\n  VMA_ASSERT(allocator && allocation);\n\n  VMA_DEBUG_LOG(\"vmaInvalidateAllocation\");\n\n  VMA_DEBUG_GLOBAL_MUTEX_LOCK\n\n  const VkResult res = allocator->FlushOrInvalidateAllocation(allocation, offset, size, VMA_CACHE_INVALIDATE);\n\n  return res;\n}\n\nVMA_CALL_PRE VkResult VMA_CALL_POST vmaFlushAllocations(\n    VmaAllocator allocator,\n    uint32_t allocationCount,\n    const VmaAllocation* allocations,\n    const VkDeviceSize* offsets,\n    const VkDeviceSize* sizes)\n{\n  VMA_ASSERT(allocator);\n\n  if(allocationCount == 0)\n  {\n    return VK_SUCCESS;\n  }\n\n  VMA_ASSERT(allocations);\n\n  VMA_DEBUG_LOG(\"vmaFlushAllocations\");\n\n  VMA_DEBUG_GLOBAL_MUTEX_LOCK\n\n  const VkResult res = allocator->FlushOrInvalidateAllocations(allocationCount, allocations, offsets, sizes, VMA_CACHE_FLUSH);\n\n  return res;\n}\n\nVMA_CALL_PRE VkResult VMA_CALL_POST vmaInvalidateAllocations(\n    VmaAllocator allocator,\n    uint32_t allocationCount,\n    const VmaAllocation* allocations,\n    const VkDeviceSize* offsets,\n    const VkDeviceSize* sizes)\n{\n  VMA_ASSERT(allocator);\n\n  if(allocationCount == 0)\n  {\n    return VK_SUCCESS;\n  }\n\n  VMA_ASSERT(allocations);\n\n  VMA_DEBUG_LOG(\"vmaInvalidateAllocations\");\n\n  VMA_DEBUG_GLOBAL_MUTEX_LOCK\n\n  const VkResult res = allocator->FlushOrInvalidateAllocations(allocationCount, allocations, offsets, sizes, VMA_CACHE_INVALIDATE);\n\n  return res;\n}\n\nVMA_CALL_PRE VkResult VMA_CALL_POST vmaCheckCorruption(\n    VmaAllocator allocator,\n    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 vmaBeginDefragmentation(\n    VmaAllocator allocator,\n    const VmaDefragmentationInfo* pInfo,\n    VmaDefragmentationContext* pContext)\n{\n  VMA_ASSERT(allocator && pInfo && pContext);\n\n  VMA_DEBUG_LOG(\"vmaBeginDefragmentation\");\n\n  if (pInfo->pool != VMA_NULL)\n  {\n    // Check if run on supported algorithms\n    if (pInfo->pool->m_BlockVector.GetAlgorithm() & VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT)\n      return VK_ERROR_FEATURE_NOT_PRESENT;\n  }\n\n  VMA_DEBUG_GLOBAL_MUTEX_LOCK\n\n  *pContext = vma_new(allocator, VmaDefragmentationContext_T)(allocator, *pInfo);\n  return VK_SUCCESS;\n}\n\nVMA_CALL_PRE void VMA_CALL_POST vmaEndDefragmentation(\n    VmaAllocator allocator,\n    VmaDefragmentationContext context,\n    VmaDefragmentationStats* pStats)\n{\n  VMA_ASSERT(allocator && context);\n\n  VMA_DEBUG_LOG(\"vmaEndDefragmentation\");\n\n  VMA_DEBUG_GLOBAL_MUTEX_LOCK\n\n  if (pStats)\n    context->GetStats(*pStats);\n  vma_delete(allocator, context);\n}\n\nVMA_CALL_PRE VkResult VMA_CALL_POST vmaBeginDefragmentationPass(\n    VmaAllocator VMA_NOT_NULL allocator,\n    VmaDefragmentationContext VMA_NOT_NULL context,\n    VmaDefragmentationPassMoveInfo* VMA_NOT_NULL pPassInfo)\n{\n  VMA_ASSERT(context && pPassInfo);\n\n  VMA_DEBUG_LOG(\"vmaBeginDefragmentationPass\");\n\n  VMA_DEBUG_GLOBAL_MUTEX_LOCK\n\n  return context->DefragmentPassBegin(*pPassInfo);\n}\n\nVMA_CALL_PRE VkResult VMA_CALL_POST vmaEndDefragmentationPass(\n    VmaAllocator VMA_NOT_NULL allocator,\n    VmaDefragmentationContext VMA_NOT_NULL context,\n    VmaDefragmentationPassMoveInfo* VMA_NOT_NULL pPassInfo)\n{\n  VMA_ASSERT(context && pPassInfo);\n\n  VMA_DEBUG_LOG(\"vmaEndDefragmentationPass\");\n\n  VMA_DEBUG_GLOBAL_MUTEX_LOCK\n\n  return context->DefragmentPassEnd(*pPassInfo);\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  {\n    return VK_ERROR_INITIALIZATION_FAILED;\n  }\n  if((pBufferCreateInfo->usage & VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT_COPY) != 0 &&\n      !allocator->m_UseKhrBufferDeviceAddress)\n  {\n    VMA_ASSERT(0 && \"Creating a buffer with VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT is not valid if VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT was not used.\");\n    return VK_ERROR_INITIALIZATION_FAILED;\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  {\n    // 2. vkGetBufferMemoryRequirements.\n    VkMemoryRequirements vkMemReq = {};\n    bool requiresDedicatedAllocation = false;\n    bool prefersDedicatedAllocation  = false;\n    allocator->GetBufferMemoryRequirements(*pBuffer, vkMemReq,\n                                           requiresDedicatedAllocation, prefersDedicatedAllocation);\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        pBufferCreateInfo->usage, // dedicatedBufferImageUsage\n        *pAllocationCreateInfo,\n        VMA_SUBALLOCATION_TYPE_BUFFER,\n        1, // allocationCount\n        pAllocation);\n\n    if(res >= 0)\n    {\n      // 3. Bind buffer with memory.\n      if((pAllocationCreateInfo->flags & VMA_ALLOCATION_CREATE_DONT_BIND_BIT) == 0)\n      {\n        res = allocator->BindBufferMemory(*pAllocation, 0, *pBuffer, VMA_NULL);\n      }\n      if(res >= 0)\n      {\n// All steps succeeded.\n#if VMA_STATS_STRING_ENABLED\n        (*pAllocation)->InitBufferImageUsage(pBufferCreateInfo->usage);\n#endif\n        if(pAllocationInfo != VMA_NULL)\n        {\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 VkResult VMA_CALL_POST vmaCreateBufferWithAlignment(\n    VmaAllocator allocator,\n    const VkBufferCreateInfo* pBufferCreateInfo,\n    const VmaAllocationCreateInfo* pAllocationCreateInfo,\n    VkDeviceSize minAlignment,\n    VkBuffer* pBuffer,\n    VmaAllocation* pAllocation,\n    VmaAllocationInfo* pAllocationInfo)\n{\n  VMA_ASSERT(allocator && pBufferCreateInfo && pAllocationCreateInfo && VmaIsPow2(minAlignment) && pBuffer && pAllocation);\n\n  if(pBufferCreateInfo->size == 0)\n  {\n    return VK_ERROR_INITIALIZATION_FAILED;\n  }\n  if((pBufferCreateInfo->usage & VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT_COPY) != 0 &&\n      !allocator->m_UseKhrBufferDeviceAddress)\n  {\n    VMA_ASSERT(0 && \"Creating a buffer with VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT is not valid if VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT was not used.\");\n    return VK_ERROR_INITIALIZATION_FAILED;\n  }\n\n  VMA_DEBUG_LOG(\"vmaCreateBufferWithAlignment\");\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  {\n    // 2. vkGetBufferMemoryRequirements.\n    VkMemoryRequirements vkMemReq = {};\n    bool requiresDedicatedAllocation = false;\n    bool prefersDedicatedAllocation  = false;\n    allocator->GetBufferMemoryRequirements(*pBuffer, vkMemReq,\n                                           requiresDedicatedAllocation, prefersDedicatedAllocation);\n\n    // 2a. Include minAlignment\n    vkMemReq.alignment = VMA_MAX(vkMemReq.alignment, minAlignment);\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        pBufferCreateInfo->usage, // dedicatedBufferImageUsage\n        *pAllocationCreateInfo,\n        VMA_SUBALLOCATION_TYPE_BUFFER,\n        1, // allocationCount\n        pAllocation);\n\n    if(res >= 0)\n    {\n      // 3. Bind buffer with memory.\n      if((pAllocationCreateInfo->flags & VMA_ALLOCATION_CREATE_DONT_BIND_BIT) == 0)\n      {\n        res = allocator->BindBufferMemory(*pAllocation, 0, *pBuffer, VMA_NULL);\n      }\n      if(res >= 0)\n      {\n// All steps succeeded.\n#if VMA_STATS_STRING_ENABLED\n        (*pAllocation)->InitBufferImageUsage(pBufferCreateInfo->usage);\n#endif\n        if(pAllocationInfo != VMA_NULL)\n        {\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 VkResult VMA_CALL_POST vmaCreateAliasingBuffer(\n    VmaAllocator VMA_NOT_NULL allocator,\n    VmaAllocation VMA_NOT_NULL allocation,\n    const VkBufferCreateInfo* VMA_NOT_NULL pBufferCreateInfo,\n    VkBuffer VMA_NULLABLE_NON_DISPATCHABLE* VMA_NOT_NULL pBuffer)\n{\n  return vmaCreateAliasingBuffer2(allocator, allocation, 0, pBufferCreateInfo, pBuffer);\n}\n\nVMA_CALL_PRE VkResult VMA_CALL_POST vmaCreateAliasingBuffer2(\n    VmaAllocator VMA_NOT_NULL allocator,\n    VmaAllocation VMA_NOT_NULL allocation,\n    VkDeviceSize allocationLocalOffset,\n    const VkBufferCreateInfo* VMA_NOT_NULL pBufferCreateInfo,\n    VkBuffer VMA_NULLABLE_NON_DISPATCHABLE* VMA_NOT_NULL pBuffer)\n{\n  VMA_ASSERT(allocator && pBufferCreateInfo && pBuffer && allocation);\n  VMA_ASSERT(allocationLocalOffset + pBufferCreateInfo->size <= allocation->GetSize());\n\n  VMA_DEBUG_LOG(\"vmaCreateAliasingBuffer2\");\n\n  *pBuffer = VK_NULL_HANDLE;\n\n  if (pBufferCreateInfo->size == 0)\n  {\n    return VK_ERROR_INITIALIZATION_FAILED;\n  }\n  if ((pBufferCreateInfo->usage & VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT_COPY) != 0 &&\n      !allocator->m_UseKhrBufferDeviceAddress)\n  {\n    VMA_ASSERT(0 && \"Creating a buffer with VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT is not valid if VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT was not used.\");\n    return VK_ERROR_INITIALIZATION_FAILED;\n  }\n\n  VMA_DEBUG_GLOBAL_MUTEX_LOCK\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  {\n    // 2. Bind buffer with memory.\n    res = allocator->BindBufferMemory(allocation, allocationLocalOffset, *pBuffer, VMA_NULL);\n    if (res >= 0)\n    {\n      return VK_SUCCESS;\n    }\n    (*allocator->GetVulkanFunctions().vkDestroyBuffer)(allocator->m_hDevice, *pBuffer, allocator->GetAllocationCallbacks());\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  {\n    return;\n  }\n\n  VMA_DEBUG_LOG(\"vmaDestroyBuffer\");\n\n  VMA_DEBUG_GLOBAL_MUTEX_LOCK\n\n  if(buffer != VK_NULL_HANDLE)\n  {\n    (*allocator->GetVulkanFunctions().vkDestroyBuffer)(allocator->m_hDevice, buffer, allocator->GetAllocationCallbacks());\n  }\n\n  if(allocation != VK_NULL_HANDLE)\n  {\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  {\n    return VK_ERROR_INITIALIZATION_FAILED;\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  {\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        pImageCreateInfo->usage, // dedicatedBufferImageUsage\n        *pAllocationCreateInfo,\n        suballocType,\n        1, // allocationCount\n        pAllocation);\n\n    if(res >= 0)\n    {\n      // 3. Bind image with memory.\n      if((pAllocationCreateInfo->flags & VMA_ALLOCATION_CREATE_DONT_BIND_BIT) == 0)\n      {\n        res = allocator->BindImageMemory(*pAllocation, 0, *pImage, VMA_NULL);\n      }\n      if(res >= 0)\n      {\n// All steps succeeded.\n#if VMA_STATS_STRING_ENABLED\n        (*pAllocation)->InitBufferImageUsage(pImageCreateInfo->usage);\n#endif\n        if(pAllocationInfo != VMA_NULL)\n        {\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 VkResult VMA_CALL_POST vmaCreateAliasingImage(\n    VmaAllocator VMA_NOT_NULL allocator,\n    VmaAllocation VMA_NOT_NULL allocation,\n    const VkImageCreateInfo* VMA_NOT_NULL pImageCreateInfo,\n    VkImage VMA_NULLABLE_NON_DISPATCHABLE* VMA_NOT_NULL pImage)\n{\n  return vmaCreateAliasingImage2(allocator, allocation, 0, pImageCreateInfo, pImage);\n}\n\nVMA_CALL_PRE VkResult VMA_CALL_POST vmaCreateAliasingImage2(\n    VmaAllocator VMA_NOT_NULL allocator,\n    VmaAllocation VMA_NOT_NULL allocation,\n    VkDeviceSize allocationLocalOffset,\n    const VkImageCreateInfo* VMA_NOT_NULL pImageCreateInfo,\n    VkImage VMA_NULLABLE_NON_DISPATCHABLE* VMA_NOT_NULL pImage)\n{\n  VMA_ASSERT(allocator && pImageCreateInfo && pImage && allocation);\n\n  *pImage = VK_NULL_HANDLE;\n\n  VMA_DEBUG_LOG(\"vmaCreateImage2\");\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  {\n    return VK_ERROR_INITIALIZATION_FAILED;\n  }\n\n  VMA_DEBUG_GLOBAL_MUTEX_LOCK\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  {\n    // 2. Bind image with memory.\n    res = allocator->BindImageMemory(allocation, allocationLocalOffset, *pImage, VMA_NULL);\n    if (res >= 0)\n    {\n      return VK_SUCCESS;\n    }\n    (*allocator->GetVulkanFunctions().vkDestroyImage)(allocator->m_hDevice, *pImage, allocator->GetAllocationCallbacks());\n  }\n  return res;\n}\n\nVMA_CALL_PRE void VMA_CALL_POST vmaDestroyImage(\n    VmaAllocator VMA_NOT_NULL allocator,\n    VkImage VMA_NULLABLE_NON_DISPATCHABLE image,\n    VmaAllocation VMA_NULLABLE allocation)\n{\n  VMA_ASSERT(allocator);\n\n  if(image == VK_NULL_HANDLE && allocation == VK_NULL_HANDLE)\n  {\n    return;\n  }\n\n  VMA_DEBUG_LOG(\"vmaDestroyImage\");\n\n  VMA_DEBUG_GLOBAL_MUTEX_LOCK\n\n  if(image != VK_NULL_HANDLE)\n  {\n    (*allocator->GetVulkanFunctions().vkDestroyImage)(allocator->m_hDevice, image, allocator->GetAllocationCallbacks());\n  }\n  if(allocation != VK_NULL_HANDLE)\n  {\n    allocator->FreeMemory(\n        1, // allocationCount\n        &allocation);\n  }\n}\n\nVMA_CALL_PRE VkResult VMA_CALL_POST vmaCreateVirtualBlock(\n    const VmaVirtualBlockCreateInfo* VMA_NOT_NULL pCreateInfo,\n    VmaVirtualBlock VMA_NULLABLE * VMA_NOT_NULL pVirtualBlock)\n{\n  VMA_ASSERT(pCreateInfo && pVirtualBlock);\n  VMA_ASSERT(pCreateInfo->size > 0);\n  VMA_DEBUG_LOG(\"vmaCreateVirtualBlock\");\n  VMA_DEBUG_GLOBAL_MUTEX_LOCK;\n  *pVirtualBlock = vma_new(pCreateInfo->pAllocationCallbacks, VmaVirtualBlock_T)(*pCreateInfo);\n  VkResult res = (*pVirtualBlock)->Init();\n  if(res < 0)\n  {\n    vma_delete(pCreateInfo->pAllocationCallbacks, *pVirtualBlock);\n    *pVirtualBlock = VK_NULL_HANDLE;\n  }\n  return res;\n}\n\nVMA_CALL_PRE void VMA_CALL_POST vmaDestroyVirtualBlock(VmaVirtualBlock VMA_NULLABLE virtualBlock)\n{\n  if(virtualBlock != VK_NULL_HANDLE)\n  {\n    VMA_DEBUG_LOG(\"vmaDestroyVirtualBlock\");\n    VMA_DEBUG_GLOBAL_MUTEX_LOCK;\n    VkAllocationCallbacks allocationCallbacks = virtualBlock->m_AllocationCallbacks; // Have to copy the callbacks when destroying.\n    vma_delete(&allocationCallbacks, virtualBlock);\n  }\n}\n\nVMA_CALL_PRE VkBool32 VMA_CALL_POST vmaIsVirtualBlockEmpty(VmaVirtualBlock VMA_NOT_NULL virtualBlock)\n{\n  VMA_ASSERT(virtualBlock != VK_NULL_HANDLE);\n  VMA_DEBUG_LOG(\"vmaIsVirtualBlockEmpty\");\n  VMA_DEBUG_GLOBAL_MUTEX_LOCK;\n  return virtualBlock->IsEmpty() ? VK_TRUE : VK_FALSE;\n}\n\nVMA_CALL_PRE void VMA_CALL_POST vmaGetVirtualAllocationInfo(VmaVirtualBlock VMA_NOT_NULL virtualBlock,\n                                                            VmaVirtualAllocation VMA_NOT_NULL_NON_DISPATCHABLE allocation, VmaVirtualAllocationInfo* VMA_NOT_NULL pVirtualAllocInfo)\n{\n  VMA_ASSERT(virtualBlock != VK_NULL_HANDLE && pVirtualAllocInfo != VMA_NULL);\n  VMA_DEBUG_LOG(\"vmaGetVirtualAllocationInfo\");\n  VMA_DEBUG_GLOBAL_MUTEX_LOCK;\n  virtualBlock->GetAllocationInfo(allocation, *pVirtualAllocInfo);\n}\n\nVMA_CALL_PRE VkResult VMA_CALL_POST vmaVirtualAllocate(VmaVirtualBlock VMA_NOT_NULL virtualBlock,\n                                                       const VmaVirtualAllocationCreateInfo* VMA_NOT_NULL pCreateInfo, VmaVirtualAllocation VMA_NULLABLE_NON_DISPATCHABLE* VMA_NOT_NULL pAllocation,\n                                                       VkDeviceSize* VMA_NULLABLE pOffset)\n{\n  VMA_ASSERT(virtualBlock != VK_NULL_HANDLE && pCreateInfo != VMA_NULL && pAllocation != VMA_NULL);\n  VMA_DEBUG_LOG(\"vmaVirtualAllocate\");\n  VMA_DEBUG_GLOBAL_MUTEX_LOCK;\n  return virtualBlock->Allocate(*pCreateInfo, *pAllocation, pOffset);\n}\n\nVMA_CALL_PRE void VMA_CALL_POST vmaVirtualFree(VmaVirtualBlock VMA_NOT_NULL virtualBlock, VmaVirtualAllocation VMA_NULLABLE_NON_DISPATCHABLE allocation)\n{\n  if(allocation != VK_NULL_HANDLE)\n  {\n    VMA_ASSERT(virtualBlock != VK_NULL_HANDLE);\n    VMA_DEBUG_LOG(\"vmaVirtualFree\");\n    VMA_DEBUG_GLOBAL_MUTEX_LOCK;\n    virtualBlock->Free(allocation);\n  }\n}\n\nVMA_CALL_PRE void VMA_CALL_POST vmaClearVirtualBlock(VmaVirtualBlock VMA_NOT_NULL virtualBlock)\n{\n  VMA_ASSERT(virtualBlock != VK_NULL_HANDLE);\n  VMA_DEBUG_LOG(\"vmaClearVirtualBlock\");\n  VMA_DEBUG_GLOBAL_MUTEX_LOCK;\n  virtualBlock->Clear();\n}\n\nVMA_CALL_PRE void VMA_CALL_POST vmaSetVirtualAllocationUserData(VmaVirtualBlock VMA_NOT_NULL virtualBlock,\n                                                                VmaVirtualAllocation VMA_NOT_NULL_NON_DISPATCHABLE allocation, void* VMA_NULLABLE pUserData)\n{\n  VMA_ASSERT(virtualBlock != VK_NULL_HANDLE);\n  VMA_DEBUG_LOG(\"vmaSetVirtualAllocationUserData\");\n  VMA_DEBUG_GLOBAL_MUTEX_LOCK;\n  virtualBlock->SetAllocationUserData(allocation, pUserData);\n}\n\nVMA_CALL_PRE void VMA_CALL_POST vmaGetVirtualBlockStatistics(VmaVirtualBlock VMA_NOT_NULL virtualBlock,\n                                                             VmaStatistics* VMA_NOT_NULL pStats)\n{\n  VMA_ASSERT(virtualBlock != VK_NULL_HANDLE && pStats != VMA_NULL);\n  VMA_DEBUG_LOG(\"vmaGetVirtualBlockStatistics\");\n  VMA_DEBUG_GLOBAL_MUTEX_LOCK;\n  virtualBlock->GetStatistics(*pStats);\n}\n\nVMA_CALL_PRE void VMA_CALL_POST vmaCalculateVirtualBlockStatistics(VmaVirtualBlock VMA_NOT_NULL virtualBlock,\n                                                                   VmaDetailedStatistics* VMA_NOT_NULL pStats)\n{\n  VMA_ASSERT(virtualBlock != VK_NULL_HANDLE && pStats != VMA_NULL);\n  VMA_DEBUG_LOG(\"vmaCalculateVirtualBlockStatistics\");\n  VMA_DEBUG_GLOBAL_MUTEX_LOCK;\n  virtualBlock->CalculateDetailedStatistics(*pStats);\n}\n\n#if VMA_STATS_STRING_ENABLED\n\nVMA_CALL_PRE void VMA_CALL_POST vmaBuildVirtualBlockStatsString(VmaVirtualBlock VMA_NOT_NULL virtualBlock,\n                                                                char* VMA_NULLABLE * VMA_NOT_NULL ppStatsString, VkBool32 detailedMap)\n{\n  VMA_ASSERT(virtualBlock != VK_NULL_HANDLE && ppStatsString != VMA_NULL);\n  VMA_DEBUG_GLOBAL_MUTEX_LOCK;\n  const VkAllocationCallbacks* allocationCallbacks = virtualBlock->GetAllocationCallbacks();\n  VmaStringBuilder sb(allocationCallbacks);\n  virtualBlock->BuildStatsString(detailedMap != VK_FALSE, sb);\n  *ppStatsString = VmaCreateStringCopy(allocationCallbacks, sb.GetData(), sb.GetLength());\n}\n\nVMA_CALL_PRE void VMA_CALL_POST vmaFreeVirtualBlockStatsString(VmaVirtualBlock VMA_NOT_NULL virtualBlock,\n                                                               char* VMA_NULLABLE pStatsString)\n{\n  if(pStatsString != VMA_NULL)\n  {\n    VMA_ASSERT(virtualBlock != VK_NULL_HANDLE);\n    VMA_DEBUG_GLOBAL_MUTEX_LOCK;\n    VmaFreeString(virtualBlock->GetAllocationCallbacks(), pStatsString);\n  }\n}\n#endif // VMA_STATS_STRING_ENABLED\n#endif // _VMA_PUBLIC_INTERFACE\n#endif // VMA_IMPLEMENTATION\n\n/**\n\\page quick_start Quick start\n\n\\section quick_start_project_setup Project setup\n\nVulkan Memory Allocator comes in form of a \"stb-style\" single header file.\nYou don't need to build it as a separate library project.\nYou 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,\nlike it tends to be in case of inline functions or C++ templates.\nIt means that implementation is bundled with interface in a single file and needs to be extracted using preprocessor macro.\nIf you don't do it properly, you will get linker errors.\n\nTo 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 exactly 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\nIt may be a good idea to create dedicated CPP file just for this purpose.\n\nThis library includes header `<vulkan/vulkan.h>`, which in turn\nincludes `<windows.h>` on Windows. If you need some specific macros defined\nbefore including these headers (like `WIN32_LEAN_AND_MEAN` or\n`WINVER` for Windows, `VK_USE_PLATFORM_WIN32_KHR` for Vulkan), you must define\nthem before every `#include` of this library.\n\nThis library is written in C++, but has C-compatible interface.\nThus you can include and use vk_mem_alloc.h in C or C++ code, but full\nimplementation with `VMA_IMPLEMENTATION` macro must be compiled as C++, NOT as C.\nSome features of C++14 are used. STL containers, RTTI, or C++ exceptions are not used.\n\n\n\\section quick_start_initialization Initialization\n\nAt program startup:\n\n-# Initialize Vulkan to have `VkPhysicalDevice`, `VkDevice` and `VkInstance` object.\n-# Fill VmaAllocatorCreateInfo structure and create #VmaAllocator object by\n   calling vmaCreateAllocator().\n\nOnly members `physicalDevice`, `device`, `instance` are required.\nHowever, you should inform the library which Vulkan version do you use by setting\nVmaAllocatorCreateInfo::vulkanApiVersion and which extensions did you enable\nby setting VmaAllocatorCreateInfo::flags (like #VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT for VK_KHR_buffer_device_address).\nOtherwise, VMA would use only features of Vulkan 1.0 core with no extensions.\n\n\\subsection quick_start_initialization_selecting_vulkan_version Selecting Vulkan version\n\nVMA supports Vulkan version down to 1.0, for backward compatibility.\nIf you want to use higher version, you need to inform the library about it.\nThis is a two-step process.\n\n<b>Step 1: Compile time.</b> By default, VMA compiles with code supporting the highest\nVulkan version found in the included `<vulkan/vulkan.h>` that is also supported by the library.\nIf this is OK, you don't need to do anything.\nHowever, if you want to compile VMA as if only some lower Vulkan version was available,\ndefine macro `VMA_VULKAN_VERSION` before every `#include \"vk_mem_alloc.h\"`.\nIt should have decimal numeric value in form of ABBBCCC, where A = major, BBB = minor, CCC = patch Vulkan version.\nFor example, to compile against Vulkan 1.2:\n\n\\code\n#define VMA_VULKAN_VERSION 1002000 // Vulkan 1.2\n#include \"vk_mem_alloc.h\"\n\\endcode\n\n<b>Step 2: Runtime.</b> Even when compiled with higher Vulkan version available,\nVMA can use only features of a lower version, which is configurable during creation of the #VmaAllocator object.\nBy default, only Vulkan 1.0 is used.\nTo initialize the allocator with support for higher Vulkan version, you need to set member\nVmaAllocatorCreateInfo::vulkanApiVersion to an appropriate value, e.g. using constants like `VK_API_VERSION_1_2`.\nSee code sample below.\n\n\\subsection quick_start_initialization_importing_vulkan_functions Importing Vulkan functions\n\nYou may need to configure importing Vulkan functions. There are 3 ways to do this:\n\n-# **If you link with Vulkan static library** (e.g. \"vulkan-1.lib\" on Windows):\n   - You don't need to do anything.\n   - VMA will use these, as macro `VMA_STATIC_VULKAN_FUNCTIONS` is defined to 1 by default.\n-# **If you want VMA to fetch pointers to Vulkan functions dynamically** using `vkGetInstanceProcAddr`,\n   `vkGetDeviceProcAddr` (this is the option presented in the example below):\n   - Define `VMA_STATIC_VULKAN_FUNCTIONS` to 0, `VMA_DYNAMIC_VULKAN_FUNCTIONS` to 1.\n   - Provide pointers to these two functions via VmaVulkanFunctions::vkGetInstanceProcAddr,\n     VmaVulkanFunctions::vkGetDeviceProcAddr.\n   - The library will fetch pointers to all other functions it needs internally.\n-# **If you fetch pointers to all Vulkan functions in a custom way**, e.g. using some loader like\n   [Volk](https://github.com/zeux/volk):\n   - Define `VMA_STATIC_VULKAN_FUNCTIONS` and `VMA_DYNAMIC_VULKAN_FUNCTIONS` to 0.\n   - Pass these pointers via structure #VmaVulkanFunctions.\n\nExample for case 2:\n\n\\code\n#define VMA_STATIC_VULKAN_FUNCTIONS 0\n#define VMA_DYNAMIC_VULKAN_FUNCTIONS 1\n#include \"vk_mem_alloc.h\"\n\n...\n\nVmaVulkanFunctions vulkanFunctions = {};\nvulkanFunctions.vkGetInstanceProcAddr = &vkGetInstanceProcAddr;\nvulkanFunctions.vkGetDeviceProcAddr = &vkGetDeviceProcAddr;\n\nVmaAllocatorCreateInfo allocatorCreateInfo = {};\nallocatorCreateInfo.vulkanApiVersion = VK_API_VERSION_1_2;\nallocatorCreateInfo.physicalDevice = physicalDevice;\nallocatorCreateInfo.device = device;\nallocatorCreateInfo.instance = instance;\nallocatorCreateInfo.pVulkanFunctions = &vulkanFunctions;\n\nVmaAllocator allocator;\nvmaCreateAllocator(&allocatorCreateInfo, &allocator);\n\\endcode\n\n\n\\section quick_start_resource_allocation Resource allocation\n\nWhen 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, plus #VmaAllocation objects that represents its underlying memory.\n\n\\code\nVkBufferCreateInfo bufferInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };\nbufferInfo.size = 65536;\nbufferInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;\n\nVmaAllocationCreateInfo allocInfo = {};\nallocInfo.usage = VMA_MEMORY_USAGE_AUTO;\n\nVkBuffer buffer;\nVmaAllocation allocation;\nvmaCreateBuffer(allocator, &bufferInfo, &allocInfo, &buffer, &allocation, nullptr);\n\\endcode\n\nDon't forget to destroy your objects when no longer needed:\n\n\\code\nvmaDestroyBuffer(allocator, buffer, allocation);\nvmaDestroyAllocator(allocator);\n\\endcode\n\n\n\\page choosing_memory_type Choosing memory type\n\nPhysical devices in Vulkan support various combinations of memory heaps and\ntypes. Help with choosing correct and optimal memory type for your specific\nresource is one of the key features of this library. You can use it by filling\nappropriate members of VmaAllocationCreateInfo structure, as described below.\nYou 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: vmaFindMemoryTypeIndexForBufferInfo(),\n   vmaFindMemoryTypeIndexForImageInfo(), vmaFindMemoryTypeIndex().\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-# **This is the easiest and recommended way to use this library:**\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().\n\nWhen using 3. or 4., the library internally queries Vulkan for memory types\nsupported for that buffer or image (function `vkGetBufferMemoryRequirements()`)\nand uses only one of these types.\n\nIf no memory type can be found that meets all the requirements, these functions\nreturn `VK_ERROR_FEATURE_NOT_PRESENT`.\n\nYou can leave VmaAllocationCreateInfo structure completely filled with zeros.\nIt means no requirements are specified for memory type.\nIt is valid, although not very useful.\n\n\\section choosing_memory_type_usage Usage\n\nThe easiest way to specify memory requirements is to fill member\nVmaAllocationCreateInfo::usage using one of the values of enum #VmaMemoryUsage.\nIt defines high level, common usage types.\nSince version 3 of the library, it is recommended to use #VMA_MEMORY_USAGE_AUTO to let it select best memory type for your resource automatically.\n\nFor example, if you want to create a uniform buffer that will be filled using\ntransfer only once or infrequently and then used for rendering every frame as a uniform buffer, you can\ndo it using following code. The buffer will most likely end up in a memory type with\n`VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT` to be fast to access by the GPU device.\n\n\\code\nVkBufferCreateInfo bufferInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };\nbufferInfo.size = 65536;\nbufferInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;\n\nVmaAllocationCreateInfo allocInfo = {};\nallocInfo.usage = VMA_MEMORY_USAGE_AUTO;\n\nVkBuffer buffer;\nVmaAllocation allocation;\nvmaCreateBuffer(allocator, &bufferInfo, &allocInfo, &buffer, &allocation, nullptr);\n\\endcode\n\nIf you have a preference for putting the resource in GPU (device) memory or CPU (host) memory\non systems with discrete graphics card that have the memories separate, you can use\n#VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE or #VMA_MEMORY_USAGE_AUTO_PREFER_HOST.\n\nWhen using `VMA_MEMORY_USAGE_AUTO*` while you want to map the allocated memory,\nyou also need to specify one of the host access flags:\n#VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT or #VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT.\nThis will help the library decide about preferred memory type to ensure it has `VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT`\nso you can map it.\n\nFor example, a staging buffer that will be filled via mapped pointer and then\nused as a source of transfer to the buffer described previously can be created like this.\nIt will likely end up in a memory type that is `HOST_VISIBLE` and `HOST_COHERENT`\nbut not `HOST_CACHED` (meaning uncached, write-combined) and not `DEVICE_LOCAL` (meaning system RAM).\n\n\\code\nVkBufferCreateInfo stagingBufferInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };\nstagingBufferInfo.size = 65536;\nstagingBufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;\n\nVmaAllocationCreateInfo stagingAllocInfo = {};\nstagingAllocInfo.usage = VMA_MEMORY_USAGE_AUTO;\nstagingAllocInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT;\n\nVkBuffer stagingBuffer;\nVmaAllocation stagingAllocation;\nvmaCreateBuffer(allocator, &stagingBufferInfo, &stagingAllocInfo, &stagingBuffer, &stagingAllocation, nullptr);\n\\endcode\n\nFor more examples of creating different kinds of resources, see chapter \\ref usage_patterns.\n\nUsage values `VMA_MEMORY_USAGE_AUTO*` are legal to use only when the library knows\nabout the resource being created by having `VkBufferCreateInfo` / `VkImageCreateInfo` passed,\nso they work with functions like: vmaCreateBuffer(), vmaCreateImage(), vmaFindMemoryTypeIndexForBufferInfo() etc.\nIf you allocate raw memory using function vmaAllocateMemory(), you have to use other means of selecting\nmemory type, as described below.\n\n\\note\nOld usage values (`VMA_MEMORY_USAGE_GPU_ONLY`, `VMA_MEMORY_USAGE_CPU_ONLY`,\n`VMA_MEMORY_USAGE_CPU_TO_GPU`, `VMA_MEMORY_USAGE_GPU_TO_CPU`, `VMA_MEMORY_USAGE_CPU_COPY`)\nare still available and work same way as in previous versions of the library\nfor backward compatibility, but they are not recommended.\n\n\\section choosing_memory_type_required_preferred_flags Required and preferred flags\n\nYou can specify more detailed requirements by filling members\nVmaAllocationCreateInfo::requiredFlags and VmaAllocationCreateInfo::preferredFlags\nwith a combination of bits from enum `VkMemoryPropertyFlags`. For example,\nif you want to create a buffer that will be persistently mapped on host (so it\nmust be `HOST_VISIBLE`) and preferably will also be `HOST_COHERENT` and `HOST_CACHED`,\nuse following code:\n\n\\code\nVmaAllocationCreateInfo allocInfo = {};\nallocInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT;\nallocInfo.preferredFlags = VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT;\nallocInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT;\n\nVkBuffer buffer;\nVmaAllocation allocation;\nvmaCreateBuffer(allocator, &bufferInfo, &allocInfo, &buffer, &allocation, nullptr);\n\\endcode\n\nA memory type is chosen that has all the required flags and as many preferred\nflags set as possible.\n\nValue passed in VmaAllocationCreateInfo::usage is internally converted to a set of required and preferred flags,\nplus some extra \"magic\" (heuristics).\n\n\\section choosing_memory_type_explicit_memory_types Explicit memory types\n\nIf you inspected memory types available on the physical device and you have\na preference for memory types that you want to use, you can fill member\nVmaAllocationCreateInfo::memoryTypeBits. It is a bit mask, where each bit set\nmeans that a memory type with that index is allowed to be used for the\nallocation. Special value 0, just like `UINT32_MAX`, means there are no\nrestrictions to memory type index.\n\nPlease note that this member is NOT just a memory type index.\nStill you can use it to choose just one, specific memory type.\nFor example, if you already determined that your buffer should be created in\nmemory type 2, use following code:\n\n\\code\nuint32_t memoryTypeIndex = 2;\n\nVmaAllocationCreateInfo allocInfo = {};\nallocInfo.memoryTypeBits = 1u << memoryTypeIndex;\n\nVkBuffer buffer;\nVmaAllocation allocation;\nvmaCreateBuffer(allocator, &bufferInfo, &allocInfo, &buffer, &allocation, nullptr);\n\\endcode\n\n\n\\section choosing_memory_type_custom_memory_pools Custom memory pools\n\nIf you allocate from custom memory pool, all the ways of specifying memory\nrequirements described above are not applicable and the aforementioned members\nof VmaAllocationCreateInfo structure are ignored. Memory type is selected\nexplicitly when creating the pool and then used to make all the allocations from\nthat pool. For further details, see \\ref custom_memory_pools.\n\n\\section choosing_memory_type_dedicated_allocations Dedicated allocations\n\nMemory for allocations is reserved out of larger block of `VkDeviceMemory`\nallocated from Vulkan internally. That is the main feature of this whole library.\nYou can still request a separate memory block to be created for an allocation,\njust like you would do in a trivial solution without using any allocator.\nIn that case, a buffer or image is always bound to that memory at offset 0.\nThis is called a \"dedicated allocation\".\nYou can explicitly request it by using flag #VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT.\nThe 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\nTo \"map memory\" in Vulkan means to obtain a CPU pointer to `VkDeviceMemory`,\nto be able to read from it or write to it in CPU code.\nMapping is possible only of memory allocated from a memory type that has\n`VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT` flag.\nFunctions `vkMapMemory()`, `vkUnmapMemory()` are designed for this purpose.\nYou can use them directly with memory allocated by this library,\nbut it is not recommended because of following issue:\nMapping the same `VkDeviceMemory` block multiple times is illegal - only one mapping at a time is allowed.\nThis includes mapping disjoint regions. Mapping is not reference-counted internally by Vulkan.\nBecause of this, Vulkan Memory Allocator provides following facilities:\n\n\\note If you want to be able to map an allocation, you need to specify one of the flags\n#VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT or #VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT\nin VmaAllocationCreateInfo::flags. These flags are required for an allocation to be mappable\nwhen using #VMA_MEMORY_USAGE_AUTO or other `VMA_MEMORY_USAGE_AUTO*` enum values.\nFor other usage values they are ignored and every such allocation made in `HOST_VISIBLE` memory type is mappable,\nbut they can still be used for consistency.\n\n\\section memory_mapping_mapping_functions Mapping functions\n\nThe library provides following functions for mapping of a specific #VmaAllocation: vmaMapMemory(), vmaUnmapMemory().\nThey are safer and more convenient to use than standard Vulkan functions.\nYou can map an allocation multiple times simultaneously - mapping is reference-counted internally.\nYou can also map different allocations simultaneously regardless of whether they use the same `VkDeviceMemory` block.\nThe way it is implemented is that the library always maps entire memory block, not just region of the allocation.\nFor further details, see description of vmaMapMemory() function.\nExample:\n\n\\code\n// Having these objects initialized:\nstruct ConstantBuffer\n{\n    ...\n};\nConstantBuffer constantBufferData = ...\n\nVmaAllocator allocator = ...\nVkBuffer constantBuffer = ...\nVmaAllocation constantBufferAllocation = ...\n\n// You can map and fill your buffer using following code:\n\nvoid* mappedData;\nvmaMapMemory(allocator, constantBufferAllocation, &mappedData);\nmemcpy(mappedData, &constantBufferData, sizeof(constantBufferData));\nvmaUnmapMemory(allocator, constantBufferAllocation);\n\\endcode\n\nWhen 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\nIt happens because the library maps entire `VkDeviceMemory` block, where different\ntypes of images and buffers may end up together, especially on GPUs with unified memory like Intel.\nYou can safely ignore it if you are sure you access only memory of the intended\nobject that you wanted to map.\n\n\n\\section memory_mapping_persistently_mapped_memory Persistently mapped memory\n\nKeeping your memory persistently mapped is generally OK in Vulkan.\nYou don't need to unmap it before using its data on the GPU.\nThe library provides a special feature designed for that:\nAllocations made with #VMA_ALLOCATION_CREATE_MAPPED_BIT flag set in\nVmaAllocationCreateInfo::flags stay mapped all the time,\nso you can just access CPU pointer to it any time\nwithout a need to call any \"map\" or \"unmap\" function.\nExample:\n\n\\code\nVkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };\nbufCreateInfo.size = sizeof(ConstantBuffer);\nbufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;\n\nVmaAllocationCreateInfo allocCreateInfo = {};\nallocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO;\nallocCreateInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT |\n    VMA_ALLOCATION_CREATE_MAPPED_BIT;\n\nVkBuffer buf;\nVmaAllocation alloc;\nVmaAllocationInfo allocInfo;\nvmaCreateBuffer(allocator, &bufCreateInfo, &allocCreateInfo, &buf, &alloc, &allocInfo);\n\n// Buffer is already mapped. You can access its memory.\nmemcpy(allocInfo.pMappedData, &constantBufferData, sizeof(constantBufferData));\n\\endcode\n\n\\note #VMA_ALLOCATION_CREATE_MAPPED_BIT by itself doesn't guarantee that the allocation will end up\nin a mappable memory type.\nFor this, you need to also specify #VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT or\n#VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT.\n#VMA_ALLOCATION_CREATE_MAPPED_BIT only guarantees that if the memory is `HOST_VISIBLE`, the allocation will be mapped on creation.\nFor an example of how to make use of this fact, see section \\ref usage_patterns_advanced_data_uploading.\n\n\\section memory_mapping_cache_control Cache flush and invalidate\n\nMemory in Vulkan doesn't need to be unmapped before using it on GPU,\nbut unless a memory types has `VK_MEMORY_PROPERTY_HOST_COHERENT_BIT` flag set,\nyou need to manually **invalidate** cache before reading of mapped pointer\nand **flush** cache after writing to mapped pointer.\nMap/unmap operations don't do that automatically.\nVulkan provides following functions for this purpose `vkFlushMappedMemoryRanges()`,\n`vkInvalidateMappedMemoryRanges()`, but this library provides more convenient\nfunctions that refer to given allocation object: vmaFlushAllocation(),\nvmaInvalidateAllocation(),\nor multiple objects at once: vmaFlushAllocations(), vmaInvalidateAllocations().\n\nRegions of memory specified for flush/invalidate must be aligned to\n`VkPhysicalDeviceLimits::nonCoherentAtomSize`. This is automatically ensured by the library.\nIn any memory type that is `HOST_VISIBLE` but not `HOST_COHERENT`, all allocations\nwithin 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\nAlso, Windows drivers from all 3 PC GPU vendors (AMD, Intel, NVIDIA)\ncurrently provide `HOST_COHERENT` flag on all memory types that are\n`HOST_VISIBLE`, so on PC you may not need to bother.\n\n\n\\page staying_within_budget Staying within budget\n\nWhen developing a graphics-intensive game or program, it is important to avoid allocating\nmore GPU memory than it is physically available. When the memory is over-committed,\nvarious bad things can happen, depending on the specific GPU, graphics driver, and\noperating 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\nTo query for current memory usage and available budget, use function vmaGetHeapBudgets().\nReturned structure #VmaBudget contains quantities expressed in bytes, per Vulkan memory heap.\n\nPlease note that this function returns different information and works faster than\nvmaCalculateStatistics(). vmaGetHeapBudgets() can be called every frame or even before every\nallocation, while vmaCalculateStatistics() is intended to be used rarely,\nonly to obtain statistical information, e.g. for debugging purposes.\n\nIt is recommended to use <b>VK_EXT_memory_budget</b> device extension to obtain information\nabout the budget from Vulkan device. VMA is able to use this extension automatically.\nWhen not enabled, the allocator behaves same way, but then it estimates current usage\nand available budget based on its internal information and Vulkan memory heap sizes,\nwhich may be less precise. In order to use this extension:\n\n1. 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!\n2. Use flag #VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT when creating #VmaAllocator object.\n3. 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\nThere are many ways in which you can try to stay within the budget.\n\nFirst, when making new allocation requires allocating a new memory block, the library\ntries 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\ndedicated memory for just this resource.\n\nIf the size of the requested resource plus current memory usage is more than the\nbudget, by default the library still tries to create it, leaving it to the Vulkan\nimplementation whether the allocation succeeds or fails. You can change this behavior\nby using #VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT flag. With it, the allocation is\nnot made if it would exceed the budget or if the budget is already exceeded.\nVMA then tries to make the allocation from the next eligible Vulkan memory type.\nThe all of them fail, the call then fails with `VK_ERROR_OUT_OF_DEVICE_MEMORY`.\nExample usage pattern may be to pass the #VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT flag\nwhen creating resources that are not essential for the application (e.g. the texture\nof a specific object) and not to pass it when creating critically important resources\n(e.g. render targets).\n\nOn AMD graphics cards there is a custom vendor extension available: <b>VK_AMD_memory_overallocation_behavior</b>\nthat allows to control the behavior of the Vulkan implementation in out-of-memory cases -\nwhether it should fail with an error code or still allow the allocation.\nUsage of this extension involves only passing extra structure on Vulkan device creation,\nso it is out of scope of this library.\n\nFinally, you can also use #VMA_ALLOCATION_CREATE_NEVER_ALLOCATE_BIT flag to make sure\na new allocation is created only when it fits inside one of the existing memory blocks.\nIf it would require to allocate a new block, if fails instead with `VK_ERROR_OUT_OF_DEVICE_MEMORY`.\nThis also ensures that the function call is very fast because it never goes to Vulkan\nto obtain a new block.\n\n\\note Creating \\ref custom_memory_pools with VmaPoolCreateInfo::minBlockCount\nset to more than 0 will currently try to allocate memory blocks without checking whether they\nfit within budget.\n\n\n\\page resource_aliasing Resource aliasing (overlap)\n\nNew explicit graphics APIs (Vulkan and Direct3D 12), thanks to manual memory\nmanagement, give an opportunity to alias (overlap) multiple resources in the\nsame region of memory - a feature not available in the old APIs (Direct3D 11, OpenGL).\nIt can be useful to save video memory, but it must be used with caution.\n\nFor example, if you know the flow of your whole render frame in advance, you\nare going to use some intermediate textures or buffers only during a small range of render passes,\nand you know these ranges don't overlap in time, you can bind these resources to\nthe same place in memory, even if they have completely different parameters (width, height, format etc.).\n\n![Resource aliasing (overlap)](../gfx/Aliasing.png)\n\nSuch scenario is possible using VMA, but you need to create your images manually.\nThen you need to calculate parameters of an allocation to be made using formula:\n\n- allocation size = max(size of each image)\n- allocation alignment = max(alignment of each image)\n- allocation memoryTypeBits = bitwise AND(memoryTypeBits of each image)\n\nFollowing example shows two different images bound to the same place in memory,\nallocated to fit largest of them.\n\n\\code\n// A 512x512 texture to be sampled.\nVkImageCreateInfo img1CreateInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO };\nimg1CreateInfo.imageType = VK_IMAGE_TYPE_2D;\nimg1CreateInfo.extent.width = 512;\nimg1CreateInfo.extent.height = 512;\nimg1CreateInfo.extent.depth = 1;\nimg1CreateInfo.mipLevels = 10;\nimg1CreateInfo.arrayLayers = 1;\nimg1CreateInfo.format = VK_FORMAT_R8G8B8A8_SRGB;\nimg1CreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL;\nimg1CreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;\nimg1CreateInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;\nimg1CreateInfo.samples = VK_SAMPLE_COUNT_1_BIT;\n\n// A full screen texture to be used as color attachment.\nVkImageCreateInfo img2CreateInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO };\nimg2CreateInfo.imageType = VK_IMAGE_TYPE_2D;\nimg2CreateInfo.extent.width = 1920;\nimg2CreateInfo.extent.height = 1080;\nimg2CreateInfo.extent.depth = 1;\nimg2CreateInfo.mipLevels = 1;\nimg2CreateInfo.arrayLayers = 1;\nimg2CreateInfo.format = VK_FORMAT_R8G8B8A8_UNORM;\nimg2CreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL;\nimg2CreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;\nimg2CreateInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;\nimg2CreateInfo.samples = VK_SAMPLE_COUNT_1_BIT;\n\nVkImage img1;\nres = vkCreateImage(device, &img1CreateInfo, nullptr, &img1);\nVkImage img2;\nres = vkCreateImage(device, &img2CreateInfo, nullptr, &img2);\n\nVkMemoryRequirements img1MemReq;\nvkGetImageMemoryRequirements(device, img1, &img1MemReq);\nVkMemoryRequirements img2MemReq;\nvkGetImageMemoryRequirements(device, img2, &img2MemReq);\n\nVkMemoryRequirements finalMemReq = {};\nfinalMemReq.size = std::max(img1MemReq.size, img2MemReq.size);\nfinalMemReq.alignment = std::max(img1MemReq.alignment, img2MemReq.alignment);\nfinalMemReq.memoryTypeBits = img1MemReq.memoryTypeBits & img2MemReq.memoryTypeBits;\n// Validate if(finalMemReq.memoryTypeBits != 0)\n\nVmaAllocationCreateInfo allocCreateInfo = {};\nallocCreateInfo.preferredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;\n\nVmaAllocation alloc;\nres = vmaAllocateMemory(allocator, &finalMemReq, &allocCreateInfo, &alloc, nullptr);\n\nres = vmaBindImageMemory(allocator, alloc, img1);\nres = vmaBindImageMemory(allocator, alloc, img2);\n\n// You can use img1, img2 here, but not at the same time!\n\nvmaFreeMemory(allocator, alloc);\nvkDestroyImage(allocator, img2, nullptr);\nvkDestroyImage(allocator, img1, nullptr);\n\\endcode\n\nRemember that using resources that alias in memory requires proper synchronization.\nYou need to issue a memory barrier to make sure commands that use `img1` and `img2`\ndon't overlap on GPU timeline.\nYou also need to treat a resource after aliasing as uninitialized - containing garbage data.\nFor example, if you use `img1` and then want to use `img2`, you need to issue\nan image memory barrier for `img2` with `oldLayout` = `VK_IMAGE_LAYOUT_UNDEFINED`.\n\nAdditional considerations:\n\n- Vulkan also allows to interpret contents of memory between aliasing resources consistently in some cases.\nSee chapter 11.8. \"Memory Aliasing\" of Vulkan specification or `VK_IMAGE_CREATE_ALIAS_BIT` flag.\n- You can create more complex layout where different images and buffers are bound\nat different offsets inside one large allocation. For example, one can imagine\na big texture used in some render passes, aliasing with a set of many small buffers\nused between in some further passes. To bind a resource at non-zero offset in an allocation,\nuse vmaBindBufferMemory2() / vmaBindImageMemory2().\n- Before allocating memory for the resources you want to alias, check `memoryTypeBits`\nreturned in memory requirements of each resource to make sure the bits overlap.\nSome GPUs may expose multiple memory types suitable e.g. only for buffers or\nimages with `COLOR_ATTACHMENT` usage, so the sets of memory types supported by your\nresources may be disjoint. Aliasing them is not possible in that case.\n\n\n\\page custom_memory_pools Custom memory pools\n\nA memory pool contains a number of `VkDeviceMemory` blocks.\nThe library automatically creates and manages default pool for each memory type available on the device.\nDefault memory pool automatically grows in size.\nSize of allocated blocks is also variable and managed automatically.\n\nYou can create custom pool and allocate memory out of it.\nIt 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- Use extra parameters for a set of your allocations that are available in #VmaPoolCreateInfo but not in\n  #VmaAllocationCreateInfo - e.g., custom minimum alignment, custom `pNext` chain.\n- Perform defragmentation on a specific subset of your allocations.\n\nTo 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\nExample:\n\n\\code\n// Find memoryTypeIndex for the pool.\nVkBufferCreateInfo sampleBufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };\nsampleBufCreateInfo.size = 0x10000; // Doesn't matter.\nsampleBufCreateInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;\n\nVmaAllocationCreateInfo sampleAllocCreateInfo = {};\nsampleAllocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO;\n\nuint32_t memTypeIndex;\nVkResult res = vmaFindMemoryTypeIndexForBufferInfo(allocator,\n    &sampleBufCreateInfo, &sampleAllocCreateInfo, &memTypeIndex);\n// Check res...\n\n// Create a pool that can have at most 2 blocks, 128 MiB each.\nVmaPoolCreateInfo poolCreateInfo = {};\npoolCreateInfo.memoryTypeIndex = memTypeIndex;\npoolCreateInfo.blockSize = 128ull * 1024 * 1024;\npoolCreateInfo.maxBlockCount = 2;\n\nVmaPool pool;\nres = vmaCreatePool(allocator, &poolCreateInfo, &pool);\n// Check res...\n\n// Allocate a buffer out of it.\nVkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };\nbufCreateInfo.size = 1024;\nbufCreateInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;\n\nVmaAllocationCreateInfo allocCreateInfo = {};\nallocCreateInfo.pool = pool;\n\nVkBuffer buf;\nVmaAllocation alloc;\nres = vmaCreateBuffer(allocator, &bufCreateInfo, &allocCreateInfo, &buf, &alloc, nullptr);\n// Check res...\n\\endcode\n\nYou have to free all allocations made from this pool before destroying it.\n\n\\code\nvmaDestroyBuffer(allocator, buf, alloc);\nvmaDestroyPool(allocator, pool);\n\\endcode\n\nNew versions of this library support creating dedicated allocations in custom pools.\nIt is supported only when VmaPoolCreateInfo::blockSize = 0.\nTo use this feature, set VmaAllocationCreateInfo::pool to the pointer to your custom pool and\nVmaAllocationCreateInfo::flags to #VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT.\n\n\\note Excessive use of custom pools is a common mistake when using this library.\nCustom pools may be useful for special purposes - when you want to\nkeep certain type of resources separate e.g. to reserve minimum amount of memory\nfor them or limit maximum amount of memory they can occupy. For most\nresources this is not needed and so it is not recommended to create #VmaPool\nobjects and allocations out of them. Allocating from the default pool is sufficient.\n\n\n\\section custom_memory_pools_MemTypeIndex Choosing memory type index\n\nWhen creating a pool, you must explicitly specify memory type index.\nTo find the one suitable for your buffers or images, you can use helper functions\nvmaFindMemoryTypeIndexForBufferInfo(), vmaFindMemoryTypeIndexForImageInfo().\nYou need to provide structures with example parameters of buffers or images\nthat you are going to create in that pool.\n\n\\code\nVkBufferCreateInfo exampleBufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };\nexampleBufCreateInfo.size = 1024; // Doesn't matter\nexampleBufCreateInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;\n\nVmaAllocationCreateInfo allocCreateInfo = {};\nallocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO;\n\nuint32_t memTypeIndex;\nvmaFindMemoryTypeIndexForBufferInfo(allocator, &exampleBufCreateInfo, &allocCreateInfo, &memTypeIndex);\n\nVmaPoolCreateInfo poolCreateInfo = {};\npoolCreateInfo.memoryTypeIndex = memTypeIndex;\n// ...\n\\endcode\n\nWhen 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\nEach Vulkan memory block managed by this library has accompanying metadata that\nkeeps track of used and unused regions. By default, the metadata structure and\nalgorithm tries to find best place for new allocations among free regions to\noptimize 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\nSometimes there is a need to use simpler, linear allocation algorithm. You can\ncreate 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\ncreates new allocations after last one and doesn't reuse free regions after\nallocations freed in the middle. It results in better allocation performance and\nless memory consumed by metadata.\n\n![Linear allocation algorithm](../gfx/Linear_allocator_2_algo_linear.png)\n\nWith this one flag, you can create a custom pool that can be used in many ways:\nfree-at-once, stack, double stack, and ring buffer. See below for details.\nYou don't need to specify explicitly which of these options you are going to use - it is detected automatically.\n\n\\subsection linear_algorithm_free_at_once Free-at-once\n\nIn a pool that uses linear algorithm, you still need to free all the allocations\nindividually, e.g. by using vmaFreeMemory() or vmaDestroyBuffer(). You can free\nthem in any order. New allocations are always made after last one - free space\nin the middle is not reused. However, when you release all the allocation and\nthe pool becomes empty, allocation starts from the beginning again. This way you\ncan use linear algorithm to speed up creation of allocations that you are going\nto release all at once.\n\n![Free-at-once](../gfx/Linear_allocator_3_free_at_once.png)\n\nThis mode is also available for pools created with VmaPoolCreateInfo::maxBlockCount\nvalue that allows multiple memory blocks.\n\n\\subsection linear_algorithm_stack Stack\n\nWhen you free an allocation that was created last, its space can be reused.\nThanks to this, if you always release allocations in the order opposite to their\ncreation (LIFO - Last In First Out), you can achieve behavior of a stack.\n\n![Stack](../gfx/Linear_allocator_4_stack.png)\n\nThis mode is also available for pools created with VmaPoolCreateInfo::maxBlockCount\nvalue that allows multiple memory blocks.\n\n\\subsection linear_algorithm_double_stack Double stack\n\nThe space reserved by a custom pool with linear algorithm may be used by two\nstacks:\n\n- First, default one, growing up from offset 0.\n- Second, \"upper\" one, growing down from the end towards lower offsets.\n\nTo make allocation from the upper stack, add flag #VMA_ALLOCATION_CREATE_UPPER_ADDRESS_BIT\nto VmaAllocationCreateInfo::flags.\n\n![Double stack](../gfx/Linear_allocator_7_double_stack.png)\n\nDouble stack is available only in pools with one memory block -\nVmaPoolCreateInfo::maxBlockCount must be 1. Otherwise behavior is undefined.\n\nWhen the two stacks' ends meet so there is not enough space between them for a\nnew allocation, such allocation fails with usual\n`VK_ERROR_OUT_OF_DEVICE_MEMORY` error.\n\n\\subsection linear_algorithm_ring_buffer Ring buffer\n\nWhen you free some allocations from the beginning and there is not enough free space\nfor a new one at the end of a pool, allocator's \"cursor\" wraps around to the\nbeginning and starts allocation there. Thanks to this, if you always release\nallocations in the same order as you created them (FIFO - First In First Out),\nyou can achieve behavior of a ring buffer / queue.\n\n![Ring buffer](../gfx/Linear_allocator_5_ring_buffer.png)\n\nRing buffer is available only in pools with one memory block -\nVmaPoolCreateInfo::maxBlockCount must be 1. Otherwise behavior is undefined.\n\n\\note \\ref defragmentation is not supported in custom pools created with #VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT.\n\n\n\\page defragmentation Defragmentation\n\nInterleaved allocations and deallocations of many objects of varying size can\ncause fragmentation over time, which can lead to a situation where the library is unable\nto find a continuous range of free memory for a new allocation despite there is\nenough free space, just scattered across many small free ranges between existing\nallocations.\n\nTo mitigate this problem, you can use defragmentation feature.\nIt doesn't happen automatically though and needs your cooperation,\nbecause VMA is a low level library that only allocates memory.\nIt cannot recreate buffers and images in a new place as it doesn't remember the contents of `VkBufferCreateInfo` / `VkImageCreateInfo` structures.\nIt cannot copy their contents as it doesn't record any commands to a command buffer.\n\nExample:\n\n\\code\nVmaDefragmentationInfo defragInfo = {};\ndefragInfo.pool = myPool;\ndefragInfo.flags = VMA_DEFRAGMENTATION_FLAG_ALGORITHM_FAST_BIT;\n\nVmaDefragmentationContext defragCtx;\nVkResult res = vmaBeginDefragmentation(allocator, &defragInfo, &defragCtx);\n// Check res...\n\nfor(;;)\n{\n    VmaDefragmentationPassMoveInfo pass;\n    res = vmaBeginDefragmentationPass(allocator, defragCtx, &pass);\n    if(res == VK_SUCCESS)\n        break;\n    else if(res != VK_INCOMPLETE)\n        // Handle error...\n\n    for(uint32_t i = 0; i < pass.moveCount; ++i)\n    {\n        // Inspect pass.pMoves[i].srcAllocation, identify what buffer/image it represents.\n        VmaAllocationInfo allocInfo;\n        vmaGetAllocationInfo(allocator, pass.pMoves[i].srcAllocation, &allocInfo);\n        MyEngineResourceData* resData = (MyEngineResourceData*)allocInfo.pUserData;\n\n        // Recreate and bind this buffer/image at: pass.pMoves[i].dstMemory, pass.pMoves[i].dstOffset.\n        VkImageCreateInfo imgCreateInfo = ...\n        VkImage newImg;\n        res = vkCreateImage(device, &imgCreateInfo, nullptr, &newImg);\n        // Check res...\n        res = vmaBindImageMemory(allocator, pass.pMoves[i].dstTmpAllocation, newImg);\n        // Check res...\n\n        // Issue a vkCmdCopyBuffer/vkCmdCopyImage to copy its content to the new place.\n        vkCmdCopyImage(cmdBuf, resData->img, ..., newImg, ...);\n    }\n\n    // Make sure the copy commands finished executing.\n    vkWaitForFences(...);\n\n    // Destroy old buffers/images bound with pass.pMoves[i].srcAllocation.\n    for(uint32_t i = 0; i < pass.moveCount; ++i)\n    {\n        // ...\n        vkDestroyImage(device, resData->img, nullptr);\n    }\n\n    // Update appropriate descriptors to point to the new places...\n\n    res = vmaEndDefragmentationPass(allocator, defragCtx, &pass);\n    if(res == VK_SUCCESS)\n        break;\n    else if(res != VK_INCOMPLETE)\n        // Handle error...\n}\n\nvmaEndDefragmentation(allocator, defragCtx, nullptr);\n\\endcode\n\nAlthough functions like vmaCreateBuffer(), vmaCreateImage(), vmaDestroyBuffer(), vmaDestroyImage()\ncreate/destroy an allocation and a buffer/image at once, these are just a shortcut for\ncreating the resource, allocating memory, and binding them together.\nDefragmentation works on memory allocations only. You must handle the rest manually.\nDefragmentation is an iterative process that should repreat \"passes\" as long as related functions\nreturn `VK_INCOMPLETE` not `VK_SUCCESS`.\nIn each pass:\n\n1. vmaBeginDefragmentationPass() function call:\n   - Calculates and returns the list of allocations to be moved in this pass.\n     Note this can be a time-consuming process.\n   - Reserves destination memory for them by creating temporary destination allocations\n     that you can query for their `VkDeviceMemory` + offset using vmaGetAllocationInfo().\n2. Inside the pass, **you should**:\n   - Inspect the returned list of allocations to be moved.\n   - Create new buffers/images and bind them at the returned destination temporary allocations.\n   - Copy data from source to destination resources if necessary.\n   - Destroy the source buffers/images, but NOT their allocations.\n3. vmaEndDefragmentationPass() function call:\n   - Frees the source memory reserved for the allocations that are moved.\n   - Modifies source #VmaAllocation objects that are moved to point to the destination reserved memory.\n   - Frees `VkDeviceMemory` blocks that became empty.\n\nUnlike in previous iterations of the defragmentation API, there is no list of \"movable\" allocations passed as a parameter.\nDefragmentation algorithm tries to move all suitable allocations.\nYou can, however, refuse to move some of them inside a defragmentation pass, by setting\n`pass.pMoves[i].operation` to #VMA_DEFRAGMENTATION_MOVE_OPERATION_IGNORE.\nThis is not recommended and may result in suboptimal packing of the allocations after defragmentation.\nIf you cannot ensure any allocation can be moved, it is better to keep movable allocations separate in a custom pool.\n\nInside a pass, for each allocation that should be moved:\n\n- You should copy its data from the source to the destination place by calling e.g. `vkCmdCopyBuffer()`, `vkCmdCopyImage()`.\n  - You need to make sure these commands finished executing before destroying the source buffers/images and before calling vmaEndDefragmentationPass().\n- If a resource doesn't contain any meaningful data, e.g. it is a transient color attachment image to be cleared,\n  filled, and used temporarily in each rendering frame, you can just recreate this image\n  without copying its data.\n- If the resource is in `HOST_VISIBLE` and `HOST_CACHED` memory, you can copy its data on the CPU\n  using `memcpy()`.\n- If you cannot move the allocation, you can set `pass.pMoves[i].operation` to #VMA_DEFRAGMENTATION_MOVE_OPERATION_IGNORE.\n  This will cancel the move.\n  - vmaEndDefragmentationPass() will then free the destination memory\n    not the source memory of the allocation, leaving it unchanged.\n- If you decide the allocation is unimportant and can be destroyed instead of moved (e.g. it wasn't used for long time),\n  you can set `pass.pMoves[i].operation` to #VMA_DEFRAGMENTATION_MOVE_OPERATION_DESTROY.\n  - vmaEndDefragmentationPass() will then free both source and destination memory, and will destroy the source #VmaAllocation object.\n\nYou can defragment a specific custom pool by setting VmaDefragmentationInfo::pool\n(like in the example above) or all the default pools by setting this member to null.\n\nDefragmentation is always performed in each pool separately.\nAllocations are never moved between different Vulkan memory types.\nThe size of the destination memory reserved for a moved allocation is the same as the original one.\nAlignment of an allocation as it was determined using `vkGetBufferMemoryRequirements()` etc. is also respected after defragmentation.\nBuffers/images should be recreated with the same `VkBufferCreateInfo` / `VkImageCreateInfo` parameters as the original ones.\n\nYou can perform the defragmentation incrementally to limit the number of allocations and bytes to be moved\nin each pass, e.g. to call it in sync with render frames and not to experience too big hitches.\nSee members: VmaDefragmentationInfo::maxBytesPerPass, VmaDefragmentationInfo::maxAllocationsPerPass.\n\nIt is also safe to perform the defragmentation asynchronously to render frames and other Vulkan and VMA\nusage, possibly from multiple threads, with the exception that allocations\nreturned in VmaDefragmentationPassMoveInfo::pMoves shouldn't be destroyed until the defragmentation pass is ended.\n\n<b>Mapping</b> is preserved on allocations that are moved during defragmentation.\nWhether through #VMA_ALLOCATION_CREATE_MAPPED_BIT or vmaMapMemory(), the allocations\nare mapped at their new place. Of course, pointer to the mapped data changes, so it needs to be queried\nusing VmaAllocationInfo::pMappedData.\n\n\\note Defragmentation is not supported in custom pools created with #VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT.\n\n\n\\page statistics Statistics\n\nThis library contains several functions that return information about its internal state,\nespecially the amount of memory allocated from Vulkan.\n\n\\section statistics_numeric_statistics Numeric statistics\n\nIf you need to obtain basic statistics about memory usage per heap, together with current budget,\nyou can call function vmaGetHeapBudgets() and inspect structure #VmaBudget.\nThis is useful to keep track of memory usage and stay within budget\n(see also \\ref staying_within_budget).\nExample:\n\n\\code\nuint32_t heapIndex = ...\n\nVmaBudget budgets[VK_MAX_MEMORY_HEAPS];\nvmaGetHeapBudgets(allocator, budgets);\n\nprintf(\"My heap currently has %u allocations taking %llu B,\\n\",\n    budgets[heapIndex].statistics.allocationCount,\n    budgets[heapIndex].statistics.allocationBytes);\nprintf(\"allocated out of %u Vulkan device memory blocks taking %llu B,\\n\",\n    budgets[heapIndex].statistics.blockCount,\n    budgets[heapIndex].statistics.blockBytes);\nprintf(\"Vulkan reports total usage %llu B with budget %llu B.\\n\",\n    budgets[heapIndex].usage,\n    budgets[heapIndex].budget);\n\\endcode\n\nYou can query for more detailed statistics per memory heap, type, and totals,\nincluding minimum and maximum allocation size and unused range size,\nby calling function vmaCalculateStatistics() and inspecting structure #VmaTotalStatistics.\nThis function is slower though, as it has to traverse all the internal data structures,\nso it should be used only for debugging purposes.\n\nYou can query for statistics of a custom pool using function vmaGetPoolStatistics()\nor vmaCalculatePoolStatistics().\n\nYou can query for information about a specific allocation using function vmaGetAllocationInfo().\nIt fill structure #VmaAllocationInfo.\n\n\\section statistics_json_dump JSON dump\n\nYou can dump internal state of the allocator to a string in JSON format using function vmaBuildStatsString().\nThe result is guaranteed to be correct JSON.\nIt uses ANSI encoding.\nAny strings provided by user (see [Allocation names](@ref allocation_names))\nare copied as-is and properly escaped for JSON, so if they use UTF-8, ISO-8859-2 or any other encoding,\nthis JSON string can be treated as using this encoding.\nIt must be freed using function vmaFreeStatsString().\n\nThe format of this JSON string is not part of official documentation of the library,\nbut it will not change in backward-incompatible way without increasing library major version number\nand appropriate mention in changelog.\n\nThe JSON string contains all the data that can be obtained using vmaCalculateStatistics().\nIt can also contain detailed map of allocated memory blocks and their regions -\nfree and occupied by allocations.\nThis 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\nYou can annotate allocations with your own information, e.g. for debugging purposes.\nTo do that, fill VmaAllocationCreateInfo::pUserData field when creating\nan allocation. It is an opaque `void*` pointer. You can use it e.g. as a pointer,\nsome handle, index, key, ordinal number or any other value that would associate\nthe allocation with your custom metadata.\nIt is useful to identify appropriate data structures in your engine given #VmaAllocation,\ne.g. when doing \\ref defragmentation.\n\n\\code\nVkBufferCreateInfo bufCreateInfo = ...\n\nMyBufferMetadata* pMetadata = CreateBufferMetadata();\n\nVmaAllocationCreateInfo allocCreateInfo = {};\nallocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO;\nallocCreateInfo.pUserData = pMetadata;\n\nVkBuffer buffer;\nVmaAllocation allocation;\nvmaCreateBuffer(allocator, &bufCreateInfo, &allocCreateInfo, &buffer, &allocation, nullptr);\n\\endcode\n\nThe pointer may be later retrieved as VmaAllocationInfo::pUserData:\n\n\\code\nVmaAllocationInfo allocInfo;\nvmaGetAllocationInfo(allocator, allocation, &allocInfo);\nMyBufferMetadata* pMetadata = (MyBufferMetadata*)allocInfo.pUserData;\n\\endcode\n\nIt can also be changed using function vmaSetAllocationUserData().\n\nValues of (non-zero) allocations' `pUserData` are printed in JSON report created by\nvmaBuildStatsString() in hexadecimal form.\n\n\\section allocation_names Allocation names\n\nAn allocation can also carry a null-terminated string, giving a name to the allocation.\nTo set it, call vmaSetAllocationName().\nThe library creates internal copy of the string, so the pointer you pass doesn't need\nto be valid for whole lifetime of the allocation. You can free it after the call.\n\n\\code\nstd::string imageName = \"Texture: \";\nimageName += fileName;\nvmaSetAllocationName(allocator, allocation, imageName.c_str());\n\\endcode\n\nThe string can be later retrieved by inspecting VmaAllocationInfo::pName.\nIt is also printed in JSON report created by vmaBuildStatsString().\n\n\\note Setting string name to VMA allocation doesn't automatically set it to the Vulkan buffer or image created with it.\nYou must do it manually using an extension like VK_EXT_debug_utils, which is independent of this library.\n\n\n\\page virtual_allocator Virtual allocator\n\nAs an extra feature, the core allocation algorithm of the library is exposed through a simple and convenient API of \"virtual allocator\".\nIt doesn't allocate any real GPU memory. It just keeps track of used and free regions of a \"virtual block\".\nYou can use it to allocate your own memory or other objects, even completely unrelated to Vulkan.\nA common use case is sub-allocation of pieces of one large GPU buffer.\n\n\\section virtual_allocator_creating_virtual_block Creating virtual block\n\nTo use this functionality, there is no main \"allocator\" object.\nYou don't need to have #VmaAllocator object created.\nAll you need to do is to create a separate #VmaVirtualBlock object for each block of memory you want to be managed by the allocator:\n\n-# Fill in #VmaVirtualBlockCreateInfo structure.\n-# Call vmaCreateVirtualBlock(). Get new #VmaVirtualBlock object.\n\nExample:\n\n\\code\nVmaVirtualBlockCreateInfo blockCreateInfo = {};\nblockCreateInfo.size = 1048576; // 1 MB\n\nVmaVirtualBlock block;\nVkResult res = vmaCreateVirtualBlock(&blockCreateInfo, &block);\n\\endcode\n\n\\section virtual_allocator_making_virtual_allocations Making virtual allocations\n\n#VmaVirtualBlock object contains internal data structure that keeps track of free and occupied regions\nusing the same code as the main Vulkan memory allocator.\nSimilarly to #VmaAllocation for standard GPU allocations, there is #VmaVirtualAllocation type\nthat represents an opaque handle to an allocation within the virtual block.\n\nIn order to make such allocation:\n\n-# Fill in #VmaVirtualAllocationCreateInfo structure.\n-# Call vmaVirtualAllocate(). Get new #VmaVirtualAllocation object that represents the allocation.\n   You can also receive `VkDeviceSize offset` that was assigned to the allocation.\n\nExample:\n\n\\code\nVmaVirtualAllocationCreateInfo allocCreateInfo = {};\nallocCreateInfo.size = 4096; // 4 KB\n\nVmaVirtualAllocation alloc;\nVkDeviceSize offset;\nres = vmaVirtualAllocate(block, &allocCreateInfo, &alloc, &offset);\nif(res == VK_SUCCESS)\n{\n    // Use the 4 KB of your memory starting at offset.\n}\nelse\n{\n    // Allocation failed - no space for it could be found. Handle this error!\n}\n\\endcode\n\n\\section virtual_allocator_deallocation Deallocation\n\nWhen no longer needed, an allocation can be freed by calling vmaVirtualFree().\nYou can only pass to this function an allocation that was previously returned by vmaVirtualAllocate()\ncalled for the same #VmaVirtualBlock.\n\nWhen whole block is no longer needed, the block object can be released by calling vmaDestroyVirtualBlock().\nAll allocations must be freed before the block is destroyed, which is checked internally by an assert.\nHowever, if you don't want to call vmaVirtualFree() for each allocation, you can use vmaClearVirtualBlock() to free them all at once -\na feature not available in normal Vulkan memory allocator. Example:\n\n\\code\nvmaVirtualFree(block, alloc);\nvmaDestroyVirtualBlock(block);\n\\endcode\n\n\\section virtual_allocator_allocation_parameters Allocation parameters\n\nYou can attach a custom pointer to each allocation by using vmaSetVirtualAllocationUserData().\nIts default value is null.\nIt can be used to store any data that needs to be associated with that allocation - e.g. an index, a handle, or a pointer to some\nlarger data structure containing more information. Example:\n\n\\code\nstruct CustomAllocData\n{\n    std::string m_AllocName;\n};\nCustomAllocData* allocData = new CustomAllocData();\nallocData->m_AllocName = \"My allocation 1\";\nvmaSetVirtualAllocationUserData(block, alloc, allocData);\n\\endcode\n\nThe pointer can later be fetched, along with allocation offset and size, by passing the allocation handle to function\nvmaGetVirtualAllocationInfo() and inspecting returned structure #VmaVirtualAllocationInfo.\nIf you allocated a new object to be used as the custom pointer, don't forget to delete that object before freeing the allocation!\nExample:\n\n\\code\nVmaVirtualAllocationInfo allocInfo;\nvmaGetVirtualAllocationInfo(block, alloc, &allocInfo);\ndelete (CustomAllocData*)allocInfo.pUserData;\n\nvmaVirtualFree(block, alloc);\n\\endcode\n\n\\section virtual_allocator_alignment_and_units Alignment and units\n\nIt feels natural to express sizes and offsets in bytes.\nIf an offset of an allocation needs to be aligned to a multiply of some number (e.g. 4 bytes), you can fill optional member\nVmaVirtualAllocationCreateInfo::alignment to request it. Example:\n\n\\code\nVmaVirtualAllocationCreateInfo allocCreateInfo = {};\nallocCreateInfo.size = 4096; // 4 KB\nallocCreateInfo.alignment = 4; // Returned offset must be a multiply of 4 B\n\nVmaVirtualAllocation alloc;\nres = vmaVirtualAllocate(block, &allocCreateInfo, &alloc, nullptr);\n\\endcode\n\nAlignments of different allocations made from one block may vary.\nHowever, if all alignments and sizes are always multiply of some size e.g. 4 B or `sizeof(MyDataStruct)`,\nyou can express all sizes, alignments, and offsets in multiples of that size instead of individual bytes.\nIt might be more convenient, but you need to make sure to use this new unit consistently in all the places:\n\n- VmaVirtualBlockCreateInfo::size\n- VmaVirtualAllocationCreateInfo::size and VmaVirtualAllocationCreateInfo::alignment\n- Using offset returned by vmaVirtualAllocate() or in VmaVirtualAllocationInfo::offset\n\n\\section virtual_allocator_statistics Statistics\n\nYou can obtain statistics of a virtual block using vmaGetVirtualBlockStatistics()\n(to get brief statistics that are fast to calculate)\nor vmaCalculateVirtualBlockStatistics() (to get more detailed statistics, slower to calculate).\nThe functions fill structures #VmaStatistics, #VmaDetailedStatistics respectively - same as used by the normal Vulkan memory allocator.\nExample:\n\n\\code\nVmaStatistics stats;\nvmaGetVirtualBlockStatistics(block, &stats);\nprintf(\"My virtual block has %llu bytes used by %u virtual allocations\\n\",\n    stats.allocationBytes, stats.allocationCount);\n\\endcode\n\nYou can also request a full list of allocations and free regions as a string in JSON format by calling\nvmaBuildVirtualBlockStatsString().\nReturned string must be later freed using vmaFreeVirtualBlockStatsString().\nThe format of this string differs from the one returned by the main Vulkan allocator, but it is similar.\n\n\\section virtual_allocator_additional_considerations Additional considerations\n\nThe \"virtual allocator\" functionality is implemented on a level of individual memory blocks.\nKeeping track of a whole collection of blocks, allocating new ones when out of free space,\ndeleting empty ones, and deciding which one to try first for a new allocation must be implemented by the user.\n\nAlternative allocation algorithms are supported, just like in custom pools of the real GPU memory.\nSee enum #VmaVirtualBlockCreateFlagBits to learn how to specify them (e.g. #VMA_VIRTUAL_BLOCK_CREATE_LINEAR_ALGORITHM_BIT).\nYou can find their description in chapter \\ref custom_memory_pools.\nAllocation strategies are also supported.\nSee enum #VmaVirtualAllocationCreateFlagBits to learn how to specify them (e.g. #VMA_VIRTUAL_ALLOCATION_CREATE_STRATEGY_MIN_TIME_BIT).\n\nFollowing features are supported only by the allocator of the real GPU memory and not by virtual allocations:\nbuffer-image granularity, `VMA_DEBUG_MARGIN`, `VMA_MIN_ALIGNMENT`.\n\n\n\\page debugging_memory_usage Debugging incorrect memory usage\n\nIf you suspect a bug with memory usage, like usage of uninitialized memory or\nmemory being overwritten out of bounds of an allocation,\nyou can use debug features of this library to verify this.\n\n\\section debugging_memory_usage_initialization Memory initialization\n\nIf you experience a bug with incorrect and nondeterministic data in your program and you suspect uninitialized memory to be used,\nyou can enable automatic memory initialization to verify this.\nTo 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\nIt makes memory of new allocations initialized to bit pattern `0xDCDCDCDC`.\nBefore an allocation is destroyed, its memory is filled with bit pattern `0xEFEFEFEF`.\nMemory is automatically mapped and unmapped if necessary.\n\nIf you find these values while debugging your program, good chances are that you incorrectly\nread Vulkan memory that is allocated but not initialized, or already freed, respectively.\n\nMemory initialization works only with memory types that are `HOST_VISIBLE` and with allocations that can be mapped.\nIt works also with dedicated allocations.\n\n\\section debugging_memory_usage_margins Margins\n\nBy 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\nDefine macro `VMA_DEBUG_MARGIN` to some non-zero value (e.g. 16) to enforce specified\nnumber of bytes as a margin 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\nIf your bug goes away after enabling margins, it means it may be caused by memory\nbeing overwritten outside of allocation boundaries. It is not 100% certain though.\nChange in application behavior may also be caused by different order and distribution\nof allocations across memory blocks after margins are applied.\n\nMargins work with all types of memory.\n\nMargin is applied only to allocations made out of memory blocks and not to dedicated\nallocations, which have their own memory block of specific size.\nIt is thus not applied to allocations made using #VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT flag\nor those automatically decided to put into dedicated allocations, e.g. due to its\nlarge size or recommended by VK_KHR_dedicated_allocation extension.\n\nMargins appear in [JSON dump](@ref statistics_json_dump) as part of free space.\n\nNote that enabling margins increases memory usage and fragmentation.\n\nMargins do not apply to \\ref virtual_allocator.\n\n\\section debugging_memory_usage_corruption_detection Corruption detection\n\nYou can additionally define macro `VMA_DEBUG_DETECT_CORRUPTION` to 1 to enable validation\nof 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\nWhen this feature is enabled, number of bytes specified as `VMA_DEBUG_MARGIN`\n(it must be multiply of 4) after every allocation is filled with a magic number.\nThis idea is also know as \"canary\".\nMemory is automatically mapped and unmapped if necessary.\n\nThis number is validated automatically when the allocation is destroyed.\nIf it is not equal to the expected value, `VMA_ASSERT()` is executed.\nIt clearly means that either CPU or GPU overwritten the memory outside of boundaries of the allocation,\nwhich indicates a serious bug.\n\nYou can also explicitly request checking margins of all allocations in all memory blocks\nthat belong to specified memory types by using function vmaCheckCorruption(),\nor in memory blocks that belong to specified custom pool, by using function\nvmaCheckPoolCorruption().\n\nMargin validation (corruption detection) works only for memory types that are\n`HOST_VISIBLE` and `HOST_COHERENT`.\n\n\n\\page opengl_interop OpenGL Interop\n\nVMA provides some features that help with interoperability with OpenGL.\n\n\\section opengl_interop_exporting_memory Exporting memory\n\nIf you want to attach `VkExportMemoryAllocateInfoKHR` structure to `pNext` chain of memory allocations made by the library:\n\nIt is recommended to create \\ref custom_memory_pools for such allocations.\nDefine and fill in your `VkExportMemoryAllocateInfoKHR` structure and attach it to VmaPoolCreateInfo::pMemoryAllocateNext\nwhile creating the custom pool.\nPlease note that the structure must remain alive and unchanged for the whole lifetime of the #VmaPool,\nnot only while creating it, as no copy of the structure is made,\nbut its original pointer is used for each allocation instead.\n\nIf you want to export all memory allocated by the library from certain memory types,\nalso dedicated allocations or other allocations made from default pools,\nan alternative solution is to fill in VmaAllocatorCreateInfo::pTypeExternalMemoryHandleTypes.\nIt should point to an array with `VkExternalMemoryHandleTypeFlagsKHR` to be automatically passed by the library\nthrough `VkExportMemoryAllocateInfoKHR` on each allocation made from a specific memory type.\nPlease note that new versions of the library also support dedicated allocations created in custom pools.\n\nYou should not mix these two methods in a way that allows to apply both to the same memory type.\nOtherwise, `VkExportMemoryAllocateInfoKHR` structure would be attached twice to the `pNext` chain of `VkMemoryAllocateInfo`.\n\n\n\\section opengl_interop_custom_alignment Custom alignment\n\nBuffers or images exported to a different API like OpenGL may require a different alignment,\nhigher than the one used by the library automatically, queried from functions like `vkGetBufferMemoryRequirements`.\nTo impose such alignment:\n\nIt is recommended to create \\ref custom_memory_pools for such allocations.\nSet VmaPoolCreateInfo::minAllocationAlignment member to the minimum alignment required for each allocation\nto be made out of this pool.\nThe alignment actually used will be the maximum of this member and the alignment returned for the specific buffer or image\nfrom a function like `vkGetBufferMemoryRequirements`, which is called by VMA automatically.\n\nIf you want to create a buffer with a specific minimum alignment out of default pools,\nuse special function vmaCreateBufferWithAlignment(), which takes additional parameter `minAlignment`.\n\nNote the problem of alignment affects only resources placed inside bigger `VkDeviceMemory` blocks and not dedicated\nallocations, as these, by definition, always have alignment = 0 because the resource is bound to the beginning of its dedicated block.\nContrary to Direct3D 12, Vulkan doesn't have a concept of alignment of the entire memory block passed on its allocation.\n\n\n\\page usage_patterns Recommended usage patterns\n\nVulkan gives great flexibility in memory allocation.\nThis chapter shows the most common patterns.\n\nSee 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_gpu_only GPU-only resource\n\n<b>When:</b>\nAny resources that you frequently write and read on GPU,\ne.g. images used as color attachments (aka \"render targets\"), depth-stencil attachments,\nimages/buffers used as storage image/buffer (aka \"Unordered Access View (UAV)\").\n\n<b>What to do:</b>\nLet the library select the optimal memory type, which will likely have `VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT`.\n\n\\code\nVkImageCreateInfo imgCreateInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO };\nimgCreateInfo.imageType = VK_IMAGE_TYPE_2D;\nimgCreateInfo.extent.width = 3840;\nimgCreateInfo.extent.height = 2160;\nimgCreateInfo.extent.depth = 1;\nimgCreateInfo.mipLevels = 1;\nimgCreateInfo.arrayLayers = 1;\nimgCreateInfo.format = VK_FORMAT_R8G8B8A8_UNORM;\nimgCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL;\nimgCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;\nimgCreateInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;\nimgCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT;\n\nVmaAllocationCreateInfo allocCreateInfo = {};\nallocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO;\nallocCreateInfo.flags = VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT;\nallocCreateInfo.priority = 1.0f;\n\nVkImage img;\nVmaAllocation alloc;\nvmaCreateImage(allocator, &imgCreateInfo, &allocCreateInfo, &img, &alloc, nullptr);\n\\endcode\n\n<b>Also consider:</b>\nConsider creating them as dedicated allocations using #VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT,\nespecially if they are large or if you plan to destroy and recreate them with different sizes\ne.g. when display resolution changes.\nPrefer to create such resources first and all other GPU resources (like textures and vertex buffers) later.\nWhen VK_EXT_memory_priority extension is enabled, it is also worth setting high priority to such allocation\nto decrease chances to be evicted to system memory by the operating system.\n\n\\section usage_patterns_staging_copy_upload Staging copy for upload\n\n<b>When:</b>\nA \"staging\" buffer than you want to map and fill from CPU code, then use as a source of transfer\nto some GPU resource.\n\n<b>What to do:</b>\nUse flag #VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT.\nLet the library select the optimal memory type, which will always have `VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT`.\n\n\\code\nVkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };\nbufCreateInfo.size = 65536;\nbufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;\n\nVmaAllocationCreateInfo allocCreateInfo = {};\nallocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO;\nallocCreateInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT |\n    VMA_ALLOCATION_CREATE_MAPPED_BIT;\n\nVkBuffer buf;\nVmaAllocation alloc;\nVmaAllocationInfo allocInfo;\nvmaCreateBuffer(allocator, &bufCreateInfo, &allocCreateInfo, &buf, &alloc, &allocInfo);\n\n...\n\nmemcpy(allocInfo.pMappedData, myData, myDataSize);\n\\endcode\n\n<b>Also consider:</b>\nYou can map the allocation using vmaMapMemory() or you can create it as persistenly mapped\nusing #VMA_ALLOCATION_CREATE_MAPPED_BIT, as in the example above.\n\n\n\\section usage_patterns_readback Readback\n\n<b>When:</b>\nBuffers for data written by or transferred from the GPU that you want to read back on the CPU,\ne.g. results of some computations.\n\n<b>What to do:</b>\nUse flag #VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT.\nLet the library select the optimal memory type, which will always have `VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT`\nand `VK_MEMORY_PROPERTY_HOST_CACHED_BIT`.\n\n\\code\nVkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };\nbufCreateInfo.size = 65536;\nbufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT;\n\nVmaAllocationCreateInfo allocCreateInfo = {};\nallocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO;\nallocCreateInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT |\n    VMA_ALLOCATION_CREATE_MAPPED_BIT;\n\nVkBuffer buf;\nVmaAllocation alloc;\nVmaAllocationInfo allocInfo;\nvmaCreateBuffer(allocator, &bufCreateInfo, &allocCreateInfo, &buf, &alloc, &allocInfo);\n\n...\n\nconst float* downloadedData = (const float*)allocInfo.pMappedData;\n\\endcode\n\n\n\\section usage_patterns_advanced_data_uploading Advanced data uploading\n\nFor resources that you frequently write on CPU via mapped pointer and\nfrequently read on GPU e.g. as a uniform buffer (also called \"dynamic\"), multiple options are possible:\n\n-# Easiest solution is to have one copy of the resource in `HOST_VISIBLE` memory,\n   even if it means system RAM (not `DEVICE_LOCAL`) on systems with a discrete graphics card,\n   and make the device reach out to that resource directly.\n   - Reads performed by the device will then go through PCI Express bus.\n     The performance of this access may be limited, but it may be fine depending on the size\n     of this resource (whether it is small enough to quickly end up in GPU cache) and the sparsity\n     of access.\n-# On systems with unified memory (e.g. AMD APU or Intel integrated graphics, mobile chips),\n   a memory type may be available that is both `HOST_VISIBLE` (available for mapping) and `DEVICE_LOCAL`\n   (fast to access from the GPU). Then, it is likely the best choice for such type of resource.\n-# Systems with a discrete graphics card and separate video memory may or may not expose\n   a memory type that is both `HOST_VISIBLE` and `DEVICE_LOCAL`, also known as Base Address Register (BAR).\n   If they do, it represents a piece of VRAM (or entire VRAM, if ReBAR is enabled in the motherboard BIOS)\n   that is available to CPU for mapping.\n   - Writes performed by the host to that memory go through PCI Express bus.\n     The performance of these writes may be limited, but it may be fine, especially on PCIe 4.0,\n     as long as rules of using uncached and write-combined memory are followed - only sequential writes and no reads.\n-# Finally, you may need or prefer to create a separate copy of the resource in `DEVICE_LOCAL` memory,\n   a separate \"staging\" copy in `HOST_VISIBLE` memory and perform an explicit transfer command between them.\n\nThankfully, VMA offers an aid to create and use such resources in the the way optimal\nfor the current Vulkan device. To help the library make the best choice,\nuse flag #VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT together with\n#VMA_ALLOCATION_CREATE_HOST_ACCESS_ALLOW_TRANSFER_INSTEAD_BIT.\nIt will then prefer a memory type that is both `DEVICE_LOCAL` and `HOST_VISIBLE` (integrated memory or BAR),\nbut if no such memory type is available or allocation from it fails\n(PC graphics cards have only 256 MB of BAR by default, unless ReBAR is supported and enabled in BIOS),\nit will fall back to `DEVICE_LOCAL` memory for fast GPU access.\nIt is then up to you to detect that the allocation ended up in a memory type that is not `HOST_VISIBLE`,\nso you need to create another \"staging\" allocation and perform explicit transfers.\n\n\\code\nVkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };\nbufCreateInfo.size = 65536;\nbufCreateInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;\n\nVmaAllocationCreateInfo allocCreateInfo = {};\nallocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO;\nallocCreateInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT |\n    VMA_ALLOCATION_CREATE_HOST_ACCESS_ALLOW_TRANSFER_INSTEAD_BIT |\n    VMA_ALLOCATION_CREATE_MAPPED_BIT;\n\nVkBuffer buf;\nVmaAllocation alloc;\nVmaAllocationInfo allocInfo;\nvmaCreateBuffer(allocator, &bufCreateInfo, &allocCreateInfo, &buf, &alloc, &allocInfo);\n\nVkMemoryPropertyFlags memPropFlags;\nvmaGetAllocationMemoryProperties(allocator, alloc, &memPropFlags);\n\nif(memPropFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT)\n{\n    // Allocation ended up in a mappable memory and is already mapped - write to it directly.\n\n    // [Executed in runtime]:\n    memcpy(allocInfo.pMappedData, myData, myDataSize);\n}\nelse\n{\n    // Allocation ended up in a non-mappable memory - need to transfer.\n    VkBufferCreateInfo stagingBufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };\n    stagingBufCreateInfo.size = 65536;\n    stagingBufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;\n\n    VmaAllocationCreateInfo stagingAllocCreateInfo = {};\n    stagingAllocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO;\n    stagingAllocCreateInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT |\n        VMA_ALLOCATION_CREATE_MAPPED_BIT;\n\n    VkBuffer stagingBuf;\n    VmaAllocation stagingAlloc;\n    VmaAllocationInfo stagingAllocInfo;\n    vmaCreateBuffer(allocator, &stagingBufCreateInfo, &stagingAllocCreateInfo,\n        &stagingBuf, &stagingAlloc, stagingAllocInfo);\n\n    // [Executed in runtime]:\n    memcpy(stagingAllocInfo.pMappedData, myData, myDataSize);\n    //vkCmdPipelineBarrier: VK_ACCESS_HOST_WRITE_BIT --> VK_ACCESS_TRANSFER_READ_BIT\n    VkBufferCopy bufCopy = {\n        0, // srcOffset\n        0, // dstOffset,\n        myDataSize); // size\n    vkCmdCopyBuffer(cmdBuf, stagingBuf, buf, 1, &bufCopy);\n}\n\\endcode\n\n\\section usage_patterns_other_use_cases Other use cases\n\nHere are some other, less obvious use cases and their recommended settings:\n\n- An image that is used only as transfer source and destination, but it should stay on the device,\n  as it is used to temporarily store a copy of some texture, e.g. from the current to the next frame,\n  for temporal antialiasing or other temporal effects.\n  - Use `VkImageCreateInfo::usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT`\n  - Use VmaAllocationCreateInfo::usage = #VMA_MEMORY_USAGE_AUTO\n- An image that is used only as transfer source and destination, but it should be placed\n  in the system RAM despite it doesn't need to be mapped, because it serves as a \"swap\" copy to evict\n  least recently used textures from VRAM.\n  - Use `VkImageCreateInfo::usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT`\n  - Use VmaAllocationCreateInfo::usage = #VMA_MEMORY_USAGE_AUTO_PREFER_HOST,\n    as VMA needs a hint here to differentiate from the previous case.\n- A buffer that you want to map and write from the CPU, directly read from the GPU\n  (e.g. as a uniform or vertex buffer), but you have a clear preference to place it in device or\n  host memory due to its large size.\n  - Use `VkBufferCreateInfo::usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT`\n  - Use VmaAllocationCreateInfo::usage = #VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE or #VMA_MEMORY_USAGE_AUTO_PREFER_HOST\n  - Use VmaAllocationCreateInfo::flags = #VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT\n\n\n\\page configuration Configuration\n\nPlease check \"CONFIGURATION SECTION\" in the code to find macros that you can define\nbefore each include of this file or change directly in this file to provide\nyour own implementation of basic facilities like assert, `min()` and `max()` functions,\nmutex, atomic etc.\nThe library uses its own implementation of containers by default, but you can switch to using\nSTL containers instead.\n\nFor example, define `VMA_ASSERT(expr)` before including the library to provide\ncustom implementation of the assertion, compatible with your project.\nBy default it is defined to standard C `assert(expr)` in `_DEBUG` configuration\nand empty otherwise.\n\n\\section config_Vulkan_functions Pointers to Vulkan functions\n\nThere are multiple ways to import pointers to Vulkan functions in the library.\nIn the simplest case you don't need to do anything.\nIf the compilation or linking of your program or the initialization of the #VmaAllocator\ndoesn't work for you, you can try to reconfigure it.\n\nFirst, the allocator tries to fetch pointers to Vulkan functions linked statically,\nlike this:\n\n\\code\nm_VulkanFunctions.vkAllocateMemory = (PFN_vkAllocateMemory)vkAllocateMemory;\n\\endcode\n\nIf you want to disable this feature, set configuration macro: `#define VMA_STATIC_VULKAN_FUNCTIONS 0`.\n\nSecond, you can provide the pointers yourself by setting member VmaAllocatorCreateInfo::pVulkanFunctions.\nYou can fetch them e.g. using functions `vkGetInstanceProcAddr` and `vkGetDeviceProcAddr` or\nby using a helper library like [volk](https://github.com/zeux/volk).\n\nThird, VMA tries to fetch remaining pointers that are still null by calling\n`vkGetInstanceProcAddr` and `vkGetDeviceProcAddr` on its own.\nYou need to only fill in VmaVulkanFunctions::vkGetInstanceProcAddr and VmaVulkanFunctions::vkGetDeviceProcAddr.\nOther pointers will be fetched automatically.\nIf you want to disable this feature, set configuration macro: `#define VMA_DYNAMIC_VULKAN_FUNCTIONS 0`.\n\nFinally, all the function pointers required by the library (considering selected\nVulkan version and enabled extensions) are checked with `VMA_ASSERT` if they are not null.\n\n\n\\section custom_memory_allocator Custom host memory allocator\n\nIf you use custom allocator for CPU memory rather than default operator `new`\nand `delete` from C++, you can make this library using your allocator as well\nby filling optional member VmaAllocatorCreateInfo::pAllocationCallbacks. These\nfunctions will be passed to Vulkan, as well as used by the library itself to\nmake any CPU-side allocations.\n\n\\section allocation_callbacks Device memory allocation callbacks\n\nThe library makes calls to `vkAllocateMemory()` and `vkFreeMemory()` internally.\nYou can setup callbacks to be informed about these calls, e.g. for the purpose\nof gathering some statistics. To do it, fill optional member\nVmaAllocatorCreateInfo::pDeviceMemoryCallbacks.\n\n\\section heap_memory_limit Device heap memory limit\n\nWhen device memory of certain heap runs out of free space, new allocations may\nfail (returning error code) or they may succeed, silently pushing some existing_\nmemory blocks from GPU VRAM to system RAM (which degrades performance). This\nbehavior is implementation-dependent - it depends on GPU vendor and graphics\ndriver.\n\nOn AMD cards it can be controlled while creating Vulkan device object by using\nVK_AMD_memory_overallocation_behavior extension, if available.\n\nAlternatively, if you want to test how your program behaves with limited amount of Vulkan device\nmemory available without switching your graphics card to one that really has\nsmaller VRAM, you can use a feature of this library intended for this purpose.\nTo do it, fill optional member VmaAllocatorCreateInfo::pHeapSizeLimit.\n\n\n\n\\page vk_khr_dedicated_allocation VK_KHR_dedicated_allocation\n\nVK_KHR_dedicated_allocation is a Vulkan extension which can be used to improve\nperformance on some GPUs. It augments Vulkan API with possibility to query\ndriver whether it prefers particular buffer or image to have its own, dedicated\nallocation (separate `VkDeviceMemory` block) for better efficiency - to be able\nto do some internal optimizations. The extension is supported by this library.\nIt will be used automatically when enabled.\n\nIt has been promoted to core Vulkan 1.1, so if you use eligible Vulkan version\nand inform VMA about it by setting VmaAllocatorCreateInfo::vulkanApiVersion,\nyou are all set.\n\nOtherwise, if you want to use it as an extension:\n\n1 . When creating Vulkan device, check if following 2 device extensions are\nsupported (call `vkEnumerateDeviceExtensionProperties()`).\nIf yes, enable them (fill `VkDeviceCreateInfo::ppEnabledExtensionNames`).\n\n- VK_KHR_get_memory_requirements2\n- VK_KHR_dedicated_allocation\n\nIf you enabled these extensions:\n\n2 . Use #VMA_ALLOCATOR_CREATE_KHR_DEDICATED_ALLOCATION_BIT flag when creating\nyour #VmaAllocator to inform the library that you enabled required extensions\nand you want the library to use them.\n\n\\code\nallocatorInfo.flags |= VMA_ALLOCATOR_CREATE_KHR_DEDICATED_ALLOCATION_BIT;\n\nvmaCreateAllocator(&allocatorInfo, &allocator);\n\\endcode\n\nThat is all. The extension will be automatically used whenever you create a\nbuffer using vmaCreateBuffer() or image using vmaCreateImage().\n\nWhen using the extension together with Vulkan Validation Layer, you will receive\nwarnings like this:\n\n_vkBindBufferMemory(): Binding memory to buffer 0x33 but vkGetBufferMemoryRequirements() has not been called on that buffer._\n\nIt 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\nunaware of it.\n\nTo learn more about this extension, see:\n\n- [VK_KHR_dedicated_allocation in Vulkan specification](https://www.khronos.org/registry/vulkan/specs/1.2-extensions/html/chap50.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 vk_ext_memory_priority VK_EXT_memory_priority\n\nVK_EXT_memory_priority is a device extension that allows to pass additional \"priority\"\nvalue to Vulkan memory allocations that the implementation may use prefer certain\nbuffers and images that are critical for performance to stay in device-local memory\nin cases when the memory is over-subscribed, while some others may be moved to the system memory.\n\nVMA offers convenient usage of this extension.\nIf you enable it, you can pass \"priority\" parameter when creating allocations or custom pools\nand the library automatically passes the value to Vulkan using this extension.\n\nIf you want to use this extension in connection with VMA, follow these steps:\n\n\\section vk_ext_memory_priority_initialization Initialization\n\n1) Call `vkEnumerateDeviceExtensionProperties` for the physical device.\nCheck if the extension is supported - if returned array of `VkExtensionProperties` contains \"VK_EXT_memory_priority\".\n\n2) Call `vkGetPhysicalDeviceFeatures2` for the physical device instead of old `vkGetPhysicalDeviceFeatures`.\nAttach additional structure `VkPhysicalDeviceMemoryPriorityFeaturesEXT` to `VkPhysicalDeviceFeatures2::pNext` to be returned.\nCheck if the device feature is really supported - check if `VkPhysicalDeviceMemoryPriorityFeaturesEXT::memoryPriority` is true.\n\n3) While creating device with `vkCreateDevice`, enable this extension - add \"VK_EXT_memory_priority\"\nto the list passed as `VkDeviceCreateInfo::ppEnabledExtensionNames`.\n\n4) While creating the device, also don't set `VkDeviceCreateInfo::pEnabledFeatures`.\nFill in `VkPhysicalDeviceFeatures2` structure instead and pass it as `VkDeviceCreateInfo::pNext`.\nEnable this device feature - attach additional structure `VkPhysicalDeviceMemoryPriorityFeaturesEXT` to\n`VkPhysicalDeviceFeatures2::pNext` chain and set its member `memoryPriority` to `VK_TRUE`.\n\n5) While creating #VmaAllocator with vmaCreateAllocator() inform VMA that you\nhave enabled this extension and feature - add #VMA_ALLOCATOR_CREATE_EXT_MEMORY_PRIORITY_BIT\nto VmaAllocatorCreateInfo::flags.\n\n\\section vk_ext_memory_priority_usage Usage\n\nWhen using this extension, you should initialize following member:\n\n- VmaAllocationCreateInfo::priority when creating a dedicated allocation with #VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT.\n- VmaPoolCreateInfo::priority when creating a custom pool.\n\nIt should be a floating-point value between `0.0f` and `1.0f`, where recommended default is `0.5f`.\nMemory allocated with higher value can be treated by the Vulkan implementation as higher priority\nand so it can have lower chances of being pushed out to system memory, experiencing degraded performance.\n\nIt might be a good idea to create performance-critical resources like color-attachment or depth-stencil images\nas dedicated and set high priority to them. For example:\n\n\\code\nVkImageCreateInfo imgCreateInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO };\nimgCreateInfo.imageType = VK_IMAGE_TYPE_2D;\nimgCreateInfo.extent.width = 3840;\nimgCreateInfo.extent.height = 2160;\nimgCreateInfo.extent.depth = 1;\nimgCreateInfo.mipLevels = 1;\nimgCreateInfo.arrayLayers = 1;\nimgCreateInfo.format = VK_FORMAT_R8G8B8A8_UNORM;\nimgCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL;\nimgCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;\nimgCreateInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;\nimgCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT;\n\nVmaAllocationCreateInfo allocCreateInfo = {};\nallocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO;\nallocCreateInfo.flags = VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT;\nallocCreateInfo.priority = 1.0f;\n\nVkImage img;\nVmaAllocation alloc;\nvmaCreateImage(allocator, &imgCreateInfo, &allocCreateInfo, &img, &alloc, nullptr);\n\\endcode\n\n`priority` member is ignored in the following situations:\n\n- Allocations created in custom pools: They inherit the priority, along with all other allocation parameters\n  from the parametrs passed in #VmaPoolCreateInfo when the pool was created.\n- Allocations created in default pools: They inherit the priority from the parameters\n  VMA used when creating default pools, which means `priority == 0.5f`.\n\n\n\\page vk_amd_device_coherent_memory VK_AMD_device_coherent_memory\n\nVK_AMD_device_coherent_memory is a device extension that enables access to\nadditional memory types with `VK_MEMORY_PROPERTY_DEVICE_COHERENT_BIT_AMD` and\n`VK_MEMORY_PROPERTY_DEVICE_UNCACHED_BIT_AMD` flag. It is useful mostly for\nallocation of buffers intended for writing \"breadcrumb markers\" in between passes\nor draw calls, which in turn are useful for debugging GPU crash/hang/TDR cases.\n\nWhen the extension is available but has not been enabled, Vulkan physical device\nstill exposes those memory types, but their usage is forbidden. VMA automatically\ntakes care of that - it returns `VK_ERROR_FEATURE_NOT_PRESENT` when an attempt\nto allocate memory of such type is made.\n\nIf you want to use this extension in connection with VMA, follow these steps:\n\n\\section vk_amd_device_coherent_memory_initialization Initialization\n\n1) Call `vkEnumerateDeviceExtensionProperties` for the physical device.\nCheck if the extension is supported - if returned array of `VkExtensionProperties` contains \"VK_AMD_device_coherent_memory\".\n\n2) Call `vkGetPhysicalDeviceFeatures2` for the physical device instead of old `vkGetPhysicalDeviceFeatures`.\nAttach additional structure `VkPhysicalDeviceCoherentMemoryFeaturesAMD` to `VkPhysicalDeviceFeatures2::pNext` to be returned.\nCheck if the device feature is really supported - check if `VkPhysicalDeviceCoherentMemoryFeaturesAMD::deviceCoherentMemory` is true.\n\n3) While creating device with `vkCreateDevice`, enable this extension - add \"VK_AMD_device_coherent_memory\"\nto the list passed as `VkDeviceCreateInfo::ppEnabledExtensionNames`.\n\n4) While creating the device, also don't set `VkDeviceCreateInfo::pEnabledFeatures`.\nFill in `VkPhysicalDeviceFeatures2` structure instead and pass it as `VkDeviceCreateInfo::pNext`.\nEnable this device feature - attach additional structure `VkPhysicalDeviceCoherentMemoryFeaturesAMD` to\n`VkPhysicalDeviceFeatures2::pNext` and set its member `deviceCoherentMemory` to `VK_TRUE`.\n\n5) While creating #VmaAllocator with vmaCreateAllocator() inform VMA that you\nhave enabled this extension and feature - add #VMA_ALLOCATOR_CREATE_AMD_DEVICE_COHERENT_MEMORY_BIT\nto VmaAllocatorCreateInfo::flags.\n\n\\section vk_amd_device_coherent_memory_usage Usage\n\nAfter following steps described above, you can create VMA allocations and custom pools\nout of the special `DEVICE_COHERENT` and `DEVICE_UNCACHED` memory types on eligible\ndevices. There are multiple ways to do it, for example:\n\n- You can request or prefer to allocate out of such memory types by adding\n  `VK_MEMORY_PROPERTY_DEVICE_UNCACHED_BIT_AMD` to VmaAllocationCreateInfo::requiredFlags\n  or VmaAllocationCreateInfo::preferredFlags. Those flags can be freely mixed with\n  other ways of \\ref choosing_memory_type, like setting VmaAllocationCreateInfo::usage.\n- If you manually found memory type index to use for this purpose, force allocation\n  from this specific index by setting VmaAllocationCreateInfo::memoryTypeBits `= 1u << index`.\n\n\\section vk_amd_device_coherent_memory_more_information More information\n\nTo learn more about this extension, see [VK_AMD_device_coherent_memory in Vulkan specification](https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VK_AMD_device_coherent_memory.html)\n\nExample use of this extension can be found in the code of the sample and test suite\naccompanying this library.\n\n\n\\page enabling_buffer_device_address Enabling buffer device address\n\nDevice extension VK_KHR_buffer_device_address\nallow to fetch raw GPU pointer to a buffer and pass it for usage in a shader code.\nIt has been promoted to core Vulkan 1.2.\n\nIf you want to use this feature in connection with VMA, follow these steps:\n\n\\section enabling_buffer_device_address_initialization Initialization\n\n1) (For Vulkan version < 1.2) Call `vkEnumerateDeviceExtensionProperties` for the physical device.\nCheck if the extension is supported - if returned array of `VkExtensionProperties` contains\n\"VK_KHR_buffer_device_address\".\n\n2) Call `vkGetPhysicalDeviceFeatures2` for the physical device instead of old `vkGetPhysicalDeviceFeatures`.\nAttach additional structure `VkPhysicalDeviceBufferDeviceAddressFeatures*` to `VkPhysicalDeviceFeatures2::pNext` to be returned.\nCheck if the device feature is really supported - check if `VkPhysicalDeviceBufferDeviceAddressFeatures::bufferDeviceAddress` is true.\n\n3) (For Vulkan version < 1.2) While creating device with `vkCreateDevice`, enable this extension - add\n\"VK_KHR_buffer_device_address\" to the list passed as `VkDeviceCreateInfo::ppEnabledExtensionNames`.\n\n4) While creating the device, also don't set `VkDeviceCreateInfo::pEnabledFeatures`.\nFill in `VkPhysicalDeviceFeatures2` structure instead and pass it as `VkDeviceCreateInfo::pNext`.\nEnable this device feature - attach additional structure `VkPhysicalDeviceBufferDeviceAddressFeatures*` to\n`VkPhysicalDeviceFeatures2::pNext` and set its member `bufferDeviceAddress` to `VK_TRUE`.\n\n5) While creating #VmaAllocator with vmaCreateAllocator() inform VMA that you\nhave enabled this feature - add #VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT\nto VmaAllocatorCreateInfo::flags.\n\n\\section enabling_buffer_device_address_usage Usage\n\nAfter following steps described above, you can create buffers with `VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT*` using VMA.\nThe library automatically adds `VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT*` to\nallocated memory blocks wherever it might be needed.\n\nPlease note that the library supports only `VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT*`.\nThe second part of this functionality related to \"capture and replay\" is not supported,\nas it is intended for usage in debugging tools like RenderDoc, not in everyday Vulkan usage.\n\n\\section enabling_buffer_device_address_more_information More information\n\nTo learn more about this extension, see [VK_KHR_buffer_device_address in Vulkan specification](https://www.khronos.org/registry/vulkan/specs/1.2-extensions/html/chap46.html#VK_KHR_buffer_device_address)\n\nExample use of this extension can be found in the code of the sample and test suite\naccompanying this library.\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  This includes allocation and deallocation from default memory pool, as well as custom #VmaPool.\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- #VmaVirtualBlock is not safe to be used from multiple threads simultaneously.\n\n\\section general_considerations_versioning_and_compatibility Versioning and compatibility\n\nThe library uses [**Semantic Versioning**](https://semver.org/),\nwhich means version numbers follow convention: Major.Minor.Patch (e.g. 2.3.0), where:\n\n- Incremented Patch version means a release is backward- and forward-compatible,\n  introducing only some internal improvements, bug fixes, optimizations etc.\n  or changes that are out of scope of the official API described in this documentation.\n- Incremented Minor version means a release is backward-compatible,\n  so existing code that uses the library should continue to work, while some new\n  symbols could have been added: new structures, functions, new values in existing\n  enums and bit flags, new structure members, but not new function parameters.\n- Incrementing Major version means a release could break some backward compatibility.\n\nAll changes between official releases are documented in file \"CHANGELOG.md\".\n\n\\warning Backward compatibility is considered on the level of C++ source code, not binary linkage.\nAdding new members to existing structures is treated as backward compatible if initializing\nthe new members to binary zero results in the old behavior.\nYou should always fully initialize all library structures to zeros and not rely on their\nexact binary size.\n\n\\section general_considerations_validation_layer_warnings Validation layer warnings\n\nWhen using this library, you can meet following types of warnings issued by\nVulkan validation layer. They don't necessarily indicate a bug, so you may need\nto 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 may happen when you use [defragmentation](@ref defragmentation).\n\n\\section general_considerations_allocation_algorithm Allocation algorithm\n\nThe 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, 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\nFeatures deliberately excluded from the scope of this library:\n\n-# **Data transfer.** Uploading (streaming) 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   VMA doesn't record any commands to a `VkCommandBuffer`. It just allocates memory.\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 is 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   Success of an allocation is just checked with an assert.\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   There are many preprocessor macros that make some variables unused, function parameters unreferenced,\n   or conditional expressions constant in some configurations.\n   The code of this library should not be bigger or more complicated just to silence these warnings.\n   It is recommended to disable such warnings instead.\n-# This is a C++ library with C interface. **Bindings or ports to any other programming languages** are welcome as external projects but\n   are not going to be included into this repository.\n*/"
  }
]